This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new a3fdfb920d FINERACT-2312: full term configuration added
a3fdfb920d is described below
commit a3fdfb920d8857d10a6255746aee26d784963bff
Author: Attila Budai <[email protected]>
AuthorDate: Mon Dec 1 15:44:42 2025 +0100
FINERACT-2312: full term configuration added
---
.../loanaccount/api/LoanApiConstants.java | 2 +
.../loanaccount/data/LoanAccountData.java | 16 +-
.../portfolio/loanaccount/domain/Loan.java | 26 ++-
.../loanproduct/LoanProductConstants.java | 1 +
.../api/LoanProductsApiResourceSwagger.java | 6 +
.../loanproduct/data/LoanProductData.java | 133 ++++++++-------
.../portfolio/loanproduct/domain/LoanProduct.java | 9 +-
.../domain/LoanProductTrancheDetails.java | 6 +-
.../loanaccount/api/LoansApiResourceSwagger.java | 8 +
.../serialization/LoanApplicationValidator.java | 27 ++-
.../loanaccount/service/LoanAssemblerImpl.java | 20 ++-
.../loanaccount/service/LoanProductAssembler.java | 5 +-
.../LoanProductTrancheDetailsUpdateUtil.java | 9 +
.../service/LoanReadPlatformServiceImpl.java | 5 +-
.../serialization/LoanProductDataValidator.java | 40 ++++-
.../LoanProductReadPlatformServiceImpl.java | 22 +--
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../parts/0207_add_allow_full_term_for_tranche.xml | 39 +++++
.../portfolio/loanaccount/domain/LoanBuilder.java | 8 +-
.../LoanProductValidationStepDefinitions.java | 2 +-
.../LoanDisbursementDetailsIntegrationTest.java | 187 +++++++++++++++++++++
.../common/loans/LoanApplicationTestBuilder.java | 9 +
.../common/loans/LoanProductTestBuilder.java | 11 ++
23 files changed, 492 insertions(+), 100 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
index fa651a64cc..ace70a9b59 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java
@@ -195,6 +195,8 @@ public interface LoanApiConstants {
String INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE =
"interestRecognitionOnDisbursementDate";
+ String ALLOW_FULL_TERM_FOR_TRANCHE = "allowFullTermForTranche";
+
// Loan Summary Transaction Types
List<LoanTransactionType> LOAN_SUMMARY_TRANSACTION_TYPES =
List.of(LoanTransactionType.CHARGE_ADJUSTMENT, //
LoanTransactionType.CHARGEBACK, //
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
index bca5e59bd2..d97a52b9d1 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java
@@ -269,6 +269,7 @@ public class LoanAccountData {
private Boolean enableInstallmentLevelDelinquency;
private LocalDate lastClosedBusinessDate;
private Boolean chargedOff;
+ private Boolean allowFullTermForTranche;
private Boolean enableDownPayment;
private BigDecimal disbursedAmountPercentageForDownPayment;
@@ -480,12 +481,12 @@ public class LoanAccountData {
final BigDecimal disbursedAmountPercentageForDownPayment, final
boolean enableAutoRepaymentForDownPayment,
final boolean enableInstallmentLevelDelinquency, final
EnumOptionData loanScheduleType,
final EnumOptionData loanScheduleProcessingType, final Integer
fixedLength, final StringEnumOptionData chargeOffBehaviour,
- final boolean isInterestRecognitionOnDisbursementDate, final
StringEnumOptionData daysInYearCustomStrategy,
- final boolean enableIncomeCapitalization, final
StringEnumOptionData capitalizedIncomeCalculationType,
- final StringEnumOptionData capitalizedIncomeStrategy,
StringEnumOptionData capitalizedIncomeType,
- final boolean enableBuyDownFee, final StringEnumOptionData
buyDownFeeCalculationType,
- final StringEnumOptionData buyDownFeeStrategy, final
StringEnumOptionData buyDownFeeIncomeType,
- final boolean merchantBuyDownFee) {
+ final boolean isInterestRecognitionOnDisbursementDate, final
boolean allowFullTermForTranche,
+ final StringEnumOptionData daysInYearCustomStrategy, final boolean
enableIncomeCapitalization,
+ final StringEnumOptionData capitalizedIncomeCalculationType, final
StringEnumOptionData capitalizedIncomeStrategy,
+ StringEnumOptionData capitalizedIncomeType, final boolean
enableBuyDownFee,
+ final StringEnumOptionData buyDownFeeCalculationType, final
StringEnumOptionData buyDownFeeStrategy,
+ final StringEnumOptionData buyDownFeeIncomeType, final boolean
merchantBuyDownFee) {
final CollectionData delinquent = CollectionData.template();
@@ -531,7 +532,8 @@ public class LoanAccountData {
.setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency).setLoanScheduleType(loanScheduleType)
.setLoanScheduleProcessingType(loanScheduleProcessingType).setFixedLength(fixedLength)
.setChargeOffBehaviour(chargeOffBehaviour).setInterestRecognitionOnDisbursementDate(isInterestRecognitionOnDisbursementDate)
-
.setDaysInYearCustomStrategy(daysInYearCustomStrategy).setEnableIncomeCapitalization(enableIncomeCapitalization)
+
.setAllowFullTermForTranche(allowFullTermForTranche).setDaysInYearCustomStrategy(daysInYearCustomStrategy)
+ .setEnableIncomeCapitalization(enableIncomeCapitalization)
.setCapitalizedIncomeCalculationType(capitalizedIncomeCalculationType)
.setCapitalizedIncomeStrategy(capitalizedIncomeStrategy).setCapitalizedIncomeType(capitalizedIncomeType)
.setEnableBuyDownFee(enableBuyDownFee).setBuyDownFeeCalculationType(buyDownFeeCalculationType)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 601b8c6bba..d8bfda8bc6 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -427,6 +427,9 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
@Column(name = "enable_installment_level_delinquency", nullable = false)
private boolean enableInstallmentLevelDelinquency = false;
+ @Column(name = "allow_full_term_for_tranche", nullable = false)
+ private boolean allowFullTermForTranche = false;
+
public static Loan newIndividualLoanApplication(final String accountNo,
final Client client, final AccountType loanType,
final LoanProduct loanProduct, final Fund fund, final Staff
officer, final CodeValue loanPurpose,
final LoanRepaymentScheduleTransactionProcessor
transactionProcessingStrategy,
@@ -436,12 +439,12 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
final Boolean createStandingInstructionAtDisbursement, final
Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates,
final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms
loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
- final LocalDate submittedOnDate) {
+ final LocalDate submittedOnDate, final Boolean
allowFullTermForTranche) {
return new Loan(accountNo, client, null, loanType, fund, officer,
loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, collateral,
null, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId,
loanApplicationTerms, enableInstallmentLevelDelinquency,
- submittedOnDate);
+ submittedOnDate, allowFullTermForTranche);
}
public static Loan newGroupLoanApplication(final String accountNo, final
Group group, final AccountType loanType,
@@ -453,12 +456,12 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
final Boolean createStandingInstructionAtDisbursement, final
Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates,
final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms
loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
- final LocalDate submittedOnDate) {
+ final LocalDate submittedOnDate, final Boolean
allowFullTermForTranche) {
return new Loan(accountNo, null, group, loanType, fund, officer,
loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId,
loanApplicationTerms, enableInstallmentLevelDelinquency,
- submittedOnDate);
+ submittedOnDate, allowFullTermForTranche);
}
public static Loan newIndividualLoanApplicationFromGroup(final String
accountNo, final Client client, final Group group,
@@ -470,12 +473,12 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
final Boolean createStandingInstructionAtDisbursement, final
Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates,
final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms
loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
- final LocalDate submittedOnDate) {
+ final LocalDate submittedOnDate, final Boolean
allowFullTermForTranche) {
return new Loan(accountNo, client, group, loanType, fund, officer,
loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId,
loanApplicationTerms, enableInstallmentLevelDelinquency,
- submittedOnDate);
+ submittedOnDate, allowFullTermForTranche);
}
protected Loan() {
@@ -491,7 +494,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
final Boolean createStandingInstructionAtDisbursement, final
Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates,
final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms
loanApplicationTerms, final Boolean enableInstallmentLevelDelinquency,
- final LocalDate submittedOnDate) {
+ final LocalDate submittedOnDate, final Boolean
allowFullTermForTranche) {
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.isFloatingInterestRate = isFloatingInterestRate;
@@ -572,6 +575,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
this.loanInterestRecalculationDetails.updateLoan(this);
}
this.enableInstallmentLevelDelinquency =
enableInstallmentLevelDelinquency;
+ this.allowFullTermForTranche = allowFullTermForTranche;
this.getLoanProductRelatedDetail()
.setEnableAccrualActivityPosting(loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting());
}
@@ -1783,6 +1787,14 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
this.enableInstallmentLevelDelinquency =
enableInstallmentLevelDelinquency;
}
+ public boolean isAllowFullTermForTranche() {
+ return this.allowFullTermForTranche;
+ }
+
+ public void updateAllowFullTermForTranche(boolean allowFullTermForTranche)
{
+ this.allowFullTermForTranche = allowFullTermForTranche;
+ }
+
public void deductFromNetDisbursalAmount(final BigDecimal subtrahend) {
this.netDisbursalAmount = this.netDisbursalAmount.subtract(subtrahend);
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
index 7a3784a395..9039ad0bb7 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java
@@ -72,6 +72,7 @@ public interface LoanProductConstants {
String MULTI_DISBURSE_LOAN_PARAMETER_NAME = "multiDisburseLoan";
String MAX_TRANCHE_COUNT_PARAMETER_NAME = "maxTrancheCount";
String OUTSTANDING_LOAN_BALANCE_PARAMETER_NAME = "outstandingLoanBalance";
+ String ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME = "allowFullTermForTranche";
String GRACE_ON_ARREARS_AGEING_PARAMETER_NAME = "graceOnArrearsAgeing";
String OVERDUE_DAYS_FOR_NPA_PARAMETER_NAME = "overdueDaysForNPA";
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index 7dc3a16c34..e558aedf7d 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -309,6 +309,8 @@ public final class LoanProductsApiResourceSwagger {
// Multi Disburse
@Schema(example = "true")
public Boolean multiDisburseLoan;
+ @Schema(example = "false", description = "Allow full term length for
each tranche disbursement. Only available for PROGRESSIVE schedule type with
multi-disbursement enabled.")
+ public Boolean allowFullTermForTranche;
@Schema(example = "50")
public Integer principalThresholdForLastInstallment;
@Schema(example = "true")
@@ -1481,6 +1483,8 @@ public final class LoanProductsApiResourceSwagger {
public Boolean isRatesEnabled;
@Schema(example = "true")
public Boolean multiDisburseLoan;
+ @Schema(example = "false", description = "Allow full term length for
each tranche disbursement. Only available for PROGRESSIVE schedule type with
multi-disbursement enabled.")
+ public Boolean allowFullTermForTranche;
@Schema(example = "3")
public Integer maxTrancheCount;
@Schema(example = "36000.000000")
@@ -1794,6 +1798,8 @@ public final class LoanProductsApiResourceSwagger {
// Multi Disburse
@Schema(example = "true")
public Boolean multiDisburseLoan;
+ @Schema(example = "false", description = "Allow full term length for
each tranche disbursement. Only available for PROGRESSIVE schedule type with
multi-disbursement enabled.")
+ public Boolean allowFullTermForTranche;
@Schema(example = "50")
public Integer principalThresholdForLastInstallment;
@Schema(example = "true")
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index cb6edc56cc..8a4e2598de 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -217,6 +217,7 @@ public class LoanProductData implements Serializable {
private final Boolean allowApprovedDisbursedAmountsOverApplied;
private final String overAppliedCalculationType;
private final Integer overAppliedNumber;
+ private final Boolean allowFullTermForTranche;
private final BigDecimal principalThresholdForLastInstallment;
@@ -332,6 +333,7 @@ public class LoanProductData implements Serializable {
final Boolean allowApprovedDisbursedAmountsOverApplied = false;
final String overAppliedCalculationType = null;
final Integer overAppliedNumber = null;
+ final Boolean allowFullTermForTranche = null;
final LoanProductGuaranteeData productGuaranteeData = null;
final boolean holdGuaranteeFunds = false;
@@ -392,21 +394,21 @@ public class LoanProductData implements Serializable {
includeInBorrowerCycle, useBorrowerCycle, startDate,
closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations,
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
disallowExpectedDisbursements,
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType,
overAppliedNumber,
- graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
productGuaranteeData,
- principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
- installmentAmountInMultiplesOf,
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates,
floatingRateId,
- floatingRateName, interestRateDifferential,
minDifferentialLendingRate, defaultDifferentialLendingRate,
- maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGap, maximumGap,
- syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
- overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation, creditAllocation, repaymentStartDateType,
enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength,
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate,
daysInYearTypeCustomStrategy, enableIncomeCapitalization,
- capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
capitalizedIncomeType, enableBuyDownFee,
- buyDownFeeCalculationType, buyDownFeeStrategy,
buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
- writeOffReasonOptions);
+ allowFullTermForTranche, graceOnArrearsAgeing,
overdueDaysForNPA, daysInMonthType, daysInYearType,
+ isInterestRecalculationEnabled, interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
+ productGuaranteeData, principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion,
+ canDefineInstallmentAmount, installmentAmountInMultiplesOf,
loanProductConfigurableAttributes,
+ isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate,
+ defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed,
+ isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup,
+ isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions,
+ delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
+ disbursedAmountPercentageDownPayment,
enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation,
+ repaymentStartDateType, enableInstallmentLevelDelinquency,
loanScheduleType, loanScheduleProcessingType, fixedLength,
+ enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate,
+ daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
+ capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType,
+ merchantBuyDownFee, writeOffReasonsToExpenseMappings,
writeOffReasonOptions);
}
@@ -473,6 +475,7 @@ public class LoanProductData implements Serializable {
final Boolean allowApprovedDisbursedAmountsOverApplied = false;
final String overAppliedCalculationType = null;
final Integer overAppliedNumber = null;
+ final Boolean allowFullTermForTranche = null;
final EnumOptionData daysInMonthType = null;
final EnumOptionData daysInYearType = null;
@@ -532,21 +535,21 @@ public class LoanProductData implements Serializable {
includeInBorrowerCycle, useBorrowerCycle, startDate,
closeDate, status, externalId, principalVariations,
interestRateVariations, numberOfRepaymentVariations,
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
disallowExpectedDisbursements,
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType,
overAppliedNumber,
- graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
productGuaranteeData,
- principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
- installmentAmountInMultiplesOf,
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates,
floatingRateId,
- floatingRateName, interestRateDifferential,
minDifferentialLendingRate, defaultDifferentialLendingRate,
- maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGap, maximumGap,
- syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
- overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation, creditAllocation, repaymentStartDateType,
enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength,
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate,
daysInYearTypeCustomStrategy, enableIncomeCapitalization,
- capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
capitalizedIncomeType, enableBuyDownFee,
- buyDownFeeCalculationType, buyDownFeeStrategy,
buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
- writeOffReasonOptions);
+ allowFullTermForTranche, graceOnArrearsAgeing,
overdueDaysForNPA, daysInMonthType, daysInYearType,
+ isInterestRecalculationEnabled, interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
+ productGuaranteeData, principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion,
+ canDefineInstallmentAmount, installmentAmountInMultiplesOf,
loanProductConfigurableAttributes,
+ isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate,
+ defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed,
+ isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup,
+ isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions,
+ delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
+ disbursedAmountPercentageDownPayment,
enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation,
+ repaymentStartDateType, enableInstallmentLevelDelinquency,
loanScheduleType, loanScheduleProcessingType, fixedLength,
+ enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate,
+ daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
+ capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType,
+ merchantBuyDownFee, writeOffReasonsToExpenseMappings,
writeOffReasonOptions);
}
@@ -619,6 +622,7 @@ public class LoanProductData implements Serializable {
final Boolean allowApprovedDisbursedAmountsOverApplied = false;
final String overAppliedCalculationType = null;
final Integer overAppliedNumber = null;
+ final Boolean allowFullTermForTranche = null;
final EnumOptionData daysInMonthType =
CommonEnumerations.daysInMonthType(DaysInMonthType.ACTUAL);
final EnumOptionData daysInYearType =
CommonEnumerations.daysInYearType(DaysInYearType.ACTUAL);
@@ -679,21 +683,21 @@ public class LoanProductData implements Serializable {
includeInBorrowerCycle, useBorrowerCycle, startDate,
closeDate, status, externalId, principalVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance, disallowExpectedDisbursements,
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType,
- overAppliedNumber, graceOnArrearsAgeing, overdueDaysForNPA,
daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
productGuaranteeData,
- principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
- installmentAmountInMultiplesOf,
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates,
floatingRateId,
- floatingRateName, interestRateDifferential,
minDifferentialLendingRate, defaultDifferentialLendingRate,
- maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGap, maximumGap,
- syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
- overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation, creditAllocation, repaymentStartDateType,
enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength,
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate,
daysInYearTypeCustomStrategy, enableIncomeCapitalization,
- capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
capitalizedIncomeType, enableBuyDownFee,
- buyDownFeeCalculationType, buyDownFeeStrategy,
buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
- writeOffReasonOptions);
+ overAppliedNumber, allowFullTermForTranche,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
+ isInterestRecalculationEnabled, interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
+ productGuaranteeData, principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion,
+ canDefineInstallmentAmount, installmentAmountInMultiplesOf,
loanProductConfigurableAttributes,
+ isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate,
+ defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed,
+ isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup,
+ isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions,
+ delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
+ disbursedAmountPercentageDownPayment,
enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation,
+ repaymentStartDateType, enableInstallmentLevelDelinquency,
loanScheduleType, loanScheduleProcessingType, fixedLength,
+ enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate,
+ daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
+ capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType,
+ merchantBuyDownFee, writeOffReasonsToExpenseMappings,
writeOffReasonOptions);
}
@@ -760,6 +764,7 @@ public class LoanProductData implements Serializable {
final Boolean allowApprovedDisbursedAmountsOverApplied = false;
final String overAppliedCalculationType = null;
final Integer overAppliedNumber = null;
+ final Boolean allowFullTermForTranche = null;
final EnumOptionData daysInMonthType =
CommonEnumerations.daysInMonthType(DaysInMonthType.ACTUAL);
final EnumOptionData daysInYearType =
CommonEnumerations.daysInYearType(DaysInYearType.ACTUAL);
@@ -820,21 +825,21 @@ public class LoanProductData implements Serializable {
includeInBorrowerCycle, useBorrowerCycle, startDate,
closeDate, status, externalId, principalVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance, disallowExpectedDisbursements,
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType,
- overAppliedNumber, graceOnArrearsAgeing, overdueDaysForNPA,
daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
productGuaranteeData,
- principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
- installmentAmountInMultiplesOf,
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates,
floatingRateId,
- floatingRateName, interestRateDifferential,
minDifferentialLendingRate, defaultDifferentialLendingRate,
- maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGap, maximumGap,
- syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, rates, isRatesEnabled,
- fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
- overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
- paymentAllocation, creditAllocationData,
repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType,
- loanScheduleProcessingType, fixedLength,
enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour,
- interestRecognitionOnDisbursementDate,
daysInYearTypeCustomStrategy, enableIncomeCapitalization,
- capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
capitalizedIncomeType, enableBuyDownFee,
- buyDownFeeCalculationType, buyDownFeeStrategy,
buyDownFeeIncomeType, merchantBuyDownFee, writeOffReasonsToExpenseMappings,
- writeOffReasonOptions);
+ overAppliedNumber, allowFullTermForTranche,
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
+ isInterestRecalculationEnabled, interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
+ productGuaranteeData, principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion,
+ canDefineInstallmentAmount, installmentAmountInMultiplesOf,
loanProductConfigurableAttributes,
+ isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate,
+ defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed,
+ isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup,
+ isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions,
+ delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment,
+ disbursedAmountPercentageDownPayment,
enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocationData,
+ repaymentStartDateType, enableInstallmentLevelDelinquency,
loanScheduleType, loanScheduleProcessingType, fixedLength,
+ enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate,
+ daysInYearTypeCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
+ capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType,
+ merchantBuyDownFee, writeOffReasonsToExpenseMappings,
writeOffReasonOptions);
}
public static LoanProductData withAccountingDetails(final LoanProductData
productData, final Map<String, Object> accountingMappings,
@@ -873,9 +878,9 @@ public class LoanProductData implements Serializable {
Collection<LoanProductBorrowerCycleVariationData>
numberOfRepaymentVariations, Boolean multiDisburseLoan,
Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final
Boolean disallowExpectedDisbursements,
final Boolean allowApprovedDisbursedAmountsOverApplied, final
String overAppliedCalculationType,
- final Integer overAppliedNumber, final Integer
graceOnArrearsAgeing, final Integer overdueDaysForNPA,
- final EnumOptionData daysInMonthType, final EnumOptionData
daysInYearType, final boolean isInterestRecalculationEnabled,
- final LoanProductInterestRecalculationData
interestRecalculationData,
+ final Integer overAppliedNumber, final Boolean
allowFullTermForTranche, final Integer graceOnArrearsAgeing,
+ final Integer overdueDaysForNPA, final EnumOptionData
daysInMonthType, final EnumOptionData daysInYearType,
+ final boolean isInterestRecalculationEnabled, final
LoanProductInterestRecalculationData interestRecalculationData,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment,
boolean holdGuaranteeFunds,
final LoanProductGuaranteeData loanProductGuaranteeData, final
BigDecimal principalThresholdForLastInstallment,
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean
canDefineInstallmentAmount,
@@ -1000,6 +1005,7 @@ public class LoanProductData implements Serializable {
this.allowApprovedDisbursedAmountsOverApplied =
allowApprovedDisbursedAmountsOverApplied;
this.overAppliedCalculationType = overAppliedCalculationType;
this.overAppliedNumber = overAppliedNumber;
+ this.allowFullTermForTranche = allowFullTermForTranche;
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
this.overdueDaysForNPA = overdueDaysForNPA;
@@ -1189,6 +1195,7 @@ public class LoanProductData implements Serializable {
this.allowApprovedDisbursedAmountsOverApplied =
productData.allowApprovedDisbursedAmountsOverApplied;
this.overAppliedCalculationType =
productData.overAppliedCalculationType;
this.overAppliedNumber = productData.overAppliedNumber;
+ this.allowFullTermForTranche = productData.allowFullTermForTranche;
this.minimumDaysBetweenDisbursalAndFirstRepayment =
productData.minimumDaysBetweenDisbursalAndFirstRepayment;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
index b4d3da9896..9f79209630 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java
@@ -288,7 +288,7 @@ public class LoanProduct extends
AbstractPersistableCustom<Long> {
final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy,
final LoanCapitalizedIncomeType capitalizedIncomeType,
final boolean enableBuyDownFee, final
LoanBuyDownFeeCalculationType buyDownFeeCalculationType,
final LoanBuyDownFeeStrategy buyDownFeeStrategy, final
LoanBuyDownFeeIncomeType buyDownFeeIncomeType,
- final boolean merchantBuyDownFee) {
+ final boolean merchantBuyDownFee, final boolean
allowFullTermForTranche) {
this.fund = fund;
this.transactionProcessingStrategyCode =
transactionProcessingStrategyCode;
@@ -364,7 +364,8 @@ public class LoanProduct extends
AbstractPersistableCustom<Long> {
loanConfigurableAttributes.updateLoanProduct(this);
}
- this.loanProductTrancheDetails = new
LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance);
+ this.loanProductTrancheDetails = new
LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount,
outstandingLoanBalance,
+ allowFullTermForTranche);
this.overdueDaysForNPA = overdueDaysForNPA;
this.productInterestRecalculationDetails =
productInterestRecalculationDetails;
this.minimumDaysBetweenDisbursalAndFirstRepayment =
minimumDaysBetweenDisbursalAndFirstRepayment;
@@ -763,4 +764,8 @@ public class LoanProduct extends
AbstractPersistableCustom<Long> {
this.enableInstallmentLevelDelinquency =
enableInstallmentLevelDelinquency;
}
+ public boolean isAllowFullTermForTranche() {
+ return this.loanProductTrancheDetails != null &&
this.loanProductTrancheDetails.isAllowFullTermForTranche();
+ }
+
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductTrancheDetails.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductTrancheDetails.java
index 2a7142adcb..5a552f63fa 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductTrancheDetails.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductTrancheDetails.java
@@ -38,15 +38,19 @@ public class LoanProductTrancheDetails {
@Column(name = "max_outstanding_loan_balance", scale = 6, precision = 19)
private BigDecimal outstandingLoanBalance;
+ @Column(name = "allow_full_term_for_tranche")
+ private boolean allowFullTermForTranche;
+
protected LoanProductTrancheDetails() {
// TODO Auto-generated constructor stub
}
public LoanProductTrancheDetails(final boolean multiDisburseLoan, final
Integer maxTrancheCount,
- final BigDecimal outstandingLoanBalance) {
+ final BigDecimal outstandingLoanBalance, final boolean
allowFullTermForTranche) {
this.multiDisburseLoan = multiDisburseLoan;
this.maxTrancheCount = maxTrancheCount;
this.outstandingLoanBalance = outstandingLoanBalance;
+ this.allowFullTermForTranche = allowFullTermForTranche;
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index 40d97e3fc4..8d97966ca6 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -603,6 +603,8 @@ final class LoansApiResourceSwagger {
public String writeoffReason;
public GetLoansLoanIdLinkedAccount linkedAccount;
public Set<GetLoansLoanIdDisbursementDetails> disbursementDetails;
+ @Schema(example = "false", description = "Allow full term length
for each tranche disbursement")
+ public Boolean allowFullTermForTranche;
@Schema(example = "1100.000000")
public BigDecimal fixedEmiAmount;
@Schema(example = "35000.000000")
@@ -1198,6 +1200,8 @@ final class LoansApiResourceSwagger {
public List<GetLoansLoanIdTransactions> transactions;
@Schema(description = "Set of GetLoansLoanIdDisbursementDetails")
public Set<GetLoansLoanIdDisbursementDetails> disbursementDetails;
+ @Schema(example = "false", description = "Allow full term length for
each tranche disbursement")
+ public Boolean allowFullTermForTranche;
@Schema(description = "Delinquent data")
public GetLoansLoanIdDelinquencySummary delinquent;
@Schema(description = "Set of charges")
@@ -1344,6 +1348,8 @@ final class LoansApiResourceSwagger {
public String externalId;
@Schema(description = "List of PostLoansDisbursementData")
public List<PostLoansDisbursementData> disbursementData;
+ @Schema(example = "false", description = "Allow full term length for
each tranche disbursement")
+ public Boolean allowFullTermForTranche;
@Schema(description = "Maximum allowed outstanding balance")
public BigDecimal maxOutstandingLoanBalance;
@Schema(example = "[2011, 10, 20]")
@@ -1550,6 +1556,8 @@ final class LoansApiResourceSwagger {
public List<PutLoansLoanIdChanges> charges;
public List<PutLoansLoanIdCollateral> collateral;
public List<PutLoansLoanIdDisbursementData> disbursementData;
+ @Schema(example = "false", description = "Allow full term length for
each tranche disbursement")
+ public Boolean allowFullTermForTranche;
@Schema(example = "HORIZONTAL")
public String loanScheduleProcessingType;
@Schema(example = "false")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java
index 10d4c3f8dc..6401f9957f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java
@@ -175,7 +175,8 @@ public final class LoanApplicationValidator {
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
LoanProductConstants.FIXED_LENGTH,
LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
LoanProductConstants.ENABLE_DOWN_PAYMENT,
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT,
LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
- LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE,
LoanApiConstants.daysInYearCustomStrategyParameterName));
+ LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE,
LoanApiConstants.daysInYearCustomStrategyParameterName,
+ LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE));
public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
private final FromJsonHelper fromApiJsonHelper;
@@ -324,6 +325,18 @@ public final class LoanApplicationValidator {
}
}
+ if
(this.fromApiJsonHelper.parameterExists(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE,
element)) {
+ final Boolean allowFullTermForTranche = this.fromApiJsonHelper
+
.extractBooleanNamed(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE, element);
+
baseDataValidator.reset().parameter(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE).value(allowFullTermForTranche)
+ .ignoreIfNull().validateForBooleanValue();
+
+ if (Boolean.TRUE.equals(allowFullTermForTranche) &&
!loanProduct.isAllowFullTermForTranche()) {
+
baseDataValidator.reset().parameter(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE).failWithCode("not.allowed.by.product",
+ "Full term tranche cannot be enabled because the
loan product does not allow it");
+ }
+ }
+
BigDecimal fixedPrincipalPercentagePerInstallment =
this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)
@@ -946,6 +959,18 @@ public final class LoanApplicationValidator {
}
}
+ if
(this.fromApiJsonHelper.parameterExists(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE,
element)) {
+ final Boolean allowFullTermForTranche = this.fromApiJsonHelper
+
.extractBooleanNamed(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE, element);
+
baseDataValidator.reset().parameter(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE).value(allowFullTermForTranche)
+ .ignoreIfNull().validateForBooleanValue();
+
+ if (Boolean.TRUE.equals(allowFullTermForTranche) &&
!loanProduct.isAllowFullTermForTranche()) {
+
baseDataValidator.reset().parameter(LoanApiConstants.ALLOW_FULL_TERM_FOR_TRANCHE).failWithCode("not.allowed.by.product",
+ "Full term tranche cannot be enabled because the
loan product does not allow it");
+ }
+ }
+
BigDecimal fixedPrincipalPercentagePerInstallment =
this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
index 0f251096b4..9944ac03b7 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java
@@ -248,6 +248,12 @@ public class LoanAssemblerImpl implements LoanAssembler {
isEnableInstallmentLevelDelinquency =
loanProduct.isEnableInstallmentLevelDelinquency();
}
+ Boolean allowFullTermForTranche = this.fromApiJsonHelper
+
.extractBooleanNamed(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME,
element);
+ if (allowFullTermForTranche == null) {
+ allowFullTermForTranche = loanProduct.isAllowFullTermForTranche();
+ }
+
final boolean isHolidayEnabled =
this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
Long officeId = client != null ? client.getOffice().getId() :
group.getOffice().getId();
final List<Holiday> holidays =
this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId,
@@ -262,19 +268,19 @@ public class LoanAssemblerImpl implements LoanAssembler {
syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement,
isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId,
loanApplicationTerms, isEnableInstallmentLevelDelinquency,
- submittedOnDate);
+ submittedOnDate, allowFullTermForTranche);
} else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group,
loanAccountType, loanProduct, fund, loanOfficer, loanPurpose,
transactionProcessingStrategy, loanProductRelatedDetail,
loanCharges, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
- isEnableInstallmentLevelDelinquency, submittedOnDate);
+ isEnableInstallmentLevelDelinquency, submittedOnDate,
allowFullTermForTranche);
} else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo,
client, loanAccountType, loanProduct, fund, loanOfficer,
loanPurpose, transactionProcessingStrategy,
loanProductRelatedDetail, loanCharges, collateral, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
- isEnableInstallmentLevelDelinquency, submittedOnDate);
+ isEnableInstallmentLevelDelinquency, submittedOnDate,
allowFullTermForTranche);
} else {
throw new IllegalStateException("No loan application exists for
either a client or group (or both).");
}
@@ -843,6 +849,14 @@ public class LoanAssemblerImpl implements LoanAssembler {
loan.updateEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency);
}
+ // update allow full term for tranche
+ if
(command.isChangeInBooleanParameterNamed(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME,
+ loan.isAllowFullTermForTranche())) {
+ final Boolean allowFullTermForTranche = command
+
.booleanObjectValueOfParameterNamed(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME);
+ loan.updateAllowFullTermForTranche(allowFullTermForTranche);
+ }
+
if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule");
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java
index 07644c8d55..ddc728a975 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductAssembler.java
@@ -267,6 +267,9 @@ public class LoanProductAssembler {
final Integer overAppliedNumber =
command.integerValueOfParameterNamed(LoanProductConstants.OVER_APPLIED_NUMBER);
+ final boolean allowFullTermForTranche =
command.parameterExists(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME)
+ &&
command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME);
+
final Integer dueDaysForRepaymentEvent =
command.integerValueOfParameterNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT);
final Integer overDueDaysForRepaymentEvent = command
.integerValueOfParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT);
@@ -353,7 +356,7 @@ public class LoanProductAssembler {
enableAccrualActivityPosting, supportedInterestRefundTypes,
chargeOffBehaviour, interestRecognitionOnDisbursementDate,
daysInYearCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType, capitalizedIncomeStrategy,
capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType,
- merchantBuyDownFee);
+ merchantBuyDownFee, allowFullTermForTranche);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductTrancheDetailsUpdateUtil.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductTrancheDetailsUpdateUtil.java
index 045097cc2f..37f4dfd8c6 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductTrancheDetailsUpdateUtil.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductTrancheDetailsUpdateUtil.java
@@ -54,9 +54,18 @@ public class LoanProductTrancheDetailsUpdateUtil {
actualChanges.put(LoanProductConstants.OUTSTANDING_LOAN_BALANCE_PARAMETER_NAME,
newValue);
loanProductTrancheDetails.setOutstandingLoanBalance(newValue);
}
+
+ if
(command.isChangeInBooleanParameterNamed(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME,
+ loanProductTrancheDetails.isAllowFullTermForTranche())) {
+ final boolean newValue = command
+
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME);
+
actualChanges.put(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME,
newValue);
+ loanProductTrancheDetails.setAllowFullTermForTranche(newValue);
+ }
} else {
loanProductTrancheDetails.setMaxTrancheCount(null);
loanProductTrancheDetails.setOutstandingLoanBalance(null);
+ loanProductTrancheDetails.setAllowFullTermForTranche(false);
}
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 9b05453977..cc8595aa50 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -865,7 +865,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
+ " topup.topup_amount as topupAmount,
l.last_closed_business_date as lastClosedBusinessDate,l.overpaidon_date as
overpaidOnDate, "
+ " l.is_charged_off as isChargedOff,
l.charge_off_reason_cv_id as chargeOffReasonId, codec.code_value as
chargeOffReason, l.charged_off_on_date as chargedOffOnDate,
l.enable_down_payment as enableDownPayment,
l.disbursed_amount_percentage_for_down_payment as
disbursedAmountPercentageForDownPayment,
l.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment,"
+ " cobu.username as chargedOffByUsername, cobu.firstname
as chargedOffByFirstname, cobu.lastname as chargedOffByLastname,
l.loan_schedule_type as loanScheduleType, l.loan_schedule_processing_type as
loanScheduleProcessingType, "
- + " l.charge_off_behaviour as chargeOffBehaviour,
l.interest_recognition_on_disbursement_date as
interestRecognitionOnDisbursementDate " //
+ + " l.charge_off_behaviour as chargeOffBehaviour,
l.interest_recognition_on_disbursement_date as
interestRecognitionOnDisbursementDate, l.allow_full_term_for_tranche as
allowFullTermForTranche " //
+ " from m_loan l" //
+ " join m_product_loan lp on lp.id = l.product_id" //
+ " left join m_loan_recalculation_details lir on
lir.loan_id = l.id join m_currency rc on rc."
@@ -1238,6 +1238,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
final Integer fixedLength = JdbcSupport.getInteger(rs,
"fixedLength");
final LoanChargeOffBehaviour chargeOffBehaviour =
LoanChargeOffBehaviour.valueOf(rs.getString("chargeOffBehaviour"));
final boolean interestRecognitionOnDisbursementDate =
rs.getBoolean("interestRecognitionOnDisbursementDate");
+ final boolean allowFullTermForTranche =
rs.getBoolean("allowFullTermForTranche");
final StringEnumOptionData daysInYearCustomStrategy =
ApiFacingEnum.getStringEnumOptionData(DaysInYearCustomStrategyType.class,
rs.getString("daysInYearCustomStrategy"));
final boolean enableIncomeCapitalization =
rs.getBoolean("enableIncomeCapitalization");
@@ -1274,7 +1275,7 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
lastClosedBusinessDate, overpaidOnDate, isChargedOff,
enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment,
enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(),
loanScheduleProcessingType.asEnumOptionData(),
fixedLength, chargeOffBehaviour.getValueAsStringEnumOptionData(),
- interestRecognitionOnDisbursementDate,
daysInYearCustomStrategy, enableIncomeCapitalization,
+ interestRecognitionOnDisbursementDate,
allowFullTermForTranche, daysInYearCustomStrategy, enableIncomeCapitalization,
capitalizedIncomeCalculationType,
capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee,
buyDownFeeCalculationType, buyDownFeeStrategy,
buyDownFeeIncomeType, merchantBuyDownFee);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 2bc26a6b7c..49242644d4 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -208,7 +208,8 @@ public final class LoanProductDataValidator {
LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(),
LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue(),
LoanProductConstants.MERCHANT_BUY_DOWN_FEE_PARAM_NAME,
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue(),
//
-
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue()
//
+
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue(),
//
+ LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME //
));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = {
LoanProductConstants.amortizationTypeParamName,
@@ -1062,6 +1063,43 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(INTEREST_TYPE).value(interestType).ignoreIfNull().inMinMaxRange(0,
1);
}
+ // Determine effective values for allowFullTermForTranche validation
+ // For updates, fall back to existing product values if not in request
+ Boolean effectiveMultiDisburseLoan = multiDisburseLoan;
+ if
(!this.fromApiJsonHelper.parameterExists(LoanProductConstants.MULTI_DISBURSE_LOAN_PARAMETER_NAME,
element)
+ && loanProduct != null) {
+ effectiveMultiDisburseLoan = loanProduct.isMultiDisburseLoan();
+ }
+
+ String effectiveLoanScheduleType =
LoanScheduleType.CUMULATIVE.toString();
+ if
(fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE,
element)) {
+ effectiveLoanScheduleType =
fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE,
element);
+ } else if (loanProduct != null) {
+ effectiveLoanScheduleType =
loanProduct.getLoanProductRelatedDetail().getLoanScheduleType().toString();
+ }
+
+ Boolean effectiveAllowFullTermForTranche = false;
+ if
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME,
element)) {
+ effectiveAllowFullTermForTranche = this.fromApiJsonHelper
+
.extractBooleanNamed(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME,
element);
+
baseDataValidator.reset().parameter(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME)
+
.value(effectiveAllowFullTermForTranche).ignoreIfNull().validateForBooleanValue();
+ } else if (loanProduct != null &&
loanProduct.getLoanProductTrancheDetails() != null) {
+ effectiveAllowFullTermForTranche =
loanProduct.getLoanProductTrancheDetails().isAllowFullTermForTranche();
+ }
+
+ // Validate: allowFullTermForTranche requires multi-disburse and
PROGRESSIVE schedule
+ if (Boolean.TRUE.equals(effectiveAllowFullTermForTranche)) {
+ if (!Boolean.TRUE.equals(effectiveMultiDisburseLoan)) {
+
baseDataValidator.reset().parameter(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME).failWithCode(
+ "requires.multi.disburse.loan", "Full term tranche can
only be enabled for multi-disbursement loan products");
+ }
+ if
(!LoanScheduleType.PROGRESSIVE.toString().equals(effectiveLoanScheduleType)) {
+
baseDataValidator.reset().parameter(LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME).failWithCode(
+ "requires.progressive.schedule.type", "Full term
tranche can only be enabled for PROGRESSIVE loan schedule type");
+ }
+ }
+
final String overAppliedCalculationType =
this.fromApiJsonHelper.extractStringNamed(OVER_APPLIED_CALCULATION_TYPE,
element);
baseDataValidator.reset().parameter(OVER_APPLIED_CALCULATION_TYPE).value(overAppliedCalculationType).notExceedingLengthOf(10);
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
index 4444f42ade..d10879c143 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
@@ -254,7 +254,7 @@ public class LoanProductReadPlatformServiceImpl implements
LoanProductReadPlatfo
+ "lp.min_days_between_disbursal_and_first_repayment As
minimumDaysBetweenDisbursalAndFirstRepayment, "
+ "lp.amortization_method_enum as amortizationMethod,
lp.arrearstolerance_amount as tolerance, "
+ "lp.accounting_type as accountingType,
lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as
useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, "
- + "lp.allow_multiple_disbursals as multiDisburseLoan,
lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as
outstandingLoanBalance, "
+ + "lp.allow_multiple_disbursals as multiDisburseLoan,
lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as
outstandingLoanBalance, lp.allow_full_term_for_tranche as
allowFullTermForTranche, "
+ "lp.disallow_expected_disbursements as
disallowExpectedDisbursements, lp.allow_approved_disbursed_amounts_over_applied
as allowApprovedDisbursedAmountsOverApplied, lp.over_applied_calculation_type
as overAppliedCalculationType, over_applied_number as overAppliedNumber, "
+ "lp.days_in_month_enum as daysInMonth,
lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as
isInterestRecalculationEnabled, "
+ "lp.can_define_fixed_emi_amount as
canDefineInstallmentAmount, lp.installment_amount_in_multiples_of as
installmentAmountInMultiplesOf, "
@@ -439,6 +439,7 @@ public class LoanProductReadPlatformServiceImpl implements
LoanProductReadPlatfo
final Boolean allowApprovedDisbursedAmountsOverApplied =
rs.getBoolean("allowApprovedDisbursedAmountsOverApplied");
final String overAppliedCalculationType =
rs.getString("overAppliedCalculationType");
final Integer overAppliedNumber = rs.getInt("overAppliedNumber");
+ final Boolean allowFullTermForTranche =
rs.getBoolean("allowFullTermForTranche");
final int daysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");
final EnumOptionData daysInMonthType =
CommonEnumerations.daysInMonthType(daysInMonth);
@@ -591,15 +592,16 @@ public class LoanProductReadPlatformServiceImpl
implements LoanProductReadPlatfo
principalVariationsForBorrowerCycle,
interestRateVariationsForBorrowerCycle,
numberOfRepaymentVariationsForBorrowerCycle,
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
disallowExpectedDisbursements,
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType,
overAppliedNumber,
- graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled,
- interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds,
loanProductGuaranteeData,
- principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
- installmentAmountInMultiplesOf, allowAttributeOverrides,
isLinkedToFloatingInterestRates, floatingRateId,
- floatingRateName, interestRateDifferential,
minDifferentialLendingRate, defaultDifferentialLendingRate,
- maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed,
minimumGap,
- maximumGap, syncExpectedWithDisbursementDate,
canUseForTopup, isEqualAmortization, rateOptions, this.rates,
- isRatesEnabled, fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket,
- dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent,
enableDownPayment, disbursedAmountPercentageForDownPayment,
+ allowFullTermForTranche, graceOnArrearsAgeing,
overdueDaysForNPA, daysInMonthType, daysInYearType,
+ isInterestRecalculationEnabled, interestRecalculationData,
minimumDaysBetweenDisbursalAndFirstRepayment,
+ holdGuaranteeFunds, loanProductGuaranteeData,
principalThresholdForLastInstallment,
+ accountMovesOutOfNPAOnlyOnArrearsCompletion,
canDefineInstallmentAmount, installmentAmountInMultiplesOf,
+ allowAttributeOverrides, isLinkedToFloatingInterestRates,
floatingRateId, floatingRateName, interestRateDifferential,
+ minDifferentialLendingRate,
defaultDifferentialLendingRate, maxDifferentialLendingRate,
+ isFloatingInterestRateCalculationAllowed,
isVariableIntallmentsAllowed, minimumGap, maximumGap,
+ syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, rateOptions, this.rates, isRatesEnabled,
+ fixedPrincipalPercentagePerInstallment,
delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
+ overDueDaysForRepaymentEvent, enableDownPayment,
disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, advancedPaymentData,
creditAllocationData, repaymentStartDateType,
enableInstallmentLevelDelinquency,
loanScheduleType.asEnumOptionData(),
loanScheduleProcessingType.asEnumOptionData(),
fixedLength, enableAccrualActivityPosting,
supportedInterestRefundTypes,
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index b5dabf2d0a..2b164480b9 100644
---
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -225,4 +225,5 @@
<include
file="parts/0204_transaction_summary_with_asset_owner_and_from_asset_owner_id_for_buybacks.xml"
relativeToChangelogFile="true" />
<include file="parts/0205_add_read_familymembers_permission.xml"
relativeToChangelogFile="true" />
<include
file="parts/0206_transaction_summary_with_asset_owner_classification_name_bug_fix.xml"
relativeToChangelogFile="true" />
+ <include file="parts/0207_add_allow_full_term_for_tranche.xml"
relativeToChangelogFile="true" />
</databaseChangeLog>
diff --git
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0207_add_allow_full_term_for_tranche.xml
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0207_add_allow_full_term_for_tranche.xml
new file mode 100644
index 0000000000..80b0041d83
--- /dev/null
+++
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0207_add_allow_full_term_for_tranche.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet author="fineract" id="1">
+ <addColumn tableName="m_product_loan">
+ <column defaultValueBoolean="false" type="boolean"
name="allow_full_term_for_tranche">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+ <changeSet author="fineract" id="2">
+ <addColumn tableName="m_loan">
+ <column defaultValueBoolean="false" type="boolean"
name="allow_full_term_for_tranche">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+</databaseChangeLog>
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java
index fc5a720942..1db4500c5f 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java
@@ -80,6 +80,7 @@ public class LoanBuilder {
private ExternalId externalId = ExternalId.empty();
private LoanApplicationTerms loanApplicationTerms =
mock(LoanApplicationTerms.class);
private Boolean enableInstallmentLevelDelinquency = false;
+ private Boolean allowFullTermForTranche = false;
private LocalDate submittedOnDate = LocalDate.now(ZoneId.systemDefault());
private LocalDate approvedOnDate;
private LocalDate expectedDisbursementDate;
@@ -115,7 +116,7 @@ public class LoanBuilder {
transactionProcessor, loanRepaymentScheduleDetail,
charges, collateral, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential,
rates, fixedPrincipalPercentagePerInstallment, externalId,
loanApplicationTerms, enableInstallmentLevelDelinquency,
- submittedOnDate);
+ submittedOnDate, allowFullTermForTranche);
if (id != null) {
loan.setId(id);
@@ -338,6 +339,11 @@ public class LoanBuilder {
return this;
}
+ public LoanBuilder withAllowFullTermForTranche(Boolean
allowFullTermForTranche) {
+ this.allowFullTermForTranche = allowFullTermForTranche;
+ return this;
+ }
+
public LoanBuilder withSubmittedOnDate(LocalDate submittedOnDate) {
this.submittedOnDate = submittedOnDate;
return this;
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanproduct/LoanProductValidationStepDefinitions.java
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanproduct/LoanProductValidationStepDefinitions.java
index 35c071523c..e1e7297295 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanproduct/LoanProductValidationStepDefinitions.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanproduct/LoanProductValidationStepDefinitions.java
@@ -72,7 +72,7 @@ public class LoanProductValidationStepDefinitions implements
En {
private LoanProduct newLoanProduct(boolean allowMultipleDisbursals) {
LoanProduct lp = new LoanProduct();
- lp.setLoanProductTrancheDetails(new
LoanProductTrancheDetails(allowMultipleDisbursals, null, null));
+ lp.setLoanProductTrancheDetails(new
LoanProductTrancheDetails(allowMultipleDisbursals, null, null, false));
return lp;
}
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java
index 996f31da22..6d683485b8 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java
@@ -33,15 +33,19 @@ import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdDisbursementDetails;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
@@ -53,6 +57,8 @@ import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
+import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -693,4 +699,185 @@ public class LoanDisbursementDetailsIntegrationTest {
equalTo("error.msg.loan.disbursal.amount.can't.be.greater.than.maximum.applied.loan.amount.calculation"))
.expectStatusCode(403).build();
}
+
+ private AdvancedPaymentData createDefaultPaymentAllocation(String
futureInstallmentAllocationRule) {
+ AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
+ advancedPaymentData.setTransactionType("DEFAULT");
+
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
+
+ List<PaymentAllocationOrder> paymentAllocationOrders =
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
+ PaymentAllocationType.PAST_DUE_FEE,
PaymentAllocationType.PAST_DUE_PRINCIPAL,
PaymentAllocationType.PAST_DUE_INTEREST,
+ PaymentAllocationType.DUE_PENALTY,
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
+ PaymentAllocationType.DUE_INTEREST,
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
+ PaymentAllocationType.IN_ADVANCE_PRINCIPAL,
PaymentAllocationType.IN_ADVANCE_INTEREST);
+
+ advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
+ return advancedPaymentData;
+ }
+
+ private List<PaymentAllocationOrder>
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
+ AtomicInteger integer = new AtomicInteger(1);
+ return Arrays.stream(paymentAllocationTypes).map(pat -> {
+ PaymentAllocationOrder paymentAllocationOrder = new
PaymentAllocationOrder();
+ paymentAllocationOrder.setPaymentAllocationRule(pat.name());
+ paymentAllocationOrder.setOrder(integer.getAndIncrement());
+ return paymentAllocationOrder;
+ }).toList();
+ }
+
+ @Test
+ public void testCreateLoanProductWithFullTermTrancheEnabled() {
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation("NEXT_INSTALLMENT");
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments()
+ .withInterestTypeAsDecliningBalance().withMoratorium("",
"").withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+
.withInterestTypeAsDecliningBalance().withMultiDisburse().withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
+
.addAdvancedPaymentAllocation(defaultAllocation).withAllowFullTermForTranche(true).build(null);
+
+ final Integer loanProductId =
this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ log.info("------------------LOAN PRODUCT CREATED WITH ID-----------
{}", loanProductId);
+
+ GetLoanProductsProductIdResponse loanProduct =
this.loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(loanProduct);
+ assertEquals(true, loanProduct.getMultiDisburseLoan());
+ assertEquals(true, loanProduct.getAllowFullTermForTranche());
+ log.info("-------------------LOAN PRODUCT WITH allowFullTermForTranche
CREATED SUCCESSFULLY-------");
+ }
+
+ @Test
+ public void
testCreateLoanProductWithFullTermTrancheOnCumulativeShouldFail() {
+ final ResponseSpecification errorResponse = new ResponseSpecBuilder()
+ .expectBody("userMessageGlobalisationCode",
equalTo("validation.msg.validation.errors.exist"))
+ .expectBody("errors[0].userMessageGlobalisationCode",
+
equalTo("validation.msg.loanproduct.allowFullTermForTranche.requires.progressive.schedule.type"))
+ .expectStatusCode(400).build();
+
+ final LoanTransactionHelper validationErrorHelper = new
LoanTransactionHelper(this.requestSpec, errorResponse);
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments()
+ .withInterestTypeAsDecliningBalance().withMoratorium("",
"").withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+
.withInterestTypeAsDecliningBalance().withMultiDisburse().withLoanScheduleType(LoanScheduleType.CUMULATIVE)
+ .withAllowFullTermForTranche(true).build(null);
+
+ validationErrorHelper.getLoanProductId(loanProductJSON);
+ log.info("-------------------LOAN PRODUCT WITH allowFullTermForTranche
ON CUMULATIVE FAILED AS EXPECTED-------");
+ }
+
+ @Test
+ public void
testCreateLoanProductWithFullTermTrancheOnSingleDisburseShouldFail() {
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation("NEXT_INSTALLMENT");
+
+ final ResponseSpecification errorResponse = new ResponseSpecBuilder()
+ .expectBody("userMessageGlobalisationCode",
equalTo("validation.msg.validation.errors.exist"))
+ .expectBody("errors[0].userMessageGlobalisationCode",
+
equalTo("validation.msg.loanproduct.allowFullTermForTranche.requires.multi.disburse.loan"))
+ .expectStatusCode(400).build();
+
+ final LoanTransactionHelper validationErrorHelper = new
LoanTransactionHelper(this.requestSpec, errorResponse);
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments()
+ .withInterestTypeAsDecliningBalance().withMoratorium("",
"").withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+
.withInterestTypeAsDecliningBalance().withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
+
.addAdvancedPaymentAllocation(defaultAllocation).withAllowFullTermForTranche(true).build(null);
+
+ validationErrorHelper.getLoanProductId(loanProductJSON);
+ log.info("-------------------LOAN PRODUCT WITH allowFullTermForTranche
ON SINGLE DISBURSE FAILED AS EXPECTED-------");
+ }
+
+ @Test
+ public void testUpdateLoanProductPreservesAllowFullTermForTranche() {
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation("NEXT_INSTALLMENT");
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments()
+ .withInterestTypeAsDecliningBalance().withMoratorium("",
"").withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+
.withInterestTypeAsDecliningBalance().withMultiDisburse().withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
+
.addAdvancedPaymentAllocation(defaultAllocation).withAllowFullTermForTranche(true).build(null);
+
+ final Integer loanProductId =
this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ log.info("------------------LOAN PRODUCT CREATED WITH ID-----------
{}", loanProductId);
+
+ GetLoanProductsProductIdResponse loanProduct =
this.loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(loanProduct);
+ assertEquals(true, loanProduct.getAllowFullTermForTranche());
+
+ org.apache.fineract.client.models.PutLoanProductsProductIdRequest
updateRequest = new
org.apache.fineract.client.models.PutLoanProductsProductIdRequest();
+ updateRequest.setDescription("Updated description");
+ this.loanTransactionHelper.updateLoanProduct((long) loanProductId,
updateRequest);
+
+ GetLoanProductsProductIdResponse updatedProduct =
this.loanTransactionHelper.getLoanProduct(loanProductId);
+ assertNotNull(updatedProduct);
+ assertEquals(true, updatedProduct.getAllowFullTermForTranche());
+ assertEquals("Updated description", updatedProduct.getDescription());
+ log.info("-------------------LOAN PRODUCT UPDATE PRESERVED
allowFullTermForTranche FLAG-------");
+ }
+
+ @Test
+ public void testLoanInheritsAllowFullTermForTrancheFromProduct() {
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation("NEXT_INSTALLMENT");
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments()
+ .withInterestTypeAsDecliningBalance().withMoratorium("",
"").withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+
.withInterestTypeAsDecliningBalance().withMultiDisburse().withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
+
.addAdvancedPaymentAllocation(defaultAllocation).withAllowFullTermForTranche(true).build(null);
+
+ final Integer loanProductId =
this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ log.info("------------------LOAN PRODUCT CREATED WITH ID-----------
{}", loanProductId);
+
+ final Integer clientId = ClientHelper.createClient(this.requestSpec,
this.responseSpec);
+ log.info("------------------CLIENT CREATED WITH ID----------- {}",
clientId);
+
+ List<HashMap> createTranches = new ArrayList<>();
+
createTranches.add(this.loanTransactionHelper.createTrancheDetail(null, "01
March 2014", "5000"));
+
createTranches.add(this.loanTransactionHelper.createTrancheDetail(null, "01
April 2014", "5000"));
+
+ final String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("10000").withLoanTermFrequency("12")
+
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
+
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("1").withExpectedDisbursementDate("01
March 2014")
+ .withTranches(createTranches).withSubmittedOnDate("01 March
2014")
+
.withRepaymentStrategy(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+ .build(clientId.toString(), loanProductId.toString(), null);
+
+ final Integer loanId =
this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+ log.info("------------------LOAN CREATED WITH ID----------- {}",
loanId);
+
+ GetLoansLoanIdResponse loanDetails =
this.loanTransactionHelper.getLoanDetails((long) loanId);
+ assertNotNull(loanDetails);
+ assertEquals(true, loanDetails.getAllowFullTermForTranche());
+ log.info("-------------------LOAN INHERITED allowFullTermForTranche
FROM PRODUCT SUCCESSFULLY-------");
+ }
+
+ @Test
+ public void testLoanLevelOverrideOfAllowFullTermForTranche() {
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation("NEXT_INSTALLMENT");
+
+ final String loanProductJSON = new
LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments()
+ .withInterestTypeAsDecliningBalance().withMoratorium("",
"").withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+
.withInterestTypeAsDecliningBalance().withMultiDisburse().withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
+
.addAdvancedPaymentAllocation(defaultAllocation).withAllowFullTermForTranche(true).build(null);
+
+ final Integer loanProductId =
this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+ log.info("------------------LOAN PRODUCT CREATED WITH ID-----------
{}", loanProductId);
+
+ final Integer clientId = ClientHelper.createClient(this.requestSpec,
this.responseSpec);
+ log.info("------------------CLIENT CREATED WITH ID----------- {}",
clientId);
+
+ List<HashMap> createTranches = new ArrayList<>();
+
createTranches.add(this.loanTransactionHelper.createTrancheDetail(null, "01
March 2014", "5000"));
+
createTranches.add(this.loanTransactionHelper.createTrancheDetail(null, "01
April 2014", "5000"));
+
+ final String loanApplicationJSON = new
LoanApplicationTestBuilder().withPrincipal("10000").withLoanTermFrequency("12")
+
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
+
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("1").withExpectedDisbursementDate("01
March 2014")
+ .withTranches(createTranches).withSubmittedOnDate("01 March
2014")
+
.withRepaymentStrategy(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY).withAllowFullTermForTranche(false)
+ .build(clientId.toString(), loanProductId.toString(), null);
+
+ final Integer loanId =
this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+ log.info("------------------LOAN CREATED WITH ID----------- {}",
loanId);
+
+ GetLoansLoanIdResponse loanDetails =
this.loanTransactionHelper.getLoanDetails((long) loanId);
+ assertNotNull(loanDetails);
+ assertEquals(false, loanDetails.getAllowFullTermForTranche());
+ log.info("-------------------LOAN LEVEL OVERRIDE OF
allowFullTermForTranche WORKED SUCCESSFULLY-------");
+ }
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
index 469b06cb27..f7463eb3e2 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java
@@ -88,6 +88,7 @@ public class LoanApplicationTestBuilder {
private boolean enableDownPayment = false;
private boolean enableAutoRepaymentForDownPayment = false;
private String disbursedAmountPercentageDownPayment;
+ private Boolean allowFullTermForTranche = null;
public String build(final String clientID, final String groupID, final
String loanProductId, final String savingsID) {
final HashMap<String, Object> map = new HashMap<>();
@@ -220,6 +221,9 @@ public class LoanApplicationTestBuilder {
if (disbursedAmountPercentageDownPayment != null) {
map.put("disbursedAmountPercentageDownPayment",
disbursedAmountPercentageDownPayment);
}
+ if (allowFullTermForTranche != null) {
+ map.put("allowFullTermForTranche", allowFullTermForTranche);
+ }
LOG.info("Loan Application request : {} ", map);
return new Gson().toJson(map);
}
@@ -468,4 +472,9 @@ public class LoanApplicationTestBuilder {
return this;
}
+ public LoanApplicationTestBuilder withAllowFullTermForTranche(final
Boolean allowFullTermForTranche) {
+ this.allowFullTermForTranche = allowFullTermForTranche;
+ return this;
+ }
+
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index 8ae4bcc1df..2b706f6dbf 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -113,6 +113,7 @@ public class LoanProductTestBuilder {
private Account feeAndPenaltyAssetAccount;
private Boolean multiDisburseLoan = false;
+ private Boolean allowFullTermForTranche = false;
private final String outstandingLoanBalance = "35000";
private String maxTrancheCount = "3";
private Boolean disallowExpectedDisbursements = false;
@@ -225,6 +226,7 @@ public class LoanProductTestBuilder {
}
if (this.multiDisburseLoan) {
map.put("multiDisburseLoan", this.multiDisburseLoan);
+ map.put("allowFullTermForTranche", this.allowFullTermForTranche);
map.put("maxTrancheCount", this.maxTrancheCount);
map.put("outstandingLoanBalance", this.outstandingLoanBalance);
map.put("disallowExpectedDisbursements",
this.disallowExpectedDisbursements);
@@ -242,6 +244,10 @@ public class LoanProductTestBuilder {
map.put("maxTrancheCount", this.maxTrancheCount);
map.put("outstandingLoanBalance", this.outstandingLoanBalance);
}
+ // Always send allowFullTermForTranche when it's true (for validation
testing of single-disburse scenarios)
+ if (this.allowFullTermForTranche && !this.multiDisburseLoan) {
+ map.put("allowFullTermForTranche", this.allowFullTermForTranche);
+ }
if (this.fullAccountingConfig != null) {
map.putAll(this.fullAccountingConfig.toMap());
@@ -742,6 +748,11 @@ public class LoanProductTestBuilder {
return this;
}
+ public LoanProductTestBuilder withAllowFullTermForTranche(boolean
allowFullTermForTranche) {
+ this.allowFullTermForTranche = allowFullTermForTranche;
+ return this;
+ }
+
public LoanProductTestBuilder withFeeToIncomeAccountMapping(final Long
chargeId, final Long accountId) {
if (this.feeToIncomeAccountMappings == null) {
this.feeToIncomeAccountMappings = new ArrayList<>();