An easy point of sale system with automatic inventory tracking. https://netsyms.biz/apps/nickelbox/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

reports.php 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <?php
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. // Detect if loaded by the user or by PHP
  6. if (count(get_included_files()) == 1) {
  7. define("LOADED", true);
  8. } else {
  9. define("LOADED", false);
  10. }
  11. require_once __DIR__ . "/../required.php";
  12. use League\Csv\Writer;
  13. use League\Csv\HTMLConverter;
  14. use odsPhpGenerator\ods;
  15. use odsPhpGenerator\odsTable;
  16. use odsPhpGenerator\odsTableRow;
  17. use odsPhpGenerator\odsTableColumn;
  18. use odsPhpGenerator\odsTableCellString;
  19. use odsPhpGenerator\odsStyleTableColumn;
  20. use odsPhpGenerator\odsStyleTableCell;
  21. require_once __DIR__ . "/userinfo.php";
  22. require_once __DIR__ . "/login.php";
  23. // Allow access with a download code, for mobile app and stuff
  24. $date = date("Y-m-d H:i:s");
  25. $allowed_users = [];
  26. $requester = -1;
  27. if (isset($VARS['code']) && LOADED) {
  28. if (!$database->has('report_access_codes', ["AND" => ['code' => $VARS['code'], 'expires[>]' => $date]])) {
  29. dieifnotloggedin();
  30. $requester = $_SESSION['uid'];
  31. } else {
  32. $requester = $database->get('report_access_codes', 'uid', ['code' => $VARS['code']]);
  33. }
  34. } else {
  35. dieifnotloggedin();
  36. $requester = $_SESSION['uid'];
  37. }
  38. if (account_has_permission($_SESSION['username'], "ADMIN")) {
  39. $allowed_users = true;
  40. } else {
  41. if (account_has_permission($_SESSION['username'], "QWIKCLOCK_MANAGE")) {
  42. $allowed_users = getManagedUIDs($requester);
  43. }
  44. if (account_has_permission($_SESSION['username'], "QWIKCLOCK_EDITSELF")) {
  45. $allowed_users[] = $_SESSION['uid'];
  46. }
  47. }
  48. // Delete old DB entries
  49. $database->delete('report_access_codes', ['expires[<=]' => $date]);
  50. if (LOADED) {
  51. $user = null;
  52. if (isset($VARS['type']) && isset($VARS['format'])) {
  53. generateReport($VARS['type'], $VARS['format'], $VARS['register'], $VARS['startdate'], $VARS['enddate']);
  54. die();
  55. } else {
  56. lang("invalid parameters");
  57. die();
  58. }
  59. }
  60. function getCashFlowReport($register = null, $start = null, $end = null) {
  61. global $database;
  62. $where = [];
  63. if (!is_null($register) && $database->has('registers', ['registerid' => $register])) {
  64. $where["registers.registerid"] = $register;
  65. }
  66. if ((bool) strtotime($start) == TRUE) {
  67. $where["OR #open"] = [
  68. "open[>=]" => date("Y-m-d", strtotime($start)),
  69. "close[>=]" => date("Y-m-d", strtotime($start))
  70. ];
  71. }
  72. if ((bool) strtotime($end) == TRUE) {
  73. // Make the date be the end of the day, not the start
  74. $where["close[<=]"] = date("Y-m-d", strtotime($end)) . " 23:59:59";
  75. }
  76. if (count($where) > 1) {
  77. $where = ["AND" => $where];
  78. }
  79. $cash = $database->select(
  80. "cash_drawer", [
  81. '[>]registers' => ['cash_drawer.registerid' => 'registerid'],
  82. '[>]transactions' => ['cash_drawer.cashid' => 'cashid'],
  83. '[>]payments' => ['transactions.txid' => 'txid'],
  84. '[>]payment_types' => ['payments.type' => 'typeid']
  85. ], [
  86. "cash_drawer.cashid",
  87. "registers.registername",
  88. "cash_drawer.registerid",
  89. "open",
  90. "close",
  91. "payments.type",
  92. "payments.amount",
  93. "payment_types.typename"
  94. ], $where
  95. );
  96. $header = [lang("register", false), lang("open", false), lang("close", false), lang("cash", false), lang("card", false), lang("check", false), lang("crypto", false), lang("gift card", false), lang("free", false)];
  97. $out = [$header];
  98. $registers = [];
  99. foreach ($cash as $c) {
  100. $registers[$c['registerid']]['name'] = $c['registername'];
  101. $registers[$c['registerid']]['id'] = $c['registerid'];
  102. $registers[$c['registerid']]['open'] = date(DATETIME_FORMAT, strtotime($c['open']));
  103. if (is_null($c['close'])) {
  104. $registers[$c['registerid']]['close'] = date(DATETIME_FORMAT);
  105. } else {
  106. $registers[$c['registerid']]['close'] = date(DATETIME_FORMAT, strtotime($c['close']));
  107. }
  108. if (!isset($registers[$c['registerid']][$c['typename']])) {
  109. $registers[$c['registerid']][$c['typename']] = 0.0;
  110. }
  111. $registers[$c['registerid']][$c['typename']] += $c['amount'];
  112. }
  113. foreach ($registers as $r) {
  114. $types = $database->select('payment_types', 'typename');
  115. foreach ($types as $t) {
  116. if (!isset($r[$t])) {
  117. $r[$t] = 0.0;
  118. }
  119. }
  120. $out[] = [
  121. $r['name'],
  122. $r['open'],
  123. $r['close'],
  124. $r['cash'] . "",
  125. $r['card'] . "",
  126. $r['check'] . "",
  127. $r['crypto'] . "",
  128. $r['giftcard'] . "",
  129. $r['free'] . ""
  130. ];
  131. }
  132. return $out;
  133. }
  134. function getReportData($type, $register = null, $start = null, $end = null) {
  135. switch ($type) {
  136. case "cashflow":
  137. return getCashFlowReport($register, $start, $end);
  138. default:
  139. return [["error"]];
  140. }
  141. }
  142. function dataToCSV($data, $name = "report", $register = null, $start = null, $end = null) {
  143. $csv = Writer::createFromString('');
  144. $usernotice = "";
  145. $usertitle = "";
  146. $datetitle = "";
  147. if ($start != null && (bool) strtotime($start)) {
  148. $datenotice = lang2("report filtered to start date", ["date" => date(DATE_FORMAT, strtotime($start))], false);
  149. $datetitle = "_" . date(DATE_FORMAT, strtotime($start));
  150. $csv->insertOne([$datenotice]);
  151. }
  152. if ($end != null && (bool) strtotime($end)) {
  153. $datenotice = lang2("report filtered to end date", ["date" => date(DATE_FORMAT, strtotime($end))], false);
  154. $datetitle .= ($datetitle == "" ? "_" : "-") . date(DATE_FORMAT, strtotime($end));
  155. $csv->insertOne([$datenotice]);
  156. }
  157. $csv->insertAll($data);
  158. header('Content-type: text/csv');
  159. header('Content-Disposition: attachment; filename="' . $name . $usertitle . $datetitle . "_" . date("Y-m-d_Hi") . ".csv" . '"');
  160. echo $csv;
  161. die();
  162. }
  163. function dataToODS($data, $name = "report", $register = null, $start = null, $end = null) {
  164. $ods = new ods();
  165. $styleColumn = new odsStyleTableColumn();
  166. $styleColumn->setUseOptimalColumnWidth(true);
  167. $headerstyle = new odsStyleTableCell();
  168. $headerstyle->setFontWeight("bold");
  169. $table = new odsTable($name);
  170. for ($i = 0; $i < count($data[0]); $i++) {
  171. $table->addTableColumn(new odsTableColumn($styleColumn));
  172. }
  173. $usernotice = "";
  174. $usertitle = "";
  175. $datetitle = "";
  176. if ($user != null && array_key_exists('username', $user) && array_key_exists('name', $user)) {
  177. $usernotice = lang2("report filtered to user", ["name" => $user['name'], "username" => $user['username']], false);
  178. $usertitle = "_" . $user['username'];
  179. $row = new odsTableRow();
  180. $row->addCell(new odsTableCellString($usernotice));
  181. $table->addRow($row);
  182. }
  183. if ($start != null && (bool) strtotime($start)) {
  184. $datenotice = lang2("report filtered to start date", ["date" => date(DATE_FORMAT, strtotime($start))], false);
  185. $datetitle = "_" . date(DATE_FORMAT, strtotime($start));
  186. $row = new odsTableRow();
  187. $row->addCell(new odsTableCellString($datenotice));
  188. $table->addRow($row);
  189. }
  190. if ($end != null && (bool) strtotime($end)) {
  191. $datenotice = lang2("report filtered to end date", ["date" => date(DATE_FORMAT, strtotime($end))], false);
  192. $datetitle .= ($datetitle == "" ? "_" : "-") . date(DATE_FORMAT, strtotime($end));
  193. $row = new odsTableRow();
  194. $row->addCell(new odsTableCellString($datenotice));
  195. $table->addRow($row);
  196. }
  197. $rowid = 0;
  198. foreach ($data as $datarow) {
  199. $row = new odsTableRow();
  200. foreach ($datarow as $cell) {
  201. if ($rowid == 0) {
  202. $row->addCell(new odsTableCellString($cell, $headerstyle));
  203. } else {
  204. $row->addCell(new odsTableCellString($cell));
  205. }
  206. }
  207. $table->addRow($row);
  208. $rowid++;
  209. }
  210. $ods->addTable($table);
  211. $ods->downloadOdsFile($name . $usertitle . $datetitle . "_" . date("Y-m-d_Hi") . ".ods");
  212. }
  213. function dataToHTML($data, $name = "report", $register = null, $start = null, $end = null) {
  214. global $SECURE_NONCE;
  215. // HTML exporter doesn't like null values
  216. for ($i = 0; $i < count($data); $i++) {
  217. for ($j = 0; $j < count($data[$i]); $j++) {
  218. if (is_null($data[$i][$j])) {
  219. $data[$i][$j] = '';
  220. }
  221. }
  222. }
  223. $datenotice = "";
  224. $datetitle = "";
  225. if ($start != null && (bool) strtotime($start)) {
  226. $datenotice = "<span>" . lang2("report filtered to start date", ["date" => date(DATE_FORMAT, strtotime($start))], false) . "</span><br />";
  227. $datetitle = "_" . date(DATE_FORMAT, strtotime($start));
  228. }
  229. if ($end != null && (bool) strtotime($end)) {
  230. $datenotice .= "<span>" . lang2("report filtered to end date", ["date" => date(DATE_FORMAT, strtotime($end))], false) . "</span><br />";
  231. $datetitle .= ($datetitle == "" ? "_" : "-") . date(DATE_FORMAT, strtotime($end));
  232. }
  233. header('Content-type: text/html');
  234. $converter = new HTMLConverter();
  235. $out = "<!DOCTYPE html>\n"
  236. . "<meta charset=\"utf-8\">\n"
  237. . "<meta name=\"viewport\" content=\"width=device-width\">\n"
  238. . "<title>" . $name . $datetitle . "_" . date("Y-m-d_Hi") . "</title>\n"
  239. . <<<STYLE
  240. <style nonce="$SECURE_NONCE">
  241. .table-csv-data {
  242. border-collapse: collapse;
  243. }
  244. .table-csv-data tr:first-child {
  245. font-weight: bold;
  246. }
  247. .table-csv-data tr td {
  248. border: 1px solid black;
  249. }
  250. </style>
  251. STYLE
  252. . $datenotice
  253. . $converter->convert($data);
  254. echo $out;
  255. }
  256. function generateReport($type, $format, $register = null, $start = null, $end = null, $deleted = true) {
  257. $data = getReportData($type, $register, $start, $end, $deleted);
  258. switch ($format) {
  259. case "ods":
  260. dataToODS($data, $type, $register, $start, $end);
  261. break;
  262. case "html":
  263. dataToHTML($data, $type, $register, $start, $end);
  264. break;
  265. case "csv":
  266. default:
  267. echo dataToCSV($data, $type, $register, $start, $end);
  268. break;
  269. }
  270. }