Add BinStack integration, barcode scanning, and item lookup
This commit is contained in:
parent
9e1b524b93
commit
40bc592a8d
23
action.php
23
action.php
@ -7,7 +7,6 @@
|
|||||||
/**
|
/**
|
||||||
* Make things happen when buttons are pressed and forms submitted.
|
* Make things happen when buttons are pressed and forms submitted.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once __DIR__ . "/required.php";
|
require_once __DIR__ . "/required.php";
|
||||||
|
|
||||||
if ($VARS['action'] !== "signout") {
|
if ($VARS['action'] !== "signout") {
|
||||||
@ -31,6 +30,28 @@ function returnToSender($msg, $arg = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch ($VARS['action']) {
|
switch ($VARS['action']) {
|
||||||
|
case "itemsearch":
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
if (!is_empty($VARS['q'])) {
|
||||||
|
$where["AND"]["OR"] = [
|
||||||
|
"name[~]" => $VARS['q'],
|
||||||
|
"code1[~]" => $VARS['q'],
|
||||||
|
"code2[~]" => $VARS['q']
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
exit(json_encode(["status" => "ERROR", "items" => false]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = $binstack->select('items', [
|
||||||
|
'itemid (id)',
|
||||||
|
'name',
|
||||||
|
'code1',
|
||||||
|
'code2',
|
||||||
|
'cost',
|
||||||
|
'price'
|
||||||
|
], $where);
|
||||||
|
$items = (count($items) > 0 ? $items : false);
|
||||||
|
exit(json_encode(["status" => "OK", "items" => $items]));
|
||||||
case "signout":
|
case "signout":
|
||||||
session_destroy();
|
session_destroy();
|
||||||
header('Location: index.php');
|
header('Location: index.php');
|
||||||
|
@ -16,6 +16,7 @@ define("PAGES", [
|
|||||||
"navbar" => true,
|
"navbar" => true,
|
||||||
"icon" => "far fa-money-bill-alt",
|
"icon" => "far fa-money-bill-alt",
|
||||||
"scripts" => [
|
"scripts" => [
|
||||||
|
"static/js/bsalert.js",
|
||||||
"static/js/pos.js",
|
"static/js/pos.js",
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@ -3,4 +3,3 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
?>
|
?>
|
||||||
<h1>Hello World</h1>
|
|
@ -15,51 +15,17 @@
|
|||||||
<div class="card-header p-1">
|
<div class="card-header p-1">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text"><i class="fas fa-barcode"></i></span>
|
<span class="input-group-text px-2"><i class="fas fa-barcode"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" id="barcode" placeholder="<?php lang("barcode"); ?>" />
|
<input type="text" class="form-control" id="barcode" placeholder="<?php lang("barcode"); ?>" />
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-link" type="button"><i class="fas fa-plus"></i></button>
|
<button class="btn btn-link" type="button" id="barcodebtn"><i class="fas fa-search"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="list-group" id="pos-lines-box">
|
<div class="list-group" id="pos-lines-box">
|
||||||
<?php
|
<!-- Items go here -->
|
||||||
for ($i = 0; $i < 5; $i++) {
|
|
||||||
?>
|
|
||||||
<div class="list-group-item">
|
|
||||||
<div class="d-flex w-100 justify-content-between mb-2">
|
|
||||||
<h5 class="item-name">
|
|
||||||
Cool Widget
|
|
||||||
</h5>
|
|
||||||
<h5>
|
|
||||||
<small class="item-code">659321</small>
|
|
||||||
<span class="badge badge-light">
|
|
||||||
$<span class="line-total">10.23</span>
|
|
||||||
</span>
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="d-inline-flex">
|
|
||||||
<div class="input-group input-group-sm qty-control">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text mx-0 px-0"><b>$</b></span>
|
|
||||||
</div>
|
|
||||||
<input type="number" class="form-control item-price" value="10.23"/>
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><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" 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>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,6 +92,7 @@ date_default_timezone_set(TIMEZONE);
|
|||||||
use Medoo\Medoo;
|
use Medoo\Medoo;
|
||||||
|
|
||||||
$database;
|
$database;
|
||||||
|
$binstack;
|
||||||
try {
|
try {
|
||||||
$database = new Medoo([
|
$database = new Medoo([
|
||||||
'database_type' => DB_TYPE,
|
'database_type' => DB_TYPE,
|
||||||
@ -101,6 +102,14 @@ try {
|
|||||||
'password' => DB_PASS,
|
'password' => DB_PASS,
|
||||||
'charset' => DB_CHARSET
|
'charset' => DB_CHARSET
|
||||||
]);
|
]);
|
||||||
|
$binstack = new Medoo([
|
||||||
|
'database_type' => BINSTACK_DB_TYPE,
|
||||||
|
'database_name' => BINSTACK_DB_NAME,
|
||||||
|
'server' => BINSTACK_DB_SERVER,
|
||||||
|
'username' => BINSTACK_DB_USER,
|
||||||
|
'password' => BINSTACK_DB_PASS,
|
||||||
|
'charset' => BINSTACK_DB_CHARSET
|
||||||
|
]);
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
//header('HTTP/1.1 500 Internal Server Error');
|
//header('HTTP/1.1 500 Internal Server Error');
|
||||||
sendError("Database error. Try again later. $ex");
|
sendError("Database error. Try again later. $ex");
|
||||||
|
@ -17,6 +17,15 @@ define("DB_USER", "nickelbox");
|
|||||||
define("DB_PASS", "");
|
define("DB_PASS", "");
|
||||||
define("DB_CHARSET", "utf8");
|
define("DB_CHARSET", "utf8");
|
||||||
|
|
||||||
|
// BinStack database connection settings
|
||||||
|
define("BINSTACK_DB_TYPE", "mysql");
|
||||||
|
define("BINSTACK_DB_NAME", "inventory");
|
||||||
|
define("BINSTACK_DB_SERVER", "localhost");
|
||||||
|
define("BINSTACK_DB_USER", "inventory");
|
||||||
|
define("BINSTACK_DB_PASS", "");
|
||||||
|
define("BINSTACK_DB_CHARSET", "utf8");
|
||||||
|
|
||||||
|
|
||||||
// Name of the app.
|
// Name of the app.
|
||||||
define("SITE_TITLE", "NickelBox");
|
define("SITE_TITLE", "NickelBox");
|
||||||
|
|
||||||
|
91
static/js/bsalert.js
Normal file
91
static/js/bsalert.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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 bsprompt(title, message, okbtn, cancelbtn, type, callback) {
|
||||||
|
var html = '<div class="modal fade" id="bsprompt">'
|
||||||
|
+ ' <div class="modal-dialog">'
|
||||||
|
+ ' <div class="modal-content">'
|
||||||
|
+ ' <div class="modal-header">'
|
||||||
|
+ ' <h5 class="modal-title" id="bsprompt-title"></h5>'
|
||||||
|
+ ' <button type="button" class="close" data-dismiss="modal" aria-label="Close">'
|
||||||
|
+ ' <span aria-hidden="true">×</span>'
|
||||||
|
+ ' </button>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' <div class="modal-body" id="bsprompt-body">'
|
||||||
|
+ ' <div id="bsprompt-message"></div>'
|
||||||
|
+ ' <input id="bsprompt-input" class="form-control" />'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' <div class="modal-footer">'
|
||||||
|
+ ' <button type="button" class="btn btn-secondary" data-dismiss="modal" id="bsprompt-cancel">Cancel</button>'
|
||||||
|
+ ' <button type="button" class="btn btn-primary" id="bsprompt-ok">OK</button>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ '</div>';
|
||||||
|
$("body").append(html);
|
||||||
|
$("#bsprompt-title").text(title);
|
||||||
|
$("#bsprompt-message").text(message);
|
||||||
|
$("#bsprompt-ok").text(okbtn);
|
||||||
|
$("#bsprompt-cancel").text(cancelbtn);
|
||||||
|
$("#bsprompt-input").attr("type", type);
|
||||||
|
$("#bsprompt-input").on("keypress", function (e) {
|
||||||
|
if (e.which === 13) {
|
||||||
|
callback($("#bsprompt-input").val());
|
||||||
|
$("#bsprompt").modal("hide");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#bsprompt-ok").on("click", function () {
|
||||||
|
callback($("#bsprompt-input").val());
|
||||||
|
$("#bsprompt").modal("hide");
|
||||||
|
});
|
||||||
|
$("#bsprompt").on("shown.bs.modal", function () {
|
||||||
|
$("#bsprompt-input").focus();
|
||||||
|
});
|
||||||
|
$("#bsprompt").on("hidden.bs.modal", function () {
|
||||||
|
$("#bsprompt").remove();
|
||||||
|
});
|
||||||
|
$("#bsprompt").modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
function bschoices(title, message, options, cancelbtn, callback) {
|
||||||
|
var html = '<div class="modal fade" id="bschoices">'
|
||||||
|
+ ' <div class="modal-dialog">'
|
||||||
|
+ ' <div class="modal-content">'
|
||||||
|
+ ' <div class="modal-header">'
|
||||||
|
+ ' <h5 class="modal-title" id="bschoices-title"></h5>'
|
||||||
|
+ ' <button type="button" class="close" data-dismiss="modal" aria-label="Close">'
|
||||||
|
+ ' <span aria-hidden="true">×</span>'
|
||||||
|
+ ' </button>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' <div class="modal-body" id="bschoices-body">'
|
||||||
|
+ ' <div id="bschoices-message" class="mb-2"></div>'
|
||||||
|
+ ' <div class="list-group" id="bschoices-list"></div>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' <div class="modal-footer">'
|
||||||
|
+ ' <button type="button" class="btn btn-secondary" data-dismiss="modal" id="bschoices-cancel">Cancel</button>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ ' </div>'
|
||||||
|
+ '</div>';
|
||||||
|
$("body").append(html);
|
||||||
|
$("#bschoices-title").text(title);
|
||||||
|
$("#bschoices-message").text(message);
|
||||||
|
$("#bschoices-cancel").text(cancelbtn);
|
||||||
|
for (var i = 0; i < options.length; i++) {
|
||||||
|
var text = options[i]["text"];
|
||||||
|
var val = options[i]["val"];
|
||||||
|
$("#bschoices-list").append('<div class="list-group-item bschoices-option" data-value="' + val + '">' + text + '</div>');
|
||||||
|
}
|
||||||
|
$(".bschoices-option").css("cursor", "pointer");
|
||||||
|
$(".bschoices-option").on("click", function () {
|
||||||
|
callback($(this).data("value"));
|
||||||
|
$("#bschoices").modal("hide");
|
||||||
|
});
|
||||||
|
$("#bschoices").on("hidden.bs.modal", function () {
|
||||||
|
$("#bschoices").remove();
|
||||||
|
});
|
||||||
|
$("#bschoices").modal("show");
|
||||||
|
}
|
122
static/js/pos.js
122
static/js/pos.js
@ -9,13 +9,14 @@ function addItem(name, code, price) {
|
|||||||
updateQty($(".list-group-item[data-code='" + code + "']").find(".qty-plus"), 1);
|
updateQty($(".list-group-item[data-code='" + code + "']").find(".qty-plus"), 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
price = (price * 1.0).toFixed(2);
|
||||||
$("#pos-lines-box").append('<div class="list-group-item" data-code="' + code + '">'
|
$("#pos-lines-box").append('<div class="list-group-item" data-code="' + code + '">'
|
||||||
+ '<div class="d-flex w-100 justify-content-between mb-2">'
|
+ '<div class="d-flex w-100 justify-content-between mb-2">'
|
||||||
+ '<h5 class="item-name">'
|
+ '<h5 class="item-name">'
|
||||||
+ name
|
+ name
|
||||||
+ '</h5>'
|
+ '</h5>'
|
||||||
+ '<h5>'
|
+ '<h5>'
|
||||||
+ '<small class="item-code">' + code + '</small>'
|
+ '<small class="item-code mr-1">' + code + '</small>'
|
||||||
+ '<span class="badge badge-light">'
|
+ '<span class="badge badge-light">'
|
||||||
+ '$<span class="line-total">'
|
+ '$<span class="line-total">'
|
||||||
+ price
|
+ price
|
||||||
@ -24,16 +25,16 @@ function addItem(name, code, price) {
|
|||||||
+ '</h5>'
|
+ '</h5>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
+ '<div class="d-inline-flex">'
|
+ '<div class="d-inline-flex">'
|
||||||
+ '<div class="input-group input-group-sm qty-control">'
|
+ '<div class="input-group qty-control">'
|
||||||
+ '<div class="input-group-prepend">'
|
+ '<div class="input-group-prepend">'
|
||||||
+ '<span class="input-group-text mx-0 px-0"><b>$</b></span>'
|
+ '<span class="input-group-text pr-1"><b>$</b></span>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
+ '<input type="number" class="form-control item-price" value="' + price + '"/>'
|
+ '<input type="money" class="form-control item-price" value="' + price + '"/>'
|
||||||
+ '<div class="input-group-prepend">'
|
+ '<div class="input-group-prepend">'
|
||||||
+ '<span class="input-group-text"><i class="fas fa-times"></i></span>'
|
+ '<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>'
|
+ '<button class="btn btn-red qty-minus" type="button"><i class="fas fa-trash"></i></button>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
+ '<input type="number" class="form-control item-qty" value="1" />'
|
+ '<input type="number" class="form-control item-qty px-2" value="1" />'
|
||||||
+ '<div class="input-group-append">'
|
+ '<div class="input-group-append">'
|
||||||
+ '<button class="btn btn-light-green qty-plus" type="button"><i class="fas fa-plus"></i></button>'
|
+ '<button class="btn btn-light-green qty-plus" type="button"><i class="fas fa-plus"></i></button>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
@ -42,7 +43,81 @@ function addItem(name, code, price) {
|
|||||||
+ '</div>');
|
+ '</div>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addItem(item['name'], code, price);
|
||||||
|
$("#barcode").val("");
|
||||||
|
}
|
||||||
|
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 += " ($" + data['items'][i]['price'] + ")";
|
||||||
|
}
|
||||||
|
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 recalculate() {
|
function recalculate() {
|
||||||
|
removezero();
|
||||||
var total = 0.0;
|
var total = 0.0;
|
||||||
$("#pos-lines-box .list-group-item").each(function () {
|
$("#pos-lines-box .list-group-item").each(function () {
|
||||||
var each = $(".item-price", this).val() * 1.0;
|
var each = $(".item-price", this).val() * 1.0;
|
||||||
@ -50,6 +125,7 @@ function recalculate() {
|
|||||||
var line = each * qty;
|
var line = each * qty;
|
||||||
$(".line-total", this).text(line.toFixed(2));
|
$(".line-total", this).text(line.toFixed(2));
|
||||||
$(".item-price", this).val(each.toFixed(2));
|
$(".item-price", this).val(each.toFixed(2));
|
||||||
|
console.log(each.toFixed(2));
|
||||||
total += line;
|
total += line;
|
||||||
});
|
});
|
||||||
$(".grand-total").text(total.toFixed(2));
|
$(".grand-total").text(total.toFixed(2));
|
||||||
@ -83,4 +159,36 @@ $("#pos-lines-box").on("click", ".qty-plus", function () {
|
|||||||
|
|
||||||
$("#pos-lines-box").on("change", ".item-qty,.item-price", function () {
|
$("#pos-lines-box").on("change", ".item-qty,.item-price", function () {
|
||||||
recalculate();
|
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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#barcodebtn").on("click", function () {
|
||||||
|
findItem($("#barcode").val());
|
||||||
|
});
|
||||||
|
|
||||||
|
$("body").on("keypress", "input[type=money],input[type=number]", function (e) {
|
||||||
|
//console.log(String.fromCharCode(e.which) + "|" + e.which);
|
||||||
|
var c = String.fromCharCode(e.which);
|
||||||
|
var k = e.which;
|
||||||
|
if (/[0-9]|[\.]/.test(c)) {
|
||||||
|
// Numbers and period
|
||||||
|
} else if (k == 0 || k == 8) {
|
||||||
|
// Delete, backspace, etc
|
||||||
|
} else {
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#barcode").focus();
|
Loading…
x
Reference in New Issue
Block a user