@unchainedshop/core 5.0.0-alpha.10 → 5.0.0-alpha.11

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 (129) hide show
  1. package/lib/bulk-exporter/handlers/exportUsersHandler.js +1 -0
  2. package/lib/directors/BasePricingSheet.d.ts +2 -0
  3. package/lib/directors/DeliveryPricingSheet.js +9 -4
  4. package/lib/directors/EnrollmentAdapter.d.ts +11 -1
  5. package/lib/directors/EnrollmentAdapter.js +7 -1
  6. package/lib/directors/EnrollmentDirector.d.ts +2 -1
  7. package/lib/directors/EnrollmentDirector.js +6 -4
  8. package/lib/directors/OrderPricingSheet.js +6 -7
  9. package/lib/directors/PaymentPricingSheet.js +9 -4
  10. package/lib/directors/ProductPricingAdapter.d.ts +1 -0
  11. package/lib/directors/ProductPricingDirector.js +1 -0
  12. package/lib/directors/ProductPricingSheet.js +7 -4
  13. package/lib/directors/QuotationAdapter.js +5 -2
  14. package/lib/factory/registerEnrollment.d.ts +8 -1
  15. package/lib/factory/registerEnrollment.js +12 -14
  16. package/lib/factory/registerQuotation.d.ts +2 -1
  17. package/lib/factory/registerQuotation.js +3 -2
  18. package/lib/plugins/PluginRegistry.d.ts +1 -1
  19. package/lib/plugins/PluginRegistry.js +8 -2
  20. package/lib/services/addCartQuotation.d.ts +9 -13
  21. package/lib/services/addCartQuotation.js +43 -38
  22. package/lib/services/calculateDiscountTotal.d.ts +5 -1
  23. package/lib/services/calculateDiscountTotal.js +36 -28
  24. package/lib/services/checkoutOrder.js +4 -0
  25. package/lib/services/index.d.ts +8 -0
  26. package/lib/services/index.js +8 -0
  27. package/lib/services/initializeEnrollment.d.ts +1 -1
  28. package/lib/services/initializeEnrollment.js +11 -0
  29. package/lib/services/migrateOrderCart.js +2 -2
  30. package/lib/services/migrateUserData.js +3 -0
  31. package/lib/services/processEnrollment.js +6 -3
  32. package/lib/services/processOrder.js +4 -4
  33. package/lib/services/reserveOrderQuotationsForCheckout.d.ts +8 -0
  34. package/lib/services/reserveOrderQuotationsForCheckout.js +49 -0
  35. package/lib/services/simulateConfigurablePriceRange.js +1 -1
  36. package/lib/services/simulateDeliveryPricing.js +1 -1
  37. package/lib/services/simulatePaymentPricing.js +1 -1
  38. package/lib/services/suspendEnrollment.d.ts +5 -0
  39. package/lib/services/suspendEnrollment.js +21 -0
  40. package/lib/services/terminateEnrollment.d.ts +6 -2
  41. package/lib/services/terminateEnrollment.js +27 -7
  42. package/lib/services/updateCartItem.d.ts +7 -7
  43. package/lib/services/updateCartItem.js +28 -25
  44. package/lib/services/updateEnrollmentPlan.d.ts +5 -0
  45. package/lib/services/updateEnrollmentPlan.js +26 -0
  46. package/lib/services/validateOrder.js +7 -2
  47. package/package.json +22 -22
  48. package/lib/errors/common.d.ts +0 -568
  49. package/lib/errors/common.js +0 -789
  50. package/lib/errors/index.d.ts +0 -1
  51. package/lib/errors/index.js +0 -1
  52. package/lib/services/addCartDiscount.d.ts +0 -10
  53. package/lib/services/addCartDiscount.js +0 -27
  54. package/lib/services/addCartItem.d.ts +0 -15
  55. package/lib/services/addCartItem.js +0 -41
  56. package/lib/services/addEmail.d.ts +0 -7
  57. package/lib/services/addEmail.js +0 -17
  58. package/lib/services/addProductAssignment.d.ts +0 -3
  59. package/lib/services/addProductAssignment.js +0 -63
  60. package/lib/services/authenticateWithPassword.d.ts +0 -15
  61. package/lib/services/authenticateWithPassword.js +0 -34
  62. package/lib/services/changePassword.d.ts +0 -10
  63. package/lib/services/changePassword.js +0 -19
  64. package/lib/services/countOrdersWithFilters.d.ts +0 -3
  65. package/lib/services/countOrdersWithFilters.js +0 -25
  66. package/lib/services/createAssortment.d.ts +0 -7
  67. package/lib/services/createAssortment.js +0 -10
  68. package/lib/services/createBookmark.d.ts +0 -8
  69. package/lib/services/createBookmark.js +0 -21
  70. package/lib/services/createDeliveryProvider.d.ts +0 -6
  71. package/lib/services/createDeliveryProvider.js +0 -19
  72. package/lib/services/createEnrollment.d.ts +0 -17
  73. package/lib/services/createEnrollment.js +0 -33
  74. package/lib/services/createProduct.d.ts +0 -7
  75. package/lib/services/createProduct.js +0 -8
  76. package/lib/services/createProductBundleItem.d.ts +0 -5
  77. package/lib/services/createProductBundleItem.js +0 -25
  78. package/lib/services/createProductReview.d.ts +0 -8
  79. package/lib/services/createProductReview.js +0 -12
  80. package/lib/services/deleteDeadCarts.d.ts +0 -2
  81. package/lib/services/deleteDeadCarts.js +0 -17
  82. package/lib/services/deliverOrder.d.ts +0 -5
  83. package/lib/services/deliverOrder.js +0 -26
  84. package/lib/services/findActiveWorkTypes.d.ts +0 -2
  85. package/lib/services/findActiveWorkTypes.js +0 -6
  86. package/lib/services/findDeliveryInterfaces.d.ts +0 -7
  87. package/lib/services/findDeliveryInterfaces.js +0 -13
  88. package/lib/services/findOrdersWithFilters.d.ts +0 -9
  89. package/lib/services/findOrdersWithFilters.js +0 -25
  90. package/lib/services/findPaymentInterfaces.d.ts +0 -7
  91. package/lib/services/findPaymentInterfaces.js +0 -13
  92. package/lib/services/findWarehousingInterfaces.d.ts +0 -7
  93. package/lib/services/findWarehousingInterfaces.js +0 -13
  94. package/lib/services/getDeliveryDiscounts.d.ts +0 -8
  95. package/lib/services/getDeliveryDiscounts.js +0 -14
  96. package/lib/services/getPaymentDiscounts.d.ts +0 -8
  97. package/lib/services/getPaymentDiscounts.js +0 -14
  98. package/lib/services/getShopInfo.d.ts +0 -25
  99. package/lib/services/getShopInfo.js +0 -47
  100. package/lib/services/impersonateUser.d.ts +0 -8
  101. package/lib/services/impersonateUser.js +0 -20
  102. package/lib/services/loginAsGuest.d.ts +0 -10
  103. package/lib/services/loginAsGuest.js +0 -19
  104. package/lib/services/obfuscateWorkerData.d.ts +0 -4
  105. package/lib/services/obfuscateWorkerData.js +0 -4
  106. package/lib/services/orderPricingAccessors.d.ts +0 -18
  107. package/lib/services/orderPricingAccessors.js +0 -29
  108. package/lib/services/payOrder.d.ts +0 -5
  109. package/lib/services/payOrder.js +0 -25
  110. package/lib/services/registerUser.d.ts +0 -14
  111. package/lib/services/registerUser.js +0 -44
  112. package/lib/services/removeBookmark.d.ts +0 -6
  113. package/lib/services/removeBookmark.js +0 -10
  114. package/lib/services/removeCartItem.d.ts +0 -6
  115. package/lib/services/removeCartItem.js +0 -19
  116. package/lib/services/resetPasswordWithMigration.d.ts +0 -14
  117. package/lib/services/resetPasswordWithMigration.js +0 -31
  118. package/lib/services/resolveWorkType.d.ts +0 -1
  119. package/lib/services/resolveWorkType.js +0 -7
  120. package/lib/services/searchProductsWithAssortment.d.ts +0 -19
  121. package/lib/services/searchProductsWithAssortment.js +0 -17
  122. package/lib/services/toggleBookmark.d.ts +0 -8
  123. package/lib/services/toggleBookmark.js +0 -33
  124. package/lib/services/updateCart.d.ts +0 -15
  125. package/lib/services/updateCart.js +0 -27
  126. package/lib/services/updateEnrollment.d.ts +0 -13
  127. package/lib/services/updateEnrollment.js +0 -36
  128. package/lib/services/verifyEmailWithMigration.d.ts +0 -15
  129. package/lib/services/verifyEmailWithMigration.js +0 -23
