feat: consolidate monthly GL for loan interest accrual and demand#1328
feat: consolidate monthly GL for loan interest accrual and demand#1328Nihantra-Patel wants to merge 8 commits into
Conversation
Add optional month-end GL consolidation to collapse the per-day GL of Loan Interest Accrual and Loan Demand into one voucher per loan per month, gated by company fields loan_gl_consolidation + start date. - Defer per-doc GL (on submit and cancel) when consolidation is enabled; cancelled-after-consolidation docs are netted out via reversing deltas. - New Process Consolidated Loan GL doctype (one voucher per loan/month, posted on run date) with a Consolidated Loan GL Detail breakdown table showing source type, disbursement, account, amount and source doc count. - Consolidation is idempotent per month; reposts and backdated changes settle via deltas without rewriting closed months. Skip empty months. - Ledger view button; scheduler hook; repost reconsolidation trigger.
- Validate loan_gl_consolidation_start_date: must be first of a future month, after the last posted loan GL, so enabling the feature never leaves existing daily GL half-consolidated (no data migration needed). - Fix reconsolidate_gl: gate on whether the start date is set rather than on repost_date. A repost from before the start date still regenerates deferred docs in later months; those were being skipped and left with no GL. Now reconsolidate every month from max(repost_date, start) to today.
- Defer the per-accrual NPA suspense JE (income -> suspense) when consolidation is on, and post one consolidated suspense JE per loan per month. Uses a recompute-from-live approach (cancel the month's JE, repost the sum of live NPA accruals) so it stays correct and easy to debug after any cancel or repost. - Consolidation Details table: add Source Document link (populated when a line comes from a single accrual/demand), remove Party Type/Party, and count distinct source documents in # Docs instead of GL lines.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #1328 +/- ##
===========================================
+ Coverage 83.04% 83.25% +0.21%
===========================================
Files 157 161 +4
Lines 11268 11842 +574
===========================================
+ Hits 9357 9859 +502
- Misses 1911 1983 +72
🚀 New features to boost your workflow:
|
Confidence Score: 4/5Both prior defects are fixed; the remaining risk is the complexity of a new month-end GL consolidation path touching core accounting records. Both prior review findings are addressed: the synchronous repost loop is now enqueued and LoanDemand deferral correctly gates on demand_date. No new defects found. Score reflects the inherent risk of a large, novel accounting pathway that is difficult to fully validate through review alone. process_consolidated_loan_gl.py — build_consolidated_gl, flag_covered_docs, and sync_consolidated_suspense_je handle all accounting state transitions and warrant close QA attention. Reviews (4): Last reviewed commit: "fix: gate Loan Demand GL deferral on dem..." | Re-trigger Greptile |
Split company-wide GL consolidation into batches of 3000 loans, enqueued on the long queue, so it does not run thousands of loan submits in one job for companies with a large number of loans. Move the repost's month-by-month reconsolidation loop to a background job too, since it was creating one voucher per month synchronously inside the repost's on_submit. Also trim long comments down to short one-liners, and remove a test that had no assertions.
Per-iteration commits in the batch/month loops are intentional, so a later failure does not roll back earlier loans or months already processed in the same job.
Loan Interest Accrual's build_gl_map returns None (not an empty list) for accruals on or after a loan's write-off date. The consolidation job looped over that return value directly, so a written-off loan with deferred accruals under consolidation would crash with a TypeError. Guard with 'or []', matching Loan Demand's build_gl_map. Add tests for edge cases that had no automated coverage: non-NPA loan (no suspense JE), written-off loan (no GL after write-off), async GL reversal being superseded by consolidation, immutable ledger on (reversal via a forward entry, not in-place cancellation), NPA plus repost plus mid-month cancel together, and LOC loans with multiple disbursements for both accrual and demand.
posting_date is always set to today in validate(), while demand_date is the demand's own economic date and can be backdated. Deferral was gated on posting_date, but the consolidation queries (get_deferred_docs, loan_has_deferred_gl) scope by demand_date. A demand with demand_date before the consolidation start_date but created (posting_date) on or after it would defer with gl_posted=0, yet fall outside every consolidation query's date window -- stuck with no GL forever. Gate on demand_date instead, matching Loan Interest Accrual which already gates and queries on the same field. Add a regression test.
Problem
With
loan_accrual_frequency = Daily, every Loan Interest Accrual and Loan Demand posts its own GL entries every day. Daily accrual is still needed for NPA handling, penal interest, backdated transactions and accurate interest — but the GL footprint doesn't need to be daily.Solution
Decouple GL posting frequency from accrual frequency. Accruals and demands are still created and submitted daily, but their GL is deferred and rolled up into one consolidated voucher per loan per month.
On a real production-style test loan: GL rows went from ~4,076 to 72 (~98% reduction).
1. Company Configuration
Two custom fields on Company (via
install.py):loan_gl_consolidation(Check) — enables monthly GL consolidationloan_gl_consolidation_start_date(Date) — only accruals/demands on or after this date are consolidatedDisabled by default, no behavior change for existing companies. Validation in
overrides/company.pyrequires the start date to be the first day of a future month and after the last posted loan GL, so every consolidated month starts empty and no data migration is needed.2. Deferral of Per-Document GL
Modified
Loan Interest Accrual.make_gl_entriesandLoan Demand.make_gl_entries. GL dict generation moved into a reusablebuild_gl_map(). When consolidation is enabled, no GL is posted on submit or cancel, andgl_posted = 0. Cancelled documents are netted out later by the monthly job using reversing deltas.New fields on both doctypes:
gl_posted(whether GL has been posted) andconsolidated_gl_voucher(link to the consolidated voucher).3. New DocType: Process Consolidated Loan GL
One submittable voucher per loan per month, named
PCLGL-<loan>-<month_end>-#####.It aggregates deferred accruals and demands for the month, produces net GL rows grouped by account, and posts on the run date. It's idempotent (re-running posts only the delta), handles reposts and backdated changes, skips empty months, and never rewrites a month's GL — cancellations post reversing deltas instead. Fully compatible with immutable ledger. Standard Ledger button provided.
Child table Consolidated Loan GL Detail stores the breakdown per row: Source Type (Accrual/Demand), Source Document (only when exactly one doc contributes), Loan Disbursement (LOC dimension), Account, Debit, Credit, and # Docs.
4. NPA Suspense JE Consolidation
Per-accrual NPA suspense JEs are deferred and consolidated to one JE per loan per month. Each run cancels the month's existing suspense JE, recomputes from live accruals, and posts one fresh JE equal to that month's live NPA interest — self-correcting across cancels and reposts, no duplicates. The one-time transition JE when a loan first becomes NPA is unchanged.
5. Wiring
A month-end scheduler job (
hooks.py) self-gates and only runs on the last day of the month. After a repost, every affected month from the repost date (or consolidation start date) through today is automatically reconsolidated using deltas.Date Semantics
No existing posting semantics changed:
posting_datedemand_dateEdge Cases Verified
async_gl_reversal+ consolidationdemand_date, notposting_dateTests (
test_process_consolidated_loan_gl.py)async_gl_reversalsuperseded by consolidationdemand_date, notposting_dateKnown Limitation
Only accruals and demands on or after the configured start date participate in consolidation. Existing historical daily GL is intentionally not retroactively consolidated — enforced by the start-date validation, so consolidation always begins at a clean future boundary.