Merge pull request #653 from pramodn02/MIFOSX-730

Mifosx 730
This commit is contained in:
Keith Woodlock
2013-11-03 16:04:38 -08:00
15 changed files with 407 additions and 134 deletions
@@ -56,6 +56,14 @@ public enum ChargeCalculationType {
return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue()); return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue());
} }
public boolean isPercentageOfAmountAndInterest() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue());
}
public boolean isPercentageOfInterest() {
return this.value.equals(ChargeCalculationType.PERCENT_OF_INTEREST.getValue());
}
public boolean isFlat() { public boolean isFlat() {
return this.value.equals(ChargeCalculationType.FLAT.getValue()); return this.value.equals(ChargeCalculationType.FLAT.getValue());
} }
@@ -63,4 +71,8 @@ public enum ChargeCalculationType {
public boolean isAllowedSavingsChargeCalculationType() { public boolean isAllowedSavingsChargeCalculationType() {
return isFlat() || isPercentageOfAmount(); return isFlat() || isPercentageOfAmount();
} }
public boolean isPercentageBased(){
return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest();
}
} }
@@ -16,7 +16,8 @@ public enum ChargeTimeType {
SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings
WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings
ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings
MONTHLY_FEE(7, "chargeTimeType.monthlyFee"); // only for savings MONTHLY_FEE(7, "chargeTimeType.monthlyFee"), // only for savings
INSTALMENT_FEE(8,"chargeTimeType.instalmentFee");//only for loan charges
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -47,34 +48,37 @@ public enum ChargeTimeType {
public static ChargeTimeType fromInt(final Integer chargeTime) { public static ChargeTimeType fromInt(final Integer chargeTime) {
ChargeTimeType chargeTimeType = ChargeTimeType.INVALID; ChargeTimeType chargeTimeType = ChargeTimeType.INVALID;
if (chargeTime != null) { if (chargeTime != null) {
switch (chargeTime) { switch (chargeTime) {
case 1: case 1:
chargeTimeType = DISBURSEMENT; chargeTimeType = DISBURSEMENT;
break; break;
case 2: case 2:
chargeTimeType = SPECIFIED_DUE_DATE; chargeTimeType = SPECIFIED_DUE_DATE;
break; break;
case 3: case 3:
chargeTimeType = SAVINGS_ACTIVATION; chargeTimeType = SAVINGS_ACTIVATION;
break; break;
case 4: case 4:
chargeTimeType = SAVINGS_CLOSURE; chargeTimeType = SAVINGS_CLOSURE;
break; break;
case 5: case 5:
chargeTimeType = WITHDRAWAL_FEE; chargeTimeType = WITHDRAWAL_FEE;
break; break;
case 6: case 6:
chargeTimeType = ANNUAL_FEE; chargeTimeType = ANNUAL_FEE;
break; break;
case 7: case 7:
chargeTimeType = MONTHLY_FEE; chargeTimeType = MONTHLY_FEE;
break; break;
default: case 8:
chargeTimeType = INVALID; chargeTimeType = INSTALMENT_FEE;
break; break;
} default:
chargeTimeType = INVALID;
break;
} }
return chargeTimeType; }
return chargeTimeType;
} }
public boolean isTimeOfDisbursement() { public boolean isTimeOfDisbursement() {
@@ -105,8 +109,13 @@ public enum ChargeTimeType {
return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue()); return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue());
} }
public boolean isInstalmentFee() {
return this.value.equals(ChargeTimeType.INSTALMENT_FEE.getValue());
}
public boolean isAllowedLoanChargeTime() { public boolean isAllowedLoanChargeTime() {
return isTimeOfDisbursement() || isOnSpecifiedDueDate(); return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee();
} }
public boolean isAllowedSavingsChargeTime() { public boolean isAllowedSavingsChargeTime() {
@@ -11,7 +11,7 @@ public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRule
/*** enum of reasons of why Loan Charge cannot be waived **/ /*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_PAYED_REASON { public static enum LOAN_CHARGE_CANNOT_BE_PAYED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, CHARGE_NOT_ACCOUNT_TRANSFER; ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, CHARGE_NOT_ACCOUNT_TRANSFER,CHARGE_NOT_PAYABLE;
public String errorMessage() { public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) { if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
@@ -20,7 +20,11 @@ public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRule
return "This loan charge has already been waived"; return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "This loan charge can be payed as the loan associated with it is currently inactive"; return "This loan charge can be payed as the loan associated with it is currently inactive";
} else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) { return "This loan charge can be payed as the charge payment mode is not account transfer"; } } else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
return "This loan charge can be payed as the charge payment mode is not account transfer";
}else if (name().toString().equalsIgnoreCase("CHARGE_NOT_PAYABLE")) {
return "This loan charge is not Payable through account transfer";
}
return name().toString(); return name().toString();
} }
@@ -32,7 +36,11 @@ public class LoanChargeCannotBePayedException extends AbstractPlatformDomainRule
return "error.msg.loan.charge.already.waived"; return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "error.msg.loan.charge.associated.loan.inactive"; return "error.msg.loan.charge.associated.loan.inactive";
} else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) { return "error.msg.loan.charge.payment.mode.not.account.transfer"; } } else if (name().toString().equalsIgnoreCase("CHARGE_NOT_ACCOUNT_TRANSFER")) {
return "error.msg.loan.charge.payment.mode.not.account.transfer";
}else if (name().toString().equalsIgnoreCase("CHARGE_NOT_PAYABLE")) {
return "error.msg.loan.charge.payment.not.allowed.account.transfer";
}
return name().toString(); return name().toString();
} }
} }
@@ -11,14 +11,19 @@ public class LoanChargeCannotBeWaivedException extends AbstractPlatformDomainRul
/*** enum of reasons of why Loan Charge cannot be waived **/ /*** enum of reasons of why Loan Charge cannot be waived **/
public static enum LOAN_CHARGE_CANNOT_BE_WAIVED_REASON { public static enum LOAN_CHARGE_CANNOT_BE_WAIVED_REASON {
ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE; ALREADY_PAID, ALREADY_WAIVED, LOAN_INACTIVE, WAIVE_NOT_ALLOWED_FOR_CHARGE;
public String errorMessage() { public String errorMessage() {
if (name().toString().equalsIgnoreCase("ALREADY_PAID")) { if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
return "This loan charge has been completely paid"; return "This loan charge has been completely paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) { } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "This loan charge has already been waived"; return "This loan charge has already been waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { return "This loan charge can be waived as the loan associated with it is currently inactive"; } } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "This loan charge can be waived as the loan associated with it is currently inactive";
} else if(name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")){
return "This loan charge can be waived";
}
return name().toString(); return name().toString();
} }
@@ -27,7 +32,11 @@ public class LoanChargeCannotBeWaivedException extends AbstractPlatformDomainRul
return "error.msg.loan.charge.already.paid"; return "error.msg.loan.charge.already.paid";
} else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) { } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
return "error.msg.loan.charge.already.waived"; return "error.msg.loan.charge.already.waived";
} else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) { return "error.msg.loan.charge.associated.loan.inactive"; } } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
return "error.msg.loan.charge.associated.loan.inactive";
} else if(name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) {
return "error.msg.loan.charge.waive.not.allowed";
}
return name().toString(); return name().toString();
} }
} }
@@ -27,9 +27,9 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
public List<EnumOptionData> retrieveCalculationTypes() { public List<EnumOptionData> retrieveCalculationTypes() {
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT) chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT),
// chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST),
// chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST) chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST)
); );
} }
@@ -64,12 +64,12 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
@Override @Override
public List<EnumOptionData> retrieveLoanCalculationTypes() { public List<EnumOptionData> retrieveLoanCalculationTypes() {
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT)); return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST), chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST));
} }
@Override @Override
public List<EnumOptionData> retrieveLoanCollectionTimeTypes() { public List<EnumOptionData> retrieveLoanCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE)); return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE),chargeTimeType(ChargeTimeType.INSTALMENT_FEE));
} }
@Override @Override
@@ -43,6 +43,9 @@ public class ChargeEnumerations {
case MONTHLY_FEE: case MONTHLY_FEE:
optionData = new EnumOptionData(ChargeTimeType.MONTHLY_FEE.getValue().longValue(), ChargeTimeType.MONTHLY_FEE.getCode(), "Monthly Fee"); optionData = new EnumOptionData(ChargeTimeType.MONTHLY_FEE.getValue().longValue(), ChargeTimeType.MONTHLY_FEE.getCode(), "Monthly Fee");
break; break;
case INSTALMENT_FEE:
optionData = new EnumOptionData(ChargeTimeType.INSTALMENT_FEE.getValue().longValue(), ChargeTimeType.INSTALMENT_FEE.getCode(), "Instalment Fee");
break;
default: default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid"); optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break; break;
@@ -427,9 +427,6 @@ public class Loan extends AbstractPersistable<Long> {
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
loanCharge.update(this);
setOfLoanCharges().add(loanCharge);
this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
@@ -452,6 +449,19 @@ public class Loan extends AbstractPersistable<Long> {
} }
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
loanCharge.update(this);
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
setOfLoanCharges().add(loanCharge);
return changedTransactionDetail; return changedTransactionDetail;
} }
@@ -624,9 +634,9 @@ public class Loan extends AbstractPersistable<Long> {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(3); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(3);
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
if (setOfLoanCharges().contains(loanCharge)) { if (setOfLoanCharges().contains(loanCharge)) {
final Map<String, Object> loanChargeChanges = loanCharge.update(command, getPrincpal().getAmount()); BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
final Map<String, Object> loanChargeChanges = loanCharge.update(command, amount);
actualChanges.putAll(loanChargeChanges); actualChanges.putAll(loanChargeChanges);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
@@ -656,6 +666,30 @@ public class Loan extends AbstractPersistable<Long> {
return actualChanges; return actualChanges;
} }
/**
* @param loanCharge
* @return
*/
private BigDecimal calculateAmountPercentageAppliedTo(final LoanCharge loanCharge) {
BigDecimal amount = BigDecimal.ZERO;
switch (loanCharge.getChargeCalculation()) {
case PERCENT_OF_AMOUNT:
amount = getPrincpal().getAmount();
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
final BigDecimal totalInterestCharged = this.loanSummaryWrapper.calculateTotalInterestCharged(
repaymentScheduleInstallments, getCurrency()).getAmount();
amount = getPrincpal().getAmount().add(totalInterestCharged);
break;
case PERCENT_OF_INTEREST:
amount = getSummary().getTotalInterestCharged();
break;
default:
break;
}
return amount;
}
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) { final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
@@ -765,7 +799,21 @@ public class Loan extends AbstractPersistable<Long> {
public void updateLoanCharges(final Set<LoanCharge> loanCharges) { public void updateLoanCharges(final Set<LoanCharge> loanCharges) {
setOfLoanCharges().clear(); setOfLoanCharges().clear();
setOfLoanCharges().addAll(associateChargesWithThisLoan(loanCharges)); for (final LoanCharge loanCharge : loanCharges) {
loanCharge.update(this);
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
}
setOfLoanCharges().addAll(loanCharges);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
@@ -1012,6 +1060,17 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put("recalculateLoanSchedule", true); actualChanges.put("recalculateLoanSchedule", true);
for (final LoanCharge loanCharge : possiblyModifedLoanCharges) { for (final LoanCharge loanCharge : possiblyModifedLoanCharges) {
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
} }
} }
@@ -1143,6 +1202,17 @@ public class Loan extends AbstractPersistable<Long> {
// charges are optional // charges are optional
for (final LoanCharge loanCharge : setOfLoanCharges()) { for (final LoanCharge loanCharge : setOfLoanCharges()) {
BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
if(loanCharge.getChargeCalculation().isPercentageBased()){
chargeAmt = loanCharge.getPercentage();
}else{
chargeAmt = loanCharge.amount();
if(loanCharge.isInstalmentFee()){
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(this.repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, this.repaymentScheduleDetail().getNumberOfRepayments());
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
} }
@@ -103,9 +103,23 @@ public class LoanCharge extends AbstractPersistable<Long> {
final ChargeTimeType chargeTime = null; final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null; final ChargeCalculationType chargeCalculation = null;
final ChargePaymentMode chargePaymentMode = null; final ChargePaymentMode chargePaymentMode = null;
BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
switch (ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation())) {
case PERCENT_OF_AMOUNT:
amountPercentageAppliedTo = loan.getPrincpal().getAmount();
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
amountPercentageAppliedTo = loan.getPrincpal().getAmount().add(loan.getSummary().getTotalInterestCharged());
break;
case PERCENT_OF_INTEREST:
amountPercentageAppliedTo = loan.getSummary().getTotalInterestCharged();
break;
default:
break;
}
return new LoanCharge(loan, chargeDefinition, loan.getPrincpal().getAmount(), amount, chargeTime, chargeCalculation, dueDate, return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
chargePaymentMode); chargePaymentMode, null);
} }
/* /*
@@ -113,8 +127,8 @@ public class LoanCharge extends AbstractPersistable<Long> {
*/ */
public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount, public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode) { final ChargePaymentMode chargePaymentMode,final Integer numberOfRepayments) {
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode); return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode, numberOfRepayments);
} }
protected LoanCharge() { protected LoanCharge() {
@@ -123,7 +137,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount, public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode) { final ChargePaymentMode chargePaymentMode, Integer numberOfRepayments) {
this.loan = loan; this.loan = loan;
this.charge = chargeDefinition; this.charge = chargeDefinition;
this.penaltyCharge = chargeDefinition.isPenalty(); this.penaltyCharge = chargeDefinition.isPenalty();
@@ -162,12 +176,11 @@ public class LoanCharge extends AbstractPersistable<Long> {
if (chargePaymentMode != null) { if (chargePaymentMode != null) {
this.chargePaymentMode = chargePaymentMode.getValue(); this.chargePaymentMode = chargePaymentMode.getValue();
} }
populateDerivedFields(loanPrincipal, chargeAmount,numberOfRepayments);
populateDerivedFields(loanPrincipal,chargeAmount);
this.paid = determineIfFullyPaid(); this.paid = determineIfFullyPaid();
} }
private void populateDerivedFields(final BigDecimal loanPrincipal, final BigDecimal chargeAmount) { private void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount,Integer numberOfRepayments) {
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
case INVALID: case INVALID:
@@ -181,40 +194,31 @@ public class LoanCharge extends AbstractPersistable<Long> {
break; break;
case FLAT: case FLAT:
this.percentage = null; this.percentage = null;
this.amount = chargeAmount;
this.amountPercentageAppliedTo = null; this.amountPercentageAppliedTo = null;
this.amountPaid = null; this.amountPaid = null;
this.amountOutstanding = chargeAmount; if(this.isInstalmentFee()){
if(numberOfRepayments == null){
numberOfRepayments = this.loan.repaymentScheduleDetail().getNumberOfRepayments();
}
this.amount = chargeAmount.multiply(BigDecimal.valueOf(numberOfRepayments));
}else{
this.amount = chargeAmount;
}
this.amountOutstanding = this.amount;
this.amountWaived = null; this.amountWaived = null;
this.amountWrittenOff = null; this.amountWrittenOff = null;
break; break;
case PERCENT_OF_AMOUNT: case PERCENT_OF_AMOUNT:
case PERCENT_OF_AMOUNT_AND_INTEREST:
case PERCENT_OF_INTEREST:
this.percentage = chargeAmount; this.percentage = chargeAmount;
this.amountPercentageAppliedTo = loanPrincipal; this.amountPercentageAppliedTo = amountPercentageAppliedTo;
this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage)); this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage));
this.amountPaid = null; this.amountPaid = null;
this.amountOutstanding = calculateOutstanding(); this.amountOutstanding = calculateOutstanding();
this.amountWaived = null; this.amountWaived = null;
this.amountWrittenOff = null; this.amountWrittenOff = null;
break; break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
this.percentage = null;
this.amount = null;
this.amountPercentageAppliedTo = null;
this.amountPaid = null;
this.amountOutstanding = BigDecimal.ZERO;
this.amountWaived = null;
this.amountWrittenOff = null;
break;
case PERCENT_OF_INTEREST:
this.percentage = null;
this.amount = null;
this.amountPercentageAppliedTo = null;
this.amountPaid = null;
this.amountOutstanding = BigDecimal.ZERO;
this.amountWaived = null;
this.amountWrittenOff = null;
break;
} }
} }
@@ -255,7 +259,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.loan = loan; this.loan = loan;
} }
public void update(final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal) { public void update(final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal, Integer numberOfRepayments) {
if (dueDate != null) { if (dueDate != null) {
this.dueDate = dueDate.toDate(); this.dueDate = dueDate.toDate();
} }
@@ -265,31 +269,28 @@ public class LoanCharge extends AbstractPersistable<Long> {
case INVALID: case INVALID:
break; break;
case FLAT: case FLAT:
this.amount = amount; if(this.isInstalmentFee()){
if(numberOfRepayments == null){
numberOfRepayments = this.loan.repaymentScheduleDetail().getNumberOfRepayments();
}
this.amount = amount.multiply(BigDecimal.valueOf(numberOfRepayments));
}else{
this.amount = amount;
}
break; break;
case PERCENT_OF_AMOUNT: case PERCENT_OF_AMOUNT:
case PERCENT_OF_AMOUNT_AND_INTEREST:
case PERCENT_OF_INTEREST:
this.percentage = amount; this.percentage = amount;
this.amountPercentageAppliedTo = loanPrincipal; this.amountPercentageAppliedTo = loanPrincipal;
this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage)); this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage));
this.amountOutstanding = calculateOutstanding(); this.amountOutstanding = calculateOutstanding();
break; break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
this.percentage = amount;
this.amount = null;
this.amountPercentageAppliedTo = null;
this.amountOutstanding = null;
break;
case PERCENT_OF_INTEREST:
this.percentage = amount;
this.amount = null;
this.amountPercentageAppliedTo = null;
this.amountOutstanding = null;
break;
} }
} }
} }
public Map<String, Object> update(final JsonCommand command, final BigDecimal loanPrincipal) { public Map<String, Object> update(final JsonCommand command, final BigDecimal amount) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(7); final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(7);
@@ -316,25 +317,19 @@ public class LoanCharge extends AbstractPersistable<Long> {
case INVALID: case INVALID:
break; break;
case FLAT: case FLAT:
this.amount = newValue; if(this.isInstalmentFee()){
this.amount = newValue.multiply(BigDecimal.valueOf(this.loan.repaymentScheduleDetail().getNumberOfRepayments()));
}else{
this.amount = newValue;
}
break; break;
case PERCENT_OF_AMOUNT: case PERCENT_OF_AMOUNT:
this.percentage = newValue;
this.amountPercentageAppliedTo = loanPrincipal;
this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage));
this.amountOutstanding = calculateOutstanding();
break;
case PERCENT_OF_AMOUNT_AND_INTEREST: case PERCENT_OF_AMOUNT_AND_INTEREST:
this.percentage = newValue;
this.amount = null;
this.amountPercentageAppliedTo = null;
this.amountOutstanding = null;
break;
case PERCENT_OF_INTEREST: case PERCENT_OF_INTEREST:
this.percentage = newValue; this.percentage = newValue;
this.amount = null; this.amountPercentageAppliedTo = amount;
this.amountPercentageAppliedTo = null; this.amount = minimumAndMaximumCap(percentageOf(this.amountPercentageAppliedTo, this.percentage));
this.amountOutstanding = null; this.amountOutstanding = calculateOutstanding();
break; break;
} }
} }
@@ -350,6 +345,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE); return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE);
} }
public boolean isInstalmentFee() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.INSTALMENT_FEE);
}
private boolean isGreaterThanZero(final BigDecimal value) { private boolean isGreaterThanZero(final BigDecimal value) {
return value.compareTo(BigDecimal.ZERO) == 1; return value.compareTo(BigDecimal.ZERO) == 1;
} }
@@ -367,11 +366,16 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
private boolean determineIfFullyPaid() { private boolean determineIfFullyPaid() {
if(this.amount == null){
return true;
}
return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0; return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
} }
private BigDecimal calculateOutstanding() { private BigDecimal calculateOutstanding() {
if(this.amount == null){
return null;
}
BigDecimal amountPaidLocal = BigDecimal.ZERO; BigDecimal amountPaidLocal = BigDecimal.ZERO;
if (this.amountPaid != null) { if (this.amountPaid != null) {
amountPaidLocal = this.amountPaid; amountPaidLocal = this.amountPaid;
@@ -573,4 +577,20 @@ public class LoanCharge extends AbstractPersistable<Long> {
public ChargePaymentMode getChargePaymentMode() { public ChargePaymentMode getChargePaymentMode() {
return ChargePaymentMode.fromInt(this.chargePaymentMode); return ChargePaymentMode.fromInt(this.chargePaymentMode);
} }
public ChargeCalculationType getChargeCalculation() {
return ChargeCalculationType.fromInt(this.chargeCalculation);
}
public BigDecimal getPercentage() {
return this.percentage;
}
public void updateAmount(BigDecimal amount) {
this.amount = amount;
calculateOutstanding();
}
} }
@@ -5,6 +5,7 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.domain; package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -25,14 +26,14 @@ public class LoanRepaymentScheduleProcessingWrapper {
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
currency); currency,period,repaymentPeriods.size());
final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges,
currency); currency);
final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
loanCharges, currency); loanCharges, currency);
final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(),
loanCharges, currency); loanCharges, currency,period,repaymentPeriods.size());
final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(),
loanCharges, currency); loanCharges, currency);
final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate,
@@ -47,13 +48,42 @@ public class LoanRepaymentScheduleProcessingWrapper {
} }
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges, private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency) { final MonetaryCurrency monetaryCurrency,LoanRepaymentScheduleInstallment period,int numberOfRepayments) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) { if (loanCharge.isFeeCharge()) {
cumulative = cumulative.plus(loanCharge.amount()); if (loanCharge.isInstalmentFee()) {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount()).add(
period.getInterestCharged(monetaryCurrency).getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(period.getInterestCharged(monetaryCurrency).getAmount());
} else {
amount = amount.add(period.getPrincipal(monetaryCurrency).getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getLoan().getSummary().getTotalPrincipalDisbursed()).add(period.getLoan().getSummary().getTotalInterestCharged());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(period.getLoan().getSummary().getTotalInterestCharged());
} else {
amount = amount.add(period.getLoan().getSummary().getTotalPrincipalDisbursed());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount());
}
} }
} }
@@ -89,13 +119,42 @@ public class LoanRepaymentScheduleProcessingWrapper {
} }
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency) { final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,LoanRepaymentScheduleInstallment period,int numberOfRepayments) {
Money cumulative = Money.zero(currency); Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { if (loanCharge.isPenaltyCharge()) {
cumulative = cumulative.plus(loanCharge.amount()); if (loanCharge.isInstalmentFee()) {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getPrincipal(currency).getAmount())
.add(period.getInterestCharged(currency).getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(period.getInterestCharged(currency).getAmount());
} else {
amount = amount.add(period.getPrincipal(currency).getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getLoan().getSummary().getTotalPrincipalDisbursed()).add(period.getLoan().getSummary().getTotalInterestCharged());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(period.getLoan().getSummary().getTotalInterestCharged());
} else {
amount = amount.add(period.getLoan().getSummary().getTotalPrincipalDisbursed());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount());
}
} }
} }
@@ -274,4 +274,9 @@ public final class LoanSummary {
.plus(this.totalFeeChargesOutstanding).plus(this.totalPenaltyChargesOutstanding); .plus(this.totalFeeChargesOutstanding).plus(this.totalPenaltyChargesOutstanding);
this.totalOutstanding = totalOutstanding.getAmount(); this.totalOutstanding = totalOutstanding.getAmount();
} }
public BigDecimal getTotalPrincipalDisbursed() {
return this.totalPrincipalDisbursed;
}
} }
@@ -257,14 +257,21 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) { private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) {
LoanCharge earliestUnpaidCharge = null; LoanCharge earliestUnpaidCharge = null;
LoanCharge installemntCharge = null;
for (final LoanCharge loanCharge : charges) { for (final LoanCharge loanCharge : charges) {
if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isNotFullyPaid() && !loanCharge.isDueAtDisbursement()) {
if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) { if(loanCharge.isInstalmentFee()){
if(installemntCharge == null){
installemntCharge = loanCharge;
}
}else if (earliestUnpaidCharge == null || loanCharge.getDueLocalDate().isBefore(earliestUnpaidCharge.getDueLocalDate())) {
earliestUnpaidCharge = loanCharge; earliestUnpaidCharge = loanCharge;
} }
} }
} }
if(earliestUnpaidCharge == null || earliestUnpaidCharge.getDueLocalDate().isAfter(new LocalDate())){
earliestUnpaidCharge = installemntCharge;
}
return earliestUnpaidCharge; return earliestUnpaidCharge;
} }
@@ -108,9 +108,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal()); outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal());
// 7. determine fees and penalties // 7. determine fees and penalties
final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, currency); final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, currency,
principalInterestForThisPeriod, principalDisbursed, totalInterestChargedForFullLoanTerm, numberOfRepayments);
final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges,
currency); currency, principalInterestForThisPeriod, principalDisbursed, totalInterestChargedForFullLoanTerm, numberOfRepayments);
// 8. sum up real totalInstallmentDue from components // 8. sum up real totalInstallmentDue from components
final Money totalInstallmentDue = principalInterestForThisPeriod.principal() // final Money totalInstallmentDue = principalInterestForThisPeriod.principal() //
@@ -174,13 +175,43 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges, private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency) { final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod,
final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) { if (loanCharge.isFeeCharge()) {
cumulative = cumulative.plus(loanCharge.amount()); if (loanCharge.isInstalmentFee()) {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalInterestForThisPeriod.principal().getAmount()).add(
principalInterestForThisPeriod.interest().getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
} else {
amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
} else {
amount = amount.add(principalDisbursed.getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount());
}
} }
} }
@@ -188,13 +219,44 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency) { final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) { if (loanCharge.isPenaltyCharge()) {
cumulative = cumulative.plus(loanCharge.amount()); if (loanCharge.isInstalmentFee()) {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalInterestForThisPeriod.principal().getAmount()).add(
principalInterestForThisPeriod.interest().getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
} else {
amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
} else {
amount = amount.add(principalDisbursed.getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount());
}
} }
} }
@@ -325,12 +325,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
existingLoanApplication.updateTransactionProcessingStrategy(strategy); existingLoanApplication.updateTransactionProcessingStrategy(strategy);
} }
final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) {
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson());
existingLoanApplication.updateLoanCharges(loanCharges);
}
final String collateralParamName = "collateral"; final String collateralParamName = "collateral";
if (changes.containsKey(collateralParamName)) { if (changes.containsKey(collateralParamName)) {
final Set<LoanCollateral> loanCollateral = this.loanCollateralAssembler.fromParsedJson(command.parsedJson()); final Set<LoanCollateral> loanCollateral = this.loanCollateralAssembler.fromParsedJson(command.parsedJson());
@@ -347,6 +341,12 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
existingLoanApplication.updateLoanSchedule(loanSchedule); existingLoanApplication.updateLoanSchedule(loanSchedule);
} }
final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) {
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson());
existingLoanApplication.updateLoanCharges(loanCharges);
}
this.loanRepository.saveAndFlush(existingLoanApplication); this.loanRepository.saveAndFlush(existingLoanApplication);
final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote"); final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
@@ -47,6 +47,7 @@ public class LoanChargeAssembler {
final Set<LoanCharge> loanCharges = new HashSet<LoanCharge>(); final Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
if (element.isJsonObject()) { if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject(); final JsonObject topLevelJsonElement = element.getAsJsonObject();
@@ -83,14 +84,14 @@ public class LoanChargeAssembler {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode); chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
} }
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime, final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum); chargeCalculation, dueDate, chargePaymentModeEnum,numberOfRepayments);
loanCharges.add(loanCharge); loanCharges.add(loanCharge);
} else { } else {
final Long loanChargeId = id; final Long loanChargeId = id;
final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId); final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId);
if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); } if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); }
loanCharge.update(amount, dueDate, principal); loanCharge.update(amount, dueDate, principal, numberOfRepayments);
loanCharges.add(loanCharge); loanCharges.add(loanCharge);
} }
@@ -925,7 +925,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loanCharge.isWaived()) { if (loanCharge.isWaived()) {
throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_WAIVED, loanCharge.getId()); throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaid()) { throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_PAID, } else if (loanCharge.isPaid()) { throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.ALREADY_PAID,
loanCharge.getId()); } loanCharge.getId());
} else if (loanCharge.isInstalmentFee()){
throw new LoanChargeCannotBeWaivedException(LOAN_CHARGE_CANNOT_BE_WAIVED_REASON.WAIVE_NOT_ALLOWED_FOR_CHARGE,
loanCharge.getId());
}
final Map<String, Object> changes = new LinkedHashMap<String, Object>(3); final Map<String, Object> changes = new LinkedHashMap<String, Object>(3);
@@ -1001,7 +1005,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loanCharge.isWaived()) { if (loanCharge.isWaived()) {
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId()); throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_WAIVED, loanCharge.getId());
} else if (loanCharge.isPaid()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID, } else if (loanCharge.isPaid()) { throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.ALREADY_PAID,
loanCharge.getId()); } loanCharge.getId());
}else if (loanCharge.isInstalmentFee()){
throw new LoanChargeCannotBePayedException(LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_PAYABLE,
loanCharge.getId());
}
if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { throw new LoanChargeCannotBePayedException( if (!loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) { throw new LoanChargeCannotBePayedException(
LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_ACCOUNT_TRANSFER, loanCharge.getId()); } LOAN_CHARGE_CANNOT_BE_PAYED_REASON.CHARGE_NOT_ACCOUNT_TRANSFER, loanCharge.getId()); }