@qrwise/pos-calculations 0.0.1 → 0.0.6

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
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -19,82 +20,128 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
20
  // src/index.ts
20
21
  var index_exports = {};
21
22
  __export(index_exports, {
22
- calculateTotals: () => calculateTotals
23
+ calculateTotals: () => calculateTotals,
24
+ clamp: () => clamp,
25
+ format2: () => format2,
26
+ getDiscountBreakdown: () => getDiscountBreakdown,
27
+ isAddVatAfterDiscount: () => isAddVatAfterDiscount,
28
+ isVatExemptDiscount: () => isVatExemptDiscount,
29
+ toMoneyNumber: () => toMoneyNumber
23
30
  });
24
31
  module.exports = __toCommonJS(index_exports);
25
32
 
26
33
  // src/calculate-totals.ts
27
- function round(value) {
28
- 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"]);
29
49
  }
30
50
  function isVatExemptDiscount(discountType) {
31
- 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
+ };
32
63
  }
33
64
  function calculateTotals(input) {
34
65
  const {
35
- discountableTotalAmount,
36
- nonDiscountableTotalAmount,
37
- serviceChargeableTotalAmount,
38
- voucherType,
66
+ discountableTotalAmount = 0,
67
+ nonDiscountableTotalAmount = 0,
68
+ serviceChargeableTotalAmount = 0,
69
+ voucherType = null,
39
70
  voucherRate = 0,
40
- discountType,
71
+ discountType = null,
41
72
  discountRate = 0,
42
- vatRate,
43
- serviceChargeRate,
44
- togoCharge,
73
+ vatRate = 12,
74
+ serviceChargeRate = 0,
75
+ togoCharge = 0,
45
76
  diningOption = "FOR_HERE"
46
77
  } = input;
47
- const grossAmount = round(discountableTotalAmount + nonDiscountableTotalAmount);
78
+ const grossAmount = format2(discountableTotalAmount + nonDiscountableTotalAmount);
48
79
  let voucherDiscount = 0;
49
80
  if (voucherType === "FIXED") {
50
- voucherDiscount = Math.min(voucherRate, grossAmount);
81
+ voucherDiscount = Math.min(toMoneyNumber(voucherRate), grossAmount);
51
82
  }
52
83
  if (voucherType === "PERCENTAGE") {
53
- voucherDiscount = grossAmount * (voucherRate / 100);
84
+ voucherDiscount = grossAmount * (toMoneyNumber(voucherRate) / 100);
54
85
  }
55
- voucherDiscount = round(voucherDiscount);
56
- const subtotalAfterVoucher = round(grossAmount - voucherDiscount);
57
- const vatDivisor = 1 + vatRate / 100;
58
- let vatNet = round(subtotalAfterVoucher / vatDivisor);
59
- let vat = round(subtotalAfterVoucher - vatNet);
86
+ voucherDiscount = format2(clamp(voucherDiscount, 0, grossAmount));
87
+ const subtotalAfterVoucher = format2(grossAmount - voucherDiscount);
88
+ const vatDivisor = 1 + toMoneyNumber(vatRate, 12) / 100;
89
+ let vatNet = format2(subtotalAfterVoucher / vatDivisor);
90
+ let vat = format2(subtotalAfterVoucher - vatNet);
60
91
  let discount = 0;
61
- if (discountType) {
62
- const discountableVatNet = round(discountableTotalAmount / vatDivisor);
63
- discount = round(discountableVatNet * (discountRate / 100));
64
- vatNet = round(vatNet - discount);
92
+ const hasDiscount = !!discountType && toMoneyNumber(discountRate) > 0;
93
+ if (hasDiscount) {
94
+ const discountableVatNet = format2(discountableTotalAmount / vatDivisor);
95
+ discount = format2(discountableVatNet * (toMoneyNumber(discountRate) / 100));
96
+ vatNet = format2(vatNet - discount);
65
97
  if (isVatExemptDiscount(discountType)) {
66
98
  vat = 0;
67
99
  }
100
+ if (isAddVatAfterDiscount(discountType)) {
101
+ vat = format2(vatNet * (toMoneyNumber(vatRate, 12) / 100));
102
+ }
68
103
  }
69
104
  let serviceCharge = 0;
70
- if (diningOption === "FOR_HERE" && serviceChargeRate > 0) {
71
- serviceCharge = round(serviceChargeableTotalAmount * (serviceChargeRate / 100));
105
+ if (diningOption === "FOR_HERE" && serviceChargeRate > 0 && serviceChargeableTotalAmount > 0) {
106
+ const serviceChargeVatNet = format2(serviceChargeableTotalAmount / vatDivisor);
107
+ serviceCharge = format2(serviceChargeVatNet * (toMoneyNumber(serviceChargeRate) / 100));
72
108
  }
73
- const finalTogoCharge = diningOption === "TO_GO" ? togoCharge : 0;
74
- const totalAmount = round(Math.max(0, vatNet + vat + serviceCharge + finalTogoCharge));
109
+ const finalTogoCharge = diningOption === "TO_GO" ? toMoneyNumber(togoCharge) : 0;
110
+ const totalAmount = format2(Math.max(0, vatNet + vat + serviceCharge + finalTogoCharge));
111
+ const signedVoucherDiscount = -voucherDiscount;
112
+ const signedDiscount = -discount;
75
113
  return {
76
114
  grossAmount,
77
- voucherDiscount: -voucherDiscount,
115
+ voucherDiscount: signedVoucherDiscount,
78
116
  subtotalAfterVoucher,
79
117
  vatNet,
80
118
  vat,
81
- discount: -discount,
119
+ discount: signedDiscount,
82
120
  serviceCharge,
83
121
  togoCharge: finalTogoCharge,
84
122
  totalAmount,
85
123
  bir: {
86
- vatSales: vat > 0 ? vatNet + Math.abs(discount) : 0,
87
- vatExemptSales: vat === 0 ? vatNet + Math.abs(discount) : 0,
124
+ vatSales: vat > 0 ? format2(vatNet + Math.abs(signedDiscount)) : 0,
125
+ vatExemptSales: vat <= 0 ? format2(vatNet + Math.abs(signedDiscount)) : 0,
88
126
  vatZeroRatedSales: 0,
89
127
  nonTaxableSales: 0,
90
128
  vat,
91
- discount,
129
+ discount: signedDiscount,
92
130
  serviceCharge,
93
- amountPayable: totalAmount
94
- }
131
+ togoCharge: finalTogoCharge,
132
+ amountPayable: totalAmount,
133
+ totalSales: vat > 0 ? subtotalAfterVoucher : format2(vatNet + Math.abs(signedDiscount))
134
+ },
135
+ discountBreakdown: getDiscountBreakdown(discountType, signedDiscount)
95
136
  };
96
137
  }
97
138
  // Annotate the CommonJS export names for ESM import in node:
98
139
  0 && (module.exports = {
99
- calculateTotals
140
+ calculateTotals,
141
+ clamp,
142
+ format2,
143
+ getDiscountBreakdown,
144
+ isAddVatAfterDiscount,
145
+ isVatExemptDiscount,
146
+ toMoneyNumber
100
147
  });
package/dist/index.d.cts CHANGED
@@ -4,14 +4,14 @@ 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;
12
+ vatRate?: number;
13
+ serviceChargeRate?: number;
14
+ togoCharge?: number;
15
15
  diningOption?: DiningOption;
16
16
  }
