implement JS password scoring, td_stock approve logicc
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user