Forráskód Böngészése

updated Kosztorysy

Piotr Labudda 10 éve
szülő
commit
cd471528f5

+ 119 - 0
SE/se-lib/ProjectKosztorysCennik.php

@@ -0,0 +1,119 @@
+<?php
+
+Lib::loadClass('ProjectKosztorysSchema');
+
+class ProjectKosztorysCennik {
+
+  /**
+	 * @returns [ $id_zasob => [ 'price' => $price, 'ID', 'id_zasob', 'id_company', 'id_project', 'unit', 'quantity' ] ]
+	 */
+  public static function getCennik($idProject, $idCompany = 0) {
+    $schema = ProjectKosztorysSchema::getSchema();
+    $typeIdList = array_keys($schema['config']['type']);
+    $sqlTypeIdList = implode(',', $typeIdList);
+    $pdo = DB::getPDO();
+    $sth = $pdo->prepare("
+      select o.ID
+        , o.CRM_LISTA_ZASOBOW_ID as id_zasob
+        , o.COMPANIES_ID as id_company
+        , o.ID_PROJECT as id_project
+        , o.OFFER_PRICE_PER_RESOURCE_UNIT as price
+        , o.RESOURCE_UNIT_TYPE as unit
+    --		, o.OFFER_UNIT_TYPE as unit
+        , o.REQUIRED_RESOURCE_UNITS as quantity
+      from CRM_LISTA_ZASOBOW_OFFERS o
+      where o.CRM_LISTA_ZASOBOW_ID in({$sqlTypeIdList})
+    --		and o.A_STATUS not in ('DELETED')
+        and o.ID_PROJECT = :id_project
+        and o.COMPANIES_ID = :id_company
+        and o.RESOURCE_UNIT_TYPE != 'ROBOCIZNA'
+    ");
+    $sth->bindValue(':id_project', $idProject, PDO::PARAM_INT);
+    $sth->bindValue(':id_company', $idCompany, PDO::PARAM_INT);
+    $sth->execute();
+    $cennikRaw = $sth->fetchAll();
+    $cennik = array();
+    foreach ($cennikRaw as $itemRaw) {
+      $item = $itemRaw;
+      $item['price'] = round($item['price'], 2);
+      $cennik[$itemRaw['id_zasob']] = $item;
+    }
+    return $cennik;
+  }
+
+  /**
+	 * @returns [ $id_zasob => [ 'price' => $price, 'ID', 'id_zasob', 'id_company', 'id_project', 'unit', 'quantity' ] ]
+	 */
+  public static function getWorkCennik($idProject, $idCompany = 0) {
+    $schema = ProjectKosztorysSchema::getSchema();
+    $typeIdList = array_keys($schema['config']['type']);
+    $sqlTypeIdList = implode(',', $typeIdList);
+    $pdo = DB::getPDO();
+    $sth = $pdo->prepare("
+      select o.ID
+        , o.CRM_LISTA_ZASOBOW_ID as id_zasob
+        , o.COMPANIES_ID as id_company
+        , o.ID_PROJECT as id_project
+        , o.OFFER_PRICE_PER_RESOURCE_UNIT as price
+        , o.RESOURCE_UNIT_TYPE as unit
+    --		, o.OFFER_UNIT_TYPE as unit
+        , o.REQUIRED_RESOURCE_UNITS as quantity
+      from CRM_LISTA_ZASOBOW_OFFERS o
+      where o.CRM_LISTA_ZASOBOW_ID in({$sqlTypeIdList})
+    --		and o.A_STATUS not in ('DELETED')
+        and o.ID_PROJECT = :id_project
+        and o.COMPANIES_ID = :id_company
+        and o.RESOURCE_UNIT_TYPE = 'ROBOCIZNA'
+    ");
+    $sth->bindValue(':id_project', $idProject, PDO::PARAM_INT);
+    $sth->bindValue(':id_company', $idCompany, PDO::PARAM_INT);
+    $sth->execute();
+    $robociznaCennikRaw = $sth->fetchAll();
+    $robociznaCennik = array();
+    foreach ($robociznaCennikRaw as $itemRaw) {
+      $item = $itemRaw;
+      $item['price'] = round($item['price'], 2);
+      $robociznaCennik[$itemRaw['id_zasob']] = $item;
+    }
+    return $robociznaCennik;
+  }
+
+  /**
+	 * @returns [ $id_zasob => [ 'price' => $price, 'ID', 'id_zasob', 'id_company', 'id_project', 'unit', 'quantity' ] ]
+	 */
+	public static function getDefaultCennik($idCompany = 0) {
+		$schema = ProjectKosztorysSchema::getSchema();
+		$typeIdList = array_keys($schema['config']['type']);
+		$sqlTypeIdList = implode(',', $typeIdList);
+		$pdo = DB::getPDO();
+		$sth = $pdo->prepare("
+			select o.ID
+				, o.CRM_LISTA_ZASOBOW_ID as id_zasob
+				, o.COMPANIES_ID as id_company
+				, o.ID_PROJECT as id_project
+				, o.OFFER_PRICE_PER_RESOURCE_UNIT as price
+				, o.RESOURCE_UNIT_TYPE as unit
+    --		, o.OFFER_UNIT_TYPE as unit
+				, o.REQUIRED_RESOURCE_UNITS as quantity
+			from CRM_LISTA_ZASOBOW_OFFERS o
+			where o.CRM_LISTA_ZASOBOW_ID in({$sqlTypeIdList})
+    --		and o.A_STATUS not in ('DELETED')
+				and o.ID_PROJECT = 0
+				and o.COMPANIES_ID = :id_company
+		");
+		$sth->bindValue(':id_company', $idCompany, PDO::PARAM_INT);
+		$sth->execute();
+		$cennikRaw = $sth->fetchAll();
+		$cennik = array();
+		foreach ($cennikRaw as $itemRaw) {
+			$item = $itemRaw;
+			$item['price'] = round($item['price'], 2);
+			if (!empty($cennik[$itemRaw['id_zasob']])) {
+				if ($itemRaw['ID'] < $cennik[$itemRaw['id_zasob']]['ID']) continue;
+			}
+			$cennik[$item['id_zasob']] = $item;
+		}
+		return $cennik;
+	}
+
+}

+ 137 - 0
SE/se-lib/ProjectKosztorysModel.php

@@ -0,0 +1,137 @@
+<?php
+
+class ProjectKosztorysModel {
+
+  public $idProject;
+  public $path;
+  public $title;
+  public $owner;
+  public $ownerName;
+  public $ownerByProjectId;
+  public $subProjectList;
+
+  public function __construct($idProject) {
+    $this->idProject = $idProject;
+    $this->fetchInfo($this->idProject);
+    $this->subProjectList = $this->fetchSubProjectList($this->idProject, $this->path);
+    $this->fixSubProjectOwner($this->idProject, $this->owner, $this->ownerName, $this->ownerByProjectId);
+		DBG::_('DBG', '>3', "this", $this, __CLASS__, __FUNCTION__, __LINE__);
+  }
+
+  public function getSubProjectIds() {
+    if (null === $this->subProjectList) $this->subProjectList = $this->fetchSubProjectList($this->idProject, $this->path);
+    return array_keys($this->subProjectList);
+  }
+
+  public function getSubProjectList() {
+    if (null === $this->subProjectList) $this->subProjectList = $this->fetchSubProjectList($this->idProject, $this->path);
+    return $this->subProjectList;
+  }
+
+  public function getProjectName($idProject) {
+    if (null === $this->subProjectList) $this->subProjectList = $this->fetchSubProjectList($this->idProject, $this->path);
+    if ($idProject == $this->idProject) return $this->title;
+    if (!array_key_exists($idProject, $this->subProjectList)) return null;// TODO: throw exception or query for data?
+    return $this->subProjectList[$idProject]['title'];
+  }
+
+  public function getSubProject($idSubProj) {
+    if (null === $this->subProjectList) $this->subProjectList = $this->fetchSubProjectList($this->idProject, $this->path);
+    return (array_key_exists($idSubProj, $this->subProjectList))? $this->subProjectList[$idSubProj] : null;
+  }
+
+  public function getPath() {
+    if (null !== $this->path) return $this->path;
+    $this->fetchInfo($this->idProject);
+    return $this->path;
+  }
+
+  public function fetchInfo($idProject) {
+    $rows = DB::getPDO()->fetchAll("
+      select p.ID
+        , p.path
+        , p.P_ID
+        , p.M_DIST_DESC as title
+        , p.L_APPOITMENT_USER as owner
+        , IF('' != p.L_APPOITMENT_USER
+          , (select u.ADM_NAME from ADMIN_USERS u where u.ADM_ACCOUNT = p.L_APPOITMENT_USER limit 1)
+          , '') as ownerName
+      from IN7_MK_BAZA_DYSTRYBUCJI p
+      where p.ID = '{$idProject}'
+    ");
+    if (empty($rows)) throw new Exception("Cannot find path for project '{$idProject}'");
+    $projectRaw = $rows[0];
+    if (empty($projectRaw['path'])) throw new Exception("Empty path for project '{$idProject}'");
+    $this->path = $projectRaw['path'];
+    $this->title = $projectRaw['title'];
+    $this->owner = $projectRaw['owner'];
+    $this->ownerName = $projectRaw['ownerName'];
+    $this->ownerByProjectId = null;
+    if (empty($this->owner) && !empty($this->path)) {// find owner in parent projects
+      $parentIds = explode('-', $this->path);
+      if (0 == $parentIds[0]) array_shift($parentIds);
+      $parentIds = array_reverse($parentIds);
+      DBG::_('DBG', '>3', "parentIds", $parentIds, __CLASS__, __FUNCTION__, __LINE__);
+      if (!empty($parentIds)) {
+        $sqlIds = implode(",", $parentIds);
+        $parentProjById = DB::getPDO()->fetchAllByKey("
+          select p.ID
+            , p.path
+            , p.P_ID
+            , p.M_DIST_DESC as title
+            , p.L_APPOITMENT_USER as owner
+            , IF('' != p.L_APPOITMENT_USER
+              , (select u.ADM_NAME from ADMIN_USERS u where u.ADM_ACCOUNT = p.L_APPOITMENT_USER limit 1)
+              , '') as ownerName
+          from IN7_MK_BAZA_DYSTRYBUCJI p
+          where p.ID in({$sqlIds})
+        ", $key = 'ID');
+        //DBG::table("parentIds", $parentProjById, __CLASS__, __FUNCTION__, __LINE__);
+      }
+      foreach ($parentIds as $idParent) {
+        if (!empty($parentProjById[$idParent])) {
+          if (!empty($parentProjById[$idParent]['owner'])) {
+            $this->owner = $parentProjById[$idParent]['owner'];
+            $this->ownerName = $parentProjById[$idParent]['ownerName'];
+            $this->ownerByProjectId = $idParent;
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  public function fetchSubProjectList($idProject, $path) {
+		$subProjectList = DB::getPDO()->fetchAllByKey("
+			select p.ID, p.path, p.P_ID
+          , p.M_DIST_DESC as title
+          , p.L_APPOITMENT_USER as owner
+          , IF('' != p.L_APPOITMENT_USER
+            , (select u.ADM_NAME from ADMIN_USERS u where u.ADM_ACCOUNT = p.L_APPOITMENT_USER limit 1)
+            , '') as ownerName
+			from IN7_MK_BAZA_DYSTRYBUCJI p
+			where p.path like '{$path}-%'
+				and p.A_STATUS not in('DELETED')
+		", 'ID');
+		return $subProjectList;
+	}
+
+  public function fixSubProjectOwner($parentIdProject, $parentOwner, $parentOwnerName, $ownerByProjectId) {
+    if (empty($this->owner)) return;
+    if (empty($this->subProjectList)) return;
+
+    foreach ($this->subProjectList as $idSubProj => $subProj) {
+      if ($parentIdProject != $subProj['P_ID']) continue;
+      if (!empty($subProj['owner'])) {
+        $this->subProjectList[$idSubProj]['ownerByProjectId'] = $subProj['ID'];
+        $this->fixSubProjectOwner($subProj['ID'], $subProj['owner'], $subProj['ownerName'], $subProj['ID']);
+        continue;
+      }
+      $this->subProjectList[$idSubProj]['owner'] = $parentOwner;
+      $this->subProjectList[$idSubProj]['ownerName'] = $parentOwnerName;
+      $this->subProjectList[$idSubProj]['ownerByProjectId'] = $ownerByProjectId;
+      $this->fixSubProjectOwner($subProj['ID'], $parentOwner, $parentOwnerName, $ownerByProjectId);
+    }
+  }
+
+}

+ 142 - 0
SE/se-lib/ProjectKosztorysSchema.php

@@ -0,0 +1,142 @@
+<?php
+
+class ProjectKosztorysSchema {
+
+  public static function getSchema() {
+    static $_schema = null;
+		if (null !== $_schema) return $_schema;
+    /*
+			22444 INNE Kosztorys - zasoby
+				22445 INNE Kabel 2J do wdmuchiwania w mikrorurce 7/4 mm z wdmuchiwaniem
+				22446 INNE Kabel 4J do wdmuchiwania w mikrorurce 7/4 mm z wdmuchiwaniem
+				... Labels for order form
+				22460 TABELA Światłowód (Alias do [20225] TABELA default_db/Rozdzielcza_Kabel_Swiatlowodowy_wsg84)
+					22461 20299 KOMORKA ZASOB (Alias do [20299] KOMORKA OznKabla)
+					22462 20292 KOMORKA JEDNOSTKA_METR ilość [m] (Alias do [20292] KOMORKA Dlugosc)
+				... where to search for data
+		*/
+		{
+			$pdo = DB::getPDO();
+			$sth = $pdo->prepare("
+				select z.ID, z.TYPE, z.DESC, z.ALIAS_ID
+					, c.ID as c_ID, c.TYPE as c_TYPE, c.DESC as c_DESC, c.ALIAS_ID as c_ALIAS_ID
+					, a.ID as a_ID, a.TYPE as a_TYPE, a.DESC as a_DESC, a.ALIAS_ID as a_ALIAS_ID
+					, za.ID as za_ID, za.TYPE as za_TYPE, za.DESC as za_DESC, za.ALIAS_ID as za_ALIAS_ID
+				from CRM_LISTA_ZASOBOW z
+					left join CRM_LISTA_ZASOBOW c  on(c.PARENT_ID = z.ID)
+					left join CRM_LISTA_ZASOBOW a  on(a.ID  = z.ALIAS_ID and z.ALIAS_ID > 0)
+					left join CRM_LISTA_ZASOBOW za on(za.ID = c.ALIAS_ID and c.ALIAS_ID > 0)
+				where z.PARENT_ID = 22444  -- TODO how to find ID, Typespecial with link
+			");
+			$sth->execute();
+			$rawConf = $sth->fetchAll();
+			$conf = array();
+			$conf['type'] = array();
+			$conf['layer'] = array();
+			foreach ($rawConf as $z) {
+				if ('INNE' == $z['TYPE']) {
+					if (!array_key_exists($z['ID'], $conf['type'])) {
+						$conf['type'][$z['ID']] = $z['DESC'];
+					}
+				}
+			}
+			foreach ($rawConf as $z) {
+				if ('TABELA' == $z['TYPE']) {
+					if (!array_key_exists($z['ID'], $conf['layer'])) {
+						$layer = array();
+						$layer['label'] = $z['DESC'];
+						$layer['tabela_id'] = $z['a_ID'];
+						$layer['tabela_name'] = $z['a_DESC'];
+						$layer['jednostka'] = '';
+						$layer['zasob_type'] = '';
+						$layer['zasob_field'] = '';
+						$layer['zasob_id'] = '';
+						$layer['ilosc_field'] = '';
+						$layer['type'] = array();
+						$conf['layer'][$z['ID']] = $layer;
+					}
+					if ($z['c_ID'] > 0) {
+						if ('JEDNOSTKA_' == substr($z['c_DESC'], 0, 10)) {
+							$layer = $conf['layer'][$z['ID']];
+							$layer['jednostka'] = substr($z['c_DESC'], 10);
+							$layer['ilosc_field'] = $z['za_DESC'];
+							$layer['zasob_id'] = $z['za_ID'];
+							$conf['layer'][$z['ID']] = $layer;
+						}
+						/*
+							[za_ID] => 20299
+							[za_TYPE] => KOMORKA
+							[za_DESC] => OznKabla
+							[za_ALIAS_ID] => 0
+						*/
+						switch ($z['c_DESC']) {
+							case 'ZASOB': {
+								if ($z['za_ID'] > 0) {
+									$layer = $conf['layer'][$z['ID']];
+									$layer['zasob_type'] = $z['c_DESC'];
+									$layer['zasob_field'] = $z['za_DESC'];
+									$layer['zasob_id'] = $z['za_ID'];
+									$conf['layer'][$z['ID']] = $layer;
+								}
+							} break;
+							case 'ZASOB_ID': {
+								if ($z['za_ID'] > 0) {
+									$layer = $conf['layer'][$z['ID']];
+									$layer['zasob_type'] = $z['c_DESC'];
+									$layer['zasob_id'] = $z['za_ID'];
+									$layer['zasob_label'] = $z['za_DESC'];
+									$conf['layer'][$z['ID']] = $layer;
+									$conf['layer'][$z['ID']]['type'][$z['za_ID']] = $z['za_DESC'];
+								}
+							} break;
+							case 'TYPE': {
+								//DBG::_(true, true, "z", $z, __CLASS__, __FUNCTION__, __LINE__);
+								if (empty($z['c_ALIAS_ID'])) throw new Exception("Schema error - brak ALIAS_ID dla typu");
+								if (empty($conf['type'][$z['za_ID']])) ;// TODO: throw exception
+								$conf['layer'][$z['ID']]['type'][$z['za_ID']] = $z['za_DESC'];
+							} break;
+						}
+					}
+				}
+			}
+			foreach ($rawConf as $z) {
+				if ('INNE' == $z['TYPE']) {
+				} else if ('TABELA' == $z['TYPE']) {
+				} else {
+					SE_Layout::alert('warnig', "BUG: unimplemented type '{$z['TYPE']}' for zasob nr: {$z['ID']}");
+				}
+			}
+			DBG::_('DBG', '>1', "config", $conf, __CLASS__, __FUNCTION__, __LINE__);
+			{// validate schema - show warnings
+				foreach ($conf['layer'] as $layer) {
+					try {
+						if (empty($layer['tabela_id'])) throw new Exception("brak zdefiniowanego aliasa do tabeli dla warstwy {$layer['label']}");
+						if (empty($layer['jednostka'])) throw new Exception("brak jednostki dla warstwy [{$layer['tabela_id']}] {$layer['label']}");
+						switch ($layer['jednostka']) {
+							case 'METR': break;
+							case 'SZTUKA': break;
+							default: throw new Exception("Nieznana jednostka '{$layer['jednostka']}' dla warstwy  [{$layer['tabela_id']}] {$layer['label']}");
+						}
+					} catch (Exception $e) {
+						SE_Layout::alert('warning', $e->getMessage());
+					}
+				}
+			}
+			DBG::_('DBG', '>2', "rawConf", $rawConf, __CLASS__, __FUNCTION__, __LINE__);
+			$_schema['config'] = $conf;
+		}
+
+		$_schema['nr'] = "Nr projektu";
+		$_schema['title'] = "Tytuł projektu";
+		$_schema['ownerName'] = "Osoba prowadząca";
+		$_schema['cost_total'] = "Szacowany koszt projektu [zł]";
+    return $_schema;
+  }
+
+  public static function getLayerJednostka($idLayer) {
+    $schema = self::getSchema();
+    if (empty($schema['config']['layer'][$idLayer])) return null;// TODO: throw exception?
+    return $schema['config']['layer'][$idLayer]['jednostka'];
+  }
+
+}

+ 465 - 551
SE/se-lib/Route/UrlAction/ProjektyKosztyWstepnychRobot.php

@@ -1,9 +1,14 @@
 <?php
 
 Lib::loadClass('RouteBase');
+Lib::loadClass('ProjectKosztorysSchema');
+Lib::loadClass('ProjectKosztorysModel');
+Lib::loadClass('ProjectKosztorysCennik');
 
 class Route_UrlAction_ProjektyKosztyWstepnychRobot extends RouteBase {// TODO: UrlActionBase @see Route_UrlAction
 
+	public $_model = array();
+
 	public function handleAuth() {
 		if (!User::logged()) {
 			User::authByRequest();
@@ -33,9 +38,35 @@ class Route_UrlAction_ProjektyKosztyWstepnychRobot extends RouteBase {// TODO: U
 		try {
 			$idProject = V::get('ID_PROJECT', 0, $_REQUEST, 'int');
 			$idCompany = V::get('ID_COMPANY', 0, $_REQUEST, 'int');
+			$admin = true;
+
+			$ofertaArgs = compact('idProject', 'idCompany', 'admin');
 			$this->panel($idProject, $idCompany);
 			if ($idProject > 0) {
-				$this->oferta($idProject, $idCompany, $admin = true);
+				$this->oferta($ofertaArgs);
+			} else {
+				$this->defaultOferta();
+			}
+		} catch (Exception $e) {
+			SE_Layout::alert('danger', "Error #" . $e->getCode() .  "|" . $e->getLine() .  ": " . $e->getMessage());
+		}
+		SE_Layout::dol();
+	}
+
+	public function ofertaCompanyAction() {
+		// TODO: check if user is allowed to run this action
+		SE_Layout::gora();
+		SE_Layout::menu();
+		try {
+			$idProject = V::get('ID_PROJECT', 0, $_REQUEST, 'int');
+			$idCompany = V::get('ID_COMPANY', 0, $_REQUEST, 'int');// TODO: $idCompany from ADMIN_USERS
+			$admin = false;
+			$companyAdmin = true;
+
+			$ofertaArgs = compact('idProject', 'idCompany', 'admin', 'companyAdmin');
+			$this->panel($idProject, $idCompany);
+			if ($idProject > 0) {
+				$this->oferta($ofertaArgs);
 			} else {
 				$this->defaultOferta();
 			}
@@ -67,9 +98,12 @@ class Route_UrlAction_ProjektyKosztyWstepnychRobot extends RouteBase {// TODO: U
 		try {
 			$idProject = V::get('ID_PROJECT', 0, $_REQUEST, 'int');
 			$idCompany = V::get('ID_COMPANY', 0, $_REQUEST, 'int');
+			$admin = false;
+
 			if (!$idProject) throw new Exception("Wrong param in 'ID_PROJECT' - expected integer!");// TODO: show select box if not defined
+			$ofertaArgs = compact('idProject', 'idCompany', 'admin');
 			$this->panel($idProject, $idCompany);
-			$this->oferta($idProject, $idCompany, $admin = false);
+			$this->oferta($ofertaArgs);
 		} catch (Exception $e) {
 			SE_Layout::alert('danger', "Error #" . $e->getCode() .  "|" . $e->getLine() .  ": " . $e->getMessage());
 		}
@@ -606,9 +640,10 @@ SQL_FUN;
 		return $info;
 	}
 
-	public function kosztorysXmlAction() {
+	public function kosztorysXmlAction() {// TODO: using old schema fields Agr_*
+/*
 		$idProject = 1921;
-		$schema = $this->getSchema();
+		$schema = ProjectKosztorysSchema::getSchema();
 		$data = $this->_fetchKosztorysData($idProject);
 		//DBG::_('DBG', '>1', "XMLWriter", class_exists('XMLWriter'), __CLASS__, __FUNCTION__, __LINE__);
 		//header('Content-type: application/xml; charset=utf-8');
@@ -624,7 +659,7 @@ SQL_FUN;
 			$xmlWriter->writeAttributeNS('xmlns', 'p5', 'http://www.w3.org/2000/xmlns/', 'https://biuro.biall-net.pl/wfs');
 			for ($i = 1; $i <= 10; $i++) {
 				$idProject += 1;
-				$schema = $this->getSchema();
+				$schema = ProjectKosztorysSchema::getSchema();
 				$data = $this->_fetchKosztorysData($idProject);
 				$xmlWriter->startElement('kosztorys');
 					$xmlWriter->startElement('projekt');
@@ -635,33 +670,34 @@ SQL_FUN;
 					$xmlWriter->endElement();
 				$xmlWriter->endElement();
 			}
-/*
-			$memXmlWriter = new XMLWriter();
-			$memXmlWriter->openMemory();
-			$memXmlWriter->setIndent(true);
 
-			for ($i = 1; $i <= 10; $i++) {
-				$idProject += 1;
-				$schema = $this->getSchema();
-				$data = $this->_fetchKosztorysData($idProject);
-
-				$memXmlWriter->startElement('kosztorys');
-					$memXmlWriter->writeAttribute('id', $idProject);
-					$memXmlWriter->writeAttributeNS('p5', 'typeName', 'https://biuro.biall-net.pl/wfs', 'Kosztorys');
-				$memXmlWriter->text('book_'.$i);
-				$memXmlWriter->endElement();
+			// $memXmlWriter = new XMLWriter();
+			// $memXmlWriter->openMemory();
+			// $memXmlWriter->setIndent(true);
+			//
+			// for ($i = 1; $i <= 10; $i++) {
+			// 	$idProject += 1;
+			// 	$schema = ProjectKosztorysSchema::getSchema();
+			// 	$data = $this->_fetchKosztorysData($idProject);
+			//
+			// 	$memXmlWriter->startElement('kosztorys');
+			// 		$memXmlWriter->writeAttribute('id', $idProject);
+			// 		$memXmlWriter->writeAttributeNS('p5', 'typeName', 'https://biuro.biall-net.pl/wfs', 'Kosztorys');
+			// 	$memXmlWriter->text('book_'.$i);
+			// 	$memXmlWriter->endElement();
+			//
+			// 	if ($i % 5 == 0) {
+			// 		$batchXmlString = $memXmlWriter->outputMemory(true);
+			// 		$xmlWriter->writeRaw($batchXmlString);
+			// 	}
+			// }
+			// $memXmlWriter->flush();
+			// unset($memXmlWriter);
 
-				if ($i % 5 == 0) {
-					$batchXmlString = $memXmlWriter->outputMemory(true);
-					$xmlWriter->writeRaw($batchXmlString);
-				}
-			}
-			$memXmlWriter->flush();
-			unset($memXmlWriter);
-*/
 			$xmlWriter->endElement();
 			$xmlWriter->endDocument();
 		}
+*/
 	}
 
 	public function panel($idProject, $idCompany = 0) {
@@ -671,9 +707,6 @@ SQL_FUN;
 		?>
 <div class="jumbotron">
   <div class="container">
-<?php
-		$schema = $this->getSchema();
-?>
 		<div class="row">
 			<div class="col-md-12">
 				<?php if ($idProject > 0) : ?>
@@ -681,6 +714,7 @@ SQL_FUN;
 					<a class="btn btn-default" href="index.php?_route=UrlAction_ProjektyKosztyWstepnychRobot&ID_PROJECT=<?php echo $idProject; ?>&_print=1" target="_blank">Wydruk Kosztorysu</a>
 					<a class="btn btn-default" href="index.php?_route=UrlAction_ProjektyKosztyWstepnychRobot&ID_PROJECT=<?php echo $idProject; ?>&_task=oferta">oferta</a>
 					<a class="btn btn-default" href="index.php?_route=UrlAction_ProjektyKosztyWstepnychRobot&ID_PROJECT=<?php echo $idProject; ?>&_task=ofertaAdmin">oferta (Admin)</a>
+					<a class="btn btn-default" href="index.php?_route=UrlAction_ProjektyKosztyWstepnychRobot&ID_PROJECT=<?php echo $idProject; ?>&_task=ofertaCompany">oferta company</a>
 				<?php endif; ?>
 				<a class="btn btn-default" href="index.php?_route=UrlAction_ProjektyKosztyWstepnychRobot&_task=ofertaDefaultAdmin" target="_blank">oferta domyślna (Admin)</a>
 			</div>
@@ -714,7 +748,7 @@ SQL_FUN;
 	public function saveOffer($idProject, $idCompany, $args, $admin = false) {
 		DBG::_('DBG', '>1', "args", $args, __CLASS__, __FUNCTION__, __LINE__);
 		if (!$admin) return;
-		$cennik = $this->getCennik($idProject, $idCompany);
+		$cennik = ProjectKosztorysCennik::getCennik($idProject, $idCompany);
 		DBG::_('DBG', '>1', "cennik", $cennik, __CLASS__, __FUNCTION__, __LINE__);
 		$pdo = DB::getPDO();
 		{
@@ -782,7 +816,7 @@ SQL_FUN;
 			$edit__sth->bindParam(':price', $edit_price, PDO::PARAM_STR);
 			$edit__sth->bindParam(':unit', $edit_unit, PDO::PARAM_STR);
 		}
-		$schema = $this->getSchema();
+		$schema = ProjectKosztorysSchema::getSchema();
 		foreach ($schema['config']['layer'] as $idLayer => $layData) {
 			DBG::_('DBG', '>1', "layData", $layData, __CLASS__, __FUNCTION__, __LINE__);
 			foreach ($layData['type'] as $idType => $typeLabel) {
@@ -815,7 +849,7 @@ SQL_FUN;
 	}
 	public function saveDefaultOffer($args) {
 		DBG::_('DBG', '>1', "args", $args, __CLASS__, __FUNCTION__, __LINE__);
-		$cennik = $this->getDefaultCennik();
+		$cennik = ProjectKosztorysCennik::getDefaultCennik();
 		DBG::_('DBG', '>1', "cennik", $cennik, __CLASS__, __FUNCTION__, __LINE__);
 		$idCompany = 0;
 		$idProject = 0;
@@ -884,7 +918,7 @@ SQL_FUN;
 			$edit__sth->bindParam(':price', $edit_price, PDO::PARAM_STR);
 			$edit__sth->bindParam(':unit', $edit_unit, PDO::PARAM_STR);
 		}
-		$schema = $this->getSchema();
+		$schema = ProjectKosztorysSchema::getSchema();
 		foreach ($schema['config']['layer'] as $idLayer => $layData) {
 			DBG::_('DBG', '>1', "layData", $layData, __CLASS__, __FUNCTION__, __LINE__);
 			foreach ($layData['type'] as $idType => $typeLabel) {
@@ -913,87 +947,13 @@ SQL_FUN;
 		}
 	}
 
-	/**
-	 * @returns [ $id_zasob => [ 'price' => $price, 'ID', 'id_zasob', 'id_company', 'id_project', 'unit', 'quantity' ] ]
-	 */
-	public function getDefaultCennik($idCompany = 0) {
-		$schema = $this->getSchema();
-		$typeIdList = array_keys($schema['config']['type']);
-		$sqlTypeIdList = implode(',', $typeIdList);
-		$pdo = DB::getPDO();
-		$sth = $pdo->prepare("
-			select o.ID
-				, o.CRM_LISTA_ZASOBOW_ID as id_zasob
-				, o.COMPANIES_ID as id_company
-				, o.ID_PROJECT as id_project
-				, o.OFFER_PRICE_PER_RESOURCE_UNIT as price
-				, o.RESOURCE_UNIT_TYPE as unit
---				, o.OFFER_UNIT_TYPE as unit
-				, o.REQUIRED_RESOURCE_UNITS as quantity
-			from CRM_LISTA_ZASOBOW_OFFERS o
-			where o.CRM_LISTA_ZASOBOW_ID in({$sqlTypeIdList})
---				and o.A_STATUS not in ('DELETED')
-				and o.ID_PROJECT = 0
-				and o.COMPANIES_ID = :id_company
-		");
-		$sth->bindValue(':id_company', $idCompany, PDO::PARAM_INT);
-		$sth->execute();
-		$cennikRaw = $sth->fetchAll();
-		$cennik = array();
-		foreach ($cennikRaw as $itemRaw) {
-			$item = $itemRaw;
-			$item['price'] = round($item['price'], 2);
-			if (!empty($cennik[$itemRaw['id_zasob']])) {
-				if ($itemRaw['ID'] < $cennik[$itemRaw['id_zasob']]['ID']) continue;
-			}
-			$cennik[$item['id_zasob']] = $item;
-		}
-		return $cennik;
-	}
-
-	/**
-	 * @returns [ $id_zasob => [ 'price' => $price, 'ID', 'id_zasob', 'id_company', 'id_project', 'unit', 'quantity' ] ]
-	 */
-	public function getCennik($idProject, $idCompany = 0) {
-		$schema = $this->getSchema();
-		$typeIdList = array_keys($schema['config']['type']);
-		$sqlTypeIdList = implode(',', $typeIdList);
-		$pdo = DB::getPDO();
-		$sth = $pdo->prepare("
-			select o.ID
-				, o.CRM_LISTA_ZASOBOW_ID as id_zasob
-				, o.COMPANIES_ID as id_company
-				, o.ID_PROJECT as id_project
-				, o.OFFER_PRICE_PER_RESOURCE_UNIT as price
-				, o.RESOURCE_UNIT_TYPE as unit
---				, o.OFFER_UNIT_TYPE as unit
-				, o.REQUIRED_RESOURCE_UNITS as quantity
-			from CRM_LISTA_ZASOBOW_OFFERS o
-			where o.CRM_LISTA_ZASOBOW_ID in({$sqlTypeIdList})
---				and o.A_STATUS not in ('DELETED')
-				and o.ID_PROJECT = :id_project
-				and o.COMPANIES_ID = :id_company
-		");
-		$sth->bindValue(':id_project', $idProject, PDO::PARAM_INT);
-		$sth->bindValue(':id_company', $idCompany, PDO::PARAM_INT);
-		$sth->execute();
-		$cennikRaw = $sth->fetchAll();
-		$cennik = array();
-		foreach ($cennikRaw as $itemRaw) {
-			$item = $itemRaw;
-			$item['price'] = round($item['price'], 2);
-			$cennik[$itemRaw['id_zasob']] = $item;
-		}
-		return $cennik;
-	}
-
 	public function defaultOferta($idCompany = 0) {
 		if ('1' == V::get('save_offer', '', $_POST)) {
 			$this->saveDefaultOffer($_POST);
 		}
-		$schema = $this->getSchema();
+		$schema = ProjectKosztorysSchema::getSchema();
 		if (empty($schema['config']['type'])) throw new Exception("Schema error - brak zdefiniowanych typów");
-		$cennik = $this->getDefaultCennik($idCompany);
+		$cennik = ProjectKosztorysCennik::getDefaultCennik($idCompany);
 		//DBG::_(true, true, "cennik", $cennik, __CLASS__, __FUNCTION__, __LINE__);
 		?>
 <div class="container">
@@ -1005,7 +965,7 @@ SQL_FUN;
 			<tr>
 				<th style="padding:0 6px">id zasobu</th>
 				<th style="padding:0 6px">typ</th>
-				<th style="padding:0 6px">jednotka miary</th>
+				<th style="padding:0 6px">jednostka miary</th>
 				<th style="padding:0 6px">cena jednostkowa</th>
 			</tr>
 			<?php foreach ($layData['type'] as $idType => $typeLabel) : ?>
@@ -1033,16 +993,16 @@ SQL_FUN;
 	/**
 	 * @returns [ 'cost_total' => :numeric, 'sub_costs' => [ $id_zasob => 'price' ] ]
 	 */
-	public function getProjectCostForCennik($idProject, $idCompany = 0) {
+	public function getProjectCostByCennik($idProject, $idCompany = 0) {
 		$projCost = array();
 		$projCost['cost_total'] = 0;
 		$projCost['sub_costs'] = array();
 
-		$defCennik = $this->getDefaultCennik($idCompany);
+		$defCennik = ProjectKosztorysCennik::getDefaultCennik($idCompany);
 		// $defCennik = [ $id_zasob => [ 'price' => $price, 'ID', 'id_zasob', 'id_company', 'id_project', 'unit', 'quantity' ] ]
-		$cennik = $this->getCennik($idProject, $idCompany);
+		$cennik = ProjectKosztorysCennik::getCennik($idProject, $idCompany);
 		$data = $this->getData($idProject, $admin);
-		foreach ($data as $idLayer => $layData) {
+		foreach ($data['summary'] as $idLayer => $layData) {
 			foreach ($layData['data'] as $typeData) {
 				if (!$typeData['idType']) continue;
 				$price = (!empty($cennik[$typeData['idType']]['price']))? $cennik[$typeData['idType']]['price'] : 0;
@@ -1053,13 +1013,49 @@ SQL_FUN;
 				$projCost['cost_total'] += $cost;
 			}
 		}
+		// TODO: fetch sub costs for sub projects
+		// $data['by_project'] = array_reduce($data['rawDataByType'], function($result, $row) {
+		// 	$idProj = $row['ID_PROJECT'];
+		// 	$idLayer = $row['idLayer'];
+		// 	$idType = $row['idType'];
+		// 	if (!array_key_exists($idProj, $result)) $result[$idProj] = array();
+		// 	if (!array_key_exists($idLayer, $result)) $result[$idProj][$idLayer] = array();
+		// 	if (!array_key_exists($idType, $result[$idProj][$idLayer])) {
+		// 		$result[$idProj][$idLayer][$idType] = $row;
+		// 	} else {
+		// 		$result[$idProj][$idLayer][$idType]['ilosc'] += $row['ilosc'];
+		$projCost['sub_proj'] = array();
+		foreach ($data['by_project'] as $idSubProj => $subProjData) {
+			foreach ($subProjData as $idLayer => $layData) {
+				foreach ($layData as $idType => $typeData) {
+					$price = (!empty($cennik[$typeData['idType']]['price']))? $cennik[$typeData['idType']]['price'] : 0;
+					$defPrice = (!empty($defCennik[$typeData['idType']]['price']))? $defCennik[$typeData['idType']]['price'] : 0;
+					$cost = ($price > 0)? $price : $defPrice;
+					$cost = $typeData['ilosc'] * $cost;
+					$projCost['sub_proj'][$idSubProj][$idType] = $cost;
+				}
+			}
+		}
 		return $projCost;
 	}
 
+	public function getModel($idProject) {
+		if (array_key_exists($idProject, $this->_model)) return $this->_model[$idProject];
+		$this->_model[$idProject] = new ProjectKosztorysModel($idProject);
+		return $this->_model[$idProject];
+	}
+
 	public function kosztorys($idProject) {
-		$schema = $this->getSchema();
-		$data = $this->_fetchKosztorysData($idProject);
-		$projCost = $this->getProjectCostForCennik($idProject, $idCompany = 0);
+		$idCompany = 0;
+		$admin = false;
+		$companyAdmin = false;
+		$model = $this->getModel($idProject);
+		//DBG::table("subProjectList", $model->subProjectList, __CLASS__, __FUNCTION__, __LINE__);
+		$schema = ProjectKosztorysSchema::getSchema();
+
+		$projCost = $this->getProjectCostByCennik($idProject, $idCompany);
+		//DBG::_(true, true, "projCost", $projCost, __CLASS__, __FUNCTION__, __LINE__);
+		$viewLayerDataArgs = compact('idProject', 'idCompany', 'admin', 'companyAdmin', 'projCost');
 ?>
 <div class="container">
 	<h1>Kosztorys wstępny robót telekomunikacyjnych</h1>
@@ -1067,37 +1063,43 @@ SQL_FUN;
 		<tr>
 			<th><?php echo $schema['nr']; ?></th>
 			<th><?php echo $schema['title']; ?></th>
-			<th><?php echo $schema['owner_name']; ?></th>
+			<th><?php echo $schema['ownerName']; ?></th>
 			<th style="text-align:right"><?php echo $schema['cost_total']; ?></th>
 		</tr>
 		<tr>
-			<td><?php echo $data['nr']; ?></td>
-			<td><?php echo $data['title']; ?></td>
-			<td><?php echo $data['owner_name']; ?></td>
+			<td><?php echo $model->idProject; ?></td>
+			<td><?php echo $model->title; ?></td>
+			<td><?php echo $model->ownerName; ?></td>
 			<td style="text-align:right"><?php echo number_format($projCost['cost_total'], 2, ',', ' '); ?></td>
 		</tr>
 	</table>
-	<?php $this->viewLayersData($idProject, $idCompany = 0, $admin = false, $projCost['sub_costs']); ?>
+	<?php $this->viewLayersData($viewLayerDataArgs); ?>
 </div>
 <?php
-		DBG::_('DBG', '>0', "data", $data, __CLASS__, __FUNCTION__, __LINE__);
 		DBG::_('DBG', '>0', "schema", $schema, __CLASS__, __FUNCTION__, __LINE__);
 		DBG::_('DBG', '>0', "projCost", $projCost, __CLASS__, __FUNCTION__, __LINE__);
 	}
 
-	public function oferta($idProject, $idCompany = 0, $admin = false) {
+	public function oferta($args) {
+		$idProject = $args['idProject'];
+		$idCompany = V::get('idCompany', 0, $args);
+		$admin = V::get('admin', false, $args);
+		$companyAdmin = V::get('companyAdmin', false, $args);
+
 		if ($admin && '1' == V::get('save_offer', '', $_POST)) {
 			$this->saveOffer($idProject, $idCompany, $_POST, $admin);
 		}
-		$defCennik = $this->getDefaultCennik($idCompany);
-		$cennik = $this->getCennik($idProject, $idCompany);
+		$defCennik = ProjectKosztorysCennik::getDefaultCennik($idCompany);
+		$cennik = ProjectKosztorysCennik::getCennik($idProject, $idCompany);
+
+		$viewLayerDataArgs = compact('idProject', 'idCompany', 'admin', 'companyAdmin');
 		?>
 <div class="container">
 	<?php if ($admin) : ?>
 		<form action="" method="post">
 	<?php endif; ?>
-	<?php $this->viewLayersData($idProject, $idCompany, $admin); ?>
-	<?php if ($admin) : ?>
+	<?php $this->viewLayersData($viewLayerDataArgs); ?>
+	<?php if ($admin || $companyAdmin) : ?>
 		<?php foreach ($cennik as $item) : ?>
 			<input type="hidden" name="edit_price_id_<?php echo $item['id_zasob']; ?>" value="<?php echo $item['ID']; ?>">
 		<?php endforeach; ?>
@@ -1109,32 +1111,69 @@ SQL_FUN;
 <?php
 	}
 
-	public function viewLayersData($idProject, $idCompany = 0, $admin = false, $projSubCost = array()) {
-		$schema = $this->getSchema();
+	public function viewLayersData($args) {
+		$idProject = $args['idProject'];
+		$idCompany = V::get('idCompany', 0, $args);
+		$admin = V::get('admin', false, $args);
+		$companyAdmin = V::get('companyAdmin', false, $args);
+		$projCosts = V::get('projCosts', array(), $args);
+
+		$schema = ProjectKosztorysSchema::getSchema();
 		$conf = $schema['config'];
 		DBG::_('DBG', '>1', "conf", $conf, __CLASS__, __FUNCTION__, __LINE__);
 
+		$summaryTypeCost = V::get('sub_costs', array(), $projCosts);
+		$subProjCost = V::get('sub_proj', array(), $projCosts);
+
 		$data = $this->getData($idProject, $admin);
+		$dataSummary = $data['summary'];
+		$dataSubProj = $data['by_project'];
+		// $dataSubProj = array();
+		// foreach ($data['by_project'] as $idSubProj => $subProjData) {
+		// 	if ($idProject != $idSubProj) $dataSubProj[$idSubProj] = $subProjData;
+		// }
 		DBG::_('DBG', '>1', "data", $data, __CLASS__, __FUNCTION__, __LINE__);
 
-		$defCennik = $this->getDefaultCennik($idCompany);
-		$cennik = $this->getCennik($idProject, $idCompany);
+		$defCennik = ProjectKosztorysCennik::getDefaultCennik($idCompany);
+		$cennik = ProjectKosztorysCennik::getCennik($idProject, $idCompany);
+		$workCennik = ProjectKosztorysCennik::getWorkCennik($idProject, $idCompany);
+		$additionalCosts = array();
+		$additionalCosts[] = array();
+		$additionalCosts[] = array();
+		$additionalCosts[] = array();
+		$additionalSummaryTypeCost = array();
 ?>
-	<?php foreach ($data as $idLayer => $layData) : ?>
-		<h4 style="padding:0 6px"><?php echo $layData['label']; ?></h4>
-		<table class="tabel table-bordered" style="width:100%">
-		<tbody>
+<style type="text/css">
+/* Print Styles */
+@media print {
+	body { font-size:10px; }
+	th, td { font-size:10px; }
+	h1 { font-size:2em; }
+	h2 { font-size:1.6em; }
+	h3 { font-size:1.4em; }
+	h4 { font-size:1.2em; }
+}
+</style>
+<?php $tblCols = 6 + ($admin ? 1 : 0) + ((!empty($summaryTypeCost))? 1 : 0); ?>
+<table class="tabel table-bordered" style="width:100%; margin-bottom:6px; page-break-inside:avoid">
+	<tbody>
+		<?php foreach ($dataSummary as $idLayer => $layData) : ?>
 			<tr>
-				<th style="padding:0 6px">typ</th>
-				<th style="padding:0 6px; text-align:right">ilość</th>
-				<th style="padding:0 6px; text-align:right">jednotka miary</th>
-				<th style="padding:0 6px; text-align:right">cena jednostkowa</th>
+				<td colspan="<?php echo $tblCols; ?>" style="padding:0 6px; font-size:1.2em; line-height:2em; font-style:italic"><?php echo $layData['label']; ?></td>
+			</tr>
+			<tr>
+				<td style="padding:0 6px">typ</td>
+				<td style="width:60px; padding:0 6px; text-align:right">ilość</td>
+				<td style="width:80px; padding:0 6px; text-align:right">jednostka</td>
+				<td style="padding:0 6px; text-align:right">cena jednostkowa</td>
+				<td style="padding:0 6px; text-align:right">cena jedn. (robocizna)</td>
 				<?php if ($admin) : ?>
-					<th style="padding:0 6px; text-align:right">cena jednostkowa (domyślna)</th>
+					<td style="padding:0 6px; text-align:right">cena jedn. (domyślna)</td>
 				<?php endif; ?>
-				<?php if (!empty($projSubCost)) : ?>
-					<th style="padding:0 6px; text-align:right">szacowany koszt [zł]</th>
+				<?php if (!empty($summaryTypeCost)) : ?>
+					<td style="padding:0 6px; text-align:right">szacowany koszt [zł]</td>
 				<?php endif; ?>
+				<td style="padding:0 6px; text-align:right">uwagi</td>
 			</tr>
 			<?php foreach ($layData['data'] as $typeData) : ?>
 				<?php if (!$typeData['idType']) : ?>
@@ -1153,31 +1192,205 @@ SQL_FUN;
 						</td>
 					</tr>
 				<?php else : ?>
-					<?php $defPrice = (!empty($defCennik[$typeData['idType']]['price']))? $defCennik[$typeData['idType']]['price'] : 0; ?>
-					<?php $price = (!empty($cennik[$typeData['idType']]['price']))? $cennik[$typeData['idType']]['price'] : $defPrice; ?>
+					<?php $idType = $typeData['idType']; ?>
+					<?php $defPrice = (!empty($defCennik[$idType]['price']))? $defCennik[$idType]['price'] : 0; ?>
+					<?php $price = (!empty($cennik[$idType]['price']))? $cennik[$idType]['price'] : $defPrice; ?>
+					<?php $workPrice = (!empty($workCennik[$idType]['price']))? $workCennik[$idType]['price'] : 0; ?>
+					<?php $uwagi = (!empty($cennik[$idType]['notes']))? $cennik[$idType]['notes'] : '';// TODO: uwagi ?>
 					<tr>
-						<td style="padding:0 6px" title="[<?php echo $typeData['idType']; ?>] <?php echo $typeData['type']; ?>"><?php echo $typeData['type']; ?></td>
+						<td style="padding:0 6px" title="[<?php echo $idType; ?>] <?php echo $typeData['type']; ?>"> - <?php echo $typeData['type']; ?></td>
 						<td style="padding:0 6px; text-align:right"><?php echo $typeData['ilosc']; ?></td>
-						<td style="padding:0 6px; text-align:right"><?php echo $typeData['jednostka']; ?></td>
+						<td style="padding:0 6px; text-align:right"><?php echo ProjectKosztorysSchema::getLayerJednostka($idLayer); ?></td>
 						<td style="padding:3px 6px; text-align:right">
-						<?php if ($admin) : ?>
-							<input type="text" style="text-align:right" class="form-control input-sm" name="price_<?php echo $typeData['idType']; ?>" value="<?php echo $price; ?>"/>
+						<?php if ($admin || $companyAdmin) : ?>
+							<input type="text" style="text-align:right" class="form-control input-sm" name="price_<?php echo $idType; ?>" value="<?php echo $price; ?>"/>
 						<?php else : ?>
-							<?php echo $price; ?>
+							<?php echo number_format($price, 2, ',', ' '); ?>
+						<?php endif; ?>
+						</td>
+						<td style="padding:3px 6px; text-align:right">
+						<?php if ($admin || $companyAdmin) : ?>
+							<input type="text" style="text-align:right" class="form-control input-sm" name="work_price_<?php echo $idType; ?>" value="<?php echo $workPrice; ?>"/>
+						<?php else : ?>
+							<?php echo number_format($workPrice, 2, ',', ' '); ?>
 						<?php endif; ?>
 						</td>
 						<?php if ($admin) : ?>
 							<td style="padding:3px 6px; text-align:right"><?php echo $defPrice; ?></td>
 						<?php endif; ?>
-						<?php if (!empty($projSubCost)) : ?>
-							<td style="padding:3px 6px; text-align:right"><?php echo number_format(V::get($typeData['idType'], 0, $projSubCost), 2, ',', ' '); ?></td>
+						<?php if (!empty($summaryTypeCost)) : ?>
+							<td style="padding:3px 6px; text-align:right"><?php echo number_format(V::get($idType, 0, $summaryTypeCost), 2, ',', ' '); ?></td>
 						<?php endif; ?>
+						<td style="padding:3px 6px; text-align:right">
+							<?php if ($admin || $companyAdmin) : ?>
+								<input type="text" style="text-align:left" class="form-control input-sm" name="uwagi_<?php echo $idType; ?>" value="<?php echo $uwagi; ?>"/>
+							<?php else : ?>
+								<?php echo $uwagi; ?>
+							<?php endif; ?>
+						</td>
 					</tr>
 				<?php endif; ?>
 			<?php endforeach; ?>
-		</tbody>
+		<?php endforeach; ?>
+
+		<?php if (!empty($additionalCosts)) : ?>
+			<tr>
+				<td colspan="<?php echo $tblCols; ?>" style="padding:0 6px; font-size:1.2em; line-height:2em; font-style:italic">Koszty dodatkowe</td>
+			</tr>
+			<tr>
+				<td style="padding:0 6px">typ</td>
+				<td style="width:60px; padding:0 6px; text-align:right">ilość</td>
+				<td style="width:80px; padding:0 6px; text-align:right">jednostka</td>
+				<td style="padding:0 6px; text-align:right">cena jednostkowa</td>
+				<td style="padding:0 6px; text-align:right">cena jedn. (robocizna)</td>
+				<?php if ($admin) : ?>
+					<td style="padding:0 6px; text-align:right">cena jedn. (domyślna)</td>
+				<?php endif; ?>
+				<?php if (!empty($summaryTypeCost)) : ?>
+					<td style="padding:0 6px; text-align:right">szacowany koszt [zł]</td>
+				<?php endif; ?>
+				<td style="padding:0 6px; text-align:right">uwagi</td>
+			</tr>
+			<?php foreach ($additionalCosts as $idType => $additionalCost) : ?>
+				<?php $additionalPrice =  0;//TODO: get from $additionalCennik or $additionalCost ?>
+				<?php $additionalWorkPrice =  0;//TODO: get from $additionalWorkCennik or $additionalCost ?>
+				<?php $additionalCount =  0;//TODO: fetch ?>
+				<?php $additionalUnit =  '';//TODO: fetch ?>
+				<?php $jednostka =  '';//TODO: fetch ?>
+				<?php $ilosc =  '';//TODO: fetch ?>
+				<?php $typeLabel =  '';//TODO: fetch ?>
+				<?php $price =  0;//TODO: fetch ?>
+				<?php $defPrice =  0;//TODO: fetch ?>
+				<?php $workPrice =  0;//TODO: fetch ?>
+				<?php $uwagi =  '';//TODO: fetch ?>
+				<tr>
+					<td style="padding:0 6px" >
+						<?php if ($admin || $companyAdmin) : ?>
+							<input type="text" style="text-align:right" class="form-control input-sm" name="additional_label_<?php echo $idType; ?>" value="<?php echo $typeLabel; ?>"/>
+						<?php else : ?>
+							- <?php echo $typeLabel; ?>
+						<?php endif; ?>
+					</td>
+					<td style="padding:0 6px; text-align:right">
+						<?php if ($admin || $companyAdmin) : ?>
+							<input type="text" style="text-align:right" class="form-control input-sm" name="additional_count_<?php echo $idType; ?>" value="<?php echo $additionalCount; ?>"/>
+						<?php else : ?>
+							<?php echo $ilosc; ?>
+						<?php endif; ?>
+					</td>
+					<td style="padding:0 6px; text-align:right">
+						<?php if ($admin || $companyAdmin) : ?>
+							<input type="text" style="text-align:right" class="form-control input-sm" name="additional_unit_<?php echo $idType; ?>" value="<?php echo $additionalUnit; ?>"/>
+						<?php else : ?>
+							<?php echo $jednostka; ?>
+						<?php endif; ?>
+					</td>
+					<td style="padding:3px 6px; text-align:right">
+					<?php if ($admin || $companyAdmin) : ?>
+						<input type="text" style="text-align:right" class="form-control input-sm" name="additional_price_<?php echo $idType; ?>" value="<?php echo $additionalPrice; ?>"/>
+					<?php else : ?>
+						<?php echo number_format($price, 2, ',', ' '); ?>
+					<?php endif; ?>
+					</td>
+					<td style="padding:3px 6px; text-align:right">
+					<?php if ($admin || $companyAdmin) : ?>
+						<input type="text" style="text-align:right" class="form-control input-sm" name="additional_work_price_<?php echo $idType; ?>" value="<?php echo $additionalWorkPrice; ?>"/>
+					<?php else : ?>
+						<?php echo number_format($workPrice, 2, ',', ' '); ?>
+					<?php endif; ?>
+					</td>
+					<?php if ($admin) : ?>
+						<td style="padding:3px 6px; text-align:right"></td>
+					<?php endif; ?>
+					<?php if (!empty($additionalSummaryTypeCost)) : ?>
+						<td style="padding:3px 6px; text-align:right"><?php echo number_format(V::get($idType, 0, $additionalSummaryTypeCost), 2, ',', ' '); ?></td>
+					<?php endif; ?>
+					<td style="padding:3px 6px; text-align:right">
+						<?php if ($admin || $companyAdmin) : ?>
+							<input type="text" style="text-align:left" class="form-control input-sm" name="additional_uwagi_<?php echo $idType; ?>" value="<?php echo $uwagi; ?>"/>
+						<?php else : ?>
+							<?php echo $uwagi; ?>
+						<?php endif; ?>
+					</td>
+				</tr>
+			<?php endforeach; ?>
+		<?php endif; ?>
+	</tbody>
+</table>
+
+
+<?php $tblCols = 4 + ($admin ? 1 : 0) + ((!empty($summaryTypeCost))? 1 : 0); ?>
+	<?php if (!empty($dataSubProj)) : ?>
+		<p style="page-break-before:always; margin-top:60px"></p>
+		<h3>Składowe wg projektów podrzędnych:</h3>
+		<?php foreach ($dataSubProj as $idSubProj => $subProjData) : ?>
+			<table class="tabel table-bordered" style="width:100%; margin-bottom:6px; page-break-inside:avoid">
+				<tbody>
+					<tr>
+						<th colspan="<?php echo $tblCols; ?>"
+								style="padding:0 6px">
+								<h4>Projekt <?php echo $idSubProj; ?>: <i><?php echo $this->getModel($idProject)->getProjectName($idSubProj); ?></i>:</h4>
+							</th>
+					</tr>
+					<?php foreach ($subProjData as $idLayer => $layData) : ?>
+						<tr>
+							<td colspan="<?php echo $tblCols; ?>"
+									style="padding:0 6px; font-size:1.2em; line-height:2em; font-style:italic"><?php echo $conf['layer'][$idLayer]['label']; ?></td>
+						</tr>
+						<tr>
+							<td style="padding:0 6px">typ</td>
+							<td style="padding:0 6px; text-align:right">ilość</td>
+							<td style="padding:0 6px; text-align:right">jednostka</td>
+							<?php if ($admin) : ?>
+								<td style="padding:0 6px; text-align:right">cena jednostkowa</td>
+								<td style="padding:0 6px; text-align:right">cena jedn. (domyślna)</td>
+							<?php endif; ?>
+							<?php if (!empty($subProjCost)) : ?>
+								<td style="padding:0 6px; text-align:right">szacowany koszt [zł]</td>
+							<?php endif; ?>
+						</tr>
+						<?php foreach ($layData as $idType => $typeData) : ?>
+							<?php if (!$typeData['idType']) : ?>
+								<tr>
+									<td colspan="4">
+										<?php if ($admin) {
+												$fixLink = 'index.php?MENU_INIT=VIEWTABLE_AJAX&ZASOB_ID=' . $conf['layer'][$idLayer]['tabela_id'];
+												$fixLink .= '&f_' . $conf['layer'][$idLayer]['zasob_field'] . '=%3D' . $typeData['type'];
+												$fixLink .= '&_hash=' . uniqid();
+												$fixProjectLink = '<a target="_blank" href="' . $fixLink . "&f_ID_PROJECT={$idProject}" . '">' . "Popraw" . '</a>' . " (dane dla projektu [{$idProject}])";
+												$fixAllLink = '<a target="_blank" href="' . $fixLink . '">' . "Popraw wszystkie" . '</a>';
+												SE_Layout::alert('danger', "Niezdefiniowany typ: <code>{$typeData['type']}</code> - {$fixProjectLink}, {$fixAllLink}");
+											} ?>
+										<?php DBG::_('DBG', '>1', "Error conf", $conf, __CLASS__, __FUNCTION__, __LINE__); ?>
+										<?php DBG::_('DBG', '>1', "Error", $typeData, __CLASS__, __FUNCTION__, __LINE__); ?>
+									</td>
+								</tr>
+							<?php else : ?>
+								<?php $defPrice = (!empty($defCennik[$typeData['idType']]['price']))? $defCennik[$typeData['idType']]['price'] : 0; ?>
+								<?php $price = (!empty($cennik[$typeData['idType']]['price']))? $cennik[$typeData['idType']]['price'] : $defPrice; ?>
+								<tr>
+									<td style="padding:0 6px" title="[<?php echo $typeData['idType']; ?>] <?php echo $typeData['type']; ?>"><?php echo $typeData['type']; ?></td>
+									<td style="padding:0 6px; text-align:right"><?php echo $typeData['ilosc']; ?></td>
+									<td style="padding:0 6px; text-align:right"><?php echo ProjectKosztorysSchema::getLayerJednostka($idLayer); ?></td>
+									<?php if ($admin) : ?>
+										<td style="padding:3px 6px; text-align:right">
+											<input type="text" style="text-align:right" class="form-control input-sm" name="price_<?php echo $typeData['idType']; ?>" value="<?php echo $price; ?>"/>
+										</td>
+									<?php endif; ?>
+									<?php if ($admin) : ?>
+										<td style="padding:3px 6px; text-align:right"><?php echo $defPrice; ?></td>
+									<?php endif; ?>
+									<?php if (!empty($subProjCost)) : ?>
+										<td style="padding:3px 6px; text-align:right"><?php echo number_format(V::get($typeData['idType'], 0, $subProjCost[$idSubProj]), 2, ',', ' '); ?></td>
+									<?php endif; ?>
+								</tr>
+							<?php endif; ?>
+						<?php endforeach; ?>
+					<?php endforeach; ?>
+				<?php endforeach; ?>
+			</tbody>
 		</table>
-	<?php endforeach; ?>
+	<?php endif; ?>
 <?php
 	}
 
@@ -1190,419 +1403,120 @@ SQL_FUN;
 		";
 	}
 
-	public function getSchema() {
-		static $_schema = null;
-		if (null !== $_schema) return $_schema;
-		$_schema = array();
-		/*
-			22444 INNE Kosztorys - zasoby
-				22445 INNE Kabel 2J do wdmuchiwania w mikrorurce 7/4 mm z wdmuchiwaniem
-				22446 INNE Kabel 4J do wdmuchiwania w mikrorurce 7/4 mm z wdmuchiwaniem
-				... Labels for order form
-				22460 TABELA Światłowód (Alias do [20225] TABELA default_db/Rozdzielcza_Kabel_Swiatlowodowy_wsg84)
-					22461 20299 KOMORKA ZASOB (Alias do [20299] KOMORKA OznKabla)
-					22462 20292 KOMORKA JEDNOSTKA_METR ilość [m] (Alias do [20292] KOMORKA Dlugosc)
-				... where to search for data
-		*/
-		{
-			$pdo = DB::getPDO();
-			$sth = $pdo->prepare("
-				select z.ID, z.TYPE, z.DESC, z.ALIAS_ID
-					, c.ID as c_ID, c.TYPE as c_TYPE, c.DESC as c_DESC, c.ALIAS_ID as c_ALIAS_ID
-					, a.ID as a_ID, a.TYPE as a_TYPE, a.DESC as a_DESC, a.ALIAS_ID as a_ALIAS_ID
-					, za.ID as za_ID, za.TYPE as za_TYPE, za.DESC as za_DESC, za.ALIAS_ID as za_ALIAS_ID
-				from CRM_LISTA_ZASOBOW z
-					left join CRM_LISTA_ZASOBOW c  on(c.PARENT_ID = z.ID)
-					left join CRM_LISTA_ZASOBOW a  on(a.ID  = z.ALIAS_ID and z.ALIAS_ID > 0)
-					left join CRM_LISTA_ZASOBOW za on(za.ID = c.ALIAS_ID and c.ALIAS_ID > 0)
-				where z.PARENT_ID = 22444  -- TODO how to find ID, Typespecial with link
-			");
-			$sth->execute();
-			$rawConf = $sth->fetchAll();
-			$conf = array();
-			$conf['type'] = array();
-			$conf['layer'] = array();
-			foreach ($rawConf as $z) {
-				if ('INNE' == $z['TYPE']) {
-					if (!array_key_exists($z['ID'], $conf['type'])) {
-						$conf['type'][$z['ID']] = $z['DESC'];
-					}
-				}
-			}
-			foreach ($rawConf as $z) {
-				if ('TABELA' == $z['TYPE']) {
-					if (!array_key_exists($z['ID'], $conf['layer'])) {
-						$layer = array();
-						$layer['label'] = $z['DESC'];
-						$layer['tabela_id'] = $z['a_ID'];
-						$layer['tabela_name'] = $z['a_DESC'];
-						$layer['jednostka'] = '';
-						$layer['zasob_type'] = '';
-						$layer['zasob_field'] = '';
-						$layer['zasob_id'] = '';
-						$layer['ilosc_field'] = '';
-						$layer['type'] = array();
-						$conf['layer'][$z['ID']] = $layer;
-					}
-					if ($z['c_ID'] > 0) {
-						if ('JEDNOSTKA_' == substr($z['c_DESC'], 0, 10)) {
-							$layer = $conf['layer'][$z['ID']];
-							$layer['jednostka'] = substr($z['c_DESC'], 10);
-							$layer['ilosc_field'] = $z['za_DESC'];
-							$layer['zasob_id'] = $z['za_ID'];
-							$conf['layer'][$z['ID']] = $layer;
-						}
-						/*
-							[za_ID] => 20299
-							[za_TYPE] => KOMORKA
-							[za_DESC] => OznKabla
-							[za_ALIAS_ID] => 0
-						*/
-						switch ($z['c_DESC']) {
-							case 'ZASOB': {
-								if ($z['za_ID'] > 0) {
-									$layer = $conf['layer'][$z['ID']];
-									$layer['zasob_type'] = $z['c_DESC'];
-									$layer['zasob_field'] = $z['za_DESC'];
-									$layer['zasob_id'] = $z['za_ID'];
-									$conf['layer'][$z['ID']] = $layer;
-								}
-							} break;
-							case 'ZASOB_ID': {
-								if ($z['za_ID'] > 0) {
-									$layer = $conf['layer'][$z['ID']];
-									$layer['zasob_type'] = $z['c_DESC'];
-									$layer['zasob_id'] = $z['za_ID'];
-									$layer['zasob_label'] = $z['za_DESC'];
-									$conf['layer'][$z['ID']] = $layer;
-									$conf['layer'][$z['ID']]['type'][$z['za_ID']] = $z['za_DESC'];
-								}
-							} break;
-							case 'TYPE': {
-								//DBG::_(true, true, "z", $z, __CLASS__, __FUNCTION__, __LINE__);
-								if (empty($z['c_ALIAS_ID'])) throw new Exception("Schema error - brak ALIAS_ID dla typu");
-								if (empty($conf['type'][$z['za_ID']])) ;// TODO: throw exception
-								$conf['layer'][$z['ID']]['type'][$z['za_ID']] = $z['za_DESC'];
-							} break;
-						}
-					}
-				}
-			}
-			foreach ($rawConf as $z) {
-				if ('INNE' == $z['TYPE']) {
-				} else if ('TABELA' == $z['TYPE']) {
-				} else {
-					SE_Layout::alert('warnig', "BUG: unimplemented type '{$z['TYPE']}' for zasob nr: {$z['ID']}");
-				}
-			}
-			DBG::_('DBG', '>1', "config", $conf, __CLASS__, __FUNCTION__, __LINE__);
-			{// validate schema - show warnings
-				foreach ($conf['layer'] as $layer) {
-					try {
-						if (empty($layer['tabela_id'])) throw new Exception("brak zdefiniowanego aliasa do tabeli dla warstwy {$layer['label']}");
-						if (empty($layer['jednostka'])) throw new Exception("brak jednostki dla warstwy [{$layer['tabela_id']}] {$layer['label']}");
-						switch ($layer['jednostka']) {
-							case 'METR': break;
-							case 'SZTUKA': break;
-							default: throw new Exception("Nieznana jednostka '{$layer['jednostka']}' dla warstwy  [{$layer['tabela_id']}] {$layer['label']}");
-						}
-					} catch (Exception $e) {
-						SE_Layout::alert('warning', $e->getMessage());
-					}
-				}
-			}
-			DBG::_('DBG', '>2', "rawConf", $rawConf, __CLASS__, __FUNCTION__, __LINE__);
-			$_schema['config'] = $conf;
-		}
-
-		$_schema['nr'] = "Nr projektu";
-		$_schema['title'] = "Tytuł projektu";
-		$_schema['owner_name'] = "Osoba prowadząca";
-		$_schema['cost_total'] = "Szacowany koszt projektu [zł]";
-		$_schema['sub_costs'] = array();
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Wykop";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$layerConf['_agr_fields_to_cols']['ilosc'] = "ilość [m]";
-			$layerConf['_agr_fields_to_cols']['cena'] = "podstawowa cena [zł/m]";
-			$layerConf['_agr_fields_to_cols']['koszt'] = "koszt [zł]";
-			$_schema['sub_costs']['Rozdzielcza_Wykop_przedmiar_na_mikrorurki'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Mikrokanalizacja do klienta";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$layerConf['_agr_fields_to_cols']['ilosc'] = "ilość [m]";
-			$layerConf['_agr_fields_to_cols']['cena'] = "podstawowa cena [zł/m]";
-			$layerConf['_agr_fields_to_cols']['koszt'] = "koszt [zł]";
-			$_schema['sub_costs']['Rozdzielcza_Mikrokanalizacja_do_klienta'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Przeciski";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$layerConf['_agr_fields_to_cols']['ilosc'] = "ilość [m]";
-			$layerConf['_agr_fields_to_cols']['cena'] = "podstawowa cena [zł/m]";
-			$layerConf['_agr_fields_to_cols']['koszt'] = "koszt [zł]";
-			$_schema['sub_costs']['Rozdzielcza_Przeciski_110mm'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Zabruki";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$layerConf['_agr_fields_to_cols']['ilosc'] = "ilość [m]";
-			$layerConf['_agr_fields_to_cols']['cena'] = "podstawowa cena [zł/m]";
-			$layerConf['_agr_fields_to_cols']['koszt'] = "koszt [zł]";
-			$_schema['sub_costs']['Rozdzielcza_Zabruki'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Światłowód";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$layerConf['_agr_fields_to_cols']['ilosc'] = "ilość [m]";
-			$layerConf['_agr_fields_to_cols']['cena'] = "podstawowa cena [zł/m]";
-			$layerConf['_agr_fields_to_cols']['koszt'] = "koszt [zł]";
-			{
-				$layerSubCostsConf = array();
-				$layerSubCostsConf['group_by_field'] = 'wlokien_j';
-				$layerSubCostsConf['sql_agr_func'] = array();
-				$layerSubCostsConf['sql_agr_func']['sum_dlugosc'] = array();//sum(Dlugosc) as sum_dlugosc
-				$layerSubCostsConf['sql_agr_func']['sum_dlugosc']['func'] = 'sum';
-				$layerSubCostsConf['sql_agr_func']['sum_dlugosc']['field'] = 'Dlugosc';
-				$layerSubCostsConf['labels'] = array();
-				$layerSubCostsConf['labels']['cost_type'] = "rodzaj kosztu";
-				$layerSubCostsConf['labels']['sum_dlugosc'] = "suma długości";
-				$layerConf['sub_costs'] = $layerSubCostsConf;
-			}
-			$_schema['sub_costs']['Rozdzielcza_Kabel_Swiatlowodowy_wsg84'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Studnie - TODO (Lokalizacje)";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$_schema['sub_costs']['__STUDNIE__'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Rury osłonowe - TODO";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$_schema['sub_costs']['Rura_oslonowa_rozdzielcza_magistralna'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Pakiet mikrorurek - TODO";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$_schema['sub_costs']['__PAKIET_MIKRORUREK__'] = $layerConf;
-		}
-		{
-			$layerConf = array();
-			$layerConf['label'] = "Koszty dodatkowe - TODO";
-			$layerConf['_agr_fields_to_cols'] = array();
-			$_schema['sub_costs']['__KOSZTY_DODATKOWE__'] = $layerConf;
-		}
-		return $_schema;
-	}
-
 	public function getData($idProject, $admin = 0) {
 		static $_data = null;
-		if (!is_array($_data)) $_data = array();
-		if (!array_key_exists($idProject, $_data)) $_data[$idProject] = $this->fetchData($idProject, $admin);
-		return $_data[$idProject];
+		if (null === $_data) {
+			$idSubProject = $this->getModel($idProject)->getSubProjectIds();
+			if (!empty($idSubProject)) {
+				$idSubProject[] = $idProject;
+			}
+			$_data = $this->fetchData($idSubProject, $admin);
+		}
+		//if (!array_key_exists($idProject, $_data)) $_data[$idProject] = $this->fetchData($idProject, $admin);
+		//return $_data[$idProject];
+		return $_data;
 	}
 
 	public function fetchData($idProject, $admin = 0) {
 		$data = array();
-		$schema = $this->getSchema();
+		$data['summary'] = array();// [ $idType ] = ['type', 'jednostka', 'ilosc']
+		$data['rawDataByType'] = array();
+		$schema = ProjectKosztorysSchema::getSchema();// OK only 'config'
 		$conf = $schema['config'];
 		$pdo = DB::getPDO();
 		foreach ($conf['layer'] as $idLayer => $layer) {
-			$layData = array();
-			$layData['label'] = $layer['label'];
-			{
-				if (!$layer['tabela_name']) continue;// TODO: throw error in validate
-				$tblName = $layer['tabela_name'];
-				$sqlIloscField = (!empty($layer['ilosc_field']))? $layer['ilosc_field'] : 'ID';
-				$sqlIlosc = ('SZTUKA' == $layer['jednostka'])? "count(1)" : "sum(t.{$sqlIloscField})";
-				$sqlZasobField = (!empty($layer['zasob_field']))? $layer['zasob_field'] : 'ID';
-				$sqlGroupBy = (!empty($layer['zasob_field']))? "group by t.{$sqlZasobField}" : '';
-				$sth = $pdo->prepare("
-					select t.{$sqlZasobField}
+			if (!$layer['tabela_name']) continue;// TODO: throw error in validate
+			if ('ZASOB' == $layer['zasob_type']) {
+			} else if ('ZASOB_ID' == $layer['zasob_type']) {
+			} else {
+				continue;// TODO: validate config error
+			}
+
+			$tblName = $layer['tabela_name'];
+			$sqlIloscField = (!empty($layer['ilosc_field']))? $layer['ilosc_field'] : 'ID';
+			$sqlIlosc = ('SZTUKA' == $layer['jednostka'])? "count(1)" : "sum(t.{$sqlIloscField})";
+			$sqlZasobField = (!empty($layer['zasob_field']))? $layer['zasob_field'] : 'ID';
+			$sqlGroupBy = "group by t.ID_PROJECT" . ((!empty($layer['zasob_field']))? ", t.{$sqlZasobField}" : '');
+			$sqlIdProject = (is_array($idProject))? implode(",", $idProject) : (int)$idProject;
+			$sql = "
+				select t.{$sqlZasobField}
+						, t.ID_PROJECT
 						, {$sqlIlosc} as ilosc
-					from {$tblName} t
-					where t.ID_PROJECT = :id_project
-					{$sqlGroupBy}
-				");
-				$sth->bindValue(':id_project', $idProject, PDO::PARAM_INT);
-				$sth->execute();
-				$rawLayData = $sth->fetchAll();
-				$layData['_raw'] = $rawLayData;
-				$layData['_rawSql'] = "
-					select t.{$sqlZasobField}
-						, sum(t.{$sqlIloscField}) as ilosc
-					from {$tblName} t
-					where t.ID_PROJECT = {$idProject}
-					{$sqlGroupBy}
-				";
-				$iloscByType = array();
-				$hasIlosc = false;
-				foreach ($rawLayData as $d) {
-					if ($d['ilosc'] <= 0) continue;
-					$hasIlosc = true;
-					$idType = 0;
-					$iloscType = null;
-					DBG::_('DBG', '>1', "layer", $layer, __CLASS__, __FUNCTION__, __LINE__);
-					DBG::_('DBG', '>1', "conf", $conf, __CLASS__, __FUNCTION__, __LINE__);
-					if ('ZASOB' == $layer['zasob_type']) {
-						$iloscType = array();
-						$iloscType['type'] = trim($d[$sqlZasobField]);
-						$idType = array_search($iloscType['type'], $conf['type']);
-						$iloscType['idType'] = $idType;
-						$iloscType['jednostka'] = $layer['jednostka'];
-						$iloscType['ilosc'] = $d['ilosc'];
-					} else if ('ZASOB_ID' == $layer['zasob_type']) {
-						$iloscType = array();
-						$iloscType['type'] = $layer['zasob_label'];
-						$idType = (array_key_exists($layer['zasob_id'], $conf['type']))? $layer['zasob_id'] : null;
-						$iloscType['idType'] = $idType;
-						$iloscType['jednostka'] = $layer['jednostka'];
-						$iloscType['ilosc'] = $d['ilosc'];
-					} else {
-						// TODO: BUG
-					}
-					if (!empty($iloscType) && $idType) {
-						if (!array_key_exists($idType, $iloscByType)) {
-							$iloscByType[$idType] = $iloscType;
-						} else {
-							DBG::_('DBG', '>1', "merge stats {$idType}", array($iloscByType[$idType], $iloscType), __CLASS__, __FUNCTION__, __LINE__);
-							$iloscByType[$idType]['ilosc'] += $iloscType['ilosc'];
-						}
-					} else {
-						DBG::_('DBG', '>1', "skip stats", $d, __CLASS__, __FUNCTION__, __LINE__);
-						if ($admin) SE_Layout::alert('warning', "Pomijanie nieznanego oznaczenia '{$iloscType['type']}' w ilości {$iloscType['ilosc']} - warstwa '{$layer['label']}' ({$layer['tabela_name']})");
-					}
+				from {$tblName} t
+				where t.ID_PROJECT in({$sqlIdProject})
+				{$sqlGroupBy}
+			";
+			$data['_DBG_sql'][$idLayer] = $sql;
+			$rawLayData = $pdo->fetchAll($sql);
+			$rawLayData = array_filter($rawLayData, function($row) {
+				return ($row['ilosc'] > 0);
+			});
+			foreach ($rawLayData as $idx => &$row) {
+				if ('ZASOB' == $layer['zasob_type']) {
+					$row['type'] = trim($row[$sqlZasobField]);
+					$row['idType'] = array_search($row['type'], $conf['type']);
+				} else if ('ZASOB_ID' == $layer['zasob_type']) {
+					$row['type'] = $layer['zasob_label'];
+					$row['idType'] = (array_key_exists($layer['zasob_id'], $conf['type']))? $layer['zasob_id'] : null;
 				}
-				if ($hasIlosc) $layData['data'] = $iloscByType;
+				$row['idLayer'] = $idLayer;
+				unset($row[$sqlZasobField]);
+				$data['rawDataByType'][] = $row;
 			}
-			if (!empty($layData['data'])) $data[$idLayer] = $layData;
 		}
-		return $data;
-	}
-
-	public function _fetchKosztorysData($idProject) {
-		$project = array();
-		$schema = $this->getSchema();
-
-		if(0){// TEST join by ogc
-$exampleOgcJoin = <<<OGC_JOIN
-<wfs:Query typeNames="p5_default_db:IN7_MK_BAZA_DYSTRYBUCJI p5_default_db:ADMIN_USERS" aliases="p u">
-	<fes:Filter>
-		 <fes:And>
-				<fes:PropertyIsEqualTo>
-					 <fes:ValueReference>p/ID</fes:ValueReference>
-					 <fes:Literal>{$idProject}</fes:Literal>
-				</fes:PropertyIsEqualTo>
-				<fes:PropertyIsEqualTo>
-					 <fes:ValueReference>u/ADM_ACCOUNT<fes:ValueReference>
-					 <fes:ValueReference>p/L_APPOITMENT_USER</fes:ValueReference>
-				</fes:PropertyIsEqualTo>
-		 </fes:And>
-	</fes:Filter>
-</wfs:Query>
-OGC_JOIN;
-		}
-
-		$pdo = DB::getPDO();
-		{
-			$sth = $pdo->prepare("
-				select p.ID, p.M_DIST_DESC, p.L_APPOITMENT_USER
-					, p.koszt_wspolny
-					, p.koszt_na_budynek
-					, p.koszt_na_mieszkanie
-					, p.Agr_Rozdzielcza_Wykop_przedmiar_na_mikrorurki_ilosc
-					, p.Agr_Rozdzielcza_Wykop_przedmiar_na_mikrorurki_cena
-					, p.Agr_Rozdzielcza_Wykop_przedmiar_na_mikrorurki_koszt
-					, p.Agr_Rozdzielcza_Mikrokanalizacja_do_klienta_ilosc
-					, p.Agr_Rozdzielcza_Mikrokanalizacja_do_klienta_cena
-					, p.Agr_Rozdzielcza_Mikrokanalizacja_do_klienta_koszt
-					, p.Agr_metrow_mikrorurek_5szt
-					, p.Agr_metrow_mikrorurek_5szt_cena
-					, p.Agr_Rozdzielcza_Przeciski_110mm_ilosc
-					, p.Agr_Rozdzielcza_Przeciski_110mm_cena
-					, p.Agr_Rozdzielcza_Przeciski_110mm_koszt
-					, p.Agr_Rozdzielcza_Zabruki_ilosc
-					, p.Agr_Rozdzielcza_Zabruki_cena
-					, p.Agr_Rozdzielcza_Zabruki_koszt
-					, p.Agr_Rozdzielcza_wezly_ilosc
-					, p.Agr_Rozdzielcza_wezly_cena
-					, p.Agr_Rozdzielcza_wezly_koszt
-					, p.Agr_Rozdzielcza_koszty_dodatkowe_wsg84
-					, p.Agr_Rozdzielcza_rurociag_wsg84_ilosc
-					, p.Agr_Rozdzielcza_rurociag_wsg84_cena
-					, p.Agr_Rozdzielcza_rurociag_wsg84_koszt
-					, p.Agr_Rozdzielcza_Kabel_Swiatlowodowy_wsg84_ilosc
-					, p.Agr_Rozdzielcza_Kabel_Swiatlowodowy_wsg84_cena
-					, p.Agr_Rozdzielcza_Kabel_Swiatlowodowy_wsg84_koszt
-					, p.Agr_USERS2_MARKETING_ilosc
-					, p.Agr_USERS2_MARKETING_cena
-					, p.Agr_USERS2_MARKETING_koszt
-					, p.Agr_BUILDINGS_ilosc
-				from IN7_MK_BAZA_DYSTRYBUCJI p
-				where p.ID = :ID_PROJECT
-				-- TODO: check perms!
-			");
-			$sth->bindValue('ID_PROJECT', $idProject, PDO::PARAM_INT);
-			$sth->execute();
-			$projectList = $sth->fetchAll();
-			if (empty($projectList)) throw new Exception("404 - Project Not Found");
-			$projectRaw = reset($projectList);
-		}
-		{
-			$project['nr'] = $projectRaw['ID'];
-			$project['title'] = $projectRaw['M_DIST_DESC'];
-			$project['owner_name'] = $this->fetchUserName($projectRaw['L_APPOITMENT_USER']);
-			$project['cost_total'] = $projectRaw['koszt_wspolny'];
-			foreach ($schema['sub_costs'] as $layerName => $layerConf) {
-				$values = array();
-				foreach ($layerConf['_agr_fields_to_cols'] as $fldName => $label) {
-					$values[$fldName] = V::get("Agr_{$layerName}_{$fldName}", '', $projectRaw);
-				}
-				$project['sub_costs'][$layerName] = $values;
-				if (!empty($layerConf['sub_costs'])) {
-					$sub_costs = array();
-					{
-						$groupByField = $layerConf['sub_costs']['group_by_field'];
-						$sqlFields = array();
-						foreach ($layerConf['sub_costs']['sql_agr_func'] as $fldName => $funcConf) {
-							$sqlFuncName = $funcConf['func'];
-							$sqlFuncField = $funcConf['field'];
-							$sqlFields[] = "{$sqlFuncName}(l.{$sqlFuncField}) as {$fldName}";
-						}
-						$sqlFields = implode(", ", $sqlFields);
-						$sth = $pdo->prepare("
-							select l.{$groupByField} as cost_type
-								, $sqlFields
-							from Rozdzielcza_Kabel_Swiatlowodowy_wsg84 l
-							where l.ID_PROJECT = :ID_PROJECT
-							group by l.{$groupByField}
-						");
-						$sth->bindValue('ID_PROJECT', $idProject, PDO::PARAM_INT);
-						$sth->execute();
-						$sub_costs = $sth->fetchAll();
-					}
-					$project['sub_costs'][$layerName]['sub_costs'] = $sub_costs;
+		//DBG::_(true, true, "data", $data, __CLASS__, __FUNCTION__, __LINE__);
+		//DBG::table("data['rawDataByType']", $data['rawDataByType'], __CLASS__, __FUNCTION__, __LINE__);
+		if ($admin) {
+			foreach ($data['rawDataByType'] as $row) {
+				$layer = $conf['layer'][$row['idLayer']];
+				if ($row['idType'] <= 0) {
+					SE_Layout::alert('warning', "Pomijanie nieznanego oznaczenia '{$row['type']}' w ilości {$row['ilosc']} - warstwa '{$layer['label']}' ({$layer['tabela_name']}), projekt nr {$row['ID_PROJECT']}");
 				}
 			}
 		}
-		$project['_raw'] = $projectRaw;
-
-		return $project;
-	}
+		$data['rawDataByType'] = array_filter($data['rawDataByType'], function($row) {
+			return ($row['idType'] > 0);
+		});
+		//DBG::table("data['rawDataByType'] - clean", $data['rawDataByType'], __CLASS__, __FUNCTION__, __LINE__);
+		$data['by_layer'] = array_reduce($data['rawDataByType'], function($result, $row) {
+			$idLayer = $row['idLayer'];
+			$idType = $row['idType'];
+			if (!array_key_exists($idLayer, $result)) $result[$idLayer] = array();
+			if (!array_key_exists($idType, $result[$idLayer])) {
+				$result[$idLayer][$idType] = $row;
+			} else {
+				$result[$idLayer][$idType]['ilosc'] += $row['ilosc'];
+			}
+			return $result;
+		}, array());
+		//DBG::_(true, true, "data['by_layer']", $data['by_layer'], __CLASS__, __FUNCTION__, __LINE__);
+		$data['summary'] = array();
+		foreach ($data['by_layer'] as $idLayer => $iloscByType) {
+			$layData = array();
+			$layData['label'] = $conf['layer'][$idLayer]['label'];
+			$layData['data'] = $iloscByType;
+			foreach ($layData['data'] as &$iloscData) {
+				$iloscData['jednostka'] = $conf['layer'][$idLayer]['jednostka'];
+			}
+			$data['summary'][$idLayer] = $layData;
+		}
+		//DBG::_(true, true, "data['summary']", $data['summary'], __CLASS__, __FUNCTION__, __LINE__);
 
-	public function fetchUserName($userLogin) {
-		$userName = $userLogin;
-		// TODO: sql from ADMIN_USERS
-		return $userName;
+		$data['by_project'] = array_reduce($data['rawDataByType'], function($result, $row) {
+			$idProj = $row['ID_PROJECT'];
+			$idLayer = $row['idLayer'];
+			$idType = $row['idType'];
+			if (!array_key_exists($idProj, $result)) $result[$idProj] = array();
+			if (!array_key_exists($idLayer, $result[$idProj])) $result[$idProj][$idLayer] = array();
+			if (!array_key_exists($idType, $result[$idProj][$idLayer])) {
+				$result[$idProj][$idLayer][$idType] = $row;
+			} else {
+				$result[$idProj][$idLayer][$idType]['ilosc'] += $row['ilosc'];
+			}
+			return $result;
+		}, array());
+		//DBG::_(true, true, "data['by_project']", $data['by_project'], __CLASS__, __FUNCTION__, __LINE__);
+		return $data;
 	}
 
 }