@jsdev_ninja/core 0.13.26 → 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 (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 +149 -145
  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/lib/entities/Order.d.ts +3 -0
  8. package/dist/lib/entities/Order.d.ts.map +1 -1
  9. package/dist/lib/entities/Order.js +1 -0
  10. package/dist/lib/entities/Store.d.ts +46 -0
  11. package/dist/lib/entities/Store.d.ts.map +1 -1
  12. package/dist/lib/entities/Store.js +3 -0
  13. package/dist/tsconfig.app.tsbuildinfo +1 -1
  14. package/package.json +9 -2
  15. package/eslint.config.js +0 -28
  16. package/index.html +0 -13
  17. package/lib/entities/Address.ts +0 -13
  18. package/lib/entities/Atoms.ts +0 -11
  19. package/lib/entities/Cart.ts +0 -24
  20. package/lib/entities/Category.ts +0 -35
  21. package/lib/entities/Company.ts +0 -9
  22. package/lib/entities/DeliveryNote.ts +0 -70
  23. package/lib/entities/Discount/__tests__/engine.test.ts +0 -337
  24. package/lib/entities/Discount/__tests__/factory.test.ts +0 -91
  25. package/lib/entities/Discount/__tests__/integration.test.ts +0 -132
  26. package/lib/entities/Discount/__tests__/simple.test.ts +0 -215
  27. package/lib/entities/Discount/__tests__/utils.test.ts +0 -69
  28. package/lib/entities/Discount/engine.ts +0 -138
  29. package/lib/entities/Discount/factory.ts +0 -25
  30. package/lib/entities/Discount/index.ts +0 -10
  31. package/lib/entities/Discount/strategies/BundleStrategy.ts +0 -119
  32. package/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts +0 -295
  33. package/lib/entities/Discount/strategy.ts +0 -6
  34. package/lib/entities/Discount/types.ts +0 -68
  35. package/lib/entities/Discount/utils.ts +0 -42
  36. package/lib/entities/FavoriteProduct.ts +0 -12
  37. package/lib/entities/Invoice.ts +0 -57
  38. package/lib/entities/Locale.ts +0 -8
  39. package/lib/entities/Order.ts +0 -66
  40. package/lib/entities/Organization.ts +0 -23
  41. package/lib/entities/Product.ts +0 -68
  42. package/lib/entities/Profile.ts +0 -56
  43. package/lib/entities/Store.ts +0 -23
  44. package/lib/entities/index.ts +0 -14
  45. package/lib/firebase-api/app.ts +0 -18
  46. package/lib/firebase-api/index.ts +0 -46
  47. package/lib/index.tsx +0 -3
  48. package/lib/utils/index.ts +0 -127
  49. package/src/main.tsx +0 -1
  50. package/src/vite-env.d.ts +0 -1
  51. package/tsconfig.app.json +0 -20
  52. package/tsconfig.json +0 -7
  53. package/tsconfig.node.json +0 -26
  54. package/vite.config.ts +0 -27
  55. package/vitest.config.ts +0 -10
@@ -1,24 +0,0 @@
1
- import { z } from "zod";
2
- import { ProductSchema } from "./Product";
3
-
4
- export const CartItemProductSchema = z.object({
5
- product: ProductSchema,
6
- originalPrice: z.number().optional(),
7
- finalPrice: z.number().optional(),
8
- finalDiscount: z.number().optional(),
9
- amount: z.number().positive({ message: "Quantity must be a positive number." }),
10
- });
11
-
12
- export type TCartItemProduct = z.infer<typeof CartItemProductSchema>;
13
-
14
- export const CartSchema = z.object({
15
- type: z.literal("Cart"),
16
- id: z.string().uuid(),
17
- companyId: z.string().uuid(),
18
- storeId: z.string().uuid(),
19
- userId: z.string().uuid(),
20
- status: z.enum(["active", "draft", "completed"]),
21
- items: z.array(CartItemProductSchema),
22
- });
23
-
24
- export type TCart = z.infer<typeof CartSchema>;
@@ -1,35 +0,0 @@
1
- import { z } from "zod";
2
- import { LocaleSchema } from "./Locale";
3
-
4
- export const BaseCategorySchema = z.object({
5
- id: z.string().min(1),
6
- companyId: z.string().min(1),
7
- storeId: z.string().min(1),
8
- parentId: z.string().nullish(),
9
- tag: z.string().optional(),
10
- locales: z.array(LocaleSchema),
11
- depth: z.number(),
12
- });
13
-
14
- type Category = z.infer<typeof BaseCategorySchema> & {
15
- children: Category[];
16
- };
17
-
18
- export const CategorySchema: z.ZodType<Category> = BaseCategorySchema.extend({
19
- children: z.lazy(() => CategorySchema.array()),
20
- });
21
-
22
- // export type TCategory = z.infer<typeof CategorySchema>;
23
- export type TCategory = z.infer<typeof BaseCategorySchema> & {
24
- children: TCategory[];
25
- };
26
- export const TFlattenCategorySchema = BaseCategorySchema.extend({
27
- index: z.number(),
28
- depth: z.number(),
29
- collapsed: z.boolean().optional(),
30
- children: z.array(CategorySchema),
31
- });
32
-
33
- export type TFlattenCategory = z.infer<typeof TFlattenCategorySchema>;
34
-
35
- export type TNewCategory = Omit<TCategory, "id">;
@@ -1,9 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const CompanySchema = z.object({
4
- id: z.string(),
5
- name: z.string(),
6
- websiteDomains: z.array(z.string()),
7
- });
8
-
9
- export type TCompany = z.infer<typeof CompanySchema>;
@@ -1,70 +0,0 @@
1
- import { z } from "zod";
2
-
3
- // Schema for the calculated data section
4
- export const CalculatedDataSchema = z.object({
5
- _COMMENT: z.string().optional(),
6
- transaction_id: z.string(),
7
- date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format"),
8
- currency: z.string().length(3, "Currency must be 3 characters"),
9
- rate: z.number().positive(),
10
- vat: z.string().regex(/^\d+\.\d{2}$/, "VAT must be in format XX.XX"),
11
- vat_price: z.number().positive(),
12
- price_discount: z.number(),
13
- price_discount_in_currency: z.number(),
14
- price_total: z.string().regex(/^\d+\.\d{2}$/, "Price total must be in format XX.XX"),
15
- price_total_in_currency: z.number().positive(),
16
- });
17
-
18
- export const EzDeliveryNoteSchema = z.object({
19
- doc_uuid: z.string().uuid("Document UUID must be a valid UUID"),
20
- pdf_link: z.string().url("PDF link must be a valid URL"),
21
- pdf_link_copy: z.string().url("PDF copy link must be a valid URL"),
22
- doc_number: z.string().min(1, "Document number is required"),
23
- sent_mails: z.array(z.string().email("Each email must be valid")),
24
- success: z.boolean(),
25
- ua_uuid: z.string().uuid("UA UUID must be a valid UUID"),
26
- calculatedData: CalculatedDataSchema,
27
- warning: z.string().optional(),
28
- date: z.number().optional(),
29
- });
30
-
31
- export const DeliveryNoteSchema = z.object({
32
- id: z.string().min(1, "ID is required"),
33
- number: z.string().min(1, "Number is required"),
34
- date: z.number().min(1, "Date is required"),
35
- createdAt: z.number().min(1, "Created at is required"),
36
- status: z.enum(["pending", "paid", "cancelled"]),
37
- companyDetails: z
38
- .object({
39
- name: z.string().min(1, "Name is required").optional(),
40
- address: z.string().min(1, "Address is required").optional(),
41
- phone: z.string().min(1, "Phone is required").optional(),
42
- email: z.string().email("Email must be valid").optional(),
43
- })
44
- .optional(),
45
- clientDetails: z
46
- .object({
47
- name: z.string().min(1, "Name is required").optional(),
48
- address: z.string().min(1, "Address is required").optional(),
49
- phone: z.string().min(1, "Phone is required").optional(),
50
- email: z.string().email("Email must be valid").optional(),
51
- })
52
- .optional(),
53
- items: z
54
- .array(
55
- z.object({
56
- name: z.string().min(1, "Name is required").optional(),
57
- price: z.number().min(1, "Price is required").optional(),
58
- quantity: z.number().min(1, "Quantity is required").optional(),
59
- total: z.number().min(1, "Total is required").optional(),
60
- })
61
- )
62
- .optional(),
63
- total: z.number().min(1, "Total is required").optional(),
64
- vat: z.number().min(1, "VAT is required").optional(),
65
- link: z.string().url("Link must be a valid URL").optional(),
66
- });
67
- // Type inference
68
- export type TEzDeliveryNote = z.infer<typeof EzDeliveryNoteSchema>;
69
- export type TDeliveryNote = z.infer<typeof DeliveryNoteSchema>;
70
- export type TCalculatedData = z.infer<typeof CalculatedDataSchema>;
@@ -1,337 +0,0 @@
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
- import { TDiscount } from "../types";
6
-
7
- describe("DiscountEngine", () => {
8
- beforeEach(() => {
9
- DiscountStrategyFactory.clearStrategies();
10
- DiscountStrategyFactory.registerStrategy("bundle", new BundleDiscountStrategy());
11
- });
12
-
13
- afterEach(() => {
14
- DiscountStrategyFactory.clearStrategies();
15
- });
16
-
17
- describe("calculateDiscounts", () => {
18
- it("should apply bundle discount correctly", () => {
19
- const cart = [
20
- {
21
- amount: 2,
22
- product: {
23
- id: "product1",
24
- price: 10,
25
- },
26
- },
27
- {
28
- amount: 1,
29
- product: {
30
- id: "product2",
31
- price: 15,
32
- },
33
- },
34
- ];
35
-
36
- const discounts: TDiscount[] = [
37
- {
38
- type: "Discount",
39
- storeId: "store1",
40
- companyId: "company1",
41
- id: "bundle1",
42
- name: [{ lang: "he", value: "Buy 3 for $25" }],
43
- active: true,
44
- startDate: Date.now() - 1000,
45
- endDate: Date.now() + 1000,
46
- variant: {
47
- variantType: "bundle",
48
- productsId: ["product1", "product2"],
49
- requiredQuantity: 3,
50
- bundlePrice: 25,
51
- },
52
- conditions: {
53
- stackable: false,
54
- },
55
- },
56
- ];
57
-
58
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
59
-
60
- expect(result.totalDiscount).toBe(10);
61
- expect(result.appliedDiscounts).toHaveLength(1);
62
- expect(result.appliedDiscounts[0].discountId).toBe("bundle1");
63
- expect(result.items).toHaveLength(2);
64
-
65
- // Check final prices
66
- const product1Item = result.items.find(item => item.product.id === "product1");
67
- expect(product1Item?.finalPrice).toBeCloseTo(7.14, 2); // 10 - (5.71/2) = 7.145
68
-
69
- const product2Item = result.items.find(item => item.product.id === "product2");
70
- expect(product2Item?.finalPrice).toBeCloseTo(10.71, 2); // 15 - 4.29 = 10.71
71
- });
72
-
73
- it("should handle multiple discounts", () => {
74
- const cart = [
75
- {
76
- amount: 4,
77
- product: {
78
- id: "product1",
79
- price: 10,
80
- },
81
- },
82
- ];
83
-
84
- const discounts: TDiscount[] = [
85
- {
86
- type: "Discount",
87
- storeId: "store1",
88
- companyId: "company1",
89
- id: "bundle1",
90
- name: [{ lang: "he", value: "Buy 3 for $25" }],
91
- active: true,
92
- startDate: Date.now() - 1000,
93
- endDate: Date.now() + 1000,
94
- variant: {
95
- variantType: "bundle",
96
- productsId: ["product1"],
97
- requiredQuantity: 3,
98
- bundlePrice: 25,
99
- },
100
- conditions: {
101
- stackable: true, // Allow stacking
102
- },
103
- },
104
- {
105
- type: "Discount",
106
- storeId: "store1",
107
- companyId: "company1",
108
- id: "bundle2",
109
- name: [{ lang: "he", value: "Buy 2 for $15" }],
110
- active: true,
111
- startDate: Date.now() - 1000,
112
- endDate: Date.now() + 1000,
113
- variant: {
114
- variantType: "bundle",
115
- productsId: ["product1"],
116
- requiredQuantity: 2,
117
- bundlePrice: 15,
118
- },
119
- conditions: {
120
- stackable: true,
121
- },
122
- },
123
- ];
124
-
125
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
126
-
127
- expect(result.appliedDiscounts).toHaveLength(2);
128
- expect(result.totalDiscount).toBeGreaterThan(0);
129
- });
130
-
131
- it("should filter out inactive discounts", () => {
132
- const cart = [
133
- {
134
- amount: 3,
135
- product: {
136
- id: "product1",
137
- price: 10,
138
- },
139
- },
140
- ];
141
-
142
- const discounts: TDiscount[] = [
143
- {
144
- type: "Discount",
145
- storeId: "store1",
146
- companyId: "company1",
147
- id: "bundle1",
148
- name: [{ lang: "he", value: "Buy 3 for $25" }],
149
- active: false, // Inactive
150
- startDate: Date.now() - 1000,
151
- endDate: Date.now() + 1000,
152
- variant: {
153
- variantType: "bundle",
154
- productsId: ["product1"],
155
- requiredQuantity: 3,
156
- bundlePrice: 25,
157
- },
158
- conditions: {
159
- stackable: false,
160
- },
161
- },
162
- ];
163
-
164
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
165
-
166
- expect(result.totalDiscount).toBe(0);
167
- expect(result.appliedDiscounts).toHaveLength(0);
168
- });
169
-
170
- it("should handle empty cart", () => {
171
- const cart: Array<{
172
- amount: number;
173
- product: {
174
- id: string;
175
- price: number;
176
- };
177
- }> = [];
178
- const discounts: TDiscount[] = [
179
- {
180
- type: "Discount",
181
- storeId: "store1",
182
- companyId: "company1",
183
- id: "bundle1",
184
- name: [{ lang: "he", value: "Buy 3 for $25" }],
185
- active: true,
186
- startDate: Date.now() - 1000,
187
- endDate: Date.now() + 1000,
188
- variant: {
189
- variantType: "bundle",
190
- productsId: ["product1"],
191
- requiredQuantity: 3,
192
- bundlePrice: 25,
193
- },
194
- conditions: {
195
- stackable: false,
196
- },
197
- },
198
- ];
199
-
200
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
201
-
202
- expect(result.totalDiscount).toBe(0);
203
- expect(result.appliedDiscounts).toHaveLength(0);
204
- expect(result.items).toHaveLength(0);
205
- });
206
-
207
- it("should handle empty discounts", () => {
208
- const cart = [
209
- {
210
- amount: 2,
211
- product: {
212
- id: "product1",
213
- price: 10,
214
- },
215
- },
216
- ];
217
-
218
- const discounts: TDiscount[] = [];
219
-
220
- const result = DiscountEngine.calculateDiscounts(cart, discounts);
221
-
222
- expect(result.totalDiscount).toBe(0);
223
- expect(result.appliedDiscounts).toHaveLength(0);
224
- expect(result.items).toHaveLength(1);
225
- expect(result.items[0].finalPrice).toBe(10); // No discount applied
226
- });
227
- });
228
-
229
- describe("isDiscountActive", () => {
230
- it("should return true for active discount", () => {
231
- const discount: TDiscount = {
232
- type: "Discount",
233
- storeId: "store1",
234
- companyId: "company1",
235
- id: "bundle1",
236
- name: [{ lang: "he", value: "Buy 3 for $25" }],
237
- active: true,
238
- startDate: Date.now() - 1000,
239
- endDate: Date.now() + 1000,
240
- variant: {
241
- variantType: "bundle",
242
- productsId: ["product1"],
243
- requiredQuantity: 3,
244
- bundlePrice: 25,
245
- },
246
- };
247
-
248
- expect(DiscountEngine.isDiscountActive(discount)).toBe(true);
249
- });
250
-
251
- it("should return false for inactive discount", () => {
252
- const discount: TDiscount = {
253
- type: "Discount",
254
- storeId: "store1",
255
- companyId: "company1",
256
- id: "bundle1",
257
- name: [{ lang: "he", value: "Buy 3 for $25" }],
258
- active: false,
259
- startDate: Date.now() - 1000,
260
- endDate: Date.now() + 1000,
261
- variant: {
262
- variantType: "bundle",
263
- productsId: ["product1"],
264
- requiredQuantity: 3,
265
- bundlePrice: 25,
266
- },
267
- };
268
-
269
- expect(DiscountEngine.isDiscountActive(discount)).toBe(false);
270
- });
271
-
272
- it("should return false for expired discount", () => {
273
- const discount: TDiscount = {
274
- type: "Discount",
275
- storeId: "store1",
276
- companyId: "company1",
277
- id: "bundle1",
278
- name: [{ lang: "he", value: "Buy 3 for $25" }],
279
- active: true,
280
- startDate: Date.now() - 2000,
281
- endDate: Date.now() - 1000, // Expired
282
- variant: {
283
- variantType: "bundle",
284
- productsId: ["product1"],
285
- requiredQuantity: 3,
286
- bundlePrice: 25,
287
- },
288
- };
289
-
290
- expect(DiscountEngine.isDiscountActive(discount)).toBe(false);
291
- });
292
- });
293
-
294
- describe("getActiveDiscounts", () => {
295
- it("should filter active discounts", () => {
296
- const discounts: TDiscount[] = [
297
- {
298
- type: "Discount",
299
- storeId: "store1",
300
- companyId: "company1",
301
- id: "active1",
302
- name: [{ lang: "he", value: "Active Discount" }],
303
- active: true,
304
- startDate: Date.now() - 1000,
305
- endDate: Date.now() + 1000,
306
- variant: {
307
- variantType: "bundle",
308
- productsId: ["product1"],
309
- requiredQuantity: 3,
310
- bundlePrice: 25,
311
- },
312
- },
313
- {
314
- type: "Discount",
315
- storeId: "store1",
316
- companyId: "company1",
317
- id: "inactive1",
318
- name: [{ lang: "he", value: "Inactive Discount" }],
319
- active: false,
320
- startDate: Date.now() - 1000,
321
- endDate: Date.now() + 1000,
322
- variant: {
323
- variantType: "bundle",
324
- productsId: ["product1"],
325
- requiredQuantity: 3,
326
- bundlePrice: 25,
327
- },
328
- },
329
- ];
330
-
331
- const activeDiscounts = DiscountEngine.getActiveDiscounts(discounts);
332
-
333
- expect(activeDiscounts).toHaveLength(1);
334
- expect(activeDiscounts[0].id).toBe("active1");
335
- });
336
- });
337
- });
@@ -1,91 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { DiscountStrategyFactory } from "../factory";
3
- import { DiscountStrategy } from "../strategy";
4
- import { TDiscount } from "../types";
5
-
6
- describe("DiscountStrategyFactory", () => {
7
- beforeEach(() => {
8
- DiscountStrategyFactory.clearStrategies();
9
- });
10
-
11
- afterEach(() => {
12
- DiscountStrategyFactory.clearStrategies();
13
- });
14
-
15
- it("should register and get strategy", () => {
16
- const mockStrategy: DiscountStrategy = {
17
- canApply: () => false,
18
- calculate: () => ({ applicable: false, discountAmount: 0, affectedItems: [] }),
19
- };
20
-
21
- DiscountStrategyFactory.registerStrategy("bundle", mockStrategy);
22
-
23
- const discount: TDiscount = {
24
- type: "Discount",
25
- storeId: "store1",
26
- companyId: "company1",
27
- id: "test1",
28
- name: [{ lang: "he", value: "Test Discount" }],
29
- active: true,
30
- startDate: Date.now() - 1000,
31
- endDate: Date.now() + 1000,
32
- variant: {
33
- variantType: "bundle",
34
- productsId: ["product1"],
35
- requiredQuantity: 3,
36
- bundlePrice: 25,
37
- },
38
- };
39
-
40
- const result = DiscountStrategyFactory.getStrategy(discount);
41
- expect(result).toBe(mockStrategy);
42
- });
43
-
44
- it("should return null for unregistered strategy", () => {
45
- const discount: TDiscount = {
46
- type: "Discount",
47
- storeId: "store1",
48
- companyId: "company1",
49
- id: "test1",
50
- name: [{ lang: "he", value: "Test Discount" }],
51
- active: true,
52
- startDate: Date.now() - 1000,
53
- endDate: Date.now() + 1000,
54
- variant: {
55
- variantType: "bundle",
56
- productsId: ["product1"],
57
- requiredQuantity: 3,
58
- bundlePrice: 25,
59
- },
60
- };
61
-
62
- const result = DiscountStrategyFactory.getStrategy(discount);
63
- expect(result).toBeNull();
64
- });
65
-
66
- it("should get registered types", () => {
67
- const mockStrategy: DiscountStrategy = {
68
- canApply: () => false,
69
- calculate: () => ({ applicable: false, discountAmount: 0, affectedItems: [] }),
70
- };
71
-
72
- DiscountStrategyFactory.registerStrategy("bundle", mockStrategy);
73
- DiscountStrategyFactory.registerStrategy("percentage", mockStrategy);
74
-
75
- const types = DiscountStrategyFactory.getRegisteredTypes();
76
- expect(types).toEqual(["bundle", "percentage"]);
77
- });
78
-
79
- it("should clear strategies", () => {
80
- const mockStrategy: DiscountStrategy = {
81
- canApply: () => false,
82
- calculate: () => ({ applicable: false, discountAmount: 0, affectedItems: [] }),
83
- };
84
-
85
- DiscountStrategyFactory.registerStrategy("bundle", mockStrategy);
86
- expect(DiscountStrategyFactory.getRegisteredTypes()).toHaveLength(1);
87
-
88
- DiscountStrategyFactory.clearStrategies();
89
- expect(DiscountStrategyFactory.getRegisteredTypes()).toHaveLength(0);
90
- });
91
- });
@@ -1,132 +0,0 @@
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
- import { TDiscount } from "../types";
6
-
7
- describe("Discount System Integration", () => {
8
- beforeEach(() => {
9
- DiscountStrategyFactory.clearStrategies();
10
- DiscountStrategyFactory.registerStrategy("bundle", new BundleDiscountStrategy());
11
- });
12
-
13
- afterEach(() => {
14
- DiscountStrategyFactory.clearStrategies();
15
- });
16
-
17
- it("should handle real-world bundle scenario", () => {
18
- // Scenario: Buy 3 dairy products for $20
19
- const cart = [
20
- {
21
- amount: 2, // 2 milk
22
- product: {
23
- id: "milk",
24
- price: 8, // $8 each
25
- },
26
- },
27
- {
28
- amount: 1, // 1 yogurt
29
- product: {
30
- id: "yogurt",
31
- price: 6, // $6 each
32
- },
33
- },
34
- {
35
- amount: 1, // 1 bread (not in bundle)
36
- product: {
37
- id: "bread",
38
- price: 4, // $4 each
39
- },
40
- },
41
- ];
42
-
43
- const dairyBundle: TDiscount = {
44
- type: "Discount",
45
- storeId: "store1",
46
- companyId: "company1",
47
- id: "dairy-bundle",
48
- name: [{ lang: "he", value: "3 Dairy Products for $20" }],
49
- active: true,
50
- startDate: Date.now() - 1000,
51
- endDate: Date.now() + 1000,
52
- variant: {
53
- variantType: "bundle",
54
- productsId: ["milk", "yogurt", "cheese"], // Dairy products
55
- requiredQuantity: 3,
56
- bundlePrice: 20,
57
- },
58
- conditions: {
59
- stackable: false,
60
- },
61
- };
62
-
63
- const result = DiscountEngine.calculateDiscounts(cart, [dairyBundle]);
64
-
65
- // Original prices:
66
- // milk: 2 × $8 = $16
67
- // yogurt: 1 × $6 = $6
68
- // bread: 1 × $4 = $4
69
- // Total: $26
70
-
71
- // Bundle applies to milk + yogurt (3 items for $20)
72
- // Remaining: bread at $4
73
- // New total: $24
74
-
75
- expect(result.totalDiscount).toBe(2); // $26 - $24
76
- expect(result.appliedDiscounts).toHaveLength(1);
77
- expect(result.appliedDiscounts[0].discountId).toBe("dairy-bundle");
78
-
79
- // Check individual item prices
80
- const milkItem = result.items.find(item => item.product.id === "milk");
81
- expect(milkItem?.finalPrice).toBeCloseTo(7.28, 2); // 8 - (1.45/2) = 7.275
82
-
83
- const yogurtItem = result.items.find(item => item.product.id === "yogurt");
84
- expect(yogurtItem?.finalPrice).toBeCloseTo(5.45, 2); // 6 - 0.55 = 5.45
85
-
86
- const breadItem = result.items.find(item => item.product.id === "bread");
87
- expect(breadItem?.finalPrice).toBe(4); // No discount
88
- });
89
-
90
- it("should handle BOGO scenario", () => {
91
- // Scenario: Buy 2 Get 1 Free
92
- const cart = [
93
- {
94
- amount: 3, // Buy 3 items
95
- product: {
96
- id: "product1",
97
- price: 10, // $10 each
98
- },
99
- },
100
- ];
101
-
102
- const bogoDiscount: TDiscount = {
103
- type: "Discount",
104
- storeId: "store1",
105
- companyId: "company1",
106
- id: "bogo",
107
- name: [{ lang: "he", value: "Buy 2 Get 1 Free" }],
108
- active: true,
109
- startDate: Date.now() - 1000,
110
- endDate: Date.now() + 1000,
111
- variant: {
112
- variantType: "bundle",
113
- productsId: ["product1"],
114
- requiredQuantity: 3, // Buy 2 + get 1 free = 3 items
115
- bundlePrice: 20, // Price of 2 items (10 × 2)
116
- },
117
- conditions: {
118
- stackable: false,
119
- },
120
- };
121
-
122
- const result = DiscountEngine.calculateDiscounts(cart, [bogoDiscount]);
123
-
124
- // Original: 3 × $10 = $30
125
- // Bundle: 3 items for $20
126
- // Discount: $10
127
-
128
- expect(result.totalDiscount).toBe(10);
129
- expect(result.appliedDiscounts).toHaveLength(1);
130
- expect(result.items[0].finalPrice).toBeCloseTo(6.67, 2); // $20 / 3
131
- });
132
- });