Document Line Item Shape
Overview
All document types (Sale, Order, Quote, Bill) use a shared line item shape so the frontend ItemsTotals / calculateTotals works identically. This prevents divergent subtotal, tax, and amount displays across modules.
Contract
Location: apps/backend/src/document-calculations/domain/document-line-item.types.ts
DocumentLineItemShape
interface DocumentLineItemShape {
quantity: number;
amount: number; // gross (tax-inclusive) line total
baseAmount?: number; // net (pre-tax) when applicable
taxes: DocumentLineItemTaxDetail[];
discountDetail?: LineDiscountDetail;
discount?: number; // line-level discount total in display currency
baseDiscount?: number; // line-level discount total in base currency
bundleDetail?: LineBundleDetail;
bundle?: number; // line-level bundle savings in display currency
baseBundle?: number; // line-level bundle savings in base currency
}
LineBundleDetail
Bundle savings applied at line level (mirrors LineDiscountDetail):
unitPriceOriginal,unitPriceFinal– before/after bundlediscountTotal– per-unit savingspriceSource–"bundle"bundles[]– per-application (bundleApplicationId, createdBy, createdAt)
DocumentLineItemTaxDetail
Per-line tax breakdown aligned with TaxBreakdownItem from LineItemTaxService:
shortName,shortCode– display labelstaxableUnitCode– currency or unittaxableAmount,taxableBaseAmount– amounts in major unitstaxAmount,taxBaseAmount– tax amounts in major units
LineDiscountDetail
Discount applied at line level:
unitPriceOriginal,unitPriceFinal– before/after discountsdiscountTotal– total discount amountpriceSource– e.g.price_list,manualdiscounts[]– per-discount breakdown (type, method, value, ruleId, etc.)
Restaurant Bundle / Combo Line Item Hierarchy
Restaurant combos introduce a parent/child order_item hierarchy and a structured order_item_modifier table. These sit alongside (not inside) DocumentLineItemShape — the shape applies per-line; the hierarchy links lines together.
order_item line types
line_type | Description |
|---|---|
product | Standard single-product line (default) |
bundle_parent | Synthetic header row for a combo; unitPriceSnapshot = 0, price lives on children |
bundle_child | One selected option inside a combo; linked to parent via parent_order_item_id |
Key columns on order_item:
parent_order_item_id— self-FK; set on everybundle_child;NULLonproductandbundle_parentrowsbundle_id— FK →bundle; set onbundle_parentandbundle_childrowsbundle_component_group_id— FK →bundle_component_group; set onbundle_childrows (nullable for legacy flat combos)line_type—order_item_line_typeenum;DEFAULT 'product'
order_item_modifier table
Structured per-item modifier records (replaces the legacy modifiers JSON blob for restaurant items):
| Column | Type | Notes |
|---|---|---|
order_item_id | uuid FK | Links to bundle_child order_item |
product_modifier_group_id | uuid FK (nullable) | Modifier group from catalog |
product_modifier_id | uuid FK (nullable) | Specific modifier from catalog |
quantity | numeric(20,6) | Default 1 |
unit_price_adjustment | money_minor | Per-unit price delta |
total_price_adjustment | money_minor | quantity × unit_price_adjustment |
name_snapshot | varchar(150) | Name at order time |
kitchen_label_snapshot | varchar(150) | KDS label at order time |
bundle_application lifecycle
bundle_application now tracks the full lifecycle via:
status—'applied'(default) or'removed'pricing_snapshot— JSON array of per-line price allocationsremoved_at,removed_by,removal_reason— audit fields set on removal
Service entry points
| Flow | Method | Module |
|---|---|---|
| New combo builder (group-based) | BundlesService.buildRestaurantBundle() | bundles |
| Legacy flat combo | OrdersService.addOrderCombo() | restaurant/orders |
| Remove combo | OrdersService.removeOrderBundle() | restaurant/orders |
Who Conforms
| Module | Adapter | When |
|---|---|---|
| Sale | Sale item DTO / response mapping | Always |
| Order | enrichOrderItemsWithTaxes output | On read |
| Quote | Quote item DTO / response mapping | On read |
Usage
- Backend: Use
LineItemTaxService.recomputeItemAmountAndTaxesorrecomputeTaxBreakdownFromGrossAmountto produce items conforming toDocumentLineItemShape. - Frontend:
ItemsTotals/calculateTotalsconsumes this shape to show subtotal, tax, and amount. - Tests: Use
DocumentLineItemShapein fixtures when asserting totals.
DocumentTotalsShape
Header totals for Order, Sale, Quote, Bill. Reusable across modules.
interface DocumentTotalsShape {
totalAmount: number; // gross, tax-inclusive
totalBaseAmount?: number;
subtotal?: number;
taxAmount?: number;
cartDiscountTotal?: number;
}
Validation: call LineItemTaxService.validateHeaderTotalMatchesLineTotals(items, headerTotal, { cartDiscountTotal }) before persist when client sends total.
Adding New Line Item Fields
Use this checklist when adding monetary or metadata fields to document line items. Update this list if new touchpoints are introduced.
1. Canonical Types (Backend)
| File | What to change |
|---|---|
apps/backend/src/document-calculations/domain/document-line-item.types.ts | Add field to DocumentLineItemShape |
apps/backend/src/document-calculations/application/line-item-tax.service.ts | Add to LineItemRecomputedResult and recomputeItemAmountAndTaxes if computed there |
2. Document Adapters (Backend)
| Module | File | Touchpoints |
|---|---|---|
| Sales | apps/backend/src/sales/application/sales.service.ts | applySaleBundle, addSaleCombo, removeSaleBundle, enrichSaleItemsWithDiscountFields, create/update item mapping |
| Quotes | apps/backend/src/quotes/application/quotes.service.ts | applyQuoteBundle, add-combo flow, remove flow, create/update item mapping, enrichment |
| Orders | apps/backend/src/restaurant/application/orders.service.ts | applyOrderBundle, addOrderCombo, removeOrderBundle, enrichOrderItemsWithTaxes |
3. Global / Shared Types
| File | What to change |
|---|---|
packages/global/types/bundle.types.ts | New interfaces (e.g. LineBundleDetail) |
packages/global/types/discount.types.ts | New interfaces for discount-related fields |
packages/global/enums/discount.enums.ts | New enum values (e.g. PriceSource.Bundle) |
4. Frontend Types (PWA)
| File | What to change |
|---|---|
apps/frontend-pwa/src/types/sale.ts | SaleItem (or sale_detail item shape) |
apps/frontend-pwa/src/types/quote.ts | QuoteDetailItem |
apps/frontend-pwa/src/types/restaurant.ts | RestaurantOrderItem |
apps/frontend-pwa/src/utils/calculations.ts | ItemWithTotal interface |
5. Frontend Display & Logic
| File | What to change |
|---|---|
apps/frontend-pwa/src/components/common/ItemsTotals.tsx | New totals row or field display if applicable |
apps/frontend-pwa/src/hooks/useItemCalculations.ts | If field affects tax/amount calculations |
| Form components (SaleForm, QuoteForm, etc.) | If field is editable or synced from API |
6. FEL (Electronic Invoicing)
| File | What to change |
|---|---|
apps/backend/src/fel/application/fel.service.ts | Item mapping to DTE/invoice format |
apps/backend/src/fel/domain/fel.interface.ts | InvoiceItem if field maps to FEL schema |
apps/backend/src/fel/application/xml-to-dte-json.service.ts | XML serialization if FEL schema changes |
7. Documentation
| File | What to change |
|---|---|
docs/architecture/document-line-item-shape.md | Contract section + this checklist |
docs/bundles/README.md | If bundle-related |
docs/fel/ | If FEL-related |
References
- ADR-002: Document Calculations Architecture
- Document Modules Checklist
LineItemTaxServiceindocument-calculations/application/