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
+205
View File
@@ -0,0 +1,205 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
$barcode = trim($data['barcode'] ?? '');
if (!$barcode) {
$answer['message'] = 'No barcode provided.';
http_response_code(400);
exit(json_encode($answer));
}
function findProduct(PDO $pdo, int $company_id, string $sku): array|false {
$sth = $pdo->prepare(
"SELECT sku, product_name, uom
FROM md_product
WHERE company_id = :company_id
AND sku = :sku
AND status > 0
LIMIT 1"
);
$sth->execute([':company_id' => $company_id, ':sku' => $sku]);
return $sth->fetch(PDO::FETCH_ASSOC);
}
function validateLotSerial(PDO $pdo, int $company_id, string $sku, string $lot, string $serial): void {
if ($serial === '') return;
$warehouses = $pdo->prepare(
"SELECT id FROM md_warehouse
WHERE company_id = :company_id AND status = 1"
);
$warehouses->execute([':company_id' => $company_id]);
foreach ($warehouses->fetchAll(PDO::FETCH_COLUMN) as $warehouse_id) {
$table = 'td_stock_' . (int)$warehouse_id;
$sth = $pdo->prepare(
"SELECT lot_number
FROM `{$table}`
WHERE company_id = :company_id
AND product_sku = :sku
AND serial_number = :serial
AND status != -1
LIMIT 1"
);
$sth->execute([':company_id' => $company_id, ':sku' => $sku, ':serial' => $serial]);
$existing_lot = $sth->fetchColumn();
if ($existing_lot !== false && (string)$existing_lot !== $lot) {
throw new Exception('Serial number belongs to a different lot.');
}
}
}
function findPreparedLabel(PDO $pdo, int $company_id, string $barcode, string $type): array|false {
$sth = $pdo->prepare(
"SELECT *
FROM md_barcode
WHERE company_id = :company_id
AND barcode = :barcode
AND barcode_type = :barcode_type
LIMIT 1"
);
$sth->execute([
':company_id' => $company_id,
':barcode' => $barcode,
':barcode_type' => $type,
]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if ($row && (int)$row['status'] !== 1) {
throw new Exception('Barcode is disabled.');
}
return $row ?: false;
}
function advancedLocation(PDO $pdo, int $company_id): bool {
$sth = $pdo->prepare(
"SELECT value FROM company_setting
WHERE company_id = :company_id AND setting_key = 'advanced_location'
LIMIT 1"
);
$sth->execute([':company_id' => $company_id]);
$value = $sth->fetchColumn();
return (int)($value === false ? 0 : $value) === 1;
}
function validateLocation(PDO $pdo, int $company_id, int $warehouse_id, string $zone, string $aisle, string $rack): void {
$sth = $pdo->prepare(
"SELECT 1 FROM md_rack
WHERE company_id = :company_id
AND warehouse = :warehouse
AND zone = :zone
AND aisle = :aisle
AND rack = :rack
LIMIT 1"
);
$sth->execute([
':company_id' => $company_id,
':warehouse' => $warehouse_id,
':zone' => $zone,
':aisle' => $aisle,
':rack' => $rack,
]);
if (!$sth->fetchColumn()) throw new Exception('Location not found.');
}
$parts = explode('|', $barcode);
$label_type = strtoupper($parts[0] ?? '');
try {
if ($label_type === 'SKU') {
if (count($parts) !== 4) throw new Exception('Invalid SKU label format.');
$sku = trim($parts[1]);
$lot = trim($parts[2]);
$serial = trim($parts[3]);
if ($sku === '' || $lot === '' || $serial === '') throw new Exception('SKU, lot, and serial are required.');
$label = findPreparedLabel($pdo2, $company_id, $barcode, 'sku');
if (!$label) throw new Exception('SKU barcode label was not prepared.');
if ($label['product_sku'] !== $sku || $label['lot_number'] !== $lot || $label['serial_number'] !== $serial) {
throw new Exception('SKU barcode label does not match its registry.');
}
$product = findProduct($pdo2, $company_id, $sku);
if (!$product) throw new Exception('SKU not found.');
validateLotSerial($pdo2, $company_id, $sku, $lot, $serial);
$answer['success'] = 1;
$answer['type'] = 'sku';
$answer['sku'] = $product['sku'];
$answer['product_name'] = $product['product_name'];
$answer['uom'] = $product['uom'] ?? 'pcs';
$answer['lot_number'] = $lot;
$answer['serial_number'] = $serial;
exit(json_encode($answer));
}
if ($label_type === 'LOC') {
$advanced = advancedLocation($pdo1, $company_id);
if ($advanced && count($parts) !== 5) throw new Exception('Invalid advanced location label format.');
if (!$advanced && count($parts) !== 3) throw new Exception('Invalid location label format.');
$warehouse_id = (int)($parts[1] ?? 0);
if ($advanced) {
$zone = trim($parts[2]);
$aisle = trim($parts[3]);
$rack = trim($parts[4]);
} else {
$rack = trim($parts[2]);
$zone = $rack;
$aisle = $rack;
}
if (!$warehouse_id || $zone === '' || $aisle === '' || $rack === '') throw new Exception('Location label is incomplete.');
$label = findPreparedLabel($pdo2, $company_id, $barcode, 'loc');
if (!$label) throw new Exception('Location barcode label was not prepared.');
if ((int)$label['warehouse_id'] !== $warehouse_id || $label['zone'] !== $zone || $label['aisle'] !== $aisle || $label['rack'] !== $rack) {
throw new Exception('Location barcode label does not match its registry.');
}
validateLocation($pdo2, $company_id, $warehouse_id, $zone, $aisle, $rack);
$answer['success'] = 1;
$answer['type'] = 'loc';
$answer['warehouse_id'] = $warehouse_id;
$answer['zone'] = $zone;
$answer['aisle'] = $aisle;
$answer['rack'] = $rack;
exit(json_encode($answer));
}
} catch (Exception $e) {
$answer['message'] = $e->getMessage();
http_response_code(400);
exit(json_encode($answer));
}
$sth = $pdo2->prepare(
"SELECT sku, product_name, uom
FROM md_product
WHERE company_id = :company_id
AND (barcode = :barcode OR sku = :sku)
AND status > 0
ORDER BY (barcode = :barcode_order) DESC
LIMIT 1"
);
$sth->execute([
':company_id' => $company_id,
':barcode' => $barcode,
':sku' => $barcode,
':barcode_order' => $barcode,
]);
$product = $sth->fetch(PDO::FETCH_ASSOC);
if (!$product) {
$answer['message'] = 'Barcode not found.';
http_response_code(404);
exit(json_encode($answer));
}
$answer['success'] = 1;
$answer['type'] = 'raw';
$answer['sku'] = $product['sku'];
$answer['product_name'] = $product['product_name'];
$answer['uom'] = $product['uom'] ?? 'pcs';
exit(json_encode($answer));
?>
@@ -0,0 +1,153 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
function validateLocation(PDO $pdo, int $company_id, int $warehouse_id, string $zone, string $aisle, string $rack): void {
$sth = $pdo->prepare(
"SELECT 1 FROM md_rack
WHERE company_id = :company_id
AND warehouse = :warehouse
AND zone = :zone
AND aisle = :aisle
AND rack = :rack
LIMIT 1"
);
$sth->execute([
':company_id' => $company_id,
':warehouse' => $warehouse_id,
':zone' => $zone,
':aisle' => $aisle,
':rack' => $rack,
]);
if (!$sth->fetchColumn()) {
$answer = ['success' => 0, 'message' => 'Location not found.'];
http_response_code(404);
exit(json_encode($answer));
}
}
$label_action = $data['label_action'] ?? 'create';
$barcode = trim($data['barcode'] ?? '');
$warehouse_id = (int)($data['warehouse_id'] ?? 0);
$zone = trim($data['zone'] ?? '');
$aisle = trim($data['aisle'] ?? '');
$rack = trim($data['rack'] ?? '');
if ($label_action === 'list') {
$sth = $pdo2->prepare(
"SELECT barcode, warehouse_id, zone, aisle, rack, status, print_count, last_printed_dt
FROM md_barcode
WHERE company_id = :company_id
AND barcode_type = 'loc'"
);
$sth->execute([':company_id' => $company_id]);
$answer['success'] = 1;
$answer['output'] = $sth->fetchAll(PDO::FETCH_ASSOC);
exit(json_encode($answer));
}
if ($label_action === 'print') {
if ($barcode === '') {
$answer['message'] = 'No barcode provided.';
http_response_code(400);
exit(json_encode($answer));
}
$sth = $pdo2->prepare(
"UPDATE md_barcode
SET print_count = print_count + 1,
last_printed_dt = NOW()
WHERE company_id = :company_id
AND barcode = :barcode
AND barcode_type = 'loc'
AND status = 1"
);
$sth->execute([':company_id' => $company_id, ':barcode' => $barcode]);
$answer['success'] = 1;
exit(json_encode($answer));
}
if ($label_action === 'disable') {
if ($barcode === '') {
$answer['message'] = 'No barcode provided.';
http_response_code(400);
exit(json_encode($answer));
}
$sth = $pdo2->prepare(
"UPDATE md_barcode
SET status = 0
WHERE company_id = :company_id
AND barcode = :barcode
AND barcode_type = 'loc'
AND status = 1"
);
$sth->execute([':company_id' => $company_id, ':barcode' => $barcode]);
$answer['success'] = 1;
exit(json_encode($answer));
}
if ($label_action === 'enable') {
if ($barcode === '') {
$answer['message'] = 'No barcode provided.';
http_response_code(400);
exit(json_encode($answer));
}
$sth = $pdo2->prepare(
"UPDATE md_barcode
SET status = 1
WHERE company_id = :company_id
AND barcode = :barcode
AND barcode_type = 'loc'
AND status <> 1"
);
$sth->execute([':company_id' => $company_id, ':barcode' => $barcode]);
$answer['success'] = 1;
exit(json_encode($answer));
}
if ($barcode === '' || !$warehouse_id || $zone === '' || $aisle === '' || $rack === '') {
$answer['message'] = 'Location barcode data is incomplete.';
http_response_code(400);
exit(json_encode($answer));
}
validateLocation($pdo2, $company_id, $warehouse_id, $zone, $aisle, $rack);
$sth = $pdo2->prepare(
"INSERT INTO md_barcode
(company_id, barcode_type, barcode, warehouse_id, zone, aisle, rack)
VALUES
(:company_id, 'loc', :barcode, :warehouse_id, :zone, :aisle, :rack)
ON DUPLICATE KEY UPDATE
barcode_type = VALUES(barcode_type),
warehouse_id = VALUES(warehouse_id),
zone = VALUES(zone),
aisle = VALUES(aisle),
rack = VALUES(rack),
status = 1"
);
$sth->execute([
':company_id' => $company_id,
':barcode' => $barcode,
':warehouse_id' => $warehouse_id,
':zone' => $zone,
':aisle' => $aisle,
':rack' => $rack,
]);
$answer['success'] = 1;
$answer['output'] = [
'barcode' => $barcode,
'warehouse_id' => $warehouse_id,
'zone' => $zone,
'aisle' => $aisle,
'rack' => $rack,
];
exit(json_encode($answer));
?>
+245
View File
@@ -0,0 +1,245 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
function stockTableNameFromWarehouseId(int $warehouse_id): string {
return 'td_stock_' . $warehouse_id;
}
function findProduct(PDO $pdo, int $company_id, string $sku): array|false {
$sth = $pdo->prepare(
"SELECT sku, product_name, uom
FROM md_product
WHERE company_id = :company_id
AND sku = :sku
AND status > 0
LIMIT 1"
);
$sth->execute([':company_id' => $company_id, ':sku' => $sku]);
return $sth->fetch(PDO::FETCH_ASSOC);
}
function activeStockLotExists(PDO $pdo, int $company_id, string $sku, string $lot): bool {
$warehouses = $pdo->prepare(
"SELECT id FROM md_warehouse
WHERE company_id = :company_id AND status = 1"
);
$warehouses->execute([':company_id' => $company_id]);
foreach ($warehouses->fetchAll(PDO::FETCH_COLUMN) as $warehouse_id) {
$table = stockTableNameFromWarehouseId((int)$warehouse_id);
$sth = $pdo->prepare(
"SELECT ROUND(SUM(COALESCE(`in`, 0)) - SUM(COALESCE(`out`, 0)), 2) AS balance
FROM `{$table}`
WHERE company_id = :company_id
AND product_sku = :product_sku
AND lot_number = :lot_number
AND status = 1"
);
$sth->execute([
':company_id' => $company_id,
':product_sku' => $sku,
':lot_number' => $lot,
]);
if ((float)$sth->fetchColumn() > 0) return true;
}
return false;
}
function findLot(PDO $pdo, int $company_id, string $sku, string $lot): array|false {
$sth = $pdo->prepare(
"SELECT
l.product_sku,
l.lot_number,
l.expiry_date,
p.product_name,
p.uom
FROM md_lot l
INNER JOIN md_product p
ON p.company_id = l.company_id
AND p.sku = l.product_sku
WHERE l.company_id = :company_id
AND l.product_sku = :product_sku
AND l.lot_number = :lot_number
AND p.status > 0
LIMIT 1"
);
$sth->execute([
':company_id' => $company_id,
':product_sku' => $sku,
':lot_number' => $lot,
]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if ($row) return $row;
$product = findProduct($pdo, $company_id, $sku);
if (!$product || !activeStockLotExists($pdo, $company_id, $sku, $lot)) return false;
return [
'product_sku' => $product['sku'],
'lot_number' => $lot,
'expiry_date' => null,
'product_name' => $product['product_name'],
'uom' => $product['uom'],
];
}
function listLabels(PDO $pdo, int $company_id, string $sku, string $lot): array {
$params = [':company_id' => $company_id];
$where = "WHERE b.company_id = :company_id";
if ($sku !== '') {
$where .= " AND b.product_sku = :product_sku";
$params[':product_sku'] = $sku;
}
if ($lot !== '') {
$where .= " AND b.lot_number = :lot_number";
$params[':lot_number'] = $lot;
}
$sth = $pdo->prepare(
"SELECT
b.id,
b.product_sku,
b.lot_number,
b.serial_number,
b.barcode,
b.status,
b.print_count,
b.last_printed_dt,
b.created_dt,
p.product_name
FROM md_barcode b
LEFT JOIN md_product p
ON p.company_id = b.company_id
AND p.sku = b.product_sku
{$where}
AND b.barcode_type = 'sku'
ORDER BY b.created_dt DESC, b.id DESC"
);
$sth->execute($params);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
$label_action = $data['label_action'] ?? 'read';
$product_sku = trim($data['product_sku'] ?? '');
$lot_number = trim($data['lot_number'] ?? '');
if ($label_action === 'print') {
$barcode = trim($data['barcode'] ?? '');
if ($barcode === '') {
$answer['message'] = 'No barcode provided.';
http_response_code(400);
exit(json_encode($answer));
}
$sth = $pdo2->prepare(
"UPDATE md_barcode
SET print_count = print_count + 1,
last_printed_dt = NOW()
WHERE company_id = :company_id
AND barcode = :barcode
AND barcode_type = 'sku'
AND status = 1"
);
$sth->execute([':company_id' => $company_id, ':barcode' => $barcode]);
$answer['success'] = 1;
exit(json_encode($answer));
}
if ($label_action === 'disable') {
$barcode = trim($data['barcode'] ?? '');
if ($barcode === '') {
$answer['message'] = 'No barcode provided.';
http_response_code(400);
exit(json_encode($answer));
}
$sth = $pdo2->prepare(
"UPDATE md_barcode
SET status = 0
WHERE company_id = :company_id
AND barcode = :barcode
AND barcode_type = 'sku'
AND status = 1"
);
$sth->execute([':company_id' => $company_id, ':barcode' => $barcode]);
$answer['success'] = 1;
exit(json_encode($answer));
}
if ($label_action === 'create') {
$serial_number = trim($data['serial_number'] ?? '');
$allow_unlisted = !empty($data['allow_unlisted']);
if ($product_sku === '' || $lot_number === '' || $serial_number === '') {
$answer['message'] = 'SKU, lot no, and serial no are required.';
http_response_code(400);
exit(json_encode($answer));
}
$lot = findLot($pdo2, $company_id, $product_sku, $lot_number);
if (!$lot) {
if (!$allow_unlisted) {
$answer['message'] = 'Lot not found.';
http_response_code(404);
exit(json_encode($answer));
}
$product = findProduct($pdo2, $company_id, $product_sku);
if (!$product) {
$answer['message'] = 'SKU not found.';
http_response_code(404);
exit(json_encode($answer));
}
$lot = [
'product_sku' => $product['sku'],
'lot_number' => $lot_number,
'expiry_date' => null,
'product_name' => $product['product_name'],
'uom' => $product['uom'],
];
}
$barcode = "SKU|{$product_sku}|{$lot_number}|{$serial_number}";
$sth = $pdo2->prepare(
"INSERT INTO md_barcode
(company_id, barcode_type, barcode, product_sku, lot_number, serial_number)
VALUES
(:company_id, 'sku', :barcode, :product_sku, :lot_number, :serial_number)
ON DUPLICATE KEY UPDATE
barcode_type = VALUES(barcode_type),
product_sku = VALUES(product_sku),
lot_number = VALUES(lot_number),
serial_number = VALUES(serial_number),
status = 1"
);
$sth->execute([
':company_id' => $company_id,
':product_sku' => $product_sku,
':lot_number' => $lot_number,
':serial_number' => $serial_number,
':barcode' => $barcode,
]);
$answer['success'] = 1;
$answer['output'] = [
'product_sku' => $product_sku,
'product_name' => $lot['product_name'],
'lot_number' => $lot_number,
'serial_number' => $serial_number,
'barcode' => $barcode,
];
exit(json_encode($answer));
}
$answer['success'] = 1;
$answer['output'] = listLabels($pdo2, $company_id, $product_sku, $lot_number);
exit(json_encode($answer));
?>
+159
View File
@@ -0,0 +1,159 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
function stockTableNameFromWarehouseId(int $warehouse_id): string {
return 'td_stock_' . $warehouse_id;
}
$sth = $pdo2->prepare(
"SELECT
l.id,
l.product_sku,
l.lot_number,
l.expiry_date,
l.description,
p.product_name,
p.uom,
'md_lot' AS source
FROM md_lot l
INNER JOIN md_product p
ON p.company_id = l.company_id
AND p.sku = l.product_sku
WHERE l.company_id = :company_id
AND p.status > 0
ORDER BY l.product_sku ASC, l.lot_number ASC"
);
$sth->execute([':company_id' => $company_id]);
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
$seen = [];
foreach ($rows as $row) {
$seen[$row['product_sku'] . '|' . $row['lot_number']] = true;
}
$warehouses = $pdo2->prepare(
"SELECT id FROM md_warehouse
WHERE company_id = :company_id AND status = 1"
);
$warehouses->execute([':company_id' => $company_id]);
foreach ($warehouses->fetchAll(PDO::FETCH_COLUMN) as $warehouse_id) {
$table = stockTableNameFromWarehouseId((int)$warehouse_id);
$stock = $pdo2->query(
"SELECT
s.product_sku,
s.lot_number,
p.product_name,
p.uom,
ROUND(SUM(COALESCE(s.`in`, 0)) - SUM(COALESCE(s.`out`, 0)), 2) AS balance
FROM `{$table}` s
INNER JOIN md_product p
ON p.company_id = s.company_id
AND p.sku = s.product_sku
WHERE s.company_id = " . (int)$company_id . "
AND s.status = 1
AND s.lot_number IS NOT NULL
AND s.lot_number <> ''
AND p.status > 0
GROUP BY s.product_sku, s.lot_number, p.product_name, p.uom
HAVING balance > 0"
);
foreach ($stock->fetchAll(PDO::FETCH_ASSOC) as $row) {
$key = $row['product_sku'] . '|' . $row['lot_number'];
if (isset($seen[$key])) continue;
$seen[$key] = true;
$rows[] = [
'id' => null,
'product_sku' => $row['product_sku'],
'lot_number' => $row['lot_number'],
'expiry_date' => null,
'description' => 'Found in active stock; missing md_lot row.',
'product_name'=> $row['product_name'],
'uom' => $row['uom'],
'source' => 'active_stock',
];
}
}
$barcodes = $pdo2->prepare(
"SELECT
b.product_sku,
b.lot_number,
b.serial_number,
b.barcode,
b.status,
b.created_dt,
p.product_name,
p.uom
FROM md_barcode b
INNER JOIN md_product p
ON p.company_id = b.company_id
AND p.sku = b.product_sku
WHERE b.company_id = :company_id
AND b.barcode_type = 'sku'
AND b.product_sku IS NOT NULL
AND b.product_sku <> ''
AND b.lot_number IS NOT NULL
AND b.lot_number <> ''
AND p.status > 0
ORDER BY b.created_dt DESC, b.id DESC"
);
$barcodes->execute([':company_id' => $company_id]);
foreach ($barcodes->fetchAll(PDO::FETCH_ASSOC) as $row) {
$key = $row['product_sku'] . '|' . $row['lot_number'];
if (isset($seen[$key])) continue;
$seen[$key] = true;
$rows[] = [
'id' => null,
'product_sku' => $row['product_sku'],
'lot_number' => $row['lot_number'],
'expiry_date' => null,
'description' => 'Prepared barcode exists; missing md_lot row.',
'product_name' => $row['product_name'],
'uom' => $row['uom'],
'source' => 'md_barcode',
'serial_number' => $row['serial_number'],
'barcode' => $row['barcode'],
];
}
$status_rows = $pdo2->prepare(
"SELECT
product_sku,
lot_number,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS active_count,
SUM(CASE WHEN status <> 1 THEN 1 ELSE 0 END) AS disabled_count
FROM md_barcode
WHERE company_id = :company_id
AND barcode_type = 'sku'
AND product_sku IS NOT NULL
AND product_sku <> ''
AND lot_number IS NOT NULL
AND lot_number <> ''
GROUP BY product_sku, lot_number"
);
$status_rows->execute([':company_id' => $company_id]);
$label_status = [];
foreach ($status_rows->fetchAll(PDO::FETCH_ASSOC) as $row) {
$label_status[$row['product_sku'] . '|' . $row['lot_number']] = [
'active_count' => (int)$row['active_count'],
'disabled_count' => (int)$row['disabled_count'],
];
}
foreach ($rows as &$row) {
$status = $label_status[$row['product_sku'] . '|' . $row['lot_number']] ?? null;
$row['barcode_active_count'] = $status['active_count'] ?? 0;
$row['barcode_disabled_count'] = $status['disabled_count'] ?? 0;
}
unset($row);
usort($rows, fn($a, $b) => [$a['product_sku'], $a['lot_number']] <=> [$b['product_sku'], $b['lot_number']]);
$answer['success'] = 1;
$answer['output'] = $rows;
exit(json_encode($answer));
?>
+17
View File
@@ -0,0 +1,17 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
$sth = $pdo2->prepare(
"SELECT sku, product_name, uom
FROM md_product
WHERE company_id = :company_id
AND status > 0
ORDER BY sku ASC"
);
$sth->execute([':company_id' => $company_id]);
$answer['success'] = 1;
$answer['output'] = $sth->fetchAll(PDO::FETCH_ASSOC);
exit(json_encode($answer));
?>
@@ -0,0 +1,86 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/WarehouseManager.php';
$whMgmt = new WarehouseManager($pdo2, $company_id);
$mode = $data['mode'] ?? '';
$warehouse_id = (int)($data['warehouse_id'] ?? 0);
$zone = trim($data['zone'] ?? '');
$aisle = trim($data['aisle'] ?? '');
$rack = trim($data['rack'] ?? '');
$product_sku = trim($data['product_sku'] ?? '');
$lot_number = trim($data['lot_number'] ?? '');
$serial = trim($data['serial_number'] ?? '');
if (!$warehouse_id || $zone === '' || $aisle === '' || $rack === '') {
$answer['message'] = 'Location label is incomplete.';
http_response_code(400);
exit(json_encode($answer));
}
function rackRow(PDO $pdo, int $company_id, int $warehouse_id, string $zone, string $aisle, string $rack): array|false {
$sth = $pdo->prepare(
"SELECT id, product_sku, td_stock_id
FROM md_rack
WHERE company_id = :company_id
AND warehouse = :warehouse
AND zone = :zone
AND aisle = :aisle
AND rack = :rack
LIMIT 1"
);
$sth->execute([
':company_id' => $company_id,
':warehouse' => $warehouse_id,
':zone' => $zone,
':aisle' => $aisle,
':rack' => $rack,
]);
return $sth->fetch(PDO::FETCH_ASSOC);
}
try {
$rack_row = rackRow($pdo2, $company_id, $warehouse_id, $zone, $aisle, $rack);
if (!$rack_row) throw new Exception("Location {$zone}-{$aisle}-{$rack} does not exist.");
if ($mode === 'in' || $mode === 'to') {
if ($rack_row['product_sku'] !== null) {
throw new Exception("Location {$zone}-{$aisle}-{$rack} is already occupied by {$rack_row['product_sku']}.");
}
if ($mode === 'in' && $product_sku !== '') {
$whMgmt->validateStockUnique($product_sku, $lot_number ?: null, $serial ?: null);
}
} elseif ($mode === 'out' || $mode === 'from') {
if ($product_sku === '') throw new Exception('Scan the SKU label first.');
$stock = $whMgmt->getRackStock($warehouse_id, $zone, $aisle, $rack);
if (!$stock) {
throw new Exception("Location {$zone}-{$aisle}-{$rack} has no approved stock to move out.");
}
if ($stock['product_sku'] !== $product_sku) {
throw new Exception("Location holds {$stock['product_sku']}, not {$product_sku}.");
}
if ($lot_number !== '' && (string)$stock['lot_number'] !== $lot_number) {
throw new Exception("Location holds lot '{$stock['lot_number']}', not '{$lot_number}'.");
}
if ($serial !== '' && (string)$stock['serial_number'] !== $serial) {
throw new Exception("Location holds serial '{$stock['serial_number']}', not '{$serial}'.");
}
} else {
throw new Exception('Invalid scan validation mode.');
}
$answer['success'] = 1;
exit(json_encode($answer));
} catch (Exception $e) {
$answer['message'] = $e->getMessage();
http_response_code(400);
exit(json_encode($answer));
}
?>