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/>
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/>
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>
</tr>
<tr class=alt>
@@ -11052,6 +11053,22 @@ Request Body:
<td class=fielddesc>Rules for calculating the amount for a particular Charge.
Refer <a href="#charges_template">Charges Template</a> for additional details</td>
</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>
</div>
</div>
@@ -11144,6 +11161,11 @@ GET https://DomainName/api/v1/charges/template
"id": 6,
"code": "chargeTimeType.annualFee",
"value": "Annual Fee"
},
{
"id": 7,
"code": "chargeTimeType.monthlyFee",
"value": "Monthly Fee"
}
],
"chargePaymetModeOptions":
@@ -11201,6 +11223,10 @@ GET https://DomainName/api/v1/charges/template
{ "code" : "chargeTimeType.annualFee",
"id" : 6,
"value" : "Annual Fee"
},
{ "code" : "chargeTimeType.monthlyFee",
"id" : 7,
"value" : "Monthly Fee"
}
]
}
@@ -11283,7 +11309,31 @@ Request Body:
{
"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>
@@ -13242,6 +13292,46 @@ Content-Type: application/json
"code": "chargepaymentmode.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) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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,
dateFormat);
dataValidationErrors.add(error);
@@ -32,6 +32,8 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
private final EnumOptionData chargeAppliesTo;
private final EnumOptionData chargeCalculationType;
private final EnumOptionData chargePaymentMode;
private final MonthDay feeOnMonthDay;
private final Integer feeInterval;
private final Collection<CurrencyData> currencyOptions;
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,
chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions, loansChargeCalculationTypeOptions,
loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, null, null);
}
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,
template.chargeCalculationTypeOptions, template.chargeAppliesToOptions, template.chargeTimeTypeOptions,
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,
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 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,
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval);
}
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 chargeCalculationType = null;
final EnumOptionData chargePaymentMode = null;
final MonthDay feeOnMonthDay = null;
final Integer feeInterval = null;
final Boolean penalty = isPenalty;
final Boolean active = false;
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,
penalty, active, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions);
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, feeOnMonthDay, feeInterval);
}
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> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
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.name = name;
this.amount = amount;
@@ -124,6 +128,8 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
this.chargeAppliesTo = chargeAppliesTo;
this.chargeCalculationType = chargeCalculationType;
this.chargePaymentMode = chargePaymentMode;
this.feeInterval = feeInterval;
this.feeOnMonthDay = feeOnMonthDay;
this.penalty = penalty;
this.active = active;
this.currencyOptions = currencyOptions;
@@ -178,10 +184,9 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
final BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
final Collection<ChargeData> chargeOptions = null;
final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null;
return SavingsAccountChargeData.instance(savingsChargeId, this.id, savingsAccountId, this.name, this.currency, this.amount, amountPaid, amountWaived,
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.HashCodeBuilder;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
@@ -56,6 +57,15 @@ public class Charge extends AbstractPersistable<Long> {
@Column(name = "charge_payment_mode_enum")
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)
private boolean penalty;
@@ -79,8 +89,11 @@ public class Charge extends AbstractPersistable<Long> {
final boolean penalty = command.booleanPrimitiveValueOfParameterNamed("penalty");
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() {
@@ -89,7 +102,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 boolean active, final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval) {
this.name = name;
this.amount = amount;
this.currencyCode = currencyCode;
@@ -103,6 +116,12 @@ public class Charge extends AbstractPersistable<Long> {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
if (isMonthlyFee()) {
this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
this.feeOnDay = feeOnMonthDay.getDayOfMonth();
this.feeInterval = feeInterval;
}
if (isSavingsCharge()) {
if (!isAllowedSavingsChargeTime()) {
@@ -129,7 +148,7 @@ public class Charge extends AbstractPersistable<Long> {
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@@ -246,13 +265,20 @@ public class Charge extends AbstractPersistable<Long> {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.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()) {
if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
}
}
}
final String chargeAppliesToParamName = "chargeAppliesTo";
@@ -297,7 +323,33 @@ public class Charge extends AbstractPersistable<Long> {
actualChanges.put("locale", localeAsInput);
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";
if (command.isChangeInBooleanParameterNamed(penaltyParamName, this.penalty)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(penaltyParamName);
@@ -339,10 +391,26 @@ public class Charge extends AbstractPersistable<Long> {
final EnumOptionData chargePaymentmode = ChargeEnumerations.chargePaymentMode(this.chargePaymentMode);
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, this.penalty, this.active);
chargePaymentmode, this.getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active);
}
public Integer getChargePaymentMode() {
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_CLOSURE(4, "chargeTimeType.savingsClosure"), // 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 String code;
@@ -64,6 +65,9 @@ public enum ChargeTimeType {
case 6:
chargeTimeType = ANNUAL_FEE;
break;
case 7:
chargeTimeType = MONTHLY_FEE;
break;
default:
chargeTimeType = INVALID;
break;
@@ -95,11 +99,16 @@ public enum ChargeTimeType {
return this.value.equals(ChargeTimeType.ANNUAL_FEE.getValue());
}
public boolean isMonthlyFee() {
return this.value.equals(ChargeTimeType.MONTHLY_FEE.getValue());
}
public boolean isAllowedLoanChargeTime() {
return isTimeOfDisbursement() || isOnSpecifiedDueDate();
}
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 org.apache.commons.lang.StringUtils;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
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",
"currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty",
"active", "chargePaymentMode"));
"active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat"));
private final FromJsonHelper fromApiJsonHelper;
@@ -90,6 +91,15 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final Boolean active = this.fromApiJsonHelper.extractBooleanNamed("active", element);
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);
}
@@ -130,6 +140,16 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull()
.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)) {
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),
//chargeTimeType(ChargeTimeType.SAVINGS_CLOSURE),
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:
optionData = new EnumOptionData(ChargeTimeType.ANNUAL_FEE.getValue().longValue(), ChargeTimeType.ANNUAL_FEE.getCode(), "Annual Fee");
break;
case MONTHLY_FEE:
optionData = new EnumOptionData(ChargeTimeType.MONTHLY_FEE.getValue().longValue(), ChargeTimeType.MONTHLY_FEE.getCode(), "Monthly Fee");
break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break;
@@ -11,6 +11,7 @@ import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
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, "
+ "c.charge_applies_to_enum as chargeAppliesTo, c.charge_time_enum as chargeTime, "
+ "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, "
+ "oc.decimal_places as currencyDecimalPlaces,oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode from m_charge c "
+ "c.charge_calculation_enum as chargeCalculation, c.is_penalty as penalty, "
+ "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 from m_charge c "
+ "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 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,
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 feeAmountParamName = "feeAmount";//to be deleted
public static final String feeOnMonthDayParamName = "feeOnMonthDay";
public static final String feeIntervalParamName = "feeInterval";
public static final String accountingRuleParamName = "accountingRule";
public static final String paymentTypeIdParamName = "paymentTypeId";
public static final String transactionAccountNumberParamName = "accountNumber";
@@ -203,7 +204,7 @@ public class SavingsApiConstants {
amountPaidParamName, chargeOptionsParamName));
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(
amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
@@ -40,6 +40,9 @@ public class SavingsAccountChargeData {
@SuppressWarnings("unused")
private final MonthDay feeOnMonthDay;
@SuppressWarnings("unused")
private final Integer feeInterval;
private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage;
@@ -91,29 +94,29 @@ public class SavingsAccountChargeData {
final boolean penalty = false;
final LocalDate dueAsOfDate = null;
final MonthDay feeOnMonthDay = null;
final Integer feeInterval = null;
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
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,
final BigDecimal amount, final BigDecimal amountPaid, final BigDecimal amountWaived, final BigDecimal amountWrittenOff,
final BigDecimal amountOutstanding, final EnumOptionData chargeTimeType, final LocalDate dueAsOfDate,
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,
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,
final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo, final CurrencyData currency, final BigDecimal amount, final BigDecimal amountPaid,
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.chargeId = chargeId;
this.accountId = accountId;
@@ -133,6 +136,7 @@ public class SavingsAccountChargeData {
this.chargeOptions = chargeOptions;
this.penalty = penalty;
this.feeOnMonthDay = feeOnMonthDay;
this.feeInterval = feeInterval;
}
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.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.feeIntervalParamName;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
@@ -92,6 +94,18 @@ public class SavingsAccountChargeDataValidator {
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
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);
}
@@ -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.clientIdParamName;
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.fieldOfficerIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.groupIdParamName;
@@ -196,6 +196,7 @@ public class SavingsAccountDataValidator {
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final String monthDayFormat = this.fromApiJsonHelper.extractMonthDayFormatParameter(topLevelJsonElement);
if (topLevelJsonElement.has(chargesParamName) && topLevelJsonElement.get(chargesParamName).isJsonArray()) {
final JsonArray array = topLevelJsonElement.get(chargesParamName).getAsJsonArray();
for (int i = 0; i < array.size(); i++) {
@@ -209,6 +210,16 @@ public class SavingsAccountDataValidator {
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
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();
}
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);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -1207,12 +1207,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge();
if (transactionToUndo.isChargeTransaction()) {
chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
if(chargeToUndo.isAnnualFee()){
chargeToUndo.updateToPreviousAnnualFeeDueDate();
}
} else if (transactionToUndo.isWaiveCharge()) {
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
for (SavingsAccountCharge charge : this.charges()) {
charge.updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
charge.updateToNextDueDateFrom(getActivationLocalDate());
}
//auto pay the activation time charges
@@ -1677,11 +1673,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
// waive charge
final Money amountWaived = savingsAccountCharge.waive(getCurrency());
handleWaiverChargeTransactions(savingsAccountCharge, amountWaived);
//update next annual fee due date
savingsAccountCharge.updateToNextAnnualFeeDueDate();
}
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);
}
}
if(savingsAccountCharge.isAnnualFee() || savingsAccountCharge.isMonthlyFee()){
//update due date
if(isActive()){
savingsAccountCharge.updateToNextAnnualFeeDueDateFrom(getActivationLocalDate());
savingsAccountCharge.updateToNextDueDateFrom(getActivationLocalDate());
}else if(isApproved()){
savingsAccountCharge.updateToNextAnnualFeeDueDateFrom(getApprovedOnLocalDate());
}
//if account is in submitted and pending approval state,
//upon account activation annual fee charges due date get updated.
savingsAccountCharge.updateToNextDueDateFrom(getApprovedOnLocalDate());
}
}
//activation charge and withdrawal charges not required this validation
@@ -1843,9 +1838,6 @@ public class SavingsAccount extends AbstractPersistable<Long> {
}
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) {
@@ -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.dateFormatParamName;
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.localeParamName;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -64,6 +65,9 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
@Column(name = "fee_on_day", nullable = true)
private Integer feeOnDay;
@Column(name = "fee_interval", nullable = true)
private Integer feeInterval;
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@@ -107,20 +111,20 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName);
final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
final MonthDay feeOnMonthDay = command.extractMonthDayNamed(feeOnMonthDayParamName);
final Integer feeInterval = command.integerValueOfParameterNamed(feeIntervalParamName);
final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null;
final boolean status = true;
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,
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,
feeOnMonthDay);
feeOnMonthDay, feeInterval);
}
protected SavingsAccountCharge() {
@@ -129,7 +133,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
private SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate, final boolean status,
final MonthDay feeOnMonthDay) {
MonthDay feeOnMonthDay, final Integer feeInterval) {
this.savingsAccount = savingsAccount;
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) {
final String defaultUserMessage = "Savings Account charge is missing due date.";
throw new SavingsAccountChargeWithoutMandatoryFieldException("savingsaccount.charge", dueAsOfDateParamName,
defaultUserMessage, chargeDefinition.getId(), chargeDefinition.getName());
}
this.feeOnMonth = feeOnMonthDay.getMonthOfYear();
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();
@@ -170,14 +185,23 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
final BigDecimal transactionAmount = new BigDecimal(0);
populateDerivedFields(transactionAmount, chargeAmount);
resetOutstandingAmount();// reset only if it's withdrawal or annual fee
if (this.isWithdrawalFee()) {
this.amountOutstanding = BigDecimal.ZERO;
}
this.paid = determineIfFullyPaid();
this.status = status;
}
private void resetOutstandingAmount() {
if (this.isWithdrawalFee()) {
this.amountOutstanding = BigDecimal.ZERO;
public void recalculateAmountOutstanding() {
if (isMonthlyFee() || isAnnualFee()) {
// 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);
this.amountPaid = amountPaid.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency);
if (this.isWithdrawalFee()) {
this.amountOutstanding = BigDecimal.ZERO;
}
//to reset amount outstanding for annual and monthly fee
recalculateAmountOutstanding();
updateToPreviousDueDate();//reset annual and monthly due date.
this.paid = false;
}
@@ -262,6 +290,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountWaived = this.amountOutstanding;
this.amountOutstanding = BigDecimal.ZERO;
this.waived = true;
recalculateAmountOutstanding();
updateToNextDueDate();
return getAmountWaived(currency);
}
@@ -271,10 +303,12 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountWaived = amountWaived.getAmount();
this.amountOutstanding = calculateAmountOutstanding(currency);
this.waived = false;
recalculateAmountOutstanding();
updateToPreviousDueDate();
}
public Money pay(final MonetaryCurrency currency, final Money amountPaid) {
Money amountPaidToDate = Money.of(currency, this.amountPaid);
Money amountOutstanding = Money.of(currency, this.amountOutstanding);
amountPaidToDate = amountPaidToDate.plus(amountPaid);
@@ -282,7 +316,14 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = amountOutstanding.getAmount();
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) {
@@ -293,7 +334,7 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
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;
if (dueDate != null) {
this.dueDate = dueDate.toDate();
@@ -304,6 +345,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
this.feeOnDay = feeOnMonthDay.getDayOfMonth();
}
if (feeInterval != null) {
this.feeInterval = feeInterval;
}
if (amount != null) {
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
case INVALID:
@@ -574,6 +619,10 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).isAnnualFee();
}
public boolean isMonthlyFee() {
return ChargeTimeType.fromInt(this.chargeTime).isMonthlyFee();
}
public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
if (this.currencyCode() == null || matchingCurrencyCode == null) { return false; }
return this.currencyCode().equalsIgnoreCase(matchingCurrencyCode);
@@ -613,30 +662,60 @@ public class SavingsAccountCharge extends AbstractPersistable<Long> {
return amountPaybale;
}
public void updateToNextAnnualFeeDueDateFrom(final LocalDate startingDate) {
if (isAnnualFee()) {
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay);
if (startingDate.isAfter(nextDueLocalDate)) {
nextDueLocalDate = nextDueLocalDate.plusYears(1);
public void updateToNextDueDateFrom(final LocalDate startingDate) {
if (isAnnualFee() || isMonthlyFee()) {
LocalDate nextDueLocalDate = startingDate.withMonthOfYear(this.feeOnMonth);
while (startingDate.isAfter(nextDueLocalDate)) {
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();
}
}
public void updateToNextAnnualFeeDueDate() {
if (isAnnualFee()) {
public void updateToNextDueDate() {
if(isAnnualFee() || isMonthlyFee()){
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() {
if (isAnnualFee()) {
LocalDate previousDueLocalDate = new LocalDate(dueDate);
this.dueDate = previousDueLocalDate.withMonthOfYear(this.feeOnMonth).withDayOfMonth(this.feeOnDay).minusYears(1).toDate();
public void updateToPreviousDueDate() {
if(isAnnualFee() || isMonthlyFee()){
LocalDate nextDueLocalDate = new LocalDate(dueDate);
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() {
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.dueAsOfDateParamName;
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 java.math.BigDecimal;
@@ -82,6 +83,7 @@ public class SavingsAccountChargeAssembler {
dateFormat, locale);
final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement, monthDayFormat, locale);
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement, locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
@@ -92,18 +94,19 @@ public class SavingsAccountChargeAssembler {
throw new ChargeCannotBeAppliedToException("savings.product", errorMessage, chargeDefinition.getId());
}
final ChargeTimeType chargeTime = null;
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
ChargeTimeType.fromInt(chargeTimeType);
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
}
final ChargeCalculationType chargeCalculation = null;
ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) {
ChargeCalculationType.fromInt(chargeCalculationType);
chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
}
final boolean status = true;
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay);
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, status, feeOnMonthDay, feeInterval);
savingsAccountCharges.add(savingsAccountCharge);
} else {
final Long savingsAccountChargeId = id;
@@ -111,7 +114,7 @@ public class SavingsAccountChargeAssembler {
.findOne(savingsAccountChargeId);
if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
savingsAccountCharge.update(amount, dueDate, feeOnMonthDay);
savingsAccountCharge.update(amount, dueDate, feeOnMonthDay, feeInterval);
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.dueAsOfDateParamName;
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 java.math.BigDecimal;
@@ -73,6 +74,7 @@ public class SavingsProductChargeAssembler {
final LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
dateFormat, locale);
final MonthDay feeOnMonthDay = this.fromApiJsonHelper.extractMonthDayNamed(feeOnMonthDayParamName, savingsChargeElement);
final Integer feeInterval = this.fromApiJsonHelper.extractIntegerNamed(feeIntervalParamName, savingsChargeElement, locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
@@ -85,7 +87,7 @@ public class SavingsProductChargeAssembler {
ChargeCalculationType.fromInt(chargeCalculationType);
}
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, true, feeOnMonthDay);
chargeDefinition, amount, chargeTime, chargeCalculation, dueDate, true, feeOnMonthDay, feeInterval);
savingsAccountCharges.add(savingsAccountCharge);
} else {
final Long savingsAccountChargeId = id;
@@ -93,7 +95,7 @@ public class SavingsProductChargeAssembler {
.findOne(savingsAccountChargeId);
if (savingsAccountCharge == null) { throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId); }
savingsAccountCharge.update(amount, dueDate, null);
savingsAccountCharge.update(amount, dueDate, null, null);
savingsAccountCharges.add(savingsAccountCharge);
}
@@ -65,7 +65,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
+ "sc.is_penalty as penalty, "
+ "sc.charge_due_date as dueAsOfDate, "
+ "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, "
+ "c.currency_code as currencyCode, oc.name as currencyName, "
+ "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 LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final Integer feeInterval = JdbcSupport.getInteger(rs, "feeInterval");
MonthDay feeOnMonthDay = null;
final Integer feeOnMonth = JdbcSupport.getInteger(rs, "feeOnMonth");
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,
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.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name());
account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode());
this.savingAccountRepository.save(account);