@@ -1,44 +1,52 @@
1
1
  import { OrderPricingRowCategory, OrderPricingSheet, DeliveryPricingRowCategory, DeliveryPricingSheet, ProductPricingRowCategory, ProductPricingSheet, PaymentPricingRowCategory, PaymentPricingSheet, } from "../directors/index.js";
2
- export async function calculateDiscountTotalService(order, orderDiscount) {
3
- const orderDiscountId = orderDiscount._id;
2
+ export async function calculateDiscountTotalService(order, orderDiscount, { useNetPrice = false } = {}) {
3
+ const discountId = orderDiscount._id;
4
4
  const orderDelivery = await this.orders.deliveries.findDelivery({
5
5
  orderDeliveryId: order.deliveryId,
6
6
  });
7
- const orderDeliveryDiscountSum = DeliveryPricingSheet({
8
- calculation: orderDelivery?.calculation || [],
9
- currencyCode: order.currencyCode,
10
- }).total({ category: DeliveryPricingRowCategory.Discount, discountId: orderDiscountId });
11
7
  const orderPayment = await this.orders.payments.findOrderPayment({
12
8
  orderPaymentId: order.paymentId,
13
9
  });
14
- const orderPaymentDiscountSum = PaymentPricingSheet({
15
- calculation: orderPayment?.calculation || [],
16
- currencyCode: order.currencyCode,
17
- }).total({ category: PaymentPricingRowCategory.Discount, discountId: orderDiscountId });
18
10
  const orderPositions = await this.orders.positions.findOrderPositions({
19
11
  orderId: order._id,
20
12
  });
21
- const orderPositionDiscounts = orderPositions.map((orderPosition) => ProductPricingSheet({
22
- calculation: orderPosition.calculation || [],
23
- currencyCode: order.currencyCode,
24
- quantity: orderPosition.quantity,
25
- }).total({
26
- category: ProductPricingRowCategory.Discount,
27
- discountId: orderDiscountId,
28
- }));
29
- const orderDiscountSum = OrderPricingSheet({
30
- calculation: order.calculation,
31
- currencyCode: order.currencyCode,
32
- }).total({ category: OrderPricingRowCategory.Discounts, discountId: orderDiscountId });
33
- const prices = [
34
- orderDeliveryDiscountSum.amount,
35
- orderPaymentDiscountSum.amount,
36
- ...orderPositionDiscounts.map((positionDiscount) => positionDiscount.amount),
37
- orderDiscountSum.amount,
13
+ const discountedSheets = [
14
+ {
15
+ category: DeliveryPricingRowCategory.Discount,
16
+ sheet: DeliveryPricingSheet({
17
+ calculation: orderDelivery?.calculation || [],
18
+ currencyCode: order.currencyCode,
19
+ }),
20
+ },
21
+ {
22
+ category: PaymentPricingRowCategory.Discount,
23
+ sheet: PaymentPricingSheet({
24
+ calculation: orderPayment?.calculation || [],
25
+ currencyCode: order.currencyCode,
26
+ }),
27
+ },
28
+ ...orderPositions.map((orderPosition) => ({
29
+ category: ProductPricingRowCategory.Discount,
30
+ sheet: ProductPricingSheet({
31
+ calculation: orderPosition.calculation || [],
32
+ currencyCode: order.currencyCode,
33
+ quantity: orderPosition.quantity,
34
+ }),
35
+ })),
36
+ {
37
+ category: OrderPricingRowCategory.Discounts,
38
+ sheet: OrderPricingSheet({
39
+ calculation: order.calculation,
40
+ currencyCode: order.currencyCode,
41
+ }),
42
+ },
38
43
  ];
39
- const amount = prices.reduce((oldValue, price) => oldValue + (price || 0), 0);
44
+ const amount = discountedSheets.reduce((sum, { category, sheet }) => sum + (sheet.total({ category, discountId, useNetPrice }).amount || 0), 0);
45
+ const taxAmount = discountedSheets.reduce((sum, { category, sheet }) => sum + sheet.taxSum({ baseCategory: category, discountId }), 0);
40
46
  return {
41
47
  amount,
42
48
  currencyCode: order.currencyCode,
49
+ isTaxable: taxAmount !== 0,
50
+ isNetPrice: useNetPrice,
43
51
  };
44
52
  }
@@ -3,6 +3,7 @@ import { nextUserCartService } from "./nextUserCart.js";
3
3
  import { validateOrderService } from "./validateOrder.js";
4
4
  import { processOrderService } from "./processOrder.js";
5
5
  import { reserveOrderDiscountsForCheckout } from "./reserveOrderDiscountsForCheckout.js";
6
+ import { reserveOrderQuotationsForCheckout } from "./reserveOrderQuotationsForCheckout.js";
6
7
  export async function checkoutOrderService(orderId, transactionContext) {
7
8
  const order = await this.orders.findOrder({ orderId });
8
9
  if (!order)
@@ -12,7 +13,9 @@ export async function checkoutOrderService(orderId, transactionContext) {
12
13
  await validateOrderService.bind(this)(order);
13
14
  const lock = await this.orders.acquireLock(order._id, 'checkout');
14
15
  let discounts;
16
+ let quotations;
15
17
  try {
18
+ quotations = await reserveOrderQuotationsForCheckout.call(this, order);
16
19
  discounts = await reserveOrderDiscountsForCheckout.call(this, order);
17
20
  const processedOrder = await processOrderService.bind(this)(order, transactionContext);
18
21
  if (processedOrder.billingAddress) {
@@ -31,6 +34,7 @@ export async function checkoutOrderService(orderId, transactionContext) {
31
34
  }
32
35
  finally {
33
36
  await discounts?.release();
37
+ await quotations?.release();
34
38
  await lock.release();
35
39
  }
36
40
  }
@@ -31,6 +31,8 @@ import { rejectOrderService } from './rejectOrder.ts';
31
31
  import { discountedEntitiesService } from './discountedEntities.ts';
32
32
  import { createManualOrderDiscountService } from './createManualOrderDiscount.ts';
33
33
  import { initializeEnrollmentService } from './initializeEnrollment.ts';
34
+ import { suspendEnrollmentService } from './suspendEnrollment.ts';
35
+ import { updateEnrollmentPlanService } from './updateEnrollmentPlan.ts';
34
36
  import { activateEnrollmentService } from './activateEnrollment.ts';
35
37
  import { terminateEnrollmentService } from './terminateEnrollment.ts';
36
38
  import { invalidateFilterCacheService } from './invalidateFilterCache.ts';
@@ -48,6 +50,8 @@ import { createFilterOptionService } from './createFilterOption.ts';
48
50
  import { removeFilterOptionService } from './removeFilterOption.ts';
49
51
  import { removeCartDiscountService } from './removeCartDiscount.ts';
50
52
  import { addMultipleCartProductsService } from './addMultipleCartProducts.ts';
53
+ import { addCartQuotationService } from './addCartQuotation.ts';
54
+ import { updateCartItemService } from './updateCartItem.ts';
51
55
  import { ercMetadataService } from './ercMetadata.ts';
52
56
  import { simulateProductPricingService } from './simulateProductPricing.ts';
53
57
  import { simulateProductDispatchingService } from './simulateProductDispatching.ts';
@@ -105,6 +109,8 @@ export default function initServices(modules: Modules, customServices?: CustomSe
105
109
  resolveOrderItemDispatches: Bound<typeof resolveOrderItemDispatchesService>;
106
110
  removeCartDiscount: Bound<typeof removeCartDiscountService>;
107
111
  addMultipleCartProducts: Bound<typeof addMultipleCartProductsService>;
112
+ addCartQuotation: Bound<typeof addCartQuotationService>;
113
+ updateCartItem: Bound<typeof updateCartItemService>;
108
114
  };
109
115
  products: {
110
116
  simulateProductPricing: Bound<typeof simulateProductPricingService>;
@@ -131,6 +137,8 @@ export default function initServices(modules: Modules, customServices?: CustomSe
131
137
  initializeEnrollment: Bound<typeof initializeEnrollmentService>;
132
138
  activateEnrollment: Bound<typeof activateEnrollmentService>;
133
139
  terminateEnrollment: Bound<typeof terminateEnrollmentService>;
140
+ suspendEnrollment: Bound<typeof suspendEnrollmentService>;
141
+ updateEnrollmentPlan: Bound<typeof updateEnrollmentPlanService>;
134
142
  };
135
143
  quotations: {
136
144
  fulfillQuotation: Bound<typeof fulfillQuotationService>;
@@ -30,6 +30,8 @@ import { rejectOrderService } from "./rejectOrder.js";
30
30
  import { discountedEntitiesService } from "./discountedEntities.js";
31
31
  import { createManualOrderDiscountService } from "./createManualOrderDiscount.js";
32
32
  import { initializeEnrollmentService } from "./initializeEnrollment.js";
33
+ import { suspendEnrollmentService } from "./suspendEnrollment.js";
34
+ import { updateEnrollmentPlanService } from "./updateEnrollmentPlan.js";
33
35
  import { activateEnrollmentService } from "./activateEnrollment.js";
34
36
  import { terminateEnrollmentService } from "./terminateEnrollment.js";
35
37
  import { invalidateFilterCacheService } from "./invalidateFilterCache.js";
@@ -47,6 +49,8 @@ import { createFilterOptionService } from "./createFilterOption.js";
47
49
  import { removeFilterOptionService } from "./removeFilterOption.js";
48
50
  import { removeCartDiscountService } from "./removeCartDiscount.js";
49
51
  import { addMultipleCartProductsService } from "./addMultipleCartProducts.js";
52
+ import { addCartQuotationService } from "./addCartQuotation.js";
53
+ import { updateCartItemService } from "./updateCartItem.js";
50
54
  import { ercMetadataService } from "./ercMetadata.js";
51
55
  import { simulateProductPricingService } from "./simulateProductPricing.js";
52
56
  import { simulateProductDispatchingService } from "./simulateProductDispatching.js";
@@ -116,6 +120,8 @@ export default function initServices(modules, customServices = {}) {
116
120
  resolveOrderItemDispatches: resolveOrderItemDispatchesService,
117
121
  removeCartDiscount: removeCartDiscountService,
118
122
  addMultipleCartProducts: addMultipleCartProductsService,
123
+ addCartQuotation: addCartQuotationService,
124
+ updateCartItem: updateCartItemService,
119
125
  },
120
126
  products: {
121
127
  simulateProductPricing: simulateProductPricingService,
@@ -142,6 +148,8 @@ export default function initServices(modules, customServices = {}) {
142
148
  initializeEnrollment: initializeEnrollmentService,
143
149
  activateEnrollment: activateEnrollmentService,
144
150
  terminateEnrollment: terminateEnrollmentService,
151
+ suspendEnrollment: suspendEnrollmentService,
152
+ updateEnrollmentPlan: updateEnrollmentPlanService,
145
153
  },
146
154
  quotations: {
147
155
  fulfillQuotation: fulfillQuotationService,
@@ -1,4 +1,4 @@
1
- import type { Enrollment } from '@unchainedshop/core-enrollments';
1
+ import { type Enrollment } from '@unchainedshop/core-enrollments';
2
2
  import type { Modules } from '../modules.ts';
3
3
  export declare function initializeEnrollmentService(this: Modules, enrollment: Enrollment, params: {
4
4
  orderIdForFirstPeriod?: string;
@@ -1,3 +1,4 @@
1
+ import { addToDate } from '@unchainedshop/core-enrollments';
1
2
  import { EnrollmentDirector } from "../core-index.js";
2
3
  import { processEnrollmentService } from "./processEnrollment.js";
3
4
  import { addMessageService } from "./addMessage.js";
@@ -14,6 +15,16 @@ export async function initializeEnrollmentService(enrollment, params) {
14
15
  orderId: params.orderIdForFirstPeriod,
15
16
  }));
16
17
  }
18
+ const { minimumCommitmentPeriods, billingInterval, billingIntervalCount } = product?.plan || {};
19
+ if (minimumCommitmentPeriods && billingInterval) {
20
+ const contractStartDate = updatedEnrollment.periods.find(({ isTrial }) => !isTrial)?.start || new Date();
21
+ updatedEnrollment = (await this.enrollments.updateCommitment(enrollment._id, {
22
+ contractStartDate,
23
+ minimumCommitmentEnd: addToDate(contractStartDate, {
24
+ [billingInterval.toLowerCase()]: (billingIntervalCount || 1) * minimumCommitmentPeriods,
25
+ }),
26
+ }));
27
+ }
17
28
  const processedEnrollment = await processEnrollmentService.bind(this)(updatedEnrollment);
18
29
  const user = await this.users.findUserById(enrollment.userId);
19
30
  const locale = this.users.userLocale(user);
@@ -12,10 +12,10 @@ export async function migrateOrderCartsService({ fromUserId, toUserId, shouldMer
12
12
  return toCart;
13
13
  }
14
14
  if (!toCart || !shouldMerge) {
15
- this.orders.setCartOwner({ orderId: fromCart._id, userId: toUserId });
15
+ await this.orders.setCartOwner({ orderId: fromCart._id, userId: toUserId });
16
16
  return updateCalculationService.bind(this)(fromCart._id);
17
17
  }
18
- this.orders.moveCartPositions({ fromOrderId: fromCart._id, toOrderId: toCart._id });
18
+ await this.orders.moveCartPositions({ fromOrderId: fromCart._id, toOrderId: toCart._id });
19
19
  if (fromCart.billingAddress && !toCart.billingAddress) {
20
20
  await this.orders.updateBillingAddress(toCart._id, fromCart.billingAddress);
21
21
  }
@@ -4,6 +4,9 @@ import { migrateOrderCartsService } from "./migrateOrderCart.js";
4
4
  export async function migrateUserDataService(userIdBeforeLogin, userId) {
5
5
  const user = await this.users.findUserById(userId);
6
6
  const userBeforeLogin = await this.users.findUserById(userIdBeforeLogin);
7
+ if (userBeforeLogin?.guest) {
8
+ await this.quotations.replaceUserIdOfOpenQuotations(userIdBeforeLogin, userId);
9
+ }
7
10
  await migrateOrderCartsService.bind(this)({
8
11
  fromUserId: userIdBeforeLogin,
9
12
  toUserId: userId,
@@ -3,6 +3,12 @@ import { EnrollmentDirector } from "../core-index.js";
3
3
  import { createServiceError } from "../errors.js";
4
4
  const findNextStatus = async (enrollment, modules) => {
5
5
  let status = enrollment.status;
6
+ if (modules.enrollments.isExpired(enrollment, {}))
7
+ return EnrollmentStatus.TERMINATED;
8
+ if (status === EnrollmentStatus.SUSPENDED) {
9
+ const resumeDue = enrollment.resumeAt && new Date(enrollment.resumeAt).getTime() <= Date.now();
10
+ return resumeDue ? EnrollmentStatus.ACTIVE : status;
11
+ }
6
12
  const product = await modules.products.findProduct({
7
13
  productId: enrollment.productId,
8
14
  });
@@ -19,9 +25,6 @@ const findNextStatus = async (enrollment, modules) => {
19
25
  status = EnrollmentStatus.PAUSED;
20
26
  }
21
27
  }
22
- else if (modules.enrollments.isExpired(enrollment, {})) {
23
- status = EnrollmentStatus.TERMINATED;
24
- }
25
28
  return status;
26
29
  };
27
30
  export async function processEnrollmentService(enrollment) {
@@ -7,14 +7,14 @@ import { fulfillQuotationService } from "./fulfillQuotation.js";
7
7
  import { createLogger } from '@unchainedshop/logger';
8
8
  import { createServiceError } from "../errors.js";
9
9
  const logger = createLogger('unchained:core:processOrder');
10
- const isAutoConfirmationEnabled = async ({ orderPayment, orderDelivery, }, modules) => {
10
+ const isAutoConfirmationEnabled = async ({ order, orderPayment, orderDelivery, }, modules) => {
11
11
  if (orderPayment.status !== OrderPaymentStatus.PAID) {
12
12
  const paymentProvider = await modules.payment.paymentProviders.findProvider({
13
13
  paymentProviderId: orderPayment.paymentProviderId,
14
14
  });
15
15
  if (!paymentProvider)
16
16
  throw createServiceError('PaymentProviderNotFoundError', `Payment provider not found: ${orderPayment.paymentProviderId}`);
17
- const actions = await PaymentDirector.actions(paymentProvider, {}, { modules });
17
+ const actions = await PaymentDirector.actions(paymentProvider, { order, orderPayment, userId: order.userId }, { modules });
18
18
  if (!actions.isPayLaterAllowed())
19
19
  return false;
20
20
  }
@@ -24,7 +24,7 @@ const isAutoConfirmationEnabled = async ({ orderPayment, orderDelivery, }, modul
24
24
  });
25
25
  if (!deliveryProvider)
26
26
  throw createServiceError('DeliveryProviderNotFoundError', `Delivery provider not found: ${orderDelivery.deliveryProviderId}`);
27
- const director = await DeliveryDirector.actions(deliveryProvider, {}, { modules });
27
+ const director = await DeliveryDirector.actions(deliveryProvider, { order, orderDelivery }, { modules });
28
28
  if (!director.isAutoReleaseAllowed())
29
29
  return false;
30
30
  }
@@ -50,7 +50,7 @@ const findNextStatus = async (status, order, modules) => {
50
50
  if (!orderDelivery)
51
51
  return status;
52
52
  if (status === OrderStatus.PENDING) {
53
- if (await isAutoConfirmationEnabled({ orderPayment, orderDelivery }, modules)) {
53
+ if (await isAutoConfirmationEnabled({ order, orderPayment, orderDelivery }, modules)) {
54
54
  return OrderStatus.CONFIRMED;
55
55
  }
56
56
  }
@@ -0,0 +1,8 @@
1
+ import { type Order } from '@unchainedshop/core-orders';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function isQuotationReserved(this: Modules, quotationId: string, { exceptOrderId }?: {
4
+ exceptOrderId?: string;
5
+ }): Promise<boolean>;
6
+ export declare function reserveOrderQuotationsForCheckout(this: Modules, order: Order): Promise<{
7
+ release: () => Promise<void>;
8
+ }>;
@@ -0,0 +1,49 @@
1
+ import { OrderStatus } from '@unchainedshop/core-orders';
2
+ import { createServiceError } from "../errors.js";
3
+ const RESERVATION_LOCK_TTL = 60000;
4
+ export async function isQuotationReserved(quotationId, { exceptOrderId } = {}) {
5
+ const orderIds = (await this.orders.positions.findOrderPositions({ quotationId }))
6
+ .map(({ orderId }) => orderId)
7
+ .filter((orderId) => orderId !== exceptOrderId);
8
+ if (!orderIds.length)
9
+ return false;
10
+ const count = await this.orders.count({
11
+ orderIds,
12
+ status: [OrderStatus.PENDING, OrderStatus.CONFIRMED, OrderStatus.FULFILLED],
13
+ });
14
+ return count > 0;
15
+ }
16
+ export async function reserveOrderQuotationsForCheckout(order) {
17
+ const locks = [];
18
+ const release = async () => {
19
+ await Promise.all(locks.map((lock) => lock.release()));
20
+ };
21
+ try {
22
+ const positions = await this.orders.positions.findOrderPositions({ orderId: order._id });
23
+ const quotationIds = [
24
+ ...new Set(positions.map(({ quotationId }) => quotationId).filter(Boolean)),
25
+ ].sort();
26
+ for (const quotationId of quotationIds) {
27
+ try {
28
+ locks.push(await this.orders.acquireLock(quotationId, 'quotation', RESERVATION_LOCK_TTL));
29
+ }
30
+ catch (error) {
31
+ if (!error?.message?.startsWith('Could not acquire lock'))
32
+ throw error;
33
+ throw createServiceError('QuotationInvalidError', 'Quotation is being ordered by another order');
34
+ }
35
+ const quotation = await this.quotations.findQuotation({ quotationId });
36
+ if (!quotation || !this.quotations.isProposalValid(quotation)) {
37
+ throw createServiceError('QuotationInvalidError', 'Quotation expired, fulfilled or not valid for this order, please request a new offer');
38
+ }
39
+ if (await isQuotationReserved.call(this, quotationId, { exceptOrderId: order._id })) {
40
+ throw createServiceError('QuotationInvalidError', 'Quotation is reserved by another order');
41
+ }
42
+ }
43
+ return { release };
44
+ }
45
+ catch (error) {
46
+ await release();
47
+ throw error;
48
+ }
49
+ }
@@ -22,7 +22,7 @@ export async function simulateConfigurablePriceRangeService({ product, currencyC
22
22
  return {
23
23
  ...unitPrice,
24
24
  isNetPrice: useNetPrice,
25
- isTaxable: pricing.taxSum() > 0,
25
+ isTaxable: pricing.taxSum() !== 0,
26
26
  currencyCode: pricing.currencyCode,
27
27
  };
28
28
  }))).filter(Boolean);
@@ -17,7 +17,7 @@ export async function simulateDeliveryPricingService(pricingContext, options = {
17
17
  amount: orderPrice.amount,
18
18
  currencyCode: orderPrice.currencyCode,
19
19
  countryCode: pricingContext.countryCode,
20
- isTaxable: pricing.taxSum() > 0,
20
+ isTaxable: pricing.taxSum() !== 0,
21
21
  isNetPrice: options.useNetPrice || false,
22
22
  };
23
23
  }
@@ -17,7 +17,7 @@ export async function simulatePaymentPricingService(pricingContext, options = {}
17
17
  amount: orderPrice.amount,
18
18
  currencyCode: orderPrice.currencyCode,
19
19
  countryCode: pricingContext.countryCode,
20
- isTaxable: pricing.taxSum() > 0,
20
+ isTaxable: pricing.taxSum() !== 0,
21
21
  isNetPrice: options.useNetPrice || false,
22
22
  };
23
23
  }
@@ -0,0 +1,5 @@
1
+ import { type Enrollment } from '@unchainedshop/core-enrollments';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function suspendEnrollmentService(this: Modules, enrollment: Enrollment, { resumeAt }?: {
4
+ resumeAt?: Date;
5
+ }): Promise<Enrollment>;
@@ -0,0 +1,21 @@
1
+ import { EnrollmentStatus } from '@unchainedshop/core-enrollments';
2
+ import { addMessageService } from "./addMessage.js";
3
+ export async function suspendEnrollmentService(enrollment, { resumeAt } = {}) {
4
+ if (enrollment.status === EnrollmentStatus.TERMINATED)
5
+ return enrollment;
6
+ let updatedEnrollment = (await this.enrollments.updateStatus(enrollment._id, {
7
+ status: EnrollmentStatus.SUSPENDED,
8
+ info: 'suspended manually',
9
+ }));
10
+ if (resumeAt) {
11
+ updatedEnrollment = (await this.enrollments.updateResumeAt(enrollment._id, resumeAt));
12
+ }
13
+ const user = await this.users.findUserById(enrollment.userId);
14
+ const locale = this.users.userLocale(user);
15
+ await addMessageService.bind(this)('ENROLLMENT_STATUS', {
16
+ reason: 'status_change',
17
+ locale: locale.baseName,
18
+ enrollmentId: updatedEnrollment._id,
19
+ });
20
+ return updatedEnrollment;
21
+ }
@@ -1,2 +1,6 @@
1
- import { type Enrollment } from '@unchainedshop/core-enrollments';
2
- export declare function terminateEnrollmentService(enrollment: Enrollment): Promise<any>;
1
+ import { type Enrollment, type EnrollmentTerminationReason } from '@unchainedshop/core-enrollments';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function terminateEnrollmentService(this: Modules, enrollment: Enrollment, { reason, comment }?: {
4
+ reason?: EnrollmentTerminationReason;
5
+ comment?: string;
6
+ }): Promise<Enrollment>;
@@ -1,14 +1,34 @@
1
- import { EnrollmentStatus } from '@unchainedshop/core-enrollments';
1
+ import { EnrollmentStatus, } from '@unchainedshop/core-enrollments';
2
2
  import { processEnrollmentService } from "./processEnrollment.js";
3
3
  import { addMessageService } from "./addMessage.js";
4
- export async function terminateEnrollmentService(enrollment) {
4
+ import { EnrollmentDirector } from "../core-index.js";
5
+ import { createServiceError } from "../errors.js";
6
+ export async function terminateEnrollmentService(enrollment, { reason, comment } = {}) {
5
7
  if (enrollment.status === EnrollmentStatus.TERMINATED)
6
8
  return enrollment;
7
- let updatedEnrollment = await this.enrollments.updateStatus(enrollment._id, {
8
- status: EnrollmentStatus.TERMINATED,
9
- info: 'terminated manually',
10
- });
11
- updatedEnrollment = await processEnrollmentService.bind(this)(updatedEnrollment);
9
+ const product = await this.products.findProduct({ productId: enrollment.productId });
10
+ if (!product)
11
+ throw createServiceError('ProductNotFoundError', 'Product not found for enrollment');
12
+ const director = await EnrollmentDirector.actions({ enrollment, product }, { modules: this });
13
+ const terminationDate = await director.terminationDate({ referenceDate: new Date() });
14
+ if (!terminationDate) {
15
+ throw createServiceError('EnrollmentTerminationNotAllowedError', 'Enrollment termination is not allowed at this time');
16
+ }
17
+ const expires = new Date(Math.min(Math.max(terminationDate.getTime(), enrollment.minimumCommitmentEnd?.getTime() ?? 0), enrollment.expires?.getTime() ?? Infinity));
18
+ if (reason || comment) {
19
+ await this.enrollments.updateCancellation(enrollment._id, { reason, comment });
20
+ }
21
+ let updatedEnrollment;
22
+ if (expires.getTime() > Date.now()) {
23
+ updatedEnrollment = (await this.enrollments.updateExpiry(enrollment._id, expires));
24
+ }
25
+ else {
26
+ updatedEnrollment = (await this.enrollments.updateStatus(enrollment._id, {
27
+ status: EnrollmentStatus.TERMINATED,
28
+ info: 'terminated manually',
29
+ }));
30
+ updatedEnrollment = await processEnrollmentService.bind(this)(updatedEnrollment);
31
+ }
12
32
  const user = await this.users.findUserById(enrollment.userId);
13
33
  const locale = this.users.userLocale(user);
14
34
  await addMessageService.bind(this)('ENROLLMENT_STATUS', {
@@ -1,11 +1,11 @@
1
- import { type OrderPosition } from '@unchainedshop/core-orders';
1
+ import { type Order, type OrderPosition } from '@unchainedshop/core-orders';
2
2
  import type { Modules } from '../modules.ts';
3
- export interface UpdateCartItemParams {
4
- itemId: string;
5
- quantity?: number;
3
+ export declare function updateCartItemService(this: Modules, { order, item, quantity, configuration, }: {
4
+ order: Order;
5
+ item: OrderPosition;
6
+ quantity?: number | null;
6
7
  configuration?: {
7
8
  key: string;
8
9
  value: string;
9
- }[];
10
- }
11
- export declare function updateCartItemService(this: Modules, params: UpdateCartItemParams, context: any): Promise<OrderPosition>;
10
+ }[] | null;
11
+ }, unchainedAPI?: Record<string, any>): Promise<OrderPosition | null>;
@@ -1,37 +1,40 @@
1
1
  import { ordersSettings } from '@unchainedshop/core-orders';
2
- import { updateCalculationService } from "./updateCalculation.js";
3
- import { OrderItemNotFoundError, OrderNotFoundError, OrderWrongStatusError, OrderQuantityTooLowError, ProductNotFoundError, } from "../errors/index.js";
4
- export async function updateCartItemService(params, context) {
5
- const { itemId, configuration, quantity = 1 } = params;
6
- const item = await this.orders.positions.findOrderPosition({ itemId });
7
- if (!item) {
8
- throw new OrderItemNotFoundError({ orderItemId: itemId });
9
- }
10
- const order = await this.orders.findOrder({ orderId: item.orderId });
11
- if (!order) {
12
- throw new OrderNotFoundError({ orderId: item.orderId });
13
- }
14
- if (!this.orders.isCart(order)) {
15
- throw new OrderWrongStatusError({ status: order.status });
16
- }
2
+ import { QuotationDirector } from "../directors/QuotationDirector.js";
3
+ import { createServiceError } from "../errors.js";
4
+ export async function updateCartItemService({ order, item, quantity, configuration, }, unchainedAPI = {}) {
5
+ const context = { ...unchainedAPI, modules: this };
17
6
  const product = await this.products.findProduct({ productId: item.productId });
18
7
  if (!product) {
19
- throw new ProductNotFoundError({ productId: item.productId });
8
+ throw createServiceError('ProductNotFoundError', `Product not found: ${item.productId}`);
20
9
  }
21
- if (quantity !== null && quantity < 1) {
22
- throw new OrderQuantityTooLowError({ quantity });
10
+ let itemQuantity = quantity === undefined ? 1 : quantity;
11
+ let itemConfiguration = configuration;
12
+ if (item.quotationId) {
13
+ const quotation = await this.quotations.findQuotation({ quotationId: item.quotationId });
14
+ if (!quotation) {
15
+ throw createServiceError('QuotationNotFoundError', `Quotation not found: ${item.quotationId}`);
16
+ }
17
+ const director = await QuotationDirector.actions({ quotation }, context);
18
+ const requestedQuantity = quantity || item.quantity;
19
+ const quotationConfiguration = await director.transformItemConfiguration({
20
+ quantity: requestedQuantity,
21
+ configuration: configuration || item.configuration || [],
22
+ });
23
+ if (!quotationConfiguration) {
24
+ throw createServiceError('QuotationItemConfigurationError', `Quotation ${quotation._id} does not take quantity ${requestedQuantity}`);
25
+ }
26
+ itemQuantity = quotationConfiguration.quantity || requestedQuantity;
27
+ itemConfiguration = quotationConfiguration.configuration;
23
28
  }
24
29
  await ordersSettings.validateOrderPosition({
25
30
  order,
26
31
  product,
27
- configuration,
28
- quantityDiff: quantity - item.quantity,
32
+ configuration: itemConfiguration,
33
+ quantityDiff: itemQuantity - item.quantity,
29
34
  }, context);
30
- await this.orders.positions.updateProductItem({
35
+ return this.orders.positions.updateProductItem({
31
36
  orderPositionId: item._id,
32
- quantity: quantity || null,
33
- configuration: configuration || null,
37
+ quantity: itemQuantity || null,
38
+ configuration: itemConfiguration || null,
34
39
  });
35
- await updateCalculationService.bind(this)(order._id);
36
- return (await this.orders.positions.findOrderPosition({ itemId: item._id }));
37
40
  }
@@ -0,0 +1,5 @@
1
+ import type { Enrollment, EnrollmentPlan } from '@unchainedshop/core-enrollments';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function updateEnrollmentPlanService(this: Modules, enrollment: Enrollment, { plan }: {
4
+ plan: EnrollmentPlan;
5
+ }): Promise<Enrollment>;
@@ -0,0 +1,26 @@
1
+ import { emit } from '@unchainedshop/events';
2
+ import { EnrollmentDirector } from "../core-index.js";
3
+ import { processEnrollmentService } from "./processEnrollment.js";
4
+ import { addMessageService } from "./addMessage.js";
5
+ import { createServiceError } from "../errors.js";
6
+ export async function updateEnrollmentPlanService(enrollment, { plan }) {
7
+ const product = await this.products.findProduct({ productId: enrollment.productId });
8
+ if (!product)
9
+ throw createServiceError('ProductNotFoundError', 'Product not found for enrollment');
10
+ const director = await EnrollmentDirector.actions({ enrollment, product }, { modules: this });
11
+ const newPlan = await director.transformPlanToNewPlan({ plan });
12
+ if (!newPlan) {
13
+ throw createServiceError('EnrollmentPlanChangeNotSupportedError', 'Plan change is not supported for this enrollment');
14
+ }
15
+ let updatedEnrollment = (await this.enrollments.updatePlan(enrollment._id, newPlan));
16
+ updatedEnrollment = await processEnrollmentService.bind(this)(updatedEnrollment);
17
+ await emit('ENROLLMENT_PLAN_CHANGE', { enrollment: updatedEnrollment });
18
+ const user = await this.users.findUserById(enrollment.userId);
19
+ const locale = this.users.userLocale(user);
20
+ await addMessageService.bind(this)('ENROLLMENT_STATUS', {
21
+ reason: 'plan_change',
22
+ locale: locale.baseName,
23
+ enrollmentId: updatedEnrollment._id,
24
+ });
25
+ return updatedEnrollment;
26
+ }
@@ -29,8 +29,13 @@ export async function validateOrderService(order) {
29
29
  (await this.quotations.findQuotation({
30
30
  quotationId: orderPosition.quotationId,
31
31
  }));
32
- if (quotation && !this.quotations.isProposalValid(quotation)) {
33
- throw createServiceError('QuotationInvalidError', 'Quotation expired or fulfilled, please request a new offer');
32
+ if (quotation &&
33
+ !this.quotations.isProposalValidFor(quotation, {
34
+ userId: order.userId,
35
+ productId: orderPosition.productId,
36
+ currencyCode: order.currencyCode,
37
+ })) {
38
+ throw createServiceError('QuotationInvalidError', 'Quotation expired, fulfilled or not valid for this order, please request a new offer');
34
39
  }
35
40
  }
36
41
  }