0
LEFT JOIN clients ON client_id = invoice_client_id
LEFT JOIN expenses ON expense_id = stock_expense_id
LEFT JOIN users ON user_id = stock_user_id AND stock_user_id > 0
WHERE $range_sql
ORDER BY stock_created_at DESC, stock_id DESC");
while ($row = mysqli_fetch_assoc($sql)) {
$row['type_label'] = productStockTypes()[$row['stock_type']]['label'] ?? $row['stock_type'];
$row['invoice_open'] = $row['invoice_number'] !== null && hasClientAccess(intval($row['invoice_client_id']));
$moves[] = $row;
}
$product_columns = [
'product_name' => ['label' => 'Product', 'html' => function ($row) {
return '' . escapeHtml($row['product_name']) . ''
. ($row['product_code'] || $row['vendor_name'] ? '
' . escapeHtml(implode(' ยท ', array_filter([$row['product_code'], $row['vendor_name']]))) . '
' : '');
}],
'product_code' => ['label' => 'Code', 'screen' => false],
'vendor_name' => ['label' => 'Vendor', 'screen' => false],
'product_location' => ['label' => 'Location', 'class' => 'small'],
'on_hand' => ['label' => 'On shelf', 'type' => 'decimal', 'decimals' => 2, 'html' => function ($row) {
$tone = $row['status'] === 'Out' ? 'text-danger-emphasis fw-semibold' : ($row['status'] === 'Reorder' ? 'text-warning-emphasis fw-semibold' : '');
return '
$period,
'export' => ['products' => 'Products', 'moves' => 'Stock moves'],
]); ?>
= reportStats([
['label' => 'On the shelf', 'value' => reportMoney($value_now), 'title' => 'At each product\'s cost today',
'sub' => escapeHtml(billingQuantity($units_now)) . '
units'],
['label' => 'Received', 'value' => reportMoney($in_value), 'tone' => $in_value > 0 ? 'success' : '',
'sub' => escapeHtml(billingQuantity($in_units)) . '
units in the period'],
['label' => 'Cost of sales', 'value' => reportMoney($out_value), 'title' => 'What the stock invoiced in the period cost you, less what came back',
'sub' => escapeHtml(billingQuantity($out_units)) . '
units sold' . ($off_value > 0 ? ' ·
' . escapeHtml(reportMoney($off_value)) . ' written off' : '')],
['label' => 'To reorder', 'value' => reportCount($reorder), 'tone' => $reorder ? 'warning' : '', 'url' => '/agent/products.php?type=reorder',
'sub' => $reorder ? '
at or under their reorder level' : '
nothing low'],
]) ?>
= reportSectionOpen('In and out, at cost', escapeHtml($period['range'])) ?>
= reportChart(['type' => 'bar', 'labels' => $buckets['labels'], 'series' => $series, 'format' => 'money'], '', 'Stock received, sold and written off over the period, at cost') ?>
= reportSectionOpen('Products', count($products) . ' tracked or moved') ?>
= reportTable($product_columns, $products, [
'empty' => reportEmpty('No product tracks its stock. Turn on Track stock on a product to see it here.', 'fa-warehouse', 'secondary'),
'export_table' => 'products',
'totals' => ['product_name' => 'Total', 'value' => round($value_now, 2), 'received' => $in_units, 'sold' => $out_units, 'sold_value' => round($out_value, 2)],
'totals_html' => ['received' => escapeHtml(billingQuantity($in_units)), 'sold' => escapeHtml(billingQuantity($out_units))],
]) ?>
Value is what is on the shelf at each product's cost today. In, sold and written off are for the period; cost of sales is at the cost each sale was made at.
= reportSectionOpen('Stock moves', escapeHtml($period['range'])) ?>
= reportTable($move_columns, $moves, [
'empty' => reportEmpty('Nothing came in or went out in this period.', 'fa-warehouse', 'secondary'),
'limit' => 300, 'export_table' => 'moves', 'sortable' => true,
]) ?>