diff --git a/action.php b/action.php index 0a4be2c..4e2f24b 100644 --- a/action.php +++ b/action.php @@ -8,6 +8,7 @@ * Make things happen when buttons are pressed and forms submitted. */ require_once __DIR__ . "/required.php"; +require_once __DIR__ . "/lib/userinfo.php"; if ($VARS['action'] !== "signout") { dieifnotloggedin(); @@ -30,6 +31,166 @@ function returnToSender($msg, $arg = "") { } switch ($VARS['action']) { + case "finish_transaction": + header("Content-Type: application/json"); + $items = $VARS['items']; + $payments = $VARS['payments']; + $customer = $VARS['customer']; + $register = $VARS['register']; + + if ($customer != "" && !$database->has('customers', ['customerid' => $customer])) { + exit(json_encode(["status" => "ERROR", "message" => lang("invalid customer", false)])); + // exit(json_encode(["status" => "ERROR", "message" => lang("", false)])); + } + if ($register != "" && !$database->has('registers', ['registerid' => $register])) { + exit(json_encode(["status" => "ERROR", "message" => lang("invalid register", false)])); + } + if ($register != "" && !$database->has('cash_drawer', ['AND' => ['registerid' => $register, 'close' => null]])) { + exit(json_encode(["status" => "ERROR", "message" => lang("cash not open", false)])); + } + + $totalcharge = 0.00; + $totalpaid = 0.00; + foreach ($items as $i) { + $totalcharge += $i['each'] * $i['qty']; + if (!$binstack->has('items', ['itemid' => $i['id']])) { + exit(json_encode(["status" => "ERROR", "message" => lang("invalid item", false)])); + } + } + foreach ($payments as $p) { + if (!$database->has('payment_types', ['typename' => $p['type']])) { + exit(json_encode(["status" => "ERROR", "message" => lang("invalid payment type", false)])); + } + $totalpaid += $p['amount']; + if ($p['type'] == "giftcard") { + if (!$database->has('certificates', ['AND' => ['amount[>=]' => $p['amount'], 'deleted[!]' => 1, 'certcode' => $p['code']]])) { + exit(json_encode(["status" => "ERROR", "message" => lang("invalid giftcard", false)])); + } + } + } + + if ($totalcharge > $totalpaid) { + exit(json_encode(["status" => "ERROR", "message" => lang("insufficient payment", false)])); + } + + $cashid = null; + if ($register != "") { + $cashid = $database->get('cash_drawer', 'cashid', ['AND' => ['registerid' => $register, 'close' => null]]); + } + + $database->insert('transactions', [ + 'txdate' => date('Y-m-d H:i:s'), + 'customerid' => ($customer != "" ? $customer : null), + 'type' => 1, + 'cashier' => $_SESSION['uid'], + 'cashid' => $cashid + ]); + $txid = $database->id(); + + foreach ($items as $i) { + $itemname = $binstack->get('items', 'name', ['itemid' => $i['id']]); + $database->insert('lines', [ + 'txid' => $txid, + 'amount' => $i['each'], + 'name' => $itemname, + 'itemid' => $i['id'], + 'qty' => $i['qty'] + ]); + } + + foreach ($payments as $p) { + $certid = null; + if ($p['type'] == "giftcard") { + $certid = $database->get('certificates', 'certid', ['certcode' => $p['code']]); + } + $type = $database->get('payment_types', 'typeid', ['typename' => $p['type']]); + $database->insert('payments', [ + 'amount' => $p['amount'], + 'data' => '', + 'type' => $type, + 'txid' => $txid, + 'certid' => $certid + ]); + } + + exit(json_encode(["status" => "OK", "txid" => $txid])); + + break; + case "getreceipt": + header("Content-Type: text/html"); + if (!$database->has('transactions', ['txid' => $VARS['txid']])) { + exit(json_encode(["status" => "ERROR", "txid" => null])); + } + + $tx = $database->get('transactions', ['txid', 'txdate', 'customerid', 'type', 'cashier'], ['txid' => $VARS['txid']]); + + $txid = $tx['txid']; + $datetime = date(DATETIME_FORMAT, strtotime($tx['txdate'])); + $type = $tx['type']; + $cashier = getUserByID($tx['cashier'])['name']; + $customerid = $tx['customerid']; + $customerline = (is_null($customerid) ? "" : "
Customer: $customerid"); + + $itemhtml = ""; + $items = $database->select('lines', ['amount', 'name', 'itemid', 'qty'], ['txid' => $txid]); + $total = 0.0; + foreach ($items as $i) { + $itemhtml .= "\n"; + $itemhtml .= '
'; + $itemhtml .= '
' . $i['name'] . '
'; + $itemhtml .= '
$' . $i['amount'] . '
'; + $itemhtml .= '
x' . $i['qty'] . '
'; + $itemhtml .= '
$' . ($i['qty'] * $i['amount']) . '
'; + $itemhtml .= '
'; + $total += ($i['qty'] * $i['amount']); + } + + $paymenthtml = ""; + $payments = $database->select('payments', [ + '[>]payment_types' => ['type' => 'typeid'] + ], [ + 'amount', 'type', 'typename', 'text' + ], [ + 'txid' => $txid + ]); + foreach ($payments as $p) { + $paymenthtml .= "\n"; + $paymenthtml .= '
'; + $paymenthtml .= '
' . lang($p['text'], false) . '
'; + $paymenthtml .= '
$' . $p['amount'] . '
'; + $paymenthtml .= '
'; + } + + + $html = << + + +Tx #$txid + +
+Date: $datetime
+Tx. ID: $txid
+Cashier: $cashier +$customerline +
+
+$itemhtml +
+
+
+$paymenthtml +
+
+Total: $$total +END; + exit($html); + break; case "itemsearch": header("Content-Type: application/json"); if (!is_empty($VARS['q'])) { @@ -52,6 +213,18 @@ switch ($VARS['action']) { ], $where); $items = (count($items) > 0 ? $items : false); exit(json_encode(["status" => "OK", "items" => $items])); + case "giftcard_lookup": + header("Content-Type: application/json"); + $code = $VARS['code']; + if (empty($code)) { + exit(json_encode(["status" => "ERROR", "cards" => []])); + } + $cards = $database->select('certificates', ['certid (id)', 'certcode (code)', 'amount (balance)', 'start_amount (amount)'], ['certcode' => $code]); + exit(json_encode(["status" => "OK", "cards" => $cards])); + break; + case "session_keepalive": + header("Content-Type: application/json"); + exit(json_encode(["status" => "OK"])); case "signout": session_destroy(); header('Location: index.php'); diff --git a/database.mwb b/database.mwb index 5d68632..985c57c 100644 Binary files a/database.mwb and b/database.mwb differ diff --git a/lang/en_us.php b/lang/en_us.php index b03d6dc..2ef708f 100644 --- a/lang/en_us.php +++ b/lang/en_us.php @@ -31,5 +31,18 @@ define("STRINGS", [ "home" => "Home", "point of sale" => "Point of Sale", "barcode" => "Barcode", - "barcode or search" => "Barcode or Search" + "barcode or search" => "Barcode or Search", + "cash" => "Cash", + "check" => "Check", + "card" => "Card", + "crypto" => "Crypto", + "gift card" => "Gift Card", + "free" => "Free", + "paid" => "Paid", + "owed" => "Owed", + "change" => "Change", + "enter payment" => "Enter Payment", + "receipt" => "Receipt", + "close" => "Close", + "print" => "Print", ]); \ No newline at end of file diff --git a/pages.php b/pages.php index 53a85e1..8cfa00d 100644 --- a/pages.php +++ b/pages.php @@ -15,8 +15,14 @@ define("PAGES", [ "title" => "point of sale", "navbar" => true, "icon" => "far fa-money-bill-alt", + "styles" => [ + "static/css/pos.css", + ], "scripts" => [ "static/js/bsalert.js", + "static/js/pos_items.js", + "static/js/pos_payment.js", + "static/js/pos_finish.js", "static/js/pos.js", ] ], diff --git a/pages/pos.php b/pages/pos.php index 2e2adc0..fb9a70e 100644 --- a/pages/pos.php +++ b/pages/pos.php @@ -5,6 +5,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ?> + +
@@ -20,7 +40,7 @@
-
+
@@ -29,7 +49,46 @@
-
$0.00
+
$0.00
+
+ +
+
+
+
+ select('payment_types', ['typeid (id)', 'typename (name)', 'icon', 'text']); + foreach ($payment_methods as $data) { + ?> +
+ + +
+ +
+
+
+
+
+ $ +
+
+ $0.00 +
+
+ $0.00 +
+
+
+ +
+ +
+ +
+
\ No newline at end of file diff --git a/required.php b/required.php index 144398d..e1585ba 100644 --- a/required.php +++ b/required.php @@ -39,7 +39,7 @@ if ($_SESSION['mobile'] === TRUE) { . "object-src 'none'; " . "img-src * data:; " . "media-src 'self'; " - . "frame-src 'none'; " + . "frame-src 'self'; " . "font-src 'self'; " . "connect-src *; " . "style-src 'self' 'unsafe-inline' $captcha_server; " @@ -50,7 +50,7 @@ if ($_SESSION['mobile'] === TRUE) { . "object-src 'none'; " . "img-src * data:; " . "media-src 'self'; " - . "frame-src 'none'; " + . "frame-src 'self'; " . "font-src 'self'; " . "connect-src *; " . "style-src 'self' 'nonce-$SECURE_NONCE' $captcha_server; " diff --git a/settings.template.php b/settings.template.php index e149500..3868736 100644 --- a/settings.template.php +++ b/settings.template.php @@ -40,6 +40,9 @@ define("PORTAL_KEY", "123"); // For supported values, see http://php.net/manual/en/timezones.php define("TIMEZONE", "America/Denver"); +define("DATETIME_FORMAT", "M j Y g:i A"); // 12 hour time +#define("DATETIME_FORMAT", "M j Y G:i"); // 24 hour time + // Base URL for site links. define('URL', '.'); diff --git a/static/css/app.css b/static/css/app.css index a887292..2f6f367 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -52,18 +52,4 @@ body { .footer { margin-top: 10em; text-align: center; -} - -input[type="number"]::-webkit-outer-spin-button, -input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -input[type="number"] { - -moz-appearance: textfield; -} - -#pos-lines-box { - max-height: calc(100vh - 200px); - overflow-y: scroll; } \ No newline at end of file diff --git a/static/css/pos.css b/static/css/pos.css new file mode 100644 index 0000000..bec682a --- /dev/null +++ b/static/css/pos.css @@ -0,0 +1,28 @@ +/* +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/. +*/ + +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +input[type="number"] { + -moz-appearance: textfield; +} + +#pos-lines-box { + max-height: calc(100vh - 150px); + overflow-y: auto; +} + +.payment-method-button { + cursor: pointer; +} + +#receiptframe { + height: 50vh; + border: 0; +} \ No newline at end of file diff --git a/static/js/bsalert.js b/static/js/bsalert.js index ced2c14..1df3af3 100644 --- a/static/js/bsalert.js +++ b/static/js/bsalert.js @@ -4,6 +4,49 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +function bsalert(title, message, okbtn, cancelbtn, callback) { + var html = ''; + $("body").append(html); + $("#bsalert-title").text(title); + $("#bsalert-message").text(message); + if (typeof okbtn != "string") { + okbtn = "OK"; + } + $("#bsalert-ok").text(okbtn); + if (typeof cancelbtn != "string") { + cancelbtn = "Cancel"; + } + $("#bsalert-cancel").text(cancelbtn); + $("#bsalert-ok").on("click", function () { + if (typeof callback != 'undefined') { + callback(); + } + $("#bsalert").modal("hide"); + }); + $("#bsalert").on("hidden.bs.modal", function () { + $("#bsalert").remove(); + }); + $("#bsalert").modal("show"); +} + function bsprompt(title, message, okbtn, cancelbtn, type, callback) { var html = '