MIFOSX-172: wip - support for loan disbursal and undo with reversing transactions.

This commit is contained in:
Keith Woodlock
2013-02-18 15:32:56 +00:00
parent 4030608442
commit bb63d2cb2d
10 changed files with 550 additions and 596 deletions
@@ -1085,7 +1085,6 @@ CREATE TABLE `m_loan_transaction` (
`id` bigint(20) NOT NULL AUTO_INCREMENT, `id` bigint(20) NOT NULL AUTO_INCREMENT,
`loan_id` bigint(20) NOT NULL, `loan_id` bigint(20) NOT NULL,
`transaction_type_enum` smallint(5) NOT NULL, `transaction_type_enum` smallint(5) NOT NULL,
`contra_id` bigint(20) DEFAULT NULL,
`transaction_date` date NOT NULL, `transaction_date` date NOT NULL,
`amount` decimal(19,6) NOT NULL, `amount` decimal(19,6) NOT NULL,
`createdby_id` bigint(20) DEFAULT NULL, `createdby_id` bigint(20) DEFAULT NULL,
@@ -1096,11 +1095,10 @@ CREATE TABLE `m_loan_transaction` (
`interest_portion_derived` decimal(19,6) DEFAULT NULL, `interest_portion_derived` decimal(19,6) DEFAULT NULL,
`fee_charges_portion_derived` decimal(19,6) DEFAULT NULL, `fee_charges_portion_derived` decimal(19,6) DEFAULT NULL,
`penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL, `penalty_charges_portion_derived` decimal(19,6) DEFAULT NULL,
`is_reversed` tinyint(1) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `FKCFCEA42640BE0710` (`loan_id`), KEY `FKCFCEA42640BE0710` (`loan_id`),
KEY `FKCFCEA426FC69F3F1` (`contra_id`), CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
CONSTRAINT `FKCFCEA42640BE0710` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
CONSTRAINT `FKCFCEA426FC69F3F1` FOREIGN KEY (`contra_id`) REFERENCES `m_loan_transaction` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
@@ -1939,4 +1937,4 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2013-02-14 23:47:02 -- Dump completed on 2013-02-18 13:20:24
@@ -0,0 +1,11 @@
ALTER TABLE `m_loan_transaction`
ADD COLUMN `is_reversed` TINYINT(1) NOT NULL AFTER `penalty_charges_portion_derived`;
-- === update is_reversed column to true for all fields where contra_id is not null
UPDATE `m_loan_transaction` SET `is_reversed` = 1 WHERE contra_id is not null;
ALTER TABLE `m_loan_transaction` DROP FOREIGN KEY `FKCFCEA426FC69F3F1`;
ALTER TABLE `m_loan_transaction`
DROP COLUMN `contra_id` ,
DROP INDEX `FKCFCEA426FC69F3F1`;
@@ -221,7 +221,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
} }
/*** Debit loan Portfolio and credit Fund source for Disbursal **/ /*** Debit loan Portfolio and credit Fund source for Disbursal **/
if (loanTransactionDTO.isDisbursement()) { if (loanTransactionDTO.isDisbursement() && !loanTransactionDTO.isContra()) {
final GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId, final GLAccount loanPortfolioAccount = getLinkedFinAccountForLoanProduct(loanProductId,
CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO); CASH_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO);
@@ -1106,40 +1106,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
handleDisbursementTransaction(expectedDisbursedOnLocalDate); handleDisbursementTransaction(expectedDisbursedOnLocalDate);
} }
public Map<String, Object> deriveDisbursementData(final CurrencyData currencyData, final List<Long> existingTransactionIds) {
final Map<String, Object> accountingBridgeData = new LinkedHashMap<String, Object>(20);
accountingBridgeData.put("loanId", this.getId());
accountingBridgeData.put("loanProductId", this.productId());
accountingBridgeData.put("officeId", this.getOfficeId());
accountingBridgeData.put("cashBasedAccountingEnabled", this.isCashBasedAccountingEnabledOnLoanProduct());
accountingBridgeData.put("accrualBasedAccountingEnabled", this.isAccrualBasedAccountingEnabledOnLoanProduct());
final List<Map<String, Object>> newLoanTransactions = new ArrayList<Map<String, Object>>();
final List<LoanTransaction> disbursementTransactions = findDisbursementTransactions();
for (LoanTransaction loanTransaction : disbursementTransactions) {
if (!existingTransactionIds.contains(loanTransaction.getId())) {
newLoanTransactions.add(loanTransaction.toMapData(currencyData));
}
}
accountingBridgeData.put("newLoanTransactions", newLoanTransactions);
return accountingBridgeData;
}
private List<LoanTransaction> findDisbursementTransactions() {
List<LoanTransaction> found = new ArrayList<LoanTransaction>();
for (LoanTransaction transaction : this.loanTransactions) {
if (transaction.isDisbursement() || transaction.isRepaymentAtDisbursement()) {
found.add(transaction);
}
}
return found;
}
private Collection<Long> findExistingTransactionIds() { private Collection<Long> findExistingTransactionIds() {
Collection<Long> ids = new ArrayList<Long>(); Collection<Long> ids = new ArrayList<Long>();
@@ -1151,9 +1117,22 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return ids; return ids;
} }
private Collection<Long> findExistingReversedTransactionIds() {
final Collection<Long> ids = new ArrayList<Long>();
for (LoanTransaction transaction : this.loanTransactions) {
if (transaction.isReversed()) {
ids.add(transaction.getId());
}
}
return ids;
}
public Map<String, Object> disburse(final AppUser currentUser, final JsonCommand command, public Map<String, Object> disburse(final AppUser currentUser, final JsonCommand command,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final ApplicationCurrency currency, final LoanLifecycleStateMachine loanLifecycleStateMachine, final ApplicationCurrency currency,
final List<Long> existingTransactionIds) { final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
@@ -1161,6 +1140,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus));
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
actualChanges.put("status", LoanEnumerations.status(this.loanStatus)); actualChanges.put("status", LoanEnumerations.status(this.loanStatus));
@@ -1186,8 +1169,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (isRepaymentScheduleRegenerationRequiredForDisbursement(disbursedOn)) { if (isRepaymentScheduleRegenerationRequiredForDisbursement(disbursedOn)) {
regenerateRepaymentSchedule(currency); regenerateRepaymentSchedule(currency);
} }
existingTransactionIds.addAll(findExistingTransactionIds());
} }
return actualChanges; return actualChanges;
@@ -1276,7 +1257,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
public Map<String, Object> undoDisbursal(final LoanLifecycleStateMachine loanLifecycleStateMachine, public Map<String, Object> undoDisbursal(final LoanLifecycleStateMachine loanLifecycleStateMachine,
final List<Long> existingTransactionIds) { final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>();
@@ -1291,8 +1272,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
actualChanges.put("disbursedOnDate", ""); actualChanges.put("disbursedOnDate", "");
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
contraExistingTransactions(); reverseExistingTransactions();
updateLoanToPreDisbursalState(); updateLoanToPreDisbursalState();
} }
@@ -1300,20 +1282,13 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return actualChanges; return actualChanges;
} }
private final void contraExistingTransactions() { private final void reverseExistingTransactions() {
// final List<LoanTransaction> contras = new
// ArrayList<LoanTransaction>();
for (LoanTransaction transaction : this.loanTransactions) { for (LoanTransaction transaction : this.loanTransactions) {
if (transaction.isNotContra()) { transaction.reverse();
LoanTransaction contra = transaction.contra();
// contras.add(contra);
} }
} }
// this.loanTransactions.addAll(contras);
}
private void updateLoanToPreDisbursalState() { private void updateLoanToPreDisbursalState() {
this.disbursedOnDate = null; this.disbursedOnDate = null;
@@ -1415,7 +1390,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() { private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() {
List<LoanTransaction> repaymentsOrWaivers = new ArrayList<LoanTransaction>(); List<LoanTransaction> repaymentsOrWaivers = new ArrayList<LoanTransaction>();
for (LoanTransaction transaction : this.loanTransactions) { for (LoanTransaction transaction : this.loanTransactions) {
if (!transaction.isDisbursement() && transaction.isNotContra()) { if (!transaction.isDisbursement() && transaction.isNotReversed()) {
repaymentsOrWaivers.add(transaction); repaymentsOrWaivers.add(transaction);
} }
} }
@@ -1462,7 +1437,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
for (LoanTransaction previousTransaction : loanTransactions) { for (LoanTransaction previousTransaction : loanTransactions) {
if (!previousTransaction.isDisbursement() && previousTransaction.isNotContra()) { if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed()) {
if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate()) if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())
|| currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) { || currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) {
isChronologicallyLatestRepaymentOrWaiver = false; isChronologicallyLatestRepaymentOrWaiver = false;
@@ -1480,7 +1455,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
LocalDate currentTransactionDate = loanTransaction.getTransactionDate(); LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
for (LoanTransaction previousTransaction : loanTransactions) { for (LoanTransaction previousTransaction : loanTransactions) {
if (previousTransaction.isNotContra()) { if (previousTransaction.isNotReversed()) {
if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate()) if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())
|| currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) { || currentTransactionDate.isEqual(previousTransaction.getTransactionDate())) {
isChronologicallyLatestRepaymentOrWaiver = false; isChronologicallyLatestRepaymentOrWaiver = false;
@@ -1574,7 +1549,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
throw new InvalidLoanTransactionTypeException("transaction", "adjustment.is.not.allowed.on.closed.loan", errorMessage); throw new InvalidLoanTransactionTypeException("transaction", "adjustment.is.not.allowed.on.closed.loan", errorMessage);
} }
transactionForAdjustment.contra(); transactionForAdjustment.reverse();
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment); handleRepaymentOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment);
} }
@@ -2137,7 +2112,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return getCurrencyCode().equalsIgnoreCase(matchingCurrencyCode); return getCurrencyCode().equalsIgnoreCase(matchingCurrencyCode);
} }
private String getCurrencyCode() { public String getCurrencyCode() {
return this.loanRepaymentScheduleDetail.getPrincipal().getCurrencyCode(); return this.loanRepaymentScheduleDetail.getPrincipal().getCurrencyCode();
} }
@@ -2256,7 +2231,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.loanOfficer; return this.loanOfficer;
} }
public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final List<Long> existingTransactionIds) { public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final Map<String, Object> accountingBridgeData = new LinkedHashMap<String, Object>(); final Map<String, Object> accountingBridgeData = new LinkedHashMap<String, Object>();
accountingBridgeData.put("loanId", this.getId()); accountingBridgeData.put("loanId", this.getId());
@@ -2267,7 +2243,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final List<Map<String, Object>> newLoanTransactions = new ArrayList<Map<String, Object>>(); final List<Map<String, Object>> newLoanTransactions = new ArrayList<Map<String, Object>>();
for (LoanTransaction transaction : this.loanTransactions) { for (LoanTransaction transaction : this.loanTransactions) {
if (!existingTransactionIds.contains(transaction.getId())) { if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) {
newLoanTransactions.add(transaction.toMapData(currencyData));
} else if (!existingTransactionIds.contains(transaction.getId())) {
newLoanTransactions.add(transaction.toMapData(currencyData)); newLoanTransactions.add(transaction.toMapData(currencyData));
} }
} }
@@ -96,14 +96,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.completed = false; this.completed = false;
} }
public LoanRepaymentScheduleInstallment( public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
final Loan loan, final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
final Integer installmentNumber,
final LocalDate fromDate,
final LocalDate dueDate,
final BigDecimal principal,
final BigDecimal interest,
final BigDecimal feeCharges,
final BigDecimal penaltyCharges) { final BigDecimal penaltyCharges) {
this.loan = loan; this.loan = loan;
this.installmentNumber = installmentNumber; this.installmentNumber = installmentNumber;
@@ -179,7 +173,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
} }
public Money getInterestOutstanding(final MonetaryCurrency currency) { public Money getInterestOutstanding(final MonetaryCurrency currency) {
final Money interestAccountedFor = getInterestCompleted(currency).plus(getInterestWaived(currency)).plus(getInterestWrittenOff(currency)); final Money interestAccountedFor = getInterestCompleted(currency).plus(getInterestWaived(currency)).plus(
getInterestWrittenOff(currency));
return getInterest(currency).minus(interestAccountedFor); return getInterest(currency).minus(interestAccountedFor);
} }
@@ -200,7 +195,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
} }
public Money getFeeChargesOutstanding(final MonetaryCurrency currency) { public Money getFeeChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getFeeChargesCompleted(currency).plus(getFeeChargesWaived(currency)).plus(getFeeChargesWrittenOff(currency)); final Money feeChargesAccountedFor = getFeeChargesCompleted(currency).plus(getFeeChargesWaived(currency)).plus(
getFeeChargesWrittenOff(currency));
return getFeeCharges(currency).minus(feeChargesAccountedFor); return getFeeCharges(currency).minus(feeChargesAccountedFor);
} }
@@ -221,7 +217,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
} }
public Money getPenaltyChargesOutstanding(final MonetaryCurrency currency) { public Money getPenaltyChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getPenaltyChargesCompleted(currency).plus(getPenaltyChargesWaived(currency)).plus(getPenaltyChargesWrittenOff(currency)); final Money feeChargesAccountedFor = getPenaltyChargesCompleted(currency).plus(getPenaltyChargesWaived(currency)).plus(
getPenaltyChargesWrittenOff(currency));
return getPenaltyCharges(currency).minus(feeChargesAccountedFor); return getPenaltyCharges(currency).minus(feeChargesAccountedFor);
} }
@@ -234,7 +231,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
} }
public Money getTotalOutstanding(final MonetaryCurrency currency) { public Money getTotalOutstanding(final MonetaryCurrency currency) {
return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)).plus(getFeeChargesOutstanding(currency)).plus(getPenaltyChargesOutstanding(currency)); return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)).plus(getFeeChargesOutstanding(currency))
.plus(getPenaltyChargesOutstanding(currency));
} }
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
@@ -439,13 +437,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return this.getDueDate().isBefore(transactionDate); return this.getDueDate().isBefore(transactionDate);
} }
public void updateChargePortion( public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
final Money feeChargesDue, final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
final Money feeChargesWaived,
final Money feeChargesWrittenOff,
final Money penaltyChargesDue,
final Money penaltyChargesWaived,
final Money penaltyChargesWrittenOff) {
this.feeCharges = defaultToNullIfZero(feeChargesDue.getAmount()); this.feeCharges = defaultToNullIfZero(feeChargesDue.getAmount());
this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount()); this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount());
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount()); this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount());
@@ -10,12 +10,10 @@ import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
@@ -36,7 +34,7 @@ import org.mifosplatform.useradministration.domain.AppUser;
*/ */
@Entity @Entity
@Table(name = "m_loan_transaction") @Table(name = "m_loan_transaction")
public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> { public final class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
@ManyToOne(optional = false) @ManyToOne(optional = false)
@JoinColumn(name = "loan_id", nullable = false) @JoinColumn(name = "loan_id", nullable = false)
@@ -64,9 +62,8 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "transaction_type_enum", nullable = false) @Column(name = "transaction_type_enum", nullable = false)
private final Integer typeOf; private final Integer typeOf;
@OneToOne(optional = true, cascade = { CascadeType.PERSIST }) @Column(name = "is_reversed", nullable = false)
@JoinColumn(name = "contra_id") private boolean reversed;
private LoanTransaction contra;
protected LoanTransaction() { protected LoanTransaction() {
this.loan = null; this.loan = null;
@@ -98,21 +95,10 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
return waiver; return waiver;
} }
private static LoanTransaction contra(final LoanTransaction originalTransaction) {
LoanTransaction contra = new LoanTransaction(null, LoanTransactionType.CONTRA, originalTransaction.getAmount().negate(),
new LocalDate(originalTransaction.getDateOf()));
contra.updateContra(originalTransaction);
return contra;
}
public static LoanTransaction writeoff(final Loan loan, final LocalDate writeOffDate) { public static LoanTransaction writeoff(final Loan loan, final LocalDate writeOffDate) {
return new LoanTransaction(loan, LoanTransactionType.WRITEOFF, null, writeOffDate); return new LoanTransaction(loan, LoanTransactionType.WRITEOFF, null, writeOffDate);
} }
public void updateContra(final LoanTransaction transaction) {
this.contra = transaction;
}
private LoanTransaction(final Loan loan, final LoanTransactionType type, final BigDecimal amount, final LocalDate date) { private LoanTransaction(final Loan loan, final LoanTransactionType type, final BigDecimal amount, final LocalDate date) {
this.loan = loan; this.loan = loan;
this.typeOf = type.getValue(); this.typeOf = type.getValue();
@@ -120,108 +106,21 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
this.dateOf = date.toDateMidnight().toDate(); this.dateOf = date.toDateMidnight().toDate();
} }
public BigDecimal getAmount() { public void reverse() {
return this.amount; this.reversed = true;
} }
public Money getAmount(final MonetaryCurrency currency) { public void resetDerivedComponents() {
return Money.of(currency, this.amount); this.principalPortion = null;
this.interestPortion = null;
this.feeChargesPortion = null;
this.penaltyChargesPortion = null;
} }
public LocalDate getTransactionDate() {
return new LocalDate(this.dateOf);
}
public Date getDateOf() {
return dateOf;
}
public LoanTransactionType getTypeOf() {
return LoanTransactionType.fromInt(this.typeOf);
}
public boolean isRepayment() {
return LoanTransactionType.REPAYMENT.equals(getTypeOf()) && isNotContra();
}
public boolean isNotRepayment() {
return !isRepayment();
}
public boolean isNotContra() {
return this.contra == null;
}
public boolean isContra() {
return this.contra != null;
}
public boolean isDisbursement() {
return LoanTransactionType.DISBURSEMENT.equals(getTypeOf());
}
public boolean isRepaymentAtDisbursement() {
return LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(getTypeOf());
}
public boolean isInterestWaiver() {
return LoanTransactionType.WAIVE_INTEREST.equals(getTypeOf()) && isNotContra();
}
public boolean isChargesWaiver() {
return LoanTransactionType.WAIVE_CHARGES.equals(getTypeOf()) && isNotContra();
}
public boolean isNotInterestWaiver() {
return !isInterestWaiver();
}
public boolean isWaiver() {
return isInterestWaiver() || isChargesWaiver();
}
public boolean isNotWaiver() {
return !isInterestWaiver() && !isChargesWaiver();
}
public boolean isWriteOff() {
return getTypeOf().isWriteOff() && isNotContra();
}
public boolean isIdentifiedBy(final Long identifier) {
return this.getId().equals(identifier);
}
public boolean isBelongingToLoanOf(final Loan check) {
return this.loan.getId().equals(check.getId());
}
public boolean isNotBelongingToLoanOf(final Loan check) {
return !isBelongingToLoanOf(check);
}
public LoanTransaction contra() {
this.contra = LoanTransaction.contra(this);
contra.updateLoan(this.loan);
return this.contra;
}
// public LoanTransaction newContra() {
// // should update some flag old entry being contra'd
// LoanTransaction contra = LoanTransaction.contra(this);
// contra.updateLoan(this.loan);
// return contra;
// }
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
this.loan = loan; this.loan = loan;
} }
public boolean isNonZero() {
return this.amount.subtract(BigDecimal.ZERO).doubleValue() > 0;
}
/** /**
* This updates the derived fields of a loan transaction for the principal, * This updates the derived fields of a loan transaction for the principal,
* interest and interest waived portions. * interest and interest waived portions.
@@ -282,11 +181,92 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
return this.penaltyChargesPortion; return this.penaltyChargesPortion;
} }
public void resetDerivedComponents() { public BigDecimal getAmount() {
this.principalPortion = null; return this.amount;
this.interestPortion = null; }
this.feeChargesPortion = null;
this.penaltyChargesPortion = null; public Money getAmount(final MonetaryCurrency currency) {
return Money.of(currency, this.amount);
}
public LocalDate getTransactionDate() {
return new LocalDate(this.dateOf);
}
public Date getDateOf() {
return dateOf;
}
public LoanTransactionType getTypeOf() {
return LoanTransactionType.fromInt(this.typeOf);
}
public boolean isReversed() {
return this.reversed;
}
public boolean isNotReversed() {
return !isReversed();
}
public boolean isRepayment() {
return LoanTransactionType.REPAYMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotRepayment() {
return !isRepayment();
}
public boolean isDisbursement() {
return LoanTransactionType.DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isRepaymentAtDisbursement() {
return LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
}
private boolean isRecoveryRepayment() {
return LoanTransactionType.RECOVERY_REPAYMENT.equals(getTypeOf()) && isNotReversed();
}
public boolean isInterestWaiver() {
return LoanTransactionType.WAIVE_INTEREST.equals(getTypeOf()) && isNotReversed();
}
public boolean isChargesWaiver() {
return LoanTransactionType.WAIVE_CHARGES.equals(getTypeOf()) && isNotReversed();
}
public boolean isNotInterestWaiver() {
return !isInterestWaiver();
}
public boolean isWaiver() {
return isInterestWaiver() || isChargesWaiver();
}
public boolean isNotWaiver() {
return !isInterestWaiver() && !isChargesWaiver();
}
public boolean isWriteOff() {
return getTypeOf().isWriteOff() && isNotReversed();
}
public boolean isIdentifiedBy(final Long identifier) {
return this.getId().equals(identifier);
}
public boolean isBelongingToLoanOf(final Loan check) {
return this.loan.getId().equals(check.getId());
}
public boolean isNotBelongingToLoanOf(final Loan check) {
return !isBelongingToLoanOf(check);
}
public boolean isNonZero() {
return this.amount.subtract(BigDecimal.ZERO).doubleValue() > 0;
} }
public boolean isGreaterThan(final Money monetaryAmount) { public boolean isGreaterThan(final Money monetaryAmount) {
@@ -318,19 +298,18 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
public Map<String, Object> toMapData(final CurrencyData currencyData) { public Map<String, Object> toMapData(final CurrencyData currencyData) {
final Map<String, Object> thisTransactionData = new LinkedHashMap<String, Object>(); final Map<String, Object> thisTransactionData = new LinkedHashMap<String, Object>();
LoanTransactionType type = LoanTransactionType.fromInt(this.typeOf);
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(this.typeOf); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(this.typeOf);
thisTransactionData.put("id", this.getId()); thisTransactionData.put("id", this.getId());
thisTransactionData.put("type", transactionType); thisTransactionData.put("type", transactionType);
thisTransactionData.put("disbursement", type.isDisbursement()); thisTransactionData.put("disbursement", Boolean.valueOf(this.isDisbursement()));
thisTransactionData.put("repaymentAtDisbursement", type.isRepaymentAtDisbursement()); thisTransactionData.put("repaymentAtDisbursement", Boolean.valueOf(this.isRepaymentAtDisbursement()));
thisTransactionData.put("repayment", type.isRepayment()); thisTransactionData.put("repayment", Boolean.valueOf(this.isRepayment()));
thisTransactionData.put("recoveryRepayment", type.isRecoveryRepayment()); thisTransactionData.put("recoveryRepayment", Boolean.valueOf(this.isRecoveryRepayment()));
thisTransactionData.put("contra", type.isContra()); thisTransactionData.put("contra", Boolean.valueOf(this.isReversed()));
thisTransactionData.put("waiveInterest", type.isWaiveInterest()); thisTransactionData.put("waiveInterest", Boolean.valueOf(this.isInterestWaiver()));
thisTransactionData.put("waiveCharges", type.isWaiveCharges()); thisTransactionData.put("waiveCharges", Boolean.valueOf(this.isChargesWaiver()));
thisTransactionData.put("writeOff", type.isWriteOff()); thisTransactionData.put("writeOff", Boolean.valueOf(this.isWriteOff()));
thisTransactionData.put("date", this.getTransactionDate()); thisTransactionData.put("date", this.getTransactionDate());
thisTransactionData.put("currency", currencyData); thisTransactionData.put("currency", currencyData);
@@ -77,35 +77,31 @@ public enum LoanTransactionType {
return loanTransactionType; return loanTransactionType;
} }
public Boolean isDisbursement() { public boolean isDisbursement() {
return this.value.equals(LoanTransactionType.DISBURSEMENT.getValue()); return this.value.equals(LoanTransactionType.DISBURSEMENT.getValue());
} }
public Boolean isRepaymentAtDisbursement() { public boolean isRepaymentAtDisbursement() {
return this.value.equals(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue()); return this.value.equals(LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue());
} }
public Boolean isRepayment() { public boolean isRepayment() {
return this.value.equals(LoanTransactionType.REPAYMENT.getValue()); return this.value.equals(LoanTransactionType.REPAYMENT.getValue());
} }
public Boolean isRecoveryRepayment() { public boolean isRecoveryRepayment() {
return this.value.equals(LoanTransactionType.RECOVERY_REPAYMENT.getValue()); return this.value.equals(LoanTransactionType.RECOVERY_REPAYMENT.getValue());
} }
public Boolean isContra() { public boolean isWaiveInterest() {
return this.value.equals(LoanTransactionType.CONTRA.getValue());
}
public Boolean isWaiveInterest() {
return this.value.equals(LoanTransactionType.WAIVE_INTEREST.getValue()); return this.value.equals(LoanTransactionType.WAIVE_INTEREST.getValue());
} }
public Boolean isWaiveCharges() { public boolean isWaiveCharges() {
return this.value.equals(LoanTransactionType.WAIVE_CHARGES.getValue()); return this.value.equals(LoanTransactionType.WAIVE_CHARGES.getValue());
} }
public Boolean isWriteOff() { public boolean isWriteOff() {
return this.value.equals(LoanTransactionType.WRITEOFF.getValue()); return this.value.equals(LoanTransactionType.WRITEOFF.getValue());
} }
} }
@@ -12,7 +12,7 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceN
*/ */
public class LoanNotFoundException extends AbstractPlatformResourceNotFoundException { public class LoanNotFoundException extends AbstractPlatformResourceNotFoundException {
public LoanNotFoundException(Long id) { public LoanNotFoundException(final Long id) {
super("error.msg.loan.id.invalid", "Loan with identifier " + id + " does not exist", id); super("error.msg.loan.id.invalid", "Loan with identifier " + id + " does not exist", id);
} }
} }
@@ -200,7 +200,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
// repayments at time of disbursement (e.g. charges) // repayments at time of disbursement (e.g. charges)
String sql = "select " String sql = "select "
+ rm.LoanPaymentsSchema() + rm.LoanPaymentsSchema()
+ " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and tr.contra_id is null order by tr.transaction_date ASC"; + " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and tr.is_reversed=0 order by tr.transaction_date ASC";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
} catch (EmptyResultDataAccessException e) { } catch (EmptyResultDataAccessException e) {
return null; return null;
@@ -130,13 +130,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getPrincpal().getCurrencyCode()); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getPrincpal().getCurrencyCode());
/***
* In cases where an "Undo disbursement is done and the loan is
* disbursed again, we need to ensure the old transactions are entered
* again in accounting
***/
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = loan.disburse(currentUser, command, defaultLoanLifecycleStateMachine(), currency, existingTransactionIds); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = loan.disburse(currentUser, command, defaultLoanLifecycleStateMachine(), currency,
existingTransactionIds, existingReversedTransactionIds);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -145,7 +142,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
final Map<String, Object> accountingBridgeData = loan.deriveDisbursementData(currency.toData(), existingTransactionIds); final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
} }
@@ -169,7 +167,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = loan.undoDisbursal(defaultLoanLifecycleStateMachine(), existingTransactionIds); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final Map<String, Object> changes = loan.undoDisbursal(defaultLoanLifecycleStateMachine(), existingTransactionIds,
existingReversedTransactionIds);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
this.loanRepository.saveAndFlush(loan); this.loanRepository.saveAndFlush(loan);
@@ -179,10 +179,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
final Loan loanRefetch = retrieveLoanBy(loanId); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getPrincpal().getCurrencyCode()); existingReversedTransactionIds);
final Map<String, Object> accountingBridgeData = loanRefetch.deriveAccountingBridgeData(currency.toData(), existingTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
} }
@@ -736,7 +735,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LoanTransactionDTO loanTransactionDTO = new LoanTransactionDTO(loanTransaction.getId().toString(), loanTransaction.getDateOf(), LoanTransactionDTO loanTransactionDTO = new LoanTransactionDTO(loanTransaction.getId().toString(), loanTransaction.getDateOf(),
loanTransaction.getAmount(), loanTransaction.getPrincipalPortion(), loanTransaction.getInterestPortion(), loanTransaction.getAmount(), loanTransaction.getPrincipalPortion(), loanTransaction.getInterestPortion(),
loanTransaction.getFeePortion(), loanTransaction.getPenaltyChargesPortion(), loanTransaction.isDisbursement(), loanTransaction.getFeePortion(), loanTransaction.getPenaltyChargesPortion(), loanTransaction.isDisbursement(),
loanTransaction.isRepayment(), loanTransaction.isRepaymentAtDisbursement(), loanTransaction.isContra(), loanTransaction.isRepayment(), loanTransaction.isRepaymentAtDisbursement(), loanTransaction.isReversed(),
loanTransaction.isWriteOff()); loanTransaction.isWriteOff());
loanTransactionDTOs.add(loanTransactionDTO); loanTransactionDTOs.add(loanTransactionDTO);
} }