Skip to content

Commit de1b1a1

Browse files
committed
FINERACT-2455: Update loan details and balances
1 parent fc6f3cc commit de1b1a1

67 files changed

Lines changed: 1576 additions & 1513 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

fineract-doc/src/docs/en/chapters/features/working-capital-amortization-schedule.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ The amortization schedule model is calculated from these loan-level parameters:
233233

234234
| `originationFeeAmount` | Money | Upfront discount/fee. Amortized over lifecycle.
235235
| `netDisbursementAmount` | Money | Principal disbursed. Must be positive.
236-
| `totalPaymentValue` | Money | Merchant's TPV.
236+
| `totalPaymentVolume` | Money | Merchant's TPV.
237237
| `periodPaymentRate` | BigDecimal | Rate applied to TPV (e.g., 0.18 = 18%).
238238
| `npvDayCount` | Integer | Day-count convention (e.g., 360). Must be positive.
239239
| `expectedDisbursementDate` | Date | Loan start date.
@@ -539,7 +539,7 @@ Creates a new Working Capital loan product with EIR amortization configuration.
539539

540540
=== POST /v1/working-capital-loans
541541

542-
Creates a new Working Capital loan application. Required fields include: `productId`, `clientId`, `principal`, `totalPayment`, `periodPaymentRate`, `submittedOnDate`, `expectedDisbursementDate`.
542+
Creates a new Working Capital loan application. Required fields include: `productId`, `clientId`, `principal`, `totalPaymentVolume`, `periodPaymentRate`, `submittedOnDate`, `expectedDisbursementDate`.
543543

544544
=== POST /v1/working-capital-loans/{loanId}?command=approve
545545

