• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Seniru / defendxstore / 14990977406

13 May 2025 07:40AM UTC coverage: 66.681%. First build
14990977406

Pull #98

github

web-flow
Merge 6c6a97d48 into 53385cce8
Pull Request #98: [WIP] New test cases

380 of 697 branches covered (54.52%)

Branch coverage included in aggregate %.

82 of 90 new or added lines in 7 files covered. (91.11%)

1215 of 1695 relevant lines covered (71.68%)

35.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.52
/backend/src/controllers/orders.js
1
require("dotenv").config()
3✔
2

3
const { StatusCodes } = require("http-status-codes")
3✔
4
const createResponse = require("../utils/createResponse")
3✔
5
const jwt = require("jsonwebtoken")
3✔
6

7
const Order = require("../models/Order")
3✔
8
const User = require("../models/User")
3✔
9
const Item = require("../models/Item")
3✔
10
const PromoCode = require("../models/Promocodes")
3✔
11
const logger = require("../utils/logger")
3✔
12
const { sendMail } = require("../services/email")
3✔
13
const OrderReport = require("../models/reports/OrderReport")
3✔
14
const Expense = require("../models/Expense")
3✔
15
const ExcelJS = require("exceljs")
3✔
16
const { addTable, createAttachment, columns } = require("../utils/spreadsheets")
3✔
17
const { default: mongoose } = require("mongoose")
3✔
18

19
const getAllOrdersSpreadsheet = async (res, orders) => {
3✔
20
    const workbook = new ExcelJS.Workbook()
×
21
    const worksheet = workbook.addWorksheet("Orders")
×
22

23
    worksheet.columns = columns.orders
×
24

25
    addTable(
×
26
        worksheet,
27
        [
28
            "",
29
            "Ordered date",
30
            "Item count",
31
            "Price",
32
            "Delivery address",
33
            "Username",
34
            "Assigned agent",
35
            "Invoice",
36
        ],
37
        orders.map((order) => [
×
38
            order._id,
39
            order.orderdate.toLocaleString(),
40
            order.items.length,
41
            order.price,
42
            order.deliveryAddress,
43
            order.user?.username || "Deleted account",
×
44
            order.assignedAgent?.username,
45
            `${process.env.FRONTEND_URL}/invoice?id=${order._id}`,
46
        ]),
47
    )
48

49
    return createAttachment(workbook, res)
×
50
}
51

52
const getOrders = async (req, res, next) => {
3✔
53
    try {
15✔
54
        const { status, downloadSheet } = req.query
15✔
55
        if (status && !["pending", "on_the_way", "delivered"].includes(status))
15✔
56
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid status")
3✔
57
        const user = await User.findOne({ username: req.user.username }).exec()
12✔
58

59
        const query = { user: user._id }
12✔
60
        if (status) query.status = status
12✔
61
        let orders = await Order.find(query)
12✔
62
            .populate("items.product")
63
            .populate({ path: "user", select: "username email contactNumber" })
64
            .populate({ path: "assignedAgent", select: "username email contactNumber" })
65
            .exec()
66

67
        orders = orders.map((order) => {
12✔
68
            const groupedItems = order.items.reduce((acc, item) => {
84✔
69
                const product = item.product
182✔
70
                const key = `${product.itemName}-${item.color}-${item.size}`
182✔
71

72
                if (!acc[key]) {
182!
73
                    acc[key] = {
182✔
74
                        ...product.toObject(),
75
                        color: item.color || null,
182!
76
                        size: item.size || null,
182!
77
                        quantity: 0,
78
                    }
79
                }
80

81
                acc[key].quantity += item.quantity || 1
182✔
82
                return acc
182✔
83
            }, {})
84

85
            return {
84✔
86
                ...order.toObject(),
87
                items: Object.values(groupedItems),
88
            }
89
        })
90

91
        if (downloadSheet == "true") return getAllOrdersSpreadsheet(res, orders)
12!
92

93
        return createResponse(res, StatusCodes.OK, orders)
12✔
94
    } catch (error) {
95
        next(error)
×
96
    }
97
}
98

