FINERACT-2080: Extract loanLifecycleStateMachine from Loan entity, refactor transition functionality

This commit is contained in:
Oleksii Novikov
2025-04-23 10:12:53 +02:00
committed by Adam Saghy
parent 79c006774e
commit 64dddfca48
24 changed files with 419 additions and 494 deletions
@@ -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<LoanStatus> 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);
}
}
}
@@ -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<Long> {
@Column(name = "loan_product_counter")
private Integer loanProductCounter;
@Setter
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
private Set<LoanCharge> charges = new HashSet<>();
@@ -321,15 +318,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
private List<LoanTransaction> 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<Long> {
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<Long> {
}
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<Long> {
}
}
public void updateLoanSummaryAndStatus() {
updateLoanSummaryDerivedFields();
doPostLoanTransactionChecks(getLastUserTransactionDate(), loanLifecycleStateMachine);
}
private boolean isInterestRecalculationEnabledForProduct() {
return this.loanProduct.isInterestRecalculationEnabled();
}
@@ -1111,35 +1066,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return maturityDate;
}
public Map<String, Object> undoApproval(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
final Map<String, Object> 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<Long> findExistingTransactionIds() {
return getLoanTransactions().stream() //
.filter(loanTransaction -> loanTransaction.getId() != null) //
@@ -1161,18 +1087,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
.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<LoanDisbursementDetails> fetchUndisbursedDetail() {
Collection<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
LocalDate date = null;
@@ -1304,60 +1218,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
.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<Long> {
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<Long> {
Money[] receivables = retrieveIncomeOutstandingTillDate(closureDate);
Money totalPrincipal = Money.of(getCurrency(), this.getSummary().getTotalPrincipalOutstanding());
totalPrincipal = totalPrincipal.minus(receivables[3]);
final Set<LoanInterestRecalcualtionAdditionalDetails> 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) {
@@ -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);
}
@@ -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);
@@ -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<LoanCharge> charges = loan.getActiveCharges();
@@ -76,9 +78,8 @@ public class LoanChargeService {
}
}
public void makeChargePayment(final Loan loan, final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final LoanTransaction paymentTransaction, final Integer installmentNumber) {
public void makeChargePayment(final Loan loan, final Long chargeId, final List<Long> existingTransactionIds,
final List<Long> 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);
}
}
@@ -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);
}
@@ -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;
@@ -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<Long> existingTransactionIds,
public void makeRefund(final Loan loan, final LoanTransaction loanTransaction, final List<Long> existingTransactionIds,
final List<Long> 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<Long> existingTransactionIds,
public void makeRefundForActiveLoan(final Loan loan, final LoanTransaction loanTransaction, final List<Long> existingTransactionIds,
final List<Long> 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<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final List<Long> existingTransactionIds, final List<Long> 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());
}
}
@@ -211,12 +211,12 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
@Override
@Transactional
public void reverseTransfersWithFromAccountType(final Long accountNumber, final PortfolioAccountType accountTypeId) {
List<AccountTransferTransaction> acccountTransfers = null;
List<AccountTransferTransaction> 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<Long> fromTransactionIds,
final PortfolioAccountType accountTypeId) {
List<AccountTransferTransaction> acccountTransfers = new ArrayList<>();
List<AccountTransferTransaction> accountTransfers = new ArrayList<>();
if (accountTypeId.isLoanAccount()) {
List<List<Long>> 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<AccountTransferTransaction> acccountTransfers = null;
List<AccountTransferTransaction> 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<AccountTransferTransaction> acccountTransfers) {
for (final AccountTransferTransaction accountTransfer : acccountTransfers) {
private void undoTransactions(final List<AccountTransferTransaction> 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();
@@ -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<Long> existingTransactionIds,
private void makeRepayment(final Loan loan, final LoanTransaction repaymentTransaction, final List<Long> existingTransactionIds,
final List<Long> 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);
}
}
@@ -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<String, Object> 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);
@@ -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);
}
}
@@ -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<LoanRepaymentScheduleInstallment> 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.");
}
@@ -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<String> 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<LocalDate> 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
@@ -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<Loan> 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<String, Object> changes = loan.undoApproval(defaultLoanLifecycleStateMachine);
final Map<String, Object> 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<Loan> 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<String, Object> 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<String, Object> 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<String, Object> undoApproval(final Loan loan) {
final Map<String, Object> 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;
}
}
@@ -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);
@@ -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<String, Object> changes,
public LoanTransaction waiveLoanCharge(final Loan loan, final LoanCharge loanCharge, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> 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;
}
@@ -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<String, Object> actualChanges) {
final List<Long> 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);
@@ -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<LoanTransaction> 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<LoanTransaction> loanTransactionOptional = closeAsWrittenOff(loan, command, loanLifecycleStateMachine, changes,
existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
final Optional<LoanTransaction> 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<LoanTransaction> loanTransactionOptional = close(loan, command, loanLifecycleStateMachine, changes,
existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
final Optional<LoanTransaction> 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<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> 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<Long> 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<LoanTransaction> closeAsWrittenOff(final Loan loan, final JsonCommand command,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes,
private Optional<LoanTransaction> closeAsWrittenOff(final Loan loan, final JsonCommand command, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> 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<LoanTransaction> close(final Loan loan, final JsonCommand command,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map<String, Object> changes,
private Optional<LoanTransaction> close(final Loan loan, final JsonCommand command, final Map<String, Object> changes,
final List<Long> existingTransactionIds, final List<Long> 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<Long> existingTransactionIds,
private void waiveInterest(final Loan loan, final LoanTransaction waiveInterestTransaction, final List<Long> existingTransactionIds,
final List<Long> 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<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
private void undoWrittenOff(final Loan loan, final List<Long> existingTransactionIds, final List<Long> 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<String, Object> changes) {
public void closeAsMarkedForReschedule(final Loan loan, final JsonCommand command, final Map<String, Object> 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;
}
}
@@ -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<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final ExternalId reversalExternalId) {
final LoanTransaction transactionForAdjustment, final List<Long> existingTransactionIds,
final List<Long> 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);
}
}
@@ -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
@@ -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
@@ -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<DateUtils> mockedDateUtils = mockStatic(DateUtils.class)) {
try (MockedStatic<DateUtils> mockedDateUtils = mockStatic(DateUtils.class);
MockedStatic<MoneyHelper> 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);
}
@@ -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);