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,18 +1282,11 @@ 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() {
@@ -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));
} }
} }
@@ -28,82 +28,76 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
@ManyToOne(optional = false) @ManyToOne(optional = false)
@JoinColumn(name = "loan_id") @JoinColumn(name = "loan_id")
private Loan loan; private Loan loan;
@Column(name = "installment", nullable=false) @Column(name = "installment", nullable = false)
private final Integer installmentNumber; private final Integer installmentNumber;
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "fromdate", nullable=true) @Column(name = "fromdate", nullable = true)
private final Date fromDate; private final Date fromDate;
@Temporal(TemporalType.DATE) @Temporal(TemporalType.DATE)
@Column(name = "duedate", nullable=false) @Column(name = "duedate", nullable = false)
private final Date dueDate; private final Date dueDate;
@Column(name = "principal_amount", scale=6, precision=19, nullable=true) @Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal principal; private BigDecimal principal;
@Column(name = "principal_completed_derived", scale=6, precision=19, nullable=true) @Column(name = "principal_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalCompleted; private BigDecimal principalCompleted;
@Column(name = "principal_writtenoff_derived", scale=6, precision=19, nullable=true) @Column(name = "principal_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalWrittenOff; private BigDecimal principalWrittenOff;
@Column(name = "interest_amount", scale=6, precision=19, nullable=true) @Column(name = "interest_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal interest; private BigDecimal interest;
@Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable=true) @Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestCompleted; private BigDecimal interestCompleted;
@Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable=true) @Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWaived; private BigDecimal interestWaived;
@Column(name = "interest_writtenoff_derived", scale=6, precision=19, nullable=true) @Column(name = "interest_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestWrittenOff; private BigDecimal interestWrittenOff;
@Column(name = "fee_charges_amount", scale=6, precision=19, nullable=true) @Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal feeCharges; private BigDecimal feeCharges;
@Column(name = "fee_charges_completed_derived", scale=6, precision=19, nullable=true) @Column(name = "fee_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesCompleted; private BigDecimal feeChargesCompleted;
@Column(name = "fee_charges_writtenoff_derived", scale=6, precision=19, nullable=true) @Column(name = "fee_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWrittenOff; private BigDecimal feeChargesWrittenOff;
@Column(name = "fee_charges_waived_derived", scale=6, precision=19, nullable=true) @Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWaived; private BigDecimal feeChargesWaived;
@Column(name = "penalty_charges_amount", scale=6, precision=19, nullable=true) @Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyCharges; private BigDecimal penaltyCharges;
@Column(name = "penalty_charges_completed_derived", scale=6, precision=19, nullable=true) @Column(name = "penalty_charges_completed_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesCompleted; private BigDecimal penaltyChargesCompleted;
@Column(name = "penalty_charges_writtenoff_derived", scale=6, precision=19, nullable=true) @Column(name = "penalty_charges_writtenoff_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWrittenOff; private BigDecimal penaltyChargesWrittenOff;
@Column(name = "penalty_charges_waived_derived", scale=6, precision=19, nullable=true) @Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWaived; private BigDecimal penaltyChargesWaived;
@Column(name="completed_derived", nullable=false) @Column(name = "completed_derived", nullable = false)
private boolean completed; private boolean completed;
protected LoanRepaymentScheduleInstallment() { protected LoanRepaymentScheduleInstallment() {
this.installmentNumber = null; this.installmentNumber = null;
this.fromDate = null; this.fromDate = null;
this.dueDate = null; this.dueDate = null;
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;
@@ -116,15 +110,15 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.completed = false; this.completed = false;
} }
private BigDecimal defaultToNullIfZero(final BigDecimal value) { private BigDecimal defaultToNullIfZero(final BigDecimal value) {
BigDecimal result = value; BigDecimal result = value;
if (BigDecimal.ZERO.compareTo(value) == 0) { if (BigDecimal.ZERO.compareTo(value) == 0) {
result = null; result = null;
} }
return result; return result;
} }
public Loan getLoan() { public Loan getLoan() {
return this.loan; return this.loan;
} }
@@ -133,324 +127,323 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
} }
public LocalDate getFromDate() { public LocalDate getFromDate() {
LocalDate fromLocalDate = null; LocalDate fromLocalDate = null;
if (this.fromDate != null) { if (this.fromDate != null) {
fromLocalDate = new LocalDate(this.fromDate); fromLocalDate = new LocalDate(this.fromDate);
} }
return fromLocalDate; return fromLocalDate;
} }
public LocalDate getDueDate() { public LocalDate getDueDate() {
return new LocalDate(this.dueDate); return new LocalDate(this.dueDate);
} }
public Money getPrincipal(final MonetaryCurrency currency) { public Money getPrincipal(final MonetaryCurrency currency) {
return Money.of(currency, this.principal); return Money.of(currency, this.principal);
} }
public Money getPrincipalCompleted(final MonetaryCurrency currency) { public Money getPrincipalCompleted(final MonetaryCurrency currency) {
return Money.of(currency, this.principalCompleted); return Money.of(currency, this.principalCompleted);
} }
public Money getPrincipalWrittenOff(final MonetaryCurrency currency) { public Money getPrincipalWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.principalWrittenOff); return Money.of(currency, this.principalWrittenOff);
} }
public Money getPrincipalOutstanding(final MonetaryCurrency currency) { public Money getPrincipalOutstanding(final MonetaryCurrency currency) {
final Money principalAccountedFor = getPrincipalCompleted(currency).plus(getPrincipalWrittenOff(currency)); final Money principalAccountedFor = getPrincipalCompleted(currency).plus(getPrincipalWrittenOff(currency));
return getPrincipal(currency).minus(principalAccountedFor); return getPrincipal(currency).minus(principalAccountedFor);
} }
public Money getInterest(final MonetaryCurrency currency) { public Money getInterest(final MonetaryCurrency currency) {
return Money.of(currency, this.interest); return Money.of(currency, this.interest);
} }
public Money getInterestCompleted(final MonetaryCurrency currency) { public Money getInterestCompleted(final MonetaryCurrency currency) {
return Money.of(currency, this.interestCompleted); return Money.of(currency, this.interestCompleted);
} }
public Money getInterestWaived(final MonetaryCurrency currency) { public Money getInterestWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.interestWaived); return Money.of(currency, this.interestWaived);
} }
public Money getInterestWrittenOff(final MonetaryCurrency currency) { public Money getInterestWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.interestWrittenOff); return Money.of(currency, this.interestWrittenOff);
} }
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(
return getInterest(currency).minus(interestAccountedFor); getInterestWrittenOff(currency));
} return getInterest(currency).minus(interestAccountedFor);
}
public Money getFeeCharges(final MonetaryCurrency currency) {
return Money.of(currency, this.feeCharges); public Money getFeeCharges(final MonetaryCurrency currency) {
} return Money.of(currency, this.feeCharges);
}
public Money getFeeChargesCompleted(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesCompleted); public Money getFeeChargesCompleted(final MonetaryCurrency currency) {
} return Money.of(currency, this.feeChargesCompleted);
}
public Money getFeeChargesWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesWaived); public Money getFeeChargesWaived(final MonetaryCurrency currency) {
} return Money.of(currency, this.feeChargesWaived);
}
public Money getFeeChargesWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesWrittenOff); public Money getFeeChargesWrittenOff(final MonetaryCurrency currency) {
} return Money.of(currency, this.feeChargesWrittenOff);
}
public Money getFeeChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getFeeChargesCompleted(currency).plus(getFeeChargesWaived(currency)).plus(getFeeChargesWrittenOff(currency)); public Money getFeeChargesOutstanding(final MonetaryCurrency currency) {
return getFeeCharges(currency).minus(feeChargesAccountedFor); final Money feeChargesAccountedFor = getFeeChargesCompleted(currency).plus(getFeeChargesWaived(currency)).plus(
} getFeeChargesWrittenOff(currency));
return getFeeCharges(currency).minus(feeChargesAccountedFor);
public Money getPenaltyCharges(final MonetaryCurrency currency) { }
return Money.of(currency, this.penaltyCharges);
} public Money getPenaltyCharges(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyCharges);
public Money getPenaltyChargesCompleted(final MonetaryCurrency currency) { }
return Money.of(currency, this.penaltyChargesCompleted);
} public Money getPenaltyChargesCompleted(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesCompleted);
public Money getPenaltyChargesWaived(final MonetaryCurrency currency) { }
return Money.of(currency, this.penaltyChargesWaived);
} public Money getPenaltyChargesWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesWaived);
public Money getPenaltyChargesWrittenOff(final MonetaryCurrency currency) { }
return Money.of(currency, this.penaltyChargesWrittenOff);
} public Money getPenaltyChargesWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesWrittenOff);
}
public Money getPenaltyChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getPenaltyChargesCompleted(currency).plus(getPenaltyChargesWaived(currency)).plus(
getPenaltyChargesWrittenOff(currency));
return getPenaltyCharges(currency).minus(feeChargesAccountedFor);
}
public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterestOutstanding(currency).isGreaterThanZero();
}
public Money getTotalPrincipalAndInterest(final MonetaryCurrency currency) {
return getPrincipal(currency).plus(getInterest(currency));
}
public Money getTotalOutstanding(final MonetaryCurrency currency) {
return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)).plus(getFeeChargesOutstanding(currency))
.plus(getPenaltyChargesOutstanding(currency));
}
public void updateLoan(final Loan loan) {
this.loan = loan;
}
public boolean isFullyCompleted() {
return this.completed;
}
public boolean isNotFullyCompleted() {
return !this.completed;
}
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
return !isPrincipalCompleted(currency);
}
public boolean isPrincipalCompleted(final MonetaryCurrency currency) {
return getPrincipalOutstanding(currency).isZero();
}
public Money getPenaltyChargesOutstanding(final MonetaryCurrency currency) { public void resetDerivedComponents() {
final Money feeChargesAccountedFor = getPenaltyChargesCompleted(currency).plus(getPenaltyChargesWaived(currency)).plus(getPenaltyChargesWrittenOff(currency)); this.principalCompleted = null;
return getPenaltyCharges(currency).minus(feeChargesAccountedFor); this.principalWrittenOff = null;
} this.interestCompleted = null;
this.interestWaived = null;
public boolean isInterestDue(final MonetaryCurrency currency) { this.interestWrittenOff = null;
return getInterestOutstanding(currency).isGreaterThanZero(); this.feeChargesCompleted = null;
} this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
public Money getTotalPrincipalAndInterest(final MonetaryCurrency currency) { this.penaltyChargesCompleted = null;
return getPrincipal(currency).plus(getInterest(currency)); this.penaltyChargesWaived = null;
} this.penaltyChargesWrittenOff = null;
public Money getTotalOutstanding(final MonetaryCurrency currency) { this.completed = false;
return getPrincipalOutstanding(currency).plus(getInterestOutstanding(currency)).plus(getFeeChargesOutstanding(currency)).plus(getPenaltyChargesOutstanding(currency)); }
}
public Money payPenaltyChargesComponent(final Money transactionAmountRemaining) {
public void updateLoan(final Loan loan) {
this.loan = loan; final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
} Money penaltyPortionOfTransaction = Money.zero(currency);
public boolean isFullyCompleted() { final Money penaltyChargesDue = getPenaltyChargesOutstanding(currency);
return this.completed; if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesDue)) {
} this.penaltyChargesCompleted = getPenaltyChargesCompleted(currency).plus(penaltyChargesDue).getAmount();
penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(penaltyChargesDue);
public boolean isNotFullyCompleted() { } else {
return !this.completed; this.penaltyChargesCompleted = getPenaltyChargesCompleted(currency).plus(transactionAmountRemaining).getAmount();
} penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(transactionAmountRemaining);
}
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
return !isPrincipalCompleted(currency); this.penaltyChargesCompleted = defaultToNullIfZero(this.penaltyChargesCompleted);
}
this.completed = getTotalOutstanding(currency).isZero();
return penaltyPortionOfTransaction;
}
public boolean isPrincipalCompleted(final MonetaryCurrency currency) { public Money payFeeChargesComponent(final Money transactionAmountRemaining) {
return getPrincipalOutstanding(currency).isZero();
}
public void resetDerivedComponents() { final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
this.principalCompleted = null; Money feePortionOfTransaction = Money.zero(currency);
this.principalWrittenOff = null;
this.interestCompleted = null;
this.interestWaived = null;
this.interestWrittenOff = null;
this.feeChargesCompleted = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.penaltyChargesCompleted = null;
this.penaltyChargesWaived = null;
this.penaltyChargesWrittenOff = null;
this.completed = false; final Money feeChargesDue = getFeeChargesOutstanding(currency);
} if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesDue)) {
this.feeChargesCompleted = getFeeChargesCompleted(currency).plus(feeChargesDue).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(feeChargesDue);
} else {
this.feeChargesCompleted = getFeeChargesCompleted(currency).plus(transactionAmountRemaining).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(transactionAmountRemaining);
}
this.feeChargesCompleted = defaultToNullIfZero(this.feeChargesCompleted);
public Money payPenaltyChargesComponent(final Money transactionAmountRemaining) { this.completed = getTotalOutstanding(currency).isZero();
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); return feePortionOfTransaction;
Money penaltyPortionOfTransaction = Money.zero(currency); }
final Money penaltyChargesDue = getPenaltyChargesOutstanding(currency); public Money payInterestComponent(final Money transactionAmountRemaining) {
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesDue)) {
this.penaltyChargesCompleted = getPenaltyChargesCompleted(currency).plus(penaltyChargesDue).getAmount();
penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(penaltyChargesDue);
} else {
this.penaltyChargesCompleted = getPenaltyChargesCompleted(currency).plus(transactionAmountRemaining).getAmount();
penaltyPortionOfTransaction = penaltyPortionOfTransaction.plus(transactionAmountRemaining);
}
this.penaltyChargesCompleted = defaultToNullIfZero(this.penaltyChargesCompleted); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money interestPortionOfTransaction = Money.zero(currency);
this.completed = getTotalOutstanding(currency).isZero();
return penaltyPortionOfTransaction; final Money interestDue = getInterestOutstanding(currency);
} if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestCompleted = getInterestCompleted(currency).plus(interestDue).getAmount();
interestPortionOfTransaction = interestPortionOfTransaction.plus(interestDue);
} else {
this.interestCompleted = getInterestCompleted(currency).plus(transactionAmountRemaining).getAmount();
interestPortionOfTransaction = interestPortionOfTransaction.plus(transactionAmountRemaining);
}
public Money payFeeChargesComponent(final Money transactionAmountRemaining) { this.interestCompleted = defaultToNullIfZero(this.interestCompleted);
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); this.completed = getTotalOutstanding(currency).isZero();
Money feePortionOfTransaction = Money.zero(currency);
final Money feeChargesDue = getFeeChargesOutstanding(currency); return interestPortionOfTransaction;
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesDue)) { }
this.feeChargesCompleted = getFeeChargesCompleted(currency).plus(feeChargesDue).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(feeChargesDue);
} else {
this.feeChargesCompleted = getFeeChargesCompleted(currency).plus(transactionAmountRemaining).getAmount();
feePortionOfTransaction = feePortionOfTransaction.plus(transactionAmountRemaining);
}
this.feeChargesCompleted = defaultToNullIfZero(this.feeChargesCompleted); public Money payPrincipalComponent(final Money transactionAmountRemaining) {
this.completed = getTotalOutstanding(currency).isZero(); final MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money principalPortionOfTransaction = Money.zero(currency);
return feePortionOfTransaction; final Money principalDue = getPrincipalOutstanding(currency);
} if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) {
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
} else {
this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmountRemaining).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining);
}
public Money payInterestComponent(final Money transactionAmountRemaining) { this.principalCompleted = defaultToNullIfZero(this.principalCompleted);
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); this.completed = getTotalOutstanding(currency).isZero();
Money interestPortionOfTransaction = Money.zero(currency);
final Money interestDue = getInterestOutstanding(currency); return principalPortionOfTransaction;
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) { }
this.interestCompleted = getInterestCompleted(currency).plus(interestDue).getAmount();
interestPortionOfTransaction = interestPortionOfTransaction.plus(interestDue);
} else {
this.interestCompleted = getInterestCompleted(currency).plus(transactionAmountRemaining).getAmount();
interestPortionOfTransaction = interestPortionOfTransaction.plus(transactionAmountRemaining);
}
this.interestCompleted = defaultToNullIfZero(this.interestCompleted); public Money waiveInterestComponent(final Money transactionAmountRemaining) {
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedInterestPortionOfTransaction = Money.zero(currency);
this.completed = getTotalOutstanding(currency).isZero(); Money interestDue = getInterestOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) {
this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount();
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(interestDue);
} else {
this.interestWaived = getInterestWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(transactionAmountRemaining);
}
return interestPortionOfTransaction; this.interestWaived = defaultToNullIfZero(this.interestWaived);
}
public Money payPrincipalComponent(final Money transactionAmountRemaining) { this.completed = getTotalOutstanding(currency).isZero();
final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); return waivedInterestPortionOfTransaction;
Money principalPortionOfTransaction = Money.zero(currency); }
final Money principalDue = getPrincipalOutstanding(currency); public Money waivePenaltyChargesComponent(final Money transactionAmountRemaining) {
if (transactionAmountRemaining.isGreaterThanOrEqualTo(principalDue)) { MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
this.principalCompleted = getPrincipalCompleted(currency).plus(principalDue).getAmount(); Money waivedPenaltyChargesPortionOfTransaction = Money.zero(currency);
principalPortionOfTransaction = principalPortionOfTransaction.plus(principalDue);
} else {
this.principalCompleted = getPrincipalCompleted(currency).plus(transactionAmountRemaining).getAmount();
principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining);
}
this.principalCompleted = defaultToNullIfZero(this.principalCompleted); Money penanltiesDue = getPenaltyChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penanltiesDue)) {
this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(penanltiesDue).getAmount();
waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(penanltiesDue);
} else {
this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(transactionAmountRemaining);
}
this.completed = getTotalOutstanding(currency).isZero(); this.penaltyChargesWaived = defaultToNullIfZero(this.penaltyChargesWaived);
return principalPortionOfTransaction;
}
public Money waiveInterestComponent(final Money transactionAmountRemaining) { this.completed = getTotalOutstanding(currency).isZero();
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedInterestPortionOfTransaction = Money.zero(currency);
Money interestDue = getInterestOutstanding(currency); return waivedPenaltyChargesPortionOfTransaction;
if (transactionAmountRemaining.isGreaterThanOrEqualTo(interestDue)) { }
this.interestWaived = getInterestWaived(currency).plus(interestDue).getAmount();
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(interestDue);
} else {
this.interestWaived = getInterestWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(transactionAmountRemaining);
}
this.interestWaived = defaultToNullIfZero(this.interestWaived); public Money waiveFeeChargesComponent(final Money transactionAmountRemaining) {
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedFeeChargesPortionOfTransaction = Money.zero(currency);
this.completed = getTotalOutstanding(currency).isZero(); Money feesDue = getPenaltyChargesOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) {
this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount();
waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(feesDue);
} else {
this.feeChargesWaived = getFeeChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(transactionAmountRemaining);
}
return waivedInterestPortionOfTransaction; this.feeChargesWaived = defaultToNullIfZero(this.feeChargesWaived);
}
public Money waivePenaltyChargesComponent(final Money transactionAmountRemaining) { this.completed = getTotalOutstanding(currency).isZero();
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedPenaltyChargesPortionOfTransaction = Money.zero(currency);
Money penanltiesDue = getPenaltyChargesOutstanding(currency); return waivedFeeChargesPortionOfTransaction;
if (transactionAmountRemaining.isGreaterThanOrEqualTo(penanltiesDue)) { }
this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(penanltiesDue).getAmount();
waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(penanltiesDue);
} else {
this.penaltyChargesWaived = getPenaltyChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedPenaltyChargesPortionOfTransaction = waivedPenaltyChargesPortionOfTransaction.plus(transactionAmountRemaining);
}
this.penaltyChargesWaived = defaultToNullIfZero(this.penaltyChargesWaived); public Money writeOffOutstandingPrincipal(final MonetaryCurrency currency) {
this.completed = getTotalOutstanding(currency).isZero(); final Money principalDue = getPrincipalOutstanding(currency);
this.principalWrittenOff = defaultToNullIfZero(principalDue.getAmount());
this.completed = getTotalOutstanding(currency).isZero();
return waivedPenaltyChargesPortionOfTransaction; return principalDue;
} }
public Money waiveFeeChargesComponent(final Money transactionAmountRemaining) { public Money writeOffOutstandingInterest(final MonetaryCurrency currency) {
MonetaryCurrency currency = transactionAmountRemaining.getCurrency();
Money waivedFeeChargesPortionOfTransaction = Money.zero(currency);
Money feesDue = getPenaltyChargesOutstanding(currency); final Money interestDue = getInterestOutstanding(currency);
if (transactionAmountRemaining.isGreaterThanOrEqualTo(feesDue)) { this.interestWrittenOff = defaultToNullIfZero(interestDue.getAmount());
this.feeChargesWaived = getFeeChargesWaived(currency).plus(feesDue).getAmount(); this.completed = getTotalOutstanding(currency).isZero();
waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(feesDue);
} else {
this.feeChargesWaived = getFeeChargesWaived(currency).plus(transactionAmountRemaining).getAmount();
waivedFeeChargesPortionOfTransaction = waivedFeeChargesPortionOfTransaction.plus(transactionAmountRemaining);
}
this.feeChargesWaived = defaultToNullIfZero(this.feeChargesWaived); return interestDue;
}
this.completed = getTotalOutstanding(currency).isZero(); public boolean isOverdueOn(final LocalDate transactionDate) {
return this.getDueDate().isBefore(transactionDate);
}
return waivedFeeChargesPortionOfTransaction; public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
} final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
this.feeCharges = defaultToNullIfZero(feeChargesDue.getAmount());
public Money writeOffOutstandingPrincipal(final MonetaryCurrency currency) { this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount());
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount());
final Money principalDue = getPrincipalOutstanding(currency); this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.getAmount());
this.principalWrittenOff = defaultToNullIfZero(principalDue.getAmount()); this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.getAmount());
this.completed = getTotalOutstanding(currency).isZero(); this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount());
}
return principalDue;
}
public Money writeOffOutstandingInterest(final MonetaryCurrency currency) {
final Money interestDue = getInterestOutstanding(currency);
this.interestWrittenOff = defaultToNullIfZero(interestDue.getAmount());
this.completed = getTotalOutstanding(currency).isZero();
return interestDue;
}
public boolean isOverdueOn(final LocalDate transactionDate) {
return this.getDueDate().isBefore(transactionDate);
}
public void updateChargePortion(
final Money feeChargesDue,
final Money feeChargesWaived,
final Money feeChargesWrittenOff,
final Money penaltyChargesDue,
final Money penaltyChargesWaived,
final Money penaltyChargesWrittenOff) {
this.feeCharges = defaultToNullIfZero(feeChargesDue.getAmount());
this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount());
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount());
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.getAmount());
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.getAmount());
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.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);
} }