Implement in-person transaction flow (close #7)
parent
4f6ba30afe
commit
aff3a068b1
Binary file not shown.
@ -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;
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function sendTransactionToServer(callback) {
|
||||||
|
var items = [];
|
||||||
|
var payments = [];
|
||||||
|
var customer = '';
|
||||||
|
var register = '';
|
||||||
|
$("#pos-lines-box .list-group-item").each(function () {
|
||||||
|
var each = $(".item-price", this).val() * 1.0;
|
||||||
|
var qty = $(".item-qty", this).val() * 1.0;
|
||||||
|
var code = $(this).data("code");
|
||||||
|
var id = $(this).data("itemid");
|
||||||
|
items.push({
|
||||||
|
each: each,
|
||||||
|
qty: qty,
|
||||||
|
code: code,
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$("#payment-lines .list-group-item").each(function () {
|
||||||
|
var amount = $(".payment-entry", this).val() * 1.0;
|
||||||
|
var type = $(".payment-entry", this).data("type");
|
||||||
|
var code = '';
|
||||||
|
if ($(".giftcard-number", this).length) {
|
||||||
|
code = $(".giftcard-number", this).val();
|
||||||
|
}
|
||||||
|
payments.push({
|
||||||
|
amount: amount,
|
||||||
|
type: type,
|
||||||
|
code: code
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$.post("action.php", {
|
||||||
|
action: "finish_transaction",
|
||||||
|
items: items,
|
||||||
|
payments: payments,
|
||||||
|
customer: customer,
|
||||||
|
register: register
|
||||||
|
}, function (data) {
|
||||||
|
if (data.status == "OK") {
|
||||||
|
callback(data);
|
||||||
|
} else {
|
||||||
|
bsalert("Error", "The transaction could not be completed:<br />" + data.message);
|
||||||
|
}
|
||||||
|
}).fail(function () {
|
||||||
|
bsalert("Error", "The transaction could not be completed due to an unknown error.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showReceipt(txid) {
|
||||||
|
$("#receiptframe").attr("src", 'action.php?action=getreceipt&txid=' + txid);
|
||||||
|
$("#receiptmodal").modal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishTransaction() {
|
||||||
|
sendTransactionToServer(function (data) {
|
||||||
|
showReceipt(data.txid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#finishbtn").click(function () {
|
||||||
|
recalculate();
|
||||||
|
var owed = $("#owed-amount").text() * 1.0;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#receiptprintbtn").click(function () {
|
||||||
|
document.getElementById("receiptframe").contentWindow.print();
|
||||||
|
})
|
||||||
|
|
||||||
|
$("#receiptmodal").on("hide.bs.modal", function () {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function addItem(name, code, price, id) {
|
||||||
|
if ($(".list-group-item[data-code='" + code + "']").length) {
|
||||||
|
updateQty($(".list-group-item[data-code='" + code + "']").find(".qty-plus"), 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
price = (price * 1.0).toFixed(2);
|
||||||
|
$("#pos-lines-box").append('<div class="list-group-item" data-code="' + code + '" data-itemid="' + id + '">'
|
||||||
|
+ '<div class="d-flex w-100 justify-content-between mb-2">'
|
||||||
|
+ '<h5 class="item-name">'
|
||||||
|
+ name
|
||||||
|
+ '</h5>'
|
||||||
|
+ '<h5>'
|
||||||
|
+ '<small class="item-code mr-1">' + code + '</small>'
|
||||||
|
+ '<span class="badge badge-light">'
|
||||||
|
+ '$<span class="line-total">'
|
||||||
|
+ price
|
||||||
|
+ '</span>'
|
||||||
|
+ '</span>'
|
||||||
|
+ '</h5>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="d-inline-flex">'
|
||||||
|
+ '<div class="input-group qty-control">'
|
||||||
|
+ '<div class="input-group-prepend">'
|
||||||
|
+ '<span class="input-group-text pr-1"><b>$</b></span>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<input type="money" class="form-control item-price" value="' + price + '"/>'
|
||||||
|
+ '<div class="input-group-prepend">'
|
||||||
|
+ '<span class="input-group-text px-2"><i class="fas fa-times"></i></span>'
|
||||||
|
+ '<button class="btn btn-red qty-minus" type="button"><i class="fas fa-trash"></i></button>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<input type="number" class="form-control item-qty px-2" value="1" />'
|
||||||
|
+ '<div class="input-group-append">'
|
||||||
|
+ '<button class="btn btn-light-green qty-plus" type="button"><i class="fas fa-plus"></i></button>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div>');
|
||||||
|
recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function findItem(q) {
|
||||||
|
function decodeThenAddItem(item) {
|
||||||
|
var code = item.code1;
|
||||||
|
console.log(code);
|
||||||
|
if (code == "" && item["code2"] != "") {
|
||||||
|
code = item["code2"];
|
||||||
|
} else if (code == "") {
|
||||||
|
code = "---";
|
||||||
|
}
|
||||||
|
var price = item['price'];
|
||||||
|
if (price == null || price == "" || price == 0) {
|
||||||
|
if (!$(".list-group-item[data-code='" + code + "']").length) {
|
||||||
|
bsprompt("Enter Price",
|
||||||
|
"No price set. Enter a price for this item:",
|
||||||
|
"Add Item",
|
||||||
|
"Cancel",
|
||||||
|
"number",
|
||||||
|
function (result) {
|
||||||
|
addItem(item['name'], code, result, item['id']);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addItem(item['name'], code, price, item['id']);
|
||||||
|
}
|
||||||
|
if (q == "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.get("action.php", {
|
||||||
|
action: "itemsearch",
|
||||||
|
q: q
|
||||||
|
}, function (data) {
|
||||||
|
if (data['items'].length == 1) {
|
||||||
|
decodeThenAddItem(data['items'][0]);
|
||||||
|
} else if (data['items'].length > 1) {
|
||||||
|
var options = [];
|
||||||
|
for (var i = 0; i < data['items'].length; i++) {
|
||||||
|
var text = data['items'][i]['name'];
|
||||||
|
if (data['items'][i]['price'] != null) {
|
||||||
|
text += " <span class=\"ml-auto\">$" + data['items'][i]['price'] + "</span>";
|
||||||
|
}
|
||||||
|
options.push({"text": text, "val": data['items'][i]['id']});
|
||||||
|
}
|
||||||
|
bschoices(
|
||||||
|
"Multiple Results",
|
||||||
|
"More than one item match the query. Pick the correct one:",
|
||||||
|
options,
|
||||||
|
"Cancel",
|
||||||
|
function (result) {
|
||||||
|
for (var i = 0; i < data['items'].length; i++) {
|
||||||
|
if (data['items'][i]['id'] == result) {
|
||||||
|
decodeThenAddItem(data['items'][i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}).fail(function () {
|
||||||
|
alert("Error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removezero() {
|
||||||
|
$("#pos-lines-box .list-group-item").each(function () {
|
||||||
|
var qty = $(".item-qty", this).val() * 1.0;
|
||||||
|
if (qty == 0) {
|
||||||
|
$(this).remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQty(btn, diff) {
|
||||||
|
var qtybox = $(btn).parent().parent().find(".item-qty");
|
||||||
|
var qty = parseInt(qtybox.val());
|
||||||
|
qty += diff;
|
||||||
|
if (qty > 0) {
|
||||||
|
qtybox.val(qty);
|
||||||
|
var minbtn = $(btn).parent().parent().find(".qty-minus");
|
||||||
|
if (qty == 1) {
|
||||||
|
minbtn.html("<i class=\"fas fa-trash\"></i>");
|
||||||
|
} else {
|
||||||
|
minbtn.html("<i class=\"fas fa-minus\"></i>");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qtybox.closest(".list-group-item").remove();
|
||||||
|
}
|
||||||
|
recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#pos-lines-box").on("click", ".qty-minus", function () {
|
||||||
|
updateQty(this, -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#pos-lines-box").on("click", ".qty-plus", function () {
|
||||||
|
updateQty(this, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#pos-lines-box").on("change blur", ".item-qty,.item-price", function () {
|
||||||
|
recalculate();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#pos-lines-box").on("keypress", ".item-qty,.item-price", function (e) {
|
||||||
|
if (e.which === 13) {
|
||||||
|
recalculate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#barcode").on('keypress', function (e) {
|
||||||
|
if (e.which === 13) {
|
||||||
|
findItem($("#barcode").val());
|
||||||
|
$("#barcode").val("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#barcodebtn").on("click", function () {
|
||||||
|
findItem($("#barcode").val());
|
||||||
|
$("#barcode").val("");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#barcode").focus();
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function addPayment(type, icon, text) {
|
||||||
|
var extrafield = "";
|
||||||
|
if (type == "giftcard") {
|
||||||
|
extrafield = ''
|
||||||
|
+ ' <div class="input-group-prepend input-group-append">'
|
||||||
|
+ ' <span class="input-group-text">'
|
||||||
|
+ ' #'
|
||||||
|
+ ' </span>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' <input class="form-control giftcard-number" type="number" />';
|
||||||
|
}
|
||||||
|
var amount = "";
|
||||||
|
// Autofill the exact due amount for payment methods that are flexible like that
|
||||||
|
if (type == "check" || type == "card" || type == "crypto") {
|
||||||
|
if ($("#owed-amount").text() * 1.0 > 0) {
|
||||||
|
amount = ($("#owed-amount").text() * 1.0).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#payment-lines").append(''
|
||||||
|
+ '<div class="list-group-item">'
|
||||||
|
+ ' <div class="input-group">'
|
||||||
|
+ ' <div class="input-group-prepend">'
|
||||||
|
+ ' <span class="input-group-text">'
|
||||||
|
+ ' <i class="' + icon + ' fa-fw mr-1"></i> '
|
||||||
|
+ text
|
||||||
|
+ ' </span>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' <div class="input-group-prepend">'
|
||||||
|
+ ' <span class="input-group-text">'
|
||||||
|
+ ' $'
|
||||||
|
+ ' </span>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' <input class="form-control payment-entry" type="money" data-type="' + type + '" value="' + amount + '" />'
|
||||||
|
+ extrafield
|
||||||
|
+ ' <div class="input-group-append">'
|
||||||
|
+ ' <span class="btn btn-outline-danger remove-payment-btn">'
|
||||||
|
+ ' <i class="fas fa-trash"></i>'
|
||||||
|
+ ' </span>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ '</div>');
|
||||||
|
$("#payment-lines .payment-entry").last().focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGiftCardBalance() {
|
||||||
|
$("#payment-lines .list-group-item:has(.giftcard-number)").each(function () {
|
||||||
|
var paymentbox = $(".payment-entry", this);
|
||||||
|
var cardnumberbox = $(".giftcard-number", this);
|
||||||
|
var amount = paymentbox.val() * 1.0;
|
||||||
|
var cardnumber = cardnumberbox.val();
|
||||||
|
if (cardnumber == "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.get("action.php", {
|
||||||
|
action: "giftcard_lookup",
|
||||||
|
code: cardnumber
|
||||||
|
}, function (json) {
|
||||||
|
if (json.status == "OK") {
|
||||||
|
if (json.cards.length == 0) {
|
||||||
|
bsalert("Invalid Gift Card", "Gift card #" + cardnumber + " does not exist. Remove it to finish the transaction.");
|
||||||
|
cardnumberbox.addClass('is-invalid');
|
||||||
|
} else if (json.cards.length == 1) {
|
||||||
|
if (json.cards[0].balance < amount) {
|
||||||
|
bsalert("Insufficient Gift Card Balance", "Gift card #" + cardnumber + " does not contain enough funds. The amount has been set to the full amount remaining on the card ($" + json.cards[0].balance + ").");
|
||||||
|
paymentbox.val(json.cards[0].balance);
|
||||||
|
}
|
||||||
|
cardnumberbox.removeClass('is-invalid');
|
||||||
|
} else {
|
||||||
|
bsalert("Invalid Gift Card", "Unable to determine which gift card #" + cardnumber + " is referring to. Remove it to finish the transaction.");
|
||||||
|
cardnumberbox.addClass('is-invalid');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bsalert("Invalid Gift Card", "Unable to determine which gift card #" + cardnumber + " is referring to. Remove it to finish the transaction.");
|
||||||
|
cardnumberbox.addClass('is-invalid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#payment-lines").on("change keyup blur", ".payment-entry", function () {
|
||||||
|
recalculate();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#payment-lines").on("blur", ".giftcard-number,.payment-entry[data-type=giftcard]", function () {
|
||||||
|
checkGiftCardBalance();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#payment-lines").on("keypress", ".giftcard-number,.payment-entry[data-type=giftcard]", function (e) {
|
||||||
|
if (e.which === 13) {
|
||||||
|
checkGiftCardBalance();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#payment-lines").on("click", ".remove-payment-btn", function () {
|
||||||
|
$(this).closest(".list-group-item").remove();
|
||||||
|
recalculate();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#paymentbtn").click(function () {
|
||||||
|
$("#paymentui").removeClass("d-none");
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".payment-method-button").click(function () {
|
||||||
|
addPayment($(this).data("payment-method"), $(this).data("icon"), $(this).data("text"));
|
||||||
|
});
|
Loading…
Reference in New Issue