17
17
  interface CalculateTotalsResult {
@@ -32,10 +32,31 @@ interface CalculateTotalsResult {
32
32
  vat: number;
33
33
  discount: number;
34
34
  serviceCharge: number;
35
+ togoCharge: number;
35
36
  amountPayable: number;
37
+ totalSales: number;
38
+ };
39
+ discountBreakdown: {
40
+ sc: number;
41
+ pwd: number;
42
+ solo: number;
43
+ naac: number;
44
+ mov: number;
36
45
  };
37
46
  }
38
47
 
48
+ declare const clamp: (n: number, min?: number, max?: number) => number;
49
+ declare const toMoneyNumber: (v: unknown, fallback?: number) => number;
50
+ declare function format2(value: number): number;
51
+ declare function isAddVatAfterDiscount(discountType?: string | null): boolean;
52
+ declare function isVatExemptDiscount(discountType?: string | null): boolean;
53
+ declare function getDiscountBreakdown(discountType: string | null | undefined, discountedAmount: number): {
54
+ sc: number;
55
+ pwd: number;
56
+ solo: number;
57
+ naac: number;
58
+ mov: number;
59
+ };
39
60
  declare function calculateTotals(input: CalculateTotalsInput): CalculateTotalsResult;
40
61
 
41
- export { type CalculateTotalsInput, type CalculateTotalsResult, type DiningOption, type DiscountType, type VoucherType, calculateTotals };
62
+ 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,14 +4,14 @@ 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;
12
+ vatRate?: number;
13
+ serviceChargeRate?: number;
14
+ togoCharge?: number;
15
15
  diningOption?: DiningOption;
