Skip to content

FINERACT-2455: Working Capital Loan - Write-Off - #6223

Open
alberto-art3ch wants to merge 2 commits into
apache:developfrom
openMF:FINERACT-2455/working-capital-loan-write-off
Open

FINERACT-2455: Working Capital Loan - Write-Off#6223
alberto-art3ch wants to merge 2 commits into
apache:developfrom
openMF:FINERACT-2455/working-capital-loan-write-off

Conversation

@alberto-art3ch

Copy link
Copy Markdown
Contributor

Description

Adds Write-Off and Undo Write-Off for Working Capital Loans, matching cumulative/progressive loans.

Write-off is terminal: zeroes outstanding balances and closes the loan as CLOSED_WRITTEN_OFF; undo reopens it to ACTIVE and restores the balance. Incoming parameters follow progressive loans.

Accounting (accrual): Dr LOSSES_WRITTEN_OFF (total) · Cr LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE per portion (no interest leg). Undo reverses via offsetting mirrors.

Includes: domain/state machine + dedicated write-off domain service, transaction/handlers/API/validator wiring, accounting WRITEOFF case, business events, DB migration 0063 (0062 reserved for the in-review Charge-Off PR), integration + E2E tests, and a fineract-doc chapter.

FINERACT-2455

Checklist

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Write the commit message as per our guidelines
  • Acknowledge that we will not review PRs that are not passing the build ("green") - it is your responsibility to get a proposed PR to pass the build, not primarily the project's maintainers.
  • Create/update unit or integration tests for verifying the changes made.
  • Follow our coding conventions.
  • Add required Swagger annotation and update API documentation at fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm with details of any API changes
  • This PR must not be a "code dump". Large changes can be made in a branch, with assistance. Ask for help on the developer mailing list.
  • If merging this PR resolves a JIRA issue, I will mark that issue as resolved and set "Fix Version/s" appropriately.

Your assigned reviewer(s) will follow our guidelines for code reviews.

@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch from 875df73 to 73e7c91 Compare August 4, 2026 02:33
@alberto-art3ch
alberto-art3ch marked this pull request as ready for review August 4, 2026 02:36
@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch 3 times, most recently from 6152194 to 246c7ad Compare August 4, 2026 14:36

@galovics galovics left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration and permission wiring are solid, WorkingCapitalLoanWriteOffDomainService is clean. One real gap:

// TODO(D3): confirm the backdating rule. The ticket says "not supporting backdated transactions"; progressive
// loans allow a floor at the last user transaction date. Add the agreed date constraint here once confirmed.

The validator only checks "not null" and "not in the future" for the write-off date - the actual backdating restriction the ticket calls for is admittedly unresolved and unimplemented. As it stands, a write-off can be backdated to any past date (before other transactions, before disbursement, whatever), and the write-off domain service doesn't guard against it either (zeroes outstanding unconditionally). For a terminal, balance-zeroing operation this seems worth nailing down before merge rather than after - self-acknowledged TODOs on the actual validation rule the PR is supposed to implement are the kind of thing I'd want resolved, not shipped.

@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch 5 times, most recently from e6282ca to d4e7c38 Compare August 5, 2026 03:20
@adamsaghy

Copy link
Copy Markdown
Contributor

@alberto-art3ch Please kindly see my concerns:

  1. Migration 0066 never inserts the permissions. Charge-off's parts/0063_wc_loan_charge_off.xml:70-88 inserts CHARGEOFF_WORKINGCAPITALLOAN / UNDOCHARGEOFF_WORKINGCAPITALLOAN into m_permission; 0066_wc_loan_write_off.xml inserts nothing.

Two consequences:

