diff --git a/app/assets/utils/classes/ProductManager.php b/app/assets/utils/classes/ProductManager.php index 8734ae0..a3ba21c 100644 --- a/app/assets/utils/classes/ProductManager.php +++ b/app/assets/utils/classes/ProductManager.php @@ -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); } -} \ No newline at end of file +} diff --git a/app/assets/utils/classes/PurchaseOrderManager.php b/app/assets/utils/classes/PurchaseOrderManager.php index b200b75..95041d5 100644 --- a/app/assets/utils/classes/PurchaseOrderManager.php +++ b/app/assets/utils/classes/PurchaseOrderManager.php @@ -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, diff --git a/app/assets/utils/classes/ReportManager.php b/app/assets/utils/classes/ReportManager.php index fda8738..5ec3cfa 100644 --- a/app/assets/utils/classes/ReportManager.php +++ b/app/assets/utils/classes/ReportManager.php @@ -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); diff --git a/app/assets/utils/classes/StockManager.php b/app/assets/utils/classes/StockManager.php index 4f07294..882acf1 100644 --- a/app/assets/utils/classes/StockManager.php +++ b/app/assets/utils/classes/StockManager.php @@ -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'], diff --git a/app/dashboard/low_stock_products.php b/app/dashboard/low_stock_products.php index 1725cfe..bb71a24 100644 --- a/app/dashboard/low_stock_products.php +++ b/app/dashboard/low_stock_products.php @@ -254,7 +254,12 @@