wip:improving tracebililty for loan transaction updates

This commit is contained in:
Vishwas Babu AJ
2013-03-21 02:03:38 +05:30
parent 043e8dc4fd
commit d811304841
6 changed files with 175 additions and 44 deletions
@@ -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<LoanTransaction> transactionsPostDisbursement,
final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate,
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> 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;
}
}
@@ -0,0 +1,28 @@
package org.mifosplatform.portfolio.loanaccount.domain;
import java.util.ArrayList;
import java.util.List;
public class ChangedTransactionDetail {
private List<LoanTransaction> newTransactions = new ArrayList<LoanTransaction>();
private List<LoanTransaction> reversedTransactions = new ArrayList<LoanTransaction>();
public List<LoanTransaction> getNewTransactions() {
return this.newTransactions;
}
public void setNewTransactions(List<LoanTransaction> newTransactions) {
this.newTransactions = newTransactions;
}
public List<LoanTransaction> getReversedTransactions() {
return this.reversedTransactions;
}
public void setReversedTransactions(List<LoanTransaction> reversedTransactions) {
this.reversedTransactions = reversedTransactions;
}
}
@@ -378,8 +378,9 @@ public class Loan extends AbstractPersistable<Long> {
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
}
public LoanTransaction addLoanCharge(final LoanCharge loanCharge, final List<Long> existingTransactionIds,
public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
ChangedTransactionDetail changedTransactionDetail = null;
validateLoanIsNotClosed(loanCharge);
@@ -412,8 +413,11 @@ public class Loan extends AbstractPersistable<Long> {
if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> 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<Long> {
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<Long> {
* @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<Long> {
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<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, setOfLoanCharges());
@@ -1396,24 +1407,24 @@ public class Loan extends AbstractPersistable<Long> {
return waiveInterestTransaction;
}
public LoanTransaction makeRepayment(final LocalDate transactionDate, final BigDecimal transactionAmount,
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> 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<Long> {
setOfLoanCharges());
} else {
final List<LoanTransaction> 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<LoanTransaction> retreiveListOfTransactionsPostDisbursement() {
@@ -1614,19 +1630,15 @@ public class Loan extends AbstractPersistable<Long> {
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<Long> existingTransactionIds, final List<Long> 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<Long> {
transactionForAdjustment.reverse();
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment);
changedTransactionDetail = handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
transactionForAdjustment);
}
return newTransactionDetail;
return changedTransactionDetail;
}
private boolean isOverPaid() {
@@ -16,7 +16,7 @@ public interface LoanRepaymentScheduleTransactionProcessor {
void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges);
void handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency,
ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
@@ -85,15 +85,35 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
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<Long> {
return thisTransactionData;
}
public Loan getLoan() {
return this.loan;
}
}
@@ -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<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
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<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
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<Long> existingReversedTransactionIds = new ArrayList<Long>();
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);