@qrwise/pos-calculations 0.0.5 → 0.0.7

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/index.cjs CHANGED
@@ -20,82 +20,137 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- calculateTotals: () => calculateTotals
23
+ calculateTotals: () => calculateTotals,
24
+ clamp: () => clamp,
25
+ format2: () => format2,
26
+ getDiscountBreakdown: () => getDiscountBreakdown,
27
+ isAddVatAfterDiscount: () => isAddVatAfterDiscount,
28
+ isVatExemptDiscount: () => isVatExemptDiscount,
29
+ toMoneyNumber: () => toMoneyNumber
24
30
  });
25
31
  module.exports = __toCommonJS(index_exports);
26
32
 
27
33
  // src/calculate-totals.ts
28
- function round(value) {
29
- return Number(value.toFixed(2));
34
+ var clamp = (n, min = 0, max = Number.POSITIVE_INFINITY) => Math.min(Math.max(n, min), max);
35
+ var toMoneyNumber = (v, fallback = 0) => {
36
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v.replace(/,/g, "").trim()) : fallback;
37
+ if (!Number.isFinite(n)) return fallback;
38
+ return n;
39
+ };
40
+ function format2(value) {
41
+ return Number((value || 0).toFixed(2));
42
+ }
43
+ function includesAny(value, keywords) {
44
+ return keywords.some((keyword) => value.includes(keyword));
45
+ }
46
+ function isAddVatAfterDiscount(discountType) {
47
+ const value = String(discountType || "").toLowerCase();
48
+ return includesAny(value, ["solo", "solo parent"]) || includesAny(value, ["national", "athlete", "naac"]) || includesAny(value, ["uniformed", "uniformed personnel", "mov"]);
30
49
  }
31
50
  function isVatExemptDiscount(discountType) {
32
- return ["SENIOR", "PWD", "NAAC"].includes(String(discountType || "").toUpperCase());
51
+ const value = String(discountType || "").toLowerCase();
52
+ return includesAny(value, ["senior", "sc"]) || includesAny(value, ["pwd", "person with disability"]);
53
+ }
54
+ function getDiscountBreakdown(discountType, discountedAmount) {
55
+ const value = String(discountType || "").toLowerCase();
56
+ return {
57
+ sc: includesAny(value, ["senior", "sc"]) ? discountedAmount : 0,
58
+ pwd: includesAny(value, ["pwd", "person with disability"]) ? discountedAmount : 0,
59
+ solo: includesAny(value, ["solo", "solo parent"]) ? discountedAmount : 0,
60
+ naac: includesAny(value, ["national", "athlete", "naac"]) ? discountedAmount : 0,
61
+ mov: includesAny(value, ["uniformed", "uniformed personnel", "mov"]) ? discountedAmount : 0
62
+ };
33
63
  }