99
const sendInvoiceToEmail = async (user, data) => {
3✔
100
    try {
3✔
101
        let rows = []
3✔
102

103
        const groupedItems = Object.values(
3✔
104
            data.items.reduce((acc, item) => {
105
                const product = item.product
6✔
106
                const key = `${item.product.itemName}-${item.color}-${item.size}`
6✔
107

108
                if (!acc[key]) {
6!
109
                    acc[key] = {
6✔
110
                        ...product.toObject(),
111
                        color: item.color || null,
6!
112
                        size: item.size || null,
6!
113
                        quantity: 0,
114
                    }
115
                }
116

117
                acc[key].quantity += item.quantity || 1
6✔
118
                return acc
6✔
119
            }, {}),
120
        )
121

122
        for (let item of groupedItems) {
3✔
123
            let row = `
6✔
124
            <tr>
125
                <td style="padding: 8px"><b>${item.itemName}</b> (${item.size})</td>
126
                <td style="padding: 8px">
127
                    <span
128
                        style="
129
                            display: inline-block;
130
                            width: 15px;
131
                            height: 15px;
132
                            background-color: ${item.color};
133
                            border-radius: 3px;
134
                        "
135
                    ></span>
136
                </td>
137
                <td style="padding: 8px; text-align: right">LKR ${item.price}</td>
138
                <td style="padding: 8px; text-align: right">${item.quantity}</td>
139
                <td style="padding: 8px; text-align: right">LKR ${item.price * item.quantity}</td>
140
            </tr>`
141
            rows.push(row)
6✔
142
        }
143

144
        sendMail(user.email, "DefendxStore Order Invoice", "invoice", {
3✔
145
            email: user.email,
146
            username: user.username,
147
            orderId: data.orderId,
148
            orderDate: data.orderDate,
149
            deliveryAddress: data.deliveryAddress,
150
            total: data.total,
151
            items: rows.join(""),
152
        })
153
    } catch (error) {
154
        logger.error(error.toString())
×
155
    }
156
}
157

158
const createOrder = async (req, res, next) => {
3✔
159
    try {
18✔
160
        const { deliveryAddress, promocode } = req.body
18✔
161
        if (!deliveryAddress)
18✔
162
            return createResponse(res, StatusCodes.BAD_REQUEST, "Delivery address is required")
3✔
163

164
        const user = await User.findOne({ username: req.user.username })
15✔
165
            .populate({ path: "cart.product" })
166
            .exec()
167
        const cart = user.cart
15✔
168

169
        if (!Array.isArray(cart)) {
15!
170
            return createResponse(
×
171
                res,
172
                StatusCodes.BAD_REQUEST,
173
                "Invalid cart data. Cart must be an array.",
174
            )
175
        }
176

177
        if (cart.length === 0) {
15✔
178
            return createResponse(res, StatusCodes.BAD_REQUEST, "Cart is empty.")
3✔
179
        }
180

181
        let total = cart.map((item) => item.product.price).reduce((total, val) => total + val)
24✔
182
        // apply promotion codes
183
        if (promocode) {
12✔
184
            const code = await PromoCode.findOne({ promocode }).exec()
6✔
185
            if (!code)
6✔
186
                return createResponse(res, StatusCodes.NOT_FOUND, {
3✔
187
                    field: "promocode",
188
                    message: "Promocode not found",
189
                })
190
            const discount = total * (code.discount / 100)
3✔
191
            total -= discount
3✔
192
        }
193

194
        const order = new Order({
9✔
195
            user: user._id,
196
            status: "pending",
197
            orderdate: Date.now(),
198
            deliveryAddress,
199
            items: cart,
200
            price: total,
201
        })
202
        await order.save()
9✔
203

204
        if (user.verified)
9✔
205
            await sendInvoiceToEmail(user, {
3✔
206
                orderId: order._id,
207
                orderDate: order.orderdate,
208
                deliveryAddress: order.deliveryAddress,
209
                items: cart,
210
                total,
211
            })
212

213
        user.cart = []
9✔
214
        await user.save()
9✔
215

216
        await OrderReport.create({
9✔
217
            user: user._id,
218
            action: OrderReport.actions.createOrder,
219
            data: {},
220
        })
221

222
        try {
9✔
223
            for (let item of order.items) {
9✔
224
                await user.incrementProgress("casualShopper")
18✔
225
                if (item.product.itemName == "The Rose Collection - Embroidered")
18!
226
                    await user.incrementProgress("roseEnthusiast")
×
227
                await Item.findByIdAndUpdate(item.product._id, {
18✔
228
                    $inc: { quantity: -1 },
229
                }).exec()
230
            }
231
        } catch (error) {
NEW
232
            if (error.name === "ValidationError")
×
NEW
233
                return createResponse(res, StatusCodes.BAD_REQUEST, error.message)
×
234
            logger.error(error.toString())
×
235
        }
236

237
        return createResponse(res, StatusCodes.CREATED, order)
9✔
238
    } catch (error) {
239
        next(error)
×
240
    }
241
}
242

