Loan Write-Off Transaction
Overview
A Loan Write-Off is a transaction where a bank removes a non-performing loan from its active loan portfolio and recognizes it as a bad debt loss. This is a critical accounting transaction that reflects the bank's recognition that the loan is uncollectible, though it does not legally release the borrower from the debt obligation.
Key Characteristics
- Purpose: Remove uncollectible loans from active portfolio and recognize loss
- Transaction Type:
WROF(Write-Off) - Complexity: High - Regulatory requirements, provisioning, GL adjustments
- Multi-Entity Impact: LoanAccount, all LoanSchedules, GL accounts (provision, write-off expense)
- Critical Requirement: Accurate provisioning and regulatory compliance
Write-Off vs Loan Closure
| Aspect | Write-Off | Payoff/Closure |
|---|---|---|
| Borrower Obligation | Still legally liable (can pursue collection) | Fully discharged |
| Loan State | WRITTEN_OFF (special state) | CLOSED |
| Payment Source | Bank provisions/reserves | Customer payment |
| GL Impact | Expense recognition (loss) | Asset reduction |
| Regulatory | NPL removal, capital impact | Normal closure |
| Collection | Can continue post write-off | No further collection |
Critical: A write-off is an accounting transaction, not a legal forgiveness. The bank can still pursue collection, and if recovered, the amount is recognized as recovery income.
Transaction Flow
Entities Impacted
Scenario 1: Fully Provisioned Write-Off
Context:
- Loan: NGN 2,000,000 principal, 22% annual interest
- Days Past Due: 365 days (1 year overdue)
- Outstanding Principal: NGN 1,800,000 (10% paid before default)
- Accrued Interest: NGN 396,000 (unpaid)
- Unpaid Penalties: NGN 45,000 (arrears penalties)
- Unpaid Fees: NGN 12,000 (late payment fees)
- Provision Balance: NGN 2,253,000 (100% provisioned)
- Collection Efforts: Exhausted (12 months, no recovery)
Write-Off Calculation:
WriteOffAmount = 1,800,000 (principal)
+ 396,000 (interest)
+ 45,000 (penalties)
+ 12,000 (fees)
= 2,253,000
ProvisionUsed = 2,253,000 (100% covered)
AdditionalExpense = 0 (fully provisioned)
```text
**Entities Impacted** (22 ImpactedEntity records):
```typescript
{
transactionId: "WROF-001",
transactionType: "Loan Write-Off",
writeOffAmount: 2253000.00,
writeOffDate: "2025-12-28",
writeOffReason: "Non-performing loan, 365 days overdue, collection efforts exhausted",
impactedEntities: [
// 1-8: LoanSchedule impacts (8 outstanding schedules)
// Schedule 29 (example)
{
entityType: "LoanSchedule",
entityId: 329,
entityKey: "SCH-329",
fieldName: "PrincipalDue",
oldValue: 225000.00,
newValue: 225000.00, // Unchanged (still due legally)
deltaAmount: 0
},
{
entityType: "LoanSchedule",
entityId: 329,
fieldName: "OutstandingBalance",
oldValue: 225000.00,
newValue: 225000.00, // Tracked for collection
deltaAmount: 0
},
{
entityType: "LoanSchedule",
entityId: 329,
fieldName: "State",
oldValue: "OVERDUE",
newValue: "WRITTEN_OFF",
deltaAmount: 0
},
{
entityType: "LoanSchedule",
entityId: 329,
fieldName: "WriteOffDate",
oldValue: null,
newValue: "2025-12-28",
deltaAmount: 0
},
// ... (7 more schedules, each with 4 field changes = 28 records)
// 9-16: LoanAccount impacts
{
entityType: "LoanAccount",
entityId: 102,
entityKey: "LOAN-102",
fieldName: "PrincipalBalance",
oldValue: 1800000.00,
newValue: 0.00, // Removed from active portfolio
deltaAmount: -1800000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "InterestBalance",
oldValue: 396000.00,
newValue: 0.00,
deltaAmount: -396000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "PenaltyBalance",
oldValue: 45000.00,
newValue: 0.00,
deltaAmount: -45000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "FeesBalance",
oldValue: 12000.00,
newValue: 0.00,
deltaAmount: -12000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "WriteOffAmount",
oldValue: 0.00,
newValue: 2253000.00,
deltaAmount: +2253000.00
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "WriteOffDate",
oldValue: null,
newValue: "2025-12-28",
deltaAmount: 0
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "LoanState",
oldValue: "OVERDUE",
newValue: "WRITTEN_OFF",
deltaAmount: 0
},
{
entityType: "LoanAccount",
entityId: 102,
fieldName: "LastCollectionAttemptDate",
oldValue: "2025-12-15",
newValue: "2025-12-15",
deltaAmount: 0
},
// 17-22: GL Account impacts
{
entityType: "GLAccount",
entityKey: "1108-PROVISION-FOR-LOAN-LOSSES",
fieldName: "DebitAmount",
deltaAmount: +2253000.00 // DR: Use provision
},
{
entityType: "GLAccount",
entityKey: "1101-LOANS-TO-CUSTOMERS",
fieldName: "CreditAmount",
deltaAmount: +1800000.00 // CR: Remove loan asset
},
{
entityType: "GLAccount",
entityKey: "1105-INTEREST-RECEIVABLE",
fieldName: "CreditAmount",
deltaAmount: +396000.00 // CR: Write off interest
},
{
entityType: "GLAccount",
entityKey: "1106-FEES-RECEIVABLE",
fieldName: "CreditAmount",
deltaAmount: +12000.00 // CR: Write off fees
},
{
entityType: "GLAccount",
entityKey: "1107-PENALTIES-RECEIVABLE",
fieldName: "CreditAmount",
deltaAmount: +45000.00 // CR: Write off penalties
},
{
entityType: "GLAccount",
entityKey: "NPL-REGISTER-OFF-BALANCE-SHEET",
fieldName: "MemoEntry",
deltaAmount: +2253000.00 // Track for collection
}
]
}
```text
**Note**: Outstanding amounts remain tracked for collection purposes but removed from balance sheet.
---
### Scenario 2: Under-Provisioned Write-Off
**Context**:
- Loan: NGN 500,000 principal, 18% annual interest
- Days Past Due: 180 days (6 months overdue)
- Outstanding Principal: NGN 480,000 (4% paid)
- Accrued Interest: NGN 86,400 (unpaid)
- Unpaid Penalties: NGN 15,000
- Unpaid Fees: NGN 5,000
- **Provision Balance**: NGN 400,000 (only 68% provisioned)
- Write-Off Decision: Management decision due to legal issues
**Write-Off Calculation**:
```typescript
WriteOffAmount = 480,000 (principal)
+ 86,400 (interest)
+ 15,000 (penalties)
+ 5,000 (fees)
= 586,400
ProvisionUsed = 400,000 (from provision)
AdditionalExpense = 186,400 (under-provisioned - additional P&L hit)
```text
**Key GL Impacts**:
```typescript
{
transactionId: "WROF-002",
writeOffAmount: 586400.00,
provisionUsed: 400000.00,
additionalExpense: 186400.00,
impactedEntities: [
// LoanAccount impacts
{
entityType: "LoanAccount",
entityId: 103,
fieldName: "PrincipalBalance",
oldValue: 480000.00,
newValue: 0.00,
deltaAmount: -480000.00
},
{
entityType: "LoanAccount",
entityId: 103,
fieldName: "WriteOffAmount",
oldValue: 0.00,
newValue: 586400.00,
deltaAmount: +586400.00
},
{
entityType: "LoanAccount",
entityId: 103,
fieldName: "LoanState",
oldValue: "OVERDUE",
newValue: "WRITTEN_OFF",
deltaAmount: 0
},
// GL impacts - TWO expense sources
{
entityType: "GLAccount",
entityKey: "1108-PROVISION-FOR-LOAN-LOSSES",
fieldName: "DebitAmount",
deltaAmount: +400000.00 // DR: Use existing provision
},
{
entityType: "GLAccount",
entityKey: "5201-BAD-DEBT-EXPENSE",
fieldName: "DebitAmount",
deltaAmount: +186400.00 // DR: Additional expense (P&L hit)
},
{
entityType: "GLAccount",
entityKey: "1101-LOANS-TO-CUSTOMERS",
fieldName: "CreditAmount",
deltaAmount: +480000.00 // CR: Remove principal
},
{
entityType: "GLAccount",
entityKey: "1105-INTEREST-RECEIVABLE",
fieldName: "CreditAmount",
deltaAmount: +86400.00 // CR: Write off interest
},
{
entityType: "GLAccount",
entityKey: "1106-FEES-RECEIVABLE",
fieldName: "CreditAmount",
deltaAmount: +5000.00 // CR: Write off fees
},
{
entityType: "GLAccount",
entityKey: "1107-PENALTIES-RECEIVABLE",
fieldName: "CreditAmount",
deltaAmount: +15000.00 // CR: Write off penalties
}
]
}
```text
**Critical Impact**: Under-provisioned write-off directly hits P&L with additional expense of NGN 186,400.
---
## Write-Off Implementation
### Step 1: Validate Write-Off Eligibility
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 2: Calculate Write-Off Amount
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
### Step 3: Execute Write-Off Transaction
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
---
## GL Posting Examples
### Example 1: Fully Provisioned Write-Off
**Transaction**: Write-off NGN 2,253,000 (fully provisioned)
```text
DR: 1108-PROVISION-FOR-LOAN-LOSSES 2,253,000.00 (Use provision)
CR: 1101-LOANS-TO-CUSTOMERS 1,800,000.00 (Remove principal)
CR: 1105-INTEREST-RECEIVABLE 396,000.00 (Write off interest)
CR: 1106-FEES-RECEIVABLE 12,000.00 (Write off fees)
CR: 1107-PENALTIES-RECEIVABLE 45,000.00 (Write off penalties)
MEMO: NPL-REGISTER (Off Balance Sheet) 2,253,000.00 (Track for collection)
```text
**Net Effect**:
- Assets decrease: 2,253,000 (loans + receivables)
- Provision decrease: 2,253,000 (already expensed in prior periods)
- **No P&L impact** (provision already taken)
### Example 2: Under-Provisioned Write-Off
**Transaction**: Write-off NGN 586,400 (68% provisioned - NGN 400,000)
```text
DR: 1108-PROVISION-FOR-LOAN-LOSSES 400,000.00 (Use existing provision)
DR: 5201-BAD-DEBT-EXPENSE 186,400.00 (Additional expense)
CR: 1101-LOANS-TO-CUSTOMERS 480,000.00 (Remove principal)
CR: 1105-INTEREST-RECEIVABLE 86,400.00 (Write off interest)
CR: 1106-FEES-RECEIVABLE 5,000.00 (Write off fees)
CR: 1107-PENALTIES-RECEIVABLE 15,000.00 (Write off penalties)
MEMO: NPL-REGISTER (Off Balance Sheet) 586,400.00 (Track for collection)
```text
**Net Effect**:
- Assets decrease: 586,400 (loans + receivables)
- Provision decrease: 400,000
- **P&L impact: -186,400** (additional bad debt expense)
---
## Recovery After Write-Off
### Recovery Transaction
If a written-off loan is recovered (partially or fully), it's recognized as **recovery income**:
:::warning[Code Removed]
**Implementation details removed for security.**
Contact support for implementation guidance.
:::text
**Recovery GL Entry**:
```text
DR: 1001-CASH 100,000.00 (Recovery received)
CR: 4301-RECOVERY-INCOME 100,000.00 (Income recognized)
```text
---
## API Usage
### Request: Validate Write-Off Eligibility
```http
POST /api/v2/loans/{loanAccountId}/writeoff/validate
Content-Type: application/json
{
"writeOffReason": "Non-performing loan, 365 days overdue, exhausted collection"
}
```text
### Response: Eligibility Validation
```json
{
"loanAccountId": 102,
"loanAccountKey": "LOAN-102",
"isEligible": true,
"daysPastDue": 365,
"collectionAttempts": 8,
"outstandingBalance": 2253000.00,
"provisioningBalance": 2253000.00,
"provisioningPercentage": 100.00,
"additionalExpense": 0.00,
"regulatoryRequirement": "MUST write-off per CBN Prudential Guidelines (NPL > 365 days)",
"warnings": [],
"validationErrors": []
}
```text
### Request: Execute Write-Off
```http
POST /api/v2/loans/{loanAccountId}/writeoff/execute
Content-Type: application/json
Authorization: Bearer {token}
{
"writeOffDate": "2025-12-28",
"writeOffReason": "Non-performing loan, 365 days overdue, collection exhausted",
"approvedBy": "CREDIT_COMMITTEE",
"approvalReference": "CC-2025-12-15-089",
"notes": "Borrower declared bankrupt, no collateral value"
}
```text
### Response: Write-Off Executed
```json
{
"transactionId": 456,
"transactionKey": "WROF-001",
"transactionType": "LOAN_WRITE_OFF",
"state": "COMPLETED",
"loanAccountId": 102,
"loanAccountKey": "LOAN-102",
"writeOffAmount": 2253000.00,
"writeOffDate": "2025-12-28",
"completedDate": "2025-12-28T14:20:00Z",
"breakdown": {
"outstandingPrincipal": 1800000.00,
"accruedInterest": 396000.00,
"unpaidPenalties": 45000.00,
"unpaidFees": 12000.00,
"provisionUsed": 2253000.00,
"additionalExpense": 0.00
},
"schedulesWrittenOff": 8,
"loanAccountState": "WRITTEN_OFF",
"regulatoryImpact": {
"nplRemoval": 2253000.00,
"capitalRelief": 112650.00, // 5% of write-off
"provisionRelease": 2253000.00
},
"impactedEntities": [
{
"entityType": "LoanAccount",
"entityId": 102,
"changes": [
{
"fieldName": "PrincipalBalance",
"oldValue": 1800000.00,
"newValue": 0.00,
"delta": -1800000.00
},
{
"fieldName": "State",
"oldValue": "OVERDUE",
"newValue": "WRITTEN_OFF"
}
]
}
]
}
```text
---
## Validation Rules
### Pre-Execution Validation
1. **Loan Account State**:
- Must be `ACTIVE` or `OVERDUE`
- Cannot be `CLOSED`, `CANCELLED`, or already `WRITTEN_OFF`
2. **Days Past Due (Regulatory)**:
- Minimum 180 days (Nigeria CBN guideline)
- Mandatory write-off at 365 days for NPL > 1 year
3. **Collection Efforts**:
- Minimum 3 collection attempts documented
- Legal action considered or exhausted
4. **Approval Requirements**:
- **ALWAYS requires credit committee approval**
- Approval reference must be provided
- Approver authorization verified
5. **Provisioning Check**:
- Calculate provisioning percentage
- Warn if under-provisioned (< 100%)
- Show additional P&L impact
### Post-Execution Validation
6. **All Schedules Written Off**:
- Verify all schedules marked as `WRITTEN_OFF`
- Confirm write-off dates set
7. **Loan Account Zeroed**:
- PrincipalBalance = 0
- InterestBalance = 0
- PenaltyBalance = 0
- FeesBalance = 0
- State = `WRITTEN_OFF`
8. **GL Balancing**:
- Total debits = Total credits
- Provision + Additional Expense = Total Write-Off Amount
9. **Off-Balance Sheet Tracking**:
- Add to NPL Register (memo account)
- Track for collection and recovery
---
## Error Codes
| Code | Description | Resolution |
|------|-------------|------------|
| **WROF_001** | Loan account not found | Verify loan account exists |
| **WROF_002** | Loan already written off | Cannot write off twice |
| **WROF_003** | Loan not overdue | Must be NPL (180+ days) |
| **WROF_004** | Insufficient days past due | Wait for minimum 180 days |
| **WROF_005** | Missing collection efforts | Document collection attempts |
| **WROF_006** | Missing approval | Requires credit committee approval |
| **WROF_007** | Invalid approval reference | Verify approval document |
| **WROF_008** | Provisioning error | Check provisioning account |
| **WROF_009** | GL posting failed | Contact support |
| **WROF_010** | Regulatory compliance failed | Check CBN guidelines |
---
## Best Practices
### For Product Managers
1. **Provisioning Policy**:
- Maintain 100% provisioning for NPL > 180 days
- Incremental provisioning: 50% at 90 days, 100% at 180 days
- Reduces P&L shock at write-off
2. **Write-Off Criteria**:
- Clear policy on when to write off (days, amount thresholds)
- Balance between regulatory requirements and operational needs
- Consider cost of continued collection vs write-off
3. **Collection Post Write-Off**:
- Continue collection efforts (legal liability remains)
- Track recoveries for income recognition
- Maintain off-balance sheet register
### For Developers
1. **Impact Tracking**:
- Track EVERY schedule write-off with state changes
- Store write-off reason and approval reference
- Link provisioning movements to transaction
2. **Regulatory Compliance**:
- Implement CBN prudential guidelines
- Auto-flag loans for write-off at 365 days
- Generate regulatory reports (NPL, provisioning)
3. **Recovery Tracking**:
- Implement recovery transaction type
- Link recoveries to original write-off
- Track collection costs vs recovery amount
### For Bank Operations
1. **Write-Off Process**:
- Document all collection efforts before write-off
- Obtain credit committee approval
- Verify provisioning adequacy
- Generate write-off certificate
2. **Post-Write-Off Management**:
- Transfer to collections/legal department
- Update credit bureau (if applicable)
- Monitor for recovery opportunities
- Maintain off-balance sheet register
3. **Reconciliation**:
- Monthly NPL write-off report
- Verify GL postings vs loan balances
- Track provisioning movements
- Recovery income reconciliation
---
## V2 API Commands
### Architecture Overview
The V2 loan write-off command follows the **Delegation Pattern**:
- **V2 Facade**: `InitiateLoanWriteOffCommand` (BPMCore compatible)
- **V1 Implementation**: Delegates to existing V1 `LoanWriteOffCommand` with full business logic
- **BPM Integration**: Accepts parameters via `BpmUtil.GetPropertyValue()`
**Implementation**: `CB.Administration.Api/Commands/BPMCore/Loans/AdministrationCoreLoanCommandHandlers.Transactions.cs`
---
### InitiateLoanWriteOffCommand
**Purpose**: Write off an unrecoverable loan (non-performing loan closure)
**Command**: `InitiateLoanWriteOffCommand`
**Delegation**: → `LoanWriteOffCommand` (V1 implementation)
#### BPM Parameters
```json
{
"commandName": "InitiateLoanWriteOffCommand",
"data": {
"accountEncodedKey": "string (mandatory)",
"clientEncodedKey": "string (mandatory)",
"writeOffReason": "string (optional)",
"transactionDate": "DateTime (optional)",
"notes": "string (optional)"
}
}
Parameter Details
| Parameter | Type | Required | Description |
|---|---|---|---|
accountEncodedKey | string | ✅ Yes | Loan account to write off |
clientEncodedKey | string | ✅ Yes | Client/borrower encoded key |
writeOffReason | string | ⌠No | Reason code (e.g., "UNRECOVERABLE") |
transactionDate | DateTime | ⌠No | Write-off date (defaults to today) |
notes | string | ⌠No | Write-off notes/remarks |
Balance Impact
Loan Account:
| Balance Field | Change | Reason |
|---|---|---|
PrincipalBalance | → 0 | Written off |
InterestBalance | → 0 | Written off |
PenaltyBalance | → 0 | Written off |
FeesBalance | → 0 | Written off |
State | → WRITTEN_OFF | Loan closed as NPL |
WriteOffDate | Set to transaction date | Track write-off date |
WriteOffAmount | Total written off | Track amount |
GL Impact: Debits provisioning account, credits loan asset account
Example Response
{
"isSuccessful": true,
"transactionId": "TXN-LOAN-WO-20251229-0001",
"transactionState": "SETTLED",
"message": "Loan written off successfully",
"data": {
"loanAccountKey": "8a8080827f23loan017f23def456",
"writeOffAmount": 450000.00,
"writeOffDate": "2025-12-29T10:00:00Z",
"loanState": "WRITTEN_OFF",
"components": {
"principalWrittenOff": 400000.00,
"interestWrittenOff": 45000.00,
"penaltiesWrittenOff": 5000.00,
"feesWrittenOff": 0.00
}
}
}
Implementation Checklist
Phase 1: Validation & Calculation
- Implement
ValidateWriteOffEligibilityAsyncwith regulatory checks - Add days past due calculation
- Add collection efforts tracking
- Implement
CalculateWriteOffAmountAsyncwith provisioning analysis - Unit tests for validation rules (10+ scenarios)
Phase 2: Write-Off Execution
- Implement
ProcessLoanWriteOffAsyncwith approval workflow - Add schedule write-off logic with state management
- Add loan account write-off with field tracking
- Add provisioning updates
- Integration tests for write-off execution
Phase 3: GL Posting
- Implement
PostWriteOffGLEntriesAsyncfor all scenarios - Handle fully provisioned write-offs
- Handle under-provisioned write-offs (additional expense)
- Add off-balance sheet memo entries (NPL register)
- Unit tests for GL balancing
Phase 4: Recovery Processing
- Implement
ProcessLoanRecoveryAsyncfor post-write-off recoveries - Add recovery GL entries (recovery income)
- Link recoveries to original write-off transaction
- Track collection costs
- Recovery income reporting
Phase 5: API & Reporting
- Create write-off validation endpoint
- Create write-off execution endpoint
- Create recovery transaction endpoint
- Add NPL write-off register report
- Add recovery tracking report
- Regulatory reporting (CBN format)
Phase 6: Testing & Deployment
- End-to-end tests (5+ scenarios including recovery)
- Regulatory compliance tests (CBN guidelines)
- Performance tests
- User acceptance testing
- Production deployment with audit trail
Developer Resources
For API implementation details, see:
Document Version: 1.0
Last Updated: December 28, 2025
Related Documents: