@unchainedshop/core-orders 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/.npm/package/README +7 -0
  2. package/.npm/package/npm-shrinkwrap.json +190 -0
  3. package/.versions +54 -0
  4. package/README.md +5 -0
  5. package/lib/db/OrderDeliveriesCollection.d.ts +3 -0
  6. package/lib/db/OrderDeliveriesCollection.js +8 -0
  7. package/lib/db/OrderDeliveriesCollection.js.map +1 -0
  8. package/lib/db/OrderDeliveriesSchema.d.ts +1 -0
  9. package/lib/db/OrderDeliveriesSchema.js +20 -0
  10. package/lib/db/OrderDeliveriesSchema.js.map +1 -0
  11. package/lib/db/OrderDiscountTrigger.d.ts +4 -0
  12. package/lib/db/OrderDiscountTrigger.js +6 -0
  13. package/lib/db/OrderDiscountTrigger.js.map +1 -0
  14. package/lib/db/OrderDiscountsCollection.d.ts +3 -0
  15. package/lib/db/OrderDiscountsCollection.js +11 -0
  16. package/lib/db/OrderDiscountsCollection.js.map +1 -0
  17. package/lib/db/OrderDiscountsSchema.d.ts +1 -0
  18. package/lib/db/OrderDiscountsSchema.js +12 -0
  19. package/lib/db/OrderDiscountsSchema.js.map +1 -0
  20. package/lib/db/OrderPaymentsCollection.d.ts +3 -0
  21. package/lib/db/OrderPaymentsCollection.js +8 -0
  22. package/lib/db/OrderPaymentsCollection.js.map +1 -0
  23. package/lib/db/OrderPaymentsSchema.d.ts +1 -0
  24. package/lib/db/OrderPaymentsSchema.js +20 -0
  25. package/lib/db/OrderPaymentsSchema.js.map +1 -0
  26. package/lib/db/OrderPositionsCollection.d.ts +3 -0
  27. package/lib/db/OrderPositionsCollection.js +11 -0
  28. package/lib/db/OrderPositionsCollection.js.map +1 -0
  29. package/lib/db/OrderPositionsSchema.d.ts +1 -0
  30. package/lib/db/OrderPositionsSchema.js +37 -0
  31. package/lib/db/OrderPositionsSchema.js.map +1 -0
  32. package/lib/db/OrdersCollection.d.ts +3 -0
  33. package/lib/db/OrdersCollection.js +33 -0
  34. package/lib/db/OrdersCollection.js.map +1 -0
  35. package/lib/db/OrdersSchema.d.ts +1 -0
  36. package/lib/db/OrdersSchema.js +30 -0
  37. package/lib/db/OrdersSchema.js.map +1 -0
  38. package/lib/director/OrderDiscountAdapter.d.ts +2 -0
  39. package/lib/director/OrderDiscountAdapter.js +3 -0
  40. package/lib/director/OrderDiscountAdapter.js.map +1 -0
  41. package/lib/director/OrderDiscountDirector.d.ts +2 -0
  42. package/lib/director/OrderDiscountDirector.js +3 -0
  43. package/lib/director/OrderDiscountDirector.js.map +1 -0
  44. package/lib/director/OrderPricingAdapter.d.ts +3 -0
  45. package/lib/director/OrderPricingAdapter.js +25 -0
  46. package/lib/director/OrderPricingAdapter.js.map +1 -0
  47. package/lib/director/OrderPricingDirector.d.ts +2 -0
  48. package/lib/director/OrderPricingDirector.js +48 -0
  49. package/lib/director/OrderPricingDirector.js.map +1 -0
  50. package/lib/director/OrderPricingSheet.d.ts +3 -0
  51. package/lib/director/OrderPricingSheet.js +113 -0
  52. package/lib/director/OrderPricingSheet.js.map +1 -0
  53. package/lib/module/configureOrderDeliveriesModule.d.ts +7 -0
  54. package/lib/module/configureOrderDeliveriesModule.js +164 -0
  55. package/lib/module/configureOrderDeliveriesModule.js.map +1 -0
  56. package/lib/module/configureOrderDiscountsModule.d.ts +7 -0
  57. package/lib/module/configureOrderDiscountsModule.js +183 -0
  58. package/lib/module/configureOrderDiscountsModule.js.map +1 -0
  59. package/lib/module/configureOrderPaymentsModule.d.ts +7 -0
  60. package/lib/module/configureOrderPaymentsModule.js +229 -0
  61. package/lib/module/configureOrderPaymentsModule.js.map +1 -0
  62. package/lib/module/configureOrderPositionsModule.d.ts +7 -0
  63. package/lib/module/configureOrderPositionsModule.js +213 -0
  64. package/lib/module/configureOrderPositionsModule.js.map +1 -0
  65. package/lib/module/configureOrdersModule-mutations.d.ts +12 -0
  66. package/lib/module/configureOrdersModule-mutations.js +162 -0
  67. package/lib/module/configureOrdersModule-mutations.js.map +1 -0
  68. package/lib/module/configureOrdersModule-processing.d.ts +13 -0
  69. package/lib/module/configureOrdersModule-processing.js +343 -0
  70. package/lib/module/configureOrdersModule-processing.js.map +1 -0
  71. package/lib/module/configureOrdersModule-queries.d.ts +5 -0
  72. package/lib/module/configureOrdersModule-queries.js +55 -0
  73. package/lib/module/configureOrdersModule-queries.js.map +1 -0
  74. package/lib/module/configureOrdersModule-transformations.d.ts +5 -0
  75. package/lib/module/configureOrdersModule-transformations.js +108 -0
  76. package/lib/module/configureOrdersModule-transformations.js.map +1 -0
  77. package/lib/module/configureOrdersModule.d.ts +3 -0
  78. package/lib/module/configureOrdersModule.js +239 -0
  79. package/lib/module/configureOrdersModule.js.map +1 -0
  80. package/lib/orders-index.d.ts +11 -0
  81. package/lib/orders-index.js +12 -0
  82. package/lib/orders-index.js.map +1 -0
  83. package/lib/orders-settings.d.ts +20 -0
  84. package/lib/orders-settings.js +17 -0
  85. package/lib/orders-settings.js.map +1 -0
  86. package/lib/service/createUserCartService.d.ts +2 -0
  87. package/lib/service/createUserCartService.js +23 -0
  88. package/lib/service/createUserCartService.js.map +1 -0
  89. package/lib/service/migrateOrderCartService.d.ts +2 -0
  90. package/lib/service/migrateOrderCartService.js +10 -0
  91. package/lib/service/migrateOrderCartService.js.map +1 -0
  92. package/lib/service/orderServices.d.ts +2 -0
  93. package/lib/service/orderServices.js +7 -0
  94. package/lib/service/orderServices.js.map +1 -0
  95. package/package.js +33 -0
  96. package/package.json +43 -0
  97. package/plugins/discount-100-off.ts +44 -0
  98. package/plugins/discount-half-price-manual.ts +43 -0
  99. package/plugins/discount-half-price.ts +46 -0
  100. package/plugins/order-delivery.ts +45 -0
  101. package/plugins/order-discount.ts +158 -0
  102. package/plugins/order-items-discount.ts +116 -0
  103. package/plugins/order-items.ts +61 -0
  104. package/plugins/order-payment.ts +46 -0
  105. package/src/db/OrderDeliveriesCollection.ts +12 -0
  106. package/src/db/OrderDeliveriesSchema.js +24 -0
  107. package/src/db/OrderDiscountTrigger.ts +4 -0
  108. package/src/db/OrderDiscountsCollection.ts +15 -0
  109. package/src/db/OrderDiscountsSchema.js +15 -0
  110. package/src/db/OrderPaymentsCollection.ts +12 -0
  111. package/src/db/OrderPaymentsSchema.js +24 -0
  112. package/src/db/OrderPositionsCollection.ts +15 -0
  113. package/src/db/OrderPositionsSchema.js +41 -0
  114. package/src/db/OrdersCollection.ts +37 -0
  115. package/src/db/OrdersSchema.js +34 -0
  116. package/src/director/OrderDiscountAdapter.ts +5 -0
  117. package/src/director/OrderDiscountDirector.ts +4 -0
  118. package/src/director/OrderPricingAdapter.ts +41 -0
  119. package/src/director/OrderPricingDirector.ts +68 -0
  120. package/src/director/OrderPricingSheet.ts +138 -0
  121. package/src/module/configureOrderDeliveriesModule.ts +240 -0
  122. package/src/module/configureOrderDiscountsModule.ts +245 -0
  123. package/src/module/configureOrderPaymentsModule.ts +331 -0
  124. package/src/module/configureOrderPositionsModule.ts +329 -0
  125. package/src/module/configureOrdersModule-mutations.ts +230 -0
  126. package/src/module/configureOrdersModule-processing.ts +506 -0
  127. package/src/module/configureOrdersModule-queries.ts +71 -0
  128. package/src/module/configureOrdersModule-transformations.ts +145 -0
  129. package/src/module/configureOrdersModule.ts +359 -0
  130. package/src/orders-index.ts +15 -0
  131. package/src/orders-settings.ts +23 -0
  132. package/src/service/createUserCartService.ts +38 -0
  133. package/src/service/migrateOrderCartService.ts +22 -0
  134. package/src/service/orderServices.ts +8 -0
  135. package/tests/orders-index.test.ts +50 -0
  136. package/tsconfig.build.json +26 -0
  137. package/tsconfig.json +8 -0
