Costing Logic ‐ Triggers, Order of Operations, and Actions - hmislk/hmis GitHub Wiki
Costing Logic: Triggers, Order of Operations, and Actions
A. Notation & Entities
- Bill →
Bill, details inBillFinanceDetails(BFD) - Line →
BillItem, details inBillItemFinanceDetails(BIFD) - Pharma line snapshot →
PharmaceuticalBillItem(PBI) withunitsPerPack - “QtyInUnits” means
qty × unitsPerPackfor AMPP; for AMP it equalsqty.
B. Core Formulas (used by multiple steps)
-
LineGrossRate =
purchaseRate -
LineNetRate =
purchaseRate + lineTaxRate + lineExpenseRate – lineDiscountRate -
LineGrossTotal =
purchaseRate × qty -
LineDiscount =
lineDiscountRate × qty -
LineTax =
lineTaxRate × qty -
LineExpense =
lineExpenseRate × qty -
LineNetTotal =
LineGrossTotal + LineTax + LineExpense – LineDiscount -
QtyInUnits =
- AMPP:
qty × unitsPerPack - AMP:
qty freeQtyInUnitsdefined likewise
- AMPP:
-
LineCostRate (per unit) =
(LineNetTotal ÷ (QtyInUnits + FreeQtyInUnits)) -
Allocation Base =
LineNetTotal(free quantities excluded from base) -
Allocated amounts per line =
(LineNetTotal ÷ Σ LineNetTotal of the bill) × (bill‑level amount) -
Final per-line totals
TotalDiscount = LineDiscount + AllocBillDiscountTotalTax = LineTax + AllocBillTaxTotalExpense = LineExpense + AllocBillExpenseGrossTotal = LineGrossTotalNetTotal = LineNetTotal + (AllocBillTax + AllocBillExpense – AllocBillDiscount)
- Per-line rates (display) = corresponding totals ÷
qty(not including free). - Stock Valuation CostRate =
(Σ(LineNetTotal) + Σ(AllocBillTax + AllocBillExpense – AllocBillDiscount)) ÷ (Σ(QtyInUnits + FreeQtyInUnits)).
Rounding: compute with high‑precision decimal; round to currency at the last step of each level (line totals; then bill totals). Distribute any allocation rounding remainder by largest fractional part.
C. Global Invariants (validate on every save/approve)
- Exactly one PBI per pharmacy
BillItem; error if 0 or >1. - For lab/collection items: exactly one
PatientInvestigationper relevantBillItem. - Snapshot values (
unitsPerPack, tax regime, inclusive/exclusive flags) must exist on BIFD/PBI. - Signs: in‑stock = +qty; out‑stock = –qty; expenditure = negative value; income = positive.
- Idempotence: Re-running calculations with the same inputs must produce identical BIFD/BFD.
D. Event Matrix — “What to do when …”
D1) User changes a line‑level input
Inputs: qty, freeQty, purchaseRate, lineDiscountRate, lineTaxRate, lineExpenseRate, AMPP↔AMP selection.
Actions (in order):
-
Ensure PBI exists (pharmacy) and snapshot
unitsPerPackis present; refreshQtyInUnits/freeQtyInUnits. -
Recompute line calculations using Section B (1–9). Write to BIFD.
-
Do NOT recalculate or redistribute bill‑level allocations yet (they depend on all lines).
-
Recompute bill aggregates driven by lines in BFD (Σ LineGrossTotal, Σ LineNetTotal, etc.).
-
If any bill‑level input (discount/tax/expense) is non‑zero, go to D3 to reallocate; else stop.
- Rationale: allocation base changed with this line; allocations must be refreshed.
D2) User deletes a line
Actions:
- Remove/retire BIFD and associated PBI/PI as per domain rule.
- Recompute bill aggregates (Σ across remaining lines).
- Re‑run allocation across remaining lines (D3).
- Recompute final line totals and BFD.
D3) User changes a bill‑level input
Inputs: billDiscount, billTax, billExpensesIncluded, billExpensesExcluded, or tax inclusive/exclusive flag.
Actions (in order):
-
Persist bill‑level input(s) to
Bill/BFD; confirm policy flags snapshot onBill. -
Ensure all lines have up‑to‑date LineNetTotal (if not, recompute D1 for affected lines).
-
Compute Allocation Base = Σ
LineNetTotalfor all lines (exclude free qty). -
Allocate
billDiscount,billTax, andbillExpensesIncludedto each line:alloc = baseShare × billAmount(Section B‑11), round and reconcile remainder.- Write to BIFD fields (allocated discount/tax/expense).
-
For each line, recompute Final Item Totals and per‑line rates (Section B‑12, B‑13).
-
Update BFD Net Total using line totals (no circular re‑sum of allocations back to bill inputs).
-
Recompute Stock Valuation CostRate (Section B‑14) if this bill affects inventory valuation.
D4) User toggles AMP ↔ AMPP or changes unitsPerPack
Actions:
- Snapshot
unitsPerPackto BIFD/PBI. - Recompute
QtyInUnits/freeQtyInUnits. - Recompute line calculations (B 1–9).
- Recompute bill aggregates; then reallocate bill‑level values (D3).
- Update final line totals and BFD.
D5) User changes inclusive/exclusive tax policy on the bill
Actions:
- Persist policy flag on
Bill. - Reinterpret
lineTaxRate/purchaseRateas per policy, recompute all lines (B 1–9). - Recompute bill aggregates → reallocate (D3) → finalize.
D6) Approving/Posting the bill (finalize)
Pre‑checks:
- All invariants hold (one PBI/PI per line, non‑null snapshots, no negative
qtyon procurement, etc.). - Allocations exist and sum to bill inputs within rounding tolerance. Actions:
- Lock bill; re-run full compute path (lines → bill aggregates → allocations → totals).
- Persist final BIFD and BFD with policy/version stamp and rounding notes.
- Post inventory: increase stock by
(QtyInUnits + FreeQtyInUnits)at CostRate. - Post GL entries according to mapping (inventory, creditors/cash, taxes, discounts, capitalized expenses).
D7) Returns/Credit Notes for a received bill
Actions:
- Identify original line(s) and use their historical CostRate and historical allocation proportions.
- Create return line(s) with negative quantities; reverse both line components and their allocated bill components proportionally.
- Recompute bill aggregates of the return document; do not recalc original bill.
- Post inventory decrease at historical CostRate; post GL reversals.
D8) Donations / zero‑price receipts
Actions:
- If policy = zero‑cost: set CostRate = 0; still compute quantities.
- If policy = fair‑value capitalization: set valuation amount; fill BIFD “valuation source”; CostRate = valuation ÷ (QtyInUnits + FreeQtyInUnits).
- Post inventory accordingly; no creditors.
D9) Editing a historical bill
Actions:
- Disallow edits after posting unless a revision document pattern is used (debit/credit notes).
- If edit is permitted: recalc using the original policy/version snapshot stored on the bill to avoid reinterpretation with new rules.
- Recompute and re‑post differences only.
E. Save‑Time Validation Checklist (fail fast)
- Missing or multiple PBI/PI for lines that require them.
unitsPerPacknull for AMPP.- Negative or zero
qtyfor procurement (except returns). - Allocation remainder not reconciled to zero at bill level.
- Sum of allocated components ≠ bill inputs (beyond rounding tolerance).
- Stock valuation recomputed and consistent with line totals.
F. Suggested Function Boundaries (idempotent)
recalculateLine(BillItem bi)→ updates BIFD for that line (B 1–9)recalculateBillAggregates(Bill bill)→ writes BFD Σ of line‑driven valuesallocateBillValuesToLines(Bill bill)→ writes allocated amounts to each BIFDrecalculateLineFinalTotals(BillItem bi)→ applies Section B‑12/13recalculateStockValuation(Bill bill)→ computes Section B‑14 for inventory postingvalidateBill(Bill bill)→ runs invariant and rounding checksfinalizeAndPost(Bill bill)→ D6 sequence (locks, recompute, persist, post)
Each function must be a pure function of persisted inputs; no hidden state. Calling the whole pipeline twice must yield identical numbers.