@@ -573,7 +573,7 @@ Example Response (first 3 payments shown):
573573
{
574574
"originationFeeAmount": 1000.00,
575575
"netDisbursementAmount": 9000.00,
576-
"totalPaymentValue": 100000.00,
576+
"totalPaymentVolume": 100000.00,
577577
"periodPaymentRate": 0.18,
578578
"npvDayCount": 360,
579579
"expectedDisbursementDate": "2019-01-01",

fineract-doc/src/docs/en/chapters/features/working-capital-eir-calculation.adoc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ GET /v1/working-capital-loans/{loanId}/amortization-schedule
182182
{
183183
"discountFeeAmount": 5000.00,
184184
"netDisbursementAmount": 95000.00,
185-
"totalPaymentValue": 105000.00,
185+
"totalPaymentVolume": 105000.00,
186186
"periodPaymentRate": 0.0027778,
187187
"npvDayCount": 360,
188188
"expectedDisbursementDate": "2024-01-15",
@@ -276,7 +276,7 @@ GET /v1/working-capital-loans/external-id/{loanExternalId}/rate-changes
276276

277277
=== EIR Computation
278278

279-
* `expectedPayment = (totalPaymentValue × periodPaymentRate) / npvDayCount`
279+
* `expectedPayment = (totalPaymentVolume × periodPaymentRate) / npvDayCount`
280280
* `originalPaymentNumber = ceil((netDisbursementAmount + discountFeeAmount) / expectedPayment)`
281281
* `EIR = TvmFunctions.rate(originalPaymentNumber, −expectedPayment, netDisbursementAmount)`
282282
* The Newton-Raphson solver converges to a tolerance of `1E-12` with a maximum of 500 iterations.
@@ -324,7 +324,7 @@ The `ProjectedAmortizationScheduleModel` progresses through four operations:
324324

325325
**Setup:**
326326
* Loan amount: 100,000; discount fee: 5,000 (deducted upfront)
327-
* `periodPaymentRate = 0.0027778`, `npvDayCount = 360`, `totalPaymentValue = 105,000`
327+
* `periodPaymentRate = 0.0027778`, `npvDayCount = 360`, `totalPaymentVolume = 105,000`
328328

329329
**Action:**
330330
The system computes `expectedPayment = (105,000 × 0.0027778) / 360 ≈ 291.67` and `originalPaymentNumber = ceil(100,000 / 291.67) = 344`. EIR is solved via Newton-Raphson: `EIR = RATE(344, −291.67, 95,000)`. The schedule is stored in `m_wc_loan_amortization_model` with one row per day (344 payment rows plus the disbursement row).
@@ -356,7 +356,7 @@ The system computes `expectedPayment = (105,000 × 0.0027778) / 360 ≈ 291.67`
356356

357357
Working Capital Loan EIR calculation provides time-value-of-money-based income recognition for revolving credit. Key aspects include:
358358

359-
* EIR is derived analytically using Newton-Raphson from `totalPaymentValue`, `periodPaymentRate`, `npvDayCount`, and `discountFeeAmount`.
359+
* EIR is derived analytically using Newton-Raphson from `totalPaymentVolume`, `periodPaymentRate`, `npvDayCount`, and `discountFeeAmount`.
360360
* The full amortization schedule is serialized to JSON in `m_wc_loan_amortization_model` for efficient retrieval without recomputation.
361361
* Each scheduled payment carries a `discountFactor` and `npvValue`, enabling present-value-based income recognition through the `deferredBalance` amortization mechanism.
362362
* Mid-lifecycle rate changes are handled via `RateSegment` splits, preserving historical actuals while recalculating only future payments with the new EIR.

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalLoanRequestFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public PostWorkingCapitalLoansRequest defaultWorkingCapitalLoansRequest(Long cli
5858
.submittedOnDate(DATE_SUBMIT_STRING)//
5959
.expectedDisbursementDate(DATE_SUBMIT_STRING)//
6060
.principalAmount(DEFAULT_PRINCIPAL)//
61-
.totalPayment(DEFAULT_TOTAL_PAYMENT)//
61+
.totalPaymentVolume(DEFAULT_TOTAL_PAYMENT)//
6262
.periodPaymentRate(DEFAULT_PERIOD_PAYMENT_RATE)//
6363
.discount(DEFAULT_DISCOUNT_ZERO)//
6464
.locale(DEFAULT_LOCALE)//

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkingCapitalLoanTestHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public Long insertLoan(final LoanStatus status, final LocalDate lastClosedBusine
8181
.addValue("principal", DEFAULT_PRINCIPAL)//
8282
.addValue("principal_amount_proposed", DEFAULT_PRINCIPAL)//
8383
.addValue("approved_principal", DEFAULT_PRINCIPAL)//
84-
.addValue("total_payment", DEFAULT_PRINCIPAL);
84+
.addValue("total_payment_volume", DEFAULT_PRINCIPAL);
8585
final Number key = wcLoanInsert.executeAndReturnKey(params);
8686
return Objects.requireNonNull(key, "Generated key must not be null").longValue();
8787
}

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
4646
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
4747
import org.apache.fineract.client.models.GetWorkingCapitalLoanTransactionIdResponse;
48-
import org.apache.fineract.client.models.GetWorkingCapitalLoansLoanIdResponse;
48+
import org.apache.fineract.client.models.GetWorkingCapitalLoanTransactionsResponse;
4949
import org.apache.fineract.client.models.GlobalConfigurationPropertyData;
5050
import org.apache.fineract.client.models.PageExternalTransferData;
5151
import org.apache.fineract.client.models.PostClientsResponse;
@@ -307,13 +307,13 @@ public void workingCapitalLoanDisbursalTransactionEventCheck(final Long loanId)
307307

308308
public void workingCapitalLoanDisbursalTransactionEventCheck(final Long loanId, final BigDecimal expectedAmount) {
309309
waitForTransactionCommit();
310-
final GetWorkingCapitalLoansLoanIdResponse body = ok(
311-
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
312-
if (body.getTransactions() == null || body.getTransactions().isEmpty()) {
310+
final GetWorkingCapitalLoanTransactionsResponse body = ok(
311+
() -> fineractClient.workingCapitalLoanTransactions().retrieveWorkingCapitalLoanTransactionsById(loanId));
312+
if (body.getContent() == null || body.getContent().isEmpty()) {
313313
throw new IllegalStateException("No Working Capital Loan transactions found");
314314
}
315315

316-
final GetWorkingCapitalLoanTransactionIdResponse disbursementTransaction = body.getTransactions().stream()
316+
final GetWorkingCapitalLoanTransactionIdResponse disbursementTransaction = body.getContent().stream()
317317
.filter(t -> t.getType() != null && "loanTransactionType.disbursement".equals(t.getType().getCode())
318318
&& !Boolean.TRUE.equals(t.getReversed()))
319319
.reduce((first, second) -> second).orElseThrow(() -> new IllegalStateException("Disbursement transaction not found"));
@@ -327,13 +327,13 @@ public void workingCapitalLoanDisbursalTransactionEventCheck(final Long loanId,
327327

328328
public void workingCapitalLoanCreditBalanceRefundTransactionEventCheck(final Long loanId, final BigDecimal expectedAmount) {
329329
waitForTransactionCommit();
330-
final GetWorkingCapitalLoansLoanIdResponse body = ok(
331-
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
332-
if (body.getTransactions() == null || body.getTransactions().isEmpty()) {
330+
final GetWorkingCapitalLoanTransactionsResponse body = ok(
331+
() -> fineractClient.workingCapitalLoanTransactions().retrieveWorkingCapitalLoanTransactionsById(loanId));
332+
if (body.getContent() == null || body.getContent().isEmpty()) {
333333
throw new IllegalStateException("No Working Capital Loan transactions found");
334334
}
335335

336-
final GetWorkingCapitalLoanTransactionIdResponse cbrTransaction = body.getTransactions().stream()
336+
final GetWorkingCapitalLoanTransactionIdResponse cbrTransaction = body.getContent().stream()
337337
.filter(t -> t.getType() != null && "loanTransactionType.creditBalanceRefund".equals(t.getType().getCode())
338338
&& !Boolean.TRUE.equals(t.getReversed()))
339339
.reduce((first, second) -> second)
@@ -352,13 +352,13 @@ public void workingCapitalLoanUndoDisbursalTransactionEventCheck(final Long loan
352352

353353
public void workingCapitalLoanUndoDisbursalTransactionEventCheck(final Long loanId, final BigDecimal expectedAmount) {
354354
waitForTransactionCommit();
355-
final GetWorkingCapitalLoansLoanIdResponse body = ok(
356-
() -> fineractClient.workingCapitalLoans().retrieveWorkingCapitalLoanById(loanId));
357-
if (body.getTransactions() == null || body.getTransactions().isEmpty()) {
355+
final GetWorkingCapitalLoanTransactionsResponse body = ok(
356+
() -> fineractClient.workingCapitalLoanTransactions().retrieveWorkingCapitalLoanTransactionsById(loanId));
357+
if (body.getContent() == null || body.getContent().isEmpty()) {
358358
throw new IllegalStateException("No Working Capital Loan transactions found");
359359
}
360360

361-
final GetWorkingCapitalLoanTransactionIdResponse reversedDisbursementTransaction = body.getTransactions().stream()
361+
final GetWorkingCapitalLoanTransactionIdResponse reversedDisbursementTransaction = body.getContent().stream()
362362
.filter(t -> t.getType() != null && "loanTransactionType.disbursement".equals(t.getType().getCode())
363363
&& Boolean.TRUE.equals(t.getReversed()))
364364
.reduce((first, second) -> second)

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,16 @@ public class WorkingCapitalAmortizationScheduleStepDef extends AbstractStepDef {
4949

5050
private final FineractFeignClient fineractFeignClient;
5151

52-
@When("Admin generates a projected amortization schedule with discountFeeAmount {double}, netDisbursementAmount {double}, totalPaymentValue {double}, periodPaymentRate {double}, npvDayCount {int}, expectedDisbursementDate {string}")
52+
@When("Admin generates a projected amortization schedule with discountFeeAmount {double}, netDisbursementAmount {double}, totalPaymentVolume {double}, periodPaymentRate {double}, npvDayCount {int}, expectedDisbursementDate {string}")
5353
public void generateAmortizationSchedule(final double discountFeeAmount, final double netDisbursementAmount,
54-
final double totalPaymentValue, final double periodPaymentRate, final int npvDayCount, final String expectedDisbursementDate) {
54+
final double totalPaymentVolume, final double periodPaymentRate, final int npvDayCount, final String expectedDisbursementDate) {
5555
final Long loanId = extractLoanId();
5656
final WorkingCapitalLoansApi api = fineractFeignClient.create(WorkingCapitalLoansApi.class);
5757

5858
final ProjectedAmortizationScheduleGenerateRequest request = new ProjectedAmortizationScheduleGenerateRequest();
5959
request.setDiscountFeeAmount(BigDecimal.valueOf(discountFeeAmount));
6060
request.setNetDisbursementAmount(BigDecimal.valueOf(netDisbursementAmount));
61-
request.setTotalPaymentValue(BigDecimal.valueOf(totalPaymentValue));
61+
request.setTotalPaymentVolume(BigDecimal.valueOf(totalPaymentVolume));
6262
request.setPeriodPaymentRate(BigDecimal.valueOf(periodPaymentRate));
6363
request.setNpvDayCount(npvDayCount);
6464
request.setExpectedDisbursementDate(LocalDate.parse(expectedDisbursementDate));
@@ -96,7 +96,7 @@ private void verifySummaryFields(final DataTable dataTable) {
9696

9797
assertDecimal(assertions, "discountFeeAmount", response.getDiscountFeeAmount(), expected.get("discountFeeAmount"));
9898
assertDecimal(assertions, "netDisbursementAmount", response.getNetDisbursementAmount(), expected.get("netDisbursementAmount"));
99-
assertDecimal(assertions, "totalPaymentValue", response.getTotalPaymentValue(), expected.get("totalPaymentValue"));
99+
assertDecimal(assertions, "totalPaymentVolume", response.getTotalPaymentVolume(), expected.get("totalPaymentVolume"));
100100
assertDecimal(assertions, "periodPaymentRate", response.getPeriodPaymentRate(), expected.get("periodPaymentRate"));
101101
assertInt(assertions, "npvDayCount", response.getNpvDayCount(), expected.get("npvDayCount"));
102102
assertDecimal(assertions, "expectedPaymentAmount", response.getExpectedPaymentAmount(), expected.get("expectedPaymentAmount"));

0 commit comments

Comments
 (0)