@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.
- package/dist/core.cjs.js +1 -1
- package/dist/core.cjs.js.map +1 -1
- package/dist/core.es.js +149 -145
- package/dist/core.es.js.map +1 -1
- package/dist/core.umd.js +1 -1
- package/dist/core.umd.js.map +1 -1
- package/dist/lib/entities/Order.d.ts +3 -0
- package/dist/lib/entities/Order.d.ts.map +1 -1
- package/dist/lib/entities/Order.js +1 -0
- package/dist/lib/entities/Store.d.ts +46 -0
- package/dist/lib/entities/Store.d.ts.map +1 -1
- package/dist/lib/entities/Store.js +3 -0
- package/dist/tsconfig.app.tsbuildinfo +1 -1
- package/package.json +9 -2
- package/eslint.config.js +0 -28
- package/index.html +0 -13
- package/lib/entities/Address.ts +0 -13
- package/lib/entities/Atoms.ts +0 -11
- package/lib/entities/Cart.ts +0 -24
- package/lib/entities/Category.ts +0 -35
- package/lib/entities/Company.ts +0 -9
- package/lib/entities/DeliveryNote.ts +0 -70
- package/lib/entities/Discount/__tests__/engine.test.ts +0 -337
- package/lib/entities/Discount/__tests__/factory.test.ts +0 -91
- package/lib/entities/Discount/__tests__/integration.test.ts +0 -132
- package/lib/entities/Discount/__tests__/simple.test.ts +0 -215
- package/lib/entities/Discount/__tests__/utils.test.ts +0 -69
- package/lib/entities/Discount/engine.ts +0 -138
- package/lib/entities/Discount/factory.ts +0 -25
- package/lib/entities/Discount/index.ts +0 -10
- package/lib/entities/Discount/strategies/BundleStrategy.ts +0 -119
- package/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts +0 -295
- package/lib/entities/Discount/strategy.ts +0 -6
- package/lib/entities/Discount/types.ts +0 -68
- package/lib/entities/Discount/utils.ts +0 -42
- package/lib/entities/FavoriteProduct.ts +0 -12
- package/lib/entities/Invoice.ts +0 -57
- package/lib/entities/Locale.ts +0 -8
- package/lib/entities/Order.ts +0 -66
- package/lib/entities/Organization.ts +0 -23
- package/lib/entities/Product.ts +0 -68
- package/lib/entities/Profile.ts +0 -56
- package/lib/entities/Store.ts +0 -23
- package/lib/entities/index.ts +0 -14
- package/lib/firebase-api/app.ts +0 -18
- package/lib/firebase-api/index.ts +0 -46
- package/lib/index.tsx +0 -3
- package/lib/utils/index.ts +0 -127
- package/src/main.tsx +0 -1
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.app.json +0 -20
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -27
- package/vitest.config.ts +0 -10
package/lib/entities/Cart.ts
DELETED
|
@@ -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>;
|
package/lib/entities/Category.ts
DELETED
|
@@ -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">;
|
package/lib/entities/Company.ts
DELETED
|
@@ -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
|
-
});
|