FINERACT-1374:Withdrawal-Fee-Scheme

This commit is contained in:
rrpawar96
2021-11-13 14:19:44 +08:00
committed by Petri Tuomola
parent 7ff3f38243
commit 4a4cbf11be
11 changed files with 428 additions and 43 deletions
@@ -30,6 +30,7 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountChargeData;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
@@ -43,6 +44,10 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
private final String name;
private final boolean active;
private final boolean penalty;
private final boolean freeWithdrawal;
private final Integer freeWithdrawalChargeFrequency;
private final Integer restartFrequency;
private final Integer restartFrequencyEnum;
private final CurrencyData currency;
private final BigDecimal amount;
private final EnumOptionData chargeTimeType;
@@ -94,8 +99,8 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
final GLAccountData account = null;
final TaxGroupData taxGroupData = null;
return new ChargeData(null, null, null, null, null, null, null, null, false, false, taxGroupData, currencyOptions,
chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions,
return new ChargeData(null, null, null, null, null, null, null, null, false, false, false, null, null, null, taxGroupData,
currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, null, null, null, null, null,
feeFrequencyOptions, account, incomeOrLiabilityAccountOptions, taxGroupOptions, shareChargeCalculationTypeOptions,
@@ -104,7 +109,8 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
public static ChargeData withTemplate(final ChargeData charge, final ChargeData template) {
return new ChargeData(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, charge.chargeAppliesTo,
charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, charge.taxGroup,
charge.chargeCalculationType, charge.chargePaymentMode, charge.penalty, charge.active, charge.freeWithdrawal,
charge.freeWithdrawalChargeFrequency, charge.restartFrequency, charge.restartFrequencyEnum, charge.taxGroup,
template.currencyOptions, template.chargeCalculationTypeOptions, template.chargeAppliesToOptions,
template.chargeTimeTypeOptions, template.chargePaymetModeOptions, template.loanChargeCalculationTypeOptions,
template.loanChargeTimeTypeOptions, template.savingsChargeCalculationTypeOptions, template.savingsChargeTimeTypeOptions,
@@ -118,7 +124,8 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
public static ChargeData instance(final Long id, final String name, final BigDecimal amount, final CurrencyData currency,
final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, final EnumOptionData chargeCalculationType,
final EnumOptionData chargePaymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final boolean penalty,
final boolean active, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
final boolean active, final boolean freeWithdrawal, final Integer freeWithdrawalChargeFrequency, final Integer restartFrequency,
final Integer restartFrequencyEnum, final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
final GLAccountData accountData, TaxGroupData taxGroupData) {
final Collection<CurrencyData> currencyOptions = null;
@@ -141,12 +148,13 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
final List<GLAccountData> expenseAccountOptions = null;
final List<GLAccountData> assetAccountOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, taxGroupData, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, accountData,
incomeOrLiabilityAccountOptions, taxGroupOptions, shareChargeCalculationTypeOptions, shareChargeTimeTypeOptions,
accountMappingForChargeConfig, expenseAccountOptions, assetAccountOptions);
penalty, active, freeWithdrawal, freeWithdrawalChargeFrequency, restartFrequency, restartFrequencyEnum, taxGroupData,
currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval,
minCap, maxCap, feeFrequency, feeFrequencyOptions, accountData, incomeOrLiabilityAccountOptions, taxGroupOptions,
shareChargeCalculationTypeOptions, shareChargeTimeTypeOptions, accountMappingForChargeConfig, expenseAccountOptions,
assetAccountOptions);
}
public static ChargeData lookup(final Long id, final String name, final boolean isPenalty) {
@@ -160,6 +168,12 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
final Integer feeInterval = null;
final Boolean penalty = isPenalty;
final Boolean active = false;
final Boolean freeWithdrawal = false;
final Integer freeWithdrawalChargeFrequency = null;
final Integer restartFrequency = null;
final Integer restartFrequencyEnum = null;
final Boolean isPaymentType = false;
final PaymentTypeData paymentTypeOptions = null;
final BigDecimal minCap = null;
final BigDecimal maxCap = null;
final Collection<CurrencyData> currencyOptions = null;
@@ -186,25 +200,27 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
final List<GLAccountData> assetAccountOptions = null;
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType, chargePaymentMode,
penalty, active, taxGroupData, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions,
chargePaymentModeOptions, loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions,
savingsChargeCalculationTypeOptions, savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions,
clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, feeFrequencyOptions, account,
incomeOrLiabilityAccountOptions, taxGroupOptions, shareChargeCalculationTypeOptions, shareChargeTimeTypeOptions,
accountMappingForChargeConfig, expenseAccountOptions, assetAccountOptions);
penalty, active, freeWithdrawal, freeWithdrawalChargeFrequency, restartFrequency, restartFrequencyEnum, taxGroupData,
currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions, chargePaymentModeOptions,
loansChargeCalculationTypeOptions, loansChargeTimeTypeOptions, savingsChargeCalculationTypeOptions,
savingsChargeTimeTypeOptions, clientChargeCalculationTypeOptions, clientChargeTimeTypeOptions, feeOnMonthDay, feeInterval,
minCap, maxCap, feeFrequency, feeFrequencyOptions, account, incomeOrLiabilityAccountOptions, taxGroupOptions,
shareChargeCalculationTypeOptions, shareChargeTimeTypeOptions, accountMappingForChargeConfig, expenseAccountOptions,
assetAccountOptions);
}
private ChargeData(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 TaxGroupData taxGroupData,
final Collection<CurrencyData> currencyOptions, final List<EnumOptionData> chargeCalculationTypeOptions,
final List<EnumOptionData> chargeAppliesToOptions, final List<EnumOptionData> chargeTimeTypeOptions,
final List<EnumOptionData> chargePaymentModeOptions, final List<EnumOptionData> loansChargeCalculationTypeOptions,
final List<EnumOptionData> loansChargeTimeTypeOptions, final List<EnumOptionData> savingsChargeCalculationTypeOptions,
final List<EnumOptionData> savingsChargeTimeTypeOptions, final List<EnumOptionData> clientChargeCalculationTypeOptions,
final List<EnumOptionData> clientChargeTimeTypeOptions, final MonthDay feeOnMonthDay, final Integer feeInterval,
final BigDecimal minCap, final BigDecimal maxCap, final EnumOptionData feeFrequency,
final List<EnumOptionData> feeFrequencyOptions, final GLAccountData account,
final EnumOptionData chargePaymentMode, final boolean penalty, final boolean active, final boolean freeWithdrawal,
final Integer freeWithdrawalChargeFrequency, final Integer restartFrequency, final Integer restartFrequencyEnum,
final TaxGroupData taxGroupData, final Collection<CurrencyData> currencyOptions,
final List<EnumOptionData> chargeCalculationTypeOptions, final List<EnumOptionData> chargeAppliesToOptions,
final List<EnumOptionData> chargeTimeTypeOptions, final List<EnumOptionData> chargePaymentModeOptions,
final List<EnumOptionData> loansChargeCalculationTypeOptions, final List<EnumOptionData> loansChargeTimeTypeOptions,
final List<EnumOptionData> savingsChargeCalculationTypeOptions, final List<EnumOptionData> savingsChargeTimeTypeOptions,
final List<EnumOptionData> clientChargeCalculationTypeOptions, final List<EnumOptionData> clientChargeTimeTypeOptions,
final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap, final BigDecimal maxCap,
final EnumOptionData feeFrequency, final List<EnumOptionData> feeFrequencyOptions, final GLAccountData account,
final Map<String, List<GLAccountData>> incomeOrLiabilityAccountOptions, final Collection<TaxGroupData> taxGroupOptions,
final List<EnumOptionData> shareChargeCalculationTypeOptions, final List<EnumOptionData> shareChargeTimeTypeOptions,
final String accountMappingForChargeConfig, final List<GLAccountData> expenseAccountOptions,
@@ -221,6 +237,10 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
this.feeOnMonthDay = feeOnMonthDay;
this.penalty = penalty;
this.active = active;
this.freeWithdrawal = freeWithdrawal;
this.freeWithdrawalChargeFrequency = freeWithdrawalChargeFrequency;
this.restartFrequency = restartFrequency;
this.restartFrequencyEnum = restartFrequencyEnum;
this.minCap = minCap;
this.maxCap = maxCap;
this.currencyOptions = currencyOptions;
@@ -294,12 +314,17 @@ public final class ChargeData implements Comparable<ChargeData>, Serializable {
final Collection<ChargeData> chargeOptions = null;
final LocalDate dueAsOfDate = null;
final Boolean isActive = null;
final Boolean isFreeWithdrawal = null;
final Integer freeWithdrawalChargeFrequency = null;
final Integer restartFrequency = null;
final Integer restartFrequencyEnum = null;
final LocalDate inactivationDate = 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, this.feeOnMonthDay, this.feeInterval, isActive,
inactivationDate);
isFreeWithdrawal, freeWithdrawalChargeFrequency, restartFrequency, restartFrequencyEnum, inactivationDate);
}
public ShareAccountChargeData toShareAccountChargeData() {
@@ -48,6 +48,7 @@ import org.apache.fineract.portfolio.charge.exception.ChargeDueAtDisbursementCan
import org.apache.fineract.portfolio.charge.exception.ChargeMustBePenaltyException;
import org.apache.fineract.portfolio.charge.exception.ChargeParameterUpdateNotSupportedException;
import org.apache.fineract.portfolio.charge.service.ChargeEnumerations;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
@@ -103,6 +104,18 @@ public class Charge extends AbstractPersistableCustom {
@Column(name = "fee_frequency", nullable = true)
private Integer feeFrequency;
@Column(name = "is_free_withdrawal", nullable = false)
private boolean enableFreeWithdrawal;
@Column(name = "free_withdrawal_charge_frequency", nullable = true)
private Integer freeWithdrawalFrequency;
@Column(name = "restart_frequency", nullable = true)
private Integer restartFrequency;
@Column(name = "restart_frequency_enum", nullable = true)
private Integer restartFrequencyEnum;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "income_or_liability_account_id")
private GLAccount account;
@@ -112,6 +125,7 @@ public class Charge extends AbstractPersistableCustom {
private TaxGroup taxGroup;
public static Charge fromJson(final JsonCommand command, final GLAccount account, final TaxGroup taxGroup) {
final String name = command.stringValueOfParameterNamed("name");
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
@@ -132,8 +146,23 @@ public class Charge extends AbstractPersistableCustom {
final BigDecimal maxCap = command.bigDecimalValueOfParameterNamed("maxCap");
final Integer feeFrequency = command.integerValueOfParameterNamed("feeFrequency");
boolean enableFreeWithdrawalCharge = false;
enableFreeWithdrawalCharge = command.booleanPrimitiveValueOfParameterNamed("enableFreeWithdrawalCharge");
Integer freeWithdrawalFrequency = null;
Integer restartCountFrequency = null;
PeriodFrequencyType countFrequencyType = null;
if (enableFreeWithdrawalCharge) {
freeWithdrawalFrequency = command.integerValueOfParameterNamed("freeWithdrawalFrequency");
restartCountFrequency = command.integerValueOfParameterNamed("restartCountFrequency");
countFrequencyType = PeriodFrequencyType.fromInt(command.integerValueOfParameterNamed("countFrequencyType"));
}
return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTimeType, chargeCalculationType, penalty, active, paymentMode,
feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, account, taxGroup);
feeOnMonthDay, feeInterval, minCap, maxCap, feeFrequency, enableFreeWithdrawalCharge, freeWithdrawalFrequency,
restartCountFrequency, countFrequencyType, account, taxGroup);
}
protected Charge() {}
@@ -141,7 +170,9 @@ public class Charge extends AbstractPersistableCustom {
private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean penalty, final boolean active,
final ChargePaymentMode paymentMode, final MonthDay feeOnMonthDay, final Integer feeInterval, final BigDecimal minCap,
final BigDecimal maxCap, final Integer feeFrequency, final GLAccount account, final TaxGroup taxGroup) {
final BigDecimal maxCap, final Integer feeFrequency, final boolean enableFreeWithdrawalCharge,
final Integer freeWithdrawalFrequency, final Integer restartFrequency, final PeriodFrequencyType restartFrequencyEnum,
final GLAccount account, final TaxGroup taxGroup) {
this.name = name;
this.amount = amount;
this.currencyCode = currencyCode;
@@ -186,6 +217,13 @@ public class Charge extends AbstractPersistableCustom {
"savings.charge.calculation.type.percentage.allowed.only.for.withdrawal.or.NoActivity");
}
if (enableFreeWithdrawalCharge) {
this.enableFreeWithdrawal = enableFreeWithdrawalCharge;
this.freeWithdrawalFrequency = freeWithdrawalFrequency;
this.restartFrequency = restartFrequency;
this.restartFrequencyEnum = restartFrequencyEnum.getValue();
}
} else if (isLoanCharge()) {
if (penalty && (chargeTime.isTimeOfDisbursement() || chargeTime.isTrancheDisbursement())) {
@@ -201,6 +239,7 @@ public class Charge extends AbstractPersistableCustom {
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
}
}
if (isPercentageOfApprovedAmount()) {
this.minCap = minCap;
this.maxCap = maxCap;
@@ -291,6 +330,22 @@ public class Charge extends AbstractPersistableCustom {
return this.maxCap;
}
public boolean isEnableFreeWithdrawal() {
return this.enableFreeWithdrawal;
}
public Integer getFrequencyFreeWithdrawalCharge() {
return this.freeWithdrawalFrequency;
}
public Integer getRestartFrequency() {
return this.restartFrequency;
}
public Integer getRestartFrequencyEnum() {
return this.restartFrequencyEnum;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
@@ -355,6 +410,35 @@ public class Charge extends AbstractPersistableCustom {
}
}
final String freeWithdrawalFrequencyParamName = "freeWithdrawalFrequency";
if (command.isChangeInIntegerParameterNamed(freeWithdrawalFrequencyParamName, this.freeWithdrawalFrequency)) {
final Integer enableFreeWithdrawalChargeNewValue = command.integerValueOfParameterNamed(freeWithdrawalFrequencyParamName);
actualChanges.put(freeWithdrawalFrequencyParamName, enableFreeWithdrawalChargeNewValue);
this.freeWithdrawalFrequency = enableFreeWithdrawalChargeNewValue;
}
final String restartCountFrequencyParamName = "restartCountFrequency";
if (command.isChangeInIntegerParameterNamed(restartCountFrequencyParamName, this.restartFrequency)) {
final Integer restartCountFrequencyNewValue = command.integerValueOfParameterNamed(restartCountFrequencyParamName);
actualChanges.put(restartCountFrequencyParamName, restartCountFrequencyNewValue);
this.restartFrequency = restartCountFrequencyNewValue;
}
final String countFrequencyTypeParamName = "countFrequencyType";
if (command.isChangeInIntegerParameterNamed(countFrequencyTypeParamName, this.restartFrequencyEnum)) {
final Integer countFrequencyTypeNewValue = command.integerValueOfParameterNamed(countFrequencyTypeParamName);
actualChanges.put(countFrequencyTypeParamName, countFrequencyTypeNewValue);
this.restartFrequencyEnum = ChargeTimeType.fromInt(countFrequencyTypeNewValue).getValue();
}
final String enableFreeWithdrawalChargeParamName = "enableFreeWithdrawalCharge";
if (command.isChangeInBooleanParameterNamed(enableFreeWithdrawalChargeParamName, this.enableFreeWithdrawal)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enableFreeWithdrawalChargeParamName);
actualChanges.put(enableFreeWithdrawalChargeParamName, newValue);
this.enableFreeWithdrawal = newValue;
}
final String chargeAppliesToParamName = "chargeAppliesTo";
if (command.isChangeInIntegerParameterNamed(chargeAppliesToParamName, this.chargeAppliesTo)) {
/*
@@ -531,8 +615,9 @@ public class Charge extends AbstractPersistableCustom {
final CurrencyData currency = new CurrencyData(this.currencyCode, null, 0, 0, null, null);
return ChargeData.instance(getId(), this.name, this.amount, currency, chargeTimeType, chargeAppliesTo, chargeCalculationType,
chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.minCap, this.maxCap,
feeFrequencyType, accountData, taxGroupData);
chargePaymentmode, getFeeOnMonthDay(), this.feeInterval, this.penalty, this.active, this.enableFreeWithdrawal,
this.freeWithdrawalFrequency, this.restartFrequency, this.restartFrequencyEnum, this.minCap, this.maxCap, feeFrequencyType,
accountData, taxGroupData);
}
public Integer getChargePaymentMode() {
@@ -54,6 +54,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name", "amount", "locale", "currencyCode",
"currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty",
"active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency",
"enableFreeWithdrawalCharge", "freeWithdrawalFrequency", "restartCountFrequency", "countFrequencyType",
ChargesApiConstants.glAccountIdParamName, ChargesApiConstants.taxGroupIdParamName));
private final FromJsonHelper fromApiJsonHelper;
@@ -91,6 +92,28 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
final Integer feeFrequency = this.fromApiJsonHelper.extractIntegerNamed("feeFrequency", element, Locale.getDefault());
baseDataValidator.reset().parameter("feeFrequency").value(feeFrequency).inMinMaxRange(0, 3);
if (this.fromApiJsonHelper.parameterExists("enableFreeWithdrawalCharge", element)) {
final Boolean enableFreeWithdrawalCharge = this.fromApiJsonHelper.extractBooleanNamed("enableFreeWithdrawalCharge", element);
baseDataValidator.reset().parameter("enableFreeWithdrawalCharge").value(enableFreeWithdrawalCharge).notNull();
if (enableFreeWithdrawalCharge) {
final Integer freeWithdrawalFrequency = this.fromApiJsonHelper.extractIntegerNamed("freeWithdrawalFrequency", element,
Locale.getDefault());
baseDataValidator.reset().parameter("freeWithdrawalFrequency").value(freeWithdrawalFrequency).integerGreaterThanZero();
final Integer restartCountFrequency = this.fromApiJsonHelper.extractIntegerNamed("restartCountFrequency", element,
Locale.getDefault());
baseDataValidator.reset().parameter("restartCountFrequency").value(restartCountFrequency).integerGreaterThanZero();
final Integer countFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("countFrequencyType", element,
Locale.getDefault());
baseDataValidator.reset().parameter("countFrequencyType").value(countFrequencyType);
}
}
if (feeFrequency != null) {
baseDataValidator.reset().parameter("feeInterval").value(feeInterval).notNull();
}
@@ -155,6 +178,7 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
.isOneOfTheseValues(ChargeCalculationType.validValuesForSavings());
}
} else if (appliesTo.isClientCharge()) {
// client applicable validation
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
@@ -277,6 +301,39 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
.isOneOfTheseValues(ChargeAppliesTo.validValues());
}
Boolean enableFreeWithdrawalCharge = false;
if (this.fromApiJsonHelper.parameterExists("enableFreeWithdrawalCharge", element)) {
enableFreeWithdrawalCharge = this.fromApiJsonHelper.extractBooleanNamed("enableFreeWithdrawalCharge", element);
baseDataValidator.reset().parameter("enableFreeWithdrawalCharge").value(enableFreeWithdrawalCharge).notNull();
if (enableFreeWithdrawalCharge) {
final Integer freeWithdrawalFrequency = this.fromApiJsonHelper.extractIntegerNamed("freeWithdrawalFrequency", element,
Locale.getDefault());
baseDataValidator.reset().parameter("freeWithdrawalFrequency").value(freeWithdrawalFrequency).integerGreaterThanZero();
final Integer restartCountFrequency = this.fromApiJsonHelper.extractIntegerNamed("restartCountFrequency", element,
Locale.getDefault());
baseDataValidator.reset().parameter("restartCountFrequency").value(restartCountFrequency).integerGreaterThanZero();
final Integer countFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("countFrequencyType", element,
Locale.getDefault());
baseDataValidator.reset().parameter("countFrequencyType").value(countFrequencyType);
}
}
if (this.fromApiJsonHelper.parameterExists("chargeAppliesTo", element)) {
final Integer chargeAppliesTo = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeAppliesTo", element);
baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull()
.isOneOfTheseValues(ChargeAppliesTo.validValues());
}
if (this.fromApiJsonHelper.parameterExists("chargeAppliesTo", element)) {
final Integer chargeAppliesTo = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeAppliesTo", element);
baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull()
.isOneOfTheseValues(ChargeAppliesTo.validValues());
}
if (this.fromApiJsonHelper.parameterExists("chargeTimeType", element)) {
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed("chargeTimeType", element);
@@ -287,7 +287,8 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
+ "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, "
+ "c.is_active as active, c.is_free_withdrawal as isFreeWithdrawal, c.free_withdrawal_charge_frequency as freeWithdrawalChargeFrequency, c.restart_frequency as restartFrequency, c.restart_frequency_enum as restartFrequencyEnum,"
+ "oc.name as currencyName, oc.decimal_places as currencyDecimalPlaces, "
+ "oc.currency_multiplesof as inMultiplesOf, oc.display_symbol as currencyDisplaySymbol, "
+ "oc.internationalized_name_code as currencyNameCode, c.fee_on_day as feeOnDay, c.fee_on_month as feeOnMonth, "
+ "c.fee_interval as feeInterval, c.fee_frequency as feeFrequency,c.min_cap as minCap,c.max_cap as maxCap, "
@@ -372,9 +373,14 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName);
}
final boolean isFreeWithdrawal = rs.getBoolean("isFreeWithdrawal");
final int freeWithdrawalChargeFrequency = rs.getInt("freeWithdrawalChargeFrequency");
final int restartFrequency = rs.getInt("restartFrequency");
final int restartFrequencyEnum = rs.getInt("restartFrequencyEnum");
return ChargeData.instance(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType,
chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, minCap, maxCap, feeFrequencyType, glAccountData,
taxGroupData);
chargePaymentMode, feeOnMonthDay, feeInterval, penalty, active, isFreeWithdrawal, freeWithdrawalChargeFrequency,
restartFrequency, restartFrequencyEnum, minCap, maxCap, feeFrequencyType, glAccountData, taxGroupData);
}
}
@@ -42,6 +42,7 @@ import org.apache.fineract.portfolio.charge.exception.ChargeNotFoundException;
import org.apache.fineract.portfolio.charge.serialization.ChargeDefinitionCommandFromApiJsonDeserializer;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper;
import org.slf4j.Logger;
@@ -67,13 +68,14 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
private final FineractEntityAccessUtil fineractEntityAccessUtil;
private final GLAccountRepositoryWrapper glAccountRepository;
private final TaxGroupRepositoryWrapper taxGroupRepository;
private final PaymentTypeRepositoryWrapper paymentTyperepositoryWrapper;
@Autowired
public ChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final ChargeDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer, final ChargeRepository chargeRepository,
final LoanProductRepository loanProductRepository, final RoutingDataSource dataSource,
final FineractEntityAccessUtil fineractEntityAccessUtil, final GLAccountRepositoryWrapper glAccountRepository,
final TaxGroupRepositoryWrapper taxGroupRepository) {
final TaxGroupRepositoryWrapper taxGroupRepository, final PaymentTypeRepositoryWrapper paymentTyperepositoryWrapper) {
this.context = context;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.dataSource = dataSource;
@@ -83,6 +85,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
this.fineractEntityAccessUtil = fineractEntityAccessUtil;
this.glAccountRepository = glAccountRepository;
this.taxGroupRepository = taxGroupRepository;
this.paymentTyperepositoryWrapper = paymentTyperepositoryWrapper;
}
@Transactional
@@ -74,6 +74,14 @@ public class SavingsAccountChargeData implements Serializable {
private final Boolean isActive;
private final Boolean isFreeWithdrawal;
private final Integer freeWithdrawalChargeFrequency;
private final Integer restartFrequency;
private final Integer restartFrequencyEnum;
private final LocalDate inactivationDate;
private final Collection<ChargeData> chargeOptions;
@@ -101,6 +109,10 @@ public class SavingsAccountChargeData implements Serializable {
this.isActive = null;
this.inactivationDate = null;
this.chargeOptions = null;
this.isFreeWithdrawal = null;
this.freeWithdrawalChargeFrequency = null;
this.restartFrequency = null;
this.restartFrequencyEnum = null;
}
public static SavingsAccountChargeData template(final Collection<ChargeData> chargeOptions) {
@@ -123,11 +135,17 @@ public class SavingsAccountChargeData implements Serializable {
final MonthDay feeOnMonthDay = null;
final Integer feeInterval = null;
final Boolean isActive = null;
final Boolean isFreeWithdrawal = null;
final Integer freeWithdrawalChargeFrequency = null;
final Integer restartFrequency = null;
final Integer restartFrequencyEnum = null;
final LocalDate inactivationDate = null;
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate);
penalty, feeOnMonthDay, feeInterval, isActive, isFreeWithdrawal, freeWithdrawalChargeFrequency, restartFrequency,
restartFrequencyEnum, inactivationDate);
}
public static SavingsAccountChargeData instance(final Long id, final Long chargeId, final Long accountId, final String name,
@@ -135,11 +153,14 @@ public class SavingsAccountChargeData implements Serializable {
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 Integer feeInterval, final Boolean isActive, final LocalDate inactivationDate) {
final MonthDay feeOnMonthDay, final Integer feeInterval, final Boolean isActive, final Boolean isFreeWithdrawal,
final Integer freeWithdrawalChargeFrequency, final Integer restartFrequency, final Integer restartFrequencyEnum,
final LocalDate inactivationDate) {
return new SavingsAccountChargeData(id, chargeId, accountId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions,
penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate);
penalty, feeOnMonthDay, feeInterval, isActive, isFreeWithdrawal, freeWithdrawalChargeFrequency, restartFrequency,
restartFrequencyEnum, inactivationDate);
}
private SavingsAccountChargeData(final Long id, final Long chargeId, final Long accountId, final String name,
@@ -147,7 +168,9 @@ public class SavingsAccountChargeData implements Serializable {
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 Integer feeInterval, final Boolean isActive, final LocalDate inactivationDate) {
final MonthDay feeOnMonthDay, final Integer feeInterval, final Boolean isActive, final Boolean isFreeWithdrawal,
final Integer freeWithdrawalChargeFrequency, final Integer restartFrequency, final Integer restartFrequencyEnum,
final LocalDate inactivationDate) {
this.id = id;
this.chargeId = chargeId;
this.accountId = accountId;
@@ -170,6 +193,10 @@ public class SavingsAccountChargeData implements Serializable {
this.feeInterval = feeInterval;
this.isActive = isActive;
this.inactivationDate = inactivationDate;
this.isFreeWithdrawal = isFreeWithdrawal;
this.freeWithdrawalChargeFrequency = freeWithdrawalChargeFrequency;
this.restartFrequency = restartFrequency;
this.restartFrequencyEnum = restartFrequencyEnum;
}
private BigDecimal getAmountOrPercentage() {
@@ -36,6 +36,7 @@ import com.google.gson.JsonArray;
import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -50,6 +51,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
@@ -92,6 +94,7 @@ import org.apache.fineract.portfolio.charge.exception.SavingsAccountChargeNotFou
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
@@ -1038,7 +1041,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (applyWithdrawFee) {
// auto pay withdrawal fee
payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser());
payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser(),
transactionDTO.getPaymentDetail());
}
final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount());
@@ -1066,15 +1070,99 @@ public class SavingsAccount extends AbstractPersistableCustom {
return result;
}
private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final AppUser user) {
private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final AppUser user,
final PaymentDetail paymentDetail) {
for (SavingsAccountCharge charge : this.charges()) {
if (charge.isWithdrawalFee() && charge.isActive()) {
charge.updateWithdralFeeAmount(transactionAmount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user);
if (charge.getFreeWithdrawalCount() == null) {
charge.setFreeWithdrawalCount(0);
}
if (charge.isEnableFreeWithdrawal()) {
resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user);
} else {
charge.updateWithdralFeeAmount(transactionAmount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user);
}
}
}
}
private void resetFreeChargeDaysCount(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate,
final AppUser user) {
Date resetDate = charge.getResetChargeDate();
Integer restartPeriod = charge.getRestartFrequency();
if (charge.getRestartFrequencyEnum() == 2) { // calculation for months
Date currentDate = new Date();
LocalDate localDate = currentDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
Integer currentMonth = localDate.getMonthValue();
LocalDate resetLocalDate = null;
if (resetDate == null) {
resetLocalDate = this.activatedOnDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
} else {
resetLocalDate = resetDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
Integer resetMonth = resetLocalDate.getMonthValue();
LocalDate gapIntervalMonth = resetLocalDate.plusMonths(restartPeriod);
Integer gapMonth = gapIntervalMonth.getMonthValue();
Integer resetYear = resetLocalDate.getYear();
Integer currentYear = localDate.getYear();
YearMonth gapYearMonth = YearMonth.from(gapIntervalMonth);
YearMonth localYearMonth = YearMonth.from(localDate);
if (localYearMonth.isBefore(gapYearMonth)) {
countValidation(charge, transactionAmount, transactionDate, user);
} else {
discountCharge(1, charge);
}
} else { // calculation for days
Long completedDays = null;
Long days = null;
if (resetDate == null) {
completedDays = DateUtils.getDateOfTenant().getTime() - this.activatedOnDate.getTime();
days = TimeUnit.DAYS.convert(completedDays, TimeUnit.MILLISECONDS);
} else {
completedDays = DateUtils.getDateOfTenant().getTime() - resetDate.getTime();
days = TimeUnit.DAYS.convert(completedDays, TimeUnit.MILLISECONDS);
}
Integer totalDays = days.intValue();
if (totalDays < restartPeriod) {
countValidation(charge, transactionAmount, transactionDate, user);
} else {
discountCharge(1, charge);
}
}
}
private void countValidation(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate,
final AppUser user) {
if (charge.getFreeWithdrawalCount() < charge.getFrequencyFreeWithdrawalCharge()) {
final Integer count = charge.getFreeWithdrawalCount() + 1;
charge.setFreeWithdrawalCount(count);
charge.updateNoWithdrawalFee();
} else {
charge.updateWithdralFeeAmount(transactionAmount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user);
}
}
private void discountCharge(Integer freeWithdrawalCount, SavingsAccountCharge charge) {
charge.setFreeWithdrawalCount(freeWithdrawalCount);
charge.setDiscountDueDate(DateUtils.getDateOfTenant());
charge.updateNoWithdrawalFee();
}
public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate) {
boolean transactionBeforeLastInterestPosting = false;
@@ -94,6 +94,13 @@ public class SavingsAccountCharge extends AbstractPersistableCustom {
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@Column(name = "free_withdrawal_count", nullable = true)
private Integer freeWithdrawalCount;
@Temporal(TemporalType.DATE)
@Column(name = "charge_reset_date", nullable = true)
private Date chargeResetDate;
@Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true)
private BigDecimal percentage;
@@ -666,6 +673,38 @@ public class SavingsAccountCharge extends AbstractPersistableCustom {
return this.charge;
}
public boolean isEnableFreeWithdrawal() {
return charge.isEnableFreeWithdrawal();
}
public Integer getFrequencyFreeWithdrawalCharge() { // number of times free withdrawal allowed
return charge.getFrequencyFreeWithdrawalCharge();
}
public Integer getRestartFrequency() { // numeric value of which numeric-period, count should restart
return charge.getRestartFrequency();
}
public Integer getRestartFrequencyEnum() { // enum day/week/month for restarting the count.
return charge.getRestartFrequencyEnum();
}
public Integer getFreeWithdrawalCount() {
return freeWithdrawalCount;
}
public void setFreeWithdrawalCount(Integer freeWithdrawalCount) {
this.freeWithdrawalCount = freeWithdrawalCount;
}
public Date getResetChargeDate() {
return chargeResetDate;
}
public void setDiscountDueDate(final Date date) {
this.chargeResetDate = date;
}
public SavingsAccount savingsAccount() {
return this.savingsAccount;
}
@@ -758,6 +797,10 @@ public class SavingsAccountCharge extends AbstractPersistableCustom {
return amountOutstanding = calculateWithdralFeeAmount(transactionAmount);
}
public BigDecimal updateNoWithdrawalFee() {
return amountOutstanding = BigDecimal.ZERO;
}
public void updateToNextDueDateFrom(final LocalDate startingDate) {
if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) {
this.dueDate = Date.from(getNextDueDateFrom(startingDate).atStartOfDay(ZoneId.systemDefault()).toInstant());
@@ -82,6 +82,7 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
+ "sc.fee_on_month as feeOnMonth, " + "sc.fee_on_day as feeOnDay, sc.fee_interval as feeInterval, "
+ "sc.charge_calculation_enum as chargeCalculation, "
+ "sc.is_active as isActive, sc.inactivated_on_date as inactivationDate, "
+ "c.is_free_withdrawal as isFreeWithdrawal, c.free_withdrawal_charge_frequency as freeWithdrawalChargeFrequency, c.restart_frequency as restartFrequency, c.restart_frequency_enum as restartFrequencyEnum, "
+ "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.internationalized_name_code as currencyNameCode from m_charge c "
@@ -133,11 +134,17 @@ public class SavingsAccountChargeReadPlatformServiceImpl implements SavingsAccou
final Boolean isActive = rs.getBoolean("isActive");
final LocalDate inactivationDate = JdbcSupport.getLocalDate(rs, "inactivationDate");
final Boolean isFreeWithdrawal = rs.getBoolean("isFreeWithdrawal");
final Integer freeWithdrawalChargeFrequency = JdbcSupport.getInteger(rs, "freeWithdrawalChargeFrequency");
final Integer restartFrequency = JdbcSupport.getInteger(rs, "restartFrequency");
final Integer restartFrequencyEnum = JdbcSupport.getInteger(rs, "restartFrequencyEnum");
final Collection<ChargeData> chargeOptions = null;
return SavingsAccountChargeData.instance(id, chargeId, accountId, name, currency, amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf,
amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay, feeInterval, isActive, inactivationDate);
amountPercentageAppliedTo, chargeOptions, penalty, feeOnMonthDay, feeInterval, isActive, isFreeWithdrawal,
freeWithdrawalChargeFrequency, restartFrequency, restartFrequencyEnum, inactivationDate);
}
}
@@ -1025,6 +1025,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewFromJson(savingsAccount, chargeDefinition, command);
if (chargeDefinition.isEnableFreeWithdrawal()) {
savingsAccountCharge.setFreeWithdrawalCount(0);
}
if (savingsAccountCharge.getDueLocalDate() != null) {
// transaction date should not be on a holiday or non working day
if (!this.configurationDomainService.allowTransactionsOnHolidayEnabled()
@@ -0,0 +1,40 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
--
ALTER TABLE `m_charge` ADD COLUMN `is_free_withdrawal` INT NOT NULL DEFAULT '0' AFTER `fee_frequency`;
ALTER TABLE `m_charge`
ADD COLUMN `free_withdrawal_charge_frequency` INT NULL DEFAULT '0' AFTER `is_free_withdrawal`;
ALTER TABLE `m_charge`
ADD COLUMN `restart_frequency` INT NULL DEFAULT '0' AFTER `free_withdrawal_charge_frequency`;
ALTER TABLE `m_charge`
ADD COLUMN `restart_frequency_enum` INT NULL DEFAULT '0' AFTER `restart_frequency`;
ALTER TABLE `m_savings_account_charge`
ADD COLUMN `free_withdrawal_count` INT NULL DEFAULT '0' AFTER `fee_interval`;
ALTER TABLE `m_savings_account_charge`
ADD COLUMN `charge_reset_date` DATE NULL DEFAULT NULL AFTER `free_withdrawal_count`;