@jsdev_ninja/core 0.12.7 → 0.12.9
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 +988 -837
- 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/entities/Discount/__tests__/engine.test.d.ts +2 -0
- package/dist/entities/Discount/__tests__/engine.test.d.ts.map +1 -0
- package/dist/entities/Discount/__tests__/engine.test.js +298 -0
- package/dist/entities/Discount/__tests__/factory.test.d.ts +2 -0
- package/dist/entities/Discount/__tests__/factory.test.d.ts.map +1 -0
- package/dist/entities/Discount/__tests__/factory.test.js +75 -0
- package/dist/entities/Discount/__tests__/integration.test.d.ts +2 -0
- package/dist/entities/Discount/__tests__/integration.test.d.ts.map +1 -0
- package/dist/entities/Discount/__tests__/integration.test.js +115 -0
- package/dist/entities/Discount/__tests__/simple.test.d.ts +2 -0
- package/dist/entities/Discount/__tests__/simple.test.d.ts.map +1 -0
- package/dist/entities/Discount/__tests__/simple.test.js +186 -0
- package/dist/entities/Discount/__tests__/utils.test.d.ts +2 -0
- package/dist/entities/Discount/__tests__/utils.test.d.ts.map +1 -0
- package/dist/entities/Discount/__tests__/utils.test.js +55 -0
- package/dist/entities/Discount/engine.d.ts +29 -0
- package/dist/entities/Discount/engine.d.ts.map +1 -0
- package/dist/entities/Discount/engine.js +80 -0
- package/dist/entities/Discount/factory.d.ts +10 -0
- package/dist/entities/Discount/factory.d.ts.map +1 -0
- package/dist/entities/Discount/factory.js +18 -0
- package/dist/entities/Discount/index.d.ts +9 -0
- package/dist/entities/Discount/index.d.ts.map +1 -0
- package/dist/entities/Discount/index.js +8 -0
- package/dist/entities/Discount/strategies/BundleStrategy.d.ts +12 -0
- package/dist/entities/Discount/strategies/BundleStrategy.d.ts.map +1 -0
- package/dist/entities/Discount/strategies/BundleStrategy.js +78 -0
- package/dist/entities/Discount/strategies/__tests__/BundleStrategy.test.d.ts +2 -0
- package/dist/entities/Discount/strategies/__tests__/BundleStrategy.test.d.ts.map +1 -0
- package/dist/entities/Discount/strategies/__tests__/BundleStrategy.test.js +265 -0
- package/dist/entities/Discount/strategy.d.ts +6 -0
- package/dist/entities/Discount/strategy.d.ts.map +1 -0
- package/dist/entities/Discount/strategy.js +1 -0
- package/dist/entities/Discount/types.d.ts +148 -0
- package/dist/entities/Discount/types.d.ts.map +1 -0
- package/dist/entities/Discount/types.js +29 -0
- package/dist/entities/Discount/utils.d.ts +29 -0
- package/dist/entities/Discount/utils.d.ts.map +1 -0
- package/dist/entities/Discount/utils.js +39 -0
- package/dist/tsconfig.app.tsbuildinfo +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +27 -63
- package/lib/entities/Discount/strategies/BundleStrategy.ts +115 -119
- package/lib/entities/Discount/strategies/__tests__/BundleStrategy.test.ts +12 -7
- package/lib/entities/Discount/types.ts +44 -44
- package/lib/utils/index.ts +32 -83
- package/package.json +1 -1
- package/dist/entities/Discount.d.ts +0 -71
- package/dist/entities/Discount.d.ts.map +0 -1
- package/dist/entities/Discount.js +0 -20
- package/lib/entities/Discount.ts +0 -23
|
@@ -2,122 +2,118 @@ import { DiscountStrategy } from "../strategy";
|
|
|
2
2
|
import { TDiscount, DiscountResult, DiscountContext } from "../types";
|
|
3
3
|
|
|
4
4
|
export class BundleDiscountStrategy implements DiscountStrategy {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
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
|
+
}
|
|
@@ -56,16 +56,19 @@ describe("BundleDiscountStrategy", () => {
|
|
|
56
56
|
expect(strategy.canApply(discount, mockContext)).toBe(true);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it("should return
|
|
59
|
+
it("should return true for bundle variant type", () => {
|
|
60
60
|
const discount = {
|
|
61
61
|
...mockDiscount,
|
|
62
62
|
variant: {
|
|
63
|
-
variantType: "
|
|
64
|
-
|
|
63
|
+
variantType: "bundle" as const,
|
|
64
|
+
productsId: ["product1", "product2"],
|
|
65
|
+
requiredQuantity: 3,
|
|
66
|
+
bundlePrice: 25,
|
|
65
67
|
},
|
|
66
68
|
};
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
// This should work since it's a bundle type and we have 3 items total
|
|
71
|
+
expect(strategy.canApply(discount, mockContext)).toBe(true);
|
|
69
72
|
});
|
|
70
73
|
|
|
71
74
|
it("should return false when not enough items", () => {
|
|
@@ -223,12 +226,14 @@ describe("BundleDiscountStrategy", () => {
|
|
|
223
226
|
expect(result.affectedItems[0].discountAmount).toBe(10);
|
|
224
227
|
});
|
|
225
228
|
|
|
226
|
-
it("should return not applicable for
|
|
229
|
+
it("should return not applicable for invalid bundle", () => {
|
|
227
230
|
const discount = {
|
|
228
231
|
...mockDiscount,
|
|
229
232
|
variant: {
|
|
230
|
-
variantType: "
|
|
231
|
-
|
|
233
|
+
variantType: "bundle" as const,
|
|
234
|
+
productsId: ["product1"],
|
|
235
|
+
requiredQuantity: 10, // Too many required
|
|
236
|
+
bundlePrice: 25,
|
|
232
237
|
},
|
|
233
238
|
};
|
|
234
239
|
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
// Simple discount conditions
|
|
4
|
-
export const DiscountConditionsSchema = z
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
export const DiscountConditionsSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
minSpend: z.number().positive().optional(),
|
|
7
|
+
stackable: z.boolean().default(false),
|
|
8
|
+
})
|
|
9
|
+
.optional();
|
|
8
10
|
|
|
9
11
|
// Bundle discount - buy X items for Y price
|
|
10
12
|
export const DiscountVariantSchema = z.discriminatedUnion("variantType", [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
z.object({
|
|
14
|
+
variantType: z.literal("bundle"),
|
|
15
|
+
productsId: z.array(z.string().nonempty()).min(1), // Which products are included
|
|
16
|
+
requiredQuantity: z.number().positive(), // How many items needed (e.g., 3)
|
|
17
|
+
bundlePrice: z.number().positive(), // Total price for the bundle (e.g., $25)
|
|
18
|
+
}),
|
|
17
19
|
]);
|
|
18
20
|
|
|
19
21
|
export const DiscountSchema = z.object({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
type: z.literal("Discount"),
|
|
23
|
+
storeId: z.string().min(1),
|
|
24
|
+
companyId: z.string().min(1),
|
|
25
|
+
id: z.string().min(1),
|
|
26
|
+
name: z.array(z.object({ lang: z.enum(["he"]), value: z.string().nonempty() })),
|
|
27
|
+
active: z.boolean(),
|
|
28
|
+
startDate: z.number(),
|
|
29
|
+
endDate: z.number(),
|
|
30
|
+
variant: DiscountVariantSchema,
|
|
31
|
+
conditions: DiscountConditionsSchema,
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
export type TDiscount = z.infer<typeof DiscountSchema>;
|
|
@@ -35,34 +37,32 @@ export type DiscountConditions = z.infer<typeof DiscountConditionsSchema>;
|
|
|
35
37
|
|
|
36
38
|
// Simple result types
|
|
37
39
|
export interface DiscountResult {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
applicable: boolean;
|
|
41
|
+
discountAmount: number;
|
|
42
|
+
affectedItems: Array<{
|
|
43
|
+
productId: string;
|
|
44
|
+
quantity: number;
|
|
45
|
+
originalPrice: number;
|
|
46
|
+
discountedPrice: number;
|
|
47
|
+
discountAmount: number;
|
|
48
|
+
}>;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface AppliedDiscount {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
discountId: string;
|
|
53
|
+
discountName: string;
|
|
54
|
+
discountAmount: number;
|
|
55
|
+
affectedItems: DiscountResult["affectedItems"];
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
export interface DiscountContext {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
cart: Array<{
|
|
60
|
+
amount: number;
|
|
61
|
+
product: {
|
|
62
|
+
id: string;
|
|
63
|
+
price: number;
|
|
64
|
+
};
|
|
65
|
+
}>;
|
|
66
|
+
user?: Record<string, unknown>;
|
|
67
|
+
appliedDiscounts: AppliedDiscount[];
|
|
66
68
|
}
|
|
67
|
-
|
|
68
|
-
|
package/lib/utils/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TCart, TDiscount, TProduct, TStore } from "../entities";
|
|
2
|
+
import { DiscountEngine } from "../entities/Discount/engine";
|
|
2
3
|
|
|
3
4
|
const CONFIG = {
|
|
4
5
|
VAT: 18,
|
|
@@ -26,10 +27,6 @@ function getPriceAfterDiscount(product: TProduct) {
|
|
|
26
27
|
return product.price;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
// mark product that used in discount
|
|
30
|
-
// get final price for product and discount id
|
|
31
|
-
// filter better discount
|
|
32
|
-
|
|
33
30
|
// main
|
|
34
31
|
export function getCartCost({
|
|
35
32
|
cart,
|
|
@@ -41,89 +38,31 @@ export function getCartCost({
|
|
|
41
38
|
store: TStore;
|
|
42
39
|
}) {
|
|
43
40
|
const { isVatIncludedInPrice } = store;
|
|
44
|
-
|
|
41
|
+
|
|
42
|
+
// Convert cart items to the format expected by the discount engine
|
|
43
|
+
const cartForEngine = cart.map((item) => ({
|
|
44
|
+
amount: item.amount,
|
|
45
|
+
product: {
|
|
46
|
+
id: item.product.id,
|
|
47
|
+
price: item.product.price,
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Apply discounts using the new discount engine
|
|
52
|
+
const discountResult = DiscountEngine.calculateDiscounts(cartForEngine, discounts);
|
|
53
|
+
|
|
54
|
+
// Map the results back to the original format with additional product info
|
|
55
|
+
const result = cart.map((item, index) => {
|
|
56
|
+
const engineItem = discountResult.items[index];
|
|
45
57
|
return {
|
|
46
58
|
amount: item.amount,
|
|
47
59
|
product: { ...item.product },
|
|
48
60
|
originalPrice: item.product.price,
|
|
49
|
-
finalPrice: getPriceAfterDiscount(item.product),
|
|
50
|
-
finalDiscount: calculateDiscount(item.product),
|
|
61
|
+
finalPrice: engineItem ? engineItem.finalPrice : getPriceAfterDiscount(item.product),
|
|
62
|
+
finalDiscount: engineItem ? engineItem.finalDiscount : calculateDiscount(item.product),
|
|
51
63
|
};
|
|
52
64
|
});
|
|
53
65
|
|
|
54
|
-
// calculate delivery price before
|
|
55
|
-
// check if cart cost is greater than free delivery price
|
|
56
|
-
|
|
57
|
-
const activeDiscounts = discounts.filter((discount) => {
|
|
58
|
-
if (discount.variant.variantType === "bundle") {
|
|
59
|
-
const productsTotal =
|
|
60
|
-
cart?.reduce((total, item) => {
|
|
61
|
-
if (discount.variant.productsId.includes(item.product.id)) {
|
|
62
|
-
total += item.amount;
|
|
63
|
-
return total;
|
|
64
|
-
}
|
|
65
|
-
return total;
|
|
66
|
-
}, 0) ?? 0;
|
|
67
|
-
if (productsTotal >= discount.variant.requiredQuantity) {
|
|
68
|
-
// const times = Math.floor(productsTotal / discount.variant.requiredQuantity);
|
|
69
|
-
// console.log("yes", times, discount.variant.discountPrice);
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
74
|
-
});
|
|
75
|
-
console.log("activeDiscounts", activeDiscounts);
|
|
76
|
-
|
|
77
|
-
activeDiscounts.forEach((discount) => {
|
|
78
|
-
if (discount.variant.variantType === "bundle") {
|
|
79
|
-
// get all products in cart
|
|
80
|
-
const products = cart.filter((item) =>
|
|
81
|
-
discount.variant.productsId.includes(item.product.id)
|
|
82
|
-
);
|
|
83
|
-
const productsTotal =
|
|
84
|
-
products?.reduce((total, item) => {
|
|
85
|
-
if (discount.variant.productsId.includes(item.product.id)) {
|
|
86
|
-
total += item.amount;
|
|
87
|
-
return total;
|
|
88
|
-
}
|
|
89
|
-
return total;
|
|
90
|
-
}, 0) ?? 0;
|
|
91
|
-
|
|
92
|
-
const times = Math.floor(productsTotal / discount.variant.requiredQuantity);
|
|
93
|
-
const price = getPriceAfterDiscount(products[0]?.product);
|
|
94
|
-
const _discount = calculateDiscount(products[0]?.product);
|
|
95
|
-
console.log("price", price, _discount);
|
|
96
|
-
const discountPrice =
|
|
97
|
-
Number(
|
|
98
|
-
(discount.variant.discountPrice / discount.variant.requiredQuantity).toFixed(2)
|
|
99
|
-
) * 1;
|
|
100
|
-
|
|
101
|
-
console.log("discountPrice", discountPrice);
|
|
102
|
-
const totalDiscount =
|
|
103
|
-
(price * discount.variant.requiredQuantity - discount.variant.discountPrice) * times;
|
|
104
|
-
|
|
105
|
-
const originalPrice = productsTotal * price;
|
|
106
|
-
const discountPriceFinal = originalPrice - totalDiscount;
|
|
107
|
-
|
|
108
|
-
const averagePrice = Number((discountPriceFinal / productsTotal).toFixed(2));
|
|
109
|
-
|
|
110
|
-
result = result.map((item) => {
|
|
111
|
-
if (discount.variant.productsId.includes(item.product.id)) {
|
|
112
|
-
return {
|
|
113
|
-
...item,
|
|
114
|
-
finalPrice: averagePrice,
|
|
115
|
-
originalPrice: item.product.price,
|
|
116
|
-
discountPrice: price,
|
|
117
|
-
finalDiscount: item.finalDiscount + (item.product.price - averagePrice),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return item;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// find average price
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
66
|
const cartDetails = result.reduce(
|
|
128
67
|
(acc, item) => {
|
|
129
68
|
const { product, amount, finalPrice, finalDiscount } = item;
|
|
@@ -147,10 +86,20 @@ export function getCartCost({
|
|
|
147
86
|
acc.vat = Number((acc.vat + vat).toFixed(2));
|
|
148
87
|
}
|
|
149
88
|
|
|
150
|
-
|
|
89
|
+
// Round finalPrice to prevent floating point errors from discount engine
|
|
90
|
+
const roundedFinalPrice = Number(finalPrice.toFixed(2));
|
|
91
|
+
|
|
92
|
+
acc.cost += amount * roundedFinalPrice;
|
|
151
93
|
acc.discount += finalDiscount ? amount * finalDiscount : finalDiscount;
|
|
152
|
-
acc.finalCost += amount *
|
|
153
|
-
acc.productsCost +=
|
|
94
|
+
acc.finalCost += amount * roundedFinalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
|
|
95
|
+
acc.productsCost +=
|
|
96
|
+
amount * roundedFinalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
|
|
97
|
+
|
|
98
|
+
// Round all accumulated values to prevent floating point errors
|
|
99
|
+
acc.cost = Number(acc.cost.toFixed(2));
|
|
100
|
+
acc.discount = Number(acc.discount.toFixed(2));
|
|
101
|
+
acc.finalCost = Number(acc.finalCost.toFixed(2));
|
|
102
|
+
acc.productsCost = Number(acc.productsCost.toFixed(2));
|
|
154
103
|
|
|
155
104
|
return acc;
|
|
156
105
|
},
|
package/package.json
CHANGED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
export declare const DiscountSchema: z.ZodObject<{
|
|
3
|
-
type: z.ZodLiteral<"Discount">;
|
|
4
|
-
storeId: z.ZodString;
|
|
5
|
-
companyId: z.ZodString;
|
|
6
|
-
id: z.ZodString;
|
|
7
|
-
name: z.ZodArray<z.ZodObject<{
|
|
8
|
-
lang: z.ZodEnum<["he"]>;
|
|
9
|
-
value: z.ZodString;
|
|
10
|
-
}, "strip", z.ZodTypeAny, {
|
|
11
|
-
value: string;
|
|
12
|
-
lang: "he";
|
|
13
|
-
}, {
|
|
14
|
-
value: string;
|
|
15
|
-
lang: "he";
|
|
16
|
-
}>, "many">;
|
|
17
|
-
active: z.ZodBoolean;
|
|
18
|
-
variant: z.ZodDiscriminatedUnion<"variantType", [z.ZodObject<{
|
|
19
|
-
variantType: z.ZodLiteral<"bundle">;
|
|
20
|
-
productsId: z.ZodArray<z.ZodString, "many">;
|
|
21
|
-
requiredQuantity: z.ZodNumber;
|
|
22
|
-
discountPrice: z.ZodNumber;
|
|
23
|
-
}, "strip", z.ZodTypeAny, {
|
|
24
|
-
variantType: "bundle";
|
|
25
|
-
productsId: string[];
|
|
26
|
-
requiredQuantity: number;
|
|
27
|
-
discountPrice: number;
|
|
28
|
-
}, {
|
|
29
|
-
variantType: "bundle";
|
|
30
|
-
productsId: string[];
|
|
31
|
-
requiredQuantity: number;
|
|
32
|
-
discountPrice: number;
|
|
33
|
-
}>]>;
|
|
34
|
-
images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
35
|
-
}, "strip", z.ZodTypeAny, {
|
|
36
|
-
type: "Discount";
|
|
37
|
-
id: string;
|
|
38
|
-
companyId: string;
|
|
39
|
-
storeId: string;
|
|
40
|
-
name: {
|
|
41
|
-
value: string;
|
|
42
|
-
lang: "he";
|
|
43
|
-
}[];
|
|
44
|
-
active: boolean;
|
|
45
|
-
variant: {
|
|
46
|
-
variantType: "bundle";
|
|
47
|
-
productsId: string[];
|
|
48
|
-
requiredQuantity: number;
|
|
49
|
-
discountPrice: number;
|
|
50
|
-
};
|
|
51
|
-
images?: string[] | undefined;
|
|
52
|
-
}, {
|
|
53
|
-
type: "Discount";
|
|
54
|
-
id: string;
|
|
55
|
-
companyId: string;
|
|
56
|
-
storeId: string;
|
|
57
|
-
name: {
|
|
58
|
-
value: string;
|
|
59
|
-
lang: "he";
|
|
60
|
-
}[];
|
|
61
|
-
active: boolean;
|
|
62
|
-
variant: {
|
|
63
|
-
variantType: "bundle";
|
|
64
|
-
productsId: string[];
|
|
65
|
-
requiredQuantity: number;
|
|
66
|
-
discountPrice: number;
|
|
67
|
-
};
|
|
68
|
-
images?: string[] | undefined;
|
|
69
|
-
}>;
|
|
70
|
-
export type TDiscount = z.infer<typeof DiscountSchema>;
|
|
71
|
-
//# sourceMappingURL=Discount.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Discount.d.ts","sourceRoot":"","sources":["../../lib/entities/Discount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBzB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { LocaleSchema } from "./Locale";
|
|
3
|
-
const text = z.string().min(1);
|
|
4
|
-
export const DiscountSchema = z.object({
|
|
5
|
-
type: z.literal("Discount"),
|
|
6
|
-
storeId: text,
|
|
7
|
-
companyId: text,
|
|
8
|
-
id: text,
|
|
9
|
-
name: z.array(LocaleSchema),
|
|
10
|
-
active: z.boolean(),
|
|
11
|
-
variant: z.discriminatedUnion("variantType", [
|
|
12
|
-
z.object({
|
|
13
|
-
variantType: z.literal("bundle"),
|
|
14
|
-
productsId: z.array(z.string()).min(1),
|
|
15
|
-
requiredQuantity: z.number().positive(),
|
|
16
|
-
discountPrice: z.number().positive(),
|
|
17
|
-
}),
|
|
18
|
-
]),
|
|
19
|
-
images: z.array(z.string().nonempty()).optional(),
|
|
20
|
-
});
|