From 64dddfca4895a42d5c62901561ce9edfa4e94035 Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Mon, 7 Apr 2025 23:57:03 +0300 Subject: [PATCH] FINERACT-2080: Extract loanLifecycleStateMachine from Loan entity, refactor transition functionality --- .../DefaultLoanLifecycleStateMachine.java | 117 ++++++++++++- .../portfolio/loanaccount/domain/Loan.java | 155 +----------------- .../domain/LoanLifecycleStateMachine.java | 18 ++ .../loanaccount/service/LoanAssembler.java | 2 - .../service/LoanChargeService.java | 49 +++++- .../LoanDownPaymentHandlerService.java | 4 +- .../LoanDownPaymentHandlerServiceImpl.java | 13 +- .../service/LoanRefundService.java | 30 +--- ...ountTransfersWritePlatformServiceImpl.java | 38 ++--- .../domain/LoanAccountDomainServiceJpa.java | 36 ++-- .../service/LoanScheduleAssembler.java | 4 +- ...cheduleCalculationPlatformServiceImpl.java | 10 +- ...heduleRequestWritePlatformServiceImpl.java | 18 +- .../LoanApplicationValidator.java | 17 +- ...WritePlatformServiceJpaRepositoryImpl.java | 65 +++++--- .../service/LoanAssemblerImpl.java | 23 +-- .../LoanChargeWritePlatformServiceImpl.java | 22 +-- .../service/LoanDisbursementService.java | 3 +- ...WritePlatformServiceJpaRepositoryImpl.java | 150 ++++------------- .../adjustment/LoanAdjustmentServiceImpl.java | 16 +- .../starter/LoanAccountConfiguration.java | 66 ++++---- .../LoanAdjustmentServiceImplTest.java | 8 +- ...oanChargeWritePlatformServiceImplTest.java | 43 ++++- ...LoanDownPaymentHandlerServiceImplTest.java | 6 +- 24 files changed, 419 insertions(+), 494 deletions(-) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java index 76659ad30..bccbe3f43 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java @@ -19,21 +19,21 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.math.BigDecimal; +import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; // TODO: introduce tests for the state machine +@Slf4j @Component @RequiredArgsConstructor public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachine { - private static Logger LOG = LoggerFactory.getLogger(DefaultLoanLifecycleStateMachine.class); - private static final List ALLOWED_LOAN_STATUSES = List.of(LoanStatus.values()); private final BusinessEventNotifierService businessEventNotifierService; @@ -46,7 +46,29 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi @Override public void transition(final LoanEvent loanEvent, final Loan loan) { loan.updateLoanSummaryDerivedFields(); + internalTransition(loanEvent, loan); + } + @Override + public void determineAndTransition(final Loan loan, final LocalDate transactionDate) { + if (loan.getStatus() == null) { + return; + } + + loan.updateLoanSummaryDerivedFields(); + + final LoanStatusTransition transition = determineTransition(loan, loan.getStatus(), transactionDate); + + if (MathUtil.isEmpty(loan.getTotalOverpaid())) { + loan.setOverpaidOnDate(null); + } + + if (transition.transitionNeeded()) { + internalTransition(transition.event(), loan); + } + } + + private void internalTransition(final LoanEvent loanEvent, final Loan loan) { LoanStatus oldStatus = loan.getStatus(); LoanStatus newStatus = getNextStatus(loanEvent, loan); if (newStatus != null) { @@ -58,7 +80,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi } // set mandatory field states based on new status after the transition - LOG.debug("Transitioning loan {} status from {} to {}", loan.getId(), oldStatus, newStatus); + log.debug("Transitioning loan {} status from {} to {}", loan.getId(), oldStatus, newStatus); switch (newStatus) { case SUBMITTED_AND_PENDING_APPROVAL -> { loan.setApprovedOnDate(null); @@ -276,4 +298,89 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi return allowed; } + + private LoanStatusTransition determineTransition(final Loan loan, final LoanStatus currentStatus, final LocalDate transactionDate) { + final boolean hasOutstanding = loan.getSummary().getTotalOutstanding(loan.getCurrency()).isGreaterThanZero(); + final boolean isRepaidInFull = loan.getSummary().isRepaidInFull(loan.getCurrency()); + final boolean isOverpaid = MathUtil.isGreaterThanZero(loan.getTotalOverpaid()); + final boolean isAllChargesPaid = loan.getLoanCharges().stream().allMatch( + charge -> !charge.isActive() || charge.amount().compareTo(BigDecimal.ZERO) <= 0 || charge.isPaid() || charge.isWaived()); + + if (currentStatus.isOverpaid()) { + return determineTransitionFromOverpaid(loan, isOverpaid, isRepaidInFull, isAllChargesPaid, hasOutstanding, transactionDate); + } else if (currentStatus.isClosedObligationsMet()) { + return determineTransitionFromClosedObligationsMet(loan, isOverpaid, hasOutstanding, transactionDate); + } else if (currentStatus.isActive()) { + return determineTransitionFromActive(loan, isOverpaid, isRepaidInFull, isAllChargesPaid, transactionDate); + } else if (currentStatus.isClosedWrittenOff() || currentStatus.isClosedWithOutsandingAmountMarkedForReschedule()) { + return determineTransitionFromClosedWrittenOffOrRescheduled(loan, hasOutstanding); + } + + return LoanStatusTransition.noTransition(currentStatus); + } + + private LoanStatusTransition determineTransitionFromOverpaid(final Loan loan, final boolean isOverpaid, final boolean isRepaidInFull, + final boolean isAllChargesPaid, final boolean hasOutstanding, final LocalDate transactionDate) { + if (!isOverpaid) { + if (isRepaidInFull && isAllChargesPaid) { + loan.setClosedOnDate(transactionDate); + loan.setActualMaturityDate(transactionDate); + return LoanStatusTransition.to(LoanStatus.CLOSED_OBLIGATIONS_MET, LoanEvent.LOAN_CREDIT_BALANCE_REFUND); + } else if (hasOutstanding) { + loan.handleMaturityDateActivate(); + return LoanStatusTransition.to(LoanStatus.ACTIVE, LoanEvent.LOAN_REPAYMENT_OR_WAIVER); + } + } + return LoanStatusTransition.noTransition(LoanStatus.OVERPAID); + } + + private LoanStatusTransition determineTransitionFromClosedObligationsMet(final Loan loan, final boolean isOverpaid, + final boolean hasOutstanding, LocalDate transactionDate) { + if (isOverpaid) { + loan.setOverpaidOnDate(transactionDate); + loan.setClosedOnDate(null); + loan.setActualMaturityDate(null); + return LoanStatusTransition.to(LoanStatus.OVERPAID, LoanEvent.LOAN_OVERPAYMENT); + } else if (hasOutstanding) { + loan.setClosedOnDate(null); + loan.setActualMaturityDate(null); + loan.handleMaturityDateActivate(); + return LoanStatusTransition.to(LoanStatus.ACTIVE, LoanEvent.LOAN_REPAYMENT_OR_WAIVER); + } + return LoanStatusTransition.noTransition(LoanStatus.CLOSED_OBLIGATIONS_MET); + } + + private LoanStatusTransition determineTransitionFromActive(final Loan loan, final boolean isOverpaid, final boolean isRepaidInFull, + final boolean isAllChargesPaid, final LocalDate transactionDate) { + if (isOverpaid) { + loan.setOverpaidOnDate(transactionDate); + loan.setActualMaturityDate(null); + return LoanStatusTransition.to(LoanStatus.OVERPAID, LoanEvent.LOAN_OVERPAYMENT); + } else if (isRepaidInFull && isAllChargesPaid) { + loan.setClosedOnDate(transactionDate); + loan.setActualMaturityDate(transactionDate); + return LoanStatusTransition.to(LoanStatus.CLOSED_OBLIGATIONS_MET, LoanEvent.REPAID_IN_FULL); + } + return LoanStatusTransition.noTransition(LoanStatus.ACTIVE); + } + + private LoanStatusTransition determineTransitionFromClosedWrittenOffOrRescheduled(final Loan loan, final boolean hasOutstanding) { + if (hasOutstanding) { + return LoanStatusTransition.to(LoanStatus.ACTIVE, LoanEvent.LOAN_ADJUST_TRANSACTION); + } + return LoanStatusTransition.noTransition(loan.getStatus()); + } + + private record LoanStatusTransition(LoanStatus targetStatus, LoanEvent event, boolean transitionNeeded) { + + public static LoanStatusTransition to(final LoanStatus targetStatus, final LoanEvent event) { + return new LoanStatusTransition(targetStatus, event, true); + } + + public static LoanStatusTransition noTransition(final LoanStatus currentStatus) { + return new LoanStatusTransition(currentStatus, null, false); + } + + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 41704d4dc..5f970c3b8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -45,10 +45,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -82,7 +80,6 @@ import org.apache.fineract.portfolio.collateral.domain.LoanCollateral; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.group.domain.Group; -import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; @@ -90,7 +87,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransact import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; -import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.useradministration.domain.AppUser; @@ -298,6 +294,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "loan_product_counter") private Integer loanProductCounter; + @Setter @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY) private Set charges = new HashSet<>(); @@ -321,15 +318,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY) private List loanTransactions = new ArrayList<>(); + @Setter @Embedded private LoanSummary summary; @Transient private boolean accountNumberRequiresAutoGeneration; - @Transient - private LoanLifecycleStateMachine loanLifecycleStateMachine; - @Setter() @Column(name = "principal_amount_proposed", scale = 6, precision = 19, nullable = false) private BigDecimal proposedPrincipal; @@ -638,40 +633,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return collateral; } - public void addLoanCharge(final LoanCharge loanCharge) { - loanCharge.update(this); - - final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge); - BigDecimal chargeAmt; - BigDecimal totalChargeAmt = BigDecimal.ZERO; - if (loanCharge.getChargeCalculation().isPercentageBased()) { - chargeAmt = loanCharge.getPercentage(); - if (loanCharge.isInstalmentFee()) { - totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge); - } else if (loanCharge.isOverdueInstallmentCharge()) { - totalChargeAmt = loanCharge.amountOutstanding(); - } - } else { - chargeAmt = loanCharge.amountOrPercentage(); - } - loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt); - - // NOTE: must add new loan charge to set of loan charges before - // reprocessing the repayment schedule. - if (this.charges == null) { - this.charges = new HashSet<>(); - } - this.charges.add(loanCharge); - this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); - - // store Id's of existing loan transactions and existing reversed loan transactions - final SingleLoanChargeRepaymentScheduleProcessingWrapper wrapper = new SingleLoanChargeRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), getRepaymentScheduleInstallments(), loanCharge); - updateLoanSummaryDerivedFields(); - - loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_ADDED, this); - } - /** * Creates a loanTransaction for "Apply Charge Event" with transaction date set to "suppliedTransactionDate". The * newly created transaction is also added to the Loan on which this method is called. @@ -771,9 +732,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } public LoanDisbursementDetails addLoanDisbursementDetails(final LocalDate expectedDisbursementDate, final BigDecimal principal) { - final LocalDate actualDisbursementDate = null; - final LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, - principal, this.netDisbursalAmount, false); + final LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, null, principal, + this.netDisbursalAmount, false); disbursementDetails.updateLoan(this); this.disbursementDetails.add(disbursementDetails); return disbursementDetails; @@ -992,11 +952,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } } - public void updateLoanSummaryAndStatus() { - updateLoanSummaryDerivedFields(); - doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine); - } - private boolean isInterestRecalculationEnabledForProduct() { return this.loanProduct.isInterestRecalculationEnabled(); } @@ -1111,35 +1066,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return maturityDate; } - public Map undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) { - final Map actualChanges = new LinkedHashMap<>(); - - final LoanStatus currentStatus = getStatus(); - final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVAL_UNDO, this); - if (!statusEnum.hasStateOf(currentStatus)) { - loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, this); - actualChanges.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); - - this.approvedOnDate = null; - this.approvedBy = null; - - if (this.approvedPrincipal.compareTo(this.proposedPrincipal) != 0) { - this.approvedPrincipal = this.proposedPrincipal; - this.loanRepaymentScheduleDetail.setPrincipal(this.proposedPrincipal); - - actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, this.proposedPrincipal); - actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, this.proposedPrincipal); - - } - - actualChanges.put(APPROVED_ON_DATE, ""); - - this.loanOfficerHistory.clear(); - } - - return actualChanges; - } - public List findExistingTransactionIds() { return getLoanTransactions().stream() // .filter(loanTransaction -> loanTransaction.getId() != null) // @@ -1161,18 +1087,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { .collect(Collectors.toList()); } - public boolean canDisburse() { - final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, this); - - boolean isMultiTrancheDisburse = false; - LoanStatus actualLoanStatus = getStatus(); - if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) - && isAllTranchesNotDisbursed()) { - isMultiTrancheDisburse = true; - } - return !statusEnum.hasStateOf(actualLoanStatus) || isMultiTrancheDisburse; - } - public Collection fetchUndisbursedDetail() { Collection disbursementDetails = new ArrayList<>(); LocalDate date = null; @@ -1304,60 +1218,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { .sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList()); } - public boolean doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - boolean statusChanged = checkAndHandleLoanStatus(transactionDate, loanLifecycleStateMachine); - resetOverpaidDateIfNeeded(); - return statusChanged; - } - - private boolean checkAndHandleLoanStatus(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - boolean statusChanged = false; - boolean isOverpaid = MathUtil.isGreaterThanZero(totalOverpaid); - - if (isOverpaid) { - handleLoanOverpayment(transactionDate, loanLifecycleStateMachine); - statusChanged = true; - } else if (this.summary.isRepaidInFull(getCurrency())) { - handleLoanRepaymentInFull(transactionDate, loanLifecycleStateMachine); - statusChanged = true; - } else { - loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, this); - } - - return statusChanged; - } - - private void resetOverpaidDateIfNeeded() { - if (MathUtil.isEmpty(totalOverpaid)) { - this.overpaidOnDate = null; - } - } - - private void handleLoanRepaymentInFull(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - boolean isAllChargesPaid = this.charges.stream() // - .allMatch(charge -> !charge.isActive() || charge.amount().compareTo(BigDecimal.ZERO) <= 0 || charge.isPaid() - || charge.isWaived()); - - if (isAllChargesPaid) { - this.closedOnDate = transactionDate; - this.actualMaturityDate = transactionDate; - loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, this); - - } else if (getStatus().isOverpaid()) { - if (MathUtil.isEmpty(totalOverpaid)) { - this.overpaidOnDate = null; - } - loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, this); - } - } - - private void handleLoanOverpayment(LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - this.overpaidOnDate = transactionDate; - loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, this); - this.closedOnDate = null; - this.actualMaturityDate = null; - } - public boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction) { boolean isChronologicallyLatestRepaymentOrWaiver = true; @@ -1852,10 +1712,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return receivableInterest; } - public void setHelpers(final LoanLifecycleStateMachine loanLifecycleStateMachine) { - this.loanLifecycleStateMachine = loanLifecycleStateMachine; - } - public boolean isSyncDisbursementWithMeeting() { return this.syncDisbursementWithMeeting != null && this.syncDisbursementWithMeeting; } @@ -2486,10 +2342,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { Money[] receivables = retrieveIncomeOutstandingTillDate(closureDate); Money totalPrincipal = Money.of(getCurrency(), this.getSummary().getTotalPrincipalOutstanding()); totalPrincipal = totalPrincipal.minus(receivables[3]); - final Set compoundingDetails = null; final LocalDate currentDate = DateUtils.getBusinessLocalDate(); return new LoanRepaymentScheduleInstallment(null, 0, currentDate, currentDate, totalPrincipal.getAmount(), - receivables[0].getAmount(), receivables[1].getAmount(), receivables[2].getAmount(), false, compoundingDetails); + receivables[0].getAmount(), receivables[1].getAmount(), receivables[2].getAmount(), false, null); } private Money[] retrieveIncomeOutstandingTillDate(final LocalDate paymentDate) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanLifecycleStateMachine.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanLifecycleStateMachine.java index e4fc99d64..e697aa2b2 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanLifecycleStateMachine.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanLifecycleStateMachine.java @@ -18,9 +18,27 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; +import java.time.LocalDate; + public interface LoanLifecycleStateMachine { LoanStatus dryTransition(LoanEvent loanEvent, Loan loan); void transition(LoanEvent loanEvent, Loan loan); + + /** + * Determines the appropriate loan status based on the loan's current financial condition. This method is designed + * to be called at the end of processing to ensure accurate status transitions. + * + * For example, it can determine whether a loan that was OVERPAID should transition to: - CLOSED_OBLIGATIONS_MET (if + * overpayment amount is now zero) - ACTIVE (if there is still outstanding balance) - Remain as OVERPAID (if still + * overpaid) + * + * @param loan + * The loan whose status should be evaluated + * @param transactionDate + * The date of the transaction that may have affected the loan status + */ + void determineAndTransition(Loan loan, LocalDate transactionDate); + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java index 59f8dc92b..a665b9eb5 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java @@ -39,8 +39,6 @@ public interface LoanAssembler { Loan assembleFrom(JsonCommand command); - void setHelpers(Loan loanAccount); - void accountNumberGeneration(JsonCommand command, Loan loan); CodeValue findCodeValueByIdIfProvided(Long codeValueId); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java index 34a1613af..1383987e7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java @@ -32,6 +32,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachin import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; @@ -41,6 +42,7 @@ public class LoanChargeService { private final LoanChargeValidator loanChargeValidator; private final LoanTransactionProcessingService loanTransactionProcessingService; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; public void recalculateAllCharges(final Loan loan) { Set charges = loan.getActiveCharges(); @@ -76,9 +78,8 @@ public class LoanChargeService { } } - public void makeChargePayment(final Loan loan, final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine, - final List existingTransactionIds, final List existingReversedTransactionIds, - final LoanTransaction paymentTransaction, final Integer installmentNumber) { + public void makeChargePayment(final Loan loan, final Long chargeId, final List existingTransactionIds, + final List existingReversedTransactionIds, final LoanTransaction paymentTransaction, final Integer installmentNumber) { loanChargeValidator.validateChargePaymentNotInFuture(paymentTransaction); existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); @@ -88,11 +89,11 @@ public class LoanChargeService { charge = loanCharge; } } - handleChargePaidTransaction(loan, charge, paymentTransaction, loanLifecycleStateMachine, installmentNumber); + handleChargePaidTransaction(loan, charge, paymentTransaction, installmentNumber); } public void handleChargePaidTransaction(final Loan loan, final LoanCharge charge, final LoanTransaction chargesPayment, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final Integer installmentNumber) { + final Integer installmentNumber) { chargesPayment.updateLoan(loan); final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, chargesPayment.getAmount(loan.getCurrency()).getAmount(), installmentNumber); @@ -118,7 +119,41 @@ public class LoanChargeService { new TransactionCtx(loan.getCurrency(), chargePaymentInstallments, loanCharges, new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); - loan.updateLoanSummaryDerivedFields(); - loan.doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, chargesPayment.getTransactionDate()); } + + public void addLoanCharge(final Loan loan, final LoanCharge loanCharge) { + loanCharge.update(loan); + + final BigDecimal amount = loan.calculateAmountPercentageAppliedTo(loanCharge); + BigDecimal chargeAmt; + BigDecimal totalChargeAmt = BigDecimal.ZERO; + if (loanCharge.getChargeCalculation().isPercentageBased()) { + chargeAmt = loanCharge.getPercentage(); + if (loanCharge.isInstalmentFee()) { + totalChargeAmt = loan.calculatePerInstallmentChargeAmount(loanCharge); + } else if (loanCharge.isOverdueInstallmentCharge()) { + totalChargeAmt = loanCharge.amountOutstanding(); + } + } else { + chargeAmt = loanCharge.amountOrPercentage(); + } + loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt); + + // NOTE: must add new loan charge to set of loan charges before + // reprocessing the repayment schedule. + if (loan.getLoanCharges() == null) { + loan.setCharges(new HashSet<>()); + } + loan.getLoanCharges().add(loanCharge); + loan.setSummary(loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement())); + + // store Id's of existing loan transactions and existing reversed loan transactions + final SingleLoanChargeRepaymentScheduleProcessingWrapper wrapper = new SingleLoanChargeRepaymentScheduleProcessingWrapper(); + wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loanCharge); + loan.updateLoanSummaryDerivedFields(); + + loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_ADDED, loan); + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java index e2e7b38e2..f57b2c5ee 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java @@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.service; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; public interface LoanDownPaymentHandlerService { @@ -30,6 +29,5 @@ public interface LoanDownPaymentHandlerService { LoanTransaction disbursementTransaction, Loan loan); void handleRepaymentOrRecoveryOrWaiverTransaction(Loan loan, LoanTransaction newTransactionDetail, - LoanLifecycleStateMachine loanLifecycleStateMachine, LoanTransaction transactionForAdjustment, - ScheduleGeneratorDTO scheduleGeneratorDTO); + LoanTransaction transactionForAdjustment, ScheduleGeneratorDTO scheduleGeneratorDTO); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java index ccff84527..8b984321b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java @@ -61,6 +61,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler private final LoanRefundValidator loanRefundValidator; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanTransactionProcessingService loanTransactionProcessingService; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; @Override public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command, @@ -77,8 +78,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler @Override public void handleRepaymentOrRecoveryOrWaiverTransaction(final Loan loan, final LoanTransaction loanTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction, - final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final LoanTransaction adjustedTransaction, final ScheduleGeneratorDTO scheduleGeneratorDTO) { if (loanTransaction.isRecoveryRepayment()) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_RECOVERY_PAYMENT, loan); } @@ -158,13 +158,13 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler reprocessLoanTransactionsService.reprocessTransactions(loan); } - loan.updateLoanSummaryDerivedFields(); - /** * FIXME: Vishwas, skipping post loan transaction checks for Loan recoveries **/ if (loanTransaction.isNotRecoveryRepayment()) { - loan.doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate()); + } else { + loan.updateLoanSummaryDerivedFields(); } if (loan.getLoanProduct().isMultiDisburseLoan()) { @@ -223,8 +223,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler loanDownPaymentTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); - handleRepaymentOrRecoveryOrWaiverTransaction(loan, downPaymentTransaction, loan.getLoanLifecycleStateMachine(), null, - scheduleGeneratorDTO); + handleRepaymentOrRecoveryOrWaiverTransaction(loan, downPaymentTransaction, null, scheduleGeneratorDTO); return downPaymentTransaction; } else { return null; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java index 0cfb9bb38..66b10bf24 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanRefundService.java @@ -21,7 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.time.LocalDate; import java.util.List; import lombok.RequiredArgsConstructor; -import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; @@ -35,9 +34,9 @@ public class LoanRefundService { private final LoanRefundValidator loanRefundValidator; private final LoanTransactionProcessingService loadTransactionProcessingService; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; - public void makeRefund(final Loan loan, final LoanTransaction loanTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, + public void makeRefund(final Loan loan, final LoanTransaction loanTransaction, final List existingTransactionIds, final List existingReversedTransactionIds) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); @@ -49,8 +48,7 @@ public class LoanRefundService { if (loanTransaction.isNotZero()) { loan.addLoanTransaction(loanTransaction); } - loan.updateLoanSummaryDerivedFields(); - loan.doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate()); } public LocalDate extractTransactionDate(final Loan loan, final LoanTransaction loanTransaction) { @@ -59,18 +57,16 @@ public class LoanRefundService { return loanTransactionDate; } - public void makeRefundForActiveLoan(final Loan loan, final LoanTransaction loanTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, + public void makeRefundForActiveLoan(final Loan loan, final LoanTransaction loanTransaction, final List existingTransactionIds, final List existingReversedTransactionIds) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - handleRefundTransaction(loan, loanTransaction, loanLifecycleStateMachine); + handleRefundTransaction(loan, loanTransaction); } public void creditBalanceRefund(final Loan loan, final LoanTransaction newCreditBalanceRefundTransaction, - final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, final List existingTransactionIds, - final List existingReversedTransactionIds) { + final List existingTransactionIds, final List existingReversedTransactionIds) { loanRefundValidator.validateCreditBalanceRefund(loan, newCreditBalanceRefundTransaction); existingTransactionIds.addAll(loan.findExistingTransactionIds()); @@ -78,17 +74,10 @@ public class LoanRefundService { loan.getLoanTransactions().add(newCreditBalanceRefundTransaction); - loan.updateLoanSummaryDerivedFields(); - - if (MathUtil.isEmpty(loan.getTotalOverpaid())) { - loan.setOverpaidOnDate(null); - loan.setClosedOnDate(newCreditBalanceRefundTransaction.getTransactionDate()); - defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_CREDIT_BALANCE_REFUND, loan); - } + loanLifecycleStateMachine.determineAndTransition(loan, newCreditBalanceRefundTransaction.getTransactionDate()); } - private void handleRefundTransaction(final Loan loan, final LoanTransaction loanTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine) { + private void handleRefundTransaction(final Loan loan, final LoanTransaction loanTransaction) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_REFUND, loan); loanTransaction.updateLoan(loan); @@ -110,7 +99,6 @@ public class LoanRefundService { new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); - loan.updateLoanSummaryDerivedFields(); - loan.doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index 6ce295331..7eea9969e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -211,12 +211,12 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer @Override @Transactional public void reverseTransfersWithFromAccountType(final Long accountNumber, final PortfolioAccountType accountTypeId) { - List acccountTransfers = null; + List accountTransfers = null; if (accountTypeId.isLoanAccount()) { - acccountTransfers = this.accountTransferRepository.findByFromLoanId(accountNumber); + accountTransfers = this.accountTransferRepository.findByFromLoanId(accountNumber); } - if (acccountTransfers != null && acccountTransfers.size() > 0) { - undoTransactions(acccountTransfers); + if (accountTransfers != null && !accountTransfers.isEmpty()) { + undoTransactions(accountTransfers); } } @@ -225,35 +225,31 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer @Transactional public void reverseTransfersWithFromAccountTransactions(final Collection fromTransactionIds, final PortfolioAccountType accountTypeId) { - List acccountTransfers = new ArrayList<>(); + List accountTransfers = new ArrayList<>(); if (accountTypeId.isLoanAccount()) { List> partitions = Lists.partition(fromTransactionIds.stream().toList(), fineractProperties.getQuery().getInClauseParameterSizeLimit()); - partitions.forEach(partition -> acccountTransfers.addAll(this.accountTransferRepository.findByFromLoanTransactions(partition))); + partitions.forEach(partition -> accountTransfers.addAll(this.accountTransferRepository.findByFromLoanTransactions(partition))); } - if (acccountTransfers.size() > 0) { - undoTransactions(acccountTransfers); + if (!accountTransfers.isEmpty()) { + undoTransactions(accountTransfers); } - } @Override @Transactional public void reverseAllTransactions(final Long accountId, final PortfolioAccountType accountTypeId) { - List acccountTransfers = null; + List accountTransfers = null; if (accountTypeId.isLoanAccount()) { - acccountTransfers = this.accountTransferRepository.findAllByLoanId(accountId); + accountTransfers = this.accountTransferRepository.findAllByLoanId(accountId); } - if (acccountTransfers != null && acccountTransfers.size() > 0) { - undoTransactions(acccountTransfers); + if (accountTransfers != null && !accountTransfers.isEmpty()) { + undoTransactions(accountTransfers); } } - /** - * @param acccountTransfers - */ - private void undoTransactions(final List acccountTransfers) { - for (final AccountTransferTransaction accountTransfer : acccountTransfers) { + private void undoTransactions(final List accountTransfers) { + for (final AccountTransferTransaction accountTransfer : accountTransfers) { if (accountTransfer.getFromLoanTransaction() != null) { this.loanAccountDomainService.reverseTransfer(accountTransfer.getFromLoanTransaction()); } @@ -298,14 +294,12 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId()); } else { toLoanAccount = accountTransferDTO.getLoan(); - this.loanAccountAssembler.setHelpers(toLoanAccount); } } else { fromSavingsAccount = accountTransferDetails.fromSavingsAccount(); this.savingsAccountAssembler.setHelpers(fromSavingsAccount); toLoanAccount = accountTransferDetails.toLoanAccount(); - this.loanAccountAssembler.setHelpers(toLoanAccount); } final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, @@ -414,12 +408,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId()); } else { fromLoanAccount = accountTransferDTO.getLoan(); - this.loanAccountAssembler.setHelpers(fromLoanAccount); } toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId(), backdatedTxnsAllowedTill); } else { fromLoanAccount = accountTransferDetails.fromLoanAccount(); - this.loanAccountAssembler.setHelpers(fromLoanAccount); toSavingsAccount = accountTransferDetails.toSavingsAccount(); this.savingsAccountAssembler.setHelpers(toSavingsAccount); } @@ -472,14 +464,12 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer fromLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId()); } else { fromLoanAccount = accountTransferDTO.getFromLoan(); - this.loanAccountAssembler.setHelpers(fromLoanAccount); } Loan toLoanAccount = null; if (accountTransferDTO.getToLoan() == null) { toLoanAccount = this.loanAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId()); } else { toLoanAccount = accountTransferDTO.getToLoan(); - this.loanAccountAssembler.setHelpers(toLoanAccount); } ExternalId externalIdForDisbursement = accountTransferDTO.getTxnExternalId(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 439d7a743..4003292c5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -141,7 +141,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final PostDatedChecksRepository postDatedChecksRepository; private final LoanCollateralManagementRepository loanCollateralManagementRepository; private final DelinquencyWritePlatformService delinquencyWritePlatformService; - private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final ExternalIdFactory externalIdFactory; private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper; @@ -259,8 +259,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanDownPaymentTransactionValidator.validateRepaymentTypeAccountStatus(loan, newRepaymentTransaction, event); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, event, newRepaymentTransaction.getTransactionDate()); - makeRepayment(loan, newRepaymentTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds, - existingReversedTransactionIds, scheduleGeneratorDTO); + makeRepayment(loan, newRepaymentTransaction, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.reprocessExistingAccruals(loan); @@ -421,8 +420,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { LoanEvent.LOAN_CHARGE_PAYMENT); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_CHARGE_PAYMENT, newPaymentTransaction.getTransactionDate()); - loanChargeService.makeChargePayment(loan, chargeId, defaultLoanLifecycleStateMachine, existingTransactionIds, - existingReversedTransactionIds, newPaymentTransaction, installmentNumber); + loanChargeService.makeChargePayment(loan, chargeId, existingTransactionIds, existingReversedTransactionIds, + newPaymentTransaction, installmentNumber); } loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction); loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -520,8 +519,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(newRefundTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); - loanRefundService.makeRefund(loan, newRefundTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds, - existingReversedTransactionIds); + loanRefundService.makeRefund(loan, newRefundTransaction, existingTransactionIds, existingReversedTransactionIds); loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(newRefundTransaction); this.loanRepositoryWrapper.saveAndFlush(loan); @@ -655,8 +653,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_CREDIT_BALANCE_REFUND); loanTransactionValidator.validateRefundDateIsAfterLastRepayment(loan, newCreditBalanceRefundTransaction.getTransactionDate()); - loanRefundService.creditBalanceRefund(loan, newCreditBalanceRefundTransaction, defaultLoanLifecycleStateMachine, - existingTransactionIds, existingReversedTransactionIds); + loanRefundService.creditBalanceRefund(loan, newCreditBalanceRefundTransaction, existingTransactionIds, + existingReversedTransactionIds); newCreditBalanceRefundTransaction = this.loanTransactionRepository.saveAndFlush(newCreditBalanceRefundTransaction); @@ -709,8 +707,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { allowTransactionsOnNonWorkingDay); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_REFUND, newRefundTransaction.getTransactionDate()); - loanRefundService.makeRefundForActiveLoan(loan, newRefundTransaction, defaultLoanLifecycleStateMachine, existingTransactionIds, - existingReversedTransactionIds); + loanRefundService.makeRefundForActiveLoan(loan, newRefundTransaction, existingTransactionIds, existingReversedTransactionIds); this.loanTransactionRepository.saveAndFlush(newRefundTransaction); @@ -777,7 +774,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { loanForeclosureValidator.validateForForeclosure(loan, payment.getTransactionDate()); } loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_FORECLOSURE); - handleForeClosureTransactions(loan, payment, defaultLoanLifecycleStateMachine, scheduleGeneratorDTO); + handleForeClosureTransactions(loan, payment, scheduleGeneratorDTO); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -918,8 +915,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } } - loan.updateLoanSummaryDerivedFields(); - loan.doPostLoanTransactionChecks(transactionDate, defaultLoanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, transactionDate); switch (loanTransactionType) { case MERCHANT_ISSUED_REFUND -> businessEventNotifierService @@ -1062,21 +1058,19 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } @SuppressWarnings("null") - private void makeRepayment(final Loan loan, final LoanTransaction repaymentTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, + private void makeRepayment(final Loan loan, final LoanTransaction repaymentTransaction, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { loanChargeValidator.validateRepaymentTypeTransactionNotBeforeAChargeRefund(loan, repaymentTransaction, "created"); existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, loanLifecycleStateMachine, - null, scheduleGeneratorDTO); + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, null, scheduleGeneratorDTO); } private void handleForeClosureTransactions(final Loan loan, final LoanTransaction repaymentTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final ScheduleGeneratorDTO scheduleGeneratorDTO) { + final ScheduleGeneratorDTO scheduleGeneratorDTO) { loan.setLoanSubStatus(LoanSubStatus.FORECLOSED); - loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, loanLifecycleStateMachine, - null, scheduleGeneratorDTO); + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, repaymentTransaction, null, scheduleGeneratorDTO); } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index b4ac0b05c..fe27ecb80 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -160,7 +160,7 @@ public class LoanScheduleAssembler { private final LoanUtilService loanUtilService; private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler; private final LoanRepositoryWrapper loanRepositoryWrapper; - private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final LoanAccrualsProcessingService loanAccrualsProcessingService; private final LoanDisbursementService loanDisbursementService; private final LoanChargeService loanChargeService; @@ -1479,7 +1479,7 @@ public class LoanScheduleAssembler { final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); final Map actualChanges = new HashMap<>(); - defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVED, loan); + loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVED, loan); actualChanges.put(PARAM_STATUS, LoanEnumerations.status(loan.getStatus())); LocalDate approvedOn = command.localDateValueOfParameterNamed(APPROVED_ON_DATE); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index 440a42c6f..8bfc0b235 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -35,7 +35,6 @@ import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; -import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; @@ -63,7 +62,6 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC private final CurrencyReadPlatformService currencyReadPlatformService; private final LoanUtilService loanUtilService; private final LoanRepositoryWrapper loanRepository; - private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final LoanTermVariationsMapper loanTermVariationsMapper; @Override @@ -204,16 +202,12 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC } private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) { - final LocalDate recalculateFrom = null; - ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); + final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null); return loanTermVariationsMapper.constructLoanApplicationTerms(scheduleGeneratorDTO, loan); } private Loan fetchLoan(final Long accountId) { - final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true); - loanAccount.setHelpers(defaultLoanLifecycleStateMachine); - - return loanAccount; + return loanRepository.findOneWithNotFoundDetection(accountId, true); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 0a52c8fd1..60de8d07e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; @@ -51,7 +52,6 @@ import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; @@ -84,8 +84,6 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService; import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent; import org.apache.fineract.useradministration.domain.AppUser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.NonTransientDataAccessException; @@ -93,11 +91,11 @@ import org.springframework.orm.jpa.JpaSystemException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service @RequiredArgsConstructor public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanRescheduleRequestWritePlatformService { - private static final Logger LOG = LoggerFactory.getLogger(LoanRescheduleRequestWritePlatformServiceImpl.class); private static final DefaultScheduledDateGenerator DEFAULT_SCHEDULED_DATE_GENERATOR = new DefaultScheduledDateGenerator(); private final CodeValueRepositoryWrapper codeValueRepositoryWrapper; @@ -258,17 +256,15 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche if (rescheduleFromDate != null && endDate != null && emi != null) { LoanTermVariations parent = null; - LocalDate rescheduleFromLocDate = rescheduleFromDate; - LocalDate endDateLocDate = endDate; final Integer termType = LoanTermVariationType.EMI_AMOUNT.getValue(); List installments = loan.getRepaymentScheduleInstallments(); for (LoanRepaymentScheduleInstallment installment : installments) { - if (!DateUtils.isBefore(installment.getDueDate(), rescheduleFromLocDate) - && !DateUtils.isAfter(installment.getDueDate(), endDateLocDate)) { + if (!DateUtils.isBefore(installment.getDueDate(), rescheduleFromDate) + && !DateUtils.isAfter(installment.getDueDate(), endDate)) { createLoanTermVariations(loanRescheduleRequest, termType, loan, installment.getDueDate(), installment.getDueDate(), loanRescheduleRequestToTermVariationMappings, isActive, true, emi, parent); } - if (DateUtils.isAfter(installment.getDueDate(), endDateLocDate)) { + if (DateUtils.isAfter(installment.getDueDate(), endDate)) { break; } } @@ -430,8 +426,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche .determineProcessor(loan.transactionProcessingStrategy()); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), loanApplicationTerms.getInterestMethod()); - final LoanLifecycleStateMachine loanLifecycleStateMachine = null; - loan.setHelpers(loanLifecycleStateMachine); final LoanScheduleDTO loanScheduleDTO = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan, loanApplicationTerms.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, rescheduleFromDate); @@ -565,7 +559,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche * **/ private void handleDataIntegrityViolation(final NonTransientDataAccessException dve) { - LOG.error("Error occured.", dve); + log.error("Error occurred.", dve); throw ErrorHandler.getMappable(dve, "error.msg.loan.reschedule.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java index 147021029..9e74e10db 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java @@ -199,7 +199,7 @@ public final class LoanApplicationValidator { private final WorkingDaysRepositoryWrapper workingDaysRepository; private final HolidayRepository holidayRepository; private final SavingsAccountRepositoryWrapper savingsAccountRepository; - private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final CalendarInstanceRepository calendarInstanceRepository; private final LoanUtilService loanUtilService; private final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService; @@ -799,7 +799,6 @@ public final class LoanApplicationValidator { private void fixedLengthValidations(final JsonElement element) { validateOrThrow("loan", baseDataValidator -> { - boolean isInterestBearing = false; final String transactionProcessingStrategy = this.fromApiJsonHelper .extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element); final Integer numberOfRepayments = this.fromApiJsonHelper @@ -809,7 +808,7 @@ public final class LoanApplicationValidator { final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper .extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element); - isInterestBearing = interestRatePerPeriod != null && interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0; + final boolean isInterestBearing = interestRatePerPeriod != null && interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0; loanProductDataValidator.fixedLengthValidations(transactionProcessingStrategy, isInterestBearing, numberOfRepayments, repaymentEvery, element, baseDataValidator); }); @@ -1267,7 +1266,7 @@ public final class LoanApplicationValidator { final Set supportedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.idParameterName, LoanApiConstants.clientCollateralIdParameterName, LoanApiConstants.quantityParameterName)); final JsonArray array = topLevelJsonElement.get(LoanApiConstants.collateralParameterName).getAsJsonArray(); - if (array.size() > 0) { + if (!array.isEmpty()) { BigDecimal totalAmount = BigDecimal.ZERO; for (int i = 1; i <= array.size(); i++) { final JsonObject collateralItemElement = array.get(i - 1).getAsJsonObject(); @@ -1688,7 +1687,7 @@ public final class LoanApplicationValidator { final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName, element); List expectedDisbursementDates = new ArrayList<>(); - if (variationArray != null && variationArray.size() > 0) { + if (variationArray != null && !variationArray.isEmpty()) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) { boolean isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam, element); @@ -1890,15 +1889,14 @@ public final class LoanApplicationValidator { ? this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, element) : originalExpectedDisbursementDate; - String defaultUserMessage = ""; if (DateUtils.isBefore(submittedOnDate, startDate)) { - defaultUserMessage = "submittedOnDate cannot be before the loan product startDate."; + String defaultUserMessage = "submittedOnDate cannot be before the loan product startDate."; throw new LoanApplicationDateException("submitted.on.date.cannot.be.before.the.loan.product.start.date", defaultUserMessage, submittedOnDate.toString(), startDate.toString()); } if (closeDate != null && DateUtils.isAfter(submittedOnDate, closeDate)) { - defaultUserMessage = "submittedOnDate cannot be after the loan product closeDate."; + String defaultUserMessage = "submittedOnDate cannot be after the loan product closeDate."; throw new LoanApplicationDateException("submitted.on.date.cannot.be.after.the.loan.product.close.date", defaultUserMessage, submittedOnDate.toString(), closeDate.toString()); } @@ -2024,7 +2022,6 @@ public final class LoanApplicationValidator { baseDataValidator.reset().parameter(LoanApiConstants.noteParameterName).value(note).notExceedingLengthOf(1000); final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); - loan.setHelpers(defaultLoanLifecycleStateMachine); final Client client = loan.client(); if (client != null && client.isNotActive()) { @@ -2116,7 +2113,7 @@ public final class LoanApplicationValidator { throw new InvalidLoanStateTransitionException("approval", "cannot.be.a.future.date", errorMessage, approvedOnDate); } - final LoanStatus newStatus = defaultLoanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVED, loan); + final LoanStatus newStatus = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVED, loan); if (newStatus.hasStateOf(loan.getStatus())) { final String defaultUserMessage = "Loan is already approved."; final ApiParameterError error = ApiParameterError diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index bddd111a9..e902b10fe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.loanaccount.service; +import static org.apache.fineract.portfolio.loanaccount.domain.Loan.APPROVED_ON_DATE; +import static org.apache.fineract.portfolio.loanaccount.domain.Loan.PARAM_STATUS; import static org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD; import com.google.gson.JsonArray; @@ -29,6 +31,7 @@ import java.time.LocalDate; import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -75,7 +78,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; @@ -87,6 +89,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationVa import org.apache.fineract.portfolio.loanaccount.serialization.LoanDownPaymentTransactionValidator; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; +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.savings.data.GroupSavingsIndividualMonitoringAccountData; @@ -108,7 +111,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final LoanRepositoryWrapper loanRepositoryWrapper; private final NoteRepository noteRepository; private final LoanAssembler loanAssembler; - private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final CalendarRepository calendarRepository; private final CalendarInstanceRepository calendarInstanceRepository; private final SavingsAccountRepositoryWrapper savingsAccountRepository; @@ -121,7 +123,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final GLIMAccountInfoRepository glimRepository; private final LoanRepository loanRepository; private final GSIMReadPlatformService gsimReadPlatformService; - private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final LoanAccrualsProcessingService loanAccrualsProcessingService; private final LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator; private final LoanScheduleService loanScheduleService; @@ -505,9 +507,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override public CommandProcessingResult approveGLIMLoanAppication(final Long loanId, final JsonCommand command) { - - final Long parentLoanId = loanId; - GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(parentLoanId).orElseThrow(); + GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(loanId).orElseThrow(); JsonArray approvalFormData = command.arrayOfParameterNamed("approvalFormData"); JsonObject jsonObject = null; @@ -579,11 +579,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override public CommandProcessingResult undoGLIMLoanApplicationApproval(final Long loanId, final JsonCommand command) { - - // GroupLoanIndividualMonitoringAccount - // glimAccount=glimRepository.findOne(loanId); - final Long parentLoanId = loanId; - GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(parentLoanId).orElseThrow(); + GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(loanId).orElseThrow(); List childLoans = this.loanRepository.findByGlimId(loanId); CommandProcessingResult result = null; @@ -610,19 +606,16 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override public CommandProcessingResult undoApplicationApproval(final Long loanId, final JsonCommand command) { - this.loanApplicationValidator.validateForUndo(command.json()); Loan loan = retrieveLoanBy(loanId); loanApplicationTransitionValidator.checkClientOrGroupActive(loan); loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_APPROVAL_UNDO); - final Map changes = loan.undoApproval(defaultLoanLifecycleStateMachine); + final Map changes = undoApproval(loan); if (!changes.isEmpty()) { - // If loan approved amount is not same as loan amount demanded, then // during undo, restore the demand amount to principal amount. - if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey(LoanApiConstants.disbursementPrincipalParameterName)) { LocalDate recalculateFrom = null; @@ -655,11 +648,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Transactional @Override public CommandProcessingResult rejectGLIMApplicationApproval(final Long glimId, final JsonCommand command) { - - // GroupLoanIndividualMonitoringAccount - // glimAccount=glimRepository.findOne(loanId); - final Long parentLoanId = glimId; - GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(parentLoanId).orElseThrow(); + GroupLoanIndividualMonitoringAccount parentLoan = glimRepository.findById(glimId).orElseThrow(); List childLoans = this.loanRepository.findByGlimId(glimId); CommandProcessingResult result = null; @@ -699,7 +688,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa // loan application rejection final AppUser currentUser = getAppUserIfPresent(); - defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, loan); + loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, loan); final Map changes = loanAssembler.updateLoanApplicationAttributesForRejection(loan, command, currentUser); if (!changes.isEmpty()) { @@ -737,7 +726,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa // loan application withdrawal final AppUser currentUser = getAppUserIfPresent(); - defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN, loan); + loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN, loan); final Map changes = loanAssembler.updateLoanApplicationAttributesForWithdrawal(loan, command, currentUser); // Release attached collaterals if (loan.getLoanType().isIndividualAccount()) { @@ -764,9 +753,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } private Loan retrieveLoanBy(final Long loanId) { - final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); - loan.setHelpers(defaultLoanLifecycleStateMachine); - return loan; + return loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); } private AppUser getAppUserIfPresent() { @@ -862,4 +849,32 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa loan.updateLoanCollateral(loanCollateralManagements); } + private Map undoApproval(final Loan loan) { + final Map actualChanges = new LinkedHashMap<>(); + + final LoanStatus currentStatus = loan.getStatus(); + final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_APPROVAL_UNDO, loan); + if (!statusEnum.hasStateOf(currentStatus)) { + loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVAL_UNDO, loan); + actualChanges.put(PARAM_STATUS, LoanEnumerations.status(loan.getStatus())); + + loan.setApprovedOnDate(null); + loan.setApprovedBy(null); + + if (loan.getApprovedPrincipal().compareTo(loan.getProposedPrincipal()) != 0) { + loan.setApprovedPrincipal(loan.getProposedPrincipal()); + loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getProposedPrincipal()); + + actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, loan.getProposedPrincipal()); + actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, loan.getProposedPrincipal()); + } + + actualChanges.put(APPROVED_ON_DATE, ""); + + loan.getLoanOfficerHistory().clear(); + } + + return actualChanges; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java index 3938f6e78..73ad304f5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java @@ -76,7 +76,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement; import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; -import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; @@ -123,7 +122,6 @@ public class LoanAssemblerImpl implements LoanAssembler { private final ConfigurationDomainService configurationDomainService; private final WorkingDaysRepositoryWrapper workingDaysRepository; private final RateAssembler rateAssembler; - private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final ExternalIdFactory externalIdFactory; private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository; private final GLIMAccountInfoRepository glimRepository; @@ -147,31 +145,17 @@ public class LoanAssemblerImpl implements LoanAssembler { @Override public Loan assembleFrom(final Long accountId, final boolean loadLazyCollections) { - final Loan loanAccount = loanRepository.findOneWithNotFoundDetection(accountId, loadLazyCollections); - setHelpers(loanAccount); - - return loanAccount; + return loanRepository.findOneWithNotFoundDetection(accountId, loadLazyCollections); } @Override public Loan assembleFrom(final ExternalId externalId) { - final Loan loanAccount = loanRepository.findOneWithNotFoundDetection(externalId, true); - setHelpers(loanAccount); - - return loanAccount; + return loanRepository.findOneWithNotFoundDetection(externalId, true); } @Override public Loan assembleFrom(final ExternalId externalId, final boolean loadLazyCollections) { - final Loan loanAccount = loanRepository.findOneWithNotFoundDetection(externalId, loadLazyCollections); - setHelpers(loanAccount); - - return loanAccount; - } - - @Override - public void setHelpers(final Loan loanAccount) { - loanAccount.setHelpers(defaultLoanLifecycleStateMachine); + return loanRepository.findOneWithNotFoundDetection(externalId, loadLazyCollections); } @Override @@ -298,7 +282,6 @@ public class LoanAssemblerImpl implements LoanAssembler { loanSchedule.updateLoanSchedule(loanApplication, loanScheduleModel); copyAdvancedPaymentRulesIfApplicable(transactionProcessingStrategyCode, loanProduct, loanApplication); - loanApplication.setHelpers(defaultLoanLifecycleStateMachine); // TODO: review loanChargeService.recalculateAllCharges(loanApplication); topUpLoanConfiguration(element, loanApplication); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index a9aa3e76e..03c39763f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -162,7 +162,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo private final LoanWritePlatformService loanWritePlatformService; private final LoanUtilService loanUtilService; private final LoanChargeReadPlatformService loanChargeReadPlatformService; - private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService; private final FromJsonHelper fromApiJsonHelper; private final ConfigurationDomainService configurationDomainService; @@ -181,6 +181,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo private final LoanAccountService loanAccountService; private final LoanAdjustmentService loanAdjustmentService; private final LoanAccountingBridgeMapper loanAccountingBridgeMapper; + private final LoanChargeService loanChargeService; private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) { return DateUtils.isAfter(loanCharge.getDueDate(), e.getFromDate()) && !DateUtils.isAfter(loanCharge.getDueDate(), e.getDueDate()); @@ -305,7 +306,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo } else { reprocessLoanTransactionsService.reprocessTransactions(loan); } - loan.doPostLoanTransactionChecks(transactionDate, loan.getLoanLifecycleStateMachine()); + loanLifecycleStateMachine.determineAndTransition(loan, transactionDate); loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); } @@ -571,15 +572,13 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo } loanChargeValidator.validateLoanIsNotClosed(loan, loanCharge); - final LoanTransaction waiveTransaction = waiveLoanCharge(loan, loanCharge, defaultLoanLifecycleStateMachine, changes, - existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge, - externalId); + final LoanTransaction waiveTransaction = waiveLoanCharge(loan, loanCharge, changes, existingTransactionIds, + existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge, externalId); if (loan.isInterestBearingAndInterestRecalculationEnabled() && DateUtils.isBefore(loanCharge.getDueLocalDate(), DateUtils.getBusinessLocalDate())) { loanAccrualsProcessingService.reprocessExistingAccruals(loan); loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); - } this.loanTransactionRepository.saveAndFlush(waiveTransaction); @@ -928,7 +927,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); } loanAccountService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction); - loan.updateLoanSummaryAndStatus(); + loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate()); loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -1109,7 +1108,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo loanChargeValidator.validateChargeAdditionForDisbursedLoan(loan, loanCharge); loanChargeValidator.validateChargeHasValidSpecifiedDateIfApplicable(loan, loanCharge, loan.getDisbursementDate()); - loan.addLoanCharge(loanCharge); + loanChargeService.addLoanCharge(loan, loanCharge); loanCharge = this.loanChargeRepository.saveAndFlush(loanCharge); // we want to apply charge transactions only for those loans charges that are applied when a loan is active and @@ -1433,8 +1432,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo } } - public LoanTransaction waiveLoanCharge(final Loan loan, final LoanCharge loanCharge, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, + public LoanTransaction waiveLoanCharge(final Loan loan, final LoanCharge loanCharge, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge, final ExternalId externalId) { final Money amountWaived = loanCharge.waive(loan.getCurrency(), loanInstallmentNumber); @@ -1512,9 +1510,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo loan.getActiveCharges()); } - loan.updateLoanSummaryDerivedFields(); - - loan.doPostLoanTransactionChecks(waiveLoanChargeTransaction.getTransactionDate(), loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, waiveLoanChargeTransaction.getTransactionDate()); return waiveLoanChargeTransaction; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java index 6e4460e89..a58fad1f0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java @@ -62,6 +62,7 @@ public class LoanDisbursementService { private final LoanChargeValidator loanChargeValidator; private final LoanDisbursementValidator loanDisbursementValidator; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanChargeService loanChargeService; public void updateDisbursementDetails(final Loan loan, final JsonCommand jsonCommand, final Map actualChanges) { final List disbursementList = loan.fetchDisbursementIds(); @@ -251,7 +252,7 @@ public class LoanDisbursementService { loanChargeValidator.validateChargeAdditionForDisbursedLoan(loan, loanCharge); loanChargeValidator.validateChargeHasValidSpecifiedDateIfApplicable(loan, loanCharge, loan.getDisbursementDate()); - loan.addLoanCharge(loanCharge); + loanChargeService.addLoanCharge(loan, loanCharge); } actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal); actualChanges.put(RECALCULATE_LOAN_SCHEDULE, true); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index fc0b8345b..88ce0dadd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -368,7 +368,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); - if (loan.canDisburse()) { + if (canDisburse(loan)) { // Get netDisbursalAmount from disbursal screen field. final BigDecimal netDisbursalAmount = command .bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName); @@ -756,8 +756,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf // disbursement and actual disbursement happens on same date loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_DISBURSED); updateLoanCounters(loan, actualDisbursementDate); - boolean canDisburse = loan.canDisburse(); - if (canDisburse) { + if (canDisburse(loan)) { Money amountBeforeAdjust = loan.getPrincipal(); Money disburseAmount = loanDisbursementService.adjustDisburseAmount(loan, command, actualDisbursementDate); boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal()); @@ -960,76 +959,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf chargeRefundChargeType); } - private void handleLoanOverpayment(Loan loan, LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { - loan.setOverpaidOnDate(transactionDate); - loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, loan); - loan.setClosedOnDate(null); - loan.setActualMaturityDate(null); - } - - private void handleLoanRepaymentInFull(final Loan loan, final LocalDate transactionDate, - final LoanLifecycleStateMachine loanLifecycleStateMachine) { - - boolean isAllChargesPaid = true; - for (final LoanCharge loanCharge : loan.getCharges()) { - if (loanCharge.isActive() && loanCharge.amount().compareTo(BigDecimal.ZERO) > 0 - && !(loanCharge.isPaid() || loanCharge.isWaived())) { - isAllChargesPaid = false; - break; - } - } - if (isAllChargesPaid) { - loan.setClosedOnDate(transactionDate); - loan.setActualMaturityDate(transactionDate); - loanLifecycleStateMachine.transition(LoanEvent.REPAID_IN_FULL, loan); - } else if (loan.getLoanStatus().isOverpaid()) { - if (loan.getTotalOverpaid() == null || BigDecimal.ZERO.compareTo(loan.getTotalOverpaid()) == 0) { - loan.setOverpaidOnDate(null); - } - loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan); - } - } - - private boolean doPostLoanTransactionChecks(final Loan loan, final LocalDate transactionDate, - final LoanLifecycleStateMachine loanLifecycleStateMachine) { - boolean statusChanged = false; - boolean isOverpaid = loan.getTotalOverpaid() != null && loan.getTotalOverpaid().compareTo(BigDecimal.ZERO) > 0; - if (isOverpaid) { - // FIXME - kw - update account balance to negative amount. - handleLoanOverpayment(loan, transactionDate, loanLifecycleStateMachine); - statusChanged = true; - } else if (loan.getSummary().isRepaidInFull(loan.getCurrency())) { - handleLoanRepaymentInFull(loan, transactionDate, loanLifecycleStateMachine); - statusChanged = true; - } else { - loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan); - } - if (loan.getTotalOverpaid() == null || BigDecimal.ZERO.compareTo(loan.getTotalOverpaid()) == 0) { - loan.setOverpaidOnDate(null); - } - return statusChanged; - } - - // TODO check - public boolean isChronologicallyTheLatestTransaction(final LoanTransaction loanTransaction, - final List loanTransactions) { - boolean isChronologicallyLatestRepaymentOrWaiver = true; - - final LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); - for (final LoanTransaction previousTransaction : loanTransactions) { - if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed() - && (DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate()) - || (DateUtils.isEqual(currentTransactionDate, previousTransaction.getTransactionDate()) - && ((loanTransaction.getId() == null && previousTransaction.getId() == null) - || (loanTransaction.getId() != null && (previousTransaction.getId() == null - || loanTransaction.getId().compareTo(previousTransaction.getId()) < 0)))))) { - isChronologicallyLatestRepaymentOrWaiver = false; - break; - } - } - return isChronologicallyLatestRepaymentOrWaiver; - } - private void recalculateLoanWithInterestPaymentWaiverTxn(Loan loan, LoanTransaction newInterestPaymentWaiverTransaction) { LocalDate recalculateFrom = null; LocalDate transactionDate = newInterestPaymentWaiverTransaction.getTransactionDate(); @@ -1078,9 +1007,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf reprocessChangedLoanTransactions(loan, scheduleGeneratorDTO); } - loan.updateLoanSummaryDerivedFields(); - - doPostLoanTransactionChecks(loan, newInterestPaymentWaiverTransaction.getTransactionDate(), loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, newInterestPaymentWaiverTransaction.getTransactionDate()); } private void reprocessChangedLoanTransactions(Loan loan, ScheduleGeneratorDTO scheduleGeneratorDTO) { @@ -1411,7 +1338,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf LoanTransactionRelationTypeEnum.CHARGEBACK); this.loanTransactionRelationRepository.save(loanTransactionRelation); - handleChargebackTransaction(loan, newTransaction, loanLifecycleStateMachine); + handleChargebackTransaction(loan, newTransaction); newTransaction = this.loanTransactionRepository.saveAndFlush(newTransaction); @@ -1502,8 +1429,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf LoanEvent.LOAN_REPAYMENT_OR_WAIVER); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_REPAYMENT_OR_WAIVER, waiveInterestTransaction.getTransactionDate()); - waiveInterest(loan, waiveInterestTransaction, loanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds, - scheduleGeneratorDTO); + waiveInterest(loan, waiveInterestTransaction, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.reprocessExistingAccruals(loan); @@ -1598,8 +1524,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .withLoanId(loanId) // .with(changes); - final Optional loanTransactionOptional = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes, - existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); + final Optional loanTransactionOptional = closeAsWrittenOff(loan, command, changes, existingTransactionIds, + existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); if (loanTransactionOptional.isPresent()) { final LoanTransaction loanTransaction = loanTransactionOptional.get(); @@ -1666,8 +1592,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LocalDate closureDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE); loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.LOAN_CLOSED); loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.REPAID_IN_FULL, closureDate); - final Optional loanTransactionOptional = close(loan, command, loanLifecycleStateMachine, changes, - existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); + final Optional loanTransactionOptional = close(loan, command, changes, existingTransactionIds, + existingReversedTransactionIds, scheduleGeneratorDTO); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -1747,7 +1673,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changes.put("locale", command.locale()); changes.put("dateFormat", command.dateFormat()); - closeAsMarkedForReschedule(loan, command, loanLifecycleStateMachine, changes); + closeAsMarkedForReschedule(loan, command, changes); saveLoanWithDataIntegrityViolationChecks(loan); @@ -1816,8 +1742,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public LoanTransaction initiateLoanTransfer(final Loan loan, final LocalDate transferDate) { - - this.loanAssembler.setHelpers(loan); checkClientOrGroupActive(loan); validateTransactionsForTransfer(loan, transferDate); @@ -1828,7 +1752,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf ExternalId externalId = externalIdFactory.create(); final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate, externalId); loan.addLoanTransaction(newTransferTransaction); - LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine; loanLifecycleStateMachine.transition(LoanEvent.LOAN_INITIATE_TRANSFER, loan); this.loanTransactionRepository.saveAndFlush(newTransferTransaction); @@ -1843,7 +1766,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Override public LoanTransaction acceptLoanTransfer(final Loan loan, final LocalDate transferDate, final Office acceptedInOffice, final Staff loanOfficer) { - this.loanAssembler.setHelpers(loan); businessEventNotifierService.notifyPreBusinessEvent(new LoanAcceptTransferBusinessEvent(loan)); final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); @@ -1851,7 +1773,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate, externalId); loan.addLoanTransaction(newTransferAcceptanceTransaction); - LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine; if (loan.getTotalOverpaid() != null) { loanLifecycleStateMachine.transition(LoanEvent.LOAN_OVERPAYMENT, loan); } else { @@ -1873,7 +1794,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public LoanTransaction withdrawLoanTransfer(final Loan loan, final LocalDate transferDate) { - this.loanAssembler.setHelpers(loan); businessEventNotifierService.notifyPreBusinessEvent(new LoanWithdrawTransferBusinessEvent(loan)); final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); @@ -1884,7 +1804,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate, externalId); loan.addLoanTransaction(newTransferAcceptanceTransaction); - LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine; loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAW_TRANSFER, loan); this.loanTransactionRepository.saveAndFlush(newTransferAcceptanceTransaction); @@ -1899,9 +1818,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public void rejectLoanTransfer(final Loan loan) { - this.loanAssembler.setHelpers(loan); businessEventNotifierService.notifyPreBusinessEvent(new LoanRejectTransferBusinessEvent(loan)); - LoanLifecycleStateMachine loanLifecycleStateMachine = this.loanLifecycleStateMachine; loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECT_TRANSFER, loan); saveLoanWithDataIntegrityViolationChecks(loan); businessEventNotifierService.notifyPostBusinessEvent(new LoanRejectTransferBusinessEvent(loan)); @@ -2281,7 +2198,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf businessEventNotifierService.notifyPreBusinessEvent(new LoanUndoWrittenOffBusinessEvent(writeOffTransaction)); loanDownPaymentTransactionValidator.validateAccountStatus(loan, LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); - undoWrittenOff(loan, loanLifecycleStateMachine, existingTransactionIds, existingReversedTransactionIds); + undoWrittenOff(loan, existingTransactionIds, existingReversedTransactionIds); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { loanAccrualsProcessingService.processIncomePostingAndAccruals(loan); @@ -3079,8 +2996,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } - public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine) { + public void handleChargebackTransaction(final Loan loan, LoanTransaction chargebackTransaction) { loanTransactionValidator.validateIfTransactionIsChargeback(chargebackTransaction); loan.addLoanTransaction(chargebackTransaction); @@ -3093,10 +3009,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); } - loan.updateLoanSummaryDerivedFields(); - if (!loan.doPostLoanTransactionChecks(chargebackTransaction.getTransactionDate(), loanLifecycleStateMachine)) { - loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGEBACK, loan); - } + loanLifecycleStateMachine.determineAndTransition(loan, chargebackTransaction.getTransactionDate()); } private void validateIsMultiDisbursalLoanAndDisbursedMoreThanOneTranche(Loan loan) { @@ -3232,8 +3145,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getLoanTransactions().retainAll(retainTransactions); } - private Optional closeAsWrittenOff(final Loan loan, final JsonCommand command, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, + private Optional closeAsWrittenOff(final Loan loan, final JsonCommand command, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) { closeDisbursements(loan, scheduleGeneratorDTO); @@ -3308,12 +3220,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } reprocessLoanTransactionsService.reprocessTransactions(loan); LocalDate lastLoanTransactionDate = loan.getLatestTransactionDate(); - loan.doPostLoanTransactionChecks(lastLoanTransactionDate, loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, lastLoanTransactionDate); } } - private Optional close(final Loan loan, final JsonCommand command, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, + private Optional close(final Loan loan, final JsonCommand command, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); @@ -3458,25 +3369,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanScheduleService.recalculateSchedule(loan, scheduleGeneratorDTO); actualChanges.put("undolastdisbursal", "true"); actualChanges.put("disbursedAmount", loan.getDisbursedAmount()); - loan.updateLoanSummaryDerivedFields(); - - loan.doPostLoanTransactionChecks(loan.getLastUserTransactionDate(), loanLifecycleStateMachine); + loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate()); return actualChanges; } - private void waiveInterest(final Loan loan, final LoanTransaction waiveInterestTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, + private void waiveInterest(final Loan loan, final LoanTransaction waiveInterestTransaction, final List existingTransactionIds, final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, waiveInterestTransaction, - loanLifecycleStateMachine, null, scheduleGeneratorDTO); + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, waiveInterestTransaction, null, + scheduleGeneratorDTO); } - private void undoWrittenOff(final Loan loan, final LoanLifecycleStateMachine loanLifecycleStateMachine, - final List existingTransactionIds, final List existingReversedTransactionIds) { + private void undoWrittenOff(final Loan loan, final List existingTransactionIds, final List existingReversedTransactionIds) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); final LoanTransaction writeOffTransaction = loan.findWriteOffTransaction(); @@ -3528,8 +3435,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf * Behaviour added to comply with capability of previous mifos product to support easier transition to fineract * platform. */ - public void closeAsMarkedForReschedule(final Loan loan, final JsonCommand command, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes) { + public void closeAsMarkedForReschedule(final Loan loan, final JsonCommand command, final Map changes) { final LocalDate rescheduledOn = command.localDateValueOfParameterNamed(TRANSACTION_DATE); loan.setClosedOnDate(rescheduledOn); @@ -3545,4 +3451,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loanTransactionValidator.validateLoanRescheduleDate(loan); } + + private boolean canDisburse(final Loan loan) { + final LoanStatus statusEnum = this.loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_DISBURSED, loan); + + boolean isMultiTrancheDisburse = false; + LoanStatus actualLoanStatus = loan.getStatus(); + if ((actualLoanStatus.isActive() || actualLoanStatus.isClosedObligationsMet() || actualLoanStatus.isOverpaid()) + && loan.isAllTranchesNotDisbursed()) { + isMultiTrancheDisburse = true; + } + return !statusEnum.hasStateOf(actualLoanStatus) || isMultiTrancheDisburse; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java index 3d1215353..9888ab964 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java @@ -141,8 +141,8 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); - adjustExistingTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, - existingReversedTransactionIds, scheduleGeneratorDTO, reversalTxnExternalId); + adjustExistingTransaction(loan, newTransactionDetail, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, + scheduleGeneratorDTO, reversalTxnExternalId); loanAccrualsProcessingService.reprocessExistingAccruals(loan); if (loan.isInterestBearingAndInterestRecalculationEnabled()) { @@ -185,7 +185,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountTransactions(transactionIds, PortfolioAccountType.LOAN); } - loan.updateLoanSummaryAndStatus(); + loanLifecycleStateMachine.determineAndTransition(loan, loan.getLastUserTransactionDate()); loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(), false); @@ -221,9 +221,9 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { } public void adjustExistingTransaction(final Loan loan, final LoanTransaction newTransactionDetail, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, - final List existingTransactionIds, final List existingReversedTransactionIds, - final ScheduleGeneratorDTO scheduleGeneratorDTO, final ExternalId reversalExternalId) { + final LoanTransaction transactionForAdjustment, final List existingTransactionIds, + final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, + final ExternalId reversalExternalId) { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); @@ -268,8 +268,8 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { } if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) { - loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, - loanLifecycleStateMachine, transactionForAdjustment, scheduleGeneratorDTO); + loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, transactionForAdjustment, + scheduleGeneratorDTO); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java index 28a8fc219..21423934a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java @@ -201,21 +201,19 @@ public class LoanAccountConfiguration { public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context, LoanApplicationTransitionValidator loanApplicationTransitionValidator, LoanApplicationValidator loanApplicationValidator, LoanRepositoryWrapper loanRepositoryWrapper, NoteRepository noteRepository, LoanAssembler loanAssembler, - LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, CalendarRepository calendarRepository, CalendarInstanceRepository calendarInstanceRepository, SavingsAccountRepositoryWrapper savingsAccountRepository, AccountAssociationsRepository accountAssociationsRepository, BusinessEventNotifierService businessEventNotifierService, LoanScheduleAssembler loanScheduleAssembler, LoanUtilService loanUtilService, CalendarReadPlatformService calendarReadPlatformService, EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService, - LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccrualsProcessingService loanAccrualsProcessingService, + LoanLifecycleStateMachine loanLifecycleStateMachine, LoanAccrualsProcessingService loanAccrualsProcessingService, LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService) { return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, loanApplicationTransitionValidator, - loanApplicationValidator, loanRepositoryWrapper, noteRepository, loanAssembler, - loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountRepository, - accountAssociationsRepository, businessEventNotifierService, loanScheduleAssembler, loanUtilService, - calendarReadPlatformService, entityDatatableChecksWritePlatformService, glimRepository, loanRepository, - gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanAccrualsProcessingService, + loanApplicationValidator, loanRepositoryWrapper, noteRepository, loanAssembler, calendarRepository, + calendarInstanceRepository, savingsAccountRepository, accountAssociationsRepository, businessEventNotifierService, + loanScheduleAssembler, loanUtilService, calendarReadPlatformService, entityDatatableChecksWritePlatformService, + glimRepository, loanRepository, gsimReadPlatformService, loanLifecycleStateMachine, loanAccrualsProcessingService, loanDownPaymentTransactionValidator, loanScheduleService); } @@ -235,8 +233,7 @@ public class LoanAccountConfiguration { LoanCollateralAssembler collateralAssembler, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, HolidayRepository holidayRepository, ConfigurationDomainService configurationDomainService, - WorkingDaysRepositoryWrapper workingDaysRepository, RateAssembler rateAssembler, - LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, ExternalIdFactory externalIdFactory, + WorkingDaysRepositoryWrapper workingDaysRepository, RateAssembler rateAssembler, ExternalIdFactory externalIdFactory, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, GLIMAccountInfoRepository glimRepository, AccountNumberGenerator accountNumberGenerator, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService, LoanCollateralAssembler loanCollateralAssembler, LoanScheduleCalculationPlatformService calculationPlatformService, @@ -247,10 +244,10 @@ public class LoanAccountConfiguration { return new LoanAssemblerImpl(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository, fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService, workingDaysRepository, - rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory, accountNumberFormatRepository, glimRepository, - accountNumberGenerator, glimAccountInfoWritePlatformService, loanCollateralAssembler, calculationPlatformService, - loanDisbursementDetailsAssembler, loanChargeMapper, loanCollateralManagementMapper, loanAccrualsProcessingService, - loanDisbursementService, loanChargeService, loanOfficerService, loanSchedule); + rateAssembler, externalIdFactory, accountNumberFormatRepository, glimRepository, accountNumberGenerator, + glimAccountInfoWritePlatformService, loanCollateralAssembler, calculationPlatformService, loanDisbursementDetailsAssembler, + loanChargeMapper, loanCollateralManagementMapper, loanAccrualsProcessingService, loanDisbursementService, loanChargeService, + loanOfficerService, loanSchedule); } @Bean @@ -293,7 +290,7 @@ public class LoanAccountConfiguration { AccountTransfersWritePlatformService accountTransfersWritePlatformService, LoanRepositoryWrapper loanRepositoryWrapper, JournalEntryWritePlatformService journalEntryWritePlatformService, LoanAccountDomainService loanAccountDomainService, LoanChargeRepository loanChargeRepository, LoanWritePlatformService loanWritePlatformService, LoanUtilService loanUtilService, - LoanChargeReadPlatformService loanChargeReadPlatformService, LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, + LoanChargeReadPlatformService loanChargeReadPlatformService, LoanLifecycleStateMachine loanLifecycleStateMachine, AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, FromJsonHelper fromApiJsonHelper, ConfigurationDomainService configurationDomainService, LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, @@ -304,16 +301,16 @@ public class LoanAccountConfiguration { LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanChargeValidator loanChargeValidator, LoanScheduleService loanScheduleService, ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService, LoanAdjustmentService loanAdjustmentService, - LoanAccountingBridgeMapper loanAccountingBridgeMapper) { + LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanChargeService loanChargeService) { return new LoanChargeWritePlatformServiceImpl(loanChargeApiJsonValidator, loanAssembler, chargeRepository, businessEventNotifierService, loanTransactionRepository, accountTransfersWritePlatformService, loanRepositoryWrapper, journalEntryWritePlatformService, loanAccountDomainService, loanChargeRepository, loanWritePlatformService, loanUtilService, - loanChargeReadPlatformService, defaultLoanLifecycleStateMachine, accountAssociationsReadPlatformService, fromApiJsonHelper, + loanChargeReadPlatformService, loanLifecycleStateMachine, accountAssociationsReadPlatformService, fromApiJsonHelper, configurationDomainService, loanRepaymentScheduleTransactionProcessorFactory, externalIdFactory, accountTransferDetailRepository, loanChargeAssembler, paymentDetailWritePlatformService, noteRepository, loanAccrualTransactionBusinessEventService, loanAccrualsProcessingService, loanDownPaymentTransactionValidator, loanChargeValidator, loanScheduleService, reprocessLoanTransactionsService, loanAccountService, loanAdjustmentService, - loanAccountingBridgeMapper); + loanAccountingBridgeMapper, loanChargeService); } @Bean @@ -395,7 +392,7 @@ public class LoanAccountConfiguration { RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler, PostDatedChecksRepository postDatedChecksRepository, LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository, - LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService, + LoanLifecycleStateMachine loanLifecycleStateMachine, LoanAccountLockService loanAccountLockService, ExternalIdFactory externalIdFactory, LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService, ErrorHandler errorHandler, LoanDownPaymentHandlerService loanDownPaymentHandlerService, LoanTransactionAssembler loanTransactionAssembler, LoanAccrualsProcessingService loanAccrualsProcessingService, @@ -415,12 +412,12 @@ public class LoanAccountConfiguration { loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService, guarantorDomainService, loanUtilService, entityDatatableChecksWritePlatformService, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler, - postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, defaultLoanLifecycleStateMachine, - loanAccountLockService, externalIdFactory, loanAccrualTransactionBusinessEventService, errorHandler, - loanDownPaymentHandlerService, loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, - loanDownPaymentTransactionValidator, loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, - reprocessLoanTransactionsService, loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, - loanMapper, loanTransactionProcessingService); + postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository, loanLifecycleStateMachine, loanAccountLockService, + externalIdFactory, loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService, + loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator, + loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, reprocessLoanTransactionsService, + loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, loanMapper, + loanTransactionProcessingService); } @Bean @@ -437,10 +434,10 @@ public class LoanAccountConfiguration { LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService, LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService, - LoanTransactionProcessingService loanTransactionProcessingService) { + LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) { return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService, loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator, - reprocessLoanTransactionsService, loanTransactionProcessingService); + reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine); } @Bean @@ -452,15 +449,17 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanDisbursementService.class) public LoanDisbursementService loanDisbursementService(LoanChargeValidator loanChargeValidator, - LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService) { - return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService); + LoanDisbursementValidator loanDisbursementValidator, ReprocessLoanTransactionsService reprocessLoanTransactionsService, + LoanChargeService loanChargeService) { + return new LoanDisbursementService(loanChargeValidator, loanDisbursementValidator, reprocessLoanTransactionsService, + loanChargeService); } @Bean @ConditionalOnMissingBean(LoanChargeService.class) public LoanChargeService loanChargeService(LoanChargeValidator loanChargeValidator, - LoanTransactionProcessingService loanTransactionProcessingService) { - return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService); + LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) { + return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine); } @Bean @@ -480,9 +479,10 @@ public class LoanAccountConfiguration { @Bean @ConditionalOnMissingBean(LoanRefundService.class) - public LoanRefundService loanRefundService(LoanRefundValidator loanRefundValidator, - LoanTransactionProcessingService loanTransactionProcessingService) { - return new LoanRefundService(loanRefundValidator, loanTransactionProcessingService); + public LoanRefundService loanRefundService(final LoanRefundValidator loanRefundValidator, + final LoanTransactionProcessingService loanTransactionProcessingService, + final LoanLifecycleStateMachine loanLifecycleStateMachine) { + return new LoanRefundService(loanRefundValidator, loanTransactionProcessingService, loanLifecycleStateMachine); } @Bean diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java index 16588e705..f9a6a9bde 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java @@ -241,8 +241,8 @@ class LoanAdjustmentServiceImplTest { when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true); // Act - underTest.adjustExistingTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment, - existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId); + underTest.adjustExistingTransaction(loan, newTransactionDetail, transactionForAdjustment, existingTransactionIds, + existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId); // Assert // Verify that related transaction is reversed and event is triggered @@ -286,8 +286,8 @@ class LoanAdjustmentServiceImplTest { when(newTransactionDetail.isRepaymentLikeType()).thenReturn(true); // Act - underTest.adjustExistingTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment, - existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId); + underTest.adjustExistingTransaction(loan, newTransactionDetail, transactionForAdjustment, existingTransactionIds, + existingReversedTransactionIds, scheduleGeneratorDTO, reversalExternalId); // Assert // Verify that no related transactions are reversed diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java index fc433bfef..8b3309da1 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java @@ -30,8 +30,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.math.BigDecimal; import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.stream.Stream; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; @@ -41,7 +44,9 @@ import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.charge.domain.Charge; +import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO; @@ -50,9 +55,12 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.mapper.LoanAccountingBridgeMapper; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; @@ -150,6 +158,18 @@ class LoanChargeWritePlatformServiceImplTest { @Mock private LoanAccountingBridgeMapper loanAccountingBridgeMapper; + @Mock + private LoanChargeService loanChargeService; + + @Mock + private ChargeCalculationType chargeCalculationType; + + @Mock + private SingleLoanChargeRepaymentScheduleProcessingWrapper wrapper; + + @Mock + private LoanLifecycleStateMachine loanLifecycleStateMachine; + @BeforeEach void setUp() { when(loanAssembler.assembleFrom(LOAN_ID)).thenReturn(loan); @@ -158,8 +178,13 @@ class LoanChargeWritePlatformServiceImplTest { when(chargeDefinition.getCurrencyCode()).thenReturn(CURRENCY_CODE); when(loanChargeAssembler.createNewFromJson(loan, chargeDefinition, jsonCommand)).thenReturn(loanCharge); when(loan.repaymentScheduleDetail()).thenReturn(loanRepaymentScheduleDetail); + when(loanRepaymentScheduleDetail.getLoanScheduleType()).thenReturn(LoanScheduleType.CUMULATIVE); + when(loan.getLoanRepaymentScheduleDetail()).thenReturn(loanRepaymentScheduleDetail); when(loan.hasCurrencyCodeOf(CURRENCY_CODE)).thenReturn(true); when(loanCharge.getChargePaymentMode()).thenReturn(ChargePaymentMode.REGULAR); + when(loanCharge.getChargeCalculation()).thenReturn(chargeCalculationType); + when(chargeCalculationType.isPercentageBased()).thenReturn(false); + when(loanCharge.amountOrPercentage()).thenReturn(BigDecimal.TEN); when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE); when(loanChargeRepository.saveAndFlush(any(LoanCharge.class))).thenReturn(loanCharge); when(loan.getCurrency()).thenReturn(monetaryCurrency); @@ -170,12 +195,23 @@ class LoanChargeWritePlatformServiceImplTest { when(loan.findExistingTransactionIds()).thenReturn(existingTransactionIds); when(loan.findExistingReversedTransactionIds()).thenReturn(existingReversedTransactionIds); + when(loan.getLoanCharges()).thenReturn(new HashSet<>()); + when(loan.getDisbursementDate()).thenReturn(LocalDate.now(ZoneId.systemDefault())); + when(loan.getRepaymentScheduleInstallments()).thenReturn(new ArrayList<>()); + when(loan.calculateAmountPercentageAppliedTo(any(LoanCharge.class))).thenReturn(BigDecimal.TEN); + when(loan.fetchNumberOfInstallmensAfterExceptions()).thenReturn(5); + when(loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(any(BigDecimal.class))).thenReturn(null); + when(loan.deriveSumTotalOfChargesDueAtDisbursement()).thenReturn(BigDecimal.ZERO); + when(loanCharge.getDueLocalDate()).thenReturn(LocalDate.now(ZoneId.systemDefault())); + when(loanCharge.getEffectiveDueDate()).thenReturn(LocalDate.now(ZoneId.systemDefault())); + when(loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()).thenReturn(false); when(loan.isCashBasedAccountingEnabledOnLoanProduct()).thenReturn(false); when(loan.isUpfrontAccrualAccountingEnabledOnLoanProduct()).thenReturn(false); when(loanAccountingBridgeMapper.deriveAccountingBridgeData(anyString(), anyList(), anyList(), anyBoolean(), any(Loan.class))).thenReturn(new AccountingBridgeDataDTO()); doNothing().when(journalEntryWritePlatformService).createJournalEntriesForLoan(any(AccountingBridgeDataDTO.class)); + doNothing().when(loanChargeService).addLoanCharge(any(Loan.class), any(LoanCharge.class)); } @ParameterizedTest @@ -189,8 +225,13 @@ class LoanChargeWritePlatformServiceImplTest { when(loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()).thenReturn(true); } - try (MockedStatic mockedDateUtils = mockStatic(DateUtils.class)) { + try (MockedStatic mockedDateUtils = mockStatic(DateUtils.class); + MockedStatic mockedMoneyHelper = mockStatic(MoneyHelper.class)) { + mockedDateUtils.when(DateUtils::getBusinessLocalDate).thenReturn(businessDate); + mockedDateUtils.when(() -> DateUtils.isBeforeBusinessDate(any(LocalDate.class))).thenReturn(false); + mockedMoneyHelper.when(MoneyHelper::getMathContext).thenReturn(java.math.MathContext.DECIMAL64); + mockedMoneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(java.math.RoundingMode.HALF_EVEN); loanChargeWritePlatformService.addLoanCharge(LOAN_ID, jsonCommand); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java index 93f4284de..77c74d56c 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java @@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; @@ -107,13 +108,16 @@ public class LoanDownPaymentHandlerServiceImplTest { @Mock private LoanTransactionProcessingService loanTransactionProcessingService; + @Mock + private LoanLifecycleStateMachine loanLifecycleStateMachine; + private LoanDownPaymentHandlerServiceImpl underTest; @BeforeEach public void setUp() { underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService, loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator, - reprocessLoanTransactionsService, loanTransactionProcessingService); + reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine); moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP)); moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP); tempConfigServiceMock.when(TemporaryConfigurationServiceContainer::isExternalIdAutoGenerationEnabled).thenReturn(true);