const Order = require('../models/Order'); const { log } = require('../utils/activityLogger'); const { getBusinessDate, getBusinessDayRange, getBusinessDateRange, getCurrentBusinessDayRange, } = require('../utils/businessDate'); // POST /api/orders — create single order exports.createOrder = async (req, res) => { const body = req.body; // localId acts as idempotency key — if already synced, return existing const existing = await Order.findOne({ localId: body.localId }); if (existing) return res.status(200).json(existing); const order = await Order.create({ ...body, cashier: req.user._id }); log(req.user, 'CREATE_ORDER', { entity: 'Order', entityId: order._id, detail: `Rs.${order.netTotal} ${order.orderType}` }); res.status(201).json(order); }; // POST /api/orders/bulk-sync — push batch of offline orders exports.bulkSync = async (req, res) => { const { orders } = req.body; if (!Array.isArray(orders) || orders.length === 0) return res.status(400).json({ message: 'Orders array required' }); const results = []; for (const orderData of orders) { const existing = await Order.findOne({ localId: orderData.localId }); if (existing) { if (orderData.status === 'voided' && existing.status !== 'voided') { existing.status = 'voided'; existing.voidedBy = req.user._id; existing.voidedAt = new Date(); await existing.save(); results.push({ localId: orderData.localId, status: 'updated_to_voided', id: existing._id }); } else { results.push({ localId: orderData.localId, status: 'already_synced', id: existing._id }); } } else { const order = await Order.create({ ...orderData, cashier: req.user._id }); results.push({ localId: orderData.localId, status: 'synced', id: order._id }); } } log(req.user, 'BULK_SYNC', { detail: `${results.length} orders synced` }); res.json({ results }); }; // GET /api/orders — owner sees all with filters; cashier sees active shift's orders only exports.getOrders = async (req, res) => { const isOwner = req.user.role === 'owner'; const { from, to, status, orderType, page = 1, limit = 100 } = req.query; const filter = {}; if (isOwner) { // Owner: apply all filters as requested if (status) filter.status = status; if (orderType) filter.orderType = orderType; if (from || to) { if (from && to && from === to) { const { from: start, to: end } = getBusinessDayRange(from); filter.createdAt = { $gte: start, $lte: end }; } else { const { from: start, to: end } = getBusinessDateRange(from, to); filter.createdAt = { $gte: start, $lte: end }; } } } else { // Cashier: active business shift orders by default, or specific date range if passed if (from || to) { if (from && to && from === to) { const { from: start, to: end } = getBusinessDayRange(from); filter.createdAt = { $gte: start, $lte: end }; } else { const { from: start, to: end } = getBusinessDateRange(from, to); filter.createdAt = { $gte: start, $lte: end }; } } else { const { from: start, to: end } = getCurrentBusinessDayRange(); filter.createdAt = { $gte: start, $lte: end }; } if (status) filter.status = status; } const [orders, total] = await Promise.all([ Order.find(filter) .populate('cashier', 'name username') .sort({ createdAt: -1 }) .skip((page - 1) * limit) .limit(Number(limit)), Order.countDocuments(filter), ]); res.json({ orders, total, page: Number(page), pages: Math.ceil(total / limit) }); }; // PUT /api/orders/:id/void — requires admin PIN exports.voidOrder = async (req, res) => { const { pin } = req.body; if (!pin) return res.status(400).json({ message: 'Admin PIN required' }); // Only owner can void post-payment if (req.user.role !== 'owner') return res.status(403).json({ message: 'Only owner can void orders' }); const User = require('../models/User'); const owner = await User.findById(req.user._id); const pinOk = await owner.matchPin(pin); if (!pinOk) return res.status(401).json({ message: 'Incorrect PIN' }); const order = await Order.findById(req.params.id); if (!order) return res.status(404).json({ message: 'Order not found' }); if (order.status === 'voided') return res.status(400).json({ message: 'Order already voided' }); order.status = 'voided'; order.voidedBy = req.user._id; order.voidedAt = new Date(); await order.save(); log(req.user, 'VOID_ORDER', { entity: 'Order', entityId: order._id, detail: `Voided order Rs.${order.netTotal}` }); res.json(order); }; // PUT /api/orders/:id/cancel — cashier cancel (current shift's orders only, no PIN required) exports.cancelOrder = async (req, res) => { const order = await Order.findById(req.params.id); if (!order) return res.status(404).json({ message: 'Order not found' }); if (order.status === 'voided') return res.status(400).json({ message: 'Order already cancelled' }); // Cashiers can only cancel orders from the active business shift const { from: currentShiftStart } = getCurrentBusinessDayRange(); if (order.createdAt < currentShiftStart) { return res.status(403).json({ message: 'Cannot cancel orders from previous shifts. Ask the owner to void it.' }); } order.status = 'voided'; order.voidedBy = req.user._id; order.voidedAt = new Date(); await order.save(); log(req.user, 'CASHIER_CANCEL', { entity: 'Order', entityId: order._id, detail: `Cashier cancelled Rs.${order.netTotal}` }); res.json(order); };