@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.
- package/.npm/package/README +7 -0
- package/.npm/package/npm-shrinkwrap.json +190 -0
- package/.versions +54 -0
- package/README.md +5 -0
- package/lib/db/OrderDeliveriesCollection.d.ts +3 -0
- package/lib/db/OrderDeliveriesCollection.js +8 -0
- package/lib/db/OrderDeliveriesCollection.js.map +1 -0
- package/lib/db/OrderDeliveriesSchema.d.ts +1 -0
- package/lib/db/OrderDeliveriesSchema.js +20 -0
- package/lib/db/OrderDeliveriesSchema.js.map +1 -0
- package/lib/db/OrderDiscountTrigger.d.ts +4 -0
- package/lib/db/OrderDiscountTrigger.js +6 -0
- package/lib/db/OrderDiscountTrigger.js.map +1 -0
- package/lib/db/OrderDiscountsCollection.d.ts +3 -0
- package/lib/db/OrderDiscountsCollection.js +11 -0
- package/lib/db/OrderDiscountsCollection.js.map +1 -0
- package/lib/db/OrderDiscountsSchema.d.ts +1 -0
- package/lib/db/OrderDiscountsSchema.js +12 -0
- package/lib/db/OrderDiscountsSchema.js.map +1 -0
- package/lib/db/OrderPaymentsCollection.d.ts +3 -0
- package/lib/db/OrderPaymentsCollection.js +8 -0
- package/lib/db/OrderPaymentsCollection.js.map +1 -0
- package/lib/db/OrderPaymentsSchema.d.ts +1 -0
- package/lib/db/OrderPaymentsSchema.js +20 -0
- package/lib/db/OrderPaymentsSchema.js.map +1 -0
- package/lib/db/OrderPositionsCollection.d.ts +3 -0
- package/lib/db/OrderPositionsCollection.js +11 -0
- package/lib/db/OrderPositionsCollection.js.map +1 -0
- package/lib/db/OrderPositionsSchema.d.ts +1 -0
- package/lib/db/OrderPositionsSchema.js +37 -0
- package/lib/db/OrderPositionsSchema.js.map +1 -0
- package/lib/db/OrdersCollection.d.ts +3 -0
- package/lib/db/OrdersCollection.js +33 -0
- package/lib/db/OrdersCollection.js.map +1 -0
- package/lib/db/OrdersSchema.d.ts +1 -0
- package/lib/db/OrdersSchema.js +30 -0
- package/lib/db/OrdersSchema.js.map +1 -0
- package/lib/director/OrderDiscountAdapter.d.ts +2 -0
- package/lib/director/OrderDiscountAdapter.js +3 -0
- package/lib/director/OrderDiscountAdapter.js.map +1 -0
- package/lib/director/OrderDiscountDirector.d.ts +2 -0
- package/lib/director/OrderDiscountDirector.js +3 -0
- package/lib/director/OrderDiscountDirector.js.map +1 -0
- package/lib/director/OrderPricingAdapter.d.ts +3 -0
- package/lib/director/OrderPricingAdapter.js +25 -0
- package/lib/director/OrderPricingAdapter.js.map +1 -0
- package/lib/director/OrderPricingDirector.d.ts +2 -0
- package/lib/director/OrderPricingDirector.js +48 -0
- package/lib/director/OrderPricingDirector.js.map +1 -0
- package/lib/director/OrderPricingSheet.d.ts +3 -0
- package/lib/director/OrderPricingSheet.js +113 -0
- package/lib/director/OrderPricingSheet.js.map +1 -0
- package/lib/module/configureOrderDeliveriesModule.d.ts +7 -0
- package/lib/module/configureOrderDeliveriesModule.js +164 -0
- package/lib/module/configureOrderDeliveriesModule.js.map +1 -0
- package/lib/module/configureOrderDiscountsModule.d.ts +7 -0
- package/lib/module/configureOrderDiscountsModule.js +183 -0
- package/lib/module/configureOrderDiscountsModule.js.map +1 -0
- package/lib/module/configureOrderPaymentsModule.d.ts +7 -0
- package/lib/module/configureOrderPaymentsModule.js +229 -0
- package/lib/module/configureOrderPaymentsModule.js.map +1 -0
- package/lib/module/configureOrderPositionsModule.d.ts +7 -0
- package/lib/module/configureOrderPositionsModule.js +213 -0
- package/lib/module/configureOrderPositionsModule.js.map +1 -0
- package/lib/module/configureOrdersModule-mutations.d.ts +12 -0
- package/lib/module/configureOrdersModule-mutations.js +162 -0
- package/lib/module/configureOrdersModule-mutations.js.map +1 -0
- package/lib/module/configureOrdersModule-processing.d.ts +13 -0
- package/lib/module/configureOrdersModule-processing.js +343 -0
- package/lib/module/configureOrdersModule-processing.js.map +1 -0
- package/lib/module/configureOrdersModule-queries.d.ts +5 -0
- package/lib/module/configureOrdersModule-queries.js +55 -0
- package/lib/module/configureOrdersModule-queries.js.map +1 -0
- package/lib/module/configureOrdersModule-transformations.d.ts +5 -0
- package/lib/module/configureOrdersModule-transformations.js +108 -0
- package/lib/module/configureOrdersModule-transformations.js.map +1 -0
- package/lib/module/configureOrdersModule.d.ts +3 -0
- package/lib/module/configureOrdersModule.js +239 -0
- package/lib/module/configureOrdersModule.js.map +1 -0
- package/lib/orders-index.d.ts +11 -0
- package/lib/orders-index.js +12 -0
- package/lib/orders-index.js.map +1 -0
- package/lib/orders-settings.d.ts +20 -0
- package/lib/orders-settings.js +17 -0
- package/lib/orders-settings.js.map +1 -0
- package/lib/service/createUserCartService.d.ts +2 -0
- package/lib/service/createUserCartService.js +23 -0
- package/lib/service/createUserCartService.js.map +1 -0
- package/lib/service/migrateOrderCartService.d.ts +2 -0
- package/lib/service/migrateOrderCartService.js +10 -0
- package/lib/service/migrateOrderCartService.js.map +1 -0
- package/lib/service/orderServices.d.ts +2 -0
- package/lib/service/orderServices.js +7 -0
- package/lib/service/orderServices.js.map +1 -0
- package/package.js +33 -0
- package/package.json +43 -0
- package/plugins/discount-100-off.ts +44 -0
- package/plugins/discount-half-price-manual.ts +43 -0
- package/plugins/discount-half-price.ts +46 -0
- package/plugins/order-delivery.ts +45 -0
- package/plugins/order-discount.ts +158 -0
- package/plugins/order-items-discount.ts +116 -0
- package/plugins/order-items.ts +61 -0
- package/plugins/order-payment.ts +46 -0
- package/src/db/OrderDeliveriesCollection.ts +12 -0
- package/src/db/OrderDeliveriesSchema.js +24 -0
- package/src/db/OrderDiscountTrigger.ts +4 -0
- package/src/db/OrderDiscountsCollection.ts +15 -0
- package/src/db/OrderDiscountsSchema.js +15 -0
- package/src/db/OrderPaymentsCollection.ts +12 -0
- package/src/db/OrderPaymentsSchema.js +24 -0
- package/src/db/OrderPositionsCollection.ts +15 -0
- package/src/db/OrderPositionsSchema.js +41 -0
- package/src/db/OrdersCollection.ts +37 -0
- package/src/db/OrdersSchema.js +34 -0
- package/src/director/OrderDiscountAdapter.ts +5 -0
- package/src/director/OrderDiscountDirector.ts +4 -0
- package/src/director/OrderPricingAdapter.ts +41 -0
- package/src/director/OrderPricingDirector.ts +68 -0
- package/src/director/OrderPricingSheet.ts +138 -0
- package/src/module/configureOrderDeliveriesModule.ts +240 -0
- package/src/module/configureOrderDiscountsModule.ts +245 -0
- package/src/module/configureOrderPaymentsModule.ts +331 -0
- package/src/module/configureOrderPositionsModule.ts +329 -0
- package/src/module/configureOrdersModule-mutations.ts +230 -0
- package/src/module/configureOrdersModule-processing.ts +506 -0
- package/src/module/configureOrdersModule-queries.ts +71 -0
- package/src/module/configureOrdersModule-transformations.ts +145 -0
- package/src/module/configureOrdersModule.ts +359 -0
- package/src/orders-index.ts +15 -0
- package/src/orders-settings.ts +23 -0
- package/src/service/createUserCartService.ts +38 -0
- package/src/service/migrateOrderCartService.ts +22 -0
- package/src/service/orderServices.ts +8 -0
- package/tests/orders-index.test.ts +50 -0
- package/tsconfig.build.json +26 -0
- 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
|
+
};
|