@jsdev_ninja/core 0.13.27 → 0.13.28

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 (142) 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 +3614 -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 +62 -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 +16 -0
  78. package/dist/lib/entities/Product.d.ts +694 -0
  79. package/dist/lib/entities/Product.d.ts.map +1 -0
  80. package/dist/lib/entities/Product.js +61 -0
  81. package/dist/lib/entities/Profile.d.ts +98 -0
  82. package/dist/lib/entities/Profile.d.ts.map +1 -0
  83. package/dist/lib/entities/Profile.js +50 -0
  84. package/dist/lib/entities/Store.d.ts +93 -0
  85. package/dist/lib/entities/Store.d.ts.map +1 -0
  86. package/dist/lib/entities/Store.js +20 -0
  87. package/dist/lib/entities/index.d.ts +15 -0
  88. package/dist/lib/entities/index.d.ts.map +1 -0
  89. package/dist/lib/firebase-api/app.d.ts +2 -0
  90. package/dist/lib/firebase-api/app.d.ts.map +1 -0
  91. package/dist/lib/firebase-api/app.js +16 -0
  92. package/dist/lib/firebase-api/index.d.ts +75 -0
  93. package/dist/lib/firebase-api/index.d.ts.map +1 -0
  94. package/dist/lib/firebase-api/index.js +33 -0
  95. package/dist/lib/index.d.ts +4 -0
  96. package/dist/lib/index.d.ts.map +1 -0
  97. package/dist/lib/utils/index.d.ts +109 -0
  98. package/dist/lib/utils/index.d.ts.map +1 -0
  99. package/dist/lib/utils/index.js +95 -0
  100. package/dist/tsconfig.app.tsbuildinfo +1 -0
  101. package/dist/tsconfig.node.tsbuildinfo +1 -0
  102. package/dist/vite.config.d.ts +3 -0
  103. package/dist/vite.config.d.ts.map +1 -0
  104. package/dist/vite.config.js +25 -0
  105. package/package.json +8 -1
  106. package/eslint.config.js +0 -28
  107. package/index.html +0 -13
  108. package/lib/entities/Address.ts +0 -13
  109. package/lib/entities/Cart.ts +0 -24
  110. package/lib/entities/Category.ts +0 -35
  111. package/lib/entities/Company.ts +0 -9
  112. package/lib/entities/DeliveryNote.ts +0 -70
  113. package/lib/entities/Discount/__tests__/engine.test.ts +0 -337
  114. package/lib/entities/Discount/__tests__/factory.test.ts +0 -91
  115. package/lib/entities/Discount/__tests__/integration.test.ts +0 -132
  116. package/lib/entities/Discount/__tests__/simple.test.ts +0 -215
  117. package/lib/entities/Discount/__tests__/utils.test.ts +0 -69
  118. package/lib/entities/Discount/engine.ts +0 -138
  119. package/lib/entities/Discount/factory.ts +0 -25
  120. package/lib/entities/Discount/strategies/BundleStrategy.ts +0 -119
  121. package/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts +0 -295
  122. package/lib/entities/Discount/strategy.ts +0 -6
  123. package/lib/entities/Discount/types.ts +0 -68
  124. package/lib/entities/FavoriteProduct.ts +0 -12
  125. package/lib/entities/Invoice.ts +0 -57
  126. package/lib/entities/Order.ts +0 -67
  127. package/lib/entities/Organization.ts +0 -23
  128. package/lib/entities/Product.ts +0 -68
  129. package/lib/entities/Profile.ts +0 -56
  130. package/lib/entities/Store.ts +0 -24
  131. package/lib/firebase-api/app.ts +0 -18
  132. package/lib/firebase-api/index.ts +0 -46
  133. package/lib/utils/index.ts +0 -127
  134. package/src/vite-env.d.ts +0 -1
  135. package/tsconfig.app.json +0 -20
  136. package/tsconfig.json +0 -7
  137. package/tsconfig.node.json +0 -26
  138. package/vite.config.ts +0 -27
  139. package/vitest.config.ts +0 -10
  140. /package/{src/main.tsx → dist/lib/entities/Discount/strategy.js} +0 -0
  141. /package/{lib/entities/index.ts → dist/lib/entities/index.js} +0 -0
  142. /package/{lib/index.tsx → dist/lib/index.js} +0 -0
