Spaces:
Running
Running
File size: 5,631 Bytes
1e480b5 ec62259 1e480b5 bcea9e7 1e480b5 ec62259 1e480b5 ec62259 1e480b5 ec62259 bcea9e7 ec62259 bcea9e7 ec62259 bcea9e7 1e480b5 ec62259 1e480b5 ec62259 1e480b5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 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);
};
|