With the maker-checker global config enabled, ConfigurationDomainServiceJpa.java:54 does permissionRepository.findOneByCode("WRITEOFF_WORKINGCAPITALLOAN") and throws PermissionNotFoundException — every write-off call fails.
No role other than a super-user (ALL_FUNCTIONS) can ever be granted the permission.
The new doc chapter documents both permissions as if they exist. Add the two m_permission changesets mirroring 0063.

  1. writeOffReasonId vs progressive's writeoffReasonId. The AC says incoming parameters follow progressive loans. Progressive/cumulative use lower-case writeoffReasonId — LoanTransactionValidatorImpl.java:437 and LoanWritePlatformServiceJpaRepositoryImpl.java:1500. The PR introduces writeOffReasonId. Everything else in the set (transactionDate, note, locale, dateFormat, externalId) matches.

Also the field was not added to PostWorkingCapitalLoanTransactionsRequest in WorkingCapitalLoanTransactionsApiResourceSwagger.java:192-215, even though chargeOffReasonId is there (line 209). So the generated client can't send a write-off reason at all, which is also why no test exercises it. Same for reversalExternalId on undo — accepted by the validator, absent from the request model.

  1. Write-off of an already charged-off loan double-credits the receivables. Charge-off keeps the loan ACTIVE and validateWriteOff only requires ACTIVE, so write-off is reachable straight after a charge-off. chargeOffPostings already credits LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE for the full outstanding; the new writeOffPostings credits them again — the service hardcodes postJournalEntries(..., false) and the WRITEOFF case ignores isChargedOff entirely. Regular loans branch explicitly at AccrualBasedAccountingProcessorForLoan.java:1379 into createJournalEntriesForWriteOffsWhenLoanIsChargedOff (Dr CHARGE_OFF_EXPENSE / INCOME_FROM_CHARGE_OFF_FEES / _PENALTY, Cr FUND_SOURCE, with CHARGE_OFF_FRAUD_EXPENSE when the loan is flagged fraud — and m_wc_loan.is_fraud exists here too).

Let's replicate the implementation from the progressive loan handling.

  1. The "post-write-off lock" NOTE in the doc is wrong. The chapter says the lock is "pending confirmation and not yet enforced". It's actually already enforced by pre-existing status gates: repayment/goodwill/payout-refund via REPAYMENT_LIKE_TXN_ALLOWED_LOAN_STATUSES (WorkingCapitalLoanDataValidator.java:136), transaction undo via validateUndoTransaction (line 958), charges via WorkingCapitalLoanChargeWritePlatformServiceImpl.java:398, charge-off and undo-disbursal via their own ACTIVE checks. So the AC is met — but nothing pins it. Drop the NOTE and add a test or two asserting a repayment and a transaction-undo are rejected on a written-off loan.

  2. No status-changed / balance-changed events, no delinquency reset. Regular write-off emits LoanBalanceChangedBusinessEvent and calls setLoanDelinquencyTag; the WC repayment path emits WorkingCapitalLoanBalanceChangedBusinessEvent and calls delinquencyRangeScheduleService.reprocessDelinquencySchedule. The write-off service emits only the two new transaction events. Since CLOSED_WRITTEN_OFF is excluded from COB (WorkingCapitalLoanRetrieveIdServiceImpl NON_CLOSED_LOAN_STATUSES), a delinquent loan stays tagged delinquent forever after write-off, and undo never re-derives it.

  3. Null balance is silently tolerated. zeroOutstanding returns early when balance == null and the service defaults all portions to zero — the loan closes as written-off with a zero-amount transaction. Charge-off deliberately throws error.msg.wc.loan.balance.not.found for exactly this case. Make it consistent.

@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch from d4e7c38 to 056ce10 Compare August 6, 2026 20:24
@alberto-art3ch

Copy link
Copy Markdown
Contributor Author

@alberto-art3ch Please kindly see my concerns:

  1. Migration 0066 never inserts the permissions. Charge-off's parts/0063_wc_loan_charge_off.xml:70-88 inserts CHARGEOFF_WORKINGCAPITALLOAN / UNDOCHARGEOFF_WORKINGCAPITALLOAN into m_permission; 0066_wc_loan_write_off.xml inserts nothing.

Two consequences:

With the maker-checker global config enabled, ConfigurationDomainServiceJpa.java:54 does permissionRepository.findOneByCode("WRITEOFF_WORKINGCAPITALLOAN") and throws PermissionNotFoundException — every write-off call fails. No role other than a super-user (ALL_FUNCTIONS) can ever be granted the permission. The new doc chapter documents both permissions as if they exist. Add the two m_permission changesets mirroring 0063.

  1. writeOffReasonId vs progressive's writeoffReasonId. The AC says incoming parameters follow progressive loans. Progressive/cumulative use lower-case writeoffReasonId — LoanTransactionValidatorImpl.java:437 and LoanWritePlatformServiceJpaRepositoryImpl.java:1500. The PR introduces writeOffReasonId. Everything else in the set (transactionDate, note, locale, dateFormat, externalId) matches.

Also the field was not added to PostWorkingCapitalLoanTransactionsRequest in WorkingCapitalLoanTransactionsApiResourceSwagger.java:192-215, even though chargeOffReasonId is there (line 209). So the generated client can't send a write-off reason at all, which is also why no test exercises it. Same for reversalExternalId on undo — accepted by the validator, absent from the request model.

  1. Write-off of an already charged-off loan double-credits the receivables. Charge-off keeps the loan ACTIVE and validateWriteOff only requires ACTIVE, so write-off is reachable straight after a charge-off. chargeOffPostings already credits LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE for the full outstanding; the new writeOffPostings credits them again — the service hardcodes postJournalEntries(..., false) and the WRITEOFF case ignores isChargedOff entirely. Regular loans branch explicitly at AccrualBasedAccountingProcessorForLoan.java:1379 into createJournalEntriesForWriteOffsWhenLoanIsChargedOff (Dr CHARGE_OFF_EXPENSE / INCOME_FROM_CHARGE_OFF_FEES / _PENALTY, Cr FUND_SOURCE, with CHARGE_OFF_FRAUD_EXPENSE when the loan is flagged fraud — and m_wc_loan.is_fraud exists here too).

Let's replicate the implementation from the progressive loan handling.

  1. The "post-write-off lock" NOTE in the doc is wrong. The chapter says the lock is "pending confirmation and not yet enforced". It's actually already enforced by pre-existing status gates: repayment/goodwill/payout-refund via REPAYMENT_LIKE_TXN_ALLOWED_LOAN_STATUSES (WorkingCapitalLoanDataValidator.java:136), transaction undo via validateUndoTransaction (line 958), charges via WorkingCapitalLoanChargeWritePlatformServiceImpl.java:398, charge-off and undo-disbursal via their own ACTIVE checks. So the AC is met — but nothing pins it. Drop the NOTE and add a test or two asserting a repayment and a transaction-undo are rejected on a written-off loan.
  2. No status-changed / balance-changed events, no delinquency reset. Regular write-off emits LoanBalanceChangedBusinessEvent and calls setLoanDelinquencyTag; the WC repayment path emits WorkingCapitalLoanBalanceChangedBusinessEvent and calls delinquencyRangeScheduleService.reprocessDelinquencySchedule. The write-off service emits only the two new transaction events. Since CLOSED_WRITTEN_OFF is excluded from COB (WorkingCapitalLoanRetrieveIdServiceImpl NON_CLOSED_LOAN_STATUSES), a delinquent loan stays tagged delinquent forever after write-off, and undo never re-derives it.
  3. Null balance is silently tolerated. zeroOutstanding returns early when balance == null and the service defaults all portions to zero — the loan closes as written-off with a zero-amount transaction. Charge-off deliberately throws error.msg.wc.loan.balance.not.found for exactly this case. Make it consistent.

