diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java index 2e57a7cf9..a6e62ea25 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -29,8 +29,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen * existing in which case the entire loan schedule needs to be re-processed. */ @Override - public void handleTransaction(final LocalDate disbursementDate, final List transactionsPostDisbursement, - final MonetaryCurrency currency, final List installments, final Set charges) { + public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate, + final List transactionsPostDisbursement, final MonetaryCurrency currency, + final List installments, final Set charges) { if (charges != null) { for (LoanCharge loanCharge : charges) { @@ -49,16 +50,36 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(currency, disbursementDate, installments, charges); + ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail(); for (LoanTransaction loanTransaction : transactionsPostDisbursement) { if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver()) { - loanTransaction.resetDerivedComponents(); - handleTransaction(loanTransaction, currency, installments, charges); + // pass through for new transactions + if (loanTransaction.getId() == null) { + loanTransaction.resetDerivedComponents(); + handleTransaction(loanTransaction, currency, installments, charges); + } else { + /** + * reverse existing transactions and create new transactions + * in their Lieu + **/ + LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); + + // Reverse the original loan transaction + loanTransaction.reverse(); + changedTransactionDetail.getReversedTransactions().add(loanTransaction); + // Reset derived component of new loan transaction + newLoanTransaction.resetDerivedComponents(); + changedTransactionDetail.getNewTransactions().add(newLoanTransaction); + handleTransaction(newLoanTransaction, currency, installments, charges); + } + } else if (loanTransaction.isWriteOff()) { loanTransaction.resetDerivedComponents(); handleWriteOff(loanTransaction, currency, installments); } } + return changedTransactionDetail; } /** @@ -149,8 +170,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen for (LoanCharge loanCharge : charges) { if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) { - if (earliestUnpaidCharge == null - || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) { + if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) { earliestUnpaidCharge = loanCharge; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/ChangedTransactionDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/ChangedTransactionDetail.java new file mode 100755 index 000000000..db73b3ec0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/ChangedTransactionDetail.java @@ -0,0 +1,28 @@ +package org.mifosplatform.portfolio.loanaccount.domain; + +import java.util.ArrayList; +import java.util.List; + +public class ChangedTransactionDetail { + + private List newTransactions = new ArrayList(); + + private List reversedTransactions = new ArrayList(); + + public List getNewTransactions() { + return this.newTransactions; + } + + public void setNewTransactions(List newTransactions) { + this.newTransactions = newTransactions; + } + + public List getReversedTransactions() { + return this.reversedTransactions; + } + + public void setReversedTransactions(List reversedTransactions) { + this.reversedTransactions = reversedTransactions; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 7f84811a6..50e0e23c2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -378,8 +378,9 @@ public class Loan extends AbstractPersistable { this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration; } - public LoanTransaction addLoanCharge(final LoanCharge loanCharge, final List existingTransactionIds, + public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge, final List existingTransactionIds, final List existingReversedTransactionIds) { + ChangedTransactionDetail changedTransactionDetail = null; validateLoanIsNotClosed(loanCharge); @@ -412,8 +413,11 @@ public class Loan extends AbstractPersistable { if (!loanCharge.isDueAtDisbursement()) { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); - loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); + changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); + for (LoanTransaction transaction : changedTransactionDetail.getNewTransactions()) { + transaction.updateLoan(this); + } } else { // just reprocess the loan schedule only for now. LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); @@ -424,9 +428,8 @@ public class Loan extends AbstractPersistable { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final LoanTransaction applyLoanChargeTransaction = getChargeAppliedTransaction(loanCharge, null); - return applyLoanChargeTransaction; + return changedTransactionDetail; } /** @@ -441,7 +444,7 @@ public class Loan extends AbstractPersistable { * @param suppliedTransactionDate * @return */ - private LoanTransaction getChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) { + public LoanTransaction getChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) { final Money chargeAmount = loanCharge.getAmount(getCurrency()); Money feeCharges = chargeAmount; Money penaltyCharges = Money.zero(loanCurrency()); @@ -615,6 +618,14 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) { + /**** + * TODO vishwas Currently we do not allow waiving fully paid loan + * charge and waiving partially paid loan charges only waives the + * remaining amount. + * + * Consider removing this block of code or logically completing it + * for the future by getting the list of affected Transactions + ***/ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); @@ -1396,24 +1407,24 @@ public class Loan extends AbstractPersistable { return waiveInterestTransaction; } - public LoanTransaction makeRepayment(final LocalDate transactionDate, final BigDecimal transactionAmount, + public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, final List existingReversedTransactionIds) { - final Money repayment = Money.of(loanCurrency(), transactionAmount); - final LoanTransaction loanTransaction = LoanTransaction.repayment(repayment, transactionDate); - existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - handleRepaymentOrWaiverTransaction(loanTransaction, loanLifecycleStateMachine, null); + ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrWaiverTransaction(repaymentTransaction, + loanLifecycleStateMachine, null); - return loanTransaction; + return changedTransactionDetail; } - private void handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction, + private ChangedTransactionDetail handleRepaymentOrWaiverTransaction(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) { + ChangedTransactionDetail changedTransactionDetail = null; + final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, LoanStatus.fromInt(this.loanStatus)); this.loanStatus = statusEnum.getValue(); @@ -1463,13 +1474,18 @@ public class Loan extends AbstractPersistable { setOfLoanCharges()); } else { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); - loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); + changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges()); + for (LoanTransaction newLoanTransaction : changedTransactionDetail.getNewTransactions()) { + newLoanTransaction.updateLoan(this); + } } updateLoanSummaryDerivedFields(); doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); + + return changedTransactionDetail; } private List retreiveListOfTransactionsPostDisbursement() { @@ -1614,19 +1630,15 @@ public class Loan extends AbstractPersistable { return LoanTransaction.waiver(this, possibleInterestToWaive, transactionDate); } - public LoanTransaction adjustExistingTransaction(final LocalDate transactionDate, final BigDecimal transactionAmountValue, + public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final List existingTransactionIds, final List existingReversedTransactionIds) { + ChangedTransactionDetail changedTransactionDetail = null; + existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final Money transactionAmount = Money.of(loanCurrency(), transactionAmountValue); - LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmount, transactionDate); - if (transactionForAdjustment.isInterestWaiver()) { - newTransactionDetail = LoanTransaction.waiver(this, transactionAmount, transactionDate); - } - if (transactionForAdjustment.isNotRepayment() && transactionForAdjustment.isNotWaiver()) { final String errorMessage = "Only transactions of type repayment or waiver can be adjusted."; throw new InvalidLoanTransactionTypeException("transaction", "adjustment.is.only.allowed.to.repayment.or.waiver.transaction", @@ -1640,10 +1652,11 @@ public class Loan extends AbstractPersistable { transactionForAdjustment.reverse(); if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { - handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment); + changedTransactionDetail = handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, + transactionForAdjustment); } - return newTransactionDetail; + return changedTransactionDetail; } private boolean isOverPaid() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessor.java index fa07d7f0b..c659b9bed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessor.java @@ -16,7 +16,7 @@ public interface LoanRepaymentScheduleTransactionProcessor { void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List installments, Set charges); - void handleTransaction(LocalDate disbursementDate, List repaymentsOrWaivers, MonetaryCurrency currency, + ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List repaymentsOrWaivers, MonetaryCurrency currency, List repaymentScheduleInstallments, Set charges); void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 90037fc16..9b4dbe03d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -85,15 +85,35 @@ public final class LoanTransaction extends AbstractPersistable { public static LoanTransaction waiver(final Loan loan, final Money waived, final LocalDate waiveDate) { return new LoanTransaction(loan, LoanTransactionType.WAIVE_INTEREST, waived.getAmount(), waiveDate); } - - public static LoanTransaction applyLoanCharge(final Loan loan, final Money amount, final LocalDate applyDate, - final Money feeCharges, final Money penaltyCharges) { + + public static LoanTransaction copyTransactionProperties(LoanTransaction loanTransaction) { + return new LoanTransaction(loanTransaction.loan, loanTransaction.typeOf, loanTransaction.dateOf, loanTransaction.amount, + loanTransaction.principalPortion, loanTransaction.interestPortion, loanTransaction.feeChargesPortion, + loanTransaction.penaltyChargesPortion, loanTransaction.reversed); + } + + public static LoanTransaction applyLoanCharge(final Loan loan, final Money amount, final LocalDate applyDate, final Money feeCharges, + final Money penaltyCharges) { LoanTransaction applyCharge = new LoanTransaction(loan, LoanTransactionType.APPLY_CHARGES, amount.getAmount(), applyDate); applyCharge.updateChargesComponents(feeCharges, penaltyCharges); return applyCharge; } + private LoanTransaction(Loan loan, Integer typeOf, Date dateOf, BigDecimal amount, BigDecimal principalPortion, + BigDecimal interestPortion, BigDecimal feeChargesPortion, BigDecimal penaltyChargesPortion, boolean reversed) { + super(); + this.loan = loan; + this.typeOf = typeOf; + this.dateOf = dateOf; + this.amount = amount; + this.principalPortion = principalPortion; + this.interestPortion = interestPortion; + this.feeChargesPortion = feeChargesPortion; + this.penaltyChargesPortion = penaltyChargesPortion; + this.reversed = reversed; + } + public static LoanTransaction waiveLoanCharge(final Loan loan, final Money waived, final LocalDate waiveDate, final Money feeChargesWaived, final Money penaltyChargesWaived) { LoanTransaction waiver = new LoanTransaction(loan, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate); @@ -305,9 +325,8 @@ public final class LoanTransaction extends AbstractPersistable { return thisTransactionData; } - public Loan getLoan() { return this.loan; } - + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index cece3ba8f..147b92751 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -21,6 +21,7 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository; +import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; @@ -32,6 +33,7 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedExce import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LOAN_CHARGE_CANNOT_BE_WAIVED_REASON; import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; +import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; @@ -207,15 +209,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); - final LoanTransaction loanRepayment = loan.makeRepayment(transactionDate, transactionAmount, defaultLoanLifecycleStateMachine(), - existingTransactionIds, existingReversedTransactionIds); - this.loanTransactionRepository.save(loanRepayment); + final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); + final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(repaymentAmount, transactionDate); + + final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, + defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds); + + this.loanTransactionRepository.save(newRepaymentTransaction); + /*** + * TODO Vishwas Batch save is giving me a + * HibernateOptimisticLockingFailureException, looping and saving for + * the time being, not a major issue for now as this loop is entered + * only in edge cases (when a payment is made before the latest payment + * recorded against the loan) + ***/ + if (changedTransactionDetail != null) { + for (LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { + this.loanTransactionRepository.save(loanTransaction); + } + } this.loanRepository.save(loan); final String noteText = command.stringValueOfParameterNamed("note"); if (StringUtils.isNotBlank(noteText)) { changes.put("note", noteText); - Note note = Note.loanTransactionNote(loan, loanRepayment, noteText); + Note note = Note.loanTransactionNote(loan, newRepaymentTransaction, noteText); this.noteRepository.save(note); } @@ -223,7 +241,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // - .withEntityId(loanRepayment.getId()) // + .withEntityId(newRepaymentTransaction.getId()) // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // .withGroupId(loan.getGroupId()) // @@ -257,13 +275,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList(); final List existingReversedTransactionIds = new ArrayList(); - final LoanTransaction newTransactionDetail = loan.adjustExistingTransaction(transactionDate, transactionAmount, + final Money transactionAmountAsMoney = Money.of(loan.getCurrency(), transactionAmount); + LoanTransaction newTransactionDetail = LoanTransaction.repayment(transactionAmountAsMoney, transactionDate); + if (transactionToAdjust.isInterestWaiver()) { + newTransactionDetail = LoanTransaction.waiver(loan, transactionAmountAsMoney, transactionDate); + } + + final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds); if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { this.loanTransactionRepository.save(newTransactionDetail); } + /*** + * TODO Vishwas Batch save is giving me a + * HibernateOptimisticLockingFailureException, looping and saving for + * the time being, not a major issue for now as this loop is entered + * only in edge cases (when a payment is made before the latest payment + * recorded against the loan) + ***/ + if (changedTransactionDetail != null) { + for (LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { + this.loanTransactionRepository.save(loanTransaction); + } + } + this.loanRepository.save(loan); final String noteText = command.stringValueOfParameterNamed("note"); @@ -495,12 +532,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingReversedTransactionIds = new ArrayList(); this.loanChargeRepository.save(loanCharge); - LoanTransaction applyChargeTransaction = loan.addLoanCharge(loanCharge, existingTransactionIds, existingReversedTransactionIds); + final LoanTransaction applyLoanChargeTransaction = loan.getChargeAppliedTransaction(loanCharge, null); + final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge, existingTransactionIds, + existingReversedTransactionIds); // we want to apply charge transactions only for those loans charges // that are applied when a loan is active if (loan.status().isActive()) { - this.loanTransactionRepository.save(applyChargeTransaction); + this.loanTransactionRepository.save(applyLoanChargeTransaction); + /*** + * TODO Vishwas Batch save is giving me a + * HibernateOptimisticLockingFailureException, looping and saving + * for the time being, not a major issue for now as this loop is + * entered only in edge cases (when a payment is made before the + * latest payment recorded against the loan) + ***/ + if (changedTransactionDetail != null) { + for (LoanTransaction loanTransaction : changedTransactionDetail.getNewTransactions()) { + this.loanTransactionRepository.save(loanTransaction); + } + } } this.loanRepository.save(loan);