diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java index 747acb3fa..c9949e16f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java @@ -401,8 +401,8 @@ public class InteropServiceImpl implements InteropService { PaymentDetail paymentDetail = instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null); SavingsAccountTransaction holdTransaction = SavingsAccountTransaction.holdAmount(savingsAccount, savingsAccount.office(), - paymentDetail, transactionDate.toLocalDate(), Money.of(savingsAccount.getCurrency(), total), new Date(), - getLoginUser()); + paymentDetail, transactionDate.toLocalDate(), Money.of(savingsAccount.getCurrency(), total), new Date(), getLoginUser(), + false); MonetaryCurrency accountCurrency = savingsAccount.getCurrency().copy(); holdTransaction.updateRunningBalance( Money.of(accountCurrency, savingsAccount.getWithdrawableBalance().subtract(holdTransaction.getAmount()))); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java index 4dd397368..4fe8d64b1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java @@ -135,6 +135,8 @@ public class SavingsApiConstants { public static final String minOverdraftForInterestCalculationParamName = "minOverdraftForInterestCalculation"; public static final String minRequiredBalanceParamName = "minRequiredBalance"; public static final String enforceMinRequiredBalanceParamName = "enforceMinRequiredBalance"; + public static final String maxAllowedLienLimitParamName = "maxAllowedLienLimit"; + public static final String lienAllowedParamName = "lienAllowed"; public static final String minBalanceForInterestCalculationParamName = "minBalanceForInterestCalculation"; public static final String withdrawBalanceParamName = "withdrawBalance"; public static final String onHoldFundsParamName = "onHoldFunds"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index 79a767caa..a9c8161a2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -18,6 +18,11 @@ */ package org.apache.fineract.portfolio.savings.api; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Collection; import javax.ws.rs.Consumes; @@ -130,6 +135,9 @@ public class SavingsAccountTransactionsApiResource { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountTransactionsRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountTransactionsResponse.class))) }) public String transaction(@PathParam("savingsId") final Long savingsId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { try { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java new file mode 100644 index 000000000..96e2a5347 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java @@ -0,0 +1,60 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.savings.api; + +import io.swagger.v3.oas.annotations.media.Schema; + +final class SavingsAccountTransactionsApiResourceSwagger { + + private SavingsAccountTransactionsApiResourceSwagger() {} + + @Schema(description = "PostSavingsAccountTransactionsRequest") + public static final class PostSavingsAccountTransactionsRequest { + + private PostSavingsAccountTransactionsRequest() {} + + @Schema(example = "27 March 2022") + public String transactionDate; + @Schema(example = "1000") + public Integer transactionAmount; + @Schema(example = "en") + public String locale; + @Schema(example = "dd MMMM yyyy") + public String dateFormat; + @Schema(example = "true") + public String lienAllowed; + @Schema(example = "String") + public String reasonForBlock; + } + + @Schema(description = "PostSavingsAccountTransactionsResponse") + public static final class PostSavingsAccountTransactionsResponse { + + private PostSavingsAccountTransactionsResponse() {} + + @Schema(example = "1") + public Integer officeId; + @Schema(example = "1") + public Integer clientId; + @Schema(example = "1") + public Integer savingsId; + @Schema(example = "1") + public Integer resourceId; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java index d01f4dc0a..57727c27d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java @@ -135,7 +135,7 @@ public class SavingsProductsApiResource { @Operation(summary = "Create a Savings Product", description = "Creates a Savings Product\n\n" + "Mandatory Fields: name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf, nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType,accountingRule\n\n" + "Mandatory Fields for Cash based accounting (accountingRule = 2): savingsReferenceAccountId, savingsControlAccountId, interestOnSavingsAccountId, incomeFromFeeAccountId, transfersInSuspenseAccountId, incomeFromPenaltyAccountId\n\n" - + "Optional Fields: minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation,withHoldTax,taxGroupId,accountMapping") + + "Optional Fields: minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation,withHoldTax,taxGroupId,accountMapping, lienAllowed, maxAllowedLienLimit") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.PostSavingsProductsRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.PostSavingsProductsResponse.class))) }) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountConstant.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountConstant.java index e7cbb41ed..0809ee9a1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountConstant.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountConstant.java @@ -37,9 +37,9 @@ public class SavingsAccountConstant extends SavingsApiConstants { lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, // withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName, chargesParamName, allowOverdraftParamName, - overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, - nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, withHoldTaxParamName, datatables, - gsimApplicationId, gsimLastApplication)); + overdraftLimitParamName, minRequiredBalanceParamName, enforceMinRequiredBalanceParamName, lienAllowedParamName, + maxAllowedLienLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, + withHoldTaxParamName, datatables, gsimApplicationId, gsimLastApplication)); /** * These parameters will match the class level parameters of {@link SavingsAccountData}. Where possible, we try to diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java index e42cfc978..dbc354737 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java @@ -80,6 +80,8 @@ public final class SavingsAccountData implements Serializable { private final BigDecimal overdraftLimit; private final BigDecimal minRequiredBalance; private final boolean enforceMinRequiredBalance; + private final BigDecimal maxAllowedLienLimit; + private final boolean lienAllowed; private final BigDecimal minBalanceForInterestCalculation; private final BigDecimal onHoldFunds; private final boolean withHoldTax; @@ -191,6 +193,8 @@ public final class SavingsAccountData implements Serializable { this.overdraftLimit = overdraftLimit; this.minRequiredBalance = null; this.enforceMinRequiredBalance = false; + this.maxAllowedLienLimit = null; + this.lienAllowed = false; this.minBalanceForInterestCalculation = null; this.onHoldFunds = null; this.withHoldTax = false; @@ -546,6 +550,8 @@ public final class SavingsAccountData implements Serializable { this.overdraftLimit = overdraftLimit; this.minRequiredBalance = null; this.enforceMinRequiredBalance = false; + this.maxAllowedLienLimit = null; + this.lienAllowed = false; this.minBalanceForInterestCalculation = null; this.onHoldFunds = null; this.withHoldTax = false; @@ -594,11 +600,11 @@ public final class SavingsAccountData implements Serializable { final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, final SavingsAccountSummaryData summary, final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, - final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, - final boolean withHoldTax, final TaxGroupData taxGroup, final LocalDate lastActiveTransactionDate, - final boolean isDormancyTrackingActive, final Integer daysToInactive, final Integer daysToDormancy, final Integer daysToEscheat, - final BigDecimal savingsAmountOnHold) { + final BigDecimal maxAllowedLienLimit, final boolean lienAllowed, final BigDecimal minBalanceForInterestCalculation, + final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft, + final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, + final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive, + final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) { final Collection productOptions = null; final Collection fieldOfficerOptions = null; @@ -620,9 +626,9 @@ public final class SavingsAccountData implements Serializable { transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, - minOverdraftForInterestCalculation, withHoldTax, taxGroup, lastActiveTransactionDate, isDormancyTrackingActive, - daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); + enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, lastActiveTransactionDate, + isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) { @@ -659,6 +665,8 @@ public final class SavingsAccountData implements Serializable { final BigDecimal minOverdraftForInterestCalculation = null; final BigDecimal minRequiredBalance = null; final boolean enforceMinRequiredBalance = false; + final BigDecimal maxAllowedLienLimit = null; + final boolean lienAllowed = false; final BigDecimal minBalanceForInterestCalculation = null; final BigDecimal onHoldFunds = null; @@ -694,9 +702,9 @@ public final class SavingsAccountData implements Serializable { withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, - minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, lastActiveTransactionDate, - isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); + minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, + onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo, @@ -731,6 +739,8 @@ public final class SavingsAccountData implements Serializable { final BigDecimal minOverdraftForInterestCalculation = null; final BigDecimal minRequiredBalance = null; final boolean enforceMinRequiredBalance = false; + final BigDecimal maxAllowedLienLimit = null; + final boolean lienAllowed = false; final BigDecimal minBalanceForInterestCalculation = null; final BigDecimal onHoldFunds = null; @@ -766,9 +776,9 @@ public final class SavingsAccountData implements Serializable { withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, - minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, lastActiveTransactionDate, - isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); + minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, + onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, @@ -801,10 +811,10 @@ public final class SavingsAccountData implements Serializable { template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions, template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions, account.allowOverdraft, account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance, - account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, - account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate, - account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat, - account.savingsAmountOnHold); + account.maxAllowedLienLimit, account.lienAllowed, account.minBalanceForInterestCalculation, account.onHoldFunds, + account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.withHoldTax, + account.taxGroup, account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive, + account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, @@ -827,10 +837,11 @@ public final class SavingsAccountData implements Serializable { interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, account.allowOverdraft, account.overdraftLimit, account.minRequiredBalance, - account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation, account.onHoldFunds, - account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.withHoldTax, - account.taxGroup, account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive, - account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold); + account.enforceMinRequiredBalance, account.maxAllowedLienLimit, account.lienAllowed, + account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, + account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate, + account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat, + account.savingsAmountOnHold); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -867,6 +878,8 @@ public final class SavingsAccountData implements Serializable { final BigDecimal minOverdraftForInterestCalculation = null; final BigDecimal minRequiredBalance = null; final boolean enforceMinRequiredBalance = false; + final BigDecimal maxAllowedLienLimit = null; + final boolean lienAllowed = false; final BigDecimal minBalanceForInterestCalculation = null; final BigDecimal onHoldFunds = null; final boolean withHoldTax = false; @@ -902,9 +915,9 @@ public final class SavingsAccountData implements Serializable { withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, - minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, lastActiveTransactionDate, - isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); + minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, + onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId, @@ -924,11 +937,11 @@ public final class SavingsAccountData implements Serializable { final Collection lockinPeriodFrequencyTypeOptions, final Collection withdrawalFeeTypeOptions, final Collection charges, final Collection chargeOptions, final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, - final BigDecimal minBalanceForInterestCalculation, final BigDecimal onHoldFunds, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, - final boolean withHoldTax, final TaxGroupData taxGroup, final LocalDate lastActiveTransactionDate, - final boolean isDormancyTrackingActive, final Integer daysToInactive, final Integer daysToDormancy, final Integer daysToEscheat, - final BigDecimal savingsAmountOnHold) { + final BigDecimal maxAllowedLienLimit, final boolean lienAllowd, final BigDecimal minBalanceForInterestCalculation, + final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft, + final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, + final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive, + final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) { this.id = id; this.accountNo = accountNo; this.depositType = depositType; @@ -986,6 +999,8 @@ public final class SavingsAccountData implements Serializable { this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation; this.minRequiredBalance = minRequiredBalance; this.enforceMinRequiredBalance = enforceMinRequiredBalance; + this.maxAllowedLienLimit = maxAllowedLienLimit; + this.lienAllowed = lienAllowd; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; this.onHoldFunds = onHoldFunds; this.withHoldTax = withHoldTax; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java index a1bf9bf49..b43c78bc5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountDataValidator.java @@ -33,8 +33,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interest import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName; @@ -60,6 +62,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; @@ -221,7 +224,7 @@ public class SavingsAccountDataValidator { validateSavingsCharges(element, baseDataValidator); validateOverdraftParams(baseDataValidator, element); - + validateLienParams(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -415,6 +418,29 @@ public class SavingsAccountDataValidator { } } + private void validateLienParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + if (this.fromApiJsonHelper.parameterExists(lienAllowedParamName, element)) { + final Boolean lienAllowed = this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element); + baseDataValidator.reset().parameter(lienAllowedParamName).value(lienAllowed).ignoreIfNull().validateForBooleanValue(); + } + + if (BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element))) { + final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, + element); + baseDataValidator.reset().parameter(maxAllowedLienLimitParamName).value(maxAllowedLienLimit).ignoreIfNull() + .zeroOrPositiveAmount(); + } + if (BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element)) + && BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element))) { + final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, + element); + final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element); + if (overdraftLimit.compareTo(maxAllowedLienLimit) > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("Overdraft.limit.can.not.be.greater.than.lien.limit"); + } + } + } + private void validateOverdraftParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) { if (this.fromApiJsonHelper.parameterExists(allowOverdraftParamName, element)) { final Boolean allowOverdraft = this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java index 3338635a8..5afb3fb71 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java @@ -73,6 +73,7 @@ public final class SavingsAccountTransactionData implements Serializable { private final boolean isManualTransaction; private final Boolean isReversal; private final Long originalTransactionId; + private final Boolean lienTransaction; private final Long releaseTransactionId; private final String reasonForBlock; private Set chargesPaidByData = new HashSet<>(); @@ -105,29 +106,31 @@ public final class SavingsAccountTransactionData implements Serializable { String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId, SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat) { return new SavingsAccountTransactionData(transactionAmount, transactionDate, paymentTypeId, accountNumber, checkNumber, routingCode, - receiptNumber, bankNumber, savingsAccountId, transactionType, rowIndex, locale, dateFormat); + receiptNumber, bankNumber, savingsAccountId, transactionType, rowIndex, locale, dateFormat, false); } public static SavingsAccountTransactionData interestPosting(final SavingsAccountData savingsAccount, final LocalDate date, final Money amount, final boolean isManualTransaction) { final boolean isReversed = false; + final Boolean lienTransaction = false; final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.INTEREST_POSTING; SavingsAccountTransactionEnumData savingsAccountTransactionEnumData = new SavingsAccountTransactionEnumData( savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(), savingsAccountTransactionType.getValue().toString()); return new SavingsAccountTransactionData(amount.getAmount(), date, savingsAccount.getId(), savingsAccountTransactionEnumData, - isReversed, null, isManualTransaction); + isReversed, null, isManualTransaction, lienTransaction); } public static SavingsAccountTransactionData overdraftInterest(final SavingsAccountData savingsAccount, final LocalDate date, final Money amount, final boolean isManualTransaction) { final boolean isReversed = false; + final Boolean lienTransaction = false; final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.OVERDRAFT_INTEREST; SavingsAccountTransactionEnumData savingsAccountTransactionEnumData = new SavingsAccountTransactionEnumData( savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(), savingsAccountTransactionType.getValue().toString()); return new SavingsAccountTransactionData(amount.getAmount(), date, savingsAccount.getId(), savingsAccountTransactionEnumData, - isReversed, null, isManualTransaction); + isReversed, null, isManualTransaction, lienTransaction); } public List getTaxDetails() { @@ -311,12 +314,13 @@ public final class SavingsAccountTransactionData implements Serializable { return new SavingsAccountTransactionData(accountTransaction.getSavingsAccountId(), null, accountTransaction.getPaymentDetailData(), accountTransaction.getTransactionType(), accountTransaction.getTransactionLocalDate(), accountTransaction.getSubmittedOnDate(), accountTransaction.getAmount(), accountTransaction.isReversed(), null, - accountTransaction.isManualTransaction()); + accountTransaction.isManualTransaction(), accountTransaction.lienTransaction); } private SavingsAccountTransactionData(final Long savingsId, final Long officeId, final PaymentDetailData paymentDetailData, final SavingsAccountTransactionEnumData savingsAccountTransactionType, final LocalDate transactionDate, final Date createdDate, - final BigDecimal amount, final boolean isReversed, final Long userId, final boolean isManualTransaction) { + final BigDecimal amount, final boolean isReversed, final Long userId, final boolean isManualTransaction, + final Boolean lienTransaction) { this.savingsAccountId = savingsId; this.paymentDetailData = paymentDetailData; this.transactionType = savingsAccountTransactionType; @@ -324,6 +328,7 @@ public final class SavingsAccountTransactionData implements Serializable { this.submittedOnDate = createdDate; this.amount = amount; this.isManualTransaction = isManualTransaction; + this.lienTransaction = lienTransaction; this.id = null; this.accountId = null; this.accountNo = null; @@ -442,12 +447,13 @@ public final class SavingsAccountTransactionData implements Serializable { final Money amount, final Map taxDetails) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.WITHHOLD_TAX; SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData( savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(), savingsAccountTransactionType.getValue().toString()); SavingsAccountTransactionData accountTransaction = new SavingsAccountTransactionData(amount.getAmount(), date, - savingsAccount.getId(), transactionType, isReversed, null, isManualTransaction); + savingsAccount.getId(), transactionType, isReversed, null, isManualTransaction, lienTransaction); updateTaxDetails(taxDetails, accountTransaction); return accountTransaction; } @@ -515,7 +521,8 @@ public final class SavingsAccountTransactionData implements Serializable { } private SavingsAccountTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long savingsAccountId, - SavingsAccountTransactionEnumData transactionType, boolean isReversed, String locale, boolean isManualTransaction) { + SavingsAccountTransactionEnumData transactionType, boolean isReversed, String locale, boolean isManualTransaction, + final Boolean lienTransaction) { this.id = null; this.transactionType = transactionType; this.accountId = null; @@ -548,13 +555,15 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = isManualTransaction; this.isReversal = null; this.originalTransactionId = null; + this.lienTransaction = lienTransaction; this.releaseTransactionId = null; this.reasonForBlock = null; } private SavingsAccountTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId, - SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat) { + SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat, + final Boolean lienTransaction) { this.id = null; this.transactionType = transactionType; this.accountId = null; @@ -587,6 +596,7 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = null; this.originalTransactionId = null; + this.lienTransaction = lienTransaction; this.releaseTransactionId = null; this.reasonForBlock = null; } @@ -594,7 +604,7 @@ public final class SavingsAccountTransactionData implements Serializable { private SavingsAccountTransactionData(Integer id, BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId, SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat, - BigDecimal cumulativeBalance) { + BigDecimal cumulativeBalance, final Boolean lienTransaction) { this.id = null; this.transactionType = transactionType; this.accountId = null; @@ -628,6 +638,7 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = null; this.originalTransactionId = null; + this.lienTransaction = lienTransaction; this.releaseTransactionId = null; this.reasonForBlock = null; } @@ -721,24 +732,27 @@ public final class SavingsAccountTransactionData implements Serializable { final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount, runningBalance, reversed, submittedOnDate, paymentTypeOptions, interestedPostedAsOn, - cumulativeBalance, balanceEndDate); + cumulativeBalance, balanceEndDate, false); } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance, final boolean reversed, final LocalDate submittedOnDate, final Collection paymentTypeOptions, - final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate) { + final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate, + final Boolean lienTransaction) { this(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount, - runningBalance, reversed, paymentTypeOptions, submittedOnDate, interestedPostedAsOn, cumulativeBalance, balanceEndDate); + runningBalance, reversed, paymentTypeOptions, submittedOnDate, interestedPostedAsOn, cumulativeBalance, balanceEndDate, + lienTransaction); } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance, final boolean reversed, final Collection paymentTypeOptions, final LocalDate submittedOnDate, - final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate) { + final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate, + final Boolean lienTransaction) { this.id = id; this.transactionDate = date; this.transactionType = transactionType; @@ -762,6 +776,7 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = null; this.originalTransactionId = null; + this.lienTransaction = lienTransaction; this.releaseTransactionId = null; this.reasonForBlock = null; } @@ -774,7 +789,7 @@ public final class SavingsAccountTransactionData implements Serializable { final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount, runningBalance, reversed, transfer, paymentTypeOptions, interestedPostedAsOn, - submittedByUsername, note); + submittedByUsername, note, false); } public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType, @@ -782,17 +797,18 @@ public final class SavingsAccountTransactionData implements Serializable { final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance, final boolean reversed, final AccountTransferData transfer, final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note, final Boolean isReversal, final Long originalTransactionId, - final Long releaseTransactionId, final String reasonForBlock) { + final Boolean lienTransaction, final Long releaseTransactionId, final String reasonForBlock) { final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount, runningBalance, reversed, transfer, paymentTypeOptions, submittedOnDate, - interestedPostedAsOn, submittedByUsername, note, isReversal, originalTransactionId, releaseTransactionId, reasonForBlock); + interestedPostedAsOn, submittedByUsername, note, isReversal, originalTransactionId, lienTransaction, releaseTransactionId, + reasonForBlock); } public static SavingsAccountTransactionData create(final Long id) { final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, null, null, null, null, null, null, null, null, null, false, null, paymentTypeOptions, - null, false, null, null, null, null); + null, false, null, null, null, null, false, null, null); } public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo, @@ -808,9 +824,10 @@ public final class SavingsAccountTransactionData implements Serializable { final boolean interestedPostedAsOn = false; final String submittedByUsername = null; final String note = null; + final Boolean lienTransaction = false; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate, currency, amount, outstandingChargeAmount, runningBalance, reversed, null, null, interestedPostedAsOn, submittedByUsername, - note); + note, lienTransaction); } public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData, @@ -821,17 +838,19 @@ public final class SavingsAccountTransactionData implements Serializable { savingsAccountTransactionData.amount, savingsAccountTransactionData.outstandingChargeAmount, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed, savingsAccountTransactionData.transfer, paymentTypeOptions, savingsAccountTransactionData.interestedPostedAsOn, - savingsAccountTransactionData.submittedByUsername, savingsAccountTransactionData.note); + savingsAccountTransactionData.submittedByUsername, savingsAccountTransactionData.note, + savingsAccountTransactionData.lienTransaction); } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance, final boolean reversed, final AccountTransferData transfer, final Collection paymentTypeOptions, - final boolean interestedPostedAsOn, final String submittedByUsername, final String note) { + final boolean interestedPostedAsOn, final String submittedByUsername, final String note, final Boolean lienTransaction) { this(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount, - runningBalance, reversed, transfer, paymentTypeOptions, date, interestedPostedAsOn, submittedByUsername, note, null, null); + runningBalance, reversed, transfer, paymentTypeOptions, date, interestedPostedAsOn, submittedByUsername, note, null, null, + lienTransaction, null, null); } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, @@ -839,7 +858,8 @@ public final class SavingsAccountTransactionData implements Serializable { final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance, final boolean reversed, final AccountTransferData transfer, final Collection paymentTypeOptions, final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note, - final Boolean isReversal, final Long originalTransactionId, final Long releaseTransactionId, final String reasonForBlock) { + final Boolean isReversal, final Long originalTransactionId, final Boolean lienTransaction, final Long releaseTransactionId, + final String reasonForBlock) { this.id = id; this.transactionType = transactionType; this.paymentDetailData = paymentDetailData; @@ -865,6 +885,7 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = isReversal; this.originalTransactionId = originalTransactionId; + this.lienTransaction = lienTransaction; this.releaseTransactionId = releaseTransactionId; this.reasonForBlock = reasonForBlock; } @@ -873,7 +894,8 @@ public final class SavingsAccountTransactionData implements Serializable { final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance, final boolean reversed, final AccountTransferData transfer, final Collection paymentTypeOptions, - final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance) { + final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, + final Boolean lienTransaction) { this.id = id; this.transactionType = transactionType; this.paymentDetailData = paymentDetailData; @@ -900,41 +922,7 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = null; this.originalTransactionId = null; - this.releaseTransactionId = null; - this.reasonForBlock = null; - } - - private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, - final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, - final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance, - final boolean reversed, final AccountTransferData transfer, final Collection paymentTypeOptions, - final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note, - final Boolean isReversal, final Long originalTransactionId) { - this.id = id; - this.transactionType = transactionType; - this.paymentDetailData = paymentDetailData; - this.accountId = savingsId; - this.accountNo = savingsAccountNo; - this.date = date; - this.currency = currency; - this.amount = amount; - this.outstandingChargeAmount = outstandingChargeAmount; - this.runningBalance = runningBalance; - this.reversed = reversed; - this.transfer = transfer; - this.paymentTypeOptions = paymentTypeOptions; - if (submittedOnDate != null) { - this.submittedOnDate = Date.from(submittedOnDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); - } else { - this.submittedOnDate = null; - } - - this.interestedPostedAsOn = interestedPostedAsOn; - this.submittedByUsername = submittedByUsername; - this.note = note; - this.isManualTransaction = false; - this.isReversal = isReversal; - this.originalTransactionId = originalTransactionId; + this.lienTransaction = lienTransaction; this.releaseTransactionId = null; this.reasonForBlock = null; } @@ -953,7 +941,7 @@ public final class SavingsAccountTransactionData implements Serializable { savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed, savingsAccountTransactionData.transfer, savingsAccountTransactionData.paymentTypeOptions, savingsAccountTransactionData.interestedPostedAsOn, savingsAccountTransactionData.submittedByUsername, - savingsAccountTransactionData.note); + savingsAccountTransactionData.note, savingsAccountTransactionData.lienTransaction); } public void setId(final Long id) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java index 86235bc9a..79a982986 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java @@ -23,7 +23,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.activate import static org.apache.fineract.portfolio.savings.SavingsApiConstants.bankNumberParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.checkNumberParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.closedOnDateParamName; -import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.paymentTypeIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.receiptNumberParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.routingCodeParamName; @@ -44,6 +44,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -53,8 +54,6 @@ import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; @@ -71,8 +70,7 @@ public class SavingsAccountTransactionDataValidator { private final FromJsonHelper fromApiJsonHelper; private static final Set SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>( Arrays.asList(transactionDateParamName, SavingsApiConstants.dateFormatParamName, SavingsApiConstants.localeParamName, - transactionAmountParamName, lienParamName, SavingsApiConstants.reasonForBlockParamName)); - + transactionAmountParamName, lienAllowedParamName, SavingsApiConstants.reasonForBlockParamName)); private final ConfigurationDomainService configurationDomainService; private final SavingsAccountAssembler savingAccountAssembler; @@ -217,7 +215,7 @@ public class SavingsAccountTransactionDataValidator { } - public SavingsAccountTransaction validateHoldAndAssembleForm(final String json, final SavingsAccount account, final AppUser createdUser, + public void validateHoldAndAssembleForm(final String json, final SavingsAccount account, final AppUser createdUser, final boolean backdatedTxnsAllowedTill) { if (StringUtils.isBlank(json)) { @@ -249,21 +247,43 @@ public class SavingsAccountTransactionDataValidator { .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); } - Boolean lien = false; - if (this.fromApiJsonHelper.parameterExists(lienParamName, element)) { - lien = this.fromApiJsonHelper.extractBooleanNamed(lienParamName, element); - } + Boolean isEnforceMinRequiredBalanceEnabled = account.getEnforceMinRequiredBalance(); + Boolean isAccountLienEnabled = account.isLienAllowed(); + Boolean isOverdraftEnabled = account.isAllowOverdraft(); - if (account.getEnforceMinRequiredBalance()) { - if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) { - if (!lien) { - if (account.getWithdrawableBalanceWithoutMinimumBalance().compareTo(account.getMinRequiredBalance()) < 0) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId()); + Boolean lienAllowed = false; + if (BooleanUtils.isTrue(fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element))) { + lienAllowed = this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element); + if (isAccountLienEnabled) { + if (isOverdraftEnabled) { + if (account.getOverdraftLimit().compareTo(account.getMaxAllowedLienLimit()) > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "Overdraft.limit.can.not.be.greater.than.lien.limit", account.getId()); } } + if (amount.compareTo(account.getMaxAllowedLienLimit()) > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("lien.limit.exceeded", account.getId()); + } + } else { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("lien.is.not.allowed.in.product.level", account.getId()); + } + } else { + if (isOverdraftEnabled) { + if (amount.compareTo(account.getWithdrawableBalance()) > 0 && amount.compareTo(account.getOverdraftLimit()) > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient.balance", account.getId()); + } + } + if (isEnforceMinRequiredBalanceEnabled) { + if (amount.compareTo(account.getWithdrawableBalance()) > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient.balance", account.getId()); + } + } + if (!isOverdraftEnabled && !isEnforceMinRequiredBalanceEnabled) { + if (amount.compareTo(account.getWithdrawableBalance()) > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient.balance", account.getId()); + } } } - LocalDate lastTransactionDate = null; if (!backdatedTxnsAllowedTill) { @@ -279,13 +299,6 @@ public class SavingsAccountTransactionDataValidator { } throwExceptionIfValidationWarningsExist(dataValidationErrors); - final PaymentDetail paymentDetails = null; - Date createdDate = new Date(); - - SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails, - transactionDate, Money.of(account.getCurrency(), amount), createdDate, createdUser); - - return transaction; } public SavingsAccountTransaction validateReleaseAmountAndAssembleForm(final SavingsAccountTransaction holdTransaction, @@ -312,7 +325,6 @@ public class SavingsAccountTransactionDataValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); Date createdDate = new Date(); LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); - SavingsAccountTransaction transaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate, createdUser); return transaction; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java index c9ae6b147..bed16288e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductData.java @@ -56,6 +56,8 @@ public final class SavingsProductData implements Serializable { private final BigDecimal overdraftLimit; private final BigDecimal minRequiredBalance; private final boolean enforceMinRequiredBalance; + private final BigDecimal maxAllowedLienLimit; + private final boolean lienAllowed; private final BigDecimal minBalanceForInterestCalculation; private final BigDecimal nominalAnnualInterestRateOverdraft; private final BigDecimal minOverdraftForInterestCalculation; @@ -124,6 +126,8 @@ public final class SavingsProductData implements Serializable { final BigDecimal overdraftLimit = null; final BigDecimal minRequiredBalance = null; final boolean enforceMinRequiredBalance = false; + final BigDecimal maxAllowedLienLimit = null; + final boolean lienAllowed = false; final BigDecimal minBalanceForInterestCalculation = null; final BigDecimal nominalAnnualInterestRateOverdraft = null; final BigDecimal minOverdraftForInterestCalculation = null; @@ -141,9 +145,9 @@ public final class SavingsProductData implements Serializable { interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, - minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, - minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, isDormancyTrackingActive, daysToInactive, - daysToDormancy, daysToEscheat, accountMappingForPayment); + minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, + isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, accountMappingForPayment); } public static SavingsProductData withCharges(final SavingsProductData product, final Collection charges) { @@ -157,10 +161,11 @@ public final class SavingsProductData implements Serializable { product.lockinPeriodFrequencyTypeOptions, product.withdrawalFeeTypeOptions, product.paymentTypeOptions, product.accountingRuleOptions, product.accountingMappingOptions, charges, product.chargeOptions, product.penaltyOptions, product.feeToIncomeAccountMappings, product.penaltyToIncomeAccountMappings, product.allowOverdraft, product.overdraftLimit, - product.minRequiredBalance, product.enforceMinRequiredBalance, product.minBalanceForInterestCalculation, - product.nominalAnnualInterestRateOverdraft, product.minOverdraftForInterestCalculation, product.withHoldTax, - product.taxGroup, product.taxGroupOptions, product.isDormancyTrackingActive, product.daysToInactive, product.daysToDormancy, - product.daysToEscheat, product.accountMappingForPayment); + product.minRequiredBalance, product.enforceMinRequiredBalance, product.maxAllowedLienLimit, product.lienAllowed, + product.minBalanceForInterestCalculation, product.nominalAnnualInterestRateOverdraft, + product.minOverdraftForInterestCalculation, product.withHoldTax, product.taxGroup, product.taxGroupOptions, + product.isDormancyTrackingActive, product.daysToInactive, product.daysToDormancy, product.daysToEscheat, + product.accountMappingForPayment); } /** @@ -192,10 +197,10 @@ public final class SavingsProductData implements Serializable { accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, existingProduct.feeToIncomeAccountMappings, existingProduct.penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.overdraftLimit, existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, - existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, - existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions, - existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive, existingProduct.daysToDormancy, - existingProduct.daysToEscheat, accountMappingForPayment); + existingProduct.maxAllowedLienLimit, existingProduct.lienAllowed, existingProduct.minBalanceForInterestCalculation, + existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minOverdraftForInterestCalculation, + existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions, existingProduct.isDormancyTrackingActive, + existingProduct.daysToInactive, existingProduct.daysToDormancy, existingProduct.daysToEscheat, accountMappingForPayment); } public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct, @@ -227,11 +232,12 @@ public final class SavingsProductData implements Serializable { interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, existingProduct.allowOverdraft, existingProduct.overdraftLimit, - existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, - existingProduct.minBalanceForInterestCalculation, existingProduct.nominalAnnualInterestRateOverdraft, - existingProduct.minOverdraftForInterestCalculation, existingProduct.withHoldTax, existingProduct.taxGroup, - existingProduct.taxGroupOptions, existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive, - existingProduct.daysToDormancy, existingProduct.daysToEscheat, existingProduct.accountMappingForPayment); + existingProduct.minRequiredBalance, existingProduct.enforceMinRequiredBalance, existingProduct.maxAllowedLienLimit, + existingProduct.lienAllowed, existingProduct.minBalanceForInterestCalculation, + existingProduct.nominalAnnualInterestRateOverdraft, existingProduct.minOverdraftForInterestCalculation, + existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions, + existingProduct.isDormancyTrackingActive, existingProduct.daysToInactive, existingProduct.daysToDormancy, + existingProduct.daysToEscheat, existingProduct.accountMappingForPayment); } public static SavingsProductData instance(final Long id, final String name, final String shortName, final String description, @@ -240,7 +246,8 @@ public final class SavingsProductData implements Serializable { final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, final EnumOptionData accountingType, final boolean allowOverdraft, final BigDecimal overdraftLimit, - final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal minBalanceForInterestCalculation, + final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, final BigDecimal maxAllowedLienLimit, + final boolean lienAllowed, final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat) { @@ -273,9 +280,9 @@ public final class SavingsProductData implements Serializable { interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, - minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, - minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, isDormancyTrackingActive, daysToInactive, - daysToDormancy, daysToEscheat, accountMappingForPayment); + minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, + isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, accountMappingForPayment); } public static SavingsProductData lookup(final Long id, final String name) { @@ -301,6 +308,8 @@ public final class SavingsProductData implements Serializable { final BigDecimal minOverdraftForInterestCalculation = null; final BigDecimal minRequiredBalance = null; final boolean enforceMinRequiredBalance = false; + final BigDecimal maxAllowedLienLimit = null; + final boolean lienAllowed = false; final BigDecimal minBalanceForInterestCalculation = null; final boolean withHoldTax = false; final TaxGroupData taxGroup = null; @@ -334,9 +343,9 @@ public final class SavingsProductData implements Serializable { interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, allowOverdraft, overdraftLimit, - minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, - minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, isDormancyTrackingActive, daysToInactive, - daysToDormancy, daysToEscheat, accountMappingForPayment); + minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, taxGroupOptions, + isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, accountMappingForPayment); } public static SavingsProductData createForInterestPosting(final Long id, final EnumOptionData accountingRule) { @@ -387,6 +396,8 @@ public final class SavingsProductData implements Serializable { this.overdraftLimit = null; this.minRequiredBalance = null; this.enforceMinRequiredBalance = false; + this.maxAllowedLienLimit = null; + this.lienAllowed = false; this.minBalanceForInterestCalculation = null; this.nominalAnnualInterestRateOverdraft = null; this.minOverdraftForInterestCalculation = null; @@ -418,10 +429,11 @@ public final class SavingsProductData implements Serializable { final Collection feeToIncomeAccountMappings, final Collection penaltyToIncomeAccountMappings, final boolean allowOverdraft, final BigDecimal overdraftLimit, final BigDecimal minRequiredBalance, final boolean enforceMinRequiredBalance, - final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, - final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, - final Collection taxGroupOptions, final Boolean isDormancyTrackingActive, final Long daysToInactive, - final Long daysToDormancy, final Long daysToEscheat, final String accountMappingForPayment) { + final BigDecimal maxAllowedLienLimit, final boolean lienAllowed, final BigDecimal minBalanceForInterestCalculation, + final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, + final boolean withHoldTax, final TaxGroupData taxGroup, final Collection taxGroupOptions, + final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat, + final String accountMappingForPayment) { this.id = id; this.name = name; this.shortName = shortName; @@ -468,6 +480,8 @@ public final class SavingsProductData implements Serializable { this.overdraftLimit = overdraftLimit; this.minRequiredBalance = minRequiredBalance; this.enforceMinRequiredBalance = enforceMinRequiredBalance; + this.maxAllowedLienLimit = maxAllowedLienLimit; + this.lienAllowed = lienAllowed; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft; this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation; @@ -563,6 +577,10 @@ public final class SavingsProductData implements Serializable { return minRequiredBalance; } + public BigDecimal getMaxAllowedLienLimit() { + return maxAllowedLienLimit; + } + public Long getId() { return id; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java index 3c5072901..368c1a87a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsProductDataValidator.java @@ -34,8 +34,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interest import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; @@ -62,6 +64,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams; import org.apache.fineract.accounting.common.AccountingRuleType; @@ -103,6 +106,7 @@ public class SavingsProductDataValidator { daysToInactiveParamName, daysToEscheatParamName, allowOverdraftParamName, overdraftLimitParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, SavingsApiConstants.minRequiredBalanceParamName, SavingsApiConstants.enforceMinRequiredBalanceParamName, + SavingsApiConstants.maxAllowedLienLimitParamName, SavingsApiConstants.lienAllowedParamName, minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName)); @Autowired @@ -340,9 +344,8 @@ public class SavingsProductDataValidator { baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation) .ignoreIfNull().zeroOrPositiveAmount(); } - validateTaxWithHoldingParams(baseDataValidator, element, true); - + validateLienParams(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -545,7 +548,7 @@ public class SavingsProductDataValidator { } validateTaxWithHoldingParams(baseDataValidator, element, false); - + validateLienParams(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -650,6 +653,29 @@ public class SavingsProductDataValidator { } + private void validateLienParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + if (this.fromApiJsonHelper.parameterExists(lienAllowedParamName, element)) { + final Boolean lienAllowed = this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element); + baseDataValidator.reset().parameter(lienAllowedParamName).value(lienAllowed).ignoreIfNull().validateForBooleanValue(); + } + + if (this.fromApiJsonHelper.parameterExists(maxAllowedLienLimitParamName, element)) { + final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, + element); + baseDataValidator.reset().parameter(maxAllowedLienLimitParamName).value(maxAllowedLienLimit).ignoreIfNull() + .zeroOrPositiveAmount(); + } + if (BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element)) + && BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element))) { + final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, + element); + final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element); + if (overdraftLimit.compareTo(maxAllowedLienLimit) > 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("Overdraft.limit.can.not.be.greater.than.lien.limit"); + } + } + } + private void validateTaxWithHoldingParams(final DataValidatorBuilder baseDataValidator, final JsonElement element, final boolean isCreate) { if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) { 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 8b56a5875..821f58e53 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 @@ -22,9 +22,11 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName; @@ -293,6 +295,12 @@ public class SavingsAccount extends AbstractPersistableCustom { @Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true) private BigDecimal minRequiredBalance; + @Column(name = "is_lien_allowed", nullable = false) + private boolean lienAllowed; + + @Column(name = "max_allowed_lien_limit", scale = 6, precision = 19, nullable = true) + private BigDecimal maxAllowedLienLimit; + @Column(name = "on_hold_funds_derived", scale = 6, precision = 19, nullable = true) private BigDecimal onHoldFunds; @@ -337,7 +345,6 @@ public class SavingsAccount extends AbstractPersistableCustom { @Column(name = "total_savings_amount_on_hold", scale = 6, precision = 19, nullable = true) private BigDecimal savingsOnHoldAmount; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true, fetch = FetchType.LAZY) protected List identifiers = new ArrayList<>(); @@ -354,15 +361,17 @@ public class SavingsAccount extends AbstractPersistableCustom { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, - final BigDecimal minRequiredBalance, final BigDecimal nominalAnnualInterestRateOverdraft, - final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax) { + final BigDecimal minRequiredBalance, final BigDecimal maxAllowedLienLimit, final boolean lienAllowed, + final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, + final boolean withHoldTax) { final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL; return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, enforceMinRequiredBalance, - minRequiredBalance, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax); + minRequiredBalance, maxAllowedLienLimit, lienAllowed, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax); } protected SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer, @@ -377,8 +386,8 @@ public class SavingsAccount extends AbstractPersistableCustom { this(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate, submittedBy, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, - withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, false, null, null, null, - withHoldTax); + withdrawalFeeApplicableForTransfer, savingsAccountCharges, allowOverdraft, overdraftLimit, false, null, null, false, null, + null, withHoldTax); } protected SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff savingsOfficer, @@ -390,8 +399,8 @@ public class SavingsAccount extends AbstractPersistableCustom { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final Set savingsAccountCharges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, - final BigDecimal minRequiredBalance, final BigDecimal nominalAnnualInterestRateOverdraft, - final BigDecimal minOverdraftForInterestCalculation, boolean withHoldTax) { + final BigDecimal minRequiredBalance, final BigDecimal maxAllowedLienLimit, final boolean lienAllowed, + final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, boolean withHoldTax) { this.client = client; this.group = group; this.product = product; @@ -434,6 +443,8 @@ public class SavingsAccount extends AbstractPersistableCustom { this.enforceMinRequiredBalance = enforceMinRequiredBalance; this.minRequiredBalance = minRequiredBalance; + this.lienAllowed = lienAllowed; + this.maxAllowedLienLimit = maxAllowedLienLimit; this.minBalanceForInterestCalculation = product.minBalanceForInterestCalculation(); // this.savingsOfficerHistory = null; this.withHoldTax = withHoldTax; @@ -1751,6 +1762,18 @@ public class SavingsAccount extends AbstractPersistableCustom { actualChanges.put(localeParamName, localeAsInput); this.minRequiredBalance = newValue; } + if (command.isChangeInBooleanParameterNamed(lienAllowedParamName, this.lienAllowed)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName); + actualChanges.put(lienAllowedParamName, newValue); + this.lienAllowed = newValue; + } + + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(maxAllowedLienLimitParamName, this.maxAllowedLienLimit)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName); + actualChanges.put(maxAllowedLienLimitParamName, newValue); + actualChanges.put(localeParamName, localeAsInput); + this.maxAllowedLienLimit = newValue; + } if (command.isChangeInBooleanParameterNamed(withHoldTaxParamName, this.withHoldTax)) { final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); @@ -1950,6 +1973,10 @@ public class SavingsAccount extends AbstractPersistableCustom { return this.enforceMinRequiredBalance; } + public Boolean getLienAllowed() { + return this.lienAllowed; + } + public void unassignSavingsOfficer() { this.savingsOfficer = null; } @@ -3429,6 +3456,11 @@ public class SavingsAccount extends AbstractPersistableCustom { .subtract(this.getSavingsHoldAmount()); } + public BigDecimal getWithdrawableBalanceWithLien() { + return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds()) + .subtract(this.getSavingsHoldAmount()); + } + public BigDecimal getWithdrawableBalanceWithoutMinimumBalance() { return getAccountBalance().subtract(this.getOnHoldFunds()).subtract(this.getSavingsHoldAmount()); } @@ -3833,6 +3865,14 @@ public class SavingsAccount extends AbstractPersistableCustom { return this.enforceMinRequiredBalance; } + public BigDecimal getMaxAllowedLienLimit() { + return this.maxAllowedLienLimit; + } + + public boolean isLienAllowed() { + return this.lienAllowed; + } + public BigDecimal getMinBalanceForInterestCalculation() { return this.minBalanceForInterestCalculation; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index 622823c94..dcc20cdda 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -30,8 +30,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interest import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName; @@ -294,6 +296,20 @@ public class SavingsAccountAssembler { minRequiredBalance = product.minRequiredBalance(); } + boolean lienAllowed = false; + if (command.parameterExists(lienAllowedParamName)) { + lienAllowed = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName); + } else { + lienAllowed = product.isLienAllowed(); + } + + BigDecimal maxAllowedLienLimit = BigDecimal.ZERO; + if (command.parameterExists(maxAllowedLienLimitParamName)) { + maxAllowedLienLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName); + } else { + maxAllowedLienLimit = product.maxAllowedLienLimit(); + } + boolean withHoldTax = product.withHoldTax(); if (command.parameterExists(withHoldTaxParamName)) { withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName); @@ -306,8 +322,8 @@ public class SavingsAccountAssembler { externalId, accountType, submittedOnDate, submittedBy, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges, allowOverdraft, - overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, nominalAnnualInterestRateOverdraft, - minOverdraftForInterestCalculation, withHoldTax); + overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, maxAllowedLienLimit, lienAllowed, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME); @@ -423,8 +439,9 @@ public class SavingsAccountAssembler { product.interestPostingPeriodType(), product.interestCalculationType(), product.interestCalculationDaysInYearType(), product.minRequiredOpeningBalance(), product.lockinPeriodFrequency(), product.lockinPeriodFrequencyType(), product.isWithdrawalFeeApplicableForTransfer(), charges, product.isAllowOverdraft(), product.overdraftLimit(), - product.isMinRequiredBalanceEnforced(), product.minRequiredBalance(), product.nominalAnnualInterestRateOverdraft(), - product.minOverdraftForInterestCalculation(), product.withHoldTax()); + product.isMinRequiredBalanceEnforced(), product.minRequiredBalance(), product.maxAllowedLienLimit(), + product.isLienAllowed(), product.nominalAnnualInterestRateOverdraft(), product.minOverdraftForInterestCalculation(), + product.withHoldTax()); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java index ae1124690..58e9ff875 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java @@ -24,6 +24,7 @@ import java.time.format.DateTimeFormatter; import java.util.Set; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; +import org.apache.fineract.useradministration.domain.AppUser; public interface SavingsAccountDomainService { @@ -43,4 +44,7 @@ public interface SavingsAccountDomainService { SavingsAccountTransaction handleReversal(SavingsAccount account, SavingsAccountTransaction savingsAccountTransaction, boolean backdatedTxnsAllowedTill); + + SavingsAccountTransaction handleHold(SavingsAccount account, AppUser createdUser, BigDecimal amount, LocalDate transactionDate, + Boolean lienAllowed); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index aee89bee8..7e575e4db 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEntity; import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BusinessEvents; @@ -219,6 +220,18 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi return deposit; } + @Transactional + @Override + public SavingsAccountTransaction handleHold(final SavingsAccount account, final AppUser createdUser, BigDecimal amount, + LocalDate transactionDate, Boolean lienAllowed) { + final PaymentDetail paymentDetails = null; + Date createdDate = new Date(); + + SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails, + transactionDate, Money.of(account.getCurrency(), amount), createdDate, createdUser, lienAllowed); + return transaction; + } + @Override public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate, final BigDecimal transactionAmount, final boolean backdatedTxnsAllowedTill) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index 6b2c9c119..a4f2208ff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -140,6 +140,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { @Column(name = "original_transaction_id") private Long originalTxnId; + @Column(name = "is_lien_transaction") + private Boolean lienTransaction; + SavingsAccountTransaction() { this.dateOf = null; this.typeOf = null; @@ -150,8 +153,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.DEPOSIT.getValue(), date, - createdDate, amount, isReversed, appUser, isManualTransaction); + createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office, @@ -159,54 +163,61 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final SavingsAccountTransactionType savingsAccountTransactionType) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, savingsAccountTransactionType.getValue(), date, - createdDate, amount, isReversed, appUser, isManualTransaction); + createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction withdrawal(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAWAL.getValue(), - date, createdDate, amount, isReversed, appUser, isManualTransaction); + date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction interestPosting(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final boolean isManualTransaction) { final boolean isReversed = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.INTEREST_POSTING.getValue(), date, - amount, isReversed, null, isManualTransaction); + amount, isReversed, null, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction overdraftInterest(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final boolean isManualTransaction) { final boolean isReversed = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue(), date, - amount, isReversed, null, isManualTransaction); + amount, isReversed, null, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final AppUser appUser) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount, - isReversed, appUser, isManualTransaction); + isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction annualFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final AppUser appUser) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, - isReversed, appUser, isManualTransaction); + isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction charge(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final AppUser appUser) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.PAY_CHARGE.getValue(), date, amount, - isReversed, appUser, isManualTransaction); + isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction from(final Integer transactionTypeEnum, final LocalDate transactionDate, @@ -221,8 +232,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final Money amount, final AppUser appUser) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WAIVE_CHARGES.getValue(), date, amount, - isReversed, appUser, isManualTransaction); + isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -230,9 +242,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.INITIATE_TRANSFER.getValue(), date, new Date(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction); + savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -240,9 +253,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.APPROVE_TRANSFER.getValue(), date, new Date(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction); + savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -250,17 +264,20 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue(), date, new Date(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction); + savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction withHoldTax(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final Map taxDetails) { final boolean isReversed = false; final boolean isManualTransaction = false; + final Boolean lienTransaction = false; SavingsAccountTransaction accountTransaction = new SavingsAccountTransaction(savingsAccount, office, - SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null, isManualTransaction); + SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null, isManualTransaction, + lienTransaction); updateTaxDetails(taxDetails, accountTransaction); return accountTransaction; } @@ -269,9 +286,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean accountTransaction) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; + final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, savingsAccount.office(), paymentDetail, SavingsAccountTransactionType.ESCHEAT.getValue(), date, new Date(), savingsAccount.getSummary().getAccountBalance(), - isReversed, appUser, accountTransaction); + isReversed, appUser, accountTransaction, lienTransaction); } public static void updateTaxDetails(final Map taxDetails, @@ -287,25 +305,27 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { public static SavingsAccountTransaction copyTransaction(SavingsAccountTransaction accountTransaction) { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, accountTransaction.typeOf, accountTransaction.transactionLocalDate(), accountTransaction.createdDate, - accountTransaction.amount, accountTransaction.reversed, accountTransaction.appUser, accountTransaction.isManualTransaction); + accountTransaction.amount, accountTransaction.reversed, accountTransaction.appUser, accountTransaction.isManualTransaction, + accountTransaction.lienTransaction); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final Integer typeOf, final LocalDate transactionLocalDate, final Money amount, final boolean isReversed, final AppUser appUser, - final boolean isManualTransaction) { - this(savingsAccount, office, null, typeOf, transactionLocalDate, new Date(), amount, isReversed, appUser, isManualTransaction); + final boolean isManualTransaction, final Boolean lienTransaction) { + this(savingsAccount, office, null, typeOf, transactionLocalDate, new Date(), amount, isReversed, appUser, isManualTransaction, + lienTransaction); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final Money amount, - final boolean isReversed, final AppUser appUser, final boolean isManualTransaction) { + final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction) { this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, createdDate, amount.getAmount(), isReversed, appUser, - isManualTransaction); + isManualTransaction, lienTransaction); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final BigDecimal amount, - final boolean isReversed, final AppUser appUser, final boolean isManualTransaction) { + final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction) { this.savingsAccount = savingsAccount; this.office = office; this.typeOf = typeOf; @@ -316,6 +336,7 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { this.createdDate = createdDate; this.appUser = appUser; this.isManualTransaction = isManualTransaction; + this.lienTransaction = lienTransaction; } private SavingsAccountTransaction(final Integer transactionTypeEnum, final LocalDate transactionDate, final BigDecimal amount, @@ -337,18 +358,19 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { } public static SavingsAccountTransaction holdAmount(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser, + final Boolean lienTransaction) { final boolean isReversed = false; final boolean isManualTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.AMOUNT_HOLD.getValue(), - date, createdDate, amount, isReversed, appUser, isManualTransaction); + date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); } public static SavingsAccountTransaction releaseAmount(SavingsAccountTransaction accountTransaction, LocalDate transactionDate, Date createdDate, final AppUser appUser) { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, SavingsAccountTransactionType.AMOUNT_RELEASE.getValue(), transactionDate, createdDate, accountTransaction.amount, - accountTransaction.reversed, appUser, accountTransaction.isManualTransaction); + accountTransaction.reversed, appUser, accountTransaction.isManualTransaction, accountTransaction.lienTransaction); } public static SavingsAccountTransaction reversal(SavingsAccountTransaction accountTransaction) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java index d0de0ed3c..50e83af01 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java @@ -35,9 +35,11 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interest import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName; @@ -179,6 +181,12 @@ public class SavingsProduct extends AbstractPersistableCustom { @Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true) private BigDecimal minRequiredBalance; + @Column(name = "is_lien_allowed", nullable = false) + private boolean lienAllowed; + + @Column(name = "max_allowed_lien_limit", scale = 6, precision = 19, nullable = true) + private BigDecimal maxAllowedLienLimit; + @Column(name = "min_balance_for_interest_calculation", scale = 6, precision = 19, nullable = true) private BigDecimal minBalanceForInterestCalculation; @@ -209,17 +217,17 @@ public class SavingsProduct extends AbstractPersistableCustom { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, - final BigDecimal minRequiredBalance, final BigDecimal minBalanceForInterestCalculation, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, boolean withHoldTax, - TaxGroup taxGroup, final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, - final Long daysToEscheat) { + final BigDecimal minRequiredBalance, final boolean lienAllowed, final BigDecimal maxAllowedLienLimit, + final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, + final BigDecimal minOverdraftForInterestCalculation, boolean withHoldTax, TaxGroup taxGroup, + final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat) { return new SavingsProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, - allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, isDormancyTrackingActive, - daysToInactive, daysToDormancy, daysToEscheat); + allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, lienAllowed, maxAllowedLienLimit, + minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, + taxGroup, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); } protected SavingsProduct() { @@ -238,7 +246,7 @@ public class SavingsProduct extends AbstractPersistableCustom { this(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit, - false, null, minBalanceForInterestCalculation, null, null, withHoldTax, taxGroup, null, null, null, null); + false, null, false, null, minBalanceForInterestCalculation, null, null, withHoldTax, taxGroup, null, null, null, null); } protected SavingsProduct(final String name, final String shortName, final String description, final MonetaryCurrency currency, @@ -248,10 +256,10 @@ public class SavingsProduct extends AbstractPersistableCustom { final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType, final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType, final Set charges, final boolean allowOverdraft, final BigDecimal overdraftLimit, final boolean enforceMinRequiredBalance, - final BigDecimal minRequiredBalance, BigDecimal minBalanceForInterestCalculation, - final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, - final boolean withHoldTax, final TaxGroup taxGroup, final Boolean isDormancyTrackingActive, final Long daysToInactive, - final Long daysToDormancy, final Long daysToEscheat) { + final BigDecimal minRequiredBalance, final boolean lienAllowed, final BigDecimal maxAllowedLienLimit, + BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft, + final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroup taxGroup, + final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat) { this.name = name; this.shortName = shortName; @@ -293,6 +301,8 @@ public class SavingsProduct extends AbstractPersistableCustom { this.enforceMinRequiredBalance = enforceMinRequiredBalance; this.minRequiredBalance = minRequiredBalance; + this.lienAllowed = lienAllowed; + this.maxAllowedLienLimit = maxAllowedLienLimit; this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; this.withHoldTax = withHoldTax; this.taxGroup = taxGroup; @@ -540,6 +550,19 @@ public class SavingsProduct extends AbstractPersistableCustom { this.minRequiredBalance = newValue; } + if (command.isChangeInBooleanParameterNamed(lienAllowedParamName, this.lienAllowed)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName); + actualChanges.put(lienAllowedParamName, newValue); + this.lienAllowed = newValue; + } + + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(maxAllowedLienLimitParamName, this.maxAllowedLienLimit)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName); + actualChanges.put(maxAllowedLienLimitParamName, newValue); + actualChanges.put(localeParamName, localeAsInput); + this.maxAllowedLienLimit = newValue; + } + if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minBalanceForInterestCalculationParamName, this.minBalanceForInterestCalculation)) { final BigDecimal newValue = command @@ -689,6 +712,14 @@ public class SavingsProduct extends AbstractPersistableCustom { return this.enforceMinRequiredBalance; } + public BigDecimal maxAllowedLienLimit() { + return this.maxAllowedLienLimit; + } + + public boolean isLienAllowed() { + return this.lienAllowed; + } + public Set charges() { return this.charges; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java index 00bc484d8..a9f3862ae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProductAssembler.java @@ -34,8 +34,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interest import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName; @@ -170,6 +172,16 @@ public class SavingsProductAssembler { if (command.parameterExists(minRequiredBalanceParamName)) { minRequiredBalance = command.bigDecimalValueOfParameterNamed(minRequiredBalanceParamName); } + + boolean lienAllowed = false; + if (command.parameterExists(lienAllowedParamName)) { + lienAllowed = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName); + } + + BigDecimal maxAllowedLienLimit = BigDecimal.ZERO; + if (command.parameterExists(maxAllowedLienLimitParamName)) { + maxAllowedLienLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName); + } final BigDecimal minBalanceForInterestCalculation = command .bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName); @@ -184,9 +196,9 @@ public class SavingsProductAssembler { return SavingsProduct.createNew(name, shortName, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, accountingRuleType, charges, - allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, minBalanceForInterestCalculation, - nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, isDormancyTrackingActive, - daysToInactive, daysToDormancy, daysToEscheat); + allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, lienAllowed, maxAllowedLienLimit, + minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, + taxGroup, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); } public Set assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 0888ea820..53fa4f9e2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -332,6 +332,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); sqlBuilder.append("sa.min_required_balance as minRequiredBalance, "); sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, "); + sqlBuilder.append("sa.max_allowed_lien_limit as maxAllowedLienLimit, "); + sqlBuilder.append("sa.is_lien_allowed as lienAllowed, "); sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, "); sqlBuilder.append("sa.withhold_tax as withHoldTax, "); sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, "); @@ -575,14 +577,17 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); + final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit"); + final boolean lienAllowed = rs.getBoolean("lienAllowed"); savingsAccountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, null, null, null, null, productId, null, null, null, status, subStatus, null, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, - enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, - minOverdraftForInterestCalculation, withHoldTax, taxGroupData, lastActiveTransactionDate, - isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount); + enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, + nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData, + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, + onHoldAmount); savingsAccountData.setClientData(clientData); savingsAccountData.setGroupGeneralData(groupGeneralData); @@ -811,6 +816,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); sqlBuilder.append("sa.min_required_balance as minRequiredBalance, "); sqlBuilder.append("sa.enforce_min_required_balance as enforceMinRequiredBalance, "); + sqlBuilder.append("sa.max_allowed_lien_limit as maxAllowedLienLimit, "); + sqlBuilder.append("sa.is_lien_allowed as lienAllowed, "); sqlBuilder.append("sa.on_hold_funds_derived as onHoldFunds, "); sqlBuilder.append("sa.withhold_tax as withHoldTax, "); sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, "); @@ -991,6 +998,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); + final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit"); + final boolean lienAllowed = rs.getBoolean("lienAllowed"); + /* * final BigDecimal annualFeeAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "annualFeeAmount"); * @@ -1057,9 +1067,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, - minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, - withHoldTax, taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, - daysToEscheat, onHoldAmount); + maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax, taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, + daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount); } } @@ -1320,7 +1330,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append(" au.username as submittedByUsername, "); sqlBuilder.append(" nt.note as transactionNote, "); sqlBuilder.append("tr.running_balance_derived as runningBalance, tr.is_reversed as reversed,"); - sqlBuilder.append("tr.is_reversal as isReversal, tr.original_transaction_id as originalTransactionId,"); + sqlBuilder.append( + "tr.is_reversal as isReversal, tr.original_transaction_id as originalTransactionId, tr.is_lien_transaction as lienTransaction, "); sqlBuilder.append("fromtran.id as fromTransferId, fromtran.is_reversed as fromTransferReversed,"); sqlBuilder.append("fromtran.transaction_date as fromTransferDate, fromtran.amount as fromTransferAmount,"); sqlBuilder.append("fromtran.description as fromTransferDescription,"); @@ -1368,6 +1379,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final boolean reversed = rs.getBoolean("reversed"); final boolean isReversal = rs.getBoolean("isReversal"); final Long originalTransactionId = rs.getLong("originalTransactionId"); + final Boolean lienTransaction = rs.getBoolean("lienTransaction"); final Long savingsId = rs.getLong("savingsId"); final String accountNo = rs.getString("accountNo"); @@ -1422,7 +1434,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final String note = rs.getString("transactionNote"); return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, amount, outstandingChargeAmount, runningBalance, reversed, transfer, submittedOnDate, postInterestAsOn, - submittedByUsername, note, isReversal, originalTransactionId, releaseTransactionId, reasonForBlock); + submittedByUsername, note, isReversal, originalTransactionId, lienTransaction, releaseTransactionId, reasonForBlock); } } @@ -1510,7 +1522,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead // "); // sqlBuilder.append("sp.annual_fee_on_day as annualFeeOnDay "); sqlBuilder.append("sp.min_required_balance as minRequiredBalance, "); - sqlBuilder.append("sp.enforce_min_required_balance as enforceMinRequiredBalance "); + sqlBuilder.append("sp.enforce_min_required_balance as enforceMinRequiredBalance, "); + sqlBuilder.append("sp.max_allowed_lien_limit as maxAllowedLienLimit, "); + sqlBuilder.append("sp.is_lien_allowed as lienAllowed "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code "); sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id "); @@ -1580,6 +1594,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); + final BigDecimal maxAllowedLienLimit = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxAllowedLienLimit"); + final boolean lienAllowed = rs.getBoolean("lienAllowed"); final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minBalanceForInterestCalculation"); @@ -1639,9 +1655,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead nominalAnnualIterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, - minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, - withHoldTax, taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, - daysToEscheat, savingsAmountOnHold); + maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, + minOverdraftForInterestCalculation, withHoldTax, taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, + daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } } 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 68acfaf16..d1dfe3c43 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 @@ -23,7 +23,9 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transactionAmountParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transactionDateParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawBalanceParamName; @@ -1811,6 +1813,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final AppUser submittedBy = this.context.authenticatedUser(); final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + final LocalDate transactionDate = command.localDateValueOfParameterNamed(transactionDateParamName); + final boolean lienAllowed = command.booleanObjectValueOfParameterNamed(lienAllowedParamName); checkClientOrGroupActive(account); @@ -1823,11 +1827,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi runningBalance = runningBalance.minus(amount); } + this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(), account, submittedBy, + backdatedTxnsAllowedTill); + SavingsAccountTransaction transaction = this.savingsAccountDomainService.handleHold(account, getAppUserIfPresent(), amount, + transactionDate, lienAllowed); account.holdAmount(amount); - - SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(), - account, submittedBy, backdatedTxnsAllowedTill); - transaction.updateRunningBalance(runningBalance); final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java index 54f052725..a343ccf24 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsProductReadPlatformServiceImpl.java @@ -129,6 +129,8 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead sqlBuilder.append("sp.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, "); sqlBuilder.append("sp.min_required_balance as minRequiredBalance, "); sqlBuilder.append("sp.enforce_min_required_balance as enforceMinRequiredBalance, "); + sqlBuilder.append("sp.max_allowed_lien_limit as maxAllowedLienLimit, "); + sqlBuilder.append("sp.is_lien_allowed as lienAllowed, "); sqlBuilder.append("sp.min_balance_for_interest_calculation as minBalanceForInterestCalculation,"); sqlBuilder.append("sp.accounting_type as accountingType, "); sqlBuilder.append("sp.withhold_tax as withHoldTax,"); @@ -203,6 +205,8 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead final BigDecimal minRequiredBalance = rs.getBigDecimal("minRequiredBalance"); final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); + final BigDecimal maxAllowedLienLimit = rs.getBigDecimal("maxAllowedLienLimit"); + final boolean lienAllowed = rs.getBoolean("lienAllowed"); final BigDecimal minBalanceForInterestCalculation = rs.getBigDecimal("minBalanceForInterestCalculation"); final boolean withHoldTax = rs.getBoolean("withHoldTax"); @@ -221,9 +225,9 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead return SavingsProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, - accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, - minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, - taxGroupData, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); + accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, maxAllowedLienLimit, + lienAllowed, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, + withHoldTax, taxGroupData, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); } } diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index bc73503ce..7dc81891f 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -29,4 +29,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0010_lien_allowed_on_savings_account_products.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0010_lien_allowed_on_savings_account_products.xml new file mode 100644 index 000000000..1630e1e98 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0010_lien_allowed_on_savings_account_products.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index ef2fa2fbb..6f711320b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -2017,6 +2017,68 @@ public class ClientSavingsIntegrationTest { enforceMinRequiredBalance, allowOverdraft, taxGroupId, false); } + // LienAtProductLevel + private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final String minOpenningBalance, String minBalanceForInterestCalculation, final boolean enforceMinRequiredBalance, + final boolean allowOverDraft, final boolean lienAllowed) { + + LOG.info("------------------------------CREATING NEW SAVINGS PRODUCT WITH LIEN---------------------------------------"); + SavingsProductHelper savingsProductHelper = new SavingsProductHelper(); + if (lienAllowed) { + final String maxAllowedLienLimit = "2000.0"; + savingsProductHelper.withLienAllowed(maxAllowedLienLimit); + } + if (enforceMinRequiredBalance) { + final String minRequiredBalance = "100.0"; + savingsProductHelper.withMinRequiredBalance(minRequiredBalance); + savingsProductHelper.withEnforceMinRequiredBalance("true"); + } + if (allowOverDraft) { + final String overDraftLimit = "500.0"; + savingsProductHelper.withOverDraft(overDraftLimit); + } + final String savingsProductJSON = savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily() + + .withInterestPostingPeriodTypeAsMonthly() + + .withInterestCalculationPeriodTypeAsDailyBalance() + + .withMinBalanceForInterestCalculation(minBalanceForInterestCalculation) + + .withMinimumOpenningBalance(minOpenningBalance).build(); + + return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); + } + + // LienAtProductlevel with Overdraft Limit > Lien Limit + private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final String minOpenningBalance, String minBalanceForInterestCalculation, final boolean enforceMinRequiredBalance, + final boolean allowOverDraft, final String overDraftLimit, final boolean lienAllowed, final String lienAllowedLimit) { + LOG.info("------------------------------CREATING NEW SAVINGS PRODUCT WITH LIEN---------------------------------------"); + SavingsProductHelper savingsProductHelper = new SavingsProductHelper(); + if (lienAllowed) { + savingsProductHelper.withLienAllowed(lienAllowedLimit); + } + if (enforceMinRequiredBalance) { + final String minRequiredBalance = "100.0"; + savingsProductHelper.withMinRequiredBalance(minRequiredBalance); + savingsProductHelper.withEnforceMinRequiredBalance("true"); + } + if (allowOverDraft) { + savingsProductHelper.withOverDraft(overDraftLimit); + } + final String savingsProductJSON = savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily() + + .withInterestPostingPeriodTypeAsMonthly() + + .withInterestCalculationPeriodTypeAsDailyBalance() + + .withMinBalanceForInterestCalculation(minBalanceForInterestCalculation) + + .withMinimumOpenningBalance(minOpenningBalance).build(); + return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); + } + private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final String minOpenningBalance, String minBalanceForInterestCalculation, String minRequiredBalance, String enforceMinRequiredBalance, final boolean allowOverdraft, final String taxGroupId, boolean withDormancy) { @@ -2177,26 +2239,23 @@ public class ClientSavingsIntegrationTest { @SuppressWarnings("unchecked") @Test - public void testSavingsAccountLienStatus() { - + public void testSavingsAccountLienAllowedAtProductLevelWithEnforceBalance() { this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec, new ResponseSpecBuilder().build()); - final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); - this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); - final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); final String minBalanceForInterestCalculation = null; - final String minRequiredBalance = null; - final String enforceMinRequiredBalance = "true"; + final boolean enforceMinRequiredBalance = true; final boolean allowOverdraft = false; + final boolean lienAllowed = true; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, - minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + minBalanceForInterestCalculation, enforceMinRequiredBalance, allowOverdraft, lienAllowed); Assertions.assertNotNull(savingsProductID); final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); @@ -2211,38 +2270,264 @@ public class ClientSavingsIntegrationTest { savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); - List error = (List) savingsAccountHelperValidationError.holdAmountInSavingsAccount(savingsId, "1500", false, - SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); // lien:false + List error = (List) savingsAccountHelperValidationError.holdAmountInSavingsAccount(savingsId, "2000", false, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("validation.msg.savingsaccount.insufficient.balance", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); - assertEquals("validation.msg.savingsaccount.insufficient balance", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); - - Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, "1500", true, + Integer lienHoldTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, "2000", true, SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); - Float balance = Float.parseFloat("-500"); + Float balance = Float.parseFloat("-1000"); - assertEquals(balance, summary.get("availableBalance"), "Verifying available Balance is -500"); - - Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", + assertEquals(balance, summary.get("availableBalance"), "Verifying available Balance is -1000"); + Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1200", SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); Calendar todaysDate = Calendar.getInstance(); final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime()); ArrayList savingsAccountErrorData = (ArrayList) validationErrorHelper.withdrawalFromSavingsAccount(savingsId, - "1000", TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + "200", TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance", savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); - Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "500", TRANSACTION_DATE, + Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "100", TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); Assertions.assertNotNull(withdrawTransactionId); } + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountLienAllowedAtProductLevelWithOverDraftLimit() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec, + new ResponseSpecBuilder().build()); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); + final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + + final String minBalanceForInterestCalculation = null; + final boolean enforceMinRequiredBalance = false; + final boolean allowOverdraft = true; + final boolean lienAllowed = true; + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, enforceMinRequiredBalance, allowOverdraft, lienAllowed); + Assertions.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsProductID); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + List error = (List) savingsAccountHelperValidationError.holdAmountInSavingsAccount(savingsId, "2000", false, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("validation.msg.savingsaccount.insufficient.balance", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + Integer lienHoldTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, "2000", true, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + + Float balance = Float.parseFloat("-1000"); + + assertEquals(balance, summary.get("availableBalance"), "Verifying available Balance is -1000"); + + Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1200", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + Calendar todaysDate = Calendar.getInstance(); + final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime()); + + ArrayList savingsAccountErrorData = (ArrayList) validationErrorHelper.withdrawalFromSavingsAccount(savingsId, + "300", TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + + assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance", + savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "200", TRANSACTION_DATE, + CommonConstants.RESPONSE_RESOURCE_ID); + + Assertions.assertNotNull(withdrawTransactionId); + } + + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountLienAllowedAtProductLevelWithNoConfig() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec, + new ResponseSpecBuilder().build()); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); + final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + + final String minBalanceForInterestCalculation = null; + final boolean enforceMinRequiredBalance = false; + final boolean allowOverdraft = false; + final boolean lienAllowed = true; + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, enforceMinRequiredBalance, allowOverdraft, lienAllowed); + Assertions.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsProductID); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + List error = (List) savingsAccountHelperValidationError.holdAmountInSavingsAccount(savingsId, "2000", false, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("validation.msg.savingsaccount.insufficient.balance", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + Integer lienHoldTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, "2000", true, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + + Float balance = Float.parseFloat("-1000"); + + assertEquals(balance, summary.get("availableBalance"), "Verifying available Balance is -1000"); + + Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1100", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + Calendar todaysDate = Calendar.getInstance(); + final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime()); + + ArrayList savingsAccountErrorData = (ArrayList) validationErrorHelper.withdrawalFromSavingsAccount(savingsId, + "200", TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);// withdrawable amount = 200; + + assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance", + savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "100", TRANSACTION_DATE, + CommonConstants.RESPONSE_RESOURCE_ID); + + Assertions.assertNotNull(withdrawTransactionId); + + } + + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountWithoutLienAllowed() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec, + new ResponseSpecBuilder().build()); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); + final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + + final String minBalanceForInterestCalculation = null; + final boolean enforceMinRequiredBalance = false; + final boolean allowOverdraft = true; + final boolean lienAllowed = false; + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, enforceMinRequiredBalance, allowOverdraft, lienAllowed); + Assertions.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsProductID); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + List error = (List) savingsAccountHelperValidationError.holdAmountInSavingsAccount(savingsId, "2000", true, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("validation.msg.savingsaccount.lien.is.not.allowed.in.product.level", + error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + Integer lienHoldTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, "1500", false, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);// as per overdraft limit + + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + + Float balance = Float.parseFloat("-500"); // opening balance + + assertEquals(balance, summary.get("availableBalance"), "Verifying available Balance is -500"); + + Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, "2000", // available + // to + // use + // 1100 + // and + // another + // 1000 + // on + // transactional + // hold + + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + Calendar todaysDate = Calendar.getInstance(); + final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime()); + + ArrayList savingsAccountErrorData = (ArrayList) validationErrorHelper.withdrawalFromSavingsAccount(savingsId, + "1600", TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);// can not withdraw: amount on transactional + // hold + + assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance", + savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "1500", + TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + + Assertions.assertNotNull(withdrawTransactionId); + + } + + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountLienAllowedAtProductLevelWithOverDraftLimitGreaterThanLienLimit() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build(); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + + final String minBalanceForInterestCalculation = null; + final boolean enforceMinRequiredBalance = false; + final boolean allowOverdraft = true; + final String overDraftLimit = "2000.0"; + final boolean lienAllowed = true; + final String lineAllowedLimit = "1000.0"; + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, errorResponse, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, enforceMinRequiredBalance, allowOverdraft, overDraftLimit, lienAllowed, lineAllowedLimit); + Assertions.assertNull(savingsProductID); + + } + /** * incorrect savings account balance when charge transaction is reversed during an overdraft recalculate Daily * Balances diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index e0335b1bb..6ec624203 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -377,11 +377,12 @@ public class SavingsAccountHelper { getActivatedSavingsAsJSON(), isBlock); } - public Object holdAmountInSavingsAccount(final Integer savingsID, final String amount, final Boolean lienStatus, String date, + public Object holdAmountInSavingsAccount(final Integer savingsID, final String amount, final Boolean lienAllowed, String date, String jsonAttributeToGetback) { LOG.info("--------------------------------- SAVINGS TRANSACTION HOLD AMOUNT--------------------------------"); + return performSavingActions(createSavingsTransactionURL(HOLD_AMOUNT_SAVINGS_COMMAND, savingsID), - getLienSavingsTransactionJSON(amount, date, lienStatus), jsonAttributeToGetback); + getLienSavingsTransactionJSON(amount, date, lienAllowed), jsonAttributeToGetback); } public Integer releaseAmount(final Integer savingsId, final Integer transactionId) { @@ -459,13 +460,13 @@ public class SavingsAccountHelper { return savingsAccountWithdrawalJson; } - private String getLienSavingsTransactionJSON(final String amount, final String transactionDate, final Boolean lien) { + private String getLienSavingsTransactionJSON(final String amount, final String transactionDate, final Boolean lienAllowed) { final HashMap map = new HashMap<>(); map.put("locale", CommonConstants.LOCALE); map.put("dateFormat", CommonConstants.DATE_FORMAT); map.put("transactionDate", transactionDate); map.put("transactionAmount", amount); - map.put("lien", lien); + map.put("lienAllowed", lienAllowed); map.put("reasonForBlock", "unUsualActivity"); String savingsAccountWithdrawalJson = new Gson().toJson(map); LOG.info(savingsAccountWithdrawalJson); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java index 507cd0b4b..4b1b66b84 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java @@ -86,6 +86,8 @@ public class SavingsProductHelper { private String overdraftLimit = null; private String minRequiredBalance = null; private String enforceMinRequiredBalance = "false"; + private String lienAllowed = "false"; + private String maxAllowedLienLimit = null; private Boolean withHoldTax = false; private String taxGroupId = null; private boolean isDormancyTrackingActive = false; @@ -129,6 +131,8 @@ public class SavingsProductHelper { map.put("overdraftLimit", this.overdraftLimit); map.put("minRequiredBalance", this.minRequiredBalance); map.put("enforceMinRequiredBalance", this.enforceMinRequiredBalance); + map.put("lienAllowed", this.lienAllowed); + map.put("maxAllowedLienLimit", this.maxAllowedLienLimit); map.put("withHoldTax", this.withHoldTax.toString()); if (withHoldTax) { @@ -216,16 +220,22 @@ public class SavingsProductHelper { return this; } - public SavingsProductHelper withMinRequiredBalance(String minBalance) { - this.minRequiredBalance = minBalance; + public SavingsProductHelper withMinRequiredBalance(final String minRequiredBalance) { + this.minRequiredBalance = minRequiredBalance; return this; } - public SavingsProductHelper withEnforceMinRequiredBalance(String enforceMinRequiredBalance) { + public SavingsProductHelper withEnforceMinRequiredBalance(final String enforceMinRequiredBalance) { this.enforceMinRequiredBalance = enforceMinRequiredBalance; return this; } + public SavingsProductHelper withLienAllowed(final String maxAllowedLienLimit) { + this.lienAllowed = "true"; + this.maxAllowedLienLimit = maxAllowedLienLimit; + return this; + } + public SavingsProductHelper withOverDraft(final String overdraftLimit) { this.allowOverdraft = "true"; this.overdraftLimit = overdraftLimit;