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

@@ -29,40 +29,46 @@ router.get('/', async (req, res) => {
let whereClause = '1=1';
const params = [];
let paramCounter = 1;
if (search) {
whereClause += ' AND (po.po_id LIKE ? OR po.vendor LIKE ?)';
params.push(`%${search}%`, `%${search}%`);
whereClause += ` AND (po.po_id ILIKE $${paramCounter} OR po.vendor ILIKE $${paramCounter})`;
params.push(`%${search}%`);
paramCounter++;
}
if (status && status !== 'all') {
whereClause += ' AND po.status = ?';
whereClause += ` AND po.status = $${paramCounter}`;
params.push(Number(status));
paramCounter++;
}
if (vendor && vendor !== 'all') {
whereClause += ' AND po.vendor = ?';
whereClause += ` AND po.vendor = $${paramCounter}`;
params.push(vendor);
paramCounter++;
}
if (startDate) {
whereClause += ' AND po.date >= ?';
whereClause += ` AND po.date >= $${paramCounter}`;
params.push(startDate);
paramCounter++;
}
if (endDate) {
whereClause += ' AND po.date <= ?';
whereClause += ` AND po.date <= $${paramCounter}`;
params.push(endDate);
paramCounter++;
}
// Get filtered summary metrics
const [summary] = await pool.query(`
const { rows: [summary] } = await pool.query(`
WITH po_totals AS (
SELECT
po_id,
SUM(ordered) as total_ordered,
SUM(received) as total_received,
CAST(SUM(ordered * cost_price) AS DECIMAL(15,3)) as total_cost
ROUND(SUM(ordered * cost_price)::numeric, 3) as total_cost
FROM purchase_orders po
WHERE ${whereClause}
GROUP BY po_id
@@ -72,26 +78,26 @@ router.get('/', async (req, res) => {
SUM(total_ordered) as total_ordered,
SUM(total_received) as total_received,
ROUND(
SUM(total_received) / NULLIF(SUM(total_ordered), 0), 3
(SUM(total_received)::numeric / NULLIF(SUM(total_ordered), 0)), 3
) as fulfillment_rate,
CAST(SUM(total_cost) AS DECIMAL(15,3)) as total_value,
CAST(AVG(total_cost) AS DECIMAL(15,3)) as avg_cost
ROUND(SUM(total_cost)::numeric, 3) as total_value,
ROUND(AVG(total_cost)::numeric, 3) as avg_cost
FROM po_totals
`, params);
// Get total count for pagination
const [countResult] = await pool.query(`
const { rows: [countResult] } = await pool.query(`
SELECT COUNT(DISTINCT po_id) as total
FROM purchase_orders po
WHERE ${whereClause}
`, params);
const total = countResult[0].total;
const total = countResult.total;
const offset = (page - 1) * limit;
const pages = Math.ceil(total / limit);
// Get recent purchase orders
const [orders] = await pool.query(`
const { rows: orders } = await pool.query(`
WITH po_totals AS (
SELECT
po_id,
@@ -101,10 +107,10 @@ router.get('/', async (req, res) => {
receiving_status,
COUNT(DISTINCT pid) as total_items,
SUM(ordered) as total_quantity,
CAST(SUM(ordered * cost_price) AS DECIMAL(15,3)) as total_cost,
ROUND(SUM(ordered * cost_price)::numeric, 3) as total_cost,
SUM(received) as total_received,
ROUND(
SUM(received) / NULLIF(SUM(ordered), 0), 3
(SUM(received)::numeric / NULLIF(SUM(ordered), 0)), 3
) as fulfillment_rate
FROM purchase_orders po
WHERE ${whereClause}
@@ -113,7 +119,7 @@ router.get('/', async (req, res) => {
SELECT
po_id as id,
vendor as vendor_name,
DATE_FORMAT(date, '%Y-%m-%d') as order_date,
to_char(date, 'YYYY-MM-DD') as order_date,
status,
receiving_status,
total_items,
@@ -124,21 +130,21 @@ router.get('/', async (req, res) => {
FROM po_totals
ORDER BY
CASE
WHEN ? = 'order_date' THEN date
WHEN ? = 'vendor_name' THEN vendor
WHEN ? = 'total_cost' THEN CAST(total_cost AS DECIMAL(15,3))
WHEN ? = 'total_received' THEN CAST(total_received AS DECIMAL(15,3))
WHEN ? = 'total_items' THEN CAST(total_items AS SIGNED)
WHEN ? = 'total_quantity' THEN CAST(total_quantity AS SIGNED)
WHEN ? = 'fulfillment_rate' THEN CAST(fulfillment_rate AS DECIMAL(5,3))
WHEN ? = 'status' THEN status
WHEN $${paramCounter} = 'order_date' THEN date
WHEN $${paramCounter} = 'vendor_name' THEN vendor
WHEN $${paramCounter} = 'total_cost' THEN total_cost
WHEN $${paramCounter} = 'total_received' THEN total_received
WHEN $${paramCounter} = 'total_items' THEN total_items
WHEN $${paramCounter} = 'total_quantity' THEN total_quantity
WHEN $${paramCounter} = 'fulfillment_rate' THEN fulfillment_rate
WHEN $${paramCounter} = 'status' THEN status
ELSE date
END ${sortDirection === 'desc' ? 'DESC' : 'ASC'}
LIMIT ? OFFSET ?
`, [...params, sortColumn, sortColumn, sortColumn, sortColumn, sortColumn, sortColumn, sortColumn, sortColumn, Number(limit), offset]);
LIMIT $${paramCounter + 1} OFFSET $${paramCounter + 2}
`, [...params, sortColumn, Number(limit), offset]);
// Get unique vendors for filter options
const [vendors] = await pool.query(`
const { rows: vendors } = await pool.query(`
SELECT DISTINCT vendor
FROM purchase_orders
WHERE vendor IS NOT NULL AND vendor != ''
@@ -146,7 +152,7 @@ router.get('/', async (req, res) => {
`);
// Get unique statuses for filter options
const [statuses] = await pool.query(`
const { rows: statuses } = await pool.query(`
SELECT DISTINCT status
FROM purchase_orders
WHERE status IS NOT NULL
@@ -169,12 +175,12 @@ router.get('/', async (req, res) => {
// Parse summary metrics
const parsedSummary = {
order_count: Number(summary[0].order_count) || 0,
total_ordered: Number(summary[0].total_ordered) || 0,
total_received: Number(summary[0].total_received) || 0,
fulfillment_rate: Number(summary[0].fulfillment_rate) || 0,
total_value: Number(summary[0].total_value) || 0,
avg_cost: Number(summary[0].avg_cost) || 0
order_count: Number(summary.order_count) || 0,
total_ordered: Number(summary.total_ordered) || 0,
total_received: Number(summary.total_received) || 0,
fulfillment_rate: Number(summary.fulfillment_rate) || 0,
total_value: Number(summary.total_value) || 0,
avg_cost: Number(summary.avg_cost) || 0
};
res.json({
@@ -202,7 +208,7 @@ router.get('/vendor-metrics', async (req, res) => {
try {
const pool = req.app.locals.pool;
const [metrics] = await pool.query(`
const { rows: metrics } = await pool.query(`
WITH delivery_metrics AS (
SELECT
vendor,
@@ -213,7 +219,7 @@ router.get('/vendor-metrics', async (req, res) => {
CASE
WHEN status >= ${STATUS.RECEIVING_STARTED} AND receiving_status >= ${RECEIVING_STATUS.PARTIAL_RECEIVED}
AND received_date IS NOT NULL AND date IS NOT NULL
THEN DATEDIFF(received_date, date)
THEN (received_date - date)::integer
ELSE NULL
END as delivery_days
FROM purchase_orders
@@ -226,18 +232,18 @@ router.get('/vendor-metrics', async (req, res) => {
SUM(ordered) as total_ordered,
SUM(received) as total_received,
ROUND(
SUM(received) / NULLIF(SUM(ordered), 0), 3
(SUM(received)::numeric / NULLIF(SUM(ordered), 0)), 3
) as fulfillment_rate,
CAST(ROUND(
SUM(ordered * cost_price) / NULLIF(SUM(ordered), 0), 2
) AS DECIMAL(15,3)) as avg_unit_cost,
CAST(SUM(ordered * cost_price) AS DECIMAL(15,3)) as total_spend,
ROUND(
AVG(NULLIF(delivery_days, 0)), 1
(SUM(ordered * cost_price)::numeric / NULLIF(SUM(ordered), 0)), 2
) as avg_unit_cost,
ROUND(SUM(ordered * cost_price)::numeric, 3) as total_spend,
ROUND(
AVG(NULLIF(delivery_days, 0))::numeric, 1
) as avg_delivery_days
FROM delivery_metrics
GROUP BY vendor
HAVING total_orders > 0
HAVING COUNT(DISTINCT po_id) > 0
ORDER BY total_spend DESC
`);
@@ -251,7 +257,7 @@ router.get('/vendor-metrics', async (req, res) => {
fulfillment_rate: Number(vendor.fulfillment_rate) || 0,
avg_unit_cost: Number(vendor.avg_unit_cost) || 0,
total_spend: Number(vendor.total_spend) || 0,
avg_delivery_days: vendor.avg_delivery_days === null ? null : Number(vendor.avg_delivery_days)
avg_delivery_days: Number(vendor.avg_delivery_days) || 0
}));
res.json(parsedMetrics);