waive and payment of installment charge corrections

This commit is contained in:
Pramod Nuthakki
2013-11-13 11:45:59 +05:30
parent 51d8083dc5
commit d15b40bf33
10 changed files with 209 additions and 73 deletions
@@ -0,0 +1,19 @@
CREATE TABLE `m_loan_installment_charge` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_charge_id` BIGINT(20) NOT NULL,
`loan_schedule_id` BIGINT(20) NOT NULL,
`due_date` DATE NULL DEFAULT NULL,
`amount` DECIMAL(19,6) NOT NULL,
`amount_paid_derived` DECIMAL(19,6) NULL DEFAULT NULL,
`amount_waived_derived` DECIMAL(19,6) NULL DEFAULT NULL,
`amount_writtenoff_derived` DECIMAL(19,6) NULL DEFAULT NULL,
`amount_outstanding_derived` DECIMAL(19,6) NOT NULL DEFAULT '0.000000',
`is_paid_derived` TINYINT(1) NOT NULL DEFAULT '0',
`waived` TINYINT(1) NOT NULL DEFAULT '0',
`amount_through_charge_payment` DECIMAL(19,6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_loan_charge_id_charge_schedule` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
CONSTRAINT `FK_loan_schedule_id_charge_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
@@ -21,45 +21,32 @@ import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
public class LoanChargeData { public class LoanChargeData {
private final Long id; private final Long id;
@SuppressWarnings("unused")
private final Long chargeId; private final Long chargeId;
@SuppressWarnings("unused")
private final String name; private final String name;
@SuppressWarnings("unused")
private final EnumOptionData chargeTimeType; private final EnumOptionData chargeTimeType;
private final LocalDate dueDate; private final LocalDate dueDate;
@SuppressWarnings("unused")
private final EnumOptionData chargeCalculationType; private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage; private final BigDecimal percentage;
@SuppressWarnings("unused")
private final BigDecimal amountPercentageAppliedTo; private final BigDecimal amountPercentageAppliedTo;
@SuppressWarnings("unused")
private final CurrencyData currency; private final CurrencyData currency;
@SuppressWarnings("unused")
private final BigDecimal amount; private final BigDecimal amount;
@SuppressWarnings("unused")
private final BigDecimal amountPaid; private final BigDecimal amountPaid;
@SuppressWarnings("unused")
private final BigDecimal amountWaived; private final BigDecimal amountWaived;
@SuppressWarnings("unused")
private final BigDecimal amountWrittenOff; private final BigDecimal amountWrittenOff;
private final BigDecimal amountOutstanding; private final BigDecimal amountOutstanding;
@SuppressWarnings("unused")
private final BigDecimal amountOrPercentage; private final BigDecimal amountOrPercentage;
@SuppressWarnings("unused")
private final Collection<ChargeData> chargeOptions; private final Collection<ChargeData> chargeOptions;
@SuppressWarnings("unused")
private final boolean penalty; private final boolean penalty;
private final EnumOptionData chargePaymentMode; private final EnumOptionData chargePaymentMode;
@@ -68,17 +55,15 @@ public class LoanChargeData {
private final boolean waived; private final boolean waived;
@SuppressWarnings("unused")
private final boolean chargePayable; private final boolean chargePayable;
private final Long loanId; private final Long loanId;
@SuppressWarnings("unused")
private final BigDecimal minCap; private final BigDecimal minCap;
@SuppressWarnings("unused")
private final BigDecimal maxCap; private final BigDecimal maxCap;
@SuppressWarnings("unused")
private final Collection<LoanInstallmentChargeData> installmentChargeData; private final Collection<LoanInstallmentChargeData> installmentChargeData;
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) { public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
@@ -181,7 +166,7 @@ public class LoanChargeData {
this.installmentChargeData = installmentChargeData; this.installmentChargeData = installmentChargeData;
} }
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, final Long loanId, Collection<LoanInstallmentChargeData> installmentChargeData) { public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, EnumOptionData chargeTimeType, final Long loanId, Collection<LoanInstallmentChargeData> installmentChargeData) {
this.id = id; this.id = id;
this.chargeId = null; this.chargeId = null;
this.name = null; this.name = null;
@@ -191,7 +176,7 @@ public class LoanChargeData {
this.amountWaived = null; this.amountWaived = null;
this.amountWrittenOff = null; this.amountWrittenOff = null;
this.amountOutstanding = amountOutstanding; this.amountOutstanding = amountOutstanding;
this.chargeTimeType = null; this.chargeTimeType = chargeTimeType;
this.dueDate = dueAsOfDate; this.dueDate = dueAsOfDate;
this.chargeCalculationType = null; this.chargeCalculationType = null;
this.percentage = null; this.percentage = null;
@@ -885,8 +885,9 @@ public class Loan extends AbstractPersistable<Long> {
} }
public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) { public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) {
clearInstallmentLoanCharge();
this.repaymentScheduleInstallments.clear(); this.repaymentScheduleInstallments.clear();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) { if (scheduledLoanInstallment.isRepaymentPeriod()) {
@@ -901,6 +902,15 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanScheduleDependentDerivedFields(); updateLoanScheduleDependentDerivedFields();
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
}
private void clearInstallmentLoanCharge(){
for(LoanCharge loanCharge:this.charges){
if(loanCharge.isInstalmentFee()){
loanCharge.clearLoanInstallmentCharges();
}
}
} }
private void updateLoanScheduleDependentDerivedFields() { private void updateLoanScheduleDependentDerivedFields() {
@@ -2979,20 +2989,21 @@ public class Loan extends AbstractPersistable<Long> {
return this.charges; return this.charges;
} }
public Set<LoanInstallmentCharge> createInstallmentLoanCharges(final LoanCharge loanCharge){ public Set<LoanInstallmentCharge> generateInstallmentLoanCharges(final LoanCharge loanCharge){
Set<LoanInstallmentCharge> loanChargePerInstallments = new HashSet<LoanInstallmentCharge>(); Set<LoanInstallmentCharge> loanChargePerInstallments = new HashSet<LoanInstallmentCharge>();
if(loanCharge.isInstalmentFee()){ if(loanCharge.isInstalmentFee()){
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
BigDecimal amount = BigDecimal.ZERO;; BigDecimal amount = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isFlat()){ if(loanCharge.getChargeCalculation().isFlat()){
amount = loanCharge.amount(); amount = loanCharge.amount().divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
}else{ }else{
amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), installment).getAmount(); amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), installment).getAmount();
} }
LoanInstallmentCharge loanChargePerInstallment = new LoanInstallmentCharge(amount, loanCharge, installment); LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment);
loanChargePerInstallments.add(loanChargePerInstallment); loanChargePerInstallments.add(loanInstallmentCharge);
} }
} }
return loanChargePerInstallments; return loanChargePerInstallments;
} }
} }
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -107,7 +108,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
@LazyCollection(LazyCollectionOption.FALSE) @LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loancharge", orphanRemoval = true) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loancharge", orphanRemoval = true)
private Set<LoanInstallmentCharge> loanChargePerInstallments = new HashSet<LoanInstallmentCharge>(); private Set<LoanInstallmentCharge> loanInstallmentCharge = new HashSet<LoanInstallmentCharge>();
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) { public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
@@ -248,14 +249,14 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
this.amountOrPercentage = chargeAmount; this.amountOrPercentage = chargeAmount;
if(this.loan!=null && this.isInstalmentFee()){ if(this.loan!=null && this.isInstalmentFee()){
Set<LoanInstallmentCharge> chargePerInstallments = this.loan.createInstallmentLoanCharges(this); Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if(this.loanChargePerInstallments.isEmpty()){ if(this.loanInstallmentCharge.isEmpty()){
this.loanChargePerInstallments.addAll(chargePerInstallments); this.loanInstallmentCharge.addAll(chargePerInstallments);
}else{ }else{
int index = 0; int index = 0;
LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()]; LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments); LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for(LoanInstallmentCharge chargePerInstallment:this.loanChargePerInstallments){ for(LoanInstallmentCharge chargePerInstallment:this.loanInstallmentCharge){
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]); chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
} }
} }
@@ -275,12 +276,18 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.amountOutstanding = calculateAmountOutstanding(currency); this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false; this.paid = false;
this.waived = false; this.waived = false;
for(LoanInstallmentCharge installmentCharge:loanInstallmentCharge){
installmentCharge.resetPaidAmount(currency);
}
} }
public void resetPaidAmount(final MonetaryCurrency currency) { public void resetPaidAmount(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO; this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency); this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false; this.paid = false;
for(LoanInstallmentCharge installmentCharge:loanInstallmentCharge){
installmentCharge.resetPaidAmount(currency);
}
} }
public Money waive(final MonetaryCurrency currency, Integer loanInstallmentNumber) { public Money waive(final MonetaryCurrency currency, Integer loanInstallmentNumber) {
@@ -347,14 +354,14 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
this.amountOrPercentage = amount; this.amountOrPercentage = amount;
if(this.loan !=null && this.isInstalmentFee()){ if(this.loan !=null && this.isInstalmentFee()){
Set<LoanInstallmentCharge> chargePerInstallments = this.loan.createInstallmentLoanCharges(this); Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if(this.loanChargePerInstallments.isEmpty()){ if(this.loanInstallmentCharge.isEmpty()){
this.loanChargePerInstallments.addAll(chargePerInstallments); this.loanInstallmentCharge.addAll(chargePerInstallments);
}else{ }else{
int index = 0; int index = 0;
LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()]; LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments); LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for(LoanInstallmentCharge chargePerInstallment:this.loanChargePerInstallments){ for(LoanInstallmentCharge chargePerInstallment:this.loanInstallmentCharge){
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]); chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
} }
} }
@@ -410,14 +417,14 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
this.amountOrPercentage = newValue; this.amountOrPercentage = newValue;
if(this.isInstalmentFee()){ if(this.isInstalmentFee()){
Set<LoanInstallmentCharge> chargePerInstallments = this.loan.createInstallmentLoanCharges(this); Set<LoanInstallmentCharge> chargePerInstallments = this.loan.generateInstallmentLoanCharges(this);
if(this.loanChargePerInstallments.isEmpty()){ if(this.loanInstallmentCharge.isEmpty()){
this.loanChargePerInstallments.addAll(chargePerInstallments); this.loanInstallmentCharge.addAll(chargePerInstallments);
}else{ }else{
int index = 0; int index = 0;
LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()]; LoanInstallmentCharge[] loanChargePerInstallments = new LoanInstallmentCharge[chargePerInstallments.size()];
LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments); LoanInstallmentCharge[] loanChargePerInstallmentArray = chargePerInstallments.toArray(loanChargePerInstallments);
for(LoanInstallmentCharge chargePerInstallment:this.loanChargePerInstallments){ for(LoanInstallmentCharge chargePerInstallment:this.loanInstallmentCharge){
chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]); chargePerInstallment.copyFrom(loanChargePerInstallmentArray[index++]);
} }
} }
@@ -603,17 +610,18 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
/** /**
* @param feeAmount TODO
* @param processAmount * @param processAmount
* Amount used to pay off this charge * Amount used to pay off this charge
* @return Actual amount paid on this charge * @return Actual amount paid on this charge
*/ */
public Money updatePaidAmountBy(final Money incrementBy,final Integer installmentNumber) { public Money updatePaidAmountBy(final Money incrementBy,final Integer installmentNumber, Money feeAmount) {
Money processAmount = Money.zero(incrementBy.getCurrency()); Money processAmount = Money.zero(incrementBy.getCurrency());
if(this.isInstalmentFee()){ if(this.isInstalmentFee()){
if(installmentNumber == null){ if(installmentNumber == null){
processAmount = getUnpaidInstallmentLoanCharge().updatePaidAmountBy(incrementBy); processAmount = getUnpaidInstallmentLoanCharge().updatePaidAmountBy(incrementBy, feeAmount);
}else{ }else{
processAmount = getInstallmentLoanCharge(installmentNumber).updatePaidAmountBy(incrementBy); processAmount = getInstallmentLoanCharge(installmentNumber).updatePaidAmountBy(incrementBy, feeAmount);
} }
}else{ }else{
processAmount = incrementBy; processAmount = incrementBy;
@@ -631,9 +639,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
amountPaidOnThisCharge = processAmount; amountPaidOnThisCharge = processAmount;
amountPaidToDate = amountPaidToDate.plus(processAmount); amountPaidToDate = amountPaidToDate.plus(processAmount);
this.amountPaid = amountPaidToDate.getAmount(); this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
final Money amountExpected = Money.of(processAmount.getCurrency(), this.amount);
this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount();
} }
this.paid = determineIfFullyPaid(); this.paid = determineIfFullyPaid();
@@ -695,7 +701,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() { public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() {
LoanInstallmentCharge unpaidChargePerInstallment = null; LoanInstallmentCharge unpaidChargePerInstallment = null;
for (LoanInstallmentCharge loanChargePerInstallment : this.loanChargePerInstallments) { for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending() if (loanChargePerInstallment.isPending()
&& (unpaidChargePerInstallment == null || unpaidChargePerInstallment.getRepaymentInstallment().getDueDate() && (unpaidChargePerInstallment == null || unpaidChargePerInstallment.getRepaymentInstallment().getDueDate()
.isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { .isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
@@ -706,7 +712,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
public LoanInstallmentCharge getInstallmentLoanCharge(LocalDate periodDueDate) { public LoanInstallmentCharge getInstallmentLoanCharge(LocalDate periodDueDate) {
for (LoanInstallmentCharge loanChargePerInstallment : this.loanChargePerInstallments) { for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (periodDueDate.isEqual(loanChargePerInstallment.getRepaymentInstallment().getDueDate())) { if (periodDueDate.isEqual(loanChargePerInstallment.getRepaymentInstallment().getDueDate())) {
return loanChargePerInstallment; return loanChargePerInstallment;
} }
@@ -715,11 +721,33 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
public LoanInstallmentCharge getInstallmentLoanCharge(Integer installmentNumber) { public LoanInstallmentCharge getInstallmentLoanCharge(Integer installmentNumber) {
for (LoanInstallmentCharge loanChargePerInstallment : this.loanChargePerInstallments) { for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue()) ){ if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue()) ){
return loanChargePerInstallment; return loanChargePerInstallment;
} }
} }
return null; return null;
} }
public void clearLoanInstallmentCharges(){
this.loanInstallmentCharge.clear();
}
public void addLoanInstallmentCharges(Collection<LoanInstallmentCharge> installmentCharges){
this.loanInstallmentCharge.addAll(installmentCharges);
}
public boolean hasNoLoanInstallmentCharges(){
return this.loanInstallmentCharge.isEmpty();
}
public LoanRepaymentScheduleInstallment fetchRepaymentInstallment(Money trasferedAmount){
for (LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending() &&
trasferedAmount.getAmount().equals(loanChargePerInstallment.getAmountThroughChargePayment(trasferedAmount.getCurrency()).getAmount()) ){
return loanChargePerInstallment.getRepaymentInstallment();
}
}
return null;
}
} }
@@ -44,6 +44,9 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
@Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false) @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
private BigDecimal amountOutstanding; private BigDecimal amountOutstanding;
@Column(name = "amount_through_charge_payment", scale = 6, precision = 19, nullable = true)
private BigDecimal amountThroughChargePayment;
@Column(name = "is_paid_derived", nullable = false) @Column(name = "is_paid_derived", nullable = false)
private boolean paid = false; private boolean paid = false;
@@ -122,12 +125,24 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
public BigDecimal getAmount() { public BigDecimal getAmount() {
return this.amount; return this.amount;
} }
public Money getAmount(final MonetaryCurrency currency) {
return Money.of(currency, this.amount);
}
private Money getAmountPaid(final MonetaryCurrency currency) {
return Money.of(currency, this.amountPaid);
}
public BigDecimal getAmountOutstanding() { public BigDecimal getAmountOutstanding() {
return this.amountOutstanding; return this.amountOutstanding;
} }
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
}
public boolean isPaid() { public boolean isPaid() {
return this.paid; return this.paid;
} }
@@ -145,7 +160,7 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
return this.installment; return this.installment;
} }
public Money updatePaidAmountBy(final Money incrementBy) { public Money updatePaidAmountBy(final Money incrementBy, Money feeAmount) {
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid); Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding); final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
@@ -160,11 +175,9 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
amountPaidOnThisCharge = incrementBy; amountPaidOnThisCharge = incrementBy;
amountPaidToDate = amountPaidToDate.plus(incrementBy); amountPaidToDate = amountPaidToDate.plus(incrementBy);
this.amountPaid = amountPaidToDate.getAmount(); this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
final Money amountExpected = Money.of(incrementBy.getCurrency(), this.amount);
this.amountOutstanding = amountExpected.minus(amountPaidToDate).getAmount();
} }
this.amountThroughChargePayment = feeAmount.getAmount();
this.paid = determineIfFullyPaid(); this.paid = determineIfFullyPaid();
return amountPaidOnThisCharge; return amountPaidOnThisCharge;
@@ -174,4 +187,25 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
public Money getAmountWrittenOff(final MonetaryCurrency currency) { public Money getAmountWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.amountWrittenOff); return Money.of(currency, this.amountWrittenOff);
} }
public void resetPaidAmount(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
}
public void resetToOriginal(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountWaived = BigDecimal.ZERO;
this.amountWrittenOff = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
this.waived = false;
}
public Money getAmountThroughChargePayment(final MonetaryCurrency currency) {
return Money.of(currency, this.amountThroughChargePayment);
}
} }
@@ -13,6 +13,7 @@ import java.util.Set;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
@@ -105,11 +106,6 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
@Column(name = "obligations_met_on_date") @Column(name = "obligations_met_on_date")
private Date obligationsMetOnDate; private Date obligationsMetOnDate;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL)
private Set<LoanInstallmentCharge> loanChargePerInstallments = new HashSet<LoanInstallmentCharge>();
protected LoanRepaymentScheduleInstallment() { protected LoanRepaymentScheduleInstallment() {
this.installmentNumber = null; this.installmentNumber = null;
this.fromDate = null; this.fromDate = null;
@@ -69,12 +69,24 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<LoanTransaction>(); final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<LoanTransaction>();
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
if (loanTransaction.isChargePayment()) { if (loanTransaction.isChargePayment()) {
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<LoanRepaymentScheduleInstallment>();
final Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid(); final Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
final Set<LoanCharge> transferCharges = new HashSet<LoanCharge>(); final Set<LoanCharge> transferCharges = new HashSet<LoanCharge>();
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) { for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
transferCharges.add(chargePaidBy.getLoanCharge()); LoanCharge loanCharge = chargePaidBy.getLoanCharge();
transferCharges.add(loanCharge);
if(loanCharge.isInstalmentFee()){
LoanRepaymentScheduleInstallment installment = null;
if(loanCharge.isFeeCharge()){
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency));
}else{
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency));
}
if(installment !=null){
chargePaymentInstallments.add(installment);
}
}
} }
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<LoanRepaymentScheduleInstallment>();
LocalDate startDate = disbursementDate; LocalDate startDate = disbursementDate;
for (final LoanRepaymentScheduleInstallment installment : installments) { for (final LoanRepaymentScheduleInstallment installment : installments) {
for (final LoanCharge loanCharge : transferCharges) { for (final LoanCharge loanCharge : transferCharges) {
@@ -221,7 +233,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
Money amountRemaining = feeCharges; Money amountRemaining = feeCharges;
while(amountRemaining.isGreaterThanZero()) { while(amountRemaining.isGreaterThanZero()) {
final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges); final LoanCharge unpaidCharge = findEarliestUnpaidChargeFromUnOrderedSet(charges);
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining,installmentNumber); Money feeAmount = feeCharges.zero();
if(loanTransaction.isChargePayment()){
feeAmount = feeCharges;
}
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining,installmentNumber, feeAmount);
if (!amountPaidTowardsCharge.isZero()) { if (!amountPaidTowardsCharge.isZero()) {
if (!loanTransaction.isChargePayment()) { if (!loanTransaction.isChargePayment()) {
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(loanTransaction, unpaidCharge,
@@ -5,8 +5,10 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.service; package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -47,6 +49,8 @@ import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
@@ -109,6 +113,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
private final SavingsAccountAssembler savingsAccountAssembler; private final SavingsAccountAssembler savingsAccountAssembler;
private final AccountAssociationsRepository accountAssociationsRepository; private final AccountAssociationsRepository accountAssociationsRepository;
private final LoanChargeRepository loanChargeRepository;
@Autowired @Autowired
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
@@ -122,7 +127,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final LoanSummaryWrapper loanSummaryWrapper, final GroupRepositoryWrapper groupRepository, final LoanSummaryWrapper loanSummaryWrapper, final GroupRepositoryWrapper groupRepository,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository, final CalendarRepository calendarRepository, final CalendarInstanceRepository calendarInstanceRepository,
final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository) { final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository,
final LoanChargeRepository loanChargeRepository) {
this.context = context; this.context = context;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator;
@@ -145,6 +151,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
this.calendarInstanceRepository = calendarInstanceRepository; this.calendarInstanceRepository = calendarInstanceRepository;
this.savingsAccountAssembler = savingsAccountAssembler; this.savingsAccountAssembler = savingsAccountAssembler;
this.accountAssociationsRepository = accountAssociationsRepository; this.accountAssociationsRepository = accountAssociationsRepository;
this.loanChargeRepository = loanChargeRepository;
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -333,7 +340,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
if (changes.containsKey("recalculateLoanSchedule")) { if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule"); changes.remove("recalculateLoanSchedule");
final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json()); final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json());
final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper); final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper);
@@ -354,6 +361,14 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Note note = Note.loanNote(existingLoanApplication, submittedOnNote); final Note note = Note.loanNote(existingLoanApplication, submittedOnNote);
this.noteRepository.save(note); this.noteRepository.save(note);
} }
for (final LoanCharge loanCharge : existingLoanApplication.charges()) {
if(loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()){
Set<LoanInstallmentCharge> chargePerInstallments = existingLoanApplication.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
}
final Long calendarId = command.longValueOfParameterNamed("calendarId"); final Long calendarId = command.longValueOfParameterNamed("calendarId");
Calendar calendar = null; Calendar calendar = null;
@@ -169,7 +169,10 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
public String schema() { public String schema() {
return "lc.id as id, lc.due_for_collection_as_of_date as dueAsOfDate, " return "lc.id as id, lc.due_for_collection_as_of_date as dueAsOfDate, "
+ "lc.amount_outstanding_derived as amountOutstanding, " + "loan.id as loanId " + "from m_loan_charge lc " + "lc.amount_outstanding_derived as amountOutstanding, "
+ "lc.charge_time_enum as chargeTime, "
+ "loan.id as loanId "
+ "from m_loan_charge lc "
+ "join m_loan loan on loan.id = lc.loan_id "; + "join m_loan loan on loan.id = lc.loan_id ";
} }
@@ -180,7 +183,10 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate"); final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final Long loanId = rs.getLong("loanId"); final Long loanId = rs.getLong("loanId");
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding"); final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
return new LoanChargeData(id, dueAsOfDate, amountOutstanding, loanId, null); final int chargeTime = rs.getInt("chargeTime");
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
return new LoanChargeData(id, dueAsOfDate, amountOutstanding, chargeTimeType, loanId, null);
} }
} }
@@ -190,7 +196,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
String sql = "select " + rm.schema() String sql = "select " + rm.schema()
+ "where lic.loan_charge_id= ? "; + "where lic.loan_charge_id= ? ";
if(onlyPaymentPendingCharges){ if(onlyPaymentPendingCharges){
sql = sql +"and lc.waived =0 and lc.is_paid_derived=0"; sql = sql +"and lic.waived =0 and lic.is_paid_derived=0";
} }
return this.jdbcTemplate.query(sql, rm, new Object[] { loanChargeId }); return this.jdbcTemplate.query(sql, rm, new Object[] { loanChargeId });
} }
@@ -200,7 +206,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
public String schema() { public String schema() {
return " lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, " return " lsi.installment as installmentNumber, lsi.duedate as dueAsOfDate, "
+ "lic.amount_outstanding_derived as amountOutstanding," + "lic.amount_outstanding_derived as amountOutstanding,"
+ "lic.amount as amount" + "lic.amount as amount, "
+ "lic.is_paid_derived as paid, " + "lic.is_paid_derived as paid, "
+ "lic.waived as waied " + "lic.waived as waied "
+ "from m_loan_installment_charge lic " + "from m_loan_installment_charge lic "
@@ -266,6 +266,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amount()); disBuLoanCharges.put(loanCharge.getId(), loanCharge.amount());
}else if(loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()){
Set<LoanInstallmentCharge> chargePerInstallments = loan.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
} }
} }
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
@@ -384,6 +388,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
} }
final Set<LoanCharge> loanCharges = loan.charges();
final Map<Long, BigDecimal> disBuLoanCharges = new HashMap<Long, BigDecimal>();
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueAtDisbursement() && loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
disBuLoanCharges.put(loanCharge.getId(), loanCharge.amount());
}else if(loanCharge.isInstalmentFee() && loanCharge.hasNoLoanInstallmentCharges()){
Set<LoanInstallmentCharge> chargePerInstallments = loan.generateInstallmentLoanCharges(loanCharge);
loanCharge.addLoanInstallmentCharges(chargePerInstallments);
this.loanChargeRepository.save(loanCharge);
}
}
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
for (final Map.Entry<Long, BigDecimal> entrySet : disBuLoanCharges.entrySet()) {
final PortfolioAccountData savingAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loan.getId());
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.accountId(), loan.getId(), "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
}
} }
return changes; return changes;
} }
@@ -933,12 +957,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Integer loanInstallmentNumber = null; Integer loanInstallmentNumber = null;
if (loanCharge.isInstalmentFee()){ if (loanCharge.isInstalmentFee()){
LoanInstallmentCharge chargePerInstallment = null; LoanInstallmentCharge chargePerInstallment = null;
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate"); if(!StringUtils.isBlank(command.json())){
final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber"); final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
if(dueDate !=null){ final Integer installmentNumber = command.integerValueOfParameterNamed("installmentNumber");
chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate); if(dueDate !=null){
}else if(installmentNumber!=null){ chargePerInstallment = loanCharge.getInstallmentLoanCharge(dueDate);
chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber); }else if(installmentNumber!=null){
chargePerInstallment = loanCharge.getInstallmentLoanCharge(installmentNumber);
}
} }
if(chargePerInstallment == null){ if(chargePerInstallment == null){
chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge(); chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge();
@@ -1090,7 +1116,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Collection<LoanInstallmentChargeData> chargePerInstallments = this.LoanChargeReadPlatformService.retrieveInstallmentLoanCharges(chargeData.getId(), true); Collection<LoanInstallmentChargeData> chargePerInstallments = this.LoanChargeReadPlatformService.retrieveInstallmentLoanCharges(chargeData.getId(), true);
PortfolioAccountData portfolioAccountData = null; PortfolioAccountData portfolioAccountData = null;
for(LoanInstallmentChargeData installmentChargeData : chargePerInstallments){ for(LoanInstallmentChargeData installmentChargeData : chargePerInstallments){
if(installmentChargeData.getDueDate().isAfter(new LocalDate())){ if(!installmentChargeData.getDueDate().isAfter(new LocalDate())){
if(portfolioAccountData == null){ if(portfolioAccountData == null){
portfolioAccountData = this.accountAssociationsReadPlatformService portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanAssociation(chargeData.getLoanId()); .retriveLoanAssociation(chargeData.getLoanId());