243
const getOrder = async (req, res, next) => {
3✔
244
    try {
15✔
245
        const { id } = req.params
15✔
246
        const user = req.user
15✔
247

248
        if (!mongoose.isValidObjectId(id))
15✔
249
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid order ID")
3✔
250

251
        const order = await Order.findOne({ _id: id })
12✔
252
            .populate("items.product")
253
            .populate({ path: "user", select: "username email" })
254
            .exec()
255
        if (!order) return createResponse(res, StatusCodes.NOT_FOUND, "Order not found")
12✔
256
        if (!user.roles.includes("DELIVERY_AGENT") && user.username !== order.user.username)
9✔
257
            return createResponse(
3✔
258
                res,
259
                StatusCodes.FORBIDDEN,
260
                "You are not authorized to access this order",
261
            )
262

263
        const groupedItems = order.items.reduce((acc, item) => {
6✔
264
            const product = item.product
14✔
265
            const key = `${item.product.itemName}-${item.color}-${item.size}`
14✔
266

267
            if (!acc[key]) {
14!
268
                acc[key] = {
14✔
269
                    ...product.toObject(),
270
                    color: item.color || null,
14!
271
                    size: item.size || null,
14!
272
                    quantity: 0,
273
                }
274
            }
275

276
            acc[key].quantity += item.quantity || 1
14✔
277
            return acc
14✔
278
        }, {})
279

280
        return createResponse(res, StatusCodes.OK, {
6✔
281
            ...order.toObject(),
282
            items: Object.values(groupedItems),
283
        })
284
    } catch (error) {
285
        next(error)
×
286
    }
287
}
288

289
const deleteOrder = async (req, res, next) => {
3✔
290
    try {
18✔
291
        const { id } = req.params
18✔
292
        if (!mongoose.isValidObjectId(id))
18✔
293
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid order ID")
3✔
294

295
        const reqUser = req.user
15✔
296
        const user = await User.findOne({ username: reqUser.username }).exec()
15✔
297
        const order = await Order.findById(id).populate("user").exec()
15✔
298
        if (!order) return createResponse(res, StatusCodes.NOT_FOUND, "Order not found")
15✔
299

300
        const isAdminOrAgent =
301
            reqUser.roles.includes("ADMIN") || reqUser.roles.includes("DELIVERY_AGENT")
12✔
302
        const isOwner = user.username === order.user.username
12✔
303

304
        if (!isAdminOrAgent && !isOwner)
12✔
305
            return createResponse(res, StatusCodes.FORBIDDEN, "You cannot delete this order")
3✔
306

307
        await Order.findByIdAndDelete(id)
9✔
308

309
        await OrderReport.create({
9✔
310
            user: user._id,
311
            action: OrderReport.actions.deleteOrder,
312
            data: { orderId: order._id },
313
        })
314

315
        return createResponse(res, StatusCodes.OK, "Order deleted")
9✔
316
    } catch (error) {
317
        next(error)
×
318
    }
319
}
320

