FINERACT-2090: restructure loan disbursements
more refactors
This commit is contained in:
committed by
Adam Saghy
parent
79f32d32a3
commit
c9d1b81cc6
+6
-8
@@ -158,15 +158,13 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransferDetails details = new ExternalAssetOwnerTransferDetails();
|
||||
details.setExternalAssetOwnerTransfer(externalAssetOwnerTransfer);
|
||||
details.setTotalOutstanding(Objects.requireNonNullElse(loan.getLoanSummary().getTotalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalPrincipalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalInterestOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalInterestOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalPrincipalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalInterestOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalInterestOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalFeeChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalFeeChargesOutstanding(), BigDecimal.ZERO));
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalFeeChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPenaltyChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalPenaltyChargesOutstanding(), BigDecimal.ZERO));
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalPenaltyChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalOverpaid(Objects.requireNonNullElse(loan.getTotalOverpaid(), BigDecimal.ZERO));
|
||||
return details;
|
||||
}
|
||||
@@ -179,7 +177,7 @@ public class LoanAccountOwnerTransferBusinessStep implements LoanCOBBusinessStep
|
||||
}
|
||||
|
||||
private boolean isTransferable(final Loan loan) {
|
||||
return MathUtil.nullToDefault(loan.getLoanSummary().getTotalOutstanding(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0;
|
||||
return MathUtil.nullToDefault(loan.getSummary().getTotalOutstanding(), BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
private void handleSameDaySaleAndBuyback(final LocalDate settlementDate, final List<ExternalAssetOwnerTransfer> transferDataList,
|
||||
|
||||
+4
-4
@@ -80,10 +80,10 @@ public class AccountingServiceImpl implements AccountingService {
|
||||
// transaction properties
|
||||
final Long transactionId = transfer.getId();
|
||||
final LocalDate transactionDate = transfer.getSettlementDate();
|
||||
final BigDecimal principalAmount = loan.getLoanSummary().getTotalPrincipalOutstanding();
|
||||
final BigDecimal interestAmount = loan.getLoanSummary().getTotalInterestOutstanding();
|
||||
final BigDecimal feesAmount = loan.getLoanSummary().getTotalFeeChargesOutstanding();
|
||||
final BigDecimal penaltiesAmount = loan.getLoanSummary().getTotalPenaltyChargesOutstanding();
|
||||
final BigDecimal principalAmount = loan.getSummary().getTotalPrincipalOutstanding();
|
||||
final BigDecimal interestAmount = loan.getSummary().getTotalInterestOutstanding();
|
||||
final BigDecimal feesAmount = loan.getSummary().getTotalFeeChargesOutstanding();
|
||||
final BigDecimal penaltiesAmount = loan.getSummary().getTotalPenaltyChargesOutstanding();
|
||||
final BigDecimal overPaymentAmount = loan.getTotalOverpaid();
|
||||
|
||||
// Moving money to asset transfer account
|
||||
|
||||
+5
-7
@@ -165,15 +165,13 @@ public class LoanAccountOwnerTransferServiceImpl implements LoanAccountOwnerTran
|
||||
ExternalAssetOwnerTransfer externalAssetOwnerTransfer) {
|
||||
ExternalAssetOwnerTransferDetails details = new ExternalAssetOwnerTransferDetails();
|
||||
details.setExternalAssetOwnerTransfer(externalAssetOwnerTransfer);
|
||||
details.setTotalOutstanding(Objects.requireNonNullElse(loan.getLoanSummary().getTotalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalPrincipalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalInterestOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalInterestOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPrincipalOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalPrincipalOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalInterestOutstanding(Objects.requireNonNullElse(loan.getSummary().getTotalInterestOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalFeeChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalFeeChargesOutstanding(), BigDecimal.ZERO));
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalFeeChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalPenaltyChargesOutstanding(
|
||||
Objects.requireNonNullElse(loan.getLoanSummary().getTotalPenaltyChargesOutstanding(), BigDecimal.ZERO));
|
||||
Objects.requireNonNullElse(loan.getSummary().getTotalPenaltyChargesOutstanding(), BigDecimal.ZERO));
|
||||
details.setTotalOverpaid(Objects.requireNonNullElse(loan.getTotalOverpaid(), BigDecimal.ZERO));
|
||||
return details;
|
||||
}
|
||||
|
||||
+5
-5
@@ -203,7 +203,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
ExternalAssetOwnerTransfer secondResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(firstResponseItem.getStatus()).thenReturn(ExternalTransferStatus.BUYBACK);
|
||||
@@ -248,7 +248,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(newTransfer.getStatus()).thenReturn(ExternalTransferStatus.ACTIVE);
|
||||
// when
|
||||
@@ -296,7 +296,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ZERO);
|
||||
when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ZERO);
|
||||
when(newTransfer.getStatus()).thenReturn(ExternalTransferStatus.DECLINED);
|
||||
@@ -340,7 +340,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
ExternalAssetOwnerTransfer newTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(externalAssetOwnerTransferRepository.save(any())).thenReturn(firstResponseItem).thenReturn(newTransfer);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ONE.negate());
|
||||
when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ONE.negate());
|
||||
when(newTransfer.getStatus()).thenReturn(ExternalTransferStatus.DECLINED);
|
||||
@@ -389,7 +389,7 @@ public class LoanAccountOwnerTransferBusinessStepTest {
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.ZERO);
|
||||
when(loanForProcessing.getTotalOverpaid()).thenReturn(BigDecimal.ONE);
|
||||
ExternalAssetOwnerTransfer firstResponseItem = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ public class LoanAccountOwnerTransferServiceTest {
|
||||
final Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
|
||||
ExternalAssetOwnerTransfer pendingBuybackTransfer = Mockito.mock(ExternalAssetOwnerTransfer.class);
|
||||
when(pendingBuybackTransfer.getStatus()).thenReturn(BUYBACK);
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
||||
if (anyOfAllowedWhenComingFrom(from, LoanStatus.APPROVED, LoanStatus.CLOSED_OBLIGATIONS_MET)) {
|
||||
newState = activeTransition();
|
||||
} else if (from.isOverpaid() && loan.getTotalOverpaidAsMoney().isZero()) {
|
||||
if (loan.getLoanSummary().getTotalOutstanding(loan.getCurrency()).isZero()) {
|
||||
if (loan.getSummary().getTotalOutstanding(loan.getCurrency()).isZero()) {
|
||||
newState = closeObligationsMetTransition();
|
||||
} else {
|
||||
newState = activeTransition();
|
||||
|
||||
+19
-156
@@ -613,7 +613,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return this.loanRepaymentScheduleDetail.getNumberOfRepayments();
|
||||
}
|
||||
|
||||
private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
|
||||
public LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) {
|
||||
if (this.summary == null) {
|
||||
this.summary = LoanSummary.create(feeChargesDueAtDisbursement);
|
||||
} else {
|
||||
@@ -634,7 +634,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
this.summary.updateTotalWaived(this.summary.getTotalWaived().subtract(amountWaived));
|
||||
}
|
||||
|
||||
private BigDecimal deriveSumTotalOfChargesDueAtDisbursement() {
|
||||
public BigDecimal deriveSumTotalOfChargesDueAtDisbursement() {
|
||||
return getActiveCharges().stream() //
|
||||
.filter(LoanCharge::isDueAtDisbursement) //
|
||||
.map(LoanCharge::amount) //
|
||||
@@ -1721,108 +1721,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges,
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) {
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE);
|
||||
|
||||
this.disbursedBy = currentUser;
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
actualChanges.put(LOCALE, command.locale());
|
||||
actualChanges.put(DATE_FORMAT, command.dateFormat());
|
||||
actualChanges.put(ACTUAL_DISBURSEMENT_DATE, command.stringValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE));
|
||||
|
||||
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
|
||||
|
||||
// validate if disbursement date is a holiday or a non-working day
|
||||
validateDisbursementDateIsOnNonWorkingDay(holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
|
||||
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
|
||||
|
||||
regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(),
|
||||
isDisbursementMissed(), scheduleGeneratorDTO);
|
||||
|
||||
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
handleDisbursementTransaction(actualDisbursementDate, paymentDetail);
|
||||
updateLoanSummaryDerivedFields();
|
||||
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
|
||||
|
||||
/*
|
||||
* Add an interest applied transaction of the interest is accrued upfront (Up front accrual), no accounting or
|
||||
* cash based accounting is selected
|
||||
*/
|
||||
if (((isMultiDisburmentLoan() && getDisbursedLoanDisbursementDetails().size() == 1) || !isMultiDisburmentLoan())
|
||||
&& isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
|
||||
actualDisbursementDate, externalId);
|
||||
addLoanTransaction(interestAppliedTransaction);
|
||||
}
|
||||
|
||||
ChangedTransactionDetail result = reprocessTransactionForDisbursement();
|
||||
this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, this);
|
||||
actualChanges.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus));
|
||||
return result;
|
||||
}
|
||||
|
||||
private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam,
|
||||
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||
if ((interestRecalculationEnabledParam && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
}
|
||||
|
||||
private List<LoanDisbursementDetails> getDisbursedLoanDisbursementDetails() {
|
||||
public List<LoanDisbursementDetails> getDisbursedLoanDisbursementDetails() {
|
||||
return getDisbursementDetails().stream() //
|
||||
.filter(it -> it.actualDisbursementDate() != null) //
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
|
||||
final LocalDate actualDisbursementDate, BigDecimal emiAmount, LocalDate nextPossibleRepaymentDate,
|
||||
LocalDate rescheduledRepaymentDate) {
|
||||
boolean isEmiAmountChanged = false;
|
||||
if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.isCanDefineInstallmentAmount()) && emiAmount != null
|
||||
&& emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
|
||||
if (this.loanProduct.isMultiDisburseLoan()) {
|
||||
final LocalDate dateValue = null;
|
||||
final boolean isSpecificToInstallment = false;
|
||||
final Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled = scheduleGeneratorDTO
|
||||
.isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled();
|
||||
LocalDate effectiveDateFrom = actualDisbursementDate;
|
||||
if (!isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled && actualDisbursementDate.equals(nextPossibleRepaymentDate)) {
|
||||
effectiveDateFrom = nextPossibleRepaymentDate.plusDays(1);
|
||||
}
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
||||
effectiveDateFrom, emiAmount, dateValue, isSpecificToInstallment, this, LoanStatus.ACTIVE.getValue());
|
||||
this.loanTermVariations.add(loanVariationTerms);
|
||||
} else {
|
||||
this.fixedEmiAmount = emiAmount;
|
||||
}
|
||||
isEmiAmountChanged = true;
|
||||
}
|
||||
if (rescheduledRepaymentDate != null && this.loanProduct.isMultiDisburseLoan()) {
|
||||
final boolean isSpecificToInstallment = false;
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.DUE_DATE.getValue(),
|
||||
nextPossibleRepaymentDate, emiAmount, rescheduledRepaymentDate, isSpecificToInstallment, this,
|
||||
LoanStatus.ACTIVE.getValue());
|
||||
this.loanTermVariations.add(loanVariationTerms);
|
||||
}
|
||||
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged
|
||||
|| rescheduledRepaymentDate != null) {
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
} else {
|
||||
regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canDisburse(final LocalDate actualDisbursementDate) {
|
||||
LocalDate loanSubmittedOnDate = this.submittedOnDate;
|
||||
final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, this);
|
||||
@@ -1918,25 +1822,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail reprocessTransactionForDisbursement() {
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
if (this.loanProduct.isMultiDisburseLoan()) {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
}
|
||||
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
||||
}
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
private Collection<LoanDisbursementDetails> fetchUndisbursedDetail() {
|
||||
Collection<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
|
||||
LocalDate date = null;
|
||||
@@ -1993,15 +1878,19 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
private boolean isDisbursementAllowed() {
|
||||
List<LoanDisbursementDetails> disbursementDetails = getDisbursementDetails();
|
||||
return disbursementDetails == null || disbursementDetails.isEmpty()
|
||||
boolean isSingleDisburseLoanDisbursementAllowed = disbursementDetails == null || disbursementDetails.isEmpty()
|
||||
|| disbursementDetails.stream().anyMatch(it -> it.actualDisbursementDate() == null);
|
||||
boolean isMultiDisburseLoanDisbursementAllowed = isMultiDisburmentLoan()
|
||||
&& (disbursementDetails == null || disbursementDetails.stream().filter(it -> it.actualDisbursementDate() != null)
|
||||
.count() < loanProduct.getLoanProductTrancheDetails().maxTrancheCount());
|
||||
return isSingleDisburseLoanDisbursementAllowed || isMultiDisburseLoanDisbursementAllowed;
|
||||
}
|
||||
|
||||
private boolean atLeastOnceDisbursed() {
|
||||
return getDisbursementDetails().stream().anyMatch(it -> it.actualDisbursementDate() != null);
|
||||
}
|
||||
|
||||
private void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
|
||||
public void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment repaymentPeriod : installments) {
|
||||
repaymentPeriod.updateDerivedFields(loanCurrency(), actualDisbursementDate);
|
||||
@@ -2075,7 +1964,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return interestRate;
|
||||
}
|
||||
|
||||
private void handleDisbursementTransaction(final LocalDate disbursedOn, final PaymentDetail paymentDetail) {
|
||||
public void handleDisbursementTransaction(final LocalDate disbursedOn, final PaymentDetail paymentDetail) {
|
||||
// add repayment transaction to track incoming money from client to mfi
|
||||
// for (charges due at time of disbursement)
|
||||
|
||||
@@ -2099,11 +1988,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
* create a Charge applied transaction if Up front Accrual, None or Cash based accounting is enabled
|
||||
*/
|
||||
if ((charge.getCharge().getChargeTimeType().equals(ChargeTimeType.DISBURSEMENT.getValue())
|
||||
&& disbursedOn.equals(actualDisbursementDate) && (actualDisbursementDate != null) && !charge.isWaived()
|
||||
&& !charge.isFullyPaid())
|
||||
&& disbursedOn.equals(actualDisbursementDate) && !charge.isWaived() && !charge.isFullyPaid())
|
||||
|| (charge.getCharge().getChargeTimeType().equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue())
|
||||
&& disbursedOn.equals(actualDisbursementDate) && (actualDisbursementDate != null) && !charge.isWaived()
|
||||
&& !charge.isFullyPaid())) {
|
||||
&& disbursedOn.equals(actualDisbursementDate) && !charge.isWaived() && !charge.isFullyPaid())) {
|
||||
if (totalFeeChargesDueAtDisbursement.isGreaterThanZero() && !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
|
||||
charge.markAsFullyPaid();
|
||||
// Add "Loan Charge Paid By" details to this transaction
|
||||
@@ -2244,7 +2131,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
actualChanges.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus));
|
||||
|
||||
final LocalDate actualDisbursementDate = getDisbursementDate();
|
||||
final boolean isScheduleRegenerateRequired = isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate);
|
||||
final boolean isScheduleRegenerateRequired = isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate);
|
||||
this.actualDisbursementDate = null;
|
||||
this.disbursedBy = null;
|
||||
boolean isDisbursedAmountChanged = !MathUtil.isEqualTo(approvedPrincipal,
|
||||
@@ -2461,7 +2348,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
if (loanTransaction.isRecoveryRepayment()
|
||||
&& loanTransaction.getAmount(loanCurrency()).getAmount().compareTo(getLoanSummary().getTotalWrittenOff()) > 0) {
|
||||
&& loanTransaction.getAmount(loanCurrency()).getAmount().compareTo(getSummary().getTotalWrittenOff()) > 0) {
|
||||
final String errorMessage = "The transaction amount cannot greater than the remaining written off amount.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.greater.than.total.written.off", errorMessage);
|
||||
}
|
||||
@@ -3210,8 +3097,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
public boolean isAllTranchesNotDisbursed() {
|
||||
LoanStatus actualLoanStatus = LoanStatus.fromInt(this.loanStatus);
|
||||
return this.loanProduct.isMultiDisburseLoan() && (actualLoanStatus.isActive() || actualLoanStatus.isApproved()
|
||||
|| actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) && isDisbursementAllowed();
|
||||
boolean isInRightStatus = actualLoanStatus.isActive() || actualLoanStatus.isApproved() || actualLoanStatus.isClosedObligationsMet()
|
||||
|| actualLoanStatus.isOverpaid();
|
||||
return this.loanProduct.isMultiDisburseLoan() && isInRightStatus && isDisbursementAllowed();
|
||||
}
|
||||
|
||||
private boolean hasDisbursementTransaction() {
|
||||
@@ -3263,7 +3151,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return principal;
|
||||
}
|
||||
|
||||
private boolean isActualDisbursedOnDateEarlierOrLaterThanExpected(final LocalDate actualDisbursedOnDate) {
|
||||
public boolean isActualDisbursedOnDateEarlierOrLaterThanExpected(final LocalDate actualDisbursedOnDate) {
|
||||
boolean isRegenerationRequired = false;
|
||||
if (this.loanProduct.isMultiDisburseLoan()) {
|
||||
LoanDisbursementDetails details = fetchLastDisburseDetail();
|
||||
@@ -3274,10 +3162,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return isRegenerationRequired || !DateUtils.isEqual(actualDisbursedOnDate, this.expectedDisbursementDate);
|
||||
}
|
||||
|
||||
private boolean isRepaymentScheduleRegenerationRequiredForDisbursement(final LocalDate actualDisbursementDate) {
|
||||
return isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate);
|
||||
}
|
||||
|
||||
private Money getTotalPaidInRepayments() {
|
||||
Money cumulativePaid = Money.zero(loanCurrency());
|
||||
|
||||
@@ -3814,20 +3698,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from startDate
|
||||
}
|
||||
|
||||
private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
|
||||
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())) {
|
||||
final String errorMessage = "Expected disbursement date cannot be on a non working day";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDisbursementDateIsOnHoliday(final boolean allowTransactionsOnHoliday, final List<Holiday> holidays) {
|
||||
if (!allowTransactionsOnHoliday && HolidayUtil.isHoliday(getDisbursementDate(), holidays)) {
|
||||
final String errorMessage = "Expected disbursement date cannot be on a holiday";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
||||
final boolean allowTransactionsOnNonWorkingDay) {
|
||||
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
||||
@@ -4838,13 +4708,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Loan summary embedded object
|
||||
**/
|
||||
public LoanSummary getLoanSummary() {
|
||||
return this.summary;
|
||||
}
|
||||
|
||||
public void updateRescheduledByUser(AppUser rescheduledByUser) {
|
||||
this.rescheduledByUser = rescheduledByUser;
|
||||
}
|
||||
@@ -5245,7 +5108,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
public LoanRepaymentScheduleInstallment fetchLoanForeclosureDetail(final LocalDate closureDate) {
|
||||
Money[] receivables = retriveIncomeOutstandingTillDate(closureDate);
|
||||
Money totalPrincipal = Money.of(getCurrency(), this.getLoanSummary().getTotalPrincipalOutstanding());
|
||||
Money totalPrincipal = Money.of(getCurrency(), this.getSummary().getTotalPrincipalOutstanding());
|
||||
totalPrincipal = totalPrincipal.minus(receivables[3]);
|
||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
||||
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
||||
|
||||
+18
-18
@@ -262,25 +262,25 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
|
||||
final ExternalId externalId) {
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate,
|
||||
loan.getLoanSummary().getTotalOutstanding(), loan.getLoanSummary().getTotalPrincipalOutstanding(),
|
||||
loan.getLoanSummary().getTotalInterestOutstanding(), loan.getLoanSummary().getTotalFeeChargesOutstanding(),
|
||||
loan.getLoanSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
|
||||
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
|
||||
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
|
||||
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate,
|
||||
final ExternalId externalId) {
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate,
|
||||
loan.getLoanSummary().getTotalOutstanding(), loan.getLoanSummary().getTotalPrincipalOutstanding(),
|
||||
loan.getLoanSummary().getTotalInterestOutstanding(), loan.getLoanSummary().getTotalFeeChargesOutstanding(),
|
||||
loan.getLoanSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
|
||||
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
|
||||
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
|
||||
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate,
|
||||
final ExternalId externalId) {
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate,
|
||||
loan.getLoanSummary().getTotalOutstanding(), loan.getLoanSummary().getTotalPrincipalOutstanding(),
|
||||
loan.getLoanSummary().getTotalInterestOutstanding(), loan.getLoanSummary().getTotalFeeChargesOutstanding(),
|
||||
loan.getLoanSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
|
||||
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
|
||||
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
|
||||
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail,
|
||||
@@ -369,19 +369,19 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
}
|
||||
|
||||
public static LoanTransaction chargeOff(final Loan loan, final LocalDate chargeOffDate, final ExternalId externalId) {
|
||||
BigDecimal principalPortion = loan.getLoanSummary().getTotalPrincipalOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalPrincipalOutstanding()
|
||||
BigDecimal principalPortion = loan.getSummary().getTotalPrincipalOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getSummary().getTotalPrincipalOutstanding()
|
||||
: null;
|
||||
BigDecimal interestPortion = loan.getLoanSummary().getTotalInterestOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalInterestOutstanding()
|
||||
BigDecimal interestPortion = loan.getSummary().getTotalInterestOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getSummary().getTotalInterestOutstanding()
|
||||
: null;
|
||||
BigDecimal feePortion = loan.getLoanSummary().getTotalFeeChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalFeeChargesOutstanding()
|
||||
BigDecimal feePortion = loan.getSummary().getTotalFeeChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getSummary().getTotalFeeChargesOutstanding()
|
||||
: null;
|
||||
BigDecimal penaltyPortion = loan.getLoanSummary().getTotalPenaltyChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getLoanSummary().getTotalPenaltyChargesOutstanding()
|
||||
BigDecimal penaltyPortion = loan.getSummary().getTotalPenaltyChargesOutstanding().compareTo(BigDecimal.ZERO) != 0
|
||||
? loan.getSummary().getTotalPenaltyChargesOutstanding()
|
||||
: null;
|
||||
BigDecimal totalOutstanding = loan.getLoanSummary().getTotalOutstanding();
|
||||
BigDecimal totalOutstanding = loan.getSummary().getTotalOutstanding();
|
||||
|
||||
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_OFF.getValue(), chargeOffDate, totalOutstanding,
|
||||
principalPortion, interestPortion, feePortion, penaltyPortion, null, false, null, externalId);
|
||||
|
||||
+1
-1
@@ -2494,7 +2494,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
currency, applyInterestRecalculation);
|
||||
retainedInstallments.addAll(newRepaymentScheduleInstallments);
|
||||
loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
|
||||
loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getLoanSummary().getTotalInterestCharged()));
|
||||
loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged()));
|
||||
} else {
|
||||
loanApplicationTerms.getLoanTermVariations().resetVariations();
|
||||
periods.clear();
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ public class LoanArrearsAgingServiceImpl implements LoanArrearsAgingService {
|
||||
Map<Long, List<LoanSchedulePeriodData>> scheduleDate = this.jdbcTemplate.query(originalScheduleExtractor.schema,
|
||||
originalScheduleExtractor);
|
||||
if (scheduleDate.size() > 0) {
|
||||
List<Map<String, Object>> transactions = getLoanSummary(loan.getId(), loan.getLoanSummary());
|
||||
List<Map<String, Object>> transactions = getLoanSummary(loan.getId(), loan.getSummary());
|
||||
updateScheduleWithPaidDetail(scheduleDate, transactions);
|
||||
createInsertStatements(updateStatement, scheduleDate, count == 0);
|
||||
if (updateStatement.size() == 1) {
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ public class CheckLoanRepaymentDueBusinessStep implements LoanCOBBusinessStep {
|
||||
LoanRepaymentScheduleInstallment repaymentScheduleInstallment, LocalDate repaymentDate, List<LoanStatus> nonDisbursedStatuses) {
|
||||
return repaymentDate.minusDays(numberOfDaysBeforeDueDateToRaiseEvent).equals(currentDate)
|
||||
&& !nonDisbursedStatuses.contains(loan.getStatus())
|
||||
&& loan.getLoanSummary().getTotalOutstanding().compareTo(BigDecimal.ZERO) > 0
|
||||
&& loan.getSummary().getTotalOutstanding().compareTo(BigDecimal.ZERO) > 0
|
||||
&& repaymentScheduleInstallment.getTotalOutstanding(loan.getCurrency()).isGreaterThanZero();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -45,8 +45,7 @@ public class CheckLoanRepaymentOverdueBusinessStep implements LoanCOBBusinessSte
|
||||
public Loan execute(Loan loan) {
|
||||
List<LoanStatus> nonDisbursedStatuses = Arrays.asList(LoanStatus.INVALID, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL,
|
||||
LoanStatus.APPROVED);
|
||||
if (!nonDisbursedStatuses.contains(loan.getStatus())
|
||||
&& loan.getLoanSummary().getTotalOutstanding().compareTo(BigDecimal.ZERO) > 0) {
|
||||
if (!nonDisbursedStatuses.contains(loan.getStatus()) && loan.getSummary().getTotalOutstanding().compareTo(BigDecimal.ZERO) > 0) {
|
||||
log.debug("start processing loan repayment overdue business step for loan with Id [{}]", loan.getId());
|
||||
Long numberOfDaysAfterDueDateToRaiseEvent = configurationDomainService.retrieveRepaymentOverdueDays();
|
||||
if (loan.getLoanProduct().getOverDueDaysForRepaymentEvent() != null) {
|
||||
|
||||
+2
-2
@@ -97,8 +97,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
|
||||
|
||||
if (loan.loanProduct().isMultiDisburseLoan()) {
|
||||
BigDecimal disbursedAmount = loan.getDisbursedAmount();
|
||||
BigDecimal principalRepaid = loan.getLoanSummary().getTotalPrincipalRepaid();
|
||||
BigDecimal principalWrittenOff = loan.getLoanSummary().getTotalPrincipalWrittenOff();
|
||||
BigDecimal principalRepaid = loan.getSummary().getTotalPrincipalRepaid();
|
||||
BigDecimal principalWrittenOff = loan.getSummary().getTotalPrincipalWrittenOff();
|
||||
if (disbursedAmount.subtract(principalWrittenOff).subtract(principalRepaid).compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
+136
-35
@@ -38,11 +38,15 @@ import org.apache.commons.lang3.StringUtils;
|
||||
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.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
@@ -52,26 +56,35 @@ import org.apache.fineract.organisation.monetary.exception.CurrencyNotFoundExcep
|
||||
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
|
||||
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.apache.fineract.portfolio.calendar.domain.Calendar;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
|
||||
import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
||||
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.LoanCollateralManagement;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanChargeRefundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanRepaymentScheduleNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -81,8 +94,11 @@ public final class LoanTransactionValidator {
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final LoanApplicationValidator fromApiJsonDeserializer;
|
||||
private final LoanRepository loanRepository;
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final ApplicationCurrencyRepository applicationCurrencyRepository;
|
||||
private final LoanUtilService loanUtilService;
|
||||
private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService;
|
||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
@@ -91,13 +107,129 @@ public final class LoanTransactionValidator {
|
||||
}
|
||||
}
|
||||
|
||||
public void validateDisbursement(final String json, boolean isAccountTransfer) {
|
||||
|
||||
public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, Long loanId) {
|
||||
String json = command.json();
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
Set<String> disbursementParameters = null;
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getDisbursementParameters(isAccountTransfer));
|
||||
|
||||
LoanApplicationValidator.validateOrThrow("loan.disbursement", baseDataValidator -> {
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("actualDisbursementDate", element);
|
||||
baseDataValidator.reset().parameter("actualDisbursementDate").value(actualDisbursementDate).notNull();
|
||||
|
||||
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
||||
|
||||
final BigDecimal principal = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalDisbursedParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.principalDisbursedParameterName).value(principal).ignoreIfNull()
|
||||
.positiveAmount();
|
||||
|
||||
final BigDecimal netDisbursalAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.disbursementNetDisbursalAmountParameterName).value(netDisbursalAmount)
|
||||
.ignoreIfNull().positiveAmount();
|
||||
|
||||
final BigDecimal emiAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).ignoreIfNull()
|
||||
.positiveAmount().notGreaterThanMax(principal);
|
||||
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
|
||||
if (command.parameterExists("postDatedChecks")) {
|
||||
this.validateDisbursementWithPostDatedChecks(command.json(), loanId);
|
||||
}
|
||||
|
||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
validateLoanClientIsActive(loan);
|
||||
validateLoanGroupIsActive(loan);
|
||||
|
||||
if (loan.isChargedOff() && DateUtils.isBefore(actualDisbursementDate, loan.getChargedOffOnDate())) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||
+ loan.getId()
|
||||
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||
loan.getId());
|
||||
}
|
||||
|
||||
boolean isSingleDisburseLoan = !loan.getLoanProduct().isMultiDisburseLoan();
|
||||
boolean isSingleDisburseNotApprovedOrDisbursedAlready = isSingleDisburseLoan && !(loan.isApproved() && loan.isNotDisbursed());
|
||||
boolean isMultiDisburseLoanAndAllTranchesDisbursed = loan.getLoanProduct().isMultiDisburseLoan()
|
||||
&& !loan.isAllTranchesNotDisbursed();
|
||||
if (isSingleDisburseNotApprovedOrDisbursedAlready || isMultiDisburseLoanAndAllTranchesDisbursed) {
|
||||
final String defaultUserMessage = "Loan Disbursal is not allowed. Loan Account is not in approved and not disbursed state.";
|
||||
final ApiParameterError error = ApiParameterError
|
||||
.generalError("error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage);
|
||||
baseDataValidator.getDataValidationErrors().add(error);
|
||||
}
|
||||
|
||||
final BigDecimal disbursedAmount = loan.getDisbursedAmount();
|
||||
final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
|
||||
|
||||
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) {
|
||||
BigDecimal totalCollateral = collectTotalCollateral(loanCollateralManagements);
|
||||
|
||||
// Validate the loan collateral value against the disbursedAmount
|
||||
if (disbursedAmount.compareTo(totalCollateral) > 0) {
|
||||
throw new LoanCollateralAmountNotSufficientException(disbursedAmount);
|
||||
}
|
||||
}
|
||||
|
||||
// validate ActualDisbursement Date Against Expected Disbursement Date
|
||||
LoanProduct loanProduct = loan.loanProduct();
|
||||
if (loanProduct.isSyncExpectedWithDisbursementDate()) {
|
||||
if (!loan.getExpectedDisbursedOnLocalDate().equals(actualDisbursementDate)) {
|
||||
throw new DateMismatchException(actualDisbursementDate, loan.getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
}
|
||||
|
||||
entityDatatableChecksWritePlatformService.runTheCheckForProduct(loan.getId(), EntityTables.LOAN.getName(),
|
||||
StatusEnum.DISBURSE.getValue(), EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
|
||||
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null);
|
||||
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
|
||||
CalendarEntityType.LOANS.getValue());
|
||||
if (loan.isSyncDisbursementWithMeeting()) {
|
||||
validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance,
|
||||
scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), scheduleGeneratorDTO.getNumberOfdays());
|
||||
}
|
||||
|
||||
// validate if disbursement date is a holiday or a non-working day
|
||||
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
|
||||
if (!holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()
|
||||
&& !WorkingDaysUtil.isWorkingDay(holidayDetailDTO.getWorkingDays(), loan.getDisbursementDate())) {
|
||||
final String errorMessage = "Expected disbursement date cannot be on a non working day";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage,
|
||||
loan.getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
if (!holidayDetailDTO.isAllowTransactionsOnHoliday()
|
||||
&& HolidayUtil.isHoliday(loan.getDisbursementDate(), holidayDetailDTO.getHolidays())) {
|
||||
final String errorMessage = "Expected disbursement date cannot be on a holiday";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage,
|
||||
loan.getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static @NotNull BigDecimal collectTotalCollateral(Set<LoanCollateralManagement> loanCollateralManagements) {
|
||||
BigDecimal totalCollateral = BigDecimal.ZERO;
|
||||
|
||||
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
|
||||
BigDecimal quantity = loanCollateralManagement.getQuantity();
|
||||
BigDecimal pctToBase = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getPctToBase();
|
||||
BigDecimal basePrice = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getBasePrice();
|
||||
totalCollateral = totalCollateral.add(quantity.multiply(basePrice).multiply(pctToBase).divide(BigDecimal.valueOf(100)));
|
||||
}
|
||||
return totalCollateral;
|
||||
}
|
||||
|
||||
private static @NotNull Set<String> getDisbursementParameters(boolean isAccountTransfer) {
|
||||
Set<String> disbursementParameters;
|
||||
|
||||
if (isAccountTransfer) {
|
||||
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
|
||||
@@ -109,38 +241,7 @@ public final class LoanTransactionValidator {
|
||||
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.fixedEmiAmountParameterName,
|
||||
LoanApiConstants.postDatedChecks, LoanApiConstants.disbursementNetDisbursalAmountParameterName));
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.disbursement");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
final LocalDate actualDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("actualDisbursementDate", element);
|
||||
baseDataValidator.reset().parameter("actualDisbursementDate").value(actualDisbursementDate).notNull();
|
||||
|
||||
final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
|
||||
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
|
||||
|
||||
final BigDecimal principal = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalDisbursedParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.principalDisbursedParameterName).value(principal).ignoreIfNull()
|
||||
.positiveAmount();
|
||||
|
||||
final BigDecimal netDisbursalAmount = this.fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.disbursementNetDisbursalAmountParameterName).value(netDisbursalAmount)
|
||||
.ignoreIfNull().positiveAmount();
|
||||
|
||||
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
|
||||
.notGreaterThanMax(principal);
|
||||
|
||||
validatePaymentDetails(baseDataValidator, element);
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
return disbursementParameters;
|
||||
}
|
||||
|
||||
public void validateDisbursementWithPostDatedChecks(final String json, final Long loanId) {
|
||||
|
||||
+134
-99
@@ -46,6 +46,7 @@ import org.apache.fineract.cob.service.LoanAccountLockService;
|
||||
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.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
@@ -135,7 +136,6 @@ import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateN
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement;
|
||||
import org.apache.fineract.portfolio.collateralmanagement.exception.LoanCollateralAmountNotSufficientException;
|
||||
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
|
||||
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
|
||||
import org.apache.fineract.portfolio.collectionsheet.command.SingleDisbursalCommand;
|
||||
@@ -164,6 +164,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
|
||||
@@ -194,6 +196,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionVa
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
|
||||
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
||||
import org.apache.fineract.portfolio.note.domain.Note;
|
||||
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
@@ -213,6 +216,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatformService {
|
||||
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanTransactionValidator loanTransactionValidator;
|
||||
private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
|
||||
@@ -251,7 +255,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler;
|
||||
private final PostDatedChecksRepository postDatedChecksRepository;
|
||||
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
|
||||
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
||||
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
|
||||
private final LoanAccountLockService loanAccountLockService;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||
@@ -294,28 +298,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@Override
|
||||
public CommandProcessingResult disburseLoan(final Long loanId, final JsonCommand command, Boolean isAccountTransfer,
|
||||
Boolean isWithoutAutoPayment) {
|
||||
loanTransactionValidator.validateDisbursement(command, isAccountTransfer, loanId);
|
||||
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
this.loanTransactionValidator.validateDisbursement(command.json(), isAccountTransfer);
|
||||
|
||||
if (command.parameterExists("postDatedChecks")) {
|
||||
// validate with post dated checks for the disbursement
|
||||
this.loanTransactionValidator.validateDisbursementWithPostDatedChecks(command.json(), loanId);
|
||||
}
|
||||
|
||||
Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
// Fail fast if client/group is not active or actual loan status disallows disbursal
|
||||
checkClientOrGroupActive(loan);
|
||||
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
|
||||
if (loan.isChargedOff() && DateUtils.isBefore(actualDisbursementDate, loan.getChargedOffOnDate())) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.transaction.date.cannot.be.earlier.than.charge.off.date", "Loan: "
|
||||
+ loanId
|
||||
+ " backdated transaction is not allowed. Transaction date cannot be earlier than the charge-off date of the loan",
|
||||
loanId);
|
||||
}
|
||||
Loan loan = loanAssembler.assembleFrom(loanId);
|
||||
|
||||
if (loan.loanProduct().isDisallowExpectedDisbursements()) {
|
||||
List<LoanDisbursementDetails> filteredList = loan.getDisbursementDetails().stream()
|
||||
@@ -323,8 +308,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
// Check whether a new LoanDisbursementDetails is required
|
||||
if (filteredList.isEmpty()) {
|
||||
// create artificial 'tranche/expected disbursal' as current disburse code expects it for
|
||||
// multi-disbursal
|
||||
// products
|
||||
// multi-disbursal products
|
||||
final LocalDate artificialExpectedDate = loan.getExpectedDisbursedOnLocalDate();
|
||||
LoanDisbursementDetails disbursementDetail = new LoanDisbursementDetails(artificialExpectedDate, null,
|
||||
loan.getDisbursedAmount(), null, false);
|
||||
@@ -332,61 +316,23 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
loan.getAllDisbursementDetails().add(disbursementDetail);
|
||||
}
|
||||
}
|
||||
loan.validateAccountStatus(LoanEvent.LOAN_DISBURSED);
|
||||
|
||||
// Get disbursedAmount
|
||||
final BigDecimal disbursedAmount = loan.getDisbursedAmount();
|
||||
final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
|
||||
|
||||
// Get relevant loan collateral modules
|
||||
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) {
|
||||
|
||||
BigDecimal totalCollateral = BigDecimal.valueOf(0);
|
||||
|
||||
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
|
||||
BigDecimal quantity = loanCollateralManagement.getQuantity();
|
||||
BigDecimal pctToBase = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getPctToBase();
|
||||
BigDecimal basePrice = loanCollateralManagement.getClientCollateralManagement().getCollaterals().getBasePrice();
|
||||
totalCollateral = totalCollateral.add(quantity.multiply(basePrice).multiply(pctToBase).divide(BigDecimal.valueOf(100)));
|
||||
}
|
||||
|
||||
// Validate the loan collateral value against the disbursedAmount
|
||||
if (disbursedAmount.compareTo(totalCollateral) > 0) {
|
||||
throw new LoanCollateralAmountNotSufficientException(disbursedAmount);
|
||||
}
|
||||
}
|
||||
|
||||
// validate ActualDisbursement Date Against Expected Disbursement Date
|
||||
LoanProduct loanProduct = loan.loanProduct();
|
||||
if (loanProduct.isSyncExpectedWithDisbursementDate()) {
|
||||
syncExpectedDateWithActualDisbursementDate(loan, actualDisbursementDate);
|
||||
}
|
||||
|
||||
final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling();
|
||||
final LocalDate rescheduledRepaymentDate = command.localDateValueOfParameterNamed("adjustRepaymentDate");
|
||||
|
||||
entityDatatableChecksWritePlatformService.runTheCheckForProduct(loanId, EntityTables.LOAN.getName(), StatusEnum.DISBURSE.getValue(),
|
||||
EntityTables.LOAN.getForeignKeyColumnNameOnDatatable(), loan.productId());
|
||||
|
||||
LocalDate recalculateFrom = null;
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
if (!loan.isMultiDisburmentLoan()) {
|
||||
loan.setActualDisbursementDate(actualDisbursementDate);
|
||||
}
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
|
||||
// validate actual disbursement date against meeting date
|
||||
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
|
||||
CalendarEntityType.LOANS.getValue());
|
||||
if (loan.isSyncDisbursementWithMeeting()) {
|
||||
this.loanTransactionValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance,
|
||||
scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), scheduleGeneratorDTO.getNumberOfdays());
|
||||
}
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null);
|
||||
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanDisbursalBusinessEvent(loan));
|
||||
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
final Map<String, Object> changes = new LinkedHashMap<>();
|
||||
|
||||
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
@@ -400,12 +346,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
// Recalculate first repayment date based in actual disbursement date.
|
||||
updateLoanCounters(loan, actualDisbursementDate);
|
||||
Money amountBeforeAdjust = loan.getPrincipal();
|
||||
boolean canDisburse = loan.canDisburse(actualDisbursementDate);
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
final Locale locale = command.extractLocale();
|
||||
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
|
||||
if (canDisburse) {
|
||||
|
||||
if (loan.canDisburse(actualDisbursementDate)) {
|
||||
// Get netDisbursalAmount from disbursal screen field.
|
||||
final BigDecimal netDisbursalAmount = command
|
||||
.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName);
|
||||
@@ -441,9 +385,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
amountToDisburse = disburseAmount.minus(loanOutstanding);
|
||||
|
||||
disburseLoanToLoan(loan, command, loanOutstanding);
|
||||
}
|
||||
|
||||
LoanTransaction disbursementTransaction = null;
|
||||
if (isAccountTransfer) {
|
||||
disburseLoanToSavings(loan, command, amountToDisburse, paymentDetail);
|
||||
@@ -463,17 +407,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
*/
|
||||
recalculateSchedule = true;
|
||||
}
|
||||
|
||||
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
|
||||
rescheduledRepaymentDate);
|
||||
boolean downPaymentEnabled = loan.repaymentScheduleDetail().isEnableDownPayment();
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || downPaymentEnabled) {
|
||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
if (isPaymentTypeApplicableForDisbursementCharge) {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
||||
} else {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
}
|
||||
ChangedTransactionDetail changedTransactionDetail = disburseLoan(command, isPaymentTypeApplicableForDisbursementCharge,
|
||||
paymentDetail, loan, currentUser, changes, scheduleGeneratorDTO);
|
||||
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
@@ -612,6 +554,69 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
private ChangedTransactionDetail disburseLoan(JsonCommand command, boolean isPaymentTypeApplicableForDisbursementCharge,
|
||||
PaymentDetail paymentDetail, Loan loan, AppUser currentUser, Map<String, Object> changes,
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
final PaymentDetail paymentDetail1 = isPaymentTypeApplicableForDisbursementCharge ? paymentDetail : null;
|
||||
final LocalDate actualDisbursementDate1 = command.localDateValueOfParameterNamed(Loan.ACTUAL_DISBURSEMENT_DATE);
|
||||
|
||||
loan.setDisbursedBy(currentUser);
|
||||
loan.updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
changes.put(Loan.LOCALE, command.locale());
|
||||
changes.put(Loan.DATE_FORMAT, command.dateFormat());
|
||||
changes.put(Loan.ACTUAL_DISBURSEMENT_DATE, command.stringValueOfParameterNamed(Loan.ACTUAL_DISBURSEMENT_DATE));
|
||||
|
||||
boolean disbursementMissedParam = loan.isDisbursementMissed();
|
||||
LocalDate firstInstallmentDueDate = loan.fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||
if ((loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
|
||||
&& (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) {
|
||||
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
|
||||
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
|
||||
loan.updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate1);
|
||||
loan.handleDisbursementTransaction(actualDisbursementDate1, paymentDetail1);
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
final Money interestApplied = Money.of(loan.getCurrency(), loan.getSummary().getTotalInterestCharged());
|
||||
|
||||
/*
|
||||
* Add an interest applied transaction of the interest is accrued upfront (Up front accrual), no accounting or
|
||||
* cash based accounting is selected
|
||||
*/
|
||||
if (((loan.isMultiDisburmentLoan() && loan.getDisbursedLoanDisbursementDetails().size() == 1) || !loan.isMultiDisburmentLoan())
|
||||
&& loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(loan.getOffice(), loan, interestApplied,
|
||||
actualDisbursementDate1, externalId);
|
||||
loan.addLoanTransaction(interestAppliedTransaction);
|
||||
}
|
||||
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory
|
||||
.determineProcessor(loan.getTransactionProcessingStrategyCode());
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loan.getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||
loan.getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(loan);
|
||||
}
|
||||
loan.getLoanTransactions().addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
||||
}
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
}
|
||||
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, loan);
|
||||
changes.put(Loan.PARAM_STATUS, LoanEnumerations.status(loan.getLoanStatus()));
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
private void updatePostDatedChecks(Set<PostDatedChecks> postDatedChecks) {
|
||||
this.postDatedChecksRepository.saveAll(postDatedChecks);
|
||||
}
|
||||
@@ -799,11 +804,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || downPaymentEnabled) {
|
||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
if (configurationDomainService.isPaymentTypeApplicableForDisbursementCharge()) {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
||||
} else {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
}
|
||||
changedTransactionDetail = disburseLoan(command, configurationDomainService.isPaymentTypeApplicableForDisbursementCharge(),
|
||||
paymentDetail, loan, currentUser, changes, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
|
||||
@@ -1132,7 +1134,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
|
||||
doPostLoanTransactionChecks(loan, newInterestPaymentWaiverTransaction.getTransactionDate(), defaultLoanLifecycleStateMachine);
|
||||
doPostLoanTransactionChecks(loan, newInterestPaymentWaiverTransaction.getTransactionDate(), loanLifecycleStateMachine);
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
@@ -1519,7 +1521,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
|
||||
defaultLoanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
|
||||
loanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
|
||||
scheduleGeneratorDTO, reversalTxnExternalId);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
@@ -1681,7 +1683,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
newTransaction = this.loanTransactionRepository.saveAndFlush(newTransaction);
|
||||
|
||||
loan.handleChargebackTransaction(newTransaction, defaultLoanLifecycleStateMachine);
|
||||
loan.handleChargebackTransaction(newTransaction, loanLifecycleStateMachine);
|
||||
|
||||
loan = saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
@@ -1765,8 +1767,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
|
||||
defaultLoanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, loanLifecycleStateMachine,
|
||||
existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
@@ -1863,7 +1865,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine, changes,
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, loanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
@@ -1937,8 +1939,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine, changes,
|
||||
existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.close(command, loanLifecycleStateMachine, changes, existingTransactionIds,
|
||||
existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
@@ -2039,7 +2041,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
changes.put("locale", command.locale());
|
||||
changes.put("dateFormat", command.dateFormat());
|
||||
|
||||
loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine, changes);
|
||||
loan.closeAsMarkedForReschedule(command, loanLifecycleStateMachine, changes);
|
||||
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
|
||||
@@ -2083,7 +2085,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
private void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount) {
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
||||
|
||||
@@ -2098,7 +2099,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
|
||||
private void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) {
|
||||
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
|
||||
|
||||
@@ -2118,7 +2118,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
LoanTransactionType.DISBURSEMENT.getValue(), null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null,
|
||||
txnExternalId, loan, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -2136,7 +2135,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
ExternalId externalId = externalIdFactory.create();
|
||||
final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate, externalId);
|
||||
loan.addLoanTransaction(newTransferTransaction);
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine;
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine;
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_INITIATE_TRANSFER, loan);
|
||||
|
||||
this.loanTransactionRepository.saveAndFlush(newTransferTransaction);
|
||||
@@ -2159,7 +2158,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate,
|
||||
externalId);
|
||||
loan.addLoanTransaction(newTransferAcceptanceTransaction);
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine;
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine;
|
||||
if (loan.getTotalOverpaid() != null) {
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, loan);
|
||||
} else {
|
||||
@@ -2192,7 +2191,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate,
|
||||
externalId);
|
||||
loan.addLoanTransaction(newTransferAcceptanceTransaction);
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine;
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine;
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAW_TRANSFER, loan);
|
||||
|
||||
this.loanTransactionRepository.saveAndFlush(newTransferAcceptanceTransaction);
|
||||
@@ -2209,7 +2208,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
public void rejectLoanTransfer(final Loan loan) {
|
||||
this.loanAssembler.setHelpers(loan);
|
||||
businessEventNotifierService.notifyPreBusinessEvent(new LoanRejectTransferBusinessEvent(loan));
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine;
|
||||
LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine;
|
||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECT_TRANSFER, loan);
|
||||
saveLoanWithDataIntegrityViolationChecks(loan);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanRejectTransferBusinessEvent(loan));
|
||||
@@ -2610,7 +2609,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(defaultLoanLifecycleStateMachine, existingTransactionIds,
|
||||
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(loanLifecycleStateMachine, existingTransactionIds,
|
||||
existingReversedTransactionIds, scheduleGeneratorDTO);
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
@@ -2885,8 +2884,44 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LocalDate rescheduledRepaymentDate) {
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName);
|
||||
loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount,
|
||||
nextPossibleRepaymentDate, rescheduledRepaymentDate);
|
||||
|
||||
boolean isEmiAmountChanged = false;
|
||||
LoanProduct loanProduct = loan.getLoanProduct();
|
||||
if ((loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) && emiAmount != null
|
||||
&& emiAmount.compareTo(loan.retriveLastEmiAmount()) != 0) {
|
||||
if (loanProduct.isMultiDisburseLoan()) {
|
||||
final LocalDate dateValue = null;
|
||||
final boolean isSpecificToInstallment = false;
|
||||
final Boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled = scheduleGeneratorDTO
|
||||
.isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled();
|
||||
LocalDate effectiveDateFrom = actualDisbursementDate;
|
||||
if (!isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled && actualDisbursementDate.equals(nextPossibleRepaymentDate)) {
|
||||
effectiveDateFrom = nextPossibleRepaymentDate.plusDays(1);
|
||||
}
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
||||
effectiveDateFrom, emiAmount, dateValue, isSpecificToInstallment, loan, LoanStatus.ACTIVE.getValue());
|
||||
loan.getLoanTermVariations().add(loanVariationTerms);
|
||||
} else {
|
||||
loan.setFixedEmiAmount(emiAmount);
|
||||
}
|
||||
isEmiAmountChanged = true;
|
||||
}
|
||||
if (rescheduledRepaymentDate != null && loanProduct.isMultiDisburseLoan()) {
|
||||
final boolean isSpecificToInstallment = false;
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.DUE_DATE.getValue(),
|
||||
nextPossibleRepaymentDate, emiAmount, rescheduledRepaymentDate, isSpecificToInstallment, loan,
|
||||
LoanStatus.ACTIVE.getValue());
|
||||
loan.getLoanTermVariations().add(loanVariationTerms);
|
||||
}
|
||||
|
||||
if (loan.isActualDisbursedOnDateEarlierOrLaterThanExpected(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged
|
||||
|| rescheduledRepaymentDate != null) {
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
} else {
|
||||
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
||||
}
|
||||
}
|
||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
|
||||
|
||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
|
||||
+14
-13
@@ -346,8 +346,8 @@ public class LoanAccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanWritePlatformService.class)
|
||||
public LoanWritePlatformService loanWritePlatformService(PlatformSecurityContext context,
|
||||
LoanTransactionValidator loanTransactionValidator,
|
||||
public LoanWritePlatformService loanWritePlatformService(LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory,
|
||||
PlatformSecurityContext context, LoanTransactionValidator loanTransactionValidator,
|
||||
LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
LoanAccountDomainService loanAccountDomainService, NoteRepository noteRepository,
|
||||
LoanTransactionRepository loanTransactionRepository, LoanTransactionRelationRepository loanTransactionRelationRepository,
|
||||
@@ -373,17 +373,18 @@ public class LoanAccountConfiguration {
|
||||
LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, ErrorHandler errorHandler,
|
||||
LoanDownPaymentHandlerService loanDownPaymentHandlerService, AccountTransferRepository accountTransferRepository,
|
||||
LoanTransactionAssembler loanTransactionAssembler, LoanAccrualsProcessingService loanAccrualsProcessingService) {
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
|
||||
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
|
||||
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
|
||||
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
|
||||
accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService,
|
||||
loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService,
|
||||
loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService,
|
||||
guarantorDomainService, loanUtilService, loanSummaryWrapper, entityDatatableChecksWritePlatformService,
|
||||
transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository,
|
||||
repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository,
|
||||
defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
|
||||
return new LoanWritePlatformServiceJpaRepositoryImpl(transactionProcessorFactory, context, loanTransactionValidator,
|
||||
loanUpdateCommandFromApiJsonDeserializer, loanRepositoryWrapper, loanAccountDomainService, noteRepository,
|
||||
loanTransactionRepository, loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService,
|
||||
calendarInstanceRepository, paymentDetailWritePlatformService, holidayRepository, configurationDomainService,
|
||||
workingDaysRepository, accountTransfersWritePlatformService, accountTransfersReadPlatformService,
|
||||
accountAssociationsReadPlatformService, loanReadPlatformService, fromApiJsonHelper, calendarRepository,
|
||||
loanScheduleHistoryWritePlatformService, loanApplicationValidator, accountAssociationRepository,
|
||||
accountTransferDetailRepository, businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper,
|
||||
entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository,
|
||||
cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler,
|
||||
postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine,
|
||||
loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
|
||||
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, accountTransferRepository,
|
||||
loanTransactionAssembler, loanAccrualsProcessingService);
|
||||
}
|
||||
|
||||
+4
-4
@@ -95,8 +95,8 @@ public class CheckLoanRepaymentDueBusinessStepTest {
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(null);
|
||||
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanForProcessing.getCurrency()).thenReturn(currency);
|
||||
when(repaymentInstallment.getTotalOutstanding(currency)).thenReturn(money);
|
||||
when(money.isGreaterThanZero()).thenReturn(true);
|
||||
@@ -153,8 +153,8 @@ public class CheckLoanRepaymentDueBusinessStepTest {
|
||||
// Loan Product setting overrides global settings
|
||||
when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(1);
|
||||
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanForProcessing.getCurrency()).thenReturn(currency);
|
||||
when(repaymentInstallment.getTotalOutstanding(currency)).thenReturn(money);
|
||||
when(money.isGreaterThanZero()).thenReturn(true);
|
||||
|
||||
+12
-12
@@ -111,8 +111,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
|
||||
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanForProcessing.getCurrency()).thenReturn(currency);
|
||||
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
|
||||
|
||||
@@ -138,8 +138,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
|
||||
loanInstallmentRepaymentDueDateBefore5Days, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
|
||||
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0)));
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
|
||||
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
|
||||
// when
|
||||
@@ -166,8 +166,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallmentPaidOff);
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
|
||||
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
|
||||
|
||||
@@ -196,8 +196,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
|
||||
when(loanForProcessing.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
||||
// product configuration overrides global configuration
|
||||
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(1);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.valueOf(100));
|
||||
when(loanForProcessing.getCurrency()).thenReturn(currency);
|
||||
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
|
||||
|
||||
@@ -216,8 +216,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
|
||||
Loan loanForProcessing = Mockito.mock(Loan.class);
|
||||
LoanSummary loanSummary = Mockito.mock(LoanSummary.class);
|
||||
when(loanForProcessing.getStatus()).thenReturn(LoanStatus.ACTIVE);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.ZERO);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.ZERO);
|
||||
// when
|
||||
Loan processedLoan = underTest.execute(loanForProcessing);
|
||||
// then - No Business Event raised
|
||||
@@ -240,8 +240,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
|
||||
BigDecimal.valueOf(1.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0)));
|
||||
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
|
||||
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(1);
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getLoanSummary().getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary().getTotalOutstanding()).thenReturn(BigDecimal.ONE);
|
||||
when(loanForProcessing.getCurrency()).thenReturn(currency);
|
||||
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
|
||||
// when
|
||||
|
||||
+2
-2
@@ -117,7 +117,7 @@ public class LoanRepaymentBusinessEventSerializerTest {
|
||||
when(loanForProcessing.getId()).thenReturn(1L);
|
||||
when(loanForProcessing.getAccountNumber()).thenReturn("0001");
|
||||
when(loanForProcessing.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0));
|
||||
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
|
||||
when(loanCurrency.getCode()).thenReturn("CODE");
|
||||
@@ -168,7 +168,7 @@ public class LoanRepaymentBusinessEventSerializerTest {
|
||||
when(loanForProcessing.getId()).thenReturn(null);
|
||||
when(loanForProcessing.getAccountNumber()).thenReturn("0001");
|
||||
when(loanForProcessing.getExternalId()).thenReturn(ExternalIdFactory.produce("externalId"));
|
||||
when(loanForProcessing.getLoanSummary()).thenReturn(loanSummary);
|
||||
when(loanForProcessing.getSummary()).thenReturn(loanSummary);
|
||||
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0));
|
||||
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
|
||||
when(loanCurrency.getCode()).thenReturn("CODE");
|
||||
|
||||
+2
-2
@@ -143,7 +143,7 @@ class DefaultLoanLifecycleStateMachineTest {
|
||||
Mockito.when(loan.getPlainStatus()).thenReturn(LoanStatus.OVERPAID.getValue());
|
||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.OVERPAID);
|
||||
Mockito.when(loan.getTotalOverpaidAsMoney()).thenReturn(zero);
|
||||
Mockito.when(loan.getLoanSummary()).thenReturn(loanSummary);
|
||||
Mockito.when(loan.getSummary()).thenReturn(loanSummary);
|
||||
Mockito.when(loanSummary.getTotalOutstanding(eq(currency))).thenReturn(one);
|
||||
// when
|
||||
underTest.transition(LoanEvent.LOAN_DISBURSED, loan);
|
||||
@@ -163,7 +163,7 @@ class DefaultLoanLifecycleStateMachineTest {
|
||||
Mockito.when(loan.getPlainStatus()).thenReturn(LoanStatus.OVERPAID.getValue());
|
||||
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.OVERPAID);
|
||||
Mockito.when(loan.getTotalOverpaidAsMoney()).thenReturn(zero);
|
||||
Mockito.when(loan.getLoanSummary()).thenReturn(loanSummary);
|
||||
Mockito.when(loan.getSummary()).thenReturn(loanSummary);
|
||||
Mockito.when(loanSummary.getTotalOutstanding(currency)).thenReturn(zero);
|
||||
// when
|
||||
underTest.transition(LoanEvent.LOAN_DISBURSED, loan);
|
||||
|
||||
+10
-11
@@ -130,22 +130,21 @@ public class LoanRepaymentRescheduleAtDisbursementTest {
|
||||
this.loanTransactionHelper.disburseLoanWithRepaymentReschedule(disbursementDate, loanID, adjustRepaymentDate);
|
||||
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
|
||||
|
||||
ArrayList<HashMap> loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec,
|
||||
ArrayList<HashMap> loanRepaymentSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec,
|
||||
loanID);
|
||||
HashMap firstInstallement = loanRepaymnetSchedule.get(1);
|
||||
Map<String, Object> expectedvalues = new HashMap<>(3);
|
||||
HashMap firstInstallment = loanRepaymentSchedule.get(1);
|
||||
Map<String, Object> expectedValues = new HashMap<>(3);
|
||||
Calendar date = Calendar.getInstance(Utils.getTimeZoneOfTenant());
|
||||
date.set(2015, Calendar.MARCH, 16);
|
||||
expectedvalues.put("dueDate", getDateAsArray(date, 0));
|
||||
expectedvalues.put("principalDue", "834.71");
|
||||
expectedvalues.put("interestDue", "49.32");
|
||||
expectedvalues.put("feeChargesDue", "0");
|
||||
expectedvalues.put("penaltyChargesDue", "0");
|
||||
expectedvalues.put("totalDueForPeriod", "884.03");
|
||||
expectedValues.put("dueDate", getDateAsArray(date, 0));
|
||||
expectedValues.put("principalDue", "834.71");
|
||||
expectedValues.put("interestDue", "49.32");
|
||||
expectedValues.put("feeChargesDue", "0");
|
||||
expectedValues.put("penaltyChargesDue", "0");
|
||||
expectedValues.put("totalDueForPeriod", "884.03");
|
||||
|
||||
// VALIDATE REPAYMENT SCHEDULE
|
||||
verifyLoanRepaymentSchedule(firstInstallement, expectedvalues);
|
||||
|
||||
verifyLoanRepaymentSchedule(firstInstallment, expectedValues);
|
||||
}
|
||||
|
||||
private void addCollaterals(List<HashMap> collaterals, Integer collateralId, BigDecimal quantity) {
|
||||
|
||||
Reference in New Issue
Block a user