16
16
  }
17
17
  interface CalculateTotalsResult {
@@ -32,10 +32,31 @@ interface CalculateTotalsResult {
32
32
  vat: number;
33
33
  discount: number;
34
34
  serviceCharge: number;
35
+ togoCharge: number;
35
36
  amountPayable: number;
37
+ totalSales: number;
38
+ };
39
+ discountBreakdown: {
40
+ sc: number;
41
+ pwd: number;
42
+ solo: number;
43
+ naac: number;
44
+ mov: number;
36
45
  };
37
46
  }
38
47
 
48
+ declare const clamp: (n: number, min?: number, max?: number) => number;
49
+ declare const toMoneyNumber: (v: unknown, fallback?: number) => number;
50
+ declare function format2(value: number): number;
51
+ declare function isAddVatAfterDiscount(discountType?: string | null): boolean;
52
+ declare function isVatExemptDiscount(discountType?: string | null): boolean;
53
+ declare function getDiscountBreakdown(discountType: string | null | undefined, discountedAmount: number): {
54
+ sc: number;
55
+ pwd: number;
56
+ solo: number;
57
+ naac: number;
58
+ mov: number;
59
+ };
39
60
  declare function calculateTotals(input: CalculateTotalsInput): CalculateTotalsResult;
40
61
 
41
- export { type CalculateTotalsInput, type CalculateTotalsResult, type DiningOption, type DiscountType, type VoucherType, calculateTotals };
62
+ 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,114 @@
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,
41
+ vatRate = 12,
42
+ serviceChargeRate = 0,
43
+ togoCharge = 0,
20
44
  diningOption = "FOR_HERE"
21
45
  } = input;
22
- const grossAmount = round(discountableTotalAmount + nonDiscountableTotalAmount);
46
+ const grossAmount = format2(discountableTotalAmount + nonDiscountableTotalAmount);
23
47
  let voucherDiscount = 0;
24
48
  if (voucherType === "FIXED") {
25
- voucherDiscount = Math.min(voucherRate, grossAmount);
49
+ voucherDiscount = Math.min(toMoneyNumber(voucherRate), grossAmount);
26
50
  }
27
51
  if (voucherType === "PERCENTAGE") {
28
- voucherDiscount = grossAmount * (voucherRate / 100);
52
+ voucherDiscount = grossAmount * (toMoneyNumber(voucherRate) / 100);
29
53
  }
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);
54
+ voucherDiscount = format2(clamp(voucherDiscount, 0, grossAmount));
55
+ const subtotalAfterVoucher = format2(grossAmount - voucherDiscount);
56
+ const vatDivisor = 1 + toMoneyNumber(vatRate, 12) / 100;
57
+ let vatNet = format2(subtotalAfterVoucher / vatDivisor);
58
+ let vat = format2(subtotalAfterVoucher - vatNet);
35
59
  let discount = 0;
