Skip to content

Commit aff009c

Browse files
Jose Alberto Hernandezadamsaghy
authored andcommitted
FINERTACT-1724: Advanced payment allocation fixes for Loan Schedule
1 parent 834cfb6 commit aff009c

26 files changed

Lines changed: 181 additions & 38 deletions

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,21 @@ public Map<String, Object> update(final JsonCommand command, final AprCalculator
352352
this.currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
353353
}
354354

355+
final String loanScheduleTypeParamName = LoanProductConstants.LOAN_SCHEDULE_TYPE;
356+
if (command.isChangeInStringParameterNamed(loanScheduleTypeParamName, loanScheduleType.toString())) {
357+
LoanScheduleType newLoanScheduleType = LoanScheduleType.valueOf(command.stringValueOfParameterNamed(loanScheduleTypeParamName));
358+
actualChanges.put(loanScheduleTypeParamName, newLoanScheduleType);
359+
loanScheduleType = newLoanScheduleType;
360+
}
361+
362+
final String loanScheduleProcessingTypeParamName = LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE;
363+
if (command.isChangeInStringParameterNamed(loanScheduleProcessingTypeParamName, loanScheduleProcessingType.toString())) {
364+
LoanScheduleProcessingType newLoanScheduleProcessingType = LoanScheduleProcessingType
365+
.valueOf(command.stringValueOfParameterNamed(loanScheduleProcessingTypeParamName));
366+
actualChanges.put(loanScheduleProcessingTypeParamName, newLoanScheduleProcessingType);
367+
loanScheduleProcessingType = newLoanScheduleProcessingType;
368+
}
369+
355370
final Map<String, Object> loanApplicationAttributeChanges = updateLoanApplicationAttributes(command, aprCalculator);
356371

357372
actualChanges.putAll(loanApplicationAttributeChanges);

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
4242
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
4343
import org.apache.fineract.infrastructure.core.service.DateUtils;
44-
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
4544
import org.apache.fineract.organisation.holiday.domain.Holiday;
4645
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
4746
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
@@ -137,7 +136,6 @@ public class LoanScheduleAssembler {
137136
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
138137
private final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator;
139138
private final CalendarInstanceRepository calendarInstanceRepository;
140-
private final PlatformSecurityContext context;
141139
private final LoanUtilService loanUtilService;
142140

143141
@Autowired
@@ -150,8 +148,7 @@ public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanP
150148
final WorkingDaysRepositoryWrapper workingDaysRepository,
151149
final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
152150
final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator,
153-
final CalendarInstanceRepository calendarInstanceRepository, final PlatformSecurityContext context,
154-
final LoanUtilService loanUtilService) {
151+
final CalendarInstanceRepository calendarInstanceRepository, final LoanUtilService loanUtilService) {
155152
this.fromApiJsonHelper = fromApiJsonHelper;
156153
this.loanProductRepository = loanProductRepository;
157154
this.applicationCurrencyRepository = applicationCurrencyRepository;
@@ -167,7 +164,6 @@ public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanP
167164
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
168165
this.variableLoanScheduleFromApiJsonValidator = variableLoanScheduleFromApiJsonValidator;
169166
this.calendarInstanceRepository = calendarInstanceRepository;
170-
this.context = context;
171167
this.loanUtilService = loanUtilService;
172168
}
173169

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -781,16 +781,27 @@ public void validateForCreate(final JsonCommand command) {
781781
.value(enableInstallmentLevelDelinquency).ignoreIfNull().validateForBooleanValue();
782782
}
783783

784-
final String loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element);
785-
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType).ignoreIfNull()
786-
.isOneOfEnumValues(LoanScheduleType.class);
784+
String loanScheduleType = LoanScheduleType.CUMULATIVE.name();
785+
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)) {
786+
loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element);
787+
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType)
788+
.isOneOfEnumValues(LoanScheduleType.class);
789+
}
790+
791+
if (!LoanScheduleType.PROGRESSIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
792+
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
793+
.equals(transactionProcessingStrategyCode)) {
794+
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).failWithCode(
795+
"supported.only.for.progressive.loan.schedule.type",
796+
"Progressive repayment schedule processing is only available with `Advanced payment allocation` strategy");
797+
}
787798

788799
String loanScheduleProcessingType = LoanScheduleProcessingType.HORIZONTAL.name();
789800
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) {
790801
loanScheduleProcessingType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
791802
element);
792803
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).value(loanScheduleProcessingType)
793-
.ignoreIfNull().isOneOfEnumValues(LoanScheduleProcessingType.class);
804+
.isOneOfEnumValues(LoanScheduleProcessingType.class);
794805

795806
if (LoanScheduleProcessingType.VERTICAL.equals(LoanScheduleProcessingType.valueOf(loanScheduleProcessingType))
796807
&& !AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
@@ -1784,18 +1795,27 @@ public void validateForUpdate(final JsonCommand command, final LoanProduct loanP
17841795
.value(enableInstallmentLevelDelinquency).ignoreIfNull().validateForBooleanValue();
17851796
}
17861797

1798+
String loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType().name();
17871799
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)) {
1788-
final String loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element);
1789-
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType).ignoreIfNull()
1800+
loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element);
1801+
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType)
17901802
.isOneOfEnumValues(LoanScheduleType.class);
1803+
1804+
if (!LoanScheduleType.PROGRESSIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
1805+
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
1806+
.equals(transactionProcessingStrategyCode)) {
1807+
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).failWithCode(
1808+
"supported.only.for.progressive.loan.schedule.type",
1809+
"Progressive repayment schedule processing is only available with `Advanced payment allocation` strategy");
1810+
}
17911811
}
17921812

17931813
String loanScheduleProcessingType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleProcessingType().name();
17941814
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) {
17951815
loanScheduleProcessingType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
17961816
element);
17971817
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).value(loanScheduleProcessingType)
1798-
.ignoreIfNull().isOneOfEnumValues(LoanScheduleProcessingType.class);
1818+
.isOneOfEnumValues(LoanScheduleProcessingType.class);
17991819
}
18001820

18011821
List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules();

integration-tests/src/test/java/org/apache/fineract/integrationtests/AccountTransferTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
5151
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
5252
import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
53+
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
5354
import org.junit.jupiter.api.AfterEach;
5455
import org.junit.jupiter.api.Assertions;
5556
import org.junit.jupiter.api.BeforeEach;
@@ -479,6 +480,7 @@ private Integer createLoanProduct(final Account... accounts) {
479480
.withAmortizationTypeAsEqualInstallments() //
480481
.withInterestTypeAsDecliningBalance() //
481482
.withAccountingRuleAsCashBased(accounts)//
483+
.withLoanScheduleType(LoanScheduleType.CUMULATIVE)//
482484
.build(null);
483485
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
484486
}

integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2171,7 +2171,7 @@ public void uc108() {
21712171
});
21722172
}
21732173

