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);
};