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 atomicDeductStock(db, productId, quantity) { const result = db.prepare( 'UPDATE products SET quantity_on_hand = quantity_on_hand - ? WHERE id = ? AND quantity_on_hand >= ?' ).run(quantity, productId, quantity); if (result.changes === 0) { const product = db.prepare('SELECT id FROM products WHERE id = ?').get(productId); if (!product) throw new Error(`Product ${productId} not found`); throw new Error(`Insufficient stock for product id ${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, walk_in_name, 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 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' }); } const createOrder = db.transaction(() => { let resolvedCustomerId = customer_id || null; if (!resolvedCustomerId && typeof walk_in_name === 'string' && walk_in_name.trim()) { const custResult = db.prepare('INSERT INTO customers (name) VALUES (?)').run(walk_in_name.trim()); resolvedCustomerId = custResult.lastInsertRowid; } const result = db.prepare( 'INSERT INTO orders (customer_id, status, notes) VALUES (?, ?, ?)' ).run(resolvedCustomerId, status, notes || null); const orderId = result.lastInsertRowid; for (const it of items) { const qty = Number(it.quantity) || 0; atomicDeductStock(db, it.product_id, qty); } applyOrderItems(db, orderId, items); db.prepare('UPDATE orders SET updated_at = datetime(\'now\') WHERE id = ?').run(orderId); return orderId; }); const orderId = createOrder(); const order = getOrderWithItems(db, orderId); res.status(201).json(order); } catch (err) { if (err.message.startsWith('Insufficient stock') || err.message.includes('not found')) { return res.status(400).json({ error: err.message }); } 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; const updateOrder = db.transaction(() => { if (items !== undefined) { if (!Array.isArray(items)) throw new Error('items must be an array'); // Restore stock from existing items const existingItems = db.prepare('SELECT product_id, quantity FROM order_items WHERE order_id = ?').all(req.params.id); for (const ei of existingItems) { db.prepare('UPDATE products SET quantity_on_hand = quantity_on_hand + ? WHERE id = ?') .run(ei.quantity, ei.product_id); } // Deduct stock for new items atomically for (const it of items) { const qty = Number(it.quantity) || 0; if (qty <= 0) throw new Error('Quantity must be positive'); atomicDeductStock(db, it.product_id, qty); } 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); }); updateOrder(); const updated = getOrderWithItems(db, req.params.id); res.json(updated); } catch (err) { if (err.message.startsWith('Insufficient stock') || err.message.includes('not found') || err.message === 'items must be an array' || err.message === 'Quantity must be positive') { return res.status(400).json({ error: err.message }); } 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 deleteOrder = db.transaction(() => { 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); }); deleteOrder(); res.status(204).send(); } catch (err) { res.status(500).json({ error: err.message }); } }); module.exports = router;