@@ -7,7 +7,6 @@ | |||
/** | |||
* Make things happen when buttons are pressed and forms submitted. | |||
*/ | |||
require_once __DIR__ . "/required.php"; | |||
if ($VARS['action'] !== "signout") { | |||
@@ -31,6 +30,28 @@ function returnToSender($msg, $arg = "") { | |||
} | |||
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": | |||
session_destroy(); | |||
header('Location: index.php'); |
@@ -16,6 +16,7 @@ define("PAGES", [ | |||
"navbar" => true, | |||
"icon" => "far fa-money-bill-alt", | |||
"scripts" => [ | |||
"static/js/bsalert.js", | |||
"static/js/pos.js", | |||
] | |||
], |
@@ -3,4 +3,3 @@ | |||
* 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/. */ | |||
?> | |||
<h1>Hello World</h1> |
@@ -15,51 +15,17 @@ | |||
<div class="card-header p-1"> | |||
<div class="input-group"> | |||
<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> | |||
<input type="text" class="form-control" id="barcode" placeholder="<?php lang("barcode"); ?>" /> | |||
<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 class="list-group" id="pos-lines-box"> | |||
<?php | |||
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 | |||
} | |||
?> | |||
<!-- Items go here --> | |||
</div> | |||
</div> | |||
</div> |
@@ -92,6 +92,7 @@ date_default_timezone_set(TIMEZONE); | |||
use Medoo\Medoo; | |||
$database; | |||
$binstack; | |||
try { | |||
$database = new Medoo([ | |||
'database_type' => DB_TYPE, | |||
@@ -101,6 +102,14 @@ try { | |||
'password' => DB_PASS, | |||
'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) { | |||
//header('HTTP/1.1 500 Internal Server Error'); | |||
sendError("Database error. Try again later. $ex"); |
@@ -17,6 +17,15 @@ define("DB_USER", "nickelbox"); | |||
define("DB_PASS", ""); | |||
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. | |||
define("SITE_TITLE", "NickelBox"); | |||
@@ -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"); | |||
} |
@@ -9,13 +9,14 @@ function addItem(name, code, price) { | |||
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 + '">' | |||
+ '<div class="d-flex w-100 justify-content-between mb-2">' | |||
+ '<h5 class="item-name">' | |||
+ name | |||
+ '</h5>' | |||
+ '<h5>' | |||
+ '<small class="item-code">' + code + '</small>' | |||
+ '<small class="item-code mr-1">' + code + '</small>' | |||
+ '<span class="badge badge-light">' | |||
+ '$<span class="line-total">' | |||
+ price | |||
@@ -24,16 +25,16 @@ function addItem(name, code, price) { | |||
+ '</h5>' | |||
+ '</div>' | |||
+ '<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">' | |||
+ '<span class="input-group-text mx-0 px-0"><b>$</b></span>' | |||
+ '<span class="input-group-text pr-1"><b>$</b></span>' | |||
+ '</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">' | |||
+ '<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>' | |||
+ '</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">' | |||
+ '<button class="btn btn-light-green qty-plus" type="button"><i class="fas fa-plus"></i></button>' | |||
+ '</div>' | |||
@@ -42,7 +43,81 @@ function addItem(name, code, price) { | |||
+ '</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() { | |||
removezero(); | |||
var total = 0.0; | |||
$("#pos-lines-box .list-group-item").each(function () { | |||
var each = $(".item-price", this).val() * 1.0; | |||
@@ -50,6 +125,7 @@ function recalculate() { | |||
var line = each * qty; | |||
$(".line-total", this).text(line.toFixed(2)); | |||
$(".item-price", this).val(each.toFixed(2)); | |||
console.log(each.toFixed(2)); | |||
total += line; | |||
}); | |||
$(".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 () { | |||
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(); |