From a07820c666b00bc01bf0779d701288cdf9dd9a7a Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Tue, 5 Jun 2018 13:34:14 -0600 Subject: [PATCH] Add refunds, close #14 --- action.php | 101 +++++++++++++++++++ lang/en_us.php | 4 + lang/messages.php | 6 +- lib/generatereceipt.php | 78 ++++++++++++++- pages/pos.php | 189 +++++++++++++++++++++++------------- static/js/pos.js | 3 + static/js/pos_finish.js | 70 ++++++++++++- static/js/pos_management.js | 12 ++- 8 files changed, 390 insertions(+), 73 deletions(-) diff --git a/action.php b/action.php index 6f589ef..4ef89d1 100644 --- a/action.php +++ b/action.php @@ -200,6 +200,107 @@ switch ($VARS['action']) { return true; }); + if (!is_null($error)) { + exit(json_encode(["status" => "ERROR", "message" => $error])); + } else { + exit(json_encode(["status" => "OK", "txid" => $oktx])); + } + break; + case "finish_return": + header("Content-Type: application/json"); + $error = null; + $oktx = null; + $database->action(function ($database) { + global $VARS, $binstack, $error, $oktx; + + $items = $VARS['items']; + $payments = $VARS['payments']; + $customer = $VARS['customer']; + $register = $VARS['register']; + $cashid = null; + + if ($customer != "" && !$database->has('customers', ['customerid' => $customer])) { + $error = lang("invalid customer", false); + return false; + } + if ($register != "" && !$database->has('registers', ['registerid' => $register])) { + $error = lang("invalid register", false); + return false; + } + if ($register != "" && !$database->has('cash_drawer', ['AND' => ['registerid' => $register, 'close' => null]])) { + $error = lang("cash not open", false); + return false; + } + + if ($register != "") { + $cashid = $database->get('cash_drawer', 'cashid', ['AND' => ['registerid' => $register, 'close' => null]]); + } + + $totaldue = 0.00; + $totalrefund = 0.00; + foreach ($items as $i) { + $totaldue += $i['each'] * $i['qty']; + if (!$binstack->has('items', ['itemid' => $i['id']])) { + $error = lang("invalid item", false); + return false; + } + } + foreach ($payments as $p) { + if (!$database->has('payment_types', ['typename' => $p['type']])) { + $error = lang("invalid payment type", false); + return false; + } + $totalrefund += $p['amount']; + if ($p['type'] == "giftcard") { + if (!$database->has('certificates', ['AND' => ['amount[>=]' => $p['amount'], 'deleted[!]' => 1, 'certcode' => $p['code']]])) { + $error = lang("invalid giftcard", false); + return false; + } + } + } + + $database->insert('transactions', [ + 'txdate' => date('Y-m-d H:i:s'), + 'customerid' => ($customer != "" ? $customer : null), + 'type' => 2, + 'cashier' => $_SESSION['uid'], + 'cashid' => $cashid, + 'discountpercent' => 0.0 + ]); + $txid = $database->id(); + + foreach ($items as $i) { + $item = $binstack->get('items', ['name', 'qty'], ['itemid' => $i['id']]); + + $database->insert('lines', [ + 'txid' => $txid, + 'amount' => $i['each'], + 'name' => $item['name'], + 'itemid' => $i['id'], + 'qty' => $i['qty'] * -1.0 + ]); + } + + foreach ($payments as $p) { + $certid = null; + if ($p['type'] == "giftcard") { + $certid = $database->get('certificates', 'certid', ['certcode' => $p['code']]); + $database->update('certificates', ['amount[+]' => $p['amount']], ['certid' => $certid]); + } + $type = $database->get('payment_types', 'typeid', ['typename' => $p['type']]); + $database->insert('payments', [ + 'amount' => $p['amount'] * -1.0, + 'data' => '', + 'type' => $type, + 'txid' => $txid, + 'certid' => $certid + ]); + } + + $oktx = $txid; + return true; + }); + if (!is_null($error)) { exit(json_encode(["status" => "ERROR", "message" => $error])); } else { diff --git a/lang/en_us.php b/lang/en_us.php index 1dd1ebe..274ad8a 100644 --- a/lang/en_us.php +++ b/lang/en_us.php @@ -121,4 +121,8 @@ define("STRINGS", [ "cash already closed" => "Cash already closed, cannot edit this transaction. Process a return instead.", "update" => "Update", "transaction search" => "Search transactions (by Tx ID or customer)", + "return" => "Return", + "enter refund" => "Enter Refund", + "refund" => "Refund", + "cannot edit return transaction" => "Cannot edit a return transaction." ]); diff --git a/lang/messages.php b/lang/messages.php index a378b6e..7336b33 100644 --- a/lang/messages.php +++ b/lang/messages.php @@ -48,5 +48,9 @@ define("MESSAGES", [ "register_name_taken" => [ "string" => "register name taken", "type" => "danger" - ] + ], + "return_transaction_no_edit" => [ + "string" => "cannot edit return transaction", + "type" => "danger" + ], ]); diff --git a/lib/generatereceipt.php b/lib/generatereceipt.php index 4ffbcfd..e68c26a 100644 --- a/lib/generatereceipt.php +++ b/lib/generatereceipt.php @@ -14,7 +14,7 @@ class GenerateReceipt { const RECEIPT_TYPE_X = 2; const RECEIPT_TYPE_Z = 3; - static function transactionReceipt($transaction) { + private static function saleReceipt($transaction) { global $database; $receipt = new Receipt(); $tx = $database->get('transactions', ['txid', 'txdate', 'customerid', 'type', 'cashier', 'discountpercent'], ['txid' => $transaction]); @@ -90,6 +90,82 @@ class GenerateReceipt { return $receipt; } + private static function returnReceipt($transaction) { + global $database; + $receipt = new Receipt(); + $tx = $database->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) { + $paymentlines[] = new ReceiptLine(lang($p['text'], false), "", '$' . number_format($p['amount'] * -1.0, 2)); + $paid += $p['amount'] * 1.0; + } + + // Totals + $subtotalline = new ReceiptLine("Subtotal:", "", '$' . number_format($subtotal, 2)); + $paidline = new ReceiptLine("Refund:", "", '$' . number_format($paid * -1.0, 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); + $receipt->appendLine($totalline); + $receipt->appendBreak(); + $receipt->appendLines($paymentlines); + $receipt->appendBreak(); + $receipt->appendLine($paidline); + + return $receipt; + } + + static function transactionReceipt($transaction) { + global $database; + $txtype = $database->get('transactions', 'type', ['txid' => $transaction]); + switch ($txtype) { + case 1: + return GenerateReceipt::saleReceipt($transaction); + case 2: + return GenerateReceipt::returnReceipt($transaction); + default: + return new Receipt(); + } + } + static function xReceipt($registerid) { global $database; $receipt = new Receipt(); diff --git a/pages/pos.php b/pages/pos.php index ddc2ace..1133329 100644 --- a/pages/pos.php +++ b/pages/pos.php @@ -22,12 +22,22 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$registeropen) { $items = []; $payments = []; $editing = false; + $returning = false; if (isset($VARS['txid']) && $database->has('transactions', ['txid' => $VARS['txid']])) { - $editing = true; - $items = $database->select('lines', ['lineid', 'amount', 'name', 'itemid', 'qty'], ['txid' => $VARS['txid']]); - $payments = $database->select('payments', ['[>]certificates' => 'certid', '[>]payment_types' => ['type' => 'typeid']], ['payments.amount', 'typename', 'icon', 'text', 'certcode'], ['txid' => $VARS['txid']]); - $tx = $database->get('transactions', ['[>]customers' => 'customerid'], ['txid', 'discountpercent', 'transactions.customerid', 'customers.name (customername)'], ['txid' => $VARS['txid']]); - echo ""; + $tx = $database->get('transactions', ['[>]customers' => 'customerid'], ['txid', 'discountpercent', 'transactions.customerid', 'customers.name (customername)', 'cashid', 'type'], ['txid' => $VARS['txid']]); + if ($tx['type'] != 1) { + header('Location: app.php?page=pos&msg=return_transaction_no_edit'); + die(); + } + $items = $database->select('lines', ['lineid', 'amount', 'name', 'itemid', 'qty'], ['txid' => $tx['txid']]); + if ($database->has('cash_drawer', ['AND' => ['cashid' => $tx['cashid'], 'open[!]' => null, 'close' => null]])) { + $editing = true; + $payments = $database->select('payments', ['[>]certificates' => 'certid', '[>]payment_types' => ['type' => 'typeid']], ['payments.amount', 'typename', 'icon', 'text', 'certcode'], ['txid' => $tx['txid']]); + echo ""; + } else { + $returning = true; + echo ""; + } } ?> @@ -141,13 +151,20 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$registeropen) {
- $ + $
@@ -156,7 +173,7 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$registeropen) {
$
- +
-
+
"> + + + +
+ "> - - - -
+ ?>
- +
select('payment_types', ['typeid (id)', 'typename (name)', 'icon', 'text']); + $hideforreturns = ['check', 'free']; foreach ($payment_methods as $data) { + if ($returning && in_array($data['name'], $hideforreturns)) { + continue; + } ?>
@@ -236,57 +269,77 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$registeropen) {

-
- $ -
-
- $0.00 -
-
- $0.00 -
+ +
+ $ +
+
+ $0.00 +
+
+ $0.00 +
+ +
+ $ +
+
+ $0.00 +
+
+ $0.00 +
+
-
-
-
- - - - -
-
- - $ - -
- - -
+ if ($editing) { + foreach ($payments as $p) { + if ($p['amount'] <= 0) { + continue; + } + ?> +
+
+
+ + + + +
+
- # + $
- + -
- - - + if ($p['typename'] == 'giftcard') { + ?> +
+ + # + +
+ + +
+ + + +
-
- @@ -296,6 +349,8 @@ if (isset($_GET['switch']) || !isset($_SESSION['register']) || !$registeropen) {
0) { - bsalert("Incomplete Transaction", "The customer still owes $" + owed.toFixed(2) + ". Add a payment or remove items until everything is paid for."); + if ($("#return").length) { + if (owed > 0) { + bsalert("Incomplete Transaction", "The customer is still owed $" + owed.toFixed(2) + ". Add a payment or remove items until the customer is not owed anything."); + } else if ($("#change-amount").text() * 1.0 > 0) { + bsalert("Incomplete Transaction", "The customer would need to pay you $" + ($("#change-amount").text() * 1.0).toFixed(2) + " for the refund. Adjust payments until the change is zero."); + } else { + sendReturnToServer(function (data) { + showReceipt(data.txid); + }); + } } else { - finishTransaction(); + if (owed > 0) { + bsalert("Incomplete Transaction", "The customer still owes $" + owed.toFixed(2) + ". Add a payment or remove items until everything is paid for."); + } else { + finishTransaction(); + } } }); diff --git a/static/js/pos_management.js b/static/js/pos_management.js index 5e2247e..62da8d4 100644 --- a/static/js/pos_management.js +++ b/static/js/pos_management.js @@ -36,8 +36,11 @@ function showTransactionList(search) { if (data['transactions'][i]['cashier']['name'] != "") { cashiername = ' ' + data['transactions'][i]['cashier']['name']; } + buttons += ' Receipt '; if (data['transactions'][i]['editable'] === true) { - buttons += ' Edit'; + buttons += ' Edit'; + } else { + buttons += ' Return'; } html += '
' + '
' + buttons + '
' @@ -64,4 +67,11 @@ $("#transactionsearch").on('keypress', function (e) { $("#transactionsearchbtn").on("click", function () { showCustomerList($("#transactionsearch").val()); $("#transactionsearch").val(""); +}); + +$("#managermodal").on("click", ".printreceiptbtn", function () { + $("#managermodal").modal("hide"); + $("#receiptchangediv").addClass("d-none"); + $("#receiptframe").attr("src", 'action.php?action=getreceipt&txid=' + $(this).data("txid")); + $("#receiptmodal").modal(); }); \ No newline at end of file