Budget.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. <?php
  2. Lib::loadClass('RouteBase');
  3. class Route_Budget extends RouteBase {
  4. private $_costs = array();
  5. private $_plan = array();
  6. private $_projectInfo = array();
  7. private $_projectPathsOrder = array();
  8. public function handleAuth() {
  9. if (!User::logged()) {
  10. throw new HttpException('Unauthorized', 401);
  11. }
  12. }
  13. public function defaultAction() {
  14. $args = array();
  15. $args['year'] = V::get('year', '', $_REQUEST, 'int');
  16. $args['_print'] = V::get('_print', '', $_REQUEST, 'int');
  17. SE_Layout::gora();
  18. SE_Layout::menu();
  19. if (!$args['_print']) {
  20. $this->menu($args['year']);
  21. }
  22. SE_Layout::dol();
  23. }
  24. public function yearBudgetAction() {
  25. $args = array();
  26. $args['year'] = V::get('year', '', $_REQUEST, 'int');
  27. $args['groups'] = V::get('fltrGroups', array(), $_REQUEST, 'array', array('V', 'filterPositiveInteger'));
  28. $args['_print'] = V::get('_print', '', $_REQUEST, 'int');
  29. $hasData = false;
  30. $groups = null;
  31. if ($args['year'] > 0) {
  32. $hasData = $this->fetchDataByYear($args['year']);
  33. $groups = $this->getUsedUserGroups();
  34. }
  35. SE_Layout::gora();
  36. SE_Layout::menu();
  37. if (!$args['_print']) {
  38. $this->menu($args['year'], $groups, $args['groups']);
  39. }
  40. if (empty($args['year'])) {
  41. ?>
  42. <div class="alert alert-warning">
  43. Nie wybrano roku.
  44. </div>
  45. <?php
  46. SE_Layout::dol();
  47. exit;
  48. }
  49. if (!$hasData) {
  50. ?>
  51. <div class="alert alert-warning">
  52. Brak danych na wybrany rok.
  53. </div>
  54. <?php
  55. return;
  56. }
  57. //echo'<pre style="border:1px solid red;overflow:auto;max-height:400px">$costs: ';print_r($costs);echo'</pre>';
  58. $this->printCostsForYear($args['year'], $args['groups']);
  59. SE_Layout::dol();
  60. }
  61. private function menu($selectedYear, $groups = array(), $selectedGroups = array()) {
  62. //SE_Layout::menu();
  63. $year = ($selectedYear)? $selectedYear : date("Y");
  64. ?>
  65. <div class="jumbotron">
  66. <div class="container">
  67. <form class="form-inline" method="POST">
  68. <input type="hidden" name="_task" value="yearBudget" />
  69. <label for="year">Zestawienie kosztów projektów. Wybierz rok:</label>
  70. <div class="input-group date" id="fldZestYear">
  71. <input type="text" name="year" class="form-control" value="" />
  72. <span class="input-group-addon"><span class="glyphicon glyphicon-time"></span></span>
  73. </div>
  74. <?php if (!empty($groups)) : ?>
  75. <div style="margin:8px 0">
  76. <label for="fltrGroups">Pokaż tylko projekty dostępne dla grup:</label>
  77. <select multiple name="fltrGroups[]" size="<?php echo min(5, count($groups)); ?>" class="form-control">
  78. <option value=""> [ Wszystkie ] </option>
  79. <?php foreach ($groups as $idGroup => $groupLdapName) : ?>
  80. <option
  81. value="<?php echo $idGroup; ?>"
  82. <?php if (in_array($idGroup, $selectedGroups)) { echo 'selected="selected"'; } ?>
  83. ><?php echo $groupLdapName; ?></option>
  84. <?php endforeach; ?>
  85. </select>
  86. </div>
  87. <?php endif; ?>
  88. <button type="submit" id="fldZestYearBtn" class="btn btn-primary" autocomplete="off">
  89. Pokaż
  90. </button>
  91. </form>
  92. </div>
  93. </div>
  94. <script type="text/javascript">
  95. jQuery(document).ready(function () {
  96. jQuery('#fldZestYearBtn').on('click', function () {
  97. jQuery(this).text(jQuery(this).text() + '...').attr('disabled', 'disabled');
  98. jQuery(this).parent().submit();
  99. })
  100. jQuery("#fldZestYear").datetimepicker({
  101. format: "YYYY",
  102. defaultDate: new Date(<?php echo $year; ?>, <?php echo intval(date("m")); ?>, 1),
  103. // minDate: new Date(2014, 11, 1),
  104. // maxDate: "<?php echo date("Y"); ?>"
  105. });
  106. });
  107. </script>
  108. <?php
  109. }
  110. function css() {
  111. ?>
  112. <style type="text/css">
  113. .c { text-align:center; }
  114. .r { text-align:right; }
  115. .zestawienie-kosztow-tbl { border-collapse:collapse; border:1px solid #aaa; }
  116. .zestawienie-kosztow-tbl td { border:1px solid #aaa; }
  117. .zestawienie-kosztow-tbl .p2 { padding:0 2px; }
  118. .zestawienie-kosztow-tbl .nr { color:#7A7A7A; }
  119. .zestawienie-kosztow-tbl thead th { border:1px solid #aaa; }
  120. .zestawienie-kosztow-tbl tbody tr:hover td { background:#cafbfd; }
  121. .row-selected td {background-color:#d8fded;}
  122. .showOnlySelected tr { display:none; }
  123. .showOnlySelected tr.row-selected { display:table-row; }
  124. .cell-cost { padding:0 2px; min-width:30px; text-align:right; }
  125. .cell-cost-only_self { color:#197fe6; }
  126. .cell-cost-self_and_child { color:#33b2cc; }
  127. .cell-cost-only_child { color:#59a680; }
  128. .cell-plan { padding:0 2px; min-width:30px; text-align:right; color:#777; }
  129. .cell-procent { padding:0 2px; min-width:20px; text-align:right; color:#777; }
  130. .cell-procent-below100 { color:#777; }
  131. .cell-procent-100 { color:#777; }
  132. .cell-procent-over100 { color:#ff9b00; }
  133. .cell-procent-over200 { color:#f00; }
  134. /* print table background colors */
  135. table td, table th { -webkit-print-color-adjust:exact; }
  136. table { page-break-after:auto }
  137. tr { page-break-inside:avoid; page-break-after:auto; position:relative; }
  138. td { page-break-inside:avoid; page-break-after:auto; position:relative; }
  139. thead { display:table-header-group }
  140. tfoot { display:table-footer-group }
  141. </style>
  142. <?php
  143. }
  144. function printCostsForYear($year, $groups) {
  145. $months = array();
  146. for ($i = 0; $i < 12; $i++) {
  147. $months[] = $i + 1;
  148. }
  149. $this->css();
  150. if(V::get('DBG','',$_GET)){echo'<pre style="width:600px;border:1px solid red;max-height:300px;overflow:auto;display:none">';print_r($this->_costs);echo'</pre>';}
  151. ?>
  152. <div class="container">
  153. <div style="float:right;color:#aaa;"><?php echo date("Y-m-d"); ?></div>
  154. <h1>Zestawienie kosztów projektów na rok <?php echo $year; ?></h1>
  155. </div>
  156. <table cellspacing="0" cellpadding="0" border="0" id="zestawienie-kosztow-projektow" class="zestawienie-kosztow-tbl">
  157. <thead>
  158. <tr>
  159. <td colspan="3" class="p2">
  160. <span class="pull-right"><b>miesiąc</b></span>
  161. </td>
  162. <?php foreach ($months as $month) { ?>
  163. <th class="c" colspan="3"><?php echo sprintf("%02d", $month); ?></th>
  164. <?php } ?>
  165. </tr>
  166. <tr>
  167. <td colspan="3" class="p2">
  168. <span class="pull-left">
  169. <input type="checkbox" onclick="return showHideAll(this);"/> pokaż tylko zaznaczone
  170. </span>
  171. </td>
  172. <?php foreach ($months as $month) { ?>
  173. <th class="c" title="Koszty wprowadzone do korespondencji">Koszty</th>
  174. <th class="c" title="Plan budżetu">Plan</th>
  175. <th class="c" title="Procent przekroczenia planu">%</th>
  176. <?php } ?>
  177. </tr>
  178. </thead>
  179. <tbody>
  180. <?php $t = 1; ?>
  181. <?php foreach ($this->_projectPathsOrder as $projPath => $projId) : ?>
  182. <?php
  183. $projectID = $projId;
  184. $projectDesc = $this->_projectInfo[$projId]->M_DIST_DESC;
  185. $projectPath = $this->_projectInfo[$projId]->path;
  186. $projectAccess = $this->hasAccessToProject($projectID);
  187. if (!empty($groups)) {
  188. if (!$projectAccess) {
  189. //echo '<pre>TODO: filtered by acl for project';print_r($this->_projectInfo[$projId]);echo'</pre>';
  190. continue;
  191. }
  192. if (!$this->hasGroupsAccessToProjects($projectID, $groups)) {
  193. //echo '<pre>TODO: filtered by acl and groups';print_r($this->_projectInfo[$projId]);echo'</pre>';
  194. continue;
  195. }
  196. }
  197. ?>
  198. <tr class="row-<?php echo ($t = 1 - $t); ?>"
  199. data-proj_id="<?php echo $projectID; ?>"
  200. data-path="<?php echo $projectPath; ?>">
  201. <td class="p2 r nr">
  202. <input type="checkbox" name="selectedProject" onclick="return selectProject(this);" value="<?php echo $projectID; ?>" />
  203. </td>
  204. <td class="p2 l nr"><nobr><?php echo $projectPath; ?></nobr></td>
  205. <?php if (!$projectAccess) : ?>
  206. <td class="p2">***</td>
  207. <?php else : ?>
  208. <td class="p2" style="max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="<?php echo $projectDesc; ?>"><?php echo $projectDesc; ?></td>
  209. <?php endif; ?>
  210. <?php foreach ($months as $month) : ?>
  211. <?php $vMonthCost = $this->getCost($projectID, $month); ?>
  212. <?php $monthCostTotal = ($vMonthCost)? $vMonthCost->COST_TOTAL : 0; ?>
  213. <?php if (!$projectAccess) : ?>
  214. <td style="min-width:30px;text-align:right;">***</td>
  215. <?php elseif (!$vMonthCost) : ?>
  216. <td style="min-width:30px">&nbsp;</td>
  217. <?php else : ?>
  218. <?php
  219. $vCostChildOut = number_format($vMonthCost->COST_CHILD, 2);
  220. $vCostSelfOut = number_format($vMonthCost->COST_SELF, 2);
  221. $vCostTotalOut = number_format($vMonthCost->COST_TOTAL, 2);
  222. $title = "Koszt projektu {$vCostSelfOut} / koszt podprojektów $vCostChildOut";
  223. $cellCostCls = '';
  224. if ($vMonthCost->COST_CHILD > 0) {
  225. if ($vMonthCost->COST_SELF > 0) {
  226. $cellCostCls = 'cell-cost-self_and_child';
  227. } else {
  228. $cellCostCls = 'cell-cost-only_child';
  229. }
  230. } else {
  231. $cellCostCls = 'cell-cost-only_self';
  232. }
  233. ?>
  234. <td class="cell-cost <?php echo $cellCostCls; ?>"
  235. data-month_num="<?php echo $month; ?>"
  236. data-cost="<?php echo $vCostTotalOut; ?>"
  237. data-proj_path="<?php echo $projectPath; ?>"
  238. data-proj_id="<?php echo $projectID; ?>">
  239. <span class="ttip" title="<?php echo $title; ?>"><?php echo $vCostTotalOut; ?></span>
  240. </td>
  241. <?php endif; ?>
  242. <td class="cell-plan">
  243. <?php $monthPlan = $this->getPlan($projectID, $month); ?>
  244. <?php $monthPlanOut = number_format($monthPlan, 2); ?>
  245. <?php if (!$projectAccess) : ?>
  246. ***
  247. <?php elseif ($monthPlan > 0) : ?>
  248. <?php echo $monthPlan; ?>
  249. <?php else : ?>
  250. &nbsp;
  251. <?php endif; ?>
  252. </td>
  253. <?php
  254. $cellProcentCls = '';
  255. $procentOut = '&nbsp;';
  256. $monthPlan = $this->getPlan($projectID, $month);
  257. if ($monthPlan > 0) {
  258. $procentOut = round(($monthCostTotal * 100) / $monthPlan);
  259. if ($procentOut > 200) {
  260. $cellProcentCls = 'cell-procent-over200';
  261. } else if ($procentOut > 100) {
  262. $cellProcentCls = 'cell-procent-over100';
  263. } else if ($procentOut == 100) {
  264. $cellProcentCls = 'cell-procent-100';
  265. } else {
  266. $cellProcentCls = 'cell-procent-below100';
  267. }
  268. }
  269. ?>
  270. <?php if (!$projectAccess) : ?>
  271. <td style="min-width:30px;text-align:right;">***</td>
  272. <?php else : ?>
  273. <td class="cell-procent <?php echo $cellProcentCls; ?>"><?php echo $procentOut; ?></td>
  274. <?php endif; ?>
  275. <?php endforeach; ?>
  276. </tr>
  277. <?php endforeach; ?>
  278. </tbody>
  279. </table>
  280. <script>
  281. jQuery(document).ready(function() {
  282. jQuery('#zestawienie-kosztow-projektow')
  283. .find('.cell-cost')
  284. .on('click', function(e) {
  285. var $n = jQuery(this),
  286. $modal = jQuery('#projectMonthCostDetails'),
  287. projId = $n.data('proj_id'),
  288. monthNum = $n.data('month_num'),
  289. projPath = '' + $n.data('proj_path'),
  290. projPathLength = projPath.length,
  291. modalBody = document.createElement('table'),
  292. totalDescrCell = document.createElement('td'),
  293. totalCostCell = document.createElement('td'),
  294. totalEmptyCell = document.createElement('td'),
  295. totalRow = document.createElement('tr');
  296. modalBody.className = 'zestawienie-kosztow-tbl';
  297. modalBody.style.width = '100%';
  298. // $n.data() = {proj_id: 3943, proj_path: "0-4132-3943", month_num: 2}
  299. // table#proj-koresp-info > tbody#row-proj-2055-koresp-by-month-2
  300. jQuery('#proj-koresp-info').find('tbody').each(function(i, tbody) {
  301. var vPath = jQuery(tbody).data('proj_path'),
  302. vMonthNum = jQuery(tbody).data('month_num');
  303. if (monthNum != vMonthNum) return;
  304. if (projPathLength > vPath.length) return;
  305. if (projPath == vPath
  306. || (projPathLength < vPath.length && projPath + '-' == vPath.substr(0, projPathLength + 1))
  307. ) {
  308. modalBody.appendChild(tbody.cloneNode(true));
  309. }
  310. });
  311. {
  312. totalDescrCell.className = 'p2';
  313. totalDescrCell.style.textAlign = 'right';
  314. totalDescrCell.setAttribute('colspan', '2');
  315. totalDescrCell.appendChild(document.createTextNode('Suma:'));
  316. totalCostCell.className = 'cell-cost';
  317. totalCostCell.style.textAlign = 'right';
  318. totalCostCell.appendChild(document.createTextNode($n.data('cost')));
  319. totalRow.appendChild(totalDescrCell);
  320. totalRow.appendChild(totalCostCell);
  321. modalBody.appendChild(totalRow);
  322. }
  323. $modal.find('.modal-title').text('Koszty projektu nr ' + projId + ' w miesiącu <?php echo $year; ?>-' + ((monthNum > 9)? monthNum : '0' + monthNum));
  324. $modal.find('.modal-body').html(modalBody);
  325. $modal.modal({});
  326. })
  327. })
  328. </script>
  329. <!-- Modal -->
  330. <div class="modal fade" id="projectMonthCostDetails" tabindex="-1" role="dialog" aria-labelledby="projectCostDMonthetailsLabel">
  331. <div class="modal-dialog" role="document" style="min-width:800px;">
  332. <div class="modal-content">
  333. <div class="modal-header">
  334. <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  335. <h4 class="modal-title" id="projectCostDMonthetailsLabel">Modal title</h4>
  336. </div>
  337. <div class="modal-body">
  338. TODO: projectMonthCostDetails...
  339. </div>
  340. <div class="modal-footer">
  341. <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
  342. </div>
  343. </div>
  344. </div>
  345. </div>
  346. <table id="proj-koresp-info" style="display:none">
  347. <?php foreach ($this->_projectPathsOrder as $projPath => $projId) : ?>
  348. <?php
  349. $projectInfo = $this->_costs[$projId];
  350. if (!$projectInfo) {
  351. continue;
  352. }
  353. $projectID = $projId;
  354. $projectDesc = $this->_projectInfo[$projId]->M_DIST_DESC;
  355. $projectPath = $this->_projectInfo[$projId]->path;
  356. ?>
  357. <?php if (!empty($projectInfo->korespByMonth)) : ?>
  358. <?php foreach ($projectInfo->korespByMonth as $kMonth => $vKorespMonthList) : ?>
  359. <tbody id="row-proj-<?php echo $projectID; ?>-koresp-by-month-<?php echo $kMonth; ?>"
  360. data-month_num="<?php echo $kMonth; ?>"
  361. data-proj_path="<?php echo $projectPath; ?>"
  362. data-proj_id="<?php echo $projectID; ?>">
  363. <tr>
  364. <td style="padding:3px;font-size:1em;background:#eee;"><nobr><?php echo $projectPath; ?></nobr></td>
  365. <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; ?>">
  366. <!-- Koszty projektu nr <?php echo $projectID; ?> -->
  367. <!-- - miesiąc <?php echo $year; ?>-<?php echo sprintf("%02d", $kMonth); ?> -->
  368. <?php echo $projectDesc; ?>
  369. </td>
  370. </tr>
  371. <?php foreach ($vKorespMonthList as $kKorespIdx => $vKorespInfo) : ?>
  372. <?php /*
  373. * [ID] => 41235
  374. * [MONTH] => 2015-02
  375. * [K_ZAWARTOS] => Faktura za pomiar powykonawczy sieci telekomunikacyjnej
  376. * [COST] => 1000.00
  377. * [INCOME] => 0.00
  378. ? [TRANSFER_OPPOSITE_ID_PROJECT] => int
  379. */ ?>
  380. <tr>
  381. <td class="p2 r nr"><?php echo $vKorespInfo->ID; ?></td>
  382. <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>
  383. <?php $vKorespCostOut = number_format($vKorespInfo->COST, 2); ?>
  384. <td class="cell-cost cell-cost-only_child"><?php echo $vKorespCostOut; ?></td>
  385. <td>
  386. <?php $dekretId = V::get('TRANSFER_OPPOSITE_ID_PROJECT', 0, $vKorespInfo) ?>
  387. <?php if ($dekretId > 0) : ?>
  388. Dekret z projektu nr <?php echo $dekretId; ?>
  389. <?php endif; ?>
  390. </td>
  391. </tr>
  392. <?php endforeach; ?>
  393. </tbody>
  394. <?php endforeach; ?>
  395. <?php endif; ?>
  396. <?php endforeach; ?>
  397. </table>
  398. <script>
  399. jQuery(document).ready(function(){
  400. jQuery('.ttip').tooltip();
  401. });
  402. function selectProject(n) {
  403. var $n = jQuery(n);
  404. var $p = $n.parent().parent();
  405. if (n.checked) {
  406. $p.addClass('row-selected');
  407. } else {
  408. $p.removeClass('row-selected');
  409. }
  410. markSubProjects($p, $p.data('path'), n.checked);
  411. function markSubProjects($p, path, checked) {
  412. var $nextRow = $p.next('tr'),
  413. nextPath = $nextRow.data('path'),
  414. nextCheckbox = $nextRow.find('input[type="checkbox"]').get(0);
  415. if (0 !== nextPath.indexOf(path)) {
  416. return;
  417. }
  418. if (checked) {
  419. $nextRow.addClass('row-selected');
  420. } else {
  421. $nextRow.removeClass('row-selected');
  422. }
  423. nextCheckbox.checked = checked;
  424. markSubProjects($nextRow, path, checked);
  425. }
  426. }
  427. function showHideAll(n) {
  428. if (n.checked) {
  429. jQuery('#zestawienie-kosztow-projektow').find('tbody').addClass('showOnlySelected');
  430. } else {
  431. jQuery('#zestawienie-kosztow-projektow').find('tbody').removeClass('showOnlySelected');
  432. }
  433. }
  434. </script>
  435. <?php
  436. }
  437. public function getCost($idProject, $month) {
  438. if (!array_key_exists($idProject, $this->_costs)) {
  439. return null;
  440. }
  441. if (!array_key_exists($month, $this->_costs[$idProject]->costsByMonth)) {
  442. return null;
  443. }
  444. return $this->_costs[$idProject]->costsByMonth[$month];
  445. }
  446. public function getPlan($idProject, $month) {
  447. if (!array_key_exists($idProject, $this->_plan)) {
  448. return 0;
  449. }
  450. if (!array_key_exists($month, $this->_plan[$idProject])) {
  451. return 0;
  452. }
  453. return $this->_plan[$idProject][$month];
  454. }
  455. public function fetchDataByYear($year) {
  456. $this->_fetchCostsByYear($year);
  457. $this->_fetchPlanByYear($year);
  458. $this->_fetchProjectInfo();
  459. $this->_buildProjectTree();
  460. $this->_reacountCostsFromKoresp();
  461. return count($this->_projectInfo) > 1;// $this->_projectInfo[0] - Wszystkie projekty
  462. }
  463. public function _fetchPlanByYear($year) {
  464. $db = DB::getDB();
  465. $this->_plan = array();
  466. $sql = "
  467. select plan.`ID`
  468. , plan.`ID_PROJECT` AS `ID_PROJECT`
  469. , plan.`MONTH_1_VALUE`
  470. , plan.`MONTH_2_VALUE`
  471. , plan.`MONTH_3_VALUE`
  472. , plan.`MONTH_4_VALUE`
  473. , plan.`MONTH_5_VALUE`
  474. , plan.`MONTH_6_VALUE`
  475. , plan.`MONTH_7_VALUE`
  476. , plan.`MONTH_8_VALUE`
  477. , plan.`MONTH_9_VALUE`
  478. , plan.`MONTH_10_VALUE`
  479. , plan.`MONTH_11_VALUE`
  480. , plan.`MONTH_12_VALUE`
  481. from `projects_budget_year_month` plan
  482. where plan.`year`='{$year}'
  483. -- TODO: acl
  484. ";
  485. //echo'<pre style="border:1px solid red;overflow:auto;max-height:400px">';print_r($sql);echo'</pre>';
  486. $res = $db->query($sql);
  487. while ($r = $db->fetch($res)) {
  488. $plan = array();
  489. for ($i = 1; $i <= 12; $i++) {
  490. $plan[$i] = V::get("MONTH_{$i}_VALUE", 0, $r);
  491. }
  492. $this->_plan[$r->ID_PROJECT] = $plan;
  493. }
  494. return $this->_plan;
  495. }
  496. public function _fetchCostsByYear($year) {
  497. $db = DB::getDB();
  498. $this->_costs = array();
  499. $sql = "
  500. select k.`ID`
  501. , k.`ID_PROJECT` AS `ID_PROJECT`
  502. , date_format(k.`K_DATA_OTRZYMANEJ_KORESP`,'%Y-%m') AS `MONTH`
  503. , k.`COST_VALUE` AS `COST`
  504. , k.`INCOME_VALUE` AS `INCOME`
  505. , 0 as `TRANSFER_OPPOSITE_ID_PROJECT` -- TRANSFER_OPPOSITE_ID_PROJECT
  506. , k.`path`
  507. -- , IF(k.`TRANSFER_OPPOSITE_ID_PROJECT`>0
  508. -- , (select p.`path`
  509. -- from `IN7_MK_BAZA_DYSTRYBUCJI` p
  510. -- where p.`ID`=k.`TRANSFER_OPPOSITE_ID_PROJECT`
  511. -- limit 1
  512. -- )
  513. -- , '') as TRANSFER_OPPOSITE_PROJECT_PATH
  514. , '' as TRANSFER_OPPOSITE_PROJECT_PATH
  515. , k.`K_ZAWARTOS`
  516. from `IN7_DZIENNIK_KORESP` k
  517. where ((k.`COST_VALUE` > 0) or (k.`INCOME_VALUE` > 0))
  518. and k.`K_DATA_OTRZYMANEJ_KORESP` like '{$year}-%'
  519. -- TODO: acl
  520. ";
  521. //echo'<pre style="border:1px solid red;overflow:auto;max-height:400px">';print_r($sql);echo'</pre>';
  522. $res = $db->query($sql);
  523. while ($r = $db->fetch($res)) {
  524. $vProjId = ($r->ID_PROJECT > 0)? $r->ID_PROJECT : 0;
  525. $vProjPaths = array();
  526. $vProjIds = array(0 => true);
  527. if ($r->ID_PROJECT > 0) {
  528. $vProjPaths[] = $r->ID_PROJECT;
  529. if (!empty($r->path)) {
  530. $vProjPaths[] = $r->path;
  531. }
  532. }
  533. if ($r->TRANSFER_OPPOSITE_ID_PROJECT > 0) {
  534. $vProjPaths[] = $r->TRANSFER_OPPOSITE_ID_PROJECT;
  535. if (!empty($r->TRANSFER_OPPOSITE_PROJECT_PATH)) {
  536. $vProjPaths[] = $r->TRANSFER_OPPOSITE_PROJECT_PATH;
  537. }
  538. }
  539. if (!empty($vProjPaths)) {
  540. //echo'<p>DBG:$r->ID_PROJECT['.$r->ID_PROJECT.']: $vProjPaths = ' . json_encode($vProjPaths) . '</p>';
  541. $vProjPaths = implode('-', $vProjPaths);
  542. $projIds = explode('-', $vProjPaths);
  543. //echo'<p>DBG:$r->ID_PROJECT['.$r->ID_PROJECT.']: $projIds = ' . json_encode($projIds) . '</p>';
  544. foreach ($projIds as $vProjId) {
  545. if ($vProjId > 0) $vProjIds[$vProjId] = true;
  546. }
  547. }
  548. {
  549. $projectZeroInfo = new stdClass();
  550. $projectZeroInfo->ID_PROJECT = 0;
  551. $projectZeroInfo->costsByMonth = array();
  552. $projectZeroInfo->korespByMonth = array();
  553. $this->_costs[0] = $projectZeroInfo;
  554. }
  555. foreach ($vProjIds as $vProjId => $vBool) {
  556. if (!array_key_exists($vProjId, $this->_costs)) {
  557. $projectInfo = new stdClass();
  558. $projectInfo->ID_PROJECT = $vProjId;
  559. $projectInfo->costsByMonth = array();
  560. $projectInfo->korespByMonth = array();
  561. $this->_costs[$vProjId] = $projectInfo;
  562. }
  563. }
  564. $korespInfo = new stdClass();
  565. $korespInfo->ID = $r->ID;
  566. $korespInfo->MONTH = $r->MONTH;
  567. $korespInfo->K_ZAWARTOS = $r->K_ZAWARTOS;
  568. $monthNum = intval(substr($r->MONTH, 5, 2));
  569. if ($r->TRANSFER_OPPOSITE_ID_PROJECT > 0) {
  570. $korespInfo->COST = $r->COST;
  571. $korespInfo->INCOME = $r->INCOME;
  572. $korespInfo->TRANSFER_OPPOSITE_ID_PROJECT = $r->TRANSFER_OPPOSITE_ID_PROJECT;
  573. $this->_costs[$r->TRANSFER_OPPOSITE_ID_PROJECT]->korespByMonth[$monthNum][] = $korespInfo;
  574. } else if ($r->ID_PROJECT) {
  575. $korespInfo->COST = $r->COST;
  576. $korespInfo->INCOME = $r->INCOME;
  577. $this->_costs[$r->ID_PROJECT]->korespByMonth[$monthNum][] = $korespInfo;
  578. } else {
  579. $korespInfo->COST = $r->COST;
  580. $korespInfo->INCOME = $r->INCOME;
  581. $this->_costs[0]->korespByMonth[$monthNum][] = $korespInfo;
  582. }
  583. }
  584. return $this->_costs;
  585. }
  586. private function _fetchProjectInfo() {
  587. $db = DB::getDB();
  588. $hasAccessForAllProjects = true;
  589. $projectIds = array();
  590. $projectsFromCostIds = array_keys($this->_costs);
  591. foreach ($projectsFromCostIds as $idProject) $projectIds[$idProject] = true;
  592. $projectsFromPlanIds = array_keys($this->_plan);
  593. foreach ($projectsFromPlanIds as $idProject) $projectIds[$idProject] = true;
  594. foreach ($projectIds as $idProject => $vBool) $this->_projectInfo[$idProject] = new stdClass();
  595. $projectIds = array_keys($projectIds);
  596. $sqlProjIds = "'" . implode("','", $projectIds) . "'";
  597. $sql = "
  598. select p.`ID`
  599. , p.`P_ID`
  600. , p.`path`
  601. , p.`M_DIST_DESC`
  602. , p.`A_ADM_COMPANY` as aclGroupWrite
  603. , p.`A_CLASSIFIED` as aclGroupRead
  604. , p.`L_APPOITMENT_USER` as aclOwner
  605. from `IN7_MK_BAZA_DYSTRYBUCJI` p
  606. where p.`ID` in({$sqlProjIds})
  607. ";
  608. $res = $db->query($sql);
  609. while ($r = $db->fetch($res)) {
  610. $this->_projectInfo[$r->ID]->path = $r->path;
  611. $this->_projectInfo[$r->ID]->M_DIST_DESC = $r->M_DIST_DESC;
  612. $this->_projectInfo[$r->ID]->aclGroupRead = $r->aclGroupRead;
  613. $this->_projectInfo[$r->ID]->hasAccess = $this->_userHasAccessToProject($r);
  614. if (!$this->_projectInfo[$r->ID]->hasAccess) $hasAccessForAllProjects = false;
  615. }
  616. $this->_projectInfo[0]->path = '0';
  617. $this->_projectInfo[0]->M_DIST_DESC = "Wszystkie projekty";
  618. $this->_projectInfo[0]->hasAccess = $hasAccessForAllProjects;
  619. }
  620. public function hasAccessToProject($idProject) {
  621. if ($idProject >= 0) {
  622. if (array_key_exists($idProject, $this->_projectInfo)) {
  623. return V::get('hasAccess', false, $this->_projectInfo[$idProject]);
  624. }
  625. }
  626. return false;
  627. }
  628. public function hasGroupsAccessToProjects($idProject, $groups) {
  629. $selectedUserGroupNames = array();
  630. $userGroups = User::getLdapGroupsNames();
  631. foreach ($groups as $idGroup) {
  632. $selectedUserGroupNames[$idGroup] = $userGroups[$idGroup];
  633. }
  634. if ($idProject >= 0) {
  635. if (array_key_exists($idProject, $this->_projectInfo)) {
  636. $alcGroupRead = V::get('aclGroupRead', null, $this->_projectInfo[$idProject]);
  637. if (!$alcGroupRead) {
  638. return false;
  639. }
  640. if (in_array($alcGroupRead, $selectedUserGroupNames)) {
  641. return true;
  642. }
  643. }
  644. }
  645. return false;
  646. }
  647. private function _userHasAccessToProject($project) {
  648. $groups = User::getLdapGroupsNames();
  649. $userLogin = User::getLogin();
  650. if ($project->aclOwner == $userLogin) {
  651. return true;
  652. }
  653. else if (in_array($project->aclGroupRead, $groups)) {
  654. return true;
  655. }
  656. return false;
  657. }
  658. public function getUsedUserGroups() {
  659. $groups = array();
  660. $userGroups = User::getLdapGroupsNames();
  661. foreach ($this->_projectInfo as $projectInfo) {
  662. if (!empty($projectInfo->aclGroupRead)) {
  663. $groupKey = array_search($projectInfo->aclGroupRead, $userGroups);
  664. if ($groupKey !== false) {
  665. $groups[$groupKey] = $projectInfo->aclGroupRead;
  666. }
  667. }
  668. }
  669. return $groups;
  670. }
  671. private function _reacountCostsFromKoresp() {
  672. $projMonthHasCostSelfIds = array();
  673. foreach ($this->_costs as $kProjId => $vProjInfo) {
  674. $projectPath = $this->_projectInfo[$kProjId]->path;
  675. foreach ($vProjInfo->korespByMonth as $kMonthNum => $vKorespList) {
  676. $this->_createCostIfNotDefined($kProjId, $kMonthNum);
  677. foreach ($vKorespList as $vKoresp) {
  678. $this->_costs[$kProjId]->costsByMonth[$kMonthNum]->COST_SELF += $vKoresp->COST;
  679. $this->_costs[$kProjId]->costsByMonth[$kMonthNum]->INCOME_SELF += $vKoresp->INCOME;
  680. }
  681. $projHasCostSelfIds[$kProjId][$kMonthNum] = $projectPath;
  682. }
  683. }
  684. //echo'<pre style="width:600px;border:1px solid red;max-height:300px;overflow:auto;">$projHasCostSelfIds: ';print_r($projHasCostSelfIds);echo'</pre>';
  685. foreach ($projHasCostSelfIds as $kProjId => $vProjMonthsList) {
  686. if ($kProjId <= 0) continue;
  687. foreach ($vProjMonthsList as $kMonthNum => $vProjPath) {
  688. $vProjPathIds = explode('-', $vProjPath);
  689. $vProjPathIds = array_reverse($vProjPathIds);
  690. $vProjMonthCostSelf = $this->_costs[$kProjId]->costsByMonth[$kMonthNum]->COST_SELF;
  691. $vProjMonthIncomeSelf = $this->_costs[$kProjId]->costsByMonth[$kMonthNum]->INCOME_SELF;
  692. foreach ($vProjPathIds as $vProjId) {
  693. if ($vProjId == $kProjId) continue;
  694. $this->_createCostIfNotDefined($vProjId, $kMonthNum);
  695. $this->_costs[$vProjId]->costsByMonth[$kMonthNum]->COST_CHILD += $vProjMonthCostSelf;
  696. $this->_costs[$vProjId]->costsByMonth[$kMonthNum]->INCOME_CHILD += $vProjMonthIncomeSelf;
  697. }
  698. }
  699. }
  700. // recount total
  701. foreach ($this->_costs as $vProjId => $vProjInfo) {
  702. foreach ($vProjInfo->costsByMonth as $kMonthNum => $vCost) {
  703. $this->_createCostIfNotDefined($vProjId, $kMonthNum);
  704. $this->_costs[$vProjId]->costsByMonth[$kMonthNum]->COST_TOTAL = $vCost->COST_SELF + $vCost->COST_CHILD;
  705. $this->_costs[$vProjId]->costsByMonth[$kMonthNum]->INCOME_TOTAL = $vCost->INCOME_SELF + $vCost->INCOME_CHILD;
  706. }
  707. }
  708. }
  709. private function _createCostIfNotDefined($projId, $monthNum) {
  710. if (empty($this->_costs[$projId]->costsByMonth[$monthNum])) {
  711. $vEmptyCost = new stdClass();
  712. $vEmptyCost->COST_SELF = 0;
  713. $vEmptyCost->INCOME_SELF = 0;
  714. $vEmptyCost->COST_CHILD = 0;
  715. $vEmptyCost->INCOME_CHILD = 0;
  716. $vEmptyCost->COST_TOTAL = 0;
  717. $vEmptyCost->INCOME_TOTAL = 0;
  718. $this->_costs[$projId]->costsByMonth[$monthNum] = $vEmptyCost;
  719. }
  720. }
  721. public function _buildProjectTree() {
  722. $this->_projectPathsOrder = array();
  723. foreach ($this->_projectInfo as $idProject => $projectInfo) {
  724. $this->_projectPathsOrder[$projectInfo->path] = $idProject;
  725. }
  726. //echo'<pre style="width:600px;border:1px solid red;max-height:300px;overflow:auto;">projPaths: ';print_r($this->_projectPathsOrder);echo'</pre>';
  727. uksort($this->_projectPathsOrder, array($this, 'sortPathsCallback'));
  728. //echo'<pre style="width:600px;border:1px solid red;max-height:300px;overflow:auto;">projPaths sorted: ';print_r($this->_projectPathsOrder);echo'</pre>';
  729. return $this->_projectPathsOrder;
  730. }
  731. public function sortPathsCallback($a, $b) {
  732. $ea = explode('-', $a);
  733. $eb = explode('-', $b);
  734. $la = count($ea);
  735. $lb = count($eb);
  736. $lmin = min($la, $lb);
  737. for ($i = 0; $i < $lmin; $i++) {
  738. if ($ea[$i] < $eb[$i]) {
  739. return -1;
  740. } else if ($ea[$i] > $eb[$i]) {
  741. return 1;
  742. }
  743. }
  744. return $la - $lb;
  745. }
  746. public function updatePaths() {
  747. $sqlList = array();
  748. $sqlList['updateAllPaths'] = <<<SQL
  749. update `projects_budget_year_month` b
  750. set path = (select coalesce(
  751. (select p.`path` from `IN7_MK_BAZA_DYSTRYBUCJI` p where p.`ID`=b.`ID_PROJECT` limit 1)
  752. , '?'));
  753. SQL;
  754. $db = DB::getDB();
  755. if ($db->has_errors()) {
  756. throw new Exception("DB Errors: " . implode("\n<br>", $db->get_errors()));
  757. }
  758. foreach ($sqlList as $sqlName => $sql) {
  759. $res = $db->query($sql);
  760. if ($db->has_errors()) {
  761. throw new Exception("DB Errors at sql '{$sqlName}': " . implode("\n<br>", $db->get_errors()));
  762. }
  763. }
  764. }
  765. public function reinstall() {
  766. $sqlList = array();
  767. $sqlList['RemoveTrigger_BudgetPlan_BeforeInsert'] = "DROP TRIGGER IF EXISTS `projects_budget_year_month_BEFORE_INSERT`";
  768. $sqlList['CreateTrigger_BudgetPlan_BeforeInsert'] = "
  769. CREATE DEFINER=`root`@`localhost` TRIGGER `projects_budget_year_month_BEFORE_INSERT` BEFORE INSERT ON `projects_budget_year_month`
  770. FOR EACH ROW BEGIN
  771. IF NEW.ID_PROJECT IS NOT NULL and NEW.ID_PROJECT>0 THEN
  772. SET NEW.path = (select coalesce(
  773. (select p.`path` from `IN7_MK_BAZA_DYSTRYBUCJI` p where p.`ID`=NEW.`ID_PROJECT` limit 1)
  774. , '?'
  775. ));
  776. END IF;
  777. END
  778. ";
  779. $sqlList['RemoveTrigger_BudgetPlan_BeforeUpdate'] = "DROP TRIGGER IF EXISTS `projects_budget_year_month_BEFORE_UPDATE `";
  780. // throws errors:
  781. // #1146 - Table '{DATABASE_NAME}.P5-MSG:Route_FixZasobPath:ERROR: Loop detected ID=PARENT_ID' doesn't exist
  782. // #1146 - Table '{DATABASE_NAME}.P5-MSG:Route_FixZasobPath:ERROR: Parent item not exists' doesn't exist
  783. // #1146 - Table '{DATABASE_NAME}.P5-MSG:Route_FixZasobPath:ERROR: Loop detected in path' doesn't exist
  784. $sqlList['CreateTrigger_BudgetPlan_BeforeUpdate'] = "
  785. CREATE DEFINER=`root`@`localhost` TRIGGER `projects_budget_year_month_BEFORE_UPDATE` BEFORE UPDATE ON `projects_budget_year_month`
  786. FOR EACH ROW BEGIN
  787. IF NEW.ID_PROJECT IS NULL THEN
  788. SET NEW.path = '';
  789. ELSEIF OLD.ID_PROJECT IS NULL or NEW.ID_PROJECT<>OLD.ID_PROJECT THEN
  790. IF NEW.ID_PROJECT>0 THEN
  791. SET NEW.path = (select coalesce(
  792. (select p.`path` from `IN7_MK_BAZA_DYSTRYBUCJI` p where p.`ID`=NEW.`ID_PROJECT` limit 1)
  793. , '?'));
  794. ELSE
  795. SET NEW.path = '';
  796. END IF;
  797. END IF;
  798. END
  799. ";
  800. $db = DB::getDB();
  801. if ($db->has_errors()) {
  802. throw new Exception("DB Errors: " . implode("\n<br>", $db->get_errors()));
  803. }
  804. foreach ($sqlList as $sqlName => $sql) {
  805. $res = $db->query($sql);
  806. if ($db->has_errors()) {
  807. throw new Exception("DB Errors at sql '{$sqlName}': " . implode("\n<br>", $db->get_errors()));
  808. }
  809. }
  810. }
  811. }