@adamsaghy all the points are addressed:

  1. Permissions — 0066 now inserts WRITEOFF_WORKINGCAPITALLOAN and UNDOWRITEOFF_WORKINGCAPITALLOAN, mirroring 0063.
  2. Naming — renamed to writeoffReasonId to match progressive, and both it and reversalExternalId are now in PostWorkingCapitalLoanTransactionsRequest (the latter was missing for undoChargeOff too). Unit tests pin the parameter name; an IT sends the reason and asserts it round-trips.
  3. Charged-off write-off — the service now passes loan.isChargedOff(), and in that case writeOffPostings books nothing: charge-off already credited LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE for the same portions, so re-crediting drove those assets negative and booked the loss twice.
  4. Doc — the NOTE is gone, replaced by the actual gate table; tests now assert a repayment and a transaction undo are rejected on a written-off loan, and allowed again after the undo write-off.
  5. Events / delinquency — both paths reprocess the delinquency schedule and publish BalanceChanged and StatusChanged alongside the transaction event.
  6. Null balance — now throws error.msg.wc.loan.balance.not.found on write-off and undo, same as charge-off.

One open question on 3 before I align any further with core: createJournalEntriesForWriteOffsWhenLoanIsChargedOff passes CHARGE_OFF_EXPENSE as the credit and FUND_SOURCE as the debit (populateCreditDebitMaps(.., creditAccountType, debitAccountType, ..), AccrualBasedAccountingProcessorForLoan.java:1640-1647) — the opposite direction to the one described in your comment. Net of the charge-off, that reads as "loan settled in cash, zero loss recognised". Booking nothing seemed the safer treatment, since the receivables are already off the books and the loss is already in P&L. Happy to switch to core's version verbatim if you prefer the consistency.

@adamsaghy

Copy link
Copy Markdown
Contributor

@alberto-art3ch Please kindly see my concerns:

  1. Migration 0066 never inserts the permissions. Charge-off's parts/0063_wc_loan_charge_off.xml:70-88 inserts CHARGEOFF_WORKINGCAPITALLOAN / UNDOCHARGEOFF_WORKINGCAPITALLOAN into m_permission; 0066_wc_loan_write_off.xml inserts nothing.

Two consequences:
With the maker-checker global config enabled, ConfigurationDomainServiceJpa.java:54 does permissionRepository.findOneByCode("WRITEOFF_WORKINGCAPITALLOAN") and throws PermissionNotFoundException — every write-off call fails. No role other than a super-user (ALL_FUNCTIONS) can ever be granted the permission. The new doc chapter documents both permissions as if they exist. Add the two m_permission changesets mirroring 0063.

  1. writeOffReasonId vs progressive's writeoffReasonId. The AC says incoming parameters follow progressive loans. Progressive/cumulative use lower-case writeoffReasonId — LoanTransactionValidatorImpl.java:437 and LoanWritePlatformServiceJpaRepositoryImpl.java:1500. The PR introduces writeOffReasonId. Everything else in the set (transactionDate, note, locale, dateFormat, externalId) matches.

Also the field was not added to PostWorkingCapitalLoanTransactionsRequest in WorkingCapitalLoanTransactionsApiResourceSwagger.java:192-215, even though chargeOffReasonId is there (line 209). So the generated client can't send a write-off reason at all, which is also why no test exercises it. Same for reversalExternalId on undo — accepted by the validator, absent from the request model.

  1. Write-off of an already charged-off loan double-credits the receivables. Charge-off keeps the loan ACTIVE and validateWriteOff only requires ACTIVE, so write-off is reachable straight after a charge-off. chargeOffPostings already credits LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE for the full outstanding; the new writeOffPostings credits them again — the service hardcodes postJournalEntries(..., false) and the WRITEOFF case ignores isChargedOff entirely. Regular loans branch explicitly at AccrualBasedAccountingProcessorForLoan.java:1379 into createJournalEntriesForWriteOffsWhenLoanIsChargedOff (Dr CHARGE_OFF_EXPENSE / INCOME_FROM_CHARGE_OFF_FEES / _PENALTY, Cr FUND_SOURCE, with CHARGE_OFF_FRAUD_EXPENSE when the loan is flagged fraud — and m_wc_loan.is_fraud exists here too).

