Saltar al contenido principal

Document Calculations Module

Overview

Shared calculation engine for all financial document types (Sale, Order, Quote, Bill). Provides a single source of truth for tax computation, line item recomputation, document total aggregation, validation, and currency conversion.

Location: apps/backend/src/document-calculations/

This module has no HTTP endpoints — it is a pure service module consumed by other document modules.

Architecture

document-calculations/
├── document-calculations.module.ts # NestJS module (provides + exports LineItemTaxService)
├── application/
│ ├── line-item-tax.service.ts # Core calculation service
│ └── __tests__/
│ └── document-totals-consistency.spec.ts
└── domain/
├── index.ts # Barrel export for domain types
├── tax.types.ts # TaxItem, TaxBreakdownItem, LineItemRecomputedResult
├── document-line-item.types.ts # DocumentLineItemShape, DocumentTotalsShape
└── document-calculation.errors.ts # DocumentTotalMismatchError

No infrastructure layer — the module performs pure calculations with no persistence.

Domain Concepts

TaxItem

A tax definition from product.taxes.items. Contains rate, type (percentage or fixed), and optional name/code.

TaxBreakdownItem

Computed tax breakdown for a single tax on a line item. Includes taxable amounts, tax amounts, and their base currency equivalents.

DocumentLineItemShape

Canonical line item contract used by all document types. Ensures the frontend ItemsTotals component works identically across Sale, Order, and Quote views.

DocumentTotalsShape

Header totals contract: totalAmount, totalBaseAmount, optional subtotal, taxAmount, cartDiscountTotal.

Key Service Methods

MethodPurpose
extractTaxItems(taxes)Parse tax definitions from product JSON
extractTaxItemsFromProduct(product)Extract taxes from a product object
calculateTaxAmountMajor(taxes, amount, qty)Total tax in major units (tax-inclusive)
recomputeItemAmountAndTaxes(taxItems, price, qty, rate, discount)Full line item recomputation with tax breakdown
validateHeaderTotalMatchesLineTotals(items, total, opts)Invariant check before persistence
computeDocumentTotals(items, rate, cartDiscount)Aggregate line totals to document level
recomputeTaxBreakdownFromGrossAmount(amount, origGross, origTax)Derive taxes from stored snapshots
computeGrossTotalFromOrderItems(items, minorUnit)Sum order items (excludes voided/comped)
toMinorUnit(amount, minorUnit)Major to minor unit conversion (BigInt)
toMinorUnitNumber(amount, minorUnit)Major to minor unit conversion (number)

Consumer Modules

ModuleImportUsage
SalesDocumentCalculationsModuleTax enrichment, discount recomputation, total validation
OrdersDocumentCalculationsModuleOrder pricing, bill splits, payment totals
QuotesDocumentCalculationsModuleQuote item enrichment, totals, conversion to sale
App (root)DocumentCalculationsModuleGlobal availability

Tax-Inclusive Pricing Formula

lineGross = quantity × unitPriceFinal
taxableAmount = lineGross / (1 + taxRate)
taxAmount = lineGross - taxableAmount
grandTotal = sum(lineGross) // do NOT add tax on top

Header Total Invariant

Before persisting any document:

expectedTotal = sum(lineGrossAmounts) - cartDiscountTotal
headerTotal must equal expectedTotal (within rounding tolerance)

Call validateHeaderTotalMatchesLineTotals() before create/update when the client sends a header total.