Merge pull request #628 from ashok-conflux/MIFOSX-728

Add recurring fee support for savings
This commit is contained in:
Vishwas Babu A J
2013-10-09 06:27:43 -07:00
20 changed files with 408 additions and 98 deletions
+92 -2
View File
@@ -11035,7 +11035,8 @@ Request Body:
2 = Specified Due Date : Adhoc charge can be applied for loans and savings on a specified due date.<br/> 2 = Specified Due Date : Adhoc charge can be applied for loans and savings on a specified due date.<br/>
3 = Savings Activation : This charge is applicable for savings account and charged at the time of account activation. This charge will be autodeducted once the account is activated with sufficient opening balance.<br/> 3 = Savings Activation : This charge is applicable for savings account and charged at the time of account activation. This charge will be autodeducted once the account is activated with sufficient opening balance.<br/>
5 = Withdrawal fee : Charge applied to every withdrawal of savings account.<br/> 5 = Withdrawal fee : Charge applied to every withdrawal of savings account.<br/>
6 = Annual Fee : Recurring charge applied annually for savings on a specified Month and day. 6 = Annual Fee : Recurring charge applied annually for savings on a specified Month and day.<br/>
7 = Monthly Fee : Recurring charge applied on regular interval of months for savings.
</td> </td>
</tr> </tr>
<tr class=alt> <tr class=alt>
@@ -11052,6 +11053,22 @@ Request Body:
<td class=fielddesc>Rules for calculating the amount for a particular Charge. <td class=fielddesc>Rules for calculating the amount for a particular Charge.
Refer <a href="#charges_template">Charges Template</a> for additional details</td> Refer <a href="#charges_template">Charges Template</a> for additional details</td>
</tr> </tr>
<tr class=alt>
<td>feeOnMonthDay</td>
</tr>
<tr>
<td class=fielddesc>Used along with <i>monthDayFormat</i> to indicate the recurring charge due date starting on a given day of the month and follow specified <i>feeInterval</i>. e.g. fee due date starting on <b>10 May</b> with <i>feeInterval</i> of two (2) months then a recurring fee on 10th of every two months is applied to savings account. <i>monthDayFormat</i> indicates day and month formatting used e.g. "dd MMM" for 10 May <br/>
This field is mandatory if <i>chargeTimeType</i> is of type <b>Monthly Fee</b>
</td>
</tr>
<tr class=alt>
<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>
</tr>
</table> </table>
</div> </div>
</div> </div>
@@ -11144,6 +11161,11 @@ GET https://DomainName/api/v1/charges/template
"id": 6, "id": 6,
"code": "chargeTimeType.annualFee", "code": "chargeTimeType.annualFee",
"value": "Annual Fee" "value": "Annual Fee"
},
{
"id": 7,
"code": "chargeTimeType.monthlyFee",
"value": "Monthly Fee"
} }
], ],
"chargePaymetModeOptions": "chargePaymetModeOptions":
@@ -11201,6 +11223,10 @@ GET https://DomainName/api/v1/charges/template
{ "code" : "chargeTimeType.annualFee", { "code" : "chargeTimeType.annualFee",
"id" : 6, "id" : 6,
"value" : "Annual Fee" "value" : "Annual Fee"
},
{ "code" : "chargeTimeType.monthlyFee",
"id" : 7,
"value" : "Monthly Fee"
} }
] ]
} }
@@ -11283,7 +11309,31 @@ Request Body:
{ {
"resourceId": 5 "resourceId": 5
} }
</code> </code>
<code class="method-request">
POST charges
Content-Type: application/json
Request Body:
{
"locale": "en",
"name": "Quarterly Fee",
"amount": "5",
"currencyCode": "USD",
"chargeAppliesTo": "2",
"chargeTimeType": "7",
"feeOnMonthDay": "10 May",
"monthDayFormat": "dd MMM",
"feeInterval": "4",
"chargeCalculationType": "1",
"active": "true",
"penalty": "false"
}
</code>
<code class="method-response">
{
"resourceId": 6
}
</code>
</div> </div>
</div> </div>
@@ -13242,6 +13292,46 @@ Content-Type: application/json
"code": "chargepaymentmode.regular", "code": "chargepaymentmode.regular",
"value": "Regular" "value": "Regular"
} }
},
{
"id": 7,
"name": "Quarterly fee",
"active": true,
"penalty": false,
"currency": {
"code": "USD",
"name": "US Dollar",
"decimalPlaces": 2,
"displaySymbol": "$",
"nameCode": "currency.USD",
"displayLabel": "US Dollar ($)"
},
"amount": 5,
"chargeTimeType": {
"id": 7,
"code": "chargeTimeType.monthlyFee",
"value": "Monthly Fee"
},
"chargeAppliesTo": {
"id": 2,
"code": "chargeAppliesTo.savings",
"value": "Savings"
},
"chargeCalculationType": {
"id": 1,
"code": "chargeCalculationType.flat",
"value": "Flat"
},
"chargePaymentMode": {
"id": 0,
"code": "chargepaymentmode.regular",
"value": "Regular"
},
"feeOnMonthDay": [
10,
5
],
"feeInterval": 4
} }
] ]
} }
@@ -0,0 +1,8 @@
ALTER TABLE `m_charge`
ADD COLUMN `fee_on_day` SMALLINT(5) NULL AFTER `amount`,
ADD COLUMN `fee_interval` SMALLINT(5) NULL AFTER `fee_on_day`,
ADD COLUMN `fee_on_month` SMALLINT(5) NULL AFTER `fee_interval`;
ALTER TABLE `m_savings_account_charge`
ADD COLUMN `fee_interval` SMALLINT(5) NULL DEFAULT NULL AFTER `fee_on_day`;
@@ -299,7 +299,7 @@ public class JsonParserHelper {
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.month.day", final ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.month.day",
"The parameter " + parameterName + " is invalid based on the dateFormat: '" + dateFormat "The parameter " + parameterName + " is invalid based on the monthDayFormat: '" + dateFormat
+ "' and locale: '" + clientApplicationLocale + "' provided:", parameterName, valueAsString, + "' and locale: '" + clientApplicationLocale + "' provided:", parameterName, valueAsString,
dateFormat); dateFormat);
dataValidationErrors.add(error); dataValidationErrors.add(error);
@@ -32,6 +32,8 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final EnumOptionData chargeAppliesTo; private final EnumOptionData chargeAppliesTo;
private final EnumOptionData chargeCalculationType; private final EnumOptionData chargeCalculationType;
private final EnumOptionData chargePaymentMode; private final EnumOptionData chargePaymentMode;
private final MonthDay feeOnMonthDay;
private final Integer feeInterval;
private final Collection<CurrencyData> currencyOptions; private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> chargeCalculationTypeOptions;// private final List<EnumOptionData> chargeCalculationTypeOptions;//
@@ -52,7 +54,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions, return new ChargeData(null, null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions,
chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions,
loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, null, null);
} }
public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) { public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) {
@@ -60,12 +62,12 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, template.currencyOptions, charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, template.currencyOptions,
template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions, template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions,
template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions, template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions, template.loanChargeTimeTypeOptions,
template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions); template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions, charge.feeOnMonthDay, charge.feeInterval);
} }
public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, 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 chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active) { final EnumOptionData chargePaymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final boolean penalty, final boolean active) {
final Collection<CurrencyData> currencyOptions = null; final Collection<CurrencyData> currencyOptions = null;
final List<EnumOptionData> chargeCalculationTypeOptions = null; final List<EnumOptionData> chargeCalculationTypeOptions = null;
@@ -80,7 +82,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval);
} }
public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) { public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) {
@@ -90,6 +92,8 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final EnumOptionData chargeAppliesTo = null; final EnumOptionData chargeAppliesTo = null;
final EnumOptionData chargeCalculationType = null; final EnumOptionData chargeCalculationType = null;
final EnumOptionData chargePaymentMode = null; final EnumOptionData chargePaymentMode = null;
final MonthDay feeOnMonthDay = null;
final Integer feeInterval = null;
final Boolean penalty = isPenalty; final Boolean penalty = isPenalty;
final Boolean active = false; final Boolean active = false;
final Collection<CurrencyData> currencyOptions = null; final Collection<CurrencyData> currencyOptions = null;
@@ -105,7 +109,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode, return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions); savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval);
} }
private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, private ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
@@ -115,7 +119,7 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions, final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions,
final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions, final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
final List<EnumOptionData> loansChargeTimeTypeOptions, final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> loansChargeTimeTypeOptions, final List<EnumOptionData> savingsChargeCalculationTypeOptions,
final List<EnumOptionData> savingsChargeTimeTypeOptions) { final List<EnumOptionData> savingsChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.amount = amount; this.amount = amount;
@@ -124,6 +128,8 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
this.chargeAppliesTo = chargeAppliesTo; this.chargeAppliesTo = chargeAppliesTo;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.chargePaymentMode = chargePaymentMode; this.chargePaymentMode = chargePaymentMode;
this.feeInterval = feeInterval;
this.feeOnMonthDay = feeOnMonthDay;
this.penalty = penalty; this.penalty = penalty;
this.active = active; this.active = active;
this.currencyOptions = currencyOptions; this.currencyOptions = currencyOptions;
@@ -178,10 +184,9 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO; final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
final Collection<ChargeData> chargeOptions = null; final Collection<ChargeData> chargeOptions = null;
final LocalDate dueAsOfDate = null; final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null;
return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount, amountPaid, amountWaived, return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage, amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage,
amountPercentageAppliedTo, chargeOptions, this.penalty, feeOnMonthDay); amountPercentageAppliedTo, chargeOptions, this.penalty, this.feeOnMonthDay, this.feeInterval);
} }
} }
@@ -18,6 +18,7 @@ import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.HashCodeBuilder;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
@@ -56,6 +57,15 @@ public class Charge extends AbstractPersistable<Long> {
@Column(name = "charge_payment_mode_enum") @Column(name = "charge_payment_mode_enum")
private Integer chargePaymentMode; private Integer chargePaymentMode;
@Column(name = "fee_on_day", nullable = true)
private Integer feeOnDay;
@Column(name = "fee_interval", nullable = true)
private Integer feeInterval;
@Column(name = "fee_on_month", nullable = true)
private Integer feeOnMonth;
@Column(name = "is_penalty", nullable = false) @Column(name = "is_penalty", nullable = false)
private boolean penalty; private boolean penalty;
@@ -79,8 +89,11 @@ public class Charge extends AbstractPersistable<Long> {
final boolean penalty = command.booleanPrimitiveValueOfParameterNamed("penalty"); final boolean penalty = command.booleanPrimitiveValueOfParameterNamed("penalty");
final boolean active = command.booleanPrimitiveValueOfParameterNamed("active"); final boolean active = command.booleanPrimitiveValueOfParameterNamed("active");
final MonthDay feeOnMonthDay = command.extractMonthDayNamed("feeOnMonthDay");
final Integer feeInterval = command.integerValueOfParameterNamed("feeInterval");
return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode); return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode,
feeOnMonthDay, feeInterval);
} }
protected Charge() { protected Charge() {
@@ -89,7 +102,7 @@ public class Charge extends AbstractPersistable<Long> {
private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo, private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty,
final boolean active, final ChargePaymentMode paymentMode) { final boolean active, final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval) {
this.name = name; this.name = name;
this.amount = amount; this.amount = amount;
this.currencyCode = currencyCode; this.currencyCode = currencyCode;
@@ -103,6 +116,12 @@ public class Charge extends AbstractPersistable<Long> {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges"); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
if (isMonthlyFee()) {
this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
this.feeOnDay = feeOnMonthDay.getDayOfMonth();
this.feeInterval = feeInterval;
}
if (isSavingsCharge()) { if (isSavingsCharge()) {
if (!isAllowedSavingsChargeTime()) { if (!isAllowedSavingsChargeTime()) {
@@ -129,7 +148,7 @@ public class Charge extends AbstractPersistable<Long> {
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan"); .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
} }
} }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
@@ -246,13 +265,20 @@ public class Charge extends AbstractPersistable<Long> {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime) baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings"); .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
} }
//if charge time is changed to monthly then validate for feeOnMonthDay and feeInterval
if(isMonthlyFee()){
final MonthDay monthDay = command.extractMonthDayNamed("feeOnMonthDay");
baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
final Integer feeInterval = command.integerValueOfParameterNamed("feeInterval");
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull().inMinMaxRange(1, 12);
}
} else if (isLoanCharge()) { } else if (isLoanCharge()) {
if (!isAllowedLoanChargeTime()) { if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime) baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan"); .failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
} }
} }
} }
final String chargeAppliesToParamName = "chargeAppliesTo"; final String chargeAppliesToParamName = "chargeAppliesTo";
@@ -297,7 +323,33 @@ public class Charge extends AbstractPersistable<Long> {
actualChanges.put("locale", localeAsInput); actualChanges.put("locale", localeAsInput);
this.chargePaymentMode = ChargePaymentMode.fromInt(newValue).getValue(); this.chargePaymentMode = ChargePaymentMode.fromInt(newValue).getValue();
} }
if (command.hasParameter("feeOnMonthDay")) {
final MonthDay monthDay = command.extractMonthDayNamed("feeOnMonthDay");
final String actualValueEntered = command.stringValueOfParameterNamed("feeOnMonthDay");
final Integer dayOfMonthValue = monthDay.getDayOfMonth();
if (this.feeOnDay != dayOfMonthValue) {
actualChanges.put("feeOnMonthDay", actualValueEntered);
actualChanges.put("locale", localeAsInput);
this.feeOnDay = dayOfMonthValue;
}
final Integer monthOfYear = monthDay.getMonthOfYear();
if (this.feeOnMonth != monthOfYear) {
actualChanges.put("feeOnMonthDay", actualValueEntered);
actualChanges.put("locale", localeAsInput);
this.feeOnMonth = monthOfYear;
}
}
final String feeInterval = "feeInterval";
if (command.isChangeInIntegerParameterNamed(feeInterval, this.feeInterval)) {
final Integer newValue = command.integerValueOfParameterNamed(feeInterval);
actualChanges.put(feeInterval, newValue);
actualChanges.put("locale", localeAsInput);
this.feeInterval = newValue;
}
final String penaltyParamName = "penalty"; final String penaltyParamName = "penalty";
if (command.isChangeInBooleanParameterNamed(penaltyParamName, this.penalty)) { if (command.isChangeInBooleanParameterNamed(penaltyParamName, this.penalty)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(penaltyParamName); final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(penaltyParamName);
@@ -339,10 +391,26 @@ public class Charge extends AbstractPersistable<Long> {
final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode); final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null); final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType,
chargePaymentmode, this.penalty, this.active); chargePaymentmode, this.getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active);
} }
public Integer getChargePaymentMode() { public Integer getChargePaymentMode() {
return this.chargePaymentMode; return this.chargePaymentMode;
} }
public boolean isMonthlyFee() {
return ChargeTimeType.fromInt(this.chargeTime).isMonthlyFee();
}
public MonthDay getFeeOnMonthDay(){
MonthDay feeOnMonthDay = null;
if (feeOnDay != null && feeOnMonth != null) {
feeOnMonthDay = new MonthDay(feeOnMonth, feeOnDay);
}
return feeOnMonthDay;
}
public Integer feeInterval(){
return this.feeInterval;
}
} }
@@ -13,7 +13,8 @@ public enum ChargeTimeType {
SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for savings SAVINGS_ACTIVATION(3, "chargeTimeType.savingsActivation"), // only for savings
SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings SAVINGS_CLOSURE(4, "chargeTimeType.savingsClosure"), // only for savings
WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings WITHDRAWAL_FEE(5, "chargeTimeType.withdrawalFee"), // only for savings
ANNUAL_FEE(6, "chargeTimeType.annualFee"); // only for savings ANNUAL_FEE(6, "chargeTimeType.annualFee"), // only for savings
MONTHLY_FEE(7, "chargeTimeType.monthlyFee"); // only for savings
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -64,6 +65,9 @@ public enum ChargeTimeType {
case 6: case 6:
chargeTimeType = ANNUAL_FEE; chargeTimeType = ANNUAL_FEE;
break; break;
case 7:
chargeTimeType = MONTHLY_FEE;
break;
default: default:
chargeTimeType = INVALID; chargeTimeType = INVALID;
break; break;
@@ -95,11 +99,16 @@ public enum ChargeTimeType {
return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue()); return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue());
} }
public boolean isMonthlyFee() {
return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue());
}
public boolean isAllowedLoanChargeTime() { public boolean isAllowedLoanChargeTime() {
return isTimeOfDisbursement() || isOnSpecifiedDueDate(); return isTimeOfDisbursement() || isOnSpecifiedDueDate();
} }
public boolean isAllowedSavingsChargeTime() { public boolean isAllowedSavingsChargeTime() {
return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee(); return isOnSpecifiedDueDate() || isSavingsActivation() || isSavingsClosure() || isWithdrawalFee() || isAnnualFee()
|| isMonthlyFee();
} }
} }
@@ -16,6 +16,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
@@ -37,7 +38,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
*/ */
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("name", "amount", "locale", "currencyCode", private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("name", "amount", "locale", "currencyCode",
"currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty", "currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty",
"active", "chargePaymentMode")); "active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat"));
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
@@ -90,6 +91,15 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final Boolean active = this.fromApiJsonHelper.extractBooleanNamed("active", element); final Boolean active = this.fromApiJsonHelper.extractBooleanNamed("active", element);
baseDataValidator.reset().parameter("active").value(active).notNull(); baseDataValidator.reset().parameter("active").value(active).notNull();
} }
boolean isMonthlyFee = ChargeTimeType.fromInt(chargeTimeType).isMonthlyFee();
if(isMonthlyFee){
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);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -130,6 +140,16 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull() baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull()
.inMinMaxRange(ChargeTimeType.minValue(), ChargeTimeType.maxValue()); .inMinMaxRange(ChargeTimeType.minValue(), ChargeTimeType.maxValue());
} }
if (this.fromApiJsonHelper.parameterExists("feeOnMonthDay", element)) {
final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed("feeOnMonthDay", element);
baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).notNull();
}
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);
}
if (this.fromApiJsonHelper.parameterExists("chargeCalculationType", element)) { if (this.fromApiJsonHelper.parameterExists("chargeCalculationType", element)) {
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element, final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element,
@@ -83,6 +83,6 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE), chargeTimeType(ChargeTimeType.SAVINGS_ACTIVATION), return Arrays.asList(chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE), chargeTimeType(ChargeTimeType.SAVINGS_ACTIVATION),
//chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE), //chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE),
chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE), chargeTimeType(ChargeTimeType.WITHDRAWAL_FEE),
chargeTimeType(ChargeTimeType.ANNUAL_FEE)); chargeTimeType(ChargeTimeType.ANNUAL_FEE), chargeTimeType(ChargeTimeType.MONTHLY_FEE));
} }
} }
@@ -40,6 +40,9 @@ public class ChargeEnumerations {
case ANNUAL_FEE: case ANNUAL_FEE:
optionData = new EnumOptionData(ChargeTimeType.ANNUAL_FEE.getValue().longValue(), ChargeTimeType.ANNUAL_FEE.getCode(), "Annual Fee"); optionData = new EnumOptionData(ChargeTimeType.ANNUAL_FEE.getValue().longValue(), ChargeTimeType.ANNUAL_FEE.getCode(), "Annual Fee");
break; break;
case MONTHLY_FEE:
optionData = new EnumOptionData(ChargeTimeType.MONTHLY_FEE.getValue().longValue(), ChargeTimeType.MONTHLY_FEE.getCode(), "Monthly Fee");
break;
default: default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid"); optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break; break;
@@ -11,6 +11,7 @@ import java.sql.SQLException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource; import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
@@ -140,9 +141,11 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
return "c.id as id, c.name as name, c.amount as amount, c.currency_code as currencyCode, " return "c.id as id, c.name as name, c.amount as amount, c.currency_code as currencyCode, "
+ "c.charge_applies_to_enum as chargeAppliesTo, c.charge_time_enum as chargeTime, " + "c.charge_applies_to_enum as chargeAppliesTo, c.charge_time_enum as chargeTime, "
+ "c.charge_payment_mode_enum as chargePaymentMode, " + "c.charge_payment_mode_enum as chargePaymentMode, "
+ "c.charge_calculation_enum as chargeCalculation, c.is_penalty as penalty, c.is_active as active, oc.name as currencyName, " + "c.charge_calculation_enum as chargeCalculation, c.is_penalty as penalty, "
+ "oc.decimal_places as currencyDecimalPlaces,oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "c.is_active as active, oc.name as currencyName, oc.decimal_places as currencyDecimalPlaces, "
+ "oc.internationalized_name_code as currencyNameCode from m_charge c " + "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 from m_charge c "
+ "join m_organisation_currency oc on c.currency_code = oc.code"; + "join m_organisation_currency oc on c.currency_code = oc.code";
} }
@@ -185,8 +188,16 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
final boolean penalty = rs.getBoolean("penalty"); final boolean penalty = rs.getBoolean("penalty");
final boolean active = rs.getBoolean("active"); final boolean active = rs.getBoolean("active");
final Integer feeInterval = JdbcSupport.getInteger(rs, "feeInterval");
MonthDay feeOnMonthDay = null;
final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
if (feeOnDay != null) {
feeOnMonthDay = new MonthDay(feeOnMonth, feeOnDay);
}
return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType,
chargePaymentMode, penalty, active); chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active);
} }
} }
@@ -83,6 +83,7 @@ public class SavingsApiConstants {
public static final String withdrawalFeeForTransfersParamName = "withdrawalFeeForTransfers"; public static final String withdrawalFeeForTransfersParamName = "withdrawalFeeForTransfers";
public static final String feeAmountParamName = "feeAmount";//to be deleted public static final String feeAmountParamName = "feeAmount";//to be deleted
public static final String feeOnMonthDayParamName = "feeOnMonthDay"; public static final String feeOnMonthDayParamName = "feeOnMonthDay";
public static final String feeIntervalParamName = "feeInterval";
public static final String accountingRuleParamName = "accountingRule"; public static final String accountingRuleParamName = "accountingRule";
public static final String paymentTypeIdParamName = "paymentTypeId"; public static final String paymentTypeIdParamName = "paymentTypeId";
public static final String transactionAccountNumberParamName = "accountNumber"; public static final String transactionAccountNumberParamName = "accountNumber";
@@ -203,7 +204,7 @@ public class SavingsApiConstants {
amountPaidParamName, chargeOptionsParamName)); amountPaidParamName, chargeOptionsParamName));
public static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList( public static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName)); chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName, feeIntervalParamName));
public static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList( public static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName)); amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
@@ -40,6 +40,9 @@ public class SavingsAccountChargeData {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final MonthDay feeOnMonthDay; private final MonthDay feeOnMonthDay;
@SuppressWarnings("unused")
private final Integer feeInterval;
private final EnumOptionData chargeCalculationType; private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage; private final BigDecimal percentage;
@@ -91,29 +94,29 @@ public class SavingsAccountChargeData {
final boolean penalty = false; final boolean penalty = false;
final LocalDate dueAsOfDate = null; final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null; final MonthDay feeOnMonthDay = null;
final Integer feeInterval = null;
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty, feeOnMonthDay); penalty, feeOnMonthDay, feeInterval);
} }
public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final Long accountId, final String name, final CurrencyData currency, public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final Long accountId, final String name, final CurrencyData currency,
final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate, final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate,
final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) { final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay, final Integer feeInterval) {
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty, feeOnMonthDay); penalty, feeOnMonthDay, feeInterval);
} }
private SavingsAccountChargeData(final Long id, final Long chargeId, final Long accountId, final String name, final EnumOptionData chargeTimeType, private SavingsAccountChargeData(final Long id, final Long chargeId, final Long accountId, final String name, final EnumOptionData chargeTimeType,
final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage, final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid,
final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding, final BigDecimal amountWaived, final BigDecimal amountWrittenOff, final BigDecimal amountOutstanding,
final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay) { final Collection<ChargeData> chargeOptions, final boolean penalty, final MonthDay feeOnMonthDay, final Integer feeInterval) {
this.id = id; this.id = id;
this.chargeId = chargeId; this.chargeId = chargeId;
this.accountId = accountId; this.accountId = accountId;
@@ -133,6 +136,7 @@ public class SavingsAccountChargeData {
this.chargeOptions = chargeOptions; this.chargeOptions = chargeOptions;
this.penalty = penalty; this.penalty = penalty;
this.feeOnMonthDay = feeOnMonthDay; this.feeOnMonthDay = feeOnMonthDay;
this.feeInterval = feeInterval;
} }
private BigDecimal getAmountOrPercentage() { private BigDecimal getAmountOrPercentage() {
@@ -12,11 +12,13 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountPara
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -92,6 +94,18 @@ public class SavingsAccountChargeDataValidator {
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element); final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull(); baseDataValidator.reset().parameter(dueAsOfDateParamName).value(transactionDate).notNull();
} }
if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(element.getAsJsonObject());
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element.getAsJsonObject(), monthDayFormat, locale);
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull();
}
if (this.fromApiJsonHelper.parameterExists(feeIntervalParamName, element)) {
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, element, Locale.getDefault());
baseDataValidator.reset().parameter(feeIntervalParamName).value(feeInterval).notNull().inMinMaxRange(1, 12);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -11,7 +11,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdPa
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.clientIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.externalIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeAmountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.fieldOfficerIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.groupIdParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.groupIdParamName;
@@ -196,6 +196,7 @@ public class SavingsAccountDataValidator {
if (element.isJsonObject()) { if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject(); final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(topLevelJsonElement);
if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) { if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray(); final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
for (int i = 0; i < array.size(); i++) { for (int i = 0; i < array.size(); i++) {
@@ -209,6 +210,16 @@ public class SavingsAccountDataValidator {
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale); final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount(); baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, savingsChargeElement)) {
final MonthDay monthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement, monthDayFormat, locale);
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDay).notNull();
}
if (this.fromApiJsonHelper.parameterExists(feeIntervalParamName, savingsChargeElement)) {
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement, Locale.getDefault());
baseDataValidator.reset().parameter(feeIntervalParamName).value(feeInterval).notNull().inMinMaxRange(1, 12);
}
} }
} }
} }
@@ -348,18 +359,7 @@ public class SavingsAccountDataValidator {
.ignoreIfNull().validateForBooleanValue(); .ignoreIfNull().validateForBooleanValue();
} }
if (this.fromApiJsonHelper.parameterExists(feeAmountParamName, element)) {
final BigDecimal annualFeeAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(feeAmountParamName, element);
baseDataValidator.reset().parameter(feeAmountParamName).value(annualFeeAmount).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(feeOnMonthDayParamName, element)) {
final MonthDay monthDayOfAnnualFee = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, element);
baseDataValidator.reset().parameter(feeOnMonthDayParamName).value(monthDayOfAnnualFee).ignoreIfNull();
}
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -1207,12 +1207,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge(); final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge();
if (transactionToUndo.isChargeTransaction()) { if (transactionToUndo.isChargeTransaction()) {
chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
if(chargeToUndo.isAnnualFee()){
chargeToUndo.updateToPreviousAnnualFeeDueDate();
}
} else if (transactionToUndo.isWaiveCharge()) { } else if (transactionToUndo.isWaiveCharge()) {
chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency())); chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
chargeToUndo.updateToPreviousAnnualFeeDueDate();
} }
} }
} }
@@ -1459,7 +1455,7 @@ public class SavingsAccount extends AbstractPersistable<Long> {
//update annual fee due date //update annual fee due date
for (SavingsAccountCharge charge : this.charges()) { for (SavingsAccountCharge charge : this.charges()) {
charge.updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); charge.updateToNextDueDateFrom(getActivationLocalDate());
} }
//auto pay the activation time charges //auto pay the activation time charges
@@ -1677,11 +1673,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
// waive charge // waive charge
final Money amountWaived = savingsAccountCharge.waive(getCurrency()); final Money amountWaived = savingsAccountCharge.waive(getCurrency());
handleWaiverChargeTransactions(savingsAccountCharge, amountWaived); handleWaiverChargeTransactions(savingsAccountCharge, amountWaived);
//update next annual fee due date
savingsAccountCharge.updateToNextAnnualFeeDueDate();
} }
public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) { public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition) {
@@ -1738,13 +1731,15 @@ public class SavingsAccount extends AbstractPersistable<Long> {
throw new PlatformApiDataValidationException(dataValidationErrors); throw new PlatformApiDataValidationException(dataValidationErrors);
} }
}
if(savingsAccountCharge.isAnnualFee() || savingsAccountCharge.isMonthlyFee()){
//update due date
if(isActive()){ if(isActive()){
savingsAccountCharge.updateToNextAnnualFeeDueDateFrom(getActivationLocalDate()); savingsAccountCharge.updateToNextDueDateFrom(getActivationLocalDate());
}else if(isApproved()){ }else if(isApproved()){
savingsAccountCharge.updateToNextAnnualFeeDueDateFrom(getApprovedOnLocalDate()); savingsAccountCharge.updateToNextDueDateFrom(getApprovedOnLocalDate());
} }
//if account is in submitted and pending approval state,
//upon account activation annual fee charges due date get updated.
} }
//activation charge and withdrawal charges not required this validation //activation charge and withdrawal charges not required this validation
@@ -1843,9 +1838,6 @@ public class SavingsAccount extends AbstractPersistable<Long> {
} }
this.payCharge(savingsAccountCharge, chargePaid, transactionDate); this.payCharge(savingsAccountCharge, chargePaid, transactionDate);
//update next annual fee due date
savingsAccountCharge.updateToNextAnnualFeeDueDate();
} }
public void payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, final LocalDate transactionDate) { public void payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, final LocalDate transactionDate) {
@@ -8,8 +8,9 @@ package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.localeParamName;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
@@ -64,6 +65,9 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
@Column(name = "fee_on_day", nullable = true) @Column(name = "fee_on_day", nullable = true)
private Integer feeOnDay; private Integer feeOnDay;
@Column(name = "fee_interval", nullable = true)
private Integer feeInterval;
@Column(name = "charge_calculation_enum") @Column(name = "charge_calculation_enum")
private Integer chargeCalculation; private Integer chargeCalculation;
@@ -107,20 +111,20 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName); final BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName);
final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
final MonthDay feeOnMonthDay = command.extractMonthDayNamed(feeOnMonthDayParamName); final MonthDay feeOnMonthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
final Integer feeInterval = command.integerValueOfParameterNamed(feeIntervalParamName);
final ChargeTimeType chargeTime = null; final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null; final ChargeCalculationType chargeCalculation = null;
final boolean status = true; final boolean status = true;
return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status,
feeOnMonthDay); feeOnMonthDay, feeInterval);
} }
// TODO AA: refactor method signature
public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amountPayable, public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amountPayable,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status,
final MonthDay feeOnMonthDay) { final MonthDay feeOnMonthDay, final Integer feeInterval) {
return new SavingsAccountCharge(null, chargeDefinition, amountPayable, chargeTime, chargeCalculation, dueDate, status, return new SavingsAccountCharge(null, chargeDefinition, amountPayable, chargeTime, chargeCalculation, dueDate, status,
feeOnMonthDay); feeOnMonthDay, feeInterval);
} }
protected SavingsAccountCharge() { protected SavingsAccountCharge() {
@@ -129,7 +133,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
private SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount, private SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status,
final MonthDay feeOnMonthDay) { MonthDay feeOnMonthDay, final Integer feeInterval) {
this.savingsAccount = savingsAccount; this.savingsAccount = savingsAccount;
this.charge = chargeDefinition; this.charge = chargeDefinition;
@@ -145,14 +149,25 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
} }
if (isAnnualFee()) { if (isAnnualFee() || isMonthlyFee()) {
feeOnMonthDay = (feeOnMonthDay == null) ? chargeDefinition.getFeeOnMonthDay() : feeOnMonthDay;
if (feeOnMonthDay == null) { if (feeOnMonthDay == null) {
final String defaultUserMessage = "Savings Account charge is missing due date."; final String defaultUserMessage = "Savings Account charge is missing due date.";
throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName, throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName,
defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName()); defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName());
} }
this.feeOnMonth = feeOnMonthDay.getMonthOfYear(); this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
this.feeOnDay = feeOnMonthDay.getDayOfMonth(); this.feeOnDay = feeOnMonthDay.getDayOfMonth();
if (isMonthlyFee()) {
this.feeInterval = (feeInterval == null) ? chargeDefinition.feeInterval() : feeInterval;
}
} else {
this.feeOnDay = null;
this.feeOnMonth = null;
this.feeInterval = null;
} }
this.dueDate = (dueDate == null) ? null : dueDate.toDate(); this.dueDate = (dueDate == null) ? null : dueDate.toDate();
@@ -170,14 +185,23 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
final BigDecimal transactionAmount = new BigDecimal(0); final BigDecimal transactionAmount = new BigDecimal(0);
populateDerivedFields(transactionAmount, chargeAmount); populateDerivedFields(transactionAmount, chargeAmount);
resetOutstandingAmount();// reset only if it's withdrawal or annual fee
if (this.isWithdrawalFee()) {
this.amountOutstanding = BigDecimal.ZERO;
}
this.paid = determineIfFullyPaid(); this.paid = determineIfFullyPaid();
this.status = status; this.status = status;
} }
private void resetOutstandingAmount() { public void recalculateAmountOutstanding() {
if (this.isWithdrawalFee()) { if (isMonthlyFee() || isAnnualFee()) {
this.amountOutstanding = BigDecimal.ZERO; // FIXME: AA: If charge is percentage of x amount then need to
// update amount outstanding accordingly.
// Right now annual and monthly charges supports charge calculation type flat.
this.amountOutstanding = this.amount;
this.paid = false;//reset to false for recurring fee.
this.waived = false;
} }
} }
@@ -252,9 +276,13 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
amountPaid = amountPaid.minus(transactionAmount); amountPaid = amountPaid.minus(transactionAmount);
this.amountPaid = amountPaid.getAmount(); this.amountPaid = amountPaid.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency); this.amountOutstanding = calculateAmountOutstanding(currency);
if (this.isWithdrawalFee()) { if (this.isWithdrawalFee()) {
this.amountOutstanding = BigDecimal.ZERO; this.amountOutstanding = BigDecimal.ZERO;
} }
//to reset amount outstanding for annual and monthly fee
recalculateAmountOutstanding();
updateToPreviousDueDate();//reset annual and monthly due date.
this.paid = false; this.paid = false;
} }
@@ -262,6 +290,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountWaived = this.amountOutstanding; this.amountWaived = this.amountOutstanding;
this.amountOutstanding = BigDecimal.ZERO; this.amountOutstanding = BigDecimal.ZERO;
this.waived = true; this.waived = true;
recalculateAmountOutstanding();
updateToNextDueDate();
return getAmountWaived(currency); return getAmountWaived(currency);
} }
@@ -271,10 +303,12 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountWaived = amountWaived.getAmount(); this.amountWaived = amountWaived.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency); this.amountOutstanding = calculateAmountOutstanding(currency);
this.waived = false; this.waived = false;
recalculateAmountOutstanding();
updateToPreviousDueDate();
} }
public Money pay(final MonetaryCurrency currency, final Money amountPaid) { public Money pay(final MonetaryCurrency currency, final Money amountPaid) {
Money amountPaidToDate = Money.of(currency, this.amountPaid); Money amountPaidToDate = Money.of(currency, this.amountPaid);
Money amountOutstanding = Money.of(currency, this.amountOutstanding); Money amountOutstanding = Money.of(currency, this.amountOutstanding);
amountPaidToDate = amountPaidToDate.plus(amountPaid); amountPaidToDate = amountPaidToDate.plus(amountPaid);
@@ -282,7 +316,14 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountPaid = amountPaidToDate.getAmount(); this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = amountOutstanding.getAmount(); this.amountOutstanding = amountOutstanding.getAmount();
this.paid = determineIfFullyPaid(); this.paid = determineIfFullyPaid();
return amountOutstanding;
if (BigDecimal.ZERO.compareTo(this.amountOutstanding) == 0) {
//full outstanding is paid, update to next due date
updateToNextDueDate();
recalculateAmountOutstanding();
}
return Money.of(currency, this.amountOutstanding);
} }
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) { private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
@@ -293,7 +334,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.savingsAccount = savingsAccount; this.savingsAccount = savingsAccount;
} }
public void update(final BigDecimal amount, final LocalDate dueDate, final MonthDay feeOnMonthDay) { public void update(final BigDecimal amount, final LocalDate dueDate, final MonthDay feeOnMonthDay, final Integer feeInterval) {
final BigDecimal transactionAmount = BigDecimal.ZERO; final BigDecimal transactionAmount = BigDecimal.ZERO;
if (dueDate != null) { if (dueDate != null) {
this.dueDate = dueDate.toDate(); this.dueDate = dueDate.toDate();
@@ -304,6 +345,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.feeOnDay = feeOnMonthDay.getDayOfMonth(); this.feeOnDay = feeOnMonthDay.getDayOfMonth();
} }
if (feeInterval != null) {
this.feeInterval = feeInterval;
}
if (amount != null) { if (amount != null) {
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
case INVALID: case INVALID:
@@ -574,6 +619,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee(); return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee();
} }
public boolean isMonthlyFee() {
return ChargeTimeType.fromInt(this.chargeTime).isMonthlyFee();
}
public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) { public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; } if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; }
return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode); return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode);
@@ -613,30 +662,60 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return amountPaybale; return amountPaybale;
} }
public void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) { public void updateToNextDueDateFrom(final LocalDate startingDate) {
if (isAnnualFee()) { if (isAnnualFee() || isMonthlyFee()) {
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay); LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth);
if (startingDate.isAfter(nextDueLocalDate)) { while (startingDate.isAfter(nextDueLocalDate)) {
nextDueLocalDate = nextDueLocalDate.plusYears(1); if (isAnnualFee()) {
nextDueLocalDate = nextDueLocalDate.plusYears(1);
} else if (isMonthlyFee()) {
nextDueLocalDate = nextDueLocalDate.plusMonths(this.feeInterval);
}
} }
//set day of month
int maxDayOfMonth = nextDueLocalDate.dayOfMonth().withMaximumValue().getDayOfMonth();
int newDayOfMonth = (this.feeOnDay.intValue() <= maxDayOfMonth) ? this.feeOnDay.intValue() : maxDayOfMonth;
nextDueLocalDate = nextDueLocalDate.withDayOfMonth(newDayOfMonth);
this.dueDate = nextDueLocalDate.toDate(); this.dueDate = nextDueLocalDate.toDate();
} }
} }
public void updateToNextAnnualFeeDueDate() { public void updateToNextDueDate() {
if (isAnnualFee()) { if(isAnnualFee() || isMonthlyFee()){
LocalDate nextDueLocalDate = new LocalDate(dueDate); LocalDate nextDueLocalDate = new LocalDate(dueDate);
this.dueDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay).plusYears(1).toDate(); if(isAnnualFee()){
nextDueLocalDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).plusYears(1);
}else if(isMonthlyFee()){
nextDueLocalDate = nextDueLocalDate.plusMonths(this.feeInterval);
}
//set day of month
int maxDayOfMonth = nextDueLocalDate.dayOfMonth().withMaximumValue().getDayOfMonth();
int newDayOfMonth = (this.feeOnDay.intValue() < maxDayOfMonth) ? this.feeOnDay.intValue() : maxDayOfMonth;
nextDueLocalDate = nextDueLocalDate.withDayOfMonth(newDayOfMonth);
this.dueDate = nextDueLocalDate.toDate();
} }
} }
public void updateToPreviousAnnualFeeDueDate() { public void updateToPreviousDueDate() {
if (isAnnualFee()) { if(isAnnualFee() || isMonthlyFee()){
LocalDate previousDueLocalDate = new LocalDate(dueDate); LocalDate nextDueLocalDate = new LocalDate(dueDate);
this.dueDate = previousDueLocalDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay).minusYears(1).toDate(); if(isAnnualFee()){
nextDueLocalDate = nextDueLocalDate.withMonthOfYear(this.feeOnMonth).minusYears(1);
}else if(isMonthlyFee()){
nextDueLocalDate = nextDueLocalDate.minusMonths(this.feeInterval);
}
//set day of month
int maxDayOfMonth = nextDueLocalDate.dayOfMonth().withMaximumValue().getDayOfMonth();
int newDayOfMonth = (this.feeOnDay.intValue() < maxDayOfMonth) ? this.feeOnDay.intValue() : maxDayOfMonth;
nextDueLocalDate = nextDueLocalDate.withDayOfMonth(newDayOfMonth);
this.dueDate = nextDueLocalDate.toDate();
} }
} }
public boolean feeSettingsNotSet() { public boolean feeSettingsNotSet() {
return !feeSettingsSet(); return !feeSettingsSet();
} }
@@ -13,6 +13,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTime
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -82,6 +83,7 @@ public class SavingsAccountChargeAssembler {
dateFormat, locale); dateFormat, locale);
final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement, monthDayFormat, locale); final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement, monthDayFormat, locale);
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement, locale);
if (id == null) { if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
@@ -92,18 +94,19 @@ public class SavingsAccountChargeAssembler {
throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, chargeDefinition.getId()); throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, chargeDefinition.getId());
} }
final ChargeTimeType chargeTime = null; ChargeTimeType chargeTime = null;
if (chargeTimeType != null) { if (chargeTimeType != null) {
ChargeTimeType.fromInt(chargeTimeType); chargeTime = ChargeTimeType.fromInt(chargeTimeType);
} }
final ChargeCalculationType chargeCalculation = null;
ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) { if (chargeCalculationType != null) {
ChargeCalculationType.fromInt(chargeCalculationType); chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
} }
final boolean status = true; final boolean status = true;
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount( final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay); chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay, feeInterval);
savingsAccountCharges.add(savingsAccountCharge); savingsAccountCharges.add(savingsAccountCharge);
} else { } else {
final Long savingsAccountChargeId = id; final Long savingsAccountChargeId = id;
@@ -111,7 +114,7 @@ public class SavingsAccountChargeAssembler {
.findOne(savingsAccountChargeId); .findOne(savingsAccountChargeId);
if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); } if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
savingsAccountCharge.update(amount, dueDate, feeOnMonthDay); savingsAccountCharge.update(amount, dueDate, feeOnMonthDay, feeInterval);
savingsAccountCharges.add(savingsAccountCharge); savingsAccountCharges.add(savingsAccountCharge);
} }
@@ -12,6 +12,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTime
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName; import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -73,6 +74,7 @@ public class SavingsProductChargeAssembler {
final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement, final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
dateFormat, locale); dateFormat, locale);
final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement); final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement);
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement, locale);
if (id == null) { if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
@@ -85,7 +87,7 @@ public class SavingsProductChargeAssembler {
ChargeCalculationType.fromInt(chargeCalculationType); ChargeCalculationType.fromInt(chargeCalculationType);
} }
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount( final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, true, feeOnMonthDay); chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, true, feeOnMonthDay, feeInterval);
savingsAccountCharges.add(savingsAccountCharge); savingsAccountCharges.add(savingsAccountCharge);
} else { } else {
final Long savingsAccountChargeId = id; final Long savingsAccountChargeId = id;
@@ -93,7 +95,7 @@ public class SavingsProductChargeAssembler {
.findOne(savingsAccountChargeId); .findOne(savingsAccountChargeId);
if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); } if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
savingsAccountCharge.update(amount, dueDate, null); savingsAccountCharge.update(amount, dueDate, null, null);
savingsAccountCharges.add(savingsAccountCharge); savingsAccountCharges.add(savingsAccountCharge);
} }
@@ -65,7 +65,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
+ "sc.is_penalty as penalty, " + "sc.is_penalty as penalty, "
+ "sc.charge_due_date as dueAsOfDate, " + "sc.charge_due_date as dueAsOfDate, "
+ "sc.fee_on_month as feeOnMonth, " + "sc.fee_on_month as feeOnMonth, "
+ "sc.fee_on_day as feeOnDay, " + "sc.fee_on_day as feeOnDay, sc.fee_interval as feeInterval, "
+ "sc.charge_calculation_enum as chargeCalculation, " + "sc.charge_calculation_enum as chargeCalculation, "
+ "c.currency_code as currencyCode, oc.name as currencyName, " + "c.currency_code as currencyCode, oc.name as currencyName, "
+ "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, " + "oc.decimal_places as currencyDecimalPlaces, oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
@@ -104,6 +104,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime); final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate"); final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final Integer feeInterval = JdbcSupport.getInteger(rs, "feeInterval");
MonthDay feeOnMonthDay = null; MonthDay feeOnMonthDay = null;
final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth"); final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay"); final Integer feeOnDay = JdbcSupport.getInteger(rs, "feeOnDay");
@@ -119,7 +120,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived, return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf,
amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay); amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay, feeInterval);
} }
} }
@@ -849,7 +849,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
account.calculateInterestUsing(mc, today); account.calculateInterestUsing(mc, today);
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name()); account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode());
this.savingAccountRepository.save(account); this.savingAccountRepository.save(account);