Let's replicate the implementation from the progressive loan handling.

  1. The "post-write-off lock" NOTE in the doc is wrong. The chapter says the lock is "pending confirmation and not yet enforced". It's actually already enforced by pre-existing status gates: repayment/goodwill/payout-refund via REPAYMENT_LIKE_TXN_ALLOWED_LOAN_STATUSES (WorkingCapitalLoanDataValidator.java:136), transaction undo via validateUndoTransaction (line 958), charges via WorkingCapitalLoanChargeWritePlatformServiceImpl.java:398, charge-off and undo-disbursal via their own ACTIVE checks. So the AC is met — but nothing pins it. Drop the NOTE and add a test or two asserting a repayment and a transaction-undo are rejected on a written-off loan.
  2. No status-changed / balance-changed events, no delinquency reset. Regular write-off emits LoanBalanceChangedBusinessEvent and calls setLoanDelinquencyTag; the WC repayment path emits WorkingCapitalLoanBalanceChangedBusinessEvent and calls delinquencyRangeScheduleService.reprocessDelinquencySchedule. The write-off service emits only the two new transaction events. Since CLOSED_WRITTEN_OFF is excluded from COB (WorkingCapitalLoanRetrieveIdServiceImpl NON_CLOSED_LOAN_STATUSES), a delinquent loan stays tagged delinquent forever after write-off, and undo never re-derives it.
  3. Null balance is silently tolerated. zeroOutstanding returns early when balance == null and the service defaults all portions to zero — the loan closes as written-off with a zero-amount transaction. Charge-off deliberately throws error.msg.wc.loan.balance.not.found for exactly this case. Make it consistent.

@adamsaghy all the points are addressed:

  1. Permissions — 0066 now inserts WRITEOFF_WORKINGCAPITALLOAN and UNDOWRITEOFF_WORKINGCAPITALLOAN, mirroring 0063.
  2. Naming — renamed to writeoffReasonId to match progressive, and both it and reversalExternalId are now in PostWorkingCapitalLoanTransactionsRequest (the latter was missing for undoChargeOff too). Unit tests pin the parameter name; an IT sends the reason and asserts it round-trips.
  3. Charged-off write-off — the service now passes loan.isChargedOff(), and in that case writeOffPostings books nothing: charge-off already credited LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE for the same portions, so re-crediting drove those assets negative and booked the loss twice.
  4. Doc — the NOTE is gone, replaced by the actual gate table; tests now assert a repayment and a transaction undo are rejected on a written-off loan, and allowed again after the undo write-off.
  5. Events / delinquency — both paths reprocess the delinquency schedule and publish BalanceChanged and StatusChanged alongside the transaction event.
  6. Null balance — now throws error.msg.wc.loan.balance.not.found on write-off and undo, same as charge-off.

One open question on 3 before I align any further with core: createJournalEntriesForWriteOffsWhenLoanIsChargedOff passes CHARGE_OFF_EXPENSE as the credit and FUND_SOURCE as the debit (populateCreditDebitMaps(.., creditAccountType, debitAccountType, ..), AccrualBasedAccountingProcessorForLoan.java:1640-1647) — the opposite direction to the one described in your comment. Net of the charge-off, that reads as "loan settled in cash, zero loss recognised". Booking nothing seemed the safer treatment, since the receivables are already off the books and the loss is already in P&L. Happy to switch to core's version verbatim if you prefer the consistency.

@alberto-art3ch Thank you for the quick turnaround.

Regarding the accounting in case the loan was already charged-off:

  • We need to move from Charge-off GL accounts to Write-off accounts the rest of the balances.

Progressive loan implementation:
org.apache.fineract.accounting.journalentry.service.AccrualBasedAccountingProcessorForLoan#createJournalEntriesForWriteOffsWhenLoanIsChargedOff

We are looking for the same for Working Capital

@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch from 056ce10 to 3a4ab1f Compare August 8, 2026 00:01
@alberto-art3ch

Copy link
Copy Markdown
Contributor Author

@alberto-art3ch Please kindly see my concerns:

  1. Migration 0066 never inserts the permissions. Charge-off's parts/0063_wc_loan_charge_off.xml:70-88 inserts CHARGEOFF_WORKINGCAPITALLOAN / UNDOCHARGEOFF_WORKINGCAPITALLOAN into m_permission; 0066_wc_loan_write_off.xml inserts nothing.

Two consequences:
With the maker-checker global config enabled, ConfigurationDomainServiceJpa.java:54 does permissionRepository.findOneByCode("WRITEOFF_WORKINGCAPITALLOAN") and throws PermissionNotFoundException — every write-off call fails. No role other than a super-user (ALL_FUNCTIONS) can ever be granted the permission. The new doc chapter documents both permissions as if they exist. Add the two m_permission changesets mirroring 0063.

  1. writeOffReasonId vs progressive's writeoffReasonId. The AC says incoming parameters follow progressive loans. Progressive/cumulative use lower-case writeoffReasonId — LoanTransactionValidatorImpl.java:437 and LoanWritePlatformServiceJpaRepositoryImpl.java:1500. The PR introduces writeOffReasonId. Everything else in the set (transactionDate, note, locale, dateFormat, externalId) matches.

Also the field was not added to PostWorkingCapitalLoanTransactionsRequest in WorkingCapitalLoanTransactionsApiResourceSwagger.java:192-215, even though chargeOffReasonId is there (line 209). So the generated client can't send a write-off reason at all, which is also why no test exercises it. Same for reversalExternalId on undo — accepted by the validator, absent from the request model.

  1. Write-off of an already charged-off loan double-credits the receivables. Charge-off keeps the loan ACTIVE and validateWriteOff only requires ACTIVE, so write-off is reachable straight after a charge-off. chargeOffPostings already credits LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE for the full outstanding; the new writeOffPostings credits them again — the service hardcodes postJournalEntries(..., false) and the WRITEOFF case ignores isChargedOff entirely. Regular loans branch explicitly at AccrualBasedAccountingProcessorForLoan.java:1379 into createJournalEntriesForWriteOffsWhenLoanIsChargedOff (Dr CHARGE_OFF_EXPENSE / INCOME_FROM_CHARGE_OFF_FEES / _PENALTY, Cr FUND_SOURCE, with CHARGE_OFF_FRAUD_EXPENSE when the loan is flagged fraud — and m_wc_loan.is_fraud exists here too).

Let's replicate the implementation from the progressive loan handling.

  1. The "post-write-off lock" NOTE in the doc is wrong. The chapter says the lock is "pending confirmation and not yet enforced". It's actually already enforced by pre-existing status gates: repayment/goodwill/payout-refund via REPAYMENT_LIKE_TXN_ALLOWED_LOAN_STATUSES (WorkingCapitalLoanDataValidator.java:136), transaction undo via validateUndoTransaction (line 958), charges via WorkingCapitalLoanChargeWritePlatformServiceImpl.java:398, charge-off and undo-disbursal via their own ACTIVE checks. So the AC is met — but nothing pins it. Drop the NOTE and add a test or two asserting a repayment and a transaction-undo are rejected on a written-off loan.
  2. No status-changed / balance-changed events, no delinquency reset. Regular write-off emits LoanBalanceChangedBusinessEvent and calls setLoanDelinquencyTag; the WC repayment path emits WorkingCapitalLoanBalanceChangedBusinessEvent and calls delinquencyRangeScheduleService.reprocessDelinquencySchedule. The write-off service emits only the two new transaction events. Since CLOSED_WRITTEN_OFF is excluded from COB (WorkingCapitalLoanRetrieveIdServiceImpl NON_CLOSED_LOAN_STATUSES), a delinquent loan stays tagged delinquent forever after write-off, and undo never re-derives it.
  3. Null balance is silently tolerated. zeroOutstanding returns early when balance == null and the service defaults all portions to zero — the loan closes as written-off with a zero-amount transaction. Charge-off deliberately throws error.msg.wc.loan.balance.not.found for exactly this case. Make it consistent.

