MIFOSX-1923 Adding Tranche Disbursement Implementation

This commit is contained in:
unknown
2015-08-31 19:51:33 +05:30
parent da5663f15c
commit 971e23e8b7
44 changed files with 1140 additions and 441 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 + "?"
@@ -38,7 +38,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final BigDecimal minCap;
private final BigDecimal maxCap;
private final EnumOptionData feeFrequency;
private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;//
private final List<EnumOptionData> chargeAppliesToOptions;//
@@ -52,7 +52,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final List<EnumOptionData> clientChargeCalculationTypeOptions;
private final List<EnumOptionData> clientChargeTimeTypeOptions;
private final List<EnumOptionData> feeFrequencyOptions;
public static ChargeData template(final Collection<CurrencyData> currencyOptions,
final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
final List<EnumOptionData> chargeTimeTypeOptions, final List<EnumOptionData> chargePaymentModeOptions,
@@ -128,7 +128,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> clientChargeTimeTypeOptions = null;
final EnumOptionData feeFrequency = null;
final List<EnumOptionData> feeFrequencyOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
@@ -50,7 +50,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;
@@ -93,8 +93,8 @@ public class Charge extends AbstractPersistable<Long> {
final ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.integerValueOfParameterNamed("chargeAppliesTo"));
final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.integerValueOfParameterNamed("chargeTimeType"));
final ChargeCalculationType chargeCalculationType = ChargeCalculationType
.fromInt(command.integerValueOfParameterNamed("chargeCalculationType"));
final ChargeCalculationType chargeCalculationType = ChargeCalculationType.fromInt(command
.integerValueOfParameterNamed("chargeCalculationType"));
final Integer chargePaymentMode = command.integerValueOfParameterNamed("chargePaymentMode");
final ChargePaymentMode paymentMode = chargePaymentMode == null ? null : ChargePaymentMode.fromInt(chargePaymentMode);
@@ -116,14 +116,14 @@ public class Charge extends AbstractPersistable<Long> {
}
private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active,
final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap,
final BigDecimal maxCap, final Integer feeFrequency) {
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty,
final boolean active, final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval,
final BigDecimal minCap, final BigDecimal maxCap, final Integer feeFrequency) {
this.name = name;
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;
@@ -143,7 +143,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
@@ -153,7 +153,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");
@@ -161,16 +161,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;
}
@@ -208,8 +209,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() {
@@ -233,11 +234,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() {
@@ -249,7 +250,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() {
@@ -260,10 +261,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;
}
@@ -304,15 +309,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
@@ -326,12 +331,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");
}
}
@@ -365,7 +370,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");
@@ -440,7 +445,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);
@@ -455,11 +460,12 @@ public class Charge extends AbstractPersistable<Long> {
actualChanges.put("locale", localeAsInput);
this.maxCap = newValue;
}
}
if (this.penalty && ChargeTimeType.fromInt(this.chargeTime)
.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(this.name); }
if (!penalty && ChargeTimeType.fromInt(this.chargeTime).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); }
if (this.penalty && ChargeTimeType.fromInt(this.chargeTimeType).isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(
this.name); }
if (!penalty && ChargeTimeType.fromInt(this.chargeTimeType).isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -479,7 +485,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);
@@ -499,15 +505,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() {
@@ -525,4 +531,9 @@ public class Charge extends AbstractPersistable<Long> {
public Integer feeFrequency() {
return this.feeFrequency;
}
public boolean isDisbursementCharge() {
return ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.DISBURSEMENT)
|| ChargeTimeType.fromInt(this.chargeTimeType).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
}
}
@@ -11,7 +11,8 @@ public enum ChargeCalculationType {
FLAT(1, "chargeCalculationType.flat"), //
PERCENT_OF_AMOUNT(2, "chargeCalculationType.percent.of.amount"), //
PERCENT_OF_AMOUNT_AND_INTEREST(3, "chargeCalculationType.percent.of.amount.and.interest"), //
PERCENT_OF_INTEREST(4, "chargeCalculationType.percent.of.interest");
PERCENT_OF_INTEREST(4, "chargeCalculationType.percent.of.interest"),
PERCENT_OF_DISBURSEMENT_AMOUNT(5,"chargeCalculationType.percent.of.disbursement.amount");
private final Integer value;
private final String code;
@@ -31,7 +32,8 @@ public enum ChargeCalculationType {
public static Object[] validValuesForLoan() {
return new Integer[] { ChargeCalculationType.FLAT.getValue(), ChargeCalculationType.PERCENT_OF_AMOUNT.getValue(),
ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue(), ChargeCalculationType.PERCENT_OF_INTEREST.getValue() };
ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue(), ChargeCalculationType.PERCENT_OF_INTEREST.getValue(),
ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue()};
}
public static Object[] validValuesForSavings() {
@@ -57,6 +59,9 @@ public enum ChargeCalculationType {
case 4:
chargeCalculationType = PERCENT_OF_INTEREST;
break;
case 5:
chargeCalculationType = PERCENT_OF_DISBURSEMENT_AMOUNT;
break;
}
return chargeCalculationType;
}
@@ -86,6 +91,10 @@ public enum ChargeCalculationType {
}
public boolean isPercentageBased() {
return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest();
return isPercentageOfAmount() || isPercentageOfAmountAndInterest() || isPercentageOfInterest() || isPercentageOfDisbursementAmount();
}
public boolean isPercentageOfDisbursementAmount(){
return this.value.equals(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue());
}
}
@@ -10,18 +10,16 @@ public enum ChargeTimeType {
INVALID(0, "chargeTimeType.invalid"), //
DISBURSEMENT(1, "chargeTimeType.disbursement"), // only for loan charges
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"), // for loan and
// savings charges
SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for
// savings
SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings
WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings
ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings
MONTHLY_FEE(7, "chargeTimeType.monthlyFee"), // only for savings
INSTALMENT_FEE(8, "chargeTimeType.instalmentFee"), // only for loan charges
OVERDUE_INSTALLMENT(9, "chargeTimeType.overdueInstallment"), // only for
// loan charges
OVERDRAFT_FEE(10, "chargeTimeType.overdraftFee"), // only for savings
WEEKLY_FEE(11, "chargeTimeType.weeklyFee"); // only for savings
OVERDRAFT_FEE(10, "chargeTimeType.overdraftFee"),// only for savings
WEEKLY_FEE(11, "chargeTimeType.weeklyFee"), // only for savings
TRANCHE_DISBURSEMENT(12,"chargeTimeType.tranchedisbursement"); // only for loan
private final Integer value;
private final String code;
@@ -41,7 +39,7 @@ public enum ChargeTimeType {
public static Object[] validLoanValues() {
return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
ChargeTimeType.INSTALMENT_FEE.getValue(), ChargeTimeType.OVERDUE_INSTALLMENT.getValue() };
ChargeTimeType.INSTALMENT_FEE.getValue(), ChargeTimeType.OVERDUE_INSTALLMENT.getValue(), ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() };
}
public static Object[] validLoanChargeValues() {
@@ -96,6 +94,9 @@ public enum ChargeTimeType {
case 11:
chargeTimeType = WEEKLY_FEE;
break;
case 12:
chargeTimeType = TRANCHE_DISBURSEMENT;
break;
default:
chargeTimeType = INVALID;
break;
@@ -145,7 +146,7 @@ public enum ChargeTimeType {
}
public boolean isAllowedLoanChargeTime() {
return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee() || isOverdueInstallment();
return isTimeOfDisbursement() || isOnSpecifiedDueDate() || isInstalmentFee() || isOverdueInstallment() || isTrancheDisbursement();
}
public boolean isAllowedClientChargeTime() {
@@ -160,5 +161,9 @@ public enum ChargeTimeType {
public boolean isOverdraftFee() {
return this.value.equals(ChargeTimeType.OVERDRAFT_FEE.getValue());
}
public boolean isTrancheDisbursement(){
return this.value.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue());
}
}
@@ -102,7 +102,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
.isOneOfTheseValues(ChargeCalculationType.validValuesForLoan());
}
} else if (appliesTo.isSavingsCharge()) {
// savings applicable validation
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
@@ -256,7 +256,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
if (this.fromApiJsonHelper.parameterExists("chargeCalculationType", element)) {
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element,
Locale.getDefault());
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 4);
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 5);
}
if (this.fromApiJsonHelper.parameterExists("chargePaymentMode", element)) {
@@ -26,9 +26,7 @@ public interface ChargeDropdownReadPlatformService {
List<EnumOptionData> retrieveSavingsCalculationTypes();
List<EnumOptionData> retrieveSavingsCollectionTimeTypes();
List<EnumOptionData> retrieveClientCalculationTypes();
List<EnumOptionData> retrieveClientCollectionTimeTypes();
}
@@ -16,6 +16,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.*;
@Service
@@ -27,7 +28,8 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST));
chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT));
}
@Override
@@ -64,13 +66,15 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
return Arrays.asList(chargeCalculationType(ChargeCalculationType.FLAT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST));
chargeCalculationType(ChargeCalculationType.PERCENT_OF_INTEREST),
chargeCalculationType(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT));
}
@Override
public List<EnumOptionData> retrieveLoanCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE),
chargeTimeType(ChargeTimeType.INSTALMENT_FEE), chargeTimeType(ChargeTimeType.OVERDUE_INSTALLMENT));
chargeTimeType(ChargeTimeType.INSTALMENT_FEE), chargeTimeType(ChargeTimeType.OVERDUE_INSTALLMENT),
chargeTimeType(ChargeTimeType.TRANCHE_DISBURSEMENT));
}
@Override
@@ -64,6 +64,10 @@ public class ChargeEnumerations {
optionData = new EnumOptionData(ChargeTimeType.OVERDRAFT_FEE.getValue().longValue(), ChargeTimeType.OVERDRAFT_FEE.getCode(),
"Overdraft Fee");
break;
case TRANCHE_DISBURSEMENT:
optionData = new EnumOptionData(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().longValue(),
ChargeTimeType.TRANCHE_DISBURSEMENT.getCode(), "Tranche Disbursement");
break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break;
@@ -109,7 +113,7 @@ public class ChargeEnumerations {
break;
case PERCENT_OF_AMOUNT:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Amount");
ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Approved Amount");
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue().longValue(),
@@ -119,6 +123,10 @@ public class ChargeEnumerations {
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_INTEREST.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_INTEREST.getCode(), "% Interest");
break;
case PERCENT_OF_DISBURSEMENT_AMOUNT:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getCode(), "% Disbursement Amount");
break;
default:
optionData = new EnumOptionData(ChargeCalculationType.INVALID.getValue().longValue(),
ChargeCalculationType.INVALID.getCode(), "Invalid");
@@ -145,5 +153,5 @@ public class ChargeEnumerations {
}
return optionData;
}
}
@@ -10,7 +10,9 @@ 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;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
@@ -31,6 +33,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;
/**
@@ -45,7 +48,8 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
private final DropdownReadPlatformService dropdownReadPlatformService;
private final MifosEntityAccessUtil mifosEntityAccessUtil;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
public ChargeReadPlatformServiceImpl(final CurrencyReadPlatformService currencyReadPlatformService,
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource,
@@ -55,6 +59,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
this.currencyReadPlatformService = currencyReadPlatformService;
this.dropdownReadPlatformService = dropdownReadPlatformService;
this.mifosEntityAccessUtil = mifosEntityAccessUtil;
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
@Override
@@ -118,7 +123,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
.retrieveClientCalculationTypes();
final List<EnumOptionData> clientChargeTimeTypeOptions = this.chargeDropdownReadPlatformService.retrieveClientCollectionTimeTypes();
final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions,
allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
@@ -162,16 +167,17 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
@Override
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 + " ";
sql += addInClauseToSQL_toLimitChargesMappedToOffice_ifOfficeSpecificProductsEnabled();
sql += " order by c.name ";
return this.jdbcTemplate.query(sql, rm, params);
final ChargeMapper rm = new ChargeMapper();
StringBuilder excludeClause = new StringBuilder("");
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.namedParameterJdbcTemplate.query(sql, paramMap, rm);
}
/**
@@ -180,9 +186,8 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
* @param params
* @return
*/
private Object[] processChargeExclusionsForLoans(ChargeTimeType[] excludeChargeTimes, StringBuilder excludeClause, Object[] params) {
if (excludeChargeTimes != null && excludeChargeTimes.length > 0) {
excludeClause = excludeClause.append(" and c.charge_time_enum not in(?) ");
private void processChargeExclusionsForLoans(ChargeTimeType[] excludeChargeTimes, StringBuilder excludeClause) {
if (excludeChargeTimes != null && excludeChargeTimes.length > 0) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < excludeChargeTimes.length; i++) {
if (i != 0) {
@@ -190,24 +195,24 @@ 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
@@ -302,7 +307,8 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
}
final BigDecimal minCap = rs.getBigDecimal("minCap");
final BigDecimal maxCap = rs.getBigDecimal("maxCap");
return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType,
chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType);
}
@@ -102,7 +102,7 @@ public class ClientCharge extends AbstractPersistable<Long> {
this.client = client;
this.charge = charge;
this.penaltyCharge = charge.isPenalty();
this.chargeTime = charge.getChargeTime();
this.chargeTime = charge.getChargeTimeType();
this.dueDate = (dueDate == null) ? null : dueDate.toDate();
this.chargeCalculation = charge.getChargeCalculation();
@@ -15,8 +15,10 @@ public interface LoanApiConstants {
public static final String updatedDisbursementDateParameterName = "updatedExpectedDisbursementDate";
public static final String updatedDisbursementPrincipalParameterName = "updatedPrincipal";
public static final String disbursementIdParameterName = "id";
public static final String loanChargeIdParameterName = "loanChargeId";
public static final String principalDisbursedParameterName = "transactionAmount";
public static final String chargesParameterName = "charges" ;
public static final String approvedLoanAmountParameterName = "approvedLoanAmount";
public static final String approvedOnDateParameterName = "approvedOnDate";
public static final String noteParameterName = "note";
@@ -405,10 +405,9 @@ public class LoansApiResource {
if (associationParameters.contains("repaymentSchedule")) {
mandatoryResponseParameters.add("repaymentSchedule");
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = loanBasicDetails.repaymentScheduleRelatedData();
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, repaymentScheduleRelatedData,
disbursementData, loanBasicDetails.isInterestRecalculationEnabled());
disbursementData, loanBasicDetails.isInterestRecalculationEnabled(), loanBasicDetails.getTotalPaidFeeCharges());
if (associationParameters.contains("futureSchedule") && loanBasicDetails.isInterestRecalculationEnabled()) {
mandatoryResponseParameters.add("futureSchedule");
@@ -494,8 +493,13 @@ public class LoansApiResource {
fundOptions = this.fundReadPlatformService.retrieveAllFunds();
repaymentStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies();
chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
if(product.getMultiDisburseLoan()){
chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
}else{
chargeOptions = this.chargeReadPlatformService.retrieveLoanAccountApplicableCharges(loanId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT });
}
chargeTemplate = this.loanChargeReadPlatformService.retrieveLoanChargeTemplate();
allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(loanBasicDetails.officeId(),
@@ -19,13 +19,17 @@ public class DisbursementData implements Comparable<DisbursementData> {
private final LocalDate expectedDisbursementDate;
private final LocalDate actualDisbursementDate;
private final BigDecimal principal;
private final String loanChargeId;
private final BigDecimal chargeAmount;
public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
final BigDecimal principalDisbursed) {
final BigDecimal principalDisbursed, final String loanChargeId, BigDecimal chargeAmount) {
this.id = id;
this.expectedDisbursementDate = expectedDisbursementDate;
this.actualDisbursementDate = actualDisbursementDate;
this.principal = principalDisbursed;
this.loanChargeId = loanChargeId;
this.chargeAmount = chargeAmount;
}
public LocalDate disbursementDate() {
@@ -40,6 +44,10 @@ public class DisbursementData implements Comparable<DisbursementData> {
return this.principal;
}
public BigDecimal getChargeAmount(){
return this.chargeAmount;
}
public boolean isDisbursed() {
return this.actualDisbursementDate != null;
}
@@ -1387,4 +1387,12 @@ public class LoanAccountData {
return LoanStatus.fromInt(this.status.id().intValue()).isActive();
}
public boolean isMultiDisburseLoan() {
return multiDisburseLoan ;
}
public BigDecimal getTotalPaidFeeCharges() {
if(this.summary != null) return this.summary.getTotalPaidFeeCharges() ;
return BigDecimal.ZERO ;
}
}
@@ -68,6 +68,7 @@ public class LoanChargeData {
private BigDecimal amountAccrued;
private BigDecimal amountUnrecognized;
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null, null,
@@ -102,4 +102,8 @@ public class LoanSummaryData {
public BigDecimal getTotalOutstanding() {
return this.totalOutstanding;
}
public BigDecimal getTotalPaidFeeCharges() {
return feeChargesPaid ;
}
}
@@ -62,6 +62,6 @@ public class RepaymentScheduleRelatedLoanData {
}
public DisbursementData disbursementData() {
return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal);
return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, null, null);
}
}
@@ -7,7 +7,6 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@@ -30,7 +29,6 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate;
@@ -121,6 +119,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
@OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
private LoanOverdueInstallmentCharge overdueInstallmentCharge;
@OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
private LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
return createNewFromJson(loan, chargeDefinition, command, dueDate);
@@ -144,8 +145,8 @@ public class LoanCharge extends AbstractPersistable<Long> {
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (command.hasParameter("principal") && command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal")
.add(command.bigDecimalValueOfParameterNamed("interest"));
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal").add(
command.bigDecimalValueOfParameterNamed("interest"));
} else {
amountPercentageAppliedTo = loan.getPrincpal().getAmount().add(loan.getTotalInterest());
}
@@ -162,7 +163,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
BigDecimal loanCharge = BigDecimal.ZERO;
if (ChargeTimeType.fromInt(chargeDefinition.getChargeTime()).equals(ChargeTimeType.INSTALMENT_FEE)) {
if (ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType()).equals(ChargeTimeType.INSTALMENT_FEE)) {
BigDecimal percentage = amount;
if (percentage == null) {
percentage = chargeDefinition.getAmount();
@@ -198,7 +199,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.minCap = chargeDefinition.getMinCap();
this.maxCap = chargeDefinition.getMaxCap();
this.chargeTime = chargeDefinition.getChargeTime();
this.chargeTime = chargeDefinition.getChargeTimeType();
if (chargeTime != null) {
this.chargeTime = chargeTime.getValue();
}
@@ -267,6 +268,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
case PERCENT_OF_AMOUNT:
case PERCENT_OF_AMOUNT_AND_INTEREST:
case PERCENT_OF_INTEREST:
case PERCENT_OF_DISBURSEMENT_AMOUNT:
this.percentage = chargeAmount;
this.amountPercentageAppliedTo = amountPercentageAppliedTo;
if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
@@ -291,6 +293,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.paid = true;
}
public boolean isFullyPaid() {
return this.paid;
}
public void resetToOriginal(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountWaived = BigDecimal.ZERO;
@@ -335,6 +341,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
public BigDecimal getAmountPercentageAppliedTo() {
return this.amountPercentageAppliedTo;
}
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
}
@@ -366,6 +376,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
case PERCENT_OF_AMOUNT:
case PERCENT_OF_AMOUNT_AND_INTEREST:
case PERCENT_OF_INTEREST:
case PERCENT_OF_DISBURSEMENT_AMOUNT:
this.percentage = amount;
this.amountPercentageAppliedTo = loanPrincipal;
if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
@@ -395,6 +406,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
case PERCENT_OF_INTEREST:
amountPercentageAppliedTo = this.loan.getTotalInterest();
break;
case PERCENT_OF_DISBURSEMENT_AMOUNT:
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = this.loanTrancheDisbursementCharge;
amountPercentageAppliedTo = loanTrancheDisbursementCharge.getloanDisbursementDetails().principal();
break;
default:
break;
}
@@ -423,6 +438,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
final String amountParamName = "amount";
if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
BigDecimal loanCharge = null;
actualChanges.put(amountParamName, newValue);
actualChanges.put("locale", localeAsInput);
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
@@ -439,9 +455,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
case PERCENT_OF_AMOUNT:
case PERCENT_OF_AMOUNT_AND_INTEREST:
case PERCENT_OF_INTEREST:
case PERCENT_OF_DISBURSEMENT_AMOUNT:
this.percentage = newValue;
this.amountPercentageAppliedTo = amount;
BigDecimal loanCharge = BigDecimal.ZERO;
loanCharge = BigDecimal.ZERO;
if (isInstalmentFee()) {
loanCharge = this.loan.calculatePerInstallmentChargeAmount(ChargeCalculationType.fromInt(this.chargeCalculation),
this.percentage);
@@ -486,7 +503,8 @@ public class LoanCharge extends AbstractPersistable<Long> {
}
public boolean isDueAtDisbursement() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT);
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT)
|| ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
}
public boolean isSpecifiedDueDate() {
@@ -737,11 +755,12 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Override
public int hashCode() {
return new HashCodeBuilder(3, 5) //
.append(getId()) //
.append(this.charge.getId()) //
.append(this.amount).append(getDueLocalDate()) //
.toHashCode();
return 1;
/*
* return new HashCodeBuilder(3, 5) // .append(getId()) //
* .append(this.charge.getId()) //
* .append(this.amount).append(getDueLocalDate()) // .toHashCode();
*/
}
public ChargePaymentMode getChargePaymentMode() {
@@ -764,8 +783,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() {
LoanInstallmentCharge unpaidChargePerInstallment = null;
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending() && (unpaidChargePerInstallment == null || unpaidChargePerInstallment
.getRepaymentInstallment().getDueDate().isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
if (loanChargePerInstallment.isPending()
&& (unpaidChargePerInstallment == null || unpaidChargePerInstallment.getRepaymentInstallment().getDueDate()
.isAfter(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
unpaidChargePerInstallment = loanChargePerInstallment;
}
}
@@ -781,8 +801,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
public LoanInstallmentCharge getInstallmentLoanCharge(final Integer installmentNumber) {
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber()
.intValue())) { return loanChargePerInstallment; }
if (installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue())) { return loanChargePerInstallment; }
}
return null;
}
@@ -851,6 +870,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.overdueInstallmentCharge = overdueInstallmentCharge;
}
public void updateLoanTrancheDisbursementCharge(final LoanTrancheDisbursementCharge loanTrancheDisbursementCharge) {
this.loanTrancheDisbursementCharge = loanTrancheDisbursementCharge;
}
public void updateWaivedAmount(MonetaryCurrency currency) {
if (isInstalmentFee()) {
this.amountWaived = BigDecimal.ZERO;
@@ -885,6 +908,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
return this.overdueInstallmentCharge;
}
public LoanTrancheDisbursementCharge getTrancheDisbursementCharge() {
return this.loanTrancheDisbursementCharge;
}
public Money undoPaidOrPartiallyAmountBy(final Money incrementBy, final Integer installmentNumber, final Money feeAmount) {
Money processAmount = Money.zero(incrementBy.getCurrency());
if (isInstalmentFee()) {
@@ -922,8 +949,9 @@ public class LoanCharge extends AbstractPersistable<Long> {
Money outstanding = Money.of(currency, loanChargePerInstallment.getAmountOutstanding());
final boolean partiallyPaid = outstanding.isGreaterThanZero()
&& outstanding.isLessThan(loanChargePerInstallment.getAmount(currency));
if ((partiallyPaid || loanChargePerInstallment.isPaid()) && (paidChargePerInstallment == null || paidChargePerInstallment
.getRepaymentInstallment().getDueDate().isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
if ((partiallyPaid || loanChargePerInstallment.isPaid())
&& (paidChargePerInstallment == null || paidChargePerInstallment.getRepaymentInstallment().getDueDate()
.isBefore(loanChargePerInstallment.getRepaymentInstallment().getDueDate()))) {
paidChargePerInstallment = loanChargePerInstallment;
}
}
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
@@ -23,7 +24,7 @@ public class LoanChargePaidBy extends AbstractPersistable<Long> {
@JoinColumn(name = "loan_transaction_id", nullable = false)
private LoanTransaction loanTransaction;
@ManyToOne
@ManyToOne(cascade= CascadeType.ALL)
@JoinColumn(name = "loan_charge_id", nullable = false)
private LoanCharge loanCharge;
@@ -106,7 +106,7 @@ public class LoanDisbursementDetails extends AbstractPersistable<Long> {
if (this.actualDisbursementDate != null) {
actualDisburseDate = new LocalDate(this.actualDisbursementDate);
}
return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal);
return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, null, null);
}
public void updateActualDisbursementDate(Date actualDisbursementDate) {
@@ -60,7 +60,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge()) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
@@ -108,7 +108,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge()) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
@@ -129,7 +129,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
Money cumulative = Money.zero(currency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge()) {
if (loanCharge.isFeeCharge() && !loanCharge.isDueAtDisbursement()) {
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
LoanInstallmentCharge loanChargePerInstallment = loanCharge.getInstallmentLoanCharge(periodEnd);
if (loanChargePerInstallment != null) {
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@@ -192,7 +193,7 @@ public final class LoanSummary {
public void updateSummary(final MonetaryCurrency currency, final Money principal,
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LoanSummaryWrapper summaryWrapper,
final Boolean disbursed) {
final Boolean disbursed, Set<LoanCharge> charges) {
this.totalPrincipalDisbursed = principal.getAmount();
this.totalPrincipalRepaid = summaryWrapper.calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount();
@@ -214,13 +215,15 @@ public final class LoanSummary {
this.totalFeeChargesDueAtDisbursement);
this.totalFeeChargesCharged = totalFeeChargesCharged.getAmount();
Money totalFeeChargesRepaid = summaryWrapper.calculateTotalFeeChargesRepaid(repaymentScheduleInstallments, currency);
if (disbursed) {
totalFeeChargesRepaid = totalFeeChargesRepaid.plus(this.totalFeeChargesDueAtDisbursement);
Money totalFeeChargesRepaidAtDisbursement = summaryWrapper.calculateTotalChargesRepaidAtDisbursement(charges,currency);
this.totalFeeChargesRepaid = totalFeeChargesRepaidAtDisbursement.getAmount();
if(charges != null) {
this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(charges, currency).getAmount();
}else {
this.totalFeeChargesWaived = BigDecimal.ZERO;
}
this.totalFeeChargesRepaid = totalFeeChargesRepaid.getAmount();
this.totalFeeChargesWaived = summaryWrapper.calculateTotalFeeChargesWaived(repaymentScheduleInstallments, currency).getAmount();
this.totalFeeChargesWrittenOff = summaryWrapper.calculateTotalFeeChargesWrittenOff(repaymentScheduleInstallments, currency)
.getAmount();
@@ -6,6 +6,7 @@
package org.mifosplatform.portfolio.loanaccount.domain;
import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
@@ -113,11 +114,13 @@ public final class LoanSummaryWrapper {
return total;
}
public Money calculateTotalFeeChargesWaived(final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments,
public Money calculateTotalFeeChargesWaived(Set<LoanCharge> charges,
final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) {
total = total.plus(installment.getFeeChargesWaived(currency));
for (final LoanCharge charge : charges) {
if(charge.isActive()){
total = total.plus(charge.getAmountWaived(currency));
}
}
return total;
}
@@ -217,4 +220,16 @@ public final class LoanSummaryWrapper {
return overdueSince;
}
public Money calculateTotalChargesRepaidAtDisbursement(Set<LoanCharge> charges, MonetaryCurrency currency) {
Money total = Money.zero(currency);
if(charges == null) return total ;
for (final LoanCharge loanCharge : charges) {
if (!loanCharge.isPenaltyCharge() && loanCharge.getAmountPaid(currency).isGreaterThanZero()) {
total = total.plus(loanCharge.getAmountPaid(currency));
}
}
return total;
}
}
@@ -0,0 +1,52 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_loan_tranche_charges")
public class LoanTrancheCharge extends AbstractPersistable<Long> {
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "charge_id", nullable = false)
private Charge charge;
LoanTrancheCharge() {
}
LoanTrancheCharge(Charge chargeDefinition) {
this.charge = chargeDefinition ;
}
public LoanTrancheCharge(Charge charge, Loan loan) {
this.charge = charge;
this.loan = loan ;
}
public static LoanTrancheCharge createLoanTrancheCharge(Charge chargeDefinition) {
return new LoanTrancheCharge(chargeDefinition) ;
}
public static LoanTrancheCharge createLoanTrancheChargeWithLoan(Charge chargeDefinition, Loan loan) {
return new LoanTrancheCharge(chargeDefinition, loan) ;
}
public Charge getCharge() {
return charge ;
}
}
@@ -0,0 +1,40 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name="m_loan_tranche_disbursement_charge")
public class LoanTrancheDisbursementCharge extends AbstractPersistable<Long> {
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
private LoanCharge loancharge;
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name="disbursement_detail_id", referencedColumnName = "id", nullable = false)
private LoanDisbursementDetails loanDisbursementDetails;
public LoanTrancheDisbursementCharge(){
}
public LoanTrancheDisbursementCharge(final LoanCharge loanCharge, final LoanDisbursementDetails loanDisbursementDetails){
this.loancharge = loanCharge;
this.loanDisbursementDetails = loanDisbursementDetails;
}
public LoanDisbursementDetails getloanDisbursementDetails(){
return this.loanDisbursementDetails;
}
}
@@ -327,6 +327,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (loanTransaction.isChargePayment()) {
feeAmount = feeCharges;
}
if(unpaidCharge == null) break ; //All are trache charges
final Money amountPaidTowardsCharge = unpaidCharge.updatePaidAmountBy(amountRemaining, installmentNumber, feeAmount);
if (!amountPaidTowardsCharge.isZero()) {
Set<LoanChargePaidBy> chargesPaidBies = loanTransaction.getLoanChargesPaid();
@@ -63,7 +63,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// 3. setup variables for tracking important facts required for loan
// schedule generation.
Money principalDisbursed = loanApplicationTerms.getPrincipal();
Money principalDisbursed ;
if(loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) {
principalDisbursed = loanApplicationTerms.getApprovedPrincipal();
}else {
principalDisbursed = loanApplicationTerms.getPrincipal();
}
final MonetaryCurrency currency = principalDisbursed.getCurrency();
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
@@ -482,7 +488,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap,
periodStartDateApplicableForInterest, scheduledDueDate, daysInPeriodApplicableForInterest);
if (loanApplicationTerms.getFixedEmiAmount() != null
if (loanApplicationTerms.getFixedEmiAmount() != null
&& loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) {
String errorMsg = "EMI amount must be greater than : " + principalInterestForThisPeriod.interest().getAmount();
throw new MultiDisbursementEmiAmountException(errorMsg, principalInterestForThisPeriod.interest().getAmount(),
@@ -1644,7 +1650,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isFeeCharge()) {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, numberOfRepayments, cumulative, loanCharge);
} else if (loanCharge.isOverdueInstallmentCharge()
@@ -148,6 +148,8 @@ public final class LoanApplicationTerms {
private final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy;
private Money approvedPrincipal = null ;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
@@ -164,7 +166,7 @@ public final class LoanApplicationTerms {
final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance,
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar) {
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, BigDecimal approvedAmount) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
@@ -176,7 +178,7 @@ public final class LoanApplicationTerms {
disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance,
recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar);
installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -191,7 +193,7 @@ public final class LoanApplicationTerms {
final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount) {
final Calendar loanCalendar = null;
return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek,
@@ -199,7 +201,7 @@ public final class LoanApplicationTerms {
loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations,
interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType,
restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar);
loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -214,7 +216,7 @@ public final class LoanApplicationTerms {
final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -246,7 +248,7 @@ public final class LoanApplicationTerms {
loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar);
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -259,7 +261,7 @@ public final class LoanApplicationTerms {
final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment,
final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy) {
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, BigDecimal approvedAmount) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -298,11 +300,11 @@ public final class LoanApplicationTerms {
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar);
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount);
}
public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType,
final LoanApplicationTerms applicationTerms) {
final LoanApplicationTerms applicationTerms, BigDecimal approvedAmount) {
return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency,
applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, repaymentEvery,
repaymentPeriodFrequencyType, null, null, applicationTerms.amortizationMethod, applicationTerms.interestMethod,
@@ -319,7 +321,7 @@ public final class LoanApplicationTerms {
applicationTerms.recalculationFrequencyType, applicationTerms.compoundingCalendarInstance,
applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment,
applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy,
applicationTerms.loanCalendar);
applicationTerms.loanCalendar, approvedAmount);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -339,7 +341,7 @@ public final class LoanApplicationTerms {
final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType,
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar) {
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, BigDecimal approvedAmount) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
@@ -388,6 +390,7 @@ public final class LoanApplicationTerms {
this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy;
this.loanCalendar = loanCalendar;
this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount) ;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -1043,6 +1046,9 @@ public final class LoanApplicationTerms {
return this.principal;
}
public Money getApprovedPrincipal() {
return this.approvedPrincipal ;
}
public List<DisbursementData> getDisbursementDatas() {
return this.disbursementDatas;
}
@@ -52,6 +52,7 @@ import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -303,7 +304,7 @@ public class LoanScheduleAssembler {
loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData,
graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType,
restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar);
installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO);
}
private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate,
@@ -353,7 +354,7 @@ public class LoanScheduleAssembler {
.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
}
disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal));
disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null));
i++;
} while (i < disbursementDataArray.size());
}
@@ -434,13 +435,13 @@ public class LoanScheduleAssembler {
validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays);
validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays);
return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element);
return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element, null);
}
public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final JsonElement element) {
final List<Holiday> holidays, final WorkingDays workingDays, final JsonElement element, Set<LoanDisbursementDetails> disbursementDetails) {
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails);
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
@@ -372,7 +372,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
waiveLoanCharges(loan, waiveLoanCharges);
// update the Loan summary
loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true);
loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true, null);
// update the total number of schedule repayments
loan.updateNumberOfRepayments(periods.size());
@@ -43,6 +43,7 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
@@ -63,6 +64,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateM
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
@@ -484,15 +486,21 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
LoanChargeData chargeData = new LoanChargeData(charge.getId(), charge.getDueLocalDate(), charge.amountOrPercentage());
chargesMap.put(charge.getId(), chargeData);
}
Set<LoanDisbursementDetails> disbursementDetails = this.loanAssembler.fetchDisbursementData(command.parsedJson().getAsJsonObject());
/**
* Stores all charges which are passed in during modify loan
* application
**/
final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson());
final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),disbursementDetails);
/** Boolean determines if any charge has been modified **/
boolean isChargeModified = false;
Set<Charge> newTrancheChages = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson()) ;
for (Charge charge : newTrancheChages) {
existingLoanApplication.addTrancheLoanCharge(charge) ;
}
/**
* If there are any charges already present, which are now not
* passed in as a part of the request, deem the charges as modified
@@ -621,7 +629,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
final Set<LoanCollateral> loanCollateral = this.loanCollateralAssembler.fromParsedJson(command.parsedJson());
existingLoanApplication.updateLoanCollateral(loanCollateral);
}
if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule");
@@ -632,7 +640,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
existingLoanApplication.updateLoanSchedule(loanSchedule, currentUser);
existingLoanApplication.recalculateAllCharges();
}
final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) {
existingLoanApplication.updateLoanCharges(possiblyModifedLoanCharges);
@@ -872,7 +880,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
// If loan approved amount less than loan demanded amount, then need
// to recompute the schedule
if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName)) {
if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey("recalculateLoanSchedule")) {
ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
}
@@ -172,8 +172,30 @@ public class LoanAssembler {
if (loanPurposeId != null) {
loanPurpose = this.codeValueRepository.findOneWithNotFoundDetection(loanPurposeId);
}
Set<LoanDisbursementDetails> disbursementDetails = null;
BigDecimal fixedEmiAmount = null;
if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) {
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
}
BigDecimal maxOutstandingLoanBalance = null;
if (loanProduct.isMultiDisburseLoan()) {
disbursementDetails = fetchDisbursementData(element.getAsJsonObject());
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
element, locale);
if (disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
}
final Set<LoanCollateral> collateral = this.loanCollateralAssembler.fromParsedJson(element);
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element,disbursementDetails);
for (final LoanCharge loanCharge : loanCharges) {
if (!loanProduct.hasCurrencyCodeOf(loanCharge.currencyCode())) {
final String errorMessage = "Charge and Loan must have the same currency.";
@@ -197,28 +219,7 @@ public class LoanAssembler {
final String loanTypeParameterName = "loanType";
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr);
Set<LoanDisbursementDetails> disbursementDetails = null;
BigDecimal fixedEmiAmount = null;
if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstallmentAmount()) {
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
}
BigDecimal maxOutstandingLoanBalance = null;
if (loanProduct.isMultiDisburseLoan()) {
disbursementDetails = fetchDisbursementData(element.getAsJsonObject());
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
element, locale);
if (disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
}
if (clientId != null) {
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
@@ -281,7 +282,7 @@ public class LoanAssembler {
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms,
isHolidayEnabled, holidays, workingDays, element);
isHolidayEnabled, holidays, workingDays, element,disbursementDetails);
loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(),
submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay,
recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate);
@@ -289,7 +290,7 @@ public class LoanAssembler {
return loanApplication;
}
private Set<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
public Set<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
Set<LoanDisbursementDetails> disbursementDatas = new HashSet<>();
@@ -6,7 +6,9 @@
package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -19,8 +21,15 @@ import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTrancheCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -34,21 +43,49 @@ public class LoanChargeAssembler {
private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private final LoanProductRepository loanProductRepository;
@Autowired
public LoanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
final LoanChargeRepository loanChargeRepository) {
final LoanChargeRepository loanChargeRepository, final LoanProductRepository loanProductRepository) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.chargeRepository = chargeRepository;
this.loanChargeRepository = loanChargeRepository;
this.loanProductRepository = loanProductRepository;
}
public Set<LoanCharge> fromParsedJson(final JsonElement element) {
public Set<LoanCharge> fromParsedJson(final JsonElement element, Set<LoanDisbursementDetails> disbursementDetails) {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element) ;
List<Long> disbursementChargeIds = new ArrayList<>();
if(jsonDisbursement != null && jsonDisbursement.size() > 0) {
for(int i = 0 ; i < jsonDisbursement.size(); i++) {
final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject();
if(jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null){
String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString();
if(chargeIds != null) {
if (chargeIds.indexOf(",") != -1) {
String[] chargeId = chargeIds.split(",");
for (String loanChargeId : chargeId) {
disbursementChargeIds.add(Long.parseLong(loanChargeId));
}
} else {
disbursementChargeIds.add(Long.parseLong(chargeIds)) ;
}
}
}
}
}
final Set<LoanCharge> loanCharges = new HashSet<>();
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
final boolean isMultiDisbursal = loanProduct.isMultiDisburseLoan();
LocalDate expectedDisbursementDate = null;
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
@@ -92,17 +129,85 @@ public class LoanChargeAssembler {
if (chargePaymentMode != null) {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
}
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
loanCharges.add(loanCharge);
if (!isMultiDisbursal) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount, chargeTime,
chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
loanCharges.add(loanCharge);
}
if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) {
final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray();
JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject();
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.disbursementDateParameterName, disbursementDataElement, dateFormat, locale);
}
if ((chargeDefinition.isPercentageOfDisbursementAmount() || chargeDefinition.isPercentageOfApprovedAmount() || chargeDefinition
.getChargeCalculation() == ChargeCalculationType.FLAT.getValue()) && disbursementDetails != null) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
if (chargeDefinition.getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue()) {
if (chargeDefinition.isPercentageOfApprovedAmount()
&& disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal, amount,
chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum, numberOfRepayments);
loanCharges.add(loanCharge);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
} else {
if (disbursementDetail.expectedDisbursementDateAsLocalDate().equals(expectedDisbursementDate)) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition,
disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
numberOfRepayments);
loanCharges.add(loanCharge);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
}
} else if (chargeDefinition.getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition,
disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
numberOfRepayments);
loanCharges.add(loanCharge);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
}
} else {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
if (disbursementDetails != null) {
for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
if (!(disbursementDetail.expectedDisbursementDateAsLocalDate().isAfter(expectedDisbursementDate))) {
if (chargeDefinition.getChargeCalculation() == ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST
.getValue()
|| chargeDefinition.getChargeTimeType() == ChargeTimeType.SPECIFIED_DUE_DATE.getValue()
|| chargeDefinition.getChargeCalculation() == ChargeCalculationType.PERCENT_OF_INTEREST
.getValue()) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, principal,
amount, chargeTime, chargeCalculation, dueDate, chargePaymentModeEnum,
numberOfRepayments);
loanCharges.add(loanCharge);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
}
}
}
}
}
} else {
final Long loanChargeId = id;
final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeId);
if (loanCharge == null) { throw new LoanChargeNotFoundException(loanChargeId); }
loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
if(disbursementChargeIds.contains(loanChargeId) && loanCharge == null) {
//throw new LoanChargeNotFoundException(loanChargeId);
}
if(loanCharge != null) {
loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
}
}
}
@@ -110,4 +215,26 @@ public class LoanChargeAssembler {
return loanCharges;
}
public Set<Charge> getNewLoanTrancheCharges(final JsonElement element) {
final Set<Charge> associatedChargesForLoan = new HashSet<>();
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if (chargeDefinition.getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()) {
associatedChargesForLoan.add(chargeDefinition);
}
}
}
}
}
return associatedChargesForLoan;
}
}
@@ -53,9 +53,12 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
private static final class LoanChargeMapper implements RowMapper<LoanChargeData> {
public String schema() {
return "lc.id as id, c.id as chargeId, c.name as name, " + "lc.amount as amountDue, " + "lc.amount_paid_derived as amountPaid, "
+ "lc.amount_waived_derived as amountWaived, " + "lc.amount_writtenoff_derived as amountWrittenOff, "
public String schema() {
return "lc.id as id, c.id as chargeId, c.name as name, "
+ "lc.amount as amountDue, "
+ "lc.amount_paid_derived as amountPaid, "
+ "lc.amount_waived_derived as amountWaived, "
+ "lc.amount_writtenoff_derived as amountWrittenOff, "
+ "lc.amount_outstanding_derived as amountOutstanding, "
+ "lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, "
+ "lc.charge_time_enum as chargeTime, " + "lc.is_penalty as penalty, "
@@ -63,9 +66,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
@@ -96,7 +101,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);
@@ -109,6 +114,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,
@@ -133,7 +143,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final List<EnumOptionData> clientChargeTimeTypeOptions = null;
final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeFrequencyOptions);
@@ -157,7 +167,7 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
final LoanChargeMapper rm = new LoanChargeMapper();
final String sql = "select " + rm.schema() + " where lc.loan_id=? AND lc.is_active = 1"
+ " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
+ " order by ifnull(lc.due_for_collection_as_of_date,date(ifnull(dd.disbursedon_date,dd.expected_disburse_date))),lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
}
@@ -5,6 +5,7 @@
*/
package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
@@ -30,7 +31,7 @@ public interface LoanReadPlatformService {
LoanAccountData retrieveOne(Long loanId);
LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData,
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled);
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges);
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
@@ -207,13 +207,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData,
boolean isInterestRecalculationEnabled) {
boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) {
try {
this.context.authenticatedUser();
final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled);
repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled ,totalPaidFeeCharges);
final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
return this.jdbcTemplate.query(sql, fullResultsetExtractor, new Object[] { loanId });
@@ -954,9 +954,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance;
private boolean excludePastUndisbursed;
private BigDecimal totalPaidFeeCharges ;
public LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled) {
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
@@ -964,6 +964,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.outstandingLoanPrincipalBalance = this.disbursement.amount();
this.disbursementData = disbursementData;
this.excludePastUndisbursed = isInterestRecalculationEnabled;
this.totalPaidFeeCharges = totalPaidFeeCharges ;
}
public String schema() {
@@ -1014,7 +1015,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
// update totals with details of fees charged during disbursement
totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue());
totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue());
totalRepayment = totalRepayment.plus(disbursementPeriod.feeChargesPaid());
totalRepayment = totalRepayment.plus(totalPaidFeeCharges);
totalOutstanding = totalOutstanding.plus(disbursementPeriod.feeChargesDue()).minus(disbursementPeriod.feeChargesPaid());
Integer loanTermInDays = Integer.valueOf(0);
@@ -1031,17 +1032,29 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
for (DisbursementData data : disbursementData) {
if (fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate)) {
principal = principal.add(data.amount());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed());
periods.add(periodData);
if(data.getChargeAmount() == null){
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
periods.add(periodData);
}else{
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), data.getChargeAmount(), data.isDisbursed());
periods.add(periodData);
}
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
} else if (data.isDueForDisbursement(fromDate, dueDate)) {
if (!excludePastUndisbursed
|| (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) {
principal = principal.add(data.amount());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
periods.add(periodData);
if(data.getChargeAmount() == null){
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), BigDecimal.ZERO, data.isDisbursed());
periods.add(periodData);
}else{
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
data.disbursementDate(), data.amount(), data.getChargeAmount(), data.isDisbursed());
periods.add(periodData);
}
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
}
}
@@ -1329,8 +1342,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Collection<CodeValueData> loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose");
final Collection<CodeValueData> loanCollateralOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode("LoanCollateral");
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
Collection<ChargeData> chargeOptions = null;
if(loanProduct.getMultiDisburseLoan()){
chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
}else{
chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT });
}
Integer loanCycleCounter = null;
if (loanProduct.useBorrowerCycle()) {
if (clientId == null) {
@@ -1441,17 +1461,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public Collection<DisbursementData> retrieveLoanDisbursementDetails(final Long loanId) {
public Collection<DisbursementData> retrieveLoanDisbursementDetails
(final Long loanId) {
final LoanDisbursementDetailMapper rm = new LoanDisbursementDetailMapper();
final String sql = "select " + rm.schema() + " where dd.loan_id=? order by dd.expected_disburse_date";
final String sql = "select " + rm.schema() + " where dd.loan_id=? group by dd.id order by dd.expected_disburse_date";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
}
private static final class LoanDisbursementDetailMapper implements RowMapper<DisbursementData> {
public String schema() {
return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate,dd.disbursedon_date as actualDisbursementdate,dd.principal as principal "
+ "from m_loan_disbursement_detail dd";
return "dd.id as id,dd.expected_disburse_date as expectedDisbursementdate, dd.disbursedon_date as actualDisbursementdate,dd.principal as principal,sum(lc.amount) chargeAmount, lc.amount_waived_derived waivedAmount,group_concat(lc.id) loanChargeId "+
"from m_loan l inner join m_loan_disbursement_detail dd on dd.loan_id = l.id left join m_loan_tranche_disbursement_charge tdc on tdc.disbursement_detail_id=dd.id " +
"left join m_loan_charge lc on lc.id=tdc.loan_charge_id and lc.is_active=1";
}
@Override
@@ -1460,8 +1482,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate expectedDisbursementdate = JdbcSupport.getLocalDate(rs, "expectedDisbursementdate");
final LocalDate actualDisbursementdate = JdbcSupport.getLocalDate(rs, "actualDisbursementdate");
final BigDecimal principal = rs.getBigDecimal("principal");
final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal);
final String loanChargeId = rs.getString("loanChargeId");
BigDecimal chargeAmount = rs.getBigDecimal("chargeAmount");
final BigDecimal waivedAmount = rs.getBigDecimal("waivedAmount") ;
if(chargeAmount != null && waivedAmount != null)
chargeAmount = chargeAmount.subtract(waivedAmount) ;
final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal, loanChargeId,
chargeAmount);
return disbursementData;
}
@@ -76,6 +76,7 @@ import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNot
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.exception.ChargeCannotBeUpdatedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON;
@@ -120,6 +121,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInsta
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
@@ -1301,6 +1303,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build();
}
private void validateAddingNewChargeAllowed(Set<LoanDisbursementDetails> loanDisburseDetails) {
boolean pendingDisbursementAvailable = false;
for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) {
if (disbursementDetail.actualDisbursementDate() == null) {
pendingDisbursementAvailable = true ;
break ;
}
}
if(!pendingDisbursementAvailable) {
throw new ChargeCannotBeUpdatedException(
"error.msg.charge.cannot.be.updated.no.pending.disbursements.in.loan", "This charge cannot be added, No disbursement is pending");
}
}
@Transactional
@Override
public CommandProcessingResult addLoanCharge(final Long loanId, final JsonCommand command) {
@@ -1310,19 +1326,42 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
Set<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
final Long chargeDefinitionId = command.longValueOfParameterNamed("chargeId");
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
validateAddLoanCharge(loan, chargeDefinition, loanCharge);
if(loan.isDisbursed() && chargeDefinition.isDisbursementCharge()) {
validateAddingNewChargeAllowed(loanDisburseDetails) ; //validates whether any pending disbursements are available to apply this charge
}
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
boolean isAppliedOnBackDate = false;
LoanCharge loanCharge = null;
if (chargeDefinition.isPercentageOfDisbursementAmount()) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) {
if (disbursementDetail.actualDisbursementDate() == null) {
loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, disbursementDetail.principal(), null, null, null,
disbursementDetail.expectedDisbursementDateAsLocalDate(), null, null);
loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, disbursementDetail);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
validateAddLoanCharge(loan, chargeDefinition, loanCharge);
isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
}
}
loan.addTrancheLoanCharge(chargeDefinition);
} else {
loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_ADD_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN_CHARGE, loanCharge));
validateAddLoanCharge(loan, chargeDefinition, loanCharge);
isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
}
boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
boolean reprocessRequired = true;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
@@ -1331,7 +1370,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
updateOriginalSchedule(loan);
}
if (reprocessRequired) {
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
if (changedTransactionDetail != null) {
@@ -1480,10 +1518,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
this.loanChargeRepository.save(loanCharge);
loan.addLoanCharge(loanCharge);
this.loanChargeRepository.save(loanCharge);
/**
* we want to apply charge transactions only for those loans charges
* that are applied when a loan is active and the loan product uses
@@ -2349,7 +2387,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
@Override
@Transactional
public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDatas) {
@@ -2652,10 +2689,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command,
existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO, currentUser);
} else {
// BigDecimal setAmount = loan.getApprovedPrincipal();
Collection<LoanDisbursementDetails> loanDisburseDetails = loan.getDisbursementDetails();
BigDecimal setAmount = BigDecimal.ZERO;
for (LoanDisbursementDetails details : loanDisburseDetails) {
if (details.actualDisbursementDate() != null) {
if (details.expectedDisbursementDate() != null) {
setAmount = setAmount.add(details.principal());
}
}
@@ -2666,6 +2704,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser);
} else {
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
loan.processPostDisbursementTransactions() ;
}
}
@@ -19,6 +19,7 @@ import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
@@ -83,7 +84,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
@Column(name = "description")
private String description;
@ManyToMany
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id"))
private List<Charge> charges;
@@ -602,6 +603,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
public Integer getAccountingType() {
return this.accountingRule;
}
public List<Charge> getLoanProductCharges(){
return this.charges;
}
public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) {
this.loanConfigurableAttributes = loanConfigurableAttributes;
@@ -153,7 +153,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.savingsAccount = savingsAccount;
this.charge = chargeDefinition;
this.penaltyCharge = chargeDefinition.isPenalty();
this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTime() : chargeTime.getValue();
this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTimeType() : chargeTime.getValue();
if (isOnSpecifiedDueDate()) {
if (dueDate == null) {
@@ -134,8 +134,8 @@ public class SavingsAccountChargeAssembler {
Set<Charge> productCharges = savingsProduct.charges();
for (Charge charge : productCharges) {
ChargeTimeType chargeTime = null;
if (charge.getChargeTime() != null) {
chargeTime = ChargeTimeType.fromInt(charge.getChargeTime());
if (charge.getChargeTimeType() != null) {
chargeTime = ChargeTimeType.fromInt(charge.getChargeTimeType());
}
if (chargeTime != null && chargeTime.isOnSpecifiedDueDate()) {
continue;
@@ -0,0 +1,26 @@
CREATE TABLE `m_loan_tranche_disbursement_charge` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_charge_id` BIGINT(20) NOT NULL,
`disbursement_detail_id` BIGINT(20) NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;
ALTER TABLE `m_loan_tranche_disbursement_charge`
ADD CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
ADD CONSTRAINT `FK_m_loan_tranche_disbursement_charge_m_loan_disbursement_detail` FOREIGN KEY (`disbursement_detail_id`) REFERENCES `m_loan_disbursement_detail` (`id`);
CREATE TABLE `m_loan_tranche_charges` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_id` BIGINT(20) NOT NULL,
`charge_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`id`)
) ;
ALTER TABLE `m_loan_tranche_charges`
ADD CONSTRAINT `FK_m_loan_tranche_charges_m_loan` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`),
ADD CONSTRAINT `FK_m_loan_tranche_charges_m_charge` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`);