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 × unitsPerPack
for 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
freeQtyInUnits
defined 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 + AllocBillDiscount
TotalTax = LineTax + AllocBillTax
TotalExpense = LineExpense + AllocBillExpense
GrossTotal = LineGrossTotal
NetTotal = 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
PatientInvestigation
per 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
unitsPerPack
is 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 = Σ
LineNetTotal
for all lines (exclude free qty). -
Allocate
billDiscount
,billTax
, andbillExpensesIncluded
to 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
unitsPerPack
to 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
/purchaseRate
as 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
qty
on 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.
unitsPerPack
null for AMPP.- Negative or zero
qty
for 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.