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() {
return ChargeTimeType.DISBURSEMENT.getValue().equals(this.value);
return ChargeTimeType.DISBURSEMENT.equals(this);
}
public boolean isOnSpecifiedDueDate() {
return this.value.equals(ChargeTimeType.SPECIFIED_DUE_DATE.getValue());
return this.equals(ChargeTimeType.SPECIFIED_DUE_DATE);
}
public boolean isSavingsActivation() {
return this.value.equals(ChargeTimeType.SAVINGS_ACTIVATION.getValue());
return this.equals(ChargeTimeType.SAVINGS_ACTIVATION);
}
public boolean isSavingsClosure() {
return this.value.equals(ChargeTimeType.SAVINGS_CLOSURE.getValue());
return this.equals(ChargeTimeType.SAVINGS_CLOSURE);
}
public boolean isWithdrawalFee() {
return this.value.equals(ChargeTimeType.WITHDRAWAL_FEE.getValue());
return this.equals(ChargeTimeType.WITHDRAWAL_FEE);
}
public boolean isSavingsNoActivityFee() {
return this.value.equals(ChargeTimeType.SAVINGS_NOACTIVITY_FEE.getValue());
return this.equals(ChargeTimeType.SAVINGS_NOACTIVITY_FEE);
}
public boolean isAnnualFee() {
return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue());
return this.equals(ChargeTimeType.ANNUAL_FEE);
}
public boolean isMonthlyFee() {
return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue());
return this.equals(ChargeTimeType.MONTHLY_FEE);
}
public boolean isWeeklyFee() {
return this.value.equals(ChargeTimeType.WEEKLY_FEE.getValue());
return this.equals(ChargeTimeType.WEEKLY_FEE);
}
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() {
return this.value.equals(ChargeTimeType.OVERDUE_INSTALLMENT.getValue());
return this.equals(ChargeTimeType.OVERDUE_INSTALLMENT);
}
public boolean isAllowedLoanChargeTime() {
@@ -200,22 +204,22 @@ public enum ChargeTimeType {
}
public boolean isOverdraftFee() {
return this.value.equals(ChargeTimeType.OVERDRAFT_FEE.getValue());
return this.equals(ChargeTimeType.OVERDRAFT_FEE);
}
public boolean isTrancheDisbursement() {
return this.value.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue());
return this.equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
}
public boolean isShareAccountActivation() {
return this.value.equals(ChargeTimeType.SHAREACCOUNT_ACTIVATION.getValue());
return this.equals(ChargeTimeType.SHAREACCOUNT_ACTIVATION);
}
public boolean isSharesPurchase() {
return this.value.equals(ChargeTimeType.SHARE_PURCHASE.getValue());
return this.equals(ChargeTimeType.SHARE_PURCHASE);
}
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 {
String emiAmountParameterName = "fixedEmiAmount";
String fixedEmiAmountParameterName = "fixedEmiAmount";
String maxOutstandingBalanceParameterName = "maxOutstandingLoanBalance";
String disbursementDataParameterName = "disbursementData";
String expectedDisbursementDateParameterName = "expectedDisbursementDate";
@@ -36,6 +36,7 @@ public interface LoanApiConstants {
String amountParameterName = "amount";
String chargeTimeTypeParameterName = "chargeTimeType";
String chargeCalculationTypeParameterName = "chargeCalculationType";
String chargePaymentModeParameterName = "chargePaymentMode";
String principalDisbursedParameterName = "transactionAmount";
String chargesParameterName = "charges";
String loanIdTobeApproved = "loanId";
@@ -18,44 +18,30 @@
*/
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.time.LocalDate;
import lombok.Data;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
/**
* 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")
private final Long id;
private final Long chargeId;
private final BigDecimal amount;
private Long id;
private Long chargeId;
private BigDecimal amount;
@SuppressWarnings("unused")
private final Integer chargeTimeType;
private Integer chargeTimeType;
@SuppressWarnings("unused")
private final Integer chargeCalculationType;
private Integer chargeCalculationType;
@SuppressWarnings("unused")
private final 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;
}
private LocalDate dueDate;
}
@@ -19,20 +19,20 @@
package org.apache.fineract.portfolio.loanaccount.data;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Data;
@RequiredArgsConstructor
@Getter
@Data
@AllArgsConstructor
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.Set;
import java.util.function.Predicate;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
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.group.domain.Group;
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.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
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.UndoLastTrancheDisbursementException;
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.LoanScheduleGenerator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
@@ -153,6 +152,8 @@ import org.apache.fineract.useradministration.domain.AppUser;
@Entity
@Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"),
@UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") })
@Setter
@Getter
public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
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)
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 String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final BigDecimal fixedEmiAmount,
final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateralManagement> collateral, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment) {
return new Loan(accountNo, client, null, loanType, fund, officer, loanPurpose, transactionProcessingStrategyCode, loanProduct,
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) {
return new Loan(accountNo, client, null, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, collateral, null, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment);
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 String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates,
final BigDecimal fixedPrincipalPercentagePerInstallment) {
return new Loan(accountNo, null, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategyCode, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
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) {
return new Loan(accountNo, null, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
enableInstallmentLevelDelinquency, submittedOnDate);
}
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 String transactionProcessingStrategyCode, final LoanProductRelatedDetail loanRepaymentScheduleDetail,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates,
final BigDecimal fixedPrincipalPercentagePerInstallment) {
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategyCode, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
final AccountType loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
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) {
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, null, loanCharges, null, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails,
maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
enableInstallmentLevelDelinquency, submittedOnDate);
}
protected Loan() {
this.client = null;
}
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund,
final Staff loanOfficer, final CodeValue loanPurpose, final String transactionProcessingStrategyCode,
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
final Set<LoanCharge> loanCharges, final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates,
final BigDecimal fixedPrincipalPercentagePerInstallment) {
private Loan(final String accountNo, final Client client, final Group group, final AccountType loanType, final Fund fund,
final Staff loanOfficer, final CodeValue loanPurpose,
final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy, final LoanProduct loanProduct,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus, final Set<LoanCharge> loanCharges,
final Set<LoanCollateralManagement> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
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.validateRepaymentPeriodWithGraceSettings();
this.isFloatingInterestRate = isFloatingInterestRate;
this.interestRateDifferential = interestRateDifferential;
@@ -543,12 +555,14 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
this.client = client;
this.group = group;
this.loanType = loanType;
this.loanType = loanType.getValue();
this.fund = fund;
this.loanOfficer = loanOfficer;
this.loanPurpose = loanPurpose;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
this.transactionProcessingStrategyCode = transactionProcessingStrategy.getCode();
this.transactionProcessingStrategyName = transactionProcessingStrategy.getName();
this.loanProduct = loanProduct;
if (loanStatus != null) {
this.loanStatus = loanStatus.getValue();
@@ -563,7 +577,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.summary = new LoanSummary();
}
if (loanType.equals(1) && collateral != null && !collateral.isEmpty()) {
if (loanType.isIndividualAccount() && collateral != null && !collateral.isEmpty()) {
this.loanCollateralManagements = associateWithThisLoan(collateral);
} else {
this.loanCollateralManagements = null;
@@ -573,7 +587,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.syncDisbursementWithMeeting = syncDisbursementWithMeeting;
this.fixedEmiAmount = fixedEmiAmount;
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.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement;
@@ -590,7 +609,26 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
// Add net get net disbursal amount from charges and principal
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() {
@@ -652,10 +690,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.accountNumberRequiresAutoGeneration;
}
public void setAccountNumberRequiresAutoGeneration(final boolean accountNumberRequiresAutoGeneration) {
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
}
public void addLoanCharge(final LoanCharge loanCharge) {
if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) {
@@ -1460,274 +1494,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
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() {
Set<LoanCharge> charges = this.getActiveCharges();
int penaltyWaitPeriod = 0;
@@ -2068,112 +1834,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
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() {
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
@@ -4876,7 +4536,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
break;
}
return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from
// startDate
// startDate
}
private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
@@ -4945,7 +4605,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
public void updateInterestRateFrequencyType() {
this.loanRepaymentScheduleDetail.updateInterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType());
this.loanRepaymentScheduleDetail.setInterestPeriodFrequencyType(this.loanProduct.getInterestPeriodFrequencyType());
}
public Integer getTermFrequency() {
@@ -4975,14 +4635,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
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) {
if (this.client != null && this.client.getOfficeJoiningDate() != null) {
final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningDate();
@@ -4991,11 +4643,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
String action = null;
String postfix = null;
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:
errorMessage = "The date on which a loan is approved cannot be earlier than client's transfer date to this office";
action = "approval";
@@ -5400,6 +5047,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return currentDisbursementDetails;
}
public void clearDisbursementDetails() {
this.disbursementDetails.clear();
}
public LoanDisbursementDetails getDisbursementDetails(final LocalDate transactionDate, final BigDecimal transactionAmount) {
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
if (!disbursementDetail.isReversed() && disbursementDetail.getDisbursementDate().equals(transactionDate)
@@ -5525,7 +5176,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
public void updateIsInterestRecalculationEnabled() {
this.loanRepaymentScheduleDetail.updateIsInterestRecalculationEnabled(isInterestRecalculationEnabledForProduct());
this.loanRepaymentScheduleDetail.setInterestRecalculationEnabled(isInterestRecalculationEnabledForProduct());
}
public LoanInterestRecalculationDetails loanInterestRecalculationDetails() {
@@ -6196,11 +5847,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
// loanCalendar = loanCalendarInstance.getCalendar();
// }
final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.graceOnPrincipalPayment();
final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.graceOnInterestPayment();
final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.graceOnInterestCharged();
final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.getGraceOnPrincipalPayment();
final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.getGraceOnInterestPayment();
final Integer graceOnInterestCharged = this.loanRepaymentScheduleDetail.getGraceOnInterestCharged();
final LocalDate interestChargedFromDate = getInterestChargedFromDate();
final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnDueDate();
final Integer graceOnArrearsAgeing = this.loanRepaymentScheduleDetail.getGraceOnArrearsAgeing();
final Money inArrearsToleranceMoney = this.loanRepaymentScheduleDetail.getInArrearsTolerance();
final BigDecimal emiAmount = getFixedEmiAmount();
@@ -6258,7 +5909,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
public void updateNumberOfRepayments(Integer numberOfRepayments) {
this.loanRepaymentScheduleDetail.updateNumberOfRepayments(numberOfRepayments);
this.loanRepaymentScheduleDetail.setNumberOfRepayments(numberOfRepayments);
}
public void updateRescheduledOnDate(LocalDate rescheduledOnDate) {
@@ -7134,8 +6785,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return rates;
}
public Integer getLoanType() {
return loanType;
public AccountType getLoanType() {
return AccountType.fromInt(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.ChargeTimeType;
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.LoanChargePaidDetail;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
@@ -509,10 +508,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
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() {
return this.dueDate;
}
@@ -306,10 +306,10 @@ public final class LoanApplicationTerms {
final Money principalMoney = loanProductRelatedDetail.getPrincipal();
//
final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment();
final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail.recurringMoratoriumOnPrincipalPeriods();
final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment();
final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged();
final Integer graceOnPrincipalPayment = loanProductRelatedDetail.getGraceOnPrincipalPayment();
final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail.getRecurringMoratoriumOnPrincipalPeriods();
final Integer graceOnInterestPayment = loanProductRelatedDetail.getGraceOnInterestPayment();
final Integer graceOnInterestCharged = loanProductRelatedDetail.getGraceOnInterestCharged();
// Interest recalculation settings
final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
@@ -334,7 +334,7 @@ public final class LoanApplicationTerms {
repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate,
inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance,
loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
loanProductRelatedDetail.getGraceOnArrearsAgeing(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
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,
enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength);
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
defaultMaxNumberOfInstallments);
@@ -1298,21 +1296,21 @@ public class LoanProduct extends AbstractPersistableCustom {
this.loanProductRelatedDetail.isEnableDownPayment())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_DOWN_PAYMENT);
actualChanges.put(LoanProductConstants.ENABLE_DOWN_PAYMENT, newValue);
this.loanProductRelatedDetail.updateEnableDownPayment(newValue);
this.loanProductRelatedDetail.setEnableDownPayment(newValue);
}
if (command.isChangeInBigDecimalParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
this.loanProductRelatedDetail.getDisbursedAmountPercentageForDownPayment())) {
BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT);
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,
this.loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT);
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,
@@ -32,13 +32,13 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
Money getPrincipal();
Integer graceOnInterestCharged();
Integer getGraceOnInterestCharged();
Integer graceOnInterestPayment();
Integer getGraceOnInterestPayment();
Integer graceOnPrincipalPayment();
Integer getGraceOnPrincipalPayment();
Integer recurringMoratoriumOnPrincipalPeriods();
Integer getRecurringMoratoriumOnPrincipalPeriods();
Money getInArrearsTolerance();
@@ -60,5 +60,5 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
AmortizationMethod getAmortizationMethod();
Integer getGraceOnDueDate();
Integer getGraceOnArrearsAgeing();
}
@@ -24,14 +24,11 @@ import jakarta.persistence.Embedded;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
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.Money;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
@@ -48,6 +45,8 @@ import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
* a {@link Loan}.
*/
@Embeddable
@Getter
@Setter
public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentScheduleRelatedDetail {
@Embedded
@@ -241,82 +240,32 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
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
public Money getInArrearsTolerance() {
return Money.of(this.currency, this.inArrearsTolerance);
}
// TODO: REVIEW
@Override
public BigDecimal getNominalInterestRatePerPeriod() {
return this.nominalInterestRatePerPeriod == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString()));
}
// TODO: REVIEW
@Override
public PeriodFrequencyType getInterestPeriodFrequencyType() {
return this.interestPeriodFrequencyType == null ? PeriodFrequencyType.INVALID : this.interestPeriodFrequencyType;
}
// TODO: REVIEW
@Override
public BigDecimal getAnnualNominalInterestRate() {
return this.annualNominalInterestRate == null ? null
: BigDecimal.valueOf(Double.parseDouble(this.annualNominalInterestRate.stripTrailingZeros().toString()));
}
@Override
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;
}
// TODO: Move into assembler
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(20);
@@ -367,18 +316,6 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
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";
if (command.isChangeInBigDecimalParameterNamed(principalParamName, this.principal)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
@@ -557,63 +494,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.isEqualAmortization = newValue;
}
validateRepaymentPeriodWithGraceSettings();
return actualChanges;
}
public void updateCurrency(final MonetaryCurrency currency) {
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) {
public void updateInterestRateDerivedFields(final AprCalculator aprCalculator) {
this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod,
this.numberOfRepayments, this.repayEvery, this.repaymentPeriodFrequencyType);
@@ -623,15 +507,6 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
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() {
return DaysInMonthType.fromInt(this.daysInMonthType);
}
@@ -640,129 +515,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
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() {
this.nominalInterestRatePerPeriod = null;
this.interestPeriodFrequencyType = PeriodFrequencyType.INVALID;
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,
statusCode, productId);
if (tableRequiredBeforAction == null || tableRequiredBeforAction.size() < 1) {
if (tableRequiredBeforAction == null || tableRequiredBeforAction.isEmpty()) {
tableRequiredBeforAction = entityDatatableChecksRepository.findByEntityStatusAndNoProduct(entityName, statusCode);
}
if (tableRequiredBeforAction != null) {
@@ -190,7 +190,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
reqDatatables.add(datatableName);
}
}
if (reqDatatables.size() > 0) {
if (!reqDatatables.isEmpty()) {
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.StandingInstructionRepository;
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.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -250,7 +249,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
// changes to closed
disableStandingInstructionsLinkedToClosedLoan(loan);
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
// Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction
.getLoanTransactionToRepaymentScheduleMappings();
@@ -34,8 +34,10 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
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.DataValidatorBuilder;
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.serialization.VariableLoanScheduleFromApiJsonValidator;
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.loanproduct.LoanProductConstants;
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.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LoanScheduleAssembler {
private final FromJsonHelper fromApiJsonHelper;
@@ -137,35 +140,7 @@ public class LoanScheduleAssembler {
private final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanUtilService loanUtilService;
@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;
}
private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler;
public LoanApplicationTerms assembleLoanTerms(final JsonElement 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) {
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 ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
@@ -186,7 +170,9 @@ public class LoanScheduleAssembler {
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType);
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 PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType);
final Integer nthDay = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyNthDayType", element);
@@ -194,7 +180,8 @@ public class LoanScheduleAssembler {
final DayOfWeekType weekDayType = DayOfWeekType.fromInt(dayOfWeek);
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;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
@@ -206,12 +193,14 @@ public class LoanScheduleAssembler {
// interest terms
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",
element);
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
.fromInt(interestCalculationPeriodType);
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = allowOverridingInterestCalcPeriod
? InterestCalculationPeriodMethod.fromInt(interestCalculationPeriodType)
: loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod();
Boolean allowPartialPeriodInterestCalcualtion = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, element);
if (allowPartialPeriodInterestCalcualtion == null) {
@@ -322,23 +311,29 @@ public class LoanScheduleAssembler {
}
// 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
.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 LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element);
final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = this.configurationDomainService
.isInterestChargedFromDateSameAsDisbursementDate();
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element);
final Integer graceOnArrearsAgeing = allowOverridingGraceOnArrearsAging
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element)
: loanProduct.getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
// other
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);
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
@@ -638,8 +633,8 @@ public class LoanScheduleAssembler {
}
}
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
final LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element, final LoanProduct loanProduct) {
final LoanApplicationTerms loanApplicationTerms = assembleLoanApplicationTermsFrom(element, loanProduct);
return loanApplicationTerms.toLoanProductRelatedDetail();
}
@@ -671,7 +666,8 @@ public class LoanScheduleAssembler {
validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays);
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);
}
@@ -769,7 +765,7 @@ public class LoanScheduleAssembler {
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Set<LocalDate> dueDates = new TreeSet<>();
LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate();
Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().graceOnPrincipalPayment();
Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
if (graceOnPrincipal == null) {
graceOnPrincipal = 0;
}
@@ -1160,4 +1156,232 @@ public class LoanScheduleAssembler {
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.Iterator;
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.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.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
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.domain.Loan;
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.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.loanschedule.data.LoanScheduleData;
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.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanScheduleValidator;
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.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService {
private final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer;
private final LoanScheduleValidator fromApiJsonDeserializer;
private final LoanScheduleAssembler loanScheduleAssembler;
private final FromJsonHelper fromJsonHelper;
private final LoanProductRepository loanProductRepository;
private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer;
private final LoanReadPlatformService loanReadPlatformService;
private final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer;
private final LoanAssembler loanAssembler;
private final LoanApplicationValidator loanApiJsonDeserializer;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final ConfigurationDomainService configurationDomainService;
private final CurrencyReadPlatformService currencyReadPlatformService;
private final LoanUtilService loanUtilService;
@Autowired
public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
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;
}
private final LoanRepositoryWrapper loanRepository;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final LoanSummaryWrapper loanSummaryWrapper;
@Override
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
**/
final Long productId = this.fromJsonHelper.extractLongNamed("productId", query.parsedJson());
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));
if (validateParams) {
boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();
this.loanApiJsonDeserializer.validateForCreate(query.json(), isMeetingMandatoryForJLGLoans, loanProduct);
this.loanApiJsonDeserializer.validateForCreate(query);
}
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());
}
@Override
public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final Loan loan = fetchLoan(loanId);
LocalDate today = DateUtils.getBusinessLocalDate();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
@@ -210,7 +151,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
@Override
@Transactional(readOnly = true)
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);
return constructLoanScheduleData(loan);
}
@@ -262,19 +203,23 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
CurrencyData currencyData = this.currencyReadPlatformService.retrieveCurrency(currency.getCode());
LoanScheduleData scheduleData = new LoanScheduleData(currencyData, installmentData,
loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments(), principal.getAmount(), principal.getAmount(),
totalInterest.getAmount(), totalCharge.getAmount(), totalPenalty.getAmount(),
return new LoanScheduleData(currencyData, installmentData, loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments(),
principal.getAmount(), principal.getAmount(), totalInterest.getAmount(), totalCharge.getAmount(), totalPenalty.getAmount(),
principal.plus(totalCharge).plus(totalInterest).plus(totalPenalty).getAmount());
return scheduleData;
}
private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) {
final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
return loanApplicationTerms;
return loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
}
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;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
@@ -27,6 +29,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
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.PlatformApiDataValidationException;
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.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;
@Component
@@ -45,6 +60,8 @@ import org.springframework.stereotype.Component;
public final class LoanChargeApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
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 {
private final FromJsonHelper fromApiJsonHelper;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
private final LoanApplicationValidator fromApiJsonDeserializer;
private final LoanRepository loanRepository;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -79,12 +79,12 @@ public final class LoanEventApiJsonValidator {
if (isAccountTransfer) {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.disbursementNetDisbursalAmountParameterName));
} else {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
"paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.postDatedChecks, LoanApiConstants.disbursementNetDisbursalAmountParameterName));
}
@@ -111,9 +111,9 @@ public final class LoanEventApiJsonValidator {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementNetDisbursalAmountParameterName).value(netDisbursalAmount)
.ignoreIfNull().positiveAmount();
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
.notGreaterThanMax(principal);
validatePaymentDetails(baseDataValidator, element);
@@ -40,7 +40,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public final class CalculateLoanScheduleQueryFromApiJsonHelper {
public final class LoanScheduleValidator {
/**
* The parameters supported for this command.
@@ -64,7 +64,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
LoanApiConstants.submittedOnNoteParameterName, LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName,
LoanApiConstants.chargesParameterName, LoanApiConstants.collateralParameterName,
LoanApiConstants.syncDisbursementWithMeetingParameterName, LoanApiConstants.linkAccountIdParameterName,
LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.disbursementDataParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
LoanApiConstants.createStandingInstructionAtDisbursementParameterName, LoanApiConstants.isFloatingInterestRateParameterName,
LoanApiConstants.interestRateDifferentialParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
@@ -76,7 +76,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public CalculateLoanScheduleQueryFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper) {
public LoanScheduleValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@@ -31,6 +31,6 @@ public interface GLIMAccountInfoWritePlatformService {
void incrementChildAccountCount(GroupLoanIndividualMonitoringAccount glimAccount);
void addGLIMAccountInfo(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount,
Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId);
GroupLoanIndividualMonitoringAccount createGLIMAccount(String accountNumber, Group group, BigDecimal principalAmount,
Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId);
}
@@ -21,30 +21,23 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@RequiredArgsConstructor
public class GLIMAccountInfoWritePlatformServiceImpl implements GLIMAccountInfoWritePlatformService {
private final PlatformSecurityContext context;
private final GLIMAccountInfoRepository glimAccountRepository;
private final LoanRepository loanRepository;
@Override
public void addGLIMAccountInfo(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount,
Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) {
public GroupLoanIndividualMonitoringAccount createGLIMAccount(String accountNumber, Group group, BigDecimal principalAmount,
Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) {
GroupLoanIndividualMonitoringAccount glimAccountInfo = GroupLoanIndividualMonitoringAccount.getInstance(accountNumber, group,
principalAmount, childAccountsCount, isAcceptingChild, loanStatus, applicationId);
this.glimAccountRepository.save(glimAccountInfo);
return this.glimAccountRepository.save(glimAccountInfo);
}
@Override
@@ -18,29 +18,35 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
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.CodeValueRepositoryWrapper;
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.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.api.JsonQuery;
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.service.ExternalIdFactory;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
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.StaffRepository;
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.WorkingDaysRepositoryWrapper;
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.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.fund.domain.Fund;
import org.apache.fineract.portfolio.fund.domain.FundRepository;
import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.domain.GroupRepository;
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.group.domain.GroupRepositoryWrapper;
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.LoanCharge;
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.LoanRepaymentScheduleTransactionProcessorFactory;
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.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.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.MultiDisbursementDataRequiredException;
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.LoanScheduleType;
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.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
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.rate.domain.Rate;
import org.apache.fineract.portfolio.rate.service.RateAssembler;
@@ -98,7 +106,7 @@ public class LoanAssembler {
private final LoanRepositoryWrapper loanRepository;
private final LoanProductRepository loanProductRepository;
private final ClientRepositoryWrapper clientRepository;
private final GroupRepository groupRepository;
private final GroupRepositoryWrapper groupRepository;
private final FundRepository fundRepository;
private final StaffRepository staffRepository;
private final CodeValueRepositoryWrapper codeValueRepository;
@@ -110,10 +118,18 @@ public class LoanAssembler {
private final HolidayRepository holidayRepository;
private final ConfigurationDomainService configurationDomainService;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanUtilService loanUtilService;
private final RateAssembler rateAssembler;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
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) {
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 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 Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", 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 Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper
@@ -154,8 +160,13 @@ public class LoanAssembler {
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));
final BigDecimal amount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.disbursementPrincipalParameterName, element);
final Boolean allowOverridingTransactionProcessingStrategy = loanProduct.getLoanConfigurableAttributes()
.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 Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId);
CodeValue loanPurpose = null;
@@ -165,76 +176,26 @@ public class LoanAssembler {
List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
BigDecimal fixedEmiAmount = null;
if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) {
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
}
BigDecimal maxOutstandingLoanBalance = null;
if (loanProduct.isMultiDisburseLoan()) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
element, locale);
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());
}
disbursementDetails = this.loanDisbursementDetailsAssembler.fetchDisbursementData(element.getAsJsonObject());
}
final String loanTypeParameterName = "loanType";
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr);
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
Set<LoanCollateralManagement> collateral = new HashSet<>();
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanAccountType = AccountType.fromName(loanTypeStr);
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 AccountType loanAccountType = AccountType.fromName(loanTypeStr);
if (loanAccountType.isIndividualAccount()) {
collateral = this.collateralAssembler.fromParsedJson(element);
}
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
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
@@ -245,111 +206,166 @@ public class LoanAssembler {
// Here we add Rates to LoanApplication
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
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
final Boolean isFloatingInterestRate = this.fromApiJsonHelper
.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) {
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
if (client.isNotActive()) {
throw new ClientNotActiveException(clientId);
}
}
if (groupId != null) {
group = this.groupRepository.findById(groupId).orElseThrow(() -> new GroupNotFoundException(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;
group = this.groupRepository.findOneWithNotFoundDetection(groupId);
}
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", element);
ExternalId externalId = externalIdFactory.create(externalIdStr);
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
if (loanApplication == null) {
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
Boolean isEnableInstallmentLevelDelinquency = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element);
if (isEnableInstallmentLevelDelinquency != null) {
loanApplication.updateEnableInstallmentLevelDelinquency(isEnableInstallmentLevelDelinquency);
} else {
loanApplication.updateEnableInstallmentLevelDelinquency(loanProduct.isEnableInstallmentLevelDelinquency());
if (isEnableInstallmentLevelDelinquency == null) {
isEnableInstallmentLevelDelinquency = loanProduct.isEnableInstallmentLevelDelinquency();
}
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
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());
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,
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;
}
// 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) {
CodeValue codeValue = null;
if (codeValueId != null) {
@@ -377,18 +393,6 @@ public class LoanAssembler {
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,
Loan loanApplication) {
if (transactionProcessingStrategyCode.equals(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
@@ -398,11 +402,435 @@ public class LoanAssembler {
.toList();
loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules);
if (loanProduct.getCreditAllocationRules() != null && loanProduct.getCreditAllocationRules().size() > 0) {
if (loanProduct.getCreditAllocationRules() != null && !loanProduct.getCreditAllocationRules().isEmpty()) {
List<LoanCreditAllocationRule> loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream()
.map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList();
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.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.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -65,13 +65,13 @@ public class LoanChargeAssembler {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
List<Long> disbursementChargeIds = new ArrayList<>();
if (jsonDisbursement != null && jsonDisbursement.size() > 0) {
if (jsonDisbursement != null && !jsonDisbursement.isEmpty()) {
for (int i = 0; i < jsonDisbursement.size(); i++) {
final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject();
if (jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null) {
String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString();
if (chargeIds != null) {
if (chargeIds.indexOf(",") != -1) {
if (chargeIds.contains(",")) {
Iterable<String> chargeId = Splitter.on(',').split(chargeIds);
for (String loanChargeId : chargeId) {
disbursementChargeIds.add(Long.parseLong(loanChargeId));
@@ -104,7 +104,7 @@ public class LoanChargeAssembler {
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 BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale);
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale);
@@ -116,16 +116,8 @@ public class LoanChargeAssembler {
locale);
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", loanChargeElement);
final ExternalId externalId = externalIdFactory.create(externalIdStr);
if (id == null) {
if (loanChargeId == null) {
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;
if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
@@ -145,7 +137,7 @@ public class LoanChargeAssembler {
} else {
if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) {
final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray();
if (disbursementArray.size() > 0) {
if (!disbursementArray.isEmpty()) {
JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject();
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.expectedDisbursementDateParameterName, disbursementDataElement, dateFormat,
@@ -201,14 +193,14 @@ public class LoanChargeAssembler {
}
}
} else {
final Long loanChargeId = id;
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId).orElse(null);
if (loanCharge != null) {
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
loanCharge.update(amount, dueDate, numberOfRepayments);
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.service.AccountAssociationsReadPlatformService;
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.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
@@ -710,7 +709,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
paymentDetail);
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
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;
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 java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
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.HolidayRepository;
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.service.FloatingRatesReadPlatformService;
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.ScheduleGeneratorDTO;
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.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -71,7 +63,6 @@ public class LoanUtilService {
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
private final CalendarReadPlatformService calendarReadPlatformService;
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) {
@@ -288,51 +279,6 @@ public class LoanUtilService {
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) {
if (!repaymentTransactionType.isRepaymentType()) {
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.LoanCollateralManagement;
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.LoanLifecycleStateMachine;
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.service.LoanScheduleHistoryWritePlatformService;
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.LoanUpdateCommandFromApiJsonDeserializer;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -225,7 +224,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final FromJsonHelper fromApiJsonHelper;
private final CalendarRepository calendarRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper;
private final LoanApplicationValidator loanApplicationCommandFromApiJsonHelper;
private final AccountAssociationsRepository accountAssociationRepository;
private final AccountTransferDetailRepository accountTransferDetailRepository;
private final BusinessEventNotifierService businessEventNotifierService;
@@ -240,7 +239,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanRepository loanRepository;
private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler;
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanDisbursementDetailsRepository loanDisbursementDetailsRepository;
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final LoanAccountLockService loanAccountLockService;
@@ -327,8 +325,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
// Get relevant loan collateral modules
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty())
&& AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) {
BigDecimal totalCollateral = BigDecimal.valueOf(0);
@@ -996,7 +993,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
holidayDetailDto, isHolidayValidationDone);
loan = loanTransaction.getLoan();
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction);
@@ -1597,7 +1594,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanCloseBusinessEvent(loan));
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
@@ -1678,7 +1675,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
@@ -2486,7 +2483,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate nextPossibleRepaymentDate,
final LocalDate rescheduledRepaymentDate) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName);
loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount,
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.service.CodeValueReadPlatformService;
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.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
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.security.service.PlatformSecurityContext;
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.fund.domain.FundRepository;
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.service.GroupReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa;
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.LoanRepaymentScheduleInstallmentRepository;
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.LoanTransactionRepository;
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.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
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.serialization.LoanApplicationCommandFromApiJsonHelper;
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.LoanEventApiJsonValidator;
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.LoanChargeWritePlatformService;
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.LoanDownPaymentHandlerServiceImpl;
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.ReplayedTransactionBusinessEventServiceImpl;
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.LoanProductReadPlatformService;
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.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
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.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@@ -168,11 +164,8 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(GLIMAccountInfoWritePlatformService.class)
public GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService(PlatformSecurityContext context,
GLIMAccountInfoRepository glimAccountRepository,
LoanRepository loanRepository) {
return new GLIMAccountInfoWritePlatformServiceImpl(context, glimAccountRepository, loanRepository);
public GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService(GLIMAccountInfoRepository glimAccountRepository) {
return new GLIMAccountInfoWritePlatformServiceImpl(glimAccountRepository);
}
@Bean
@@ -210,38 +203,23 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanApplicationWritePlatformService.class)
public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context,
FromJsonHelper fromJsonHelper, LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
LoanProductDataValidator loanProductCommandFromApiJsonDeserializer,
LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper,
NoteRepository noteRepository, LoanScheduleCalculationPlatformService calculationPlatformService, LoanAssembler loanAssembler,
ClientRepositoryWrapper clientRepository, LoanProductRepository loanProductRepository, LoanChargeAssembler loanChargeAssembler,
LoanCollateralAssembler loanCollateralAssembler, AprCalculator aprCalculator, AccountNumberGenerator accountNumberGenerator,
LoanSummaryWrapper loanSummaryWrapper, GroupRepositoryWrapper groupRepository,
LoanApplicationValidator loanApplicationValidator, LoanRepositoryWrapper loanRepositoryWrapper, NoteRepository noteRepository,
LoanAssembler loanAssembler, LoanSummaryWrapper loanSummaryWrapper,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
CalendarRepository calendarRepository, CalendarInstanceRepository calendarInstanceRepository,
SavingsAccountAssembler savingsAccountAssembler, AccountAssociationsRepository accountAssociationsRepository,
LoanReadPlatformService loanReadPlatformService, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
BusinessEventNotifierService businessEventNotifierService, ConfigurationDomainService configurationDomainService,
LoanScheduleAssembler loanScheduleAssembler, LoanUtilService loanUtilService,
CalendarReadPlatformService calendarReadPlatformService,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
GlobalConfigurationRepositoryWrapper globalConfigurationRepository,
FineractEntityToEntityMappingRepository entityMappingRepository,
FineractEntityRelationRepository fineractEntityRelationRepository,
LoanProductReadPlatformService loanProductReadPlatformService,
RateAssembler rateAssembler, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService,
GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanProductDataValidator loanProductDataValidator) {
SavingsAccountRepositoryWrapper savingsAccountRepository, AccountAssociationsRepository accountAssociationsRepository,
LoanReadPlatformService loanReadPlatformService, BusinessEventNotifierService businessEventNotifierService,
ConfigurationDomainService configurationDomainService, LoanScheduleAssembler loanScheduleAssembler,
LoanUtilService loanUtilService, CalendarReadPlatformService calendarReadPlatformService,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GLIMAccountInfoRepository glimRepository,
LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine) {
return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, loanApplicationTransitionApiJsonValidator,
loanProductCommandFromApiJsonDeserializer, fromApiJsonDeserializer, loanRepositoryWrapper, noteRepository,
calculationPlatformService, loanAssembler, clientRepository, loanProductRepository, loanChargeAssembler,
loanCollateralAssembler, aprCalculator, accountNumberGenerator, loanSummaryWrapper, groupRepository,
loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountAssembler,
accountAssociationsRepository, loanReadPlatformService, accountNumberFormatRepository, businessEventNotifierService,
configurationDomainService, loanScheduleAssembler, loanUtilService, calendarReadPlatformService,
entityDatatableChecksWritePlatformService, globalConfigurationRepository, entityMappingRepository,
fineractEntityRelationRepository, loanProductReadPlatformService, rateAssembler, glimAccountInfoWritePlatformService,
glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanProductDataValidator);
loanApplicationValidator, loanRepositoryWrapper, noteRepository, loanAssembler, loanSummaryWrapper,
loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountRepository,
accountAssociationsRepository, loanReadPlatformService, businessEventNotifierService, configurationDomainService,
loanScheduleAssembler, loanUtilService, calendarReadPlatformService, entityDatatableChecksWritePlatformService,
glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine);
}
@Bean
@@ -254,18 +232,25 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(LoanAssembler.class)
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,
LoanScheduleAssembler loanScheduleAssembler, LoanChargeAssembler loanChargeAssembler,
LoanCollateralAssembler collateralAssembler, LoanSummaryWrapper loanSummaryWrapper,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
HolidayRepository holidayRepository, ConfigurationDomainService configurationDomainService,
WorkingDaysRepositoryWrapper workingDaysRepository, LoanUtilService loanUtilService, RateAssembler rateAssembler,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, ExternalIdFactory externalIdFactory) {
WorkingDaysRepositoryWrapper workingDaysRepository, RateAssembler rateAssembler,
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,
fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler,
loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService,
workingDaysRepository, loanUtilService, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory);
workingDaysRepository, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory, accountNumberFormatRepository,
glimRepository, accountNumberGenerator, glimAccountInfoWritePlatformService, loanCollateralAssembler,
calculationPlatformService, loanDisbursementDetailsAssembler, loanChargeMapper, loanCollateralManagementMapper);
}
@Bean
@@ -369,10 +354,9 @@ public class LoanAccountConfiguration {
CalendarInstanceRepository calendarInstanceRepository, ConfigurationDomainService configurationDomainService,
HolidayRepository holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository,
LoanScheduleGeneratorFactory loanScheduleFactory, FloatingRatesReadPlatformService floatingRatesReadPlatformService,
FromJsonHelper fromApiJsonHelper, CalendarReadPlatformService calendarReadPlatformService) {
CalendarReadPlatformService calendarReadPlatformService) {
return new LoanUtilService(applicationCurrencyRepository, calendarInstanceRepository, configurationDomainService, holidayRepository,
workingDaysRepository, loanScheduleFactory, floatingRatesReadPlatformService, fromApiJsonHelper,
calendarReadPlatformService);
workingDaysRepository, loanScheduleFactory, floatingRatesReadPlatformService, calendarReadPlatformService);
}
@Bean
@@ -390,15 +374,14 @@ public class LoanAccountConfiguration {
AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, LoanReadPlatformService loanReadPlatformService,
FromJsonHelper fromApiJsonHelper, CalendarRepository calendarRepository,
LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper,
AccountAssociationsRepository accountAssociationRepository, AccountTransferDetailRepository accountTransferDetailRepository,
BusinessEventNotifierService businessEventNotifierService, GuarantorDomainService guarantorDomainService,
LoanUtilService loanUtilService, LoanSummaryWrapper loanSummaryWrapper,
LoanApplicationValidator loanApplicationValidator, AccountAssociationsRepository accountAssociationRepository,
AccountTransferDetailRepository accountTransferDetailRepository, BusinessEventNotifierService businessEventNotifierService,
GuarantorDomainService guarantorDomainService, LoanUtilService loanUtilService, LoanSummaryWrapper loanSummaryWrapper,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, CodeValueRepositoryWrapper codeValueRepository,
CashierTransactionDataValidator cashierTransactionDataValidator, GLIMAccountInfoRepository glimRepository,
LoanRepository loanRepository, RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler,
PostDatedChecksRepository postDatedChecksRepository, LoanDisbursementDetailsRepository loanDisbursementDetailsRepository,
PostDatedChecksRepository postDatedChecksRepository,
LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService,
ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService,
@@ -410,11 +393,10 @@ public class LoanAccountConfiguration {
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService,
loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService,
loanApplicationCommandFromApiJsonHelper, accountAssociationRepository, accountTransferDetailRepository,
businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper,
entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository,
cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler,
postDatedChecksRepository, loanDisbursementDetailsRepository, loanRepaymentScheduleInstallmentRepository,
loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService,
guarantorDomainService, loanUtilService, loanSummaryWrapper, entityDatatableChecksWritePlatformService,
transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository,
repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository,
defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService);
}
@@ -439,4 +421,10 @@ public class LoanAccountConfiguration {
BusinessEventNotifierService 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.domain.LoanRepaymentScheduleTransactionProcessorFactory;
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.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
@@ -401,7 +400,7 @@ public final class LoanProductDataValidator {
}
// interest rates
BigDecimal interestRatePerPeriod = null;
boolean isInterestBearing = false;
if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)
&& this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) {
if (isEqualAmortization) {
@@ -485,6 +484,7 @@ public final class LoanProductDataValidator {
.extractBooleanNamed(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED, element);
baseDataValidator.reset().parameter(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED)
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
isInterestBearing = true;
} else {
if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) {
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");
}
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();
final String minInterestRatePerPeriodParameterName = MIN_INTEREST_RATE_PER_PERIOD;
@@ -564,10 +564,12 @@ public final class LoanProductDataValidator {
Locale.getDefault());
baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0,
4);
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
}
// Fixed Length validation
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
fixedLengthValidations(transactionProcessingStrategyCode, isInterestBearing, numberOfRepayments, repaymentEvery, element,
baseDataValidator);
// Guarantee Funds
@@ -819,6 +821,10 @@ public final class LoanProductDataValidator {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(advancedPaymentAllocationsJsonParser
.assembleLoanProductPaymentAllocationRules(command, transactionProcessingStrategyCode));
}
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1267,19 +1273,21 @@ public final class LoanProductDataValidator {
}
// grace validation
Integer graceOnPrincipalPayment = loanProduct.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
if (this.fromApiJsonHelper.parameterExists(GRACE_ON_PRINCIPAL_PAYMENT, element)) {
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_PRINCIPAL_PAYMENT,
element);
graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_PRINCIPAL_PAYMENT, element);
baseDataValidator.reset().parameter(GRACE_ON_PRINCIPAL_PAYMENT).value(graceOnPrincipalPayment).zeroOrPositiveAmount();
}
Integer graceOnInterestPayment = loanProduct.getLoanProductRelatedDetail().getGraceOnInterestPayment();
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();
}
Integer graceOnInterestCharged = loanProduct.getLoanProductRelatedDetail().getGraceOnInterestCharged();
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();
}
@@ -1368,11 +1376,11 @@ public final class LoanProductDataValidator {
}
// interest rates
BigDecimal interestRatePerPeriod = null;
boolean isLinkedToFloatingInterestRates = loanProduct.isLinkedToFloatingInterestRate();
if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) {
isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element);
}
boolean isInterestBearing = false;
if (isLinkedToFloatingInterestRates) {
if (isEqualAmortization) {
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)
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
isInterestBearing = true;
} else {
if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) {
baseDataValidator.reset().parameter(FLOATING_RATES_ID).failWithCode(
@@ -1536,7 +1545,7 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
.zeroOrPositiveAmount();
interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
BigDecimal interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
if (this.fromApiJsonHelper.parameterExists(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,
4);
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
}
// Fixed Length validation
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
fixedLengthValidations(transactionProcessingStrategyCode, isInterestBearing, numberOfRepayments, repaymentEvery, element,
baseDataValidator);
// Guarantee Funds
@@ -1806,6 +1816,11 @@ public final class LoanProductDataValidator {
"supported.only.for.progressive.loan.schedule.handling",
"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);
}
@@ -2400,13 +2415,7 @@ public final class LoanProductDataValidator {
}
}
public void fixedLengthValidations(final String transactionProcessingStrategyCode, final LoanApplicationTerms loanApplicationTerms,
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,
public void fixedLengthValidations(final String transactionProcessingStrategyCode, final boolean thereIsInterest,
final Integer numberOfRepayments, final Integer repayEvery, final JsonElement element,
final DataValidatorBuilder baseDataValidator) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.FIXED_LENGTH, element)) {
@@ -2420,15 +2429,14 @@ public final class LoanProductDataValidator {
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategyCode)) {
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",
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";
throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.zero.interest", errorMsg,
interestRatePerPeriod);
throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.zero.interest", errorMsg);
}
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.LoanTransactionHelper;
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.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -2079,8 +2080,12 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
overpaymentAccount);
assertNotNull(localLoanProductId);
Integer localLoanProductId2 = createLoanProduct("500", "15", "4", false, LoanScheduleType.CUMULATIVE, assetAccount,
incomeAccount, expenseAccount, overpaymentAccount);
assertNotNull(localLoanProductId2);
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",
FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE,
LoanScheduleProcessingType.VERTICAL.name()));
@@ -4396,6 +4401,21 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
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,
final String numberOfRepayments, boolean autoPayForDownPayment, LoanScheduleType loanScheduleType,
LoanScheduleProcessingType loanScheduleProcessingType, AdvancedPaymentData allocationRuleData, final Account... accounts) {
@@ -7479,12 +7479,16 @@ public class ClientLoanIntegrationTest {
map.put("amount", amount);
}
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"));
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) {
HashMap detail = new HashMap();
detail.put("expectedDisbursementDate", date);
@@ -155,7 +155,7 @@ public class LoanApplicationApprovalTest {
log.info("---------------------------------CLIENT CREATED WITH ID--------------------------------------------------- {}", clientId);
final Integer loanProductId = this.loanTransactionHelper
.getLoanProductId(new LoanProductTestBuilder().withInterestTypeAsDecliningBalance().withTranches(true)
.getLoanProductId(new LoanProductTestBuilder().withInterestTypeAsDecliningBalance().withTranches(false)
.withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null));
log.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}",
loanProductId);