[OOP] contact,inventory,ics,dashboard

This commit is contained in:
Thanakorn S
2026-04-28 15:44:57 +07:00
parent 3d3b497c08
commit 2061624641
49 changed files with 1946 additions and 2146 deletions
+236
View File
@@ -183,4 +183,240 @@ class ProductManager {
':company_id' => $this->company_id,
]);
}
// ─────────────────────────────────────────────────────────────
// Product queries
// ─────────────────────────────────────────────────────────────
/**
* Full product list with current warehouse balance.
*/
public function getProductList(): array
{
$sth = $this->pdo->prepare(
"SELECT a.*, IFNULL(SUM(b.total_in - b.total_out), 0) AS product_balance
FROM md_product a
LEFT JOIN warehouse_balance b
ON a.company_id = b.company_id
AND a.sku = b.product_sku
WHERE a.company_id = :company_id
GROUP BY a.id"
);
$sth->execute([':company_id' => $this->company_id]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Fetch a single product row by ID.
*/
public function getProductById(int $id): array|false
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_product
WHERE company_id = :company_id AND id = :id"
);
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
return $sth->fetch(PDO::FETCH_ASSOC);
}
/**
* Search products by SKU keyword — for autocomplete.
*/
public function searchProduct(string $keyword): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_product
WHERE company_id = :company_id
AND sku LIKE :keyword
LIMIT 50"
);
$sth->execute([
':company_id' => $this->company_id,
':keyword' => '%' . $keyword . '%',
]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
// ─────────────────────────────────────────────────────────────
// Product category queries
// ─────────────────────────────────────────────────────────────
/**
* Full category list with product count per category.
*/
public function getCategoryList(): array
{
$sth = $this->pdo->prepare(
"SELECT a.*, COUNT(b.id) AS product_count
FROM md_product_category a
LEFT JOIN md_product b
ON a.company_id = b.company_id
AND a.id = b.category
WHERE a.company_id = :company_id
GROUP BY a.id"
);
$sth->execute([':company_id' => $this->company_id]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Fetch a single category row by ID.
*/
public function getCategoryById(int $id): array|false
{
$sth = $this->pdo->prepare(
"SELECT * FROM md_product_category
WHERE company_id = :company_id AND id = :id"
);
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
return $sth->fetch(PDO::FETCH_ASSOC);
}
/**
* Insert or update a product category.
* Pass $data['id'] > 0 for update, 0 for insert.
* Must be called inside dbTransaction() by the caller.
*
* @throws Exception on validation failure
*/
public function saveCategory(array $data, array $logging): void
{
$id = (int)($data['id'] ?? 0);
$sth = $this->pdo->prepare(
"SELECT `log` FROM md_product_category
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,
':category' => $data['category'],
':slug' => $data['slug'],
':description' => $data['description'],
':status' => (int)$data['status'],
':log' => json_encode($table_log),
];
if ($id > 0) {
$params[':id'] = $id;
$this->pdo->prepare(
"UPDATE md_product_category SET
`category` = :category,
`slug` = :slug,
`description` = :description,
`status` = :status,
`log` = :log
WHERE id = :id AND company_id = :company_id"
)->execute($params);
} else {
$this->pdo->prepare(
"INSERT INTO md_product_category
(company_id, category, slug, `description`, `status`, `log`)
VALUES
(:company_id, :category, :slug, :description, :status, :log)"
)->execute($params);
}
}
// ─────────────────────────────────────────────────────────────
// Product write
// ─────────────────────────────────────────────────────────────
/**
* Insert or update a product.
* Pass $data['id'] > 0 for update, 0 for insert.
* Must be called inside dbTransaction() by the caller.
*/
public function saveProduct(array $data, array $logging, string $product_image): void
{
$id = (int)($data['id'] ?? 0);
$sth = $this->pdo->prepare(
"SELECT `log` FROM md_product
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,
':product_name' => $data['product_name'],
':sku' => $data['sku'],
':price' => $data['price'],
':min_stock' => $data['min_stock'],
':reorder_point' => $data['reorder_point'],
':category' => $data['category'],
':product_image' => $product_image,
':description' => $data['description'],
':status' => (int)$data['status'],
':log' => json_encode($table_log),
];
if ($id > 0) {
$params[':id'] = $id;
$this->pdo->prepare(
"UPDATE md_product SET
product_name = :product_name,
sku = :sku,
price = :price,
min_stock = :min_stock,
reorder_point = :reorder_point,
category = :category,
product_image = :product_image,
`description` = :description,
`status` = :status,
`log` = :log
WHERE id = :id AND company_id = :company_id"
)->execute($params);
} else {
$this->pdo->prepare(
"INSERT INTO md_product
(company_id, product_name, sku, price, min_stock, reorder_point,
category, product_image, `description`, `status`, `log`)
VALUES
(:company_id, :product_name, :sku, :price, :min_stock, :reorder_point,
:category, :product_image, :description, :status, :log)"
)->execute($params);
}
}
/**
* Full rack list with warehouse name, product info and occupancy status.
*/
public function getRackOccupancy(): array
{
$sth = $this->pdo->prepare(
"SELECT
r.id,
r.zone,
r.aisle,
r.rack,
r.product_sku,
r.td_stock_id,
mw.warehouse_name,
mw.id AS warehouse_id,
p.product_name,
CASE WHEN r.product_sku IS NOT NULL THEN 'occupied' ELSE 'empty' END AS status
FROM md_rack r
INNER JOIN md_warehouse mw
ON mw.company_id = r.company_id
AND mw.id = r.warehouse
LEFT JOIN md_product p
ON p.company_id = r.company_id
AND p.sku = r.product_sku
WHERE r.company_id = :company_id
ORDER BY mw.warehouse_name, r.zone,
CAST(r.aisle AS UNSIGNED), r.aisle,
CAST(r.rack AS UNSIGNED), r.rack"
);
$sth->execute([':company_id' => $this->companyId]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
}