@jsdev_ninja/core 0.13.27 → 0.13.29

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 (145) hide show
  1. package/dist/core.cjs.js +2 -0
  2. package/dist/core.cjs.js.map +1 -0
  3. package/dist/core.es.js +3584 -0
  4. package/dist/core.es.js.map +1 -0
  5. package/dist/core.umd.js +2 -0
  6. package/dist/core.umd.js.map +1 -0
  7. package/dist/lib/entities/Address.d.ts +28 -0
  8. package/dist/lib/entities/Address.d.ts.map +1 -0
  9. package/dist/lib/entities/Address.js +10 -0
  10. package/dist/lib/entities/Atoms.d.ts +16 -0
  11. package/dist/lib/entities/Atoms.d.ts.map +1 -0
  12. package/{lib/entities/Atoms.ts → dist/lib/entities/Atoms.js} +2 -6
  13. package/dist/lib/entities/Cart.d.ts +1278 -0
  14. package/dist/lib/entities/Cart.d.ts.map +1 -0
  15. package/dist/lib/entities/Cart.js +18 -0
  16. package/dist/lib/entities/Category.d.ts +103 -0
  17. package/dist/lib/entities/Category.d.ts.map +1 -0
  18. package/dist/lib/entities/Category.js +20 -0
  19. package/dist/lib/entities/Company.d.ts +16 -0
  20. package/dist/lib/entities/Company.d.ts.map +1 -0
  21. package/dist/lib/entities/Company.js +6 -0
  22. package/dist/lib/entities/DeliveryNote.d.ts +248 -0
  23. package/dist/lib/entities/DeliveryNote.d.ts.map +1 -0
  24. package/dist/lib/entities/DeliveryNote.js +61 -0
  25. package/dist/lib/entities/Discount/__tests__/engine.test.d.ts +2 -0
  26. package/dist/lib/entities/Discount/__tests__/engine.test.d.ts.map +1 -0
  27. package/dist/lib/entities/Discount/__tests__/engine.test.js +298 -0
  28. package/dist/lib/entities/Discount/__tests__/factory.test.d.ts +2 -0
  29. package/dist/lib/entities/Discount/__tests__/factory.test.d.ts.map +1 -0
  30. package/dist/lib/entities/Discount/__tests__/factory.test.js +75 -0
  31. package/dist/lib/entities/Discount/__tests__/integration.test.d.ts +2 -0
  32. package/dist/lib/entities/Discount/__tests__/integration.test.d.ts.map +1 -0
  33. package/dist/lib/entities/Discount/__tests__/integration.test.js +115 -0
  34. package/dist/lib/entities/Discount/__tests__/simple.test.d.ts +2 -0
  35. package/dist/lib/entities/Discount/__tests__/simple.test.d.ts.map +1 -0
  36. package/dist/lib/entities/Discount/__tests__/simple.test.js +186 -0
  37. package/dist/lib/entities/Discount/__tests__/utils.test.d.ts +2 -0
  38. package/dist/lib/entities/Discount/__tests__/utils.test.d.ts.map +1 -0
  39. package/dist/lib/entities/Discount/__tests__/utils.test.js +55 -0
  40. package/dist/lib/entities/Discount/engine.d.ts +29 -0
  41. package/dist/lib/entities/Discount/engine.d.ts.map +1 -0
  42. package/dist/lib/entities/Discount/engine.js +85 -0
  43. package/dist/lib/entities/Discount/factory.d.ts +10 -0
  44. package/dist/lib/entities/Discount/factory.d.ts.map +1 -0
  45. package/dist/lib/entities/Discount/factory.js +18 -0
  46. package/dist/lib/entities/Discount/index.d.ts +9 -0
  47. package/dist/lib/entities/Discount/index.d.ts.map +1 -0
  48. package/{lib/entities/Discount/index.ts → dist/lib/entities/Discount/index.js} +0 -2
  49. package/dist/lib/entities/Discount/strategies/BundleStrategy.d.ts +12 -0
  50. package/dist/lib/entities/Discount/strategies/BundleStrategy.d.ts.map +1 -0
  51. package/dist/lib/entities/Discount/strategies/BundleStrategy.js +78 -0
  52. package/dist/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.d.ts +2 -0
  53. package/dist/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.d.ts.map +1 -0
  54. package/dist/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.js +265 -0
  55. package/dist/lib/entities/Discount/strategy.d.ts +6 -0
  56. package/dist/lib/entities/Discount/strategy.d.ts.map +1 -0
  57. package/dist/lib/entities/Discount/types.d.ts +148 -0
  58. package/dist/lib/entities/Discount/types.d.ts.map +1 -0
  59. package/dist/lib/entities/Discount/types.js +29 -0
  60. package/dist/lib/entities/Discount/utils.d.ts +29 -0
  61. package/dist/lib/entities/Discount/utils.d.ts.map +1 -0
  62. package/{lib/entities/Discount/utils.ts → dist/lib/entities/Discount/utils.js} +12 -15
  63. package/dist/lib/entities/FavoriteProduct.d.ts +25 -0
  64. package/dist/lib/entities/FavoriteProduct.d.ts.map +1 -0
  65. package/dist/lib/entities/FavoriteProduct.js +9 -0
  66. package/dist/lib/entities/Invoice.d.ts +210 -0
  67. package/dist/lib/entities/Invoice.d.ts.map +1 -0
  68. package/dist/lib/entities/Invoice.js +49 -0
  69. package/dist/lib/entities/Locale.d.ts +22 -0
  70. package/dist/lib/entities/Locale.d.ts.map +1 -0
  71. package/{lib/entities/Locale.ts → dist/lib/entities/Locale.js} +2 -4
  72. package/dist/lib/entities/Order.d.ts +1806 -0
  73. package/dist/lib/entities/Order.d.ts.map +1 -0
  74. package/dist/lib/entities/Order.js +63 -0
  75. package/dist/lib/entities/Organization.d.ts +100 -0
  76. package/dist/lib/entities/Organization.d.ts.map +1 -0
  77. package/dist/lib/entities/Organization.js +17 -0
  78. package/dist/lib/entities/Payment/index.d.ts +4 -0
  79. package/dist/lib/entities/Payment/index.d.ts.map +1 -0
  80. package/dist/lib/entities/Payment/index.js +4 -0
  81. package/dist/lib/entities/Product.d.ts +694 -0
  82. package/dist/lib/entities/Product.d.ts.map +1 -0
  83. package/dist/lib/entities/Product.js +61 -0
  84. package/dist/lib/entities/Profile.d.ts +95 -0
  85. package/dist/lib/entities/Profile.d.ts.map +1 -0
  86. package/dist/lib/entities/Profile.js +22 -0
  87. package/dist/lib/entities/Store.d.ts +93 -0
  88. package/dist/lib/entities/Store.d.ts.map +1 -0
  89. package/dist/lib/entities/Store.js +21 -0
  90. package/dist/lib/entities/index.d.ts +16 -0
  91. package/dist/lib/entities/index.d.ts.map +1 -0
  92. package/{lib/entities/index.ts → dist/lib/entities/index.js} +1 -0
  93. package/dist/lib/firebase-api/app.d.ts +2 -0
  94. package/dist/lib/firebase-api/app.d.ts.map +1 -0
  95. package/dist/lib/firebase-api/app.js +16 -0
  96. package/dist/lib/firebase-api/index.d.ts +75 -0
  97. package/dist/lib/firebase-api/index.d.ts.map +1 -0
  98. package/dist/lib/firebase-api/index.js +33 -0
  99. package/dist/lib/index.d.ts +4 -0
  100. package/dist/lib/index.d.ts.map +1 -0
  101. package/dist/lib/utils/index.d.ts +109 -0
  102. package/dist/lib/utils/index.d.ts.map +1 -0
  103. package/dist/lib/utils/index.js +95 -0
  104. package/dist/tsconfig.app.tsbuildinfo +1 -0
  105. package/dist/tsconfig.node.tsbuildinfo +1 -0
  106. package/dist/vite.config.d.ts +3 -0
  107. package/dist/vite.config.d.ts.map +1 -0
  108. package/dist/vite.config.js +25 -0
  109. package/package.json +8 -1
  110. package/eslint.config.js +0 -28
  111. package/index.html +0 -13
  112. package/lib/entities/Address.ts +0 -13
  113. package/lib/entities/Cart.ts +0 -24
  114. package/lib/entities/Category.ts +0 -35
  115. package/lib/entities/Company.ts +0 -9
  116. package/lib/entities/DeliveryNote.ts +0 -70
  117. package/lib/entities/Discount/__tests__/engine.test.ts +0 -337
  118. package/lib/entities/Discount/__tests__/factory.test.ts +0 -91
  119. package/lib/entities/Discount/__tests__/integration.test.ts +0 -132
  120. package/lib/entities/Discount/__tests__/simple.test.ts +0 -215
  121. package/lib/entities/Discount/__tests__/utils.test.ts +0 -69
  122. package/lib/entities/Discount/engine.ts +0 -138
  123. package/lib/entities/Discount/factory.ts +0 -25
  124. package/lib/entities/Discount/strategies/BundleStrategy.ts +0 -119
  125. package/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts +0 -295
  126. package/lib/entities/Discount/strategy.ts +0 -6
  127. package/lib/entities/Discount/types.ts +0 -68
  128. package/lib/entities/FavoriteProduct.ts +0 -12
  129. package/lib/entities/Invoice.ts +0 -57
  130. package/lib/entities/Order.ts +0 -67
  131. package/lib/entities/Organization.ts +0 -23
  132. package/lib/entities/Product.ts +0 -68
  133. package/lib/entities/Profile.ts +0 -56
  134. package/lib/entities/Store.ts +0 -24
  135. package/lib/firebase-api/app.ts +0 -18
  136. package/lib/firebase-api/index.ts +0 -46
  137. package/lib/utils/index.ts +0 -127
  138. package/src/vite-env.d.ts +0 -1
  139. package/tsconfig.app.json +0 -20
  140. package/tsconfig.json +0 -7
  141. package/tsconfig.node.json +0 -26
  142. package/vite.config.ts +0 -27
  143. package/vitest.config.ts +0 -10
  144. /package/{src/main.tsx → dist/lib/entities/Discount/strategy.js} +0 -0
  145. /package/{lib/index.tsx → dist/lib/index.js} +0 -0