321
const acquireDelivery = async (req, res, next) => {
3✔
322
    try {
15✔
323
        const { id } = req.params
15✔
324
        if (!mongoose.isValidObjectId(id))
15✔
325
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid order ID")
3✔
326

327
        const user = await User.findOne({ username: req.user.username }).exec()
12✔
328
        const order = await Order.findOne({ _id: id }).exec()
12✔
329
        if (!order) return createResponse(res, StatusCodes.NOT_FOUND, "Order not found")
12✔
330
        if (order.status === "delivered")
9✔
331
            return createResponse(res, StatusCodes.FORBIDDEN, "Already completed")
3✔
332
        if (order.assignedAgent)
6✔
333
            return createResponse(res, StatusCodes.CONFLICT, "Already assigned")
3✔
334

335
        order.assignedAgent = user._id
3✔
336
        await order.save()
3✔
337

338
        const orderUser = await User.findById(order.user).exec()
3✔
339
        await orderUser.pushNotification(
3✔
340
            `${user.username} has been assigned for the delivery of your order (Order ID: #${id})`,
341
        )
342

343
        await OrderReport.create({
3✔
344
            user: user._id,
345
            action: OrderReport.actions.acquireDelivery,
346
            data: { orderId: order._id },
347
        })
348
        return createResponse(res, StatusCodes.OK, "Order acquired")
3✔
349
    } catch (error) {
350
        next(error)
×
351
    }
352
}
353

354
const updateOrderStatus = async (req, res, next) => {
3✔
355
    try {
21✔
356
        const { status } = req.body
21✔
357
        const { id } = req.params
21✔
358
        if (!status || !["pending", "on_the_way", "delivered"].includes(status))
21✔
359
            return createResponse(res, StatusCodes.BAD_REQUEST, "Invalid status")
9✔
360

361
        const user = await User.findOne({ username: req.user.username }).exec()
12✔
362
        const order = await Order.findOne({ _id: id }).exec()
12✔
363

364
        if (!order) return createResponse(res, StatusCodes.NOT_FOUND, "Order not found")
12✔
365

366
        if (!order.assignedAgent || !order.assignedAgent.equals(user._id))
9✔
367
            return createResponse(res, StatusCodes.FORBIDDEN, "This order is not assigned to you")
3✔
368

369
        order.status = status
6✔
370
        await order.save()
6✔
371

372
        const orderUser = await User.findById(order.user).exec()
6✔
373
        switch (status) {
6!
374
            case "on_the_way":
375
                await orderUser.pushNotification(`Your order is on the way! (Order ID: #${id}`)
3✔
376
                break
3✔
377
            case "delivered":
378
                await orderUser.pushNotification(
3✔
379
                    `Your order has been delivered! (Order ID: #${id})`,
380
                )
381
                break
3✔
382
            default:
383
                break
×
384
        }
385

386
        if (order.status == "delivered") {
6✔
387
            await Expense.create({
3✔
388
                date: Date.now(),
389
                amount: 200,
390
                description: `Delivery of order #${order._id}`,
391
                category: "Delivery cost",
392
            })
393
            await orderUser.incrementProgress("firstPurchase")
3✔
394
        }
395

396
        await OrderReport.create({
6✔
397
            user: user._id,
398
            action: OrderReport.actions.updateOrderStatus,
399
            data: { orderId: order._id, status },
400
        })
401
        return createResponse(res, StatusCodes.OK, "Order status updated")
6✔
402
    } catch (error) {
403
        next(error)
×
404
    }
405
}
406

407
module.exports = {
3✔
408
    getOrders,
409
    createOrder,
410
    getOrder,
411
    deleteOrder,
412
    acquireDelivery,
413
    updateOrderStatus,
414
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc