@unchainedshop/core 4.8.29 → 4.8.30
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/lib/bulk-importer/handlers/product/upsertVariations.js +5 -3
- package/lib/directors/FileDirector.d.ts +4 -4
- package/lib/directors/FileDirector.js +2 -13
- package/lib/directors/registryDirector.d.ts +2 -0
- package/lib/directors/registryDirector.js +6 -0
- package/lib/factory/registerEnrollment.d.ts +8 -1
- package/lib/factory/registerEnrollment.js +12 -14
- package/lib/factory/registerQuotation.d.ts +2 -1
- package/lib/factory/registerQuotation.js +3 -2
- package/lib/plugins/PluginRegistry.d.ts +2 -1
- package/lib/plugins/PluginRegistry.js +16 -3
- package/lib/services/addCartQuotation.d.ts +9 -13
- package/lib/services/addCartQuotation.js +43 -38
- package/lib/services/reserveOrderDiscountsForCheckout.d.ts +5 -0
- package/lib/services/reserveOrderDiscountsForCheckout.js +34 -0
- package/lib/services/reserveOrderQuotationsForCheckout.d.ts +8 -0
- package/lib/services/reserveOrderQuotationsForCheckout.js +49 -0
- package/lib/services/simulateProductDispatching.d.ts +3 -1
- package/lib/services/simulateProductDispatching.js +3 -3
- package/lib/services/simulateProductInventory.d.ts +3 -1
- package/lib/services/simulateProductInventory.js +3 -3
- package/lib/services/supportedDeliveryProviders.js +9 -0
- package/lib/services/supportedPaymentProviders.js +9 -0
- package/lib/services/supportedWarehousingProviders.js +9 -0
- package/lib/services/suspendEnrollment.d.ts +5 -0
- package/lib/services/suspendEnrollment.js +21 -0
- package/lib/services/updateCartItem.d.ts +7 -7
- package/lib/services/updateCartItem.js +28 -25
- package/lib/services/updateEnrollmentPlan.d.ts +5 -0
- package/lib/services/updateEnrollmentPlan.js +26 -0
- package/package.json +1 -1
- package/lib/errors/common.d.ts +0 -568
- package/lib/errors/common.js +0 -789
- package/lib/errors/index.d.ts +0 -1
- package/lib/errors/index.js +0 -1
- package/lib/services/addCartDiscount.d.ts +0 -10
- package/lib/services/addCartDiscount.js +0 -27
- package/lib/services/addCartItem.d.ts +0 -15
- package/lib/services/addCartItem.js +0 -41
- package/lib/services/addEmail.d.ts +0 -7
- package/lib/services/addEmail.js +0 -17
- package/lib/services/addProductAssignment.d.ts +0 -3
- package/lib/services/addProductAssignment.js +0 -63
- package/lib/services/authenticateWithPassword.d.ts +0 -15
- package/lib/services/authenticateWithPassword.js +0 -34
- package/lib/services/changePassword.d.ts +0 -10
- package/lib/services/changePassword.js +0 -19
- package/lib/services/countOrdersWithFilters.d.ts +0 -3
- package/lib/services/countOrdersWithFilters.js +0 -25
- package/lib/services/createAssortment.d.ts +0 -7
- package/lib/services/createAssortment.js +0 -10
- package/lib/services/createBookmark.d.ts +0 -8
- package/lib/services/createBookmark.js +0 -21
- package/lib/services/createDeliveryProvider.d.ts +0 -6
- package/lib/services/createDeliveryProvider.js +0 -19
- package/lib/services/createEnrollment.d.ts +0 -17
- package/lib/services/createEnrollment.js +0 -33
- package/lib/services/createProduct.d.ts +0 -7
- package/lib/services/createProduct.js +0 -8
- package/lib/services/createProductBundleItem.d.ts +0 -5
- package/lib/services/createProductBundleItem.js +0 -25
- package/lib/services/createProductReview.d.ts +0 -8
- package/lib/services/createProductReview.js +0 -12
- package/lib/services/deleteDeadCarts.d.ts +0 -2
- package/lib/services/deleteDeadCarts.js +0 -17
- package/lib/services/deliverOrder.d.ts +0 -5
- package/lib/services/deliverOrder.js +0 -26
- package/lib/services/findActiveWorkTypes.d.ts +0 -2
- package/lib/services/findActiveWorkTypes.js +0 -6
- package/lib/services/findDeliveryInterfaces.d.ts +0 -7
- package/lib/services/findDeliveryInterfaces.js +0 -13
- package/lib/services/findOrdersWithFilters.d.ts +0 -9
- package/lib/services/findOrdersWithFilters.js +0 -25
- package/lib/services/findPaymentInterfaces.d.ts +0 -7
- package/lib/services/findPaymentInterfaces.js +0 -13
- package/lib/services/findWarehousingInterfaces.d.ts +0 -7
- package/lib/services/findWarehousingInterfaces.js +0 -13
- package/lib/services/getDeliveryDiscounts.d.ts +0 -8
- package/lib/services/getDeliveryDiscounts.js +0 -14
- package/lib/services/getPaymentDiscounts.d.ts +0 -8
- package/lib/services/getPaymentDiscounts.js +0 -14
- package/lib/services/getShopInfo.d.ts +0 -25
- package/lib/services/getShopInfo.js +0 -47
- package/lib/services/impersonateUser.d.ts +0 -8
- package/lib/services/impersonateUser.js +0 -20
- package/lib/services/loginAsGuest.d.ts +0 -10
- package/lib/services/loginAsGuest.js +0 -19
- package/lib/services/obfuscateWorkerData.d.ts +0 -4
- package/lib/services/obfuscateWorkerData.js +0 -4
- package/lib/services/orderPricingAccessors.d.ts +0 -18
- package/lib/services/orderPricingAccessors.js +0 -29
- package/lib/services/payOrder.d.ts +0 -5
- package/lib/services/payOrder.js +0 -25
- package/lib/services/registerUser.d.ts +0 -14
- package/lib/services/registerUser.js +0 -44
- package/lib/services/removeBookmark.d.ts +0 -6
- package/lib/services/removeBookmark.js +0 -10
- package/lib/services/removeCartItem.d.ts +0 -6
- package/lib/services/removeCartItem.js +0 -19
- package/lib/services/resetPasswordWithMigration.d.ts +0 -14
- package/lib/services/resetPasswordWithMigration.js +0 -31
- package/lib/services/resolveWorkType.d.ts +0 -1
- package/lib/services/resolveWorkType.js +0 -7
- package/lib/services/searchProductsWithAssortment.d.ts +0 -19
- package/lib/services/searchProductsWithAssortment.js +0 -17
- package/lib/services/toggleBookmark.d.ts +0 -8
- package/lib/services/toggleBookmark.js +0 -33
- package/lib/services/updateCart.d.ts +0 -15
- package/lib/services/updateCart.js +0 -27
- package/lib/services/updateEnrollment.d.ts +0 -13
- package/lib/services/updateEnrollment.js +0 -36
- package/lib/services/verifyEmailWithMigration.d.ts +0 -15
- package/lib/services/verifyEmailWithMigration.js +0 -23
|
@@ -28,11 +28,13 @@ const upsert = async (productVariation, unchainedAPI) => {
|
|
|
28
28
|
};
|
|
29
29
|
export default async function upsertVariations({ variations, productId }, unchainedAPI) {
|
|
30
30
|
const { modules } = unchainedAPI;
|
|
31
|
-
const upsertedProductVariationIds =
|
|
31
|
+
const upsertedProductVariationIds = [];
|
|
32
|
+
for (const [index, { content, options, ...variationsRest }] of variations.entries()) {
|
|
32
33
|
const variation = await upsert({
|
|
33
34
|
...variationsRest,
|
|
34
35
|
options: options.map((option) => option.value),
|
|
35
36
|
productId,
|
|
37
|
+
sortKey: index + 1,
|
|
36
38
|
}, unchainedAPI);
|
|
37
39
|
await Promise.all(options.map(async ({ content: optionContent, value: optionValue }) => {
|
|
38
40
|
if (optionContent) {
|
|
@@ -52,8 +54,8 @@ export default async function upsertVariations({ variations, productId }, unchai
|
|
|
52
54
|
};
|
|
53
55
|
}));
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
+
upsertedProductVariationIds.push(variation._id);
|
|
58
|
+
}
|
|
57
59
|
const allVariations = await modules.products.variations.findProductVariations({
|
|
58
60
|
productId,
|
|
59
61
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { IFileAdapter, UploadedFile } from '@unchainedshop/core-files';
|
|
2
2
|
export type UploadFileCallback<UnchainedAPI = unknown> = (file: UploadedFile, unchainedAPI: UnchainedAPI) => Promise<void>;
|
|
3
3
|
export declare const FileDirector: {
|
|
4
|
-
getAdapter: (key: string) => IFileAdapter<unknown> | null;
|
|
5
|
-
getAdapters: (options?: {
|
|
6
|
-
adapterFilter?: (adapter: IFileAdapter) => boolean;
|
|
7
|
-
}) => IFileAdapter<unknown>[];
|
|
8
4
|
registerFileUploadCallback<UnchainedAPI>(directoryName: string, fn: UploadFileCallback<UnchainedAPI>): void;
|
|
9
5
|
getFileUploadCallback(directoryName: string): UploadFileCallback<unknown> | null;
|
|
6
|
+
getAdapters: (options?: {
|
|
7
|
+
adapterFilter?: ((adapter: IFileAdapter<unknown>) => boolean) | undefined;
|
|
8
|
+
} | undefined) => IFileAdapter<unknown>[];
|
|
9
|
+
getAdapter: (key: string) => IFileAdapter<unknown> | null;
|
|
10
10
|
};
|
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
import { BaseDirector } from '@unchainedshop/utils';
|
|
2
1
|
import { FileAdapter } from '@unchainedshop/core-files';
|
|
3
|
-
import {
|
|
2
|
+
import { registryDirector } from "./registryDirector.js";
|
|
4
3
|
const FileUploadRegistry = new Map();
|
|
5
|
-
const baseDirector = BaseDirector('FileDirector');
|
|
6
4
|
export const FileDirector = {
|
|
7
|
-
...
|
|
8
|
-
getAdapter: (key) => {
|
|
9
|
-
const adapters = pluginRegistry.getAdapters(FileAdapter.adapterType);
|
|
10
|
-
return adapters.find((adapter) => adapter.key === key) || null;
|
|
11
|
-
},
|
|
12
|
-
getAdapters: (options) => {
|
|
13
|
-
const { adapterFilter } = options || {};
|
|
14
|
-
const adapters = pluginRegistry.getAdapters(FileAdapter.adapterType);
|
|
15
|
-
return adapters.filter(adapterFilter || (() => true));
|
|
16
|
-
},
|
|
5
|
+
...registryDirector(FileAdapter.adapterType),
|
|
17
6
|
registerFileUploadCallback(directoryName, fn) {
|
|
18
7
|
if (!FileUploadRegistry.has(directoryName)) {
|
|
19
8
|
FileUploadRegistry.set(directoryName, fn);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { pluginRegistry } from "../plugins/PluginRegistry.js";
|
|
2
|
+
export const registryDirector = (adapterType) => ({
|
|
3
|
+
getAdapter: (key) => pluginRegistry.getAdapters(adapterType).find((adapter) => adapter.key === key) ||
|
|
4
|
+
null,
|
|
5
|
+
getAdapters: ({ adapterFilter } = {}) => pluginRegistry.getAdapters(adapterType).filter(adapterFilter || (() => true)),
|
|
6
|
+
});
|
|
@@ -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
|
-
...
|
|
20
|
-
configurationForOrder:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
:
|
|
40
|
+
: QuotationAdapter.actions(context).transformItemConfiguration(params);
|
|
40
41
|
},
|
|
41
42
|
};
|
|
42
43
|
},
|
|
@@ -35,8 +35,9 @@ declare class PluginRegistry {
|
|
|
35
35
|
getPlugin(key: string): IPlugin | undefined;
|
|
36
36
|
getAllPlugins(): IPlugin[];
|
|
37
37
|
getAdapters(adapterType: symbol): IBaseAdapter[];
|
|
38
|
+
getAllAdapters(): IBaseAdapter[];
|
|
38
39
|
hasPlugin(key: string): boolean;
|
|
39
|
-
clear(): void;
|
|
40
|
+
clear(): () => void;
|
|
40
41
|
}
|
|
41
42
|
export declare const pluginRegistry: PluginRegistry;
|
|
42
43
|
export {};
|
|
@@ -5,9 +5,11 @@ class PluginRegistry {
|
|
|
5
5
|
moduleFactories = [];
|
|
6
6
|
skippedPlugins = new Set();
|
|
7
7
|
register(plugin) {
|
|
8
|
-
|
|
8
|
+
const registered = this.plugins.get(plugin.key);
|
|
9
|
+
if (registered) {
|
|
9
10
|
logger.warn(`Plugin ${plugin.key} already registered, skipping`, {
|
|
10
11
|
label: plugin.label,
|
|
12
|
+
registeredLabel: registered.label,
|
|
11
13
|
});
|
|
12
14
|
return;
|
|
13
15
|
}
|
|
@@ -103,13 +105,24 @@ class PluginRegistry {
|
|
|
103
105
|
}
|
|
104
106
|
return result.toSorted((left, right) => (left.orderIndex ?? 0) - (right.orderIndex ?? 0));
|
|
105
107
|
}
|
|
108
|
+
getAllAdapters() {
|
|
109
|
+
return this.getAllPlugins()
|
|
110
|
+
.filter((plugin) => !this.skippedPlugins.has(plugin.key))
|
|
111
|
+
.flatMap((plugin) => plugin.adapters ?? []);
|
|
112
|
+
}
|
|
106
113
|
hasPlugin(key) {
|
|
107
114
|
return this.plugins.has(key);
|
|
108
115
|
}
|
|
109
116
|
clear() {
|
|
110
|
-
this
|
|
117
|
+
const { plugins, moduleFactories, skippedPlugins } = this;
|
|
118
|
+
this.plugins = new Map();
|
|
111
119
|
this.moduleFactories = [];
|
|
112
|
-
this.skippedPlugins
|
|
120
|
+
this.skippedPlugins = new Set();
|
|
121
|
+
return () => {
|
|
122
|
+
this.plugins = plugins;
|
|
123
|
+
this.moduleFactories = moduleFactories;
|
|
124
|
+
this.skippedPlugins = skippedPlugins;
|
|
125
|
+
};
|
|
113
126
|
}
|
|
114
127
|
}
|
|
115
128
|
export const pluginRegistry = new PluginRegistry();
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
quantity
|
|
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
|
-
|
|
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 {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
9
|
+
throw createServiceError('QuotationWrongStatusError', `Quotation ${quotation._id} is ${quotation.status}, not PROPOSED`);
|
|
17
10
|
}
|
|
18
|
-
|
|
19
|
-
|
|
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 (
|
|
27
|
-
throw
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
30
|
+
throw createServiceError('QuotationItemConfigurationError', `Quotation ${quotation._id} does not take quantity ${requestedQuantity}`);
|
|
39
31
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
32
|
+
const lineQuantity = quotationConfiguration.quantity || requestedQuantity;
|
|
33
|
+
await ordersSettings.validateOrderPosition({
|
|
34
|
+
order,
|
|
35
|
+
product,
|
|
42
36
|
configuration: quotationConfiguration.configuration,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import { OrderDiscountDirector } from "../directors/OrderDiscountDirector.js";
|
|
3
|
+
const logger = createLogger('unchained:core:checkoutOrder');
|
|
4
|
+
export async function reserveOrderDiscountsForCheckout(order) {
|
|
5
|
+
const reservations = [];
|
|
6
|
+
const release = async () => {
|
|
7
|
+
const results = await Promise.allSettled(reservations.map((reservation) => reservation.release()));
|
|
8
|
+
for (const result of results) {
|
|
9
|
+
if (result.status === 'rejected') {
|
|
10
|
+
logger.error(`Could not release a discount reservation of order ${order._id}`, {
|
|
11
|
+
error: result.reason,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
const discounts = await this.orders.discounts.findOrderDiscounts({ orderId: order._id });
|
|
18
|
+
for (const orderDiscount of discounts) {
|
|
19
|
+
const Adapter = OrderDiscountDirector.getAdapter(orderDiscount.discountKey);
|
|
20
|
+
if (!Adapter)
|
|
21
|
+
continue;
|
|
22
|
+
const adapter = await Adapter.actions({
|
|
23
|
+
context: { order, orderDiscount, code: orderDiscount.code, modules: this },
|
|
24
|
+
});
|
|
25
|
+
if (adapter.reserveForCheckout)
|
|
26
|
+
reservations.push(await adapter.reserveForCheckout());
|
|
27
|
+
}
|
|
28
|
+
return { release };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
await release();
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -3,10 +3,12 @@ import type { Product } from '@unchainedshop/core-products';
|
|
|
3
3
|
import { type EstimatedDispatch } from '../directors/WarehousingDirector.ts';
|
|
4
4
|
import type { WarehousingContext } from '../directors/WarehousingAdapter.ts';
|
|
5
5
|
import type { WarehousingProvider } from '@unchainedshop/core-warehousing';
|
|
6
|
-
|
|
6
|
+
import type { DeliveryProviderType } from '@unchainedshop/core-delivery';
|
|
7
|
+
export declare function simulateProductDispatchingService(this: Modules, { product, quantity, referenceDate, deliveryProviderType, }: {
|
|
7
8
|
product: Product;
|
|
8
9
|
quantity: number;
|
|
9
10
|
referenceDate?: Date;
|
|
11
|
+
deliveryProviderType?: DeliveryProviderType | null;
|
|
10
12
|
}): Promise<(WarehousingContext & EstimatedDispatch & {
|
|
11
13
|
warehousingProvider: WarehousingProvider;
|
|
12
14
|
})[]>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { supportedWarehousingProvidersService } from "./supportedWarehousingProviders.js";
|
|
2
2
|
import { WarehousingDirector } from "../directors/WarehousingDirector.js";
|
|
3
|
-
export async function simulateProductDispatchingService({ product, quantity, referenceDate, }) {
|
|
4
|
-
const deliveryProviders = await this.delivery.allProviders();
|
|
3
|
+
export async function simulateProductDispatchingService({ product, quantity, referenceDate, deliveryProviderType, }) {
|
|
4
|
+
const deliveryProviders = (await this.delivery.allProviders()).filter(({ type }) => !deliveryProviderType || type === deliveryProviderType);
|
|
5
5
|
return deliveryProviders.reduce(async (oldResult, deliveryProvider) => {
|
|
6
6
|
const result = await oldResult;
|
|
7
7
|
const warehousingProviders = await supportedWarehousingProvidersService.bind(this)({
|
|
@@ -25,6 +25,6 @@ export async function simulateProductDispatchingService({ product, quantity, ref
|
|
|
25
25
|
...dispatch,
|
|
26
26
|
};
|
|
27
27
|
}));
|
|
28
|
-
return result.concat(
|
|
28
|
+
return result.concat(mappedWarehousingProviders);
|
|
29
29
|
}, Promise.resolve([]));
|
|
30
30
|
}
|
|
@@ -2,9 +2,11 @@ import type { Modules } from '../modules.ts';
|
|
|
2
2
|
import type { Product } from '@unchainedshop/core-products';
|
|
3
3
|
import type { WarehousingContext } from '../directors/WarehousingAdapter.ts';
|
|
4
4
|
import type { WarehousingProvider } from '@unchainedshop/core-warehousing';
|
|
5
|
-
|
|
5
|
+
import type { DeliveryProviderType } from '@unchainedshop/core-delivery';
|
|
6
|
+
export declare function simulateProductInventoryService(this: Modules, { product, referenceDate, deliveryProviderType, }: {
|
|
6
7
|
product: Product;
|
|
7
8
|
referenceDate?: Date;
|
|
9
|
+
deliveryProviderType?: DeliveryProviderType | null;
|
|
8
10
|
}): Promise<(WarehousingContext & {
|
|
9
11
|
quantity?: number;
|
|
10
12
|
} & {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { supportedWarehousingProvidersService } from "./supportedWarehousingProviders.js";
|
|
2
2
|
import { WarehousingDirector } from "../directors/WarehousingDirector.js";
|
|
3
|
-
export async function simulateProductInventoryService({ product, referenceDate, }) {
|
|
4
|
-
const deliveryProviders = await this.delivery.allProviders();
|
|
3
|
+
export async function simulateProductInventoryService({ product, referenceDate, deliveryProviderType, }) {
|
|
4
|
+
const deliveryProviders = (await this.delivery.allProviders()).filter(({ type }) => !deliveryProviderType || type === deliveryProviderType);
|
|
5
5
|
return deliveryProviders.reduce(async (oldResult, deliveryProvider) => {
|
|
6
6
|
const result = await oldResult;
|
|
7
7
|
const warehousingProviders = await supportedWarehousingProvidersService.bind(this)({
|
|
@@ -24,6 +24,6 @@ export async function simulateProductInventoryService({ product, referenceDate,
|
|
|
24
24
|
...stock,
|
|
25
25
|
};
|
|
26
26
|
}));
|
|
27
|
-
return result.concat(
|
|
27
|
+
return result.concat(mappedWarehousingProviders);
|
|
28
28
|
}, Promise.resolve([]));
|
|
29
29
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { deliverySettings } from '@unchainedshop/core-delivery';
|
|
2
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
3
|
import { DeliveryDirector } from "../directors/index.js";
|
|
4
|
+
const logger = createLogger('unchained:core');
|
|
3
5
|
export async function supportedDeliveryProvidersService(params) {
|
|
4
6
|
const allProviders = await this.delivery.allProviders();
|
|
5
7
|
const providers = (await Promise.all(allProviders.map(async (provider) => {
|
|
8
|
+
if (!DeliveryDirector.getAdapter(provider.adapterKey)) {
|
|
9
|
+
logger.warn('Delivery provider skipped, its adapter is not registered', {
|
|
10
|
+
deliveryProviderId: provider._id,
|
|
11
|
+
adapterKey: provider.adapterKey,
|
|
12
|
+
});
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
6
15
|
const adapter = await DeliveryDirector.actions(provider, params, { modules: this });
|
|
7
16
|
return adapter.isActive() ? [provider] : [];
|
|
8
17
|
}))).flat();
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { paymentSettings } from '@unchainedshop/core-payment';
|
|
2
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
3
|
import { PaymentDirector } from "../directors/PaymentDirector.js";
|
|
4
|
+
const logger = createLogger('unchained:core');
|
|
3
5
|
export async function supportedPaymentProvidersService(params) {
|
|
4
6
|
const allProviders = await this.payment.paymentProviders.allProviders();
|
|
5
7
|
const providers = (await Promise.all(allProviders.map(async (provider) => {
|
|
8
|
+
if (!PaymentDirector.getAdapter(provider.adapterKey)) {
|
|
9
|
+
logger.warn('Payment provider skipped, its adapter is not registered', {
|
|
10
|
+
paymentProviderId: provider._id,
|
|
11
|
+
adapterKey: provider.adapterKey,
|
|
12
|
+
});
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
6
15
|
const adapter = await PaymentDirector.actions(provider, params, { modules: this });
|
|
7
16
|
return adapter.isActive() ? [provider] : [];
|
|
8
17
|
}))).flat();
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
1
2
|
import { WarehousingDirector } from "../directors/index.js";
|
|
3
|
+
const logger = createLogger('unchained:core');
|
|
2
4
|
export async function supportedWarehousingProvidersService(params) {
|
|
3
5
|
const allProviders = await this.warehousing.allProviders();
|
|
4
6
|
const providers = (await Promise.all(allProviders.map(async (provider) => {
|
|
7
|
+
if (!WarehousingDirector.getAdapter(provider.adapterKey)) {
|
|
8
|
+
logger.warn('Warehousing provider skipped, its adapter is not registered', {
|
|
9
|
+
warehousingProviderId: provider._id,
|
|
10
|
+
adapterKey: provider.adapterKey,
|
|
11
|
+
});
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
5
14
|
const adapter = await WarehousingDirector.actions(provider, params, { modules: this });
|
|
6
15
|
return adapter.isActive() ? [provider] : [];
|
|
7
16
|
}))).flat();
|
|
@@ -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,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
|
|
4
|
-
|
|
5
|
-
|
|
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>;
|