34
64
  function calculateTotals(input) {
35
65
  const {
36
- discountableTotalAmount,
37
- nonDiscountableTotalAmount,
38
- serviceChargeableTotalAmount,
39
- voucherType,
66
+ discountableTotalAmount = 0,
67
+ nonDiscountableTotalAmount = 0,
68
+ serviceChargeableTotalAmount = 0,
69
+ voucherType = null,
40
70
  voucherRate = 0,
41
- discountType,
71
+ discountType = null,
42
72
  discountRate = 0,
43
- vatRate,
44
- serviceChargeRate,
45
- togoCharge,
46
- diningOption = "FOR_HERE"
73
+ vatRate = 12,
74
+ serviceChargeRate = 0,
75
+ togoCharge = 0
47
76
  } = input;
48
- const grossAmount = round(discountableTotalAmount + nonDiscountableTotalAmount);
49
- let voucherDiscount = 0;
77
+ const totalOrderAmount = format2(discountableTotalAmount + nonDiscountableTotalAmount);
78
+ let voucherDiscounted = 0;
50
79
  if (voucherType === "FIXED") {
51
- voucherDiscount = Math.min(voucherRate, grossAmount);
80
+ voucherDiscounted = -Math.min(toMoneyNumber(voucherRate), totalOrderAmount);
52
81
  }
53
82
  if (voucherType === "PERCENTAGE") {
54
- voucherDiscount = grossAmount * (voucherRate / 100);
83
+ voucherDiscounted = -(totalOrderAmount * (toMoneyNumber(voucherRate) / 100));
55
84
  }
56
- voucherDiscount = round(voucherDiscount);
57
- const subtotalAfterVoucher = round(grossAmount - voucherDiscount);
58
- const vatDivisor = 1 + vatRate / 100;
59
- let vatNet = round(subtotalAfterVoucher / vatDivisor);
60
- let vat = round(subtotalAfterVoucher - vatNet);
61
- let discount = 0;
62
- if (discountType) {
63
- const discountableVatNet = round(discountableTotalAmount / vatDivisor);
64
- discount = round(discountableVatNet * (discountRate / 100));
65
- vatNet = round(vatNet - discount);
66
- if (isVatExemptDiscount(discountType)) {
67
- vat = 0;
85
+ const subtotal = totalOrderAmount + voucherDiscounted;
86
+ const voucherVatNet = Number((totalOrderAmount / vatRate).toFixed(2));
87
+ const voucherVat = Number((voucherVatNet * 0.12).toFixed(2));
88
+ let vatNet = Number((subtotal / vatRate).toFixed(2));
89
+ let vat = Number((vatNet * 0.12).toFixed(2));
90
+ const isSpecial = discountRate > 0;
91
+ const isAddVat = isAddVatAfterDiscount(discountType);
92
+ const discounted = Number((discountRate * -vatNet).toFixed(2));
93
+ if (isSpecial) {
94
+ vatNet = Number((vatNet + discounted).toFixed(2));
95
+ if (!isAddVat) {
96
+ vat = -vat;
68
97
  }
69
98
  }
70
99
  let serviceCharge = 0;
71
- if (diningOption === "FOR_HERE" && serviceChargeRate > 0) {
72
- serviceCharge = round(serviceChargeableTotalAmount * (serviceChargeRate / 100));
100
+ if (serviceChargeRate > 0 && serviceChargeableTotalAmount > 0) {
101
+ const scRate = serviceChargeRate / 100;
102
+ if (isSpecial) {
103
+ serviceCharge = Number(((vatNet + Math.abs(discounted)) * scRate).toFixed(2));
104
+ } else {
105
+ serviceCharge = Number((vatNet * scRate).toFixed(2));
106
+ }
73
107
  }
74
- const finalTogoCharge = diningOption === "TO_GO" ? togoCharge : 0;
75
- const totalAmount = round(Math.max(0, vatNet + vat + serviceCharge + finalTogoCharge));
108
+ let totalAmount = vatNet + vat + serviceCharge + togoCharge;
109
+ if (isSpecial && !isAddVat) {
110
+ totalAmount -= vat;
111
+ }
112
+ if (totalAmount < 0) totalAmount = 0;
76
113
  return {
77
- grossAmount,
78
- voucherDiscount: -voucherDiscount,
79
- subtotalAfterVoucher,
80
- vatNet,
114
+ gross: subtotal + Math.abs(voucherDiscounted),
115
+ hasVoucher: voucherRate > 0,
116
+ voucher: {
117
+ discount: voucherDiscounted,
118
+ net: subtotal,
119
+ vatNet: voucherVatNet,
120
+ vat: voucherVat,
121
+ forBirDiscount: -(voucherType === "FIXED" ? voucherVatNet - Number(voucherRate.toFixed(2)) : voucherVatNet * Number(voucherRate.toFixed(2)))
122
+ },
123
+ hasDiscount: discountRate > 0,
124
+ discount: {
125
+ discount: discounted,
126
+ net: vatNet
127
+ },
128
+ netOfVat: vatNet + Math.abs(discounted),
81
129
  vat,
82
- discount: -discount,
83
130
  serviceCharge,
84
- togoCharge: finalTogoCharge,
131
+ togoCharge,
85
132
  totalAmount,
86
133
  bir: {
87
- vatSales: vat > 0 ? vatNet + Math.abs(discount) : 0,
88
- vatExemptSales: vat === 0 ? vatNet + Math.abs(discount) : 0,
134
+ vatSales: vat > 0 ? vatNet + Math.abs(discounted) : 0,
135
+ vatExemptSales: vat > 0 ? 0 : vatNet + Math.abs(discounted),
89
136
  vatZeroRatedSales: 0,
90
137
  nonTaxableSales: 0,
91
138
  vat,
92
- discount,
139
+ totalSales: vat > 0 ? subtotal : vatNet + Math.abs(discounted),
140
+ discount: discounted,
93
141
  serviceCharge,
142
+ togoCharge,
94
143
  amountPayable: totalAmount
95
144
  }
96
145
  };
97
146
  }
98
147
  // Annotate the CommonJS export names for ESM import in node:
99
148
  0 && (module.exports = {
100
- calculateTotals
149
+ calculateTotals,
150
+ clamp,
151
+ format2,
152
+ getDiscountBreakdown,
153
+ isAddVatAfterDiscount,
154
+ isVatExemptDiscount,
155
+ toMoneyNumber
101
156
  });
package/dist/index.d.cts CHANGED
@@ -4,23 +4,32 @@ type DiscountType = "SENIOR" | "PWD" | "NAAC" | "SOLO" | "MOV";
4
4
  interface CalculateTotalsInput {
5
5
  discountableTotalAmount: number;
6
6
  nonDiscountableTotalAmount: number;
7
- serviceChargeableTotalAmount: number;
7
+ serviceChargeableTotalAmount?: number;
8
8
  voucherType?: VoucherType | null;
9
9
  voucherRate?: number;
10
10
  discountType?: DiscountType | null;
11
11
  discountRate?: number;
12
- vatRate: number;
13
- serviceChargeRate: number;
14
- togoCharge: number;
15
- diningOption?: DiningOption;
12
+ vatRate?: number;
13
+ serviceChargeRate?: number;
14
+ togoCharge?: number;
16
15
  }
17
16
  interface CalculateTotalsResult {
18
- grossAmount: number;
19
- voucherDiscount: number;
20
- subtotalAfterVoucher: number;
21
- vatNet: number;
17
+ gross: number;
18
+ hasVoucher: boolean;
19
+ voucher: {
20
+ discount: number;
21
+ net: number;
22
+ vatNet: number;
23
+ vat: number;
24
+ forBirDiscount: number;
25
+ };
26
+ hasDiscount: boolean;
27
+ discount: {
28
+ discount: number;
29
+ net: number;
30
+ };
31
+ netOfVat: number;
22
32
  vat: number;
23
- discount: number;
24
33
  serviceCharge: number;
25
34
  togoCharge: number;
26
35
  totalAmount: number;
@@ -30,12 +39,26 @@ interface CalculateTotalsResult {
30
39
  vatZeroRatedSales: number;
31
40
  nonTaxableSales: number;
32
41
  vat: number;
42
+ totalSales: number;
33
43
  discount: number;
34
44
  serviceCharge: number;
45
+ togoCharge: number;
35
46
  amountPayable: number;
36
47
  };
37
48
  }
38
49
 
50
+ declare const clamp: (n: number, min?: number, max?: number) => number;
51
+ declare const toMoneyNumber: (v: unknown, fallback?: number) => number;
52
+ declare function format2(value: number): number;
53
+ declare function isAddVatAfterDiscount(discountType?: string | null): boolean;
54
+ declare function isVatExemptDiscount(discountType?: string | null): boolean;
55
+ declare function getDiscountBreakdown(discountType: string | null | undefined, discountedAmount: number): {
56
+ sc: number;
57
+ pwd: number;
58
+ solo: number;
59
+ naac: number;
60
+ mov: number;
61
+ };
39
62
  declare function calculateTotals(input: CalculateTotalsInput): CalculateTotalsResult;
40
63
 
41
- export { type CalculateTotalsInput, type CalculateTotalsResult, type DiningOption, type DiscountType, type VoucherType, calculateTotals };
64
+ export { type CalculateTotalsInput, type CalculateTotalsResult, type DiningOption, type DiscountType, type VoucherType, calculateTotals, clamp, format2, getDiscountBreakdown, isAddVatAfterDiscount, isVatExemptDiscount, toMoneyNumber };
package/dist/index.d.ts CHANGED
@@ -4,23 +4,32 @@ type DiscountType = "SENIOR" | "PWD" | "NAAC" | "SOLO" | "MOV";
4
4
  interface CalculateTotalsInput {
5
5
  discountableTotalAmount: number;
6
6
  nonDiscountableTotalAmount: number;
7
- serviceChargeableTotalAmount: number;
7
+ serviceChargeableTotalAmount?: number;
8
8
  voucherType?: VoucherType | null;
9
9
  voucherRate?: number;
10
10
  discountType?: DiscountType | null;
11
11
  discountRate?: number;
12
- vatRate: number;
13
- serviceChargeRate: number;
14
- togoCharge: number;
15
- diningOption?: DiningOption;
12
+ vatRate?: number;
13
+ serviceChargeRate?: number;
14
+ togoCharge?: number;
16
15
  }
17
16
  interface CalculateTotalsResult {
18
- grossAmount: number;
19
- voucherDiscount: number;
20
- subtotalAfterVoucher: number;
21
- vatNet: number;
17
+ gross: number;
18
+ hasVoucher: boolean;
19
+ voucher: {
20
+ discount: number;
21
+ net: number;
22
+ vatNet: number;
23
+ vat: number;
24
+ forBirDiscount: number;
25
+ };
26
+ hasDiscount: boolean;
27
+ discount: {
28
+ discount: number;
29
+ net: number;
30
+ };
31
+ netOfVat: number;
22
32
  vat: number;
23
- discount: number;
24
33
  serviceCharge: number;
25
34
  togoCharge: number;
26
35
  totalAmount: number;
@@ -30,12 +39,26 @@ interface CalculateTotalsResult {
30
39
  vatZeroRatedSales: number;
31
40
  nonTaxableSales: number;
32
41
  vat: number;
42
+ totalSales: number;
33
43
  discount: number;
34
44
  serviceCharge: number;
45
+ togoCharge: number;
35
46
  amountPayable: number;
36
47
  };
37
48
  }
38
49
 
50
+ declare const clamp: (n: number, min?: number, max?: number) => number;
51
+ declare const toMoneyNumber: (v: unknown, fallback?: number) => number;
52
+ declare function format2(value: number): number;
53
+ declare function isAddVatAfterDiscount(discountType?: string | null): boolean;
54
+ declare function isVatExemptDiscount(discountType?: string | null): boolean;
55
+ declare function getDiscountBreakdown(discountType: string | null | undefined, discountedAmount: number): {
56
+ sc: number;
57
+ pwd: number;
58
+ solo: number;
59
+ naac: number;
60
+ mov: number;
61
+ };
39
62
  declare function calculateTotals(input: CalculateTotalsInput): CalculateTotalsResult;
40
63
 
41
- export { type CalculateTotalsInput, type CalculateTotalsResult, type DiningOption, type DiscountType, type VoucherType, calculateTotals };
64
+ export { type CalculateTotalsInput, type CalculateTotalsResult, type DiningOption, type DiscountType, type VoucherType, calculateTotals, clamp, format2, getDiscountBreakdown, isAddVatAfterDiscount, isVatExemptDiscount, toMoneyNumber };
package/dist/index.js CHANGED
@@ -1,74 +1,123 @@
1
1
  // src/calculate-totals.ts
2
- function round(value) {
3
- return Number(value.toFixed(2));
2
+ var clamp = (n, min = 0, max = Number.POSITIVE_INFINITY) => Math.min(Math.max(n, min), max);
3
+ var toMoneyNumber = (v, fallback = 0) => {
4
+ const n = typeof v === "number" ? v : typeof v === "string" ? Number(v.replace(/,/g, "").trim()) : fallback;
5
+ if (!Number.isFinite(n)) return fallback;
6
+ return n;
7
+ };
8
+ function format2(value) {
9
+ return Number((value || 0).toFixed(2));
10
+ }
11
+ function includesAny(value, keywords) {
12
+ return keywords.some((keyword) => value.includes(keyword));
13
+ }
14
+ function isAddVatAfterDiscount(discountType) {
15
+ const value = String(discountType || "").toLowerCase();
16
+ return includesAny(value, ["solo", "solo parent"]) || includesAny(value, ["national", "athlete", "naac"]) || includesAny(value, ["uniformed", "uniformed personnel", "mov"]);
4
17
  }
5
18
  function isVatExemptDiscount(discountType) {
6
- return ["SENIOR", "PWD", "NAAC"].includes(String(discountType || "").toUpperCase());
19
+ const value = String(discountType || "").toLowerCase();
20
+ return includesAny(value, ["senior", "sc"]) || includesAny(value, ["pwd", "person with disability"]);
21
+ }
22
+ function getDiscountBreakdown(discountType, discountedAmount) {
23
+ const value = String(discountType || "").toLowerCase();
24
+ return {
25
+ sc: includesAny(value, ["senior", "sc"]) ? discountedAmount : 0,
26
+ pwd: includesAny(value, ["pwd", "person with disability"]) ? discountedAmount : 0,
27
+ solo: includesAny(value, ["solo", "solo parent"]) ? discountedAmount : 0,
28
+ naac: includesAny(value, ["national", "athlete", "naac"]) ? discountedAmount : 0,
29
+ mov: includesAny(value, ["uniformed", "uniformed personnel", "mov"]) ? discountedAmount : 0
30
+ };
7
31
  }
8
32
  function calculateTotals(input) {
9
33
  const {
10
- discountableTotalAmount,
11
- nonDiscountableTotalAmount,
12
- serviceChargeableTotalAmount,
13
- voucherType,
34
+ discountableTotalAmount = 0,
35
+ nonDiscountableTotalAmount = 0,
36
+ serviceChargeableTotalAmount = 0,
37
+ voucherType = null,
14
38
  voucherRate = 0,
15
- discountType,
39
+ discountType = null,
16
40
  discountRate = 0,
17
- vatRate,
18
- serviceChargeRate,
19
- togoCharge,
20
- diningOption = "FOR_HERE"
41
+ vatRate = 12,
42
+ serviceChargeRate = 0,
43
+ togoCharge = 0
21
44
  } = input;
22
- const grossAmount = round(discountableTotalAmount + nonDiscountableTotalAmount);
23
- let voucherDiscount = 0;
45
+ const totalOrderAmount = format2(discountableTotalAmount + nonDiscountableTotalAmount);
46
+ let voucherDiscounted = 0;
24
47
  if (voucherType === "FIXED") {
25
- voucherDiscount = Math.min(voucherRate, grossAmount);
48
+ voucherDiscounted = -Math.min(toMoneyNumber(voucherRate), totalOrderAmount);
26
49
  }
27
50
  if (voucherType === "PERCENTAGE") {
28
- voucherDiscount = grossAmount * (voucherRate / 100);
51
+ voucherDiscounted = -(totalOrderAmount * (toMoneyNumber(voucherRate) / 100));
29
52
  }
30
- voucherDiscount = round(voucherDiscount);
31
- const subtotalAfterVoucher = round(grossAmount - voucherDiscount);
32
- const vatDivisor = 1 + vatRate / 100;
33
- let vatNet = round(subtotalAfterVoucher / vatDivisor);
34
- let vat = round(subtotalAfterVoucher - vatNet);
35
- let discount = 0;
36
- if (discountType) {
37
- const discountableVatNet = round(discountableTotalAmount / vatDivisor);
38
- discount = round(discountableVatNet * (discountRate / 100));
39
- vatNet = round(vatNet - discount);
40
- if (isVatExemptDiscount(discountType)) {
41
- vat = 0;
53
+ const subtotal = totalOrderAmount + voucherDiscounted;
54
+ const voucherVatNet = Number((totalOrderAmount / vatRate).toFixed(2));
55
+ const voucherVat = Number((voucherVatNet * 0.12).toFixed(2));
56
+ let vatNet = Number((subtotal / vatRate).toFixed(2));
57
+ let vat = Number((vatNet * 0.12).toFixed(2));
58
+ const isSpecial = discountRate > 0;
59
+ const isAddVat = isAddVatAfterDiscount(discountType);
60
+ const discounted = Number((discountRate * -vatNet).toFixed(2));
61
+ if (isSpecial) {
62
+ vatNet = Number((vatNet + discounted).toFixed(2));
63
+ if (!isAddVat) {
64
+ vat = -vat;
42
65
  }
43
66
  }
44
67
  let serviceCharge = 0;
45
- if (diningOption === "FOR_HERE" && serviceChargeRate > 0) {
46
- serviceCharge = round(serviceChargeableTotalAmount * (serviceChargeRate / 100));
68
+ if (serviceChargeRate > 0 && serviceChargeableTotalAmount > 0) {
69
+ const scRate = serviceChargeRate / 100;
70
+ if (isSpecial) {
71
+ serviceCharge = Number(((vatNet + Math.abs(discounted)) * scRate).toFixed(2));
72
+ } else {
73
+ serviceCharge = Number((vatNet * scRate).toFixed(2));
74
+ }
47
75
  }
48
- const finalTogoCharge = diningOption === "TO_GO" ? togoCharge : 0;
49
- const totalAmount = round(Math.max(0, vatNet + vat + serviceCharge + finalTogoCharge));
76
+ let totalAmount = vatNet + vat + serviceCharge + togoCharge;
77
+ if (isSpecial && !isAddVat) {
78
+ totalAmount -= vat;
79
+ }
80
+ if (totalAmount < 0) totalAmount = 0;
50
81
  return {
51
- grossAmount,
52
- voucherDiscount: -voucherDiscount,
53
- subtotalAfterVoucher,
54
- vatNet,
82
+ gross: subtotal + Math.abs(voucherDiscounted),
83
+ hasVoucher: voucherRate > 0,
84
+ voucher: {
85
+ discount: voucherDiscounted,
86
+ net: subtotal,
87
+ vatNet: voucherVatNet,
88
+ vat: voucherVat,
89
+ forBirDiscount: -(voucherType === "FIXED" ? voucherVatNet - Number(voucherRate.toFixed(2)) : voucherVatNet * Number(voucherRate.toFixed(2)))
90
+ },
91
+ hasDiscount: discountRate > 0,
92
+ discount: {
93
+ discount: discounted,
94
+ net: vatNet
95
+ },
96
+ netOfVat: vatNet + Math.abs(discounted),
55
97
  vat,
56
- discount: -discount,
57
98
  serviceCharge,
58
- togoCharge: finalTogoCharge,
99
+ togoCharge,
59
100
  totalAmount,
60
101
  bir: {
61
- vatSales: vat > 0 ? vatNet + Math.abs(discount) : 0,
62
- vatExemptSales: vat === 0 ? vatNet + Math.abs(discount) : 0,
102
+ vatSales: vat > 0 ? vatNet + Math.abs(discounted) : 0,
103
+ vatExemptSales: vat > 0 ? 0 : vatNet + Math.abs(discounted),
63
104
  vatZeroRatedSales: 0,
64
105
  nonTaxableSales: 0,
65
106
  vat,
66
- discount,
107
+ totalSales: vat > 0 ? subtotal : vatNet + Math.abs(discounted),
108
+ discount: discounted,
67
109
  serviceCharge,
110
+ togoCharge,
68
111
  amountPayable: totalAmount
69
112
  }
70
113
  };
71
114
  }
72
115
  export {
73
- calculateTotals
116
+ calculateTotals,
117
+ clamp,
118
+ format2,
119
+ getDiscountBreakdown,
120
+ isAddVatAfterDiscount,
121
+ isVatExemptDiscount,
122
+ toMoneyNumber
74
123
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qrwise/pos-calculations",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Shared POS calculations for QR Wise POS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",