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
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() {
@@ -161,4 +162,8 @@ public enum ChargeTimeType {
return this.value.equals(ChargeTimeType.OVERDRAFT_FEE.getValue());
}
public boolean isTrancheDisbursement(){
return this.value.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue());
}
}
@@ -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");
@@ -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,7 +15,9 @@ 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";
@@ -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();
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 ;
}
}
@@ -69,6 +69,7 @@ public class LoanChargeData {
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,
null, 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);
}
}
@@ -8,9 +8,34 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
@@ -21,7 +46,6 @@ import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
@@ -43,7 +67,9 @@ import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.collateral.data.CollateralData;
@@ -60,17 +86,39 @@ import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.exception.*;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.*;
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.mifosplatform.portfolio.loanproduct.domain.*;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
@@ -236,6 +284,10 @@ public class Loan extends AbstractPersistable<Long> {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private Set<LoanCharge> charges = new HashSet<>();
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private Set<LoanTrancheCharge> trancheCharges = new HashSet<>();
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private Set<LoanCollateral> collateral = null;
@@ -284,6 +336,7 @@ public class Loan extends AbstractPersistable<Long> {
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
@OrderBy(value = "expectedDisbursementDate, id")
private Set<LoanDisbursementDetails> disbursementDetails = new HashSet<>();
@OrderBy(value = "termApplicableFrom, id")
@@ -443,6 +496,9 @@ public class Loan extends AbstractPersistable<Long> {
private Set<LoanCharge> associateChargesWithThisLoan(final Set<LoanCharge> loanCharges) {
for (final LoanCharge loanCharge : loanCharges) {
loanCharge.update(this);
if(loanCharge.getTrancheDisbursementCharge() != null) {
addTrancheLoanCharge(loanCharge.getCharge()) ;
}
}
return loanCharges;
}
@@ -466,7 +522,7 @@ public class Loan extends AbstractPersistable<Long> {
validateLoanIsNotClosed(loanCharge);
if (isDisbursed() && loanCharge.isDueAtDisbursement()) {
if (isChargesAdditionAllowed() && loanCharge.isDueAtDisbursement()) {
// Note: added this constraint to restrict adding disbursement
// charges to a loan
// after it is disbursed
@@ -511,7 +567,9 @@ public class Loan extends AbstractPersistable<Long> {
if (this.charges == null) {
this.charges = new HashSet<>();
}
this.charges.add(loanCharge);
this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
// store Id's of existing loan transactions and existing reversed loan
@@ -578,8 +636,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount,
transactionDate, feeCharges, penaltyCharges, DateUtils.getLocalDateTimeOfTenant(), currentUser);
Integer installmentNumber = null;
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge,
loanCharge.getAmount(getCurrency()).getAmount(), installmentNumber);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, loanCharge.getAmount(
getCurrency()).getAmount(), installmentNumber);
applyLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
this.loanTransactions.add(applyLoanChargeTransaction);
return applyLoanChargeTransaction;
@@ -588,8 +646,8 @@ public class Loan extends AbstractPersistable<Long> {
private void handleChargePaidTransaction(final LoanCharge charge, final LoanTransaction chargesPayment,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final Integer installmentNumber) {
chargesPayment.updateLoan(this);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge,
chargesPayment.getAmount(getCurrency()).getAmount(), installmentNumber);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, chargesPayment.getAmount(getCurrency())
.getAmount(), installmentNumber);
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
this.loanTransactions.add(chargesPayment);
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT,
@@ -627,8 +685,7 @@ public class Loan extends AbstractPersistable<Long> {
private void validateLoanChargeIsNotWaived(final LoanCharge loanCharge) {
if (loanCharge.isWaived()) {
final String defaultUserMessage = "This loan charge cannot be removed as the charge as already been waived.";
throw new LoanChargeCannotBeAddedException("loanCharge", "loanCharge.is.waived", defaultUserMessage, getId(),
loanCharge.name());
throw new LoanChargeCannotBeAddedException("loanCharge", "loanCharge.is.waived", defaultUserMessage, getId(), loanCharge.name());
}
}
@@ -661,10 +718,9 @@ public class Loan extends AbstractPersistable<Long> {
// if you want ability to remove loan charges that are waived.
validateLoanChargeIsNotWaived(loanCharge);
LoanCharge charge = fetchLoanChargesById(loanCharge.getId());
final boolean removed = charge.isActive();
final boolean removed = loanCharge.isActive();
if (removed) {
charge.setActive(false);
loanCharge.setActive(false);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -687,7 +743,7 @@ public class Loan extends AbstractPersistable<Long> {
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, charges());
}
this.charges.remove(loanCharge);
updateLoanSummaryDerivedFields();
}
@@ -695,7 +751,7 @@ public class Loan extends AbstractPersistable<Long> {
if (loanCharge.isDueAtDisbursement()) {
LoanTransaction transactionToRemove = null;
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isRepaymentAtDisbursement()) {
if (transaction.isRepaymentAtDisbursement() && transaction.getLoanChargesPaid().contains(loanCharge)) {
final MonetaryCurrency currency = loanCurrency();
final Money chargeAmount = Money.of(currency, loanCharge.amount());
@@ -772,6 +828,13 @@ public class Loan extends AbstractPersistable<Long> {
case PERCENT_OF_INTEREST:
amount = getTotalInterest();
break;
case PERCENT_OF_DISBURSEMENT_AMOUNT:
if (loanCharge.getTrancheDisbursementCharge() != null) {
amount = loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().principal();
} else {
amount = getPrincpal().getAmount();
}
break;
default:
break;
}
@@ -868,10 +931,11 @@ public class Loan extends AbstractPersistable<Long> {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, transactionDate,
feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(), currentUser);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge,
waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber);
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived,
transactionDate, feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, DateUtils.getLocalDateTimeOfTenant(),
currentUser);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, waiveLoanChargeTransaction
.getAmount(getCurrency()).getAmount(), loanInstallmentNumber);
waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
this.loanTransactions.add(waiveLoanChargeTransaction);
@@ -964,11 +1028,25 @@ public class Loan extends AbstractPersistable<Long> {
LoanCharge charge = loanCharge;
// add new charges
if (loanCharge.getId() == null) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
loanCharge.update(this);
if (this.loanProduct.isMultiDisburseLoan()) {
loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(this);
for (final LoanDisbursementDetails loanDisbursementDetails : this.disbursementDetails) {
if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().getId() == null) {
if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().equals(loanDisbursementDetails)) {
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, loanDisbursementDetails);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
}
}
}
this.charges.add(loanCharge);
} else {
charge = fetchLoanChargesById(charge.getId());
existingCharges.remove(charge.getId());
if (charge != null) existingCharges.remove(charge.getId());
}
final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
BigDecimal chargeAmt = BigDecimal.ZERO;
@@ -984,8 +1062,10 @@ public class Loan extends AbstractPersistable<Long> {
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
}
}
if (charge != null)
charge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(),
totalChargeAmt);
}
/** Updated deleted charges **/
@@ -1110,7 +1190,7 @@ public class Loan extends AbstractPersistable<Long> {
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
this.summary.updateSummary(loanCurrency(), principal, this.repaymentScheduleInstallments, this.loanSummaryWrapper,
isDisbursed());
isDisbursed(), this.charges);
updateLoanOutstandingBalaces();
}
}
@@ -1414,13 +1494,13 @@ public class Loan extends AbstractPersistable<Long> {
}
if (this.loanInterestRecalculationDetails == null) {
actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, true);
this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(
this.loanProduct.getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(),
this.loanProduct.getProductInterestRecalculationDetails().getRescheduleStrategyMethod(),
this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType().getValue(),
this.loanProduct.getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate,
this.loanProduct.getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(),
this.loanProduct.getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate);
this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct
.getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct
.getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct
.getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct
.getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct
.getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct
.getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate);
this.loanInterestRecalculationDetails.updateLoan(this);
} else {
@@ -1505,98 +1585,197 @@ public class Loan extends AbstractPersistable<Long> {
return amount;
}
public void updateDisbursementDetails(final JsonCommand command, final Map<String, Object> actualChanges) {
List<Long> list = fetchDisbursementIds();
if (command.parameterExists(LoanApiConstants.disbursementDataParameterName)) {
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
String dateFormat = null;
Locale locale = null;
if (command.parsedJson().isJsonObject()) {
JsonObject topLevel = command.parsedJson().getAsJsonObject();
final String dateFormatParameter = "dateFormat";
if (topLevel.has(dateFormatParameter) && topLevel.get(dateFormatParameter).isJsonPrimitive()) {
final JsonPrimitive primitive = topLevel.get(dateFormatParameter).getAsJsonPrimitive();
dateFormat = primitive.getAsString();
// This method returns date format and locale if present in the JsonCommand
private Map<String, String> getDateFormatAndLocale(final JsonCommand jsonCommand) {
Map<String, String> returnObject = new HashMap<>();
JsonElement jsonElement = jsonCommand.parsedJson();
if (jsonElement.isJsonObject()) {
JsonObject topLevel = jsonElement.getAsJsonObject();
if (topLevel.has(LoanApiConstants.dateFormatParameterName)
&& topLevel.get(LoanApiConstants.dateFormatParameterName).isJsonPrimitive()) {
final JsonPrimitive primitive = topLevel.get(LoanApiConstants.dateFormatParameterName).getAsJsonPrimitive();
returnObject.put(LoanApiConstants.dateFormatParameterName, primitive.getAsString());
}
final String localeParameter = "locale";
if (topLevel.has(localeParameter) && topLevel.get(localeParameter).isJsonPrimitive()) {
final JsonPrimitive primitive = topLevel.get(localeParameter).getAsJsonPrimitive();
if (topLevel.has(LoanApiConstants.localeParameterName) && topLevel.get(LoanApiConstants.localeParameterName).isJsonPrimitive()) {
final JsonPrimitive primitive = topLevel.get(LoanApiConstants.localeParameterName).getAsJsonPrimitive();
String localeString = primitive.getAsString();
locale = JsonParserHelper.localeFromString(localeString);
returnObject.put(LoanApiConstants.localeParameterName, localeString);
}
}
int i = 0;
do {
final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
if (jsonObject.has(LoanApiConstants.disbursementDateParameterName)
&& jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)) {
Date expectedDisbursementDate = null;
BigDecimal principal = null;
LocalDate date = null;
Long id = null;
return returnObject;
}
private Map<String, Object> parseDisbursementDetails(final JsonObject jsonObject, String dateFormat, Locale locale) {
Map<String, Object> returnObject = new HashMap<>();
if (jsonObject.get(LoanApiConstants.disbursementDateParameterName) != null
&& jsonObject.get(LoanApiConstants.disbursementDateParameterName).isJsonPrimitive()) {
final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.disbursementDateParameterName)
.getAsJsonPrimitive();
final JsonPrimitive primitive = jsonObject.get(LoanApiConstants.disbursementDateParameterName).getAsJsonPrimitive();
final String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.disbursementDateParameterName,
dateFormat, locale);
}
}
LocalDate date = JsonParserHelper.convertFrom(valueAsString, LoanApiConstants.disbursementDateParameterName, dateFormat,
locale);
if (date != null) {
expectedDisbursementDate = date.toDate();
returnObject.put(LoanApiConstants.disbursementDateParameterName, date.toDate());
}
}
}
if (jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive() && StringUtils
.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName)
.getAsBigDecimal();
if (jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
BigDecimal principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
returnObject.put(LoanApiConstants.disbursementPrincipalParameterName, principal);
}
if (jsonObject.has(LoanApiConstants.disbursementIdParameterName)
&& jsonObject.get(LoanApiConstants.disbursementIdParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementIdParameterName).getAsString()))) {
id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong();
Long id = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementIdParameterName).getAsLong();
returnObject.put(LoanApiConstants.disbursementIdParameterName, id);
}
if (id != null) {
LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(id);
list.remove(id);
if (jsonObject.has(LoanApiConstants.loanChargeIdParameterName)
&& jsonObject.get(LoanApiConstants.loanChargeIdParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.loanChargeIdParameterName).getAsString()))) {
returnObject.put(LoanApiConstants.loanChargeIdParameterName,
jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString());
}
return returnObject;
}
public void updateDisbursementDetails(final JsonCommand jsonCommand, final Map<String, Object> actualChanges) {
List<Long> disbursementList = fetchDisbursementIds();
List<Long> loanChargeIds = fetchLoanChargeIds();
int chargeIdLength = loanChargeIds.size();
String chargeIds = null;
//From modify application page, if user removes all charges, we should get empty array.
//So we need to remove all charges applied for this loan
boolean removeAllChages = false;
if (jsonCommand.parameterExists(LoanApiConstants.chargesParameterName)) {
JsonArray chargesArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.chargesParameterName);
if (chargesArray.size() == 0) removeAllChages = true;
}
if (jsonCommand.parameterExists(LoanApiConstants.disbursementDataParameterName)) {
final JsonArray disbursementDataArray = jsonCommand.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
String dateFormat = null;
Locale locale = null;
// Gets date format and locate
Map<String, String> dateAndLocale = getDateFormatAndLocale(jsonCommand);
dateFormat = dateAndLocale.get(LoanApiConstants.dateFormatParameterName);
if (dateAndLocale.containsKey(LoanApiConstants.localeParameterName)) {
locale = JsonParserHelper.localeFromString(dateAndLocale.get(LoanApiConstants.localeParameterName));
}
for (JsonElement jsonElement : disbursementDataArray) {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
Map<String, Object> parsedDisbursementData = parseDisbursementDetails(jsonObject, dateFormat, locale);
Date expectedDisbursementDate = (Date) parsedDisbursementData.get(LoanApiConstants.disbursementDateParameterName);
BigDecimal principal = (BigDecimal) parsedDisbursementData.get(LoanApiConstants.disbursementPrincipalParameterName);
Long disbursementID = (Long) parsedDisbursementData.get(LoanApiConstants.disbursementIdParameterName);
chargeIds = (String) parsedDisbursementData.get(LoanApiConstants.loanChargeIdParameterName);
if (chargeIds != null) {
if (chargeIds.indexOf(",") != -1) {
String[] chargeId = chargeIds.split(",");
for (String loanChargeId : chargeId) {
loanChargeIds.remove(Long.parseLong(loanChargeId));
}
} else {
loanChargeIds.remove(Long.parseLong(chargeIds));
}
}
createOrUpdateDisbursementDetails(disbursementID, actualChanges, expectedDisbursementDate, principal, disbursementList);
}
removeDisbursementAndAssociatedCharges(actualChanges, disbursementList, loanChargeIds, chargeIdLength, removeAllChages);
}
}
}
private void removeDisbursementAndAssociatedCharges(final Map<String, Object> actualChanges, List<Long> disbursementList,
List<Long> loanChargeIds, int chargeIdLength, boolean removeAllChages) {
if (removeAllChages) {
LoanCharge[] tempCharges = new LoanCharge[this.charges.size()];
this.charges.toArray(tempCharges);
for (LoanCharge loanCharge : tempCharges) {
removeLoanCharge(loanCharge);
}
this.trancheCharges.clear();
} else {
if (loanChargeIds.size() > 0 && loanChargeIds.size() != chargeIdLength) {
for (Long chargeId : loanChargeIds) {
LoanCharge deleteCharge = fetchLoanChargesById(chargeId);
if (this.charges.contains(deleteCharge)) {
removeLoanCharge(deleteCharge);
}
}
}
}
for (Long id : disbursementList) {
removeChargesByDisbursementID(id);
this.disbursementDetails.remove(fetchLoanDisbursementsById(id));
actualChanges.put("recalculateLoanSchedule", true);
}
}
private void createOrUpdateDisbursementDetails(Long disbursementID, final Map<String, Object> actualChanges,
Date expectedDisbursementDate, BigDecimal principal, List<Long> existingDisbursementList) {
if (disbursementID != null) {
LoanDisbursementDetails loanDisbursementDetail = fetchLoanDisbursementsById(disbursementID);
existingDisbursementList.remove(disbursementID);
if (loanDisbursementDetail.actualDisbursementDate() == null) {
Date actualDisbursementDate = null;
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate,
actualDisbursementDate, principal);
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate,
principal);
disbursementDetails.updateLoan(this);
if (!loanDisbursementDetail.equals(disbursementDetails)) {
loanDisbursementDetail.copy(disbursementDetails);
actualChanges.put("disbursementDetailId", id);
actualChanges.put("disbursementDetailId", disbursementID);
actualChanges.put("recalculateLoanSchedule", true);
}
}
} else {
Date actualDisbursementDate = null;
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate,
actualDisbursementDate, principal);
LoanDisbursementDetails disbursementDetails = new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate,
principal);
disbursementDetails.updateLoan(this);
this.disbursementDetails.add(disbursementDetails);
for (LoanTrancheCharge trancheCharge : trancheCharges) {
Charge chargeDefinition = trancheCharge.getCharge();
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, null, null, null,
new LocalDate(expectedDisbursementDate), null, null);
loanCharge.update(this);
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetails);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
addLoanCharge(loanCharge);
}
actualChanges.put(LoanApiConstants.disbursementDataParameterName, expectedDisbursementDate + "-" + principal);
actualChanges.put("recalculateLoanSchedule", true);
}
}
i++;
} while (i < disbursementDataArray.size());
for (Long id : list) {
this.disbursementDetails.remove(fetchLoanDisbursementsById(id));
actualChanges.put("recalculateLoanSchedule", true);
private void removeChargesByDisbursementID(Long id) {
List<LoanCharge> tempCharges = new ArrayList<>();
for (LoanCharge charge : this.charges) {
LoanTrancheDisbursementCharge transCharge = charge.getTrancheDisbursementCharge();
if (transCharge != null && id.equals(transCharge.getloanDisbursementDetails().getId())) {
tempCharges.add(charge);
}
}
for (LoanCharge charge : tempCharges) {
removeLoanCharge(charge);
}
}
private List<Long> fetchLoanChargeIds() {
List<Long> list = new ArrayList<>();
for (LoanCharge charge : this.charges) {
list.add(charge.getId());
}
return list;
}
public LoanDisbursementDetails fetchLoanDisbursementsById(Long id) {
LoanDisbursementDetails loanDisbursementDetail = null;
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
@@ -1698,8 +1877,7 @@ public class Loan extends AbstractPersistable<Long> {
if (this.client != null && this.client.isActivatedAfter(submittedOn)) {
final String errorMessage = "The date on which a loan is submitted cannot be earlier than client's activation date.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage,
submittedOn);
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.client.activation.date", errorMessage, submittedOn);
}
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CREATED, submittedOn);
@@ -1742,13 +1920,13 @@ public class Loan extends AbstractPersistable<Long> {
if (recalculationCompoundingFrequencyDate != null) {
compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate();
}
this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(
this.loanProduct.getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(),
this.loanProduct.getProductInterestRecalculationDetails().getRescheduleStrategyMethod(),
this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType().getValue(),
this.loanProduct.getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate,
this.loanProduct.getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(),
this.loanProduct.getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate);
this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct
.getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct
.getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct
.getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct
.getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct
.getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct
.getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate);
this.loanInterestRecalculationDetails.updateLoan(this);
}
@@ -1927,6 +2105,7 @@ public class Loan extends AbstractPersistable<Long> {
}
}
if (loanProduct.isMultiDisburseLoan()) {
recalculateAllCharges();
if (this.disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
@@ -2068,10 +2247,9 @@ public class Loan extends AbstractPersistable<Long> {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
updateLoanSummaryDerivedFields();
LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant();
handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser);
updateLoanSummaryDerivedFields();
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
/**
@@ -2149,8 +2327,12 @@ public class Loan extends AbstractPersistable<Long> {
disburseAmount = disburseAmount.plus(disbursementDetails.principal());
}
}
} else {
if (this.loanProduct.isMultiDisburseLoan()) {
disburseAmount = Money.of(getCurrency(), principalDisbursed);
} else {
disburseAmount = disburseAmount.plus(principalDisbursed);
}
if (details.isEmpty()) {
diff = this.loanRepaymentScheduleDetail.getPrincipal().minus(principalDisbursed).getAmount();
@@ -2190,12 +2372,11 @@ public class Loan extends AbstractPersistable<Long> {
private ChangedTransactionDetail reprocessTransactionForDisbursement() {
ChangedTransactionDetail changedTransactionDetail = null;
if (this.loanProduct.isMultiDisburseLoan()) {
if (!this.actualDisbursementDate.equals(fetchLastDisburseDetail().getDisbursementDate())) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -2233,8 +2414,7 @@ public class Loan extends AbstractPersistable<Long> {
if (date != null) {
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
if (disbursementDetail.actualDisbursementDate() != null) {
if (disbursementDetail.actualDisbursementDate().after(date)
|| disbursementDetail.actualDisbursementDate().equals(date)) {
if (disbursementDetail.actualDisbursementDate().after(date) || disbursementDetail.actualDisbursementDate().equals(date)) {
date = disbursementDetail.actualDisbursementDate();
details = disbursementDetail;
}
@@ -2372,7 +2552,7 @@ public class Loan extends AbstractPersistable<Long> {
this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod);
this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, getApprovedPrincipal());
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
scheduleGeneratorDTO.getHolidayDetailDTO());
@@ -2388,21 +2568,25 @@ public class Loan extends AbstractPersistable<Long> {
* TODO Vishwas: do we need to be able to pass in payment type details
* for repayments at disbursements too?
***/
final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency());
/**
* all Charges repaid at disbursal is marked as repaid and
* "APPLY Charge" transactions are created for all other fees ( which
* are created during disbursal but not repaid)
**/
if (disbursedOn.toDate().equals(this.actualDisbursementDate)) {
Money disbursentMoney = Money.zero(getCurrency());
final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, null, disbursedOn,
null, createdDate, currentUser);
final Integer installmentNumber = null;
for (final LoanCharge charge : charges()) {
if (charge.isDueAtDisbursement()) {
if (totalFeeChargesDueAtDisbursement.isGreaterThanZero()
&& !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
Date actualDisbursementDate = getActualDisbursementDate(charge);
if (charge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue()
|| (charge.getCharge().getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()
&& disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null
&& !charge.isWaived() && !charge.isFullyPaid())) {
if (totalFeeChargesDueAtDisbursement.isGreaterThanZero() && !charge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
charge.markAsFullyPaid();
// Add "Loan Charge Paid By" details to this transaction
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge, charge.amount(),
@@ -2410,16 +2594,17 @@ public class Loan extends AbstractPersistable<Long> {
chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
disbursentMoney = disbursentMoney.plus(charge.amount());
}
} else {
} else if (disbursedOn.equals(new LocalDate(this.actualDisbursementDate))) {
/**
* create a Charge applied transaction if Upfront Accrual,
* None or Cash based accounting is enabled
* create a Charge applied transaction if Up front Accrual, None
* or Cash based accounting is enabled
**/
if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
handleChargeAppliedTransaction(charge, disbursedOn, currentUser);
}
}
}
if (disbursentMoney.isGreaterThanZero()) {
final Money zero = Money.zero(getCurrency());
chargesPayment.updateComponentsAndTotal(zero, zero, disbursentMoney, zero);
@@ -2427,7 +2612,6 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTransactions.add(chargesPayment);
updateLoanOutstandingBalaces();
}
}
if (getApprovedOnDate() != null && disbursedOn.isBefore(getApprovedOnDate())) {
final String errorMessage = "The date on which a loan is disbursed cannot be before its approval date: "
@@ -2737,8 +2921,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment(
loanTransaction.getTransactionDate());
final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment(loanTransaction
.getTransactionDate());
boolean reprocess = true;
if (isTransactionChronologicallyLatest && adjustedTransaction == null
@@ -2756,8 +2940,8 @@ public class Loan extends AbstractPersistable<Long> {
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
reprocess = true;
} else {
final LoanRepaymentScheduleInstallment nextInstallment = fetchRepaymentScheduleInstallment(
currentInstallment.getInstallmentNumber() + 1);
final LoanRepaymentScheduleInstallment nextInstallment = fetchRepaymentScheduleInstallment(currentInstallment
.getInstallmentNumber() + 1);
if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(getCurrency()).isGreaterThanZero()) {
reprocess = true;
}
@@ -2945,8 +3129,7 @@ public class Loan extends AbstractPersistable<Long> {
return isAfterLatRepayment;
}
private boolean isChronologicallyLatestTransaction(final LoanTransaction loanTransaction,
final List<LoanTransaction> loanTransactions) {
private boolean isChronologicallyLatestTransaction(final LoanTransaction loanTransaction, final List<LoanTransaction> loanTransactions) {
boolean isChronologicallyLatestRepaymentOrWaiver = true;
@@ -3073,8 +3256,8 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail;
}
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO, final AppUser currentUser) {
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
existingTransactionIds.addAll(findExistingTransactionIds());
@@ -3399,6 +3582,16 @@ public class Loan extends AbstractPersistable<Long> {
return !isDisbursed();
}
public boolean isChargesAdditionAllowed() {
boolean isDisbursed = false;
if (this.loanProduct.isMultiDisburseLoan()) {
isDisbursed = !isDisbursementAllowed();
} else {
isDisbursed = hasDisbursementTransaction();
}
return isDisbursed;
}
public boolean isDisbursed() {
return hasDisbursementTransaction();
}
@@ -3717,8 +3910,8 @@ public class Loan extends AbstractPersistable<Long> {
final String errorMessage = "The Loan officer Unassign date(" + unassignDate + ") cannot be before its assignment date ("
+ latestHistoryRecord.getStartDate() + ").";
throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(),
getLoanOfficer().getId(), latestHistoryRecord.getStartDate(), unassignDate);
throw new LoanOfficerUnassignmentDateException("cannot.be.before.assignment.date", errorMessage, getId(), getLoanOfficer()
.getId(), latestHistoryRecord.getStartDate(), unassignDate);
} else if (unassignDate.isAfter(today)) {
@@ -3993,8 +4186,8 @@ public class Loan extends AbstractPersistable<Long> {
loanRepaymentInterval, tmpFromDate);
if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) {
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate, loanRepaymentInterval,
frequency, workingDays);
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate,
loanRepaymentInterval, frequency, workingDays);
}
if (isHolidayEnabled) {
@@ -4247,8 +4440,7 @@ public class Loan extends AbstractPersistable<Long> {
}
private void validateActivityNotBeforeLastTransactionDate(final LoanEvent event, final LocalDate activityDate) {
if (!(this.repaymentScheduleDetail().isInterestRecalculationEnabled()
|| this.loanProduct().isHoldGuaranteeFundsEnabled())) { return; }
if (!(this.repaymentScheduleDetail().isInterestRecalculationEnabled() || this.loanProduct().isHoldGuaranteeFundsEnabled())) { return; }
LocalDate lastTransactionDate = getLastUserTransactionDate();
final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate();
if (lastTransactionDate.isAfter(activityDate)) {
@@ -4310,6 +4502,16 @@ public class Loan extends AbstractPersistable<Long> {
return loanCharges;
}
public Set<LoanTrancheCharge> trancheCharges() {
Set<LoanTrancheCharge> loanCharges = new HashSet<>();
if (this.trancheCharges != null) {
for (LoanTrancheCharge charge : this.trancheCharges) {
loanCharges.add(charge);
}
}
return loanCharges;
}
public Set<LoanInstallmentCharge> generateInstallmentLoanCharges(final LoanCharge loanCharge) {
final Set<LoanInstallmentCharge> loanChargePerInstallments = new HashSet<>();
if (loanCharge.isInstalmentFee()) {
@@ -4338,8 +4540,8 @@ public class Loan extends AbstractPersistable<Long> {
case LOAN_APPROVED:
if (!isSubmittedAndPendingApproval()) {
final String defaultUserMessage = "Loan Account Approval is not allowed. Loan Account is not in submitted and pending approval state.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.approve.account.is.not.submitted.and.pending.state", defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.approve.account.is.not.submitted.and.pending.state", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
@@ -4355,8 +4557,8 @@ public class Loan extends AbstractPersistable<Long> {
if ((!(isApproved() && isNotDisbursed()) && !this.loanProduct.isMultiDisburseLoan())
|| (this.loanProduct.isMultiDisburseLoan() && !isAllTranchesNotDisbursed())) {
final String defaultUserMessage = "Loan Disbursal is not allowed. Loan Account is not in approved and not disbursed state.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.disbursal.account.is.not.approve.not.disbursed.state", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
@@ -4371,24 +4573,24 @@ public class Loan extends AbstractPersistable<Long> {
case LOAN_REPAYMENT_OR_WAIVER:
if (!isOpen()) {
final String defaultUserMessage = "Loan Repayment or Waiver is not allowed. Loan Account is not active.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.repayment.or.waiver.account.is.not.active", defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.repayment.or.waiver.account.is.not.active", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
case LOAN_REJECTED:
if (!isSubmittedAndPendingApproval()) {
final String defaultUserMessage = "Loan application cannot be rejected. Loan Account is not in Submitted and Pending approval state.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.reject.account.is.not.submitted.pending.approval.state", defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.reject.account.is.not.submitted.pending.approval.state", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
case LOAN_WITHDRAWN:
if (!isSubmittedAndPendingApproval()) {
final String defaultUserMessage = "Loan application cannot be withdrawn. Loan Account is not in Submitted and Pending approval state.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.withdrawn.account.is.not.submitted.pending.approval.state", defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.withdrawn.account.is.not.submitted.pending.approval.state", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
@@ -4403,8 +4605,8 @@ public class Loan extends AbstractPersistable<Long> {
case WRITE_OFF_OUTSTANDING_UNDO:
if (!isClosedWrittenOff()) {
final String defaultUserMessage = "Loan Undo Written off is not allowed. Loan Account is not Written off.";
final ApiParameterError error = ApiParameterError
.generalError("error.msg.loan.undo.writtenoff.account.is.not.written.off", defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.undo.writtenoff.account.is.not.written.off", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
@@ -4429,8 +4631,8 @@ public class Loan extends AbstractPersistable<Long> {
case LOAN_EDIT_MULTI_DISBURSE_DATE:
if (isClosed()) {
final String defaultUserMessage = "Edit disbursement is not allowed. Loan Account is not active.";
final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.edit.disbursement.account.is.not.active",
defaultUserMessage);
final ApiParameterError error = ApiParameterError.generalError(
"error.msg.loan.edit.disbursement.account.is.not.active", defaultUserMessage);
dataValidationErrors.add(error);
}
break;
@@ -4663,10 +4865,15 @@ public class Loan extends AbstractPersistable<Long> {
}
}
processPostDisbursementTransactions();
}
public void processPostDisbursementTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
final List<LoanTransaction> copyTransactions = new ArrayList<>();
if (allNonContraTransactionsPostDisbursement.size() > 0) {
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
}
@@ -4675,6 +4882,7 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSummaryDerivedFields();
}
}
private LoanScheduleModel getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
@@ -4727,12 +4935,14 @@ public class Loan extends AbstractPersistable<Long> {
RecalculationFrequencyType compoundingFrequencyType = this.loanInterestRecalculationDetails().getCompoundingFrequencyType();
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), calculatedRepaymentsStartingFromDate,
getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount,
disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
this.loanInterestRecalculationDetails, calendarInstanceForInterestRecalculation, recalculationFrequencyType,
compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(),
this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy());
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails,
calendarInstanceForInterestRecalculation, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(),
this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy(),
getApprovedPrincipal());
return loanApplicationTerms;
}
@@ -4791,7 +5001,8 @@ public class Loan extends AbstractPersistable<Long> {
disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance,
compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod);
compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod,
getApprovedPrincipal());
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), onDate,
@@ -4924,7 +5135,7 @@ public class Loan extends AbstractPersistable<Long> {
}
disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate,
loanDisbursementDetails.principal()));
loanDisbursementDetails.principal(), null, null));
}
return disbursementData;
@@ -4995,8 +5206,8 @@ public class Loan extends AbstractPersistable<Long> {
final List<DisbursementData> disbursementData = getDisbursmentData();
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<LoanTermVariationsData>();
LoanTermVariationsData data = new LoanTermVariationsData(null, LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT),
expectedDisbursementDate, emiAmount);
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
loanVariationTermsData.add(data);
RecalculationFrequencyType recalculationFrequencyType = null;
@@ -5016,7 +5227,7 @@ public class Loan extends AbstractPersistable<Long> {
maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate,
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar);
this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal());
}
/**
@@ -5138,8 +5349,8 @@ public class Loan extends AbstractPersistable<Long> {
private void validateRefundDateIsAfterLastRepayment(final LocalDate refundTransactionDate) {
final LocalDate possibleNextRefundDate = possibleNextRefundDate();
if (possibleNextRefundDate == null || refundTransactionDate
.isBefore(possibleNextRefundDate)) { throw new InvalidRefundDateException(refundTransactionDate.toString()); }
if (possibleNextRefundDate == null || refundTransactionDate.isBefore(possibleNextRefundDate)) { throw new InvalidRefundDateException(
refundTransactionDate.toString()); }
}
@@ -5237,4 +5448,22 @@ public class Loan extends AbstractPersistable<Long> {
return lastTransactionDate == null ? now : lastTransactionDate;
}
private Date getActualDisbursementDate(final LoanCharge loanCharge) {
Date actualDisbursementDate = null;
if (loanCharge.isDueAtDisbursement() && loanCharge.isActive()) {
LoanTrancheDisbursementCharge trancheDisbursementCharge = loanCharge.getTrancheDisbursementCharge();
if (trancheDisbursementCharge != null) {
LoanDisbursementDetails details = trancheDisbursementCharge.getloanDisbursementDetails();
actualDisbursementDate = details.actualDisbursementDate();
}
}
return actualDisbursementDate;
}
public void addTrancheLoanCharge(Charge charge) {
if(!trancheCharges.contains(charge)) {
trancheCharges.add(new LoanTrancheCharge(charge, this));
}
}
}
@@ -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();
@@ -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
@@ -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,22 +129,112 @@ public class LoanChargeAssembler {
if (chargePaymentMode != null) {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
}
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); }
if(disbursementChargeIds.contains(loanChargeId) && loanCharge == null) {
//throw new LoanChargeNotFoundException(loanChargeId);
}
if(loanCharge != null) {
loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
}
}
}
}
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());
if(data.getChargeAmount() == null){
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed());
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());
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,
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);
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);
}
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
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;
@@ -603,6 +604,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
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`);