Barcode Operations

This commit is contained in:
Thanakorn S
2026-05-06 15:27:52 +07:00
parent d8131a01b7
commit ada701bd53
22 changed files with 2449 additions and 35 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+173
View File
@@ -0,0 +1,173 @@
var scanner = (function() {
var _callback = null;
var _allow_raw = true;
var _allowed_types = null;
var _camera = null;
var _modal = null;
var _buffer = '';
var _last_key_ts = 0;
var _max_key_gap = 0;
var _HW_THRESHOLD = 50;
function _on_paste(e) {
if (!_callback) return;
var el = document.activeElement;
if (el && el.tagName === 'TEXTAREA') return;
var text = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('text');
if (!text) return;
text = text.trim();
if (text.length < 3) return;
e.preventDefault();
_resolve(text);
}
function _on_keydown(e) {
var now = Date.now();
var gap = now - _last_key_ts;
if (gap > 500) {
_buffer = '';
_max_key_gap = 0;
} else if (_buffer.length > 0) {
_max_key_gap = Math.max(_max_key_gap, gap);
}
_last_key_ts = now;
if (e.key === 'Enter' && _buffer.length >= 3) {
var barcode = _buffer;
var is_scanner_input = _max_key_gap <= _HW_THRESHOLD;
_buffer = '';
_max_key_gap = 0;
if (is_scanner_input) {
e.preventDefault();
_remove_typed_barcode(barcode);
_resolve(barcode);
}
return;
}
if (e.key.length === 1) {
_buffer += e.key;
}
}
function _remove_typed_barcode(barcode) {
var el = document.activeElement;
if (!el || !('value' in el)) return;
if (el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA') return;
var value = String(el.value || '');
if (!value.endsWith(barcode)) return;
el.value = value.slice(0, value.length - barcode.length);
$(el).trigger('input');
}
function _resolve(barcode) {
if (!barcode || !_callback) return;
$.ajax({
url: server_url + 'ics/api/engine/barcode_lookup.php',
type: 'POST',
dataType: 'json',
data: { json: JSON.stringify({ barcode: barcode, action: 'read' }) },
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') },
success: function(res) {
if (res && res.success) {
if ((!_allow_raw && res.type === 'raw') || (_allowed_types && _allowed_types.indexOf(res.type) === -1)) {
_show_error('This page only accepts generated barcode labels.');
return;
}
_callback(res.sku || '', res.product_name || '', res.uom || '', res);
return;
}
_show_error('Barcode not found: ' + barcode);
},
error: function(xhr) {
var msg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : 'Scanner error. Please try again.';
_show_error(msg === 'Barcode not found.' ? 'Barcode not found: ' + barcode : msg);
}
});
}
function _build_modal() {
if (_modal) return;
$('body').append(`
<div class="modal fade" id="scanner_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="ti ti-scan me-2"></i>Scan Barcode</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="scanner_qr_reader"></div>
<div id="scanner_error" class="alert alert-danger mt-3 d-none"></div>
</div>
</div>
</div>
</div>`);
_modal = new bootstrap.Modal(document.getElementById('scanner_modal'));
document.getElementById('scanner_modal').addEventListener('hidden.bs.modal', _stop_camera);
}
function _start_camera() {
if (_camera) return;
if (typeof Html5Qrcode === 'undefined') {
_show_error('Camera scanner library is not available.');
return;
}
_camera = new Html5Qrcode('scanner_qr_reader');
_camera.start(
{ facingMode: 'environment' },
{ fps: 10, qrbox: { width: 250, height: 120 } },
function(decodedText) {
_modal.hide();
_resolve(decodedText);
},
function() {}
).catch(function(err) {
_show_error('Camera error: ' + err);
});
}
function _stop_camera() {
if (!_camera) return;
_camera.stop().catch(function() {}).finally(function() {
_camera = null;
});
}
function _show_error(msg) {
var $err = $('#scanner_error');
if ($err.length) {
$err.text(msg).removeClass('d-none');
setTimeout(function() { $err.addClass('d-none'); }, 3000);
return;
}
if (typeof bootbox !== 'undefined') bootbox.alert(msg);
}
return {
init: function(options) {
options = options || {};
_callback = options.onScan || null;
_allow_raw = options.allowRaw !== false;
_allowed_types = Array.isArray(options.allowedTypes) ? options.allowedTypes : null;
document.removeEventListener('keydown', _on_keydown);
document.removeEventListener('paste', _on_paste);
document.addEventListener('keydown', _on_keydown);
document.addEventListener('paste', _on_paste);
_build_modal();
},
open_camera: function() {
if (!_modal) _build_modal();
_modal.show();
setTimeout(_start_camera, 400);
},
destroy: function() {
document.removeEventListener('keydown', _on_keydown);
document.removeEventListener('paste', _on_paste);
_stop_camera();
}
};
})();
+6 -3
View File
@@ -317,7 +317,8 @@ class ProductManager {
$sth = $this->pdo->prepare(
"SELECT * FROM md_product
WHERE company_id = :company_id
AND sku LIKE :keyword
AND (sku LIKE :keyword OR barcode LIKE :keyword)
AND status > 0
LIMIT 50"
);
$sth->execute([
@@ -358,6 +359,7 @@ class ProductManager {
':company_id' => $this->company_id,
':product_name' => $data['product_name'],
':sku' => $data['sku'],
':barcode' => trim($data['barcode'] ?? '') ?: null,
':uom' => $data['uom'] ?? 'pcs',
':price' => $data['price'],
':cost_price' => (float)($data['cost_price'] ?? 0),
@@ -376,6 +378,7 @@ class ProductManager {
"UPDATE md_product SET
product_name = :product_name,
sku = :sku,
barcode = :barcode,
uom = :uom,
price = :price,
cost_price = :cost_price,
@@ -391,10 +394,10 @@ class ProductManager {
} else {
$this->pdo->prepare(
"INSERT INTO md_product
(company_id, product_name, sku, uom, price, cost_price, min_stock, reorder_point,
(company_id, product_name, sku, barcode, uom, price, cost_price, min_stock, reorder_point,
category, product_image, `description`, `status`, `log`)
VALUES
(:company_id, :product_name, :sku, :uom, :price, :cost_price, :min_stock, :reorder_point,
(:company_id, :product_name, :sku, :barcode, :uom, :price, :cost_price, :min_stock, :reorder_point,
:category, :product_image, :description, :status, :log)"
)->execute($params);
}