Classe methods: remove reducdancy

This commit is contained in:
Thanakorn S
2026-05-29 08:56:58 +07:00
parent ed3dd2f215
commit a0677d6d8d
9 changed files with 112 additions and 93 deletions
+2 -2
View File
@@ -84,7 +84,7 @@ class BarcodeManager {
$warehouses->execute([':company_id' => $this->company_id]); $warehouses->execute([':company_id' => $this->company_id]);
foreach ($warehouses->fetchAll(PDO::FETCH_COLUMN) as $warehouse_id) { foreach ($warehouses->fetchAll(PDO::FETCH_COLUMN) as $warehouse_id) {
$table = $this->stockTableName((int)$warehouse_id); $table = 'td_stock_' . (int)$warehouse_id;
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT lot_number "SELECT lot_number
FROM `{$table}` FROM `{$table}`
@@ -169,7 +169,7 @@ class BarcodeManager {
$warehouses->execute([':company_id' => $this->company_id]); $warehouses->execute([':company_id' => $this->company_id]);
foreach ($warehouses->fetchAll(PDO::FETCH_COLUMN) as $warehouse_id) { foreach ($warehouses->fetchAll(PDO::FETCH_COLUMN) as $warehouse_id) {
$table = $this->stockTableName((int)$warehouse_id); $table = 'td_stock_' . (int)$warehouse_id;
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT ROUND(SUM(COALESCE(`in`, 0)) - SUM(COALESCE(`out`, 0)), 2) AS balance "SELECT ROUND(SUM(COALESCE(`in`, 0)) - SUM(COALESCE(`out`, 0)), 2) AS balance
FROM `{$table}` FROM `{$table}`
+4 -7
View File
@@ -44,14 +44,11 @@ class ContactManager {
private function hasActiveStock(int $contact_id): bool { private function hasActiveStock(int $contact_id): bool {
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT t.table_name "SELECT table_name FROM information_schema.tables
FROM md_warehouse w WHERE table_schema = DATABASE()
JOIN information_schema.tables t AND table_name LIKE 'td_stock_%'"
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
); );
$sth->execute([':company_id' => $this->company_id]); $sth->execute();
$tables = $sth->fetchAll(PDO::FETCH_COLUMN); $tables = $sth->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) { foreach ($tables as $table) {
+24 -19
View File
@@ -86,20 +86,6 @@ class OrderManager {
return 'td_stock_' . $warehouse_id; return 'td_stock_' . $warehouse_id;
} }
private function getStockTables(): array
{
$sth = $this->pdo->prepare(
"SELECT t.table_name
FROM md_warehouse w
JOIN information_schema.tables t
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
);
$sth->execute([':company_id' => $this->company_id]);
return $sth->fetchAll(PDO::FETCH_COLUMN);
}
/** /**
* FIFO bin pick — find the oldest approved stock-in row for a SKU * FIFO bin pick — find the oldest approved stock-in row for a SKU
* in a given warehouse that is still bin-occupied. * in a given warehouse that is still bin-occupied.
@@ -198,7 +184,7 @@ class OrderManager {
$stock_out_id = (int)($item['stock_out_id'] ?? 0); $stock_out_id = (int)($item['stock_out_id'] ?? 0);
if ($warehouse_id <= 0 || $stock_out_id <= 0) continue; if ($warehouse_id <= 0 || $stock_out_id <= 0) continue;
$table = $this->stockTableNameFromWarehouseId($warehouse_id); $table = 'td_stock_' . $warehouse_id;
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT status FROM `{$table}` "SELECT status FROM `{$table}`
WHERE company_id = :company_id WHERE company_id = :company_id
@@ -379,7 +365,7 @@ class OrderManager {
$stock_out_id = (int)($item['stock_out_id'] ?? 0); $stock_out_id = (int)($item['stock_out_id'] ?? 0);
if ($stock_out_warehouse_id > 0 && $stock_out_id > 0) { if ($stock_out_warehouse_id > 0 && $stock_out_id > 0) {
$table = $this->stockTableNameFromWarehouseId($stock_out_warehouse_id); $table = 'td_stock_' . $stock_out_warehouse_id;
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT id, status, zone, aisle, bin, lot_number, serial_number "SELECT id, status, zone, aisle, bin, lot_number, serial_number
FROM `{$table}` FROM `{$table}`
@@ -716,6 +702,22 @@ class OrderManager {
throw new Exception(implode("\n", $preflight_errors)); throw new Exception(implode("\n", $preflight_errors));
} }
// Validate all warehouse IDs in one query before entering the item loop.
$itemWarehouseIds = array_values(array_unique(array_filter(
array_map(fn($item) => (int)($item['warehouse_id'] ?? 0), $items),
fn($id) => $id > 0
)));
if (!empty($itemWarehouseIds)) {
$placeholders = implode(',', array_fill(0, count($itemWarehouseIds), '?'));
$sth = $this->pdo->prepare(
"SELECT id FROM md_warehouse WHERE company_id = ? AND id IN ($placeholders)"
);
$sth->execute([$this->company_id, ...$itemWarehouseIds]);
$validWarehouseIds = array_flip($sth->fetchAll(PDO::FETCH_COLUMN));
} else {
$validWarehouseIds = [];
}
foreach ($items as $i => &$item) { foreach ($items as $i => &$item) {
$warehouse_id = (int)($item['warehouse_id'] ?? 0); $warehouse_id = (int)($item['warehouse_id'] ?? 0);
@@ -725,7 +727,10 @@ class OrderManager {
$bin_stock = $this->pickFifoRack($warehouse_id, $product_sku); $bin_stock = $this->pickFifoRack($warehouse_id, $product_sku);
$available_qty = (float)($bin_stock['available_qty'] ?? $bin_stock['in'] ?? 0); $available_qty = (float)($bin_stock['available_qty'] ?? $bin_stock['in'] ?? 0);
$table = $this->stockTableNameFromWarehouseId($warehouse_id); if (!isset($validWarehouseIds[$warehouse_id])) {
throw new Exception("Warehouse ID {$warehouse_id} not found.");
}
$table = 'td_stock_' . $warehouse_id;
$item_uuid = $uuid . '_' . $i; $item_uuid = $uuid . '_' . $i;
$item_log = [array_merge($logging, ['action' => 'confirm_item'])]; $item_log = [array_merge($logging, ['action' => 'confirm_item'])];
@@ -888,8 +893,8 @@ class OrderManager {
} }
// ── Reverse approved stock-out side effects, then soft-delete rows ─ // ── Reverse approved stock-out side effects, then soft-delete rows ─
$tables = $this->getStockTables();
$whMgmt = new WarehouseManager($this->pdo, $this->company_id); $whMgmt = new WarehouseManager($this->pdo, $this->company_id);
$tables = $whMgmt->getStockTables();
foreach ($tables as $table) { foreach ($tables as $table) {
if (!preg_match('/^td_stock_(\d+)$/', (string)$table, $matches)) { if (!preg_match('/^td_stock_(\d+)$/', (string)$table, $matches)) {
@@ -1034,7 +1039,7 @@ class OrderManager {
} }
// Block if any approved stock-out rows exist; user must reverse via ICS first // Block if any approved stock-out rows exist; user must reverse via ICS first
$tables = $this->getStockTables(); $tables = (new WarehouseManager($this->pdo, $this->company_id))->getStockTables();
foreach ($tables as $table) { foreach ($tables as $table) {
$chk = $this->pdo->prepare( $chk = $this->pdo->prepare(
+4 -7
View File
@@ -45,14 +45,11 @@ class ProductManager {
private function findActiveStock(string $sku): ?string { private function findActiveStock(string $sku): ?string {
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT t.table_name "SELECT table_name FROM information_schema.tables
FROM md_warehouse w WHERE table_schema = DATABASE()
JOIN information_schema.tables t AND table_name LIKE 'td_stock_%'"
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
); );
$sth->execute([':company_id' => $this->company_id]); $sth->execute();
$tables = $sth->fetchAll(PDO::FETCH_COLUMN); $tables = $sth->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) { foreach ($tables as $table) {
@@ -1,5 +1,6 @@
<?php <?php
require_once __DIR__ . '/DocumentNumberManager.php'; require_once __DIR__ . '/DocumentNumberManager.php';
require_once __DIR__ . '/WarehouseManager.php';
require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php'; require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php';
/** /**
@@ -97,7 +98,7 @@ class PurchaseOrderManager {
} }
if (!$has_any_stock_in) return 0; if (!$has_any_stock_in) return 0;
$tables = $this->getStockTables(); $tables = (new WarehouseManager($this->pdo, $this->company_id))->getStockTables();
$total = 0; $total = 0;
$pending = 0; $pending = 0;
@@ -149,20 +150,6 @@ class PurchaseOrderManager {
return 'td_stock_' . $warehouse_id; return 'td_stock_' . $warehouse_id;
} }
private function getStockTables(): array
{
$sth = $this->pdo->prepare(
"SELECT t.table_name
FROM md_warehouse w
JOIN information_schema.tables t
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
);
$sth->execute([':company_id' => $this->company_id]);
return $sth->fetchAll(PDO::FETCH_COLUMN);
}
private function syncPoItems(int $po_id, array $items): void private function syncPoItems(int $po_id, array $items): void
{ {
$this->pdo->prepare( $this->pdo->prepare(
@@ -563,6 +550,22 @@ class PurchaseOrderManager {
$stock = new StockManager($this->pdo, $this->company_id); $stock = new StockManager($this->pdo, $this->company_id);
$whMgmt = new WarehouseManager($this->pdo, $this->company_id); $whMgmt = new WarehouseManager($this->pdo, $this->company_id);
// Validate all warehouse IDs in one query before entering the item loop.
$recvWarehouseIds = array_values(array_unique(array_filter(
array_map(fn($recv) => (int)($recv['warehouse_id'] ?? $po['warehouse_id']), $receive_items),
fn($id) => $id > 0
)));
if (!empty($recvWarehouseIds)) {
$placeholders = implode(',', array_fill(0, count($recvWarehouseIds), '?'));
$sth = $this->pdo->prepare(
"SELECT id FROM md_warehouse WHERE company_id = ? AND id IN ($placeholders)"
);
$sth->execute([$this->company_id, ...$recvWarehouseIds]);
$validWarehouseIds = array_flip($sth->fetchAll(PDO::FETCH_COLUMN));
} else {
$validWarehouseIds = [];
}
foreach ($receive_items as $j => $recv) { foreach ($receive_items as $j => $recv) {
$item_id = (int)($recv['item_id'] ?? -1); $item_id = (int)($recv['item_id'] ?? -1);
$product_sku = $recv['product_sku'] ?? ''; $product_sku = $recv['product_sku'] ?? '';
@@ -632,7 +635,10 @@ class PurchaseOrderManager {
// saveStockIn() does not write source/source_id — stamp them here. // saveStockIn() does not write source/source_id — stamp them here.
// This links the stock-in row back to this PO for cancellation guards // This links the stock-in row back to this PO for cancellation guards
// and makes it appear under the "PO" source tab on the Stock In list. // and makes it appear under the "PO" source tab on the Stock In list.
$table = $this->stockTableNameFromWarehouseId($warehouse_id); if (!isset($validWarehouseIds[$warehouse_id])) {
throw new Exception("Warehouse ID {$warehouse_id} not found.");
}
$table = 'td_stock_' . $warehouse_id;
$this->pdo->prepare( $this->pdo->prepare(
"UPDATE `{$table}` SET source = 'po', source_id = :po_id "UPDATE `{$table}` SET source = 'po', source_id = :po_id
WHERE id = :id AND company_id = :company_id" WHERE id = :id AND company_id = :company_id"
@@ -746,7 +752,8 @@ class PurchaseOrderManager {
if ($status === -1) throw new Exception("PO is already cancelled."); if ($status === -1) throw new Exception("PO is already cancelled.");
// Guard: block if any approved stock-in rows exist for this PO // Guard: block if any approved stock-in rows exist for this PO
$tables = $this->getStockTables(); $whMgmt = new WarehouseManager($this->pdo, $this->company_id);
$tables = $whMgmt->getStockTables();
foreach ($tables as $table) { foreach ($tables as $table) {
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
@@ -765,8 +772,6 @@ class PurchaseOrderManager {
} }
} }
$whMgmt = new WarehouseManager($this->pdo, $this->company_id);
// Soft-delete draft stock-in rows and release their bin reservations. // Soft-delete draft stock-in rows and release their bin reservations.
foreach ($tables as $table) { foreach ($tables as $table) {
if (!preg_match('/^td_stock_(\d+)$/', $table, $m)) { if (!preg_match('/^td_stock_(\d+)$/', $table, $m)) {
@@ -887,7 +892,8 @@ class PurchaseOrderManager {
throw new Exception('Cannot delete — this PO has linked supplier returns. Delete or cancel the returns first.'); throw new Exception('Cannot delete — this PO has linked supplier returns. Delete or cancel the returns first.');
} }
$tables = $this->getStockTables(); $whMgmt = new WarehouseManager($this->pdo, $this->company_id);
$tables = $whMgmt->getStockTables();
foreach ($tables as $table) { foreach ($tables as $table) {
$sth5 = $this->pdo->prepare( $sth5 = $this->pdo->prepare(
@@ -901,7 +907,6 @@ class PurchaseOrderManager {
} }
// Release bins from draft stock-in rows and negate them // Release bins from draft stock-in rows and negate them
$whMgmt = new WarehouseManager($this->pdo, $this->company_id);
foreach ($tables as $table) { foreach ($tables as $table) {
if (!preg_match('/^td_stock_(\d+)$/', (string)$table, $matches)) continue; if (!preg_match('/^td_stock_(\d+)$/', (string)$table, $matches)) continue;
$warehouse_id = (int)$matches[1]; $warehouse_id = (int)$matches[1];
+6 -17
View File
@@ -1,6 +1,7 @@
<?php <?php
require_once __DIR__ . '/DocumentNumberManager.php'; require_once __DIR__ . '/DocumentNumberManager.php';
require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php'; require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php';
require_once __DIR__ . '/WarehouseManager.php';
/** /**
* ReturnManager * ReturnManager
@@ -88,14 +89,11 @@ class ReturnManager {
private function deriveReceiptStatus(array $ret): int private function deriveReceiptStatus(array $ret): int
{ {
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT t.table_name "SELECT table_name FROM information_schema.tables
FROM md_warehouse w WHERE table_schema = DATABASE()
JOIN information_schema.tables t AND table_name LIKE 'td_stock_%'"
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
); );
$sth->execute([':company_id' => $this->company_id]); $sth->execute();
$tables = $sth->fetchAll(PDO::FETCH_COLUMN); $tables = $sth->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) { foreach ($tables as $table) {
@@ -749,16 +747,7 @@ class ReturnManager {
} }
// Block if any approved stock-in rows exist; user must reverse via ICS first // Block if any approved stock-in rows exist; user must reverse via ICS first
$sth = $this->pdo->prepare( $tables = (new WarehouseManager($this->pdo, $this->company_id))->getStockTables();
"SELECT t.table_name
FROM md_warehouse w
JOIN information_schema.tables t
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
);
$sth->execute([':company_id' => $this->company_id]);
$tables = $sth->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) { foreach ($tables as $table) {
$chk = $this->pdo->prepare( $chk = $this->pdo->prepare(
@@ -0,0 +1,26 @@
<?php
/**
* Provides getStockTables() — discovers td_stock_* tables scoped to this
* company's active and historical warehouses via an information_schema JOIN.
*
* Requires the using class to expose:
* $this->pdo PDO
* $this->company_id int
*/
trait StockTablesTrait
{
private function getStockTables(): array
{
$sth = $this->pdo->prepare(
"SELECT t.table_name
FROM md_warehouse w
JOIN information_schema.tables t
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
);
$sth->execute([':company_id' => $this->company_id]);
return $sth->fetchAll(PDO::FETCH_COLUMN);
}
}
@@ -1,6 +1,8 @@
<?php <?php
require_once __DIR__ . '/DocumentNumberManager.php'; require_once __DIR__ . '/DocumentNumberManager.php';
require_once __DIR__ . '/StockTablesTrait.php';
require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php'; require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php';
require_once __DIR__ . '/WarehouseManager.php';
/** /**
* SupplierReturnManager * SupplierReturnManager
@@ -27,6 +29,8 @@ require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php';
*/ */
class SupplierReturnManager { class SupplierReturnManager {
use StockTablesTrait;
private PDO $pdo; private PDO $pdo;
private int $company_id; private int $company_id;
@@ -69,16 +73,7 @@ class SupplierReturnManager {
*/ */
private function deriveFulfillmentStatus(array $ret): int private function deriveFulfillmentStatus(array $ret): int
{ {
$sth = $this->pdo->prepare( $tables = $this->getStockTables();
"SELECT t.table_name
FROM md_warehouse w
JOIN information_schema.tables t
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
);
$sth->execute([':company_id' => $this->company_id]);
$tables = $sth->fetchAll(PDO::FETCH_COLUMN);
$has_stock_out = false; $has_stock_out = false;
$has_draft = false; $has_draft = false;
@@ -650,16 +645,7 @@ class SupplierReturnManager {
} }
// Block if any approved stock-out rows exist; user must reverse via ICS first // Block if any approved stock-out rows exist; user must reverse via ICS first
$sth = $this->pdo->prepare( $tables = $this->getStockTables();
"SELECT t.table_name
FROM md_warehouse w
JOIN information_schema.tables t
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
);
$sth->execute([':company_id' => $this->company_id]);
$tables = $sth->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) { foreach ($tables as $table) {
$chk = $this->pdo->prepare( $chk = $this->pdo->prepare(
@@ -52,6 +52,20 @@ class WarehouseManager {
return 'td_stock_' . $warehouse_id; return 'td_stock_' . $warehouse_id;
} }
public function getStockTables(): array
{
$sth = $this->pdo->prepare(
"SELECT t.table_name
FROM md_warehouse w
JOIN information_schema.tables t
ON t.table_schema = DATABASE()
AND t.table_name = CONCAT('td_stock_', w.id)
WHERE w.company_id = :company_id"
);
$sth->execute([':company_id' => $this->company_id]);
return $sth->fetchAll(PDO::FETCH_COLUMN);
}
public function assertStockMovementWindow(?string $date, string $context = 'Stock movement'): void public function assertStockMovementWindow(?string $date, string $context = 'Stock movement'): void
{ {
global $pdo1; global $pdo1;