[OOP] contact,inventory,ics,dashboard
This commit is contained in:
@@ -1272,6 +1272,430 @@ class WarehouseManager {
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Warehouse list queries
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List warehouses filtered by rack occupancy type.
|
||||
* type='to' → only warehouses with at least one empty rack
|
||||
* type='from' → only warehouses holding the given product_sku
|
||||
* other → all warehouses
|
||||
* Pass $id > 0 to bypass the filter (edit flows).
|
||||
*/
|
||||
public function getWarehouseList(string $type, string $product_sku = '', int $id = 0): array
|
||||
{
|
||||
$product_sku_filter = '';
|
||||
$params = [':company_id' => $this->company_id];
|
||||
|
||||
if (!$id) {
|
||||
if ($type === 'to') {
|
||||
$product_sku_filter = 'AND r.product_sku IS NULL';
|
||||
} elseif ($type === 'from') {
|
||||
$product_sku_filter = 'AND r.product_sku = :product_sku';
|
||||
$params[':product_sku'] = $product_sku;
|
||||
}
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT w.id, w.warehouse_name
|
||||
FROM md_warehouse w
|
||||
WHERE w.company_id = :company_id
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM md_rack r
|
||||
WHERE r.company_id = w.company_id
|
||||
AND r.warehouse = w.id
|
||||
$product_sku_filter
|
||||
)
|
||||
ORDER BY w.warehouse_name"
|
||||
);
|
||||
$sth->execute($params);
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full warehouse list with rack stats and manager name — for inventory listing.
|
||||
*/
|
||||
public function getWarehouseListAll(): array
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT
|
||||
a.*,
|
||||
COALESCE(r.total_racks, 0) AS capacity,
|
||||
COALESCE(r.occupied_racks, 0) AS space_used,
|
||||
COALESCE(r.unique_product, 0) AS unique_product,
|
||||
c.name AS manager_name,
|
||||
c.surname AS manager_surname
|
||||
FROM md_warehouse a
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
company_id, warehouse,
|
||||
COUNT(*) AS total_racks,
|
||||
COUNT(product_sku) AS occupied_racks,
|
||||
COUNT(DISTINCT product_sku) AS unique_product
|
||||
FROM md_rack
|
||||
GROUP BY company_id, warehouse
|
||||
) r ON a.company_id = r.company_id AND a.id = r.warehouse
|
||||
LEFT JOIN wms.user c ON a.manager = c.user_id
|
||||
WHERE a.company_id = :company_id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id]);
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single warehouse row by ID.
|
||||
*/
|
||||
public function getWarehouseById(int $id): array|false
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT * FROM md_warehouse
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
||||
return $sth->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Lot / Serial queries
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Search md_lot by product_sku + keyword — for lot number autocomplete.
|
||||
*/
|
||||
public function getLotList(string $product_sku, string $keyword): array
|
||||
{
|
||||
$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"
|
||||
);
|
||||
$sth->execute([
|
||||
':company_id' => $this->company_id,
|
||||
':product_sku' => $product_sku,
|
||||
':keyword' => '%' . $keyword . '%',
|
||||
]);
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return distinct active lots for a product_sku across all warehouses.
|
||||
* "Active" = rack is still occupied (product_sku IS NOT NULL in md_rack).
|
||||
*/
|
||||
public function getActiveLots(string $product_sku): 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);
|
||||
|
||||
$active_lots = [];
|
||||
|
||||
foreach ($warehouses as $wh) {
|
||||
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']);
|
||||
$table = "td_stock_{$safe}";
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT DISTINCT s.lot_number, l.expiry_date
|
||||
FROM `{$table}` s
|
||||
LEFT JOIN md_lot l
|
||||
ON l.company_id = s.company_id
|
||||
AND l.product_sku = s.product_sku
|
||||
AND l.lot_number = s.lot_number
|
||||
WHERE s.company_id = :company_id
|
||||
AND s.product_sku = :product_sku
|
||||
AND s.type = 'in'
|
||||
AND s.lot_number IS NOT NULL
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM md_rack r
|
||||
WHERE r.company_id = s.company_id
|
||||
AND r.td_stock_id = s.id
|
||||
AND r.product_sku IS NOT NULL
|
||||
)"
|
||||
);
|
||||
$sth->execute([
|
||||
':company_id' => $this->company_id,
|
||||
':product_sku' => $product_sku,
|
||||
]);
|
||||
|
||||
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$active_lots[$row['lot_number']] ??= $row;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($active_lots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return distinct active serial numbers for a product_sku (+ optional lot)
|
||||
* across all warehouses.
|
||||
* "Active" = rack is still occupied.
|
||||
*/
|
||||
public function getActiveSerials(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);
|
||||
|
||||
$active_serials = [];
|
||||
|
||||
foreach ($warehouses as $wh) {
|
||||
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $wh['warehouse_name']);
|
||||
$table = "td_stock_{$safe}";
|
||||
|
||||
$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.serial_number IS NOT NULL
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM md_rack r
|
||||
WHERE r.company_id = s.company_id
|
||||
AND r.td_stock_id = s.id
|
||||
AND r.product_sku IS NOT NULL
|
||||
)";
|
||||
|
||||
$params = [
|
||||
':company_id' => $this->company_id,
|
||||
':product_sku' => $product_sku,
|
||||
];
|
||||
|
||||
if (!empty($lot_number)) {
|
||||
$sql .= ' AND s.lot_number = :lot_number';
|
||||
$params[':lot_number'] = $lot_number;
|
||||
}
|
||||
|
||||
$sth = $this->pdo->prepare($sql);
|
||||
$sth->execute($params);
|
||||
|
||||
foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$active_serials[$row['serial_number']] = $row['serial_number'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($active_serials);
|
||||
}
|
||||
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Storage queries
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Full storage list with warehouse name.
|
||||
*/
|
||||
public function getStorageList(): array
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT a.*, b.warehouse_name
|
||||
FROM md_storage a
|
||||
LEFT JOIN md_warehouse b
|
||||
ON a.company_id = b.company_id
|
||||
AND a.warehouse = b.id
|
||||
WHERE a.company_id = :company_id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id]);
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single storage row by ID.
|
||||
*/
|
||||
public function getStorageById(int $id): array|false
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT * FROM md_storage
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
||||
return $sth->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Warehouse write
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Insert or update a warehouse.
|
||||
* Creates td_stock_<name> table on insert.
|
||||
* Pass $data['id'] > 0 for update, 0 for insert.
|
||||
* Must be called inside dbTransaction() by the caller.
|
||||
*/
|
||||
public function saveWarehouse(array $data, array $logging): void
|
||||
{
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT `log` FROM md_warehouse
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
||||
$table_log = json_decode($sth->fetchColumn() ?: '[]', true) ?: [];
|
||||
$table_log[] = $logging;
|
||||
|
||||
$params = [
|
||||
':company_id' => $this->company_id,
|
||||
':warehouse_name' => $data['warehouse_name'] ?? '',
|
||||
':location' => $data['location'] ?? '',
|
||||
':manager' => (int)($data['manager'] ?? 0),
|
||||
':description' => $data['description'] ?? '',
|
||||
':status' => (int)($data['status'] ?? 1),
|
||||
':log' => json_encode($table_log, JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$params[':id'] = $id;
|
||||
$this->pdo->prepare(
|
||||
"UPDATE md_warehouse SET
|
||||
`warehouse_name` = :warehouse_name,
|
||||
`location` = :location,
|
||||
`manager` = :manager,
|
||||
`description` = :description,
|
||||
`status` = :status,
|
||||
`log` = :log
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute($params);
|
||||
} else {
|
||||
$this->pdo->prepare(
|
||||
"INSERT INTO md_warehouse
|
||||
(company_id, warehouse_name, location, manager, description, status, `log`)
|
||||
VALUES
|
||||
(:company_id, :warehouse_name, :location, :manager, :description, :status, :log)"
|
||||
)->execute($params);
|
||||
|
||||
// Create the per-warehouse stock table on insert
|
||||
if (!empty($data['warehouse_name'])) {
|
||||
$safe = preg_replace('/[^a-zA-Z0-9_]/', '', $data['warehouse_name']);
|
||||
$this->pdo->exec("CREATE TABLE `td_stock_{$safe}` LIKE `td_stock`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert or update a storage record.
|
||||
* Pass $data['id'] > 0 for update, 0 for insert.
|
||||
* Must be called inside dbTransaction() by the caller.
|
||||
* Automatically syncs md_rack rows after save.
|
||||
*/
|
||||
public function saveStorage(array $data, array $logging): void
|
||||
{
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT `log` FROM md_storage
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
||||
$table_log = json_decode($sth->fetchColumn() ?: '[]', true) ?: [];
|
||||
$table_log[] = $logging;
|
||||
|
||||
$params = [
|
||||
':company_id' => $this->company_id,
|
||||
':warehouse' => $data['warehouse'] ?? '',
|
||||
':zone' => $data['zone'] ?? '',
|
||||
':aisle_from' => $data['aisle_from'] ?? '',
|
||||
':aisle_to' => $data['aisle_to'] ?? '',
|
||||
':rack_from' => $data['rack_from'] ?? '',
|
||||
':rack_to' => $data['rack_to'] ?? '',
|
||||
':description' => $data['description'] ?? '',
|
||||
':status' => (int)($data['status'] ?? 1),
|
||||
':log' => json_encode($table_log, JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
|
||||
if ($id > 0) {
|
||||
$params[':id'] = $id;
|
||||
$this->pdo->prepare(
|
||||
"UPDATE md_storage SET
|
||||
warehouse = :warehouse,
|
||||
zone = :zone,
|
||||
aisle_from = :aisle_from,
|
||||
aisle_to = :aisle_to,
|
||||
rack_from = :rack_from,
|
||||
rack_to = :rack_to,
|
||||
description = :description,
|
||||
status = :status,
|
||||
`log` = :log
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute($params);
|
||||
|
||||
$storage_id = $id;
|
||||
} else {
|
||||
$this->pdo->prepare(
|
||||
"INSERT INTO md_storage
|
||||
(company_id, warehouse, zone, aisle_from, aisle_to,
|
||||
rack_from, rack_to, description, status, `log`)
|
||||
VALUES
|
||||
(:company_id, :warehouse, :zone, :aisle_from, :aisle_to,
|
||||
:rack_from, :rack_to, :description, :status, :log)"
|
||||
)->execute($params);
|
||||
|
||||
$storage_id = (int)$this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
// Sync md_rack rows to match the new range
|
||||
$this->syncRacks($storage_id, [
|
||||
'warehouse' => $data['warehouse'],
|
||||
'zone' => $data['zone'],
|
||||
'aisle_from' => $data['aisle_from'],
|
||||
'aisle_to' => $data['aisle_to'],
|
||||
'rack_from' => $data['rack_from'],
|
||||
'rack_to' => $data['rack_to'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All distinct zones in a warehouse — for admin/read-only listing.
|
||||
*/
|
||||
public function getZonesAll(int $warehouse_id): array
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT DISTINCT zone FROM md_rack
|
||||
WHERE company_id = :company_id AND warehouse = :warehouse
|
||||
ORDER BY CAST(zone AS UNSIGNED), zone"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':warehouse' => $warehouse_id]);
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* All distinct aisles in a zone — for admin/read-only listing.
|
||||
*/
|
||||
public function getAislesAll(int $warehouse_id, string $zone): array
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT DISTINCT aisle FROM md_rack
|
||||
WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone
|
||||
ORDER BY CAST(aisle AS UNSIGNED), aisle"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':warehouse' => $warehouse_id, ':zone' => $zone]);
|
||||
return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'aisle');
|
||||
}
|
||||
|
||||
/**
|
||||
* All distinct racks in an aisle — for admin/read-only listing.
|
||||
*/
|
||||
public function getRacksAll(int $warehouse_id, string $zone, string $aisle): array
|
||||
{
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT DISTINCT rack FROM md_rack
|
||||
WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle
|
||||
ORDER BY CAST(rack AS UNSIGNED), rack"
|
||||
);
|
||||
$sth->execute([':company_id' => $this->company_id, ':warehouse' => $warehouse_id, ':zone' => $zone, ':aisle' => $aisle]);
|
||||
return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack');
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user