FINERACT-2232: Add capitalize income documentation

This commit is contained in:
Oleksii Novikov
2025-08-06 14:27:28 +02:00
committed by Adam Saghy
parent 9ed26cb236
commit 68474e250d
3 changed files with 497 additions and 0 deletions
@@ -0,0 +1,490 @@
= Capitalized Income
== Overview
Capitalized Income (formerly referred to as Origination Fee) in Apache Fineract is a fee-based or interest-based income item that is added to the principal of a loan and amortized over time. It is designed to be applied at disbursement and is treated as part of the loan's principal for repayment and interest calculations.
== Purpose
This functionality enables financial institutions to:
* Recognize deferred income (fees or interest) systematically over the loan term
* Align accounting practices with regulatory requirements
* Improve income recognition accuracy
== Supported Loan Type
[IMPORTANT]
====
Capitalized Income is only supported for:
* Progressive Loan Schedules
* Advanced Payment Allocation Strategy
Other loan schedule types and transaction processing strategies are not supported.
====
== Configuration at Loan Product Level
Capitalized income must be configured on the loan product.
The configuration options include:
* *Enable Capitalized Income*: Boolean toggle (default: disabled)
* *Calculation Mode*: Only "Flat" is currently supported
** Later "Percentage based" can be introduced
* *Amortization Strategy*: Only "EQUAL_AMORTIZATION" is supported
*** Daily equal portions are recognized over the life of the loan
*** Later other strategies can be introduced
* *Income Type*: Specifies allocation rule. Defines which "balance category" to be used.
[NOTE]
====
In Fineract, balance of a transaction is either: Principal, Fee, Penalty, Interest or overpayment
====
Options:
* FEE (default)
* INTEREST
=== GL Mapping
Required GL Account mappings when Capitalized Income is enabled:
* *Deferred Income (Liability)*: `deferredIncomeLiabilityAccountId` - mandatory when enabled
* *Income from Capitalization (Income)*: `incomeFromCapitalizationAccountId` - mandatory when enabled
[IMPORTANT]
====
Both GL accounts become mandatory when `enableIncomeCapitalization` is set to `true`.
====
=== Configuration Dependencies
When `enableIncomeCapitalization` is set to `true`, all following parameters become mandatory:
* `capitalizedIncomeCalculationType` - must be "FLAT"
* `capitalizedIncomeStrategy` - must be "EQUAL_AMORTIZATION"
* `capitalizedIncomeType` - must be "FEE" or "INTEREST"
* `deferredIncomeLiabilityAccountId` - must reference a valid GL account
* `incomeFromCapitalizationAccountId` - must reference a valid GL account
== Behavior and Calculations
* Capitalized income is added via API on or after the first disbursement date
* It is treated as a principal portion, recalculating the repayment schedule accordingly
* Interest and amortization schedules are updated to include the capitalized income amount
* Validated using formula: `(Total Disbursed + Current Capitalized Income + New Transaction Amount) ≤ Max Amount`, where Max Amount depends on loan product configuration: if `allowApprovedDisbursedAmountsOverApplied = true` uses `getOverAppliedMax(loan)`, otherwise uses `getApprovedPrincipal()`
=== Daily Amortization
* Recognized daily using the configured strategy
* Recognized portions move from Deferred Income to Income from Capitalization
==== Special Handling
* *Preclosure*: Remaining balance recognized in full on the preclosure date
* *Charge-off*: Amortization stops and remaining balance is charged off
== Transaction Types Introduced
* Capitalized Income
* Capitalized Income Amortization
* Capitalized Income Adjustment
* Capitalized Income Amortization Adjustment
=== Capitalized Income Transaction
The Capitalized Income transaction in Apache Fineract performs the following actions:
* Adds a specified amount to the loan principal
** Considered a deferred income item (such as a fee or interest)
** Booked as part of the loan's principal
** Added post-disbursement and only if the loan type supports it (currently: Progressive Loans)
* Creates a distinct loan transaction
** Separately tracked with its own transaction type ("Capitalized Income")
** Not merged with disbursements or repayments
* Updates the loan schedule
** Recalculates amortization and interest schedule to include the added amount in the outstanding principal
* Triggers accounting entries
** Debits "Loan Portfolio" (Asset)
** Credits "Deferred Income" (Liability)
** Does not recognize income upfront
* Initiates daily amortization
** Source for daily income recognition through "Capitalized Income Amortization" transactions
** Progressively converts the deferred amount to recognized income
==== Accounting Entries
[cols="2*"]
|===
|Scenario |Debit |Credit
|Capitalized Income
|Loan Portfolio (Asset)
|Deferred Income (Liability)
|===
=== Capitalized Income Amortization
A Capitalized Income Amortization transaction in Apache Fineract does the following:
* *Recognizes Deferred Income Over Time*: Transfers a portion of the capitalized income (originally posted as a liability) into recognized income (posted as interest or fee income), based on a configured daily amortization strategy.
* *Daily Posting*: The system automatically creates this transaction each day from the date of capitalized income until the loan maturity or until the full amount is amortized. This is handled by a background job during the COB (Close of Business) process.
* *Uses Equal Amortization*: The default and only supported strategy is Equal Amortization, which divides the total capitalized income evenly over the remaining number of days until the loan matures.
==== Accounting Entries
[cols="2*"]
|===
|Scenario |Debit |Credit
|Daily amortization
|Deferred Income (Liability)
|Income from Capitalization (Income)
|===
==== Stops on Events
* *Preclosure*: Triggers final amortization for remaining unrecognized income
* *Charge-off*: Halts further amortization; the remaining deferred income is charged off
[NOTE]
====
Reversal Handling: If the original Capitalized Income transaction is reversed, all associated amortization transactions are also reversed via "Capitalized Income Amortization Adjustment" transactions.
====
=== Capitalized Income Adjustment
A Capitalized Income Adjustment transaction in Apache Fineract serves to reduce the balance of an existing capitalized income transaction.
==== Purpose
* Correct overcharged or misposted capitalized income amounts
* Reflect fee waivers or negotiated reductions
* Support backdated corrections if needed
==== Transaction Behavior
* It is a credit-type transaction, reducing the capitalized income balance
* Treated similarly to other credit transactions and follows a defined allocation strategy
* Can be backdated, but not dated before the original capitalized income transaction
==== Validation Rules
* The adjustment amount must not exceed the remaining amount (original capitalized income amount minus total previous adjustments)
* Adjustment is linked to a specific Capitalized Income transaction (by ID)
* Multiple adjustments can be made against the same original transaction
* Adjustments can be reversed if needed
==== Accounting Entries
[cols="3*"]
|===
|Scenario |Debit |Credit
|Adjustment ≤ unrecognized balance
|Deferred Income (Liability)
|Loan Portfolio (Asset)
|Adjustment > unrecognized balance
|Deferred Income (Liability)
|Loan Portfolio (Asset)
|===
==== Business Event Triggers
* Triggers "Capitalized Income Adjustment" event
* Updates loan balance and possibly loan status depending on impact
==== Impact
* Reduces amortization basis
* May modify future amortization amounts
* Repayment schedule is not affected directly unless recalculated manually
=== Capitalized Income Amortization Adjustment
A Capitalized Income Amortization Adjustment in Apache Fineract is a special transaction type used to reverse previously recognized income from capitalized income amortization.
==== Purpose
* Automatically generated when a Capitalized Income transaction is reversed or when backdated Capitalized Income Adjustment affects amortization balances
* Reverses all already recognized portions (amortized income) linked to the original Capitalized Income transaction
==== When It Occurs
* Created by daily amortization (COB) or final amortization (triggered on loan closure, charge-off, or by any backdated transaction that affects capitalized income balances)
* Reverses previously recognized income when amortization needs to be adjusted
* Restores Deferred Income balances and reverses income recognition
==== Accounting Entries
[cols="3*"]
|===
|Transaction Type |Debit |Credit
|Capitalized Income Amortization Adjustment
|Income from Capitalization (Income)
|Deferred Income (Liability)
|===
==== Key Characteristics
* *System-Generated Only*: Cannot be created manually by API or UI
* *Ensures Accounting Integrity*: Keeps amortized and unrecognized balances aligned after reversals
* *Non-monetary transaction - does not trigger balance changed or status update events*
==== Business Events
* Triggers a new business event: Capitalized Income Amortization Adjustment
== API Endpoints
=== Configure Capitalized Income on Loan Product
* *Endpoint*: `/loanproducts`
* *Method*: `POST`
[source,json]
----
{
...
"enableIncomeCapitalization": true, // Mandatory
"capitalizedIncomeCalculationType": "FLAT", // Mandatory when enabled
"capitalizedIncomeStrategy": "EQUAL_AMORTIZATION", // Mandatory when enabled
"capitalizedIncomeType": "FEE", // Mandatory when enabled
"deferredIncomeLiabilityAccountId": 123, // Mandatory when enabled
"incomeFromCapitalizationAccountId": 456 // Mandatory when enabled
}
----
=== Add Capitalized Income
* *Endpoint*: `/loans/{loanId}/transactions?command=capitalizedIncome`
* *Alternative Endpoint*: `/loans/external-id/{loanExternalId}/transactions?command=capitalizedIncome`
* *Method*: `POST`
[source,json]
----
{
"transactionDate": "2025-05-01", // Mandatory
"dateFormat": "yyyy-MM-dd", // Mandatory
"locale": "en", // Mandatory
"transactionAmount": 100.0, // Mandatory
"paymentTypeId": 1, // Optional
"note": "Capitalized income fee", // Optional
"externalId": "CINCOME-001" // Optional
}
----
=== Get Capitalized Income Amortization Info
* *Endpoint*: `/loans/{loanId}/deferredincome`
* *Alternative Endpoint*: `/loans/external-id/{loanExternalId}/deferredincome`
* *Method*: `GET`
==== Response Body
[source,json]
----
{
"capitalizedIncomeData": [
{
"amount": 50.0, // Total capitalized income amount
"amortizedAmount": 1.1, // Amount already amortized
"unrecognizedAmount": 48.9, // Amount not yet amortized
"amountAdjustment": 0.0, // Any adjustments made
"chargedOffAmount": 0.0 // Amount charged off (if applicable)
}
]
}
----
=== Add Capitalized Income Adjustment
* *Endpoint*: `/loans/{loanId}/transactions/{capitalizedIncomeTransactionId}?command=capitalizedIncomeAdjustment`
* *Alternative Endpoint*: `/loans/external-id/{loanExternalId}/transactions/{capitalizedIncomeTransactionId}?command=capitalizedIncomeAdjustment`
* *Method*: `POST`
[source,json]
----
{
"transactionDate": "2025-05-01", // Mandatory
"dateFormat": "yyyy-MM-dd", // Mandatory
"locale": "en", // Mandatory
"transactionAmount": 50.0, // Mandatory
"paymentTypeId": 1, // Optional
"note": "Capitalized income fee", // Optional
"externalId": "CINCOMEADJ-001" // Optional
}
----
==== Response Body
[source,json]
----
{
"resourceId": 1,
"resourceExternalId": "CINCOMEADJ-001"
}
----
=== Capitalized Income Template API (to retrieve limits)
* *Endpoint*: `/loans/{loanId}/transactions/template?command=capitalizedIncome`
* *Alternative Endpoint*: `/loans/external-id/{loanExternalId}/transactions/template?command=capitalizedIncome`
* *Method*: `GET`
[source,json]
----
{
"paymentTypeOptions": [], // List of available payment types
"currency": {...}, // Currency configuration
"date": [2025, 5, 29], // Return the current date
"amount": 0 // Return the maximum amount that can be capitalized (approved amount - disbursed amount - capitalized income)
}
----
=== Capitalized Income Adjustment Template API (to retrieve limits)
* *Endpoint*: `/loans/{loanId}/transactions/template?command=capitalizedIncomeAdjustment&transactionId={capitalizedIncomeTransactionId}`
* *Alternative Endpoint*: `/loans/external-id/{loanExternalId}/transactions/template?command=capitalizedIncomeAdjustment&transactionId={capitalizedIncomeTransactionId}`
* *Method*: `GET`
[source,json]
----
{
"paymentTypeOptions": [], // List of available payment types
"currency": {...}, // Currency configuration
"date": [2025, 5, 29], // Return the current date
"amount": 0 // Return the maximum amount that can be adjusted (capitalized income - adjustment)
}
----
== Accounting Entries
[cols="3*"]
|===
|Transaction Type |Debit |Credit
|Capitalized Income
|Loan Portfolio (Asset)
|Deferred Income (Liability)
|Capitalized Income Amortization
|Deferred Income (Liability)
|Income from Capitalization (Income)
|Capitalized Income Adjustment
|Deferred Income (Liability)
|Loan Portfolio (Asset)
|Capitalized Income Amortization Adjustment
|Income from Capitalization (Income)
|Deferred Income (Liability)
|===
== Business Events
=== Triggered for Capitalized Income
* `LoanCapitalizedIncomeTransactionCreatedBusinessEvent`
* `LoanBalanceChangedBusinessEvent`
=== Daily Amortization
* `LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent`
* `LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent`
=== Capitalized Income Adjustment
* `LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent`
* `LoanBalanceChangedBusinessEvent`
=== Reversal
* `LoanAdjustTransactionBusinessEvent`
== Database Structure
=== Configuration
==== Stored on Loan Product (`m_product_loan`)
[cols="3*"]
|===
|Field |Data Type |Description
|`enable_income_capitalization` |`BOOLEAN` |Enable capitalized income feature (default: `false`)
|`capitalized_income_calculation_type` |`VARCHAR` |Calculation method (ENUM: `FLAT`)
|`capitalized_income_strategy` |`VARCHAR` |Amortization strategy (ENUM: `EQUAL_AMORTIZATION`)
|`capitalized_income_type` |`VARCHAR` |Income type (ENUM: `FEE`, `INTEREST`)
|===
==== Stored on Loan (`m_loan`)
[cols="3*"]
|===
|Field |Data Type |Description
|`enable_income_capitalization` |`BOOLEAN` |Enable capitalized income feature (default: `false`)
|`capitalized_income_calculation_type` |`VARCHAR` |Calculation method (ENUM: `FLAT`)
|`capitalized_income_strategy` |`VARCHAR` |Amortization strategy (ENUM: `EQUAL_AMORTIZATION`)
|`capitalized_income_type` |`VARCHAR` |Income type (ENUM: `FEE`, `INTEREST`)
|===
=== Balances
==== On Loan
[cols="3*"]
|===
|Field |Data Type |Description
|`capitalized_income_derived` |`DECIMAL(19,6)` |Total capitalized income amount (nullable)
|`capitalized_income_adjustment_derived` |`DECIMAL(19,6)` |Total adjustment amount (nullable)
|===
==== Capitalized Income Balance (`m_loan_capitalized_income_balance`)
Each capitalized income has its own balance (1 row for each transaction)
[cols="3*"]
|===
|Field |Data Type |Description
|`id` |`BIGINT` |Unique identifier (Primary Key)
|`version` |`BIGINT` |Version for optimistic locking
|`loan_id` |`BIGINT` |Associated loan ID (Foreign Key, NOT NULL)
|`loan_transaction_id` |`BIGINT` |Associated loan transaction ID (Foreign Key, NOT NULL)
|`amount` |`DECIMAL(19,6)` |Capitalized income transaction amount (NOT NULL)
|`date` |`DATE` |Capitalized income transaction date (NOT NULL)
|`unrecognized_amount` |`DECIMAL(19,6)` |Amortization - not yet recognized amount (NOT NULL)
|`charged_off_amount` |`DECIMAL(19,6)` |Charged-off balance (nullable)
|`amount_adjustment` |`DECIMAL(19,6)` |Total adjustment amount (nullable)
|`created_by` |`BIGINT` |Audit field - user who created the record
|`created_on_utc` |`DATETIME` |Creation timestamp (UTC)
|`last_modified_by` |`BIGINT` |Last modifier user ID
|`last_modified_on_utc` |`DATETIME` |Last modification timestamp (UTC)
|===
=== Constraints
* **Foreign Key Constraints:**
- `loan_id` references `m_loan(id)`
- `loan_transaction_id` references `m_loan_transaction(id)`
- `created_by` references `m_appuser(id)`
- `last_modified_by` references `m_appuser(id)`
== Notes
[IMPORTANT]
====
* Capitalized income transactions support backdating
* Adjustment transactions must not predate the original capitalized income
* No automatic reversal is supported; must be handled manually via dedicated transactions
* Proper GL accounts must be set for Deferred Income and Income from Capitalization to enable this functionality
====
@@ -0,0 +1,5 @@
= Features
This section covers specific features and functionality available in Apache Fineract.
include::capitalized-income.adoc[leveloffset=+1]
+2
View File
@@ -40,6 +40,8 @@ include::chapters/deployment/index.adoc[leveloffset=+1]
include::chapters/architecture/index.adoc[leveloffset=+1]
include::chapters/features/index.adoc[leveloffset=+1]
include::chapters/development/index.adoc[leveloffset=+1]
include::chapters/custom/index.adoc[leveloffset=+1]