Files
wms-app/app/assets/utils/classes/ProductManager.php
T

186 lines
6.5 KiB
PHP

<?php
/**
* ProductManager
*
* Encapsulates product and product category operations:
* - Soft-delete with downstream validation
*
* Note: Methods that modify data do NOT manage their own DB transactions.
* Callers are responsible for wrapping operations in dbTransaction() when atomicity is needed.
*/
class ProductManager {
private $pdo;
private $company_id;
public function __construct($pdo, $company_id) {
$this->pdo = $pdo;
$this->company_id = $company_id;
}
// ─────────────────────────────────────────────────────────────
// Private helpers
// ─────────────────────────────────────────────────────────────
/**
* Check if a SKU has any active stock across all td_stock_* tables.
* Returns the first blocking table name found, or null if clear.
*/
private function findActiveStock(string $sku): ?string {
$sth = $this->pdo->prepare(
"SELECT table_name FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name LIKE 'td_stock_%'"
);
$sth->execute();
$tables = $sth->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) {
$sth = $this->pdo->prepare(
"SELECT COUNT(*) FROM `$table`
WHERE company_id = :company_id
AND product_sku = :sku"
);
$sth->execute([
':company_id' => $this->company_id,
':sku' => $sku,
]);
if ($sth->fetchColumn() > 0) {
return $table;
}
}
return null;
}
/**
* Build a log entry array for audit context.
*/
private function buildLogEntry(string $action): array {
return [
'user_id' => $_SESSION['login_user_id'] ?? null,
'dt' => date('Y-m-d H:i:s'),
'login' => isset($_SESSION['otpTime'])
? date('Y-m-d H:i:s', $_SESSION['otpTime'])
: null,
'action' => $action,
];
}
// ─────────────────────────────────────────────────────────────
// Product category
// ─────────────────────────────────────────────────────────────
/**
* Soft-delete a product category.
*
* Blocks if any md_product references this category.
*
* @throws Exception if category not found or has dependent products
*/
public function deleteCategory(int $category_id): void {
$sth = $this->pdo->prepare(
"SELECT id, category, `log` FROM md_product_category
WHERE company_id = :company_id AND id = :id"
);
$sth->execute([
':company_id' => $this->company_id,
':id' => $category_id,
]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if (!$row) {
throw new Exception("Product category not found.");
}
// Block if any product references this category
$sth = $this->pdo->prepare(
"SELECT COUNT(*) FROM md_product
WHERE company_id = :company_id
AND category = :category_id"
);
$sth->execute([
':company_id' => $this->company_id,
':category_id' => $category_id,
]);
if ($sth->fetchColumn() > 0) {
throw new Exception(
"Cannot delete — category \"{$row['category']}\" " .
"still has products assigned to it."
);
}
// Append delete event to log
$log = json_decode($row['log'] ?? '[]', true) ?: [];
$log[] = $this->buildLogEntry('delete');
// Soft-delete
$this->pdo->prepare(
"UPDATE md_product_category
SET company_id = company_id * -1,
`log` = :log
WHERE id = :id AND company_id = :company_id"
)->execute([
':log' => json_encode($log),
':id' => $category_id,
':company_id' => $this->company_id,
]);
}
// ─────────────────────────────────────────────────────────────
// Product
// ─────────────────────────────────────────────────────────────
/**
* Soft-delete a product.
*
* Blocks if the product SKU has any active stock across any td_stock_* table.
*
* @throws Exception if product not found or has active stock
*/
public function deleteProduct(int $product_id): void {
$sth = $this->pdo->prepare(
"SELECT id, product_name, sku, `log` FROM md_product
WHERE company_id = :company_id AND id = :id"
);
$sth->execute([
':company_id' => $this->company_id,
':id' => $product_id,
]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if (!$row) {
throw new Exception("Product not found.");
}
// Block if active stock exists for this SKU in any warehouse
$blocking_table = $this->findActiveStock($row['sku']);
if ($blocking_table !== null) {
throw new Exception(
"Cannot delete — \"{$row['product_name']}\" " .
"still has active stock in the system."
);
}
// Append delete event to log
$log = json_decode($row['log'] ?? '[]', true) ?: [];
$log[] = $this->buildLogEntry('delete');
// Soft-delete
$this->pdo->prepare(
"UPDATE md_product
SET company_id = company_id * -1,
`log` = :log
WHERE id = :id AND company_id = :company_id"
)->execute([
':log' => json_encode($log),
':id' => $product_id,
':company_id' => $this->company_id,
]);
}
}