@qrwise/pos-calculations 0.0.27 → 0.0.29

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
@@ -27,6 +27,8 @@ __export(index_exports, {
27
27
  getDiscountBreakdown: () => getDiscountBreakdown,
28
28
  isAddVatAfterDiscount: () => isAddVatAfterDiscount,
29
29
  isVatExemptDiscount: () => isVatExemptDiscount,
30
+ subscriptionTaxFromBase: () => subscriptionTaxFromBase,
31
+ subscriptionTaxFromTotal: () => subscriptionTaxFromTotal,
30
32
  toMoneyNumber: () => toMoneyNumber
31
33
  });
32
34
  module.exports = __toCommonJS(index_exports);
@@ -436,6 +438,7 @@ function buildTransactionProjection(transaction, options = {}) {
436
438
  const storedZeroRatedSales = toMoneyNumber(transaction.zero_rated_sales);
437
439
  const storedNonTaxableSales = toMoneyNumber(transaction.non_taxable_sales);
438
440
  const storedNetOfVat = storedVatSales + storedVatExemptSales + storedZeroRatedSales + storedNonTaxableSales;
441
+ const serviceCharge = toMoneyNumber(transaction.service_charge_amount);
439
442
  const reconstructsVoidedVoucherSale = !isSuccessful && hasVoucher && gross > 0 && options.isVatRegistered !== void 0;
440
443
  const voidedVoucherNet = reconstructsVoidedVoucherSale ? format2(gross / (options.isVatRegistered ? 1.12 : 1)) : 0;
441
444
  const netOfVat = voidedVoucherNet || storedNetOfVat || Math.max(0, toMoneyNumber(transaction.amount_payable) - storedVat);
@@ -487,12 +490,25 @@ function buildTransactionProjection(transaction, options = {}) {
487
490
  adjustments.vatOnReturns = returned ? -vatAmount : 0;
488
491
  adjustments.vatOnCancelled = cancelled ? -vatAmount : 0;
489
492
  adjustments.totalAdjustments = Object.values(adjustments).reduce((total, amount) => total + amount, 0);
490
- const vatableSales = isSuccessful ? isNonVat ? 0 : hasVoucherAndDiscount ? format2(storedVatSales + Math.abs(otherDiscount)) : voucher > 0 ? scopedVoucherVatNet : storedVatSales : isNonVat ? 0 : netOfVat;
493
+ let vatableSales = isSuccessful ? isNonVat ? 0 : hasVoucherAndDiscount ? format2(storedVatSales + Math.abs(otherDiscount)) : voucher > 0 ? scopedVoucherVatNet : storedVatSales : isNonVat ? 0 : netOfVat;
494
+ const projectedGross = format2(vatableSales + vatAmount + vatExemptSales + storedZeroRatedSales);
495
+ const unclassifiedDiscountGross = format2(gross - projectedGross);
496
+ const storedComponentPayable = format2(storedNetOfVat + storedVat - discount + serviceCharge);
497
+ const hasHistoricalUnclassifiedDiscount = isSuccessful && !isNonVat && !hasVoucher && storedNonTaxableSales === 0 && unclassifiedDiscountGross > 0.02 && Math.abs(format2(toMoneyNumber(transaction.amount_payable) - storedComponentPayable)) <= 0.02;
498
+ if (hasHistoricalUnclassifiedDiscount) {
499
+ const inferredDiscountNet = format2(unclassifiedDiscountGross / 1.12);
500
+ const inferredDiscountVat = format2(unclassifiedDiscountGross - inferredDiscountNet);
501
+ vatableSales = format2(vatableSales + inferredDiscountNet);
502
+ vatAmount = format2(vatAmount + inferredDiscountVat);
503
+ deductions.otherDiscount = -inferredDiscountNet;
504
+ deductions.totalDeductions = format2(deductions.totalDeductions - inferredDiscountNet);
505
+ adjustments.otherDiscountAdjustment = -inferredDiscountVat;
506
+ adjustments.totalAdjustments = format2(adjustments.totalAdjustments - inferredDiscountVat);
507
+ }
491
508
  const zeroRatedSales = isSuccessful ? storedZeroRatedSales : 0;
492
509
  const netSales = isSuccessful ? format2(
493
510
  vatableSales + vatExemptSales + zeroRatedSales + (isNonVat ? 0 : storedNonTaxableSales) + deductions.scDiscount + deductions.pwdDiscount + deductions.naacDiscount + deductions.soloDiscount + deductions.movDiscount + deductions.otherDiscount
494
511
  ) : 0;
495
- const serviceCharge = toMoneyNumber(transaction.service_charge_amount);
496
512
  return {
497
513
  state,
498
514
  receipt: {
@@ -530,6 +546,41 @@ function buildTransactionProjection(transaction, options = {}) {
530
546
  discountCounts: counts
531
547
  };
532
548
  }
549
+
550
+ // src/subscription-tax.ts
551
+ function cents(amount) {
552
+ const value = Number(amount);
553
+ const result = Math.round(value * 100);
554
+ if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(result) || Math.abs(value * 100 - result) > 1e-4)
555
+ throw new Error("Invalid subscription amount");
556
+ return result;
557
+ }
558
+ function subscriptionTaxFromBase(amount) {
559
+ const base = cents(amount);
560
+ const vat = Math.round(base * 12 / 100);
561
+ if (!Number.isSafeInteger(base + vat)) throw new Error("Invalid subscription total");
562
+ return { base_price: base / 100, vat_amount: vat / 100, amount: (base + vat) / 100 };
563
+ }
564
+ function subscriptionTaxFromTotal(amount) {
565
+ const total = cents(amount);
566
+ const { vatNet } = calculateTotals({
567
+ quantity: 1,
568
+ discountableTotalAmount: total / 100,
569
+ nonDiscountableTotalAmount: 0,
570
+ serviceChargeableTotalAmount: 0,
571
+ togoableTotalAmount: 0,
572
+ voucherType: null,
573
+ voucherRate: 0,
574
+ discountType: null,
575
+ discountRate: 0,
576
+ vatRate: 112,
577
+ isVatRegistered: true,
578
+ serviceChargeRate: 0,
579
+ togoCharge: 0
580
+ });
581
+ const base = Math.round(vatNet * 100);
582
+ return { base_price: base / 100, vat_amount: (total - base) / 100, amount: total / 100 };
583
+ }
533
584
  // Annotate the CommonJS export names for ESM import in node:
534
585
  0 && (module.exports = {
535
586
  buildTransactionProjection,
@@ -539,5 +590,7 @@ function buildTransactionProjection(transaction, options = {}) {
539
590
  getDiscountBreakdown,
540
591
  isAddVatAfterDiscount,
541
592
  isVatExemptDiscount,
593
+ subscriptionTaxFromBase,
594
+ subscriptionTaxFromTotal,
542
595
  toMoneyNumber
543
596
  });
package/dist/index.d.cts CHANGED
@@ -197,4 +197,17 @@ declare function buildTransactionProjection(transaction: FinancialTransactionInp
197
197
  discountCounts: DiscountWeights;
198
198
  };
199
199
 
200
- export { type CalculateTotalsInput, type CalculateTotalsResult, type DiscountBreakdown, type DiscountRates, type DiscountType, type FinancialTransactionInput, type PaxDiscountCounts, type ReportDiscountCategory, type TransactionProjectionOptions, type VoucherType, buildTransactionProjection, calculateTotals, clamp, format2, getDiscountBreakdown, isAddVatAfterDiscount, isVatExemptDiscount, toMoneyNumber };
200
+ /** Catalog subscription prices exclude 12% VAT. Round VAT once per charge. */
201
+ declare function subscriptionTaxFromBase(amount: number | string): {
202
+ base_price: number;
203
+ vat_amount: number;
204
+ amount: number;
205
+ };
206
+ /** Preserve the actual total on legacy subscriptions and historical payments. */
207
+ declare function subscriptionTaxFromTotal(amount: number | string): {
208
+ base_price: number;
209
+ vat_amount: number;
210
+ amount: number;
211
+ };
212
+
213
+ export { type CalculateTotalsInput, type CalculateTotalsResult, type DiscountBreakdown, type DiscountRates, type DiscountType, type FinancialTransactionInput, type PaxDiscountCounts, type ReportDiscountCategory, type TransactionProjectionOptions, type VoucherType, buildTransactionProjection, calculateTotals, clamp, format2, getDiscountBreakdown, isAddVatAfterDiscount, isVatExemptDiscount, subscriptionTaxFromBase, subscriptionTaxFromTotal, toMoneyNumber };
package/dist/index.d.ts CHANGED
@@ -197,4 +197,17 @@ declare function buildTransactionProjection(transaction: FinancialTransactionInp
197
197
  discountCounts: DiscountWeights;
198
198
  };
199
199
 
200
- export { type CalculateTotalsInput, type CalculateTotalsResult, type DiscountBreakdown, type DiscountRates, type DiscountType, type FinancialTransactionInput, type PaxDiscountCounts, type ReportDiscountCategory, type TransactionProjectionOptions, type VoucherType, buildTransactionProjection, calculateTotals, clamp, format2, getDiscountBreakdown, isAddVatAfterDiscount, isVatExemptDiscount, toMoneyNumber };
200
+ /** Catalog subscription prices exclude 12% VAT. Round VAT once per charge. */
201
+ declare function subscriptionTaxFromBase(amount: number | string): {
202
+ base_price: number;
203
+ vat_amount: number;
204
+ amount: number;
205
+ };
206
+ /** Preserve the actual total on legacy subscriptions and historical payments. */
207
+ declare function subscriptionTaxFromTotal(amount: number | string): {
208
+ base_price: number;
209
+ vat_amount: number;
210
+ amount: number;
211
+ };
212
+
213
+ export { type CalculateTotalsInput, type CalculateTotalsResult, type DiscountBreakdown, type DiscountRates, type DiscountType, type FinancialTransactionInput, type PaxDiscountCounts, type ReportDiscountCategory, type TransactionProjectionOptions, type VoucherType, buildTransactionProjection, calculateTotals, clamp, format2, getDiscountBreakdown, isAddVatAfterDiscount, isVatExemptDiscount, subscriptionTaxFromBase, subscriptionTaxFromTotal, toMoneyNumber };
package/dist/index.js CHANGED
@@ -403,6 +403,7 @@ function buildTransactionProjection(transaction, options = {}) {
403
403
  const storedZeroRatedSales = toMoneyNumber(transaction.zero_rated_sales);
404
404
  const storedNonTaxableSales = toMoneyNumber(transaction.non_taxable_sales);
405
405
  const storedNetOfVat = storedVatSales + storedVatExemptSales + storedZeroRatedSales + storedNonTaxableSales;
406
+ const serviceCharge = toMoneyNumber(transaction.service_charge_amount);
406
407
  const reconstructsVoidedVoucherSale = !isSuccessful && hasVoucher && gross > 0 && options.isVatRegistered !== void 0;
407
408
  const voidedVoucherNet = reconstructsVoidedVoucherSale ? format2(gross / (options.isVatRegistered ? 1.12 : 1)) : 0;
408
409
  const netOfVat = voidedVoucherNet || storedNetOfVat || Math.max(0, toMoneyNumber(transaction.amount_payable) - storedVat);
@@ -454,12 +455,25 @@ function buildTransactionProjection(transaction, options = {}) {
454
455
  adjustments.vatOnReturns = returned ? -vatAmount : 0;
455
456
  adjustments.vatOnCancelled = cancelled ? -vatAmount : 0;
456
457
  adjustments.totalAdjustments = Object.values(adjustments).reduce((total, amount) => total + amount, 0);
457
- const vatableSales = isSuccessful ? isNonVat ? 0 : hasVoucherAndDiscount ? format2(storedVatSales + Math.abs(otherDiscount)) : voucher > 0 ? scopedVoucherVatNet : storedVatSales : isNonVat ? 0 : netOfVat;
458
+ let vatableSales = isSuccessful ? isNonVat ? 0 : hasVoucherAndDiscount ? format2(storedVatSales + Math.abs(otherDiscount)) : voucher > 0 ? scopedVoucherVatNet : storedVatSales : isNonVat ? 0 : netOfVat;
459
+ const projectedGross = format2(vatableSales + vatAmount + vatExemptSales + storedZeroRatedSales);
460
+ const unclassifiedDiscountGross = format2(gross - projectedGross);
461
+ const storedComponentPayable = format2(storedNetOfVat + storedVat - discount + serviceCharge);
462
+ const hasHistoricalUnclassifiedDiscount = isSuccessful && !isNonVat && !hasVoucher && storedNonTaxableSales === 0 && unclassifiedDiscountGross > 0.02 && Math.abs(format2(toMoneyNumber(transaction.amount_payable) - storedComponentPayable)) <= 0.02;
463
+ if (hasHistoricalUnclassifiedDiscount) {
464
+ const inferredDiscountNet = format2(unclassifiedDiscountGross / 1.12);
465
+ const inferredDiscountVat = format2(unclassifiedDiscountGross - inferredDiscountNet);
466
+ vatableSales = format2(vatableSales + inferredDiscountNet);
467
+ vatAmount = format2(vatAmount + inferredDiscountVat);
468
+ deductions.otherDiscount = -inferredDiscountNet;
469
+ deductions.totalDeductions = format2(deductions.totalDeductions - inferredDiscountNet);
470
+ adjustments.otherDiscountAdjustment = -inferredDiscountVat;
471
+ adjustments.totalAdjustments = format2(adjustments.totalAdjustments - inferredDiscountVat);
472
+ }
458
473
  const zeroRatedSales = isSuccessful ? storedZeroRatedSales : 0;
459
474
  const netSales = isSuccessful ? format2(
460
475
  vatableSales + vatExemptSales + zeroRatedSales + (isNonVat ? 0 : storedNonTaxableSales) + deductions.scDiscount + deductions.pwdDiscount + deductions.naacDiscount + deductions.soloDiscount + deductions.movDiscount + deductions.otherDiscount
461
476
  ) : 0;
462
- const serviceCharge = toMoneyNumber(transaction.service_charge_amount);
463
477
  return {
464
478
  state,
465
479
  receipt: {
@@ -497,6 +511,41 @@ function buildTransactionProjection(transaction, options = {}) {
497
511
  discountCounts: counts
498
512
  };
499
513
  }
514
+
515
+ // src/subscription-tax.ts
516
+ function cents(amount) {
517
+ const value = Number(amount);
518
+ const result = Math.round(value * 100);
519
+ if (!Number.isFinite(value) || value < 0 || !Number.isSafeInteger(result) || Math.abs(value * 100 - result) > 1e-4)
520
+ throw new Error("Invalid subscription amount");
521
+ return result;
522
+ }
523
+ function subscriptionTaxFromBase(amount) {
524
+ const base = cents(amount);
525
+ const vat = Math.round(base * 12 / 100);
526
+ if (!Number.isSafeInteger(base + vat)) throw new Error("Invalid subscription total");
527
+ return { base_price: base / 100, vat_amount: vat / 100, amount: (base + vat) / 100 };
528
+ }
529
+ function subscriptionTaxFromTotal(amount) {
530
+ const total = cents(amount);
531
+ const { vatNet } = calculateTotals({
532
+ quantity: 1,
533
+ discountableTotalAmount: total / 100,
534
+ nonDiscountableTotalAmount: 0,
535
+ serviceChargeableTotalAmount: 0,
536
+ togoableTotalAmount: 0,
537
+ voucherType: null,
538
+ voucherRate: 0,
539
+ discountType: null,
540
+ discountRate: 0,
541
+ vatRate: 112,
542
+ isVatRegistered: true,
543
+ serviceChargeRate: 0,
544
+ togoCharge: 0
545
+ });
546
+ const base = Math.round(vatNet * 100);
547
+ return { base_price: base / 100, vat_amount: (total - base) / 100, amount: total / 100 };
548
+ }
500
549
  export {
501
550
  buildTransactionProjection,
502
551
  calculateTotals,
@@ -505,5 +554,7 @@ export {
505
554
  getDiscountBreakdown,
506
555
  isAddVatAfterDiscount,
507
556
  isVatExemptDiscount,
557
+ subscriptionTaxFromBase,
558
+ subscriptionTaxFromTotal,
508
559
  toMoneyNumber
509
560
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qrwise/pos-calculations",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "Shared POS calculations for QR Wise POS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",