@unchainedshop/core 4.4.0 → 4.6.0

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 (53) hide show
  1. package/lib/bulk-importer/handlers/assortment/create.d.ts +0 -2
  2. package/lib/bulk-importer/handlers/assortment/create.js +0 -1
  3. package/lib/bulk-importer/handlers/assortment/update.d.ts +0 -2
  4. package/lib/bulk-importer/handlers/assortment/update.js +0 -1
  5. package/lib/bulk-importer/handlers/product/create.js +2 -2
  6. package/lib/bulk-importer/handlers/product/update.js +2 -2
  7. package/lib/core-index.d.ts +1 -0
  8. package/lib/core-index.js +1 -0
  9. package/lib/directors/BaseWorker.js +2 -2
  10. package/lib/directors/DeliveryPricingAdapter.d.ts +1 -1
  11. package/lib/directors/DeliveryPricingDirector.d.ts +1 -1
  12. package/lib/directors/EnrollmentDirector.d.ts +4 -1
  13. package/lib/directors/IntervalWorker.d.ts +2 -2
  14. package/lib/directors/IntervalWorker.js +4 -4
  15. package/lib/directors/PaymentDirector.js +2 -0
  16. package/lib/directors/PaymentPricingAdapter.d.ts +2 -2
  17. package/lib/directors/PaymentPricingAdapter.js +1 -1
  18. package/lib/directors/PaymentPricingDirector.d.ts +1 -1
  19. package/lib/directors/WorkerDirector.d.ts +1 -1
  20. package/lib/services/addMultipleCartProducts.d.ts +18 -0
  21. package/lib/services/addMultipleCartProducts.js +25 -0
  22. package/lib/services/createFileDownloadURL.d.ts +7 -0
  23. package/lib/services/createFileDownloadURL.js +10 -0
  24. package/lib/services/findProductSiblings.d.ts +9 -0
  25. package/lib/services/findProductSiblings.js +18 -0
  26. package/lib/services/fulfillQuotation.d.ts +3 -0
  27. package/lib/services/{fullfillQuotation.js → fulfillQuotation.js} +3 -3
  28. package/lib/services/index.d.ts +28 -2
  29. package/lib/services/index.js +28 -2
  30. package/lib/services/isTokenInvalidateable.d.ts +5 -0
  31. package/lib/services/isTokenInvalidateable.js +15 -0
  32. package/lib/services/processOrder.js +17 -9
  33. package/lib/services/rejectQuotation.js +1 -1
  34. package/lib/services/removeCartDiscount.d.ts +10 -0
  35. package/lib/services/removeCartDiscount.js +14 -0
  36. package/lib/services/removeFiles.js +1 -1
  37. package/lib/services/removeFilter.d.ts +5 -0
  38. package/lib/services/removeFilter.js +5 -0
  39. package/lib/services/resolveOrderItemDispatches.d.ts +19 -0
  40. package/lib/services/resolveOrderItemDispatches.js +38 -0
  41. package/lib/services/resolveTokenStatus.d.ts +5 -0
  42. package/lib/services/resolveTokenStatus.js +15 -0
  43. package/lib/services/simulateConfigurablePriceRange.d.ts +13 -0
  44. package/lib/services/simulateConfigurablePriceRange.js +39 -0
  45. package/lib/services/simulateDeliveryPricing.d.ts +21 -0
  46. package/lib/services/simulateDeliveryPricing.js +23 -0
  47. package/lib/services/simulatePaymentPricing.d.ts +21 -0
  48. package/lib/services/simulatePaymentPricing.js +23 -0
  49. package/lib/services/updateUserAvatarAfterUpload.js +2 -2
  50. package/lib/utils/schedule.d.ts +22 -0
  51. package/lib/utils/schedule.js +148 -0
  52. package/package.json +22 -22
  53. package/lib/services/fullfillQuotation.d.ts +0 -3
