implement JS password scoring, td_stock approve logicc
This commit is contained in:
@@ -252,7 +252,7 @@ class StockManager {
|
||||
* @param array $logging Audit entry to append to the log column.
|
||||
* @param string $uuid UUID for this transaction (shared across transfer pairs).
|
||||
*/
|
||||
public function saveStockIn(array $data, array $logging, string $uuid): void
|
||||
public function saveStockIn(array $data, array $logging, string $uuid): int
|
||||
{
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$warehouse_id = (int)($data['warehouse'] ?? 0);
|
||||
@@ -282,6 +282,8 @@ class StockManager {
|
||||
':log' => json_encode($table_log),
|
||||
]);
|
||||
|
||||
return 0; // update — no new row
|
||||
|
||||
} else {
|
||||
|
||||
$lot_number = $data['lot_number'] ?: null;
|
||||
@@ -304,10 +306,10 @@ class StockManager {
|
||||
$this->pdo->prepare(
|
||||
"INSERT INTO `$table`
|
||||
(uuid, company_id, `date`, product_sku, `in`, zone, aisle, rack,
|
||||
contact_id, `description`, `log`, `type`, lot_number, serial_number)
|
||||
contact_id, `description`, `log`, `type`, lot_number, serial_number, status)
|
||||
VALUES
|
||||
(:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack,
|
||||
:contact_id, :description, :log, 'in', :lot_number, :serial_number)"
|
||||
:contact_id, :description, :log, 'in', :lot_number, :serial_number, 0)"
|
||||
)->execute([
|
||||
':uuid' => $uuid,
|
||||
':company_id' => $this->company_id,
|
||||
@@ -326,22 +328,8 @@ class StockManager {
|
||||
|
||||
$td_stock_id = (int)$this->pdo->lastInsertId();
|
||||
|
||||
// Mark rack as occupied and link it to this stock row
|
||||
$whMgmt->occupyRack(
|
||||
$warehouse_id,
|
||||
$data['zone'], $data['aisle'], $data['rack'],
|
||||
$data['product_sku'],
|
||||
$td_stock_id
|
||||
);
|
||||
|
||||
// Update running balance (+quantity in this warehouse)
|
||||
$whMgmt->adjustBalance(
|
||||
'in',
|
||||
$warehouse_id,
|
||||
$data['product_sku'],
|
||||
$row['in'] ?? 0,
|
||||
$data['quantity']
|
||||
);
|
||||
// occupyRack and adjustBalance deferred — called from approveStock() only.
|
||||
return $td_stock_id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +353,7 @@ class StockManager {
|
||||
* @param string $uuid UUID for this transaction.
|
||||
* @throws Exception If the rack is empty, holds a different SKU/lot/serial.
|
||||
*/
|
||||
public function saveStockOut(array $data, array $logging, string $uuid): void
|
||||
public function saveStockOut(array $data, array $logging, string $uuid): int
|
||||
{
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$warehouse_id = (int)($data['warehouse'] ?? 0);
|
||||
@@ -395,6 +383,8 @@ class StockManager {
|
||||
':log' => json_encode($table_log),
|
||||
]);
|
||||
|
||||
return 0; // update — no new row
|
||||
|
||||
} else {
|
||||
|
||||
// Validate rack holds the expected product / lot / serial
|
||||
@@ -436,10 +426,10 @@ class StockManager {
|
||||
$this->pdo->prepare(
|
||||
"INSERT INTO `$table`
|
||||
(uuid, company_id, `date`, product_sku, `out`, zone, aisle, rack,
|
||||
contact_id, `description`, `log`, `type`, ref_id, lot_number, serial_number)
|
||||
contact_id, `description`, `log`, `type`, ref_id, lot_number, serial_number, status)
|
||||
VALUES
|
||||
(:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack,
|
||||
:contact_id, :description, :log, 'out', :ref_id, :lot_number, :serial_number)"
|
||||
:contact_id, :description, :log, 'out', :ref_id, :lot_number, :serial_number, 0)"
|
||||
)->execute([
|
||||
':uuid' => $uuid,
|
||||
':company_id' => $this->company_id,
|
||||
@@ -457,19 +447,10 @@ class StockManager {
|
||||
':serial_number' => $serial_number,
|
||||
]);
|
||||
|
||||
// Release the source rack and reverse balance
|
||||
$whMgmt->releaseRack(
|
||||
$warehouse_id,
|
||||
$data['zone'], $data['aisle'], $data['rack']
|
||||
);
|
||||
$out_stock_id = (int)$this->pdo->lastInsertId();
|
||||
|
||||
$whMgmt->adjustBalance(
|
||||
'out',
|
||||
$warehouse_id,
|
||||
$data['product_sku'],
|
||||
$row['out'] ?? 0,
|
||||
$quantity
|
||||
);
|
||||
// releaseRack and adjustBalance deferred — called from approveStock() only.
|
||||
return $out_stock_id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +481,7 @@ class StockManager {
|
||||
* @param string $uuid UUID shared by both the from and to rows.
|
||||
* @throws Exception If source rack validation fails or paired record is missing on update.
|
||||
*/
|
||||
public function saveStockTransfer(array $data, array $logging, string $uuid): void
|
||||
public function saveStockTransfer(array $data, array $logging, string $uuid): int
|
||||
{
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$whMgmt = new WarehouseManager($this->pdo, $this->company_id);
|
||||
@@ -564,7 +545,7 @@ class StockManager {
|
||||
':company_id' => $this->company_id,
|
||||
]);
|
||||
|
||||
return;
|
||||
return 0; // update — no new row
|
||||
}
|
||||
|
||||
// Insert: validate source rack, then create paired rows
|
||||
@@ -617,18 +598,18 @@ class StockManager {
|
||||
"INSERT INTO `$from_table`
|
||||
(uuid, company_id, `date`, product_sku, `out`,
|
||||
ref_warehouse, zone, aisle, rack,
|
||||
contact_id, `description`, `log`, `type`, lot_number, serial_number)
|
||||
contact_id, `description`, `log`, `type`, lot_number, serial_number, status)
|
||||
VALUES
|
||||
(:uuid, :company_id, :date, :product_sku, :quantity,
|
||||
:ref_warehouse, :zone, :aisle, :rack,
|
||||
:contact_id, :description, :log, 'transfer', :lot_number, :serial_number)"
|
||||
:contact_id, :description, :log, 'transfer', :lot_number, :serial_number, 0)"
|
||||
)->execute([
|
||||
':uuid' => $uuid,
|
||||
':company_id' => $this->company_id,
|
||||
':date' => date('Y-m-d H:i:s'),
|
||||
':product_sku' => $data['product_sku'],
|
||||
':quantity' => $quantity,
|
||||
':ref_warehouse' => $to_warehouse,
|
||||
':ref_warehouse' => $whMgmt->getWarehouseName($to_warehouse),
|
||||
':zone' => $from_zone,
|
||||
':aisle' => $from_aisle,
|
||||
':rack' => $from_rack,
|
||||
@@ -645,18 +626,18 @@ class StockManager {
|
||||
"INSERT INTO `$to_table`
|
||||
(uuid, company_id, `date`, product_sku, `in`,
|
||||
ref_warehouse, ref_id, zone, aisle, rack,
|
||||
contact_id, `description`, `log`, `type`, lot_number, serial_number)
|
||||
contact_id, `description`, `log`, `type`, lot_number, serial_number, status)
|
||||
VALUES
|
||||
(:uuid, :company_id, :date, :product_sku, :quantity,
|
||||
:ref_warehouse, :ref_id, :zone, :aisle, :rack,
|
||||
:contact_id, :description, :log, 'transfer', :lot_number, :serial_number)"
|
||||
:contact_id, :description, :log, 'transfer', :lot_number, :serial_number, 0)"
|
||||
)->execute([
|
||||
':uuid' => $uuid,
|
||||
':company_id' => $this->company_id,
|
||||
':date' => date('Y-m-d H:i:s'),
|
||||
':product_sku' => $data['product_sku'],
|
||||
':quantity' => $quantity,
|
||||
':ref_warehouse' => $from_warehouse,
|
||||
':ref_warehouse' => $whMgmt->getWarehouseName($from_warehouse),
|
||||
':ref_id' => $from_stock_id,
|
||||
':zone' => $to_zone,
|
||||
':aisle' => $to_aisle,
|
||||
@@ -679,13 +660,160 @@ class StockManager {
|
||||
':company_id' => $this->company_id,
|
||||
]);
|
||||
|
||||
// Rack state: release source, occupy destination
|
||||
$whMgmt->releaseRack($from_warehouse, $from_zone, $from_aisle, $from_rack);
|
||||
$whMgmt->occupyRack($to_warehouse, $to_zone, $to_aisle, $to_rack, $data['product_sku'], $to_stock_id);
|
||||
// releaseRack, occupyRack and adjustBalance deferred — called from approveStock() only.
|
||||
return $from_stock_id;
|
||||
}
|
||||
|
||||
// Balance: deduct from source, add to destination
|
||||
$whMgmt->adjustBalance('out', $from_warehouse, $data['product_sku'], 0, $quantity);
|
||||
$whMgmt->adjustBalance('in', $to_warehouse, $data['product_sku'], 0, $quantity);
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Approve
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Approve a draft td_stock row (status 0 → 1) and update warehouse_balance.
|
||||
*
|
||||
* This is the single entry point for balance updates — both auto-approve
|
||||
* (called immediately after save) and manual approve go through here.
|
||||
* adjustBalance() is never called from anywhere else.
|
||||
*
|
||||
* For transfer rows, both the outbound (from) and inbound (to) rows share
|
||||
* the same uuid. We approve both in one call so the pair is always consistent.
|
||||
*
|
||||
* @param int $id The td_stock row id.
|
||||
* @param int $warehouse_id The warehouse the row belongs to.
|
||||
* @param string $type 'in' | 'out' | 'transfer'
|
||||
* @param WarehouseManager $whMgmt Injected to keep balance logic centralised.
|
||||
* @throws Exception If the row is not found, already approved, or wrong company.
|
||||
*/
|
||||
public function approveStock(int $id, int $warehouse_id, string $type, WarehouseManager $whMgmt): void
|
||||
{
|
||||
$table = $this->resolveTable($warehouse_id);
|
||||
|
||||
// Load the row and validate ownership / status
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT * FROM `{$table}`
|
||||
WHERE id = :id AND company_id = :company_id
|
||||
LIMIT 1"
|
||||
);
|
||||
$sth->execute([':id' => $id, ':company_id' => $this->company_id]);
|
||||
$row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
throw new Exception('Stock record not found.');
|
||||
}
|
||||
|
||||
if ((int)$row['status'] === 1) {
|
||||
throw new Exception('Already approved.');
|
||||
}
|
||||
|
||||
// ── Approve this row ──────────────────────────────────────────────
|
||||
$this->pdo->prepare(
|
||||
"UPDATE `{$table}` SET status = 1 WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([':id' => $id, ':company_id' => $this->company_id]);
|
||||
|
||||
// ── Rack state + balance ──────────────────────────────────────────
|
||||
if ($type === 'in') {
|
||||
|
||||
// Occupy rack now that stock is approved
|
||||
$whMgmt->occupyRack(
|
||||
$warehouse_id,
|
||||
$row['zone'], $row['aisle'], $row['rack'],
|
||||
$row['product_sku'],
|
||||
$id
|
||||
);
|
||||
$whMgmt->adjustBalance('in', $warehouse_id, $row['product_sku'], 0, (float)$row['in']);
|
||||
|
||||
} elseif ($type === 'out') {
|
||||
|
||||
// Release rack now that stock-out is approved
|
||||
$whMgmt->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']);
|
||||
$whMgmt->adjustBalance('out', $warehouse_id, $row['product_sku'], 0, (float)$row['out']);
|
||||
|
||||
} elseif ($type === 'transfer') {
|
||||
|
||||
// Transfer always has two rows in two different tables:
|
||||
// outbound row → td_stock_<from> (out > 0, ref_warehouse = to_name)
|
||||
// inbound row → td_stock_<to> (in > 0, ref_warehouse = from_name)
|
||||
// Both rows share the same uuid. We always approve both atomically.
|
||||
// Identify which side we were given and derive the other.
|
||||
|
||||
$is_outbound = (float)$row['out'] > 0;
|
||||
|
||||
// The outbound row lives in the from-warehouse table (already loaded as $row/$table).
|
||||
// The inbound row lives in td_stock_<ref_warehouse>.
|
||||
$from_row = $is_outbound ? $row : null;
|
||||
$from_table = $is_outbound ? $table : null;
|
||||
$from_wh_id = $is_outbound ? $warehouse_id : null;
|
||||
|
||||
// Resolve the paired table from ref_warehouse
|
||||
$paired_wh_name = $row['ref_warehouse'];
|
||||
$paired_safe = preg_replace('/[^a-zA-Z0-9_]/', '', $paired_wh_name);
|
||||
$paired_table = "td_stock_{$paired_safe}";
|
||||
$paired_wh_id = $whMgmt->getWarehouseIdByName($paired_wh_name);
|
||||
|
||||
// If we received the inbound side, swap so $from_* is always outbound
|
||||
if (!$is_outbound) {
|
||||
$from_table = $paired_table;
|
||||
$from_wh_id = $paired_wh_id;
|
||||
$paired_table = $table;
|
||||
$paired_wh_id = $warehouse_id;
|
||||
}
|
||||
|
||||
// Load the inbound row from the paired table using uuid
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT * FROM `{$paired_table}`
|
||||
WHERE uuid = :uuid AND company_id = :company_id
|
||||
LIMIT 1"
|
||||
);
|
||||
$sth->execute([':uuid' => $row['uuid'], ':company_id' => $this->company_id]);
|
||||
$inbound_row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Load outbound row if we were given the inbound side
|
||||
if (!$is_outbound) {
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT * FROM `{$from_table}`
|
||||
WHERE uuid = :uuid AND company_id = :company_id
|
||||
LIMIT 1"
|
||||
);
|
||||
$sth->execute([':uuid' => $row['uuid'], ':company_id' => $this->company_id]);
|
||||
$from_row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
$from_row = $row;
|
||||
}
|
||||
|
||||
// Approve outbound row
|
||||
if ($from_row && (int)$from_row['status'] === 0) {
|
||||
$this->pdo->prepare(
|
||||
"UPDATE `{$from_table}` SET status = 1
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([':id' => $from_row['id'], ':company_id' => $this->company_id]);
|
||||
}
|
||||
|
||||
// Approve inbound row
|
||||
if ($inbound_row && (int)$inbound_row['status'] === 0) {
|
||||
$this->pdo->prepare(
|
||||
"UPDATE `{$paired_table}` SET status = 1
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([':id' => $inbound_row['id'], ':company_id' => $this->company_id]);
|
||||
}
|
||||
|
||||
// Rack state: release source, occupy destination
|
||||
if ($from_row && $from_wh_id) {
|
||||
$whMgmt->releaseRack(
|
||||
$from_wh_id,
|
||||
$from_row['zone'], $from_row['aisle'], $from_row['rack']
|
||||
);
|
||||
$whMgmt->adjustBalance('out', $from_wh_id, $from_row['product_sku'], 0, (float)$from_row['out']);
|
||||
}
|
||||
if ($inbound_row && $paired_wh_id) {
|
||||
$whMgmt->occupyRack(
|
||||
$paired_wh_id,
|
||||
$inbound_row['zone'], $inbound_row['aisle'], $inbound_row['rack'],
|
||||
$inbound_row['product_sku'],
|
||||
$inbound_row['id']
|
||||
);
|
||||
$whMgmt->adjustBalance('in', $paired_wh_id, $inbound_row['product_sku'], 0, (float)$inbound_row['in']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user