2174-
// UC109: Advanced payment allocation, cumulative loan schedule handling, rounding test
2174+
// UC109: Advanced payment allocation, progressive loan schedule handling, rounding test
21752175
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
21762176
// 1. Create a Loan product, 1000 principal, across 3 installment
21772177
// 2. Submit the loan, and check the generated repayment schedule
@@ -2183,7 +2183,7 @@ public void uc109() {
21832183
final Account incomeAccount = accountHelper.createIncomeAccount();
21842184
final Account expenseAccount = accountHelper.createExpenseAccount();
21852185
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
2186-
Integer localLoanProductId = createLoanProduct("1000", "15", "3", false, null, false, LoanScheduleType.CUMULATIVE,
2186+
Integer localLoanProductId = createLoanProduct("1000", "15", "3", false, null, false, LoanScheduleType.PROGRESSIVE,
21872187
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
21882188
assertNotNull(localLoanProductId);
21892189

@@ -2310,7 +2310,7 @@ public void uc110() {
23102310
});
23112311
}
23122312

2313-
// UC111: Advanced payment allocation, cumulative loan schedule handling, rounding test
2313+
// UC111: Advanced payment allocation, progressive loan schedule handling, rounding test
23142314
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
23152315
// 1. Create a Loan product, 40.50 principal, across 4 installment (1 down payment, 3 normal installment)
23162316
// 2. Submit the loan, and check the generated repayment schedule
@@ -2322,7 +2322,7 @@ public void uc111() {
23222322
final Account incomeAccount = accountHelper.createIncomeAccount();
23232323
final Account expenseAccount = accountHelper.createExpenseAccount();
23242324
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
2325-
Integer localLoanProductId = createLoanProduct("40.50", "15", "3", true, "25", false, LoanScheduleType.CUMULATIVE,
2325+
Integer localLoanProductId = createLoanProduct("40.50", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
23262326
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
23272327
assertNotNull(localLoanProductId);
23282328

@@ -3012,6 +3012,27 @@ public void uc118() {
30123012
});
30133013
}
30143014

3015+
// UC119: Advanced payment allocation with Loan Schedule as Cumulative
3016+
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
3017+
// 1. Create a Loan product with Adv. Pment. Alloc., but the loan schedule as Cumulative -> expect validation error
3018+
@Test
3019+
public void uc119() {
3020+
runAt("02 February 2023", () -> {
3021+
final Account assetAccount = accountHelper.createAssetAccount();
3022+
final Account incomeAccount = accountHelper.createIncomeAccount();
3023+
final Account expenseAccount = accountHelper.createExpenseAccount();
3024+
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
3025+
AdvancedPaymentData defaultPaymentAllocation = createDefaultPaymentAllocation();
3026+
3027+
ArrayList<HashMap<String, Object>> loanProductErrorData = createLoanProductGetError("500", "15", "4", false,
3028+
LoanScheduleType.CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, defaultPaymentAllocation, assetAccount,
3029+
incomeAccount, expenseAccount, overpaymentAccount);
3030+
assertNotNull(loanProductErrorData);
3031+
assertEquals("validation.msg.loanproduct.loanScheduleProcessingType.supported.only.for.progressive.loan.schedule.type",
3032+
loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
3033+
});
3034+
}
3035+
30153036
private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
30163037
Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
30173038
assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());

integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationWaiveLoanCharges.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
3636
import org.apache.fineract.integrationtests.common.ClientHelper;
3737
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
38+
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
39+
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
3840
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
3941
import org.junit.jupiter.api.Assertions;
4042
import org.junit.jupiter.api.Test;
@@ -53,7 +55,9 @@ public void testAddFeeAndWaiveAdvancedPaymentAllocationNoBackdated() {
5355
Long loanProductId = createLoanProductWithAdvancedAllocation();
5456
// Apply and Approve Loan
5557
Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 1,
56-
(req) -> req.setTransactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY));
58+
(req) -> req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
59+
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
60+
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()));
5761
// Disburse Loan
5862
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
5963
// Add Penalty
@@ -85,7 +89,9 @@ public void testAddPenaltyAndWaiveAdvancedPaymentAllocationNoBackDated() {
8589
Long loanProductId = createLoanProductWithAdvancedAllocation();
8690
// Apply and Approve Loan
8791
Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 1,
88-
(req) -> req.setTransactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY));
92+
(req) -> req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
93+
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
94+
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()));
8995
// Disburse Loan
9096
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
9197
// Add Penalty
@@ -117,8 +123,10 @@ public void testAddPenaltyAndWaiveAdvancedPaymentAllocationAndBackdatedRepayment
117123
Long loanProductId = createLoanProductWithAdvancedAllocation();
118124
// Apply and Approve Loan
119125
Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 1,
120-
(req) -> req.setTransactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY));
121-
// Disburse Loan
126+
(req) -> req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
127+
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
128+
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString())); // Disburse
129+
// Loan
122130
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
123131

124132
// set business date to
@@ -180,7 +188,9 @@ private List<PaymentAllocationOrder> getPaymentAllocationOrder(PaymentAllocation
180188

181189
protected Long createLoanProductWithAdvancedAllocation() {
182190
PostLoanProductsRequest req = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
183-
req.setTransactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
191+
req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
192+
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
193+
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString());
184194
req.addPaymentAllocationItem(createDefaultPaymentAllocationWithMixedGrouping());
185195
PostLoanProductsResponse loanProduct = loanTransactionHelper.createLoanProduct(req);
186196
return loanProduct.getResourceId();

integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@
7171
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
7272
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
7373
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
74+
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
75+
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
7476
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
7577
import org.junit.jupiter.api.Assertions;
7678
import org.junit.jupiter.api.extension.ExtendWith;
@@ -145,6 +147,7 @@ protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAcc
145147
.interestCalculationPeriodType(1)//
146148
.transactionProcessingStrategyCode(
147149
LoanProductTestBuilder.DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST_STRATEGY)//
150+
.loanScheduleType(LoanScheduleType.CUMULATIVE.toString()) //
148151
.daysInYearType(1)//
149152
.daysInMonthType(1)//
150153
.canDefineInstallmentAmount(true)//
@@ -206,6 +209,8 @@ protected PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAcc
206209

207210
return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
208211
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy")//
212+
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
213+
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()) //
209214
.addPaymentAllocationItem(defaultAllocation);
210215
}
211216

0 commit comments

Comments
 (0)