@unchainedshop/plugins 2.0.0-rc.4 → 2.0.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.
- package/jest.config.js +12 -0
- package/lib/enrollments/licensed.d.ts +4 -1
- package/lib/enrollments/licensed.js +1 -1
- package/lib/enrollments/licensed.js.map +1 -1
- package/lib/files/gridfs/index.js +12 -3
- package/lib/files/gridfs/index.js.map +1 -1
- package/lib/plugins-index.d.ts +1 -0
- package/lib/plugins-index.js +1 -0
- package/lib/plugins-index.js.map +1 -1
- package/lib/pricing/discount-100-off.d.ts +2 -1
- package/lib/pricing/discount-100-off.js +1 -1
- package/lib/pricing/discount-100-off.js.map +1 -1
- package/lib/pricing/discount-half-price-manual.d.ts +2 -1
- package/lib/pricing/discount-half-price-manual.js +1 -1
- package/lib/pricing/discount-half-price-manual.js.map +1 -1
- package/lib/pricing/discount-half-price.d.ts +2 -1
- package/lib/pricing/discount-half-price.js +1 -1
- package/lib/pricing/discount-half-price.js.map +1 -1
- package/lib/pricing/order-delivery.d.ts +2 -1
- package/lib/pricing/order-delivery.js +1 -1
- package/lib/pricing/order-delivery.js.map +1 -1
- package/lib/pricing/order-discount.d.ts +10 -1
- package/lib/pricing/order-discount.js +5 -4
- package/lib/pricing/order-discount.js.map +1 -1
- package/lib/pricing/order-items-discount.js +4 -0
- package/lib/pricing/order-items-discount.js.map +1 -1
- package/lib/pricing/product-catalog-price.d.ts +2 -1
- package/lib/pricing/product-catalog-price.js +1 -1
- package/lib/pricing/product-catalog-price.js.map +1 -1
- package/lib/pricing/product-discount.d.ts +2 -1
- package/lib/pricing/product-discount.js +2 -2
- package/lib/pricing/product-discount.js.map +1 -1
- package/lib/pricing/product-price-rateconversion.d.ts +2 -1
- package/lib/pricing/product-price-rateconversion.js +1 -1
- package/lib/pricing/product-price-rateconversion.js.map +1 -1
- package/lib/pricing/product-price-round.d.ts +2 -0
- package/lib/pricing/product-price-round.js +3 -3
- package/lib/pricing/product-price-round.js.map +1 -1
- package/lib/pricing/product-swiss-tax.d.ts +8 -0
- package/lib/pricing/product-swiss-tax.js +3 -3
- package/lib/pricing/product-swiss-tax.js.map +1 -1
- package/lib/worker/email.d.ts +1 -1
- package/lib/worker/email.js +1 -1
- package/lib/worker/email.js.map +1 -1
- package/lib/worker/push-notification.d.ts +1 -0
- package/lib/worker/push-notification.js +73 -0
- package/lib/worker/push-notification.js.map +1 -0
- package/package.json +23 -23
- package/src/enrollments/licensed.ts +1 -1
- package/src/files/gridfs/index.ts +14 -3
- package/src/plugins-index.ts +1 -0
- package/src/pricing/apply-rate.test.ts +92 -0
- package/src/pricing/configureProductPriceRounding.test.ts +44 -0
- package/src/pricing/discount-100-off.test.ts +31 -0
- package/src/pricing/discount-100-off.ts +1 -1
- package/src/pricing/discount-half-price-manual.test.ts +42 -0
- package/src/pricing/discount-half-price-manual.ts +1 -1
- package/src/pricing/discount-half-price.test.ts +40 -0
- package/src/pricing/discount-half-price.ts +1 -1
- package/src/pricing/free-delivery.test.ts +12 -0
- package/src/pricing/getTaxRate.test.ts +69 -0
- package/src/pricing/licenced-enrollment.test.ts +37 -0
- package/src/pricing/order-delivery.test.ts +9 -0
- package/src/pricing/order-delivery.ts +1 -1
- package/src/pricing/order-discount.test.ts +206 -0
- package/src/pricing/order-discount.ts +6 -4
- package/src/pricing/order-items-discount.ts +4 -1
- package/{tests/plugins-index.test.js → src/pricing/plugins-index.test.ts} +0 -0
- package/src/pricing/product-catalog-price.ts +1 -1
- package/src/pricing/product-discount.ts +2 -2
- package/src/pricing/product-price-rateconversion.ts +1 -1
- package/src/pricing/product-price-round.ts +4 -4
- package/src/pricing/product-swiss-tax.test.ts +141 -0
- package/src/pricing/product-swiss-tax.ts +3 -3
- package/src/pricing/roundToNext.test.ts +24 -0
- package/src/worker/email.ts +1 -1
- package/src/worker/push-notification.ts +106 -0
- package/tsconfig.json +3 -22
- package/lib/pricing/utils/cache.d.ts +0 -9
- package/lib/pricing/utils/cache.js +0 -29
- package/lib/pricing/utils/cache.js.map +0 -1
- package/src/pricing/utils/cache.ts +0 -34
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
|
|
2
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
3
|
+
import webPush from 'web-push';
|
|
4
|
+
const { PUSH_NOTIFICATION_PUBLIC_KEY, PUSH_NOTIFICATION_PRIVATE_KEY } = process.env;
|
|
5
|
+
const logger = createLogger('unchained:plugins:worker:push-notification');
|
|
6
|
+
const PushNotificationWorkerPlugin = {
|
|
7
|
+
...WorkerAdapter,
|
|
8
|
+
key: 'shop.unchained.worker-plugin.push-notification',
|
|
9
|
+
label: 'Push Notification',
|
|
10
|
+
version: '1.0',
|
|
11
|
+
type: 'PUSH',
|
|
12
|
+
doWork: async ({ subscription, subject, payload, urgency = null, topic = null }) => {
|
|
13
|
+
logger.debug(`${PushNotificationWorkerPlugin.key} -> doWork: Push -> ${subject}`);
|
|
14
|
+
if (!PUSH_NOTIFICATION_PUBLIC_KEY)
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
error: {
|
|
18
|
+
name: 'VAPID_PUBLIC_KEY_REQUIRED',
|
|
19
|
+
message: 'vapidPublicKey key required to send push notifications',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
if (!PUSH_NOTIFICATION_PRIVATE_KEY)
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: {
|
|
26
|
+
name: 'VAPID_PRIVATE_KEY_REQUIRED',
|
|
27
|
+
message: 'vapidPrivateKey key are required to send push notifications',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
if (!subscription) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: {
|
|
34
|
+
name: 'USER_SUBSCRIPTION_OBJECT_REQUIRED',
|
|
35
|
+
message: 'PUSH service subscription required',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const options = {
|
|
40
|
+
vapidDetails: {
|
|
41
|
+
subject,
|
|
42
|
+
publicKey: PUSH_NOTIFICATION_PUBLIC_KEY,
|
|
43
|
+
privateKey: PUSH_NOTIFICATION_PRIVATE_KEY,
|
|
44
|
+
},
|
|
45
|
+
TTL: 60,
|
|
46
|
+
};
|
|
47
|
+
if (urgency) {
|
|
48
|
+
options.urgency = urgency;
|
|
49
|
+
}
|
|
50
|
+
if (topic) {
|
|
51
|
+
options.topic = topic;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await webPush.sendNotification(subscription, payload, options);
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
error: null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: {
|
|
64
|
+
name: err.name,
|
|
65
|
+
message: err.message,
|
|
66
|
+
stack: err.stack,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
WorkerDirector.registerAdapter(PushNotificationWorkerPlugin);
|
|
73
|
+
//# sourceMappingURL=push-notification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push-notification.js","sourceRoot":"","sources":["../../src/worker/push-notification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,OAAO,MAAM,UAAU,CAAC;AAE/B,MAAM,EAAE,4BAA4B,EAAE,6BAA6B,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;AAEpF,MAAM,MAAM,GAAG,YAAY,CAAC,4CAA4C,CAAC,CAAC;AAa1E,MAAM,4BAA4B,GAS9B;IACF,GAAG,aAAa;IAEhB,GAAG,EAAE,gDAAgD;IACrD,KAAK,EAAE,mBAAmB;IAE1B,OAAO,EAAE,KAAK;IAEd,IAAI,EAAE,MAAM;IAEZ,MAAM,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE,EAAE;QACjF,MAAM,CAAC,KAAK,CAAC,GAAG,4BAA4B,CAAC,GAAG,uBAAuB,OAAO,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,4BAA4B;YAC/B,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,IAAI,EAAE,2BAA2B;oBACjC,OAAO,EAAE,wDAAwD;iBAClE;aACF,CAAC;QACJ,IAAI,CAAC,6BAA6B;YAChC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,6DAA6D;iBACvE;aACF,CAAC;QAEJ,IAAI,CAAC,YAAY,EAAE;YACjB,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,IAAI,EAAE,mCAAmC;oBACzC,OAAO,EAAE,oCAAoC;iBAC9C;aACF,CAAC;SACH;QAED,MAAM,OAAO,GAAwB;YACnC,YAAY,EAAE;gBACZ,OAAO;gBACP,SAAS,EAAE,4BAA4B;gBACvC,UAAU,EAAE,6BAA6B;aAC1C;YACD,GAAG,EAAE,EAAE;SACR,CAAC;QAEF,IAAI,OAAO,EAAE;YACX,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;SAC3B;QAED,IAAI,KAAK,EAAE;YACT,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;SACvB;QAED,IAAI;YACF,MAAM,OAAO,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAE/D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,IAAI;aACZ,CAAC;SACH;QAAC,OAAO,GAAG,EAAE;YACZ,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,OAAO,EAAE,GAAG,CAAC,OAAO;oBACpB,KAAK,EAAE,GAAG,CAAC,KAAK;iBACjB;aACF,CAAC;SACH;IACH,CAAC;CACF,CAAC;AAEF,cAAc,CAAC,eAAe,CAAC,4BAA4B,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/plugins",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"main": "lib/plugins-index.js",
|
|
5
5
|
"types": "lib/plugins-index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -76,38 +76,38 @@
|
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@types/node": "^18.11.18",
|
|
79
|
-
"@unchainedshop/api": "^2.0.
|
|
80
|
-
"@unchainedshop/
|
|
81
|
-
"@unchainedshop/core-
|
|
82
|
-
"@unchainedshop/core-
|
|
83
|
-
"@unchainedshop/core-
|
|
84
|
-
"@unchainedshop/core-
|
|
85
|
-
"@unchainedshop/core-
|
|
86
|
-
"@unchainedshop/core-
|
|
87
|
-
"@unchainedshop/core-
|
|
88
|
-
"@unchainedshop/core-
|
|
89
|
-
"@unchainedshop/core-
|
|
90
|
-
"@unchainedshop/
|
|
91
|
-
"@unchainedshop/
|
|
92
|
-
"@unchainedshop/
|
|
93
|
-
"@unchainedshop/
|
|
94
|
-
"@unchainedshop/
|
|
95
|
-
"@unchainedshop/utils": "^2.0.0-rc.4",
|
|
79
|
+
"@unchainedshop/api": "^2.0.1",
|
|
80
|
+
"@unchainedshop/core-delivery": "^2.0.1",
|
|
81
|
+
"@unchainedshop/core-enrollments": "^2.0.1",
|
|
82
|
+
"@unchainedshop/core-filters": "^2.0.1",
|
|
83
|
+
"@unchainedshop/core-messaging": "^2.0.1",
|
|
84
|
+
"@unchainedshop/core-orders": "^2.0.1",
|
|
85
|
+
"@unchainedshop/core-payment": "^2.0.1",
|
|
86
|
+
"@unchainedshop/core-products": "^2.0.1",
|
|
87
|
+
"@unchainedshop/core-quotations": "^2.0.1",
|
|
88
|
+
"@unchainedshop/core-warehousing": "^2.0.1",
|
|
89
|
+
"@unchainedshop/core-worker": "^2.0.1",
|
|
90
|
+
"@unchainedshop/events": "^2.0.1",
|
|
91
|
+
"@unchainedshop/file-upload": "^2.0.1",
|
|
92
|
+
"@unchainedshop/logger": "^2.0.1",
|
|
93
|
+
"@unchainedshop/types": "^2.0.1",
|
|
94
|
+
"@unchainedshop/utils": "^2.0.1",
|
|
96
95
|
"bluebird": "^3.7.2",
|
|
97
96
|
"event-iterator": "^2.0.0",
|
|
98
97
|
"express": "^4.18.2",
|
|
99
|
-
"jest": "^29.
|
|
98
|
+
"jest": "^29.4.1",
|
|
100
99
|
"JSONStream": "^1.3.5",
|
|
101
100
|
"minio": "^7.0.32",
|
|
102
101
|
"mongodb": "^4.13.0",
|
|
103
102
|
"node-sheets": "^1.2.0",
|
|
104
|
-
"nodemailer": "^6.
|
|
103
|
+
"nodemailer": "^6.9.1",
|
|
105
104
|
"open": "^8.4.0",
|
|
106
|
-
"redis": "^4.
|
|
105
|
+
"redis": "^4.6.4",
|
|
107
106
|
"request": "^2.88.2",
|
|
108
107
|
"stripe": "^10.17.0",
|
|
109
|
-
"ts-jest": "^29.0.
|
|
110
|
-
"typescript": "^4.9.
|
|
108
|
+
"ts-jest": "^29.0.5",
|
|
109
|
+
"typescript": "^4.9.5",
|
|
110
|
+
"web-push": "^3.5.0",
|
|
111
111
|
"xml-js": "^1.6.11",
|
|
112
112
|
"xml2js": "0.4.23"
|
|
113
113
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IEnrollmentAdapter } from '@unchainedshop/types/enrollments.js';
|
|
2
2
|
import { EnrollmentDirector, EnrollmentAdapter } from '@unchainedshop/core-enrollments';
|
|
3
3
|
|
|
4
|
-
const rangeMatcher = (date = new Date()) => {
|
|
4
|
+
export const rangeMatcher = (date = new Date()) => {
|
|
5
5
|
const timestamp = date.getTime();
|
|
6
6
|
return ({ start, end }) => {
|
|
7
7
|
const startTimestamp = new Date(start).getTime();
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import mongodb from '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
|
+
// eslint-disable-next-line
|
|
9
|
+
let { GridFSBucket } = mongodb;
|
|
10
|
+
if (isMeteor) {
|
|
11
|
+
const { MongoInternals } = require('meteor/mongo'); // eslint-disable-line
|
|
12
|
+
GridFSBucket = MongoInternals.NpmModule.GridFSBucket;
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
return {
|
|
5
16
|
createWriteStream: async (directoryName, fileId, fileName) => {
|
|
6
17
|
const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
|
|
7
|
-
const bucket = new
|
|
18
|
+
const bucket = new GridFSBucket(db, options);
|
|
8
19
|
return bucket.openUploadStreamWithId(fileId, fileName);
|
|
9
20
|
},
|
|
10
21
|
createReadStream: async (directoryName, fileId) => {
|
|
11
22
|
const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
|
|
12
|
-
const bucket = new
|
|
23
|
+
const bucket = new GridFSBucket(db, options);
|
|
13
24
|
return bucket.openDownloadStream(fileId);
|
|
14
25
|
},
|
|
15
26
|
removeFileFromBucket: async (directoryName, fileId) => {
|
|
16
27
|
const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
|
|
17
|
-
const bucket = new
|
|
28
|
+
const bucket = new GridFSBucket(db, options);
|
|
18
29
|
return bucket.delete(fileId);
|
|
19
30
|
},
|
|
20
31
|
};
|
package/src/plugins-index.ts
CHANGED
|
@@ -60,6 +60,7 @@ import './worker/http-request.js';
|
|
|
60
60
|
import './worker/heartbeat.js';
|
|
61
61
|
import './worker/email.js';
|
|
62
62
|
import './worker/sms.js';
|
|
63
|
+
import './worker/push-notification.js';
|
|
63
64
|
import './worker/update-ecb-rates.js';
|
|
64
65
|
import './worker/update-coinbase-rates.js';
|
|
65
66
|
import { configureExportToken } from './worker/export-token.js';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {applyRate} from './product-discount.js'
|
|
2
|
+
describe('applyRate', () => {
|
|
3
|
+
it('applies the rate correctly when a rate is provided', () => {
|
|
4
|
+
const configuration = {
|
|
5
|
+
rate: 0.1
|
|
6
|
+
};
|
|
7
|
+
const amount = 100;
|
|
8
|
+
expect(applyRate(configuration, amount)).toBe(10);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('applies the fixed rate correctly when a fixed rate is provided', () => {
|
|
12
|
+
const configuration = {
|
|
13
|
+
fixedRate: 50
|
|
14
|
+
};
|
|
15
|
+
const amount = 100;
|
|
16
|
+
expect(applyRate(configuration, amount)).toBe(50);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('applies the fixed rate correctly when a fixed rate is provided and the amount is less than fixed rate', () => {
|
|
20
|
+
const configuration = {
|
|
21
|
+
fixedRate: 150
|
|
22
|
+
};
|
|
23
|
+
const amount = 100;
|
|
24
|
+
expect(applyRate(configuration, amount)).toBe(100);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('applies the rate correctly when rate is less than or equal to 0', () => {
|
|
28
|
+
const configuration = {
|
|
29
|
+
rate: -0.1
|
|
30
|
+
};
|
|
31
|
+
const amount = 100;
|
|
32
|
+
expect(applyRate(configuration, amount)).toBe(-10);
|
|
33
|
+
});
|
|
34
|
+
it('applies the rate correctly when only rate is provided and amount is negative', () => {
|
|
35
|
+
const configuration = {
|
|
36
|
+
rate: 0.1
|
|
37
|
+
};
|
|
38
|
+
const amount = -100;
|
|
39
|
+
expect(applyRate(configuration, amount)).toBe(-10);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
it('applies the rate correctly when only rate is provided and amount is negative', () => {
|
|
44
|
+
const configuration = {
|
|
45
|
+
rate: 0.1
|
|
46
|
+
};
|
|
47
|
+
const amount = -100;
|
|
48
|
+
expect(applyRate(configuration, amount)).toBe(-10);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
it('applies the rate correctly when only rate is provided and amount is negative', () => {
|
|
53
|
+
const configuration = {
|
|
54
|
+
rate: 0.1
|
|
55
|
+
};
|
|
56
|
+
const amount = -100;
|
|
57
|
+
expect(applyRate(configuration, amount)).toBe(-10);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
it('applies the rate correctly when only rate is provided and amount is negative', () => {
|
|
63
|
+
const configuration = {
|
|
64
|
+
rate: 0.1
|
|
65
|
+
};
|
|
66
|
+
const amount = -100;
|
|
67
|
+
expect(applyRate(configuration, amount)).toBe(-10);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
it('returns 0 when rate and fixed rate are not provided', () => {
|
|
72
|
+
const configuration = {};
|
|
73
|
+
const amount = 100;
|
|
74
|
+
expect(applyRate(configuration, amount)).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('applies the rate correctly when only rate is provided and amount is negative', () => {
|
|
78
|
+
const configuration = {
|
|
79
|
+
rate: 0.1
|
|
80
|
+
};
|
|
81
|
+
const amount = -100;
|
|
82
|
+
expect(applyRate(configuration, amount)).toBe(-10);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns 0 when rate and fixed rate are not provided', () => {
|
|
86
|
+
const configuration = {};
|
|
87
|
+
const amount = 100;
|
|
88
|
+
expect(applyRate(configuration, amount)).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {configureProductPriceRounding, ProductPriceRoundSettings } from './product-price-round.js'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('configureProductPriceRounding', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
ProductPriceRoundSettings.configurations = {};
|
|
8
|
+
ProductPriceRoundSettings.defaultPrecision = 50;
|
|
9
|
+
ProductPriceRoundSettings.skip = [];
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('updates the default precision correctly', () => {
|
|
13
|
+
configureProductPriceRounding({ defaultPrecision: 100 } as any);
|
|
14
|
+
expect(ProductPriceRoundSettings.defaultPrecision).toBe(100);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('updates the configurations correctly', () => {
|
|
18
|
+
configureProductPriceRounding({ configurations: { 'product1': 25 } } as any);
|
|
19
|
+
expect(ProductPriceRoundSettings.configurations).toEqual({ 'product1': 25 });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('updates the skip list correctly', () => {
|
|
23
|
+
configureProductPriceRounding({ skip: ['product1'] } as any);
|
|
24
|
+
expect(ProductPriceRoundSettings.skip).toEqual(['product1']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('updates multiple properties correctly', () => {
|
|
28
|
+
configureProductPriceRounding({
|
|
29
|
+
configurations: { 'product1': 25 },
|
|
30
|
+
defaultPrecision: 100,
|
|
31
|
+
skip: ['product1']
|
|
32
|
+
});
|
|
33
|
+
expect(ProductPriceRoundSettings.configurations).toEqual({ 'product1': 25 });
|
|
34
|
+
expect(ProductPriceRoundSettings.defaultPrecision).toBe(100);
|
|
35
|
+
expect(ProductPriceRoundSettings.skip).toEqual(['product1']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('does not update any properties when no arguments are passed', () => {
|
|
39
|
+
configureProductPriceRounding({} as any);
|
|
40
|
+
expect(ProductPriceRoundSettings.configurations).toEqual({});
|
|
41
|
+
expect(ProductPriceRoundSettings.defaultPrecision).toBe(50);
|
|
42
|
+
expect(ProductPriceRoundSettings.skip).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HundredOff } from './discount-100-off.js';
|
|
2
|
+
|
|
3
|
+
describe('HundredOff', () => {
|
|
4
|
+
it('isManualAdditionAllowed', async () => {
|
|
5
|
+
let x
|
|
6
|
+
expect(await HundredOff.isManualAdditionAllowed(x)).toBeTruthy();
|
|
7
|
+
});
|
|
8
|
+
it('isManualRemovalAllowed', async () => {
|
|
9
|
+
expect(await HundredOff.isManualRemovalAllowed()).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
it('isValidForSystemTriggering', async () => {
|
|
12
|
+
const context = {};
|
|
13
|
+
const actions = await HundredOff.actions({ context } as any);
|
|
14
|
+
expect(await actions.isValidForSystemTriggering()).toBeFalsy();
|
|
15
|
+
});
|
|
16
|
+
it('isValidForCodeTriggering', async () => {
|
|
17
|
+
const context = {};
|
|
18
|
+
const actions = await HundredOff.actions({ context } as any);
|
|
19
|
+
expect(await actions.isValidForCodeTriggering({ code: '100OFF' })).toBeTruthy();
|
|
20
|
+
expect(await actions.isValidForCodeTriggering({ code: 'wrongcode' })).toBeFalsy();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('discountForPricingAdapterKey', async () => {
|
|
24
|
+
const context = {};
|
|
25
|
+
const actions = await HundredOff.actions({ context } as any);
|
|
26
|
+
expect(
|
|
27
|
+
actions.discountForPricingAdapterKey({ pricingAdapterKey: 'shop.unchained.pricing.order-discount' } as any)
|
|
28
|
+
).toEqual({ fixedRate: 10000 });
|
|
29
|
+
expect(actions.discountForPricingAdapterKey({ pricingAdapterKey: 'shop.unchained.pricing.other-discount' } as any)).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IDiscountAdapter } from '@unchainedshop/types/discount.js';
|
|
2
2
|
import { OrderDiscountDirector, OrderDiscountAdapter } from '@unchainedshop/core-orders';
|
|
3
3
|
|
|
4
|
-
const HundredOff: IDiscountAdapter = {
|
|
4
|
+
export const HundredOff: IDiscountAdapter = {
|
|
5
5
|
...OrderDiscountAdapter,
|
|
6
6
|
|
|
7
7
|
key: 'shop.unchained.discount.100-off',
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HalfPriceManual } from './discount-half-price-manual.js';
|
|
2
|
+
|
|
3
|
+
describe('HalfPriceManual', () => {
|
|
4
|
+
it('isManualAdditionAllowed', async () => {
|
|
5
|
+
let x;
|
|
6
|
+
expect(await HalfPriceManual.isManualAdditionAllowed(x)).toBeTruthy();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('isManualRemovalAllowed', async () => {
|
|
10
|
+
expect(await HalfPriceManual.isManualRemovalAllowed()).toBeTruthy();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('isValidForSystemTriggering', async () => {
|
|
14
|
+
const context = {};
|
|
15
|
+
const actions = await HalfPriceManual.actions({ context } as any);
|
|
16
|
+
expect(await actions.isValidForSystemTriggering()).toBeFalsy();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('isValidForCodeTriggering', async () => {
|
|
20
|
+
const context = {};
|
|
21
|
+
const actions = await HalfPriceManual.actions({ context } as any);
|
|
22
|
+
expect(await actions.isValidForCodeTriggering({ code: 'HALFPRICE' })).toBeTruthy();
|
|
23
|
+
expect(await actions.isValidForCodeTriggering({ code: 'othercode' })).toBeFalsy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('discountForPricingAdapterKey', async () => {
|
|
27
|
+
const context = {};
|
|
28
|
+
const actions = await HalfPriceManual.actions({ context } as any);
|
|
29
|
+
expect(
|
|
30
|
+
actions.discountForPricingAdapterKey({ pricingAdapterKey: 'shop.unchained.pricing.product-discount' } as any)
|
|
31
|
+
).toEqual({ rate: 0.5 });
|
|
32
|
+
expect(actions.discountForPricingAdapterKey({ pricingAdapterKey: 'shop.unchained.pricing.other-discount' } as any)).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('Actions', async () => {
|
|
36
|
+
const context = {};
|
|
37
|
+
const actions = await HalfPriceManual.actions({ context } as any);
|
|
38
|
+
expect(typeof actions.isValidForSystemTriggering).toBe('function');
|
|
39
|
+
expect(typeof actions.isValidForCodeTriggering).toBe('function');
|
|
40
|
+
expect(typeof actions.discountForPricingAdapterKey).toBe('function');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IDiscountAdapter } from '@unchainedshop/types/discount.js';
|
|
2
2
|
import { OrderDiscountDirector, OrderDiscountAdapter } from '@unchainedshop/core-orders';
|
|
3
3
|
|
|
4
|
-
const HalfPriceManual: IDiscountAdapter = {
|
|
4
|
+
export const HalfPriceManual: IDiscountAdapter = {
|
|
5
5
|
...OrderDiscountAdapter,
|
|
6
6
|
|
|
7
7
|
key: 'shop.unchained.discount.half-price-manual',
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HalfPrice } from './discount-half-price.js';
|
|
2
|
+
|
|
3
|
+
describe('HalfPrice', () => {
|
|
4
|
+
it('isManualAdditionAllowed', async () => {
|
|
5
|
+
expect(await HalfPrice.isManualAdditionAllowed('')).toBeFalsy();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('isManualRemovalAllowed', async () => {
|
|
9
|
+
expect(await HalfPrice.isManualRemovalAllowed()).toBeFalsy();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('isValidForSystemTriggering', async () => {
|
|
13
|
+
const context = {
|
|
14
|
+
modules: {
|
|
15
|
+
users: {
|
|
16
|
+
findUserById: import.meta.jest.fn(() => Promise.resolve({ tags: ['half-price'] }))
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
order: { userId: 'user-id' }
|
|
20
|
+
};
|
|
21
|
+
const actions = await HalfPrice.actions({ context } as any);
|
|
22
|
+
expect(await actions.isValidForSystemTriggering()).toBeTruthy();
|
|
23
|
+
|
|
24
|
+
context.modules.users.findUserById.mockImplementationOnce(() => Promise.resolve({ tags: [] }));
|
|
25
|
+
expect(await actions.isValidForSystemTriggering()).toBeFalsy();
|
|
26
|
+
});
|
|
27
|
+
it('discountForPricingAdapterKey', async () => {
|
|
28
|
+
const context = {};
|
|
29
|
+
const actions = await HalfPrice.actions({ context } as any);
|
|
30
|
+
expect(actions.discountForPricingAdapterKey({ pricingAdapterKey: 'shop.unchained.pricing.product-discount' } as any)).toEqual({ rate: 0.5 });
|
|
31
|
+
expect(actions.discountForPricingAdapterKey({ pricingAdapterKey: 'shop.unchained.pricing.other-discount' } as any)).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('isValidForCodeTriggering', async () => {
|
|
35
|
+
const context = {};
|
|
36
|
+
const actions = await HalfPrice.actions({ context } as any);
|
|
37
|
+
|
|
38
|
+
expect(await actions.isValidForCodeTriggering({code: ''})).toBeFalsy();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IDiscountAdapter } from '@unchainedshop/types/discount.js';
|
|
2
2
|
import { OrderDiscountDirector, OrderDiscountAdapter } from '@unchainedshop/core-orders';
|
|
3
3
|
|
|
4
|
-
const HalfPrice: IDiscountAdapter = {
|
|
4
|
+
export const HalfPrice: IDiscountAdapter = {
|
|
5
5
|
...OrderDiscountAdapter,
|
|
6
6
|
|
|
7
7
|
key: 'shop.unchained.discount.half-price',
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { SwissTaxCategories, getTaxRate } from './product-swiss-tax.js';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('getTaxRate', () => {
|
|
6
|
+
it('default rate', () => {
|
|
7
|
+
const context: any = {
|
|
8
|
+
product: {
|
|
9
|
+
tags: []
|
|
10
|
+
},
|
|
11
|
+
provider: {
|
|
12
|
+
configuration: [],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
expect(getTaxRate(context)).toBe(0.077);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('reduced rate', () => {
|
|
19
|
+
const context: any = {
|
|
20
|
+
product: {
|
|
21
|
+
tags: ['swiss-tax-category:reduced']
|
|
22
|
+
},
|
|
23
|
+
provider: {
|
|
24
|
+
configuration: [
|
|
25
|
+
{
|
|
26
|
+
key: 'swiss-tax-category',
|
|
27
|
+
value: SwissTaxCategories.REDUCED.rate(),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
expect(getTaxRate(context)).toBe(0.025);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('special rate', () => {
|
|
36
|
+
const context: any = {
|
|
37
|
+
product: {
|
|
38
|
+
tags: ['swiss-tax-category:special']
|
|
39
|
+
},
|
|
40
|
+
provider: {
|
|
41
|
+
configuration: [
|
|
42
|
+
{
|
|
43
|
+
key: 'swiss-tax-category',
|
|
44
|
+
value: SwissTaxCategories.SPECIAL.rate(),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
expect(getTaxRate(context)).toBe(0.037);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('default rate', () => {
|
|
53
|
+
const context: any = {
|
|
54
|
+
product: {
|
|
55
|
+
tags: ['swiss-tax-category:default']
|
|
56
|
+
},
|
|
57
|
+
provider: {
|
|
58
|
+
configuration: [
|
|
59
|
+
{
|
|
60
|
+
key: 'swiss-tax-category',
|
|
61
|
+
value: SwissTaxCategories.DEFAULT.rate(),
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
expect(getTaxRate(context)).toBe(0.077);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { rangeMatcher } from "../enrollments/licensed.js";
|
|
2
|
+
|
|
3
|
+
describe('rangeMatcher', () => {
|
|
4
|
+
const now = new Date();
|
|
5
|
+
|
|
6
|
+
const tomorrow = new Date();
|
|
7
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
8
|
+
const tomorrowTimestamp = tomorrow.getTime();
|
|
9
|
+
const yesterday = new Date();
|
|
10
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
11
|
+
const yesterdayTimestamp = yesterday.getTime();
|
|
12
|
+
|
|
13
|
+
it('matches correctly when the date is within the range', () => {
|
|
14
|
+
const range = { start: yesterday, end: tomorrow };
|
|
15
|
+
const matcher = rangeMatcher(now);
|
|
16
|
+
expect(matcher(range)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('does not match when the date is before the start of the range', () => {
|
|
20
|
+
const range = { start: tomorrow, end: tomorrow };
|
|
21
|
+
const matcher = rangeMatcher(yesterday);
|
|
22
|
+
expect(matcher(range)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('does not match when the date is after the end of the range', () => {
|
|
26
|
+
const range = { start: yesterday, end: yesterday };
|
|
27
|
+
const matcher = rangeMatcher(tomorrow);
|
|
28
|
+
expect(matcher(range)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('matches correctly when the date is not provided', () => {
|
|
32
|
+
const range = { start: yesterdayTimestamp, end: tomorrowTimestamp };
|
|
33
|
+
const matcher = rangeMatcher();
|
|
34
|
+
expect(matcher(range)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IOrderPricingAdapter } from '@unchainedshop/types/orders.pricing.js';
|
|
2
2
|
import { OrderPricingDirector, OrderPricingAdapter } from '@unchainedshop/core-orders';
|
|
3
3
|
|
|
4
|
-
const OrderDelivery: IOrderPricingAdapter = {
|
|
4
|
+
export const OrderDelivery: IOrderPricingAdapter = {
|
|
5
5
|
...OrderPricingAdapter,
|
|
6
6
|
|
|
7
7
|
key: 'shop.unchained.pricing.order-delivery',
|