implement JS password scoring, td_stock approve logicc

This commit is contained in:
Thanakorn S
2026-04-30 15:14:42 +07:00
parent db5c47b6ca
commit ac4fd9a5ad
3147 changed files with 1298 additions and 343 deletions
+148 -38
View File
@@ -216,7 +216,8 @@ class WarehouseManager {
$unions = implode(' UNION ALL ', array_map(
fn($t) => "SELECT `type`, `date` FROM `$t`
WHERE company_id = :company_id
AND product_sku = :product_sku",
AND product_sku = :product_sku
AND status = 1",
$tables
));
@@ -428,6 +429,25 @@ class WarehouseManager {
return $sth->fetchColumn();
}
/**
* Reverse lookup — return warehouse_id for a given warehouse_name.
* Used by approveStock() to resolve the destination warehouse on transfers.
*
* @param string $name The warehouse_name stored in td_stock.ref_warehouse.
* @return int|null The warehouse id, or null if not found.
*/
public function getWarehouseIdByName(string $name): ?int
{
$sth = $this->pdo->prepare(
"SELECT id FROM md_warehouse
WHERE company_id = :company_id AND warehouse_name = :name
LIMIT 1"
);
$sth->execute([':company_id' => $this->company_id, ':name' => $name]);
$id = $sth->fetchColumn();
return $id !== false ? (int)$id : null;
}
/**
* Insert a new warehouse or update an existing one.
*
@@ -853,14 +873,35 @@ class WarehouseManager {
*/
public function getLotList(string $product_sku, string $keyword): array
{
// Only return lots that have at least one active td_stock row.
// Lots whose stock was fully soft-deleted (company_id negated) are excluded.
$sth = $this->pdo->prepare(
"SELECT id, warehouse_name FROM md_warehouse
WHERE company_id = :company_id AND status = 1"
);
$sth->execute([':company_id' => $this->company_id]);
$warehouses = $sth->fetchAll(PDO::FETCH_ASSOC);
if (empty($warehouses)) return [];
$cid = (int)$this->company_id;
// Build UNION EXISTS subquery across all td_stock_* tables
$unions = implode(' UNION ALL ', array_map(function($wh) use ($cid) {
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']);
return "SELECT lot_number FROM `td_stock_{$safe}`
WHERE company_id = {$cid} AND lot_number IS NOT NULL";
}, $warehouses));
$sth = $this->pdo->prepare(
"SELECT *
FROM md_lot
WHERE company_id = :company_id
AND product_sku = :product_sku
AND lot_number LIKE :keyword
ORDER BY lot_number ASC
LIMIT 50"
FROM md_lot
WHERE company_id = :company_id
AND product_sku = :product_sku
AND lot_number LIKE :keyword
AND lot_number IN (SELECT lot_number FROM ({$unions}) AS all_lots)
ORDER BY lot_number ASC
LIMIT 50"
);
$sth->execute([
':company_id' => $this->company_id,
@@ -906,10 +947,12 @@ class WarehouseManager {
WHERE s.company_id = :company_id
AND s.product_sku = :product_sku
AND s.type = 'in'
AND s.status = 1
AND s.lot_number IS NOT NULL
AND EXISTS (
SELECT 1 FROM md_rack r
WHERE r.company_id = s.company_id
AND r.warehouse = :warehouse_id
AND r.td_stock_id = s.id
AND r.product_sku IS NOT NULL
)"
@@ -917,6 +960,7 @@ class WarehouseManager {
$sth->execute([
':company_id' => $this->company_id,
':product_sku' => $product_sku,
':warehouse_id' => $wh['id'],
]);
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
@@ -953,15 +997,18 @@ class WarehouseManager {
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']);
$table = "td_stock_{$safe}";
$wh_id = $wh['id'];
$sql = "SELECT DISTINCT s.serial_number
FROM `{$table}` s
WHERE s.company_id = :company_id
AND s.product_sku = :product_sku
AND s.type = 'in'
AND s.status = 1
AND s.serial_number IS NOT NULL
AND EXISTS (
SELECT 1 FROM md_rack r
WHERE r.company_id = s.company_id
AND r.warehouse = {$wh_id}
AND r.td_stock_id = s.id
AND r.product_sku IS NOT NULL
)";
@@ -1765,11 +1812,11 @@ class WarehouseManager {
WHERE id = :id AND company_id = :company_id"
)->execute([':id' => $stock_id, ':company_id' => $this->company_id]);
// Release the rack back to empty
$this->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']);
// Reverse the balance (subtract the previously added quantity)
$this->adjustBalance('in', $warehouse_id, $product_sku, (int)$row['in'], 0);
// Only reverse side effects if the row was approved — draft rows never had them applied
if ((int)$row['status'] === 1) {
$this->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']);
$this->adjustBalance('in', $warehouse_id, $product_sku, (int)$row['in'], 0);
}
}
/**
@@ -1809,16 +1856,17 @@ class WarehouseManager {
WHERE id = :id AND company_id = :company_id"
)->execute([':id' => $stock_id, ':company_id' => $this->company_id]);
// Re-occupy the rack with the original stock_in batch that was consumed
$this->occupyRack(
$warehouse_id,
$row['zone'], $row['aisle'], $row['rack'],
$product_sku,
(int)$row['ref_id']
);
// Reverse the balance (subtract the previously deducted quantity)
$this->adjustBalance('out', $warehouse_id, $product_sku, (int)$row['out'], 0);
// Only reverse side effects if the row was approved — draft rows never had them applied
if ((int)$row['status'] === 1) {
// Re-occupy the rack with the original stock_in batch that was consumed
$this->occupyRack(
$warehouse_id,
$row['zone'], $row['aisle'], $row['rack'],
$product_sku,
(int)$row['ref_id']
);
$this->adjustBalance('out', $warehouse_id, $product_sku, (int)$row['out'], 0);
}
}
/**
@@ -1877,30 +1925,92 @@ class WarehouseManager {
WHERE id = :id AND company_id = :company_id"
)->execute([':id' => $ref_id, ':company_id' => $this->company_id]);
// Destination rack was occupied by transfer_in — release it
$this->releaseRack(
$to_warehouse,
$to_row['zone'], $to_row['aisle'], $to_row['rack']
);
// Only reverse side effects if approved — draft rows never had them applied
if ((int)$from_row['status'] === 1) {
// Destination rack was occupied by transfer_in — release it
$this->releaseRack(
$to_warehouse,
$to_row['zone'], $to_row['aisle'], $to_row['rack']
);
// Source rack was released by transfer_out — re-occupy with original batch
$this->occupyRack(
$from_warehouse_id,
$from_row['zone'], $from_row['aisle'], $from_row['rack'],
$product_sku,
$from_stock_id
);
// Source rack was released by transfer_out — re-occupy with original batch
$this->occupyRack(
$from_warehouse_id,
$from_row['zone'], $from_row['aisle'], $from_row['rack'],
$product_sku,
$from_stock_id
);
// Reverse balances on both sides
$quantity = (int)$from_row['out'];
$this->adjustBalance('out', $from_warehouse_id, $product_sku, $quantity, 0);
$this->adjustBalance('in', $to_warehouse, $product_sku, $quantity, 0);
// Reverse balances on both sides
$quantity = (int)$from_row['out'];
$this->adjustBalance('out', $from_warehouse_id, $product_sku, $quantity, 0);
$this->adjustBalance('in', $to_warehouse, $product_sku, $quantity, 0);
}
}
// ─────────────────────────────────────────────────────────────
// REPORT BASIS — Warehouse list queries
// ─────────────────────────────────────────────────────────────
/**
* Return warehouse ids that currently hold an approved, rack-occupied stock-in
* row for a given product_sku + lot_number combination.
*
* Used to filter the warehouse dropdown in stock-out/transfer when a lot is selected,
* so only warehouses actually holding that lot are shown.
*
* @param string $product_sku
* @param string $lot_number
* @return int[] Array of warehouse ids.
*/
public function getWarehousesByLot(string $product_sku, string $lot_number): array
{
$sth = $this->pdo->prepare(
"SELECT id, warehouse_name FROM md_warehouse
WHERE company_id = :company_id AND status = 1"
);
$sth->execute([':company_id' => $this->company_id]);
$warehouses = $sth->fetchAll(PDO::FETCH_ASSOC);
$result = [];
$cid = (int)$this->company_id;
foreach ($warehouses as $wh) {
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']);
$table = "td_stock_{$safe}";
$wh_id = (int)$wh['id'];
$sth = $this->pdo->prepare(
"SELECT 1 FROM `{$table}` s
WHERE s.company_id = :company_id
AND s.product_sku = :product_sku
AND s.lot_number = :lot_number
AND s.type = 'in'
AND s.status = 1
AND EXISTS (
SELECT 1 FROM md_rack r
WHERE r.company_id = s.company_id
AND r.warehouse = :warehouse_id
AND r.td_stock_id = s.id
AND r.product_sku IS NOT NULL
)
LIMIT 1"
);
$sth->execute([
':company_id' => $cid,
':product_sku' => $product_sku,
':lot_number' => $lot_number,
':warehouse_id' => $wh_id,
]);
if ($sth->fetchColumn() !== false) {
$result[] = ['id' => $wh_id, 'warehouse_name' => $wh['warehouse_name']];
}
}
return $result;
}
/**
* Return warehouses filtered by rack availability — for stock movement warehouse selectors.
*