@@ -0,0 +1,186 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { DiscountEngine } from "../engine";
3
+ import { DiscountStrategyFactory } from "../factory";
4
+ import { BundleDiscountStrategy } from "../strategies/BundleStrategy";
5
+ describe("Simple Discount System Tests", () => {
6
+ beforeEach(() => {
7
+ DiscountStrategyFactory.clearStrategies();
8
+ DiscountStrategyFactory.registerStrategy("bundle", new BundleDiscountStrategy());
9
+ });
10
+ it("should calculate basic bundle discount correctly", () => {
11
+ const cart = [
12
+ {
13
+ amount: 2,
14
+ product: {
15
+ id: "product1",
16
+ price: 10,
17
+ },
18
+ },
19
+ {
20
+ amount: 1,
21
+ product: {
22
+ id: "product2",
23
+ price: 15,
24
+ },
25
+ },
26
+ ];
27
+ const discounts = [
28
+ {
29
+ type: "Discount",
30
+ storeId: "store1",
31
+ companyId: "company1",
32
+ id: "bundle1",
33
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
34
+ active: true,
35
+ startDate: Date.now() - 1000,
36
+ endDate: Date.now() + 1000,
37
+ variant: {
38
+ variantType: "bundle",
39
+ productsId: ["product1", "product2"],
40
+ requiredQuantity: 3,
41
+ bundlePrice: 25,
42
+ },
43
+ conditions: {
44
+ stackable: false,
45
+ },
46
+ },
47
+ ];
48
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
49
+ expect(result.totalDiscount).toBe(10);
50
+ expect(result.appliedDiscounts).toHaveLength(1);
51
+ });
52
+ it("should not apply inactive discount", () => {
53
+ const cart = [
54
+ {
55
+ amount: 3,
56
+ product: {
57
+ id: "product1",
58
+ price: 10,
59
+ },
60
+ },
61
+ ];
62
+ const discounts = [
63
+ {
64
+ type: "Discount",
65
+ storeId: "store1",
66
+ companyId: "company1",
67
+ id: "bundle1",
68
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
69
+ active: false, // Inactive
70
+ startDate: Date.now() - 1000,
71
+ endDate: Date.now() + 1000,
72
+ variant: {
73
+ variantType: "bundle",
74
+ productsId: ["product1"],
75
+ requiredQuantity: 3,
76
+ bundlePrice: 25,
77
+ },
78
+ conditions: {
79
+ stackable: false,
80
+ },
81
+ },
82
+ ];
83
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
84
+ expect(result.totalDiscount).toBe(0);
85
+ expect(result.appliedDiscounts).toHaveLength(0);
86
+ });
87
+ it("should not apply discount when not enough items", () => {
88
+ const cart = [
89
+ {
90
+ amount: 2, // Only 2 items, need 3
91
+ product: {
92
+ id: "product1",
93
+ price: 10,
94
+ },
95
+ },
96
+ ];
97
+ const discounts = [
98
+ {
99
+ type: "Discount",
100
+ storeId: "store1",
101
+ companyId: "company1",
102
+ id: "bundle1",
103
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
104
+ active: true,
105
+ startDate: Date.now() - 1000,
106
+ endDate: Date.now() + 1000,
107
+ variant: {
108
+ variantType: "bundle",
109
+ productsId: ["product1"],
110
+ requiredQuantity: 3,
111
+ bundlePrice: 25,
112
+ },
113
+ conditions: {
114
+ stackable: false,
115
+ },
116
+ },
117
+ ];
118
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
119
+ expect(result.totalDiscount).toBe(0);
120
+ expect(result.appliedDiscounts).toHaveLength(0);
121
+ });
122
+ it("should handle Buy 2 Get 1 Free scenario", () => {
123
+ const cart = [
124
+ {
125
+ amount: 3, // Buy 3 items
126
+ product: {
127
+ id: "product1",
128
+ price: 10, // $10 each
129
+ },
130
+ },
131
+ ];
132
+ const bogoDiscount = {
133
+ type: "Discount",
134
+ storeId: "store1",
135
+ companyId: "company1",
136
+ id: "bogo",
137
+ name: [{ lang: "he", value: "Buy 2 Get 1 Free" }],
138
+ active: true,
139
+ startDate: Date.now() - 1000,
140
+ endDate: Date.now() + 1000,
141
+ variant: {
142
+ variantType: "bundle",
143
+ productsId: ["product1"],
144
+ requiredQuantity: 3, // Buy 2 + get 1 free = 3 items
145
+ bundlePrice: 20, // Price of 2 items (10 × 2)
146
+ },
147
+ conditions: {
148
+ stackable: false,
149
+ },
150
+ };
151
+ const result = DiscountEngine.calculateDiscounts(cart, [bogoDiscount]);
152
+ // Original: 3 × $10 = $30
153
+ // Bundle: 3 items for $20
154
+ // Discount: $10
155
+ expect(result.totalDiscount).toBe(10);
156
+ expect(result.appliedDiscounts).toHaveLength(1);
157
+ });
158
+ it("should handle empty cart", () => {
159
+ const cart = [];
160
+ const discounts = [
161
+ {
162
+ type: "Discount",
163
+ storeId: "store1",
164
+ companyId: "company1",
165
+ id: "bundle1",
166
+ name: [{ lang: "he", value: "Buy 3 for $25" }],
167
+ active: true,
168
+ startDate: Date.now() - 1000,
169
+ endDate: Date.now() + 1000,
170
+ variant: {
171
+ variantType: "bundle",
172
+ productsId: ["product1"],
173
+ requiredQuantity: 3,
174
+ bundlePrice: 25,
175
+ },
176
+ conditions: {
177
+ stackable: false,
178
+ },
179
+ },
180
+ ];
181
+ const result = DiscountEngine.calculateDiscounts(cart, discounts);
182
+ expect(result.totalDiscount).toBe(0);
183
+ expect(result.appliedDiscounts).toHaveLength(0);
184
+ expect(result.items).toHaveLength(0);
185
+ });
186
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../../../../lib/entities/Discount/__tests__/utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { formatCurrency, formatCurrencyString, ensureNonNegative, calculatePercentageDiscount } from "../utils";
3
+ describe("Discount Utils", () => {
4
+ describe("formatCurrency", () => {
5
+ it("should format numbers to 2 decimal places", () => {
6
+ expect(formatCurrency(10)).toBe(10.00);
7
+ expect(formatCurrency(10.123)).toBe(10.12);
8
+ expect(formatCurrency(10.125)).toBe(10.13); // Rounds up
9
+ expect(formatCurrency(10.124)).toBe(10.12); // Rounds down
10
+ expect(formatCurrency(0)).toBe(0.00);
11
+ });
12
+ it("should return a number type", () => {
13
+ const result = formatCurrency(10.123);
14
+ expect(typeof result).toBe("number");
15
+ });
16
+ });
17
+ describe("formatCurrencyString", () => {
18
+ it("should format numbers to 2 decimal places as string", () => {
19
+ expect(formatCurrencyString(10)).toBe("10.00");
20
+ expect(formatCurrencyString(10.123)).toBe("10.12");
21
+ expect(formatCurrencyString(10.125)).toBe("10.13");
22
+ expect(formatCurrencyString(0)).toBe("0.00");
23
+ });
24
+ it("should return a string type", () => {
25
+ const result = formatCurrencyString(10.123);
26
+ expect(typeof result).toBe("string");
27
+ });
28
+ });
29
+ describe("ensureNonNegative", () => {
30
+ it("should return 0 for negative values", () => {
31
+ expect(ensureNonNegative(-5)).toBe(0.00);
32
+ expect(ensureNonNegative(-0.01)).toBe(0.00);
33
+ });
34
+ it("should return formatted value for positive values", () => {
35
+ expect(ensureNonNegative(10.123)).toBe(10.12);
36
+ expect(ensureNonNegative(0)).toBe(0.00);
37
+ });
38
+ });
39
+ describe("calculatePercentageDiscount", () => {
40
+ it("should calculate percentage discount correctly", () => {
41
+ expect(calculatePercentageDiscount(100, 80)).toBe(20.00); // 20% discount
42
+ expect(calculatePercentageDiscount(50, 25)).toBe(50.00); // 50% discount
43
+ expect(calculatePercentageDiscount(100, 100)).toBe(0.00); // No discount
44
+ });
45
+ it("should handle edge cases", () => {
46
+ expect(calculatePercentageDiscount(0, 0)).toBe(0.00);
47
+ expect(calculatePercentageDiscount(100, 0)).toBe(100.00); // 100% discount
48
+ });
49
+ it("should return formatted number", () => {
50
+ const result = calculatePercentageDiscount(100, 90.5);
51
+ expect(typeof result).toBe("number");
52
+ expect(result).toBe(9.50); // 9.5% discount
53
+ });
54
+ });
55
+ });
@@ -0,0 +1,29 @@
1
+ import { TDiscount, AppliedDiscount } from "./types";
2
+ export declare class DiscountEngine {
3
+ static calculateDiscounts(cart: Array<{
4
+ amount: number;
5
+ product: {
6
+ id: string;
7
+ price: number;
8
+ };
9
+ }>, discounts: TDiscount[], user?: Record<string, unknown>): {
10
+ items: Array<{
11
+ amount: number;
12
+ product: {
13
+ id: string;
14
+ price: number;
15
+ };
16
+ originalPrice: number;
17
+ finalPrice: number;
18
+ finalDiscount: number;
19
+ appliedDiscounts: string[];
20
+ }>;
21
+ totalDiscount: number;
22
+ appliedDiscounts: AppliedDiscount[];
23
+ };
24
+ private static filterActiveDiscounts;
25
+ private static calculateFinalPrices;
26
+ static isDiscountActive(discount: TDiscount): boolean;
27
+ static getActiveDiscounts(discounts: TDiscount[]): TDiscount[];
28
+ }
29
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAmB,MAAM,SAAS,CAAC;AAYtE,qBAAa,cAAc;IAC1B,MAAM,CAAC,kBAAkB,CACxB,IAAI,EAAE,KAAK,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE;YACR,EAAE,EAAE,MAAM,CAAC;YACX,KAAK,EAAE,MAAM,CAAC;SACd,CAAC;KACF,CAAC,EACF,SAAS,EAAE,SAAS,EAAE,EACtB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B;QACF,KAAK,EAAE,KAAK,CAAC;YACZ,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE;gBACR,EAAE,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM,CAAC;aACd,CAAC;YACF,aAAa,EAAE,MAAM,CAAC;YACtB,UAAU,EAAE,MAAM,CAAC;YACnB,aAAa,EAAE,MAAM,CAAC;YACtB,gBAAgB,EAAE,MAAM,EAAE,CAAC;SAC3B,CAAC,CAAC;QACH,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,eAAe,EAAE,CAAC;KACpC;IAuDD,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAOpC,OAAO,CAAC,MAAM,CAAC,oBAAoB;IA8BnC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,SAAS,GAAG,OAAO;IAKrD,MAAM,CAAC,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE;CAG9D"}
@@ -0,0 +1,85 @@
1
+ import { DiscountStrategyFactory } from "./factory";
2
+ // discount engine - calculate discounts and return final prices
3
+ // should register all discount types
4
+ // should receive all store discounts
5
+ // should check active discount that should be applied on cart
6
+ // should find best discount per product
7
+ // should apply discount to if stackable or best one
8
+ // any applyd discount should mark products that effected and price before and after and average price per product in cart
9
+ // should return items list with all calculations and final prices and discounts
10
+ export class DiscountEngine {
11
+ static calculateDiscounts(cart, discounts, user) {
12
+ const context = {
13
+ cart,
14
+ user,
15
+ appliedDiscounts: [],
16
+ };
17
+ // Filter active discounts
18
+ const activeDiscounts = this.filterActiveDiscounts(discounts);
19
+ // Apply discounts
20
+ const appliedDiscounts = [];
21
+ for (const discount of activeDiscounts) {
22
+ const strategy = DiscountStrategyFactory.getStrategy(discount);
23
+ if (!strategy)
24
+ continue;
25
+ // Check if discount can be applied
26
+ if (!strategy.canApply(discount, context))
27
+ continue;
28
+ // Check if discount is stackable
29
+ // todo
30
+ if (!discount.conditions?.stackable && appliedDiscounts.length > 0)
31
+ continue;
32
+ // Calculate discount
33
+ const result = strategy.calculate(discount, context);
34
+ if (!result.applicable)
35
+ continue;
36
+ // Apply discount
37
+ appliedDiscounts.push({
38
+ discountId: discount.id,
39
+ discountName: discount.name[0]?.value || "Discount",
40
+ discountAmount: Number(result.discountAmount.toFixed(2)),
41
+ affectedItems: result.affectedItems,
42
+ });
43
+ // Update context for next iteration
44
+ context.appliedDiscounts = appliedDiscounts;
45
+ }
46
+ // Calculate final prices
47
+ const items = this.calculateFinalPrices(cart, appliedDiscounts);
48
+ const totalDiscount = appliedDiscounts.reduce((sum, discount) => sum + discount.discountAmount, 0);
49
+ return {
50
+ items,
51
+ totalDiscount: Number(totalDiscount.toFixed(2)),
52
+ appliedDiscounts,
53
+ };
54
+ }
55
+ static filterActiveDiscounts(discounts) {
56
+ const now = Date.now();
57
+ return discounts.filter((discount) => discount.active && discount.startDate <= now && discount.endDate >= now);
58
+ }
59
+ static calculateFinalPrices(cart, appliedDiscounts) {
60
+ return cart.map((item) => {
61
+ const itemDiscounts = appliedDiscounts.filter((discount) => discount.affectedItems.some((affected) => affected.productId === item.product.id));
62
+ const totalItemDiscount = itemDiscounts.reduce((sum, discount) => {
63
+ const affectedItem = discount.affectedItems.find((affected) => affected.productId === item.product.id);
64
+ return sum + (affectedItem?.discountAmount || 0);
65
+ }, 0);
66
+ const discountPerUnit = totalItemDiscount / item.amount;
67
+ const finalPrice = item.product.price - discountPerUnit;
68
+ return {
69
+ amount: item.amount,
70
+ product: item.product,
71
+ originalPrice: Number(item.product.price.toFixed(2)),
72
+ finalPrice: Number(Math.max(0, finalPrice).toFixed(2)),
73
+ finalDiscount: Number(totalItemDiscount.toFixed(2)),
74
+ appliedDiscounts: itemDiscounts.map((d) => d.discountId),
75
+ };
76
+ });
77
+ }
78
+ static isDiscountActive(discount) {
79
+ const now = Date.now();
80
+ return discount.active && discount.startDate <= now && discount.endDate >= now;
81
+ }
82
+ static getActiveDiscounts(discounts) {
83
+ return this.filterActiveDiscounts(discounts);
84
+ }
85
+ }
@@ -0,0 +1,10 @@
1
+ import { TDiscount } from "./types";
2
+ import { DiscountStrategy } from "./strategy";
3
+ export declare class DiscountStrategyFactory {
4
+ private static strategies;
5
+ static getStrategy(discount: TDiscount): DiscountStrategy | null;
6
+ static registerStrategy(type: string, strategy: DiscountStrategy): void;
7
+ static getRegisteredTypes(): string[];
8
+ static clearStrategies(): void;
9
+ }
10
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAAC,UAAU,CAEtB;IAEH,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,GAAG,gBAAgB,GAAG,IAAI;IAIhE,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAIvE,MAAM,CAAC,kBAAkB,IAAI,MAAM,EAAE;IAIrC,MAAM,CAAC,eAAe,IAAI,IAAI;CAG/B"}
@@ -0,0 +1,18 @@
1
+ import { BundleDiscountStrategy } from "./strategies/BundleStrategy";
2
+ export class DiscountStrategyFactory {
3
+ static getStrategy(discount) {
4
+ return this.strategies.get(discount.variant.variantType) || null;
5
+ }
6
+ static registerStrategy(type, strategy) {
7
+ this.strategies.set(type, strategy);
8
+ }
9
+ static getRegisteredTypes() {
10
+ return Array.from(this.strategies.keys());
11
+ }
12
+ static clearStrategies() {
13
+ this.strategies.clear();
14
+ }
15
+ }
16
+ DiscountStrategyFactory.strategies = new Map([
17
+ ["bundle", new BundleDiscountStrategy()],
18
+ ]);
@@ -0,0 +1,9 @@
1
+ export * from "./types";
2
+ export * from "./strategy";
3
+ export * from "./factory";
4
+ export * from "./engine";
5
+ export * from "./strategies/BundleStrategy";
6
+ export * from "./utils";
7
+ export { DiscountSchema } from "./types";
8
+ export type { TDiscount } from "./types";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -4,7 +4,5 @@ export * from "./factory";
4
4
  export * from "./engine";
5
5
  export * from "./strategies/BundleStrategy";
6
6
  export * from "./utils";
7
-
8
7
  // Re-export the main discount schema for backward compatibility
9
8
  export { DiscountSchema } from "./types";
10
- export type { TDiscount } from "./types";
@@ -0,0 +1,12 @@
1
+ import { DiscountStrategy } from "../strategy";
2
+ import { TDiscount, DiscountResult, DiscountContext } from "../types";
3
+ export declare class BundleDiscountStrategy implements DiscountStrategy {
4
+ canApply(discount: TDiscount, context: DiscountContext): boolean;
5
+ calculate(discount: TDiscount, context: DiscountContext): DiscountResult;
6
+ private isDiscountActive;
7
+ private getTotalQuantity;
8
+ private calculateOriginalPrice;
9
+ private calculateDiscountedPrice;
10
+ private distributeDiscount;
11
+ }
12
+ //# sourceMappingURL=BundleStrategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BundleStrategy.d.ts","sourceRoot":"","sources":["../../../../../lib/entities/Discount/strategies/BundleStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEtE,qBAAa,sBAAuB,YAAW,gBAAgB;IAC9D,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO;IAchE,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc;IA0CxE,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,kBAAkB;CAmB1B"}
@@ -0,0 +1,78 @@
1
+ export class BundleDiscountStrategy {
2
+ canApply(discount, context) {
3
+ if (discount.variant.variantType !== "bundle")
4
+ return false;
5
+ // Check if discount is active
6
+ if (!this.isDiscountActive(discount))
7
+ return false;
8
+ const { productsId, requiredQuantity } = discount.variant;
9
+ // Check if cart has enough of the required products
10
+ const totalQuantity = this.getTotalQuantity(context.cart, productsId);
11
+ return totalQuantity >= requiredQuantity;
12
+ }
13
+ calculate(discount, context) {
14
+ if (discount.variant.variantType !== "bundle") {
15
+ return { applicable: false, discountAmount: 0, affectedItems: [] };
16
+ }
17
+ const { productsId, requiredQuantity, bundlePrice } = discount.variant;
18
+ // Get applicable items (only the products in the bundle)
19
+ const applicableItems = context.cart.filter((item) => productsId.includes(item.product.id));
20
+ const totalQuantity = this.getTotalQuantity(context.cart, productsId);
21
+ const bundleCount = Math.floor(totalQuantity / requiredQuantity);
22
+ if (bundleCount === 0) {
23
+ return { applicable: false, discountAmount: 0, affectedItems: [] };
24
+ }
25
+ // Calculate original price for bundled items
26
+ const originalPrice = this.calculateOriginalPrice(applicableItems);
27
+ // Calculate discounted price
28
+ const cartTotalQuantity = this.getTotalQuantity(context.cart, productsId);
29
+ const discountedPrice = this.calculateDiscountedPrice(originalPrice, bundlePrice, bundleCount, requiredQuantity, cartTotalQuantity);
30
+ const totalDiscount = originalPrice - discountedPrice;
31
+ // Distribute discount proportionally among items
32
+ const affectedItems = this.distributeDiscount(applicableItems, totalDiscount, originalPrice);
33
+ return {
34
+ applicable: true,
35
+ discountAmount: Number(totalDiscount.toFixed(2)),
36
+ affectedItems,
37
+ };
38
+ }
39
+ isDiscountActive(discount) {
40
+ const now = Date.now();
41
+ return discount.active && discount.startDate <= now && discount.endDate >= now;
42
+ }
43
+ getTotalQuantity(cart, productIds) {
44
+ return cart
45
+ .filter((item) => productIds.includes(item.product.id))
46
+ .reduce((sum, item) => sum + item.amount, 0);
47
+ }
48
+ calculateOriginalPrice(items) {
49
+ return items.reduce((sum, item) => sum + item.product.price * item.amount, 0);
50
+ }
51
+ calculateDiscountedPrice(originalPrice, bundlePrice, bundleCount, requiredQuantity, totalQuantity) {
52
+ // For bundle discounts, we pay the bundle price for each complete bundle
53
+ // and full price for any remaining items
54
+ const bundledItemsPrice = bundlePrice * bundleCount;
55
+ // For bundle discounts, we pay bundle price for complete bundles
56
+ // and full price for remaining items
57
+ // Since we're dealing with the total original price, we need to calculate
58
+ // how much of the original price represents the bundled items vs remaining items
59
+ const itemsInBundles = bundleCount * requiredQuantity;
60
+ const remainingItems = Math.max(0, totalQuantity - itemsInBundles);
61
+ // Calculate the proportion of original price that represents remaining items
62
+ const remainingItemsPrice = (remainingItems / totalQuantity) * originalPrice;
63
+ return bundledItemsPrice + remainingItemsPrice;
64
+ }
65
+ distributeDiscount(items, totalDiscount, originalPrice) {
66
+ const discountRatio = totalDiscount / originalPrice;
67
+ return items.map((item) => {
68
+ const itemDiscount = item.product.price * item.amount * discountRatio;
69
+ return {
70
+ productId: item.product.id,
71
+ quantity: item.amount,
72
+ originalPrice: Number(item.product.price.toFixed(2)),
73
+ discountedPrice: Number((item.product.price - itemDiscount / item.amount).toFixed(2)),
74
+ discountAmount: Number(itemDiscount.toFixed(2)),
75
+ };
76
+ });
77
+ }
78
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=BundleStrategy.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BundleStrategy.test.d.ts","sourceRoot":"","sources":["../../../../../../lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts"],"names":[],"mappings":""}