@unchainedshop/plugins 2.5.12 → 2.6.1

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 (96) hide show
  1. package/lib/events/node-event-emitter.js.map +1 -1
  2. package/lib/events/redis.js.map +1 -1
  3. package/lib/files/gridfs/gridfs-adapter.d.ts +1 -1
  4. package/lib/files/gridfs/gridfs-adapter.js +5 -1
  5. package/lib/files/gridfs/gridfs-adapter.js.map +1 -1
  6. package/lib/files/gridfs/index.d.ts +1 -1
  7. package/lib/files/gridfs/index.js +5 -12
  8. package/lib/files/gridfs/index.js.map +1 -1
  9. package/lib/files/minio/minio-adapter.d.ts +1 -1
  10. package/lib/files/minio/minio-adapter.js +9 -3
  11. package/lib/files/minio/minio-adapter.js.map +1 -1
  12. package/lib/filters/local-search.js +19 -1
  13. package/lib/filters/local-search.js.map +1 -1
  14. package/lib/payment/apple-iap/db/AppleTransactionsCollection.d.ts +4 -3
  15. package/lib/payment/apple-iap/db/AppleTransactionsCollection.js.map +1 -1
  16. package/lib/payment/apple-iap/module/configureAppleTransactionsModule.d.ts +2 -3
  17. package/lib/payment/apple-iap/module/configureAppleTransactionsModule.js +1 -1
  18. package/lib/payment/apple-iap/module/configureAppleTransactionsModule.js.map +1 -1
  19. package/lib/payment/cryptopay/db/CryptopayTransactions.d.ts +5 -5
  20. package/lib/payment/cryptopay/db/CryptopayTransactions.js.map +1 -1
  21. package/lib/payment/cryptopay/module/configureCryptopayModule.d.ts +1 -2
  22. package/lib/payment/cryptopay/module/configureCryptopayModule.js +3 -3
  23. package/lib/payment/cryptopay/module/configureCryptopayModule.js.map +1 -1
  24. package/lib/plugins-index.d.ts +4 -22
  25. package/lib/plugins-index.js.map +1 -1
  26. package/lib/pricing/delivery-swiss-tax.js +1 -1
  27. package/lib/pricing/discount-100-off.js +1 -1
  28. package/lib/pricing/discount-100-off.js.map +1 -1
  29. package/lib/pricing/discount-half-price-manual.js +1 -1
  30. package/lib/pricing/discount-half-price-manual.js.map +1 -1
  31. package/lib/pricing/discount-half-price.js +1 -1
  32. package/lib/pricing/discount-half-price.js.map +1 -1
  33. package/lib/pricing/order-discount.js +1 -1
  34. package/lib/pricing/order-items-discount.js +1 -1
  35. package/lib/pricing/order-payment.js +1 -1
  36. package/lib/pricing/order-round.d.ts +10 -0
  37. package/lib/pricing/order-round.js +113 -0
  38. package/lib/pricing/order-round.js.map +1 -0
  39. package/lib/pricing/product-discount.js +1 -1
  40. package/lib/pricing/product-price-rateconversion.js +1 -1
  41. package/lib/pricing/product-price-rateconversion.js.map +1 -1
  42. package/lib/pricing/product-round.d.ts +10 -0
  43. package/lib/pricing/product-round.js +45 -0
  44. package/lib/pricing/product-round.js.map +1 -0
  45. package/lib/pricing/product-swiss-tax.js +1 -1
  46. package/lib/pricing/product-swiss-tax.js.map +1 -1
  47. package/lib/warehousing/eth-minter.js +2 -1
  48. package/lib/warehousing/eth-minter.js.map +1 -1
  49. package/lib/warehousing/google-sheets.js +2 -2
  50. package/lib/warehousing/google-sheets.js.map +1 -1
  51. package/lib/worker/bulk-import.js +7 -9
  52. package/lib/worker/bulk-import.js.map +1 -1
  53. package/lib/worker/export-token.js +2 -1
  54. package/lib/worker/export-token.js.map +1 -1
  55. package/lib/worker/update-coinbase-rates.js +0 -1
  56. package/lib/worker/update-coinbase-rates.js.map +1 -1
  57. package/lib/worker/update-ecb-rates.js.map +1 -1
  58. package/lib/worker/update-token-ownership.js +2 -1
  59. package/lib/worker/update-token-ownership.js.map +1 -1
  60. package/package.json +30 -28
  61. package/src/events/node-event-emitter.ts +1 -2
  62. package/src/events/redis.ts +1 -2
  63. package/src/files/gridfs/gridfs-adapter.ts +8 -2
  64. package/src/files/gridfs/index.ts +5 -13
  65. package/src/files/minio/minio-adapter.ts +12 -4
  66. package/src/filters/local-search.ts +27 -4
  67. package/src/payment/apple-iap/db/AppleTransactionsCollection.ts +4 -3
  68. package/src/payment/apple-iap/module/configureAppleTransactionsModule.ts +1 -6
  69. package/src/payment/cryptopay/db/CryptopayTransactions.ts +5 -5
  70. package/src/payment/cryptopay/module/configureCryptopayModule.ts +4 -5
  71. package/src/plugins-index.ts +7 -2
  72. package/src/pricing/delivery-swiss-tax.ts +1 -1
  73. package/src/pricing/discount-100-off.ts +1 -1
  74. package/src/pricing/discount-half-price-manual.ts +1 -1
  75. package/src/pricing/discount-half-price.ts +1 -1
  76. package/src/pricing/order-discount.ts +1 -1
  77. package/src/pricing/order-items-discount.ts +1 -1
  78. package/src/pricing/order-payment.ts +1 -1
  79. package/src/pricing/order-round.ts +139 -0
  80. package/src/pricing/product-discount.ts +1 -1
  81. package/src/pricing/product-price-rateconversion.ts +1 -1
  82. package/src/pricing/product-round.ts +63 -0
  83. package/src/pricing/product-swiss-tax.test.ts +4 -4
  84. package/src/pricing/product-swiss-tax.ts +3 -3
  85. package/src/warehousing/eth-minter.ts +2 -1
  86. package/src/warehousing/google-sheets.ts +2 -2
  87. package/src/worker/bulk-import.ts +14 -20
  88. package/src/worker/export-token.ts +3 -2
  89. package/src/worker/update-coinbase-rates.ts +0 -1
  90. package/src/worker/update-ecb-rates.ts +4 -5
  91. package/src/worker/update-token-ownership.ts +2 -1
  92. package/lib/pricing/product-price-round.d.ts +0 -12
  93. package/lib/pricing/product-price-round.js +0 -53
  94. package/lib/pricing/product-price-round.js.map +0 -1
  95. package/src/pricing/configureProductPriceRounding.test.ts +0 -42
  96. package/src/pricing/product-price-round.ts +0 -75
