Fix forecasting brand list and filter on products received during period selected

This commit is contained in:
2025-01-15 23:52:50 -05:00
parent e4e23291ea
commit b44985aef4
4 changed files with 25 additions and 13 deletions

View File

@@ -547,10 +547,12 @@ router.get('/forecast', async (req, res) => {
FROM categories c FROM categories c
JOIN product_categories pc ON c.id = pc.category_id JOIN product_categories pc ON c.id = pc.category_id
JOIN products p ON pc.product_id = p.product_id JOIN products p ON pc.product_id = p.product_id
LEFT JOIN product_metrics pm ON p.product_id = pm.product_id
LEFT JOIN orders o ON p.product_id = o.product_id LEFT JOIN orders o ON p.product_id = o.product_id
AND o.date BETWEEN ? AND ? AND o.date BETWEEN ? AND ?
AND o.canceled = false AND o.canceled = false
WHERE p.brand = ? WHERE p.brand = ?
AND pm.first_received_date BETWEEN ? AND ?
GROUP BY c.id, c.name, p.brand GROUP BY c.id, c.name, p.brand
), ),
product_metrics AS ( product_metrics AS (
@@ -560,15 +562,18 @@ router.get('/forecast', async (req, res) => {
p.sku, p.sku,
p.stock_quantity, p.stock_quantity,
pc.category_id, pc.category_id,
pm.first_received_date,
COALESCE(SUM(o.quantity), 0) as total_sold, COALESCE(SUM(o.quantity), 0) as total_sold,
COALESCE(ROUND(AVG(o.price), 2), 0) as avg_price COALESCE(ROUND(AVG(o.price), 2), 0) as avg_price
FROM products p FROM products p
JOIN product_categories pc ON p.product_id = pc.product_id JOIN product_categories pc ON p.product_id = pc.product_id
JOIN product_metrics pm ON p.product_id = pm.product_id
LEFT JOIN orders o ON p.product_id = o.product_id LEFT JOIN orders o ON p.product_id = o.product_id
AND o.date BETWEEN ? AND ? AND o.date BETWEEN ? AND ?
AND o.canceled = false AND o.canceled = false
WHERE p.brand = ? WHERE p.brand = ?
GROUP BY p.product_id, p.title, p.sku, p.stock_quantity, pc.category_id AND pm.first_received_date BETWEEN ? AND ?
GROUP BY p.product_id, p.title, p.sku, p.stock_quantity, pc.category_id, pm.first_received_date
) )
SELECT SELECT
cm.*, cm.*,
@@ -579,14 +584,15 @@ router.get('/forecast', async (req, res) => {
'sku', pm.sku, 'sku', pm.sku,
'stock_quantity', pm.stock_quantity, 'stock_quantity', pm.stock_quantity,
'total_sold', pm.total_sold, 'total_sold', pm.total_sold,
'avg_price', pm.avg_price 'avg_price', pm.avg_price,
'first_received_date', DATE_FORMAT(pm.first_received_date, '%Y-%m-%d')
) )
) as products ) as products
FROM category_metrics cm FROM category_metrics cm
JOIN product_metrics pm ON cm.category_id = pm.category_id JOIN product_metrics pm ON cm.category_id = pm.category_id
GROUP BY cm.category_id, cm.category_name, cm.brand, cm.num_products, cm.avg_daily_sales, cm.total_sold, cm.avgTotalSold, cm.avg_price GROUP BY cm.category_id, cm.category_name, cm.brand, cm.num_products, cm.avg_daily_sales, cm.total_sold, cm.avgTotalSold, cm.avg_price
ORDER BY cm.total_sold DESC ORDER BY cm.total_sold DESC
`, [startDate, endDate, startDate, endDate, brand, startDate, endDate, brand]); `, [startDate, endDate, startDate, endDate, brand, startDate, endDate, startDate, endDate, brand, startDate, endDate]);
res.json(results); res.json(results);
} catch (error) { } catch (error) {

View File

@@ -20,12 +20,15 @@ router.get('/brands', async (req, res) => {
console.log('Fetching brands from database...'); console.log('Fetching brands from database...');
const [results] = await pool.query(` const [results] = await pool.query(`
SELECT DISTINCT brand SELECT DISTINCT p.brand
FROM products FROM products p
WHERE brand IS NOT NULL JOIN purchase_orders po ON p.product_id = po.product_id
AND brand != '' WHERE p.brand IS NOT NULL
AND visible = true AND p.brand != ''
ORDER BY brand AND p.visible = true
GROUP BY p.brand
HAVING SUM(po.cost_price * po.received) >= 500
ORDER BY p.brand
`); `);
console.log(`Found ${results.length} brands:`, results.slice(0, 3)); console.log(`Found ${results.length} brands:`, results.slice(0, 3));

View File

@@ -10,6 +10,7 @@ interface ProductDetail {
stock_quantity: number; stock_quantity: number;
total_sold: number; total_sold: number;
avg_price: number; avg_price: number;
first_received_date: string;
} }
export interface ForecastItem { export interface ForecastItem {
@@ -148,6 +149,7 @@ export const renderSubComponent = ({ row }: { row: any }) => {
<TableRow> <TableRow>
<TableHead>Product Name</TableHead> <TableHead>Product Name</TableHead>
<TableHead>SKU</TableHead> <TableHead>SKU</TableHead>
<TableHead>First Received</TableHead>
<TableHead>Stock Quantity</TableHead> <TableHead>Stock Quantity</TableHead>
<TableHead>Total Sold</TableHead> <TableHead>Total Sold</TableHead>
<TableHead>Average Price</TableHead> <TableHead>Average Price</TableHead>
@@ -158,6 +160,7 @@ export const renderSubComponent = ({ row }: { row: any }) => {
<TableRow key={product.product_id}> <TableRow key={product.product_id}>
<TableCell>{product.name}</TableCell> <TableCell>{product.name}</TableCell>
<TableCell>{product.sku}</TableCell> <TableCell>{product.sku}</TableCell>
<TableCell>{product.first_received_date}</TableCell>
<TableCell>{product.stock_quantity.toLocaleString()}</TableCell> <TableCell>{product.stock_quantity.toLocaleString()}</TableCell>
<TableCell>{product.total_sold.toLocaleString()}</TableCell> <TableCell>{product.total_sold.toLocaleString()}</TableCell>
<TableCell>${product.avg_price.toFixed(2)}</TableCell> <TableCell>${product.avg_price.toFixed(2)}</TableCell>

View File

@@ -1,4 +1,4 @@
import { useState } from "react"; import { useState, Fragment } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -72,6 +72,7 @@ export default function Forecasting() {
stock_quantity: Number(p.stock_quantity) || 0, stock_quantity: Number(p.stock_quantity) || 0,
total_sold: Number(p.total_sold) || 0, total_sold: Number(p.total_sold) || 0,
avg_price: Number(p.avg_price) || 0, avg_price: Number(p.avg_price) || 0,
first_received_date: p.first_received_date,
})) }))
})); }));
}, },
@@ -145,9 +146,8 @@ export default function Forecasting() {
<TableBody> <TableBody>
{table.getRowModel().rows?.length ? ( {table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row: Row<ForecastItem>) => ( table.getRowModel().rows.map((row: Row<ForecastItem>) => (
<> <Fragment key={row.id}>
<TableRow <TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"} data-state={row.getIsSelected() && "selected"}
> >
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
@@ -163,7 +163,7 @@ export default function Forecasting() {
</TableCell> </TableCell>
</TableRow> </TableRow>
)} )}
</> </Fragment>
)) ))
) : ( ) : (
<TableRow> <TableRow>