merging MIFOSX-1923

This commit is contained in:
Vishwas Babu A J
2015-09-01 22:25:42 +05:30
43 changed files with 1116 additions and 422 deletions
@@ -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 + "?"
@@ -140,7 +140,6 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> feeFrequencyOptions = null;
final GLAccountData account = null;
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
@@ -56,7 +56,7 @@ public class Charge extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
// 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<Long> {
.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<Long> {
} 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<Long> {
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<Long> {
}
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<Long> {
}
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<Long> {
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<Long> {
}
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<Long> {
}
} 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<Long> {
.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<Long> {
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<Long> {
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<Long> {
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<Long> {
}
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<Long> {
}
return incomeAccountId;
}
public boolean isDisbursementCharge() {
return ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.DISBURSEMENT)
|| ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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)) {
@@ -26,9 +26,7 @@ public interface ChargeDropdownReadPlatformService {
List<EnumOptionData> retrieveSavingsCalculationTypes();
List<EnumOptionData> retrieveSavingsCollectionTimeTypes();
List<EnumOptionData> retrieveClientCalculationTypes();
List<EnumOptionData> retrieveClientCollectionTimeTypes();
}
@@ -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<EnumOptionData> 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
@@ -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;
}
}
@@ -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<ChargeData> 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<String, Object> 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<ChargeData> 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<String, Object> 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
@@ -102,7 +102,7 @@ public class ClientCharge extends AbstractPersistable<Long> {
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();
@@ -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";
@@ -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(),
@@ -19,13 +19,17 @@ public class DisbursementData implements Comparable<DisbursementData> {
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<DisbursementData> {
return this.principal;
}
public BigDecimal getChargeAmount(){
return this.chargeAmount;
}
public boolean isDisbursed() {
return this.actualDisbursementDate != null;
}
@@ -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 ;
}
}
@@ -68,6 +68,7 @@ public class LoanChargeData {
private BigDecimal amountAccrued;
private BigDecimal amountUnrecognized;
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null, null,
@@ -102,4 +102,8 @@ public class LoanSummaryData {
public BigDecimal getTotalOutstanding() {
return this.totalOutstanding;
}
public BigDecimal getTotalPaidFeeCharges() {
return feeChargesPaid ;
}
}
@@ -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);
}
}
@@ -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<Long> {
@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<Long> {
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<Long> {
}
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<Long> {
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<Long> {
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<Long> {
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<Long> {
}
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<Long> {
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<Long> {
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<Long> {
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<Long> {
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<Long> {
}
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<Long> {
@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<Long> {
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<Long> {
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<Long> {
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<Long> {
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<Long> {
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;
}
}
@@ -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<Long> {
@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;
@@ -106,7 +106,7 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
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) {
@@ -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) {
@@ -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<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LoanSummaryWrapper summaryWrapper,
final Boolean disbursed) {
final Boolean disbursed, Set<LoanCharge> 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();
@@ -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<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
public Money calculateTotalFeeChargesWaived(Set<LoanCharge> 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<LoanCharge> 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;
}
}
@@ -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<Long> {
@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 ;
}
}
@@ -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<Long> {
@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;
}
}
@@ -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<LoanChargePaidBy> chargesPaidBies = loanTransaction.getLoanChargesPaid();
@@ -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()
@@ -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<DisbursementData> getDisbursementDatas() {
return this.disbursementDatas;
}
@@ -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<Holiday> holidays, final WorkingDays workingDays, final JsonElement element) {
final List<Holiday> holidays, final WorkingDays workingDays, final JsonElement element, Set<LoanDisbursementDetails> disbursementDetails) {
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails);
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
@@ -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());
@@ -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<LoanDisbursementDetails> disbursementDetails = this.loanAssembler.fetchDisbursementData(command.parsedJson().getAsJsonObject());
/**
* Stores all charges which are passed in during modify loan
* application
**/
final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson());
final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),disbursementDetails);
/** Boolean determines if any charge has been modified **/
boolean isChargeModified = false;
Set<Charge> 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> 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);
}
@@ -172,8 +172,30 @@ public class LoanAssembler {
if (loanPurposeId != null) {
loanPurpose = this.codeValueRepository.findOneWithNotFoundDetection(loanPurposeId);
}
Set<LoanDisbursementDetails> 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<LoanCollateral> collateral = this.loanCollateralAssembler.fromParsedJson(element);
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
final Set<LoanCharge> 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<LoanDisbursementDetails> 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<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
public Set<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
Set<LoanDisbursementDetails> disbursementDatas = new HashSet<>();
@@ -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<LoanCharge> fromParsedJson(final JsonElement element) {
public Set<LoanCharge> fromParsedJson(final JsonElement element, Set<LoanDisbursementDetails> disbursementDetails) {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element) ;
List<Long> 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<LoanCharge> 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<Charge> getNewLoanTrancheCharges(final JsonElement element) {
final Set<Charge> 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;
}
}
@@ -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 });
}
@@ -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> disbursementData, boolean isInterestRecalculationEnabled);
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges);
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
@@ -207,13 +207,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> 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> disbursementData, boolean isInterestRecalculationEnabled) {
Collection<DisbursementData> 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<CodeValueData> loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose");
final Collection<CodeValueData> loanCollateralOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode("LoanCollateral");
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
Collection<ChargeData> 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<DisbursementData> retrieveLoanDisbursementDetails(final Long loanId) {
public Collection<DisbursementData> 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<DisbursementData> {
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;
}
@@ -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<LoanDisbursementDetails> 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<LoanDisbursementDetails> 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<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> 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<OverdueLoanScheduleData> 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<LoanDisbursementDetails> 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() ;
}
}
@@ -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<Long> {
@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<Charge> charges;
@@ -602,6 +603,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
public Integer getAccountingType() {
return this.accountingRule;
}
public List<Charge> getLoanProductCharges(){
return this.charges;
}
public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) {
this.loanConfigurableAttributes = loanConfigurableAttributes;
@@ -153,7 +153,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
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) {
@@ -134,8 +134,8 @@ public class SavingsAccountChargeAssembler {
Set<Charge> 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;
@@ -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`);