@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
@@ -71,6 +71,7 @@ const USER_CSV_SCHEMA = {
71
71
  'userId',
72
72
  'quotationNumber',
73
73
  'productId',
74
+ 'quantity',
74
75
  'status',
75
76
  'price',
76
77
  'expires',
@@ -3,6 +3,8 @@ export interface PricingDiscount {
3
3
  discountId: string;
4
4
  amount: number;
5
5
  currencyCode: string;
6
+ isTaxable: boolean;
7
+ isNetPrice: boolean;
6
8
  }
7
9
  export interface IPricingSheet<Calculation extends PricingCalculation> extends IBasePricingSheet<Calculation> {
8
10
  discountPrices: (discountId?: string) => PricingDiscount[];
@@ -28,10 +28,12 @@ export const DeliveryPricingSheet = (params) => {
28
28
  meta,
29
29
  });
30
30
  },
31
- addTax({ amount, rate, meta }) {
31
+ addTax({ amount, rate, baseCategory, discountId, meta }) {
32
32
  basePricingSheet.calculation.push({
33
33
  amount,
34
34
  category: DeliveryPricingRowCategory.Tax,
35
+ baseCategory,
36
+ discountId,
35
37
  isNetPrice: false,
36
38
  isTaxable: false,
37
39
  meta,
@@ -54,17 +56,20 @@ export const DeliveryPricingSheet = (params) => {
54
56
  .filter(Boolean);
55
57
  return [...new Set(discountIds)]
56
58
  .map((discountId) => {
57
- const { currencyCode, amount } = basePricingSheet.total({
58
- category: DeliveryPricingRowCategory.Discount,
59
+ const taxAmount = pricingSheet.taxSum({
60
+ baseCategory: DeliveryPricingRowCategory.Discount,
59
61
  discountId,
60
62
  });
63
+ const amount = pricingSheet.sum({ category: DeliveryPricingRowCategory.Discount, discountId }) + taxAmount;
61
64
  if (!amount) {
62
65
  return null;
63
66
  }
64
67
  return {
65
68
  discountId,
66
69
  amount: Math.round(amount),
67
- currencyCode,
70
+ currencyCode: pricingSheet.currencyCode,
71
+ isTaxable: taxAmount !== 0,
72
+ isNetPrice: false,
68
73
  };
69
74
  })
70
75
  .filter(Boolean);
@@ -2,6 +2,7 @@ import { type IBaseAdapter } from '@unchainedshop/utils';
2
2
  import { type Enrollment, type EnrollmentOrderPositionTemplate, type EnrollmentPeriod, type EnrollmentPlan } from '@unchainedshop/core-enrollments';
3
3
  import type { Product, ProductPlan } from '@unchainedshop/core-products';
4
4
  import type { OrderPosition } from '@unchainedshop/core-orders';
5
+ import type { Modules } from '../modules.ts';
5
6
  export interface EnrollmentContext {
6
7
  enrollment: Enrollment;
7
8
  product: Product;
@@ -16,11 +17,20 @@ export interface EnrollmentAdapterActions {
16
17
  isOverdue: () => Promise<boolean>;
17
18
  isValidForActivation: () => Promise<boolean>;
18
19
  nextPeriod: () => Promise<EnrollmentPeriod | null>;
20
+ terminationDate: (params: {
21
+ referenceDate: Date;
22
+ }) => Promise<Date | null>;
23
+ transformPlanToNewPlan: (params: {
24
+ plan: EnrollmentPlan;
25
+ }) => Promise<EnrollmentPlan | null>;
19
26
  }
20
27
  export type IEnrollmentAdapter = IBaseAdapter & {
28
+ orderIndex?: number;
21
29
  isActivatedFor: (productPlan?: ProductPlan) => boolean;
22
30
  transformOrderItemToEnrollmentPlan: (orderPosition: OrderPosition, unchainedAPI: any) => Promise<EnrollmentPlan>;
23
- actions: (params: EnrollmentContext) => EnrollmentAdapterActions;
31
+ actions: (params: EnrollmentContext & {
32
+ modules: Modules;
33
+ }) => EnrollmentAdapterActions;
24
34
  };
25
35
  export declare const periodForReferenceDate: (referenceDate: Date, intervalCount?: number, interval?: string) => {
26
36
  start: Date;
@@ -56,11 +56,17 @@ export const EnrollmentAdapter = {
56
56
  }
57
57
  return acc;
58
58
  }, referenceDate);
59
- return {
59
+ const period = {
60
60
  ...periodForReferenceDate(lastEnd, plan.billingIntervalCount, plan.billingInterval),
61
61
  isTrial: false,
62
62
  };
63
+ if (enrollment?.expires && period.start.getTime() >= new Date(enrollment.expires).getTime()) {
64
+ return null;
65
+ }
66
+ return period;
63
67
  },
68
+ terminationDate: async ({ referenceDate }) => referenceDate,
69
+ transformPlanToNewPlan: async () => null,
64
70
  };
65
71
  },
66
72
  };
@@ -1,7 +1,7 @@
1
1
  import type { IBaseDirector } from '@unchainedshop/utils';
2
2
  import type { EnrollmentAdapterActions, EnrollmentContext, IEnrollmentAdapter } from './EnrollmentAdapter.ts';
3
3
  import type { OrderPosition } from '@unchainedshop/core-orders';
4
- import type { Product } from '@unchainedshop/core-products';
4
+ import type { Product, ProductPlan } from '@unchainedshop/core-products';
5
5
  import type { Enrollment } from '@unchainedshop/core-enrollments';
6
6
  import type { Modules } from '../modules.ts';
7
7
  export type IEnrollmentDirector = IBaseDirector<IEnrollmentAdapter> & {
@@ -9,6 +9,7 @@ export type IEnrollmentDirector = IBaseDirector<IEnrollmentAdapter> & {
9
9
  orderPosition: OrderPosition;
10
10
  product: Product;
11
11
  }, doc: Omit<Enrollment, 'configuration' | 'productId' | 'quantity' | 'status' | 'periods' | 'log' | '_id' | 'created'>, unchainedAPI: any) => Promise<Omit<Enrollment, 'status' | 'periods' | 'log' | '_id' | 'created'> & Pick<Partial<Enrollment>, '_id' | 'created'>>;
12
+ findSupportedAdapter: (productPlan?: ProductPlan) => IEnrollmentAdapter | null;
12
13
  actions: (enrollmentContext: EnrollmentContext, unchainedAPI: {
13
14
  modules: Modules;
14
15
  }) => Promise<EnrollmentAdapterActions>;
@@ -1,6 +1,7 @@
1
1
  import { EnrollmentAdapter } from "./EnrollmentAdapter.js";
2
2
  import { createLogger } from '@unchainedshop/logger';
3
3
  import { registryDirector } from "./registryDirector.js";
4
+ import { createServiceError } from "../errors.js";
4
5
  const logger = createLogger('unchained:core');
5
6
  const findAppropriateAdapters = (productPlan) => EnrollmentDirector.getAdapters({
6
7
  adapterFilter: (Adapter) => {
@@ -13,10 +14,11 @@ const findAppropriateAdapters = (productPlan) => EnrollmentDirector.getAdapters(
13
14
  });
14
15
  export const EnrollmentDirector = {
15
16
  ...registryDirector(EnrollmentAdapter.adapterType),
17
+ findSupportedAdapter: (productPlan) => findAppropriateAdapters(productPlan)?.[0] || null,
16
18
  transformOrderItemToEnrollment: async ({ orderPosition, product }, doc, unchainedAPI) => {
17
- const Adapter = findAppropriateAdapters(product.plan)?.[0];
19
+ const Adapter = EnrollmentDirector.findSupportedAdapter(product.plan);
18
20
  if (!Adapter) {
19
- throw new Error('No suitable enrollment plugin available for this item');
21
+ throw createServiceError('EnrollmentPlanNotSupportedError', 'No suitable enrollment plugin available for this item');
20
22
  }
21
23
  const enrollmentPlan = await Adapter.transformOrderItemToEnrollmentPlan(orderPosition, unchainedAPI);
22
24
  return {
@@ -27,9 +29,9 @@ export const EnrollmentDirector = {
27
29
  },
28
30
  actions: async (enrollmentContext, unchainedAPI) => {
29
31
  const context = { ...enrollmentContext, ...unchainedAPI };
30
- const Adapter = findAppropriateAdapters(enrollmentContext.product.plan)?.[0];
32
+ const Adapter = EnrollmentDirector.findSupportedAdapter(enrollmentContext.product.plan);
31
33
  if (!Adapter) {
32
- throw new Error('No suitable enrollment plugin available for this plan configuration');
34
+ throw createServiceError('EnrollmentPlanNotSupportedError', 'No suitable enrollment plugin available for this plan configuration');
33
35
  }
34
36
  const adapter = Adapter.actions(context);
35
37
  return adapter;
@@ -81,14 +81,11 @@ export const OrderPricingSheet = (params) => {
81
81
  .filter(Boolean);
82
82
  return [...new Set(discountIds)]
83
83
  .map((discountId) => {
84
- const amount = pricingSheet.sum({
85
- category: OrderPricingRowCategory.Discounts,
84
+ const taxAmount = pricingSheet.taxSum({
85
+ baseCategory: OrderPricingRowCategory.Discounts,
86
86
  discountId,
87
- }) +
88
- pricingSheet.taxSum({
89
- baseCategory: OrderPricingRowCategory.Discounts,
90
- discountId,
91
- });
87
+ });
88
+ const amount = pricingSheet.sum({ category: OrderPricingRowCategory.Discounts, discountId }) + taxAmount;
92
89
  if (!amount) {
93
90
  return null;
94
91
  }
@@ -96,6 +93,8 @@ export const OrderPricingSheet = (params) => {
96
93
  discountId,
97
94
  amount: Math.round(amount),
98
95
  currencyCode: pricingSheet.currencyCode,
96
+ isTaxable: taxAmount !== 0,
97
+ isNetPrice: false,
99
98
  };
100
99
  })
101
100
  .filter(Boolean);
@@ -29,10 +29,12 @@ export const PaymentPricingSheet = (params) => {
29
29
  meta,
30
30
  });
31
31
  },
32
- addTax({ amount, rate, meta }) {
32
+ addTax({ amount, rate, baseCategory, discountId, meta }) {
33
33
  basePricingSheet.calculation.push({
34
34
  category: PaymentPricingRowCategory.Tax,
35
35
  amount,
36
+ baseCategory,
37
+ discountId,
36
38
  isTaxable: false,
37
39
  isNetPrice: false,
38
40
  rate,
@@ -55,17 +57,20 @@ export const PaymentPricingSheet = (params) => {
55
57
  .filter(Boolean);
56
58
  return [...new Set(discountIds)]
57
59
  .map((discountId) => {
58
- const amount = basePricingSheet.sum({
59
- category: PaymentPricingRowCategory.Discount,
60
+ const taxAmount = pricingSheet.taxSum({
61
+ baseCategory: PaymentPricingRowCategory.Discount,
60
62
  discountId,
61
63
  });
64
+ const amount = pricingSheet.sum({ category: PaymentPricingRowCategory.Discount, discountId }) + taxAmount;
62
65
  if (!amount) {
63
66
  return null;
64
67
  }
65
68
  return {
66
69
  discountId,
67
70
  amount: Math.round(amount),
68
- currencyCode: basePricingSheet.currencyCode,
71
+ currencyCode: pricingSheet.currencyCode,
72
+ isTaxable: taxAmount !== 0,
73
+ isNetPrice: false,
69
74
  };
70
75
  })
71
76
  .filter(Boolean);
@@ -10,6 +10,7 @@ export interface ProductPricingAdapterContext extends BasePricingAdapterContext
10
10
  product: Product;
11
11
  quantity: number;
12
12
  configuration: ProductConfiguration[] | null;
13
+ quotationId?: string;
13
14
  order?: Order;
14
15
  user?: User;
15
16
  }
@@ -28,6 +28,7 @@ export const ProductPricingDirector = {
28
28
  product: product,
29
29
  quantity,
30
30
  configuration: item.configuration ?? null,
31
+ quotationId: item.quotationId,
31
32
  user: user,
32
33
  };
33
34
  }
@@ -1,4 +1,4 @@
1
- import { BasePricingSheet } from "./BasePricingSheet.js";
1
+ import { BasePricingSheet, } from "./BasePricingSheet.js";
2
2
  export const ProductPricingRowCategory = {
3
3
  Item: 'ITEM',
4
4
  Discount: 'DISCOUNT',
@@ -62,17 +62,20 @@ export const ProductPricingSheet = (params) => {
62
62
  .filter(Boolean);
63
63
  return [...new Set(discountIds)]
64
64
  .map((discountId) => {
65
- const amount = basePricingSheet.sum({
66
- category: ProductPricingRowCategory.Discount,
65
+ const taxAmount = pricingSheet.taxSum({
66
+ baseCategory: ProductPricingRowCategory.Discount,
67
67
  discountId,
68
68
  });
69
+ const amount = pricingSheet.sum({ category: ProductPricingRowCategory.Discount, discountId }) + taxAmount;
69
70
  if (!amount) {
70
71
  return null;
71
72
  }
72
73
  return {
73
74
  discountId,
74
75
  amount: Math.round(amount),
75
- currencyCode: basePricingSheet.currencyCode,
76
+ currencyCode: pricingSheet.currencyCode,
77
+ isTaxable: taxAmount !== 0,
78
+ isNetPrice: false,
76
79
  };
77
80
  })
78
81
  .filter(Boolean);
@@ -12,7 +12,7 @@ export const QuotationAdapter = {
12
12
  isActivatedFor: () => {
13
13
  return false;
14
14
  },
15
- actions: () => {
15
+ actions: (params) => {
16
16
  return {
17
17
  configurationError: () => {
18
18
  return QuotationError.NOT_IMPLEMENTED;
@@ -36,7 +36,10 @@ export const QuotationAdapter = {
36
36
  return true;
37
37
  },
38
38
  transformItemConfiguration: async ({ quantity, configuration }) => {
39
- return { quantity, configuration };
39
+ const quotedQuantity = params?.quotation?.quantity;
40
+ if (quotedQuantity && (!quantity || quantity % quotedQuantity !== 0))
41
+ return null;
42
+ return { quantity, configuration: params?.quotation?.configuration ?? configuration };
40
43
  },
41
44
  };
42
45
  },
@@ -2,8 +2,9 @@ import type { EnrollmentOrderPositionTemplate, EnrollmentPeriod, EnrollmentPlan
2
2
  import type { ProductPlan } from '@unchainedshop/core-products';
3
3
  import type { OrderPosition } from '@unchainedshop/core-orders';
4
4
  import { type EnrollmentContext, type IPlugin } from '../core-index.ts';
5
- export default function registerEnrollment({ adapterId, isActivatedFor, transformOrderItem, configurationForOrder, isOverdue, isValidForActivation, nextPeriod, }: {
5
+ export default function registerEnrollment({ adapterId, orderIndex, isActivatedFor, transformOrderItem, configurationForOrder, isOverdue, isValidForActivation, nextPeriod, terminationDate, transformPlanToNewPlan, }: {
6
6
  adapterId: string;
7
+ orderIndex?: number;
7
8
  isActivatedFor?: (productPlan?: ProductPlan) => boolean;
8
9
  transformOrderItem?: (orderPosition: OrderPosition, unchainedAPI: any) => Promise<EnrollmentPlan>;
9
10
  configurationForOrder: (params: {
@@ -15,4 +16,10 @@ export default function registerEnrollment({ adapterId, isActivatedFor, transfor
15
16
  isOverdue?: (context: EnrollmentContext) => Promise<boolean>;
16
17
  isValidForActivation?: (context: EnrollmentContext) => Promise<boolean>;
17
18
  nextPeriod?: (context: EnrollmentContext) => Promise<EnrollmentPeriod | null>;
19
+ terminationDate?: (context: EnrollmentContext, params: {
20
+ referenceDate: Date;
21
+ }) => Promise<Date | null>;
22
+ transformPlanToNewPlan?: (context: EnrollmentContext, params: {
23
+ plan: EnrollmentPlan;
24
+ }) => Promise<EnrollmentPlan | null>;
18
25
  }): IPlugin;
@@ -1,11 +1,12 @@
1
1
  import { EnrollmentAdapter, } from "../core-index.js";
2
2
  import { pluginRegistry } from "../plugins/PluginRegistry.js";
3
- export default function registerEnrollment({ adapterId, isActivatedFor, transformOrderItem, configurationForOrder, isOverdue, isValidForActivation, nextPeriod, }) {
3
+ export default function registerEnrollment({ adapterId, orderIndex, isActivatedFor, transformOrderItem, configurationForOrder, isOverdue, isValidForActivation, nextPeriod, terminationDate, transformPlanToNewPlan, }) {
4
4
  const adapter = {
5
5
  ...EnrollmentAdapter,
6
6
  key: `shop.unchained.enrollment.${adapterId}`,
7
7
  label: 'Enrollment: ' + adapterId,
8
8
  version: '1.0.0',
9
+ orderIndex: orderIndex ?? 0,
9
10
  isActivatedFor: (productPlan) => {
10
11
  return isActivatedFor ? isActivatedFor(productPlan) : true;
11
12
  },
@@ -15,20 +16,17 @@ export default function registerEnrollment({ adapterId, isActivatedFor, transfor
15
16
  : EnrollmentAdapter.transformOrderItemToEnrollmentPlan(orderPosition, unchainedAPI);
16
17
  },
17
18
  actions: (context) => {
19
+ const base = EnrollmentAdapter.actions(context);
18
20
  return {
19
- ...EnrollmentAdapter.actions(context),
20
- configurationForOrder: async (params) => {
21
- return configurationForOrder(params, context);
22
- },
23
- isOverdue: async () => {
24
- return isOverdue ? isOverdue(context) : false;
25
- },
26
- isValidForActivation: async () => {
27
- return isValidForActivation ? isValidForActivation(context) : false;
28
- },
29
- nextPeriod: async () => {
30
- return nextPeriod ? nextPeriod(context) : EnrollmentAdapter.actions(context).nextPeriod();
31
- },
21
+ ...base,
22
+ configurationForOrder: (params) => configurationForOrder(params, context),
23
+ isOverdue: () => (isOverdue ? isOverdue(context) : base.isOverdue()),
24
+ isValidForActivation: () => isValidForActivation ? isValidForActivation(context) : base.isValidForActivation(),
25
+ nextPeriod: () => (nextPeriod ? nextPeriod(context) : base.nextPeriod()),
26
+ terminationDate: (params) => terminationDate ? terminationDate(context, params) : base.terminationDate(params),
27
+ transformPlanToNewPlan: (params) => transformPlanToNewPlan
28
+ ? transformPlanToNewPlan(context, params)
29
+ : base.transformPlanToNewPlan(params),
32
30
  };
33
31
  },
34
32
  };
@@ -1,7 +1,8 @@
1
1
  import type { QuotationItemConfiguration, QuotationProposal } from '@unchainedshop/core-quotations';
2
2
  import { type QuotationContext, type IPlugin } from '../core-index.ts';
3
- export default function registerQuotation({ adapterId, isManualRequestVerificationRequired, isManualProposalRequired, quote, submitRequest, verifyRequest, rejectRequest, transformItemConfiguration, }: {
3
+ export default function registerQuotation({ adapterId, orderIndex, isManualRequestVerificationRequired, isManualProposalRequired, quote, submitRequest, verifyRequest, rejectRequest, transformItemConfiguration, }: {
4
4
  adapterId: string;
5
+ orderIndex?: number;
5
6
  isManualRequestVerificationRequired?: boolean;
6
7
  isManualProposalRequired?: boolean;
7
8
  quote?: (context: QuotationContext) => Promise<QuotationProposal>;
@@ -1,11 +1,12 @@
1
1
  import { QuotationAdapter, } from "../core-index.js";
2
2
  import { pluginRegistry } from "../plugins/PluginRegistry.js";
3
- export default function registerQuotation({ adapterId, isManualRequestVerificationRequired, isManualProposalRequired, quote, submitRequest, verifyRequest, rejectRequest, transformItemConfiguration, }) {
3
+ export default function registerQuotation({ adapterId, orderIndex, isManualRequestVerificationRequired, isManualProposalRequired, quote, submitRequest, verifyRequest, rejectRequest, transformItemConfiguration, }) {
4
4
  const adapter = {
5
5
  ...QuotationAdapter,
6
6
  key: `shop.unchained.quotation.${adapterId}`,
7
7
  label: 'Quotation: ' + adapterId,
8
8
  version: '1.0.0',
9
+ orderIndex: orderIndex ?? 0,
9
10
  isActivatedFor: () => {
10
11
  return true;
11
12
  },
@@ -36,7 +37,7 @@ export default function registerQuotation({ adapterId, isManualRequestVerificati
36
37
  transformItemConfiguration: async (params) => {
37
38
  return transformItemConfiguration
38
39
  ? transformItemConfiguration(params, context)
39
- : { quantity: params.quantity, configuration: params.configuration };
40
+ : QuotationAdapter.actions(context).transformItemConfiguration(params);
40
41
  },
41
42
  };
42
43
  },
@@ -37,7 +37,7 @@ declare class PluginRegistry {
37
37
  getAdapters(adapterType: symbol): IBaseAdapter[];
38
38
  getAllAdapters(): IBaseAdapter[];
39
39
  hasPlugin(key: string): boolean;
40
- clear(): void;
40
+ clear(): () => void;
41
41
  }
42
42
  export declare const pluginRegistry: PluginRegistry;
43
43
  export {};
@@ -114,9 +114,15 @@ class PluginRegistry {
114
114
  return this.plugins.has(key);
115
115
  }
116
116
  clear() {
117
- this.plugins.clear();
117
+ const { plugins, moduleFactories, skippedPlugins } = this;
118
+ this.plugins = new Map();
118
119
  this.moduleFactories = [];
119
- this.skippedPlugins.clear();
120
+ this.skippedPlugins = new Set();
121
+ return () => {
122
+ this.plugins = plugins;
123
+ this.moduleFactories = moduleFactories;
124
+ this.skippedPlugins = skippedPlugins;
125
+ };
120
126
  }
121
127
  }
122
128
  export const pluginRegistry = new PluginRegistry();
@@ -1,16 +1,12 @@
1
- import type { OrderPosition } from '@unchainedshop/core-orders';
2
- import type { User } from '@unchainedshop/core-users';
1
+ import { type Order, type OrderPosition } from '@unchainedshop/core-orders';
2
+ import { type Quotation } from '@unchainedshop/core-quotations';
3
3
  import type { Modules } from '../modules.ts';
4
- export interface AddCartQuotationParams {
5
- orderId?: string;
6
- quotationId: string;
7
- quantity: number;
8
- configuration: {
4
+ export declare function addCartQuotationService(this: Modules, { order, quotation, quantity, configuration, }: {
5
+ order: Order;
6
+ quotation: Quotation;
7
+ quantity?: number | null;
8
+ configuration?: {
9
9
  key: string;
10
10
  value: string;
11
- }[];
12
- user: User;
13
- countryCode: string;
14
- context: unknown;
15
- }
16
- export declare function addCartQuotationService(this: Modules, params: AddCartQuotationParams): Promise<OrderPosition>;
11
+ }[] | null;
12
+ }, unchainedAPI?: Record<string, any>): Promise<OrderPosition>;
@@ -1,52 +1,57 @@
1
+ import { ordersSettings } from '@unchainedshop/core-orders';
1
2
  import { QuotationStatus } from '@unchainedshop/core-quotations';
2
3
  import { QuotationDirector } from "../directors/QuotationDirector.js";
3
- import { updateCalculationService } from "./updateCalculation.js";
4
- import { findOrInitCartService } from "./findOrInitCart.js";
5
- import { OrderNotFoundError, OrderWrongStatusError, OrderQuantityTooLowError, ProductNotFoundError, QuotationNotFoundError, QuotationWrongStatusError, QuotationItemConfigurationError, } from "../errors/index.js";
6
- export async function addCartQuotationService(params) {
7
- const { orderId, quotationId, quantity, configuration, user, countryCode, context } = params;
8
- if (quantity < 1) {
9
- throw new OrderQuantityTooLowError({ quantity });
10
- }
11
- const quotation = await this.quotations.findQuotation({ quotationId });
12
- if (!quotation) {
13
- throw new QuotationNotFoundError({ quotationId });
14
- }
4
+ import { createServiceError } from "../errors.js";
5
+ import { isQuotationReserved } from "./reserveOrderQuotationsForCheckout.js";
6
+ export async function addCartQuotationService({ order, quotation, quantity, configuration, }, unchainedAPI = {}) {
7
+ const context = { ...unchainedAPI, modules: this };
15
8
  if (quotation.status !== QuotationStatus.PROPOSED) {
16
- throw new QuotationWrongStatusError({ status: quotation.status });
9
+ throw createServiceError('QuotationWrongStatusError', `Quotation ${quotation._id} is ${quotation.status}, not PROPOSED`);
17
10
  }
18
- const order = await findOrInitCartService.bind(this)({
19
- orderId,
20
- user,
21
- countryCode,
22
- });
23
- if (!order) {
24
- throw new OrderNotFoundError({ orderId: orderId || null });
11
+ if (quotation.currencyCode !== order.currencyCode) {
12
+ throw createServiceError('QuotationInvalidError', `Quotation ${quotation._id} is not in the currency of order ${order._id}`);
25
13
  }
26
- if (!this.orders.isCart(order)) {
27
- throw new OrderWrongStatusError({ status: order.status });
14
+ if (await isQuotationReserved.call(this, quotation._id, { exceptOrderId: order._id })) {
15
+ throw createServiceError('QuotationInvalidError', `Quotation ${quotation._id} is reserved by another order`);
28
16
  }
29
- if (!(await this.products.productExists({ productId: quotation.productId }))) {
30
- throw new ProductNotFoundError({ productId: quotation.productId });
17
+ const product = await this.products.findProduct({ productId: quotation.productId });
18
+ if (!product) {
19
+ throw createServiceError('ProductNotFoundError', `Product not found: ${quotation.productId}`);
31
20
  }
21
+ const existingPosition = (await this.orders.positions.findOrderPositions({ orderId: order._id })).find((position) => position.quotationId === quotation._id);
22
+ const existingQuantity = existingPosition?.quantity || 0;
23
+ const requestedQuantity = existingQuantity + (quantity ?? quotation.quantity ?? 1);
32
24
  const director = await QuotationDirector.actions({ quotation }, context);
33
25
  const quotationConfiguration = await director.transformItemConfiguration({
34
- quantity,
35
- configuration,
26
+ quantity: requestedQuantity,
27
+ configuration: configuration ?? existingPosition?.configuration ?? [],
36
28
  });
37
29
  if (!quotationConfiguration) {
38
- throw new QuotationItemConfigurationError({ configuration });
30
+ throw createServiceError('QuotationItemConfigurationError', `Quotation ${quotation._id} does not take quantity ${requestedQuantity}`);
39
31
  }
40
- const updatedOrderPosition = await this.orders.positions.addProductItem({
41
- quantity: quotationConfiguration.quantity || 1,
32
+ const lineQuantity = quotationConfiguration.quantity || requestedQuantity;
33
+ await ordersSettings.validateOrderPosition({
34
+ order,
35
+ product,
42
36
  configuration: quotationConfiguration.configuration,
43
- quotationId,
44
- productId: quotation.productId,
45
- originalProductId: quotation.productId,
46
- orderId: order._id,
47
- });
48
- await updateCalculationService.bind(this)(order._id);
49
- return (await this.orders.positions.findOrderPosition({
50
- itemId: updatedOrderPosition._id,
51
- }));
37
+ quantityDiff: lineQuantity - existingQuantity,
38
+ }, context);
39
+ const position = existingPosition
40
+ ? await this.orders.positions.updateProductItem({
41
+ orderPositionId: existingPosition._id,
42
+ quantity: lineQuantity,
43
+ configuration: quotationConfiguration.configuration ?? null,
44
+ })
45
+ : await this.orders.positions.addProductItem({
46
+ quantity: lineQuantity,
47
+ configuration: quotationConfiguration.configuration,
48
+ quotationId: quotation._id,
49
+ productId: quotation.productId,
50
+ originalProductId: quotation.productId,
51
+ orderId: order._id,
52
+ });
53
+ if (!position) {
54
+ throw createServiceError('OrderItemNotFoundError', `Order item not found: ${existingPosition?._id}`);
55
+ }
56
+ return position;
52
57
  }
@@ -1,6 +1,10 @@
1
1
  import type { Order, OrderDiscount } from '@unchainedshop/core-orders';
2
2
  import type { Modules } from '../modules.ts';
3
- export declare function calculateDiscountTotalService(this: Modules, order: Order, orderDiscount: OrderDiscount): Promise<{
3
+ export declare function calculateDiscountTotalService(this: Modules, order: Order, orderDiscount: OrderDiscount, { useNetPrice }?: {
4
+ useNetPrice?: boolean;
5
+ }): Promise<{
4
6
  amount: number;
5
7
  currencyCode: string;
8
+ isTaxable: boolean;
9
+ isNetPrice: boolean;
6
10
  }>;