@@ -0,0 +1,506 @@
1
+ import { Context } from '@unchainedshop/types/api';
2
+ import { Collection } from '@unchainedshop/types/common';
3
+ import { Order, OrderStatus, OrderProcessing, OrdersModule } from '@unchainedshop/types/orders';
4
+ import { OrderDelivery } from '@unchainedshop/types/orders.deliveries';
5
+ import { OrderPayment } from '@unchainedshop/types/orders.payments';
6
+ import { OrderPosition } from '@unchainedshop/types/orders.positions';
7
+ import { emit, registerEvents } from '@unchainedshop/events';
8
+ import { log } from '@unchainedshop/logger';
9
+ import { generateDbFilterById } from '@unchainedshop/utils';
10
+ import { ordersSettings } from '../orders-settings';
11
+
12
+ const ORDER_PROCESSING_EVENTS: string[] = ['ORDER_CHECKOUT', 'ORDER_CONFIRMED', 'ORDER_FULLFILLED'];
13
+
14
+ export const configureOrderModuleProcessing = ({
15
+ Orders,
16
+ OrderPositions,
17
+ OrderDeliveries,
18
+ OrderPayments,
19
+ updateCalculation,
20
+ updateStatus,
21
+ }: {
22
+ Orders: Collection<Order>;
23
+ OrderPositions: Collection<OrderPosition>;
24
+ OrderDeliveries: Collection<OrderDelivery>;
25
+ OrderPayments: Collection<OrderPayment>;
26
+ updateCalculation: OrdersModule['updateCalculation'];
27
+ updateStatus: OrdersModule['updateStatus'];
28
+ }): OrderProcessing => {
29
+ registerEvents(ORDER_PROCESSING_EVENTS);
30
+
31
+ const findOrderPositions = async (order: Order) =>
32
+ OrderPositions.find({
33
+ orderId: order._id,
34
+ quantity: { $gt: 0 },
35
+ }).toArray();
36
+
37
+ const findOrderDelivery = async (order: Order) =>
38
+ OrderDeliveries.findOne(generateDbFilterById(order.deliveryId), {});
39
+
40
+ const findOrderPayment = async (order: Order) =>
41
+ OrderPayments.findOne(generateDbFilterById(order.paymentId), {});
42
+
43
+ const missingInputDataForCheckout = async (order: Order) => {
44
+ const errors = [];
45
+ if (!order.contact) errors.push(new Error('Contact data not provided'));
46
+ if (!order.billingAddress) errors.push(new Error('Billing address not provided'));
47
+ if (!(await findOrderDelivery(order))) errors.push('No delivery provider selected');
48
+ if (!(await findOrderPayment(order))) errors.push('No payment provider selected');
49
+ return errors;
50
+ };
51
+
52
+ const itemValidationErrors = async (order: Order, { modules }: Context) => {
53
+ // Check if items are valid
54
+ const orderPositions = await findOrderPositions(order);
55
+ if (orderPositions.length === 0) {
56
+ const NoItemsError = new Error('No items to checkout');
57
+ NoItemsError.name = 'NoItemsError';
58
+ return [NoItemsError];
59
+ }
60
+ const validationErrors = await Promise.all(
61
+ orderPositions.map(async (orderPosition) => {
62
+ const errors = [];
63
+
64
+ log(`OrderPosition ${orderPosition._id} -> Validate ${orderPosition.quantity}`, {
65
+ orderId: orderPosition.orderId,
66
+ });
67
+
68
+ const product = await modules.products.findProduct({
69
+ productId: orderPosition.productId,
70
+ });
71
+
72
+ if (!modules.products.isActive(product)) {
73
+ errors.push(new Error('This product is not available anymore'));
74
+ }
75
+
76
+ const quotation =
77
+ orderPosition.quotationId &&
78
+ (await modules.quotations.findQuotation({
79
+ quotationId: orderPosition.quotationId,
80
+ }));
81
+ if (quotation && !modules.quotations.isProposalValid(quotation)) {
82
+ errors.push(new Error('Quotation expired or fullfiled, please request a new offer'));
83
+ }
84
+ return errors;
85
+ }),
86
+ );
87
+
88
+ return validationErrors.flatMap((f) => f);
89
+ };
90
+
91
+ const isAutoConfirmationEnabled = async (order: Order, requestContext: Context) => {
92
+ const { modules } = requestContext;
93
+
94
+ const orderPayment = await findOrderPayment(order);
95
+ let isBlockingOrderConfirmation =
96
+ orderPayment &&
97
+ (await modules.orders.payments.isBlockingOrderConfirmation(orderPayment, requestContext));
98
+
99
+ if (isBlockingOrderConfirmation) return false;
100
+
101
+ const orderDelivery = await findOrderDelivery(order);
102
+ isBlockingOrderConfirmation =
103
+ orderDelivery &&
104
+ (await modules.orders.deliveries.isBlockingOrderConfirmation(orderDelivery, requestContext));
105
+
106
+ if (isBlockingOrderConfirmation) return false;
107
+
108
+ if (order.status === OrderStatus.FULLFILLED || order.status === OrderStatus.CONFIRMED) {
109
+ return false;
110
+ }
111
+
112
+ return true;
113
+ };
114
+
115
+ const isAutoFullfillmentEnabled = async (order: Order, requestContext: Context) => {
116
+ const { modules } = requestContext;
117
+
118
+ const orderPayment = await findOrderPayment(order);
119
+ let isBlockingOrderFullfillment =
120
+ orderPayment && modules.orders.payments.isBlockingOrderFullfillment(orderPayment);
121
+
122
+ if (isBlockingOrderFullfillment) return false;
123
+
124
+ const orderDelivery = await findOrderDelivery(order);
125
+ isBlockingOrderFullfillment =
126
+ orderDelivery && modules.orders.deliveries.isBlockingOrderFullfillment(orderDelivery);
127
+
128
+ if (isBlockingOrderFullfillment) return false;
129
+
130
+ if (order.status === OrderStatus.FULLFILLED) {
131
+ return false;
132
+ }
133
+
134
+ return true;
135
+ };
136
+
137
+ const findNextStatus = async (
138
+ status: OrderStatus | null,
139
+ order: Order,
140
+ requestContext: Context,
141
+ ): Promise<OrderStatus> => {
142
+ if (status === null) {
143
+ if ((await missingInputDataForCheckout(order)).length === 0) {
144
+ emit('ORDER_CHECKOUT', { order });
145
+ return OrderStatus.PENDING;
146
+ }
147
+ }
148
+
149
+ if (status === OrderStatus.PENDING) {
150
+ if (await isAutoConfirmationEnabled(order, requestContext)) {
151
+ emit('ORDER_CONFIRMED', { order });
152
+ return OrderStatus.CONFIRMED;
153
+ }
154
+ }
155
+
156
+ if (status === OrderStatus.CONFIRMED) {
157
+ const isFullfilled = await isAutoFullfillmentEnabled(order, requestContext);
158
+ if (isFullfilled) {
159
+ emit('ORDER_FULLFILLED', { order });
160
+ return OrderStatus.FULLFILLED;
161
+ }
162
+ }
163
+
164
+ return status;
165
+ };
166
+
167
+ return {
168
+ checkout: async (orderId, { orderContext, paymentContext, deliveryContext }, requestContext) => {
169
+ const { modules, localeContext, userId } = requestContext;
170
+
171
+ await modules.orders.updateContext(orderId, orderContext, requestContext);
172
+ let order = await modules.orders.findOrder({ orderId });
173
+
174
+ if (order.status !== null) return order;
175
+
176
+ const errors = [
177
+ ...(await missingInputDataForCheckout(order)),
178
+ ...(await itemValidationErrors(order, requestContext)),
179
+ ].filter(Boolean);
180
+
181
+ if (errors.length > 0) {
182
+ throw new Error(errors[0]);
183
+ }
184
+
185
+ // Process order
186
+ order = await modules.orders.processOrder(
187
+ order,
188
+ {
189
+ paymentContext,
190
+ deliveryContext,
191
+ },
192
+ requestContext,
193
+ );
194
+
195
+ // After checkout, store last checkout information on user
196
+ await modules.users.updateLastBillingAddress(order.userId, order.billingAddress, userId);
197
+ await modules.users.updateLastContact(order.userId, order.contact, userId);
198
+
199
+ // Then ensure new cart is created before we return from checkout
200
+ const user = await modules.users.findUserById(order.userId);
201
+ const locale = modules.users.userLocale(user, {
202
+ localeContext,
203
+ });
204
+ await modules.orders.ensureCartForUser(
205
+ {
206
+ user,
207
+ countryCode: locale.country,
208
+ },
209
+ requestContext,
210
+ );
211
+
212
+ return order;
213
+ },
214
+
215
+ confirm: async (orderId, { orderContext, paymentContext, deliveryContext }, requestContext) => {
216
+ const { modules } = requestContext;
217
+
218
+ await modules.orders.updateContext(orderId, orderContext, requestContext);
219
+ const order = await modules.orders.findOrder({ orderId });
220
+
221
+ if (order.status !== OrderStatus.PENDING) return order;
222
+
223
+ return modules.orders.processOrder(
224
+ order,
225
+ {
226
+ paymentContext,
227
+ deliveryContext,
228
+ nextStatus: OrderStatus.CONFIRMED,
229
+ },
230
+ requestContext,
231
+ );
232
+ },
233
+
234
+ reject: async (orderId, { orderContext, paymentContext, deliveryContext }, requestContext) => {
235
+ const { modules } = requestContext;
236
+
237
+ await modules.orders.updateContext(orderId, orderContext, requestContext);
238
+ const order = await modules.orders.findOrder({ orderId });
239
+
240
+ if (order.status !== OrderStatus.PENDING) return order;
241
+
242
+ return modules.orders.processOrder(
243
+ order,
244
+ {
245
+ paymentContext,
246
+ deliveryContext,
247
+ nextStatus: OrderStatus.REJECTED,
248
+ },
249
+ requestContext,
250
+ );
251
+ },
252
+
253
+ ensureCartForUser: async ({ user, countryCode }, requestContext) => {
254
+ const { modules, services } = requestContext;
255
+
256
+ if (!ordersSettings.ensureUserHasCart) return null;
257
+
258
+ const cart = await modules.orders.cart({ countryContext: countryCode }, user);
259
+ if (cart) return cart;
260
+
261
+ return services.orders.createUserCart(
262
+ {
263
+ user,
264
+ countryCode,
265
+ },
266
+ requestContext,
267
+ );
268
+ },
269
+
270
+ migrateCart: async ({ fromCart, shouldMerge, toCart }, requestContext) => {
271
+ const fromCartId = fromCart._id;
272
+
273
+ if (!toCart || !shouldMerge) {
274
+ // No destination cart, move whole cart
275
+ await Orders.updateOne(generateDbFilterById(fromCart._id), {
276
+ $set: {
277
+ userId: requestContext.userId,
278
+ },
279
+ });
280
+ return updateCalculation(fromCartId, requestContext);
281
+ }
282
+
283
+ // Move positions
284
+ const toCartId = toCart._id;
285
+ await OrderPositions.updateMany(
286
+ { orderId: fromCartId },
287
+ {
288
+ $set: {
289
+ orderId: toCartId,
290
+ },
291
+ },
292
+ );
293
+
294
+ await updateCalculation(fromCartId, requestContext);
295
+ return updateCalculation(toCartId, requestContext);
296
+ },
297
+
298
+ processOrder: async (initialOrder, params, requestContext) => {
299
+ const { modules } = requestContext;
300
+ const { paymentContext, deliveryContext, nextStatus: forceNextStatus } = params;
301
+
302
+ const orderId = initialOrder._id;
303
+ let order = initialOrder;
304
+ let nextStatus =
305
+ forceNextStatus || (await findNextStatus(initialOrder.status, order, requestContext));
306
+
307
+ if (nextStatus === OrderStatus.PENDING) {
308
+ // auto charge during transition to pending
309
+ const orderPayment = await modules.orders.payments.findOrderPayment({
310
+ orderPaymentId: order.paymentId,
311
+ });
312
+
313
+ await modules.orders.payments.charge(
314
+ orderPayment,
315
+ { transactionContext: paymentContext },
316
+ requestContext,
317
+ );
318
+ nextStatus = await findNextStatus(nextStatus, order, requestContext);
319
+ }
320
+
321
+ if (nextStatus === OrderStatus.REJECTED) {
322
+ // auto cancel during transition to rejected
323
+ const orderPayment = await modules.orders.payments.findOrderPayment({
324
+ orderPaymentId: order.paymentId,
325
+ });
326
+ await modules.orders.payments.cancel(
327
+ orderPayment,
328
+ { transactionContext: paymentContext },
329
+ requestContext,
330
+ );
331
+ nextStatus = await findNextStatus(nextStatus, order, requestContext);
332
+ }
333
+
334
+ if (nextStatus === OrderStatus.CONFIRMED) {
335
+ // confirm pre-authorized payments
336
+ const orderPayment = await modules.orders.payments.findOrderPayment({
337
+ orderPaymentId: order.paymentId,
338
+ });
339
+ await modules.orders.payments.confirm(
340
+ orderPayment,
341
+ { transactionContext: paymentContext },
342
+ requestContext,
343
+ );
344
+
345
+ const orderDelivery = await modules.orders.deliveries.findDelivery({
346
+ orderDeliveryId: order.deliveryId,
347
+ });
348
+ if (order.status !== OrderStatus.CONFIRMED) {
349
+ // we have to stop here shortly to complete the confirmation
350
+ // before auto delivery is started, else we have no chance to create
351
+ // documents and numbers that are needed for delivery
352
+ order = await updateStatus(
353
+ orderId,
354
+ {
355
+ status: OrderStatus.CONFIRMED,
356
+ info: 'before delivery',
357
+ },
358
+ requestContext,
359
+ );
360
+
361
+ await modules.orders.deliveries.send(
362
+ orderDelivery,
363
+ {
364
+ order,
365
+ deliveryContext,
366
+ },
367
+ requestContext,
368
+ );
369
+
370
+ // Generate enrollments
371
+ if (!order.originEnrollmentId) {
372
+ const orderPositions = await modules.orders.positions.findOrderPositions({ orderId });
373
+
374
+ const mappedProductOrderPositions = await Promise.all(
375
+ orderPositions.map(async (orderPosition) => {
376
+ const product = await modules.products.findProduct({
377
+ productId: orderPosition.productId,
378
+ });
379
+ return {
380
+ orderPosition,
381
+ product,
382
+ };
383
+ }),
384
+ );
385
+
386
+ const filteredProductOrderPositions = mappedProductOrderPositions.filter(
387
+ (item) => item.product?.plan,
388
+ );
389
+
390
+ if (filteredProductOrderPositions.length > 0) {
391
+ await modules.enrollments.createFromCheckout(
392
+ order,
393
+ {
394
+ items: filteredProductOrderPositions,
395
+ context: {
396
+ paymentContext,
397
+ deliveryContext,
398
+ },
399
+ },
400
+ requestContext,
401
+ );
402
+ }
403
+ }
404
+ } else {
405
+ await modules.orders.deliveries.send(
406
+ orderDelivery,
407
+ {
408
+ order,
409
+ deliveryContext,
410
+ },
411
+ requestContext,
412
+ );
413
+ }
414
+
415
+ // Reserve items
416
+ // If we came here, the checkout succeeded, so we can reserve the items
417
+ const orderPositions = await findOrderPositions(order);
418
+ await Promise.all(
419
+ orderPositions.map(async (orderPosition) => {
420
+ if (orderPosition.quotationId) {
421
+ await modules.quotations.fullfillQuotation(
422
+ orderPosition.quotationId,
423
+ {
424
+ orderId,
425
+ orderPositionId: orderPosition._id,
426
+ },
427
+ requestContext,
428
+ );
429
+ }
430
+
431
+ log(`OrderPosition ${orderPosition._id} -> Reserve ${orderPosition.quantity}`, {
432
+ orderId,
433
+ });
434
+ }),
435
+ );
436
+
437
+ nextStatus = await findNextStatus(nextStatus, order, requestContext);
438
+
439
+ // TODO: we will use this function to keep a "Ordered in Flight" amount, allowing us to
440
+ // do live stock stuff
441
+ // 2. Reserve quantity at Warehousing Provider until order is CANCELLED/FULLFILLED
442
+ // ???
443
+ }
444
+
445
+ order = await updateStatus(
446
+ order._id,
447
+ { status: nextStatus, info: 'order processed' },
448
+ requestContext,
449
+ );
450
+
451
+ if (initialOrder.status !== order.status) {
452
+ if (order.status === OrderStatus.REJECTED) {
453
+ await modules.orders.sendOrderRejectionToCustomer(order, params, requestContext);
454
+ } else {
455
+ await modules.orders.sendOrderConfirmationToCustomer(order, params, requestContext);
456
+ }
457
+ }
458
+
459
+ return order;
460
+ },
461
+
462
+ sendOrderConfirmationToCustomer: async (order, params, { modules, localeContext, userId }) => {
463
+ const user = await modules.users.findUserById(order.userId);
464
+ const locale = modules.users.userLocale(user, {
465
+ localeContext,
466
+ });
467
+ await modules.worker.addWork(
468
+ {
469
+ type: 'MESSAGE',
470
+ retries: 0,
471
+ input: {
472
+ ...params,
473
+ locale,
474
+ template: 'ORDER_CONFIRMATION',
475
+ orderId: order._id,
476
+ },
477
+ },
478
+ userId,
479
+ );
480
+
481
+ return order;
482
+ },
483
+
484
+ sendOrderRejectionToCustomer: async (order, params, { modules, localeContext, userId }) => {
485
+ const user = await modules.users.findUserById(order.userId);
486
+ const locale = modules.users.userLocale(user, {
487
+ localeContext,
488
+ });
489
+ await modules.worker.addWork(
490
+ {
491
+ type: 'MESSAGE',
492
+ retries: 0,
493
+ input: {
494
+ ...params,
495
+ locale,
496
+ template: 'ORDER_REJECTION',
497
+ orderId: order._id,
498
+ },
499
+ },
500
+ userId,
501
+ );
502
+
503
+ return order;
504
+ },
505
+ };
506
+ };
@@ -0,0 +1,71 @@
1
+ import { Collection, FindOptions, Query } from '@unchainedshop/types/common';
2
+ import { Order, OrderQueries, OrderQuery } from '@unchainedshop/types/orders';
3
+ import { generateDbFilterById } from '@unchainedshop/utils';
4
+
5
+ const buildFindSelector = ({ includeCarts, status, userId, queryString }: OrderQuery) => {
6
+ const selector: Query = {};
7
+
8
+ if (userId) {
9
+ selector.userId = userId;
10
+ }
11
+
12
+ if (status) {
13
+ selector.status = status;
14
+ } else if (!includeCarts) {
15
+ selector.status = { $ne: null };
16
+ }
17
+
18
+ if (queryString) {
19
+ selector.$text = { $search: queryString };
20
+ }
21
+
22
+ return selector;
23
+ };
24
+
25
+ export const configureOrdersModuleQueries = ({
26
+ Orders,
27
+ }: {
28
+ Orders: Collection<Order>;
29
+ }): OrderQueries => {
30
+ return {
31
+ // Queries
32
+ count: async (query) => {
33
+ const orderCount = await Orders.countDocuments(buildFindSelector(query));
34
+ return orderCount;
35
+ },
36
+
37
+ findOrder: async ({ orderId, orderNumber }, options) => {
38
+ const selector = orderId ? generateDbFilterById(orderId) : { orderNumber };
39
+
40
+ return Orders.findOne(selector, options);
41
+ },
42
+
43
+ findOrders: async ({ limit, offset, queryString, ...query }, options) => {
44
+ const findOptions: FindOptions = {
45
+ skip: offset,
46
+ limit,
47
+ sort: {
48
+ created: -1,
49
+ },
50
+ };
51
+ const selector = buildFindSelector({ queryString, ...query });
52
+
53
+ if (queryString) {
54
+ return Orders.find(selector, {
55
+ ...options,
56
+ projection: { score: { $meta: 'textScore' } },
57
+ sort: { score: { $meta: 'textScore' } },
58
+ }).toArray();
59
+ }
60
+
61
+ return Orders.find(selector, findOptions).toArray();
62
+ },
63
+
64
+ orderExists: async ({ orderId }) => {
65
+ const orderCount = await Orders.countDocuments(generateDbFilterById(orderId), {
66
+ limit: 1,
67
+ });
68
+ return !!orderCount;
69
+ },
70
+ };
71
+ };