Use new categories correctly in existing components and handle category names with commas

This commit is contained in:
2025-01-11 01:25:52 -05:00
parent 0bc86a3fee
commit d805e49449
9 changed files with 556 additions and 53 deletions

View File

@@ -83,10 +83,36 @@ async function handleCategories(connection, productId, categoriesStr) {
return;
}
// Split categories and clean them
const categories = categoriesStr.split(',')
// Special cases that should not be split
const specialCategories = [
'Paint, Dyes & Chalk',
'Fabric Paint, Markers, and Dye',
'Crystals, Gems & Rhinestones',
'Pens, Pencils & Markers'
];
// Split categories and clean them, preserving special cases
const categories = [];
let remainingStr = categoriesStr;
// First check for special categories
for (const special of specialCategories) {
if (remainingStr.includes(special)) {
categories.push(special);
// Remove the special category from the string
remainingStr = remainingStr.replace(special, '');
}
}
// Then process any remaining regular categories
remainingStr.split(',')
.map(cat => cat.trim())
.filter(cat => cat.length > 0);
.filter(cat => cat.length > 0)
.forEach(cat => {
if (!categories.includes(cat)) {
categories.push(cat);
}
});
// Remove existing category relationships for this product
await connection.query(

View File

@@ -65,7 +65,7 @@ router.get('/profit', async (req, res) => {
// Get profit margins by category
const [byCategory] = await pool.query(`
SELECT
COALESCE(p.categories, 'Uncategorized') as category,
c.name as category,
ROUND(
(SUM(o.price * o.quantity - p.cost_price * o.quantity) /
NULLIF(SUM(o.price * o.quantity), 0)) * 100, 1
@@ -74,8 +74,10 @@ router.get('/profit', async (req, res) => {
SUM(p.cost_price * o.quantity) as cost
FROM products p
LEFT JOIN orders o ON p.product_id = o.product_id
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY p.categories
GROUP BY c.name
ORDER BY profitMargin DESC
LIMIT 10
`);
@@ -190,14 +192,16 @@ router.get('/stock', async (req, res) => {
// Get turnover by category
const [turnoverByCategory] = await pool.query(`
SELECT
COALESCE(p.categories, 'Uncategorized') as category,
c.name as category,
ROUND(SUM(o.quantity) / NULLIF(AVG(p.stock_quantity), 0), 1) as turnoverRate,
ROUND(AVG(p.stock_quantity), 0) as averageStock,
SUM(o.quantity) as totalSales
FROM products p
LEFT JOIN orders o ON p.product_id = o.product_id
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY p.categories
GROUP BY c.name
HAVING turnoverRate > 0
ORDER BY turnoverRate DESC
LIMIT 10
@@ -328,7 +332,7 @@ router.get('/categories', async (req, res) => {
// Get category performance metrics
const [performance] = await pool.query(`
SELECT
COALESCE(p.categories, 'Uncategorized') as category,
c.name as category,
SUM(o.price * o.quantity) as revenue,
SUM(o.price * o.quantity - p.cost_price * o.quantity) as profit,
ROUND(
@@ -348,8 +352,10 @@ router.get('/categories', async (req, res) => {
COUNT(DISTINCT p.product_id) as productCount
FROM products p
LEFT JOIN orders o ON p.product_id = o.product_id
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 60 DAY)
GROUP BY p.categories
GROUP BY c.name
HAVING revenue > 0
ORDER BY revenue DESC
LIMIT 10
@@ -358,12 +364,14 @@ router.get('/categories', async (req, res) => {
// Get category revenue distribution
const [distribution] = await pool.query(`
SELECT
COALESCE(p.categories, 'Uncategorized') as category,
c.name as category,
SUM(o.price * o.quantity) as value
FROM products p
LEFT JOIN orders o ON p.product_id = o.product_id
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE o.date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY p.categories
GROUP BY c.name
HAVING value > 0
ORDER BY value DESC
LIMIT 6

View File

@@ -123,11 +123,13 @@ router.get('/category-stats', async (req, res) => {
try {
const [rows] = await pool.query(`
SELECT
categories,
COUNT(*) as count
FROM products
WHERE visible = true
GROUP BY categories
c.name as category,
COUNT(DISTINCT pc.product_id) as count
FROM categories c
LEFT JOIN product_categories pc ON c.id = pc.category_id
LEFT JOIN products p ON pc.product_id = p.product_id
WHERE p.visible = true
GROUP BY c.name
ORDER BY count DESC
LIMIT 10
`);
@@ -164,13 +166,15 @@ router.get('/sales-by-category', async (req, res) => {
try {
const [rows] = await pool.query(`
SELECT
p.categories as category,
c.name as category,
SUM(o.price * o.quantity) as total
FROM orders o
JOIN products p ON o.product_id = p.product_id
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE o.canceled = false
AND DATE(o.date) >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY p.categories
GROUP BY c.name
ORDER BY total DESC
LIMIT 6
`);
@@ -266,14 +270,16 @@ router.get('/inventory-metrics', async (req, res) => {
// Get stock levels by category
const [stockLevels] = await pool.query(`
SELECT
categories as category,
c.name as category,
SUM(CASE WHEN stock_quantity > 5 THEN 1 ELSE 0 END) as inStock,
SUM(CASE WHEN stock_quantity > 0 AND stock_quantity <= 5 THEN 1 ELSE 0 END) as lowStock,
SUM(CASE WHEN stock_quantity = 0 THEN 1 ELSE 0 END) as outOfStock
FROM products
FROM products p
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE visible = true
GROUP BY categories
ORDER BY categories ASC
GROUP BY c.name
ORDER BY c.name ASC
`);
// Get top vendors with product counts and average stock
@@ -296,21 +302,25 @@ router.get('/inventory-metrics', async (req, res) => {
const [stockTurnover] = await pool.query(`
WITH CategorySales AS (
SELECT
p.categories as category,
c.name as category,
SUM(o.quantity) as units_sold
FROM products p
LEFT JOIN orders o ON p.product_id = o.product_id
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE o.canceled = false
AND DATE(o.date) >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY p.categories
GROUP BY c.name
),
CategoryStock AS (
SELECT
categories as category,
AVG(stock_quantity) as avg_stock
FROM products
WHERE visible = true
GROUP BY categories
c.name as category,
AVG(p.stock_quantity) as avg_stock
FROM products p
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
WHERE p.visible = true
GROUP BY c.name
)
SELECT
cs.category,

View File

@@ -32,7 +32,14 @@ router.get('/', async (req, res) => {
}
if (category !== 'all') {
conditions.push('categories = ?');
conditions.push(`
product_id IN (
SELECT pc.product_id
FROM product_categories pc
JOIN categories c ON pc.category_id = c.id
WHERE c.name = ?
)
`);
params.push(category);
}
@@ -75,37 +82,46 @@ router.get('/', async (req, res) => {
// Get paginated results
const query = `
SELECT
product_id,
title,
SKU,
stock_quantity,
price,
regular_price,
cost_price,
vendor,
brand,
categories,
visible,
managing_stock,
image
FROM products
p.product_id,
p.title,
p.SKU,
p.stock_quantity,
p.price,
p.regular_price,
p.cost_price,
p.vendor,
p.brand,
p.visible,
p.managing_stock,
p.image,
GROUP_CONCAT(c.name) as categories
FROM products p
LEFT JOIN product_categories pc ON p.product_id = pc.product_id
LEFT JOIN categories c ON pc.category_id = c.id
WHERE ${conditions.join(' AND ')}
GROUP BY p.product_id
ORDER BY ${sortColumn} ${sortDirection}
LIMIT ? OFFSET ?
`;
const [rows] = await pool.query(query, [...params, limit, offset]);
// Transform the categories string into an array
const productsWithCategories = rows.map(product => ({
...product,
categories: product.categories ? product.categories.split(',') : []
}));
// Get unique categories and vendors for filters
const [categories] = await pool.query(
'SELECT DISTINCT categories FROM products WHERE visible = true AND categories IS NOT NULL AND categories != "" ORDER BY categories'
'SELECT name FROM categories ORDER BY name'
);
const [vendors] = await pool.query(
'SELECT DISTINCT vendor FROM products WHERE visible = true AND vendor IS NOT NULL AND vendor != "" ORDER BY vendor'
);
res.json({
products: rows,
products: productsWithCategories,
pagination: {
total,
pages: Math.ceil(total / limit),
@@ -113,7 +129,7 @@ router.get('/', async (req, res) => {
limit
},
filters: {
categories: categories.map(c => c.categories),
categories: categories.map(c => c.name),
vendors: vendors.map(v => v.vendor)
}
});

View File

@@ -243,7 +243,7 @@ router.get('/cost-analysis', async (req, res) => {
const [analysis] = await pool.query(`
SELECT
p.categories,
c.name as categories,
COUNT(DISTINCT po.product_id) as unique_products,
ROUND(AVG(po.cost_price), 2) as avg_cost,
MIN(po.cost_price) as min_cost,
@@ -254,7 +254,9 @@ router.get('/cost-analysis', async (req, res) => {
SUM(po.ordered * po.cost_price) as total_spend
FROM purchase_orders po
JOIN products p ON po.product_id = p.product_id
GROUP BY p.categories
JOIN product_categories pc ON p.product_id = pc.product_id
JOIN categories c ON pc.category_id = c.id
GROUP BY c.name
ORDER BY total_spend DESC
`);