@adamsaghy all the points are addressed:

  1. Permissions — 0066 now inserts WRITEOFF_WORKINGCAPITALLOAN and UNDOWRITEOFF_WORKINGCAPITALLOAN, mirroring 0063.
  2. Naming — renamed to writeoffReasonId to match progressive, and both it and reversalExternalId are now in PostWorkingCapitalLoanTransactionsRequest (the latter was missing for undoChargeOff too). Unit tests pin the parameter name; an IT sends the reason and asserts it round-trips.
  3. Charged-off write-off — the service now passes loan.isChargedOff(), and in that case writeOffPostings books nothing: charge-off already credited LOAN_PORTFOLIO / FEES_RECEIVABLE / PENALTIES_RECEIVABLE for the same portions, so re-crediting drove those assets negative and booked the loss twice.
  4. Doc — the NOTE is gone, replaced by the actual gate table; tests now assert a repayment and a transaction undo are rejected on a written-off loan, and allowed again after the undo write-off.
  5. Events / delinquency — both paths reprocess the delinquency schedule and publish BalanceChanged and StatusChanged alongside the transaction event.
  6. Null balance — now throws error.msg.wc.loan.balance.not.found on write-off and undo, same as charge-off.

One open question on 3 before I align any further with core: createJournalEntriesForWriteOffsWhenLoanIsChargedOff passes CHARGE_OFF_EXPENSE as the credit and FUND_SOURCE as the debit (populateCreditDebitMaps(.., creditAccountType, debitAccountType, ..), AccrualBasedAccountingProcessorForLoan.java:1640-1647) — the opposite direction to the one described in your comment. Net of the charge-off, that reads as "loan settled in cash, zero loss recognised". Booking nothing seemed the safer treatment, since the receivables are already off the books and the loss is already in P&L. Happy to switch to core's version verbatim if you prefer the consistency.

@alberto-art3ch Thank you for the quick turnaround.

Regarding the accounting in case the loan was already charged-off:

  • We need to move from Charge-off GL accounts to Write-off accounts the rest of the balances.

Progressive loan implementation: org.apache.fineract.accounting.journalentry.service.AccrualBasedAccountingProcessorForLoan#createJournalEntriesForWriteOffsWhenLoanIsChargedOff

We are looking for the same for Working Capital

@adamsaghy Done! I've updated the accounting to be moved in the case of the Write-Off will be applied after the Charge-Off. Thanks!

@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch from 3a4ab1f to 3cab518 Compare August 11, 2026 22:21
@MarianaDmytrivBinariks
MarianaDmytrivBinariks force-pushed the FINERACT-2455/working-capital-loan-write-off branch from 3cab518 to 1e4bc36 Compare August 12, 2026 10:57
@adamsaghy

Copy link
Copy Markdown
Contributor

@alberto-art3ch Please rebase

@MarianaDmytrivBinariks
MarianaDmytrivBinariks force-pushed the FINERACT-2455/working-capital-loan-write-off branch from 1e4bc36 to 4b0f5fb Compare August 12, 2026 11:00
@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch 4 times, most recently from a2f3abd to b978ac4 Compare August 13, 2026 03:52
@alberto-art3ch
alberto-art3ch force-pushed the FINERACT-2455/working-capital-loan-write-off branch from b978ac4 to 5d32845 Compare August 14, 2026 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants