@jsdev_ninja/core 0.12.7 → 0.12.8

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 (55) hide show
  1. package/dist/core.cjs.js +1 -1
  2. package/dist/core.cjs.js.map +1 -1
  3. package/dist/core.es.js +1001 -853
  4. package/dist/core.es.js.map +1 -1
  5. package/dist/core.umd.js +1 -1
  6. package/dist/core.umd.js.map +1 -1
  7. package/dist/entities/Discount/__tests__/engine.test.d.ts +2 -0
  8. package/dist/entities/Discount/__tests__/engine.test.d.ts.map +1 -0
  9. package/dist/entities/Discount/__tests__/engine.test.js +298 -0
  10. package/dist/entities/Discount/__tests__/factory.test.d.ts +2 -0
  11. package/dist/entities/Discount/__tests__/factory.test.d.ts.map +1 -0
  12. package/dist/entities/Discount/__tests__/factory.test.js +75 -0
  13. package/dist/entities/Discount/__tests__/integration.test.d.ts +2 -0
  14. package/dist/entities/Discount/__tests__/integration.test.d.ts.map +1 -0
  15. package/dist/entities/Discount/__tests__/integration.test.js +115 -0
  16. package/dist/entities/Discount/__tests__/simple.test.d.ts +2 -0
  17. package/dist/entities/Discount/__tests__/simple.test.d.ts.map +1 -0
  18. package/dist/entities/Discount/__tests__/simple.test.js +186 -0
  19. package/dist/entities/Discount/__tests__/utils.test.d.ts +2 -0
  20. package/dist/entities/Discount/__tests__/utils.test.d.ts.map +1 -0
  21. package/dist/entities/Discount/__tests__/utils.test.js +55 -0
  22. package/dist/entities/Discount/engine.d.ts +29 -0
  23. package/dist/entities/Discount/engine.d.ts.map +1 -0
  24. package/dist/entities/Discount/engine.js +80 -0
  25. package/dist/entities/Discount/factory.d.ts +10 -0
  26. package/dist/entities/Discount/factory.d.ts.map +1 -0
  27. package/dist/entities/Discount/factory.js +18 -0
  28. package/dist/entities/Discount/index.d.ts +9 -0
  29. package/dist/entities/Discount/index.d.ts.map +1 -0
  30. package/dist/entities/Discount/index.js +8 -0
  31. package/dist/entities/Discount/strategies/BundleStrategy.d.ts +12 -0
  32. package/dist/entities/Discount/strategies/BundleStrategy.d.ts.map +1 -0
  33. package/dist/entities/Discount/strategies/BundleStrategy.js +80 -0
  34. package/dist/entities/Discount/strategies/__tests__/BundleStrategy.test.d.ts +2 -0
  35. package/dist/entities/Discount/strategies/__tests__/BundleStrategy.test.d.ts.map +1 -0
  36. package/dist/entities/Discount/strategies/__tests__/BundleStrategy.test.js +265 -0
  37. package/dist/entities/Discount/strategy.d.ts +6 -0
  38. package/dist/entities/Discount/strategy.d.ts.map +1 -0
  39. package/dist/entities/Discount/strategy.js +1 -0
  40. package/dist/entities/Discount/types.d.ts +148 -0
  41. package/dist/entities/Discount/types.d.ts.map +1 -0
  42. package/dist/entities/Discount/types.js +27 -0
  43. package/dist/entities/Discount/utils.d.ts +29 -0
  44. package/dist/entities/Discount/utils.d.ts.map +1 -0
  45. package/dist/entities/Discount/utils.js +39 -0
  46. package/dist/tsconfig.app.tsbuildinfo +1 -1
  47. package/dist/utils/index.d.ts.map +1 -1
  48. package/dist/utils/index.js +16 -60
  49. package/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts +12 -7
  50. package/lib/utils/index.ts +19 -80
  51. package/package.json +1 -1
  52. package/dist/entities/Discount.d.ts +0 -71
  53. package/dist/entities/Discount.d.ts.map +0 -1
  54. package/dist/entities/Discount.js +0 -20
  55. package/lib/entities/Discount.ts +0 -23
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.test.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/__tests__/engine.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,298 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { DiscountEngine } from "../engine";
3
+ import { DiscountStrategyFactory } from "../factory";
4
+ import { BundleDiscountStrategy } from "../strategies/BundleStrategy";
5
+ describe("DiscountEngine", () => {
6
+ beforeEach(() => {
7
+ DiscountStrategyFactory.clearStrategies();
8
+ DiscountStrategyFactory.registerStrategy("bundle", new BundleDiscountStrategy());
9
+ });
10
+ afterEach(() => {
11
+ DiscountStrategyFactory.clearStrategies();
12
+ });
13
+ describe("calculateDiscounts", () => {
14
+ it("should apply bundle discount correctly", () => {
15
+ const cart = [
16
+ {
17
+ amount: 2,
18
+ product: {
19
+ id: "product1",
20
+ price: 10,
21
+ },
22
+ },
23
+ {
24
+ amount: 1,
25
+ product: {
26
+ id: "product2",
27
+ price: 15,
28
+ },
29
+ },
30
+ ];
31
+ const discounts = [
32
+ {
33
+ type: "Discount",
34
+ storeId: "store1",
35
+ companyId: "company1",
36
+ id: "bundle1",
37
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
38
+ active: true,
39
+ startDate: Date.now() - 1000,
40
+ endDate: Date.now() + 1000,
41
+ variant: {
42
+ variantType: "bundle",
43
+ productsId: ["product1", "product2"],
44
+ requiredQuantity: 3,
45
+ bundlePrice: 25,
46
+ },
47
+ conditions: {
48
+ stackable: false,
49
+ },
50
+ },
51
+ ];
52
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
53
+ expect(result.totalDiscount).toBe(10);
54
+ expect(result.appliedDiscounts).toHaveLength(1);
55
+ expect(result.appliedDiscounts[0].discountId).toBe("bundle1");
56
+ expect(result.items).toHaveLength(2);
57
+ // Check final prices
58
+ const product1Item = result.items.find(item => item.product.id === "product1");
59
+ expect(product1Item?.finalPrice).toBeCloseTo(7.14, 2); // 10 - (5.71/2) = 7.145
60
+ const product2Item = result.items.find(item => item.product.id === "product2");
61
+ expect(product2Item?.finalPrice).toBeCloseTo(10.71, 2); // 15 - 4.29 = 10.71
62
+ });
63
+ it("should handle multiple discounts", () => {
64
+ const cart = [
65
+ {
66
+ amount: 4,
67
+ product: {
68
+ id: "product1",
69
+ price: 10,
70
+ },
71
+ },
72
+ ];
73
+ const discounts = [
74
+ {
75
+ type: "Discount",
76
+ storeId: "store1",
77
+ companyId: "company1",
78
+ id: "bundle1",
79
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
80
+ active: true,
81
+ startDate: Date.now() - 1000,
82
+ endDate: Date.now() + 1000,
83
+ variant: {
84
+ variantType: "bundle",
85
+ productsId: ["product1"],
86
+ requiredQuantity: 3,
87
+ bundlePrice: 25,
88
+ },
89
+ conditions: {
90
+ stackable: true, // Allow stacking
91
+ },
92
+ },
93
+ {
94
+ type: "Discount",
95
+ storeId: "store1",
96
+ companyId: "company1",
97
+ id: "bundle2",
98
+ name: [{ lang: "he", value: "Buy 2 for $15" }],
99
+ active: true,
100
+ startDate: Date.now() - 1000,
101
+ endDate: Date.now() + 1000,
102
+ variant: {
103
+ variantType: "bundle",
104
+ productsId: ["product1"],
105
+ requiredQuantity: 2,
106
+ bundlePrice: 15,
107
+ },
108
+ conditions: {
109
+ stackable: true,
110
+ },
111
+ },
112
+ ];
113
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
114
+ expect(result.appliedDiscounts).toHaveLength(2);
115
+ expect(result.totalDiscount).toBeGreaterThan(0);
116
+ });
117
+ it("should filter out inactive discounts", () => {
118
+ const cart = [
119
+ {
120
+ amount: 3,
121
+ product: {
122
+ id: "product1",
123
+ price: 10,
124
+ },
125
+ },
126
+ ];
127
+ const discounts = [
128
+ {
129
+ type: "Discount",
130
+ storeId: "store1",
131
+ companyId: "company1",
132
+ id: "bundle1",
133
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
134
+ active: false, // Inactive
135
+ startDate: Date.now() - 1000,
136
+ endDate: Date.now() + 1000,
137
+ variant: {
138
+ variantType: "bundle",
139
+ productsId: ["product1"],
140
+ requiredQuantity: 3,
141
+ bundlePrice: 25,
142
+ },
143
+ conditions: {
144
+ stackable: false,
145
+ },
146
+ },
147
+ ];
148
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
149
+ expect(result.totalDiscount).toBe(0);
150
+ expect(result.appliedDiscounts).toHaveLength(0);
151
+ });
152
+ it("should handle empty cart", () => {
153
+ const cart = [];
154
+ const discounts = [
155
+ {
156
+ type: "Discount",
157
+ storeId: "store1",
158
+ companyId: "company1",
159
+ id: "bundle1",
160
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
161
+ active: true,
162
+ startDate: Date.now() - 1000,
163
+ endDate: Date.now() + 1000,
164
+ variant: {
165
+ variantType: "bundle",
166
+ productsId: ["product1"],
167
+ requiredQuantity: 3,
168
+ bundlePrice: 25,
169
+ },
170
+ conditions: {
171
+ stackable: false,
172
+ },
173
+ },
174
+ ];
175
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
176
+ expect(result.totalDiscount).toBe(0);
177
+ expect(result.appliedDiscounts).toHaveLength(0);
178
+ expect(result.items).toHaveLength(0);
179
+ });
180
+ it("should handle empty discounts", () => {
181
+ const cart = [
182
+ {
183
+ amount: 2,
184
+ product: {
185
+ id: "product1",
186
+ price: 10,
187
+ },
188
+ },
189
+ ];
190
+ const discounts = [];
191
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
192
+ expect(result.totalDiscount).toBe(0);
193
+ expect(result.appliedDiscounts).toHaveLength(0);
194
+ expect(result.items).toHaveLength(1);
195
+ expect(result.items[0].finalPrice).toBe(10); // No discount applied
196
+ });
197
+ });
198
+ describe("isDiscountActive", () => {
199
+ it("should return true for active discount", () => {
200
+ const discount = {
201
+ type: "Discount",
202
+ storeId: "store1",
203
+ companyId: "company1",
204
+ id: "bundle1",
205
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
206
+ active: true,
207
+ startDate: Date.now() - 1000,
208
+ endDate: Date.now() + 1000,
209
+ variant: {
210
+ variantType: "bundle",
211
+ productsId: ["product1"],
212
+ requiredQuantity: 3,
213
+ bundlePrice: 25,
214
+ },
215
+ };
216
+ expect(DiscountEngine.isDiscountActive(discount)).toBe(true);
217
+ });
218
+ it("should return false for inactive discount", () => {
219
+ const discount = {
220
+ type: "Discount",
221
+ storeId: "store1",
222
+ companyId: "company1",
223
+ id: "bundle1",
224
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
225
+ active: false,
226
+ startDate: Date.now() - 1000,
227
+ endDate: Date.now() + 1000,
228
+ variant: {
229
+ variantType: "bundle",
230
+ productsId: ["product1"],
231
+ requiredQuantity: 3,
232
+ bundlePrice: 25,
233
+ },
234
+ };
235
+ expect(DiscountEngine.isDiscountActive(discount)).toBe(false);
236
+ });
237
+ it("should return false for expired discount", () => {
238
+ const discount = {
239
+ type: "Discount",
240
+ storeId: "store1",
241
+ companyId: "company1",
242
+ id: "bundle1",
243
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
244
+ active: true,
245
+ startDate: Date.now() - 2000,
246
+ endDate: Date.now() - 1000, // Expired
247
+ variant: {
248
+ variantType: "bundle",
249
+ productsId: ["product1"],
250
+ requiredQuantity: 3,
251
+ bundlePrice: 25,
252
+ },
253
+ };
254
+ expect(DiscountEngine.isDiscountActive(discount)).toBe(false);
255
+ });
256
+ });
257
+ describe("getActiveDiscounts", () => {
258
+ it("should filter active discounts", () => {
259
+ const discounts = [
260
+ {
261
+ type: "Discount",
262
+ storeId: "store1",
263
+ companyId: "company1",
264
+ id: "active1",
265
+ name: [{ lang: "he", value: "Active Discount" }],
266
+ active: true,
267
+ startDate: Date.now() - 1000,
268
+ endDate: Date.now() + 1000,
269
+ variant: {
270
+ variantType: "bundle",
271
+ productsId: ["product1"],
272
+ requiredQuantity: 3,
273
+ bundlePrice: 25,
274
+ },
275
+ },
276
+ {
277
+ type: "Discount",
278
+ storeId: "store1",
279
+ companyId: "company1",
280
+ id: "inactive1",
281
+ name: [{ lang: "he", value: "Inactive Discount" }],
282
+ active: false,
283
+ startDate: Date.now() - 1000,
284
+ endDate: Date.now() + 1000,
285
+ variant: {
286
+ variantType: "bundle",
287
+ productsId: ["product1"],
288
+ requiredQuantity: 3,
289
+ bundlePrice: 25,
290
+ },
291
+ },
292
+ ];
293
+ const activeDiscounts = DiscountEngine.getActiveDiscounts(discounts);
294
+ expect(activeDiscounts).toHaveLength(1);
295
+ expect(activeDiscounts[0].id).toBe("active1");
296
+ });
297
+ });
298
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=factory.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.test.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/__tests__/factory.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { DiscountStrategyFactory } from "../factory";
3
+ describe("DiscountStrategyFactory", () => {
4
+ beforeEach(() => {
5
+ DiscountStrategyFactory.clearStrategies();
6
+ });
7
+ afterEach(() => {
8
+ DiscountStrategyFactory.clearStrategies();
9
+ });
10
+ it("should register and get strategy", () => {
11
+ const mockStrategy = {
12
+ canApply: () => false,
13
+ calculate: () => ({ applicable: false, discountAmount: 0, affectedItems: [] }),
14
+ };
15
+ DiscountStrategyFactory.registerStrategy("bundle", mockStrategy);
16
+ const discount = {
17
+ type: "Discount",
18
+ storeId: "store1",
19
+ companyId: "company1",
20
+ id: "test1",
21
+ name: [{ lang: "he", value: "Test Discount" }],
22
+ active: true,
23
+ startDate: Date.now() - 1000,
24
+ endDate: Date.now() + 1000,
25
+ variant: {
26
+ variantType: "bundle",
27
+ productsId: ["product1"],
28
+ requiredQuantity: 3,
29
+ bundlePrice: 25,
30
+ },
31
+ };
32
+ const result = DiscountStrategyFactory.getStrategy(discount);
33
+ expect(result).toBe(mockStrategy);
34
+ });
35
+ it("should return null for unregistered strategy", () => {
36
+ const discount = {
37
+ type: "Discount",
38
+ storeId: "store1",
39
+ companyId: "company1",
40
+ id: "test1",
41
+ name: [{ lang: "he", value: "Test Discount" }],
42
+ active: true,
43
+ startDate: Date.now() - 1000,
44
+ endDate: Date.now() + 1000,
45
+ variant: {
46
+ variantType: "bundle",
47
+ productsId: ["product1"],
48
+ requiredQuantity: 3,
49
+ bundlePrice: 25,
50
+ },
51
+ };
52
+ const result = DiscountStrategyFactory.getStrategy(discount);
53
+ expect(result).toBeNull();
54
+ });
55
+ it("should get registered types", () => {
56
+ const mockStrategy = {
57
+ canApply: () => false,
58
+ calculate: () => ({ applicable: false, discountAmount: 0, affectedItems: [] }),
59
+ };
60
+ DiscountStrategyFactory.registerStrategy("bundle", mockStrategy);
61
+ DiscountStrategyFactory.registerStrategy("percentage", mockStrategy);
62
+ const types = DiscountStrategyFactory.getRegisteredTypes();
63
+ expect(types).toEqual(["bundle", "percentage"]);
64
+ });
65
+ it("should clear strategies", () => {
66
+ const mockStrategy = {
67
+ canApply: () => false,
68
+ calculate: () => ({ applicable: false, discountAmount: 0, affectedItems: [] }),
69
+ };
70
+ DiscountStrategyFactory.registerStrategy("bundle", mockStrategy);
71
+ expect(DiscountStrategyFactory.getRegisteredTypes()).toHaveLength(1);
72
+ DiscountStrategyFactory.clearStrategies();
73
+ expect(DiscountStrategyFactory.getRegisteredTypes()).toHaveLength(0);
74
+ });
75
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/__tests__/integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { DiscountEngine } from "../engine";
3
+ import { DiscountStrategyFactory } from "../factory";
4
+ import { BundleDiscountStrategy } from "../strategies/BundleStrategy";
5
+ describe("Discount System Integration", () => {
6
+ beforeEach(() => {
7
+ DiscountStrategyFactory.clearStrategies();
8
+ DiscountStrategyFactory.registerStrategy("bundle", new BundleDiscountStrategy());
9
+ });
10
+ afterEach(() => {
11
+ DiscountStrategyFactory.clearStrategies();
12
+ });
13
+ it("should handle real-world bundle scenario", () => {
14
+ // Scenario: Buy 3 dairy products for $20
15
+ const cart = [
16
+ {
17
+ amount: 2, // 2 milk
18
+ product: {
19
+ id: "milk",
20
+ price: 8, // $8 each
21
+ },
22
+ },
23
+ {
24
+ amount: 1, // 1 yogurt
25
+ product: {
26
+ id: "yogurt",
27
+ price: 6, // $6 each
28
+ },
29
+ },
30
+ {
31
+ amount: 1, // 1 bread (not in bundle)
32
+ product: {
33
+ id: "bread",
34
+ price: 4, // $4 each
35
+ },
36
+ },
37
+ ];
38
+ const dairyBundle = {
39
+ type: "Discount",
40
+ storeId: "store1",
41
+ companyId: "company1",
42
+ id: "dairy-bundle",
43
+ name: [{ lang: "he", value: "3 Dairy Products for $20" }],
44
+ active: true,
45
+ startDate: Date.now() - 1000,
46
+ endDate: Date.now() + 1000,
47
+ variant: {
48
+ variantType: "bundle",
49
+ productsId: ["milk", "yogurt", "cheese"], // Dairy products
50
+ requiredQuantity: 3,
51
+ bundlePrice: 20,
52
+ },
53
+ conditions: {
54
+ stackable: false,
55
+ },
56
+ };
57
+ const result = DiscountEngine.calculateDiscounts(cart, [dairyBundle]);
58
+ // Original prices:
59
+ // milk: 2 × $8 = $16
60
+ // yogurt: 1 × $6 = $6
61
+ // bread: 1 × $4 = $4
62
+ // Total: $26
63
+ // Bundle applies to milk + yogurt (3 items for $20)
64
+ // Remaining: bread at $4
65
+ // New total: $24
66
+ expect(result.totalDiscount).toBe(2); // $26 - $24
67
+ expect(result.appliedDiscounts).toHaveLength(1);
68
+ expect(result.appliedDiscounts[0].discountId).toBe("dairy-bundle");
69
+ // Check individual item prices
70
+ const milkItem = result.items.find(item => item.product.id === "milk");
71
+ expect(milkItem?.finalPrice).toBeCloseTo(7.28, 2); // 8 - (1.45/2) = 7.275
72
+ const yogurtItem = result.items.find(item => item.product.id === "yogurt");
73
+ expect(yogurtItem?.finalPrice).toBeCloseTo(5.45, 2); // 6 - 0.55 = 5.45
74
+ const breadItem = result.items.find(item => item.product.id === "bread");
75
+ expect(breadItem?.finalPrice).toBe(4); // No discount
76
+ });
77
+ it("should handle BOGO scenario", () => {
78
+ // Scenario: Buy 2 Get 1 Free
79
+ const cart = [
80
+ {
81
+ amount: 3, // Buy 3 items
82
+ product: {
83
+ id: "product1",
84
+ price: 10, // $10 each
85
+ },
86
+ },
87
+ ];
88
+ const bogoDiscount = {
89
+ type: "Discount",
90
+ storeId: "store1",
91
+ companyId: "company1",
92
+ id: "bogo",
93
+ name: [{ lang: "he", value: "Buy 2 Get 1 Free" }],
94
+ active: true,
95
+ startDate: Date.now() - 1000,
96
+ endDate: Date.now() + 1000,
97
+ variant: {
98
+ variantType: "bundle",
99
+ productsId: ["product1"],
100
+ requiredQuantity: 3, // Buy 2 + get 1 free = 3 items
101
+ bundlePrice: 20, // Price of 2 items (10 × 2)
102
+ },
103
+ conditions: {
104
+ stackable: false,
105
+ },
106
+ };
107
+ const result = DiscountEngine.calculateDiscounts(cart, [bogoDiscount]);
108
+ // Original: 3 × $10 = $30
109
+ // Bundle: 3 items for $20
110
+ // Discount: $10
111
+ expect(result.totalDiscount).toBe(10);
112
+ expect(result.appliedDiscounts).toHaveLength(1);
113
+ expect(result.items[0].finalPrice).toBeCloseTo(6.67, 2); // $20 / 3
114
+ });
115
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=simple.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simple.test.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/__tests__/simple.test.ts"],"names":[],"mappings":""}