Update backend/frontend

This commit is contained in:
2025-02-14 11:26:02 -05:00
parent 0ef1b6100e
commit cc22fd8c35
16 changed files with 552 additions and 480 deletions

View File

@@ -6,24 +6,24 @@ router.get('/stats', async (req, res) => {
try {
const pool = req.app.locals.pool;
const [results] = await pool.query(`
const { rows: [results] } = await pool.query(`
SELECT
COALESCE(
ROUND(
(SUM(o.price * o.quantity - p.cost_price * o.quantity) /
NULLIF(SUM(o.price * o.quantity), 0)) * 100, 1
NULLIF(SUM(o.price * o.quantity), 0) * 100)::numeric, 1
),
0
) as profitMargin,
COALESCE(
ROUND(
(AVG(p.price / NULLIF(p.cost_price, 0) - 1) * 100), 1
(AVG(p.price / NULLIF(p.cost_price, 0) - 1) * 100)::numeric, 1
),
0
) as averageMarkup,
COALESCE(
ROUND(
SUM(o.quantity) / NULLIF(AVG(p.stock_quantity), 0), 2
(SUM(o.quantity) / NULLIF(AVG(p.stock_quantity), 0))::numeric, 2
),
0
) as stockTurnoverRate,
@@ -31,23 +31,23 @@ router.get('/stats', async (req, res) => {
COALESCE(COUNT(DISTINCT p.categories), 0) as categoryCount,
COALESCE(
ROUND(
AVG(o.price * o.quantity), 2
AVG(o.price * o.quantity)::numeric, 2
),
0
) as averageOrderValue
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE o.date >= CURRENT_DATE - INTERVAL '30 days'
`);
// Ensure all values are numbers
const stats = {
profitMargin: Number(results[0].profitMargin) || 0,
averageMarkup: Number(results[0].averageMarkup) || 0,
stockTurnoverRate: Number(results[0].stockTurnoverRate) || 0,
vendorCount: Number(results[0].vendorCount) || 0,
categoryCount: Number(results[0].categoryCount) || 0,
averageOrderValue: Number(results[0].averageOrderValue) || 0
profitMargin: Number(results.profitmargin) || 0,
averageMarkup: Number(results.averagemarkup) || 0,
stockTurnoverRate: Number(results.stockturnoverrate) || 0,
vendorCount: Number(results.vendorcount) || 0,
categoryCount: Number(results.categorycount) || 0,
averageOrderValue: Number(results.averageordervalue) || 0
};
res.json(stats);
@@ -63,13 +63,13 @@ router.get('/profit', async (req, res) => {
const pool = req.app.locals.pool;
// Get profit margins by category with full path
const [byCategory] = await pool.query(`
const { rows: byCategory } = await pool.query(`
WITH RECURSIVE category_path AS (
SELECT
c.cat_id,
c.name,
c.parent_id,
CAST(c.name AS CHAR(1000)) as path
c.name::text as path
FROM categories c
WHERE c.parent_id IS NULL
@@ -79,7 +79,7 @@ router.get('/profit', async (req, res) => {
c.cat_id,
c.name,
c.parent_id,
CONCAT(cp.path, ' > ', c.name)
cp.path || ' > ' || c.name
FROM categories c
JOIN category_path cp ON c.parent_id = cp.cat_id
)
@@ -88,53 +88,46 @@ router.get('/profit', async (req, res) => {
cp.path as categoryPath,
ROUND(
(SUM(o.price * o.quantity - p.cost_price * o.quantity) /
NULLIF(SUM(o.price * o.quantity), 0)) * 100, 1
NULLIF(SUM(o.price * o.quantity), 0) * 100)::numeric, 1
) as profitMargin,
CAST(SUM(o.price * o.quantity) AS DECIMAL(15,3)) as revenue,
CAST(SUM(p.cost_price * o.quantity) AS DECIMAL(15,3)) as cost
ROUND(SUM(o.price * o.quantity)::numeric, 3) as revenue,
ROUND(SUM(p.cost_price * o.quantity)::numeric, 3) as cost
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
JOIN product_categories pc ON p.pid = pc.pid
JOIN categories c ON pc.cat_id = c.cat_id
JOIN category_path cp ON c.cat_id = cp.cat_id
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE o.date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY c.name, cp.path
ORDER BY profitMargin DESC
LIMIT 10
`);
// Get profit margin trend over time
const [overTime] = await pool.query(`
const { rows: overTime } = await pool.query(`
SELECT
formatted_date as date,
to_char(o.date, 'YYYY-MM-DD') as date,
ROUND(
(SUM(o.price * o.quantity - p.cost_price * o.quantity) /
NULLIF(SUM(o.price * o.quantity), 0)) * 100, 1
NULLIF(SUM(o.price * o.quantity), 0) * 100)::numeric, 1
) as profitMargin,
CAST(SUM(o.price * o.quantity) AS DECIMAL(15,3)) as revenue,
CAST(SUM(p.cost_price * o.quantity) AS DECIMAL(15,3)) as cost
ROUND(SUM(o.price * o.quantity)::numeric, 3) as revenue,
ROUND(SUM(p.cost_price * o.quantity)::numeric, 3) as cost
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
CROSS JOIN (
SELECT DATE_FORMAT(o.date, '%Y-%m-%d') as formatted_date
FROM orders o
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE_FORMAT(o.date, '%Y-%m-%d')
) dates
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
AND DATE_FORMAT(o.date, '%Y-%m-%d') = dates.formatted_date
GROUP BY formatted_date
ORDER BY formatted_date
WHERE o.date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY to_char(o.date, 'YYYY-MM-DD')
ORDER BY date
`);
// Get top performing products with category paths
const [topProducts] = await pool.query(`
const { rows: topProducts } = await pool.query(`
WITH RECURSIVE category_path AS (
SELECT
c.cat_id,
c.name,
c.parent_id,
CAST(c.name AS CHAR(1000)) as path
c.name::text as path
FROM categories c
WHERE c.parent_id IS NULL
@@ -144,7 +137,7 @@ router.get('/profit', async (req, res) => {
c.cat_id,
c.name,
c.parent_id,
CONCAT(cp.path, ' > ', c.name)
cp.path || ' > ' || c.name
FROM categories c
JOIN category_path cp ON c.parent_id = cp.cat_id
)
@@ -154,18 +147,18 @@ router.get('/profit', async (req, res) => {
cp.path as categoryPath,
ROUND(
(SUM(o.price * o.quantity - p.cost_price * o.quantity) /
NULLIF(SUM(o.price * o.quantity), 0)) * 100, 1
NULLIF(SUM(o.price * o.quantity), 0) * 100)::numeric, 1
) as profitMargin,
CAST(SUM(o.price * o.quantity) AS DECIMAL(15,3)) as revenue,
CAST(SUM(p.cost_price * o.quantity) AS DECIMAL(15,3)) as cost
ROUND(SUM(o.price * o.quantity)::numeric, 3) as revenue,
ROUND(SUM(p.cost_price * o.quantity)::numeric, 3) as cost
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
JOIN product_categories pc ON p.pid = pc.pid
JOIN categories c ON pc.cat_id = c.cat_id
JOIN category_path cp ON c.cat_id = cp.cat_id
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE o.date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY p.pid, p.title, c.name, cp.path
HAVING revenue > 0
HAVING SUM(o.price * o.quantity) > 0
ORDER BY profitMargin DESC
LIMIT 10
`);
@@ -185,7 +178,7 @@ router.get('/vendors', async (req, res) => {
console.log('Fetching vendor performance data...');
// First check if we have any vendors with sales
const [checkData] = await pool.query(`
const { rows: [checkData] } = await pool.query(`
SELECT COUNT(DISTINCT p.vendor) as vendor_count,
COUNT(DISTINCT o.order_number) as order_count
FROM products p
@@ -193,39 +186,39 @@ router.get('/vendors', async (req, res) => {
WHERE p.vendor IS NOT NULL
`);
console.log('Vendor data check:', checkData[0]);
console.log('Vendor data check:', checkData);
// Get vendor performance metrics
const [performance] = await pool.query(`
const { rows: performance } = await pool.query(`
WITH monthly_sales AS (
SELECT
p.vendor,
CAST(SUM(CASE
WHEN o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
ROUND(SUM(CASE
WHEN o.date >= CURRENT_DATE - INTERVAL '30 days'
THEN o.price * o.quantity
ELSE 0
END) AS DECIMAL(15,3)) as current_month,
CAST(SUM(CASE
WHEN o.date >= DATE_SUB(CURDATE(), INTERVAL 60 DAY)
AND o.date < DATE_SUB(CURDATE(), INTERVAL 30 DAY)
END)::numeric, 3) as current_month,
ROUND(SUM(CASE
WHEN o.date >= CURRENT_DATE - INTERVAL '60 days'
AND o.date < CURRENT_DATE - INTERVAL '30 days'
THEN o.price * o.quantity
ELSE 0
END) AS DECIMAL(15,3)) as previous_month
END)::numeric, 3) as previous_month
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
WHERE p.vendor IS NOT NULL
AND o.date >= DATE_SUB(CURDATE(), INTERVAL 60 DAY)
AND o.date >= CURRENT_DATE - INTERVAL '60 days'
GROUP BY p.vendor
)
SELECT
p.vendor,
CAST(SUM(o.price * o.quantity) AS DECIMAL(15,3)) as salesVolume,
ROUND(SUM(o.price * o.quantity)::numeric, 3) as salesVolume,
COALESCE(ROUND(
(SUM(o.price * o.quantity - p.cost_price * o.quantity) /
NULLIF(SUM(o.price * o.quantity), 0)) * 100, 1
NULLIF(SUM(o.price * o.quantity), 0) * 100)::numeric, 1
), 0) as profitMargin,
COALESCE(ROUND(
SUM(o.quantity) / NULLIF(AVG(p.stock_quantity), 0), 1
(SUM(o.quantity) / NULLIF(AVG(p.stock_quantity), 0))::numeric, 1
), 0) as stockTurnover,
COUNT(DISTINCT p.pid) as productCount,
ROUND(
@@ -236,7 +229,7 @@ router.get('/vendors', async (req, res) => {
LEFT JOIN orders o ON p.pid = o.pid
LEFT JOIN monthly_sales ms ON p.vendor = ms.vendor
WHERE p.vendor IS NOT NULL
AND o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
AND o.date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY p.vendor, ms.current_month, ms.previous_month
ORDER BY salesVolume DESC
LIMIT 10
@@ -244,45 +237,7 @@ router.get('/vendors', async (req, res) => {
console.log('Performance data:', performance);
// Get vendor comparison data
const [comparison] = await pool.query(`
SELECT
p.vendor,
CAST(COALESCE(ROUND(SUM(o.price * o.quantity) / NULLIF(COUNT(DISTINCT p.pid), 0), 2), 0) AS DECIMAL(15,3)) as salesPerProduct,
COALESCE(ROUND(AVG((o.price - p.cost_price) / NULLIF(o.price, 0) * 100), 1), 0) as averageMargin,
COUNT(DISTINCT p.pid) as size
FROM products p
LEFT JOIN orders o ON p.pid = o.pid AND o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
WHERE p.vendor IS NOT NULL
GROUP BY p.vendor
ORDER BY salesPerProduct DESC
LIMIT 20
`);
console.log('Comparison data:', comparison);
// Get vendor sales trends
const [trends] = await pool.query(`
SELECT
p.vendor,
DATE_FORMAT(o.date, '%b %Y') as month,
CAST(COALESCE(SUM(o.price * o.quantity), 0) AS DECIMAL(15,3)) as sales
FROM products p
LEFT JOIN orders o ON p.pid = o.pid
WHERE p.vendor IS NOT NULL
AND o.date >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
GROUP BY
p.vendor,
DATE_FORMAT(o.date, '%b %Y'),
DATE_FORMAT(o.date, '%Y-%m')
ORDER BY
p.vendor,
DATE_FORMAT(o.date, '%Y-%m')
`);
console.log('Trends data:', trends);
res.json({ performance, comparison, trends });
res.json({ performance });
} catch (error) {
console.error('Error fetching vendor performance:', error);
res.status(500).json({ error: 'Failed to fetch vendor performance' });