@jsdev_ninja/core 0.12.8 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"BundleStrategy.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/strategies/BundleStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEtE,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO;IAchE,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc;IA2CxE,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,kBAAkB;CAmB3B"}
1
+ {"version":3,"file":"BundleStrategy.d.ts","sourceRoot":"","sources":["../../../../lib/entities/Discount/strategies/BundleStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEtE,qBAAa,sBAAuB,YAAW,gBAAgB;IAC9D,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO;IAchE,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc;IA0CxE,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,wBAAwB;IAwBhC,OAAO,CAAC,kBAAkB;CAmB1B"}
@@ -16,7 +16,7 @@ export class BundleDiscountStrategy {
16
16
  }
17
17
  const { productsId, requiredQuantity, bundlePrice } = discount.variant;
18
18
  // Get applicable items (only the products in the bundle)
19
- const applicableItems = context.cart.filter(item => productsId.includes(item.product.id));
19
+ const applicableItems = context.cart.filter((item) => productsId.includes(item.product.id));
20
20
  const totalQuantity = this.getTotalQuantity(context.cart, productsId);
21
21
  const bundleCount = Math.floor(totalQuantity / requiredQuantity);
22
22
  if (bundleCount === 0) {
@@ -38,17 +38,15 @@ export class BundleDiscountStrategy {
38
38
  }
39
39
  isDiscountActive(discount) {
40
40
  const now = Date.now();
41
- return discount.active &&
42
- discount.startDate <= now &&
43
- discount.endDate >= now;
41
+ return discount.active && discount.startDate <= now && discount.endDate >= now;
44
42
  }
45
43
  getTotalQuantity(cart, productIds) {
46
44
  return cart
47
- .filter(item => productIds.includes(item.product.id))
45
+ .filter((item) => productIds.includes(item.product.id))
48
46
  .reduce((sum, item) => sum + item.amount, 0);
49
47
  }
50
48
  calculateOriginalPrice(items) {
51
- return items.reduce((sum, item) => sum + (item.product.price * item.amount), 0);
49
+ return items.reduce((sum, item) => sum + item.product.price * item.amount, 0);
52
50
  }
53
51
  calculateDiscountedPrice(originalPrice, bundlePrice, bundleCount, requiredQuantity, totalQuantity) {
54
52
  // For bundle discounts, we pay the bundle price for each complete bundle
@@ -66,13 +64,13 @@ export class BundleDiscountStrategy {
66
64
  }
67
65
  distributeDiscount(items, totalDiscount, originalPrice) {
68
66
  const discountRatio = totalDiscount / originalPrice;
69
- return items.map(item => {
70
- const itemDiscount = (item.product.price * item.amount) * discountRatio;
67
+ return items.map((item) => {
68
+ const itemDiscount = item.product.price * item.amount * discountRatio;
71
69
  return {
72
70
  productId: item.product.id,
73
71
  quantity: item.amount,
74
72
  originalPrice: Number(item.product.price.toFixed(2)),
75
- discountedPrice: Number((item.product.price - (itemDiscount / item.amount)).toFixed(2)),
73
+ discountedPrice: Number((item.product.price - itemDiscount / item.amount).toFixed(2)),
76
74
  discountAmount: Number(itemDiscount.toFixed(2)),
77
75
  };
78
76
  });
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/entities/Discount/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,wBAAwB;;;;;;;;;GAGxB,CAAC;AAGd,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;IAOhC,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWzB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACvD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAG1E,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,KAAK,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,KAAK,CAAC;QACV,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE;YACP,EAAE,EAAE,MAAM,CAAC;YACX,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC,CAAC;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACrC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../lib/entities/Discount/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,wBAAwB;;;;;;;;;GAKzB,CAAC;AAGb,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;IAOhC,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWzB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AACvD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAG1E,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,KAAK,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,KAAK,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE;YACR,EAAE,EAAE,MAAM,CAAC;YACX,KAAK,EAAE,MAAM,CAAC;SACd,CAAC;KACF,CAAC,CAAC;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACpC"}
@@ -1,14 +1,16 @@
1
1
  import { z } from "zod";
2
2
  // Simple discount conditions
3
- export const DiscountConditionsSchema = z.object({
3
+ export const DiscountConditionsSchema = z
4
+ .object({
4
5
  minSpend: z.number().positive().optional(),
5
6
  stackable: z.boolean().default(false),
6
- }).optional();
7
+ })
8
+ .optional();
7
9
  // Bundle discount - buy X items for Y price
8
10
  export const DiscountVariantSchema = z.discriminatedUnion("variantType", [
9
11
  z.object({
10
12
  variantType: z.literal("bundle"),
11
- productsId: z.array(z.string()).min(1), // Which products are included
13
+ productsId: z.array(z.string().nonempty()).min(1), // Which products are included
12
14
  requiredQuantity: z.number().positive(), // How many items needed (e.g., 3)
13
15
  bundlePrice: z.number().positive(), // Total price for the bundle (e.g., $25)
14
16
  }),
@@ -18,7 +20,7 @@ export const DiscountSchema = z.object({
18
20
  storeId: z.string().min(1),
19
21
  companyId: z.string().min(1),
20
22
  id: z.string().min(1),
21
- name: z.array(z.object({ lang: z.enum(["he"]), value: z.string() })),
23
+ name: z.array(z.object({ lang: z.enum(["he"]), value: z.string().nonempty() })),
22
24
  active: z.boolean(),
23
25
  startDate: z.number(),
24
26
  endDate: z.number(),
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAY,MAAM,EAAE,MAAM,aAAa,CAAC;AA8BjE,wBAAgB,WAAW,CAAC,EAC3B,IAAI,EACJ,SAAS,EACT,KAAK,GACL,EAAE;IACF,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACrB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4EA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAY,MAAM,EAAE,MAAM,aAAa,CAAC;AA8BjE,wBAAgB,WAAW,CAAC,EAC3B,IAAI,EACJ,SAAS,EACT,KAAK,GACL,EAAE;IACF,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACrB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsFA"}
@@ -35,6 +35,7 @@ export function getCartCost({ cart, discounts, store, }) {
35
35
  }));
36
36
  // Apply discounts using the new discount engine
37
37
  const discountResult = DiscountEngine.calculateDiscounts(cartForEngine, discounts);
38
+ console.log("discountResult", discountResult);
38
39
  // Map the results back to the original format with additional product info
39
40
  const result = cart.map((item, index) => {
40
41
  const engineItem = discountResult.items[index];
@@ -64,10 +65,17 @@ export function getCartCost({ cart, discounts, store, }) {
64
65
  }
65
66
  acc.vat = Number((acc.vat + vat).toFixed(2));
66
67
  }
67
- acc.cost += amount * finalPrice;
68
+ // Round finalPrice to prevent floating point errors from discount engine
69
+ const roundedFinalPrice = Number(finalPrice.toFixed(2));
70
+ acc.cost += amount * roundedFinalPrice;
68
71
  acc.discount += finalDiscount ? amount * finalDiscount : finalDiscount;
69
- acc.finalCost += amount * finalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
70
- acc.productsCost += amount * finalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
72
+ acc.finalCost += amount * roundedFinalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
73
+ acc.productsCost += amount * roundedFinalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
74
+ // Round all accumulated values to prevent floating point errors
75
+ acc.cost = Number(acc.cost.toFixed(2));
76
+ acc.discount = Number(acc.discount.toFixed(2));
77
+ acc.finalCost = Number(acc.finalCost.toFixed(2));
78
+ acc.productsCost = Number(acc.productsCost.toFixed(2));
71
79
  return acc;
72
80
  }, {
73
81
  discount: 0,
@@ -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
- 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 =>
28
- productsId.includes(item.product.id));
29
-
30
- const totalQuantity = this.getTotalQuantity(context.cart, productsId);
31
- const bundleCount = Math.floor(totalQuantity / requiredQuantity);
32
-
33
- if (bundleCount === 0) {
34
- return { applicable: false, discountAmount: 0, affectedItems: [] };
35
- }
36
-
37
- // Calculate original price for bundled items
38
- const originalPrice = this.calculateOriginalPrice(applicableItems);
39
-
40
- // Calculate discounted price
41
- const cartTotalQuantity = this.getTotalQuantity(context.cart, productsId);
42
- const discountedPrice = this.calculateDiscountedPrice(
43
- originalPrice,
44
- bundlePrice,
45
- bundleCount,
46
- requiredQuantity,
47
- cartTotalQuantity
48
- );
49
-
50
- const totalDiscount = originalPrice - discountedPrice;
51
-
52
- // Distribute discount proportionally among items
53
- const affectedItems = this.distributeDiscount(applicableItems, totalDiscount, originalPrice);
54
-
55
- return {
56
- applicable: true,
57
- discountAmount: Number(totalDiscount.toFixed(2)),
58
- affectedItems,
59
- };
60
- }
61
-
62
- private isDiscountActive(discount: TDiscount): boolean {
63
- const now = Date.now();
64
- return discount.active &&
65
- discount.startDate <= now &&
66
- discount.endDate >= now;
67
- }
68
-
69
- private getTotalQuantity(cart: DiscountContext["cart"], productIds: string[]): number {
70
- return cart
71
- .filter(item => productIds.includes(item.product.id))
72
- .reduce((sum, item) => sum + item.amount, 0);
73
- }
74
-
75
- private calculateOriginalPrice(items: DiscountContext["cart"]): number {
76
- return items.reduce((sum, item) =>
77
- sum + (item.product.price * item.amount), 0);
78
- }
79
-
80
- private calculateDiscountedPrice(
81
- originalPrice: number,
82
- bundlePrice: number,
83
- bundleCount: number,
84
- requiredQuantity: number,
85
- totalQuantity: number
86
- ): number {
87
- // For bundle discounts, we pay the bundle price for each complete bundle
88
- // and full price for any remaining items
89
- const bundledItemsPrice = bundlePrice * bundleCount;
90
-
91
- // For bundle discounts, we pay bundle price for complete bundles
92
- // and full price for remaining items
93
- // Since we're dealing with the total original price, we need to calculate
94
- // how much of the original price represents the bundled items vs remaining items
95
- const itemsInBundles = bundleCount * requiredQuantity;
96
- const remainingItems = Math.max(0, totalQuantity - itemsInBundles);
97
-
98
- // Calculate the proportion of original price that represents remaining items
99
- const remainingItemsPrice = (remainingItems / totalQuantity) * originalPrice;
100
-
101
- return bundledItemsPrice + remainingItemsPrice;
102
- }
103
-
104
- private distributeDiscount(
105
- items: DiscountContext["cart"],
106
- totalDiscount: number,
107
- originalPrice: number
108
- ): DiscountResult["affectedItems"] {
109
- const discountRatio = totalDiscount / originalPrice;
110
-
111
- return items.map(item => {
112
- const itemDiscount = (item.product.price * item.amount) * discountRatio;
113
-
114
- return {
115
- productId: item.product.id,
116
- quantity: item.amount,
117
- originalPrice: Number(item.product.price.toFixed(2)),
118
- discountedPrice: Number((item.product.price - (itemDiscount / item.amount)).toFixed(2)),
119
- discountAmount: Number(itemDiscount.toFixed(2)),
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
+ }
@@ -1,32 +1,34 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  // Simple discount conditions
4
- export const DiscountConditionsSchema = z.object({
5
- minSpend: z.number().positive().optional(),
6
- stackable: z.boolean().default(false),
7
- }).optional();
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
- z.object({
12
- variantType: z.literal("bundle"),
13
- productsId: z.array(z.string()).min(1), // Which products are included
14
- requiredQuantity: z.number().positive(), // How many items needed (e.g., 3)
15
- bundlePrice: z.number().positive(), // Total price for the bundle (e.g., $25)
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
- type: z.literal("Discount"),
21
- storeId: z.string().min(1),
22
- companyId: z.string().min(1),
23
- id: z.string().min(1),
24
- name: z.array(z.object({ lang: z.enum(["he"]), value: z.string() })),
25
- active: z.boolean(),
26
- startDate: z.number(),
27
- endDate: z.number(),
28
- variant: DiscountVariantSchema,
29
- conditions: DiscountConditionsSchema,
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
- applicable: boolean;
39
- discountAmount: number;
40
- affectedItems: Array<{
41
- productId: string;
42
- quantity: number;
43
- originalPrice: number;
44
- discountedPrice: number;
45
- discountAmount: number;
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
- discountId: string;
51
- discountName: string;
52
- discountAmount: number;
53
- affectedItems: DiscountResult["affectedItems"];
52
+ discountId: string;
53
+ discountName: string;
54
+ discountAmount: number;
55
+ affectedItems: DiscountResult["affectedItems"];
54
56
  }
55
57
 
56
58
  export interface DiscountContext {
57
- cart: Array<{
58
- amount: number;
59
- product: {
60
- id: string;
61
- price: number;
62
- };
63
- }>;
64
- user?: Record<string, unknown>;
65
- appliedDiscounts: AppliedDiscount[];
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
-
@@ -38,19 +38,19 @@ export function getCartCost({
38
38
  store: TStore;
39
39
  }) {
40
40
  const { isVatIncludedInPrice } = store;
41
-
41
+
42
42
  // Convert cart items to the format expected by the discount engine
43
- const cartForEngine = cart.map(item => ({
43
+ const cartForEngine = cart.map((item) => ({
44
44
  amount: item.amount,
45
45
  product: {
46
46
  id: item.product.id,
47
47
  price: item.product.price,
48
48
  },
49
49
  }));
50
-
50
+
51
51
  // Apply discounts using the new discount engine
52
52
  const discountResult = DiscountEngine.calculateDiscounts(cartForEngine, discounts);
53
-
53
+
54
54
  // Map the results back to the original format with additional product info
55
55
  const result = cart.map((item, index) => {
56
56
  const engineItem = discountResult.items[index];
@@ -86,10 +86,20 @@ export function getCartCost({
86
86
  acc.vat = Number((acc.vat + vat).toFixed(2));
87
87
  }
88
88
 
89
- acc.cost += amount * finalPrice;
89
+ // Round finalPrice to prevent floating point errors from discount engine
90
+ const roundedFinalPrice = Number(finalPrice.toFixed(2));
91
+
92
+ acc.cost += amount * roundedFinalPrice;
90
93
  acc.discount += finalDiscount ? amount * finalDiscount : finalDiscount;
91
- acc.finalCost += amount * finalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
92
- acc.productsCost += amount * finalPrice + (isVatIncludedInPrice ? 0 : productVatValue);
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));
93
103
 
94
104
  return acc;
95
105
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsdev_ninja/core",
3
- "version": "0.12.8",
3
+ "version": "0.12.9",
4
4
  "main": "dist/core.cjs.js",
5
5
  "module": "dist/core.es.js",
6
6
  "types": "dist/index.d.ts",