FINERACT-1968: Enhance Loan product template with advanced payment allocation

This commit is contained in:
Adam Saghy
2023-09-08 14:45:44 +02:00
parent c04f435b1a
commit 4b02527561
8 changed files with 193 additions and 32 deletions
@@ -18,8 +18,23 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
@Getter
@RequiredArgsConstructor
public enum FutureInstallmentAllocationRule {
NEXT_INSTALLMENT, //
LAST_INSTALLMENT, //
REAMORTIZATION //
NEXT_INSTALLMENT("Next installment"), //
LAST_INSTALLMENT("Last installment"), //
REAMORTIZATION("Reamortization"); //
private final String humanReadableName;
public static List<EnumOptionData> getValuesAsEnumOptionDataList() {
return Arrays.stream(values()).map(v -> new EnumOptionData((long) (v.ordinal() + 1), v.name(), v.getHumanReadableName())).toList();
}
}
@@ -18,24 +18,31 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
@Getter
@RequiredArgsConstructor
public enum PaymentAllocationTransactionType {
DEFAULT(null), //
REPAYMENT(LoanTransactionType.REPAYMENT), //
DOWN_PAYMENT(LoanTransactionType.DOWN_PAYMENT), //
MERCHANT_ISSUED_REFUND(LoanTransactionType.MERCHANT_ISSUED_REFUND), //
PAYOUT_REFUND(LoanTransactionType.PAYOUT_REFUND), //
GOODWILL_CREDIT(LoanTransactionType.GOODWILL_CREDIT), //
CHARGE_REFUND(LoanTransactionType.CHARGE_REFUND), //
CHARGE_ADJUSTMENT(LoanTransactionType.CHARGE_ADJUSTMENT), //
WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST),//
; //
DEFAULT(null, "Default"), //
REPAYMENT(LoanTransactionType.REPAYMENT, "Repayment"), //
DOWN_PAYMENT(LoanTransactionType.DOWN_PAYMENT, "Down payment"), //
MERCHANT_ISSUED_REFUND(LoanTransactionType.MERCHANT_ISSUED_REFUND, "Merchant issued refund"), //
PAYOUT_REFUND(LoanTransactionType.PAYOUT_REFUND, "Payout refund"), //
GOODWILL_CREDIT(LoanTransactionType.GOODWILL_CREDIT, "Goodwill credit"), //
CHARGE_REFUND(LoanTransactionType.CHARGE_REFUND, "Charge refund"), //
CHARGE_ADJUSTMENT(LoanTransactionType.CHARGE_ADJUSTMENT, "Charge adjustment"), //
WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST, "Waive interest");//
private final LoanTransactionType loanTransactionType;
private final String humanReadableName;
public static List<EnumOptionData> getValuesAsEnumOptionDataList() {
return Arrays.stream(values()).map(v -> new EnumOptionData((long) (v.ordinal() + 1), v.name(), v.getHumanReadableName())).toList();
}
}
@@ -26,27 +26,35 @@ import static org.apache.fineract.portfolio.loanproduct.domain.DueType.DUE;
import static org.apache.fineract.portfolio.loanproduct.domain.DueType.IN_ADVANCE;
import static org.apache.fineract.portfolio.loanproduct.domain.DueType.PAST_DUE;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
@Getter
@RequiredArgsConstructor
public enum PaymentAllocationType {
PAST_DUE_PENALTY(PAST_DUE, PENALTY), //
PAST_DUE_FEE(PAST_DUE, FEE), //
PAST_DUE_PRINCIPAL(PAST_DUE, PRINCIPAL), //
PAST_DUE_INTEREST(PAST_DUE, INTEREST), //
DUE_PENALTY(DUE, PENALTY), //
DUE_FEE(DUE, FEE), //
DUE_PRINCIPAL(DUE, PRINCIPAL), //
DUE_INTEREST(DUE, INTEREST), //
IN_ADVANCE_PENALTY(IN_ADVANCE, PENALTY), //
IN_ADVANCE_FEE(IN_ADVANCE, FEE), //
IN_ADVANCE_PRINCIPAL(IN_ADVANCE, PRINCIPAL), //
IN_ADVANCE_INTEREST(IN_ADVANCE, INTEREST); //
PAST_DUE_PENALTY(PAST_DUE, PENALTY, "Past due penalty"), //
PAST_DUE_FEE(PAST_DUE, FEE, "Past due fee"), //
PAST_DUE_PRINCIPAL(PAST_DUE, PRINCIPAL, "Past due principal"), //
PAST_DUE_INTEREST(PAST_DUE, INTEREST, "Past due interest"), //
DUE_PENALTY(DUE, PENALTY, "Due penalty"), //
DUE_FEE(DUE, FEE, "Due fee"), //
DUE_PRINCIPAL(DUE, PRINCIPAL, "Due principal"), //
DUE_INTEREST(DUE, INTEREST, "Due interest"), //
IN_ADVANCE_PENALTY(IN_ADVANCE, PENALTY, "In advance penalty"), //
IN_ADVANCE_FEE(IN_ADVANCE, FEE, "In advance fee"), //
IN_ADVANCE_PRINCIPAL(IN_ADVANCE, PRINCIPAL, "In advance principal"), //
IN_ADVANCE_INTEREST(IN_ADVANCE, INTEREST, "In advanced interest"); //
private final DueType dueType;
private final AllocationType allocationType;
private final String humanReadableName;
public static List<EnumOptionData> getValuesAsEnumOptionDataList() {
return Arrays.stream(values()).map(v -> new EnumOptionData((long) (v.ordinal() + 1), v.name(), v.getHumanReadableName())).toList();
}
}
@@ -79,6 +79,9 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixData;
import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixReadPlatformService;
@@ -407,6 +410,11 @@ public class LoanProductsApiResource {
.retrievePreCloseInterestCalculationStrategyOptions();
final List<FloatingRateData> floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive();
final List<EnumOptionData> repaymentStartDateTypeOptions = dropdownReadPlatformService.retrieveRepaymentStartDateTypeOptions();
final List<EnumOptionData> advancedPaymentAllocationTransactionTypes = PaymentAllocationTransactionType
.getValuesAsEnumOptionDataList();
final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules = FutureInstallmentAllocationRule
.getValuesAsEnumOptionDataList();
final List<EnumOptionData> advancedPaymentAllocationTypes = PaymentAllocationType.getValuesAsEnumOptionDataList();
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -414,7 +422,9 @@ public class LoanProductsApiResource {
loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions,
preCloseInterestCalculationStrategyOptions, floatingRateOptions, interestRecalculationNthDayTypeOptions,
interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, delinquencyBucketOptions, repaymentStartDateTypeOptions);
interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, delinquencyBucketOptions, repaymentStartDateTypeOptions,
advancedPaymentAllocationTransactionTypes, advancedPaymentAllocationFutureInstallmentAllocationRules,
advancedPaymentAllocationTypes);
}
}
@@ -813,7 +813,7 @@ final class LoanProductsApiResourceSwagger {
public Boolean penalty;
public GetLoanProductsCurrencyOptions currency;
@Schema(example = "100")
public Long amount;
public BigDecimal amount;
public GetLoanChargeTimeType chargeTimeType;
public GetLoanProductsChargeAppliesTo chargeAppliesTo;
public GetLoanChargeCalculationType chargeCalculationType;
@@ -865,7 +865,7 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "over payment")
public String name;
@Schema(example = "13")
public Integer glCode;
public String glCode;
@Schema(example = "false")
public Boolean disabled;
@Schema(example = "true")
@@ -888,7 +888,7 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "Loan portfolio")
public String name;
@Schema(example = "02")
public Integer glCode;
public String glCode;
@Schema(example = "false")
public Boolean disabled;
@Schema(example = "true")
@@ -923,7 +923,7 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "loans written off 2")
public String name;
@Schema(example = "12")
public Integer glCode;
public String glCode;
@Schema(example = "false")
public Boolean disabled;
@Schema(example = "true")
@@ -958,7 +958,7 @@ final class LoanProductsApiResourceSwagger {
@Schema(example = "income from interest")
public String name;
@Schema(example = "04")
public Integer glCode;
public String glCode;
@Schema(example = "false")
public Boolean disabled;
@Schema(example = "true")
@@ -1031,6 +1031,9 @@ final class LoanProductsApiResourceSwagger {
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsRescheduleStrategyType> rescheduleStrategyTypeOptions;
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsInterestRecalculationCompoundingFrequencyType> interestRecalculationFrequencyTypeOptions;
public Set<GetLoanProductsResponse.GetLoanProductsRepaymentStartDateType> repaymentStartDateTypeOptions;
public List<EnumOptionData> advancedPaymentAllocationTransactionTypes;
public List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules;
public List<EnumOptionData> advancedPaymentAllocationTypes;
}
@Schema(description = "GetLoanProductsProductIdResponse")
@@ -167,6 +167,9 @@ public class LoanProductData implements Serializable {
private final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions;
private final List<EnumOptionData> rescheduleStrategyTypeOptions;
private final List<EnumOptionData> preClosureInterestCalculationStrategyOptions;
private final List<EnumOptionData> advancedPaymentAllocationTransactionTypes;
private final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules;
private final List<EnumOptionData> advancedPaymentAllocationTypes;
private final List<EnumOptionData> interestRecalculationFrequencyTypeOptions;
private final List<FloatingRateData> floatingRateOptions;
@@ -835,6 +838,9 @@ public class LoanProductData implements Serializable {
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
this.repaymentStartDateType = repaymentStartDateType;
this.repaymentStartDateTypeOptions = null;
this.advancedPaymentAllocationTransactionTypes = null;
this.advancedPaymentAllocationFutureInstallmentAllocationRules = null;
this.advancedPaymentAllocationTypes = null;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -851,7 +857,10 @@ public class LoanProductData implements Serializable {
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> floatingRateOptions,
final List<EnumOptionData> interestRecalculationNthDayTypeOptions,
final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled,
final Collection<DelinquencyBucketData> delinquencyBucketOptions, final List<EnumOptionData> repaymentStartDateTypeOptions) {
final Collection<DelinquencyBucketData> delinquencyBucketOptions, final List<EnumOptionData> repaymentStartDateTypeOptions,
final List<EnumOptionData> advancedPaymentAllocationTransactionTypes,
final List<EnumOptionData> advancedPaymentAllocationFutureInstallmentAllocationRules,
final List<EnumOptionData> advancedPaymentAllocationTypes) {
this.id = productData.id;
this.name = productData.name;
this.shortName = productData.shortName;
@@ -992,6 +1001,9 @@ public class LoanProductData implements Serializable {
this.paymentAllocation = productData.paymentAllocation;
this.repaymentStartDateType = productData.repaymentStartDateType;
this.repaymentStartDateTypeOptions = repaymentStartDateTypeOptions;
this.advancedPaymentAllocationTransactionTypes = advancedPaymentAllocationTransactionTypes;
this.advancedPaymentAllocationFutureInstallmentAllocationRules = advancedPaymentAllocationFutureInstallmentAllocationRules;
this.advancedPaymentAllocationTypes = advancedPaymentAllocationTypes;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -0,0 +1,101 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.apache.fineract.client.models.GetLoanProductsTemplateResponse;
import org.apache.fineract.integrationtests.common.loans.LoanProductHelper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class LoanProductTemplateTest {
private static LoanProductHelper loanProductHelper;
@BeforeAll
public static void setup() {
loanProductHelper = new LoanProductHelper();
}
@Test
public void testLoanProductTemplateForAdvancedPaymentAllocation() {
GetLoanProductsTemplateResponse loanProductsTemplateResponse = loanProductHelper.getLoanProductTemplate(false);
// assert payment allocation types
assertEquals("PAST_DUE_PENALTY", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(0).getCode());
assertEquals("Past due penalty", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(0).getValue());
assertEquals("PAST_DUE_FEE", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(1).getCode());
assertEquals("Past due fee", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(1).getValue());
assertEquals("PAST_DUE_PRINCIPAL", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(2).getCode());
assertEquals("Past due principal", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(2).getValue());
assertEquals("PAST_DUE_INTEREST", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(3).getCode());
assertEquals("Past due interest", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(3).getValue());
assertEquals("DUE_PENALTY", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(4).getCode());
assertEquals("Due penalty", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(4).getValue());
assertEquals("DUE_FEE", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(5).getCode());
assertEquals("Due fee", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(5).getValue());
assertEquals("DUE_PRINCIPAL", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(6).getCode());
assertEquals("Due principal", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(6).getValue());
assertEquals("DUE_INTEREST", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(7).getCode());
assertEquals("Due interest", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(7).getValue());
assertEquals("IN_ADVANCE_PENALTY", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(8).getCode());
assertEquals("In advance penalty", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(8).getValue());
assertEquals("IN_ADVANCE_FEE", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(9).getCode());
assertEquals("In advance fee", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(9).getValue());
assertEquals("IN_ADVANCE_PRINCIPAL", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(10).getCode());
assertEquals("In advance principal", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(10).getValue());
assertEquals("IN_ADVANCE_INTEREST", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(11).getCode());
assertEquals("In advanced interest", loanProductsTemplateResponse.getAdvancedPaymentAllocationTypes().get(11).getValue());
// assert payment allocation transaction types
assertEquals("DEFAULT", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(0).getCode());
assertEquals("Default", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(0).getValue());
assertEquals("REPAYMENT", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(1).getCode());
assertEquals("Repayment", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(1).getValue());
assertEquals("DOWN_PAYMENT", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(2).getCode());
assertEquals("Down payment", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(2).getValue());
assertEquals("MERCHANT_ISSUED_REFUND",
loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(3).getCode());
assertEquals("Merchant issued refund",
loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(3).getValue());
assertEquals("PAYOUT_REFUND", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(4).getCode());
assertEquals("Payout refund", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(4).getValue());
assertEquals("GOODWILL_CREDIT", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(5).getCode());
assertEquals("Goodwill credit", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(5).getValue());
assertEquals("CHARGE_REFUND", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(6).getCode());
assertEquals("Charge refund", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(6).getValue());
assertEquals("CHARGE_ADJUSTMENT", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(7).getCode());
assertEquals("Charge adjustment", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(7).getValue());
assertEquals("WAIVE_INTEREST", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(8).getCode());
assertEquals("Waive interest", loanProductsTemplateResponse.getAdvancedPaymentAllocationTransactionTypes().get(8).getValue());
// assert future installment rules
assertEquals("NEXT_INSTALLMENT",
loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(0).getCode());
assertEquals("Next installment",
loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(0).getValue());
assertEquals("LAST_INSTALLMENT",
loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(1).getCode());
assertEquals("Last installment",
loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(1).getValue());
assertEquals("REAMORTIZATION",
loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(2).getCode());
assertEquals("Reamortization",
loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(2).getValue());
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.integrationtests.common.loans;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoanProductsTemplateResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
@@ -40,4 +41,8 @@ public class LoanProductHelper extends IntegrationTest {
public PutLoanProductsProductIdResponse updateLoanProductByExternalId(String externalId, PutLoanProductsProductIdRequest request) {
return ok(fineract().loanProducts.updateLoanProduct1(externalId, request));
}
public GetLoanProductsTemplateResponse getLoanProductTemplate(boolean isProductMixTemplate) {
return ok(fineract().loanProducts.retrieveTemplate11(isProductMixTemplate));
}
}