This is an automated email from the ASF dual-hosted git repository.

ptuomola pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 4a4cbf1  FINERACT-1374:Withdrawal-Fee-Scheme
4a4cbf1 is described below

commit 4a4cbf11be00b651d29937e774544282b73d5c09
Author: rrpawar96 <[email protected]>
AuthorDate: Fri Oct 29 16:24:40 2021 +0530

    FINERACT-1374:Withdrawal-Fee-Scheme
---
 .../fineract/portfolio/charge/data/ChargeData.java | 77 +++++++++++------
 .../fineract/portfolio/charge/domain/Charge.java   | 93 ++++++++++++++++++++-
 ...geDefinitionCommandFromApiJsonDeserializer.java | 57 +++++++++++++
 .../service/ChargeReadPlatformServiceImpl.java     | 12 ++-
 ...hargeWritePlatformServiceJpaRepositoryImpl.java |  5 +-
 .../savings/data/SavingsAccountChargeData.java     | 35 +++++++-
 .../portfolio/savings/domain/SavingsAccount.java   | 96 +++++++++++++++++++++-
 .../savings/domain/SavingsAccountCharge.java       | 43 ++++++++++
 ...avingsAccountChargeReadPlatformServiceImpl.java |  9 +-
 ...countWritePlatformServiceJpaRepositoryImpl.java |  4 +
 .../V271_1__withdrawal_charges_discount.sql        | 40 +++++++++
 11 files changed, 428 insertions(+), 43 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java
index 684d88e..d070c3a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/data/ChargeData.java
@@ -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() {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
index 2078802..670db81 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/domain/Charge.java
@@ -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() {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
index 1fa940a..e5d35e8 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/serialization/ChargeDefinitionCommandFromApiJsonDeserializer.java
@@ -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);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
index 9bf0703..26f46ea 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeReadPlatformServiceImpl.java
@@ -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);
         }
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java
index 9a611de..22959c0 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/service/ChargeWritePlatformServiceJpaRepositoryImpl.java
@@ -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
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeData.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeData.java
index 4ea79eb..715f3f4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeData.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountChargeData.java
@@ -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() {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
index 3ffbc25..bc0301e 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -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;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java
index 0ae6e4b..f0e8401 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java
@@ -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());
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java
index 9416d74..0d68fc0 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountChargeReadPlatformServiceImpl.java
@@ -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);
         }
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
index b3f0b1f..e03b897 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -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()
diff --git 
a/fineract-provider/src/main/resources/sql/migrations/core_db/V271_1__withdrawal_charges_discount.sql
 
b/fineract-provider/src/main/resources/sql/migrations/core_db/V271_1__withdrawal_charges_discount.sql
new file mode 100644
index 0000000..76c0db1
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/sql/migrations/core_db/V271_1__withdrawal_charges_discount.sql
@@ -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`;

Reply via email to