stockmovement: add summary views

This commit is contained in:
Thanakorn S
2026-05-11 16:55:23 +07:00
parent 7a87909392
commit adfd5ef017
6 changed files with 230 additions and 67 deletions
+18 -11
View File
@@ -919,25 +919,24 @@ class ReportManager
function ($wh) use ($cid) {
$table = $this->stockTableNameFromWarehouseId((int)$wh['id']);
return "SELECT
s.id,
s.product_sku,
s.lot_number,
s.zone,
s.aisle,
s.rack,
ROUND(s.`in`, 2) AS quantity,
ROUND(SUM(s.`in`) - SUM(s.`out`), 2) AS quantity,
{$wh['id']} AS warehouse_id
FROM `{$table}` s
WHERE s.company_id = {$cid}
AND s.type = 'in'
AND s.lot_number IS NOT NULL
AND s.status = 1";
AND s.status = 1
GROUP BY s.product_sku, s.lot_number, s.zone, s.aisle, s.rack
HAVING quantity > 0";
},
$warehouses
));
$sql = "SELECT
stock.id,
stock.product_sku,
stock.lot_number,
stock.zone,
@@ -982,7 +981,6 @@ class ReportManager
else { continue; }
$items[] = [
'id' => (int) $row['id'],
'product_sku' => $row['product_sku'],
'product_name' => $row['product_name'],
'category_name' => $row['category_name'],
@@ -1411,13 +1409,17 @@ class ReportManager
$product_uom = $product_row['uom'] ?? '';
// All transactions for this SKU in range, sorted by date then id.
// For stock-out rows, si.price is the purchase cost from the source stock-in (via ref_id).
// p.cost_price is the product-level fallback when si.price is 0 or ref_id is absent.
// Pricing rules per movement type:
// PO in — s.price = actual purchase cost (user-defined on PO)
// sales out — s.price = selling price; unit_cost from linked stock-in via ref_id
// return in — price derived from linked SO stock-out via ref_id; no unit_cost needed
// others — s.price = standard cost from md_product.cost_price
$sth = $this->pdo->prepare(
"SELECT
s.id,
s.date,
s.type,
s.source,
s.product_sku,
s.lot_number,
s.serial_number,
@@ -1426,14 +1428,19 @@ class ReportManager
s.rack,
ROUND(COALESCE(s.`in`, 0), 2) AS stock_in,
ROUND(COALESCE(s.`out`, 0), 2) AS stock_out,
ROUND(s.price, 4) AS price,
CASE
WHEN s.source = 'return' AND s.`in` > 0
THEN ROUND(COALESCE(NULLIF(si.price, 0), s.price, 0), 4)
ELSE
ROUND(s.price, 4)
END AS price,
ROUND(COALESCE(NULLIF(si.price, 0), p.cost_price, 0), 4) AS unit_cost,
c.contact_name,
s.description
FROM `{$table}` s
LEFT JOIN `{$table}` si
ON si.id = s.ref_id
AND s.`out` > 0
ON si.id = s.ref_id
AND (s.`out` > 0 OR s.source = 'return')
LEFT JOIN md_product p
ON p.company_id = s.company_id
AND p.sku = s.product_sku
+7 -4
View File
@@ -313,7 +313,7 @@ class ReturnManager {
$stock_out_table = $this->stockTableNameFromWarehouseId($stock_out_wh);
$stock_out_sth = $this->pdo->prepare(
"SELECT status, lot_number, serial_number
"SELECT status, lot_number, serial_number, price
FROM `{$stock_out_table}`
WHERE company_id = :company_id
AND id = :id
@@ -341,19 +341,21 @@ class ReturnManager {
$quantity = (float)($item['quantity'] ?? 0);
// INSERT stock-in row — status=1 forced
// price comes from the linked SO stock-out row (selling price), not the return form.
// ref_id links back to the stock-out row so the report can trace the original sale.
$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,
source, source_id, price, status)
source, source_id, ref_id, price, status)
VALUES
(:uuid, :company_id, :date, :product_sku, :quantity,
:zone, :aisle, :rack,
:contact_id, :description, :log, 'in',
:lot_number, :serial_number,
'return', :source_id, :price, 1)"
'return', :source_id, :ref_id, :price, 1)"
)->execute([
':uuid' => $item_uuid,
':company_id' => $this->company_id,
@@ -369,7 +371,8 @@ class ReturnManager {
':lot_number' => $stock_out['lot_number'] ?? ($item['lot_number'] ?? ''),
':serial_number' => $stock_out['serial_number'] ?? ($item['serial_number'] ?? ''),
':source_id' => $return_id,
':price' => (float)($item['price'] ?? 0),
':ref_id' => $stock_out_id,
':price' => (float)$stock_out['price'],
]);
$stock_in_id = (int)$this->pdo->lastInsertId();