From 2dd2374b12216704c2c85b6b2f0d5b7da915ea86 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Sat, 2 Jun 2018 19:07:37 -0600 Subject: [PATCH] Add X report (closes #2) --- action.php | 120 +++++----------------- lang/en_us.php | 6 +- lib/generatereceipt.php | 192 ++++++++++++++++++++++++++++++++++++ lib/receipts.php | 14 +++ nbproject/customs.json | 11 +++ pages.php | 1 + pages/pos.php | 33 ++++++- static/css/pos.css | 7 +- static/js/pos_finish.js | 2 +- static/js/pos_management.js | 15 +++ 10 files changed, 298 insertions(+), 103 deletions(-) create mode 100644 lib/generatereceipt.php create mode 100644 nbproject/customs.json create mode 100644 static/js/pos_management.js diff --git a/action.php b/action.php index b5fcae0..f99f738 100644 --- a/action.php +++ b/action.php @@ -144,114 +144,23 @@ switch ($VARS['action']) { break; case "getreceipt": - require_once __DIR__ . "/lib/receipts.php"; + require_once __DIR__ . "/lib/generatereceipt.php"; $format = "html"; $width = 64; if (isset($VARS['width']) && preg_match("/[0-9]+/", $VARS['width']) && (int) $VARS['width'] > 0) { $width = (int) $VARS['width']; } if (isset($VARS['format'])) { - switch ($VARS['format']) { - case "text": - $format = "text"; - header("Content-Type: text/plain"); - break; - case "json": - $format = "json"; - header("Content-Type: application/json"); - break; - default: - $format = "html"; - header("Content-Type: text/html"); - } + $format = $VARS['format']; } if (!$database->has('transactions', ['txid' => $VARS['txid']])) { header("Content-Type: application/json"); exit(json_encode(["status" => "ERROR", "txid" => null])); } - $receipt = new Receipt(); - $tx = $database->get('transactions', ['txid', 'txdate', 'customerid', 'type', 'cashier', 'discountpercent'], ['txid' => $VARS['txid']]); - // Info - $txid = $tx['txid']; - $datetime = date(DATETIME_FORMAT, strtotime($tx['txdate'])); - $type = $tx['type']; - $cashier = getUserByID($tx['cashier'])['name']; - $customerid = $tx['customerid']; - - // Items - $itemlines = []; - $items = $database->select('lines', ['amount', 'name', 'itemid', 'qty'], ['txid' => $txid]); - $subtotal = 0.0; - $paid = 0.0; - foreach ($items as $i) { - $itemlines[] = new ReceiptLine( - $i['name'], (float) $i['qty'] . '@' . number_format($i['amount'], 2), '$' . number_format($i['qty'] * $i['amount'] * 1.0, 2) - ); - $subtotal += $i['qty'] * $i['amount'] * 1.0; - } - // Payments - $total = $subtotal * (1.0 - ((float) $tx['discountpercent'] / 100)); - $paymentlines = []; - $payments = $database->select('payments', [ - '[>]payment_types' => ['type' => 'typeid'] - ], [ - 'amount', 'type', 'typename', 'text' - ], [ - 'txid' => $txid - ]); - foreach ($payments as $p) { - if ($p['amount'] < 0) { - continue; - } - $paymentlines[] = new ReceiptLine(lang($p['text'], false), "", '$' . number_format($p['amount'] * 1.0, 2)); - $paid += $p['amount'] * 1.0; - } - $change = $paid - $total; - if ($change <= 0) { - $change = 0.0; - } - - // Totals - $subtotalline = new ReceiptLine("Subtotal:", "", '$' . number_format($subtotal, 2)); - $paidline = new ReceiptLine("Paid:", "", '$' . number_format($paid, 2), ReceiptLine::LINEFORMAT_BOLD); - $changeline = new ReceiptLine("Change:", "", '$' . number_format($change, 2), ReceiptLine::LINEFORMAT_BOLD); - $totalline = new ReceiptLine("Total:", "", '$' . number_format($subtotal, 2), ReceiptLine::LINEFORMAT_BOLD); - - $receipt->appendLine(new ReceiptLine("Date: $datetime")); - $receipt->appendLine(new ReceiptLine("Tx. ID: $txid")); - $receipt->appendLine(new ReceiptLine("Cashier: $cashier")); - if (!is_null($customerid) && !empty($customerid)) { - $customer = $database->get('customers', 'name', ['customerid' => $customerid]); - $receipt->appendLine(new ReceiptLine("Customer: $customer")); - } - $receipt->appendBreak(); - $receipt->appendLines($itemlines); - $receipt->appendBreak(); - $receipt->appendLine($subtotalline); - if ($tx['discountpercent'] > 0) { - $receipt->appendLine(new ReceiptLine("Discount:", "", (float) $tx['discountpercent'] . '% off')); - $totalline = new ReceiptLine("Total:", "", '$' . number_format($total, 2), ReceiptLine::LINEFORMAT_BOLD); - } - $receipt->appendLine($totalline); - $receipt->appendBreak(); - $receipt->appendLines($paymentlines); - $receipt->appendBreak(); - $receipt->appendLine($paidline); - $receipt->appendLine($changeline); - - $output = ""; - switch ($format) { - case "text": - $output = $receipt->getPlainText($width); - break; - case "json": - $output = $receipt->getJson($width); - break; - default: - $output = $receipt->getHtml("Tx. #$txid"); - } - exit($output); + $receipt = GenerateReceipt::getReceipt(GenerateReceipt::RECEIPT_TYPE_TRANSACTION, $VARS['txid']); + + exit(GenerateReceipt::outputReceipt($receipt, $format, $width, "Tx. #" . $VARS['txid'])); break; case "itemsearch": header("Content-Type: application/json"); @@ -509,6 +418,25 @@ switch ($VARS['action']) { } returnToSender("register_saved"); + case "xreport": + require_once __DIR__ . "/lib/generatereceipt.php"; + $format = "html"; + $width = 64; + if (isset($VARS['width']) && preg_match("/[0-9]+/", $VARS['width']) && (int) $VARS['width'] > 0) { + $width = (int) $VARS['width']; + } + if (isset($VARS['format'])) { + $format = $VARS['format']; + } + if (!$database->has('cash_drawer', ['AND' => ['registerid' => $VARS['register'], 'open[!]' => null, 'close' => null]])) { + header("Content-Type: application/json"); + exit(json_encode(["status" => "ERROR"])); + } + + $receipt = GenerateReceipt::getReceipt(GenerateReceipt::RECEIPT_TYPE_X, null, $VARS['register']); + + exit(GenerateReceipt::outputReceipt($receipt, $format, $width, "X Report")); + break; case "session_keepalive": header("Content-Type: application/json"); exit(json_encode(["status" => "OK"])); diff --git a/lang/en_us.php b/lang/en_us.php index 4cf7179..e0ac814 100644 --- a/lang/en_us.php +++ b/lang/en_us.php @@ -111,5 +111,9 @@ define("STRINGS", [ "adding register" => "Adding register", "register saved" => "Register saved.", "register name taken" => "Register name already taken. Use a different name.", - "no open registers" => "No open cash registers. Go to the Registers page to open one." + "no open registers" => "No open cash registers. Go to the Registers page to open one.", + "register management" => "Register Management", + "manage register" => "Manage register", + "manage" => "Manage", + "x report" => "X Report", ]); diff --git a/lib/generatereceipt.php b/lib/generatereceipt.php new file mode 100644 index 0000000..a4c0066 --- /dev/null +++ b/lib/generatereceipt.php @@ -0,0 +1,192 @@ +get('transactions', ['txid', 'txdate', 'customerid', 'type', 'cashier', 'discountpercent'], ['txid' => $transaction]); + // Info + $txid = $tx['txid']; + $datetime = date(DATETIME_FORMAT, strtotime($tx['txdate'])); + $type = $tx['type']; + $cashier = getUserByID($tx['cashier'])['name']; + $customerid = $tx['customerid']; + + // Items + $itemlines = []; + $items = $database->select('lines', ['amount', 'name', 'itemid', 'qty'], ['txid' => $txid]); + $subtotal = 0.0; + $paid = 0.0; + foreach ($items as $i) { + $itemlines[] = new ReceiptLine( + $i['name'], (float) $i['qty'] . '@' . number_format($i['amount'], 2), '$' . number_format($i['qty'] * $i['amount'] * 1.0, 2) + ); + $subtotal += $i['qty'] * $i['amount'] * 1.0; + } + + // Payments + $total = $subtotal * (1.0 - ((float) $tx['discountpercent'] / 100)); + $paymentlines = []; + $payments = $database->select('payments', [ + '[>]payment_types' => ['type' => 'typeid'] + ], [ + 'amount', 'type', 'typename', 'text' + ], [ + 'txid' => $txid + ]); + foreach ($payments as $p) { + if ($p['amount'] < 0) { + continue; + } + $paymentlines[] = new ReceiptLine(lang($p['text'], false), "", '$' . number_format($p['amount'] * 1.0, 2)); + $paid += $p['amount'] * 1.0; + } + $change = $paid - $total; + if ($change <= 0) { + $change = 0.0; + } + + // Totals + $subtotalline = new ReceiptLine("Subtotal:", "", '$' . number_format($subtotal, 2)); + $paidline = new ReceiptLine("Paid:", "", '$' . number_format($paid, 2), ReceiptLine::LINEFORMAT_BOLD); + $changeline = new ReceiptLine("Change:", "", '$' . number_format($change, 2), ReceiptLine::LINEFORMAT_BOLD); + $totalline = new ReceiptLine("Total:", "", '$' . number_format($subtotal, 2), ReceiptLine::LINEFORMAT_BOLD); + + $receipt->appendLine(new ReceiptLine("Date: $datetime")); + $receipt->appendLine(new ReceiptLine("Tx. ID: $txid")); + $receipt->appendLine(new ReceiptLine("Cashier: $cashier")); + if (!is_null($customerid) && !empty($customerid)) { + $customer = $database->get('customers', 'name', ['customerid' => $customerid]); + $receipt->appendLine(new ReceiptLine("Customer: $customer")); + } + $receipt->appendBreak(); + $receipt->appendLines($itemlines); + $receipt->appendBreak(); + $receipt->appendLine($subtotalline); + if ($tx['discountpercent'] > 0) { + $receipt->appendLine(new ReceiptLine("Discount:", "", (float) $tx['discountpercent'] . '% off')); + $totalline = new ReceiptLine("Total:", "", '$' . number_format($total, 2), ReceiptLine::LINEFORMAT_BOLD); + } + $receipt->appendLine($totalline); + $receipt->appendBreak(); + $receipt->appendLines($paymentlines); + $receipt->appendBreak(); + $receipt->appendLine($paidline); + $receipt->appendLine($changeline); + + return $receipt; + } + + static function xReceipt($registerid) { + global $database; + $receipt = new Receipt(); + + $registername = $database->get('registers', 'registername', ['registerid' => $registerid]); + $cash = $database->get('cash_drawer', ['open', 'start_amount', 'cashid'], ['AND' => ['open[!]' => null, 'close' => null, 'registerid' => $registerid]]); + + $balance = []; + $paymenttypes = $database->select('payment_types', ['typename (type)', 'text']); + $payments = $database->select("payments", [ + "[>]transactions" => ['txid' => 'txid'], + "[>]payment_types" => ['type' => 'typeid'] + ], ['amount', 'typename (type)'], [ + 'AND' => [ + 'transactions.cashid' => $cash['cashid'], + ] + ]); + foreach ($paymenttypes as $t) { + $balance[$t['type']] = 0.0; + } + foreach ($payments as $p) { + $balance[$p['type']] += $p['amount']; + } + + $receipt->appendHeader(new ReceiptLine(lang("x report", false), "", "", ReceiptLine::LINEFORMAT_BOLD | ReceiptLine::LINEFORMAT_CENTER)); + + $receipt->appendLine(new ReceiptLine("Date:", "", date(DATETIME_FORMAT))); + $receipt->appendLine(new ReceiptLine("Register:", "", $registername)); + + $receipt->appendBlank(); + $receipt->appendBreak(); + $receipt->appendLine(new ReceiptLine("Opening", "", "", ReceiptLine::LINEFORMAT_CENTER)); + $receipt->appendBreak(); + $receipt->appendLine(new ReceiptLine("Date:", "", date(DATETIME_FORMAT, strtotime($cash['open'])))); + $receipt->appendLine(new ReceiptLine("Cash:", "", '$' . number_format($cash['start_amount'], 2))); + + $receipt->appendBlank(); + $receipt->appendBreak(); + $receipt->appendLine(new ReceiptLine("Sales", "", "", ReceiptLine::LINEFORMAT_CENTER)); + $receipt->appendBreak(); + foreach ($paymenttypes as $t) { + $receipt->appendLine(new ReceiptLine(lang($t['text'], false) . ":", "", '$' . number_format($balance[$t['type']], 2))); + } + + $receipt->appendBlank(); + $receipt->appendBreak(); + $receipt->appendLine(new ReceiptLine("Balance", "", "", ReceiptLine::LINEFORMAT_CENTER)); + $receipt->appendBreak(); + $receipt->appendLine(new ReceiptLine("Cash:", "", '$' . number_format($balance['cash'] + $cash['start_amount'], 2))); + + return $receipt; + } + + static function getReceipt($type, $transaction = null, $register = null, $cashid = null) { + switch ($type) { + case GenerateReceipt::RECEIPT_TYPE_X: + return GenerateReceipt::xReceipt($register); + break; + case GenerateReceipt::RECEIPT_TYPE_Y: + return GenerateReceipt::Receipt($register, $cashid); + break; + case GenerateReceipt::RECEIPT_TYPE_TRANSACTION: + return GenerateReceipt::transactionReceipt($transaction); + break; + default: + return new Receipt(); + break; + } + } + + static function outputReceipt(Receipt $receipt, $format = "html", $width = 64, $title = "") { + switch ($format) { + case "text": + $format = "text"; + header("Content-Type: text/plain"); + break; + case "json": + $format = "json"; + header("Content-Type: application/json"); + break; + default: + $format = "html"; + header("Content-Type: text/html"); + } + + $output = ""; + switch ($format) { + case "text": + $output = $receipt->getPlainText($width); + break; + case "json": + $output = $receipt->getJson($width); + break; + default: + $output = $receipt->getHtml($title); + } + return $output; + } + +} diff --git a/lib/receipts.php b/lib/receipts.php index 1374054..e3d4aa1 100644 --- a/lib/receipts.php +++ b/lib/receipts.php @@ -15,6 +15,7 @@ class ReceiptLine { const LINEFORMAT_BOLD = 2; const LINEFORMAT_HR = 4; const LINEFORMAT_CENTER = 8; + const LINEFORMAT_BLANK = 16; private $left = ""; private $middle = ""; @@ -48,6 +49,9 @@ class ReceiptLine { if ($this->hasFormat($this::LINEFORMAT_HR)) { return "
"; } + if ($this->hasFormat($this::LINEFORMAT_BLANK)) { + return "
"; + } $html = ""; if (!empty($this->left)) { $html .= '' . htmlspecialchars($this->left) . ' '; @@ -73,6 +77,9 @@ class ReceiptLine { if ($this->hasFormat($this::LINEFORMAT_HR)) { return str_repeat("-", $width); } + if ($this->hasFormat($this::LINEFORMAT_BLANK)) { + return str_repeat(" ", $width); + } $left = $this->left; $middle = $this->middle; $right = $this->right; @@ -121,6 +128,9 @@ class ReceiptLine { if ($this->hasFormat($this::LINEFORMAT_CENTER)) { $data['format'][] = "center"; } + if ($this->hasFormat($this::LINEFORMAT_BLANK)) { + $data['format'][] = "blank"; + } return $data; } @@ -158,6 +168,10 @@ class Receipt { $this->lines[] = new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_HR); } + function appendBlank() { + $this->lines[] = new ReceiptLine("", "", "", ReceiptLine::LINEFORMAT_BLANK); + } + function getHtml($title = "") { global $SECURE_NONCE; $html = << lang("no cash", false), "id" => "" ]; - -if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$database->has("registers", ['[>]cash_drawer' => ['registerid' => 'registerid']], ['AND' => ['open[!]' => null, 'close' => null, 'registerid' => $_SESSION['register']]])) { +$registeropen = $database->has("registers", ['[>]cash_drawer' => ['registerid' => 'registerid']], ['AND' => ['open[!]' => null, 'close' => null, 'registers.registerid' => $_SESSION['register']]]); +if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$registeropen) { require_once __DIR__ . "/../lib/chooseregister.php"; } else { $register = $database->get('registers', ['registerid (id)', 'registername (name)'], ['registerid' => $_SESSION['register']]); @@ -26,7 +26,7 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$database->has(" + +
@@ -98,7 +122,8 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$database->has("
-
+
$0.00
diff --git a/static/css/pos.css b/static/css/pos.css index 5931a6b..779a3b6 100644 --- a/static/css/pos.css +++ b/static/css/pos.css @@ -27,6 +27,11 @@ input[type="number"] { } #receiptframe { - height: 50vh; + height: 60vh; + border: 0; +} + +#xframe { + height: 60vh; border: 0; } \ No newline at end of file diff --git a/static/js/pos_finish.js b/static/js/pos_finish.js index 183d7a6..5fbbcdc 100644 --- a/static/js/pos_finish.js +++ b/static/js/pos_finish.js @@ -80,7 +80,7 @@ $("#finishbtn").click(function () { $("#receiptprintbtn").click(function () { document.getElementById("receiptframe").contentWindow.print(); -}) +}); $("#receiptmodal").on("hide.bs.modal", function () { window.location.reload(); diff --git a/static/js/pos_management.js b/static/js/pos_management.js new file mode 100644 index 0000000..2c223f9 --- /dev/null +++ b/static/js/pos_management.js @@ -0,0 +1,15 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + + +$("#openmanagement").click(function () { + document.getElementById("xframe").contentDocument.location.reload(true); + $("#managermodal").modal(); +}); + +$("#xprintbtn").click(function () { + document.getElementById("xframe").contentWindow.print(); +}); \ No newline at end of file