magyari-adam commented on code in PR #4913:
URL: https://github.com/apache/fineract/pull/4913#discussion_r2248160665
##########
fineract-doc/src/docs/en/chapters/features/buydown-fee.adoc:
##########
@@ -0,0 +1,637 @@
+= Buy Down Fee
+
+== Overview
+
+Buy Down Fee is a specialized fee mechanism in Apache Fineract that allows
financial institutions to collect upfront fees from borrowers to reduce their
effective interest rate over the loan term. This feature is particularly
designed for 0% interest "buy down" loans where a merchant fee is collected and
amortized into interest/fee income over the life of the loan.
+
+The key characteristic of Buy Down Fee is that the amortized fee is **NOT
visible to the customer** and **NOT affecting the repayment schedule** - it
operates as a background process for proper revenue recognition while
maintaining transparency in customer-facing loan terms.
+
+=== Key Benefits
+
+* **Interest Rate Reduction**: Borrowers can reduce their effective interest
rate by paying an upfront fee
+* **Merchant Fee Support**: Enables 0% interest loan products with
merchant-paid fees
+* **Revenue Recognition**: Provides controlled amortization of fee income over
the loan term
+* **Customer Transparency**: Fee amortization is invisible to customers,
maintaining clean loan presentation
+* **Accounting Integration**: Proper journal entries and accounting treatment
for fee transactions
+
+== Buy Down Fee Configuration
+
+=== Prerequisites
+
+Before enabling Buy Down Fee functionality, ensure the following prerequisites
are met:
+
+* **Loan Product Type**: Only Progressive Loan products support Buy Down Fee
functionality
+* **Accounting Method**: Accrual accounting must be enabled for proper income
recognition and amortization
+* **General Ledger Accounts**: Required GL accounts must be configured
+
+=== Required Configuration Parameters
+
+When creating or updating a loan product with Buy Down Fee enabled, the
following parameters are mandatory:
+
+[cols="2,1,3", options="header"]
+|===
+|Parameter |Required |Description
+
+|`enableBuyDownFee`
+|Yes
+|Boolean flag to enable/disable Buy Down Fee functionality
+
+|`buyDownFeeCalculationType`
+|Yes*
+|Calculation method for Buy Down Fee. Currently only `FLAT` is supported
+
+|`buyDownFeeStrategy`
+|Yes*
+|Amortization strategy. Currently only `EQUAL_AMORTIZATION` is supported
+
+|`buyDownFeeIncomeType`
+|Yes*
+|Income recognition type: `FEE` or `INTEREST`
+
+|`buyDownExpenseAccountId`
+|Yes*
+|GL Account ID for Buy Down Fee expense recognition
+
+|`incomeFromBuyDownAccountId`
+|Yes*
+|GL Account ID for Buy Down Fee income recognition during amortization
+
+|`deferredIncomeLiabilityAccountId`
+|Yes*
+|GL Account ID for Deferred Income Liability Account used during amortization
+
+|`loanScheduleType`
+|Yes
+|Loan schedule type, must be set to "PROGRESSIVE" for Buy Down Fee
+|===
+
+_* Required only when `enableBuyDownFee` is set to `true`_
+
+=== Accounting Mappings
+
+Buy Down Fee requires specific GL account mappings for proper financial
reporting:
+
+==== Required GL Accounts
+
+* **Buy Down Expense Account** (`buyDownExpenseAccountId`)
+ - Account Type: EXPENSE
+ - Purpose: Records the initial Buy Down Fee expense when transaction is
created
+ - Journal Entry: Debit when Buy Down Fee is added
+
+* **Income from Buy Down Account** (`incomeFromBuyDownAccountId`)
+ - Account Type: INCOME
+ - Purpose: Records income recognition during daily amortization process
+ - Journal Entry: Credit during COB amortization
+
+==== Journal Entry Flow
+
+1. **Buy Down Fee Creation**:
+ - Debit: Buy Down Expense Account
+ - Credit: Cash/Bank Account
+
+2. **Daily Amortization** (via COB):
+ - Debit: Deferred Capitalized Income
+ - Credit: Fee/Interest Income Account
+
+3. **Buy Down Fee Adjustment**:
+- Debit: Deferred Income Liability
+- Credit: Buy Down Expense Account
+
+4. **Buy Down Fee Amortization Adjustment**:
+- Debit: Deferred Income Liability
+- Credit: Income from Buy Down Account
+
+5. **Charge-off Handling**:
+- Debit: Losses Written Off Account
+- Credit: Deferred Income Liability
+
+=== Validation Rules
+
+The system enforces the following validation rules:
+
+==== Product Level Validations
+
+* Buy Down Fee can only be enabled for Progressive Loan products
+* When `enableBuyDownFee` is `true`, all related parameters become mandatory
+* Calculation type must be `FLAT` (other types not yet supported)
+* Strategy must be `EQUAL_AMORTIZATION` (other strategies not yet supported)
+* Income type must be either `FEE` or `INTEREST`
+* Both expense and income GL accounts must be provided and valid
+* GL accounts must have correct account types (EXPENSE and INCOME respectively)
+* `deferredIncomeLiabilityAccountId` mapping requirements:
+ - Must be a valid LIABILITY type GL account
+ - Represents temporary holding of not-yet-recognized income
+ - Used to track unamortized Buy Down Fee portion
+ - Cannot be zero or null when Buy Down Fee is enabled
+
+==== Transaction Level Validations
+
+* Buy Down Fee transactions can only be added to active loans
+* Transaction amount must be positive (greater than zero)
+* Transaction date cannot be before the first disbursement date
+* Loan must have Buy Down Fee enabled in its product configuration
+
+==== Adjustment Validations
+
+A Buy Down Fee adjustment is a mechanism to modify an existing Buy Down Fee
transaction after its initial creation. It allows financial institutions to:
+
+* Reduce the total Buy Down Fee amount
+* Correct errors in the initial fee calculation
+
+===== Adjustment Validations:
+
+* Adjustment amount cannot exceed the original Buy Down Fee amount
+* Total adjustments cannot exceed the remaining unadjusted amount
+* Adjustments can only be made to existing Buy Down Fee transactions
+* Standard payment details validation applies (payment type, notes, etc.)
+
+=== Configuration Examples
+
+==== Enabling Buy Down Fee
+
+[source,json]
+----
+{
+ "enableBuyDownFee": true,
+ "buyDownFeeCalculationType": "FLAT",
+ "buyDownFeeStrategy": "EQUAL_AMORTIZATION",
+ "buyDownFeeIncomeType": "FEE",
+ "buyDownExpenseAccountId": 123,
+ "incomeFromBuyDownAccountId": 456,
+ // ... other loan product parameters
+}
+----
+
+==== Disabling Buy Down Fee
+
+[source,json]
+----
+{
+ "enableBuyDownFee": false
+ // buyDownFee* parameters are optional when disabled
+}
+----
+
+=== Error Scenarios
+
+Common validation errors and their resolutions:
+
+* **"Buy Down Fee calculation type is required"**: Provide
`buyDownFeeCalculationType` when enabling Buy Down Fee
+* **"Buy Down Fee strategy is required"**: Provide `buyDownFeeStrategy` when
enabling Buy Down Fee
+* **"Buy Down Fee income type is required"**: Provide `buyDownFeeIncomeType`
when enabling Buy Down Fee
+* **"Buy Down expense account is required"**: Provide valid
`buyDownExpenseAccountId`
+* **"Income from Buy Down account is required"**: Provide valid
`incomeFromBuyDownAccountId`
+* **"Buy Down fees can only be added to active loans"**: Ensure loan status is
ACTIVE before adding Buy Down Fee transactions
+
+== API Endpoints
+
+=== Create Buy Down Fee Transaction
+
+**Endpoint**: `POST /loans/{loanId}/transactions?command=buyDownFee`
+
+**Alternative**: `POST
/loans/external-id/{loanExternalId}/transactions?command=buyDownFee`
+
+**Purpose**: Creates a new Buy Down Fee transaction for a loan, establishing
the upfront merchant fee that will be amortized over the loan term.
+
+**Request Parameters**:
+[cols="1,1,1,3"]
+|===
+|Parameter |Type |Required |Description
+
+|`transactionDate`
+|String
+|Yes
+|Transaction date in specified format (e.g., "dd MMMM yyyy")
+
+|`dateFormat`
+|String
+|Yes
+|Date format specification (e.g., "dd MMMM yyyy")
+
+|`locale`
+|String
+|Yes
+|Locale for date formatting (e.g., "en")
+
+|`transactionAmount`
+|BigDecimal
+|Yes
+|Buy down fee amount (must be positive)
+
+|`paymentTypeId`
+|Long
+|No
+|Payment type identifier
+
+|`note`
+|String
+|No
+|Transaction note/description
+
+|`externalId`
+|String
+|No
+|External transaction identifier
+|===
+
+**Request Example**:
+[source,json]
+----
+{
+ "transactionDate": "01 January 2024",
+ "dateFormat": "dd MMMM yyyy",
+ "locale": "en",
+ "transactionAmount": 50.0,
+ "note": "Merchant fee for 0% interest loan"
+}
+----
+
+**Response Structure**:
+[source,json]
+----
+{
+ "loanId": 123,
+ "resourceId": 456,
+ "resourceExternalId": "ext-123",
+ "clientId": 789,
+ "officeId": 1
+}
+----
+
+**Business Logic**:
+
+1. Validates that Buy Down Fee is enabled for the loan product
+2. Ensures transaction date is not before first disbursement date
+3. Verifies client/group is active
+4. Creates Buy Down Fee transaction with specified amount
+5. Establishes LoanBuyDownFeeBalance record for amortization tracking
+6. Posts journal entries (Debit: Buy Down Expense, Credit: Deferred
Capitalized Income)
+7. Triggers business events for transaction creation and loan balance change
+
+=== Adjust Buy Down Fee Transaction
+
+**Endpoint**: `POST
/loans/{loanId}/transactions/{transactionId}?command=buyDownFeeAdjustment`
+
+**Alternative Endpoints**:
+
+- `POST
/loans/{loanId}/transactions/external-id/{transactionExternalId}?command=buyDownFeeAdjustment`
+- `POST
/loans/external-id/{loanExternalId}/transactions/{transactionId}?command=buyDownFeeAdjustment`
+- `POST
/loans/external-id/{loanExternalId}/transactions/external-id/{transactionExternalId}?command=buyDownFeeAdjustment`
+
+**Purpose**: Adjusts an existing Buy Down Fee transaction amount. This is
effectively a reversal of the original transaction followed by creation of a
new transaction with the adjusted amount.
+
+**Path Parameters**:
+[cols="2,1,1,3", options="header"]
+|===
+|Parameter |Type |Required |Description
+
+|`loanId`
+|Long
+|Yes*
+|Internal loan identifier
+
+|`loanExternalId`
+|String
+|Yes*
+|External loan identifier (alternative endpoints)
+
+|`transactionId`
+|Long
+|Yes*
+|Internal transaction identifier
+
+|`externalTransactionId`
+|String
+|Yes*
+|External transaction identifier (alternative endpoints)
+|===
+
+_* Required based on endpoint variant used_
+
+**Request Parameters**:
+[cols="2,1,1,3", options="header"]
+|===
+|Parameter |Type |Required |Description
+
+|`transactionDate`
+|String
+|Yes
+|Adjustment date in specified format
+
+|`dateFormat`
+|String
+|Yes
+|Date format specification (e.g., "dd MMMM yyyy")
+
+|`locale`
+|String
+|Yes
+|Locale for date formatting (e.g., "en")
+
+|`transactionAmount`
+|BigDecimal
+|Yes
+|New Buy Down Fee amount (must be positive)
+
+|`paymentTypeId`
+|Long
+|No
+|Payment type identifier
+
+|`note`
+|String
+|No
+|Adjustment note/description
+
+|`externalId`
+|String
+|No
+|External adjustment identifier
+|===
+
+**Request Example**:
+[source,json]
+----
+{
+ "transactionDate": "15 January 2024",
+ "dateFormat": "dd MMMM yyyy",
+ "locale": "en",
+ "transactionAmount": 25.0,
+ "note": "Partial adjustment of buy down fee"
+}
+----
+
+**Response Structure**:
+[source,json]
+----
+{
+ "loanId": 123,
+ "resourceId": 789,
+ "resourceExternalId": "adj-456",
+ "clientId": 789,
+ "officeId": 1
+}
+----
+
+**Business Logic**:
+
+1. **Permission Check**: Validates user has write permission for LOAN resources
+2. **Transaction Validation**: Ensures the referenced transaction exists and
is a Buy Down Fee transaction
+3. **Amount Validation**: Validates adjustment amount doesn't exceed remaining
buy down fee balance
+4. **Date Validation**: Ensures transaction date is not before original buy
down fee transaction
+5. **Transaction Reversal**: Reverses the original Buy Down Fee transaction
+6. **New Transaction Creation**: Creates new Buy Down Fee transaction with
adjusted amount
+7. **Balance Update**: Updates LoanBuyDownFeeBalance with new amount
+8. **Journal Entries**: Posts appropriate accounting entries for the adjustment
+9. **Business Events**: Triggers adjustment-related business events
+
+**Implementation Details**:
+
+- Uses standard loan transaction adjustment mechanism from
`LoanTransactionsApiResource`
+- Leverages `CommandWrapperBuilder` for command processing
+- Integrates with `PortfolioCommandSourceWritePlatformService` for transaction
handling
+- No special command parameter required (unlike creation which uses
`?command=buyDownFee`)
+- Supports all four endpoint variants (internal/external ID combinations)
+
+=== Retrieve Buy Down Fee Amortization Details
+
+**Endpoint**: `GET /loans/{loanId}/buydown-fees`
+
+**Alternative**: `GET /loans/external-id/{loanExternalId}/buydown-fees`
+
+**Purpose**: Retrieves detailed amortization information for all Buy Down Fee
transactions associated with a loan.
+
+**Request Parameters**: None (path parameters only)
+
+**Path Parameters**:
+[cols="2,1,1,3", options="header"]
+|===
+|Parameter |Type |Required |Description
+
+|`loanId`
+|Long
+|Yes
+|Internal loan identifier
+
+|`loanExternalId`
+|String
+|Yes*
+|External loan identifier (alternative endpoint)
+|===
+
+_* Required only for external ID endpoint_
+
+**Response Structure**:
+[source,json]
+----
+[
+ {
+ "id": 1,
+ "loanId": 123,
+ "transactionId": 456,
+ "buyDownFeeDate": "2024-01-01",
+ "buyDownFeeAmount": 500.00,
+ "amortizedAmount": 125.00,
+ "notYetAmortizedAmount": 375.00,
+ "adjustedAmount": 0.00,
+ "chargedOffAmount": 0.00
+ }
+]
+----
+
+**Response Fields**:
+[cols="2,1,3", options="header"]
+|===
+|Field |Type |Description
+
+|`id`
+|Long
+|Buy Down Fee balance record identifier
+
+|`loanId`
+|Long
+|Internal loan identifier
+
+|`transactionId`
+|Long
+|Buy Down Fee transaction identifier
+
+|`buyDownFeeDate`
+|LocalDate
+|Date when Buy Down Fee was created
+
+|`buyDownFeeAmount`
+|BigDecimal
+|Original Buy Down Fee amount
+
+|`amortizedAmount`
+|BigDecimal
+|Amount already amortized to income
+
+|`notYetAmortizedAmount`
+|BigDecimal
+|Amount still to be amortized
+
+|`adjustedAmount`
+|BigDecimal
+|Total adjustment amount (if any)
+
+|`chargedOffAmount`
+|BigDecimal
+|Amount charged off (if loan was charged off)
+|===
+
+**Business Logic**:
+
+1. **Permission Check**: Validates user has read permission for LOAN resources
+2. **Loan Resolution**: For external ID endpoint, resolves loan ID using
`ExternalIdFactory` and `LoanReadPlatformService`
+3. **Data Retrieval**: Calls
`BuyDownFeeReadPlatformService.retrieveLoanBuyDownFeeAmortizationDetails()`
+4. **Response Assembly**: Returns array of `BuyDownFeeAmortizationDetails`
objects
+5. **Security**: Ensures authenticated user context through
`PlatformSecurityContext`
+
+=== Validation Rules
+
+**Common Validations**:
+
+- Buy Down Fee must be enabled for the loan product
+- Loan must be in active status
+- Client/Group must be active
+- Transaction date cannot be in the future
+- Transaction amount must be positive
+
+**Create Transaction Validations**:
+
+- Transaction date cannot be before first disbursement date
+- Loan must be disbursed
+- Only one Buy Down Fee transaction per loan (unless adjusted)
Review Comment:
We can have more than one buy down fee per loan
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]