face / src /controllers /orderController.js
aliroohan179's picture
feat: implement business date utility and financial reporting system including expense tracking, platform sales, and profit-loss analysis endpoints
ec62259
Raw History Blame Contribute Delete
5.63 kB
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);
};