36
- if (discountType) {
37
- const discountableVatNet = round(discountableTotalAmount / vatDivisor);
38
- discount = round(discountableVatNet * (discountRate / 100));
39
- vatNet = round(vatNet - discount);
60
+ const hasDiscount = !!discountType && toMoneyNumber(discountRate) > 0;
61
+ if (hasDiscount) {
62
+ const discountableVatNet = format2(discountableTotalAmount / vatDivisor);
63
+ discount = format2(discountableVatNet * (toMoneyNumber(discountRate) / 100));
64
+ vatNet = format2(vatNet - discount);
40
65
  if (isVatExemptDiscount(discountType)) {
41
66
  vat = 0;
42
67
  }
68
+ if (isAddVatAfterDiscount(discountType)) {
69
+ vat = format2(vatNet * (toMoneyNumber(vatRate, 12) / 100));
70
+ }
43
71
  }
44
72
  let serviceCharge = 0;
45
- if (diningOption === "FOR_HERE" && serviceChargeRate > 0) {
46
- serviceCharge = round(serviceChargeableTotalAmount * (serviceChargeRate / 100));
73
+ if (diningOption === "FOR_HERE" && serviceChargeRate > 0 && serviceChargeableTotalAmount > 0) {
74
+ const serviceChargeVatNet = format2(serviceChargeableTotalAmount / vatDivisor);
75
+ serviceCharge = format2(serviceChargeVatNet * (toMoneyNumber(serviceChargeRate) / 100));
47
76
  }
48
- const finalTogoCharge = diningOption === "TO_GO" ? togoCharge : 0;
49
- const totalAmount = round(Math.max(0, vatNet + vat + serviceCharge + finalTogoCharge));
77
+ const finalTogoCharge = diningOption === "TO_GO" ? toMoneyNumber(togoCharge) : 0;
78
+ const totalAmount = format2(Math.max(0, vatNet + vat + serviceCharge + finalTogoCharge));
79
+ const signedVoucherDiscount = -voucherDiscount;
80
+ const signedDiscount = -discount;
50
81
  return {
51
82
  grossAmount,
52
- voucherDiscount: -voucherDiscount,
83
+ voucherDiscount: signedVoucherDiscount,
53
84
  subtotalAfterVoucher,
54
85
  vatNet,
55
86
  vat,
56
- discount: -discount,
87
+ discount: signedDiscount,
57
88
  serviceCharge,
58
89
  togoCharge: finalTogoCharge,
59
90
  totalAmount,
60
91
  bir: {
61
- vatSales: vat > 0 ? vatNet + Math.abs(discount) : 0,
62
- vatExemptSales: vat === 0 ? vatNet + Math.abs(discount) : 0,
92
+ vatSales: vat > 0 ? format2(vatNet + Math.abs(signedDiscount)) : 0,
93
+ vatExemptSales: vat <= 0 ? format2(vatNet + Math.abs(signedDiscount)) : 0,
63
94
  vatZeroRatedSales: 0,
64
95
  nonTaxableSales: 0,
65
96
  vat,
66
- discount,
97
+ discount: signedDiscount,
67
98
  serviceCharge,
68
- amountPayable: totalAmount
69
- }
99
+ togoCharge: finalTogoCharge,
100
+ amountPayable: totalAmount,
101
+ totalSales: vat > 0 ? subtotalAfterVoucher : format2(vatNet + Math.abs(signedDiscount))
102
+ },
103
+ discountBreakdown: getDiscountBreakdown(discountType, signedDiscount)
70
104
  };
71
105
  }
72
106
  export {
73
- calculateTotals
107
+ calculateTotals,
108
+ clamp,
109
+ format2,
110
+ getDiscountBreakdown,
111
+ isAddVatAfterDiscount,
112
+ isVatExemptDiscount,
113
+ toMoneyNumber
74
114
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qrwise/pos-calculations",
3
- "version": "0.0.1",
3
+ "version": "0.0.6",
4
4
  "description": "Shared POS calculations for QR Wise POS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -18,8 +18,11 @@
18
18
  }
19
19
  },
20
20
  "scripts": {
21
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
22
- "prepublishOnly": "npm run build"
21
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --tsconfig tsconfig.json",
22
+ "prepublishOnly": "npm run build",
23
+ "release:patch": "npm version patch && npm publish",
24
+ "release:minor": "npm version minor && npm publish",
25
+ "release:major": "npm version major && npm publish"
23
26
  },
24
27
  "publishConfig": {
25
28
  "access": "public"