FINERACT-2090: Restructure Loan creation and modification

This commit is contained in:
Adam Saghy
2024-06-06 23:09:50 +02:00
parent a078cfd8c5
commit 92e291a9e4
34 changed files with 2458 additions and 2486 deletions
@@ -143,47 +143,51 @@ public enum ChargeTimeType {
} }
public boolean isTimeOfDisbursement() { public boolean isTimeOfDisbursement() {
return ChargeTimeType.DISBURSEMENT.getValue().equals(this.value); return ChargeTimeType.DISBURSEMENT.equals(this);
} }
public boolean isOnSpecifiedDueDate() { public boolean isOnSpecifiedDueDate() {
return this.value.equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()); return this.equals(ChargeTimeType.SPECIFIED_DUE_DATE);
} }
public boolean isSavingsActivation() { public boolean isSavingsActivation() {
return this.value.equals(ChargeTimeType.SAVINGS_ACTIVATION.getValue()); return this.equals(ChargeTimeType.SAVINGS_ACTIVATION);
} }
public boolean isSavingsClosure() { public boolean isSavingsClosure() {
return this.value.equals(ChargeTimeType.SAVINGS_CLOSURE.getValue()); return this.equals(ChargeTimeType.SAVINGS_CLOSURE);
} }
public boolean isWithdrawalFee() { public boolean isWithdrawalFee() {
return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue()); return this.equals(ChargeTimeType.WITHDRAWAL_FEE);
} }
public boolean isSavingsNoActivityFee() { public boolean isSavingsNoActivityFee() {
return this.value.equals(ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue()); return this.equals(ChargeTimeType.SAVINGS_NOACTIVITY_FEE);
} }
public boolean isAnnualFee() { public boolean isAnnualFee() {
return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue()); return this.equals(ChargeTimeType.ANNUAL_FEE);
} }
public boolean isMonthlyFee() { public boolean isMonthlyFee() {
return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue()); return this.equals(ChargeTimeType.MONTHLY_FEE);
} }
public boolean isWeeklyFee() { public boolean isWeeklyFee() {
return this.value.equals(ChargeTimeType.WEEKLY_FEE.getValue()); return this.equals(ChargeTimeType.WEEKLY_FEE);
} }
public boolean isInstalmentFee() { public boolean isInstalmentFee() {
return this.value.equals(ChargeTimeType.INSTALMENT_FEE.getValue()); return this.equals(ChargeTimeType.INSTALMENT_FEE);
}
public boolean isSpecifiedDueDate() {
return this.equals(ChargeTimeType.SPECIFIED_DUE_DATE);
} }
public boolean isOverdueInstallment() { public boolean isOverdueInstallment() {
return this.value.equals(ChargeTimeType.OVERDUE_INSTALLMENT.getValue()); return this.equals(ChargeTimeType.OVERDUE_INSTALLMENT);
} }
public boolean isAllowedLoanChargeTime() { public boolean isAllowedLoanChargeTime() {
@@ -200,22 +204,22 @@ public enum ChargeTimeType {
} }
public boolean isOverdraftFee() { public boolean isOverdraftFee() {
return this.value.equals(ChargeTimeType.OVERDRAFT_FEE.getValue()); return this.equals(ChargeTimeType.OVERDRAFT_FEE);
} }
public boolean isTrancheDisbursement() { public boolean isTrancheDisbursement() {
return this.value.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()); return this.equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
} }
public boolean isShareAccountActivation() { public boolean isShareAccountActivation() {
return this.value.equals(ChargeTimeType.SHAREACCOUNT_ACTIVATION.getValue()); return this.equals(ChargeTimeType.SHAREACCOUNT_ACTIVATION);
} }
public boolean isSharesPurchase() { public boolean isSharesPurchase() {
return this.value.equals(ChargeTimeType.SHARE_PURCHASE.getValue()); return this.equals(ChargeTimeType.SHARE_PURCHASE);
} }
public boolean isSharesRedeem() { public boolean isSharesRedeem() {
return this.value.equals(ChargeTimeType.SHARE_REDEEM.getValue()); return this.equals(ChargeTimeType.SHARE_REDEEM);
} }
} }
@@ -20,7 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.api;
public interface LoanApiConstants { public interface LoanApiConstants {
String emiAmountParameterName = "fixedEmiAmount"; String fixedEmiAmountParameterName = "fixedEmiAmount";
String maxOutstandingBalanceParameterName = "maxOutstandingLoanBalance"; String maxOutstandingBalanceParameterName = "maxOutstandingLoanBalance";
String disbursementDataParameterName = "disbursementData"; String disbursementDataParameterName = "disbursementData";
String expectedDisbursementDateParameterName = "expectedDisbursementDate"; String expectedDisbursementDateParameterName = "expectedDisbursementDate";
@@ -36,6 +36,7 @@ public interface LoanApiConstants {
String amountParameterName = "amount"; String amountParameterName = "amount";
String chargeTimeTypeParameterName = "chargeTimeType"; String chargeTimeTypeParameterName = "chargeTimeType";
String chargeCalculationTypeParameterName = "chargeCalculationType"; String chargeCalculationTypeParameterName = "chargeCalculationType";
String chargePaymentModeParameterName = "chargePaymentMode";
String principalDisbursedParameterName = "transactionAmount"; String principalDisbursedParameterName = "transactionAmount";
String chargesParameterName = "charges"; String chargesParameterName = "charges";
String loanIdTobeApproved = "loanId"; String loanIdTobeApproved = "loanId";
@@ -18,44 +18,30 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.command; package org.apache.fineract.portfolio.loanaccount.command;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.Data;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
/** /**
* Java object representation of {@link LoanCharge} API JSON. * Java object representation of {@link LoanCharge} API JSON.
*/ */
public class LoanChargeCommand implements Comparable<LoanChargeCommand> { @Data
public class LoanChargeCommand implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Long id; private Long id;
private final Long chargeId; private Long chargeId;
private final BigDecimal amount; private BigDecimal amount;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Integer chargeTimeType; private Integer chargeTimeType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Integer chargeCalculationType; private Integer chargeCalculationType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final LocalDate dueDate; private LocalDate dueDate;
public LoanChargeCommand(final Long id, final Long chargeId, final BigDecimal amount, final Integer chargeTimeType,
final Integer chargeCalculationType, final LocalDate dueDate) {
this.id = id;
this.chargeId = chargeId;
this.amount = amount;
this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType;
this.dueDate = dueDate;
}
@Override
@SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals")
public int compareTo(final LoanChargeCommand o) {
int comparison = this.chargeId.compareTo(o.chargeId);
if (comparison == 0) {
comparison = this.amount.compareTo(o.amount);
}
return comparison;
}
} }
@@ -19,20 +19,20 @@
package org.apache.fineract.portfolio.loanaccount.data; package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.Getter; import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor; import lombok.Data;
@RequiredArgsConstructor @Data
@Getter @AllArgsConstructor
public class LoanCollateralManagementData { public class LoanCollateralManagementData {
private final Long clientCollateralId; private Long clientCollateralId;
private final BigDecimal quantity; private BigDecimal quantity;
private final BigDecimal total; private BigDecimal total;
private final BigDecimal totalCollateral; private BigDecimal totalCollateral;
private final Long id; private Long id;
} }
@@ -60,6 +60,8 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer; import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
@@ -104,10 +106,8 @@ import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.command.LoanChargeCommand;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
@@ -128,7 +128,6 @@ import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementData
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException; import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
@@ -153,6 +152,8 @@ import org.apache.fineract.useradministration.domain.AppUser;
@Entity @Entity
@Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"), @Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"),
@UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") }) @UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") })
@Setter
@Getter
public class Loan extends AbstractAuditableWithUTCDateTimeCustom { public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public static final String RECALCULATE_LOAN_SCHEDULE = "recalculateLoanSchedule"; public static final String RECALCULATE_LOAN_SCHEDULE = "recalculateLoanSchedule";
@@ -475,62 +476,73 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "enable_installment_level_delinquency", nullable = false) @Column(name = "enable_installment_level_delinquency", nullable = false)
private boolean enableInstallmentLevelDelinquency = false; private boolean enableInstallmentLevelDelinquency = false;
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, 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 LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final BigDecimal fixedEmiAmount, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateralManagement> collateral, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment) { final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
return new Loan(accountNo, client, null, loanType, fund, officer, loanPurpose, transactionProcessingStrategyCode, loanProduct, final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
return new Loan(accountNo, client, null, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, collateral, null, fixedEmiAmount, disbursementDetails, loanRepaymentScheduleDetail, null, loanCharges, collateral, null, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment); fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
enableInstallmentLevelDelinquency, submittedOnDate);
} }
public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType, public static Loan newGroupLoanApplication(final String accountNo, final Group group, final AccountType loanType,
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal fixedPrincipalPercentagePerInstallment) { final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
return new Loan(accountNo, null, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategyCode, loanProduct, final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
loanRepaymentScheduleDetail, null, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, return new Loan(accountNo, null, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment); loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
enableInstallmentLevelDelinquency, submittedOnDate);
} }
public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group, public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final AccountType loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal fixedPrincipalPercentagePerInstallment) { final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategyCode, loanProduct, final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
loanRepaymentScheduleDetail, null, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment); loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
enableInstallmentLevelDelinquency, submittedOnDate);
} }
protected Loan() { protected Loan() {
this.client = null; this.client = null;
} }
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund, private Loan(final String accountNo, final Client client, final Group group, final AccountType loanType, final Fund fund,
final Staff loanOfficer, final CodeValue loanPurpose, final String transactionProcessingStrategyCode, final Staff loanOfficer, final CodeValue loanPurpose,
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus, final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy, final LoanProduct loanProduct,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus, final Set<LoanCharge> loanCharges,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal fixedPrincipalPercentagePerInstallment) { final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment,
final ExternalId externalId, final LoanApplicationTerms loanApplicationTerms, final LoanScheduleModel loanScheduleModel,
final Boolean enableInstallmentLevelDelinquency, final LocalDate submittedOnDate) {
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail; this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
this.isFloatingInterestRate = isFloatingInterestRate; this.isFloatingInterestRate = isFloatingInterestRate;
this.interestRateDifferential = interestRateDifferential; this.interestRateDifferential = interestRateDifferential;
@@ -543,12 +555,14 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
this.client = client; this.client = client;
this.group = group; this.group = group;
this.loanType = loanType; this.loanType = loanType.getValue();
this.fund = fund; this.fund = fund;
this.loanOfficer = loanOfficer; this.loanOfficer = loanOfficer;
this.loanPurpose = loanPurpose; this.loanPurpose = loanPurpose;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode; this.transactionProcessingStrategyCode = transactionProcessingStrategy.getCode();
this.transactionProcessingStrategyName = transactionProcessingStrategy.getName();
this.loanProduct = loanProduct; this.loanProduct = loanProduct;
if (loanStatus != null) { if (loanStatus != null) {
this.loanStatus = loanStatus.getValue(); this.loanStatus = loanStatus.getValue();
@@ -563,7 +577,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.summary = new LoanSummary(); this.summary = new LoanSummary();
} }
if (loanType.equals(1) && collateral != null && !collateral.isEmpty()) { if (loanType.isIndividualAccount() && collateral != null && !collateral.isEmpty()) {
this.loanCollateralManagements = associateWithThisLoan(collateral); this.loanCollateralManagements = associateWithThisLoan(collateral);
} else { } else {
this.loanCollateralManagements = null; this.loanCollateralManagements = null;
@@ -573,7 +587,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.syncDisbursementWithMeeting = syncDisbursementWithMeeting; this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
this.fixedEmiAmount = fixedEmiAmount; this.fixedEmiAmount = fixedEmiAmount;
this.maxOutstandingLoanBalance = maxOutstandingLoanBalance; this.maxOutstandingLoanBalance = maxOutstandingLoanBalance;
this.disbursementDetails = disbursementDetails; if (disbursementDetails != null) {
this.disbursementDetails = disbursementDetails;
for (final LoanDisbursementDetails loanDisbursementDetails : disbursementDetails) {
loanDisbursementDetails.updateLoan(this);
}
}
this.approvedPrincipal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount(); this.approvedPrincipal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
this.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement; this.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement;
@@ -590,7 +609,26 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
// Add net get net disbursal amount from charges and principal // Add net get net disbursal amount from charges and principal
this.netDisbursalAmount = this.approvedPrincipal.subtract(deriveSumTotalOfChargesDueAtDisbursement()); this.netDisbursalAmount = this.approvedPrincipal.subtract(deriveSumTotalOfChargesDueAtDisbursement());
this.loanStatus = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue();
this.externalId = externalId;
this.termFrequency = loanApplicationTerms.getLoanTermFrequency();
this.termPeriodFrequencyType = loanApplicationTerms.getLoanTermPeriodFrequencyType().getValue();
this.expectedDisbursementDate = loanApplicationTerms.getExpectedDisbursementDate();
this.expectedFirstRepaymentOnDate = loanApplicationTerms.getRepaymentStartFromDate();
this.interestChargedFromDate = loanApplicationTerms.getInterestChargedFromDate();
this.submittedOnDate = submittedOnDate != null ? submittedOnDate : DateUtils.getBusinessLocalDate();
updateLoanSchedule(loanScheduleModel);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
// Copy interest recalculation settings if interest recalculation is enabled
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails
.createFrom(this.loanProduct.getProductInterestRecalculationDetails());
this.loanInterestRecalculationDetails.updateLoan(this);
}
this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency;
} }
public Integer getNumberOfRepayments() { public Integer getNumberOfRepayments() {
@@ -652,10 +690,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.accountNumberRequiresAutoGeneration; return this.accountNumberRequiresAutoGeneration;
} }
public void setAccountNumberRequiresAutoGeneration(final boolean accountNumberRequiresAutoGeneration) {
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
}
public void addLoanCharge(final LoanCharge loanCharge) { public void addLoanCharge(final LoanCharge loanCharge) {
if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) { if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) {
@@ -1460,274 +1494,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine); doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine);
} }
public Map<String, Object> loanApplicationModification(final JsonCommand command, final Set<LoanCharge> possiblyModifedLoanCharges,
final Set<LoanCollateralManagement> possiblyModifiedLoanCollateralItems, final AprCalculator aprCalculator,
boolean isChargesModified, final LoanProduct loanProduct) {
final Map<String, Object> actualChanges = this.loanRepaymentScheduleDetail.updateLoanApplicationAttributes(command, aprCalculator);
final MonetaryCurrency currency = new MonetaryCurrency(loanProduct.getCurrency().getCode(),
loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getCurrencyInMultiplesOf());
this.loanRepaymentScheduleDetail.updateCurrency(currency);
if (!actualChanges.isEmpty()) {
final boolean recalculateLoanSchedule = !(actualChanges.size() == 1 && actualChanges.containsKey(IN_ARREARS_TOLERANCE));
actualChanges.put(RECALCULATE_LOAN_SCHEDULE, recalculateLoanSchedule);
isChargesModified = true;
}
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
final String accountNoParamName = ACCOUNT_NO;
if (command.isChangeInStringParameterNamed(accountNoParamName, this.accountNumber)) {
final String newValue = command.stringValueOfParameterNamed(accountNoParamName);
actualChanges.put(accountNoParamName, newValue);
this.accountNumber = StringUtils.defaultIfEmpty(newValue, null);
}
if (command.isChangeInBooleanParameterNamed(CREATE_STANDING_INSTRUCTION_AT_DISBURSEMENT,
shouldCreateStandingInstructionAtDisbursement())) {
final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(CREATE_STANDING_INSTRUCTION_AT_DISBURSEMENT);
actualChanges.put(CREATE_STANDING_INSTRUCTION_AT_DISBURSEMENT, valueAsInput);
this.createStandingInstructionAtDisbursement = valueAsInput;
}
if (command.isChangeInStringParameterNamed(EXTERNAL_ID, this.externalId.getValue())) {
final String newValue = command.stringValueOfParameterNamed(EXTERNAL_ID);
ExternalId externalId = ExternalIdFactory.produce(newValue);
if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
actualChanges.put(EXTERNAL_ID, externalId);
this.externalId = externalId;
}
// add clientId, groupId and loanType changes to actual changes
final Long clientId = this.client == null ? null : this.client.getId();
if (command.isChangeInLongParameterNamed(CLIENT_ID, clientId)) {
final Long newValue = command.longValueOfParameterNamed(CLIENT_ID);
actualChanges.put(CLIENT_ID, newValue);
}
// FIXME: AA - We may require separate api command to move loan from one
// group to another
final Long groupId = this.group == null ? null : this.group.getId();
if (command.isChangeInLongParameterNamed(GROUP_ID, groupId)) {
final Long newValue = command.longValueOfParameterNamed(GROUP_ID);
actualChanges.put(GROUP_ID, newValue);
}
if (command.isChangeInLongParameterNamed(PRODUCT_ID, this.loanProduct.getId())) {
final Long newValue = command.longValueOfParameterNamed(PRODUCT_ID);
actualChanges.put(PRODUCT_ID, newValue);
actualChanges.put(RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.isChangeInBooleanParameterNamed(IS_FLOATING_INTEREST_RATE, this.isFloatingInterestRate)) {
final Boolean newValue = command.booleanObjectValueOfParameterNamed(IS_FLOATING_INTEREST_RATE);
actualChanges.put(IS_FLOATING_INTEREST_RATE, newValue);
this.isFloatingInterestRate = newValue;
}
if (command.isChangeInBigDecimalParameterNamed(INTEREST_RATE_DIFFERENTIAL, this.interestRateDifferential)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(INTEREST_RATE_DIFFERENTIAL);
actualChanges.put(INTEREST_RATE_DIFFERENTIAL, newValue);
this.interestRateDifferential = newValue;
}
Long existingFundId = null;
if (this.fund != null) {
existingFundId = this.fund.getId();
}
if (command.isChangeInLongParameterNamed(FUND_ID, existingFundId)) {
final Long newValue = command.longValueOfParameterNamed(FUND_ID);
actualChanges.put(FUND_ID, newValue);
}
Long existingLoanOfficerId = null;
if (this.loanOfficer != null) {
existingLoanOfficerId = this.loanOfficer.getId();
}
if (command.isChangeInLongParameterNamed(LOAN_OFFICER_ID, existingLoanOfficerId)) {
final Long newValue = command.longValueOfParameterNamed(LOAN_OFFICER_ID);
actualChanges.put(LOAN_OFFICER_ID, newValue);
}
Long existingLoanPurposeId = null;
if (this.loanPurpose != null) {
existingLoanPurposeId = this.loanPurpose.getId();
}
if (command.isChangeInLongParameterNamed(LOAN_PURPOSE_ID, existingLoanPurposeId)) {
final Long newValue = command.longValueOfParameterNamed(LOAN_PURPOSE_ID);
actualChanges.put(LOAN_PURPOSE_ID, newValue);
}
if (command.isChangeInStringParameterNamed(TRANSACTION_PROCESSING_STRATEGY_CODE, transactionProcessingStrategyCode)) {
final String newValue = command.stringValueOfParameterNamed(TRANSACTION_PROCESSING_STRATEGY_CODE);
actualChanges.put(TRANSACTION_PROCESSING_STRATEGY_CODE, newValue);
}
if (command.isChangeInLocalDateParameterNamed(SUBMITTED_ON_DATE, getSubmittedOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(SUBMITTED_ON_DATE);
actualChanges.put(SUBMITTED_ON_DATE, valueAsInput);
actualChanges.put(DATE_FORMAT, dateFormatAsInput);
actualChanges.put(LOCALE, localeAsInput);
this.submittedOnDate = command.localDateValueOfParameterNamed(SUBMITTED_ON_DATE);
}
if (command.isChangeInLocalDateParameterNamed(EXPECTED_DISBURSEMENT_DATE, getExpectedDisbursedOnLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(EXPECTED_DISBURSEMENT_DATE);
actualChanges.put(EXPECTED_DISBURSEMENT_DATE, valueAsInput);
actualChanges.put(DATE_FORMAT, dateFormatAsInput);
actualChanges.put(LOCALE, localeAsInput);
actualChanges.put(RECALCULATE_LOAN_SCHEDULE, true);
this.expectedDisbursementDate = command.localDateValueOfParameterNamed(EXPECTED_DISBURSEMENT_DATE);
removeFirstDisbursementTransaction();
}
if (command.isChangeInLocalDateParameterNamed(REPAYMENTS_STARTING_FROM_DATE, getExpectedFirstRepaymentOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(REPAYMENTS_STARTING_FROM_DATE);
actualChanges.put(REPAYMENTS_STARTING_FROM_DATE, valueAsInput);
actualChanges.put(DATE_FORMAT, dateFormatAsInput);
actualChanges.put(LOCALE, localeAsInput);
actualChanges.put(RECALCULATE_LOAN_SCHEDULE, true);
this.expectedFirstRepaymentOnDate = command.localDateValueOfParameterNamed(REPAYMENTS_STARTING_FROM_DATE);
}
if (command.isChangeInBooleanParameterNamed(SYNC_DISBURSEMENT_WITH_MEETING, isSyncDisbursementWithMeeting())) {
final Boolean valueAsInput = command.booleanObjectValueOfParameterNamed(SYNC_DISBURSEMENT_WITH_MEETING);
actualChanges.put(SYNC_DISBURSEMENT_WITH_MEETING, valueAsInput);
this.syncDisbursementWithMeeting = valueAsInput;
}
if (command.isChangeInLocalDateParameterNamed(INTEREST_CHARGED_FROM_DATE, getInterestChargedFromDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(INTEREST_CHARGED_FROM_DATE);
actualChanges.put(INTEREST_CHARGED_FROM_DATE, valueAsInput);
actualChanges.put(DATE_FORMAT, dateFormatAsInput);
actualChanges.put(LOCALE, localeAsInput);
actualChanges.put(RECALCULATE_LOAN_SCHEDULE, true);
this.interestChargedFromDate = command.localDateValueOfParameterNamed(INTEREST_CHARGED_FROM_DATE);
}
// the comparison should be done with the tenant date
// (DateUtils.getBusinessDate()) and not the server date (new
// LocalDate())
if (DateUtils.isDateInTheFuture(getSubmittedOnDate())) {
final String errorMessage = "The date on which a loan is submitted cannot be in the future.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, getSubmittedOnDate());
}
if (this.client != null && DateUtils.isBefore(getSubmittedOnDate(), this.client.getActivationDate())) {
final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage,
getSubmittedOnDate());
} else if (this.group != null && DateUtils.isBefore(getSubmittedOnDate(), this.group.getActivationDate())) {
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage,
getSubmittedOnDate());
}
if (DateUtils.isAfter(getSubmittedOnDate(), getExpectedDisbursedOnLocalDate())) {
final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+ getExpectedDisbursedOnLocalDate().toString();
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
getSubmittedOnDate(), getExpectedDisbursedOnLocalDate());
}
if (isChargesModified) {
actualChanges.put(PARAM_CHARGES, getLoanCharges(possiblyModifedLoanCharges));
actualChanges.put(RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.parameterExists(PARAM_COLLATERAL) && possiblyModifiedLoanCollateralItems != null
&& !possiblyModifiedLoanCollateralItems.isEmpty()) {
Set<LoanCollateralManagement> loanCollateralManagements = this.loanCollateralManagements;
boolean isTrue = possiblyModifiedLoanCollateralItems.equals(loanCollateralManagements);
if (!isTrue) {
actualChanges.put(PARAM_COLLATERAL, getLoanCollateralDataFormCommand(possiblyModifiedLoanCollateralItems));
}
}
if (command.isChangeInIntegerParameterNamed(LOAN_TERM_FREQUENCY, this.termFrequency)) {
final Integer newValue = command.integerValueOfParameterNamed(LOAN_TERM_FREQUENCY);
actualChanges.put(LOAN_TERM_FREQUENCY, newValue);
this.termFrequency = newValue;
}
if (command.isChangeInIntegerParameterNamed(LOAN_TERM_FREQUENCY_TYPE, this.termPeriodFrequencyType)) {
final Integer newValue = command.integerValueOfParameterNamed(LOAN_TERM_FREQUENCY_TYPE);
final PeriodFrequencyType newTermPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue);
actualChanges.put(LOAN_TERM_FREQUENCY_TYPE, newTermPeriodFrequencyType.getValue());
this.termPeriodFrequencyType = newValue;
}
if (command.isChangeInBigDecimalParameterNamed(PRINCIPAL, this.approvedPrincipal)) {
this.approvedPrincipal = command.bigDecimalValueOfParameterNamed(PRINCIPAL);
}
if (command.isChangeInBigDecimalParameterNamed(PRINCIPAL, this.proposedPrincipal)) {
this.proposedPrincipal = command.bigDecimalValueOfParameterNamed(PRINCIPAL);
}
if (loanProduct.isMultiDisburseLoan()) {
updateDisbursementDetails(command, actualChanges);
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
this.maxOutstandingLoanBalance)) {
this.maxOutstandingLoanBalance = command
.bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName);
}
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if (loanProduct.isDisallowExpectedDisbursements()) {
if (disbursementDataArray != null && !disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details are not allowed";
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
} else {
if (disbursementDataArray == null || disbursementDataArray.size() == 0) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
}
} else {
this.disbursementDetails.clear();
}
if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) {
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) {
this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
actualChanges.put(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount);
actualChanges.put(RECALCULATE_LOAN_SCHEDULE, true);
}
} else {
this.fixedEmiAmount = null;
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
this.fixedPrincipalPercentagePerInstallment)) {
this.fixedPrincipalPercentagePerInstallment = command
.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName);
actualChanges.put(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
this.fixedPrincipalPercentagePerInstallment);
}
return actualChanges;
}
public void recalculateAllCharges() { public void recalculateAllCharges() {
Set<LoanCharge> charges = this.getActiveCharges(); Set<LoanCharge> charges = this.getActiveCharges();
int penaltyWaitPeriod = 0; int penaltyWaitPeriod = 0;
@@ -2068,112 +1834,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return list; return list;
} }
private LoanCollateralManagementData[] getLoanCollateralDataFormCommand(final Set<LoanCollateralManagement> setOfLoanCollateral) {
LoanCollateralManagementData[] existingLoanCollateral = null;
final List<LoanCollateralManagementData> loanCollateralList = new ArrayList<>();
for (final LoanCollateralManagement loanCollateral : setOfLoanCollateral) {
loanCollateralList.add(loanCollateral.toCommand());
}
existingLoanCollateral = loanCollateralList.toArray(new LoanCollateralManagementData[0]);
return existingLoanCollateral;
}
private LoanChargeCommand[] getLoanCharges(final Set<LoanCharge> setOfLoanCharges) {
LoanChargeCommand[] existingLoanCharges = null;
final List<LoanChargeCommand> loanChargesList = new ArrayList<>();
for (final LoanCharge loanCharge : setOfLoanCharges) {
loanChargesList.add(loanCharge.toCommand());
}
existingLoanCharges = loanChargesList.toArray(new LoanChargeCommand[0]);
return existingLoanCharges;
}
private void removeFirstDisbursementTransaction() {
List<LoanTransaction> transactions = getLoanTransactions();
for (final LoanTransaction loanTransaction : transactions) {
if (loanTransaction.isDisbursement()) {
removeLoanTransaction(loanTransaction);
break;
}
}
}
public void loanApplicationSubmittal(final LoanScheduleModel loanSchedule, final LoanApplicationTerms loanApplicationTerms,
final LoanLifecycleStateMachine lifecycleStateMachine, final LocalDate submittedOn, final ExternalId externalId,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean allowTransactionsOnNonWorkingDay) {
updateLoanSchedule(loanSchedule);
lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, this);
this.externalId = externalId;
this.termFrequency = loanApplicationTerms.getLoanTermFrequency();
this.termPeriodFrequencyType = loanApplicationTerms.getLoanTermPeriodFrequencyType().getValue();
this.submittedOnDate = submittedOn;
this.expectedDisbursementDate = loanApplicationTerms.getExpectedDisbursementDate();
this.expectedFirstRepaymentOnDate = loanApplicationTerms.getRepaymentStartFromDate();
this.interestChargedFromDate = loanApplicationTerms.getInterestChargedFromDate();
updateLoanScheduleDependentDerivedFields();
if (DateUtils.isDateInTheFuture(submittedOn)) {
final String errorMessage = "The date on which a loan is submitted cannot be in the future.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn,
DateUtils.getBusinessLocalDate());
}
if (this.client != null && this.client.isActivatedAfter(submittedOn)) {
final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn,
client.getActivationDate());
}
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CREATED, submittedOn);
if (this.group != null && this.group.isActivatedAfter(submittedOn)) {
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, submittedOn,
group.getActivationDate());
}
if (DateUtils.isAfter(submittedOn, getExpectedDisbursedOnLocalDate())) {
final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+ getExpectedDisbursedOnLocalDate().toString();
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
submittedOn, getExpectedDisbursedOnLocalDate());
}
// charges are optional
int penaltyWaitPeriod = 0;
for (final LoanCharge loanCharge : getActiveCharges()) {
recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
// validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
// Copy interest recalculation settings if interest recalculation is enabled
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails
.createFrom(this.loanProduct.getProductInterestRecalculationDetails());
this.loanInterestRecalculationDetails.updateLoan(this);
}
}
private LocalDate determineExpectedMaturityDate() { private LocalDate determineExpectedMaturityDate() {
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream() List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList(); .filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
@@ -4876,7 +4536,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
break; break;
} }
return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from
// startDate // startDate
} }
private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
@@ -4945,7 +4605,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
public void updateInterestRateFrequencyType() { public void updateInterestRateFrequencyType() {
this.loanRepaymentScheduleDetail.updateInterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType()); this.loanRepaymentScheduleDetail.setInterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType());
} }
public Integer getTermFrequency() { public Integer getTermFrequency() {
@@ -4975,14 +4635,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.loanStatus = loanStatus; this.loanStatus = loanStatus;
} }
public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final WorkingDays workingDays,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final boolean allowTransactionsOnNonWorkingDay) {
// validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
}
private void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) { private void validateActivityNotBeforeClientOrGroupTransferDate(final LoanEvent event, final LocalDate activityDate) {
if (this.client != null && this.client.getOfficeJoiningDate() != null) { if (this.client != null && this.client.getOfficeJoiningDate() != null) {
final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate(); final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate();
@@ -4991,11 +4643,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
String action = null; String action = null;
String postfix = null; String postfix = null;
switch (event) { switch (event) {
case LOAN_CREATED:
errorMessage = "The date on which a loan is submitted cannot be earlier than client's transfer date to this office";
action = "submittal";
postfix = "cannot.be.before.client.transfer.date";
break;
case LOAN_APPROVED: case LOAN_APPROVED:
errorMessage = "The date on which a loan is approved cannot be earlier than client's transfer date to this office"; errorMessage = "The date on which a loan is approved cannot be earlier than client's transfer date to this office";
action = "approval"; action = "approval";
@@ -5400,6 +5047,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return currentDisbursementDetails; return currentDisbursementDetails;
} }
public void clearDisbursementDetails() {
this.disbursementDetails.clear();
}
public LoanDisbursementDetails getDisbursementDetails(final LocalDate transactionDate, final BigDecimal transactionAmount) { public LoanDisbursementDetails getDisbursementDetails(final LocalDate transactionDate, final BigDecimal transactionAmount) {
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) { for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
if (!disbursementDetail.isReversed() && disbursementDetail.getDisbursementDate().equals(transactionDate) if (!disbursementDetail.isReversed() && disbursementDetail.getDisbursementDate().equals(transactionDate)
@@ -5525,7 +5176,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
public void updateIsInterestRecalculationEnabled() { public void updateIsInterestRecalculationEnabled() {
this.loanRepaymentScheduleDetail.updateIsInterestRecalculationEnabled(isInterestRecalculationEnabledForProduct()); this.loanRepaymentScheduleDetail.setInterestRecalculationEnabled(isInterestRecalculationEnabledForProduct());
} }
public LoanInterestRecalculationDetails loanInterestRecalculationDetails() { public LoanInterestRecalculationDetails loanInterestRecalculationDetails() {
@@ -6196,11 +5847,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
// loanCalendar = loanCalendarInstance.getCalendar(); // loanCalendar = loanCalendarInstance.getCalendar();
// } // }
final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.graceOnPrincipalPayment(); final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.getGraceOnPrincipalPayment();
final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.graceOnInterestPayment(); final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.getGraceOnInterestPayment();
final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.graceOnInterestCharged(); final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.getGraceOnInterestCharged();
final LocalDate interestChargedFromDate = getInterestChargedFromDate(); final LocalDate interestChargedFromDate = getInterestChargedFromDate();
final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnDueDate(); final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnArrearsAgeing();
final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance(); final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance();
final BigDecimal emiAmount = getFixedEmiAmount(); final BigDecimal emiAmount = getFixedEmiAmount();
@@ -6258,7 +5909,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
public void updateNumberOfRepayments(Integer numberOfRepayments) { public void updateNumberOfRepayments(Integer numberOfRepayments) {
this.loanRepaymentScheduleDetail.updateNumberOfRepayments(numberOfRepayments); this.loanRepaymentScheduleDetail.setNumberOfRepayments(numberOfRepayments);
} }
public void updateRescheduledOnDate(LocalDate rescheduledOnDate) { public void updateRescheduledOnDate(LocalDate rescheduledOnDate) {
@@ -7134,8 +6785,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return rates; return rates;
} }
public Integer getLoanType() { public AccountType getLoanType() {
return loanType; return AccountType.fromInt(loanType);
} }
public void setLoanType(Integer loanType) { public void setLoanType(Integer loanType) {
@@ -54,7 +54,6 @@ import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException; import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.command.LoanChargeCommand;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail; import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData; import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
@@ -509,10 +508,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
return value.compareTo(BigDecimal.ZERO) > 0; return value.compareTo(BigDecimal.ZERO) > 0;
} }
public LoanChargeCommand toCommand() {
return new LoanChargeCommand(getId(), this.charge.getId(), this.amount, this.chargeTime, this.chargeCalculation, getDueLocalDate());
}
public LocalDate getDueLocalDate() { public LocalDate getDueLocalDate() {
return this.dueDate; return this.dueDate;
} }
@@ -306,10 +306,10 @@ public final class LoanApplicationTerms {
final Money principalMoney = loanProductRelatedDetail.getPrincipal(); final Money principalMoney = loanProductRelatedDetail.getPrincipal();
// //
final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment(); final Integer graceOnPrincipalPayment = loanProductRelatedDetail.getGraceOnPrincipalPayment();
final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail.recurringMoratoriumOnPrincipalPeriods(); final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail.getRecurringMoratoriumOnPrincipalPeriods();
final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment(); final Integer graceOnInterestPayment = loanProductRelatedDetail.getGraceOnInterestPayment();
final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged(); final Integer graceOnInterestCharged = loanProductRelatedDetail.getGraceOnInterestCharged();
// Interest recalculation settings // Interest recalculation settings
final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
@@ -334,7 +334,7 @@ public final class LoanApplicationTerms {
repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate,
inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance,
loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, loanProductRelatedDetail.getGraceOnArrearsAgeing(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper,
@@ -0,0 +1,44 @@
/**
* 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.portfolio.loanaccount.mapper;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.portfolio.loanaccount.command.LoanChargeCommand;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(config = MapstructMapperConfig.class)
public interface LoanChargeMapper {
@Mapping(target = "id", source = "loanCharge.id")
@Mapping(target = "chargeId", source = "loanCharge.charge.id")
@Mapping(target = "amount", expression = "java(loanCharge.getAmount(currency).getAmount())")
@Mapping(target = "chargeTimeType", source = "loanCharge.chargeTimeType.value")
@Mapping(target = "chargeCalculationType", source = "loanCharge.chargeCalculation.value")
@Mapping(target = "dueDate", source = "loanCharge.dueDate")
LoanChargeCommand map(LoanCharge loanCharge, MonetaryCurrency currency);
default Set<LoanChargeCommand> map(Set<LoanCharge> loanCharge, MonetaryCurrency currency) {
return loanCharge.stream().map(lc -> map(lc, currency)).collect(Collectors.toSet());
}
}
@@ -0,0 +1,39 @@
/**
* 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.portfolio.loanaccount.mapper;
import java.util.Set;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(config = MapstructMapperConfig.class)
public interface LoanCollateralManagementMapper {
@Mapping(target = "id", source = "id")
@Mapping(target = "clientCollateralId", source = "clientCollateralManagement.id")
@Mapping(target = "quantity", source = "quantity")
@Mapping(target = "total", ignore = true)
@Mapping(target = "totalCollateral", ignore = true)
LoanCollateralManagementData map(LoanCollateralManagement loanCollateralManagement);
Set<LoanCollateralManagementData> map(Set<LoanCollateralManagement> loanCollateralManagements);
}
@@ -725,8 +725,6 @@ public class LoanProduct extends AbstractPersistableCustom {
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength); enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength);
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal, this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
defaultMaxNumberOfInstallments); defaultMaxNumberOfInstallments);
@@ -1298,21 +1296,21 @@ public class LoanProduct extends AbstractPersistableCustom {
this.loanProductRelatedDetail.isEnableDownPayment())) { this.loanProductRelatedDetail.isEnableDownPayment())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT); final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT);
actualChanges.put(LoanProductConstants.ENABLE_DOWN_PAYMENT, newValue); actualChanges.put(LoanProductConstants.ENABLE_DOWN_PAYMENT, newValue);
this.loanProductRelatedDetail.updateEnableDownPayment(newValue); this.loanProductRelatedDetail.setEnableDownPayment(newValue);
} }
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
this.loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment())) { this.loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment())) {
BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT); BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT);
actualChanges.put(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, newValue); actualChanges.put(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, newValue);
this.loanProductRelatedDetail.updateDisbursedAmountPercentageForDownPayment(newValue); this.loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment(newValue);
} }
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT,
this.loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment())) { this.loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT); final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT);
actualChanges.put(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, newValue); actualChanges.put(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, newValue);
this.loanProductRelatedDetail.updateEnableAutoRepaymentForDownPayment(newValue); this.loanProductRelatedDetail.setEnableAutoRepaymentForDownPayment(newValue);
} }
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE, if (command.isChangeInIntegerParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE,
@@ -32,13 +32,13 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
Money getPrincipal(); Money getPrincipal();
Integer graceOnInterestCharged(); Integer getGraceOnInterestCharged();
Integer graceOnInterestPayment(); Integer getGraceOnInterestPayment();
Integer graceOnPrincipalPayment(); Integer getGraceOnPrincipalPayment();
Integer recurringMoratoriumOnPrincipalPeriods(); Integer getRecurringMoratoriumOnPrincipalPeriods();
Money getInArrearsTolerance(); Money getInArrearsTolerance();
@@ -60,5 +60,5 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
AmortizationMethod getAmortizationMethod(); AmortizationMethod getAmortizationMethod();
Integer getGraceOnDueDate(); Integer getGraceOnArrearsAgeing();
} }
@@ -24,14 +24,11 @@ import jakarta.persistence.Embedded;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
@@ -48,6 +45,8 @@ import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
* a {@link Loan}. * a {@link Loan}.
*/ */
@Embeddable @Embeddable
@Getter
@Setter
public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail { public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail {
@Embedded @Embedded
@@ -241,82 +240,32 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return Money.of(this.currency, this.principal); return Money.of(this.currency, this.principal);
} }
public void setPrincipal(BigDecimal principal) {
this.principal = principal;
}
@Override
public Integer graceOnInterestCharged() {
return this.graceOnInterestCharged;
}
@Override
public Integer graceOnInterestPayment() {
return this.graceOnInterestPayment;
}
@Override
public Integer graceOnPrincipalPayment() {
return this.graceOnPrincipalPayment;
}
@Override
public Integer recurringMoratoriumOnPrincipalPeriods() {
return this.recurringMoratoriumOnPrincipalPeriods;
}
@Override @Override
public Money getInArrearsTolerance() { public Money getInArrearsTolerance() {
return Money.of(this.currency, this.inArrearsTolerance); return Money.of(this.currency, this.inArrearsTolerance);
} }
// TODO: REVIEW
@Override @Override
public BigDecimal getNominalInterestRatePerPeriod() { public BigDecimal getNominalInterestRatePerPeriod() {
return this.nominalInterestRatePerPeriod == null ? null return this.nominalInterestRatePerPeriod == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString())); : BigDecimal.valueOf(Double.parseDouble(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString()));
} }
// TODO: REVIEW
@Override @Override
public PeriodFrequencyType getInterestPeriodFrequencyType() { public PeriodFrequencyType getInterestPeriodFrequencyType() {
return this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID : this.interestPeriodFrequencyType; return this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID : this.interestPeriodFrequencyType;
} }
// TODO: REVIEW
@Override @Override
public BigDecimal getAnnualNominalInterestRate() { public BigDecimal getAnnualNominalInterestRate() {
return this.annualNominalInterestRate == null ? null return this.annualNominalInterestRate == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.annualNominalInterestRate.stripTrailingZeros().toString())); : BigDecimal.valueOf(Double.parseDouble(this.annualNominalInterestRate.stripTrailingZeros().toString()));
} }
@Override // TODO: Move into assembler
public InterestMethod getInterestMethod() {
return this.interestMethod;
}
@Override
public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
return this.interestCalculationPeriodMethod;
}
@Override
public Integer getRepayEvery() {
return this.repayEvery;
}
@Override
public PeriodFrequencyType getRepaymentPeriodFrequencyType() {
return this.repaymentPeriodFrequencyType;
}
@Override
public Integer getNumberOfRepayments() {
return this.numberOfRepayments;
}
@Override
public AmortizationMethod getAmortizationMethod() {
return this.amortizationMethod;
}
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) { public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(20); final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
@@ -367,18 +316,6 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
loanScheduleProcessingType = newLoanScheduleProcessingType; loanScheduleProcessingType = newLoanScheduleProcessingType;
} }
final Map<String, Object> loanApplicationAttributeChanges = updateLoanApplicationAttributes(command, aprCalculator);
actualChanges.putAll(loanApplicationAttributeChanges);
return actualChanges;
}
public Map<String, Object> updateLoanApplicationAttributes(final JsonCommand command, final AprCalculator aprCalculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
final String localeAsInput = command.locale();
final String principalParamName = "principal"; final String principalParamName = "principal";
if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) { if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName); final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
@@ -557,63 +494,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.isEqualAmortization = newValue; this.isEqualAmortization = newValue;
} }
validateRepaymentPeriodWithGraceSettings();
return actualChanges; return actualChanges;
} }
public void updateCurrency(final MonetaryCurrency currency) { public void updateInterestRateDerivedFields(final AprCalculator aprCalculator) {
this.currency = currency;
}
public void validateRepaymentPeriodWithGraceSettings() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanproduct");
if (this.numberOfRepayments <= defaultToZeroIfNull(this.graceOnPrincipalPayment)) {
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(this.graceOnPrincipalPayment)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
if (this.numberOfRepayments <= defaultToZeroIfNull(this.graceOnInterestPayment)) {
baseDataValidator.reset().parameter("graceOnInterestPayment").value(this.graceOnInterestPayment)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
if (this.numberOfRepayments < defaultToZeroIfNull(this.graceOnInterestCharged)) {
baseDataValidator.reset().parameter("graceOnInterestCharged").value(this.graceOnInterestCharged)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
int graceOnPrincipal = 0;
if (this.getGraceOnPrincipalPayment() != null) {
graceOnPrincipal = this.getGraceOnPrincipalPayment();
}
int recurMoratoriumOnPrincipal = 0;
if (this.recurringMoratoriumOnPrincipalPeriods() != null) {
recurMoratoriumOnPrincipal = this.recurringMoratoriumOnPrincipalPeriods();
}
if ((recurMoratoriumOnPrincipal > 0) && ((this.numberOfRepayments - graceOnPrincipal) % (recurMoratoriumOnPrincipal + 1) != 1)) {
baseDataValidator.reset().parameter("graceOnPrincipalPayments.and.recurringMoratoriumOnPrincipalPeriods")
.value(graceOnPrincipal).value(recurMoratoriumOnPrincipal)
.failWithCode("causes.principal.moratorium.for.last.installment");
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
private Integer defaultToZeroIfNull(final Integer value) {
Integer result = value;
if (value == null) {
result = 0;
}
return result;
}
private void updateInterestRateDerivedFields(final AprCalculator aprCalculator) {
this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod, this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod,
this.numberOfRepayments, this.repayEvery, this.repaymentPeriodFrequencyType); this.numberOfRepayments, this.repayEvery, this.repaymentPeriodFrequencyType);
@@ -623,15 +507,6 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return this.currency.getCode().equalsIgnoreCase(currencyCode); return this.currency.getCode().equalsIgnoreCase(currencyCode);
} }
public void updateInterestPeriodFrequencyType(final PeriodFrequencyType interestPeriodFrequencyType) {
this.interestPeriodFrequencyType = interestPeriodFrequencyType;
}
@Override
public Integer getGraceOnDueDate() {
return this.graceOnArrearsAgeing;
}
public DaysInMonthType fetchDaysInMonthType() { public DaysInMonthType fetchDaysInMonthType() {
return DaysInMonthType.fromInt(this.daysInMonthType); return DaysInMonthType.fromInt(this.daysInMonthType);
} }
@@ -640,129 +515,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return DaysInYearType.fromInt(this.daysInYearType); return DaysInYearType.fromInt(this.daysInYearType);
} }
public boolean isInterestRecalculationEnabled() {
return this.isInterestRecalculationEnabled;
}
public void updateIsInterestRecalculationEnabled(final boolean isInterestRecalculationEnabled) {
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
}
public void updateNumberOfRepayments(Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
}
public Integer getGraceOnPrincipalPayment() {
return graceOnPrincipalPayment;
}
public void setGraceOnPrincipalPayment(Integer graceOnPrincipalPayment) {
this.graceOnPrincipalPayment = graceOnPrincipalPayment;
}
public Integer getGraceOnInterestPayment() {
return graceOnInterestPayment;
}
public void setGraceOnInterestPayment(Integer graceOnInterestPayment) {
this.graceOnInterestPayment = graceOnInterestPayment;
}
public Integer getGraceOnArrearsAgeing() {
return graceOnArrearsAgeing;
}
public void setGraceOnArrearsAgeing(Integer graceOnArrearsAgeing) {
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
}
public void setInterestMethod(InterestMethod interestMethod) {
this.interestMethod = interestMethod;
}
public void setInterestCalculationPeriodMethod(InterestCalculationPeriodMethod interestCalculationPeriodMethod) {
this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
}
public void setRepayEvery(Integer repayEvery) {
this.repayEvery = repayEvery;
}
public void setRepaymentPeriodFrequencyType(PeriodFrequencyType repaymentPeriodFrequencyType) {
this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType;
}
public void setAmortizationMethod(AmortizationMethod amortizationMethod) {
this.amortizationMethod = amortizationMethod;
}
public void setInArrearsTolerance(BigDecimal inArrearsTolerance) {
this.inArrearsTolerance = inArrearsTolerance;
}
public BigDecimal getArrearsTolerance() {
return this.inArrearsTolerance;
}
public void updateForFloatingInterestRates() { public void updateForFloatingInterestRates() {
this.nominalInterestRatePerPeriod = null; this.nominalInterestRatePerPeriod = null;
this.interestPeriodFrequencyType = PeriodFrequencyType.INVALID; this.interestPeriodFrequencyType = PeriodFrequencyType.INVALID;
this.annualNominalInterestRate = null; this.annualNominalInterestRate = null;
} }
public boolean isAllowPartialPeriodInterestCalcualtion() {
return this.allowPartialPeriodInterestCalcualtion;
}
public boolean isEqualAmortization() {
return isEqualAmortization;
}
public void setEqualAmortization(boolean isEqualAmortization) {
this.isEqualAmortization = isEqualAmortization;
}
public void setNominalInterestRatePerPeriod(BigDecimal nominalInterestRatePerPeriod) {
this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod;
}
public boolean isEnableDownPayment() {
return enableDownPayment;
}
public BigDecimal getDisbursedAmountPercentageForDownPayment() {
return disbursedAmountPercentageForDownPayment;
}
public void updateEnableDownPayment(boolean enableDownPayment) {
this.enableDownPayment = enableDownPayment;
}
public void updateDisbursedAmountPercentageForDownPayment(BigDecimal disbursedAmountPercentageForDownPayment) {
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
}
public Boolean isEnableAutoRepaymentForDownPayment() {
return enableAutoRepaymentForDownPayment;
}
public void updateEnableAutoRepaymentForDownPayment(boolean enableAutoRepaymentForDownPayment) {
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
}
public LoanScheduleType getLoanScheduleType() {
return loanScheduleType;
}
public LoanScheduleProcessingType getLoanScheduleProcessingType() {
return loanScheduleProcessingType;
}
public Integer getFixedLength() {
return this.fixedLength;
}
public void setFixedLength(Integer fixedLength) {
this.fixedLength = fixedLength;
}
} }
@@ -175,7 +175,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
List<EntityDatatableChecks> tableRequiredBeforAction = entityDatatableChecksRepository.findByEntityStatusAndProduct(entityName, List<EntityDatatableChecks> tableRequiredBeforAction = entityDatatableChecksRepository.findByEntityStatusAndProduct(entityName,
statusCode, productId); statusCode, productId);
if (tableRequiredBeforAction == null || tableRequiredBeforAction.size() < 1) { if (tableRequiredBeforAction == null || tableRequiredBeforAction.isEmpty()) {
tableRequiredBeforAction = entityDatatableChecksRepository.findByEntityStatusAndNoProduct(entityName, statusCode); tableRequiredBeforAction = entityDatatableChecksRepository.findByEntityStatusAndNoProduct(entityName, statusCode);
} }
if (tableRequiredBeforAction != null) { if (tableRequiredBeforAction != null) {
@@ -190,7 +190,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
reqDatatables.add(datatableName); reqDatatables.add(datatableName);
} }
} }
if (reqDatatables.size() > 0) { if (!reqDatatables.isEmpty()) {
throw new DatatableEntryRequiredException(reqDatatables.toString()); throw new DatatableEntryRequiredException(reqDatatables.toString());
} }
} }
@@ -81,7 +81,6 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstr
import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction; import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus; import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -250,7 +249,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
// changes to closed // changes to closed
disableStandingInstructionsLinkedToClosedLoan(loan); disableStandingInstructionsLinkedToClosedLoan(loan);
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { if (loan.getLoanType().isIndividualAccount()) {
// Mark Post Dated Check as paid. // Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction
.getLoanTransactionToRepaymentScheduleMappings(); .getLoanTransactionToRepaymentScheduleMappings();
@@ -34,8 +34,10 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
@@ -100,6 +102,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanDisbursementDetailsAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
@@ -115,10 +118,10 @@ import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyTy
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor
public class LoanScheduleAssembler { public class LoanScheduleAssembler {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@@ -137,35 +140,7 @@ public class LoanScheduleAssembler {
private final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator; private final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator;
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler;
@Autowired
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository,
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator,
final CalendarInstanceRepository calendarInstanceRepository, final LoanUtilService loanUtilService) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.loanProductRepository = loanProductRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.loanScheduleFactory = loanScheduleFactory;
this.aprCalculator = aprCalculator;
this.loanChargeAssembler = loanChargeAssembler;
this.calendarRepository = calendarRepository;
this.holidayRepository = holidayRepository;
this.configurationDomainService = configurationDomainService;
this.clientRepository = clientRepository;
this.groupRepository = groupRepository;
this.workingDaysRepository = workingDaysRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.variableLoanScheduleFromApiJsonValidator = variableLoanScheduleFromApiJsonValidator;
this.calendarInstanceRepository = calendarInstanceRepository;
this.loanUtilService = loanUtilService;
}
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) { public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
final Long loanProductId = this.fromApiJsonHelper.extractLongNamed("productId", element); final Long loanProductId = this.fromApiJsonHelper.extractLongNamed("productId", element);
@@ -177,6 +152,15 @@ public class LoanScheduleAssembler {
private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement element, final LoanProduct loanProduct) { private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement element, final LoanProduct loanProduct) {
final Boolean allowOverridingAmortization = loanProduct.getLoanConfigurableAttributes().getAmortizationBoolean();
final Boolean allowOverridingArrearsTolerance = loanProduct.getLoanConfigurableAttributes().getArrearsToleranceBoolean();
final Boolean allowOverridingGraceOnArrearsAging = loanProduct.getLoanConfigurableAttributes().getGraceOnArrearsAgingBoolean();
final Boolean allowOverridingInterestCalcPeriod = loanProduct.getLoanConfigurableAttributes().getInterestCalcPeriodBoolean();
final Boolean allowOverridingInterestMethod = loanProduct.getLoanConfigurableAttributes().getInterestMethodBoolean();
final Boolean allowOverridingGraceOnPrincipalAndInterestPayment = loanProduct.getLoanConfigurableAttributes()
.getGraceOnPrincipalAndInterestPaymentBoolean();
final Boolean allowOverridingRepaymentEvery = loanProduct.getLoanConfigurableAttributes().getRepaymentEveryBoolean();
final MonetaryCurrency currency = loanProduct.getCurrency(); final MonetaryCurrency currency = loanProduct.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
@@ -186,7 +170,9 @@ public class LoanScheduleAssembler {
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType); final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType);
final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); final Integer repaymentEvery = allowOverridingRepaymentEvery
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element)
: loanProduct.getLoanProductRelatedDetail().getRepayEvery();
final Integer repaymentFrequencyType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element); final Integer repaymentFrequencyType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element);
final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType); final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType);
final Integer nthDay = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyNthDayType", element); final Integer nthDay = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyNthDayType", element);
@@ -194,7 +180,8 @@ public class LoanScheduleAssembler {
final DayOfWeekType weekDayType = DayOfWeekType.fromInt(dayOfWeek); final DayOfWeekType weekDayType = DayOfWeekType.fromInt(dayOfWeek);
final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element);
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType); final AmortizationMethod amortizationMethod = allowOverridingAmortization ? AmortizationMethod.fromInt(amortizationType)
: loanProduct.getLoanProductRelatedDetail().getAmortizationMethod();
boolean isEqualAmortization = false; boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
@@ -206,12 +193,14 @@ public class LoanScheduleAssembler {
// interest terms // interest terms
final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
final InterestMethod interestMethod = InterestMethod.fromInt(interestType); final InterestMethod interestMethod = allowOverridingInterestMethod ? InterestMethod.fromInt(interestType)
: loanProduct.getLoanProductRelatedDetail().getInterestMethod();
final Integer interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", final Integer interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType",
element); element);
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod final InterestCalculationPeriodMethod interestCalculationPeriodMethod = allowOverridingInterestCalcPeriod
.fromInt(interestCalculationPeriodType); ? InterestCalculationPeriodMethod.fromInt(interestCalculationPeriodType)
: loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod();
Boolean allowPartialPeriodInterestCalcualtion = this.fromApiJsonHelper Boolean allowPartialPeriodInterestCalcualtion = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, element); .extractBooleanNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, element);
if (allowPartialPeriodInterestCalcualtion == null) { if (allowPartialPeriodInterestCalcualtion == null) {
@@ -322,23 +311,29 @@ public class LoanScheduleAssembler {
} }
// grace details // grace details
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element); final Integer graceOnPrincipalPayment = allowOverridingGraceOnPrincipalAndInterestPayment
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element)
: loanProduct.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element); .extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element); final Integer graceOnInterestPayment = allowOverridingGraceOnPrincipalAndInterestPayment
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element)
: loanProduct.getLoanProductRelatedDetail().getGraceOnInterestPayment();
final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element); final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element);
final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = this.configurationDomainService final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = this.configurationDomainService
.isInterestChargedFromDateSameAsDisbursementDate(); .isInterestChargedFromDateSameAsDisbursementDate();
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper final Integer graceOnArrearsAgeing = allowOverridingGraceOnArrearsAging
.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element); ? this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element)
: loanProduct.getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
// other // other
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element); final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
final Money inArrearsToleranceMoney = Money.of(currency, inArrearsTolerance); final Money inArrearsToleranceMoney = allowOverridingArrearsTolerance ? Money.of(currency, inArrearsTolerance)
: loanProduct.getLoanProductRelatedDetail().getInArrearsTolerance();
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
element); element);
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
@@ -638,8 +633,8 @@ public class LoanScheduleAssembler {
} }
} }
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) { public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element, final LoanProduct loanProduct) {
final LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element); final LoanApplicationTerms loanApplicationTerms = assembleLoanApplicationTermsFrom(element, loanProduct);
return loanApplicationTerms.toLoanProductRelatedDetail(); return loanApplicationTerms.toLoanProductRelatedDetail();
} }
@@ -671,7 +666,8 @@ public class LoanScheduleAssembler {
validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays); validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays);
validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays); validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays);
List<LoanDisbursementDetails> loanDisbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject()); List<LoanDisbursementDetails> loanDisbursementDetails = this.loanDisbursementDetailsAssembler
.fetchDisbursementData(element.getAsJsonObject());
return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element, loanDisbursementDetails); return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element, loanDisbursementDetails);
} }
@@ -769,7 +765,7 @@ public class LoanScheduleAssembler {
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Set<LocalDate> dueDates = new TreeSet<>(); Set<LocalDate> dueDates = new TreeSet<>();
LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate(); LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate();
Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().graceOnPrincipalPayment(); Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
if (graceOnPrincipal == null) { if (graceOnPrincipal == null) {
graceOnPrincipal = 0; graceOnPrincipal = 0;
} }
@@ -1160,4 +1156,232 @@ public class LoanScheduleAssembler {
minimumDaysBetweenDisbursalAndFirstRepayment); minimumDaysBetweenDisbursalAndFirstRepayment);
} }
} }
public void updateProductRelatedDetails(LoanProductRelatedDetail productRelatedDetail, Loan loan) {
final Boolean amortization = loan.loanProduct().getLoanConfigurableAttributes().getAmortizationBoolean();
final Boolean arrearsTolerance = loan.loanProduct().getLoanConfigurableAttributes().getArrearsToleranceBoolean();
final Boolean graceOnArrearsAging = loan.loanProduct().getLoanConfigurableAttributes().getGraceOnArrearsAgingBoolean();
final Boolean interestCalcPeriod = loan.loanProduct().getLoanConfigurableAttributes().getInterestCalcPeriodBoolean();
final Boolean interestMethod = loan.loanProduct().getLoanConfigurableAttributes().getInterestMethodBoolean();
final Boolean graceOnPrincipalAndInterestPayment = loan.loanProduct().getLoanConfigurableAttributes()
.getGraceOnPrincipalAndInterestPaymentBoolean();
final Boolean repaymentEvery = loan.loanProduct().getLoanConfigurableAttributes().getRepaymentEveryBoolean();
if (!amortization) {
productRelatedDetail.setAmortizationMethod(loan.loanProduct().getLoanProductRelatedDetail().getAmortizationMethod());
}
if (!arrearsTolerance) {
productRelatedDetail
.setInArrearsTolerance(loan.loanProduct().getLoanProductRelatedDetail().getInArrearsTolerance().getAmount());
}
if (!graceOnArrearsAging) {
productRelatedDetail.setGraceOnArrearsAgeing(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing());
}
if (!interestCalcPeriod) {
productRelatedDetail.setInterestCalculationPeriodMethod(
loan.loanProduct().getLoanProductRelatedDetail().getInterestCalculationPeriodMethod());
}
if (!interestMethod) {
productRelatedDetail.setInterestMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestMethod());
}
if (!graceOnPrincipalAndInterestPayment) {
productRelatedDetail.setGraceOnInterestPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnInterestPayment());
productRelatedDetail.setGraceOnPrincipalPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnPrincipalPayment());
}
if (!repaymentEvery) {
productRelatedDetail.setRepayEvery(loan.loanProduct().getLoanProductRelatedDetail().getRepayEvery());
}
}
public void updateLoanApplicationAttributes(JsonCommand command, Loan loan, Map<String, Object> changes) {
final String localeAsInput = command.locale();
final String principalParamName = "principal";
LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
if (command.isChangeInBigDecimalParameterNamed(principalParamName, loanProductRelatedDetail.getPrincipal().getAmount())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
changes.put(principalParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setPrincipal(newValue);
}
final String repaymentEveryParamName = "repaymentEvery";
if (command.isChangeInIntegerParameterNamed(repaymentEveryParamName, loanProductRelatedDetail.getRepayEvery())) {
final Integer newValue = command.integerValueOfParameterNamed(repaymentEveryParamName);
changes.put(repaymentEveryParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setRepayEvery(newValue);
}
final String repaymentFrequencyTypeParamName = "repaymentFrequencyType";
if (command.isChangeInIntegerParameterNamed(repaymentFrequencyTypeParamName,
loanProductRelatedDetail.getRepaymentPeriodFrequencyType().getValue())) {
Integer newValue = command.integerValueOfParameterNamed(repaymentFrequencyTypeParamName);
changes.put(repaymentFrequencyTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setRepaymentPeriodFrequencyType(PeriodFrequencyType.fromInt(newValue));
}
if (PeriodFrequencyType.MONTHS.equals(loanProductRelatedDetail.getRepaymentPeriodFrequencyType())) {
final String repaymentFrequencyNthDayTypeParamName = "repaymentFrequencyNthDayType";
Integer newValue = command.integerValueOfParameterNamed(repaymentFrequencyNthDayTypeParamName);
changes.put(repaymentFrequencyNthDayTypeParamName, newValue);
final String repaymentFrequencyDayOfWeekTypeParamName = "repaymentFrequencyDayOfWeekType";
newValue = command.integerValueOfParameterNamed(repaymentFrequencyDayOfWeekTypeParamName);
changes.put(repaymentFrequencyDayOfWeekTypeParamName, newValue);
changes.put("locale", localeAsInput);
}
final String numberOfRepaymentsParamName = "numberOfRepayments";
if (command.isChangeInIntegerParameterNamed(numberOfRepaymentsParamName, loanProductRelatedDetail.getNumberOfRepayments())) {
final Integer newValue = command.integerValueOfParameterNamed(numberOfRepaymentsParamName);
changes.put(numberOfRepaymentsParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setNumberOfRepayments(newValue);
}
final String amortizationTypeParamName = "amortizationType";
if (command.isChangeInIntegerParameterNamed(amortizationTypeParamName,
loanProductRelatedDetail.getAmortizationMethod().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(amortizationTypeParamName);
changes.put(amortizationTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setAmortizationMethod(AmortizationMethod.fromInt(newValue));
}
final String inArrearsToleranceParamName = "inArrearsTolerance";
if (command.isChangeInBigDecimalParameterNamed(inArrearsToleranceParamName,
loanProductRelatedDetail.getInArrearsTolerance().getAmount())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(inArrearsToleranceParamName);
changes.put(inArrearsToleranceParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInArrearsTolerance(newValue);
}
final String interestRatePerPeriodParamName = "interestRatePerPeriod";
if (command.isChangeInBigDecimalParameterNamed(interestRatePerPeriodParamName,
loanProductRelatedDetail.getNominalInterestRatePerPeriod())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRatePerPeriodParamName);
changes.put(interestRatePerPeriodParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setNominalInterestRatePerPeriod(newValue);
loanProductRelatedDetail.updateInterestRateDerivedFields(aprCalculator);
}
final String interestRateFrequencyTypeParamName = "interestRateFrequencyType";
final int interestPeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType() == null
? PeriodFrequencyType.INVALID.getValue()
: loanProductRelatedDetail.getInterestPeriodFrequencyType().getValue();
if (command.isChangeInIntegerParameterNamed(interestRateFrequencyTypeParamName, interestPeriodFrequencyType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestRateFrequencyTypeParamName);
changes.put(interestRateFrequencyTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInterestPeriodFrequencyType(PeriodFrequencyType.fromInt(newValue));
loanProductRelatedDetail.updateInterestRateDerivedFields(aprCalculator);
}
final String interestTypeParamName = "interestType";
if (command.isChangeInIntegerParameterNamed(interestTypeParamName, loanProductRelatedDetail.getInterestMethod().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(interestTypeParamName);
changes.put(interestTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInterestMethod(InterestMethod.fromInt(newValue));
}
final String interestCalculationPeriodTypeParamName = "interestCalculationPeriodType";
if (command.isChangeInIntegerParameterNamed(interestCalculationPeriodTypeParamName,
loanProductRelatedDetail.getInterestCalculationPeriodMethod().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(interestCalculationPeriodTypeParamName);
changes.put(interestCalculationPeriodTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInterestCalculationPeriodMethod(InterestCalculationPeriodMethod.fromInt(newValue));
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion())) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME);
changes.put(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, newValue);
loanProductRelatedDetail.setAllowPartialPeriodInterestCalcualtion(newValue);
}
if (loanProductRelatedDetail.getInterestCalculationPeriodMethod().isDaily()) {
loanProductRelatedDetail.setAllowPartialPeriodInterestCalcualtion(false);
}
final String graceOnPrincipalPaymentParamName = "graceOnPrincipalPayment";
if (command.isChangeInIntegerParameterNamed(graceOnPrincipalPaymentParamName,
loanProductRelatedDetail.getGraceOnPrincipalPayment())) {
final Integer newValue = command.integerValueOfParameterNamed(graceOnPrincipalPaymentParamName);
changes.put(graceOnPrincipalPaymentParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnPrincipalPayment(newValue);
}
final String recurringMoratoriumOnPrincipalPeriodsParamName = "recurringMoratoriumOnPrincipalPeriods";
if (command.isChangeInIntegerParameterNamed(recurringMoratoriumOnPrincipalPeriodsParamName,
loanProductRelatedDetail.getRecurringMoratoriumOnPrincipalPeriods())) {
final Integer newValue = command.integerValueOfParameterNamed(recurringMoratoriumOnPrincipalPeriodsParamName);
changes.put(recurringMoratoriumOnPrincipalPeriodsParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setRecurringMoratoriumOnPrincipalPeriods(newValue);
}
final String graceOnInterestPaymentParamName = "graceOnInterestPayment";
if (command.isChangeInIntegerParameterNamed(graceOnInterestPaymentParamName,
loanProductRelatedDetail.getGraceOnInterestPayment())) {
final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestPaymentParamName);
changes.put(graceOnInterestPaymentParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnInterestPayment(newValue);
}
final String graceOnInterestChargedParamName = "graceOnInterestCharged";
if (command.isChangeInIntegerParameterNamed(graceOnInterestChargedParamName,
loanProductRelatedDetail.getGraceOnInterestCharged())) {
final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestChargedParamName);
changes.put(graceOnInterestChargedParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnInterestCharged(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
loanProductRelatedDetail.getGraceOnArrearsAgeing())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME);
changes.put(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnArrearsAgeing(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME,
loanProductRelatedDetail.fetchDaysInMonthType().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME);
changes.put(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setDaysInMonthType(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME,
loanProductRelatedDetail.fetchDaysInYearType().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME);
changes.put(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setDaysInYearType(newValue);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME,
loanProductRelatedDetail.isInterestRecalculationEnabled())) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME);
changes.put(LoanProductConstants.IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME, newValue);
loanProductRelatedDetail.setInterestRecalculationEnabled(newValue);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM,
loanProductRelatedDetail.isEqualAmortization())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM);
changes.put(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, newValue);
loanProductRelatedDetail.setEqualAmortization(newValue);
}
}
} }
@@ -24,80 +24,46 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonQuery; import org.apache.fineract.infrastructure.core.api.JsonQuery;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanScheduleValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@Transactional @Transactional
@RequiredArgsConstructor
public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService { public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService {
private final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer; private final LoanScheduleValidator fromApiJsonDeserializer;
private final LoanScheduleAssembler loanScheduleAssembler; private final LoanScheduleAssembler loanScheduleAssembler;
private final FromJsonHelper fromJsonHelper; private final LoanApplicationValidator loanApiJsonDeserializer;
private final LoanProductRepository loanProductRepository;
private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer;
private final LoanReadPlatformService loanReadPlatformService;
private final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer;
private final LoanAssembler loanAssembler;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final ConfigurationDomainService configurationDomainService;
private final CurrencyReadPlatformService currencyReadPlatformService; private final CurrencyReadPlatformService currencyReadPlatformService;
private final LoanUtilService loanUtilService; private final LoanUtilService loanUtilService;
private final LoanRepositoryWrapper loanRepository;
@Autowired private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer, private final LoanSummaryWrapper loanSummaryWrapper;
final LoanScheduleAssembler loanScheduleAssembler, final FromJsonHelper fromJsonHelper,
final LoanProductRepository loanProductRepository, final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer,
final LoanReadPlatformService loanReadPlatformService, final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer,
final LoanAssembler loanAssembler,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final ConfigurationDomainService configurationDomainService, final CurrencyReadPlatformService currencyReadPlatformService,
final LoanUtilService loanUtilService) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanScheduleAssembler = loanScheduleAssembler;
this.fromJsonHelper = fromJsonHelper;
this.loanProductRepository = loanProductRepository;
this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer;
this.loanReadPlatformService = loanReadPlatformService;
this.loanApiJsonDeserializer = loanApiJsonDeserializer;
this.loanAssembler = loanAssembler;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.configurationDomainService = configurationDomainService;
this.currencyReadPlatformService = currencyReadPlatformService;
this.loanUtilService = loanUtilService;
}
@Override @Override
public LoanScheduleModel calculateLoanSchedule(final JsonQuery query, Boolean validateParams) { public LoanScheduleModel calculateLoanSchedule(final JsonQuery query, Boolean validateParams) {
@@ -105,44 +71,19 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
/*** /***
* TODO: Vishwas, this is probably not required, test and remove the same * TODO: Vishwas, this is probably not required, test and remove the same
**/ **/
final Long productId = this.fromJsonHelper.extractLongNamed("productId", query.parsedJson());
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));
if (validateParams) { if (validateParams) {
boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans(); this.loanApiJsonDeserializer.validateForCreate(query);
this.loanApiJsonDeserializer.validateForCreate(query.json(), isMeetingMandatoryForJLGLoans, loanProduct);
} }
this.fromApiJsonDeserializer.validate(query.json()); this.fromApiJsonDeserializer.validate(query.json());
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
if (loanProduct.isUseBorrowerCycle()) {
final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", query.parsedJson());
final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", query.parsedJson());
Integer cycleNumber = 0;
if (clientId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, loanProduct.getId());
} else if (groupId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(), loanProduct.getId());
}
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(query.parsedJson(), baseDataValidator, loanProduct,
cycleNumber);
} else {
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(query.parsedJson(), baseDataValidator, loanProduct);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson()); return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson());
} }
@Override @Override
public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long loanId) { public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = fetchLoan(loanId);
LocalDate today = DateUtils.getBusinessLocalDate(); LocalDate today = DateUtils.getBusinessLocalDate();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
@@ -210,7 +151,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public LoanScheduleData generateLoanScheduleForVariableInstallmentRequest(Long loanId, final String json) { public LoanScheduleData generateLoanScheduleForVariableInstallmentRequest(Long loanId, final String json) {
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = fetchLoan(loanId);
this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, json); this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, json);
return constructLoanScheduleData(loan); return constructLoanScheduleData(loan);
} }
@@ -262,19 +203,23 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
CurrencyData currencyData = this.currencyReadPlatformService.retrieveCurrency(currency.getCode()); CurrencyData currencyData = this.currencyReadPlatformService.retrieveCurrency(currency.getCode());
LoanScheduleData scheduleData = new LoanScheduleData(currencyData, installmentData, return new LoanScheduleData(currencyData, installmentData, loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments(),
loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments(), principal.getAmount(), principal.getAmount(), principal.getAmount(), principal.getAmount(), totalInterest.getAmount(), totalCharge.getAmount(), totalPenalty.getAmount(),
totalInterest.getAmount(), totalCharge.getAmount(), totalPenalty.getAmount(),
principal.plus(totalCharge).plus(totalInterest).plus(totalPenalty).getAmount()); principal.plus(totalCharge).plus(totalInterest).plus(totalPenalty).getAmount());
return scheduleData;
} }
private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) { private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) {
final LocalDate recalculateFrom = null; final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO); return loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
return loanApplicationTerms; }
private Loan fetchLoan(final Long accountId) {
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
return loanAccount;
} }
} }
@@ -18,7 +18,9 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.serialization; package org.apache.fineract.portfolio.loanaccount.serialization;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -27,6 +29,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -36,8 +39,20 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.exception.InvalidCurrencyException;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@@ -45,6 +60,8 @@ import org.springframework.stereotype.Component;
public final class LoanChargeApiJsonValidator { public final class LoanChargeApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { if (!dataValidationErrors.isEmpty()) {
@@ -274,4 +291,162 @@ public final class LoanChargeApiJsonValidator {
} }
} }
} }
public void validateLoanCharges(JsonElement element, LoanProduct loanProduct, DataValidatorBuilder baseDataValidator) {
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.chargesParameterName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.get(LoanApiConstants.chargesParameterName).isJsonArray()) {
final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Set<String> supportedParameters = new HashSet<>(
Arrays.asList(LoanApiConstants.idParameterName, LoanApiConstants.chargeIdParameterName,
LoanApiConstants.amountParameterName, LoanApiConstants.chargeTimeTypeParameterName,
LoanApiConstants.chargeCalculationTypeParameterName, LoanApiConstants.dueDateParamName,
LoanApiConstants.chargePaymentModeParameterName, LoanApiConstants.externalIdParameterName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.chargesParameterName).getAsJsonArray();
for (int i = 1; i <= array.size(); i++) {
final JsonObject loanChargeElement = array.get(i - 1).getAsJsonObject();
final String arrayObjectJson = this.fromApiJsonHelper.toJson(loanChargeElement);
this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap, arrayObjectJson,
supportedParameters);
String chargeCurrencyCode;
Charge chargeDefinition;
ChargeTimeType chargeTime;
ChargeCalculationType chargeCalculation;
ChargePaymentMode chargePaymentModeEnum;
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale);
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType",
loanChargeElement, locale);
final Integer chargePaymentMode = this.fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", loanChargeElement,
locale);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.chargeIdParameterName,
loanChargeElement);
final Long loanChargeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.idParameterName, element);
if (loanChargeId == null) {
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.chargeIdParameterName, i).value(chargeId).notNull()
.integerGreaterThanZero();
} else if (chargeId == null) {
baseDataValidator.reset().parameter(LoanApiConstants.idParameterName)
.parameterAtIndexArray(LoanApiConstants.idParameterName, i).value(loanChargeId).notNull()
.integerGreaterThanZero();
}
if (loanChargeId == null && chargeId == null) {
return;
}
if (chargeId != null) {
chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
chargeCurrencyCode = chargeDefinition.getCurrencyCode();
if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
} else {
chargeTime = ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType());
}
if (chargeCalculationType != null) {
chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
} else {
chargeCalculation = ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation());
}
if (chargePaymentMode != null) {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
} else {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargeDefinition.getChargePaymentMode());
}
} else {
LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
chargeDefinition = loanCharge.getCharge();
chargeCurrencyCode = chargeDefinition.getCurrencyCode();
chargeTime = loanCharge.getChargeTimeType();
chargeCalculation = loanCharge.getChargeCalculation();
chargePaymentModeEnum = loanCharge.getChargePaymentMode();
}
if (!loanProduct.hasCurrencyCodeOf(chargeCurrencyCode)) {
final String errorMessage = "Charge and Loan must have the same currency.";
// TODO: GeneralPlatformDomainRuleException vs PlatformApiDataValidationException
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
if (chargeDefinition.isOverdueInstallment()) {
final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null,
chargeDefinition.getName());
}
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.amountParameterName,
loanChargeElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.amountParameterName, i).value(amount).notNull().positiveAmount();
if (chargeTime.isSpecifiedDueDate()) {
LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName,
loanChargeElement, dateFormat, locale);
LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
if (DateUtils.isBefore(dueDate, expectedDisbursementDate)) {
final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range.";
throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage,
expectedDisbursementDate, chargeDefinition.getName());
}
}
if (chargePaymentMode != null) {
if (chargePaymentModeEnum.isPaymentModeAccountTransfer()) {
final Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed("linkAccountId", element);
if (savingsAccountId == null) {
final String errorMessage = "one of the charges requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge", errorMessage);
}
}
}
validateInterestBearingLoanProductRestriction(chargeCalculation, chargeTime, loanProduct, baseDataValidator);
}
}
}
}
private void validateInterestBearingLoanProductRestriction(ChargeCalculationType chargeCalculationType, ChargeTimeType chargeTime,
LoanProduct loanProduct, DataValidatorBuilder baseDataValidator) {
if (loanProduct.isInterestRecalculationEnabled()) {
String errorcode = null;
switch (chargeCalculationType) {
case PERCENT_OF_AMOUNT:
if (chargeTime.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (chargeTime.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
} else if (chargeTime.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
case PERCENT_OF_INTEREST:
if (chargeTime.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
default:
break;
}
if (errorcode != null) {
baseDataValidator.reset().parameter("charges").failWithCode(errorcode);
}
}
}
} }
@@ -59,7 +59,7 @@ import org.springframework.stereotype.Component;
public final class LoanEventApiJsonValidator { public final class LoanEventApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer; private final LoanApplicationValidator fromApiJsonDeserializer;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -79,12 +79,12 @@ public final class LoanEventApiJsonValidator {
if (isAccountTransfer) { if (isAccountTransfer) {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat", disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.disbursementNetDisbursalAmountParameterName)); LoanApiConstants.disbursementNetDisbursalAmountParameterName));
} else { } else {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat", disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
"paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate", "paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.postDatedChecks, LoanApiConstants.disbursementNetDisbursalAmountParameterName)); LoanApiConstants.postDatedChecks, LoanApiConstants.disbursementNetDisbursalAmountParameterName));
} }
@@ -111,9 +111,9 @@ public final class LoanEventApiJsonValidator {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementNetDisbursalAmountParameterName).value(netDisbursalAmount) baseDataValidator.reset().parameter(LoanApiConstants.disbursementNetDisbursalAmountParameterName).value(netDisbursalAmount)
.ignoreIfNull().positiveAmount(); .ignoreIfNull().positiveAmount();
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
element); element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount() baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
.notGreaterThanMax(principal); .notGreaterThanMax(principal);
validatePaymentDetails(baseDataValidator, element); validatePaymentDetails(baseDataValidator, element);
@@ -40,7 +40,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public final class CalculateLoanScheduleQueryFromApiJsonHelper { public final class LoanScheduleValidator {
/** /**
* The parameters supported for this command. * The parameters supported for this command.
@@ -64,7 +64,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
LoanApiConstants.submittedOnNoteParameterName, LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName, LoanApiConstants.submittedOnNoteParameterName, LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName,
LoanApiConstants.chargesParameterName, LoanApiConstants.collateralParameterName, LoanApiConstants.chargesParameterName, LoanApiConstants.collateralParameterName,
LoanApiConstants.syncDisbursementWithMeetingParameterName, LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.syncDisbursementWithMeetingParameterName, LoanApiConstants.linkAccountIdParameterName,
LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.disbursementDataParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
LoanApiConstants.createStandingInstructionAtDisbursementParameterName, LoanApiConstants.isFloatingInterestRateParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName, LoanApiConstants.isFloatingInterestRateParameterName,
LoanApiConstants.interestRateDifferentialParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, LoanApiConstants.interestRateDifferentialParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
@@ -76,7 +76,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@Autowired @Autowired
public CalculateLoanScheduleQueryFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper) { public LoanScheduleValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
} }
@@ -31,6 +31,6 @@ public interface GLIMAccountInfoWritePlatformService {
void incrementChildAccountCount(GroupLoanIndividualMonitoringAccount glimAccount); void incrementChildAccountCount(GroupLoanIndividualMonitoringAccount glimAccount);
void addGLIMAccountInfo(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount, GroupLoanIndividualMonitoringAccount createGLIMAccount(String accountNumber, Group group, BigDecimal principalAmount,
Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId); Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId);
} }
@@ -21,30 +21,23 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@RequiredArgsConstructor @RequiredArgsConstructor
public class GLIMAccountInfoWritePlatformServiceImpl implements GLIMAccountInfoWritePlatformService { public class GLIMAccountInfoWritePlatformServiceImpl implements GLIMAccountInfoWritePlatformService {
private final PlatformSecurityContext context;
private final GLIMAccountInfoRepository glimAccountRepository; private final GLIMAccountInfoRepository glimAccountRepository;
private final LoanRepository loanRepository;
@Override @Override
public void addGLIMAccountInfo(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount, public GroupLoanIndividualMonitoringAccount createGLIMAccount(String accountNumber, Group group, BigDecimal principalAmount,
Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) { Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) {
GroupLoanIndividualMonitoringAccount glimAccountInfo = GroupLoanIndividualMonitoringAccount.getInstance(accountNumber, group, GroupLoanIndividualMonitoringAccount glimAccountInfo = GroupLoanIndividualMonitoringAccount.getInstance(accountNumber, group,
principalAmount, childAccountsCount, isAcceptingChild, loanStatus, applicationId); principalAmount, childAccountsCount, isAcceptingChild, loanStatus, applicationId);
this.glimAccountRepository.save(glimAccountInfo); return this.glimAccountRepository.save(glimAccountInfo);
} }
@Override @Override
@@ -18,29 +18,35 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.service; package org.apache.fineract.portfolio.loanaccount.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.api.JsonQuery;
import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository; import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType; import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
import org.apache.fineract.organisation.monetary.exception.InvalidCurrencyException; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.organisation.staff.domain.StaffRepository; import org.apache.fineract.organisation.staff.domain.StaffRepository;
import org.apache.fineract.organisation.staff.exception.StaffNotFoundException; import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
@@ -48,21 +54,20 @@ import org.apache.fineract.organisation.staff.exception.StaffRoleException;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType; import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.accountdetails.service.AccountEnumerations; import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomain;
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler; import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler;
import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.fund.domain.FundRepository; import org.apache.fineract.portfolio.fund.domain.FundRepository;
import org.apache.fineract.portfolio.fund.exception.FundNotFoundException; import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.domain.GroupRepository; import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
@@ -72,21 +77,24 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachin
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule; import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTopupDetails;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException; import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException; import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.rate.domain.Rate;
import org.apache.fineract.portfolio.rate.service.RateAssembler; import org.apache.fineract.portfolio.rate.service.RateAssembler;
@@ -98,7 +106,7 @@ public class LoanAssembler {
private final LoanRepositoryWrapper loanRepository; private final LoanRepositoryWrapper loanRepository;
private final LoanProductRepository loanProductRepository; private final LoanProductRepository loanProductRepository;
private final ClientRepositoryWrapper clientRepository; private final ClientRepositoryWrapper clientRepository;
private final GroupRepository groupRepository; private final GroupRepositoryWrapper groupRepository;
private final FundRepository fundRepository; private final FundRepository fundRepository;
private final StaffRepository staffRepository; private final StaffRepository staffRepository;
private final CodeValueRepositoryWrapper codeValueRepository; private final CodeValueRepositoryWrapper codeValueRepository;
@@ -110,10 +118,18 @@ public class LoanAssembler {
private final HolidayRepository holidayRepository; private final HolidayRepository holidayRepository;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanUtilService loanUtilService;
private final RateAssembler rateAssembler; private final RateAssembler rateAssembler;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final ExternalIdFactory externalIdFactory; private final ExternalIdFactory externalIdFactory;
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
private final GLIMAccountInfoRepository glimRepository;
private final AccountNumberGenerator accountNumberGenerator;
private final GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService;
private final LoanCollateralAssembler loanCollateralAssembler;
private final LoanScheduleCalculationPlatformService calculationPlatformService;
private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler;
private final LoanChargeMapper loanChargeMapper;
private final LoanCollateralManagementMapper loanCollateralManagementMapper;
public Loan assembleFrom(final Long accountId) { public Loan assembleFrom(final Long accountId) {
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true); final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
@@ -133,20 +149,10 @@ public class LoanAssembler {
final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element); final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element);
final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element); final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element);
return assembleApplication(element, clientId, groupId);
}
private Loan assembleApplication(final JsonElement element, final Long clientId, final Long groupId) {
final String accountNo = this.fromApiJsonHelper.extractStringNamed("accountNo", element); final String accountNo = this.fromApiJsonHelper.extractStringNamed("accountNo", element);
final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element); final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element); final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element);
final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed("loanOfficerId", element); final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed("loanOfficerId", element);
final String transactionProcessingStrategyCode = this.fromApiJsonHelper.extractStringNamed("transactionProcessingStrategyCode",
element);
final String transactionProcessingStrategyName = this.loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode).getName();
final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", element); final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", element);
final Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element); final Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper
@@ -154,8 +160,13 @@ public class LoanAssembler {
final LoanProduct loanProduct = this.loanProductRepository.findById(productId) final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId)); .orElseThrow(() -> new LoanProductNotFoundException(productId));
final BigDecimal amount = this.fromApiJsonHelper final Boolean allowOverridingTransactionProcessingStrategy = loanProduct.getLoanConfigurableAttributes()
.extractBigDecimalWithLocaleNamed(LoanApiConstants.disbursementPrincipalParameterName, element); .getTransactionProcessingStrategyBoolean();
final String transactionProcessingStrategyCode = allowOverridingTransactionProcessingStrategy
? this.fromApiJsonHelper.extractStringNamed("transactionProcessingStrategyCode", element)
: loanProduct.getTransactionProcessingStrategyCode();
final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy = this.loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode);
final Fund fund = findFundByIdIfProvided(fundId); final Fund fund = findFundByIdIfProvided(fundId);
final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId); final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId);
CodeValue loanPurpose = null; CodeValue loanPurpose = null;
@@ -165,76 +176,26 @@ public class LoanAssembler {
List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>(); List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
BigDecimal fixedEmiAmount = null; BigDecimal fixedEmiAmount = null;
if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) { if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) {
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element); fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
} }
BigDecimal maxOutstandingLoanBalance = null; BigDecimal maxOutstandingLoanBalance = null;
if (loanProduct.isMultiDisburseLoan()) { if (loanProduct.isMultiDisburseLoan()) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject()); final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName, maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
element, locale); element, locale);
disbursementDetails = this.loanDisbursementDetailsAssembler.fetchDisbursementData(element.getAsJsonObject());
disbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject());
if (loanProduct.isDisallowExpectedDisbursements()) {
if (!disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details are not allowed";
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
} else {
if (disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
}
if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
} }
final String loanTypeParameterName = "loanType"; final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr);
Set<LoanCollateralManagement> collateral = new HashSet<>(); Set<LoanCollateralManagement> collateral = new HashSet<>();
if (!StringUtils.isBlank(loanTypeStr)) { final AccountType loanAccountType = AccountType.fromName(loanTypeStr);
final AccountType loanAccountType = AccountType.fromName(loanTypeStr); if (loanAccountType.isIndividualAccount()) {
collateral = this.collateralAssembler.fromParsedJson(element);
if (loanAccountType.isIndividualAccount()) {
collateral = this.collateralAssembler.fromParsedJson(element);
if (collateral.size() > 0) {
BigDecimal totalValue = BigDecimal.ZERO;
for (LoanCollateralManagement collateralManagement : collateral) {
final CollateralManagementDomain collateralManagementDomain = collateralManagement.getClientCollateralManagement()
.getCollaterals();
BigDecimal totalCollateral = collateralManagement.getQuantity().multiply(collateralManagementDomain.getBasePrice())
.multiply(collateralManagementDomain.getPctToBase()).divide(BigDecimal.valueOf(100));
totalValue = totalValue.add(totalCollateral);
}
if (amount.compareTo(totalValue) > 0) {
throw new InvalidAmountOfCollaterals(totalValue);
}
}
}
} }
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails); final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails);
for (final LoanCharge loanCharge : loanCharges) {
if (!loanProduct.hasCurrencyCodeOf(loanCharge.currencyCode())) {
final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed("linkAccountId", element);
if (savingsAccountId == null) {
final String errorMessage = "one of the charges requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge", errorMessage);
}
}
}
BigDecimal fixedPrincipalPercentagePerInstallment = fromApiJsonHelper BigDecimal fixedPrincipalPercentagePerInstallment = fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
@@ -245,111 +206,166 @@ public class LoanAssembler {
// Here we add Rates to LoanApplication // Here we add Rates to LoanApplication
final List<Rate> rates = this.rateAssembler.fromParsedJson(element); final List<Rate> rates = this.rateAssembler.fromParsedJson(element);
final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element); final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element,
loanProduct);
final BigDecimal interestRateDifferential = this.fromApiJsonHelper final BigDecimal interestRateDifferential = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
final Boolean isFloatingInterestRate = this.fromApiJsonHelper final Boolean isFloatingInterestRate = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element); .extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element);
// PROGRESSIVE: Repayment strategy MUST be only "advanced payment allocation"
final LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType();
if (loanScheduleType.equals(LoanScheduleType.PROGRESSIVE)) {
if (!transactionProcessingStrategyCode.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.different.than.advanced.payment.allocation",
"Loan repayment strategy can not be different than Advanced Payment Allocation");
}
// CUMULATIVE: Repayment strategy CANNOT be "advanced payment allocation"
} else if (loanScheduleType.equals(LoanScheduleType.CUMULATIVE)) {
if (transactionProcessingStrategyCode.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.equal.to.advanced.payment.allocation",
"Loan repayment strategy can not be equal to Advanced Payment Allocation");
}
}
if (clientId != null) { if (clientId != null) {
client = this.clientRepository.findOneWithNotFoundDetection(clientId); client = this.clientRepository.findOneWithNotFoundDetection(clientId);
if (client.isNotActive()) {
throw new ClientNotActiveException(clientId);
}
} }
if (groupId != null) { if (groupId != null) {
group = this.groupRepository.findById(groupId).orElseThrow(() -> new GroupNotFoundException(groupId)); group = this.groupRepository.findOneWithNotFoundDetection(groupId);
if (group.isNotActive()) {
throw new GroupNotActiveException(groupId);
}
}
if (client != null && group != null) {
if (!group.hasClientAsMember(client)) {
throw new ClientNotInGroupException(clientId, groupId);
}
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), loanProduct,
fund, loanOfficer, loanPurpose, transactionProcessingStrategyCode, loanProductRelatedDetail, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment);
} else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
loanPurpose, transactionProcessingStrategyCode, loanProductRelatedDetail, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment);
} else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
loanOfficer, loanPurpose, transactionProcessingStrategyCode, loanProductRelatedDetail, loanCharges, collateral,
fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement,
isFloatingInterestRate, interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
} else {
loanApplication = null;
} }
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", element); final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", element);
ExternalId externalId = externalIdFactory.create(externalIdStr); ExternalId externalId = externalIdFactory.create(externalIdStr);
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
if (loanApplication == null) { Boolean isEnableInstallmentLevelDelinquency = this.fromApiJsonHelper
throw new IllegalStateException("No loan application exists for either a client or group (or both).");
}
loanApplication.updateTransactionProcessingStrategy(transactionProcessingStrategyCode, transactionProcessingStrategyName);
copyAdvancedPaymentRulesIfApplicable(transactionProcessingStrategyCode, loanProduct, loanApplication);
loanApplication.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
if (loanProduct.isMultiDisburseLoan()) {
for (final LoanDisbursementDetails loanDisbursementDetails : loanApplication.getDisbursementDetails()) {
loanDisbursementDetails.updateLoan(loanApplication);
}
}
final Boolean isEnableInstallmentLevelDelinquency = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element); .extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element);
if (isEnableInstallmentLevelDelinquency != null) { if (isEnableInstallmentLevelDelinquency == null) {
loanApplication.updateEnableInstallmentLevelDelinquency(isEnableInstallmentLevelDelinquency); isEnableInstallmentLevelDelinquency = loanProduct.isEnableInstallmentLevelDelinquency();
} else {
loanApplication.updateEnableInstallmentLevelDelinquency(loanProduct.isEnableInstallmentLevelDelinquency());
} }
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element); final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(), Long officeId = client != null ? client.getOffice().getId() : group.getOffice().getId();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId,
loanApplicationTerms.getExpectedDisbursementDate(), HolidayStatusType.ACTIVE.getValue()); loanApplicationTerms.getExpectedDisbursementDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms,
isHolidayEnabled, holidays, workingDays, element, disbursementDetails); isHolidayEnabled, holidays, workingDays, element, disbursementDetails);
loanApplication.loanApplicationSubmittal(loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine, submittedOnDate,
externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); if (client != null && group != null) {
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanAccountType, loanProduct, fund,
loanOfficer, loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
isEnableInstallmentLevelDelinquency, submittedOnDate);
} 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,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate);
} 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,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate);
} else {
throw new IllegalStateException("No loan application exists for either a client or group (or both).");
}
copyAdvancedPaymentRulesIfApplicable(transactionProcessingStrategyCode, loanProduct, loanApplication);
loanApplication.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
// TODO: review
loanApplication.recalculateAllCharges();
topUpLoanConfiguration(element, loanApplication);
return loanApplication; return loanApplication;
} }
// TODO: Review... it might be better somewhere else and rethink due to the account number generation logic is
// intertwined with GLIM logic
public void accountNumberGeneration(JsonCommand command, Loan loan) {
if (loan.isAccountNumberRequiresAutoGeneration()) {
JsonElement element = command.parsedJson();
final AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository.findByAccountType(EntityAccountType.LOAN);
// TODO: It is really weird to set GLIM info only if account number was not provided
// if application is of GLIM type
if (loan.getLoanType().isGLIMAccount()) {
Group group = loan.getGroup();
String accountNumber = "";
BigDecimal applicationId = BigDecimal.ZERO;
Boolean isLastChildApplication = false;
// GLIM specific parameters
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
BigDecimal applicationIdFromParam = this.fromApiJsonHelper.extractBigDecimalNamed("applicationId", element, locale);
BigDecimal totalLoan = this.fromApiJsonHelper.extractBigDecimalNamed("totalLoan", element, locale);
if (applicationIdFromParam != null) {
applicationId = applicationIdFromParam;
}
Boolean isLastChildApplicationFromParam = this.fromApiJsonHelper.extractBooleanNamed("lastApplication", element);
if (isLastChildApplicationFromParam != null) {
isLastChildApplication = isLastChildApplicationFromParam;
}
if (this.fromApiJsonHelper.extractBooleanNamed("isParentAccount", element) != null) {
// empty table check
// TODO: This count here is weird... and seems parent-empty and parent not empty looks the same
if (glimRepository.count() != 0) {
// **************Parent-Not an empty
// table********************
createAndSetGLIMAccount(totalLoan, loan, accountNumberFormat, group, applicationId);
} else {
// ************** Parent-empty
// table********************
createAndSetGLIMAccount(totalLoan, loan, accountNumberFormat, group, applicationId);
}
} else {
// TODO: This count here is weird...
if (glimRepository.count() != 0) {
// Child-Not an empty table
GroupLoanIndividualMonitoringAccount glimAccount = glimRepository.findOneByIsAcceptingChildAndApplicationId(true,
applicationId);
accountNumber = glimAccount.getAccountNumber() + (glimAccount.getChildAccountsCount() + 1);
loan.updateAccountNo(accountNumber);
this.glimAccountInfoWritePlatformService.incrementChildAccountCount(glimAccount);
loan.setGlim(glimAccount);
} else {
// **************Child-empty
// table********************
// if the glim info is empty set the current account
// as parent
createAndSetGLIMAccount(totalLoan, loan, accountNumberFormat, group, applicationId);
}
// reset in cases of last child application of glim
if (isLastChildApplication) {
this.glimAccountInfoWritePlatformService
.resetIsAcceptingChild(glimRepository.findOneByIsAcceptingChildAndApplicationId(true, applicationId));
}
}
} else { // for applications other than GLIM
loan.updateAccountNo(this.accountNumberGenerator.generate(loan, accountNumberFormat));
}
}
}
private void createAndSetGLIMAccount(BigDecimal totalLoan, Loan loan, AccountNumberFormat accountNumberFormat, Group group,
BigDecimal applicationId) {
String accountNumber;
accountNumber = this.accountNumberGenerator.generate(loan, accountNumberFormat);
loan.updateAccountNo(accountNumber + "1");
GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoWritePlatformService.createGLIMAccount(accountNumber, group,
totalLoan, 1L, true, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), applicationId);
loan.setGlim(glimAccount);
}
private void topUpLoanConfiguration(JsonElement element, Loan loan) {
if (loan.getLoanProduct().isCanUseForTopup() && loan.getClientId() != null) {
final Boolean isTopUp = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isTopup, element);
if (null == isTopUp) {
loan.setIsTopup(false);
} else {
loan.setIsTopup(isTopUp);
}
if (loan.isTopup()) {
final Long loanIdToClose = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element);
loan.setTopupLoanDetails(new LoanTopupDetails(loan, loanIdToClose));
}
}
}
public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) { public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) {
CodeValue codeValue = null; CodeValue codeValue = null;
if (codeValueId != null) { if (codeValueId != null) {
@@ -377,18 +393,6 @@ public class LoanAssembler {
return staff; return staff;
} }
public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final Loan loanApplication) {
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(),
loanApplication.getExpectedDisbursedOnLocalDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
loanApplication.validateExpectedDisbursementForHolidayAndNonWorkingDay(workingDays, allowTransactionsOnHoliday, holidays,
allowTransactionsOnNonWorkingDay);
}
private void copyAdvancedPaymentRulesIfApplicable(String transactionProcessingStrategyCode, LoanProduct loanProduct, private void copyAdvancedPaymentRulesIfApplicable(String transactionProcessingStrategyCode, LoanProduct loanProduct,
Loan loanApplication) { Loan loanApplication) {
if (transactionProcessingStrategyCode.equals(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) { if (transactionProcessingStrategyCode.equals(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
@@ -398,11 +402,435 @@ public class LoanAssembler {
.toList(); .toList();
loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules); loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules);
if (loanProduct.getCreditAllocationRules() != null && loanProduct.getCreditAllocationRules().size() > 0) { if (loanProduct.getCreditAllocationRules() != null && !loanProduct.getCreditAllocationRules().isEmpty()) {
List<LoanCreditAllocationRule> loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream() List<LoanCreditAllocationRule> loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream()
.map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList(); .map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList();
loanApplication.setCreditAllocationRules(loanCreditAllocationRules); loanApplication.setCreditAllocationRules(loanCreditAllocationRules);
} }
} }
} }
public Map<String, Object> updateFrom(JsonCommand command, Loan loan) {
final Map<String, Object> changes = new HashMap<>();
LoanProduct loanProduct;
final String productIdParamName = "productId";
final Long productId = command.longValueOfParameterNamed(productIdParamName);
if (productId == null || productId.equals(loan.getLoanProduct().getId())) {
loanProduct = loan.getLoanProduct();
} else {
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
}
final Set<LoanCharge> existingCharges = loan.getActiveCharges();
Map<Long, LoanChargeData> chargesMap = new HashMap<>();
for (LoanCharge charge : existingCharges) {
LoanChargeData chargeData = new LoanChargeData(charge.getId(), charge.getDueLocalDate(), charge.amountOrPercentage());
chargesMap.put(charge.getId(), chargeData);
}
List<LoanDisbursementDetails> disbursementDetails = this.loanDisbursementDetailsAssembler
.fetchDisbursementData(command.parsedJson().getAsJsonObject());
/**
* Stores all charges which are passed in during modify loan application
**/
final Set<LoanCharge> possiblyModifiedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),
disbursementDetails);
/** Boolean determines if any charge has been modified **/
boolean isChargeModified = false;
Set<Charge> newTrancheCharges = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson());
for (Charge charge : newTrancheCharges) {
loan.addTrancheLoanCharge(charge);
}
/**
* If there are any charges already present, which are now not passed in as a part of the request, deem the
* charges as modified
**/
if (!possiblyModifiedLoanCharges.isEmpty()) {
if (!possiblyModifiedLoanCharges.containsAll(existingCharges)) {
isChargeModified = true;
}
}
/**
* If any new charges are added or values of existing charges are modified
**/
for (LoanCharge loanCharge : possiblyModifiedLoanCharges) {
if (loanCharge.getId() == null) {
isChargeModified = true;
} else {
LoanChargeData chargeData = chargesMap.get(loanCharge.getId());
if (loanCharge.amountOrPercentage().compareTo(chargeData.getAmountOrPercentage()) != 0
|| (loanCharge.isSpecifiedDueDate() && !loanCharge.getDueLocalDate().equals(chargeData.getDueDate()))) {
isChargeModified = true;
}
}
}
Set<LoanCollateralManagement> possiblyModifedLoanCollateralItems = null;
if (command.parameterExists("loanType")) {
final String loanTypeStr = command.stringValueOfParameterNamed("loanType");
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (!StringUtils.isBlank(loanTypeStr) && loanType.isIndividualAccount()) {
possiblyModifedLoanCollateralItems = this.loanCollateralAssembler.fromParsedJson(command.parsedJson());
}
}
this.loanScheduleAssembler.updateLoanApplicationAttributes(command, loan, changes);
if (!changes.isEmpty()) {
final boolean recalculateLoanSchedule = !(changes.size() == 1
&& changes.containsKey(LoanApiConstants.inArrearsToleranceParameterName));
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, recalculateLoanSchedule);
isChargeModified = true;
}
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
if (command.isChangeInStringParameterNamed(LoanApiConstants.accountNoParameterName, loan.getAccountNumber())) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.accountNoParameterName);
changes.put(LoanApiConstants.accountNoParameterName, newValue);
loan.setAccountNumber(StringUtils.defaultIfEmpty(newValue, null));
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName,
loan.shouldCreateStandingInstructionAtDisbursement())) {
final Boolean valueAsInput = command
.booleanObjectValueOfParameterNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName);
changes.put(LoanApiConstants.createStandingInstructionAtDisbursementParameterName, valueAsInput);
loan.setCreateStandingInstructionAtDisbursement(valueAsInput);
}
if (command.isChangeInStringParameterNamed(LoanApiConstants.externalIdParameterName, loan.getExternalId().getValue())) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.externalIdParameterName);
ExternalId externalId = ExternalIdFactory.produce(newValue);
if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
changes.put(LoanApiConstants.externalIdParameterName, externalId);
loan.setExternalId(externalId);
}
// add clientId, groupId and loanType changes to actual changes
final Long clientId = loan.getClient() == null ? null : loan.getClient().getId();
if (command.isChangeInLongParameterNamed(LoanApiConstants.clientIdParameterName, clientId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.clientIdParameterName);
changes.put(LoanApiConstants.clientIdParameterName, newValue);
final Client client = this.clientRepository.findOneWithNotFoundDetection(newValue);
loan.updateClient(client);
}
// FIXME: AA - We may require separate api command to move loan from one
// group to another
final Long groupId = loan.getGroup() == null ? null : loan.getGroup().getId();
if (command.isChangeInLongParameterNamed(LoanApiConstants.groupIdParameterName, groupId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.groupIdParameterName);
changes.put(LoanApiConstants.groupIdParameterName, newValue);
final Group group = this.groupRepository.findOneWithNotFoundDetection(newValue);
loan.updateGroup(group);
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.productIdParameterName, loan.getLoanProduct().getId())) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.productIdParameterName);
changes.put(LoanApiConstants.productIdParameterName, newValue);
loan.updateLoanProduct(loanProduct);
final MonetaryCurrency currency = new MonetaryCurrency(loanProduct.getCurrency().getCode(),
loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getCurrencyInMultiplesOf());
loan.getLoanRepaymentScheduleDetail().setCurrency(currency);
if (!changes.containsKey(LoanApiConstants.interestRateFrequencyTypeParameterName)) {
loan.updateInterestRateFrequencyType();
}
if (loanProduct.isLinkedToFloatingInterestRate()) {
loan.getLoanProductRelatedDetail().updateForFloatingInterestRates();
} else {
loan.setInterestRateDifferential(null);
loan.setIsFloatingInterestRate(null);
}
loan.updateIsInterestRecalculationEnabled();
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.isFloatingInterestRateParameterName,
loan.getIsFloatingInterestRate())) {
final Boolean newValue = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isFloatingInterestRateParameterName);
changes.put(LoanApiConstants.isFloatingInterestRateParameterName, newValue);
loan.setIsFloatingInterestRate(newValue);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.interestRateDifferentialParameterName,
loan.getInterestRateDifferential())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanApiConstants.interestRateDifferentialParameterName);
changes.put(LoanApiConstants.interestRateDifferentialParameterName, newValue);
loan.setInterestRateDifferential(newValue);
}
Long existingFundId = null;
if (loan.getFund() != null) {
existingFundId = loan.getFund().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.fundIdParameterName, existingFundId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.fundIdParameterName);
changes.put(LoanApiConstants.fundIdParameterName, newValue);
final Fund fund = findFundByIdIfProvided(newValue);
loan.updateFund(fund);
}
Long existingLoanOfficerId = null;
if (loan.getLoanOfficer() != null) {
existingLoanOfficerId = loan.getLoanOfficer().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.loanOfficerIdParameterName, existingLoanOfficerId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.loanOfficerIdParameterName);
changes.put(LoanApiConstants.loanOfficerIdParameterName, newValue);
final Staff newOfficer = findLoanOfficerByIdIfProvided(newValue);
loan.updateLoanOfficerOnLoanApplication(newOfficer);
}
Long existingLoanPurposeId = null;
if (loan.getLoanPurpose() != null) {
existingLoanPurposeId = loan.getLoanPurpose().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.loanPurposeIdParameterName, existingLoanPurposeId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.loanPurposeIdParameterName);
changes.put(LoanApiConstants.loanPurposeIdParameterName, newValue);
final CodeValue loanPurpose = findCodeValueByIdIfProvided(newValue);
loan.updateLoanPurpose(loanPurpose);
}
if (command.isChangeInStringParameterNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName,
loan.getTransactionProcessingStrategyCode())
&& loanProduct.getLoanConfigurableAttributes().getTransactionProcessingStrategyBoolean()) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName);
final String transactionProcessingStrategyCode = command.stringValueOfParameterNamed("transactionProcessingStrategyCode");
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode);
changes.put(LoanApiConstants.transactionProcessingStrategyCodeParameterName, newValue);
loan.updateTransactionProcessingStrategy(transactionProcessingStrategyCode,
loanRepaymentScheduleTransactionProcessor.getName());
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.submittedOnDateParameterName, loan.getSubmittedOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.submittedOnDateParameterName);
changes.put(LoanApiConstants.submittedOnDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
loan.setSubmittedOnDate(command.localDateValueOfParameterNamed(LoanApiConstants.submittedOnDateParameterName));
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName,
loan.getExpectedDisbursedOnLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName);
changes.put(LoanApiConstants.expectedDisbursementDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setExpectedDisbursementDate(
command.localDateValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName));
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName,
loan.getExpectedFirstRepaymentOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName);
changes.put(LoanApiConstants.repaymentsStartingFromDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setExpectedFirstRepaymentOnDate(
command.localDateValueOfParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName));
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName,
loan.isSyncDisbursementWithMeeting())) {
final Boolean valueAsInput = command
.booleanObjectValueOfParameterNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName);
changes.put(LoanApiConstants.syncDisbursementWithMeetingParameterName, valueAsInput);
loan.setSyncDisbursementWithMeeting(valueAsInput);
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.interestChargedFromDateParameterName,
loan.getInterestChargedFromDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.interestChargedFromDateParameterName);
changes.put(LoanApiConstants.interestChargedFromDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setInterestChargedFromDate(command.localDateValueOfParameterNamed(LoanApiConstants.interestChargedFromDateParameterName));
}
if (isChargeModified) {
changes.put(LoanApiConstants.chargesParameterName, loanChargeMapper.map(possiblyModifiedLoanCharges, loan.getCurrency()));
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.parameterExists(LoanApiConstants.collateralParameterName) && possiblyModifedLoanCollateralItems != null
&& possiblyModifedLoanCollateralItems.equals(loan.getLoanCollateralManagements())) {
changes.put(LoanApiConstants.collateralParameterName, loanCollateralManagementMapper.map(possiblyModifedLoanCollateralItems));
}
if (command.isChangeInIntegerParameterNamed(LoanApiConstants.loanTermFrequencyParameterName, loan.getTermFrequency())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanApiConstants.loanTermFrequencyParameterName);
changes.put(LoanApiConstants.loanTermFrequencyParameterName, newValue);
loan.setTermFrequency(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanApiConstants.loanTermFrequencyTypeParameterName,
loan.getTermPeriodFrequencyType())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanApiConstants.loanTermFrequencyTypeParameterName);
changes.put(LoanApiConstants.loanTermFrequencyTypeParameterName, newValue);
loan.setTermPeriodFrequencyType(newValue);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.principalParameterName, loan.getApprovedPrincipal())) {
loan.setApprovedPrincipal(command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalParameterName));
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.principalParameterName, loan.getProposedPrincipal())) {
BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalParameterName);
changes.put(LoanApiConstants.principalParameterName, newValue);
loan.setProposedPrincipal(newValue);
}
if (loanProduct.isMultiDisburseLoan()) {
loan.updateDisbursementDetails(command, changes);
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
loan.getMaxOutstandingLoanBalance())) {
loan.setMaxOutstandingLoanBalance(
command.bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName));
}
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if (loanProduct.isDisallowExpectedDisbursements()) {
if (disbursementDataArray != null && !disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details are not allowed";
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
} else {
if (disbursementDataArray == null || disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
}
} else {
loan.clearDisbursementDetails();
}
if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) {
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedEmiAmountParameterName, loan.getFixedEmiAmount())) {
loan.setFixedEmiAmount(command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName));
changes.put(LoanApiConstants.fixedEmiAmountParameterName, loan.getFixedEmiAmount());
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
} else {
loan.setFixedEmiAmount(null);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
loan.getFixedPrincipalPercentagePerInstallment())) {
loan.setFixedPrincipalPercentagePerInstallment(
command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName));
changes.put(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, loan.getFixedPrincipalPercentagePerInstallment());
}
final LoanProductRelatedDetail productRelatedDetail = loan.repaymentScheduleDetail();
if (loan.loanProduct().getLoanConfigurableAttributes() != null) {
loanScheduleAssembler.updateProductRelatedDetails(productRelatedDetail, loan);
}
if (loan.getLoanProduct().isCanUseForTopup() && loan.getClientId() != null) {
final Boolean isTopup = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isTopup);
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.isTopup, loan.isTopup())) {
loan.setIsTopup(isTopup);
changes.put(LoanApiConstants.isTopup, isTopup);
}
if (loan.isTopup()) {
final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose);
LoanTopupDetails existingLoanTopupDetails = loan.getTopupLoanDetails();
if (existingLoanTopupDetails == null || !existingLoanTopupDetails.getLoanIdToClose().equals(loanIdToClose)
|| changes.containsKey("submittedOnDate") || changes.containsKey("expectedDisbursementDate")
|| changes.containsKey("principal") || changes.containsKey(LoanApiConstants.disbursementDataParameterName)) {
Long existingLoanIdToClose = null;
if (existingLoanTopupDetails != null) {
existingLoanIdToClose = existingLoanTopupDetails.getLoanIdToClose();
}
if (!loanIdToClose.equals(existingLoanIdToClose)) {
final LoanTopupDetails topupDetails = new LoanTopupDetails(loan, loanIdToClose);
loan.setTopupLoanDetails(topupDetails);
changes.put(LoanApiConstants.loanIdToClose, loanIdToClose);
}
}
} else {
loan.setTopupLoanDetails(null);
}
} else {
if (loan.isTopup()) {
loan.setIsTopup(false);
loan.setTopupLoanDetails(null);
changes.put(LoanApiConstants.isTopup, false);
}
}
/**
* TODO: Allow other loan types if needed.
*/
if (command.parameterExists("loanType")) {
final String loanTypeStr = command.stringValueOfParameterNamed("loanType");
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (!StringUtils.isBlank(loanTypeStr) && loanType.isIndividualAccount()) {
final String collateralParamName = "collateral";
if (changes.containsKey(collateralParamName)) {
loan.updateLoanCollateral(possiblyModifedLoanCollateralItems);
}
}
}
final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) {
loan.updateLoanCharges(possiblyModifiedLoanCharges);
}
// update installment level delinquency
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
loan.isEnableInstallmentLevelDelinquency())) {
final Boolean enableInstallmentLevelDelinquency = command
.booleanObjectValueOfParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY);
loan.updateEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency);
}
if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule");
final JsonElement parsedQuery = this.fromApiJsonHelper.parse(command.json());
final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromApiJsonHelper);
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false);
loan.updateLoanSchedule(loanSchedule);
loan.recalculateAllCharges();
}
// Changes to modify loan rates.
if (command.hasParameter(LoanProductConstants.RATES_PARAM_NAME)) {
loan.updateLoanRates(rateAssembler.fromParsedJson(command.parsedJson()));
}
return changes;
}
} }
@@ -40,7 +40,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException; import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -65,13 +65,13 @@ public class LoanChargeAssembler {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element); JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
List<Long> disbursementChargeIds = new ArrayList<>(); List<Long> disbursementChargeIds = new ArrayList<>();
if (jsonDisbursement != null && jsonDisbursement.size() > 0) { if (jsonDisbursement != null && !jsonDisbursement.isEmpty()) {
for (int i = 0; i < jsonDisbursement.size(); i++) { for (int i = 0; i < jsonDisbursement.size(); i++) {
final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject(); final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject();
if (jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null) { if (jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null) {
String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString(); String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString();
if (chargeIds != null) { if (chargeIds != null) {
if (chargeIds.indexOf(",") != -1) { if (chargeIds.contains(",")) {
Iterable<String> chargeId = Splitter.on(',').split(chargeIds); Iterable<String> chargeId = Splitter.on(',').split(chargeIds);
for (String loanChargeId : chargeId) { for (String loanChargeId : chargeId) {
disbursementChargeIds.add(Long.parseLong(loanChargeId)); disbursementChargeIds.add(Long.parseLong(loanChargeId));
@@ -104,7 +104,7 @@ public class LoanChargeAssembler {
final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement); final Long loanChargeId = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale);
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale);
@@ -116,16 +116,8 @@ public class LoanChargeAssembler {
locale); locale);
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", loanChargeElement); final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", loanChargeElement);
final ExternalId externalId = externalIdFactory.create(externalIdStr); final ExternalId externalId = externalIdFactory.create(externalIdStr);
if (id == null) { if (loanChargeId == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if (chargeDefinition.isOverdueInstallment()) {
final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null,
chargeDefinition.getName());
}
ChargeTimeType chargeTime = null; ChargeTimeType chargeTime = null;
if (chargeTimeType != null) { if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType); chargeTime = ChargeTimeType.fromInt(chargeTimeType);
@@ -145,7 +137,7 @@ public class LoanChargeAssembler {
} else { } else {
if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) { if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) {
final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray(); final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray();
if (disbursementArray.size() > 0) { if (!disbursementArray.isEmpty()) {
JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject(); JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject();
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed( expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.expectedDisbursementDateParameterName, disbursementDataElement, dateFormat, LoanApiConstants.expectedDisbursementDateParameterName, disbursementDataElement, dateFormat,
@@ -201,14 +193,14 @@ public class LoanChargeAssembler {
} }
} }
} else { } else {
final Long loanChargeId = id; final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId).orElse(null); .orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
if (loanCharge != null) {
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) { if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
loanCharge.update(amount, dueDate, numberOfRepayments); loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge); loanCharges.add(loanCharge);
}
} }
} }
} }
} }
@@ -70,7 +70,6 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferType;
import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException; import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
@@ -710,7 +709,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
paymentDetail); paymentDetail);
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements(); Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) { for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction); loanCollateralManagement.setLoanTransactionData(loanTransaction);
@@ -0,0 +1,59 @@
/**
* 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.portfolio.loanaccount.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
@RequiredArgsConstructor
public class LoanDisbursementDetailsAssembler {
private final FromJsonHelper fromApiJsonHelper;
public List<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
List<LoanDisbursementDetails> disbursementDatas = new ArrayList<>();
JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && !disbursementDataArray.isEmpty()) {
disbursementDataArray.forEach(jsonElement -> {
LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, jsonElement, dateFormat, locale);
BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.disbursementPrincipalParameterName,
jsonElement, locale);
BigDecimal netDisbursalAmount = this.fromApiJsonHelper
.extractBigDecimalNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName, jsonElement, locale);
boolean isReversed = Boolean.TRUE.equals(
this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.disbursementReversedParameterName, jsonElement));
disbursementDatas
.add(new LoanDisbursementDetails(expectedDisbursementDate, null, principal, netDisbursalAmount, isReversed));
});
}
return disbursementDatas;
}
}
@@ -18,19 +18,13 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.service; package org.apache.fineract.portfolio.loanaccount.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository; import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType; import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
@@ -52,11 +46,9 @@ import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException; import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -71,7 +63,6 @@ public class LoanUtilService {
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanScheduleGeneratorFactory loanScheduleFactory; private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
private final CalendarReadPlatformService calendarReadPlatformService; private final CalendarReadPlatformService calendarReadPlatformService;
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) { public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) {
@@ -288,51 +279,6 @@ public class LoanUtilService {
return calculatedRepaymentsStartingFromDate; return calculatedRepaymentsStartingFromDate;
} }
public List<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
List<LoanDisbursementDetails> disbursementDatas = new ArrayList<>();
if (command.has(LoanApiConstants.disbursementDataParameterName)) {
final JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
LocalDate expectedDisbursementDate = null;
LocalDate actualDisbursementDate = null;
BigDecimal principal = null;
BigDecimal netDisbursalAmount = null;
if (jsonObject.has(LoanApiConstants.expectedDisbursementDateParameterName)) {
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.expectedDisbursementDateParameterName, jsonObject, dateFormat, locale);
}
if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
&& jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString())) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanApiConstants.disbursementNetDisbursalAmountParameterName)
&& jsonObject.get(LoanApiConstants.disbursementNetDisbursalAmountParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank(
jsonObject.get(LoanApiConstants.disbursementNetDisbursalAmountParameterName).getAsString())) {
netDisbursalAmount = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementNetDisbursalAmountParameterName)
.getAsBigDecimal();
}
boolean isReversed = false;
if (jsonObject.has(LoanApiConstants.disbursementReversedParameterName)) {
isReversed = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.disbursementReversedParameterName,
jsonObject);
}
disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal,
netDisbursalAmount, isReversed));
i++;
} while (i < disbursementDataArray.size());
}
}
return disbursementDatas;
}
public void validateRepaymentTransactionType(LoanTransactionType repaymentTransactionType) { public void validateRepaymentTransactionType(LoanTransactionType repaymentTransactionType) {
if (!repaymentTransactionType.isRepaymentType()) { if (!repaymentTransactionType.isRepaymentType()) {
throw new PlatformServiceUnavailableException("error.msg.repaymentTransactionType.provided.not.a.repayment.type", throw new PlatformServiceUnavailableException("error.msg.repaymentTransactionType.provided.not.a.repayment.type",
@@ -147,7 +147,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -180,7 +179,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -225,7 +224,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final CalendarRepository calendarRepository; private final CalendarRepository calendarRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper; private final LoanApplicationValidator loanApplicationCommandFromApiJsonHelper;
private final AccountAssociationsRepository accountAssociationRepository; private final AccountAssociationsRepository accountAssociationRepository;
private final AccountTransferDetailRepository accountTransferDetailRepository; private final AccountTransferDetailRepository accountTransferDetailRepository;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
@@ -240,7 +239,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler; private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler;
private final PostDatedChecksRepository postDatedChecksRepository; private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanDisbursementDetailsRepository loanDisbursementDetailsRepository;
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository; private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final LoanAccountLockService loanAccountLockService; private final LoanAccountLockService loanAccountLockService;
@@ -327,8 +325,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements(); final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
// Get relevant loan collateral modules // Get relevant loan collateral modules
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) {
&& AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
BigDecimal totalCollateral = BigDecimal.valueOf(0); BigDecimal totalCollateral = BigDecimal.valueOf(0);
@@ -996,7 +993,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
holidayDetailDto, isHolidayValidationDone); holidayDetailDto, isHolidayValidationDone);
loan = loanTransaction.getLoan(); loan = loanTransaction.getLoan();
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements(); Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) { for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction); loanCollateralManagement.setLoanTransactionData(loanTransaction);
@@ -1597,7 +1594,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanCloseBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanCloseBusinessEvent(loan));
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements(); Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) { for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement(); ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
@@ -1678,7 +1675,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan); this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements(); Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) { for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement(); ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
@@ -2486,7 +2483,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate nextPossibleRepaymentDate, final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate nextPossibleRepaymentDate,
final LocalDate rescheduledRepaymentDate) { final LocalDate rescheduledRepaymentDate) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName); BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName);
loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount, loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount,
nextPossibleRepaymentDate, rescheduledRepaymentDate); nextPossibleRepaymentDate, rescheduledRepaymentDate);
} }
@@ -24,15 +24,12 @@ import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumb
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepositoryWrapper;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler; import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityRelationRepository;
import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMappingRepository;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
@@ -65,14 +62,12 @@ import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatform
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.fund.domain.FundRepository; import org.apache.fineract.portfolio.fund.domain.FundRepository;
import org.apache.fineract.portfolio.fund.service.FundReadPlatformService; import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
import org.apache.fineract.portfolio.group.domain.GroupRepository;
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
import org.apache.fineract.portfolio.group.service.GroupReadPlatformService; import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa; import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
@@ -82,14 +77,15 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService; import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationMapper; import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationMapper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
@@ -118,6 +114,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformS
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanDisbursementDetailsAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService; import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -131,7 +128,6 @@ import org.apache.fineract.portfolio.loanaccount.service.RecalculateInterestPost
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService; import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventServiceImpl; import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventServiceImpl;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.apache.fineract.portfolio.note.domain.NoteRepository; import org.apache.fineract.portfolio.note.domain.NoteRepository;
@@ -140,7 +136,7 @@ import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatform
import org.apache.fineract.portfolio.rate.service.RateAssembler; import org.apache.fineract.portfolio.rate.service.RateAssembler;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService; import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -168,11 +164,8 @@ public class LoanAccountConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(GLIMAccountInfoWritePlatformService.class) @ConditionalOnMissingBean(GLIMAccountInfoWritePlatformService.class)
public GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService(PlatformSecurityContext context, public GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService(GLIMAccountInfoRepository glimAccountRepository) {
GLIMAccountInfoRepository glimAccountRepository, return new GLIMAccountInfoWritePlatformServiceImpl(glimAccountRepository);
LoanRepository loanRepository) {
return new GLIMAccountInfoWritePlatformServiceImpl(context, glimAccountRepository, loanRepository);
} }
@Bean @Bean
@@ -210,38 +203,23 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanApplicationWritePlatformService.class) @ConditionalOnMissingBean(LoanApplicationWritePlatformService.class)
public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context, public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context,
FromJsonHelper fromJsonHelper, LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator, FromJsonHelper fromJsonHelper, LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
LoanProductDataValidator loanProductCommandFromApiJsonDeserializer, LoanApplicationValidator loanApplicationValidator, LoanRepositoryWrapper loanRepositoryWrapper, NoteRepository noteRepository,
LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper, LoanAssembler loanAssembler, LoanSummaryWrapper loanSummaryWrapper,
NoteRepository noteRepository, LoanScheduleCalculationPlatformService calculationPlatformService, LoanAssembler loanAssembler,
ClientRepositoryWrapper clientRepository, LoanProductRepository loanProductRepository, LoanChargeAssembler loanChargeAssembler,
LoanCollateralAssembler loanCollateralAssembler, AprCalculator aprCalculator, AccountNumberGenerator accountNumberGenerator,
LoanSummaryWrapper loanSummaryWrapper, GroupRepositoryWrapper groupRepository,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
CalendarRepository calendarRepository, CalendarInstanceRepository calendarInstanceRepository, CalendarRepository calendarRepository, CalendarInstanceRepository calendarInstanceRepository,
SavingsAccountAssembler savingsAccountAssembler, AccountAssociationsRepository accountAssociationsRepository, SavingsAccountRepositoryWrapper savingsAccountRepository, AccountAssociationsRepository accountAssociationsRepository,
LoanReadPlatformService loanReadPlatformService, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, LoanReadPlatformService loanReadPlatformService, BusinessEventNotifierService businessEventNotifierService,
BusinessEventNotifierService businessEventNotifierService, ConfigurationDomainService configurationDomainService, ConfigurationDomainService configurationDomainService, LoanScheduleAssembler loanScheduleAssembler,
LoanScheduleAssembler loanScheduleAssembler, LoanUtilService loanUtilService, LoanUtilService loanUtilService, CalendarReadPlatformService calendarReadPlatformService,
CalendarReadPlatformService calendarReadPlatformService, EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GLIMAccountInfoRepository glimRepository,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
GlobalConfigurationRepositoryWrapper globalConfigurationRepository, LoanLifecycleStateMachine defaultLoanLifecycleStateMachine) {
FineractEntityToEntityMappingRepository entityMappingRepository,
FineractEntityRelationRepository fineractEntityRelationRepository,
LoanProductReadPlatformService loanProductReadPlatformService,
RateAssembler rateAssembler, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService,
GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanProductDataValidator loanProductDataValidator) {
return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, loanApplicationTransitionApiJsonValidator, return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, loanApplicationTransitionApiJsonValidator,
loanProductCommandFromApiJsonDeserializer, fromApiJsonDeserializer, loanRepositoryWrapper, noteRepository, loanApplicationValidator, loanRepositoryWrapper, noteRepository, loanAssembler, loanSummaryWrapper,
calculationPlatformService, loanAssembler, clientRepository, loanProductRepository, loanChargeAssembler, loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountRepository,
loanCollateralAssembler, aprCalculator, accountNumberGenerator, loanSummaryWrapper, groupRepository, accountAssociationsRepository, loanReadPlatformService, businessEventNotifierService, configurationDomainService,
loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountAssembler, loanScheduleAssembler, loanUtilService, calendarReadPlatformService, entityDatatableChecksWritePlatformService,
accountAssociationsRepository, loanReadPlatformService, accountNumberFormatRepository, businessEventNotifierService, glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine);
configurationDomainService, loanScheduleAssembler, loanUtilService, calendarReadPlatformService,
entityDatatableChecksWritePlatformService, globalConfigurationRepository, entityMappingRepository,
fineractEntityRelationRepository, loanProductReadPlatformService, rateAssembler, glimAccountInfoWritePlatformService,
glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanProductDataValidator);
} }
@Bean @Bean
@@ -254,18 +232,25 @@ public class LoanAccountConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(LoanAssembler.class) @ConditionalOnMissingBean(LoanAssembler.class)
public LoanAssembler loanAssembler(FromJsonHelper fromApiJsonHelper, LoanRepositoryWrapper loanRepository, public LoanAssembler loanAssembler(FromJsonHelper fromApiJsonHelper, LoanRepositoryWrapper loanRepository,
LoanProductRepository loanProductRepository, ClientRepositoryWrapper clientRepository, GroupRepository groupRepository, LoanProductRepository loanProductRepository, ClientRepositoryWrapper clientRepository, GroupRepositoryWrapper groupRepository,
FundRepository fundRepository, StaffRepository staffRepository, CodeValueRepositoryWrapper codeValueRepository, FundRepository fundRepository, StaffRepository staffRepository, CodeValueRepositoryWrapper codeValueRepository,
LoanScheduleAssembler loanScheduleAssembler, LoanChargeAssembler loanChargeAssembler, LoanScheduleAssembler loanScheduleAssembler, LoanChargeAssembler loanChargeAssembler,
LoanCollateralAssembler collateralAssembler, LoanSummaryWrapper loanSummaryWrapper, LoanCollateralAssembler collateralAssembler, LoanSummaryWrapper loanSummaryWrapper,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
HolidayRepository holidayRepository, ConfigurationDomainService configurationDomainService, HolidayRepository holidayRepository, ConfigurationDomainService configurationDomainService,
WorkingDaysRepositoryWrapper workingDaysRepository, LoanUtilService loanUtilService, RateAssembler rateAssembler, WorkingDaysRepositoryWrapper workingDaysRepository, RateAssembler rateAssembler,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, ExternalIdFactory externalIdFactory) { LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, ExternalIdFactory externalIdFactory,
AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, GLIMAccountInfoRepository glimRepository,
AccountNumberGenerator accountNumberGenerator, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService,
LoanCollateralAssembler loanCollateralAssembler, LoanScheduleCalculationPlatformService calculationPlatformService,
LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler, LoanChargeMapper loanChargeMapper,
LoanCollateralManagementMapper loanCollateralManagementMapper) {
return new LoanAssembler(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository, return new LoanAssembler(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository,
fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler, fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler,
loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService, loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService,
workingDaysRepository, loanUtilService, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory); workingDaysRepository, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory, accountNumberFormatRepository,
glimRepository, accountNumberGenerator, glimAccountInfoWritePlatformService, loanCollateralAssembler,
calculationPlatformService, loanDisbursementDetailsAssembler, loanChargeMapper, loanCollateralManagementMapper);
} }
@Bean @Bean
@@ -369,10 +354,9 @@ public class LoanAccountConfiguration {
CalendarInstanceRepository calendarInstanceRepository, ConfigurationDomainService configurationDomainService, CalendarInstanceRepository calendarInstanceRepository, ConfigurationDomainService configurationDomainService,
HolidayRepository holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository, HolidayRepository holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository,
LoanScheduleGeneratorFactory loanScheduleFactory, FloatingRatesReadPlatformService floatingRatesReadPlatformService, LoanScheduleGeneratorFactory loanScheduleFactory, FloatingRatesReadPlatformService floatingRatesReadPlatformService,
FromJsonHelper fromApiJsonHelper, CalendarReadPlatformService calendarReadPlatformService) { CalendarReadPlatformService calendarReadPlatformService) {
return new LoanUtilService(applicationCurrencyRepository, calendarInstanceRepository, configurationDomainService, holidayRepository, return new LoanUtilService(applicationCurrencyRepository, calendarInstanceRepository, configurationDomainService, holidayRepository,
workingDaysRepository, loanScheduleFactory, floatingRatesReadPlatformService, fromApiJsonHelper, workingDaysRepository, loanScheduleFactory, floatingRatesReadPlatformService, calendarReadPlatformService);
calendarReadPlatformService);
} }
@Bean @Bean
@@ -390,15 +374,14 @@ public class LoanAccountConfiguration {
AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, LoanReadPlatformService loanReadPlatformService, AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, LoanReadPlatformService loanReadPlatformService,
FromJsonHelper fromApiJsonHelper, CalendarRepository calendarRepository, FromJsonHelper fromApiJsonHelper, CalendarRepository calendarRepository,
LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper, LoanApplicationValidator loanApplicationValidator, AccountAssociationsRepository accountAssociationRepository,
AccountAssociationsRepository accountAssociationRepository, AccountTransferDetailRepository accountTransferDetailRepository, AccountTransferDetailRepository accountTransferDetailRepository, BusinessEventNotifierService businessEventNotifierService,
BusinessEventNotifierService businessEventNotifierService, GuarantorDomainService guarantorDomainService, GuarantorDomainService guarantorDomainService, LoanUtilService loanUtilService, LoanSummaryWrapper loanSummaryWrapper,
LoanUtilService loanUtilService, LoanSummaryWrapper loanSummaryWrapper,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, CodeValueRepositoryWrapper codeValueRepository, LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, CodeValueRepositoryWrapper codeValueRepository,
CashierTransactionDataValidator cashierTransactionDataValidator, GLIMAccountInfoRepository glimRepository, CashierTransactionDataValidator cashierTransactionDataValidator, GLIMAccountInfoRepository glimRepository,
LoanRepository loanRepository, RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler, LoanRepository loanRepository, RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler,
PostDatedChecksRepository postDatedChecksRepository, LoanDisbursementDetailsRepository loanDisbursementDetailsRepository, PostDatedChecksRepository postDatedChecksRepository,
LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository, LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService, LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService,
ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService, ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService,
@@ -410,11 +393,10 @@ public class LoanAccountConfiguration {
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository, paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService, accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService,
loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService, loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService,
loanApplicationCommandFromApiJsonHelper, accountAssociationRepository, accountTransferDetailRepository, loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService,
businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper, guarantorDomainService, loanUtilService, loanSummaryWrapper, entityDatatableChecksWritePlatformService,
entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository, transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository,
cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository,
postDatedChecksRepository, loanDisbursementDetailsRepository, loanRepaymentScheduleInstallmentRepository,
defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService, defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService); loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService);
} }
@@ -439,4 +421,10 @@ public class LoanAccountConfiguration {
BusinessEventNotifierService businessEventNotifierService) { BusinessEventNotifierService businessEventNotifierService) {
return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService); return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService);
} }
@Bean
@ConditionalOnMissingBean(LoanDisbursementDetailsAssembler.class)
public LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler(FromJsonHelper fromApiJsonHelper) {
return new LoanDisbursementDetailsAssembler(fromApiJsonHelper);
}
} }
@@ -48,7 +48,6 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
@@ -401,7 +400,7 @@ public final class LoanProductDataValidator {
} }
// interest rates // interest rates
BigDecimal interestRatePerPeriod = null; boolean isInterestBearing = false;
if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element) if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)
&& this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) { && this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) {
if (isEqualAmortization) { if (isEqualAmortization) {
@@ -485,6 +484,7 @@ public final class LoanProductDataValidator {
.extractBooleanNamed(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED, element); .extractBooleanNamed(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED, element);
baseDataValidator.reset().parameter(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED) baseDataValidator.reset().parameter(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED)
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false); .value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
isInterestBearing = true;
} else { } else {
if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) { if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) {
baseDataValidator.reset().parameter(FLOATING_RATES_ID).failWithCode( baseDataValidator.reset().parameter(FLOATING_RATES_ID).failWithCode(
@@ -522,7 +522,7 @@ public final class LoanProductDataValidator {
"isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); "isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
} }
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element); BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element);
baseDataValidator.reset().parameter(INTEREST_RATE_PER_PERIOD).value(interestRatePerPeriod).notNull().zeroOrPositiveAmount(); baseDataValidator.reset().parameter(INTEREST_RATE_PER_PERIOD).value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
final String minInterestRatePerPeriodParameterName = MIN_INTEREST_RATE_PER_PERIOD; final String minInterestRatePerPeriodParameterName = MIN_INTEREST_RATE_PER_PERIOD;
@@ -564,10 +564,12 @@ public final class LoanProductDataValidator {
Locale.getDefault()); Locale.getDefault());
baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0, baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0,
4); 4);
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
} }
// Fixed Length validation // Fixed Length validation
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
fixedLengthValidations(transactionProcessingStrategyCode, isInterestBearing, numberOfRepayments, repaymentEvery, element,
baseDataValidator); baseDataValidator);
// Guarantee Funds // Guarantee Funds
@@ -819,6 +821,10 @@ public final class LoanProductDataValidator {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(advancedPaymentAllocationsJsonParser advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(advancedPaymentAllocationsJsonParser
.assembleLoanProductPaymentAllocationRules(command, transactionProcessingStrategyCode)); .assembleLoanProductPaymentAllocationRules(command, transactionProcessingStrategyCode));
} }
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -1267,19 +1273,21 @@ public final class LoanProductDataValidator {
} }
// grace validation // grace validation
Integer graceOnPrincipalPayment = loanProduct.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
if (this.fromApiJsonHelper.parameterExists(GRACE_ON_PRINCIPAL_PAYMENT, element)) { if (this.fromApiJsonHelper.parameterExists(GRACE_ON_PRINCIPAL_PAYMENT, element)) {
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_PRINCIPAL_PAYMENT, graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_PRINCIPAL_PAYMENT, element);
element);
baseDataValidator.reset().parameter(GRACE_ON_PRINCIPAL_PAYMENT).value(graceOnPrincipalPayment).zeroOrPositiveAmount(); baseDataValidator.reset().parameter(GRACE_ON_PRINCIPAL_PAYMENT).value(graceOnPrincipalPayment).zeroOrPositiveAmount();
} }
Integer graceOnInterestPayment = loanProduct.getLoanProductRelatedDetail().getGraceOnInterestPayment();
if (this.fromApiJsonHelper.parameterExists(GRACE_ON_INTEREST_PAYMENT, element)) { if (this.fromApiJsonHelper.parameterExists(GRACE_ON_INTEREST_PAYMENT, element)) {
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_PAYMENT, element); graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_PAYMENT, element);
baseDataValidator.reset().parameter(GRACE_ON_INTEREST_PAYMENT).value(graceOnInterestPayment).zeroOrPositiveAmount(); baseDataValidator.reset().parameter(GRACE_ON_INTEREST_PAYMENT).value(graceOnInterestPayment).zeroOrPositiveAmount();
} }
Integer graceOnInterestCharged = loanProduct.getLoanProductRelatedDetail().getGraceOnInterestCharged();
if (this.fromApiJsonHelper.parameterExists(GRACE_ON_INTEREST_CHARGED, element)) { if (this.fromApiJsonHelper.parameterExists(GRACE_ON_INTEREST_CHARGED, element)) {
final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_CHARGED, element); graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_CHARGED, element);
baseDataValidator.reset().parameter(GRACE_ON_INTEREST_CHARGED).value(graceOnInterestCharged).zeroOrPositiveAmount(); baseDataValidator.reset().parameter(GRACE_ON_INTEREST_CHARGED).value(graceOnInterestCharged).zeroOrPositiveAmount();
} }
@@ -1368,11 +1376,11 @@ public final class LoanProductDataValidator {
} }
// interest rates // interest rates
BigDecimal interestRatePerPeriod = null;
boolean isLinkedToFloatingInterestRates = loanProduct.isLinkedToFloatingInterestRate(); boolean isLinkedToFloatingInterestRates = loanProduct.isLinkedToFloatingInterestRate();
if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) { if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) {
isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element); isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element);
} }
boolean isInterestBearing = false;
if (isLinkedToFloatingInterestRates) { if (isLinkedToFloatingInterestRates) {
if (isEqualAmortization) { if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate", "floating interest rate"); throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate", "floating interest rate");
@@ -1481,6 +1489,7 @@ public final class LoanProductDataValidator {
} }
baseDataValidator.reset().parameter(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED) baseDataValidator.reset().parameter(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED)
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false); .value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
isInterestBearing = true;
} else { } else {
if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) { if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) {
baseDataValidator.reset().parameter(FLOATING_RATES_ID).failWithCode( baseDataValidator.reset().parameter(FLOATING_RATES_ID).failWithCode(
@@ -1536,7 +1545,7 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull() baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
.zeroOrPositiveAmount(); .zeroOrPositiveAmount();
interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod(); BigDecimal interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
if (this.fromApiJsonHelper.parameterExists(INTEREST_RATE_PER_PERIOD, element)) { if (this.fromApiJsonHelper.parameterExists(INTEREST_RATE_PER_PERIOD, element)) {
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element); interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element);
} }
@@ -1549,10 +1558,11 @@ public final class LoanProductDataValidator {
} }
baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0, baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0,
4); 4);
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
} }
// Fixed Length validation // Fixed Length validation
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element, fixedLengthValidations(transactionProcessingStrategyCode, isInterestBearing, numberOfRepayments, repaymentEvery, element,
baseDataValidator); baseDataValidator);
// Guarantee Funds // Guarantee Funds
@@ -1806,6 +1816,11 @@ public final class LoanProductDataValidator {
"supported.only.for.progressive.loan.schedule.handling", "supported.only.for.progressive.loan.schedule.handling",
"Vertical repayment schedule processing is only available with `Advanced payment allocation` strategy"); "Vertical repayment schedule processing is only available with `Advanced payment allocation` strategy");
} }
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -2400,13 +2415,7 @@ public final class LoanProductDataValidator {
} }
} }
public void fixedLengthValidations(final String transactionProcessingStrategyCode, final LoanApplicationTerms loanApplicationTerms, public void fixedLengthValidations(final String transactionProcessingStrategyCode, final boolean thereIsInterest,
final JsonElement element, final DataValidatorBuilder baseDataValidator) {
fixedLengthValidations(transactionProcessingStrategyCode, loanApplicationTerms.getAnnualNominalInterestRate(),
loanApplicationTerms.getNumberOfRepayments(), loanApplicationTerms.getRepaymentEvery(), element, baseDataValidator);
}
public void fixedLengthValidations(final String transactionProcessingStrategyCode, final BigDecimal interestRatePerPeriod,
final Integer numberOfRepayments, final Integer repayEvery, final JsonElement element, final Integer numberOfRepayments, final Integer repayEvery, final JsonElement element,
final DataValidatorBuilder baseDataValidator) { final DataValidatorBuilder baseDataValidator) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.FIXED_LENGTH, element)) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.FIXED_LENGTH, element)) {
@@ -2420,15 +2429,14 @@ public final class LoanProductDataValidator {
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategyCode)) { .equals(transactionProcessingStrategyCode)) {
final String errorMsg = "Fixed Length configuration is only allowed with " final String errorMsg = "Fixed Length configuration is only allowed with "
+ AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY + " stratefy"; + AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY + " strategy";
throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.advanced.payment.allocation", throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.advanced.payment.allocation",
errorMsg, AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY); errorMsg, AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
} }
if (interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0) { if (thereIsInterest) {
final String errorMsg = "Fixed Length configuration is only allowed for zero interest products"; final String errorMsg = "Fixed Length configuration is only allowed for zero interest products";
throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.zero.interest", errorMsg, throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.zero.interest", errorMsg);
interestRatePerPeriod);
} }
final Integer valueToCompare = ((numberOfRepayments - 1) * repayEvery) + 1; final Integer valueToCompare = ((numberOfRepayments - 1) * repayEvery) + 1;
@@ -2465,4 +2473,45 @@ public final class LoanProductDataValidator {
} }
} }
public void validateRepaymentPeriodWithGraceSettings(final Integer numberOfRepayments, final Integer graceOnPrincipalPayment,
final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final Integer recurringMoratoriumOnPrincipalPeriods,
DataValidatorBuilder baseDataValidator) {
if (numberOfRepayments <= defaultToZeroIfNull(graceOnPrincipalPayment)) {
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
if (numberOfRepayments <= defaultToZeroIfNull(graceOnInterestPayment)) {
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
if (numberOfRepayments < defaultToZeroIfNull(graceOnInterestCharged)) {
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
int graceOnPrincipal = 0;
if (graceOnPrincipalPayment != null) {
graceOnPrincipal = graceOnPrincipalPayment;
}
int recurMoratoriumOnPrincipal = 0;
if (recurringMoratoriumOnPrincipalPeriods != null) {
recurMoratoriumOnPrincipal = recurringMoratoriumOnPrincipalPeriods;
}
if ((recurMoratoriumOnPrincipal > 0) && ((numberOfRepayments - graceOnPrincipal) % (recurMoratoriumOnPrincipal + 1) != 1)) {
baseDataValidator.reset().parameter("graceOnPrincipalPayments.and.recurringMoratoriumOnPrincipalPeriods")
.value(graceOnPrincipal).value(recurMoratoriumOnPrincipal)
.failWithCode("causes.principal.moratorium.for.last.installment");
}
}
private Integer defaultToZeroIfNull(final Integer value) {
Integer result = value;
if (value == null) {
result = 0;
}
return result;
}
} }
@@ -72,6 +72,7 @@ import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -2079,8 +2080,12 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
overpaymentAccount); overpaymentAccount);
assertNotNull(localLoanProductId); assertNotNull(localLoanProductId);
Integer localLoanProductId2 = createLoanProduct("500", "15", "4", false, LoanScheduleType.CUMULATIVE, assetAccount,
incomeAccount, expenseAccount, overpaymentAccount);
assertNotNull(localLoanProductId2);
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
() -> applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, () -> applyForLoanApplication(client.getClientId(), localLoanProductId2, BigDecimal.valueOf(500.0), 45, 15, 3,
BigDecimal.ZERO, "01 January 2023", "01 January 2023", BigDecimal.ZERO, "01 January 2023", "01 January 2023",
FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE, FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE,
LoanScheduleProcessingType.VERTICAL.name())); LoanScheduleProcessingType.VERTICAL.name()));
@@ -4396,6 +4401,21 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
return loanTransactionHelper.getLoanProductId(loanProductJSON); return loanTransactionHelper.getLoanProductId(loanProductJSON);
} }
private static Integer createLoanProduct(final String principal, final String repaymentAfterEvery, final String numberOfRepayments,
boolean autoPayForDownPayment, LoanScheduleType loanScheduleType, final Account... accounts) {
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
final String loanProductJSON = new LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
.withEnableDownPayment(true, "25", autoPayForDownPayment).withinterestRatePerPeriod("0")
.withInterestRateFrequencyTypeAsMonths()
.withRepaymentStrategy(EarlyPaymentLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
.withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withInterestTypeAsDecliningBalance().withMultiDisburse()
.withDisallowExpectedDisbursements(true).withLoanScheduleType(loanScheduleType).withDaysInMonth("30").withDaysInYear("365")
.withMoratorium("0", "0").build(null);
return loanTransactionHelper.getLoanProductId(loanProductJSON);
}
private static ArrayList<HashMap<String, Object>> createLoanProductGetError(final String principal, final String repaymentAfterEvery, private static ArrayList<HashMap<String, Object>> createLoanProductGetError(final String principal, final String repaymentAfterEvery,
final String numberOfRepayments, boolean autoPayForDownPayment, LoanScheduleType loanScheduleType, final String numberOfRepayments, boolean autoPayForDownPayment, LoanScheduleType loanScheduleType,
LoanScheduleProcessingType loanScheduleProcessingType, AdvancedPaymentData allocationRuleData, final Account... accounts) { LoanScheduleProcessingType loanScheduleProcessingType, AdvancedPaymentData allocationRuleData, final Account... accounts) {
@@ -7479,12 +7479,16 @@ public class ClientLoanIntegrationTest {
map.put("amount", amount); map.put("amount", amount);
} }
if (charge.get("dueDate") != null) { if (charge.get("dueDate") != null) {
map.put("dueDate", charge.get("dueDate")); map.put("dueDate", DATE_TIME_FORMATTER.format(fromArrayToLocalDate((List) charge.get("dueDate"))));
} }
map.put("chargeId", charge.get("chargeId")); map.put("chargeId", charge.get("chargeId"));
return map; return map;
} }
private LocalDate fromArrayToLocalDate(List<Integer> dueDate) {
return LocalDate.of(dueDate.get(0), dueDate.get(1), dueDate.get(2));
}
private HashMap createTrancheDetail(final String date, final String amount) { private HashMap createTrancheDetail(final String date, final String amount) {
HashMap detail = new HashMap(); HashMap detail = new HashMap();
detail.put("expectedDisbursementDate", date); detail.put("expectedDisbursementDate", date);
@@ -155,7 +155,7 @@ public class LoanApplicationApprovalTest {
log.info("---------------------------------CLIENT CREATED WITH ID--------------------------------------------------- {}", clientId); log.info("---------------------------------CLIENT CREATED WITH ID--------------------------------------------------- {}", clientId);
final Integer loanProductId = this.loanTransactionHelper final Integer loanProductId = this.loanTransactionHelper
.getLoanProductId(new LoanProductTestBuilder().withInterestTypeAsDecliningBalance().withTranches(true) .getLoanProductId(new LoanProductTestBuilder().withInterestTypeAsDecliningBalance().withTranches(false)
.withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null)); .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null));
log.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}", log.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}",
loanProductId); loanProductId);