@@ -1,215 +0,0 @@
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
- import { TDiscount } from "../types";
6
-
7
- describe("Simple Discount System Tests", () => {
8
- beforeEach(() => {
9
- DiscountStrategyFactory.clearStrategies();
10
- DiscountStrategyFactory.registerStrategy("bundle", new BundleDiscountStrategy());
11
- });
12
-
13
- it("should calculate basic bundle discount correctly", () => {
14
- const cart = [
15
- {
16
- amount: 2,
17
- product: {
18
- id: "product1",
19
- price: 10,
20
- },
21
- },
22
- {
23
- amount: 1,
24
- product: {
25
- id: "product2",
26
- price: 15,
27
- },
28
- },
29
- ];
30
-
31
- const discounts: TDiscount[] = [
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
-
53
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
54
-
55
- expect(result.totalDiscount).toBe(10);
56
- expect(result.appliedDiscounts).toHaveLength(1);
57
- });
58
-
59
- it("should not apply inactive discount", () => {
60
- const cart = [
61
- {
62
- amount: 3,
63
- product: {
64
- id: "product1",
65
- price: 10,
66
- },
67
- },
68
- ];
69
-
70
- const discounts: TDiscount[] = [
71
- {
72
- type: "Discount",
73
- storeId: "store1",
74
- companyId: "company1",
75
- id: "bundle1",
76
- name: [{ lang: "he", value: "Buy 3 for $25" }],
77
- active: false, // Inactive
78
- startDate: Date.now() - 1000,
79
- endDate: Date.now() + 1000,
80
- variant: {
81
- variantType: "bundle",
82
- productsId: ["product1"],
83
- requiredQuantity: 3,
84
- bundlePrice: 25,
85
- },
86
- conditions: {
87
- stackable: false,
88
- },
89
- },
90
- ];
91
-
92
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
93
-
94
- expect(result.totalDiscount).toBe(0);
95
- expect(result.appliedDiscounts).toHaveLength(0);
96
- });
97
-
98
- it("should not apply discount when not enough items", () => {
99
- const cart = [
100
- {
101
- amount: 2, // Only 2 items, need 3
102
- product: {
103
- id: "product1",
104
- price: 10,
105
- },
106
- },
107
- ];
108
-
109
- const discounts: TDiscount[] = [
110
- {
111
- type: "Discount",
112
- storeId: "store1",
113
- companyId: "company1",
114
- id: "bundle1",
115
- name: [{ lang: "he", value: "Buy 3 for $25" }],
116
- active: true,
117
- startDate: Date.now() - 1000,
118
- endDate: Date.now() + 1000,
119
- variant: {
120
- variantType: "bundle",
121
- productsId: ["product1"],
122
- requiredQuantity: 3,
123
- bundlePrice: 25,
124
- },
125
- conditions: {
126
- stackable: false,
127
- },
128
- },
129
- ];
130
-
131
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
132
-
133
- expect(result.totalDiscount).toBe(0);
134
- expect(result.appliedDiscounts).toHaveLength(0);
135
- });
136
-
137
- it("should handle Buy 2 Get 1 Free scenario", () => {
138
- const cart = [
139
- {
140
- amount: 3, // Buy 3 items
141
- product: {
142
- id: "product1",
143
- price: 10, // $10 each
144
- },
145
- },
146
- ];
147
-
148
- const bogoDiscount: TDiscount = {
149
- type: "Discount",
150
- storeId: "store1",
151
- companyId: "company1",
152
- id: "bogo",
153
- name: [{ lang: "he", value: "Buy 2 Get 1 Free" }],
154
- active: true,
155
- startDate: Date.now() - 1000,
156
- endDate: Date.now() + 1000,
157
- variant: {
158
- variantType: "bundle",
159
- productsId: ["product1"],
160
- requiredQuantity: 3, // Buy 2 + get 1 free = 3 items
161
- bundlePrice: 20, // Price of 2 items (10 × 2)
162
- },
163
- conditions: {
164
- stackable: false,
165
- },
166
- };
167
-
168
- const result = DiscountEngine.calculateDiscounts(cart, [bogoDiscount]);
169
-
170
- // Original: 3 × $10 = $30
171
- // Bundle: 3 items for $20
172
- // Discount: $10
173
-
174
- expect(result.totalDiscount).toBe(10);
175
- expect(result.appliedDiscounts).toHaveLength(1);
176
- });
177
-
178
- it("should handle empty cart", () => {
179
- const cart: Array<{
180
- amount: number;
181
- product: {
182
- id: string;
183
- price: number;
184
- };
185
- }> = [];
186
-
187
- const discounts: TDiscount[] = [
188
- {
189
- type: "Discount",
190
- storeId: "store1",
191
- companyId: "company1",
192
- id: "bundle1",
193
- name: [{ lang: "he", value: "Buy 3 for $25" }],
194
- active: true,
195
- startDate: Date.now() - 1000,
196
- endDate: Date.now() + 1000,
197
- variant: {
198
- variantType: "bundle",
199
- productsId: ["product1"],
200
- requiredQuantity: 3,
201
- bundlePrice: 25,
202
- },
203
- conditions: {
204
- stackable: false,
205
- },
206
- },
207
- ];
208
-
209
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
210
-
211
- expect(result.totalDiscount).toBe(0);
212
- expect(result.appliedDiscounts).toHaveLength(0);
213
- expect(result.items).toHaveLength(0);
214
- });
215
- });
@@ -1,69 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- formatCurrency,
4
- formatCurrencyString,
5
- ensureNonNegative,
6
- calculatePercentageDiscount
7
- } from "../utils";
8
-
9
- describe("Discount Utils", () => {
10
- describe("formatCurrency", () => {
11
- it("should format numbers to 2 decimal places", () => {
12
- expect(formatCurrency(10)).toBe(10.00);
13
- expect(formatCurrency(10.123)).toBe(10.12);
14
- expect(formatCurrency(10.125)).toBe(10.13); // Rounds up
15
- expect(formatCurrency(10.124)).toBe(10.12); // Rounds down
16
- expect(formatCurrency(0)).toBe(0.00);
17
- });
18
-
19
- it("should return a number type", () => {
20
- const result = formatCurrency(10.123);
21
- expect(typeof result).toBe("number");
22
- });
23
- });
24
-
25
- describe("formatCurrencyString", () => {
26
- it("should format numbers to 2 decimal places as string", () => {
27
- expect(formatCurrencyString(10)).toBe("10.00");
28
- expect(formatCurrencyString(10.123)).toBe("10.12");
29
- expect(formatCurrencyString(10.125)).toBe("10.13");
30
- expect(formatCurrencyString(0)).toBe("0.00");
31
- });
32
-
33
- it("should return a string type", () => {
34
- const result = formatCurrencyString(10.123);
35
- expect(typeof result).toBe("string");
36
- });
37
- });
38
-
39
- describe("ensureNonNegative", () => {
40
- it("should return 0 for negative values", () => {
41
- expect(ensureNonNegative(-5)).toBe(0.00);
42
- expect(ensureNonNegative(-0.01)).toBe(0.00);
43
- });
44
-
45
- it("should return formatted value for positive values", () => {
46
- expect(ensureNonNegative(10.123)).toBe(10.12);
47
- expect(ensureNonNegative(0)).toBe(0.00);
48
- });
49
- });
50
-
51
- describe("calculatePercentageDiscount", () => {
52
- it("should calculate percentage discount correctly", () => {
53
- expect(calculatePercentageDiscount(100, 80)).toBe(20.00); // 20% discount
54
- expect(calculatePercentageDiscount(50, 25)).toBe(50.00); // 50% discount
55
- expect(calculatePercentageDiscount(100, 100)).toBe(0.00); // No discount
56
- });
57
-
58
- it("should handle edge cases", () => {
59
- expect(calculatePercentageDiscount(0, 0)).toBe(0.00);
60
- expect(calculatePercentageDiscount(100, 0)).toBe(100.00); // 100% discount
61
- });
62
-
63
- it("should return formatted number", () => {
64
- const result = calculatePercentageDiscount(100, 90.5);
65
- expect(typeof result).toBe("number");
66
- expect(result).toBe(9.50); // 9.5% discount
67
- });
68
- });
69
- });
@@ -1,138 +0,0 @@
1
- import { TDiscount, AppliedDiscount, DiscountContext } from "./types";
2
- import { DiscountStrategyFactory } from "./factory";
3
-
4
- // discount engine - calculate discounts and return final prices
5
- // should register all discount types
6
- // should receive all store discounts
7
- // should check active discount that should be applied on cart
8
- // should find best discount per product
9
- // should apply discount to if stackable or best one
10
- // any applyd discount should mark products that effected and price before and after and average price per product in cart
11
- // should return items list with all calculations and final prices and discounts
12
-
13
- export class DiscountEngine {
14
- static calculateDiscounts(
15
- cart: Array<{
16
- amount: number;
17
- product: {
18
- id: string;
19
- price: number;
20
- };
21
- }>,
22
- discounts: TDiscount[],
23
- user?: Record<string, unknown>
24
- ): {
25
- items: Array<{
26
- amount: number;
27
- product: {
28
- id: string;
29
- price: number;
30
- };
31
- originalPrice: number;
32
- finalPrice: number;
33
- finalDiscount: number;
34
- appliedDiscounts: string[];
35
- }>;
36
- totalDiscount: number;
37
- appliedDiscounts: AppliedDiscount[];
38
- } {
39
- const context: DiscountContext = {
40
- cart,
41
- user,
42
- appliedDiscounts: [],
43
- };
44
-
45
- // Filter active discounts
46
- const activeDiscounts = this.filterActiveDiscounts(discounts);
47
-
48
- // Apply discounts
49
- const appliedDiscounts: AppliedDiscount[] = [];
50
-
51
- for (const discount of activeDiscounts) {
52
- const strategy = DiscountStrategyFactory.getStrategy(discount);
53
- if (!strategy) continue;
54
-
55
- // Check if discount can be applied
56
- if (!strategy.canApply(discount, context)) continue;
57
-
58
- // Check if discount is stackable
59
- // todo
60
- if (!discount.conditions?.stackable && appliedDiscounts.length > 0) continue;
61
-
62
- // Calculate discount
63
- const result = strategy.calculate(discount, context);
64
- if (!result.applicable) continue;
65
-
66
- // Apply discount
67
- appliedDiscounts.push({
68
- discountId: discount.id,
69
- discountName: discount.name[0]?.value || "Discount",
70
- discountAmount: Number(result.discountAmount.toFixed(2)),
71
- affectedItems: result.affectedItems,
72
- });
73
-
74
- // Update context for next iteration
75
- context.appliedDiscounts = appliedDiscounts;
76
- }
77
-
78
- // Calculate final prices
79
- const items = this.calculateFinalPrices(cart, appliedDiscounts);
80
-
81
- const totalDiscount = appliedDiscounts.reduce(
82
- (sum, discount) => sum + discount.discountAmount,
83
- 0
84
- );
85
-
86
- return {
87
- items,
88
- totalDiscount: Number(totalDiscount.toFixed(2)),
89
- appliedDiscounts,
90
- };
91
- }
92
-
93
- private static filterActiveDiscounts(discounts: TDiscount[]): TDiscount[] {
94
- const now = Date.now();
95
- return discounts.filter(
96
- (discount) => discount.active && discount.startDate <= now && discount.endDate >= now
97
- );
98
- }
99
-
100
- private static calculateFinalPrices(
101
- cart: DiscountContext["cart"],
102
- appliedDiscounts: AppliedDiscount[]
103
- ) {
104
- return cart.map((item) => {
105
- const itemDiscounts = appliedDiscounts.filter((discount) =>
106
- discount.affectedItems.some((affected) => affected.productId === item.product.id)
107
- );
108
-
109
- const totalItemDiscount = itemDiscounts.reduce((sum, discount) => {
110
- const affectedItem = discount.affectedItems.find(
111
- (affected) => affected.productId === item.product.id
112
- );
113
- return sum + (affectedItem?.discountAmount || 0);
114
- }, 0);
115
-
116
- const discountPerUnit = totalItemDiscount / item.amount;
117
- const finalPrice = item.product.price - discountPerUnit;
118
-
119
- return {
120
- amount: item.amount,
121
- product: item.product,
122
- originalPrice: Number(item.product.price.toFixed(2)),
123
- finalPrice: Number(Math.max(0, finalPrice).toFixed(2)),
124
- finalDiscount: Number(totalItemDiscount.toFixed(2)),
125
- appliedDiscounts: itemDiscounts.map((d) => d.discountId),
126
- };
127
- });
128
- }
129
-
130
- static isDiscountActive(discount: TDiscount): boolean {
131
- const now = Date.now();
132
- return discount.active && discount.startDate <= now && discount.endDate >= now;
133
- }
134
-
135
- static getActiveDiscounts(discounts: TDiscount[]): TDiscount[] {
136
- return this.filterActiveDiscounts(discounts);
137
- }
138
- }
@@ -1,25 +0,0 @@
1
- import { TDiscount } from "./types";
2
- import { DiscountStrategy } from "./strategy";
3
- import { BundleDiscountStrategy } from "./strategies/BundleStrategy";
4
-
5
- export class DiscountStrategyFactory {
6
- private static strategies: Map<string, DiscountStrategy> = new Map([
7
- ["bundle", new BundleDiscountStrategy()],
8
- ]);
9
-
10
- static getStrategy(discount: TDiscount): DiscountStrategy | null {
11
- return this.strategies.get(discount.variant.variantType) || null;
12
- }
13
-
14
- static registerStrategy(type: string, strategy: DiscountStrategy): void {
15
- this.strategies.set(type, strategy);
16
- }
17
-
18
- static getRegisteredTypes(): string[] {
19
- return Array.from(this.strategies.keys());
20
- }
21
-
22
- static clearStrategies(): void {
23
- this.strategies.clear();
24
- }
25
- }
@@ -1,119 +0,0 @@
1
- import { DiscountStrategy } from "../strategy";
2
- import { TDiscount, DiscountResult, DiscountContext } from "../types";
3
-
4
- export class BundleDiscountStrategy implements DiscountStrategy {
5
- canApply(discount: TDiscount, context: DiscountContext): boolean {
6
- if (discount.variant.variantType !== "bundle") return false;
7
-
8
- // Check if discount is active
9
- if (!this.isDiscountActive(discount)) return false;
10
-
11
- const { productsId, requiredQuantity } = discount.variant;
12
-
13
- // Check if cart has enough of the required products
14
- const totalQuantity = this.getTotalQuantity(context.cart, productsId);
15
-
16
- return totalQuantity >= requiredQuantity;
17
- }
18
-
19
- calculate(discount: TDiscount, context: DiscountContext): DiscountResult {
20
- if (discount.variant.variantType !== "bundle") {
21
- return { applicable: false, discountAmount: 0, affectedItems: [] };
22
- }
23
-
24
- const { productsId, requiredQuantity, bundlePrice } = discount.variant;
25
-
26
- // Get applicable items (only the products in the bundle)
27
- const applicableItems = context.cart.filter((item) => productsId.includes(item.product.id));
28
-
29
- const totalQuantity = this.getTotalQuantity(context.cart, productsId);
30
- const bundleCount = Math.floor(totalQuantity / requiredQuantity);
31
-
32
- if (bundleCount === 0) {
33
- return { applicable: false, discountAmount: 0, affectedItems: [] };
34
- }
35
-
36
- // Calculate original price for bundled items
37
- const originalPrice = this.calculateOriginalPrice(applicableItems);
38
-
39
- // Calculate discounted price
40
- const cartTotalQuantity = this.getTotalQuantity(context.cart, productsId);
41
- const discountedPrice = this.calculateDiscountedPrice(
42
- originalPrice,
43
- bundlePrice,
44
- bundleCount,
45
- requiredQuantity,
46
- cartTotalQuantity
47
- );
48
-
49
- const totalDiscount = originalPrice - discountedPrice;
50
-
51
- // Distribute discount proportionally among items
52
- const affectedItems = this.distributeDiscount(applicableItems, totalDiscount, originalPrice);
53
-
54
- return {
55
- applicable: true,
56
- discountAmount: Number(totalDiscount.toFixed(2)),
57
- affectedItems,
58
- };
59
- }
60
-
61
- private isDiscountActive(discount: TDiscount): boolean {
62
- const now = Date.now();
63
- return discount.active && discount.startDate <= now && discount.endDate >= now;
64
- }
65
-
66
- private getTotalQuantity(cart: DiscountContext["cart"], productIds: string[]): number {
67
- return cart
68
- .filter((item) => productIds.includes(item.product.id))
69
- .reduce((sum, item) => sum + item.amount, 0);
70
- }
71
-
72
- private calculateOriginalPrice(items: DiscountContext["cart"]): number {
73
- return items.reduce((sum, item) => sum + item.product.price * item.amount, 0);
74
- }
75
-
76
- private calculateDiscountedPrice(
77
- originalPrice: number,
78
- bundlePrice: number,
79
- bundleCount: number,
80
- requiredQuantity: number,
81
- totalQuantity: number
82
- ): number {
83
- // For bundle discounts, we pay the bundle price for each complete bundle
84
- // and full price for any remaining items
85
- const bundledItemsPrice = bundlePrice * bundleCount;
86
-
87
- // For bundle discounts, we pay bundle price for complete bundles
88
- // and full price for remaining items
89
- // Since we're dealing with the total original price, we need to calculate
90
- // how much of the original price represents the bundled items vs remaining items
91
- const itemsInBundles = bundleCount * requiredQuantity;
92
- const remainingItems = Math.max(0, totalQuantity - itemsInBundles);
93
-
94
- // Calculate the proportion of original price that represents remaining items
95
- const remainingItemsPrice = (remainingItems / totalQuantity) * originalPrice;
96
-
97
- return bundledItemsPrice + remainingItemsPrice;
98
- }
99
-
100
- private distributeDiscount(
101
- items: DiscountContext["cart"],
102
- totalDiscount: number,
103
- originalPrice: number
104
- ): DiscountResult["affectedItems"] {
105
- const discountRatio = totalDiscount / originalPrice;
106
-
107
- return items.map((item) => {
108
- const itemDiscount = item.product.price * item.amount * discountRatio;
109
-
110
- return {
111
- productId: item.product.id,
112
- quantity: item.amount,
113
- originalPrice: Number(item.product.price.toFixed(2)),
114
- discountedPrice: Number((item.product.price - itemDiscount / item.amount).toFixed(2)),
115
- discountAmount: Number(itemDiscount.toFixed(2)),
116
- };
117
- });
118
- }
119
- }