Kaynağa Gözat

Merge branch 'master' into fix-acl

Piotr Labudda 10 yıl önce
ebeveyn
işleme
47068f8504

+ 9 - 4
SE/index-ajax.php

@@ -60,15 +60,13 @@ if ($cls == 'UserBookmarks') {
 		}
 		$userBookmarks->addBookmark($zasobID);
 		User::saveProfile();
-	}
-	else 	if ($task == 'remove_bookmark') {
+	} else if ($task == 'remove_bookmark') {
 		if ($zasobID <= 0) {
 			die('Error: no resource');
 		}
 		$userBookmarks->removeBookmark($zasobID);
 		User::saveProfile();
-	}
-	else 	if ($task == 'change_bookmark') {
+	} else if ($task == 'change_bookmark') {
 		if ($zasobID <= 0) {
 			die('Error: no resource');
 		}
@@ -78,6 +76,13 @@ if ($cls == 'UserBookmarks') {
 		}
 		$userBookmarks->changeBookmark($zasobID, $btnCls);
 		User::saveProfile();
+	} else if ($task == 'sort_bookmarks') {
+		$idsOrdered = V::get('ids', array(), $_REQUEST, 'array', array('V', 'filterPositiveInteger'));
+		if (empty($idsOrdered)) {
+			die('Error: no ids');
+		}
+		$userBookmarks->sortBookmarks($idsOrdered);
+		User::saveProfile();
 	}
 
 	// default - always return bookmarks

+ 13 - 0
SE/se-lib/ProcesMenu.php

