diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java index 431db2d3f..d98328d23 100755 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanTransactionHelper.java @@ -203,6 +203,11 @@ public class LoanTransactionHelper { return (Integer) response.get("resourceId"); } + public Object addChargesForAllreadyDisursedLoan(final Integer loanId, final String request, final ResponseSpecification responseSpecification) { + final String ADD_CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges?" + Utils.TENANT_IDENTIFIER; + return Utils.performServerPost(this.requestSpec, responseSpecification, ADD_CHARGES_URL, request, ""); + } + public Integer updateChargesForLoan(final Integer loanId, final Integer loanchargeId, final String request) { System.out.println("--------------------------------- ADD CHARGES FOR LOAN --------------------------------"); final String UPDATE_CHARGES_URL = "/mifosng-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId + "?" diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java index 765432ddd..0107c4dd7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/data/ChargeData.java @@ -140,7 +140,6 @@ public class ChargeData implements Comparable, Serializable { final List feeFrequencyOptions = null; final GLAccountData account = null; final Map> incomeOrLiabilityAccountOptions = null; - return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java index cd4e0a041..1a307db3f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/Charge.java @@ -56,7 +56,7 @@ public class Charge extends AbstractPersistable { private Integer chargeAppliesTo; @Column(name = "charge_time_enum", nullable = false) - private Integer chargeTime; + private Integer chargeTimeType; @Column(name = "charge_calculation_enum") private Integer chargeCalculation; @@ -133,7 +133,7 @@ public class Charge extends AbstractPersistable { this.amount = amount; this.currencyCode = currencyCode; this.chargeAppliesTo = chargeAppliesTo.getValue(); - this.chargeTime = chargeTime.getValue(); + this.chargeTimeType = chargeTime.getValue(); this.chargeCalculation = chargeCalculationType.getValue(); this.penalty = penalty; this.active = active; @@ -154,7 +154,7 @@ public class Charge extends AbstractPersistable { // TODO vishwas, this validation seems unnecessary as identical // validation is performed in the write service if (!isAllowedSavingsChargeTime()) { - baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime) + baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings"); } // TODO vishwas, this validation seems unnecessary as identical @@ -164,7 +164,7 @@ public class Charge extends AbstractPersistable { .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings"); } - if (!ChargeTimeType.fromInt(getChargeTime()).isWithdrawalFee() + if (!ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee() && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) { baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) .failWithCodeNoParameterAddedToErrorCode("savings.charge.calculation.type.percentage.allowed.only.for.withdrawal"); @@ -172,16 +172,17 @@ public class Charge extends AbstractPersistable { } else if (isLoanCharge()) { - if (penalty && chargeTime.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); } + if (penalty && (chargeTime.isTimeOfDisbursement() + || chargeTime.isTrancheDisbursement())) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); } if (!penalty && chargeTime.isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); } // TODO vishwas, this validation seems unnecessary as identical // validation is performed in the write service if (!isAllowedLoanChargeTime()) { - baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime) + baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan"); } } - if (isPercentageOfAmount()) { + if (isPercentageOfApprovedAmount()) { this.minCap = minCap; this.maxCap = maxCap; } @@ -219,8 +220,8 @@ public class Charge extends AbstractPersistable { return this.currencyCode; } - public Integer getChargeTime() { - return this.chargeTime; + public Integer getChargeTimeType() { + return this.chargeTimeType; } public Integer getChargeCalculation() { @@ -244,11 +245,11 @@ public class Charge extends AbstractPersistable { } public boolean isAllowedLoanChargeTime() { - return ChargeTimeType.fromInt(this.chargeTime).isAllowedLoanChargeTime(); + return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedLoanChargeTime(); } public boolean isAllowedClientChargeTime() { - return ChargeTimeType.fromInt(this.chargeTime).isAllowedClientChargeTime(); + return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedClientChargeTime(); } public boolean isSavingsCharge() { @@ -260,7 +261,7 @@ public class Charge extends AbstractPersistable { } public boolean isAllowedSavingsChargeTime() { - return ChargeTimeType.fromInt(this.chargeTime).isAllowedSavingsChargeTime(); + return ChargeTimeType.fromInt(this.chargeTimeType).isAllowedSavingsChargeTime(); } public boolean isAllowedSavingsChargeCalculationType() { @@ -271,10 +272,14 @@ public class Charge extends AbstractPersistable { return ChargeCalculationType.fromInt(this.chargeCalculation).isAllowedClientChargeCalculationType(); } - public boolean isPercentageOfAmount() { + public boolean isPercentageOfApprovedAmount() { return ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount(); } + public boolean isPercentageOfDisbursementAmount() { + return ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfDisbursementAmount(); + } + public BigDecimal getMinCap() { return this.minCap; } @@ -315,15 +320,15 @@ public class Charge extends AbstractPersistable { } final String chargeTimeParamName = "chargeTimeType"; - if (command.isChangeInIntegerParameterNamed(chargeTimeParamName, this.chargeTime)) { + if (command.isChangeInIntegerParameterNamed(chargeTimeParamName, this.chargeTimeType)) { final Integer newValue = command.integerValueOfParameterNamed(chargeTimeParamName); actualChanges.put(chargeTimeParamName, newValue); actualChanges.put("locale", localeAsInput); - this.chargeTime = ChargeTimeType.fromInt(newValue).getValue(); + this.chargeTimeType = ChargeTimeType.fromInt(newValue).getValue(); if (isSavingsCharge()) { if (!isAllowedSavingsChargeTime()) { - baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime) + baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings"); } // if charge time is changed to monthly then validate for @@ -337,12 +342,12 @@ public class Charge extends AbstractPersistable { } } else if (isLoanCharge()) { if (!isAllowedLoanChargeTime()) { - baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime) + baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan"); } } else if (isClientCharge()) { if (!isAllowedLoanChargeTime()) { - baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime) + baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTimeType) .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.client"); } } @@ -376,7 +381,7 @@ public class Charge extends AbstractPersistable { .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings"); } - if (!ChargeTimeType.fromInt(getChargeTime()).isWithdrawalFee() + if (!ChargeTimeType.fromInt(getChargeTimeType()).isWithdrawalFee() && ChargeCalculationType.fromInt(getChargeCalculation()).isPercentageOfAmount()) { baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation) .failWithCodeNoParameterAddedToErrorCode("charge.calculation.type.percentage.allowed.only.for.withdrawal"); @@ -451,7 +456,7 @@ public class Charge extends AbstractPersistable { this.active = newValue; } // allow min and max cap to be only added to PERCENT_OF_AMOUNT for now - if (isPercentageOfAmount()) { + if (isPercentageOfApprovedAmount()) { final String minCapParamName = "minCap"; if (command.isChangeInBigDecimalParameterNamed(minCapParamName, this.minCap)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minCapParamName); @@ -466,11 +471,13 @@ public class Charge extends AbstractPersistable { actualChanges.put("locale", localeAsInput); this.maxCap = newValue; } + } - if (this.penalty && ChargeTimeType.fromInt(this.chargeTime) + if (this.penalty && ChargeTimeType.fromInt(this.chargeTimeType) .isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(this.name); } - if (!penalty && ChargeTimeType.fromInt(this.chargeTime).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); } + if (!penalty + && ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); } if (command.isChangeInLongParameterNamed(ChargesApiConstants.glAccountIdParamName, getIncomeAccountId())) { final Long newValue = command.longValueOfParameterNamed(ChargesApiConstants.glAccountIdParamName); @@ -495,7 +502,7 @@ public class Charge extends AbstractPersistable { public ChargeData toData() { - final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(this.chargeTime); + final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(this.chargeTimeType); final EnumOptionData chargeAppliesTo = ChargeEnumerations.chargeAppliesTo(this.chargeAppliesTo); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation); final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode); @@ -519,15 +526,15 @@ public class Charge extends AbstractPersistable { } public boolean isMonthlyFee() { - return ChargeTimeType.fromInt(this.chargeTime).isMonthlyFee(); + return ChargeTimeType.fromInt(this.chargeTimeType).isMonthlyFee(); } public boolean isAnnualFee() { - return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee(); + return ChargeTimeType.fromInt(this.chargeTimeType).isAnnualFee(); } public boolean isOverdueInstallment() { - return ChargeTimeType.fromInt(this.chargeTime).isOverdueInstallment(); + return ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment(); } public MonthDay getFeeOnMonthDay() { @@ -561,4 +568,9 @@ public class Charge extends AbstractPersistable { } return incomeAccountId; } + + public boolean isDisbursementCharge() { + return ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.DISBURSEMENT) + || ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.TRANCHE_DISBURSEMENT); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java index f5b619fe1..6eb153dcb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeCalculationType.java @@ -11,7 +11,8 @@ public enum ChargeCalculationType { FLAT(1, "chargeCalculationType.flat"), // PERCENT_OF_AMOUNT(2, "chargeCalculationType.percent.of.amount"), // PERCENT_OF_AMOUNT_AND_INTEREST(3, "chargeCalculationType.percent.of.amount.and.interest"), // - PERCENT_OF_INTEREST(4, "chargeCalculationType.percent.of.interest"); + PERCENT_OF_INTEREST(4, "chargeCalculationType.percent.of.interest"), + PERCENT_OF_DISBURSEMENT_AMOUNT(5,"chargeCalculationType.percent.of.disbursement.amount"); private final Integer value; private final String code; @@ -31,7 +32,8 @@ public enum ChargeCalculationType { public static Object[] validValuesForLoan() { return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue(), - ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue(), ChargeCalculationType.PERCENT_OF_INTEREST.getValue() }; + ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue(), ChargeCalculationType.PERCENT_OF_INTEREST.getValue(), + ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue()}; } public static Object[] validValuesForSavings() { @@ -57,6 +59,9 @@ public enum ChargeCalculationType { case 4: chargeCalculationType = PERCENT_OF_INTEREST; break; + case 5: + chargeCalculationType = PERCENT_OF_DISBURSEMENT_AMOUNT; + break; } return chargeCalculationType; } @@ -86,6 +91,10 @@ public enum ChargeCalculationType { } public boolean isPercentageBased() { - return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest(); + return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest() || isPercentageOfDisbursementAmount(); + } + + public boolean isPercentageOfDisbursementAmount(){ + return this.value.equals(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue()); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java index 8e24cea61..e443897b4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/domain/ChargeTimeType.java @@ -10,18 +10,16 @@ public enum ChargeTimeType { INVALID(0, "chargeTimeType.invalid"), // DISBURSEMENT(1, "chargeTimeType.disbursement"), // only for loan charges SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), // for loan and - // savings charges SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for - // savings SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings MONTHLY_FEE(7, "chargeTimeType.monthlyFee"), // only for savings INSTALMENT_FEE(8, "chargeTimeType.instalmentFee"), // only for loan charges OVERDUE_INSTALLMENT(9, "chargeTimeType.overdueInstallment"), // only for - // loan charges - OVERDRAFT_FEE(10, "chargeTimeType.overdraftFee"), // only for savings - WEEKLY_FEE(11, "chargeTimeType.weeklyFee"); // only for savings + OVERDRAFT_FEE(10, "chargeTimeType.overdraftFee"),// only for savings + WEEKLY_FEE(11, "chargeTimeType.weeklyFee"), // only for savings + TRANCHE_DISBURSEMENT(12,"chargeTimeType.tranchedisbursement"); // only for loan private final Integer value; private final String code; @@ -41,7 +39,7 @@ public enum ChargeTimeType { public static Object[] validLoanValues() { return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(), - ChargeTimeType.INSTALMENT_FEE.getValue(), ChargeTimeType.OVERDUE_INSTALLMENT.getValue() }; + ChargeTimeType.INSTALMENT_FEE.getValue(), ChargeTimeType.OVERDUE_INSTALLMENT.getValue(), ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() }; } public static Object[] validLoanChargeValues() { @@ -96,6 +94,9 @@ public enum ChargeTimeType { case 11: chargeTimeType = WEEKLY_FEE; break; + case 12: + chargeTimeType = TRANCHE_DISBURSEMENT; + break; default: chargeTimeType = INVALID; break; @@ -145,7 +146,7 @@ public enum ChargeTimeType { } public boolean isAllowedLoanChargeTime() { - return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee() || isOverdueInstallment(); + return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee() || isOverdueInstallment() || isTrancheDisbursement(); } public boolean isAllowedClientChargeTime() { @@ -160,5 +161,9 @@ public enum ChargeTimeType { public boolean isOverdraftFee() { return this.value.equals(ChargeTimeType.OVERDRAFT_FEE.getValue()); } + + public boolean isTrancheDisbursement(){ + return this.value.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java index eccf23bf6..fc54137a9 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java @@ -104,7 +104,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType) .isOneOfTheseValues(ChargeCalculationType.validValuesForLoan()); } - + } else if (appliesTo.isSavingsCharge()) { // savings applicable validation final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element); @@ -263,7 +263,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer { if (this.fromApiJsonHelper.parameterExists("chargeCalculationType", element)) { final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element, Locale.getDefault()); - baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 4); + baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 5); } if (this.fromApiJsonHelper.parameterExists("chargePaymentMode", element)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java index de6370fd0..f6b1ce395 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformService.java @@ -26,9 +26,7 @@ public interface ChargeDropdownReadPlatformService { List retrieveSavingsCalculationTypes(); List retrieveSavingsCollectionTimeTypes(); - List retrieveClientCalculationTypes(); List retrieveClientCollectionTimeTypes(); - } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java index 1ee92d0f7..8ca7e9af3 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeDropdownReadPlatformServiceImpl.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; + import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.*; @Service @@ -27,7 +28,8 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST), - chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST)); + chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST), + chargeCalculationType(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT)); } @Override @@ -64,13 +66,15 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT), chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST), - chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST)); + chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST), + chargeCalculationType(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT)); } @Override public List retrieveLoanCollectionTimeTypes() { return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE), - chargeTimeType(ChargeTimeType.INSTALMENT_FEE), chargeTimeType(ChargeTimeType.OVERDUE_INSTALLMENT)); + chargeTimeType(ChargeTimeType.INSTALMENT_FEE), chargeTimeType(ChargeTimeType.OVERDUE_INSTALLMENT), + chargeTimeType(ChargeTimeType.TRANCHE_DISBURSEMENT)); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java index 6ed44fc4d..6cc8493f5 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeEnumerations.java @@ -64,6 +64,10 @@ public class ChargeEnumerations { optionData = new EnumOptionData(ChargeTimeType.OVERDRAFT_FEE.getValue().longValue(), ChargeTimeType.OVERDRAFT_FEE.getCode(), "Overdraft Fee"); break; + case TRANCHE_DISBURSEMENT: + optionData = new EnumOptionData(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().longValue(), + ChargeTimeType.TRANCHE_DISBURSEMENT.getCode(), "Tranche Disbursement"); + break; default: optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid"); break; @@ -109,7 +113,7 @@ public class ChargeEnumerations { break; case PERCENT_OF_AMOUNT: optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue().longValue(), - ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Amount"); + ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Approved Amount"); break; case PERCENT_OF_AMOUNT_AND_INTEREST: optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue().longValue(), @@ -119,6 +123,10 @@ public class ChargeEnumerations { optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_INTEREST.getValue().longValue(), ChargeCalculationType.PERCENT_OF_INTEREST.getCode(), "% Interest"); break; + case PERCENT_OF_DISBURSEMENT_AMOUNT: + optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue().longValue(), + ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getCode(), "% Disbursement Amount"); + break; default: optionData = new EnumOptionData(ChargeCalculationType.INVALID.getValue().longValue(), ChargeCalculationType.INVALID.getCode(), "Invalid"); @@ -145,5 +153,5 @@ public class ChargeEnumerations { } return optionData; } - + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java index 6ecc1f4f2..692b97d48 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/charge/service/ChargeReadPlatformServiceImpl.java @@ -8,8 +8,8 @@ package org.mifosplatform.portfolio.charge.service; import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,6 +34,7 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service; /** @@ -49,6 +50,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService private final DropdownReadPlatformService dropdownReadPlatformService; private final MifosEntityAccessUtil mifosEntityAccessUtil; private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; @Autowired public ChargeReadPlatformServiceImpl(final CurrencyReadPlatformService currencyReadPlatformService, @@ -61,6 +63,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService this.dropdownReadPlatformService = dropdownReadPlatformService; this.mifosEntityAccessUtil = mifosEntityAccessUtil; this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; + this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } @Override @@ -172,14 +175,15 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService public Collection retrieveLoanAccountApplicableCharges(final Long loanId, ChargeTimeType[] excludeChargeTimes) { final ChargeMapper rm = new ChargeMapper(); StringBuilder excludeClause = new StringBuilder(""); - Object[] params = new Object[] { loanId, ChargeAppliesTo.LOAN.getValue() }; - params = processChargeExclusionsForLoans(excludeChargeTimes, excludeClause, params); - String sql = "select " + rm.chargeSchema() + " join m_loan la on la.currency_code = c.currency_code" + " where la.id=?" - + " and c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=?" + excludeClause + " "; + Map paramMap = new HashMap<>(); + paramMap.put("loanId", loanId); + paramMap.put("chargeAppliesTo", ChargeAppliesTo.LOAN.getValue()); + processChargeExclusionsForLoans(excludeChargeTimes, excludeClause); + String sql = "select " + rm.chargeSchema() + " join m_loan la on la.currency_code = c.currency_code" + " where la.id=:loanId" + + " and c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=:chargeAppliesTo" + excludeClause + " "; sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled(); sql += " order by c.name "; - - return this.jdbcTemplate.query(sql, rm, params); + return this.namedParameterJdbcTemplate.query(sql, paramMap, rm); } /** @@ -188,9 +192,8 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService * @param params * @return */ - private Object[] processChargeExclusionsForLoans(ChargeTimeType[] excludeChargeTimes, StringBuilder excludeClause, Object[] params) { + private void processChargeExclusionsForLoans(ChargeTimeType[] excludeChargeTimes, StringBuilder excludeClause) { if (excludeChargeTimes != null && excludeChargeTimes.length > 0) { - excludeClause = excludeClause.append(" and c.charge_time_enum not in(?) "); StringBuilder sb = new StringBuilder(); for (int i = 0; i < excludeChargeTimes.length; i++) { if (i != 0) { @@ -198,24 +201,25 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService } sb.append(excludeChargeTimes[i].getValue()); } - params = Arrays.copyOf(params, params.length + 1); - params[params.length - 1] = sb.toString(); + excludeClause = excludeClause.append(" and c.charge_time_enum not in(" + sb.toString() + ") "); } - return params; } @Override public Collection retrieveLoanProductApplicableCharges(final Long loanProductId, ChargeTimeType[] excludeChargeTimes) { final ChargeMapper rm = new ChargeMapper(); StringBuilder excludeClause = new StringBuilder(""); - Object[] params = new Object[] { loanProductId, ChargeAppliesTo.LOAN.getValue() }; - params = processChargeExclusionsForLoans(excludeChargeTimes, excludeClause, params); - String sql = "select " + rm.chargeSchema() + " join m_product_loan lp on lp.currency_code = c.currency_code" + " where lp.id=?" - + " and c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=?" + excludeClause + " "; + Map paramMap = new HashMap<>(); + paramMap.put("productId", loanProductId); + paramMap.put("chargeAppliesTo", ChargeAppliesTo.LOAN.getValue()); + processChargeExclusionsForLoans(excludeChargeTimes, excludeClause); + String sql = "select " + rm.chargeSchema() + " join m_product_loan lp on lp.currency_code = c.currency_code" + + " where lp.id=:productId" + " and c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=:chargeAppliesTo" + + excludeClause + " "; sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled(); sql += " order by c.name "; - return this.jdbcTemplate.query(sql, rm, params); + return this.namedParameterJdbcTemplate.query(sql, paramMap, rm); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java index 6c6b53f5b..6d8573d2f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientCharge.java @@ -102,7 +102,7 @@ public class ClientCharge extends AbstractPersistable { this.client = client; this.charge = charge; this.penaltyCharge = charge.isPenalty(); - this.chargeTime = charge.getChargeTime(); + this.chargeTime = charge.getChargeTimeType(); this.dueDate = (dueDate == null) ? null : dueDate.toDate(); this.chargeCalculation = charge.getChargeCalculation(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java index b5b8fcf33..32e88f566 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java @@ -15,8 +15,10 @@ public interface LoanApiConstants { public static final String updatedDisbursementDateParameterName = "updatedExpectedDisbursementDate"; public static final String updatedDisbursementPrincipalParameterName = "updatedPrincipal"; public static final String disbursementIdParameterName = "id"; + public static final String loanChargeIdParameterName = "loanChargeId"; public static final String principalDisbursedParameterName = "transactionAmount"; - + public static final String chargesParameterName = "charges" ; + public static final String approvedLoanAmountParameterName = "approvedLoanAmount"; public static final String approvedOnDateParameterName = "approvedOnDate"; public static final String noteParameterName = "note"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index b374963c6..93df1a58b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -405,10 +405,9 @@ public class LoansApiResource { if (associationParameters.contains("repaymentSchedule")) { mandatoryResponseParameters.add("repaymentSchedule"); - final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData(); repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData, - disbursementData, loanBasicDetails.isInterestRecalculationEnabled()); + disbursementData, loanBasicDetails.isInterestRecalculationEnabled(), loanBasicDetails.getTotalPaidFeeCharges()); if (associationParameters.contains("futureSchedule") && loanBasicDetails.isInterestRecalculationEnabled()) { mandatoryResponseParameters.add("futureSchedule"); @@ -494,8 +493,13 @@ public class LoansApiResource { fundOptions = this.fundReadPlatformService.retrieveAllFunds(); repaymentStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies(); - chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId, - new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT }); + if(product.getMultiDisburseLoan()){ + chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId, + new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT }); + }else{ + chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId, + new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT }); + } chargeTemplate = this.loanChargeReadPlatformService.retrieveLoanChargeTemplate(); allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(loanBasicDetails.officeId(), diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java index 7ed6a6bd8..943e15562 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/DisbursementData.java @@ -19,13 +19,17 @@ public class DisbursementData implements Comparable { private final LocalDate expectedDisbursementDate; private final LocalDate actualDisbursementDate; private final BigDecimal principal; + private final String loanChargeId; + private final BigDecimal chargeAmount; public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate, - final BigDecimal principalDisbursed) { + final BigDecimal principalDisbursed, final String loanChargeId, BigDecimal chargeAmount) { this.id = id; this.expectedDisbursementDate = expectedDisbursementDate; this.actualDisbursementDate = actualDisbursementDate; this.principal = principalDisbursed; + this.loanChargeId = loanChargeId; + this.chargeAmount = chargeAmount; } public LocalDate disbursementDate() { @@ -40,6 +44,10 @@ public class DisbursementData implements Comparable { return this.principal; } + public BigDecimal getChargeAmount(){ + return this.chargeAmount; + } + public boolean isDisbursed() { return this.actualDisbursementDate != null; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index f1b26a19d..e390511cf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -1387,4 +1387,12 @@ public class LoanAccountData { return LoanStatus.fromInt(this.status.id().intValue()).isActive(); } + public boolean isMultiDisburseLoan() { + return multiDisburseLoan ; + } + + public BigDecimal getTotalPaidFeeCharges() { + if(this.summary != null) return this.summary.getTotalPaidFeeCharges() ; + return BigDecimal.ZERO ; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java index 7c9e82e20..b34966184 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargeData.java @@ -68,6 +68,7 @@ public class LoanChargeData { private BigDecimal amountAccrued; private BigDecimal amountUnrecognized; + public static LoanChargeData template(final Collection chargeOptions) { return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null, null, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanSummaryData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanSummaryData.java index 8c3362b2b..2bd11df96 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanSummaryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanSummaryData.java @@ -102,4 +102,8 @@ public class LoanSummaryData { public BigDecimal getTotalOutstanding() { return this.totalOutstanding; } + + public BigDecimal getTotalPaidFeeCharges() { + return feeChargesPaid ; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java index f70fc3c53..c421926a0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java @@ -62,6 +62,6 @@ public class RepaymentScheduleRelatedLoanData { } public DisbursementData disbursementData() { - return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal); + return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, null, null); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 5b1269526..7c4095fe9 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -8,9 +8,34 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; -import javax.persistence.*; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; +import javax.persistence.Version; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; @@ -21,7 +46,6 @@ import org.joda.time.LocalDateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.codes.domain.CodeValue; -import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; @@ -43,7 +67,9 @@ import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; +import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; +import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.collateral.data.CollateralData; @@ -60,17 +86,39 @@ import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; -import org.mifosplatform.portfolio.loanaccount.exception.*; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.*; +import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException; +import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; +import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; +import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException; +import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException; +import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException; +import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException; +import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; +import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException; +import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; -import org.mifosplatform.portfolio.loanproduct.domain.*; +import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; +import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; +import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.useradministration.domain.AppUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.stereotype.Component; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -236,6 +284,10 @@ public class Loan extends AbstractPersistable { @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) private Set charges = new HashSet<>(); + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) + private Set trancheCharges = new HashSet<>(); + @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) private Set collateral = null; @@ -284,6 +336,7 @@ public class Loan extends AbstractPersistable { @LazyCollection(LazyCollectionOption.FALSE) @OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true) + @OrderBy(value = "expectedDisbursementDate, id") private Set disbursementDetails = new HashSet<>(); @OrderBy(value = "termApplicableFrom, id") @@ -443,6 +496,9 @@ public class Loan extends AbstractPersistable { private Set associateChargesWithThisLoan(final Set loanCharges) { for (final LoanCharge loanCharge : loanCharges) { loanCharge.update(this); + if(loanCharge.getTrancheDisbursementCharge() != null) { + addTrancheLoanCharge(loanCharge.getCharge()) ; + } } return loanCharges; } @@ -466,7 +522,7 @@ public class Loan extends AbstractPersistable { validateLoanIsNotClosed(loanCharge); - if (isDisbursed() && loanCharge.isDueAtDisbursement()) { + if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) { // Note: added this constraint to restrict adding disbursement // charges to a loan // after it is disbursed @@ -511,7 +567,9 @@ public class Loan extends AbstractPersistable { if (this.charges == null) { this.charges = new HashSet<>(); } + this.charges.add(loanCharge); + this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); // store Id's of existing loan transactions and existing reversed loan @@ -578,8 +636,8 @@ public class Loan extends AbstractPersistable { final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount, transactionDate, feeCharges, penaltyCharges, DateUtils.getLocalDateTimeOfTenant(), currentUser); Integer installmentNumber = null; - final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, - loanCharge.getAmount(getCurrency()).getAmount(), installmentNumber); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, loanCharge.getAmount( + getCurrency()).getAmount(), installmentNumber); applyLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); this.loanTransactions.add(applyLoanChargeTransaction); return applyLoanChargeTransaction; @@ -588,8 +646,8 @@ public class Loan extends AbstractPersistable { private void handleChargePaidTransaction(final LoanCharge charge, final LoanTransaction chargesPayment, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Integer installmentNumber) { chargesPayment.updateLoan(this); - final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, - chargesPayment.getAmount(getCurrency()).getAmount(), installmentNumber); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, chargesPayment.getAmount(getCurrency()) + .getAmount(), installmentNumber); chargesPayment.getLoanChargesPaid().add(loanChargePaidBy); this.loanTransactions.add(chargesPayment); final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, @@ -627,8 +685,7 @@ public class Loan extends AbstractPersistable { private void validateLoanChargeIsNotWaived(final LoanCharge loanCharge) { if (loanCharge.isWaived()) { final String defaultUserMessage = "This loan charge cannot be removed as the charge as already been waived."; - throw new LoanChargeCannotBeAddedException("loanCharge", "loanCharge.is.waived", defaultUserMessage, getId(), - loanCharge.name()); + throw new LoanChargeCannotBeAddedException("loanCharge", "loanCharge.is.waived", defaultUserMessage, getId(), loanCharge.name()); } } @@ -661,10 +718,9 @@ public class Loan extends AbstractPersistable { // if you want ability to remove loan charges that are waived. validateLoanChargeIsNotWaived(loanCharge); - LoanCharge charge = fetchLoanChargesById(loanCharge.getId()); - final boolean removed = charge.isActive(); + final boolean removed = loanCharge.isActive(); if (removed) { - charge.setActive(false); + loanCharge.setActive(false); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); @@ -687,7 +743,7 @@ public class Loan extends AbstractPersistable { loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); } - + this.charges.remove(loanCharge); updateLoanSummaryDerivedFields(); } @@ -695,7 +751,7 @@ public class Loan extends AbstractPersistable { if (loanCharge.isDueAtDisbursement()) { LoanTransaction transactionToRemove = null; for (final LoanTransaction transaction : this.loanTransactions) { - if (transaction.isRepaymentAtDisbursement()) { + if (transaction.isRepaymentAtDisbursement() && transaction.getLoanChargesPaid().contains(loanCharge)) { final MonetaryCurrency currency = loanCurrency(); final Money chargeAmount = Money.of(currency, loanCharge.amount()); @@ -772,6 +828,13 @@ public class Loan extends AbstractPersistable { case PERCENT_OF_INTEREST: amount = getTotalInterest(); break; + case PERCENT_OF_DISBURSEMENT_AMOUNT: + if (loanCharge.getTrancheDisbursementCharge() != null) { + amount = loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().principal(); + } else { + amount = getPrincpal().getAmount(); + } + break; default: break; } @@ -868,10 +931,11 @@ public class Loan extends AbstractPersistable { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, transactionDate, - feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser); - final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, - waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber); + final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, + transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), + currentUser); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, waiveLoanChargeTransaction + .getAmount(getCurrency()).getAmount(), loanInstallmentNumber); waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); this.loanTransactions.add(waiveLoanChargeTransaction); @@ -964,11 +1028,25 @@ public class Loan extends AbstractPersistable { LoanCharge charge = loanCharge; // add new charges if (loanCharge.getId() == null) { + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; loanCharge.update(this); + if (this.loanProduct.isMultiDisburseLoan()) { + loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(this); + for (final LoanDisbursementDetails loanDisbursementDetails : this.disbursementDetails) { + if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().getId() == null) { + if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().equals(loanDisbursementDetails)) { + loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, loanDisbursementDetails); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + } + + } + } + } this.charges.add(loanCharge); + } else { charge = fetchLoanChargesById(charge.getId()); - existingCharges.remove(charge.getId()); + if (charge != null) existingCharges.remove(charge.getId()); } final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge); BigDecimal chargeAmt = BigDecimal.ZERO; @@ -984,8 +1062,10 @@ public class Loan extends AbstractPersistable { chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments())); } } - charge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(), - totalChargeAmt); + if (charge != null) + charge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(), + totalChargeAmt); + } /** Updated deleted charges **/ @@ -1110,7 +1190,7 @@ public class Loan extends AbstractPersistable { final Money principal = this.loanRepaymentScheduleDetail.getPrincipal(); this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper, - isDisbursed()); + isDisbursed(), this.charges); updateLoanOutstandingBalaces(); } } @@ -1414,13 +1494,13 @@ public class Loan extends AbstractPersistable { } if (this.loanInterestRecalculationDetails == null) { actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, true); - this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom( - this.loanProduct.getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), - this.loanProduct.getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), - this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), - this.loanProduct.getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, - this.loanProduct.getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), - this.loanProduct.getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); + this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct + .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct + .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct + .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct + .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); this.loanInterestRecalculationDetails.updateLoan(this); } else { @@ -1505,96 +1585,195 @@ public class Loan extends AbstractPersistable { return amount; } - public void updateDisbursementDetails(final JsonCommand command, final Map actualChanges) { + // This method returns date format and locale if present in the JsonCommand + private Map getDateFormatAndLocale(final JsonCommand jsonCommand) { + Map returnObject = new HashMap<>(); + JsonElement jsonElement = jsonCommand.parsedJson(); + if (jsonElement.isJsonObject()) { + JsonObject topLevel = jsonElement.getAsJsonObject(); + if (topLevel.has(LoanApiConstants.dateFormatParameterName) + && topLevel.get(LoanApiConstants.dateFormatParameterName).isJsonPrimitive()) { + final JsonPrimitive primitive = topLevel.get(LoanApiConstants.dateFormatParameterName).getAsJsonPrimitive(); + returnObject.put(LoanApiConstants.dateFormatParameterName, primitive.getAsString()); + } + if (topLevel.has(LoanApiConstants.localeParameterName) && topLevel.get(LoanApiConstants.localeParameterName).isJsonPrimitive()) { + final JsonPrimitive primitive = topLevel.get(LoanApiConstants.localeParameterName).getAsJsonPrimitive(); + String localeString = primitive.getAsString(); + returnObject.put(LoanApiConstants.localeParameterName, localeString); + } + } + return returnObject; + } - List list = fetchDisbursementIds(); - - if (command.parameterExists(LoanApiConstants.disbursementDataParameterName)) { - final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName); - if (disbursementDataArray != null && disbursementDataArray.size() > 0) { - String dateFormat = null; - Locale locale = null; - if (command.parsedJson().isJsonObject()) { - JsonObject topLevel = command.parsedJson().getAsJsonObject(); - final String dateFormatParameter = "dateFormat"; - - if (topLevel.has(dateFormatParameter) && topLevel.get(dateFormatParameter).isJsonPrimitive()) { - final JsonPrimitive primitive = topLevel.get(dateFormatParameter).getAsJsonPrimitive(); - dateFormat = primitive.getAsString(); - } - - final String localeParameter = "locale"; - if (topLevel.has(localeParameter) && topLevel.get(localeParameter).isJsonPrimitive()) { - final JsonPrimitive primitive = topLevel.get(localeParameter).getAsJsonPrimitive(); - String localeString = primitive.getAsString(); - locale = JsonParserHelper.localeFromString(localeString); - } - } - int i = 0; - do { - final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject(); - if (jsonObject.has(LoanApiConstants.disbursementDateParameterName) - && jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)) { - Date expectedDisbursementDate = null; - BigDecimal principal = null; - LocalDate date = null; - Long id = null; - if (jsonObject.get(LoanApiConstants.disbursementDateParameterName) != null - && jsonObject.get(LoanApiConstants.disbursementDateParameterName).isJsonPrimitive()) { - - final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.disbursementDateParameterName) - .getAsJsonPrimitive(); - final String valueAsString = primitive.getAsString(); - if (StringUtils.isNotBlank(valueAsString)) { - date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.disbursementDateParameterName, - dateFormat, locale); - } - } - if (date != null) { - expectedDisbursementDate = date.toDate(); - } - - if (jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() && StringUtils - .isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { - principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName) - .getAsBigDecimal(); - } - if (jsonObject.has(LoanApiConstants.disbursementIdParameterName) - && jsonObject.get(LoanApiConstants.disbursementIdParameterName).isJsonPrimitive() - && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) { - id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong(); - } - if (id != null) { - LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id); - list.remove(id); - if (loanDisbursementDetail.actualDisbursementDate() == null) { - Date actualDisbursementDate = null; - LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, - actualDisbursementDate, principal); - if (!loanDisbursementDetail.equals(disbursementDetails)) { - loanDisbursementDetail.copy(disbursementDetails); - actualChanges.put("disbursementDetailId", id); - actualChanges.put("recalculateLoanSchedule", true); - } - } - } else { - Date actualDisbursementDate = null; - LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, - actualDisbursementDate, principal); - disbursementDetails.updateLoan(this); - this.disbursementDetails.add(disbursementDetails); - actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal); - actualChanges.put("recalculateLoanSchedule", true); - } - } - i++; - } while (i < disbursementDataArray.size()); - for (Long id : list) { - this.disbursementDetails.remove(fetchLoanDisbursementsById(id)); - actualChanges.put("recalculateLoanSchedule", true); + private Map parseDisbursementDetails(final JsonObject jsonObject, String dateFormat, Locale locale) { + Map returnObject = new HashMap<>(); + if (jsonObject.get(LoanApiConstants.disbursementDateParameterName) != null + && jsonObject.get(LoanApiConstants.disbursementDateParameterName).isJsonPrimitive()) { + final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.disbursementDateParameterName).getAsJsonPrimitive(); + final String valueAsString = primitive.getAsString(); + if (StringUtils.isNotBlank(valueAsString)) { + LocalDate date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.disbursementDateParameterName, dateFormat, + locale); + if (date != null) { + returnObject.put(LoanApiConstants.disbursementDateParameterName, date.toDate()); } } } + + if (jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() + && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { + BigDecimal principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); + returnObject.put(LoanApiConstants.disbursementPrincipalParameterName, principal); + } + + if (jsonObject.has(LoanApiConstants.disbursementIdParameterName) + && jsonObject.get(LoanApiConstants.disbursementIdParameterName).isJsonPrimitive() + && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) { + Long id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong(); + returnObject.put(LoanApiConstants.disbursementIdParameterName, id); + } + + if (jsonObject.has(LoanApiConstants.loanChargeIdParameterName) + && jsonObject.get(LoanApiConstants.loanChargeIdParameterName).isJsonPrimitive() + && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.loanChargeIdParameterName).getAsString()))) { + returnObject.put(LoanApiConstants.loanChargeIdParameterName, + jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString()); + } + return returnObject; + } + + public void updateDisbursementDetails(final JsonCommand jsonCommand, final Map actualChanges) { + + List disbursementList = fetchDisbursementIds(); + List loanChargeIds = fetchLoanChargeIds(); + int chargeIdLength = loanChargeIds.size(); + String chargeIds = null; + //From modify application page, if user removes all charges, we should get empty array. + //So we need to remove all charges applied for this loan + boolean removeAllChages = false; + if (jsonCommand.parameterExists(LoanApiConstants.chargesParameterName)) { + JsonArray chargesArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.chargesParameterName); + if (chargesArray.size() == 0) removeAllChages = true; + } + + if (jsonCommand.parameterExists(LoanApiConstants.disbursementDataParameterName)) { + final JsonArray disbursementDataArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName); + if (disbursementDataArray != null && disbursementDataArray.size() > 0) { + String dateFormat = null; + Locale locale = null; + // Gets date format and locate + Map dateAndLocale = getDateFormatAndLocale(jsonCommand); + dateFormat = dateAndLocale.get(LoanApiConstants.dateFormatParameterName); + if (dateAndLocale.containsKey(LoanApiConstants.localeParameterName)) { + locale = JsonParserHelper.localeFromString(dateAndLocale.get(LoanApiConstants.localeParameterName)); + } + for (JsonElement jsonElement : disbursementDataArray) { + final JsonObject jsonObject = jsonElement.getAsJsonObject(); + Map parsedDisbursementData = parseDisbursementDetails(jsonObject, dateFormat, locale); + Date expectedDisbursementDate = (Date) parsedDisbursementData.get(LoanApiConstants.disbursementDateParameterName); + BigDecimal principal = (BigDecimal) parsedDisbursementData.get(LoanApiConstants.disbursementPrincipalParameterName); + Long disbursementID = (Long) parsedDisbursementData.get(LoanApiConstants.disbursementIdParameterName); + chargeIds = (String) parsedDisbursementData.get(LoanApiConstants.loanChargeIdParameterName); + if (chargeIds != null) { + if (chargeIds.indexOf(",") != -1) { + String[] chargeId = chargeIds.split(","); + for (String loanChargeId : chargeId) { + loanChargeIds.remove(Long.parseLong(loanChargeId)); + } + } else { + loanChargeIds.remove(Long.parseLong(chargeIds)); + } + } + createOrUpdateDisbursementDetails(disbursementID, actualChanges, expectedDisbursementDate, principal, disbursementList); + } + removeDisbursementAndAssociatedCharges(actualChanges, disbursementList, loanChargeIds, chargeIdLength, removeAllChages); + } + } + } + + private void removeDisbursementAndAssociatedCharges(final Map actualChanges, List disbursementList, + List loanChargeIds, int chargeIdLength, boolean removeAllChages) { + if (removeAllChages) { + LoanCharge[] tempCharges = new LoanCharge[this.charges.size()]; + this.charges.toArray(tempCharges); + for (LoanCharge loanCharge : tempCharges) { + removeLoanCharge(loanCharge); + } + this.trancheCharges.clear(); + } else { + if (loanChargeIds.size() > 0 && loanChargeIds.size() != chargeIdLength) { + for (Long chargeId : loanChargeIds) { + LoanCharge deleteCharge = fetchLoanChargesById(chargeId); + if (this.charges.contains(deleteCharge)) { + removeLoanCharge(deleteCharge); + } + } + } + } + for (Long id : disbursementList) { + removeChargesByDisbursementID(id); + this.disbursementDetails.remove(fetchLoanDisbursementsById(id)); + actualChanges.put("recalculateLoanSchedule", true); + } + } + + private void createOrUpdateDisbursementDetails(Long disbursementID, final Map actualChanges, + Date expectedDisbursementDate, BigDecimal principal, List existingDisbursementList) { + + if (disbursementID != null) { + LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(disbursementID); + existingDisbursementList.remove(disbursementID); + if (loanDisbursementDetail.actualDisbursementDate() == null) { + Date actualDisbursementDate = null; + LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, + principal); + disbursementDetails.updateLoan(this); + if (!loanDisbursementDetail.equals(disbursementDetails)) { + loanDisbursementDetail.copy(disbursementDetails); + actualChanges.put("disbursementDetailId", disbursementID); + actualChanges.put("recalculateLoanSchedule", true); + } + } + } else { + Date actualDisbursementDate = null; + LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, + principal); + disbursementDetails.updateLoan(this); + this.disbursementDetails.add(disbursementDetails); + for (LoanTrancheCharge trancheCharge : trancheCharges) { + Charge chargeDefinition = trancheCharge.getCharge(); + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null, + new LocalDate(expectedDisbursementDate), null, null); + loanCharge.update(this); + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, + disbursementDetails); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + addLoanCharge(loanCharge); + } + actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal); + actualChanges.put("recalculateLoanSchedule", true); + } + } + + private void removeChargesByDisbursementID(Long id) { + List tempCharges = new ArrayList<>(); + for (LoanCharge charge : this.charges) { + LoanTrancheDisbursementCharge transCharge = charge.getTrancheDisbursementCharge(); + if (transCharge != null && id.equals(transCharge.getloanDisbursementDetails().getId())) { + tempCharges.add(charge); + } + } + for (LoanCharge charge : tempCharges) { + removeLoanCharge(charge); + } + } + + private List fetchLoanChargeIds() { + List list = new ArrayList<>(); + for (LoanCharge charge : this.charges) { + list.add(charge.getId()); + } + return list; } public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) { @@ -1698,8 +1877,7 @@ public class Loan extends AbstractPersistable { if (this.client != null && this.client.isActivatedAfter(submittedOn)) { final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date."; - throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, - submittedOn); + throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn); } validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CREATED, submittedOn); @@ -1742,13 +1920,13 @@ public class Loan extends AbstractPersistable { if (recalculationCompoundingFrequencyDate != null) { compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate(); } - this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom( - this.loanProduct.getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), - this.loanProduct.getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), - this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), - this.loanProduct.getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, - this.loanProduct.getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), - this.loanProduct.getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); + this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct + .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct + .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct + .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct + .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); this.loanInterestRecalculationDetails.updateLoan(this); } @@ -1927,6 +2105,7 @@ public class Loan extends AbstractPersistable { } } if (loanProduct.isMultiDisburseLoan()) { + recalculateAllCharges(); if (this.disbursementDetails.isEmpty()) { final String errorMessage = "For this loan product, disbursement details must be provided"; @@ -2068,10 +2247,9 @@ public class Loan extends AbstractPersistable { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); - updateLoanSummaryDerivedFields(); LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser); - + updateLoanSummaryDerivedFields(); final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); /** @@ -2150,7 +2328,11 @@ public class Loan extends AbstractPersistable { } } } else { - disburseAmount = disburseAmount.plus(principalDisbursed); + if (this.loanProduct.isMultiDisburseLoan()) { + disburseAmount = Money.of(getCurrency(), principalDisbursed); + } else { + disburseAmount = disburseAmount.plus(principalDisbursed); + } if (details.isEmpty()) { diff = this.loanRepaymentScheduleDetail.getPrincipal().minus(principalDisbursed).getAmount(); @@ -2190,12 +2372,11 @@ public class Loan extends AbstractPersistable { private ChangedTransactionDetail reprocessTransactionForDisbursement() { ChangedTransactionDetail changedTransactionDetail = null; - if (this.loanProduct.isMultiDisburseLoan()) { - if (!this.actualDisbursementDate.equals(fetchLastDisburseDetail().getDisbursementDate())) { + final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + if (!allNonContraTransactionsPostDisbursement.isEmpty()) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { @@ -2233,8 +2414,7 @@ public class Loan extends AbstractPersistable { if (date != null) { for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) { if (disbursementDetail.actualDisbursementDate() != null) { - if (disbursementDetail.actualDisbursementDate().after(date) - || disbursementDetail.actualDisbursementDate().equals(date)) { + if (disbursementDetail.actualDisbursementDate().after(date) || disbursementDetail.actualDisbursementDate().equals(date)) { date = disbursementDetail.actualDisbursementDate(); details = disbursementDetail; } @@ -2372,7 +2552,7 @@ public class Loan extends AbstractPersistable { this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, - this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod); + this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, getApprovedPrincipal()); final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(), scheduleGeneratorDTO.getHolidayDetailDTO()); @@ -2388,45 +2568,49 @@ public class Loan extends AbstractPersistable { * TODO Vishwas: do we need to be able to pass in payment type details * for repayments at disbursements too? ***/ + final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency()); /** * all Charges repaid at disbursal is marked as repaid and * "APPLY Charge" transactions are created for all other fees ( which * are created during disbursal but not repaid) **/ - if (disbursedOn.toDate().equals(this.actualDisbursementDate)) { - Money disbursentMoney = Money.zero(getCurrency()); - final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, null, disbursedOn, - null, createdDate, currentUser); - final Integer installmentNumber = null; - for (final LoanCharge charge : charges()) { - if (charge.isDueAtDisbursement()) { - if (totalFeeChargesDueAtDisbursement.isGreaterThanZero() - && !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) { - charge.markAsFullyPaid(); - // Add "Loan Charge Paid By" details to this transaction - final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount(), - installmentNumber); - chargesPayment.getLoanChargesPaid().add(loanChargePaidBy); - disbursentMoney = disbursentMoney.plus(charge.amount()); - } - } else { - /** - * create a Charge applied transaction if Upfront Accrual, - * None or Cash based accounting is enabled - **/ - if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { - handleChargeAppliedTransaction(charge, disbursedOn, currentUser); - } + + Money disbursentMoney = Money.zero(getCurrency()); + final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, null, disbursedOn, + null, createdDate, currentUser); + final Integer installmentNumber = null; + for (final LoanCharge charge : charges()) { + Date actualDisbursementDate = getActualDisbursementDate(charge); + if (charge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue() + || (charge.getCharge().getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() + && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null + && !charge.isWaived() && !charge.isFullyPaid())) { + if (totalFeeChargesDueAtDisbursement.isGreaterThanZero() && !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) { + charge.markAsFullyPaid(); + // Add "Loan Charge Paid By" details to this transaction + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount(), + installmentNumber); + chargesPayment.getLoanChargesPaid().add(loanChargePaidBy); + disbursentMoney = disbursentMoney.plus(charge.amount()); + } + } else if (disbursedOn.equals(new LocalDate(this.actualDisbursementDate))) { + /** + * create a Charge applied transaction if Up front Accrual, None + * or Cash based accounting is enabled + **/ + if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { + handleChargeAppliedTransaction(charge, disbursedOn, currentUser); } } - if (disbursentMoney.isGreaterThanZero()) { - final Money zero = Money.zero(getCurrency()); - chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero); - chargesPayment.updateLoan(this); - this.loanTransactions.add(chargesPayment); - updateLoanOutstandingBalaces(); - } + } + + if (disbursentMoney.isGreaterThanZero()) { + final Money zero = Money.zero(getCurrency()); + chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero); + chargesPayment.updateLoan(this); + this.loanTransactions.add(chargesPayment); + updateLoanOutstandingBalaces(); } if (getApprovedOnDate() != null && disbursedOn.isBefore(getApprovedOnDate())) { @@ -2737,8 +2921,8 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment( - loanTransaction.getTransactionDate()); + final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment(loanTransaction + .getTransactionDate()); boolean reprocess = true; if (isTransactionChronologicallyLatest && adjustedTransaction == null @@ -2756,8 +2940,8 @@ public class Loan extends AbstractPersistable { if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) { reprocess = true; } else { - final LoanRepaymentScheduleInstallment nextInstallment = fetchRepaymentScheduleInstallment( - currentInstallment.getInstallmentNumber() + 1); + final LoanRepaymentScheduleInstallment nextInstallment = fetchRepaymentScheduleInstallment(currentInstallment + .getInstallmentNumber() + 1); if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(getCurrency()).isGreaterThanZero()) { reprocess = true; } @@ -2945,8 +3129,7 @@ public class Loan extends AbstractPersistable { return isAfterLatRepayment; } - private boolean isChronologicallyLatestTransaction(final LoanTransaction loanTransaction, - final List loanTransactions) { + private boolean isChronologicallyLatestTransaction(final LoanTransaction loanTransaction, final List loanTransactions) { boolean isChronologicallyLatestRepaymentOrWaiver = true; @@ -3073,8 +3256,8 @@ public class Loan extends AbstractPersistable { return changedTransactionDetail; } - public ChangedTransactionDetail undoWrittenOff(final List existingTransactionIds, final List existingReversedTransactionIds, - final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { + public ChangedTransactionDetail undoWrittenOff(final List existingTransactionIds, + final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) { validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); existingTransactionIds.addAll(findExistingTransactionIds()); @@ -3399,6 +3582,16 @@ public class Loan extends AbstractPersistable { return !isDisbursed(); } + public boolean isChargesAdditionAllowed() { + boolean isDisbursed = false; + if (this.loanProduct.isMultiDisburseLoan()) { + isDisbursed = !isDisbursementAllowed(); + } else { + isDisbursed = hasDisbursementTransaction(); + } + return isDisbursed; + } + public boolean isDisbursed() { return hasDisbursementTransaction(); } @@ -3717,8 +3910,8 @@ public class Loan extends AbstractPersistable { final String errorMessage = "The Loan officer Unassign date(" + unassignDate + ") cannot be before its assignment date (" + latestHistoryRecord.getStartDate() + ")."; - throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(), - getLoanOfficer().getId(), latestHistoryRecord.getStartDate(), unassignDate); + throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(), getLoanOfficer() + .getId(), latestHistoryRecord.getStartDate(), unassignDate); } else if (unassignDate.isAfter(today)) { @@ -3993,8 +4186,8 @@ public class Loan extends AbstractPersistable { loanRepaymentInterval, tmpFromDate); if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) { - newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate, loanRepaymentInterval, - frequency, workingDays); + newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate, + loanRepaymentInterval, frequency, workingDays); } if (isHolidayEnabled) { @@ -4247,8 +4440,7 @@ public class Loan extends AbstractPersistable { } private void validateActivityNotBeforeLastTransactionDate(final LoanEvent event, final LocalDate activityDate) { - if (!(this.repaymentScheduleDetail().isInterestRecalculationEnabled() - || this.loanProduct().isHoldGuaranteeFundsEnabled())) { return; } + if (!(this.repaymentScheduleDetail().isInterestRecalculationEnabled() || this.loanProduct().isHoldGuaranteeFundsEnabled())) { return; } LocalDate lastTransactionDate = getLastUserTransactionDate(); final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate(); if (lastTransactionDate.isAfter(activityDate)) { @@ -4310,6 +4502,16 @@ public class Loan extends AbstractPersistable { return loanCharges; } + public Set trancheCharges() { + Set loanCharges = new HashSet<>(); + if (this.trancheCharges != null) { + for (LoanTrancheCharge charge : this.trancheCharges) { + loanCharges.add(charge); + } + } + return loanCharges; + } + public Set generateInstallmentLoanCharges(final LoanCharge loanCharge) { final Set loanChargePerInstallments = new HashSet<>(); if (loanCharge.isInstalmentFee()) { @@ -4338,8 +4540,8 @@ public class Loan extends AbstractPersistable { case LOAN_APPROVED: if (!isSubmittedAndPendingApproval()) { final String defaultUserMessage = "Loan Account Approval is not allowed. Loan Account is not in submitted and pending approval state."; - final ApiParameterError error = ApiParameterError - .generalError("error.msg.loan.approve.account.is.not.submitted.and.pending.state", defaultUserMessage); + final ApiParameterError error = ApiParameterError.generalError( + "error.msg.loan.approve.account.is.not.submitted.and.pending.state", defaultUserMessage); dataValidationErrors.add(error); } break; @@ -4355,8 +4557,8 @@ public class Loan extends AbstractPersistable { if ((!(isApproved() && isNotDisbursed()) && !this.loanProduct.isMultiDisburseLoan()) || (this.loanProduct.isMultiDisburseLoan() && !isAllTranchesNotDisbursed())) { final String defaultUserMessage = "Loan Disbursal is not allowed. Loan Account is not in approved and not disbursed state."; - final ApiParameterError error = ApiParameterError - .generalError("error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage); + final ApiParameterError error = ApiParameterError.generalError( + "error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage); dataValidationErrors.add(error); } break; @@ -4371,24 +4573,24 @@ public class Loan extends AbstractPersistable { case LOAN_REPAYMENT_OR_WAIVER: if (!isOpen()) { final String defaultUserMessage = "Loan Repayment or Waiver is not allowed. Loan Account is not active."; - final ApiParameterError error = ApiParameterError - .generalError("error.msg.loan.repayment.or.waiver.account.is.not.active", defaultUserMessage); + final ApiParameterError error = ApiParameterError.generalError( + "error.msg.loan.repayment.or.waiver.account.is.not.active", defaultUserMessage); dataValidationErrors.add(error); } break; case LOAN_REJECTED: if (!isSubmittedAndPendingApproval()) { final String defaultUserMessage = "Loan application cannot be rejected. Loan Account is not in Submitted and Pending approval state."; - final ApiParameterError error = ApiParameterError - .generalError("error.msg.loan.reject.account.is.not.submitted.pending.approval.state", defaultUserMessage); + final ApiParameterError error = ApiParameterError.generalError( + "error.msg.loan.reject.account.is.not.submitted.pending.approval.state", defaultUserMessage); dataValidationErrors.add(error); } break; case LOAN_WITHDRAWN: if (!isSubmittedAndPendingApproval()) { final String defaultUserMessage = "Loan application cannot be withdrawn. Loan Account is not in Submitted and Pending approval state."; - final ApiParameterError error = ApiParameterError - .generalError("error.msg.loan.withdrawn.account.is.not.submitted.pending.approval.state", defaultUserMessage); + final ApiParameterError error = ApiParameterError.generalError( + "error.msg.loan.withdrawn.account.is.not.submitted.pending.approval.state", defaultUserMessage); dataValidationErrors.add(error); } break; @@ -4403,8 +4605,8 @@ public class Loan extends AbstractPersistable { case WRITE_OFF_OUTSTANDING_UNDO: if (!isClosedWrittenOff()) { final String defaultUserMessage = "Loan Undo Written off is not allowed. Loan Account is not Written off."; - final ApiParameterError error = ApiParameterError - .generalError("error.msg.loan.undo.writtenoff.account.is.not.written.off", defaultUserMessage); + final ApiParameterError error = ApiParameterError.generalError( + "error.msg.loan.undo.writtenoff.account.is.not.written.off", defaultUserMessage); dataValidationErrors.add(error); } break; @@ -4429,8 +4631,8 @@ public class Loan extends AbstractPersistable { case LOAN_EDIT_MULTI_DISBURSE_DATE: if (isClosed()) { final String defaultUserMessage = "Edit disbursement is not allowed. Loan Account is not active."; - final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.edit.disbursement.account.is.not.active", - defaultUserMessage); + final ApiParameterError error = ApiParameterError.generalError( + "error.msg.loan.edit.disbursement.account.is.not.active", defaultUserMessage); dataValidationErrors.add(error); } break; @@ -4663,17 +4865,23 @@ public class Loan extends AbstractPersistable { } } + processPostDisbursementTransactions(); + } + + public void processPostDisbursementTransactions() { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List copyTransactions = new ArrayList<>(); - for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) { - copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); - } - loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(getDisbursementDate(), copyTransactions, - getCurrency(), this.repaymentScheduleInstallments, charges()); + if (allNonContraTransactionsPostDisbursement.size() > 0) { + for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) { + copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); + } + loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(getDisbursementDate(), copyTransactions, + getCurrency(), this.repaymentScheduleInstallments, charges()); - updateLoanSummaryDerivedFields(); + updateLoanSummaryDerivedFields(); + } } private LoanScheduleModel getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) { @@ -4727,12 +4935,14 @@ public class Loan extends AbstractPersistable { RecalculationFrequencyType compoundingFrequencyType = this.loanInterestRecalculationDetails().getCompoundingFrequencyType(); final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, - loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), calculatedRepaymentsStartingFromDate, - getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, - disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), - this.loanInterestRecalculationDetails, calendarInstanceForInterestRecalculation, recalculationFrequencyType, - compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(), - this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy()); + loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), + calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, + this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, + loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails, + calendarInstanceForInterestRecalculation, recalculationFrequencyType, compoundingCalendarInstance, + compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(), + this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy(), + getApprovedPrincipal()); return loanApplicationTerms; } @@ -4791,7 +5001,8 @@ public class Loan extends AbstractPersistable { disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, - compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod); + compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, + getApprovedPrincipal()); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), onDate, @@ -4924,7 +5135,7 @@ public class Loan extends AbstractPersistable { } disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate, - loanDisbursementDetails.principal())); + loanDisbursementDetails.principal(), null, null)); } return disbursementData; @@ -4995,8 +5206,8 @@ public class Loan extends AbstractPersistable { final List disbursementData = getDisbursmentData(); final List loanVariationTermsData = new ArrayList(); - LoanTermVariationsData data = new LoanTermVariationsData(null, LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), - expectedDisbursementDate, emiAmount); + LoanTermVariationsData data = new LoanTermVariationsData(null, + LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount); loanVariationTermsData.add(data); RecalculationFrequencyType recalculationFrequencyType = null; @@ -5016,7 +5227,7 @@ public class Loan extends AbstractPersistable { maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, - this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar); + this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal()); } /** @@ -5138,8 +5349,8 @@ public class Loan extends AbstractPersistable { private void validateRefundDateIsAfterLastRepayment(final LocalDate refundTransactionDate) { final LocalDate possibleNextRefundDate = possibleNextRefundDate(); - if (possibleNextRefundDate == null || refundTransactionDate - .isBefore(possibleNextRefundDate)) { throw new InvalidRefundDateException(refundTransactionDate.toString()); } + if (possibleNextRefundDate == null || refundTransactionDate.isBefore(possibleNextRefundDate)) { throw new InvalidRefundDateException( + refundTransactionDate.toString()); } } @@ -5237,4 +5448,22 @@ public class Loan extends AbstractPersistable { return lastTransactionDate == null ? now : lastTransactionDate; } + private Date getActualDisbursementDate(final LoanCharge loanCharge) { + Date actualDisbursementDate = null; + if (loanCharge.isDueAtDisbursement() && loanCharge.isActive()) { + LoanTrancheDisbursementCharge trancheDisbursementCharge = loanCharge.getTrancheDisbursementCharge(); + if (trancheDisbursementCharge != null) { + LoanDisbursementDetails details = trancheDisbursementCharge.getloanDisbursementDetails(); + actualDisbursementDate = details.actualDisbursementDate(); + } + } + return actualDisbursementDate; + } + + public void addTrancheLoanCharge(Charge charge) { + if(!trancheCharges.contains(charge)) { + trancheCharges.add(new LoanTrancheCharge(charge, this)); + } + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java index ef7f408d4..aaa636ad1 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java @@ -7,7 +7,6 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.math.MathContext; -import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -30,7 +29,6 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; @@ -121,6 +119,9 @@ public class LoanCharge extends AbstractPersistable { @OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY) private LoanOverdueInstallmentCharge overdueInstallmentCharge; + @OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY) + private LoanTrancheDisbursementCharge loanTrancheDisbursementCharge; + public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) { final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate"); return createNewFromJson(loan, chargeDefinition, command, dueDate); @@ -144,8 +145,8 @@ public class LoanCharge extends AbstractPersistable { break; case PERCENT_OF_AMOUNT_AND_INTEREST: if (command.hasParameter("principal") && command.hasParameter("interest")) { - amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal") - .add(command.bigDecimalValueOfParameterNamed("interest")); + amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal").add( + command.bigDecimalValueOfParameterNamed("interest")); } else { amountPercentageAppliedTo = loan.getPrincpal().getAmount().add(loan.getTotalInterest()); } @@ -162,7 +163,7 @@ public class LoanCharge extends AbstractPersistable { } BigDecimal loanCharge = BigDecimal.ZERO; - if (ChargeTimeType.fromInt(chargeDefinition.getChargeTime()).equals(ChargeTimeType.INSTALMENT_FEE)) { + if (ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).equals(ChargeTimeType.INSTALMENT_FEE)) { BigDecimal percentage = amount; if (percentage == null) { percentage = chargeDefinition.getAmount(); @@ -198,7 +199,7 @@ public class LoanCharge extends AbstractPersistable { this.minCap = chargeDefinition.getMinCap(); this.maxCap = chargeDefinition.getMaxCap(); - this.chargeTime = chargeDefinition.getChargeTime(); + this.chargeTime = chargeDefinition.getChargeTimeType(); if (chargeTime != null) { this.chargeTime = chargeTime.getValue(); } @@ -267,6 +268,7 @@ public class LoanCharge extends AbstractPersistable { case PERCENT_OF_AMOUNT: case PERCENT_OF_AMOUNT_AND_INTEREST: case PERCENT_OF_INTEREST: + case PERCENT_OF_DISBURSEMENT_AMOUNT: this.percentage = chargeAmount; this.amountPercentageAppliedTo = amountPercentageAppliedTo; if (loanCharge.compareTo(BigDecimal.ZERO) == 0) { @@ -291,6 +293,10 @@ public class LoanCharge extends AbstractPersistable { this.paid = true; } + public boolean isFullyPaid() { + return this.paid; + } + public void resetToOriginal(final MonetaryCurrency currency) { this.amountPaid = BigDecimal.ZERO; this.amountWaived = BigDecimal.ZERO; @@ -335,6 +341,10 @@ public class LoanCharge extends AbstractPersistable { } + public BigDecimal getAmountPercentageAppliedTo() { + return this.amountPercentageAppliedTo; + } + private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) { return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount(); } @@ -366,6 +376,7 @@ public class LoanCharge extends AbstractPersistable { case PERCENT_OF_AMOUNT: case PERCENT_OF_AMOUNT_AND_INTEREST: case PERCENT_OF_INTEREST: + case PERCENT_OF_DISBURSEMENT_AMOUNT: this.percentage = amount; this.amountPercentageAppliedTo = loanPrincipal; if (loanCharge.compareTo(BigDecimal.ZERO) == 0) { @@ -395,6 +406,10 @@ public class LoanCharge extends AbstractPersistable { case PERCENT_OF_INTEREST: amountPercentageAppliedTo = this.loan.getTotalInterest(); break; + case PERCENT_OF_DISBURSEMENT_AMOUNT: + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = this.loanTrancheDisbursementCharge; + amountPercentageAppliedTo = loanTrancheDisbursementCharge.getloanDisbursementDetails().principal(); + break; default: break; } @@ -423,6 +438,7 @@ public class LoanCharge extends AbstractPersistable { final String amountParamName = "amount"; if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) { final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName); + BigDecimal loanCharge = null; actualChanges.put(amountParamName, newValue); actualChanges.put("locale", localeAsInput); switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { @@ -439,9 +455,10 @@ public class LoanCharge extends AbstractPersistable { case PERCENT_OF_AMOUNT: case PERCENT_OF_AMOUNT_AND_INTEREST: case PERCENT_OF_INTEREST: + case PERCENT_OF_DISBURSEMENT_AMOUNT: this.percentage = newValue; this.amountPercentageAppliedTo = amount; - BigDecimal loanCharge = BigDecimal.ZERO; + loanCharge = BigDecimal.ZERO; if (isInstalmentFee()) { loanCharge = this.loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(this.chargeCalculation), this.percentage); @@ -486,7 +503,8 @@ public class LoanCharge extends AbstractPersistable { } public boolean isDueAtDisbursement() { - return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT); + return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT) + || ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.TRANCHE_DISBURSEMENT); } public boolean isSpecifiedDueDate() { @@ -737,11 +755,12 @@ public class LoanCharge extends AbstractPersistable { @Override public int hashCode() { - return new HashCodeBuilder(3, 5) // - .append(getId()) // - .append(this.charge.getId()) // - .append(this.amount).append(getDueLocalDate()) // - .toHashCode(); + return 1; + /* + * return new HashCodeBuilder(3, 5) // .append(getId()) // + * .append(this.charge.getId()) // + * .append(this.amount).append(getDueLocalDate()) // .toHashCode(); + */ } public ChargePaymentMode getChargePaymentMode() { @@ -764,8 +783,9 @@ public class LoanCharge extends AbstractPersistable { public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() { LoanInstallmentCharge unpaidChargePerInstallment = null; for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { - if (loanChargePerInstallment.isPending() && (unpaidChargePerInstallment == null || unpaidChargePerInstallment - .getRepaymentInstallment().getDueDate().isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { + if (loanChargePerInstallment.isPending() + && (unpaidChargePerInstallment == null || unpaidChargePerInstallment.getRepaymentInstallment().getDueDate() + .isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { unpaidChargePerInstallment = loanChargePerInstallment; } } @@ -781,8 +801,7 @@ public class LoanCharge extends AbstractPersistable { public LoanInstallmentCharge getInstallmentLoanCharge(final Integer installmentNumber) { for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { - if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber() - .intValue())) { return loanChargePerInstallment; } + if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue())) { return loanChargePerInstallment; } } return null; } @@ -851,6 +870,10 @@ public class LoanCharge extends AbstractPersistable { this.overdueInstallmentCharge = overdueInstallmentCharge; } + public void updateLoanTrancheDisbursementCharge(final LoanTrancheDisbursementCharge loanTrancheDisbursementCharge) { + this.loanTrancheDisbursementCharge = loanTrancheDisbursementCharge; + } + public void updateWaivedAmount(MonetaryCurrency currency) { if (isInstalmentFee()) { this.amountWaived = BigDecimal.ZERO; @@ -885,6 +908,10 @@ public class LoanCharge extends AbstractPersistable { return this.overdueInstallmentCharge; } + public LoanTrancheDisbursementCharge getTrancheDisbursementCharge() { + return this.loanTrancheDisbursementCharge; + } + public Money undoPaidOrPartiallyAmountBy(final Money incrementBy, final Integer installmentNumber, final Money feeAmount) { Money processAmount = Money.zero(incrementBy.getCurrency()); if (isInstalmentFee()) { @@ -922,8 +949,9 @@ public class LoanCharge extends AbstractPersistable { Money outstanding = Money.of(currency, loanChargePerInstallment.getAmountOutstanding()); final boolean partiallyPaid = outstanding.isGreaterThanZero() && outstanding.isLessThan(loanChargePerInstallment.getAmount(currency)); - if ((partiallyPaid || loanChargePerInstallment.isPaid()) && (paidChargePerInstallment == null || paidChargePerInstallment - .getRepaymentInstallment().getDueDate().isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { + if ((partiallyPaid || loanChargePerInstallment.isPaid()) + && (paidChargePerInstallment == null || paidChargePerInstallment.getRepaymentInstallment().getDueDate() + .isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) { paidChargePerInstallment = loanChargePerInstallment; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java index 9db8edf35..35853ee9f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanChargePaidBy.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.JoinColumn; @@ -23,7 +24,7 @@ public class LoanChargePaidBy extends AbstractPersistable { @JoinColumn(name = "loan_transaction_id", nullable = false) private LoanTransaction loanTransaction; - @ManyToOne + @ManyToOne(cascade= CascadeType.ALL) @JoinColumn(name = "loan_charge_id", nullable = false) private LoanCharge loanCharge; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java index 29052206e..7fd07cd03 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanDisbursementDetails.java @@ -106,7 +106,7 @@ public class LoanDisbursementDetails extends AbstractPersistable { if (this.actualDisbursementDate != null) { actualDisburseDate = new LocalDate(this.actualDisbursementDate); } - return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal); + return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, null, null); } public void updateActualDisbursementDate(Date actualDisbursementDate) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index 6d4b6f573..473bd0e88 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -60,7 +60,7 @@ public class LoanRepaymentScheduleProcessingWrapper { Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isFeeCharge()) { + if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { if (loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; @@ -108,7 +108,7 @@ public class LoanRepaymentScheduleProcessingWrapper { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isFeeCharge()) { + if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { @@ -129,7 +129,7 @@ public class LoanRepaymentScheduleProcessingWrapper { Money cumulative = Money.zero(currency); for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isFeeCharge()) { + if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd); if (loanChargePerInstallment != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java index 63c448d1b..c609a47fc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.util.List; +import java.util.Set; import javax.persistence.Column; import javax.persistence.Embeddable; @@ -192,7 +193,7 @@ public final class LoanSummary { public void updateSummary(final MonetaryCurrency currency, final Money principal, final List repaymentScheduleInstallments, final LoanSummaryWrapper summaryWrapper, - final Boolean disbursed) { + final Boolean disbursed, Set charges) { this.totalPrincipalDisbursed = principal.getAmount(); this.totalPrincipalRepaid = summaryWrapper.calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount(); @@ -214,13 +215,15 @@ public final class LoanSummary { this.totalFeeChargesDueAtDisbursement); this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount(); - Money totalFeeChargesRepaid = summaryWrapper.calculateTotalFeeChargesRepaid(repaymentScheduleInstallments, currency); - if (disbursed) { - totalFeeChargesRepaid = totalFeeChargesRepaid.plus(this.totalFeeChargesDueAtDisbursement); + Money totalFeeChargesRepaidAtDisbursement = summaryWrapper.calculateTotalChargesRepaidAtDisbursement(charges,currency); + this.totalFeeChargesRepaid = totalFeeChargesRepaidAtDisbursement.getAmount(); + + if(charges != null) { + this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(charges, currency).getAmount(); + }else { + this.totalFeeChargesWaived = BigDecimal.ZERO; } - this.totalFeeChargesRepaid = totalFeeChargesRepaid.getAmount(); - - this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(repaymentScheduleInstallments, currency).getAmount(); + this.totalFeeChargesWrittenOff = summaryWrapper.calculateTotalFeeChargesWrittenOff(repaymentScheduleInstallments, currency) .getAmount(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryWrapper.java index 30f3ae19c..a48f0cc8d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummaryWrapper.java @@ -6,6 +6,7 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.util.List; +import java.util.Set; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -113,11 +114,13 @@ public final class LoanSummaryWrapper { return total; } - public Money calculateTotalFeeChargesWaived(final List repaymentScheduleInstallments, + public Money calculateTotalFeeChargesWaived(Set charges, final MonetaryCurrency currency) { Money total = Money.zero(currency); - for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { - total = total.plus(installment.getFeeChargesWaived(currency)); + for (final LoanCharge charge : charges) { + if(charge.isActive()){ + total = total.plus(charge.getAmountWaived(currency)); + } } return total; } @@ -217,4 +220,16 @@ public final class LoanSummaryWrapper { return overdueSince; } + + public Money calculateTotalChargesRepaidAtDisbursement(Set charges, MonetaryCurrency currency) { + Money total = Money.zero(currency); + if(charges == null) return total ; + for (final LoanCharge loanCharge : charges) { + if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()) { + total = total.plus(loanCharge.getAmountPaid(currency)); + } + } + return total; + + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTrancheCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTrancheCharge.java new file mode 100644 index 000000000..c52039f69 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTrancheCharge.java @@ -0,0 +1,52 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.mifosplatform.portfolio.loanaccount.domain; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.mifosplatform.portfolio.charge.domain.Charge; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_loan_tranche_charges") +public class LoanTrancheCharge extends AbstractPersistable { + + @ManyToOne(cascade = CascadeType.ALL, optional = false) + @JoinColumn(name = "loan_id", nullable = false) + private Loan loan; + + @ManyToOne(cascade = CascadeType.ALL, optional = false) + @JoinColumn(name = "charge_id", nullable = false) + private Charge charge; + + LoanTrancheCharge() { + + } + + LoanTrancheCharge(Charge chargeDefinition) { + this.charge = chargeDefinition ; + } + public LoanTrancheCharge(Charge charge, Loan loan) { + this.charge = charge; + this.loan = loan ; + } + + public static LoanTrancheCharge createLoanTrancheCharge(Charge chargeDefinition) { + return new LoanTrancheCharge(chargeDefinition) ; + } + public static LoanTrancheCharge createLoanTrancheChargeWithLoan(Charge chargeDefinition, Loan loan) { + return new LoanTrancheCharge(chargeDefinition, loan) ; + } + + public Charge getCharge() { + return charge ; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTrancheDisbursementCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTrancheDisbursementCharge.java new file mode 100644 index 000000000..73ecf4cb0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTrancheDisbursementCharge.java @@ -0,0 +1,40 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.loanaccount.domain; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name="m_loan_tranche_disbursement_charge") +public class LoanTrancheDisbursementCharge extends AbstractPersistable { + + @ManyToOne(cascade = CascadeType.ALL, optional = false) + @JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false) + private LoanCharge loancharge; + + @ManyToOne(cascade = CascadeType.ALL, optional = false) + @JoinColumn(name="disbursement_detail_id", referencedColumnName = "id", nullable = false) + private LoanDisbursementDetails loanDisbursementDetails; + + public LoanTrancheDisbursementCharge(){ + + } + + public LoanTrancheDisbursementCharge(final LoanCharge loanCharge, final LoanDisbursementDetails loanDisbursementDetails){ + this.loancharge = loanCharge; + this.loanDisbursementDetails = loanDisbursementDetails; + } + + public LoanDisbursementDetails getloanDisbursementDetails(){ + return this.loanDisbursementDetails; + } + +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 305bcf2f0..3d50d72fb 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -327,6 +327,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen if (loanTransaction.isChargePayment()) { feeAmount = feeCharges; } + if(unpaidCharge == null) break ; //All are trache charges final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount); if (!amountPaidTowardsCharge.isZero()) { Set chargesPaidBies = loanTransaction.getLoanChargesPaid(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 61da2a35d..986f7f2d9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -63,7 +63,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // 3. setup variables for tracking important facts required for loan // schedule generation. - Money principalDisbursed = loanApplicationTerms.getPrincipal(); + Money principalDisbursed ; + + if(loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) { + principalDisbursed = loanApplicationTerms.getApprovedPrincipal(); + }else { + principalDisbursed = loanApplicationTerms.getPrincipal(); + } final MonetaryCurrency currency = principalDisbursed.getCurrency(); final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); @@ -482,7 +488,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, periodStartDateApplicableForInterest, scheduledDueDate, daysInPeriodApplicableForInterest); - if (loanApplicationTerms.getFixedEmiAmount() != null + if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { String errorMsg = "EMI amount must be greater than : " + principalInterestForThisPeriod.interest().getAmount(); throw new MultiDisbursementEmiAmountException(errorMsg, principalInterestForThisPeriod.interest().getAmount(), @@ -1644,7 +1650,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money cumulative = Money.zero(monetaryCurrency); for (final LoanCharge loanCharge : loanCharges) { - if (loanCharge.isFeeCharge()) { + if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) { cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, numberOfRepayments, cumulative, loanCharge); } else if (loanCharge.isOverdueInstallmentCharge() diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index ae48943a8..41e2c9683 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -148,6 +148,8 @@ public final class LoanApplicationTerms { private final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy; + private Money approvedPrincipal = null ; + public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, @@ -164,7 +166,7 @@ public final class LoanApplicationTerms { final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar) { + final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, BigDecimal approvedAmount) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; @@ -176,7 +178,7 @@ public final class LoanApplicationTerms { disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar); + installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -191,7 +193,7 @@ public final class LoanApplicationTerms { final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod) { + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount) { final Calendar loanCalendar = null; return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, @@ -199,7 +201,7 @@ public final class LoanApplicationTerms { loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, - loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar); + loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -214,7 +216,7 @@ public final class LoanApplicationTerms { final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar) { + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -246,7 +248,7 @@ public final class LoanApplicationTerms { loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar); + loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -259,7 +261,7 @@ public final class LoanApplicationTerms { final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy) { + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, BigDecimal approvedAmount) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -298,11 +300,11 @@ public final class LoanApplicationTerms { daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar); + loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount); } public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, - final LoanApplicationTerms applicationTerms) { + final LoanApplicationTerms applicationTerms, BigDecimal approvedAmount) { return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency, applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, null, null, applicationTerms.amortizationMethod, applicationTerms.interestMethod, @@ -319,7 +321,7 @@ public final class LoanApplicationTerms { applicationTerms.recalculationFrequencyType, applicationTerms.compoundingCalendarInstance, applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment, applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy, - applicationTerms.loanCalendar); + applicationTerms.loanCalendar, approvedAmount); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -339,7 +341,7 @@ public final class LoanApplicationTerms { final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar) { + final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, BigDecimal approvedAmount) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; @@ -388,6 +390,7 @@ public final class LoanApplicationTerms { this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy; this.loanCalendar = loanCalendar; + this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount) ; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -1043,6 +1046,9 @@ public final class LoanApplicationTerms { return this.principal; } + public Money getApprovedPrincipal() { + return this.approvedPrincipal ; + } public List getDisbursementDatas() { return this.disbursementDatas; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 8a3d070ca..5834932c0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -52,6 +52,7 @@ import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; @@ -303,7 +304,7 @@ public class LoanScheduleAssembler { loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar); + installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO); } private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, @@ -353,7 +354,7 @@ public class LoanScheduleAssembler { .isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); } - disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal)); + disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null)); i++; } while (i < disbursementDataArray.size()); } @@ -434,13 +435,13 @@ public class LoanScheduleAssembler { validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays); validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays); - return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element); + return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element, null); } public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays, final JsonElement element) { + final List holidays, final WorkingDays workingDays, final JsonElement element, Set disbursementDetails) { - final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element); + final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index a983ac96d..ec1a22f7f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -372,7 +372,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche waiveLoanCharges(loan, waiveLoanCharges); // update the Loan summary - loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true); + loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true, null); // update the total number of schedule repayments loan.updateNumberOfRepayments(periods.size()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 01d71d8f9..b080d7ad7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -43,6 +43,7 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; +import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; @@ -63,6 +64,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateM import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; @@ -484,15 +486,21 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa LoanChargeData chargeData = new LoanChargeData(charge.getId(), charge.getDueLocalDate(), charge.amountOrPercentage()); chargesMap.put(charge.getId(), chargeData); } + Set disbursementDetails = this.loanAssembler.fetchDisbursementData(command.parsedJson().getAsJsonObject()); /** * Stores all charges which are passed in during modify loan * application **/ - final Set possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson()); + final Set possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),disbursementDetails); /** Boolean determines if any charge has been modified **/ boolean isChargeModified = false; + Set newTrancheChages = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson()) ; + for (Charge charge : newTrancheChages) { + existingLoanApplication.addTrancheLoanCharge(charge) ; + } + /** * If there are any charges already present, which are now not * passed in as a part of the request, deem the charges as modified @@ -621,7 +629,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final Set loanCollateral = this.loanCollateralAssembler.fromParsedJson(command.parsedJson()); existingLoanApplication.updateLoanCollateral(loanCollateral); } - + if (changes.containsKey("recalculateLoanSchedule")) { changes.remove("recalculateLoanSchedule"); @@ -632,7 +640,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa existingLoanApplication.updateLoanSchedule(loanSchedule, currentUser); existingLoanApplication.recalculateAllCharges(); } - + final String chargesParamName = "charges"; if (changes.containsKey(chargesParamName)) { existingLoanApplication.updateLoanCharges(possiblyModifedLoanCharges); @@ -872,7 +880,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa // If loan approved amount less than loan demanded amount, then need // to recompute the schedule - if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName)) { + if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey("recalculateLoanSchedule")) { ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index 19bb2a024..ceda41de4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -172,8 +172,30 @@ public class LoanAssembler { if (loanPurposeId != null) { loanPurpose = this.codeValueRepository.findOneWithNotFoundDetection(loanPurposeId); } + Set disbursementDetails = null; + BigDecimal fixedEmiAmount = null; + if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) { + fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element); + } + BigDecimal maxOutstandingLoanBalance = null; + if (loanProduct.isMultiDisburseLoan()) { + disbursementDetails = fetchDisbursementData(element.getAsJsonObject()); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject()); + maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName, + element, locale); + if (disbursementDetails.isEmpty()) { + final String errorMessage = "For this loan product, disbursement details must be provided"; + throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); + } + + if (disbursementDetails.size() > loanProduct.maxTrancheCount()) { + final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount(); + throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, + loanProduct.maxTrancheCount(), disbursementDetails.size()); + } + } final Set collateral = this.loanCollateralAssembler.fromParsedJson(element); - final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element); + final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails); for (final LoanCharge loanCharge : loanCharges) { if (!loanProduct.hasCurrencyCodeOf(loanCharge.currencyCode())) { final String errorMessage = "Charge and Loan must have the same currency."; @@ -197,28 +219,7 @@ public class LoanAssembler { final String loanTypeParameterName = "loanType"; final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element); final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr); - Set disbursementDetails = null; - BigDecimal fixedEmiAmount = null; - if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) { - fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element); - } - BigDecimal maxOutstandingLoanBalance = null; - if (loanProduct.isMultiDisburseLoan()) { - disbursementDetails = fetchDisbursementData(element.getAsJsonObject()); - final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject()); - maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName, - element, locale); - if (disbursementDetails.isEmpty()) { - final String errorMessage = "For this loan product, disbursement details must be provided"; - throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage); - } - - if (disbursementDetails.size() > loanProduct.maxTrancheCount()) { - final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount(); - throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage, - loanProduct.maxTrancheCount(), disbursementDetails.size()); - } - } + if (clientId != null) { client = this.clientRepository.findOneWithNotFoundDetection(clientId); @@ -281,7 +282,7 @@ public class LoanAssembler { final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, - isHolidayEnabled, holidays, workingDays, element); + isHolidayEnabled, holidays, workingDays, element,disbursementDetails); loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(), submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate); @@ -289,7 +290,7 @@ public class LoanAssembler { return loanApplication; } - private Set fetchDisbursementData(final JsonObject command) { + public Set fetchDisbursementData(final JsonObject command) { final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command); final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command); Set disbursementDatas = new HashSet<>(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java index 3b95294a6..cdb0756f1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeAssembler.java @@ -6,7 +6,9 @@ package org.mifosplatform.portfolio.loanaccount.service; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; @@ -19,8 +21,15 @@ import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; +import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository; +import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTrancheCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; +import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -34,21 +43,49 @@ public class LoanChargeAssembler { private final FromJsonHelper fromApiJsonHelper; private final ChargeRepositoryWrapper chargeRepository; private final LoanChargeRepository loanChargeRepository; + private final LoanProductRepository loanProductRepository; @Autowired public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository, - final LoanChargeRepository loanChargeRepository) { + final LoanChargeRepository loanChargeRepository, final LoanProductRepository loanProductRepository) { this.fromApiJsonHelper = fromApiJsonHelper; this.chargeRepository = chargeRepository; this.loanChargeRepository = loanChargeRepository; + this.loanProductRepository = loanProductRepository; } - public Set fromParsedJson(final JsonElement element) { - + public Set fromParsedJson(final JsonElement element, Set disbursementDetails) { + JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element) ; + List disbursementChargeIds = new ArrayList<>(); + + if(jsonDisbursement != null && jsonDisbursement.size() > 0) { + for(int i = 0 ; i < jsonDisbursement.size(); i++) { + final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject(); + if(jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null){ + String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString(); + if(chargeIds != null) { + if (chargeIds.indexOf(",") != -1) { + String[] chargeId = chargeIds.split(","); + for (String loanChargeId : chargeId) { + disbursementChargeIds.add(Long.parseLong(loanChargeId)); + } + } else { + disbursementChargeIds.add(Long.parseLong(chargeIds)) ; + } + } + + } + } + } + final Set loanCharges = new HashSet<>(); - final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); + final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element); + final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); + if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } + final boolean isMultiDisbursal = loanProduct.isMultiDisburseLoan(); + LocalDate expectedDisbursementDate = null; if (element.isJsonObject()) { final JsonObject topLevelJsonElement = element.getAsJsonObject(); @@ -92,17 +129,85 @@ public class LoanChargeAssembler { if (chargePaymentMode != null) { chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode); } - final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime, - chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments); - loanCharges.add(loanCharge); + if (!isMultiDisbursal) { + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime, + chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments); + loanCharges.add(loanCharge); + } + if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) { + final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray(); + JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject(); + expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanApiConstants.disbursementDateParameterName, disbursementDataElement, dateFormat, locale); + } + + if ((chargeDefinition.isPercentageOfDisbursementAmount() || chargeDefinition.isPercentageOfApprovedAmount() || chargeDefinition + .getChargeCalculation() == ChargeCalculationType.FLAT.getValue()) && disbursementDetails != null) { + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; + for (LoanDisbursementDetails disbursementDetail : disbursementDetails) { + if (chargeDefinition.getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue()) { + if (chargeDefinition.isPercentageOfApprovedAmount() + && disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) { + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, + chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments); + loanCharges.add(loanCharge); + loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + } else { + if (disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) { + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, + disbursementDetail.principal(), amount, chargeTime, chargeCalculation, + disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum, + numberOfRepayments); + loanCharges.add(loanCharge); + loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, + disbursementDetail); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + } + } + + } else if (chargeDefinition.getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()) { + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, + disbursementDetail.principal(), amount, chargeTime, chargeCalculation, + disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum, + numberOfRepayments); + loanCharges.add(loanCharge); + loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + } + } + } else { + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; + if (disbursementDetails != null) { + for (LoanDisbursementDetails disbursementDetail : disbursementDetails) { + if (!(disbursementDetail.expectedDisbursementDateAsLocalDate().isAfter(expectedDisbursementDate))) { + if (chargeDefinition.getChargeCalculation() == ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST + .getValue() + || chargeDefinition.getChargeTimeType() == ChargeTimeType.SPECIFIED_DUE_DATE.getValue() + || chargeDefinition.getChargeCalculation() == ChargeCalculationType.PERCENT_OF_INTEREST + .getValue()) { + final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, + amount, chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum, + numberOfRepayments); + loanCharges.add(loanCharge); + loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, + disbursementDetail); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + } + } + } + } + } } else { final Long loanChargeId = id; final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId); - if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); } - - loanCharge.update(amount, dueDate, numberOfRepayments); - - loanCharges.add(loanCharge); + if(disbursementChargeIds.contains(loanChargeId) && loanCharge == null) { + //throw new LoanChargeNotFoundException(loanChargeId); + } + if(loanCharge != null) { + loanCharge.update(amount, dueDate, numberOfRepayments); + loanCharges.add(loanCharge); + } } } } @@ -110,4 +215,26 @@ public class LoanChargeAssembler { return loanCharges; } + + public Set getNewLoanTrancheCharges(final JsonElement element) { + final Set associatedChargesForLoan = new HashSet<>(); + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { + final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); + for (int i = 0; i < array.size(); i++) { + final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); + final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement); + final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); + if (id == null) { + final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); + if (chargeDefinition.getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()) { + associatedChargesForLoan.add(chargeDefinition); + } + } + } + } + } + return associatedChargesForLoan; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java index 6d9dbabba..e8a10404f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanChargeReadPlatformServiceImpl.java @@ -64,9 +64,11 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform + "lc.charge_payment_mode_enum as chargePaymentMode, " + "lc.is_paid_derived as paid, " + "lc.waived as waied, " + "lc.min_cap as minCap, lc.max_cap as maxCap, " + "lc.charge_amount_or_percentage as amountOrPercentage, " + "c.currency_code as currencyCode, oc.name as currencyName, " + + "date(ifnull(dd.disbursedon_date,dd.expected_disburse_date)) as disbursementDate, " + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "oc.internationalized_name_code as currencyNameCode from m_charge c " - + "join m_organisation_currency oc on c.currency_code = oc.code " + "join m_loan_charge lc on lc.charge_id = c.id "; + + "join m_organisation_currency oc on c.currency_code = oc.code " + "join m_loan_charge lc on lc.charge_id = c.id " + + "left join m_loan_tranche_disbursement_charge dc on dc.loan_charge_id=lc.id left join m_loan_disbursement_detail dd on dd.id=dc.disbursement_detail_id "; } @Override @@ -97,7 +99,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final int chargeTime = rs.getInt("chargeTime"); final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime); - final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate"); + LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate"); final int chargeCalculation = rs.getInt("chargeCalculation"); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); @@ -110,6 +112,11 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final BigDecimal minCap = rs.getBigDecimal("minCap"); final BigDecimal maxCap = rs.getBigDecimal("maxCap"); final BigDecimal amountOrPercentage = rs.getBigDecimal("amountOrPercentage"); + final LocalDate disbursementDate = JdbcSupport.getLocalDate(rs, "disbursementDate"); + + if (disbursementDate != null) { + dueAsOfDate = disbursementDate; + } return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty, paymentMode, paid, @@ -161,7 +168,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform final LoanChargeMapper rm = new LoanChargeMapper(); final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = 1" - + " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC"; + + " order by ifnull(lc.due_for_collection_as_of_date,date(ifnull(dd.disbursedon_date,dd.expected_disburse_date))),lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC"; return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java index 35e0c66ce..684f3c3e7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.loanaccount.service; +import java.math.BigDecimal; import java.util.Collection; import java.util.Date; @@ -30,7 +31,7 @@ public interface LoanReadPlatformService { LoanAccountData retrieveOne(Long loanId); LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData, - Collection disbursementData, boolean isInterestRecalculationEnabled); + Collection disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges); Collection retrieveLoanTransactions(Long loanId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 794f87e38..1085cacf5 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -207,13 +207,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Override public LoanScheduleData retrieveRepaymentSchedule(final Long loanId, final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection disbursementData, - boolean isInterestRecalculationEnabled) { + boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) { try { this.context.authenticatedUser(); - + final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor( - repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled); + repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled ,totalPaidFeeCharges); final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment"; return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId }); @@ -954,9 +954,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private LocalDate lastDueDate; private BigDecimal outstandingLoanPrincipalBalance; private boolean excludePastUndisbursed; - + private BigDecimal totalPaidFeeCharges ; public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, - Collection disbursementData, boolean isInterestRecalculationEnabled) { + Collection disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) { this.currency = repaymentScheduleRelatedLoanData.getCurrency(); this.disbursement = repaymentScheduleRelatedLoanData.disbursementData(); this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement(); @@ -964,6 +964,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.outstandingLoanPrincipalBalance = this.disbursement.amount(); this.disbursementData = disbursementData; this.excludePastUndisbursed = isInterestRecalculationEnabled; + this.totalPaidFeeCharges = totalPaidFeeCharges ; } public String schema() { @@ -1014,7 +1015,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { // update totals with details of fees charged during disbursement totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue()); totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue()); - totalRepayment = totalRepayment.plus(disbursementPeriod.feeChargesPaid()); + totalRepayment = totalRepayment.plus(totalPaidFeeCharges); totalOutstanding = totalOutstanding.plus(disbursementPeriod.feeChargesDue()).minus(disbursementPeriod.feeChargesPaid()); Integer loanTermInDays = Integer.valueOf(0); @@ -1031,17 +1032,29 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { for (DisbursementData data : disbursementData) { if (fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate)) { principal = principal.add(data.amount()); - final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( - data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed()); - periods.add(periodData); + if(data.getChargeAmount() == null){ + final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( + data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed()); + periods.add(periodData); + }else{ + final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( + data.disbursementDate(), data.amount(), data.getChargeAmount(), data.isDisbursed()); + periods.add(periodData); + } this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount()); } else if (data.isDueForDisbursement(fromDate, dueDate)) { if (!excludePastUndisbursed || (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) { principal = principal.add(data.amount()); - final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( - data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed()); - periods.add(periodData); + if(data.getChargeAmount() == null){ + final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( + data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed()); + periods.add(periodData); + }else{ + final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( + data.disbursementDate(), data.amount(), data.getChargeAmount(), data.isDisbursed()); + periods.add(periodData); + } this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount()); } } @@ -1329,8 +1342,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Collection loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose"); final Collection loanCollateralOptions = this.codeValueReadPlatformService .retrieveCodeValuesByCode("LoanCollateral"); - final Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, - new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT }); + Collection chargeOptions = null; + if(loanProduct.getMultiDisburseLoan()){ + chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, + new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT }); + }else{ + chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, + new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT }); + } + Integer loanCycleCounter = null; if (loanProduct.useBorrowerCycle()) { if (clientId == null) { @@ -1441,17 +1461,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } @Override - public Collection retrieveLoanDisbursementDetails(final Long loanId) { + public Collection retrieveLoanDisbursementDetails + (final Long loanId) { final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper(); - final String sql = "select " + rm.schema() + " where dd.loan_id=? order by dd.expected_disburse_date"; + final String sql = "select " + rm.schema() + " where dd.loan_id=? group by dd.id order by dd.expected_disburse_date"; return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); } private static final class LoanDisbursementDetailMapper implements RowMapper { public String schema() { - return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate,dd.disbursedon_date as actualDisbursementdate,dd.principal as principal " - + "from m_loan_disbursement_detail dd"; + return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate, dd.disbursedon_date as actualDisbursementdate,dd.principal as principal,sum(lc.amount) chargeAmount, lc.amount_waived_derived waivedAmount,group_concat(lc.id) loanChargeId "+ + "from m_loan l inner join m_loan_disbursement_detail dd on dd.loan_id = l.id left join m_loan_tranche_disbursement_charge tdc on tdc.disbursement_detail_id=dd.id " + + "left join m_loan_charge lc on lc.id=tdc.loan_charge_id and lc.is_active=1"; } @Override @@ -1460,8 +1482,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate expectedDisbursementdate = JdbcSupport.getLocalDate(rs, "expectedDisbursementdate"); final LocalDate actualDisbursementdate = JdbcSupport.getLocalDate(rs, "actualDisbursementdate"); final BigDecimal principal = rs.getBigDecimal("principal"); - - final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal); + final String loanChargeId = rs.getString("loanChargeId"); + BigDecimal chargeAmount = rs.getBigDecimal("chargeAmount"); + final BigDecimal waivedAmount = rs.getBigDecimal("waivedAmount") ; + if(chargeAmount != null && waivedAmount != null) + chargeAmount = chargeAmount.subtract(waivedAmount) ; + final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal, loanChargeId, + chargeAmount); return disbursementData; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 2d1923682..c0cf49443 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -76,6 +76,7 @@ import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNot import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.mifosplatform.portfolio.charge.exception.ChargeCannotBeUpdatedException; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON; @@ -120,6 +121,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInsta import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; @@ -1301,6 +1303,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } + private void validateAddingNewChargeAllowed(Set loanDisburseDetails) { + boolean pendingDisbursementAvailable = false; + for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) { + if (disbursementDetail.actualDisbursementDate() == null) { + pendingDisbursementAvailable = true ; + break ; + } + } + if(!pendingDisbursementAvailable) { + throw new ChargeCannotBeUpdatedException( + "error.msg.charge.cannot.be.updated.no.pending.disbursements.in.loan", "This charge cannot be added, No disbursement is pending"); + } + } + @Transactional @Override public CommandProcessingResult addLoanCharge(final Long loanId, final JsonCommand command) { @@ -1310,19 +1326,42 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); + Set loanDisburseDetails = loan.getDisbursementDetails(); final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId"); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId); - final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command); - this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE, - constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge)); - - validateAddLoanCharge(loan, chargeDefinition, loanCharge); - + if(loan.isDisbursed() && chargeDefinition.isDisbursementCharge()) { + validateAddingNewChargeAllowed(loanDisburseDetails) ; //validates whether any pending disbursements are available to apply this charge + } final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); + + boolean isAppliedOnBackDate = false; + LoanCharge loanCharge = null; + if (chargeDefinition.isPercentageOfDisbursementAmount()) { + LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; + for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) { + if (disbursementDetail.actualDisbursementDate() == null) { + loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, disbursementDetail.principal(), null, null, null, + disbursementDetail.expectedDisbursementDateAsLocalDate(), null, null); + loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail); + loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE, + constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge)); + validateAddLoanCharge(loan, chargeDefinition, loanCharge); + isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); + } + } + loan.addTrancheLoanCharge(chargeDefinition); + } else { + loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command); + this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE, + constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge)); + + validateAddLoanCharge(loan, chargeDefinition, loanCharge); + isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); + } - boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); boolean reprocessRequired = true; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) { @@ -1331,7 +1370,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } updateOriginalSchedule(loan); } - if (reprocessRequired) { ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); if (changedTransactionDetail != null) { @@ -1480,10 +1518,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - this.loanChargeRepository.save(loanCharge); - loan.addLoanCharge(loanCharge); + this.loanChargeRepository.save(loanCharge); + /** * we want to apply charge transactions only for those loans charges * that are applied when a loan is active and the loan product uses @@ -2349,7 +2387,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - @Override @Transactional public void applyOverdueChargesForLoan(final Long loanId, Collection overdueLoanScheduleDatas) { @@ -2652,10 +2689,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser); } else { + // BigDecimal setAmount = loan.getApprovedPrincipal(); Collection loanDisburseDetails = loan.getDisbursementDetails(); BigDecimal setAmount = BigDecimal.ZERO; for (LoanDisbursementDetails details : loanDisburseDetails) { - if (details.actualDisbursementDate() != null) { + if (details.expectedDisbursementDate() != null) { setAmount = setAmount.add(details.principal()); } } @@ -2666,6 +2704,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); } else { loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); + loan.processPostDisbursementTransactions() ; } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 0893ce5f3..212001966 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -19,6 +19,7 @@ import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @@ -83,7 +84,7 @@ public class LoanProduct extends AbstractPersistable { @Column(name = "description") private String description; - @ManyToMany + @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id")) private List charges; @@ -602,6 +603,10 @@ public class LoanProduct extends AbstractPersistable { public Integer getAccountingType() { return this.accountingRule; } + + public List getLoanProductCharges(){ + return this.charges; + } public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) { this.loanConfigurableAttributes = loanConfigurableAttributes; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java index fea8e4630..80628aad7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountCharge.java @@ -153,7 +153,7 @@ public class SavingsAccountCharge extends AbstractPersistable { this.savingsAccount = savingsAccount; this.charge = chargeDefinition; this.penaltyCharge = chargeDefinition.isPenalty(); - this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTime() : chargeTime.getValue(); + this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTimeType() : chargeTime.getValue(); if (isOnSpecifiedDueDate()) { if (dueDate == null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java index 81dc1ff0a..93e526012 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountChargeAssembler.java @@ -134,8 +134,8 @@ public class SavingsAccountChargeAssembler { Set productCharges = savingsProduct.charges(); for (Charge charge : productCharges) { ChargeTimeType chargeTime = null; - if (charge.getChargeTime() != null) { - chargeTime = ChargeTimeType.fromInt(charge.getChargeTime()); + if (charge.getChargeTimeType() != null) { + chargeTime = ChargeTimeType.fromInt(charge.getChargeTimeType()); } if (chargeTime != null && chargeTime.isOnSpecifiedDueDate()) { continue; diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V272__loan_tranche_disbursement_charge.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V272__loan_tranche_disbursement_charge.sql new file mode 100644 index 000000000..d3a00fa35 --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V272__loan_tranche_disbursement_charge.sql @@ -0,0 +1,26 @@ +CREATE TABLE `m_loan_tranche_disbursement_charge` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `loan_charge_id` BIGINT(20) NOT NULL, + `disbursement_detail_id` BIGINT(20) NULL, + PRIMARY KEY (`id`) +) +COLLATE='utf8_general_ci' +ENGINE=InnoDB +AUTO_INCREMENT=1 +; + +ALTER TABLE `m_loan_tranche_disbursement_charge` + ADD CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`), + ADD CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_disbursement_detail` FOREIGN KEY (`disbursement_detail_id`) REFERENCES `m_loan_disbursement_detail` (`id`); + + +CREATE TABLE `m_loan_tranche_charges` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `loan_id` BIGINT(20) NOT NULL, + `charge_id` BIGINT(20) NOT NULL, + PRIMARY KEY (`id`) +) ; + +ALTER TABLE `m_loan_tranche_charges` + ADD CONSTRAINT `FK_m_loan_tranche_charges_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`), + ADD CONSTRAINT `FK_m_loan_tranche_charges_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`); \ No newline at end of file