tracking transactions that repay loan charges
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE `m_loan_charge_paid_by` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`loan_transaction_id` BIGINT(20) NOT NULL,
|
||||
`loan_charge_id` BIGINT(20) NOT NULL,
|
||||
`amount` DECIMAL(19,6) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `FK__m_loan_transaction` (`loan_transaction_id`),
|
||||
INDEX `FK__m_loan_charge` (`loan_charge_id`),
|
||||
CONSTRAINT `FK__m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
|
||||
CONSTRAINT `FK__m_loan_transaction` FOREIGN KEY (`loan_transaction_id`) REFERENCES `m_loan_transaction` (`id`)
|
||||
)
|
||||
COLLATE='utf8_general_ci'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
+19
-6
@@ -133,12 +133,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
if (loanTransaction.isNotWaiver()) {
|
||||
Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
|
||||
if (feeCharges.isGreaterThanZero()) {
|
||||
updateFeeChargesPaidAmountBy(feeCharges, charges);
|
||||
updateFeeChargesPaidAmountBy(loanTransaction, feeCharges, charges);
|
||||
}
|
||||
|
||||
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
|
||||
if (penaltyCharges.isGreaterThanZero()) {
|
||||
updatePenaltyChargesPaidAmountBy(penaltyCharges, charges);
|
||||
updatePenaltyChargesPaidAmountBy(loanTransaction, penaltyCharges, charges);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,20 +147,27 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFeeChargesPaidAmountBy(final Money feeCharges, final Set<LoanCharge> charges) {
|
||||
private void updateFeeChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges) {
|
||||
|
||||
Money amountRemaining = feeCharges;
|
||||
for (LoanCharge loanCharge : charges) {
|
||||
if (!loanCharge.isDueAtDisbursement()) {
|
||||
if (loanCharge.isFeeCharge() && loanCharge.isNotFullyPaid() && amountRemaining.isGreaterThanZero()) {
|
||||
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
|
||||
amountRemaining = unpaidCharge.updatePaidAmountBy(amountRemaining);
|
||||
Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining);
|
||||
if (!amountPaidTowardsCharge.isZero()) {
|
||||
LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, loanCharge,
|
||||
amountPaidTowardsCharge.getAmount());
|
||||
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
|
||||
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePenaltyChargesPaidAmountBy(final Money feeCharges, final Set<LoanCharge> charges) {
|
||||
private void updatePenaltyChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges,
|
||||
final Set<LoanCharge> charges) {
|
||||
|
||||
Money amountRemaining = feeCharges;
|
||||
for (LoanCharge loanCharge : charges) {
|
||||
@@ -168,7 +175,13 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
if (loanCharge.isPenaltyCharge() && amountRemaining.isGreaterThanZero()) {
|
||||
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
|
||||
amountRemaining = unpaidCharge.updatePaidAmountBy(amountRemaining);
|
||||
Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining);
|
||||
if (!amountPaidTowardsCharge.isZero()) {
|
||||
LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, loanCharge,
|
||||
amountPaidTowardsCharge.getAmount());
|
||||
loanTransaction.getLoanChargesPaid().add(loanChargePaidBy);
|
||||
amountRemaining = amountRemaining.minus(amountPaidTowardsCharge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-36
@@ -101,7 +101,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
@SuppressWarnings("unused")
|
||||
@Column(name = "loan_type_enum", nullable = false)
|
||||
private Integer loanType;
|
||||
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "product_id", nullable = false)
|
||||
private LoanProduct loanProduct;
|
||||
@@ -264,8 +264,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
@Transient
|
||||
private LoanSummaryWrapper loanSummaryWrapper;
|
||||
|
||||
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, final LoanProduct loanProduct,
|
||||
final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule,
|
||||
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral) {
|
||||
final LoanStatus status = null;
|
||||
@@ -304,8 +304,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund, final Staff loanOfficer,
|
||||
final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund,
|
||||
final Staff loanOfficer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
|
||||
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral) {
|
||||
if (StringUtils.isBlank(accountNo)) {
|
||||
@@ -677,10 +677,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
public LoanProduct loanProduct(){
|
||||
public LoanProduct loanProduct() {
|
||||
return this.loanProduct;
|
||||
}
|
||||
|
||||
|
||||
public LoanProductRelatedDetail repaymentScheduleDetail() {
|
||||
return this.loanRepaymentScheduleDetail;
|
||||
}
|
||||
@@ -822,16 +822,18 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.externalId = StringUtils.defaultIfEmpty(newValue, null);
|
||||
}
|
||||
|
||||
//add clientId, groupId and loanType changes to actual changes
|
||||
//FIXME: AA - We may require separate api command to move loan from one client to another
|
||||
// add clientId, groupId and loanType changes to actual changes
|
||||
// FIXME: AA - We may require separate api command to move loan from one
|
||||
// client to another
|
||||
final String clientIdParamName = "clientId";
|
||||
final Long clientId = this.client == null ? null : this.client.getId();
|
||||
if (command.isChangeInLongParameterNamed(clientIdParamName, clientId)) {
|
||||
final Long newValue = command.longValueOfParameterNamed(clientIdParamName);
|
||||
actualChanges.put(clientIdParamName, newValue);
|
||||
}
|
||||
|
||||
//FIXME: AA - We may require separate api command to move loan from one group to another
|
||||
|
||||
// FIXME: AA - We may require separate api command to move loan from one
|
||||
// group to another
|
||||
final String groupIdParamName = "groupId";
|
||||
final Long groupId = this.group == null ? null : this.group.getId();
|
||||
if (command.isChangeInLongParameterNamed(groupIdParamName, groupId)) {
|
||||
@@ -942,17 +944,19 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be in the future.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, getSubmittedOnDate());
|
||||
}
|
||||
|
||||
if (!(client == null)){
|
||||
if (getSubmittedOnDate().isBefore(client.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, getSubmittedOnDate());
|
||||
}
|
||||
}else if (!(group == null)){
|
||||
if (getSubmittedOnDate().isBefore(group.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, getSubmittedOnDate());
|
||||
}
|
||||
|
||||
if (!(client == null)) {
|
||||
if (getSubmittedOnDate().isBefore(client.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage,
|
||||
getSubmittedOnDate());
|
||||
}
|
||||
} else if (!(group == null)) {
|
||||
if (getSubmittedOnDate().isBefore(group.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage,
|
||||
getSubmittedOnDate());
|
||||
}
|
||||
}
|
||||
|
||||
if (getSubmittedOnDate().isAfter(getExpectedDisbursedOnLocalDate())) {
|
||||
@@ -986,7 +990,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
actualChanges.put(collateralParamName, listOfLoanCollateralData(possiblyModifedLoanCollateralItems));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
@@ -1081,16 +1085,18 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn);
|
||||
}
|
||||
|
||||
if (!(client == null)){
|
||||
if (submittedOn.isBefore(client.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn);
|
||||
}
|
||||
}else if (!(group == null)){
|
||||
if (submittedOn.isBefore(group.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage, submittedOn);
|
||||
}
|
||||
if (!(client == null)) {
|
||||
if (submittedOn.isBefore(client.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage,
|
||||
submittedOn);
|
||||
}
|
||||
} else if (!(group == null)) {
|
||||
if (submittedOn.isBefore(group.getActivationLocalDate())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage,
|
||||
submittedOn);
|
||||
}
|
||||
}
|
||||
|
||||
if (submittedOn.isAfter(getExpectedDisbursedOnLocalDate())) {
|
||||
@@ -1405,6 +1411,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
for (LoanCharge charge : setOfLoanCharges()) {
|
||||
if (charge.isDueAtDisbursement()) {
|
||||
charge.markAsFullyPaid();
|
||||
// Add "Loan Charge Paid By" details to this transaction
|
||||
LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(disbursementTransaction, charge, charge.amount());
|
||||
disbursementTransaction.getLoanChargesPaid().add(loanChargePaidBy);
|
||||
} else {
|
||||
handleChargeAppliedTransaction(charge, disbursedOn);
|
||||
}
|
||||
@@ -1419,14 +1428,15 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
if (disbursedOn.isAfter(new LocalDate())) {
|
||||
final String errorMessage = "The date on which a loan with identifier : " + this.accountNumber + " is disbursed cannot be in the future.";
|
||||
final String errorMessage = "The date on which a loan with identifier : " + this.accountNumber
|
||||
+ " is disbursed cannot be in the future.";
|
||||
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.a.future.date", errorMessage, disbursedOn);
|
||||
}
|
||||
|
||||
LocalDate firstRepaymentDueDate = this.repaymentScheduleInstallments.get(0).getDueDate();
|
||||
if (disbursedOn.isAfter(firstRepaymentDueDate)) {
|
||||
final String errorMessage = "The date on which a loan with identifier : " + this.accountNumber + " is disbursed cannot be after the first expected repayment date: "
|
||||
+ firstRepaymentDueDate.toString();
|
||||
final String errorMessage = "The date on which a loan with identifier : " + this.accountNumber
|
||||
+ " is disbursed cannot be after the first expected repayment date: " + firstRepaymentDueDate.toString();
|
||||
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.first.repayment.due.date", errorMessage,
|
||||
this.accountNumber, disbursedOn, firstRepaymentDueDate);
|
||||
}
|
||||
|
||||
+6
-1
@@ -449,6 +449,11 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
return Money.of(currency, this.amountWrittenOff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param incrementBy
|
||||
* Amount used to pay off this charge
|
||||
* @return Actual amount paid on this charge
|
||||
*/
|
||||
public Money updatePaidAmountBy(final Money incrementBy) {
|
||||
|
||||
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
|
||||
@@ -471,7 +476,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
|
||||
this.paid = determineIfFullyPaid();
|
||||
|
||||
return incrementBy.minus(amountPaidOnThisCharge);
|
||||
return amountPaidOnThisCharge;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_loan_charge_paid_by")
|
||||
public class LoanChargePaidBy extends AbstractPersistable<Long> {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "loan_transaction_id", nullable = false)
|
||||
private LoanTransaction loanTransaction;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "loan_charge_id", nullable = false)
|
||||
private LoanCharge loanCharge;
|
||||
|
||||
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
||||
private BigDecimal amount;
|
||||
|
||||
protected LoanChargePaidBy() {
|
||||
|
||||
}
|
||||
|
||||
public LoanChargePaidBy(LoanTransaction loanTransaction, LoanCharge loanCharge, BigDecimal amount) {
|
||||
this.loanTransaction = loanTransaction;
|
||||
this.loanCharge = loanCharge;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public LoanTransaction getLoanTransaction() {
|
||||
return this.loanTransaction;
|
||||
}
|
||||
|
||||
public void setLoanTransaction(LoanTransaction loanTransaction) {
|
||||
this.loanTransaction = loanTransaction;
|
||||
}
|
||||
|
||||
public LoanCharge getLoanCharge() {
|
||||
return this.loanCharge;
|
||||
}
|
||||
|
||||
public void setLoanCharge(LoanCharge loanCharge) {
|
||||
this.loanCharge = loanCharge;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
public void setAmount(BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface LoanChargePaidByRepository extends JpaRepository<LoanChargePaidBy, Long>, JpaSpecificationExecutor<LoanCharge> {
|
||||
// no added behaviour
|
||||
}
|
||||
+22
@@ -7,17 +7,23 @@ package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
@@ -70,6 +76,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
@Column(name = "is_reversed", nullable = false)
|
||||
private boolean reversed;
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loanTransaction", orphanRemoval = true)
|
||||
private Set<LoanChargePaidBy> loanChargesPaid = new HashSet<LoanChargePaidBy>();
|
||||
|
||||
protected LoanTransaction() {
|
||||
this.loan = null;
|
||||
this.dateOf = null;
|
||||
@@ -250,6 +260,10 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
return !isReversed();
|
||||
}
|
||||
|
||||
public boolean isAnyTypeOfRepayment() {
|
||||
return isRepayment() || isRepaymentAtDisbursement() || isRecoveryRepayment();
|
||||
}
|
||||
|
||||
public boolean isRepayment() {
|
||||
return LoanTransactionType.REPAYMENT.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
@@ -367,4 +381,12 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
return this.loan;
|
||||
}
|
||||
|
||||
public Set<LoanChargePaidBy> getLoanChargesPaid() {
|
||||
return this.loanChargesPaid;
|
||||
}
|
||||
|
||||
public void setLoanChargesPaid(Set<LoanChargePaidBy> loanChargesPaid) {
|
||||
this.loanChargesPaid = loanChargesPaid;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -337,6 +337,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds);
|
||||
|
||||
this.loanTransactionRepository.save(newRepaymentTransaction);
|
||||
|
||||
/***
|
||||
* TODO Vishwas Batch save is giving me a
|
||||
* HibernateOptimisticLockingFailureException, looping and saving for
|
||||
|
||||
Reference in New Issue
Block a user