Girl Scout Cookie tracking app with Express/SQLite API and React/Vite client. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
160 lines
6.4 KiB
JavaScript
160 lines
6.4 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { getDb } = require('../db');
|
|
|
|
function getOrderWithItems(db, id) {
|
|
const order = db.prepare(`
|
|
SELECT o.*, c.name as customer_name
|
|
FROM orders o
|
|
LEFT JOIN customers c ON c.id = o.customer_id
|
|
WHERE o.id = ?
|
|
`).get(id);
|
|
if (!order) return null;
|
|
const items = db.prepare(`
|
|
SELECT oi.*, p.name as product_name
|
|
FROM order_items oi
|
|
JOIN products p ON p.id = oi.product_id
|
|
WHERE oi.order_id = ?
|
|
`).all(id);
|
|
return { ...order, items };
|
|
}
|
|
|
|
function deductStockForOrder(db, orderId, newItems, existingItemsOverride) {
|
|
const existing = existingItemsOverride !== undefined
|
|
? existingItemsOverride
|
|
: db.prepare('SELECT product_id, quantity FROM order_items WHERE order_id = ?').all(orderId);
|
|
const byProduct = {};
|
|
for (const e of existing) byProduct[e.product_id] = (byProduct[e.product_id] || 0) + e.quantity;
|
|
for (const it of newItems) {
|
|
const pid = it.product_id;
|
|
const qty = Number(it.quantity) || 0;
|
|
byProduct[pid] = (byProduct[pid] || 0) - qty;
|
|
}
|
|
for (const [productId, delta] of Object.entries(byProduct)) {
|
|
if (delta === 0) continue;
|
|
const product = db.prepare('SELECT quantity_on_hand FROM products WHERE id = ?').get(productId);
|
|
if (!product) throw new Error(`Product ${productId} not found`);
|
|
const newQty = product.quantity_on_hand + delta;
|
|
if (newQty < 0) throw new Error(`Insufficient stock for product id ${productId}`);
|
|
db.prepare('UPDATE products SET quantity_on_hand = ? WHERE id = ?').run(newQty, productId);
|
|
}
|
|
}
|
|
|
|
function applyOrderItems(db, orderId, items) {
|
|
db.prepare('DELETE FROM order_items WHERE order_id = ?').run(orderId);
|
|
const productRows = db.prepare('SELECT id, price FROM products').all();
|
|
const priceByProduct = {};
|
|
for (const p of productRows) priceByProduct[p.id] = p.price;
|
|
for (const it of items) {
|
|
const price = it.price_at_sale != null ? it.price_at_sale : (priceByProduct[it.product_id] || 0);
|
|
db.prepare(
|
|
'INSERT INTO order_items (order_id, product_id, quantity, price_at_sale) VALUES (?, ?, ?, ?)'
|
|
).run(orderId, it.product_id, it.quantity, price);
|
|
}
|
|
}
|
|
|
|
router.get('/', (req, res) => {
|
|
try {
|
|
const db = getDb();
|
|
const rows = db.prepare(`
|
|
SELECT o.*, c.name as customer_name
|
|
FROM orders o
|
|
LEFT JOIN customers c ON c.id = o.customer_id
|
|
ORDER BY o.created_at DESC
|
|
`).all();
|
|
res.json(rows);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.get('/:id', (req, res) => {
|
|
try {
|
|
const db = getDb();
|
|
const order = getOrderWithItems(db, req.params.id);
|
|
if (!order) return res.status(404).json({ error: 'Order not found' });
|
|
res.json(order);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.post('/', (req, res) => {
|
|
try {
|
|
const db = getDb();
|
|
const { customer_id, status = 'pending', notes, items = [] } = req.body;
|
|
if (!Array.isArray(items) || items.length === 0) {
|
|
return res.status(400).json({ error: 'At least one order item is required' });
|
|
}
|
|
for (const it of items) {
|
|
const product = db.prepare('SELECT id, quantity_on_hand, price FROM products WHERE id = ?').get(it.product_id);
|
|
if (!product) return res.status(400).json({ error: `Product ${it.product_id} not found` });
|
|
const qty = Number(it.quantity) || 0;
|
|
if (qty <= 0) return res.status(400).json({ error: 'Quantity must be positive' });
|
|
if (product.quantity_on_hand < qty) {
|
|
return res.status(400).json({ error: `Insufficient stock for ${product.id}` });
|
|
}
|
|
}
|
|
const result = db.prepare(
|
|
'INSERT INTO orders (customer_id, status, notes) VALUES (?, ?, ?)'
|
|
).run(customer_id || null, status, notes || null);
|
|
const orderId = result.lastInsertRowid;
|
|
applyOrderItems(db, orderId, items);
|
|
deductStockForOrder(db, orderId, items, []);
|
|
db.prepare('UPDATE orders SET updated_at = datetime(\'now\') WHERE id = ?').run(orderId);
|
|
const order = getOrderWithItems(db, orderId);
|
|
res.status(201).json(order);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.put('/:id', (req, res) => {
|
|
try {
|
|
const db = getDb();
|
|
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(req.params.id);
|
|
if (!order) return res.status(404).json({ error: 'Order not found' });
|
|
const { customer_id, status, notes, items } = req.body;
|
|
if (items !== undefined) {
|
|
if (!Array.isArray(items)) return res.status(400).json({ error: 'items must be an array' });
|
|
const existingItems = db.prepare('SELECT product_id, quantity FROM order_items WHERE order_id = ?').all(req.params.id);
|
|
const newItems = items.map(it => ({ product_id: it.product_id, quantity: Number(it.quantity) || 0 }));
|
|
for (const it of newItems) {
|
|
const product = db.prepare('SELECT id, quantity_on_hand FROM products WHERE id = ?').get(it.product_id);
|
|
if (!product) return res.status(400).json({ error: `Product ${it.product_id} not found` });
|
|
}
|
|
deductStockForOrder(db, req.params.id, newItems, existingItems);
|
|
applyOrderItems(db, req.params.id, items);
|
|
}
|
|
const cid = customer_id !== undefined ? (customer_id || null) : order.customer_id;
|
|
const st = status !== undefined ? status : order.status;
|
|
const no = notes !== undefined ? notes : order.notes;
|
|
db.prepare('UPDATE orders SET customer_id = ?, status = ?, notes = ?, updated_at = datetime(\'now\') WHERE id = ?')
|
|
.run(cid, st, no, req.params.id);
|
|
const updated = getOrderWithItems(db, req.params.id);
|
|
res.json(updated);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
router.delete('/:id', (req, res) => {
|
|
try {
|
|
const db = getDb();
|
|
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(req.params.id);
|
|
if (!order) return res.status(404).json({ error: 'Order not found' });
|
|
const items = db.prepare('SELECT product_id, quantity FROM order_items WHERE order_id = ?').all(req.params.id);
|
|
for (const it of items) {
|
|
db.prepare('UPDATE products SET quantity_on_hand = quantity_on_hand + ? WHERE id = ?')
|
|
.run(it.quantity, it.product_id);
|
|
}
|
|
db.prepare('DELETE FROM order_items WHERE order_id = ?').run(req.params.id);
|
|
db.prepare('DELETE FROM orders WHERE id = ?').run(req.params.id);
|
|
res.status(204).send();
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|