Merge pull request #655 from pramodn02/MIFOSX-730

fix for percentage based approach
This commit is contained in:
Vishwas Babu A J
2013-11-05 02:09:48 -08:00
5 changed files with 105 additions and 22 deletions
@@ -11,14 +11,19 @@ public class LoanChargeCannotBeUpdatedException extends AbstractPlatformDomainRu
/*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_UPDATED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE;
ALREADY_PAID, ALREADY_WAIVED, LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE,CHARGE_INSTALLMENT_FEE;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "This loan charge has been partially/completely paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "This charge cannot be updated as the loan it is associated with is not in submitted and pending approval stage"; }
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) {
return "This charge cannot be updated as the loan it is associated with is not in submitted and pending approval stage";
} else if(name().toString().equalsIgnoreCase("CHARGE_INSTALLMENT_FEE")){
return "Installment charge update is not allowed";
}
return name().toString();
}
@@ -27,7 +32,11 @@ public class LoanChargeCannotBeUpdatedException extends AbstractPlatformDomainRu
return "error.msg.loan.charge.already.paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) { return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage"; }
} else if (name().toString().equalsIgnoreCase("LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE")) {
return "error.msg.loan.charge.associated.loan.not.in.submitted.and.pending.approval.stage";
} else if(name().toString().equalsIgnoreCase("CHARGE_INSTALLMENT_FEE")){
return "error.msg.loan.charge.installment.fee.update.not.allowed";
}
return name().toString();
}
}
@@ -58,6 +58,7 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.collateral.data.CollateralData;
@@ -452,15 +453,19 @@ public class Loan extends AbstractPersistable<Long> {
loanCharge.update(this);
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
BigDecimal totalChargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
if(loanCharge.isInstalmentFee()){
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
}
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments(), totalChargeAmt);
setOfLoanCharges().add(loanCharge);
return changedTransactionDetail;
}
@@ -636,7 +641,11 @@ public class Loan extends AbstractPersistable<Long> {
validateLoanIsNotClosed(loanCharge);
if (setOfLoanCharges().contains(loanCharge)) {
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
final Map<String, Object> loanChargeChanges = loanCharge.update(command, amount);
BigDecimal totalChargeAmt = BigDecimal.ZERO;
if(loanCharge.isInstalmentFee()){
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
}
final Map<String, Object> loanChargeChanges = loanCharge.update(command, amount, totalChargeAmt);
actualChanges.putAll(loanChargeChanges);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
@@ -689,6 +698,32 @@ public class Loan extends AbstractPersistable<Long> {
}
return amount;
}
private BigDecimal calculatePerInstallmentChargeAmount(final LoanCharge loanCharge){
return calculatePerInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage());
}
public BigDecimal calculatePerInstallmentChargeAmount(ChargeCalculationType calculationType,BigDecimal percentage){
Money amount = Money.zero(getCurrency());
for(final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments){
Money percentOf = Money.zero(getCurrency());
switch (calculationType) {
case PERCENT_OF_AMOUNT:
percentOf = installment.getPrincipal(getCurrency());
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
percentOf = installment.getPrincipal(getCurrency()).plus(installment.getInterestCharged(getCurrency()));
break;
case PERCENT_OF_INTEREST:
percentOf = installment.getInterestCharged(getCurrency());
break;
default:
break;
}
amount = amount.plus(LoanCharge.percentageOf(percentOf.getAmount(),percentage));
}
return amount.getAmount();
}
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
@@ -803,15 +838,19 @@ public class Loan extends AbstractPersistable<Long> {
loanCharge.update(this);
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
BigDecimal totalChargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
if(loanCharge.isInstalmentFee()){
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
}
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments(), totalChargeAmt);
}
setOfLoanCharges().addAll(loanCharges);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -1062,15 +1101,19 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanCharge loanCharge : possiblyModifedLoanCharges) {
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
BigDecimal totalChargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
if(loanCharge.isInstalmentFee()){
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
}
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments(), totalChargeAmt);
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
}
}
@@ -1204,15 +1247,19 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanCharge loanCharge : setOfLoanCharges()) {
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
BigDecimal totalChargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
if(loanCharge.isInstalmentFee()){
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
}
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments(), totalChargeAmt);
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
}
@@ -117,18 +117,27 @@ public class LoanCharge extends AbstractPersistable<Long> {
default:
break;
}
BigDecimal loanCharge = BigDecimal.ZERO;
if(ChargeTimeType.fromInt(chargeDefinition.getChargeTime()).equals(ChargeTimeType.INSTALMENT_FEE)){
BigDecimal percentage = amount;
if(percentage == null){
percentage = chargeDefinition.getAmount();
}
loanCharge = loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()),percentage);
}
return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
chargePaymentMode, null);
chargePaymentMode, null, loanCharge);
}
/*
* loanPrincipal is required for charges that are percentage based
*/
public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode,final Integer numberOfRepayments) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode, numberOfRepayments);
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode, numberOfRepayments, BigDecimal.ZERO);
}
protected LoanCharge() {
@@ -137,7 +146,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode, Integer numberOfRepayments) {
final ChargePaymentMode chargePaymentMode, Integer numberOfRepayments, BigDecimal loanCharge) {
this.loan = loan;
this.charge = chargeDefinition;
this.penaltyCharge = chargeDefinition.isPenalty();
@@ -176,11 +185,11 @@ public class LoanCharge extends AbstractPersistable<Long> {
if (chargePaymentMode != null) {
this.chargePaymentMode = chargePaymentMode.getValue();
}
populateDerivedFields(loanPrincipal, chargeAmount,numberOfRepayments);
populateDerivedFields(loanPrincipal, chargeAmount,numberOfRepayments, loanCharge);
this.paid = determineIfFullyPaid();
}
private void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount,Integer numberOfRepayments) {
private void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount,Integer numberOfRepayments, BigDecimal loanCharge) {
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
case INVALID:
@@ -213,7 +222,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
case PERCENT_OF_INTEREST:
this.percentage = chargeAmount;
this.amountPercentageAppliedTo = amountPercentageAppliedTo;
this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage));
if(loanCharge.compareTo(BigDecimal.ZERO) == 0){
loanCharge = percentageOf(this.amountPercentageAppliedTo);
}
this.amount = minimumAndMaximumCap(loanCharge);
this.amountPaid = null;
this.amountOutstanding = calculateOutstanding();
this.amountWaived = null;
@@ -259,7 +271,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.loan = loan;
}
public void update(final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal, Integer numberOfRepayments) {
public void update(final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal, Integer numberOfRepayments, BigDecimal loanCharge) {
if (dueDate != null) {
this.dueDate = dueDate.toDate();
}
@@ -283,14 +295,17 @@ public class LoanCharge extends AbstractPersistable<Long> {
case PERCENT_OF_INTEREST:
this.percentage = amount;
this.amountPercentageAppliedTo = loanPrincipal;
this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage));
if(loanCharge.compareTo(BigDecimal.ZERO) == 0){
loanCharge = percentageOf(this.amountPercentageAppliedTo);
}
this.amount = minimumAndMaximumCap(loanCharge);
this.amountOutstanding = calculateOutstanding();
break;
}
}
}
public Map<String, Object> update(final JsonCommand command, final BigDecimal amount) {
public Map<String, Object> update(final JsonCommand command, final BigDecimal amount, BigDecimal loanCharge) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(7);
@@ -328,7 +343,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
case PERCENT_OF_INTEREST:
this.percentage = newValue;
this.amountPercentageAppliedTo = amount;
this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage));
if(loanCharge.compareTo(BigDecimal.ZERO) == 0){
loanCharge = percentageOf(this.amountPercentageAppliedTo);
}
this.amount = minimumAndMaximumCap(loanCharge);
this.amountOutstanding = calculateOutstanding();
break;
}
@@ -349,7 +367,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.INSTALMENT_FEE);
}
private boolean isGreaterThanZero(final BigDecimal value) {
private static boolean isGreaterThanZero(final BigDecimal value) {
return value.compareTo(BigDecimal.ZERO) == 1;
}
@@ -396,7 +414,11 @@ public class LoanCharge extends AbstractPersistable<Long> {
return this.amount.subtract(totalAccountedFor);
}
private BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage) {
public BigDecimal percentageOf(final BigDecimal value) {
return percentageOf(value, this.percentage);
}
public static BigDecimal percentageOf(final BigDecimal value,BigDecimal percentage) {
BigDecimal percentageOf = BigDecimal.ZERO;
@@ -91,7 +91,7 @@ public class LoanChargeAssembler {
final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId);
if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); }
loanCharge.update(amount, dueDate, principal, numberOfRepayments);
loanCharge.update(amount, dueDate, principal, numberOfRepayments, BigDecimal.ZERO);
loanCharges.add(loanCharge);
}
@@ -890,6 +890,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// yet to be approved (are in submitted and pending status)
if (!loan.status().isSubmittedAndPendingApproval()) { throw new LoanChargeCannotBeUpdatedException(
LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.LOAN_NOT_IN_SUBMITTED_AND_PENDING_APPROVAL_STAGE, loanCharge.getId()); }
if(loanCharge.isInstalmentFee()){
throw new LoanChargeCannotBeUpdatedException(
LOAN_CHARGE_CANNOT_BE_UPDATED_REASON.CHARGE_INSTALLMENT_FEE, loanCharge.getId());
}
final Map<String, Object> changes = loan.updateLoanCharge(loanCharge, command);