@@ -5,7 +5,6 @@ export declare const AssortmentCreatePayloadSchema: z.ZodObject<{
5
5
  _id: z.ZodString;
6
6
  specification: z.ZodObject<{
7
7
  isActive: z.ZodBoolean;
8
- isBase: z.ZodOptional<z.ZodBoolean>;
9
8
  isRoot: z.ZodOptional<z.ZodBoolean>;
10
9
  sequence: z.ZodNumber;
11
10
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -71,7 +70,6 @@ declare namespace createAssortment {
71
70
  _id: z.ZodString;
72
71
  specification: z.ZodObject<{
73
72
  isActive: z.ZodBoolean;
74
- isBase: z.ZodOptional<z.ZodBoolean>;
75
73
  isRoot: z.ZodOptional<z.ZodBoolean>;
76
74
  sequence: z.ZodNumber;
77
75
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -8,7 +8,6 @@ export const AssortmentCreatePayloadSchema = z.object({
8
8
  _id: z.string(),
9
9
  specification: z.object({
10
10
  isActive: z.boolean(),
11
- isBase: z.boolean().optional(),
12
11
  isRoot: z.boolean().optional(),
13
12
  sequence: z.number(),
14
13
  tags: z.array(z.string()).optional(),
@@ -5,7 +5,6 @@ export declare const AssortmentUpdatePayloadSchema: z.ZodObject<{
5
5
  _id: z.ZodString;
6
6
  specification: z.ZodOptional<z.ZodObject<{
7
7
  isActive: z.ZodOptional<z.ZodBoolean>;
8
- isBase: z.ZodOptional<z.ZodBoolean>;
9
8
  isRoot: z.ZodOptional<z.ZodBoolean>;
10
9
  sequence: z.ZodOptional<z.ZodNumber>;
11
10
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -72,7 +71,6 @@ declare namespace updateAssortment {
72
71
  _id: z.ZodString;
73
72
  specification: z.ZodOptional<z.ZodObject<{
74
73
  isActive: z.ZodOptional<z.ZodBoolean>;
75
- isBase: z.ZodOptional<z.ZodBoolean>;
76
74
  isRoot: z.ZodOptional<z.ZodBoolean>;
77
75
  sequence: z.ZodOptional<z.ZodNumber>;
78
76
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -10,7 +10,6 @@ export const AssortmentUpdatePayloadSchema = z.object({
10
10
  specification: z
11
11
  .object({
12
12
  isActive: z.boolean().optional(),
13
- isBase: z.boolean().optional(),
14
13
  isRoot: z.boolean().optional(),
15
14
  sequence: z.number().optional(),
16
15
  tags: z.array(z.string()).optional(),
@@ -17,8 +17,8 @@ export const ProductCreateSpecificationSchema = z.object({
17
17
  maxQuantity: z.number().optional(),
18
18
  isTaxable: z.boolean().optional(),
19
19
  isNetPrice: z.boolean().optional(),
20
- currencyCode: z.string(),
21
- countryCode: z.string(),
20
+ currencyCode: z.string().min(1, 'currencyCode is required'),
21
+ countryCode: z.string().min(1, 'countryCode is required'),
22
22
  })),
23
23
  })
24
24
  .optional(),
@@ -16,8 +16,8 @@ export const ProductUpdateSpecificationSchema = z.object({
16
16
  maxQuantity: z.number().optional(),
17
17
  isTaxable: z.boolean().optional(),
18
18
  isNetPrice: z.boolean().optional(),
19
- currencyCode: z.string(),
20
- countryCode: z.string(),
19
+ currencyCode: z.string().min(1, 'currencyCode is required'),
20
+ countryCode: z.string().min(1, 'countryCode is required'),
21
21
  })),
22
22
  })
23
23
  .optional(),
@@ -7,6 +7,7 @@ export * from './bulk-importer/index.ts';
7
7
  export * from './services/index.ts';
8
8
  export * from './directors/index.ts';
9
9
  export * from './factory/index.ts';
10
+ export { default as schedule, type ScheduleData } from './utils/schedule.ts';
10
11
  export interface UnchainedCoreOptions {
11
12
  db: mongodb.Db;
12
13
  migrationRepository: MigrationRepository<UnchainedCore>;
package/lib/core-index.js CHANGED
@@ -7,6 +7,7 @@ export * from "./bulk-importer/index.js";
7
7
  export * from "./services/index.js";
8
8
  export * from "./directors/index.js";
9
9
  export * from "./factory/index.js";
10
+ export { default as schedule } from "./utils/schedule.js";
10
11
  export const initCore = async ({ db, migrationRepository, bulkImporter: bulkImporterOptions = {}, modules: customModules = {}, services: customServices = {}, options = {}, }) => {
11
12
  const bulkImporter = createBulkImporterFactory(db, bulkImporterOptions);
12
13
  const modules = await initModules({ db, migrationRepository, options }, customModules);
@@ -1,4 +1,4 @@
1
- import later from '@breejs/later';
1
+ import schedule from "../utils/schedule.js";
2
2
  import { WorkerDirector } from "./WorkerDirector.js";
3
3
  export const BaseWorker = {
4
4
  key: 'shop.unchained.worker.base',
@@ -37,7 +37,7 @@ export const BaseWorker = {
37
37
  }
38
38
  const fixedSchedule = { ...workConfig.schedule };
39
39
  fixedSchedule.schedules[0].s = [0];
40
- const nextDate = later.schedule(fixedSchedule).next(1, referenceDate);
40
+ const nextDate = schedule.schedule(fixedSchedule).next(1, referenceDate);
41
41
  nextDate.setMilliseconds(0);
42
42
  const workData = {
43
43
  type: workConfig.type,
@@ -37,7 +37,7 @@ export interface DeliveryPricingAdapterContext extends BasePricingAdapterContext
37
37
  currencyCode?: string;
38
38
  provider: DeliveryProvider;
39
39
  providerContext?: any;
40
- order: Order;
40
+ order?: Order;
41
41
  orderDelivery?: OrderDelivery;
42
42
  user: User;
43
43
  discounts: OrderDiscount[];
@@ -9,7 +9,7 @@ export type DeliveryPricingContext = {
9
9
  countryCode?: string;
10
10
  provider: DeliveryProvider;
11
11
  providerContext?: any;
12
- order: Order;
12
+ order?: Order;
13
13
  user: User;
14
14
  } | {
15
15
  currencyCode: string;
@@ -3,11 +3,14 @@ import type { EnrollmentAdapterActions, EnrollmentContext, IEnrollmentAdapter }
3
3
  import type { OrderPosition } from '@unchainedshop/core-orders';
4
4
  import type { Product } from '@unchainedshop/core-products';
5
5
  import type { Enrollment } from '@unchainedshop/core-enrollments';
6
+ import type { Modules } from '../modules.ts';
6
7
  export type IEnrollmentDirector = IBaseDirector<IEnrollmentAdapter> & {
7
8
  transformOrderItemToEnrollment: (item: {
8
9
  orderPosition: OrderPosition;
9
10
  product: Product;
10
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'>>;
11
- actions: (enrollmentContext: EnrollmentContext, unchainedAPI: any) => Promise<EnrollmentAdapterActions>;
12
+ actions: (enrollmentContext: EnrollmentContext, unchainedAPI: {
13
+ modules: Modules;
14
+ }) => Promise<EnrollmentAdapterActions>;
12
15
  };
13
16
  export declare const EnrollmentDirector: IEnrollmentDirector;
@@ -1,9 +1,9 @@
1
- import { type ScheduleData } from '@breejs/later';
1
+ import { type ScheduleData } from '../utils/schedule.ts';
2
2
  import { type IWorker } from './BaseWorker.ts';
3
3
  export interface IntervalWorkerParams {
4
4
  workerId?: string;
5
5
  batchCount?: number;
6
6
  schedule?: ScheduleData;
7
7
  }
8
- export declare const scheduleToInterval: (schedule: ScheduleData) => number;
8
+ export declare const scheduleToInterval: (scheduleData: ScheduleData) => number;
9
9
  export declare const IntervalWorker: IWorker<IntervalWorkerParams>;
@@ -1,12 +1,12 @@
1
- import later, {} from '@breejs/later';
1
+ import schedule, {} from "../utils/schedule.js";
2
2
  import { BaseWorker } from "./BaseWorker.js";
3
3
  import { createLogger } from '@unchainedshop/logger';
4
4
  const { NODE_ENV } = process.env;
5
5
  const logger = createLogger('unchained:worker:interval');
6
- const defaultSchedule = later.parse.text(NODE_ENV !== 'production' ? 'every 2 seconds' : 'every 30 seconds');
7
- export const scheduleToInterval = (schedule) => {
6
+ const defaultSchedule = schedule.parse.text(NODE_ENV !== 'production' ? 'every 2 seconds' : 'every 30 seconds');
7
+ export const scheduleToInterval = (scheduleData) => {
8
8
  const referenceDate = new Date(1000);
9
- const nextDates = later.schedule(schedule).next(2, referenceDate);
9
+ const nextDates = schedule.schedule(scheduleData).next(2, referenceDate);
10
10
  if (!nextDates || nextDates.length < 2) {
11
11
  throw new Error('Schedule must produce at least 2 consecutive dates');
12
12
  }
@@ -150,6 +150,8 @@ export const PaymentDirector = {
150
150
  const paymentProvider = await modules.payment.paymentProviders.findProvider({
151
151
  paymentProviderId: orderPayment.paymentProviderId,
152
152
  });
153
+ if (!paymentProvider)
154
+ throw new Error('Payment provider not found: ' + orderPayment.paymentProviderId);
153
155
  const actions = await PaymentDirector.actions(paymentProvider, buildPaymentProviderActionsContext(orderPayment, {
154
156
  ...context,
155
157
  transactionContext: {
@@ -12,10 +12,10 @@ export interface PaymentPricingCalculation extends PricingCalculation {
12
12
  }
13
13
  export interface PaymentPricingAdapterContext extends BasePricingAdapterContext {
14
14
  countryCode?: string;
15
- currency?: string;
15
+ currencyCode?: string;
16
16
  user: User | null;
17
17
  orderPayment?: OrderPayment;
18
- order: Order;
18
+ order?: Order;
19
19
  provider: PaymentProvider;
20
20
  discounts: OrderDiscount[];
21
21
  }
@@ -8,7 +8,7 @@ export const PaymentPricingAdapter = {
8
8
  },
9
9
  actions: (params) => {
10
10
  const { context } = params;
11
- const { currencyCode } = context.order;
11
+ const currencyCode = context.currencyCode || context.order?.currencyCode;
12
12
  const baseActions = basePricingAdapter.actions(params);
13
13
  const resultSheet = PaymentPricingSheet({ currencyCode });
14
14
  return {
@@ -6,7 +6,7 @@ export type PaymentPricingContext = {
6
6
  countryCode?: string;
7
7
  currencyCode?: string;
8
8
  user?: User;
9
- order: Order;
9
+ order?: Order;
10
10
  provider: PaymentProvider;
11
11
  providerContext?: any;
12
12
  } | {
@@ -1,6 +1,6 @@
1
1
  import type { Work, WorkData, WorkResult } from '@unchainedshop/core-worker';
2
2
  import { type IBaseDirector } from '@unchainedshop/utils';
3
- import type { ScheduleData } from '@breejs/later';
3
+ import type { ScheduleData } from '../utils/schedule.ts';
4
4
  import type { IWorkerAdapter } from './WorkerAdapter.ts';
5
5
  import type { Modules } from '../modules.ts';
6
6
  export type WorkScheduleConfiguration = Pick<WorkData, 'timeout' | 'retries' | 'priority' | 'worker' | 'scheduleId'> & {
@@ -0,0 +1,18 @@
1
+ import type { Modules } from '../modules.ts';
2
+ import type { Order } from '@unchainedshop/core-orders';
3
+ export declare function addMultipleCartProductsService(this: Modules, { orderId, items, context, }: {
4
+ orderId: string;
5
+ items: {
6
+ productId: string;
7
+ quantity: number;
8
+ configuration?: {
9
+ key: string;
10
+ value: string;
11
+ }[];
12
+ }[];
13
+ context: {
14
+ localeContext: Intl.Locale;
15
+ userId?: string;
16
+ countryCode?: string;
17
+ };
18
+ }): Promise<Order | null>;
@@ -0,0 +1,25 @@
1
+ import { ordersSettings } from '@unchainedshop/core-orders';
2
+ export async function addMultipleCartProductsService({ orderId, items, context, }) {
3
+ const order = await this.orders.findOrder({ orderId });
4
+ if (!order)
5
+ return null;
6
+ for (const { productId, quantity, configuration } of items) {
7
+ const originalProduct = await this.products.findProduct({ productId });
8
+ if (!originalProduct) {
9
+ throw new Error(`Product not found: ${productId}`);
10
+ }
11
+ if (quantity < 1) {
12
+ throw new Error(`Invalid quantity for product: ${productId}`);
13
+ }
14
+ const product = await this.products.resolveOrderableProduct(originalProduct, { configuration });
15
+ await ordersSettings.validateOrderPosition({ order, product, configuration, quantityDiff: quantity }, { modules: this, ...context });
16
+ await this.orders.positions.addProductItem({
17
+ quantity,
18
+ configuration,
19
+ originalProductId: originalProduct._id,
20
+ productId: product._id,
21
+ orderId: order._id,
22
+ });
23
+ }
24
+ return order;
25
+ }
@@ -0,0 +1,7 @@
1
+ import { type File } from '@unchainedshop/core-files';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function createFileDownloadURLService(this: Modules, { file, expires, params, }: {
4
+ file: File;
5
+ expires?: number;
6
+ params?: Record<string, any>;
7
+ }): Promise<string | null>;
@@ -0,0 +1,10 @@
1
+ import { getFileAdapter } from '@unchainedshop/core-files';
2
+ export async function createFileDownloadURLService({ file, expires, params = {}, }) {
3
+ if (!file)
4
+ return null;
5
+ const fileUploadAdapter = getFileAdapter();
6
+ const url = await fileUploadAdapter.createDownloadURL(file, expires);
7
+ if (!url)
8
+ return null;
9
+ return this.files.normalizeUrl(url, params);
10
+ }
@@ -0,0 +1,9 @@
1
+ import type { Product } from '@unchainedshop/core-products';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function findProductSiblingsService(this: Modules, { product, assortmentId, limit, offset, includeInactive, }: {
4
+ product: Product;
5
+ assortmentId?: string;
6
+ limit?: number;
7
+ offset?: number;
8
+ includeInactive?: boolean;
9
+ }): Promise<Product[]>;
@@ -0,0 +1,18 @@
1
+ export async function findProductSiblingsService({ product, assortmentId, limit, offset, includeInactive = false, }) {
2
+ const productId = product._id;
3
+ const assortmentIds = assortmentId
4
+ ? [assortmentId]
5
+ : await this.assortments.products.findAssortmentIds({ productId });
6
+ if (!assortmentIds.length)
7
+ return [];
8
+ const productIds = await this.assortments.products.findSiblings({
9
+ productId,
10
+ assortmentIds,
11
+ });
12
+ return this.products.findProducts({
13
+ productIds,
14
+ includeDrafts: includeInactive,
15
+ limit,
16
+ offset,
17
+ });
18
+ }
@@ -0,0 +1,3 @@
1
+ import { type Quotation } from '@unchainedshop/core-quotations';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function fulfillQuotationService(this: Modules, quotation: Quotation, info: any): Promise<Quotation>;
@@ -1,10 +1,10 @@
1
1
  import { QuotationStatus } from '@unchainedshop/core-quotations';
2
2
  import { processQuotationService } from "./processQuotation.js";
3
- export async function fullfillQuotationService(quotation, info) {
4
- if (quotation.status === QuotationStatus.FULLFILLED)
3
+ export async function fulfillQuotationService(quotation, info) {
4
+ if (quotation.status === QuotationStatus.FULFILLED)
5
5
  return quotation;
6
6
  const updatedQuotation = await this.quotations.updateStatus(quotation._id, {
7
- status: QuotationStatus.FULLFILLED,
7
+ status: QuotationStatus.FULFILLED,
8
8
  info: JSON.stringify(info),
9
9
  });
10
10
  return processQuotationService.bind(this)(updatedQuotation, {});
@@ -32,20 +32,31 @@ import { initializeEnrollmentService } from './initializeEnrollment.ts';
32
32
  import { activateEnrollmentService } from './activateEnrollment.ts';
33
33
  import { terminateEnrollmentService } from './terminateEnrollment.ts';
34
34
  import { invalidateFilterCacheService } from './invalidateFilterCache.ts';
35
- import { fullfillQuotationService } from './fullfillQuotation.ts';
35
+ import { fulfillQuotationService } from './fulfillQuotation.ts';
36
36
  import { processQuotationService } from './processQuotation.ts';
37
37
  import { proposeQuotationService } from './proposeQuotation.ts';
38
38
  import { rejectQuotationService } from './rejectQuotation.ts';
39
39
  import { verifyQuotationService } from './verifyQuotation.ts';
40
40
  import { loadFiltersService } from './loadFilters.ts';
41
41
  import { loadFilterOptionsService } from './loadFilterOptions.ts';
42
+ import { removeFilterService } from './removeFilter.ts';
43
+ import { removeCartDiscountService } from './removeCartDiscount.ts';
44
+ import { addMultipleCartProductsService } from './addMultipleCartProducts.ts';
42
45
  import { ercMetadataService } from './ercMetadata.ts';
43
46
  import { simulateProductPricingService } from './simulateProductPricing.ts';
44
47
  import { simulateProductDispatchingService } from './simulateProductDispatching.ts';
45
48
  import { simulateProductInventoryService } from './simulateProductInventory.ts';
49
+ import { simulateDeliveryPricingService } from './simulateDeliveryPricing.ts';
50
+ import { simulatePaymentPricingService } from './simulatePaymentPricing.ts';
46
51
  import { findOrInitCartService } from './findOrInitCart.ts';
47
52
  import { addMessageService } from './addMessage.ts';
48
53
  import { generateOrderFromEnrollmentService } from './generateOrderFromEnrollment.ts';
54
+ import { resolveOrderItemDispatchesService } from './resolveOrderItemDispatches.ts';
55
+ import { findProductSiblingsService } from './findProductSiblings.ts';
56
+ import { simulateConfigurablePriceRangeService } from './simulateConfigurablePriceRange.ts';
57
+ import { createFileDownloadURLService } from './createFileDownloadURL.ts';
58
+ import { resolveTokenStatusService } from './resolveTokenStatus.ts';
59
+ import { isTokenInvalidateableService } from './isTokenInvalidateable.ts';
49
60
  export type ServiceInterface = (this: Modules, ...args: any[]) => Promise<any> | any;
50
61
  export type Bound<T extends ServiceInterface> = OmitThisParameter<T>;
51
62
  export type CustomServices = Record<string, ServiceInterface | Record<string, ServiceInterface>>;
@@ -60,6 +71,7 @@ export default function initServices(modules: Modules, customServices?: CustomSe
60
71
  uploadFileFromStream: Bound<typeof uploadFileFromStreamService>;
61
72
  removeFiles: Bound<typeof removeFilesService>;
62
73
  createDownloadStream: Bound<typeof createDownloadStreamService>;
74
+ createFileDownloadURL: Bound<typeof createFileDownloadURLService>;
63
75
  };
64
76
  orders: {
65
77
  registerPaymentCredentials: Bound<typeof registerPaymentCredentialsService>;
@@ -78,12 +90,17 @@ export default function initServices(modules: Modules, customServices?: CustomSe
78
90
  rejectOrder: Bound<typeof rejectOrderService>;
79
91
  discountedEntities: Bound<typeof discountedEntitiesService>;
80
92
  createManualOrderDiscount: Bound<typeof createManualOrderDiscountService>;
93
+ resolveOrderItemDispatches: Bound<typeof resolveOrderItemDispatchesService>;
94
+ removeCartDiscount: Bound<typeof removeCartDiscountService>;
95
+ addMultipleCartProducts: Bound<typeof addMultipleCartProductsService>;
81
96
  };
82
97
  products: {
83
98
  simulateProductPricing: Bound<typeof simulateProductPricingService>;
84
99
  simulateProductDispatching: Bound<typeof simulateProductDispatchingService>;
85
100
  simulateProductInventory: Bound<typeof simulateProductInventoryService>;
86
101
  removeProduct: Bound<typeof removeProductService>;
102
+ findProductSiblings: Bound<typeof findProductSiblingsService>;
103
+ simulateConfigurablePriceRange: Bound<typeof simulateConfigurablePriceRangeService>;
87
104
  };
88
105
  users: {
89
106
  migrateUserData: Bound<typeof migrateUserDataService>;
@@ -99,7 +116,7 @@ export default function initServices(modules: Modules, customServices?: CustomSe
99
116
  terminateEnrollment: Bound<typeof terminateEnrollmentService>;
100
117
  };
101
118
  quotations: {
102
- fullfillQuotation: Bound<typeof fullfillQuotationService>;
119
+ fulfillQuotation: Bound<typeof fulfillQuotationService>;
103
120
  processQuotation: Bound<typeof processQuotationService>;
104
121
  proposeQuotation: Bound<typeof proposeQuotationService>;
105
122
  rejectQuotation: Bound<typeof rejectQuotationService>;
@@ -111,12 +128,21 @@ export default function initServices(modules: Modules, customServices?: CustomSe
111
128
  invalidateFilterCache: Bound<typeof invalidateFilterCacheService>;
112
129
  loadFilters: Bound<typeof loadFiltersService>;
113
130
  loadFilterOptions: Bound<typeof loadFilterOptionsService>;
131
+ removeFilter: Bound<typeof removeFilterService>;
114
132
  };
115
133
  warehousing: {
116
134
  ercMetadata: Bound<typeof ercMetadataService>;
135
+ resolveTokenStatus: Bound<typeof resolveTokenStatusService>;
136
+ isTokenInvalidateable: Bound<typeof isTokenInvalidateableService>;
117
137
  };
118
138
  worker: {
119
139
  addMessage: Bound<typeof addMessageService>;
120
140
  };
141
+ delivery: {
142
+ simulateDeliveryPricing: Bound<typeof simulateDeliveryPricingService>;
143
+ };
144
+ payment: {
145
+ simulatePaymentPricing: Bound<typeof simulatePaymentPricingService>;
146
+ };
121
147
  };
122
148
  export type Services = ReturnType<typeof initServices>;
@@ -31,20 +31,31 @@ import { initializeEnrollmentService } from "./initializeEnrollment.js";
31
31
  import { activateEnrollmentService } from "./activateEnrollment.js";
32
32
  import { terminateEnrollmentService } from "./terminateEnrollment.js";
33
33
  import { invalidateFilterCacheService } from "./invalidateFilterCache.js";
34
- import { fullfillQuotationService } from "./fullfillQuotation.js";
34
+ import { fulfillQuotationService } from "./fulfillQuotation.js";
35
35
  import { processQuotationService } from "./processQuotation.js";
36
36
  import { proposeQuotationService } from "./proposeQuotation.js";
37
37
  import { rejectQuotationService } from "./rejectQuotation.js";
38
38
  import { verifyQuotationService } from "./verifyQuotation.js";
39
39
  import { loadFiltersService } from "./loadFilters.js";
40
40
  import { loadFilterOptionsService } from "./loadFilterOptions.js";
41
+ import { removeFilterService } from "./removeFilter.js";
42
+ import { removeCartDiscountService } from "./removeCartDiscount.js";
43
+ import { addMultipleCartProductsService } from "./addMultipleCartProducts.js";
41
44
  import { ercMetadataService } from "./ercMetadata.js";
42
45
  import { simulateProductPricingService } from "./simulateProductPricing.js";
43
46
  import { simulateProductDispatchingService } from "./simulateProductDispatching.js";
44
47
  import { simulateProductInventoryService } from "./simulateProductInventory.js";
48
+ import { simulateDeliveryPricingService } from "./simulateDeliveryPricing.js";
49
+ import { simulatePaymentPricingService } from "./simulatePaymentPricing.js";
45
50
  import { findOrInitCartService } from "./findOrInitCart.js";
46
51
  import { addMessageService } from "./addMessage.js";
47
52
  import { generateOrderFromEnrollmentService } from "./generateOrderFromEnrollment.js";
53
+ import { resolveOrderItemDispatchesService } from "./resolveOrderItemDispatches.js";
54
+ import { findProductSiblingsService } from "./findProductSiblings.js";
55
+ import { simulateConfigurablePriceRangeService } from "./simulateConfigurablePriceRange.js";
56
+ import { createFileDownloadURLService } from "./createFileDownloadURL.js";
57
+ import { resolveTokenStatusService } from "./resolveTokenStatus.js";
58
+ import { isTokenInvalidateableService } from "./isTokenInvalidateable.js";
48
59
  function bindMethodsToModules(modules) {
49
60
  return {
50
61
  get(target, prop, receiver) {
@@ -71,6 +82,7 @@ export default function initServices(modules, customServices = {}) {
71
82
  uploadFileFromStream: uploadFileFromStreamService,
72
83
  removeFiles: removeFilesService,
73
84
  createDownloadStream: createDownloadStreamService,
85
+ createFileDownloadURL: createFileDownloadURLService,
74
86
  },
75
87
  orders: {
76
88
  registerPaymentCredentials: registerPaymentCredentialsService,
@@ -89,12 +101,17 @@ export default function initServices(modules, customServices = {}) {
89
101
  rejectOrder: rejectOrderService,
90
102
  discountedEntities: discountedEntitiesService,
91
103
  createManualOrderDiscount: createManualOrderDiscountService,
104
+ resolveOrderItemDispatches: resolveOrderItemDispatchesService,
105
+ removeCartDiscount: removeCartDiscountService,
106
+ addMultipleCartProducts: addMultipleCartProductsService,
92
107
  },
93
108
  products: {
94
109
  simulateProductPricing: simulateProductPricingService,
95
110
  simulateProductDispatching: simulateProductDispatchingService,
96
111
  simulateProductInventory: simulateProductInventoryService,
97
112
  removeProduct: removeProductService,
113
+ findProductSiblings: findProductSiblingsService,
114
+ simulateConfigurablePriceRange: simulateConfigurablePriceRangeService,
98
115
  },
99
116
  users: {
100
117
  migrateUserData: migrateUserDataService,
@@ -110,7 +127,7 @@ export default function initServices(modules, customServices = {}) {
110
127
  terminateEnrollment: terminateEnrollmentService,
111
128
  },
112
129
  quotations: {
113
- fullfillQuotation: fullfillQuotationService,
130
+ fulfillQuotation: fulfillQuotationService,
114
131
  processQuotation: processQuotationService,
115
132
  proposeQuotation: proposeQuotationService,
116
133
  rejectQuotation: rejectQuotationService,
@@ -122,13 +139,22 @@ export default function initServices(modules, customServices = {}) {
122
139
  invalidateFilterCache: invalidateFilterCacheService,
123
140
  loadFilters: loadFiltersService,
124
141
  loadFilterOptions: loadFilterOptionsService,
142
+ removeFilter: removeFilterService,
125
143
  },
126
144
  warehousing: {
127
145
  ercMetadata: ercMetadataService,
146
+ resolveTokenStatus: resolveTokenStatusService,
147
+ isTokenInvalidateable: isTokenInvalidateableService,
128
148
  },
129
149
  worker: {
130
150
  addMessage: addMessageService,
131
151
  },
152
+ delivery: {
153
+ simulateDeliveryPricing: simulateDeliveryPricingService,
154
+ },
155
+ payment: {
156
+ simulatePaymentPricing: simulatePaymentPricingService,
157
+ },
132
158
  };
133
159
  return new Proxy({ ...services, ...customServices }, bindMethodsToModules(modules));
134
160
  }
@@ -0,0 +1,5 @@
1
+ import type { TokenSurrogate } from '@unchainedshop/core-warehousing';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function isTokenInvalidateableService(this: Modules, { token, }: {
4
+ token: TokenSurrogate;
5
+ }): Promise<boolean>;
@@ -0,0 +1,15 @@
1
+ import { WarehousingProviderType } from '@unchainedshop/core-warehousing';
2
+ import { WarehousingDirector } from "../directors/WarehousingDirector.js";
3
+ export async function isTokenInvalidateableService({ token, }) {
4
+ const product = await this.products.findProduct({ productId: token.productId });
5
+ if (!product)
6
+ return false;
7
+ const virtualProviders = (await this.warehousing.allProviders()).filter(({ type }) => type === WarehousingProviderType.VIRTUAL);
8
+ const isInvalidateable = await WarehousingDirector.isInvalidateable(virtualProviders, {
9
+ token,
10
+ product,
11
+ quantity: token?.quantity || 1,
12
+ referenceDate: new Date(),
13
+ }, { modules: this });
14
+ return !!isInvalidateable;
15
+ }
@@ -3,14 +3,16 @@ import { createEnrollmentFromCheckoutService } from "./createEnrollmentFromCheck
3
3
  import { ProductType } from '@unchainedshop/core-products';
4
4
  import { WarehousingProviderType } from '@unchainedshop/core-warehousing';
5
5
  import { WarehousingDirector, DeliveryDirector, PaymentDirector } from "../directors/index.js";
6
- import { fullfillQuotationService } from "./fullfillQuotation.js";
6
+ import { fulfillQuotationService } from "./fulfillQuotation.js";
7
+ import { createLogger } from '@unchainedshop/logger';
8
+ const logger = createLogger('unchained:core:processOrder');
7
9
  const isAutoConfirmationEnabled = async ({ orderPayment, orderDelivery, }, modules) => {
8
10
  if (orderPayment.status !== OrderPaymentStatus.PAID) {
9
11
  const paymentProvider = await modules.payment.paymentProviders.findProvider({
10
12
  paymentProviderId: orderPayment.paymentProviderId,
11
13
  });
12
14
  if (!paymentProvider)
13
- throw new Error('Payment provider not found: ' + orderPayment.paymentProviderId);
15
+ throw new Error(`Payment provider not found: ${orderPayment.paymentProviderId}`);
14
16
  const actions = await PaymentDirector.actions(paymentProvider, {}, { modules });
15
17
  if (!actions.isPayLaterAllowed())
16
18
  return false;
@@ -20,7 +22,7 @@ const isAutoConfirmationEnabled = async ({ orderPayment, orderDelivery, }, modul
20
22
  deliveryProviderId: orderDelivery.deliveryProviderId,
21
23
  });
22
24
  if (!deliveryProvider)
23
- throw new Error('Delivery provider not found: ' + orderDelivery.deliveryProviderId);
25
+ throw new Error(`Delivery provider not found: ${orderDelivery.deliveryProviderId}`);
24
26
  const director = await DeliveryDirector.actions(deliveryProvider, {}, { modules });
25
27
  if (!director.isAutoReleaseAllowed())
26
28
  return false;
@@ -31,7 +33,7 @@ const findNextStatus = async (status, order, modules) => {
31
33
  if (status === null) {
32
34
  return OrderStatus.PENDING;
33
35
  }
34
- if (status === OrderStatus.FULLFILLED || status === OrderStatus.REJECTED) {
36
+ if (status === OrderStatus.FULFILLED || status === OrderStatus.REJECTED) {
35
37
  return status;
36
38
  }
37
39
  const orderPayment = order.paymentId &&
@@ -52,10 +54,10 @@ const findNextStatus = async (status, order, modules) => {
52
54
  }
53
55
  }
54
56
  if (status === OrderStatus.CONFIRMED) {
55
- const readyForFullfillment = orderDelivery.status === OrderDeliveryStatus.DELIVERED &&
57
+ const readyForFulfillment = orderDelivery.status === OrderDeliveryStatus.DELIVERED &&
56
58
  orderPayment.status === OrderPaymentStatus.PAID;
57
- if (readyForFullfillment) {
58
- return OrderStatus.FULLFILLED;
59
+ if (readyForFulfillment) {
60
+ return OrderStatus.FULFILLED;
59
61
  }
60
62
  }
61
63
  return status;
@@ -125,9 +127,15 @@ export async function processOrderService(initialOrder, orderTransactionContext)
125
127
  const quotation = await this.quotations.findQuotation({
126
128
  quotationId: orderPosition.quotationId,
127
129
  });
128
- if (!quotation)
130
+ if (!quotation) {
131
+ logger.warn('Quotation not found during order fulfillment', {
132
+ orderId,
133
+ orderPositionId: orderPosition._id,
134
+ quotationId: orderPosition.quotationId,
135
+ });
129
136
  return;
130
- await fullfillQuotationService.bind(this)(quotation, {
137
+ }
138
+ await fulfillQuotationService.bind(this)(quotation, {
131
139
  orderId,
132
140
  orderPositionId: orderPosition._id,
133
141
  });
@@ -1,7 +1,7 @@
1
1
  import { QuotationStatus } from '@unchainedshop/core-quotations';
2
2
  import { processQuotationService } from "./processQuotation.js";
3
3
  export async function rejectQuotationService(quotation, { quotationContext }) {
4
- if (quotation.status === QuotationStatus.FULLFILLED)
4
+ if (quotation.status === QuotationStatus.FULFILLED)
5
5
  return quotation;
6
6
  const updatedQuotation = (await this.quotations.updateStatus(quotation._id, {
7
7
  status: QuotationStatus.REJECTED,
@@ -0,0 +1,10 @@
1
+ import type { Modules } from '../modules.ts';
2
+ import { type Order, type OrderDiscount } from '@unchainedshop/core-orders';
3
+ export declare function removeCartDiscountService(this: Modules, { order, orderDiscount, requestContext, }: {
4
+ order: Order;
5
+ orderDiscount: OrderDiscount;
6
+ requestContext: {
7
+ localeContext: Intl.Locale;
8
+ userId?: string;
9
+ };
10
+ }): Promise<OrderDiscount | null>;
@@ -0,0 +1,14 @@
1
+ import { OrderDiscountDirector } from "../directors/index.js";
2
+ import { OrderDiscountTrigger } from '@unchainedshop/core-orders';
3
+ export async function removeCartDiscountService({ order, orderDiscount, requestContext, }) {
4
+ if (orderDiscount.trigger === OrderDiscountTrigger.USER) {
5
+ const Adapter = OrderDiscountDirector.getAdapter(orderDiscount.discountKey);
6
+ if (Adapter) {
7
+ const adapter = await Adapter.actions({
8
+ context: { order, orderDiscount, code: orderDiscount.code, modules: this, ...requestContext },
9
+ });
10
+ await adapter.release();
11
+ }
12
+ }
13
+ return this.orders.discounts.delete(orderDiscount._id);
14
+ }
@@ -4,7 +4,7 @@ export async function removeFilesService({ fileIds }) {
4
4
  throw Error('Media id/s to be removed not provided as a string or array');
5
5
  const fileUploadAdapter = getFileAdapter();
6
6
  const fileObjects = await this.files.findFiles({
7
- _id: { $in: fileIds },
7
+ fileIds,
8
8
  });
9
9
  try {
10
10
  await fileUploadAdapter.removeFiles(fileObjects, { modules: this });
@@ -0,0 +1,5 @@
1
+ import type { Modules } from '../modules.ts';
2
+ import type { Filter } from '@unchainedshop/core-filters';
3
+ export declare function removeFilterService(this: Modules, { filter }: {
4
+ filter: Filter;
5
+ }): Promise<Filter | null>;
@@ -0,0 +1,5 @@
1
+ export async function removeFilterService({ filter }) {
2
+ await this.assortments.filters.deleteMany({ filterId: filter._id });
3
+ await this.filters.delete(filter._id);
4
+ return filter;
5
+ }
@@ -0,0 +1,19 @@
1
+ import type { DeliveryProvider } from '@unchainedshop/core-delivery';
2
+ import type { OrderPosition } from '@unchainedshop/core-orders';
3
+ import type { Product } from '@unchainedshop/core-products';
4
+ import type { WarehousingProvider } from '@unchainedshop/core-warehousing';
5
+ import type { Modules } from '../modules.ts';
6
+ export interface OrderItemDispatch {
7
+ _id: string;
8
+ deliveryProvider: DeliveryProvider;
9
+ warehousingProvider: WarehousingProvider;
10
+ product: Product;
11
+ quantity: number;
12
+ countryCode: string;
13
+ userId: string;
14
+ shipping: Date;
15
+ earliestDelivery: Date;
16
+ }
17
+ export declare function resolveOrderItemDispatchesService(this: Modules, { orderPosition, }: {
18
+ orderPosition: OrderPosition;
19
+ }): Promise<OrderItemDispatch[] | null>;
@@ -0,0 +1,38 @@
1
+ export async function resolveOrderItemDispatchesService({ orderPosition, }) {
2
+ const scheduling = orderPosition.scheduling || [];
3
+ if (!scheduling.length)
4
+ return [];
5
+ const order = await this.orders.findOrder({ orderId: orderPosition.orderId });
6
+ if (!order)
7
+ return null;
8
+ const { countryCode, userId, deliveryId } = order;
9
+ if (!deliveryId)
10
+ return null;
11
+ const orderDelivery = await this.orders.deliveries.findDelivery({
12
+ orderDeliveryId: deliveryId,
13
+ });
14
+ if (!orderDelivery)
15
+ return null;
16
+ const deliveryProvider = await this.delivery.findProvider({
17
+ deliveryProviderId: orderDelivery.deliveryProviderId,
18
+ });
19
+ if (!deliveryProvider)
20
+ return null;
21
+ const product = await this.products.findProduct({ productId: orderPosition.productId });
22
+ if (!product)
23
+ return null;
24
+ return Promise.all(scheduling.map(async (schedule) => {
25
+ const warehousingProvider = await this.warehousing.findProvider({
26
+ warehousingProviderId: schedule.warehousingProviderId,
27
+ });
28
+ return {
29
+ warehousingProvider,
30
+ deliveryProvider,
31
+ product,
32
+ quantity: orderPosition.quantity,
33
+ countryCode,
34
+ userId,
35
+ ...schedule,
36
+ };
37
+ }));
38
+ }
@@ -0,0 +1,5 @@
1
+ import type { TokenSurrogate, TokenStatus as TokenStatusType } from '@unchainedshop/core-warehousing';
2
+ import type { Modules } from '../modules.ts';
3
+ export declare function resolveTokenStatusService(this: Modules, { token, }: {
4
+ token: TokenSurrogate;
5
+ }): Promise<TokenStatusType>;
@@ -0,0 +1,15 @@
1
+ import { TokenStatus } from '@unchainedshop/core-warehousing';
2
+ import { WorkStatus } from '@unchainedshop/core-worker';
3
+ export async function resolveTokenStatusService({ token, }) {
4
+ if (token.walletAddress && !token.userId) {
5
+ return TokenStatus.DECENTRALIZED;
6
+ }
7
+ const workItems = await this.worker.findWorkQueue({
8
+ types: ['EXPORT_TOKEN'],
9
+ status: [WorkStatus.NEW, WorkStatus.ALLOCATED],
10
+ });
11
+ if (workItems.find((item) => item.input?.token?._id === token._id)) {
12
+ return TokenStatus.EXPORTING;
13
+ }
14
+ return TokenStatus.CENTRALIZED;
15
+ }
@@ -0,0 +1,13 @@
1
+ import type { Product, ProductConfiguration, ProductPriceRange } from '@unchainedshop/core-products';
2
+ import type { User } from '@unchainedshop/core-users';
3
+ import type { Modules } from '../modules.ts';
4
+ export declare function simulateConfigurablePriceRangeService(this: Modules, { product, currencyCode, countryCode, quantity, useNetPrice, vectors, includeInactive, user, }: {
5
+ product: Product;
6
+ currencyCode: string;
7
+ countryCode: string;
8
+ quantity?: number;
9
+ useNetPrice?: boolean;
10
+ vectors?: ProductConfiguration[];
11
+ includeInactive?: boolean;
12
+ user?: User;
13
+ }): Promise<ProductPriceRange | null>;
@@ -0,0 +1,39 @@
1
+ import { ProductPricingDirector } from "../core-index.js";
2
+ export async function simulateConfigurablePriceRangeService({ product, currencyCode, countryCode, quantity = 1, useNetPrice = false, vectors, includeInactive = false, user, }) {
3
+ const products = await this.products.proxyProducts(product, vectors, {
4
+ includeInactive,
5
+ });
6
+ const filteredPrices = (await Promise.all(products.map(async (proxyProduct) => {
7
+ const pricingContext = {
8
+ product: proxyProduct,
9
+ user,
10
+ countryCode,
11
+ discounts: [],
12
+ currencyCode,
13
+ quantity,
14
+ };
15
+ const calculated = await ProductPricingDirector.rebuildCalculation(pricingContext, {
16
+ modules: this,
17
+ });
18
+ if (!calculated || !calculated.length)
19
+ return null;
20
+ const pricing = ProductPricingDirector.calculationSheet(pricingContext, calculated);
21
+ const unitPrice = pricing.unitPrice({ useNetPrice });
22
+ return {
23
+ ...unitPrice,
24
+ isNetPrice: useNetPrice,
25
+ isTaxable: pricing.taxSum() > 0,
26
+ currencyCode: pricing.currencyCode,
27
+ };
28
+ }))).filter(Boolean);
29
+ if (!filteredPrices.length)
30
+ return null;
31
+ const { minPrice, maxPrice } = this.products.prices.priceRange({
32
+ productId: product._id,
33
+ prices: filteredPrices,
34
+ });
35
+ return {
36
+ minPrice,
37
+ maxPrice,
38
+ };
39
+ }
@@ -0,0 +1,21 @@
1
+ import type { DeliveryProvider } from '@unchainedshop/core-delivery';
2
+ import type { Order } from '@unchainedshop/core-orders';
3
+ import type { User } from '@unchainedshop/core-users';
4
+ import type { Modules } from '../modules.ts';
5
+ export interface SimulatedDeliveryPrice {
6
+ amount: number;
7
+ currencyCode: string;
8
+ countryCode: string;
9
+ isTaxable: boolean;
10
+ isNetPrice: boolean;
11
+ }
12
+ export declare function simulateDeliveryPricingService(this: Modules, pricingContext: {
13
+ currencyCode: string;
14
+ countryCode: string;
15
+ provider: DeliveryProvider;
16
+ providerContext?: any;
17
+ order?: Order;
18
+ user: User;
19
+ }, options?: {
20
+ useNetPrice?: boolean;
21
+ }): Promise<SimulatedDeliveryPrice | null>;
@@ -0,0 +1,23 @@
1
+ import { DeliveryPricingDirector } from "../core-index.js";
2
+ export async function simulateDeliveryPricingService(pricingContext, options = {}) {
3
+ const context = {
4
+ currencyCode: pricingContext.currencyCode,
5
+ countryCode: pricingContext.countryCode,
6
+ provider: pricingContext.provider,
7
+ providerContext: pricingContext.providerContext,
8
+ order: pricingContext.order,
9
+ user: pricingContext.user,
10
+ };
11
+ const calculated = await DeliveryPricingDirector.rebuildCalculation(context, { modules: this });
12
+ if (!calculated?.length)
13
+ return null;
14
+ const pricing = DeliveryPricingDirector.calculationSheet(context, calculated);
15
+ const orderPrice = pricing.total({ useNetPrice: options.useNetPrice });
16
+ return {
17
+ amount: orderPrice.amount,
18
+ currencyCode: orderPrice.currencyCode,
19
+ countryCode: pricingContext.countryCode,
20
+ isTaxable: pricing.taxSum() > 0,
21
+ isNetPrice: options.useNetPrice || false,
22
+ };
23
+ }
@@ -0,0 +1,21 @@
1
+ import type { PaymentProvider } from '@unchainedshop/core-payment';
2
+ import type { Order } from '@unchainedshop/core-orders';
3
+ import type { User } from '@unchainedshop/core-users';
4
+ import type { Modules } from '../modules.ts';
5
+ export interface SimulatedPaymentPrice {
6
+ amount: number;
7
+ currencyCode: string;
8
+ countryCode: string;
9
+ isTaxable: boolean;
10
+ isNetPrice: boolean;
11
+ }
12
+ export declare function simulatePaymentPricingService(this: Modules, pricingContext: {
13
+ currencyCode: string;
14
+ countryCode: string;
15
+ provider: PaymentProvider;
16
+ providerContext?: any;
17
+ order?: Order;
18
+ user: User;
19
+ }, options?: {
20
+ useNetPrice?: boolean;
21
+ }): Promise<SimulatedPaymentPrice | null>;
@@ -0,0 +1,23 @@
1
+ import { PaymentPricingDirector } from "../core-index.js";
2
+ export async function simulatePaymentPricingService(pricingContext, options = {}) {
3
+ const context = {
4
+ currencyCode: pricingContext.currencyCode,
5
+ countryCode: pricingContext.countryCode,
6
+ provider: pricingContext.provider,
7
+ providerContext: pricingContext.providerContext,
8
+ order: pricingContext.order,
9
+ user: pricingContext.user,
10
+ };
11
+ const calculated = await PaymentPricingDirector.rebuildCalculation(context, { modules: this });
12
+ if (!calculated?.length)
13
+ return null;
14
+ const pricing = PaymentPricingDirector.calculationSheet(context, calculated);
15
+ const orderPrice = pricing.total({ useNetPrice: options.useNetPrice });
16
+ return {
17
+ amount: orderPrice.amount,
18
+ currencyCode: orderPrice.currencyCode,
19
+ countryCode: pricingContext.countryCode,
20
+ isTaxable: pricing.taxSum() > 0,
21
+ isNetPrice: options.useNetPrice || false,
22
+ };
23
+ }
@@ -4,9 +4,9 @@ const logger = createLogger('unchained:core');
4
4
  export async function updateUserAvatarAfterUploadService({ file }) {
5
5
  const { userId } = file.meta;
6
6
  const files = await this.files.findFiles({
7
- _id: { $ne: file._id },
7
+ excludeFileId: file._id,
8
8
  path: file.path,
9
- 'meta.userId': userId,
9
+ meta: { userId },
10
10
  });
11
11
  const fileIds = files.map((f) => f._id);
12
12
  try {
@@ -0,0 +1,22 @@
1
+ export interface ScheduleData {
2
+ schedules: {
3
+ s?: number[];
4
+ m?: number[];
5
+ h?: number[];
6
+ D?: number[];
7
+ M?: number[];
8
+ d?: number[];
9
+ }[];
10
+ }
11
+ declare function parseCron(cronExpr: string): ScheduleData;
12
+ declare function parseText(text: string): ScheduleData;
13
+ export declare const schedule: {
14
+ parse: {
15
+ cron: typeof parseCron;
16
+ text: typeof parseText;
17
+ };
18
+ schedule: (scheduleData: ScheduleData) => {
19
+ next: (count: number, referenceDate: Date) => Date | Date[];
20
+ };
21
+ };
22
+ export default schedule;
@@ -0,0 +1,148 @@
1
+ function parseCronField(field, min, max) {
2
+ if (field === '*')
3
+ return undefined;
4
+ const values = [];
5
+ const parts = field.split(',');
6
+ for (const part of parts) {
7
+ const stepMatch = part.match(/^(\d+|\*)-?(\d+)?\/(\d+)$|^(\*|\d+-\d+)\/(\d+)$/);
8
+ if (stepMatch || part.includes('/')) {
9
+ const [rangePart, stepPart] = part.split('/');
10
+ const step = parseInt(stepPart, 10);
11
+ let start = min;
12
+ let end = max;
13
+ if (rangePart !== '*') {
14
+ if (rangePart.includes('-')) {
15
+ const [rangeStart, rangeEnd] = rangePart.split('-').map(Number);
16
+ start = rangeStart;
17
+ end = rangeEnd;
18
+ }
19
+ else {
20
+ start = parseInt(rangePart, 10);
21
+ }
22
+ }
23
+ for (let i = start; i <= end; i += step) {
24
+ if (!values.includes(i))
25
+ values.push(i);
26
+ }
27
+ continue;
28
+ }
29
+ if (part.includes('-')) {
30
+ const [start, end] = part.split('-').map(Number);
31
+ for (let i = start; i <= end; i++) {
32
+ if (!values.includes(i))
33
+ values.push(i);
34
+ }
35
+ continue;
36
+ }
37
+ const num = parseInt(part, 10);
38
+ if (!isNaN(num) && !values.includes(num)) {
39
+ values.push(num);
40
+ }
41
+ }
42
+ return values.length > 0 ? values.sort((a, b) => a - b) : undefined;
43
+ }
44
+ function parseCron(cronExpr) {
45
+ const parts = cronExpr.trim().split(/\s+/);
46
+ if (parts.length !== 5) {
47
+ throw new Error(`Invalid cron expression: expected 5 fields, got ${parts.length}`);
48
+ }
49
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
50
+ return {
51
+ schedules: [
52
+ {
53
+ s: [0],
54
+ m: parseCronField(minute, 0, 59),
55
+ h: parseCronField(hour, 0, 23),
56
+ D: parseCronField(dayOfMonth, 1, 31),
57
+ M: parseCronField(month, 1, 12),
58
+ d: parseCronField(dayOfWeek, 0, 6),
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ function parseText(text) {
64
+ const match = text.toLowerCase().match(/every\s+(\d+)\s+(second|minute|hour)s?/);
65
+ if (!match) {
66
+ throw new Error(`Invalid schedule text: "${text}". Supported formats: "every N seconds/minutes/hours"`);
67
+ }
68
+ const value = parseInt(match[1], 10);
69
+ const unit = match[2];
70
+ switch (unit) {
71
+ case 'second': {
72
+ const seconds = [];
73
+ for (let i = 0; i < 60; i += value) {
74
+ seconds.push(i);
75
+ }
76
+ return {
77
+ schedules: [{ s: seconds }],
78
+ };
79
+ }
80
+ case 'minute': {
81
+ const minutes = [];
82
+ for (let i = 0; i < 60; i += value) {
83
+ minutes.push(i);
84
+ }
85
+ return {
86
+ schedules: [{ s: [0], m: minutes }],
87
+ };
88
+ }
89
+ case 'hour': {
90
+ const hours = [];
91
+ for (let i = 0; i < 24; i += value) {
92
+ hours.push(i);
93
+ }
94
+ return {
95
+ schedules: [{ s: [0], m: [0], h: hours }],
96
+ };
97
+ }
98
+ default:
99
+ throw new Error(`Unsupported time unit: ${unit}`);
100
+ }
101
+ }
102
+ function getNextOccurrences(scheduleData, count, referenceDate) {
103
+ const results = [];
104
+ const schedule = scheduleData.schedules[0];
105
+ const current = new Date(referenceDate.getTime());
106
+ current.setMilliseconds(0);
107
+ current.setSeconds(current.getSeconds() + 1);
108
+ const maxIterations = 366 * 24 * 60 * 60;
109
+ let iterations = 0;
110
+ while (results.length < count && iterations < maxIterations) {
111
+ iterations++;
112
+ const month = current.getMonth() + 1;
113
+ const dayOfMonth = current.getDate();
114
+ const dayOfWeek = current.getDay();
115
+ const hour = current.getHours();
116
+ const minute = current.getMinutes();
117
+ const second = current.getSeconds();
118
+ const matchesMonth = !schedule.M || schedule.M.includes(month);
119
+ const matchesDay = !schedule.D || schedule.D.includes(dayOfMonth);
120
+ const matchesDayOfWeek = !schedule.d || schedule.d.includes(dayOfWeek);
121
+ const matchesHour = !schedule.h || schedule.h.includes(hour);
122
+ const matchesMinute = !schedule.m || schedule.m.includes(minute);
123
+ const matchesSecond = !schedule.s || schedule.s.includes(second);
124
+ if (matchesMonth &&
125
+ matchesDay &&
126
+ matchesDayOfWeek &&
127
+ matchesHour &&
128
+ matchesMinute &&
129
+ matchesSecond) {
130
+ results.push(new Date(current));
131
+ }
132
+ current.setSeconds(current.getSeconds() + 1);
133
+ }
134
+ return results;
135
+ }
136
+ export const schedule = {
137
+ parse: {
138
+ cron: parseCron,
139
+ text: parseText,
140
+ },
141
+ schedule: (scheduleData) => ({
142
+ next: (count, referenceDate) => {
143
+ const results = getNextOccurrences(scheduleData, count, referenceDate);
144
+ return count === 1 ? results[0] : results;
145
+ },
146
+ }),
147
+ };
148
+ export default schedule;
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@unchainedshop/core",
3
- "version": "4.4.0",
3
+ "description": "Core orchestration package for the Unchained Engine with business services and directors",
4
+ "version": "4.6.0",
4
5
  "main": "lib/core-index.js",
5
6
  "types": "lib/core-index.d.ts",
6
7
  "type": "module",
8
+ "sideEffects": false,
7
9
  "scripts": {
8
10
  "clean": "tsc -b --clean",
9
11
  "build": "tsc -b",
@@ -33,30 +35,28 @@
33
35
  },
34
36
  "homepage": "https://github.com/unchainedshop/unchained#readme",
35
37
  "dependencies": {
36
- "@breejs/later": "^4.2.0",
37
- "@unchainedshop/core-assortments": "^4.4.0",
38
- "@unchainedshop/core-bookmarks": "^4.4.0",
39
- "@unchainedshop/core-countries": "^4.4.0",
40
- "@unchainedshop/core-currencies": "^4.4.0",
41
- "@unchainedshop/core-delivery": "^4.4.0",
42
- "@unchainedshop/core-enrollments": "^4.4.0",
43
- "@unchainedshop/core-events": "^4.4.0",
44
- "@unchainedshop/core-files": "^4.4.0",
45
- "@unchainedshop/core-filters": "^4.4.0",
46
- "@unchainedshop/core-languages": "^4.4.0",
47
- "@unchainedshop/core-orders": "^4.4.0",
48
- "@unchainedshop/core-payment": "^4.4.0",
49
- "@unchainedshop/core-products": "^4.4.0",
50
- "@unchainedshop/core-quotations": "^4.4.0",
51
- "@unchainedshop/core-users": "^4.4.0",
52
- "@unchainedshop/core-warehousing": "^4.4.0",
53
- "@unchainedshop/core-worker": "^4.4.0",
54
- "@unchainedshop/logger": "^4.4.0",
55
- "@unchainedshop/utils": "^4.4.0",
38
+ "@unchainedshop/core-assortments": "^4.6.0",
39
+ "@unchainedshop/core-bookmarks": "^4.6.0",
40
+ "@unchainedshop/core-countries": "^4.6.0",
41
+ "@unchainedshop/core-currencies": "^4.6.0",
42
+ "@unchainedshop/core-delivery": "^4.6.0",
43
+ "@unchainedshop/core-enrollments": "^4.6.0",
44
+ "@unchainedshop/core-events": "^4.6.0",
45
+ "@unchainedshop/core-files": "^4.6.0",
46
+ "@unchainedshop/core-filters": "^4.6.0",
47
+ "@unchainedshop/core-languages": "^4.6.0",
48
+ "@unchainedshop/core-orders": "^4.6.0",
49
+ "@unchainedshop/core-payment": "^4.6.0",
50
+ "@unchainedshop/core-products": "^4.6.0",
51
+ "@unchainedshop/core-quotations": "^4.6.0",
52
+ "@unchainedshop/core-users": "^4.6.0",
53
+ "@unchainedshop/core-warehousing": "^4.6.0",
54
+ "@unchainedshop/core-worker": "^4.6.0",
55
+ "@unchainedshop/logger": "^4.6.0",
56
+ "@unchainedshop/utils": "^4.6.0",
56
57
  "minipass-json-stream": "^1.0.2"
57
58
  },
58
59
  "devDependencies": {
59
- "@types/breejs__later": "^4.1.5",
60
60
  "@types/node": "^25.0.0",
61
61
  "typescript": "^5.8.3"
62
62
  }
@@ -1,3 +0,0 @@
1
- import { type Quotation } from '@unchainedshop/core-quotations';
2
- import type { Modules } from '../modules.ts';
3
- export declare function fullfillQuotationService(this: Modules, quotation: Quotation, info: any): Promise<Quotation>;