@@ -1,30 +1,22 @@
1
- import mongodb from 'mongodb';
1
+ import { mongodb } from '@unchainedshop/mongodb';
2
2
 
3
3
  export const configureGridFSFileUploadModule = ({ db }) => {
4
- // eslint-disable-next-line
5
- // @ts-ignore
6
- const isMeteor = typeof Meteor === 'object';
7
-
8
4
  // eslint-disable-next-line
9
5
  let { GridFSBucket } = mongodb;
10
- if (isMeteor) {
11
- const { MongoInternals } = require('meteor/mongo'); // eslint-disable-line
12
- GridFSBucket = MongoInternals.NpmModule.GridFSBucket;
13
- }
14
6
 
15
7
  return {
16
8
  createWriteStream: async (directoryName, fileId, fileName) => {
17
- const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
9
+ const options = { bucketName: `file_uploads_${directoryName}` };
18
10
  const bucket = new GridFSBucket(db, options);
19
- return bucket.openUploadStreamWithId(fileId, fileName, { writeConcern: { w: 0, journal: false } });
11
+ return bucket.openUploadStreamWithId(fileId, fileName);
20
12
  },
21
13
  createReadStream: async (directoryName, fileId) => {
22
- const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
14
+ const options = { bucketName: `file_uploads_${directoryName}` };
23
15
  const bucket = new GridFSBucket(db, options);
24
16
  return bucket.openDownloadStream(fileId);
25
17
  },
26
18
  removeFileFromBucket: async (directoryName, fileId) => {
27
- const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
19
+ const options = { bucketName: `file_uploads_${directoryName}` };
28
20
  const bucket = new GridFSBucket(db, options);
29
21
  return bucket.delete(fileId);
30
22
  },
@@ -2,12 +2,13 @@ import https from 'https';
2
2
  import http, { OutgoingHttpHeaders } from 'http';
3
3
  import { Readable } from 'stream';
4
4
  import { URL } from 'url';
5
- import { IFileAdapter, UploadFileData } from '@unchainedshop/types/files.js';
5
+ import { UploadFileData } from '@unchainedshop/types/files.js';
6
6
  import {
7
7
  FileAdapter,
8
8
  FileDirector,
9
9
  buildHashedFilename,
10
10
  resolveExpirationDate,
11
+ IFileAdapter,
11
12
  } from '@unchainedshop/file-upload';
12
13
 
13
14
  import { log, LogLevel } from '@unchainedshop/logger';
@@ -90,7 +91,7 @@ connectToMinio().then(function setClient(c) {
90
91
  client = c;
91
92
  });
92
93
 
93
- const createDownloadStream = async (
94
+ const createHttpDownloadStream = async (
94
95
  fileUrl: string,
95
96
  headers: OutgoingHttpHeaders,
96
97
  ): Promise<http.IncomingMessage> => {
@@ -211,8 +212,8 @@ export const MinioAdapter: IFileAdapter = {
211
212
  const fileName = fname || href.split('/').pop();
212
213
  const _id = buildHashedFilename(directoryName, fileName, new Date());
213
214
 
214
- const stream = await createDownloadStream(fileLink, headers);
215
- const type = stream?.headers?.['content-type'] || getMimeType(fileName);
215
+ const stream = await createHttpDownloadStream(fileLink, headers);
216
+ const type = mimeType.lookup(fileName) || stream.headers['content-type'];
216
217
 
217
218
  const metaData = {
218
219
  'Content-Type': type,
@@ -237,6 +238,13 @@ export const MinioAdapter: IFileAdapter = {
237
238
  url: generateMinioUrl(directoryName, _id),
238
239
  } as UploadFileData;
239
240
  },
241
+
242
+ async createDownloadStream(file) {
243
+ if (!client) throw new Error('Minio not connected, check env variables');
244
+
245
+ const stream = await client.getObject(MINIO_BUCKET_NAME, generateMinioPath(file.path, file._id));
246
+ return stream;
247
+ },
240
248
  };
241
249
 
242
250
  FileDirector.registerAdapter(MinioAdapter);
@@ -1,7 +1,9 @@
1
1
  import { FilterDirector, FilterAdapter } from '@unchainedshop/core-filters';
2
2
  import { IFilterAdapter } from '@unchainedshop/types/filters.js';
3
- import { Query } from '@unchainedshop/types/common.js';
4
3
  import escapeStringRegexp from 'escape-string-regexp';
4
+ import { mongodb } from '@unchainedshop/mongodb';
5
+ import { ProductText } from '@unchainedshop/types/products.js';
6
+ import { AssortmentText } from '@unchainedshop/types/assortments.js';
5
7
 
6
8
  const { AMAZON_DOCUMENTDB_COMPAT_MODE } = process.env;
7
9
 
@@ -11,7 +13,7 @@ const LocalSearch: IFilterAdapter = {
11
13
  key: 'shop.unchained.filters.local-search',
12
14
  label: 'Simple Fulltext search with MongoDB',
13
15
  version: '1.0.0',
14
- orderIndex: 1,
16
+ orderIndex: 10,
15
17
 
16
18
  actions: (params) => {
17
19
  return {
@@ -23,7 +25,7 @@ const LocalSearch: IFilterAdapter = {
23
25
 
24
26
  if (!queryString) return productIds;
25
27
 
26
- const selector: Query = AMAZON_DOCUMENTDB_COMPAT_MODE
28
+ const selector: mongodb.Filter<ProductText> = AMAZON_DOCUMENTDB_COMPAT_MODE
27
29
  ? {
28
30
  $or: [
29
31
  { title: { $regex: `${escapeStringRegexp(queryString)}`, $options: 'im' } },
@@ -59,7 +61,7 @@ const LocalSearch: IFilterAdapter = {
59
61
  return assortmentIds;
60
62
  }
61
63
 
62
- const selector: Query = {
64
+ const selector: mongodb.Filter<AssortmentText> = {
63
65
  $text: { $search: queryString },
64
66
  };
65
67
 
@@ -75,6 +77,27 @@ const LocalSearch: IFilterAdapter = {
75
77
 
76
78
  return assortments.map(({ assortmentId }) => assortmentId);
77
79
  },
80
+
81
+ async transformFilterSelector(last) {
82
+ const { queryString, filterIds, includeInactive } = params.searchQuery;
83
+
84
+ if (queryString && !filterIds) {
85
+ // Global search without assortment scope:
86
+ // Return all filters
87
+ const selector = { ...last };
88
+ if (selector?.key) {
89
+ // Do not restrict to keys
90
+ delete selector.key;
91
+ }
92
+ if (!includeInactive) {
93
+ // Include only active filters
94
+ selector.isActive = true;
95
+ }
96
+ return selector;
97
+ }
98
+
99
+ return last;
100
+ },
78
101
  };
79
102
  },
80
103
  };
@@ -1,12 +1,13 @@
1
- import { Db, TimestampFields, _ID } from '@unchainedshop/types/common.js';
1
+ import { mongodb } from '@unchainedshop/mongodb';
2
+ import { TimestampFields } from '@unchainedshop/types/common.js';
2
3
 
3
4
  export type AppleTransaction = {
4
- _id?: _ID;
5
+ _id?: string;
5
6
  matchedTransaction: any;
6
7
  orderId: string;
7
8
  } & TimestampFields;
8
9
 
9
- export const AppleTransactionsCollection = async (db: Db) => {
10
+ export const AppleTransactionsCollection = async (db: mongodb.Db) => {
10
11
  const AppleTransactions = db.collection<AppleTransaction>('payment_apple_iap_processed_transactions');
11
12
 
12
13
  return AppleTransactions;
@@ -1,4 +1,3 @@
1
- import { Db } from '@unchainedshop/types/common.js';
2
1
  import { AppleTransaction, AppleTransactionsCollection } from '../db/AppleTransactionsCollection.js';
3
2
 
4
3
  export interface AppleTransactionsModule {
@@ -7,11 +6,7 @@ export interface AppleTransactionsModule {
7
6
  createTransaction: (doc: AppleTransaction, userId: string) => Promise<string | null>;
8
7
  }
9
8
 
10
- export const configureAppleTransactionsModule = async ({
11
- db,
12
- }: {
13
- db: Db;
14
- }): Promise<AppleTransactionsModule> => {
9
+ export const configureAppleTransactionsModule = async ({ db }): Promise<AppleTransactionsModule> => {
15
10
  const AppleTransactions = await AppleTransactionsCollection(db);
16
11
 
17
12
  return {
@@ -1,17 +1,17 @@
1
- import { Db, TimestampFields, _ID } from '@unchainedshop/types/common.js';
2
- import { Decimal128 } from 'mongodb';
1
+ import { TimestampFields } from '@unchainedshop/types/common.js';
2
+ import { mongodb } from '@unchainedshop/mongodb';
3
3
 
4
4
  export type CryptopayTransaction = {
5
- _id?: _ID;
5
+ _id?: string;
6
6
  blockHeight: number;
7
7
  mostRecentBlockHeight: number;
8
- amount: Decimal128;
8
+ amount: mongodb.Decimal128;
9
9
  currency: string;
10
10
  decimals: number;
11
11
  contract: string;
12
12
  orderPaymentId?: string;
13
13
  } & TimestampFields;
14
14
 
15
- export const CryptopayTransactionsCollection = (db: Db) => {
15
+ export const CryptopayTransactionsCollection = (db: mongodb.Db) => {
16
16
  return db.collection<CryptopayTransaction>('cryptopay_transactions');
17
17
  };
@@ -1,5 +1,4 @@
1
- import { Db } from '@unchainedshop/types/common.js';
2
- import { Decimal128 } from 'mongodb';
1
+ import { mongodb } from '@unchainedshop/mongodb';
3
2
  import { CryptopayTransaction, CryptopayTransactionsCollection } from '../db/CryptopayTransactions.js';
4
3
 
5
4
  export interface CryptopayModule {
@@ -23,7 +22,7 @@ export interface CryptopayModule {
23
22
  getNextDerivationNumber: (currency: string) => Promise<number>;
24
23
  }
25
24
 
26
- export const configureCryptopayModule = ({ db }: { db: Db }): CryptopayModule => {
25
+ export const configureCryptopayModule = ({ db }): CryptopayModule => {
27
26
  const CryptoTransactions = CryptopayTransactionsCollection(db);
28
27
 
29
28
  const getWalletAddress: CryptopayModule['getWalletAddress'] = async (addressId) => {
@@ -64,7 +63,7 @@ export const configureCryptopayModule = ({ db }: { db: Db }): CryptopayModule =>
64
63
  decimals: null,
65
64
  mostRecentBlockHeight: 0,
66
65
  blockHeight: 0,
67
- amount: Decimal128.fromString('0'),
66
+ amount: mongodb.Decimal128.fromString('0'),
68
67
  created: new Date(),
69
68
  },
70
69
  $set: {
@@ -112,7 +111,7 @@ export const configureCryptopayModule = ({ db }: { db: Db }): CryptopayModule =>
112
111
  decimals,
113
112
  blockHeight,
114
113
  mostRecentBlockHeight: blockHeight,
115
- amount: Decimal128.fromString(amount),
114
+ amount: mongodb.Decimal128.fromString(amount),
116
115
  updated: new Date(),
117
116
  },
118
117
  },
@@ -1,5 +1,5 @@
1
1
  import { useMiddlewareWithCurrentContext } from '@unchainedshop/api/express/index.js';
2
- import { UnchainedCore } from '@unchainedshop/types/core.js';
2
+ import { ModuleInput, UnchainedCore } from '@unchainedshop/types/core.js';
3
3
  import express from 'express';
4
4
 
5
5
  // Delivery
@@ -89,7 +89,12 @@ const {
89
89
  // import './files/minio/minio-adapter';
90
90
  // import { minioHandler } from './files/minio/minio-webhook';
91
91
 
92
- export const defaultModules = {
92
+ export const defaultModules: Record<
93
+ string,
94
+ {
95
+ configure: (params: ModuleInput<any>) => any;
96
+ }
97
+ > = {
93
98
  appleTransactions: {
94
99
  configure: configureAppleTransactionsModule,
95
100
  },
@@ -33,7 +33,7 @@ export const DeliverySwissTax: IDeliveryPricingAdapter = {
33
33
  key: 'shop.unchained.pricing.delivery-swiss-tax',
34
34
  version: '1.0.0',
35
35
  label: 'Apply Swiss Tax on Delivery Fees',
36
- orderIndex: 20,
36
+ orderIndex: 80,
37
37
 
38
38
  isActivatedFor: (context) => {
39
39
  return isDeliveryAddressInSwitzerland(context);
@@ -7,7 +7,7 @@ export const HundredOff: IDiscountAdapter = {
7
7
  key: 'shop.unchained.discount.100-off',
8
8
  label: '100 Off',
9
9
  version: '1.0.0',
10
- orderIndex: 1,
10
+ orderIndex: 10,
11
11
 
12
12
  // return true if a discount is allowed to get added manually by a user
13
13
  isManualAdditionAllowed: async () => {
@@ -7,7 +7,7 @@ export const HalfPriceManual: IDiscountAdapter = {
7
7
  key: 'shop.unchained.discount.half-price-manual',
8
8
  label: 'Half Price Manual',
9
9
  version: '1.0.0',
10
- orderIndex: 2,
10
+ orderIndex: 10,
11
11
 
12
12
  // return true if a discount is allowed to get added manually by a user
13
13
  isManualAdditionAllowed: async () => {
@@ -7,7 +7,7 @@ export const HalfPrice: IDiscountAdapter = {
7
7
  key: 'shop.unchained.discount.half-price',
8
8
  label: 'Half Price',
9
9
  version: '1.0.0',
10
- orderIndex: 3,
10
+ orderIndex: 10,
11
11
 
12
12
  // return true if a discount is allowed to get added manually by a user
13
13
  isManualAdditionAllowed: async () => {
@@ -8,7 +8,7 @@ export const OrderDiscount: IOrderPricingAdapter = {
8
8
  key: 'shop.unchained.pricing.order-discount',
9
9
  version: '1.0.0',
10
10
  label: 'Apply Discounts on Total Order Value',
11
- orderIndex: 90,
11
+ orderIndex: 40,
12
12
 
13
13
  isActivatedFor: () => {
14
14
  return true;
@@ -8,7 +8,7 @@ const OrderItemsDiscount: IOrderPricingAdapter = {
8
8
  key: 'shop.unchained.pricing.order-items-discount',
9
9
  version: '1.0.0',
10
10
  label: 'Apply Discounts on Total Value Of Goods',
11
- orderIndex: 89,
11
+ orderIndex: 30,
12
12
 
13
13
  isActivatedFor: () => {
14
14
  return true;
@@ -7,7 +7,7 @@ const OrderPayment: IOrderPricingAdapter = {
7
7
  key: 'shop.unchained.pricing.order-payment',
8
8
  version: '1.0.0',
9
9
  label: 'Add Payment Fees to Order',
10
- orderIndex: 20,
10
+ orderIndex: 10,
11
11
 
12
12
  isActivatedFor: () => {
13
13
  return true;
@@ -0,0 +1,139 @@
1
+ import { OrderPricingAdapter, OrderPricingDirector } from '@unchainedshop/core-orders';
2
+ import { IOrderPricingAdapter, OrderPricingRowCategory } from '@unchainedshop/types/orders.pricing.js';
3
+
4
+ interface PriceRoundSettings {
5
+ defaultPrecision: number;
6
+ roundTo: (value: number, precision: number, currency: string) => number;
7
+ }
8
+
9
+ export const OrderPriceRound: IOrderPricingAdapter & {
10
+ configure: (params: PriceRoundSettings) => void;
11
+ settings: PriceRoundSettings;
12
+ } = {
13
+ ...OrderPricingAdapter,
14
+
15
+ key: 'shop.unchained.pricing.order-round',
16
+ version: '1.0.0',
17
+ label: 'Round order price to the next precision number',
18
+ orderIndex: 90,
19
+
20
+ isActivatedFor: () => {
21
+ return true;
22
+ },
23
+
24
+ settings: {
25
+ defaultPrecision: 5,
26
+ roundTo: (value: number, precision: number) =>
27
+ precision !== 0 ? Math.round(value / precision) * precision : value,
28
+ },
29
+
30
+ configure({ defaultPrecision, roundTo }) {
31
+ if (defaultPrecision) this.settings.defaultPrecision = defaultPrecision;
32
+ if (roundTo) this.settings.roundTo = roundTo;
33
+ },
34
+
35
+ actions: (params) => {
36
+ const pricingAdapter = OrderPricingAdapter.actions(params);
37
+
38
+ const calculateDifference = (amount: number, currency: string) => {
39
+ const roundedAmount = OrderPriceRound.settings.roundTo(
40
+ amount,
41
+ OrderPriceRound.settings.defaultPrecision,
42
+ currency,
43
+ );
44
+ return roundedAmount - amount;
45
+ };
46
+
47
+ return {
48
+ ...pricingAdapter,
49
+
50
+ calculate: async () => {
51
+ const { currency } = params.context;
52
+
53
+ const { amount: deliveryAmount } = params.calculationSheet.total({
54
+ category: OrderPricingRowCategory.Delivery,
55
+ useNetPrice: true,
56
+ });
57
+ if (deliveryAmount) {
58
+ pricingAdapter.resultSheet().addDelivery({
59
+ amount: calculateDifference(deliveryAmount, currency),
60
+ taxAmount: 0,
61
+ meta: {
62
+ adapter: OrderPriceRound.key,
63
+ },
64
+ });
65
+ }
66
+
67
+ const { amount: discountAmount } = params.calculationSheet.total({
68
+ category: OrderPricingRowCategory.Discounts,
69
+ useNetPrice: true,
70
+ });
71
+ if (discountAmount) {
72
+ pricingAdapter.resultSheet().addDiscount({
73
+ amount: calculateDifference(discountAmount, currency),
74
+ taxAmount: 0,
75
+ meta: {
76
+ adapter: OrderPriceRound.key,
77
+ },
78
+ discountId: null,
79
+ });
80
+ }
81
+
82
+ const { amount: itemsAmount } = params.calculationSheet.total({
83
+ category: OrderPricingRowCategory.Items,
84
+ useNetPrice: true,
85
+ });
86
+ if (itemsAmount) {
87
+ pricingAdapter.resultSheet().addItems({
88
+ amount: calculateDifference(itemsAmount, currency),
89
+ taxAmount: 0,
90
+ meta: {
91
+ adapter: OrderPriceRound.key,
92
+ },
93
+ });
94
+ }
95
+
96
+ const { amount: paymentAmount } = params.calculationSheet.total({
97
+ category: OrderPricingRowCategory.Payment,
98
+ useNetPrice: true,
99
+ });
100
+ if (paymentAmount) {
101
+ pricingAdapter.resultSheet().addPayment({
102
+ amount: calculateDifference(paymentAmount, currency),
103
+ taxAmount: 0,
104
+ meta: {
105
+ adapter: OrderPriceRound.key,
106
+ },
107
+ });
108
+ }
109
+
110
+ const taxesAmount = params.calculationSheet.taxSum({
111
+ category: OrderPricingRowCategory.Taxes,
112
+ });
113
+ if (taxesAmount) {
114
+ const taxDifference = calculateDifference(taxesAmount, currency);
115
+ pricingAdapter.resultSheet().calculation.push({
116
+ category: OrderPricingRowCategory.Taxes,
117
+ amount: taxDifference,
118
+ meta: {
119
+ adapter: OrderPriceRound.key,
120
+ },
121
+ });
122
+ // WORKAROUND BECAUSE ORDER TOTAL GROSS PRICE IS CALCULATED WITH A SUM OF EVERYTHING EXCEPT TAXES
123
+ // AS OF UNCHAINED <= 2.6
124
+ pricingAdapter.resultSheet().calculation.push({
125
+ category: null,
126
+ amount: taxDifference,
127
+ meta: {
128
+ adapter: OrderPriceRound.key,
129
+ },
130
+ });
131
+ }
132
+
133
+ return pricingAdapter.calculate();
134
+ },
135
+ };
136
+ },
137
+ };
138
+
139
+ OrderPricingDirector.registerAdapter(OrderPriceRound);
@@ -12,7 +12,7 @@ const ProductDiscount: IProductPricingAdapter = {
12
12
  key: 'shop.unchained.pricing.product-discount',
13
13
  version: '1.0.0',
14
14
  label: 'Apply Discounts on Product Price',
15
- orderIndex: 10,
15
+ orderIndex: 30,
16
16
 
17
17
  isActivatedFor: () => {
18
18
  return true;
@@ -7,7 +7,7 @@ export const ProductPriceRateConversion: IProductPricingAdapter = {
7
7
  key: 'shop.unchained.pricing.rate-conversion',
8
8
  version: '1.0.0',
9
9
  label: 'Generic rate conversion plugin that converts between rates if entry in DB exists.',
10
- orderIndex: 1,
10
+ orderIndex: 10,
11
11
 
12
12
  isActivatedFor: () => {
13
13
  return true;
@@ -0,0 +1,63 @@
1
+ import { IProductPricingAdapter } from '@unchainedshop/types/products.pricing.js';
2
+ import { ProductPricingAdapter, ProductPricingDirector } from '@unchainedshop/core-products';
3
+
4
+ interface PriceRoundSettings {
5
+ defaultPrecision: number;
6
+ roundTo: (value: number, precision: number, currency: string) => number;
7
+ }
8
+
9
+ export const ProductRound: IProductPricingAdapter & {
10
+ configure: (params: PriceRoundSettings) => void;
11
+ settings: PriceRoundSettings;
12
+ } = {
13
+ ...ProductPricingAdapter,
14
+
15
+ key: 'shop.unchained.pricing.product-round',
16
+ version: '1.0.0',
17
+ label: 'Round product price',
18
+ orderIndex: 90,
19
+
20
+ isActivatedFor: () => {
21
+ return true;
22
+ },
23
+
24
+ settings: {
25
+ defaultPrecision: 5,
26
+ roundTo: (value: number, precision: number) => Math.round(value / precision) * precision,
27
+ },
28
+
29
+ configure({ defaultPrecision, roundTo }) {
30
+ if (defaultPrecision) this.settings.defaultPrecision = defaultPrecision;
31
+ if (roundTo) this.settings.roundTo = roundTo;
32
+ },
33
+
34
+ actions: (params) => {
35
+ const pricingAdapter = ProductPricingAdapter.actions(params);
36
+
37
+ return {
38
+ ...pricingAdapter,
39
+
40
+ calculate: async () => {
41
+ const { currency } = params.context;
42
+ const { defaultPrecision, roundTo } = ProductRound.settings;
43
+ const { calculation = [] } = params.calculationSheet;
44
+
45
+ if (calculation?.length) {
46
+ pricingAdapter.resultSheet().resetCalculation(params.calculationSheet);
47
+
48
+ calculation.forEach((item) => {
49
+ const newAmount = roundTo(item.amount, defaultPrecision, currency);
50
+ pricingAdapter.resultSheet().calculation.push({
51
+ ...item,
52
+ amount: newAmount,
53
+ });
54
+ });
55
+ }
56
+
57
+ return pricingAdapter.calculate();
58
+ },
59
+ };
60
+ },
61
+ };
62
+
63
+ ProductPricingDirector.registerAdapter(ProductRound);
@@ -8,7 +8,7 @@ describe('getTaxRate', () => {
8
8
  expect(getTaxRate({
9
9
  product: {} as any,
10
10
  order: {} as any,
11
- })).toBe(0.077);
11
+ })).toBe(0.081);
12
12
  });
13
13
 
14
14
  it('reduced rate', () => {
@@ -17,7 +17,7 @@ describe('getTaxRate', () => {
17
17
  tags: ['swiss-tax-category:reduced']
18
18
  } as any,
19
19
  order: {} as any,
20
- })).toBe(0.025);
20
+ })).toBe(0.026);
21
21
  });
22
22
 
23
23
  it('special rate', () => {
@@ -26,7 +26,7 @@ describe('getTaxRate', () => {
26
26
  tags: ['swiss-tax-category:special']
27
27
  } as any,
28
28
  order: {} as any,
29
- })).toBe(0.037);
29
+ })).toBe(0.038);
30
30
  });
31
31
 
32
32
  it('default rate', () => {
@@ -35,7 +35,7 @@ describe('getTaxRate', () => {
35
35
  tags: ['swiss-tax-category:default']
36
36
  } as any,
37
37
  order: {} as any,
38
- })).toBe(0.077);
38
+ })).toBe(0.081);
39
39
  });
40
40
 
41
41
  })
@@ -7,8 +7,8 @@ import { SwissTaxCategories } from './tax/ch.js';
7
7
  export const getTaxRate = (context: { product: Product; order?: Order }) => {
8
8
  const { product, order } = context;
9
9
 
10
- const productSpecialTaxTag = product.tags?.find(
11
- (tag) => tag?.trim().toLowerCase().startsWith('swiss-tax-category:'),
10
+ const productSpecialTaxTag = product.tags?.find((tag) =>
11
+ tag?.trim().toLowerCase().startsWith('swiss-tax-category:'),
12
12
  );
13
13
  const taxCategory =
14
14
  Object.values(SwissTaxCategories).find(
@@ -41,7 +41,7 @@ export const ProductSwissTax: IProductPricingAdapter = {
41
41
  key: 'shop.unchained.pricing.product-swiss-tax',
42
42
  version: '1.0.0',
43
43
  label: 'Apply Swiss Tax on Product',
44
- orderIndex: 20,
44
+ orderIndex: 80,
45
45
 
46
46
  isActivatedFor: () => {
47
47
  return true;
@@ -3,9 +3,10 @@ import {
3
3
  WarehousingAdapter,
4
4
  WarehousingProviderType,
5
5
  } from '@unchainedshop/core-warehousing';
6
+ import { generateDbObjectId } from '@unchainedshop/mongodb';
6
7
  import { ProductType, ProductContractStandard } from '@unchainedshop/types/products.js';
7
8
  import { IWarehousingAdapter, WarehousingError } from '@unchainedshop/types/warehousing.js';
8
- import { generateDbObjectId, systemLocale } from '@unchainedshop/utils';
9
+ import { systemLocale } from '@unchainedshop/utils';
9
10
 
10
11
  const { MINTER_TOKEN_OFFSET = '0' } = process.env;
11
12
 
@@ -6,7 +6,7 @@ import {
6
6
  import Sheets from 'node-sheets';
7
7
  import { log, LogLevel } from '@unchainedshop/logger';
8
8
  import 'abort-controller/polyfill.js';
9
- import { LRUCache } from 'lru-cache';
9
+ import * as lruCache from 'lru-cache';
10
10
  import { IWarehousingAdapter } from '@unchainedshop/types/warehousing.js';
11
11
 
12
12
  const { NODE_ENV, GOOGLE_SHEETS_ID, GOOGLE_SHEETS_PRIVATE_KEY_DATA } = process.env;
@@ -48,7 +48,7 @@ const updateGoogleCache = async (cache) => {
48
48
  return null;
49
49
  };
50
50
 
51
- const googleCache = new LRUCache({
51
+ const googleCache = new lruCache.LRUCache({
52
52
  max: 500,
53
53
  ttl, // 1 second in dev
54
54
  allowStale: true,