@@ -886,6 +886,10 @@ jQuery(document).ready(function() {
 
 			if (priv.options.urlInit) priv.update();
 			if (priv.options.preloadData) priv.setData(priv.options.preloadData);
+
+			_cont.sortable();
+			_cont.on('sortupdate', priv.sort);
+
 		};
 
 		priv.setData = function(data) {
@@ -986,6 +990,15 @@ jQuery(document).ready(function() {
 			return false;
 		};
 
+		priv.sort = function(e, ui) {
+			var idsOrder = [];
+			_cont.find('a').each(function(ind, n){
+				idsOrder.push($(n).data('id'));
+			});
+			priv.update('sort_bookmarks', 0, '&ids[]=' + idsOrder.join('&ids[]='));
+			return true;
+		};
+
 		priv.addEditBtns = function(el) {
 			var next, btn;
 			el.wrap('<div></div>');

+ 363 - 114
SE/se-lib/Route/Budget.php

@@ -4,6 +4,11 @@ Lib::loadClass('RouteBase');
 
 class Route_Budget extends RouteBase {
 
+	private $_costs = array();
+	private $_plan = array();
+	private $_projectInfo = array();
+	private $_projectPathsOrder = array();
+
 	public function handleAuth() {
 		if (!User::logged()) {
 			throw new HttpException('Unauthorized', 401);
@@ -26,12 +31,20 @@ class Route_Budget extends RouteBase {
 	public function yearBudgetAction() {
 		$args = array();
 		$args['year'] = V::get('year', '', $_REQUEST, 'int');
+		$args['groups'] = V::get('fltrGroups', array(), $_REQUEST, 'array', array('V', 'filterPositiveInteger'));
 		$args['_print'] = V::get('_print', '', $_REQUEST, 'int');
 
+		$hasData = false;
+		$groups = null;
+		if ($args['year'] > 0) {
+			$hasData = $this->fetchDataByYear($args['year']);
+			$groups = $this->getUsedUserGroups();
+		}
+
 		SE_Layout::gora();
 		SE_Layout::menu();
 		if (!$args['_print']) {
-			$this->menu($args['year']);
+			$this->menu($args['year'], $groups, $args['groups']);
 		}
 
 		if (empty($args['year'])) {
@@ -44,8 +57,7 @@ class Route_Budget extends RouteBase {
 			exit;
 		}
 
-		$costs = $this->getCostsByYear($args['year']);
-		if (empty($costs)) {
+		if (!$hasData) {
 			?>
 			<div class="alert alert-warning">
 				Brak danych na wybrany rok.
@@ -55,12 +67,12 @@ class Route_Budget extends RouteBase {
 		}
 		//echo'<pre style="border:1px solid red;overflow:auto;max-height:400px">$costs: ';print_r($costs);echo'</pre>';
 
-		$this->printCostsForYear($args['year']);
+		$this->printCostsForYear($args['year'], $args['groups']);
 
 		SE_Layout::dol();
 	}
 
-	private function menu($selectedYear) {
+	private function menu($selectedYear, $groups = array(), $selectedGroups = array()) {
 		//SE_Layout::menu();
 		$year = ($selectedYear)? $selectedYear : date("Y");
 		?>
@@ -68,11 +80,25 @@ class Route_Budget extends RouteBase {
   <div class="container">
 		<form class="form-inline" method="POST">
 			<input type="hidden" name="_task" value="yearBudget" />
-			<label for="year">Zestawienie kosztów projektów na podstawie korespondencji:</label>
+			<label for="year">Zestawienie kosztów projektów. Wybierz rok:</label>
 			<div class="input-group date" id="fldZestYear">
 				<input type="text" name="year" class="form-control" value="" />
 				<span class="input-group-addon"><span class="glyphicon glyphicon-time"></span></span>
 			</div>
+			<?php if (!empty($groups)) : ?>
+			<div style="margin:8px 0">
+				<label for="fltrGroups">Pokaż tylko projekty dostępne dla grup:</label>
+				<select multiple name="fltrGroups[]" size="<?php echo min(5, count($groups)); ?>" class="form-control">
+					<option value=""> [ Wszystkie ] </option>
+					<?php foreach ($groups as $idGroup => $groupLdapName) : ?>
+						<option
+										value="<?php echo $idGroup; ?>"
+										<?php if (in_array($idGroup, $selectedGroups)) { echo 'selected="selected"'; } ?>
+										><?php echo $groupLdapName; ?></option>
+					<?php endforeach; ?>
+				</select>
+			</div>
+			<?php endif; ?>
 			<button type="submit" id="fldZestYearBtn" class="btn btn-primary" autocomplete="off">
 				Pokaż
 			</button>
@@ -103,20 +129,26 @@ jQuery(document).ready(function () {
 		.c { text-align:center; }
 		.r { text-align:right; }
 
-		.zestawienie-kosztow-tbl { border-collapse:collapse; border:1px solid #7EC5FF; }
-		.zestawienie-kosztow-tbl td { border:1px solid #7EC5FF; }
+		.zestawienie-kosztow-tbl { border-collapse:collapse; border:1px solid #aaa; }
+		.zestawienie-kosztow-tbl td { border:1px solid #aaa; }
 		.zestawienie-kosztow-tbl .p2 { padding:0 2px; }
 		.zestawienie-kosztow-tbl .nr { color:#7A7A7A; }
-		.zestawienie-kosztow-tbl thead th { border:1px solid #7EC5FF; }
+		.zestawienie-kosztow-tbl thead th { border:1px solid #aaa; }
 		.zestawienie-kosztow-tbl tbody tr:hover td { background:#cafbfd; }
 		.row-selected td {background-color:#d8fded;}
 		.showOnlySelected tr { display:none; }
 		.showOnlySelected tr.row-selected { display:table-row; }
 
 		.cell-cost { padding:0 2px; min-width:30px; text-align:right; }
-		.cell-cost-only_child { color:#777; }
-		.cell-cost-only_self { color:red; }
-		.cell-cost-self_and_child { color:orange; }
+		.cell-cost-only_self      { color:#197fe6; }
+		.cell-cost-self_and_child { color:#33b2cc; }
+		.cell-cost-only_child     { color:#59a680; }
+		.cell-plan { padding:0 2px; min-width:30px; text-align:right; color:#777; }
+		.cell-procent { padding:0 2px; min-width:20px; text-align:right; color:#777; }
+		.cell-procent-below100 { color:#777; }
+		.cell-procent-100 { color:#777; }
+		.cell-procent-over100 { color:#ff9b00; }
+		.cell-procent-over200 { color:#f00; }
 
 		/* print table background colors */
 		table td, table th { -webkit-print-color-adjust:exact; }
@@ -129,8 +161,7 @@ jQuery(document).ready(function () {
 		<?php
 	}
 
-	function printCostsForYear($year) {
-		$this->_costs = $this->_costs;
+	function printCostsForYear($year, $groups) {
 		$months = array();
 		for ($i = 0; $i < 12; $i++) {
 			$months[] = $i + 1;
@@ -141,72 +172,125 @@ jQuery(document).ready(function () {
 <div class="container">
 	<div style="float:right;color:#aaa;"><?php echo date("Y-m-d"); ?></div>
 	<h1>Zestawienie kosztów projektów na rok <?php echo $year; ?></h1>
+</div>
 	<table cellspacing="0" cellpadding="0" border="0" id="zestawienie-kosztow-projektow" class="zestawienie-kosztow-tbl">
 	<thead>
+		<tr>
+			<td colspan="3" class="p2">
+				<span class="pull-right"><b>miesiąc</b></span>
+			</td>
+			<?php foreach ($months as $month) { ?>
+				<th class="c" colspan="3"><?php echo sprintf("%02d", $month); ?></th>
+			<?php } ?>
+		</tr>
 		<tr>
 			<td colspan="3" class="p2">
 				<span class="pull-left">
 					<input type="checkbox" onclick="return showHideAll(this);"/> pokaż tylko zaznaczone
 				</span>
-				<span class="pull-right"><b>miesiąc</b></span>
 			</td>
 			<?php foreach ($months as $month) { ?>
-				<th class="c"><?php echo sprintf("%02d", $month); ?></th>
+				<th class="c" title="Koszty wprowadzone do korespondencji">Koszty</th>
+				<th class="c" title="Plan budżetu">Plan</th>
+				<th class="c" title="Procent przekroczenia planu">%</th>
 			<?php } ?>
 		</tr>
 	</thead>
 	<tbody>
 	<?php $t = 1; ?>
 	<?php foreach ($this->_projectPathsOrder as $projPath => $projId) : ?>
-		<?php $projectInfo = $this->_costs[$projId]; ?>
-		<?php if (empty($projectInfo)) {
-			//echo'<tr><td colspan="10"><pre>ERROR:EMPTY: Path('.$projPath.') id('.$projId.')</pre></td></tr>';
-			continue;
-		} ?>
+		<?php
+			$projectID   = $projId;
+			$projectDesc = $this->_projectInfo[$projId]->M_DIST_DESC;
+			$projectPath = $this->_projectInfo[$projId]->path;
+			$projectAccess = $this->hasAccessToProject($projectID);
+			if (!empty($groups)) {
+				if (!$projectAccess) {
+					//echo '<pre>TODO: filtered by acl for project';print_r($this->_projectInfo[$projId]);echo'</pre>';
+					continue;
+				}
+				if (!$this->hasGroupsAccessToProjects($projectID, $groups)) {
+					//echo '<pre>TODO: filtered by acl and groups';print_r($this->_projectInfo[$projId]);echo'</pre>';
+					continue;
+				}
+			}
+		?>
 		<tr class="row-<?php echo ($t = 1 - $t); ?>"
-				data-proj_id="<?php echo $projectInfo->ID_PROJECT; ?>"
-				data-path="<?php echo $projectInfo->path; ?>">
+				data-proj_id="<?php echo $projectID; ?>"
+				data-path="<?php echo $projectPath; ?>">
 			<td class="p2 r nr">
-				<input type="checkbox" name="selectedProject" onclick="return selectProject(this);" value="<?php echo $projectInfo->ID_PROJECT; ?>" />
+				<input type="checkbox" name="selectedProject" onclick="return selectProject(this);" value="<?php echo $projectID; ?>" />
 			</td>
-			<td class="p2 l nr"><?php echo $projectInfo->path; ?></td>
-			<td class="p2" style="max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="<?php echo $projectInfo->M_DIST_DESC; ?>"><?php echo $projectInfo->M_DIST_DESC; ?></td>
+			<td class="p2 l nr"><nobr><?php echo $projectPath; ?></nobr></td>
+				<?php if (!$projectAccess) : ?>
+			<td class="p2">***</td>
+				<?php else : ?>
+			<td class="p2" style="max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="<?php echo $projectDesc; ?>"><?php echo $projectDesc; ?></td>
+				<?php endif; ?>
 			<?php foreach ($months as $month) : ?>
-				<?php if (array_key_exists($month, $projectInfo->costsByMonth)) : ?>
-					<?php $vCost = V::get($month, '', $projectInfo->costsByMonth); ?>
-					<?php $vCostChildOut = number_format($vCost->COST_CHILD, 2); ?>
-					<?php $vCostSelfOut = number_format($vCost->COST_SELF, 2); ?>
-					<?php $vCostTotalOut = number_format($vCost->COST_TOTAL, 2); ?>
-					<?php if ($vCost->COST_CHILD > 0) : ?>
-						<?php  $title = "Koszt projektu {$vCostSelfOut} / koszt podprojektów $vCostChildOut"; ?>
-						<?php if ($vCost->COST_SELF > 0) : ?>
-			<td class="cell-cost cell-cost-self_and_child"
-					data-month_num="<?php echo $month; ?>"
-					data-cost="<?php echo $vCostTotalOut; ?>"
-					data-proj_path="<?php echo $projectInfo->path; ?>"
-					data-proj_id="<?php echo $projectInfo->ID_PROJECT; ?>">
-				<span class="ttip" title="<?php echo $title; ?>"><?php echo $vCostTotalOut; ?></span>
-			</td>
-						<?php else : ?>
-			<td class="cell-cost cell-cost-only_child"
+				<?php $vMonthCost = $this->getCost($projectID, $month); ?>
+				<?php $monthCostTotal = ($vMonthCost)? $vMonthCost->COST_TOTAL : 0; ?>
+				<?php if (!$projectAccess) : ?>
+			<td style="min-width:30px;text-align:right;">***</td>
+				<?php elseif (!$vMonthCost) : ?>
+			<td style="min-width:30px">&nbsp;</td>
+				<?php else : ?>
+					<?php
+						$vCostChildOut = number_format($vMonthCost->COST_CHILD, 2);
+						$vCostSelfOut = number_format($vMonthCost->COST_SELF, 2);
+						$vCostTotalOut = number_format($vMonthCost->COST_TOTAL, 2);
+						$title = "Koszt projektu {$vCostSelfOut} / koszt podprojektów $vCostChildOut";
+						$cellCostCls = '';
+						if ($vMonthCost->COST_CHILD > 0) {
+							if ($vMonthCost->COST_SELF > 0) {
+								$cellCostCls = 'cell-cost-self_and_child';
+							} else {
+								$cellCostCls = 'cell-cost-only_child';
+							}
+						} else {
+							$cellCostCls = 'cell-cost-only_self';
+						}
+					?>
+			<td class="cell-cost <?php echo $cellCostCls; ?>"
 					data-month_num="<?php echo $month; ?>"
 					data-cost="<?php echo $vCostTotalOut; ?>"
-					data-proj_path="<?php echo $projectInfo->path; ?>"
-					data-proj_id="<?php echo $projectInfo->ID_PROJECT; ?>">
+					data-proj_path="<?php echo $projectPath; ?>"
+					data-proj_id="<?php echo $projectID; ?>">
 				<span class="ttip" title="<?php echo $title; ?>"><?php echo $vCostTotalOut; ?></span>
 			</td>
-						<?php endif; ?>
-					<?php else : ?>
-			<td class="cell-cost cell-cost-only_self"
-					data-month_num="<?php echo $month; ?>"
-					data-cost="<?php echo $vCostTotalOut; ?>"
-					data-proj_path="<?php echo $projectInfo->path; ?>"
-					data-proj_id="<?php echo $projectInfo->ID_PROJECT; ?>">
-				<?php echo $vCostTotalOut; ?>
+				<?php endif; ?>
+			<td class="cell-plan">
+				<?php $monthPlan = $this->getPlan($projectID, $month); ?>
+				<?php $monthPlanOut = number_format($monthPlan, 2); ?>
+				<?php if (!$projectAccess) : ?>
+					***
+				<?php elseif ($monthPlan > 0) : ?>
+					<?php echo $monthPlan; ?>
+				<?php else : ?>
+					&nbsp;
+				<?php endif; ?>
 			</td>
-					<?php endif; ?>
+			<?php
+				$cellProcentCls = '';
+				$procentOut = '&nbsp;';
+				$monthPlan = $this->getPlan($projectID, $month);
+				if ($monthPlan > 0) {
+					$procentOut = round(($monthCostTotal * 100) / $monthPlan);
+					if ($procentOut > 200) {
+						$cellProcentCls = 'cell-procent-over200';
+					} else if ($procentOut > 100) {
+						$cellProcentCls = 'cell-procent-over100';
+					} else if ($procentOut == 100) {
+						$cellProcentCls = 'cell-procent-100';
+					} else {
+						$cellProcentCls = 'cell-procent-below100';
+					}
+				}
+			?>
+				<?php if (!$projectAccess) : ?>
+			<td style="min-width:30px;text-align:right;">***</td>
 				<?php else : ?>
-					<td style="min-width:30px">&nbsp;</td>
+			<td class="cell-procent <?php echo $cellProcentCls; ?>"><?php echo $procentOut; ?></td>
 				<?php endif; ?>
 			<?php endforeach; ?>
 		</tr>
@@ -281,18 +365,28 @@ jQuery(document).ready(function() {
 		</div>
 	</div>
 	<table id="proj-koresp-info" style="display:none">
-		<?php foreach ($this->_costs as $projId => $projectInfo) : ?>
+		<?php foreach ($this->_projectPathsOrder as $projPath => $projId) : ?>
+			<?php
+				$projectInfo = $this->_costs[$projId];
+				if (!$projectInfo) {
+					continue;
+				}
+				$projectID   = $projId;
+				$projectDesc = $this->_projectInfo[$projId]->M_DIST_DESC;
+				$projectPath = $this->_projectInfo[$projId]->path;
+			?>
 			<?php if (!empty($projectInfo->korespByMonth)) : ?>
 				<?php foreach ($projectInfo->korespByMonth as $kMonth => $vKorespMonthList) : ?>
-					<tbody id="row-proj-<?php echo $projectInfo->ID_PROJECT; ?>-koresp-by-month-<?php echo $kMonth; ?>"
+					<tbody id="row-proj-<?php echo $projectID; ?>-koresp-by-month-<?php echo $kMonth; ?>"
 								 data-month_num="<?php echo $kMonth; ?>"
-								 data-proj_path="<?php echo $projectInfo->path; ?>"
-								 data-proj_id="<?php echo $projectInfo->ID_PROJECT; ?>">
+								 data-proj_path="<?php echo $projectPath; ?>"
+								 data-proj_id="<?php echo $projectID; ?>">
 						<tr>
-							<td style="padding:3px;font-size:1em;background:#eee;"><?php echo $projectInfo->path; ?></td>
-							<td colspan="3" style="padding:3px;font-size:1.2em;background:#eee;">
-								Koszty projektu nr <?php echo $projectInfo->ID_PROJECT; ?>
+							<td style="padding:3px;font-size:1em;background:#eee;"><nobr><?php echo $projectPath; ?></nobr></td>
+							<td colspan="3" style="padding:3px;font-size:1.1em;background:#eee;max-width:500px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="<?php echo $projectDesc; ?>">
+								<!-- Koszty projektu nr <?php echo $projectID; ?> -->
 								<!-- - miesiąc <?php echo $year; ?>-<?php echo sprintf("%02d", $kMonth); ?> -->
+								<?php echo $projectDesc; ?>
 							</td>
 						</tr>
 						<?php foreach ($vKorespMonthList as $kKorespIdx => $vKorespInfo) : ?>
@@ -306,7 +400,7 @@ jQuery(document).ready(function() {
 								*/ ?>
 							<tr>
 								<td class="p2 r nr"><?php echo $vKorespInfo->ID; ?></td>
-								<td class="p2" style="max-width:400px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="<?php echo $projectInfo->M_DIST_DESC; ?>"><?php echo $vKorespInfo->K_ZAWARTOS; ?></td>
+								<td class="p2" style="max-width:400px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="<?php echo $vKorespInfo->K_ZAWARTOS; ?>"><?php echo $vKorespInfo->K_ZAWARTOS; ?></td>
 								<?php $vKorespCostOut = number_format($vKorespInfo->COST, 2); ?>
 								<td class="cell-cost cell-cost-only_child"><?php echo $vKorespCostOut; ?></td>
 								<td>
@@ -322,7 +416,6 @@ jQuery(document).ready(function() {
 			<?php endif; ?>
 		<?php endforeach; ?>
 	</table>
-</div>
 <script>
 	jQuery(document).ready(function(){
 		jQuery('.ttip').tooltip();
@@ -367,51 +460,70 @@ jQuery(document).ready(function() {
 <?php
 	}
 
-	public function getCostsByYear__OLD($year) {
+	public function getCost($idProject, $month) {
+		if (!array_key_exists($idProject, $this->_costs)) {
+			return null;
+		}
+		if (!array_key_exists($month, $this->_costs[$idProject]->costsByMonth)) {
+			return null;
+		}
+		return $this->_costs[$idProject]->costsByMonth[$month];
+	}
+
+	public function getPlan($idProject, $month) {
+		if (!array_key_exists($idProject, $this->_plan)) {
+			return 0;
+		}
+		if (!array_key_exists($month, $this->_plan[$idProject])) {
+			return 0;
+		}
+		return $this->_plan[$idProject][$month];
+	}
+
+	public function fetchDataByYear($year) {
+		$this->_fetchCostsByYear($year);
+		$this->_fetchPlanByYear($year);
+		$this->_fetchProjectInfo();
+		$this->_buildProjectTree();
+		$this->_reacountCostsFromKoresp();
+		return count($this->_projectInfo) > 1;// $this->_projectInfo[0] - Wszystkie projekty
+	}
+
+	public function _fetchPlanByYear($year) {
 		$db = DB::getDB();
-		$costs = array();
+		$this->_plan = array();
 		$sql = "
-			select
-				t.`ID_PROJECT`
-				, t.`M_DIST_DESC`
-				, t.`P_ID`
-				, t.`path`
-				, t.`MONTH`
-				, t.`COST_SELF`
-				, t.`COST_CHILD`
-				, t.`COST_TOTAL`
-				, t.`INCOME_SELF`
-				, t.`INCOME_CHILD`
-				, t.`INCOME_TOTAL`
-			from `test_budget_project_synthetics_view` as t
-			where 1=1
-				and t.`MONTH` like '{$year}-%'
+			select plan.`ID`
+				, plan.`ID_PROJECT` AS `ID_PROJECT`
+				, plan.`MONTH_1_VALUE`
+				, plan.`MONTH_2_VALUE`
+				, plan.`MONTH_3_VALUE`
+				, plan.`MONTH_4_VALUE`
+				, plan.`MONTH_5_VALUE`
+				, plan.`MONTH_6_VALUE`
+				, plan.`MONTH_7_VALUE`
+				, plan.`MONTH_8_VALUE`
+				, plan.`MONTH_9_VALUE`
+				, plan.`MONTH_10_VALUE`
+				, plan.`MONTH_11_VALUE`
+				, plan.`MONTH_12_VALUE`
+			from `projects_budget_year_month` plan
+			where plan.`year`='{$year}'
+			-- TODO: acl
 		";
+		//echo'<pre style="border:1px solid red;overflow:auto;max-height:400px">';print_r($sql);echo'</pre>';
 		$res = $db->query($sql);
 		while ($r = $db->fetch($res)) {
-			if (!array_key_exists($r->ID_PROJECT, $costs)) {
-				$projectInfo = new stdClass();
-				$projectInfo->ID_PROJECT = $r->ID_PROJECT;
-				$projectInfo->M_DIST_DESC = $r->M_DIST_DESC;
-				$projectInfo->path = $r->path;
-				$projectInfo->costsByMonth = array();
-				$costs[$r->ID_PROJECT] = $projectInfo;
+			$plan = array();
+			for ($i = 1; $i <= 12; $i++) {
+				$plan[$i] = V::get("MONTH_{$i}_VALUE", 0, $r);
 			}
-			$cost = new stdClass();
-			$cost->MONTH = $r->MONTH;
-			$cost->COST_SELF = $r->COST_SELF;
-			$cost->COST_CHILD = $r->COST_CHILD;
-			$cost->COST_TOTAL = $r->COST_TOTAL;
-			$cost->INCOME_SELF = $r->INCOME_SELF;
-			$cost->INCOME_CHILD = $r->INCOME_CHILD;
-			$cost->INCOME_TOTAL = $r->INCOME_TOTAL;
-			$monthNum = intval(substr($r->MONTH, 5, 2));
-			$costs[$r->ID_PROJECT]->costsByMonth[$monthNum] = $cost;
+			$this->_plan[$r->ID_PROJECT] = $plan;
 		}
-		return $costs;
+		return $this->_plan;
 	}
 
-	public function getCostsByYear($year) {
+	public function _fetchCostsByYear($year) {
 		$db = DB::getDB();
 		$this->_costs = array();
 		$sql = "
@@ -434,6 +546,7 @@ jQuery(document).ready(function() {
 			from `IN7_DZIENNIK_KORESP` k
 			where ((k.`COST_VALUE` > 0) or (k.`INCOME_VALUE` > 0))
 				and k.`K_DATA_OTRZYMANEJ_KORESP` like '{$year}-%'
+			-- TODO: acl
 		";
 		//echo'<pre style="border:1px solid red;overflow:auto;max-height:400px">';print_r($sql);echo'</pre>';
 		$res = $db->query($sql);
@@ -465,8 +578,6 @@ jQuery(document).ready(function() {
 			{
 				$projectZeroInfo = new stdClass();
 				$projectZeroInfo->ID_PROJECT = 0;
-				$projectZeroInfo->M_DIST_DESC = 'Wszystkie projekty';
-				$projectZeroInfo->path = '0';
 				$projectZeroInfo->costsByMonth = array();
 				$projectZeroInfo->korespByMonth = array();
 				$this->_costs[0] = $projectZeroInfo;
@@ -475,8 +586,6 @@ jQuery(document).ready(function() {
 				if (!array_key_exists($vProjId, $this->_costs)) {
 					$projectInfo = new stdClass();
 					$projectInfo->ID_PROJECT = $vProjId;
-					$projectInfo->M_DIST_DESC = '';
-					$projectInfo->path = '';
 					$projectInfo->costsByMonth = array();
 					$projectInfo->korespByMonth = array();
 					$this->_costs[$vProjId] = $projectInfo;
@@ -502,37 +611,110 @@ jQuery(document).ready(function() {
 				$this->_costs[0]->korespByMonth[$monthNum][] = $korespInfo;
 			}
 		}
-		$this->_fetchProjectInfo();
-		$this->_buildProjectTree();
-		$this->_reacountCostsFromKoresp();
 		return $this->_costs;
 	}
 
 	private function _fetchProjectInfo() {
 		$db = DB::getDB();
-		$projectIds = array_keys($this->_costs);
+		$hasAccessForAllProjects = true;
+		$projectIds = array();
+		$projectsFromCostIds = array_keys($this->_costs);
+		foreach ($projectsFromCostIds as $idProject) $projectIds[$idProject] = true;
+		$projectsFromPlanIds = array_keys($this->_plan);
+		foreach ($projectsFromPlanIds as $idProject) $projectIds[$idProject] = true;
+		foreach ($projectIds as $idProject => $vBool) $this->_projectInfo[$idProject] = new stdClass();
+		$projectIds = array_keys($projectIds);
 		$sqlProjIds = "'" . implode("','", $projectIds) . "'";
-		$sql = "select p.`ID`, p.`P_ID`, p.`path`, p.`M_DIST_DESC`
+		$sql = "
+			select p.`ID`
+				, p.`P_ID`
+				, p.`path`
+				, p.`M_DIST_DESC`
+				, p.`A_ADM_COMPANY` as aclGroupWrite
+				, p.`A_CLASSIFIED` as aclGroupRead
+				, p.`L_APPOITMENT_USER` as aclOwner
 			from `IN7_MK_BAZA_DYSTRYBUCJI` p
 			where p.`ID` in({$sqlProjIds})
 		";
 		$res = $db->query($sql);
 		while ($r = $db->fetch($res)) {
-			$this->_costs[$r->ID]->path = $r->path;
-			$this->_costs[$r->ID]->M_DIST_DESC = $r->M_DIST_DESC;
+			$this->_projectInfo[$r->ID]->path = $r->path;
+			$this->_projectInfo[$r->ID]->M_DIST_DESC = $r->M_DIST_DESC;
+			$this->_projectInfo[$r->ID]->aclGroupRead = $r->aclGroupRead;
+			$this->_projectInfo[$r->ID]->hasAccess = $this->_userHasAccessToProject($r);
+			if (!$this->_projectInfo[$r->ID]->hasAccess) $hasAccessForAllProjects = false;
+		}
+		$this->_projectInfo[0]->path = '0';
+		$this->_projectInfo[0]->M_DIST_DESC = "Wszystkie projekty";
+		$this->_projectInfo[0]->hasAccess = $hasAccessForAllProjects;
+	}
+
+	public function hasAccessToProject($idProject) {
+		if ($idProject >= 0) {
+			if (array_key_exists($idProject, $this->_projectInfo)) {
+				return V::get('hasAccess', false, $this->_projectInfo[$idProject]);
+			}
+		}
+		return false;
+	}
+
+	public function hasGroupsAccessToProjects($idProject, $groups) {
+		$selectedUserGroupNames = array();
+		$userGroups = User::getLdapGroupsNames();
+		foreach ($groups as $idGroup) {
+			$selectedUserGroupNames[$idGroup] = $userGroups[$idGroup];
+		}
+		if ($idProject >= 0) {
+			if (array_key_exists($idProject, $this->_projectInfo)) {
+				$alcGroupRead = V::get('aclGroupRead', null, $this->_projectInfo[$idProject]);
+				if (!$alcGroupRead) {
+					return false;
+				}
+				if (in_array($alcGroupRead, $selectedUserGroupNames)) {
+					return true;
+				}
+			}
 		}
+		return false;
+	}
+
+	private function _userHasAccessToProject($project) {
+		$groups = User::getLdapGroupsNames();
+		$userLogin = User::getLogin();
+		if ($project->aclOwner == $userLogin) {
+			return true;
+		}
+		else if (in_array($project->aclGroupRead, $groups)) {
+			return true;
+		}
+		return false;
+	}
+
+	public function getUsedUserGroups() {
+		$groups = array();
+		$userGroups = User::getLdapGroupsNames();
+		foreach ($this->_projectInfo as $projectInfo) {
+			if (!empty($projectInfo->aclGroupRead)) {
+				$groupKey = array_search($projectInfo->aclGroupRead, $userGroups);
+				if ($groupKey !== false) {
+					$groups[$groupKey] = $projectInfo->aclGroupRead;
+				}
+			}
+		}
+		return $groups;
 	}
 
 	private function _reacountCostsFromKoresp() {
 		$projMonthHasCostSelfIds = array();
 		foreach ($this->_costs as $kProjId => $vProjInfo) {
+			$projectPath = $this->_projectInfo[$kProjId]->path;
 			foreach ($vProjInfo->korespByMonth as $kMonthNum => $vKorespList) {
 				$this->_createCostIfNotDefined($kProjId, $kMonthNum);
 				foreach ($vKorespList as $vKoresp) {
 					$this->_costs[$kProjId]->costsByMonth[$kMonthNum]->COST_SELF += $vKoresp->COST;
 					$this->_costs[$kProjId]->costsByMonth[$kMonthNum]->INCOME_SELF += $vKoresp->INCOME;
 				}
-				$projHasCostSelfIds[$kProjId][$kMonthNum] = $vProjInfo->path;
+				$projHasCostSelfIds[$kProjId][$kMonthNum] = $projectPath;
 			}
 		}
 		//echo'<pre style="width:600px;border:1px solid red;max-height:300px;overflow:auto;">$projHasCostSelfIds: ';print_r($projHasCostSelfIds);echo'</pre>';
@@ -576,8 +758,8 @@ jQuery(document).ready(function() {
 
 	public function _buildProjectTree() {
 		$this->_projectPathsOrder = array();
-		foreach ($this->_costs as $idProject => $projectInfo) {
-			$this->_projectPathsOrder[$projectInfo->path] = $projectInfo->ID_PROJECT;
+		foreach ($this->_projectInfo as $idProject => $projectInfo) {
+			$this->_projectPathsOrder[$projectInfo->path] = $idProject;
 		}
 		//echo'<pre style="width:600px;border:1px solid red;max-height:300px;overflow:auto;">projPaths: ';print_r($this->_projectPathsOrder);echo'</pre>';
 		uksort($this->_projectPathsOrder, array($this, 'sortPathsCallback'));
@@ -601,4 +783,71 @@ jQuery(document).ready(function() {
 		return $la - $lb;
 	}
 
+	public function updatePaths() {
+		$sqlList = array();
+		$sqlList['updateAllPaths'] = <<<SQL
+update `projects_budget_year_month` b
+set path = (select coalesce(
+										(select p.`path` from `IN7_MK_BAZA_DYSTRYBUCJI` p where p.`ID`=b.`ID_PROJECT` limit 1)
+										, '?'));
+SQL;
+		$db = DB::getDB();
+		if ($db->has_errors()) {
+			throw new Exception("DB Errors: " . implode("\n<br>", $db->get_errors()));
+		}
+		foreach ($sqlList as $sqlName => $sql) {
+			$res = $db->query($sql);
+			if ($db->has_errors()) {
+				throw new Exception("DB Errors at sql '{$sqlName}': " . implode("\n<br>", $db->get_errors()));
+			}
+		}
+	}
+
+	public function reinstall() {
+		$sqlList = array();
+		$sqlList['RemoveTrigger_BudgetPlan_BeforeInsert'] = "DROP TRIGGER IF EXISTS `projects_budget_year_month_BEFORE_INSERT`";
+		$sqlList['CreateTrigger_BudgetPlan_BeforeInsert'] = "
+CREATE DEFINER=`root`@`localhost` TRIGGER `projects_budget_year_month_BEFORE_INSERT` BEFORE INSERT ON `projects_budget_year_month`
+FOR EACH ROW BEGIN
+	IF NEW.ID_PROJECT IS NOT NULL and NEW.ID_PROJECT>0 THEN
+		SET NEW.path = (select coalesce(
+			(select p.`path` from `IN7_MK_BAZA_DYSTRYBUCJI` p where p.`ID`=NEW.`ID_PROJECT` limit 1)
+			, '?'
+		));
+	END IF;
+END
+		";
+		$sqlList['RemoveTrigger_BudgetPlan_BeforeUpdate'] = "DROP TRIGGER IF EXISTS `projects_budget_year_month_BEFORE_UPDATE `";
+		// throws errors:
+		//  #1146 - Table '{DATABASE_NAME}.P5-MSG:Route_FixZasobPath:ERROR: Loop detected ID=PARENT_ID' doesn't exist
+		//  #1146 - Table '{DATABASE_NAME}.P5-MSG:Route_FixZasobPath:ERROR: Parent item not exists' doesn't exist
+		//  #1146 - Table '{DATABASE_NAME}.P5-MSG:Route_FixZasobPath:ERROR: Loop detected in path' doesn't exist
+		$sqlList['CreateTrigger_BudgetPlan_BeforeUpdate'] = "
+CREATE DEFINER=`root`@`localhost` TRIGGER `projects_budget_year_month_BEFORE_UPDATE` BEFORE UPDATE ON `projects_budget_year_month`
+FOR EACH ROW BEGIN
+	IF NEW.ID_PROJECT IS NULL THEN
+		SET NEW.path = '';
+	ELSEIF OLD.ID_PROJECT IS NULL or NEW.ID_PROJECT<>OLD.ID_PROJECT THEN
+		IF NEW.ID_PROJECT>0 THEN
+			SET NEW.path = (select coalesce(
+							(select p.`path` from `IN7_MK_BAZA_DYSTRYBUCJI` p where p.`ID`=NEW.`ID_PROJECT` limit 1)
+							, '?'));
+		ELSE
+			SET NEW.path = '';
+		END IF;
+	END IF;
+END
+		";
+		$db = DB::getDB();
+		if ($db->has_errors()) {
+			throw new Exception("DB Errors: " . implode("\n<br>", $db->get_errors()));
+		}
+		foreach ($sqlList as $sqlName => $sql) {
+			$res = $db->query($sql);
+			if ($db->has_errors()) {
+				throw new Exception("DB Errors at sql '{$sqlName}': " . implode("\n<br>", $db->get_errors()));
+			}
+		}
+	}
+
 }

+ 13 - 0
SE/se-lib/UserBookmarks.php

@@ -40,6 +40,19 @@ class UserBookmarks {
 		$_SESSION['USER_PROFILE'][$this->_sesKey][$zasobID] = $cls;
 	}
 
+	public function sortBookmarks($idsOrder) {
+		$bookmarks = $this->getBookmarks();
+		if (empty($bookmarks)) return;
+		if (empty($idsOrder)) return;
+		$sortedBookmarks = array();
+		foreach ($idsOrder as $id) {
+			if (array_key_exists($id, $bookmarks)) {
+				$sortedBookmarks[$id] = $bookmarks[$id];
+			}
+		}
+		$_SESSION['USER_PROFILE'][$this->_sesKey] = $sortedBookmarks;
+	}
+
 	public function hasBookmark($zasobID) {
 		return array_key_exists($zasobID, $_SESSION['USER_PROFILE'][$this->_sesKey]);
 	}

+ 70 - 9
SE/se-lib/V.php

@@ -10,7 +10,7 @@ class V {
 	/**
 	 * Get variable from array or object.
 	 */
-	public static function get($name, $default, &$from, $type = '') {
+	public static function get($name, $default, $from, $type = '', $filterCallback = null) {
 		$ret = null;
 		if (is_array($from)) {
 			if (array_key_exists($name, $from)) {
@@ -27,13 +27,20 @@ class V {
 			$ret = V::convert($ret, $type);
 		}
 
-		return (isset($ret))? $ret : $default;
+		if (!empty($filterCallback)) {
+			if ($type == 'array' && is_array($ret) && !empty($ret)) {
+				$ret = V::filter($ret, $filterCallback);
+			}
+		}
+
+		$ret = (null !== $ret)? $ret : $default;
+		return $ret;
 	}
 
 	/**
 	 * Convert variable type.
 	 */
-	public static function convert(&$from, $type = 'string') {
+	public static function convert($from, $type = 'string') {
 		$type = strtolower($type);
 
 		// is_scalar($from) - return TRUE if int,float,string,bool, FALSE if array,object,resource, ...
@@ -66,7 +73,7 @@ class V {
 			case 'float':
 			case 'double':
 				if (is_scalar($from)) {
-					$ret = str_replace(',','.',$from);
+					$ret = str_replace(',', '.', $from);
 					settype($ret, $type);
 				}
 				break;
@@ -81,9 +88,9 @@ class V {
 				if (is_scalar($from) || is_array($from) || is_object($from)) {
 					$ret = array();
 					$arr = $from;
-					settype($arr, $type);
+					settype($arr, 'array');
 					foreach ($arr as $v) {
-						$v = V::convert($v,'int');
+						$v = V::convert($v, 'int');
 						$ret[] = $v;
 					}
 				}
@@ -92,9 +99,9 @@ class V {
 				if (is_scalar($from) || is_array($from) || is_object($from)) {
 					$ret = array();
 					$arr = $from;
-					settype($arr, $type);
+					settype($arr, 'array');
 					foreach ($arr as $v) {
-						$v = V::convert($v,'int');
+						$v = V::convert($v, 'int');
 						if ($v <= 0) continue;
 						$ret[] = $v;
 					}
@@ -104,7 +111,7 @@ class V {
 				if (is_scalar($from) || is_array($from) || is_object($from)) {
 					$ret = array();
 					$arr = $from;
-					settype($arr, $type);
+					settype($arr, 'array');
 					foreach ($arr as $v) {
 						$v = V::convert($v, 'float');
 						$ret[] = $v;
@@ -250,4 +257,58 @@ class V {
 		return $label;
 	}
 
+	public static function filter($array, $filterCallback) {
+		if (!is_callable($filterCallback)) {
+			throw new Exception("callback is not callable '" . ((is_array($filterCallback))? implode('.', $filterCallback) : $filterCallback) . "'");
+		}
+		return array_filter($array, $filterCallback);
+	}
+
+	public static function filterNotEmpty($value) {
+		return !empty($value);
+	}
+
+	public static function filterInteger($value) {// An integer or string with integer value
+		if (is_int($value)) {
+			return true;
+		} else if (is_string($value)) {
+			if ((string)(int)$value === $value) {
+				return true;
+			}
+		}
+		return false;
+	}
+	public static function filterNegativeInteger($value) {// An integer containing only negative values (..,-2,-1)
+		if (V::filterInteger($value)) {
+			if (intval($value) < 0) {
+				return true;
+			}
+		}
+		return false;
+	}
+	public static function filterNonNegativeInteger($value) {// An integer containing only non-negative values (0,1,2,..)
+		if (V::filterInteger($value)) {
+			if (intval($value) >= 0) {
+				return true;
+			}
+		}
+		return false;
+	}
+	public static function filterNonPositiveInteger($value) {// An integer containing only non-positive values (..,-2,-1,0)
+		if (V::filterInteger($value)) {
+			if (intval($value) <= 0) {
+				return true;
+			}
+		}
+		return false;
+	}
+	public static function filterPositiveInteger($value) {// An integer containing only positive values (1,2,..)
+		if (V::filterInteger($value)) {
+			if (intval($value) > 0) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 }

+ 3 - 1
SE/se-lib/tmpl/_layout_gora.php

@@ -11,7 +11,9 @@
 -->
 	<link rel="stylesheet" href="stuff/jquery.selectize/css/selectize.bootstrap3.css" type="text/css" />
 	<link rel="stylesheet" href="stuff/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css" type="text/css" />
+	<link rel="stylesheet" href="stuff/jquery-ui-smoothness/jquery-ui-1.10.4.custom.min.css" type="text/css">
 	<link rel="stylesheet" href="stuff/main.css" type="text/css" />
+
 	<script src="stuff/jquery-2.1.0.min.js"></script>
 	<script src="stuff/jquery-plugins.js"></script>
 	<script src="stuff/jquery.form.js"></script>
@@ -22,7 +24,7 @@
 	<script src="stuff/moment/pl.js"></script>
 	<script src="stuff/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js"></script>
 	<script src="stuff/notify.min.js"></script>
-<!-- <script src="stuff/jquery-ui-1.10.4.custom.min.js"></script> -->
+	<script src="stuff/jquery-ui-1.10.4.custom.min.js"></script>
 	<script src="stuff/jquery.hotkeys.js"></script>
 	<script src="stuff/lodash.min.js"></script>
 	<script src="stuff/superagent.js"></script>