cost/price/margin
This commit is contained in:
@@ -360,6 +360,7 @@ class ProductManager {
|
||||
':sku' => $data['sku'],
|
||||
':uom' => $data['uom'] ?? 'pcs',
|
||||
':price' => $data['price'],
|
||||
':cost_price' => (float)($data['cost_price'] ?? 0),
|
||||
':min_stock' => $data['min_stock'],
|
||||
':reorder_point' => $data['reorder_point'],
|
||||
':category' => $data['category'],
|
||||
@@ -377,6 +378,7 @@ class ProductManager {
|
||||
sku = :sku,
|
||||
uom = :uom,
|
||||
price = :price,
|
||||
cost_price = :cost_price,
|
||||
min_stock = :min_stock,
|
||||
reorder_point = :reorder_point,
|
||||
category = :category,
|
||||
@@ -389,10 +391,10 @@ class ProductManager {
|
||||
} else {
|
||||
$this->pdo->prepare(
|
||||
"INSERT INTO md_product
|
||||
(company_id, product_name, sku, uom, price, min_stock, reorder_point,
|
||||
(company_id, product_name, sku, uom, price, cost_price, min_stock, reorder_point,
|
||||
category, product_image, `description`, `status`, `log`)
|
||||
VALUES
|
||||
(:company_id, :product_name, :sku, :uom, :price, :min_stock, :reorder_point,
|
||||
(:company_id, :product_name, :sku, :uom, :price, :cost_price, :min_stock, :reorder_point,
|
||||
:category, :product_image, :description, :status, :log)"
|
||||
)->execute($params);
|
||||
}
|
||||
@@ -499,4 +501,4 @@ class ProductManager {
|
||||
$sth->execute([':company_id' => $this->company_id]);
|
||||
return $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,11 +366,19 @@ class PurchaseOrderManager {
|
||||
if ($zone === '' && $rack !== '') $zone = $rack;
|
||||
if ($aisle === '' && $rack !== '') $aisle = $rack;
|
||||
|
||||
// Resolve unit_price: prefer the receive-time override, fall back to PO item price
|
||||
$po_item_price = 0;
|
||||
if ($item_id >= 0 && isset($po_items_by_id[$item_id])) {
|
||||
$po_item_price = (float)($po_items[$po_items_by_id[$item_id]]['unit_price'] ?? 0);
|
||||
}
|
||||
$unit_price = (float)($recv['unit_price'] ?? $po_item_price);
|
||||
|
||||
$stock_data = [
|
||||
'id' => 0,
|
||||
'warehouse' => $warehouse_id,
|
||||
'product_sku' => $product_sku,
|
||||
'quantity' => $quantity,
|
||||
'price' => $unit_price,
|
||||
'zone' => $zone,
|
||||
'aisle' => $aisle,
|
||||
'rack' => $rack,
|
||||
|
||||
@@ -276,13 +276,14 @@ class ReportManager
|
||||
a.product_sku,
|
||||
SUM(a.total_in) AS total_in,
|
||||
SUM(a.total_out) AS total_out,
|
||||
b.min_stock
|
||||
b.min_stock,
|
||||
b.cost_price
|
||||
FROM warehouse_balance a
|
||||
LEFT JOIN md_product b
|
||||
ON a.company_id = b.company_id
|
||||
AND a.product_sku = b.sku
|
||||
WHERE a.company_id = :company_id
|
||||
GROUP BY a.product_sku, b.min_stock";
|
||||
GROUP BY a.product_sku, b.min_stock, b.cost_price";
|
||||
return $this->fetchAll($sql);
|
||||
}
|
||||
|
||||
@@ -324,6 +325,7 @@ class ReportManager
|
||||
mp.min_stock,
|
||||
mp.reorder_point,
|
||||
mp.product_image,
|
||||
mp.cost_price,
|
||||
ROUND(SUM(wb.total_in) - SUM(wb.total_out), 2) AS balance
|
||||
FROM warehouse_balance wb
|
||||
INNER JOIN md_product mp
|
||||
@@ -336,7 +338,7 @@ class ReportManager
|
||||
AND mp.reorder_point > 0
|
||||
GROUP BY
|
||||
wb.warehouse_id, wb.product_sku, mw.warehouse_name,
|
||||
mp.product_name, mp.min_stock, mp.reorder_point, mp.product_image
|
||||
mp.product_name, mp.min_stock, mp.reorder_point, mp.product_image, mp.cost_price
|
||||
HAVING balance <= mp.reorder_point
|
||||
ORDER BY mp.product_name ASC, mw.warehouse_name ASC";
|
||||
|
||||
@@ -352,6 +354,7 @@ class ReportManager
|
||||
'product_sku' => $row['product_sku'],
|
||||
'product_name' => $row['product_name'],
|
||||
'product_image' => $row['product_image'],
|
||||
'cost_price' => (float) ($row['cost_price'] ?? 0),
|
||||
'balance' => max(0, round($balance, 2)),
|
||||
'min_stock' => (float) $row['min_stock'],
|
||||
'reorder_point' => (float) $row['reorder_point'],
|
||||
@@ -1407,7 +1410,9 @@ class ReportManager
|
||||
$product_name = $product_row['product_name'] ?: $product_sku;
|
||||
$product_uom = $product_row['uom'] ?? '';
|
||||
|
||||
// All transactions for this SKU in range, sorted by date then id
|
||||
// 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.
|
||||
$sth = $this->pdo->prepare(
|
||||
"SELECT
|
||||
s.id,
|
||||
@@ -1421,12 +1426,20 @@ 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,
|
||||
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
|
||||
LEFT JOIN md_product p
|
||||
ON p.company_id = s.company_id
|
||||
AND p.sku = s.product_sku
|
||||
LEFT JOIN md_contact c
|
||||
ON c.company_id = s.company_id
|
||||
AND c.id = s.contact_id
|
||||
ON c.company_id = s.company_id
|
||||
AND c.id = s.contact_id
|
||||
WHERE s.company_id = :company_id
|
||||
AND s.product_sku = :sku
|
||||
AND s.status = 1
|
||||
@@ -1436,11 +1449,18 @@ class ReportManager
|
||||
$sth->execute([':company_id' => $this->companyId, ':sku' => $product_sku, ':date_from' => $date_from, ':date_to' => $date_to]);
|
||||
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Append running_total to each row (carried forward from brought-forward balance)
|
||||
// Append running_total and margin to each row.
|
||||
// Margin is only meaningful for stock-out rows with both sell price and cost.
|
||||
$running = $bf_bal;
|
||||
foreach ($rows as &$row) {
|
||||
$running += (float)$row['stock_in'] - (float)$row['stock_out'];
|
||||
$row['running_total'] = round($running, 2);
|
||||
$running += (float)$row['stock_in'] - (float)$row['stock_out'];
|
||||
$row['running_total'] = round($running, 2);
|
||||
|
||||
$sell = (float)$row['price'];
|
||||
$cost = (float)$row['unit_cost'];
|
||||
$row['margin'] = ($row['stock_out'] > 0 && $sell > 0)
|
||||
? round(($sell - $cost) / $sell * 100, 2)
|
||||
: null;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
|
||||
@@ -283,10 +283,10 @@ class StockManager {
|
||||
|
||||
$this->pdo->prepare(
|
||||
"INSERT INTO `$table`
|
||||
(uuid, company_id, `date`, product_sku, `in`, zone, aisle, rack,
|
||||
(uuid, company_id, `date`, product_sku, `in`, price, zone, aisle, rack,
|
||||
contact_id, `description`, `log`, `type`, lot_number, serial_number, status)
|
||||
VALUES
|
||||
(:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack,
|
||||
(:uuid, :company_id, :date, :product_sku, :quantity, :price, :zone, :aisle, :rack,
|
||||
:contact_id, :description, :log, 'in', :lot_number, :serial_number, 0)"
|
||||
)->execute([
|
||||
':uuid' => $uuid,
|
||||
@@ -294,6 +294,7 @@ class StockManager {
|
||||
':date' => date('Y-m-d H:i:s'),
|
||||
':product_sku' => $data['product_sku'],
|
||||
':quantity' => $data['quantity'],
|
||||
':price' => (float)($data['price'] ?? 0),
|
||||
':zone' => $data['zone'],
|
||||
':aisle' => $data['aisle'],
|
||||
':rack' => $data['rack'],
|
||||
|
||||
Reference in New Issue
Block a user