Merge pull request #818 from pramodn02/MIFOSX-1025

MIFOSX-1025 - Added Overdue Penalty recurrence
This commit is contained in:
Vishwas Babu A J
2014-03-26 02:53:18 -07:00
49 changed files with 801 additions and 138 deletions
+151 -3
View File
@@ -5749,7 +5749,52 @@ GET https://DomainName/api/v1/loans/{loanId}
"disbursementDetails":[{"id":71,"expectedDisbursementDate":[2013,11,1],"principal":22000.000000,"approvedPrincipal":22000.000000}],
"fixedEmiAmount":1100.000000,
"maxOutstandingLoanBalance":35000,
"canDisburse":false
"canDisburse":false,
"emiAmountVariations": [],
"inArrears": true,
"overdueCharges": [
{
"id": 20,
"name": "overdraft penality",
"active": true,
"penalty": true,
"currency": {
"code": "USD",
"name": "US Dollar",
"decimalPlaces": 2,
"displaySymbol": "$",
"nameCode": "currency.USD",
"displayLabel": "US Dollar ($)"
},
"amount": 3.000000,
"chargeTimeType": {
"id": 9,
"code": "chargeTimeType.overdueInstallment",
"value": "overdue fees"
},
"chargeAppliesTo": {
"id": 1,
"code": "chargeAppliesTo.loan",
"value": "Loan"
},
"chargeCalculationType": {
"id": 2,
"code": "chargeCalculationType.percent.of.amount",
"value": "% Amount"
},
"chargePaymentMode": {
"id": 0,
"code": "chargepaymentmode.regular",
"value": "Regular"
},
"feeInterval": 2,
"feeFrequency": {
"id": 1,
"code": "feeFrequencyperiodFrequencyType.weeks",
"value": "Weeks"
}
}
]
}
}
</code>
@@ -12575,9 +12620,15 @@ Request Body:
<td>feeInterval</td>
</tr>
<tr>
<td class=fielddesc>Fee to be applied on a specified interval (number of months).<br/>
This field is mandatory if <i>chargeTimeType</i> is of type <b>Monthly Fee</b>
<td class=fielddesc>Fee to be applied on a specified interval.<br/>
This field is mandatory if <i>chargeTimeType</i> is of type <b>Monthly Fee</b> or <i>feeFrequency<i> is selcted
</td>
</tr>
<tr class=alt>
<td>feeFrequency</td>
</tr>
<tr>
<td class=fielddesc>This field is Optional Used to indicate the recurring(days,weeks,months and Years) charge due date starting on system calculated date for overdue penalty.<br/></td>
</tr>
</table>
</div>
@@ -12627,6 +12678,16 @@ GET https://DomainName/api/v1/charges/template
"id": 2,
"code": "chargeCalculationType.percent.of.amount",
"value": "% Amount"
},
{
"id": 3,
"code": "chargeCalculationType.percent.of.amount.and.interest",
"value": "% Loan Amount + Interest"
},
{
"id": 4,
"code": "chargeCalculationType.percent.of.interest",
"value": "% Interest"
}
],
"chargeAppliesToOptions": [
@@ -12671,6 +12732,21 @@ GET https://DomainName/api/v1/charges/template
"id": 7,
"code": "chargeTimeType.monthlyFee",
"value": "Monthly Fee"
},
{
"id": 8,
"code": "chargeTimeType.instalmentFee",
"value": "Instalment Fee"
},
{
"id": 9,
"code": "chargeTimeType.overdueInstallment",
"value": "overdue fees"
},
{
"id": 10,
"code": "chargeTimeType.overdraftFee",
"value": "Overdraft Fee"
}
],
"chargePaymetModeOptions": [
@@ -12695,6 +12771,16 @@ GET https://DomainName/api/v1/charges/template
"id": 2,
"code": "chargeCalculationType.percent.of.amount",
"value": "% Amount"
},
{
"id": 3,
"code": "chargeCalculationType.percent.of.amount.and.interest",
"value": "% Loan Amount + Interest"
},
{
"id": 4,
"code": "chargeCalculationType.percent.of.interest",
"value": "% Interest"
}
],
"loanChargeTimeTypeOptions": [
@@ -12707,6 +12793,16 @@ GET https://DomainName/api/v1/charges/template
"id": 2,
"code": "chargeTimeType.specifiedDueDate",
"value": "Specified due date"
},
{
"id": 8,
"code": "chargeTimeType.instalmentFee",
"value": "Instalment Fee"
},
{
"id": 9,
"code": "chargeTimeType.overdueInstallment",
"value": "overdue fees"
}
],
"savingsChargeCalculationTypeOptions": [
@@ -12746,6 +12842,33 @@ GET https://DomainName/api/v1/charges/template
"id": 7,
"code": "chargeTimeType.monthlyFee",
"value": "Monthly Fee"
},
{
"id": 10,
"code": "chargeTimeType.overdraftFee",
"value": "Overdraft Fee"
}
],
"feeFrequencyOptions": [
{
"id": 0,
"code": "loanTermFrequency.periodFrequencyType.days",
"value": "Days"
},
{
"id": 1,
"code": "loanTermFrequency.periodFrequencyType.weeks",
"value": "Weeks"
},
{
"id": 2,
"code": "loanTermFrequency.periodFrequencyType.months",
"value": "Months"
},
{
"id": 3,
"code": "loanTermFrequency.periodFrequencyType.years",
"value": "Years"
}
]
}
@@ -12853,6 +12976,31 @@ Request Body:
<code class="method-response">
{
"resourceId": 6
}
</code>
<code class="method-request">
POST charges
Content-Type: application/json
Request Body:
{
"chargeAppliesTo":1,
"feeFrequency":1,
"feeInterval":"2",
"name":"overdue charge",
"currencyCode":"USD",
"chargeTimeType":9,
"chargeCalculationType":1,
"chargePaymentMode":0,
"amount":"50",
"active":true,
"penalty":"true",
"locale":"en",
"monthDayFormat":"dd MMM"
}
</code>
<code class="method-response">
{
"resourceId": 7
}
</code>
</div>
@@ -0,0 +1,18 @@
ALTER TABLE `m_charge`
ADD COLUMN `fee_frequency` SMALLINT(5) NULL DEFAULT NULL AFTER `max_cap`;
CREATE TABLE `m_loan_overdue_installment_charge` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_charge_id` BIGINT(20) NOT NULL,
`loan_schedule_id` BIGINT(20) NOT NULL,
`frequency_number` INT(10) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_charge` FOREIGN KEY (`loan_charge_id`) REFERENCES `m_loan_charge` (`id`),
CONSTRAINT `FK_m_loan_overdue_installment_charge_m_loan_repayment_schedule` FOREIGN KEY (`loan_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB;
INSERT INTO `c_configuration` (`name`, `value`, `enabled`) VALUES ('grace-on-penalty-posting', 0, 1);
INSERT INTO m_loan_overdue_installment_charge (`loan_charge_id`, `loan_schedule_id`, `frequency_number`) SELECT mlc.id ,ls.id ,1 from m_loan_charge as mlc inner join m_charge mc on mc.id = mlc.charge_id inner join m_loan_repayment_schedule ls on ls.loan_id = mlc.loan_id and ls.duedate = mlc.due_for_collection_as_of_date where mc.charge_time_enum = 9 and mlc.is_active = 1;
@@ -32,4 +32,6 @@ public interface ConfigurationDomainService {
boolean isPasswordForcedResetEnable();
Long retrievePasswordLiveTime();
Long retrieveGraceOnPenaltyPostingPeriod();
}
@@ -113,6 +113,14 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
return property.getValue();
}
@Override
public Long retrieveGraceOnPenaltyPostingPeriod() {
final String propertyName = "grace-on-penalty-posting";
final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
return property.getValue();
}
@Override
public boolean isPasswordForcedResetEnable() {
@@ -34,7 +34,7 @@ import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarWeekDaysType;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
public class CalendarUtils {
@@ -14,6 +14,7 @@ import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
@@ -36,6 +37,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final Integer feeInterval;
private final BigDecimal minCap;
private final BigDecimal maxCap;
private final EnumOptionData feeFrequency;
private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;//
@@ -47,16 +49,19 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final List<EnumOptionData> loanChargeTimeTypeOptions;
private final List<EnumOptionData> savingsChargeCalculationTypeOptions;
private final List<EnumOptionData> savingsChargeTimeTypeOptions;
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,
final List<EnumOptionData> loansChargeCalculationTypeOptions, final List<EnumOptionData> loansChargeTimeTypeOptions,
final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> savingsChargeTimeTypeOptions) {
final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> savingsChargeTimeTypeOptions,
final List<EnumOptionData> feeFrequencyOptions) {
return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions,
chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions,
loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, null, null, null, null);
loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, null, null, null, null,
null, feeFrequencyOptions);
}
public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) {
@@ -65,13 +70,13 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions,
template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions,
template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions, charge.feeOnMonthDay,
charge.feeInterval, charge.minCap, charge.maxCap);
charge.feeInterval, charge.minCap, charge.maxCap, charge.feeFrequency, template.feeFrequencyOptions);
}
public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final EnumOptionData chargePaymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final boolean penalty,
final boolean active, final BigDecimal minCap, final BigDecimal maxCap) {
final boolean active, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency) {
final Collection<CurrencyData> currencyOptions = null;
final List<EnumOptionData> chargeCalculationTypeOptions = null;
@@ -82,11 +87,13 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> loansChargeTimeTypeOptions = null;
final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
final List<EnumOptionData> savingsChargeTimeTypeOptions = 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,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap);
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap,
feeFrequency, feeFrequencyOptions);
}
public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) {
@@ -111,11 +118,14 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> loansChargeTimeTypeOptions = null;
final List<EnumOptionData> savingsChargeCalculationTypeOptions = null;
final List<EnumOptionData> savingsChargeTimeTypeOptions = 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,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap);
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap,
feeFrequency, feeFrequencyOptions);
}
private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
@@ -126,7 +136,8 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
final List<EnumOptionData> loansChargeTimeTypeOptions, final List<EnumOptionData> savingsChargeCalculationTypeOptions,
final List<EnumOptionData> savingsChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval,
final BigDecimal minCap, final BigDecimal maxCap) {
final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
final List<EnumOptionData> feeFrequencyOptions) {
this.id = id;
this.name = name;
this.amount = amount;
@@ -150,6 +161,8 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
this.savingsChargeTimeTypeOptions = savingsChargeTimeTypeOptions;
this.loanChargeCalculationTypeOptions = loansChargeCalculationTypeOptions;
this.loanChargeTimeTypeOptions = loansChargeTimeTypeOptions;
this.feeFrequency = feeFrequency;
this.feeFrequencyOptions = feeFrequencyOptions;
}
@Override
@@ -199,4 +212,17 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
this.chargeCalculationType, percentage, amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay,
this.feeInterval);
}
public boolean isPenalty() {
return this.penalty;
}
public boolean isOverdueInstallmentCharge(){
boolean isOverdueInstallmentCharge = false;
if(this.chargeTimeType != null){
isOverdueInstallmentCharge = ChargeTimeType.fromInt(this.chargeTimeType.getId().intValue()).isOverdueInstallment();
}
return isOverdueInstallmentCharge;
}
}
@@ -27,6 +27,7 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.exception.ChargeDueAtDisbursementCannotBePenaltyException;
import org.mifosplatform.portfolio.charge.exception.ChargeMustBePenaltyException;
import org.mifosplatform.portfolio.charge.exception.ChargeParameterUpdateNotSupportedException;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
@@ -81,6 +82,9 @@ public class Charge extends AbstractPersistable<Long> {
@Column(name = "max_cap", scale = 6, precision = 19, nullable = true)
private BigDecimal maxCap;
@Column(name = "fee_frequency", nullable = true)
private Integer feeFrequency;
public static Charge fromJson(final JsonCommand command) {
final String name = command.stringValueOfParameterNamed("name");
@@ -101,9 +105,10 @@ public class Charge extends AbstractPersistable<Long> {
final Integer feeInterval = command.integerValueOfParameterNamed("feeInterval");
final BigDecimal minCap = command.bigDecimalValueOfParameterNamed("minCap");
final BigDecimal maxCap = command.bigDecimalValueOfParameterNamed("maxCap");
final Integer feeFrequency = command.integerValueOfParameterNamed("feeFrequency");
return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode,
feeOnMonthDay, feeInterval, minCap, maxCap);
feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency);
}
protected Charge() {
@@ -113,7 +118,7 @@ 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 BigDecimal minCap, final BigDecimal maxCap, final Integer feeFrequency) {
this.name = name;
this.amount = amount;
this.currencyCode = currencyCode;
@@ -130,8 +135,9 @@ public class Charge extends AbstractPersistable<Long> {
if (isMonthlyFee() || isAnnualFee()) {
this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
this.feeOnDay = feeOnMonthDay.getDayOfMonth();
this.feeInterval = feeInterval;
}
this.feeInterval = feeInterval;
this.feeFrequency = feeFrequency;
if (isSavingsCharge()) {
@@ -154,7 +160,7 @@ public class Charge extends AbstractPersistable<Long> {
} else if (isLoanCharge()) {
if (penalty && chargeTime.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); }
if (!penalty && chargeTime.isOverdueInstallment()) { throw new ChargeMustBePenaltyException(name); }
if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
@@ -381,6 +387,18 @@ public class Charge extends AbstractPersistable<Long> {
actualChanges.put("locale", localeAsInput);
this.feeInterval = newValue;
}
final String feeFrequency = "feeFrequency";
if (command.isChangeInIntegerParameterNamed(feeFrequency, this.feeFrequency)) {
final Integer newValue = command.integerValueOfParameterNamed(feeFrequency);
actualChanges.put(feeFrequency, newValue);
actualChanges.put("locale", localeAsInput);
this.feeFrequency = newValue;
}
if(this.feeFrequency != null){
baseDataValidator.reset().parameter("feeInterval").value(this.feeInterval).notNull();
}
final String penaltyParamName = "penalty";
if (command.isChangeInBooleanParameterNamed(penaltyParamName, this.penalty)) {
@@ -415,6 +433,7 @@ public class Charge extends AbstractPersistable<Long> {
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 (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
@@ -438,9 +457,11 @@ public class Charge extends AbstractPersistable<Long> {
final EnumOptionData chargeAppliesTo = ChargeEnumerations.chargeAppliesTo(this.chargeAppliesTo);
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(this.chargeCalculation);
final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
final EnumOptionData feeFrequencyType = ChargeEnumerations.chargePaymentMode(this.feeFrequency);
final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType,
chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.minCap, this.maxCap);
chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.minCap, this.maxCap,
feeFrequencyType);
}
public Integer getChargePaymentMode() {
@@ -455,6 +476,10 @@ public class Charge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee();
}
public boolean isOverdueInstallment() {
return ChargeTimeType.fromInt(this.chargeTime).isOverdueInstallment();
}
public MonthDay getFeeOnMonthDay() {
MonthDay feeOnMonthDay = null;
if (this.feeOnDay != null && this.feeOnMonth != null) {
@@ -466,4 +491,8 @@ public class Charge extends AbstractPersistable<Long> {
public Integer feeInterval() {
return this.feeInterval;
}
public Integer feeFrequency() {
return this.feeFrequency;
}
}
@@ -42,6 +42,11 @@ public enum ChargeTimeType {
return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
ChargeTimeType.INSTALMENT_FEE.getValue(), ChargeTimeType.OVERDUE_INSTALLMENT.getValue() };
}
public static Object[] validLoanChargeValues() {
return new Integer[] { ChargeTimeType.DISBURSEMENT.getValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getValue(),
ChargeTimeType.INSTALMENT_FEE.getValue()};
}
public static Object[] validSavingsValues() {
return new Integer[] { ChargeTimeType.SPECIFIED_DUE_DATE.getValue(), ChargeTimeType.SAVINGS_ACTIVATION.getValue(),
@@ -0,0 +1,19 @@
/**
* 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.charge.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when attempting to crate
* charge of type penalty due at disbursement .
*/
public class ChargeMustBePenaltyException extends AbstractPlatformDomainRuleException {
public ChargeMustBePenaltyException(final String name) {
super("error.msg.charge.must.be.penalty", "Charge '" + name + "' is invalid.", name, name);
}
}
@@ -41,7 +41,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
*/
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("name", "amount", "locale", "currencyCode",
"currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty",
"active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat", "minCap", "maxCap"));
"active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency"));
private final FromJsonHelper fromApiJsonHelper;
@@ -70,6 +70,16 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeCalculationType", element);
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull();
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed("feeInterval", element, Locale.getDefault());
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).integerGreaterThanZero();
final Integer feeFrequency = this.fromApiJsonHelper.extractIntegerNamed("feeFrequency", element, Locale.getDefault());
baseDataValidator.reset().parameter("feeFrequency").value(feeFrequency).inMinMaxRange(0, 3);
if (feeFrequency != null) {
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull();
}
final ChargeAppliesTo appliesTo = ChargeAppliesTo.fromInt(chargeAppliesTo);
if (appliesTo.isLoanCharge()) {
// loan applicable validation
@@ -107,7 +117,6 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed("feeOnMonthDay", element);
baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed("feeInterval", element, Locale.getDefault());
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull().inMinMaxRange(1, 12);
}
@@ -216,7 +225,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
if (this.fromApiJsonHelper.parameterExists("feeInterval", element)) {
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed("feeInterval", element, Locale.getDefault());
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull().inMinMaxRange(1, 12);
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists("chargeCalculationType", element)) {
@@ -247,6 +256,10 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final BigDecimal maxCap = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("maxCap", element.getAsJsonObject());
baseDataValidator.reset().parameter("maxCap").value(maxCap).notNull().positiveAmount();
}
if (this.fromApiJsonHelper.parameterExists("feeFrequency", element)) {
final Integer feeFrequency = this.fromApiJsonHelper.extractIntegerNamed("feeFrequency", element, Locale.getDefault());
baseDataValidator.reset().parameter("feeFrequency").value(feeFrequency).inMinMaxRange(0, 3);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -17,7 +17,7 @@ public interface ChargeReadPlatformService {
ChargeData retrieveNewChargeDetails();
Collection<ChargeData> retrieveLoanApplicableCharges(boolean feeChargesOnly);
Collection<ChargeData> retrieveLoanApplicableCharges(boolean feeChargesOnly, Integer[] excludeChargeTimes);
Collection<ChargeData> retrieveLoanApplicablePenalties();
@@ -28,4 +28,6 @@ public interface ChargeReadPlatformService {
Collection<ChargeData> retrieveSavingsAccountApplicablePenalties();
Collection<ChargeData> retrieveSavingsProductCharges(Long savingsProductId);
Collection<ChargeData> retrieveLoanProductCharges(Long loanProductId, Integer chargeTime);
}
@@ -21,6 +21,8 @@ import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformServi
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargeAppliesTo;
import org.mifosplatform.portfolio.charge.exception.ChargeNotFoundException;
import org.mifosplatform.portfolio.common.service.CommonEnumerations;
import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -35,15 +37,18 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
private final PlatformSecurityContext context;
private final CurrencyReadPlatformService currencyReadPlatformService;
private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
private final DropdownReadPlatformService dropdownReadPlatformService;
@Autowired
public ChargeReadPlatformServiceImpl(final PlatformSecurityContext context,
final CurrencyReadPlatformService currencyReadPlatformService,
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource) {
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource,
final DropdownReadPlatformService dropdownReadPlatformService) {
this.context = context;
this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.currencyReadPlatformService = currencyReadPlatformService;
this.dropdownReadPlatformService = dropdownReadPlatformService;
}
@Override
@@ -90,10 +95,11 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
.retrieveSavingsCalculationTypes();
final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCollectionTimeTypes();
final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
return ChargeData.template(currencyOptions, allowedChargeCalculationTypeOptions, allowedChargeAppliesToOptions,
allowedChargeTimeOptions, chargePaymentOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeFrequencyOptions);
}
@Override
@@ -109,19 +115,45 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
}
@Override
public Collection<ChargeData> retrieveLoanApplicableCharges(final boolean feeChargesOnly) {
public Collection<ChargeData> retrieveLoanProductCharges(final Long loanProductId, final Integer chargeTime) {
this.context.authenticatedUser();
final ChargeMapper rm = new ChargeMapper();
String sql = "select " + rm.chargeSchema()
+ " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=? order by c.name ";
final String sql = "select " + rm.loanProductChargeSchema()
+ " where c.is_deleted=0 and c.is_active=1 and plc.product_loan_id=? and c.charge_time_enum=?";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId, chargeTime });
}
@Override
public Collection<ChargeData> retrieveLoanApplicableCharges(final boolean feeChargesOnly, Integer[] excludeChargeTimes) {
this.context.authenticatedUser();
final ChargeMapper rm = new ChargeMapper();
String excludeClause = "";
Object[] params = new Object[] { ChargeAppliesTo.LOAN.getValue() };
if (excludeChargeTimes != null && excludeChargeTimes.length > 0) {
excludeClause = " and c.charge_time_enum not in(?) ";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < excludeChargeTimes.length; i++) {
if (i != 0) {
sb.append(",");
}
sb.append(excludeChargeTimes[i]);
}
params = new Object[] { ChargeAppliesTo.LOAN.getValue(), sb.toString() };
}
String sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=?"
+ excludeClause + " order by c.name ";
if (feeChargesOnly) {
sql = "select " + rm.chargeSchema()
+ " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=0 and c.charge_applies_to_enum=? order by c.name ";
+ " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=0 and c.charge_applies_to_enum=? " + excludeClause
+ "order by c.name ";
}
return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.LOAN.getValue() });
return this.jdbcTemplate.query(sql, rm, params);
}
@Override
@@ -145,8 +177,8 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
+ "c.is_active as active, oc.name as currencyName, oc.decimal_places as currencyDecimalPlaces, "
+ "oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode, c.fee_on_day as feeOnDay, c.fee_on_month as feeOnMonth, "
+ "c.fee_interval as feeInterval,c.min_cap as minCap,c.max_cap as maxCap " + "from m_charge c "
+ "join m_organisation_currency oc on c.currency_code = oc.code";
+ "c.fee_interval as feeInterval, c.fee_frequency as feeFrequency,c.min_cap as minCap,c.max_cap as maxCap "
+ "from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code";
}
public String loanProductChargeSchema() {
@@ -189,6 +221,11 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
final boolean active = rs.getBoolean("active");
final Integer feeInterval = JdbcSupport.getInteger(rs, "feeInterval");
EnumOptionData feeFrequencyType = null;
final Integer feeFrequency = JdbcSupport.getInteger(rs, "feeFrequency");
if (feeFrequency != null) {
feeFrequencyType = CommonEnumerations.termFrequencyType(feeFrequency, "feeFrequency");
}
MonthDay feeOnMonthDay = null;
final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
@@ -199,7 +236,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
final BigDecimal maxCap = rs.getBigDecimal("maxCap");
return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType,
chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap);
chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType);
}
}
@@ -96,6 +96,10 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
if (isChargeExistWithLoans || isChargeExistWithSavings) { throw new ChargeCannotBeUpdatedException(
"error.msg.charge.cannot.be.updated.it.is.used.in.loan", "This charge cannot be updated, it is used in loan"); }
}
}else if((changes.containsKey("feeFrequency") || changes.containsKey("feeInterval")) && chargeForUpdate.isLoanCharge()){
final Boolean isChargeExistWithLoans = isAnyLoanProductsAssociateWithThisCharge(chargeId);
if (isChargeExistWithLoans) { throw new ChargeCannotBeUpdatedException(
"error.msg.charge.frequency.cannot.be.updated.it.is.used.in.loan", "This charge frequency cannot be updated, it is used in loan"); }
}
if (!changes.isEmpty()) {
@@ -3,7 +3,7 @@
* 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.loanproduct.domain;
package org.mifosplatform.portfolio.common.domain;
public enum PeriodFrequencyType {
DAYS(0, "periodFrequencyType.days"), //
@@ -0,0 +1,35 @@
package org.mifosplatform.portfolio.common.service;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
public class CommonEnumerations {
public static EnumOptionData termFrequencyType(final int id, final String codePrefix) {
return termFrequencyType(PeriodFrequencyType.fromInt(id), codePrefix);
}
public static EnumOptionData termFrequencyType(final PeriodFrequencyType type, final String codePrefix) {
EnumOptionData optionData = null;
switch (type) {
case DAYS:
optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(), codePrefix
+ PeriodFrequencyType.DAYS.getCode(), "Days");
break;
case WEEKS:
optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(), codePrefix
+ PeriodFrequencyType.WEEKS.getCode(), "Weeks");
break;
case MONTHS:
optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(), codePrefix
+ PeriodFrequencyType.MONTHS.getCode(), "Months");
break;
default:
optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), PeriodFrequencyType.INVALID.getCode(),
"Invalid");
break;
}
return optionData;
}
}
@@ -0,0 +1,11 @@
package org.mifosplatform.portfolio.common.service;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
public interface DropdownReadPlatformService {
List<EnumOptionData> retrievePeriodFrequencyTypeOptions();
}
@@ -0,0 +1,23 @@
package org.mifosplatform.portfolio.common.service;
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.loanTermFrequencyType;
import java.util.Arrays;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.springframework.stereotype.Service;
@Service
public class DropdownReadPlatformServiceImpl implements DropdownReadPlatformService{
@Override
public List<EnumOptionData> retrievePeriodFrequencyTypeOptions() {
final List<EnumOptionData> loanTermFrequencyOptions = Arrays.asList(loanTermFrequencyType(PeriodFrequencyType.DAYS),
loanTermFrequencyType(PeriodFrequencyType.WEEKS), loanTermFrequencyType(PeriodFrequencyType.MONTHS),
loanTermFrequencyType(PeriodFrequencyType.YEARS));
return loanTermFrequencyOptions;
}
}
@@ -34,6 +34,7 @@ import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSeriali
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
@@ -101,7 +102,7 @@ public class LoanChargesApiResource {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly, new Integer[]{ChargeTimeType.OVERDUE_INSTALLMENT.getValue()});
final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -52,6 +52,7 @@ import org.mifosplatform.portfolio.account.service.PortfolioAccountReadPlatformS
import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.collateral.data.CollateralData;
@@ -411,7 +412,8 @@ public class LoansApiResource {
fundOptions = this.fundReadPlatformService.retrieveAllFunds();
repaymentStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies();
final boolean feeChargesOnly = false;
chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly,
new Integer[] { ChargeTimeType.OVERDUE_INSTALLMENT.getValue() });
chargeTemplate = this.loanChargeReadPlatformService.retrieveLoanChargeTemplate();
allowedLoanOfficers = this.loanReadPlatformService.retrieveAllowedLoanOfficers(loanBasicDetails.officeId(),
@@ -438,11 +440,15 @@ public class LoansApiResource {
}
Collection<ChargeData> overdueCharges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanBasicDetails.loanProductId(),
ChargeTimeType.OVERDUE_INSTALLMENT.getValue());
final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations);
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
overdueCharges);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
@@ -158,6 +158,8 @@ public class LoanAccountData {
private final Boolean inArrears;
private final Collection<ChargeData> overdueCharges;
/**
* Used to produce a {@link LoanAccountData} with only collateral options
* for now.
@@ -242,6 +244,7 @@ public class LoanAccountData {
final Map<Long, LoanBorrowerCycleData> memberVariations = null;
final LoanProductData product = null;
final Boolean inArrears = null;
final Collection<ChargeData> overdueCharges = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType,
@@ -255,7 +258,7 @@ public class LoanAccountData {
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
product, inArrears, graceOnArrearsAgeing);
product, inArrears, graceOnArrearsAgeing, overdueCharges);
}
/**
@@ -342,6 +345,7 @@ public class LoanAccountData {
final Map<Long, LoanBorrowerCycleData> memberVariations = null;
final LoanProductData product = null;
final Boolean inArrears = null;
final Collection<ChargeData> overdueCharges = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -355,7 +359,7 @@ public class LoanAccountData {
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount,
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing);
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -376,7 +380,7 @@ public class LoanAccountData {
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing);
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges);
}
/**
@@ -464,6 +468,7 @@ public class LoanAccountData {
final Map<Long, LoanBorrowerCycleData> memberVariations = null;
final LoanProductData product = null;
final Boolean inArrears = null;
final Collection<ChargeData> overdueCharges = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -477,7 +482,7 @@ public class LoanAccountData {
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing);
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
@@ -497,7 +502,7 @@ public class LoanAccountData {
acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing);
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges);
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
@@ -562,7 +567,9 @@ public class LoanAccountData {
final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
for (final ChargeData charge : product.charges()) {
charges.add(charge.toLoanChargeData());
if (!charge.isOverdueInstallmentCharge()) {
charges.add(charge.toLoanChargeData());
}
}
final Integer loancounter = null;
@@ -617,7 +624,8 @@ public class LoanAccountData {
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, product.getMultiDisburseLoan(), fixedEmi,
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears, product.getGraceOnArrearsAgeing());
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears,
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges());
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -669,7 +677,7 @@ public class LoanAccountData {
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), acc.fixedEmiAmount,
product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears,
product.getGraceOnArrearsAgeing());
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges());
}
/*
@@ -721,6 +729,7 @@ public class LoanAccountData {
final Collection<LoanTermVariationsData> emiAmountVariations = null;
final Map<Long, LoanBorrowerCycleData> memberVariations = null;
final LoanProductData product = null;
final Collection<ChargeData> overdueCharges = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -734,7 +743,7 @@ public class LoanAccountData {
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing);
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges);
}
/*
@@ -754,7 +763,7 @@ public class LoanAccountData {
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
final Collection<LoanTermVariationsData> emiAmountVariations) {
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
@@ -770,7 +779,7 @@ public class LoanAccountData {
loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,
accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing);
acc.graceOnArrearsAgeing, overdueCharges);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
@@ -781,7 +790,7 @@ public class LoanAccountData {
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations);
acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges);
}
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
@@ -800,7 +809,7 @@ public class LoanAccountData {
acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing);
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -856,7 +865,7 @@ public class LoanAccountData {
acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing);
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges);
}
private LoanAccountData(
@@ -909,7 +918,8 @@ public class LoanAccountData {
final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
final Boolean multiDisburseLoan, BigDecimal fixedEmiAmount, final BigDecimal maxOutstandingLoanBalance,
final Collection<LoanTermVariationsData> emiAmountVariations, final Map<Long, LoanBorrowerCycleData> memberVariations,
final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing) {
final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing,
final Collection<ChargeData> overdueCharges) {
this.id = id;
this.accountNo = accountNo;
this.status = status;
@@ -1046,6 +1056,7 @@ public class LoanAccountData {
this.product = product;
this.inArrears = inArrears;
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
this.overdueCharges = overdueCharges;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -1119,4 +1130,8 @@ public class LoanAccountData {
return this.group;
}
public Long loanProductId() {
return this.loanProductId;
}
}
@@ -66,6 +66,7 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedExcep
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.collateral.data.CollateralData;
import org.mifosplatform.portfolio.collateral.domain.LoanCollateral;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
@@ -92,7 +93,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.useradministration.domain.AppUser;
@@ -478,6 +478,8 @@ public class Loan extends AbstractPersistable<Long> {
chargeAmt = loanCharge.getPercentage();
if (loanCharge.isInstalmentFee()) {
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
} else if (loanCharge.isOverdueInstallmentCharge()) {
totalChargeAmt = loanCharge.amountOutstanding();
}
} else {
chargeAmt = loanCharge.amount();
@@ -1940,11 +1942,11 @@ public class Loan extends AbstractPersistable<Long> {
loanVariationTermsData.add(data);
}
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms
.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(),
getExpectedFirstRepaymentOnDate(), calculatedRepaymentsStartingFromDate, getInArrearsTolerance(),
this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData,
this.maxOutstandingLoanBalance,loanVariationTermsData, getInterestChargedFromDate());
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
loanVariationTermsData, getInterestChargedFromDate());
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, charges(),
isHolidayEnabled, holidays, workingDays);
@@ -2430,18 +2432,17 @@ public class Loan extends AbstractPersistable<Long> {
return possibleNextRepaymentDate;
}
public Money possibleNextRepaymentAmount() {
final MonetaryCurrency currency = this.loanRepaymentScheduleDetail.getPrincipal().getCurrency();
Money possibleNextRepaymentAmount = Money.zero(currency);
public LoanRepaymentScheduleInstallment possibleNextRepaymentInstallment() {
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = null;
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
if (installment.isNotFullyPaidOff()) {
possibleNextRepaymentAmount = installment.getTotalOutstanding(currency);
loanRepaymentScheduleInstallment = installment;
break;
}
}
return possibleNextRepaymentAmount;
return loanRepaymentScheduleInstallment;
}
public LoanTransaction deriveDefaultInterestWaiverTransaction() {
@@ -3735,4 +3736,16 @@ public class Loan extends AbstractPersistable<Long> {
return emiAmount;
}
public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {
LoanRepaymentScheduleInstallment installment = null;
if (installmentNumber == null) { return installment; }
for (final LoanRepaymentScheduleInstallment scheduleInstallment : this.repaymentScheduleInstallments) {
if (scheduleInstallment.getInstallmentNumber().equals(installmentNumber)) {
installment = scheduleInstallment;
break;
}
}
return installment;
}
}
@@ -18,9 +18,11 @@ import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@@ -111,11 +113,19 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Column(name = "is_active", nullable = false)
private boolean active = true;
@SuppressWarnings("unused")
@OneToOne(mappedBy = "loancharge", cascade = CascadeType.ALL, optional = true, orphanRemoval = true, fetch = FetchType.LAZY)
private LoanOverdueInstallmentCharge overdueInstallmentCharge;
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command) {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
return createNewFromJson(loan, chargeDefinition, command,dueDate);
}
public static LoanCharge createNewFromJson(final Loan loan, final Charge chargeDefinition, final JsonCommand command,final LocalDate dueDate) {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null;
@@ -123,13 +133,26 @@ public class LoanCharge extends AbstractPersistable<Long> {
BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
switch (ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation())) {
case PERCENT_OF_AMOUNT:
amountPercentageAppliedTo = loan.getPrincpal().getAmount();
if (command.hasParameter("principal")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal");
} else {
amountPercentageAppliedTo = loan.getPrincpal().getAmount();
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
amountPercentageAppliedTo = loan.getPrincpal().getAmount().add(loan.getTotalInterest());
if (command.hasParameter("principal") && command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("principal").add(
command.bigDecimalValueOfParameterNamed("interest"));
} else {
amountPercentageAppliedTo = loan.getPrincpal().getAmount().add(loan.getTotalInterest());
}
break;
case PERCENT_OF_INTEREST:
amountPercentageAppliedTo = loan.getTotalInterest();
if (command.hasParameter("interest")) {
amountPercentageAppliedTo = command.bigDecimalValueOfParameterNamed("interest");
} else {
amountPercentageAppliedTo = loan.getTotalInterest();
}
break;
default:
break;
@@ -474,6 +497,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.INSTALMENT_FEE);
}
public boolean isOverdueInstallmentCharge() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.OVERDUE_INSTALLMENT);
}
private static boolean isGreaterThanZero(final BigDecimal value) {
return value.compareTo(BigDecimal.ZERO) == 1;
}
@@ -792,4 +819,23 @@ public class LoanCharge extends AbstractPersistable<Long> {
public BigDecimal amountOrPercentage() {
return this.amountOrPercentage;
}
public BigDecimal chargeAmount() {
BigDecimal totalChargeAmount = this.amountOutstanding;
if (this.amountPaid != null) {
totalChargeAmount = totalChargeAmount.add(this.amountPaid);
}
if (this.amountWaived != null) {
totalChargeAmount = totalChargeAmount.add(this.amountWaived);
}
if (this.amountWrittenOff != null) {
totalChargeAmount = totalChargeAmount.add(this.amountWrittenOff);
}
return totalChargeAmount;
}
public void updateOverdueInstallmentCharge(LoanOverdueInstallmentCharge overdueInstallmentCharge) {
this.overdueInstallmentCharge = overdueInstallmentCharge;
}
}
@@ -0,0 +1,45 @@
/**
* 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.Column;
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_overdue_installment_charge")
public class LoanOverdueInstallmentCharge extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@ManyToOne(optional = false)
@JoinColumn(name = "loan_charge_id", referencedColumnName = "id", nullable = false)
private LoanCharge loancharge;
@SuppressWarnings("unused")
@ManyToOne(optional = false)
@JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false)
private LoanRepaymentScheduleInstallment installment;
@SuppressWarnings("unused")
@Column(name="frequency_number")
private Integer frequencyNumber;
public LoanOverdueInstallmentCharge(){
}
public LoanOverdueInstallmentCharge(final LoanCharge loanCharge,final LoanRepaymentScheduleInstallment installment, final Integer frequencyNumber){
this.loancharge = loanCharge;
this.installment = installment;
this.frequencyNumber = frequencyNumber;
}
}
@@ -75,7 +75,11 @@ public class LoanRepaymentScheduleProcessingWrapper {
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
}else if (loanCharge.isOverdueInstallmentCharge()
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
@@ -162,6 +166,10 @@ public class LoanRepaymentScheduleProcessingWrapper {
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
}else if (loanCharge.isOverdueInstallmentCharge()
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
@@ -10,15 +10,22 @@ public class OverdueLoanScheduleData {
private final BigDecimal amount;
private final String dateFormat;
private final String dueDate;
private final BigDecimal principalOverdue;
private final BigDecimal interestOverdue;
private final Integer periodNumber;
public OverdueLoanScheduleData(final Long loanId, final Long chargeId, final String dueDate, final BigDecimal amount,
final String dateFormat, final String locale) {
final String dateFormat, final String locale, final BigDecimal principalOverdue, final BigDecimal interestOverdue,
final Integer periodNumber) {
this.loanId = loanId;
this.chargeId = chargeId;
this.dueDate = dueDate;
this.amount = amount;
this.dateFormat = dateFormat;
this.locale = locale;
this.principalOverdue = principalOverdue;
this.interestOverdue = interestOverdue;
this.periodNumber = periodNumber;
}
public Long getLoanId() {
@@ -45,9 +52,16 @@ public class OverdueLoanScheduleData {
return this.locale;
}
public Integer getPeriodNumber() {
return this.periodNumber;
}
@Override
public String toString() {
return "{" + "chargeId:" + this.chargeId + ", locale:'" + this.locale + '\'' + ", amount:" + this.amount + ", dateFormat:'"
+ this.dateFormat + '\'' + ", dueDate:'" + this.dueDate + '\'' + '}';
+ this.dateFormat + '\'' + ", dueDate:'" + this.dueDate + '\'' + ", principal:'" + this.principalOverdue + '\''
+ ", interest:'" + this.interestOverdue + '\'' + '}';
}
}
@@ -73,7 +73,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate();
LocalDate actualRepaymentDate = periodStartDate;
boolean isFirstRepayment = true;
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(periodStartDate, loanApplicationTerms, isFirstRepayment);
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(periodStartDate, loanApplicationTerms,
isFirstRepayment);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);
@@ -91,7 +92,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
}
while (!outstandingBalance.isZero()) {
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment);
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isFirstRepayment);
isFirstRepayment = false;
LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
@@ -298,6 +300,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.isOverdueInstallmentCharge()
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
@@ -344,7 +350,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
}
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
} else if (loanCharge.isOverdueInstallmentCharge()
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
}else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
@@ -7,7 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.springframework.stereotype.Component;
@Component
@@ -8,7 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.LocalDateInterval;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsInOneYearCalculator {
@@ -12,7 +12,7 @@ import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.service.HolidayUtil;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@@ -33,13 +33,15 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
}
@Override
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment) {
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
boolean isFirstRepayment) {
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
LocalDate dueRepaymentPeriodDate = null;
if (isFirstRepayment && firstRepaymentPeriodDate != null) {
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
} else {
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, lastRepaymentDate);
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate);
}
return dueRepaymentPeriodDate;
}
@@ -48,7 +50,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
LocalDate adjustedDate = dueRepaymentPeriodDate;
final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, adjustedDate);
final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), adjustedDate);
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate, workingDays);
if (isHolidayEnabled) {
adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidays);
@@ -56,10 +59,10 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
return adjustedDate;
}
private LocalDate getRepaymentPeriodDate(final LoanApplicationTerms loanApplicationTerms, final LocalDate startDate) {
final int repaidEvery = loanApplicationTerms.getRepaymentEvery();
@Override
public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate) {
LocalDate dueRepaymentPeriodDate = startDate;
switch (loanApplicationTerms.getRepaymentPeriodFrequencyType()) {
switch (frequency) {
case DAYS:
dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
break;
@@ -16,13 +16,13 @@ import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public final class LoanApplicationTerms {
@@ -6,7 +6,7 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import org.joda.time.LocalDate;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
public interface PaymentPeriodsInOneYearCalculator {
@@ -10,7 +10,7 @@ import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
public interface ScheduledDateGenerator {
@@ -24,4 +24,7 @@ public interface ScheduledDateGenerator {
LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled,
List<Holiday> holidays, WorkingDays workingDays);
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
}
@@ -35,6 +35,7 @@ import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchEx
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
@@ -56,7 +57,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
@@ -17,6 +17,7 @@ import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
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.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
@@ -71,6 +72,14 @@ public class LoanChargeAssembler {
locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if (chargeDefinition.isOverdueInstallment()) {
final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge",
defaultUserMessage, null, chargeDefinition.getName());
}
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
@@ -22,4 +22,6 @@ public interface LoanChargeReadPlatformService {
Collection<LoanChargeData> retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus);
Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges);
Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(Long installmentId, Long chargeId);
}
@@ -20,6 +20,7 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeDropdownReadPlatformService;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,13 +34,16 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
private final DropdownReadPlatformService dropdownReadPlatformService;
@Autowired
public LoanChargeReadPlatformServiceImpl(final PlatformSecurityContext context,
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource) {
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource,
final DropdownReadPlatformService dropdownReadPlatformService) {
this.context = context;
this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.dropdownReadPlatformService = dropdownReadPlatformService;
}
private static final class LoanChargeMapper implements RowMapper<LoanChargeData> {
@@ -128,10 +132,11 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
.retrieveSavingsCalculationTypes();
final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCollectionTimeTypes();
final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions);
savingsChargeTimeTypeOptions, feeFrequencyOptions);
}
@Override
@@ -219,4 +224,15 @@ public class LoanChargeReadPlatformServiceImpl implements LoanChargeReadPlatform
return new LoanInstallmentChargeData(installmentNumber, dueAsOfDate, amount, amountOutstanding, paid, waived);
}
}
@Override
public Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(final Long installmentId,final Long chargeId) {
String sql = "select oic.frequency_number from m_loan_overdue_installment_charge oic inner join m_loan_charge lc on lc.id=oic.loan_charge_id where oic.loan_schedule_id = ? and lc.is_active = 1";
Object[] params = {installmentId};
if(chargeId != null){
sql += " and lc.charge_id = ? ";
params = new Object[]{installmentId,chargeId};
}
return this.jdbcTemplate.queryForList(sql, Integer.class, params);
}
}
@@ -66,7 +66,7 @@ public interface LoanReadPlatformService {
* result overdue installments with this charge already applied are not
* returned.
*/
Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallmentsNotAlreadyPenalized(final Long penaltyWaitPeriod);
Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallments(final Long penaltyWaitPeriod);
Integer retriveLoanCounter(Long groupId, Integer loanType, Long productId);
@@ -39,6 +39,7 @@ import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.domain.ClientEnumerations;
@@ -59,6 +60,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -337,12 +339,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate earliestUnpaidInstallmentDate = loan.possibleNextRepaymentDate();
final Money possibleNextRepaymentAmount = loan.possibleNextRepaymentAmount();
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.possibleNextRepaymentInstallment();
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
possibleNextRepaymentAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null, null);
loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment
.getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency)
.getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, paymentOptions, null, null, null);
}
@Override
@@ -740,8 +745,23 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final String dueDate = rs.getString("dueDate");
final String locale = "en_GB";
final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue");
final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
final BigDecimal interestExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestPaid");
final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
final BigDecimal interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff);
final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid);
final Integer installmentNumber = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "period");
final OverdueLoanScheduleData overdueLoanScheduleData = new OverdueLoanScheduleData(loanId, chargeId, dueDate, amount,
dateFormat, locale);
dateFormat, locale, principalOutstanding, interestOutstanding, installmentNumber);
return overdueLoanScheduleData;
}
@@ -1115,7 +1135,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Collection<CodeValueData> loanCollateralOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode("LoanCollateral");
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly,
new Integer[] { ChargeTimeType.OVERDUE_INSTALLMENT.getValue() });
Integer loanCycleCounter = null;
if (loanProduct.useBorrowerCycle()) {
if (clientId == null) {
@@ -1201,14 +1222,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallmentsNotAlreadyPenalized(final Long penaltyWaitPeriod) {
public Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallments(final Long penaltyWaitPeriod) {
final MusoniOverdueLoanScheduleMapper rm = new MusoniOverdueLoanScheduleMapper();
final String sql = "select "
+ rm.schema()
+ " where DATE_SUB(CURDATE(),INTERVAL ? DAY) > ls.duedate "
final String sql = "select " + rm.schema() + " where DATE_SUB(CURDATE(),INTERVAL ? DAY) > ls.duedate "
+ " and ls.completed_derived <> 1 and mc.charge_applies_to_enum =1 "
+ " and mc.charge_time_enum = 9 and ml.loan_status_id = 300 "
+ " and plc.charge_id not in (SELECT charge_id from m_loan_charge as mlc where mlc.loan_id = ml.id and mlc.charge_id = plc.charge_id and mlc.is_active = 1 and mlc.due_for_collection_as_of_date > fromdate AND mlc.due_for_collection_as_of_date <= duedate) ";
+ " and mc.charge_time_enum = 9 and ml.loan_status_id = 300 ";
return this.jdbcTemplate.query(sql, rm, new Object[] { penaltyWaitPeriod });
}
@@ -61,6 +61,7 @@ import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
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.LoanChargeCannotBeAddedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBePayedException;
@@ -76,6 +77,7 @@ import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDi
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand;
import org.mifosplatform.portfolio.collectionsheet.command.SingleRepaymentCommand;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
@@ -90,6 +92,8 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanChargeRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -100,7 +104,9 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentEx
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
@@ -151,7 +157,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final AccountTransfersWritePlatformService accountTransfersWritePlatformService;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
private final LoanChargeReadPlatformService LoanChargeReadPlatformService;
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
private final LoanReadPlatformService loanReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
private final AccountTransferRepository accountTransferRepository;
@@ -196,7 +202,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService = accountTransfersWritePlatformService;
this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
this.LoanChargeReadPlatformService = loanChargeReadPlatformService;
this.loanChargeReadPlatformService = loanChargeReadPlatformService;
this.loanReadPlatformService = loanReadPlatformService;
this.fromApiJsonHelper = fromApiJsonHelper;
this.accountTransferRepository = accountTransferRepository;
@@ -971,6 +977,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
if (chargeDefinition.isOverdueInstallment()) {
final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null, chargeDefinition.getName());
}
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
if (portfolioAccountData == null) {
@@ -1226,13 +1238,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Override
@CronTarget(jobName = JobName.TRANSFER_FEE_CHARGE_FOR_LOANS)
public void transferFeeCharges() throws JobExecutionException {
final Collection<LoanChargeData> chargeDatas = this.LoanChargeReadPlatformService.retrieveLoanChargesForFeePayment(
final Collection<LoanChargeData> chargeDatas = this.loanChargeReadPlatformService.retrieveLoanChargesForFeePayment(
ChargePaymentMode.ACCOUNT_TRANSFER.getValue(), LoanStatus.ACTIVE.getValue());
final StringBuilder sb = new StringBuilder();
if (chargeDatas != null) {
for (final LoanChargeData chargeData : chargeDatas) {
if (chargeData.isInstallmentFee()) {
final Collection<LoanInstallmentChargeData> chargePerInstallments = this.LoanChargeReadPlatformService
final Collection<LoanInstallmentChargeData> chargePerInstallments = this.loanChargeReadPlatformService
.retrieveInstallmentLoanCharges(chargeData.getId(), true);
PortfolioAccountData portfolioAccountData = null;
for (final LoanInstallmentChargeData installmentChargeData : chargePerInstallments) {
@@ -1734,7 +1746,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Long penaltyWaitPeriodValue = this.configurationDomainService.retrievePenaltyWaitPeriod();
final Collection<OverdueLoanScheduleData> overdueLoanScheduledInstallments = this.loanReadPlatformService
.retrieveAllLoansWithOverdueInstallmentsNotAlreadyPenalized(penaltyWaitPeriodValue);
.retrieveAllLoansWithOverdueInstallments(penaltyWaitPeriodValue);
if (!overdueLoanScheduledInstallments.isEmpty()) {
for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) {
@@ -1745,7 +1757,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final JsonCommand command = JsonCommand.from(overdueInstallment.toString(), parsedCommand, this.fromApiJsonHelper,
null, null, null, null, null, overdueInstallment.getLoanId(), null, null, null, null, null, null, null);
applyChargeToOverdueLoanInstallment(overdueInstallment.getLoanId(), overdueInstallment.getChargeId(), command);
applyChargeToOverdueLoanInstallment(overdueInstallment.getLoanId(), overdueInstallment.getChargeId(),
overdueInstallment.getPeriodNumber(), command);
} catch (final PlatformApiDataValidationException e) {
final List<ApiParameterError> errors = e.getErrors();
@@ -1759,50 +1772,90 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
@Transactional
public void applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final JsonCommand command) {
public void applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber,
final JsonCommand command) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(loanChargeId);
final LoanRepaymentScheduleInstallment installment = loan.fetchRepaymentScheduleInstallment(periodNumber);
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command);
Collection<Integer> frequencyNumbers = loanChargeReadPlatformService.retrieveOverdueInstallmentChargeFrequencyNumber(
installment.getId(), chargeDefinition.getId());
if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
Integer feeFrequency = chargeDefinition.feeFrequency();
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
Map<Integer, LocalDate> scheduleDates = new HashMap<Integer, LocalDate>();
final Long penaltyWaitPeriodValue = this.configurationDomainService.retrievePenaltyWaitPeriod();
final Long penaltyPostingWaitPeriodValue = this.configurationDomainService.retrieveGraceOnPenaltyPostingPeriod();
final LocalDate dueDate = command.localDateValueOfParameterNamed("dueDate");
Long diff = penaltyWaitPeriodValue + 1 - penaltyPostingWaitPeriodValue;
if (diff < 0) {
diff = 0L;
}
LocalDate startDate = dueDate.plusDays(penaltyWaitPeriodValue.intValue() + 1);
Integer frequencyNunber = 1;
if (feeFrequency == null) {
scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
} else {
while (new LocalDate().isAfter(startDate)) {
scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency),
chargeDefinition.feeInterval(), startDate);
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService.retriveLoanAssociation(loanId);
if (portfolioAccountData == null) {
final String errorMessage = loanCharge.name() + "Charge requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.add", errorMessage, loanCharge.name());
startDate = scheduleDate;
}
}
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
this.loanChargeRepository.save(loanCharge);
for (Integer frequency : frequencyNumbers) {
scheduleDates.remove(frequency);
}
final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge, existingTransactionIds,
existingReversedTransactionIds);
for (Map.Entry<Integer, LocalDate> entry : scheduleDates.entrySet()) {
// we want to apply charge transactions only for those loans charges
// that are applied when a loan is active
if (loan.status().isActive()) {
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
this.loanTransactionRepository.save(applyLoanChargeTransaction);
this.loanRepository.saveAndFlush(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue());
if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
.retriveLoanAssociation(loanId);
if (portfolioAccountData == null) {
final String errorMessage = loanCharge.name() + "Charge requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge.add", errorMessage, loanCharge.name());
}
}
// we post Journal entries only for loans in active status
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
LoanOverdueInstallmentCharge overdueInstallmentCharge = new LoanOverdueInstallmentCharge(loanCharge, installment,
entry.getKey());
loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge);
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
this.loanChargeRepository.save(loanCharge);
final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge, existingTransactionIds,
existingReversedTransactionIds);
// we want to apply charge transactions only for those loans charges
// that are applied when a loan is active
if (loan.status().isActive()) {
final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null);
this.loanTransactionRepository.save(applyLoanChargeTransaction);
this.loanRepository.saveAndFlush(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
// we post Journal entries only for loans in active status
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
}
}
@@ -231,7 +231,7 @@ public class LoanProductsApiResource {
private LoanProductData handleTemplate(final LoanProductData productData) {
final boolean feeChargesOnly = true;
Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly, null);
if (chargeOptions.isEmpty()) {
chargeOptions = null;
}
@@ -21,11 +21,11 @@ import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.fund.data.FundData;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
/**
@@ -659,4 +659,15 @@ public class LoanProductData {
public Integer getGraceOnArrearsAgeing() {
return this.graceOnArrearsAgeing;
}
public Collection<ChargeData> overdueFeeCharges(){
Collection<ChargeData> overdueFeeCharges = new ArrayList<ChargeData>();
Collection<ChargeData> charges = charges();
for(ChargeData chargeData : charges){
if(chargeData.isOverdueInstallmentCharge()){
overdueFeeCharges.add(chargeData);
}
}
return overdueFeeCharges;
}
}
@@ -38,6 +38,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
@@ -5,6 +5,8 @@
*/
package org.mifosplatform.portfolio.loanproduct.domain;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
/**
* Represents the bare minimum repayment details needed for activities related
* to generating repayment schedules.
@@ -23,6 +23,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
@@ -19,6 +19,7 @@ import java.util.Collection;
import java.util.List;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
@@ -26,7 +27,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodM
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanproduct.service;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.accountdetails.service.AccountEnumerations;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
@@ -17,7 +18,6 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodM
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
public class LoanEnumerations {
@@ -65,8 +65,8 @@ import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType;
@@ -23,6 +23,7 @@ import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
import org.mifosplatform.portfolio.charge.service.ChargeDropdownReadPlatformService;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
@@ -38,17 +39,20 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
private final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService;
private final DropdownReadPlatformService dropdownReadPlatformService;
// mappers
private final SavingsAccountChargeDueMapper chargeDueMapper;
@Autowired
public SavingsAccountChargeReadPlatformServiceImpl(final PlatformSecurityContext context,
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource) {
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource,
final DropdownReadPlatformService dropdownReadPlatformService) {
this.context = context;
this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.chargeDueMapper = new SavingsAccountChargeDueMapper();
this.dropdownReadPlatformService = dropdownReadPlatformService;
}
private static final class SavingsAccountChargeMapper implements RowMapper<SavingsAccountChargeData> {
@@ -138,10 +142,12 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
final List<EnumOptionData> savingsChargeTimeTypeOptions = this.chargeDropdownReadPlatformService
.retrieveSavingsCollectionTimeTypes();
final List<EnumOptionData> feeFrequencyOptions = this.dropdownReadPlatformService.retrievePeriodFrequencyTypeOptions();
// TODO AA : revisit for merge conflict - Not sure method signature
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions);
savingsChargeTimeTypeOptions, feeFrequencyOptions);
}
@Override
@@ -3,12 +3,12 @@ package org.mifosplatform.portfolio.loanaccount;
import java.math.BigDecimal;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
/**
* This class is used to keep in one place configurations for setting up