1
1
import mongoose from "mongoose" ;
2
+ import {
3
+ getRollbackStockLevel ,
4
+ getUpdatedStockLevel
5
+ } from "../helpers/order" ;
6
+ import { StockValidationError } from "../errors/StockValidationError" ;
2
7
import Order from "../models/Order" ;
3
8
import OrderItem from "../models/OrderItem" ;
4
9
import Seller from "../models/Seller" ;
@@ -29,57 +34,69 @@ export const createOrder = async (
29
34
is_fulfilled : false ,
30
35
fulfillment_method : orderData . fulfillmentMethod ,
31
36
seller_fulfillment_description : orderData . sellerFulfillmentDescription ,
32
- buyer_fulfillment_description : orderData . buyerFulfillmentDescription ,
37
+ buyer_fulfillment_description : orderData . buyerFulfillmentDescription ,
33
38
} ) ;
34
39
const newOrder = await order . save ( { session } ) ;
35
-
36
- if ( ! newOrder ) {
37
- logger . error ( 'Failed to create order; save returned null' ) ;
38
- throw new Error ( 'Failed to create order' ) ;
39
- }
40
- logger . debug ( 'Order created successfully' , { orderId : newOrder . _id } ) ;
40
+ if ( ! newOrder ) throw new Error ( 'Failed to create order' ) ;
41
41
42
42
/* Step 2: Fetch all SellerItem documents associated with the order */
43
43
const sellerItemIds = orderItems . map ( ( item ) => item . itemId ) ;
44
- const sellerItems = await SellerItem . find ( { _id : { $in : sellerItemIds } } ) . lean ( ) ;
44
+ const sellerItems = await SellerItem . find ( { _id : { $in : sellerItemIds } } ) . session ( session ) ;
45
+ const sellerItemMap = new Map ( sellerItems . map ( ( doc ) => [ doc . _id . toString ( ) , doc ] ) ) ;
45
46
46
- // Build a lookup map for seller items
47
- const sellerItemLookup = sellerItems . reduce ( ( acc , sellerItem ) => {
48
- acc [ sellerItem . _id . toString ( ) ] = sellerItem ;
49
- return acc ;
50
- } , { } as Record < string , any > ) ;
47
+ const bulkOrderItems = [ ] ;
48
+ const bulkSellerItemUpdates = [ ] ;
51
49
52
50
/* Step 3: Build OrderItem documents for bulk insertion */
53
- const bulkOrderItems = orderItems . map ( ( item ) => {
54
- const sellerItem = sellerItemLookup [ item . itemId ] ;
51
+ for ( const item of orderItems ) {
52
+ const sellerItem = sellerItemMap . get ( item . itemId ) ;
55
53
if ( ! sellerItem ) {
56
- logger . error ( `Failed to find seller item for ID: ${ item . itemId } ` ) ;
54
+ logger . error ( `Seller item not found for ID: ${ item . itemId } ` ) ;
57
55
throw new Error ( 'Failed to find associated seller item' ) ;
58
56
}
59
57
60
- return {
58
+ // Validate and get new stock level
59
+ const newStockLevel = getUpdatedStockLevel ( sellerItem . stock_level , item . quantity , item . itemId ) ;
60
+ if ( newStockLevel !== null ) {
61
+ bulkSellerItemUpdates . push ( {
62
+ updateOne : {
63
+ filter : { _id : sellerItem . _id } ,
64
+ update : { $set : { stock_level : newStockLevel } } ,
65
+ } ,
66
+ } ) ;
67
+ }
68
+
69
+ const subtotal = item . quantity * parseFloat ( sellerItem . price . toString ( ) ) ;
70
+ bulkOrderItems . push ( {
61
71
order_id : newOrder . _id ,
62
- seller_item_id : sellerItem . _id , // Store only the ObjectId
72
+ seller_item_id : sellerItem . _id ,
63
73
quantity : item . quantity ,
64
- subtotal : item . quantity * parseFloat ( sellerItem . price . toString ( ) ) ,
74
+ subtotal,
65
75
status : OrderItemStatusType . Pending ,
66
- } ;
67
- } ) ;
76
+ } ) ;
77
+ }
68
78
69
79
/* Step 4: Insert order items in bulk */
70
80
await OrderItem . insertMany ( bulkOrderItems , { session } ) ;
71
- logger . debug ( 'Order items inserted successfully' , { count : bulkOrderItems . length } ) ;
81
+
82
+ if ( bulkSellerItemUpdates . length > 0 ) {
83
+ await SellerItem . bulkWrite ( bulkSellerItemUpdates , { session } ) ;
84
+ }
72
85
73
86
/* Step 5: Commit the transaction */
74
87
await session . commitTransaction ( ) ;
88
+ logger . info ( 'Order and stock levels created/updated successfully' , { orderId : newOrder . _id } ) ;
75
89
76
- logger . info ( 'Order and associated items created successfully' , { orderId : newOrder . _id } ) ;
77
90
return newOrder ;
78
91
} catch ( error : any ) {
79
- /* Step 6: Roll back transaction on failure */
80
92
await session . abortTransaction ( ) ;
81
93
82
- logger . error ( `Failed to create order: ${ error } ` ) ;
94
+ if ( error instanceof StockValidationError ) {
95
+ logger . warn ( `Stock validation failed: ${ error . message } ` , { itemId : error . itemId } ) ;
96
+ } else {
97
+ logger . error ( `Failed to create order and update stock: ${ error } ` ) ;
98
+ }
99
+
83
100
throw error ;
84
101
} finally {
85
102
session . endSession ( ) ;
@@ -266,28 +283,73 @@ export const updateOrderItemStatus = async (
266
283
logger . error ( `Failed to update order item status for orderItemID ${ itemId } : ${ error } ` ) ;
267
284
throw error ;
268
285
}
269
- } ;
286
+ }
270
287
271
288
export const cancelOrder = async ( paymentId : string ) => {
289
+ const session = await mongoose . startSession ( ) ;
290
+
272
291
try {
292
+ session . startTransaction ( ) ;
293
+
294
+ /* Step 1: Cancel the order */
273
295
const cancelledOrder = await Order . findOneAndUpdate (
274
- { payment_id : paymentId } ,
275
- {
296
+ { payment_id : paymentId } ,
297
+ {
276
298
is_paid : false ,
277
- status : OrderStatusType . Cancelled
299
+ status : OrderStatusType . Cancelled ,
278
300
} ,
279
- { new : true }
280
- ) . exec ( ) ;
301
+ { new : true , session }
302
+ ) ;
281
303
282
304
if ( ! cancelledOrder ) {
283
- logger . error ( `Failed to cancel order for paymentID ${ paymentId } ` ) ;
284
- throw new Error ( "Failed to cancel order" ) ;
285
- }
305
+ logger . error ( `Failed to cancel order for paymentID ${ paymentId } ` ) ;
306
+ throw new Error ( 'Failed to cancel order' ) ;
307
+ }
308
+
309
+ /* Step 2: Get order items */
310
+ const orderItems = await OrderItem . find ( {
311
+ order_id : cancelledOrder . _id ,
312
+ status : OrderItemStatusType . Pending ,
313
+ } ) . session ( session ) ;
314
+
315
+ if ( orderItems . length === 0 ) {
316
+ logger . warn ( `No pending order items found for order ${ cancelledOrder . _id } ` ) ;
317
+ }
286
318
287
- logger . info ( `Order with paymentID ${ paymentId } successfully cancelled.` ) ;
319
+ /* Step 3: Get related seller items */
320
+ const sellerItemIds = orderItems . map ( ( item ) => item . seller_item_id ) ;
321
+ const sellerItems = await SellerItem . find ( { _id : { $in : sellerItemIds } } ) . session ( session ) ;
322
+ const sellerItemMap = new Map ( sellerItems . map ( ( item ) => [ item . _id . toString ( ) , item ] ) ) ;
323
+
324
+ const bulkSellerItemUpdates = [ ] ;
325
+
326
+ for ( const item of orderItems ) {
327
+ const sellerItem = sellerItemMap . get ( item . seller_item_id . toString ( ) ) ;
328
+ if ( ! sellerItem ) continue ;
329
+
330
+ const restoredLevel = getRollbackStockLevel ( sellerItem . stock_level , item . quantity ) ;
331
+ if ( restoredLevel !== null ) {
332
+ bulkSellerItemUpdates . push ( {
333
+ updateOne : {
334
+ filter : { _id : sellerItem . _id } ,
335
+ update : { $set : { stock_level : restoredLevel } } ,
336
+ } ,
337
+ } ) ;
338
+ }
339
+ }
340
+
341
+ if ( bulkSellerItemUpdates . length > 0 ) {
342
+ await SellerItem . bulkWrite ( bulkSellerItemUpdates , { session } ) ;
343
+ }
344
+
345
+ await session . commitTransaction ( ) ;
346
+ logger . info ( `Order with paymentID ${ paymentId } successfully cancelled and stock levels restored.` ) ;
288
347
return cancelledOrder ;
289
- } catch ( error :any ) {
290
- logger . error ( `Failed to cancel order for paymentID ${ paymentId } : ${ error } ` ) ;
348
+ } catch ( error : any ) {
349
+ await session . abortTransaction ( ) ;
350
+ logger . error ( `Failed to cancel order for paymentID ${ paymentId } : ${ error . message } ` ) ;
291
351
throw error ;
352
+ } finally {
353
+ session . endSession ( ) ;
292
354
}
293
355
} ;
0 commit comments