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 08086dcf4..4dd397368 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 @@ -80,6 +80,7 @@ public class SavingsApiConstants { public static final String externalIdParamName = "externalId"; public static final String statusParamName = "status"; public static final String subStatusParamName = "subStatus"; + public static final String reasonForBlockParamName = "reasonForBlock"; public static final String clientIdParamName = "clientId"; public static final String isRetailAccountParamName = "isRetailAccount"; public static final String autogenerateTransactionIdParamName = "autogenerateTransactionId"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java index b749896a9..9ecd0ceb9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java @@ -479,10 +479,10 @@ public class SavingsAccountsApiResource { final CommandWrapper commandRequest = builder.unblockCreditsToSavingsAccount(accountId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_ACCOUNT)) { - final CommandWrapper commandRequest = builder.withNoJsonBody().blockSavingsAccount(accountId).build(); + final CommandWrapper commandRequest = builder.blockSavingsAccount(accountId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) { - final CommandWrapper commandRequest = builder.withNoJsonBody().unblockSavingsAccount(accountId).build(); + final CommandWrapper commandRequest = builder.unblockSavingsAccount(accountId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } 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 e1722e9a4..e42cfc978 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 @@ -64,6 +64,7 @@ public final class SavingsAccountData implements Serializable { private final String fieldOfficerName; private final SavingsAccountStatusEnumData status; private final SavingsAccountSubStatusEnumData subStatus; + private final String reasonForBlock; private final SavingsAccountApplicationTimelineData timeline; private final CurrencyData currency; private final BigDecimal nominalAnnualInterestRate; @@ -174,6 +175,7 @@ public final class SavingsAccountData implements Serializable { this.fieldOfficerName = null; this.status = null; this.subStatus = null; + this.reasonForBlock = null; this.timeline = null; this.currency = null; this.nominalAnnualInterestRate = nominalAnnualInterestRate; @@ -528,6 +530,7 @@ public final class SavingsAccountData implements Serializable { this.fieldOfficerName = null; this.status = null; this.subStatus = null; + this.reasonForBlock = null; this.timeline = null; this.currency = null; this.nominalAnnualInterestRate = nominalAnnualInterestRate; @@ -584,7 +587,7 @@ public final class SavingsAccountData implements Serializable { public static SavingsAccountData instance(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId, final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldOfficerId, final String fieldOfficerName, - final SavingsAccountStatusEnumData status, SavingsAccountSubStatusEnumData subStatus, + final SavingsAccountStatusEnumData status, SavingsAccountSubStatusEnumData subStatus, final String reasonForBlock, final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal interestRate, final EnumOptionData interestCompoundingPeriodType, final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, @@ -611,7 +614,7 @@ public final class SavingsAccountData implements Serializable { final Collection chargeOptions = null; return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, - productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, interestRate, + productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, timeline, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, transactions, productOptions, fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, @@ -676,6 +679,7 @@ public final class SavingsAccountData implements Serializable { final boolean withHoldTax = false; final TaxGroupData taxGroup = null; final SavingsAccountSubStatusEnumData subStatus = null; + final String reasonForBlock = null; final LocalDate lastActiveTransactionDate = null; final boolean isDormancyTrackingActive = false; final Integer daysToInactive = null; @@ -684,15 +688,15 @@ public final class SavingsAccountData implements Serializable { final BigDecimal savingsAmountOnHold = null; return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, - productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate, - interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, 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); + productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, timeline, currency, + nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + 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); } public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo, @@ -747,6 +751,7 @@ public final class SavingsAccountData implements Serializable { final boolean withHoldTax = false; final TaxGroupData taxGroup = null; final SavingsAccountSubStatusEnumData subStatus = null; + final String reasonForBlock = null; final LocalDate lastActiveTransactionDate = null; final boolean isDormancyTrackingActive = false; final Integer daysToInactive = null; @@ -755,15 +760,15 @@ public final class SavingsAccountData implements Serializable { final BigDecimal savingsAmountOnHold = null; return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, - productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate, - interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, 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); + productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, timeline, currency, + nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + 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); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, @@ -787,18 +792,19 @@ public final class SavingsAccountData implements Serializable { return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, - account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.timeline, account.currency, - account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, - account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, - account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, - transactions, template.productOptions, template.fieldOfficerOptions, template.interestCompoundingPeriodTypeOptions, - template.interestPostingPeriodTypeOptions, 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.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.reasonForBlock, + account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, + account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType, + account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, + account.withdrawalFeeForTransfers, account.summary, transactions, template.productOptions, template.fieldOfficerOptions, + template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions, + 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); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, @@ -813,17 +819,18 @@ public final class SavingsAccountData implements Serializable { return new SavingsAccountData(account.id, account.accountNo, account.depositType, account.externalId, account.groupId, account.groupName, account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, - account.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.timeline, account.currency, - account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, - account.interestCalculationType, account.interestCalculationDaysInYearType, account.minRequiredOpeningBalance, - account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, account.withdrawalFeeForTransfers, account.summary, - transactions, productOptions, fieldOfficerOptions, 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.fieldOfficerId, account.fieldOfficerName, account.status, account.subStatus, account.reasonForBlock, + account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, + account.interestPostingPeriodType, account.interestCalculationType, account.interestCalculationDaysInYearType, + account.minRequiredOpeningBalance, account.lockinPeriodFrequency, account.lockinPeriodFrequencyType, + account.withdrawalFeeForTransfers, account.summary, transactions, productOptions, fieldOfficerOptions, + 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); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -880,6 +887,7 @@ public final class SavingsAccountData implements Serializable { final Collection charges = null; final Collection chargeOptions = null; final SavingsAccountSubStatusEnumData subStatus = null; + final String reasonForBlock = null; final LocalDate lastActiveTransactionDate = null; final boolean isDormancyTrackingActive = false; final Integer daysToInactive = null; @@ -888,28 +896,28 @@ public final class SavingsAccountData implements Serializable { final BigDecimal savingsAmountOnHold = null; return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, - productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate, - interestPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, - minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, 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); + productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, timeline, currency, + nominalAnnualInterestRate, interestPeriodType, interestPostingPeriodType, interestCalculationType, + interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, + 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); } private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId, final Long groupId, final String groupName, final Long clientId, final String clientName, final Long productId, final String productName, final Long fieldOfficerId, final String fieldOfficerName, final SavingsAccountStatusEnumData status, - final SavingsAccountSubStatusEnumData subStatus, final SavingsAccountApplicationTimelineData timeline, - final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, final EnumOptionData interestPeriodType, - final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType, - final EnumOptionData interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance, - final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, final boolean withdrawalFeeForTransfers, - final SavingsAccountSummaryData summary, final Collection transactions, - final Collection productOptions, final Collection fieldOfficerOptions, - final Collection interestCompoundingPeriodTypeOptions, + final SavingsAccountSubStatusEnumData subStatus, final String reasonForBlock, + final SavingsAccountApplicationTimelineData timeline, final CurrencyData currency, final BigDecimal nominalAnnualInterestRate, + final EnumOptionData interestPeriodType, final EnumOptionData interestPostingPeriodType, + final EnumOptionData interestCalculationType, final EnumOptionData interestCalculationDaysInYearType, + final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency, final EnumOptionData lockinPeriodFrequencyType, + final boolean withdrawalFeeForTransfers, final SavingsAccountSummaryData summary, + final Collection transactions, final Collection productOptions, + final Collection fieldOfficerOptions, final Collection interestCompoundingPeriodTypeOptions, final Collection interestPostingPeriodTypeOptions, final Collection interestCalculationTypeOptions, final Collection interestCalculationDaysInYearTypeOptions, @@ -935,6 +943,7 @@ public final class SavingsAccountData implements Serializable { this.fieldOfficerName = fieldOfficerName; this.status = status; this.subStatus = subStatus; + this.reasonForBlock = reasonForBlock; this.timeline = timeline; this.currency = currency; this.nominalAnnualInterestRate = nominalAnnualInterestRate; 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 acf5fd928..3338635a8 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,8 @@ public final class SavingsAccountTransactionData implements Serializable { private final boolean isManualTransaction; private final Boolean isReversal; private final Long originalTransactionId; + private final Long releaseTransactionId; + private final String reasonForBlock; private Set chargesPaidByData = new HashSet<>(); // templates @@ -347,6 +349,8 @@ public final class SavingsAccountTransactionData implements Serializable { this.note = null; this.isReversal = null; this.originalTransactionId = null; + this.releaseTransactionId = null; + this.reasonForBlock = null; } public boolean isChargeTransaction() { @@ -544,7 +548,8 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = isManualTransaction; this.isReversal = null; this.originalTransactionId = null; - + this.releaseTransactionId = null; + this.reasonForBlock = null; } private SavingsAccountTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, String accountNumber, @@ -582,6 +587,8 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = null; this.originalTransactionId = null; + this.releaseTransactionId = null; + this.reasonForBlock = null; } private SavingsAccountTransactionData(Integer id, BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, @@ -621,6 +628,8 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = null; this.originalTransactionId = null; + this.releaseTransactionId = null; + this.reasonForBlock = null; } public Integer getRowIndex() { @@ -753,6 +762,8 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = null; this.originalTransactionId = null; + this.releaseTransactionId = null; + this.reasonForBlock = null; } public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType, @@ -770,11 +781,12 @@ 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 LocalDate submittedOnDate, final boolean interestedPostedAsOn, - final String submittedByUsername, final String note, final Boolean isReversal, final Long originalTransactionId) { + final String submittedByUsername, final String note, final Boolean isReversal, final Long originalTransactionId, + 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); + interestedPostedAsOn, submittedByUsername, note, isReversal, originalTransactionId, releaseTransactionId, reasonForBlock); } public static SavingsAccountTransactionData create(final Long id) { @@ -827,7 +839,7 @@ 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 Boolean isReversal, final Long originalTransactionId, final Long releaseTransactionId, final String reasonForBlock) { this.id = id; this.transactionType = transactionType; this.paymentDetailData = paymentDetailData; @@ -853,6 +865,8 @@ public final class SavingsAccountTransactionData implements Serializable { this.isManualTransaction = false; this.isReversal = isReversal; this.originalTransactionId = originalTransactionId; + this.releaseTransactionId = releaseTransactionId; + this.reasonForBlock = reasonForBlock; } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, @@ -886,6 +900,43 @@ 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.releaseTransactionId = null; + this.reasonForBlock = null; } public static SavingsAccountTransactionData withWithDrawalTransactionDetails( 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 f3acadedf..86235bc9a 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 @@ -57,6 +57,8 @@ 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; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.savings.exception.TransactionBeforePivotDateNotAllowed; import org.apache.fineract.useradministration.domain.AppUser; @@ -69,14 +71,17 @@ 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)); + transactionAmountParamName, lienParamName, SavingsApiConstants.reasonForBlockParamName)); + private final ConfigurationDomainService configurationDomainService; + private final SavingsAccountAssembler savingAccountAssembler; @Autowired public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper, - final ConfigurationDomainService configurationDomainService) { + final ConfigurationDomainService configurationDomainService, final SavingsAccountAssembler savingAccountAssembler) { this.fromApiJsonHelper = fromApiJsonHelper; this.configurationDomainService = configurationDomainService; + this.savingAccountAssembler = savingAccountAssembler; } public void validateTransactionWithPivotDate(final LocalDate transactionDate, final SavingsAccount savingsAccount) { @@ -179,6 +184,13 @@ public class SavingsAccountTransactionDataValidator { account.getId()); } + if (account.getSubStatus().equals(SavingsAccountSubStatusEnum.BLOCK.getValue()) + || account.getSubStatus().equals(SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue()) + || account.getSubStatus().equals(SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue())) { + baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName) + .failWithCodeNoParameterAddedToErrorCode("account.is.in.blocked.state"); + } + validatePaymentTypeDetails(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -225,6 +237,10 @@ public class SavingsAccountTransactionDataValidator { baseDataValidator.reset().parameter(transactionAmountParamName).value(amount).notNull().positiveAmount(); final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element); + final String reasonForBlock = this.fromApiJsonHelper.extractStringNamed(SavingsApiConstants.reasonForBlockParamName, element); + baseDataValidator.reset().parameter(SavingsApiConstants.reasonForBlockParamName).value(reasonForBlock).notBlank() + .notExceedingLengthOf(100); + baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull(); boolean isActive = account.isActive(); @@ -232,21 +248,18 @@ public class SavingsAccountTransactionDataValidator { baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); } - account.holdAmount(amount); + + Boolean lien = false; + if (this.fromApiJsonHelper.parameterExists(lienParamName, element)) { + lien = this.fromApiJsonHelper.extractBooleanNamed(lienParamName, element); + } if (account.getEnforceMinRequiredBalance()) { if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId()); - } - } - - Boolean lien = false; - - if (this.fromApiJsonHelper.parameterExists(lienParamName, element)) { - lien = this.fromApiJsonHelper.extractBooleanNamed(lienParamName, element); - if (!lien) { - if (account.getWithdrawableBalanceWithoutMinimumBalance().compareTo(BigDecimal.ZERO) < 0) { - baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId()); + if (!lien) { + if (account.getWithdrawableBalanceWithoutMinimumBalance().compareTo(account.getMinRequiredBalance()) < 0) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId()); + } } } } @@ -271,6 +284,7 @@ public class SavingsAccountTransactionDataValidator { SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails, transactionDate, Money.of(account.getCurrency(), amount), createdDate, createdUser); + return transaction; } @@ -298,6 +312,7 @@ 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/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 3bf5809f7..8b56a5875 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 @@ -220,6 +220,9 @@ public class SavingsAccount extends AbstractPersistableCustom { @JoinColumn(name = "closedon_userid", nullable = true) protected AppUser closedBy; + @Column(name = "reason_for_block", nullable = true) + protected String reasonForBlock; + @Embedded protected MonetaryCurrency currency; @@ -1442,8 +1445,16 @@ public class SavingsAccount extends AbstractPersistableCustom { } if (this.getSavingsHoldAmount().compareTo(BigDecimal.ZERO) > 0) { - if (runningBalance.minus(this.getSavingsHoldAmount()).isLessThanZero()) { - throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount); + if (this.enforceMinRequiredBalance) { + if (runningBalance.minus(minRequiredBalance.plus(this.getSavingsHoldAmount())).isLessThanZero()) { + throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, + transactionAmount); + } + } else { + if (runningBalance.minus(this.getSavingsHoldAmount()).isLessThanZero()) { + throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, + transactionAmount); + } } } } @@ -3500,6 +3511,10 @@ public class SavingsAccount extends AbstractPersistableCustom { this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, transactions); } + public void updateReason(final String reasonForBlock) { + this.reasonForBlock = reasonForBlock; + } + public Map block() { final Map actualChanges = new LinkedHashMap<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java index 92c358e43..8eb54625c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java @@ -262,6 +262,10 @@ public final class SavingsAccountSummary { return this.accountBalance; } + public void setAccountBalance(BigDecimal accountBalance) { + this.accountBalance = accountBalance; + } + public BigDecimal getTotalInterestPosted() { return this.totalInterestPosted; } 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 014ca9967..6b2c9c119 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 @@ -127,6 +127,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { @Column(name = "release_id_of_hold_amount", length = 20) private Long releaseIdOfHoldAmountTransaction; + @Column(name = "reason_for_block", nullable = true) + private String reasonForBlock; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "savings_account_transaction_id", referencedColumnName = "id") private List notes = new ArrayList<>(); @@ -843,6 +846,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { this.releaseIdOfHoldAmountTransaction = releaseId; } + public void updateReason(String reasonForBlock) { + this.reasonForBlock = reasonForBlock; + } + public Long getReleaseIdOfHoldAmountTransaction() { return this.releaseIdOfHoldAmountTransaction; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockCreditsToSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockCreditsToSavingsAccountCommandHandler.java index 2c483f605..7f623b460 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockCreditsToSavingsAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockCreditsToSavingsAccountCommandHandler.java @@ -41,7 +41,7 @@ public class BlockCreditsToSavingsAccountCommandHandler implements NewCommandSou @Transactional @Override public CommandProcessingResult processCommand(JsonCommand command) { - return this.writePlatformService.blockCredits(command.getSavingsId()); + return this.writePlatformService.blockCredits(command.getSavingsId(), command); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockDebitsFromSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockDebitsFromSavingsAccountCommandHandler.java index 15bbf5044..53cb80f70 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockDebitsFromSavingsAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockDebitsFromSavingsAccountCommandHandler.java @@ -41,7 +41,7 @@ public class BlockDebitsFromSavingsAccountCommandHandler implements NewCommandSo @Transactional @Override public CommandProcessingResult processCommand(JsonCommand command) { - return this.writePlatformService.blockDebits(command.getSavingsId()); + return this.writePlatformService.blockDebits(command.getSavingsId(), command); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockSavingsAccountCommandHandler.java index 6001c668d..d2dec0012 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockSavingsAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockSavingsAccountCommandHandler.java @@ -41,7 +41,7 @@ public class BlockSavingsAccountCommandHandler implements NewCommandSourceHandle @Transactional @Override public CommandProcessingResult processCommand(JsonCommand command) { - return this.writePlatformService.blockAccount(command.getSavingsId()); + return this.writePlatformService.blockAccount(command.getSavingsId(), command); } } 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 71e67943b..0888ea820 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 @@ -576,7 +576,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final BigDecimal minRequiredBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "minRequiredBalance"); final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance"); savingsAccountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, null, null, null, null, - productId, null, null, null, status, subStatus, timeline, currency, nominalAnnualInterestRate, + productId, null, null, null, status, subStatus, null, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, @@ -746,6 +746,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("s.id fieldOfficerId, s.display_name as fieldOfficerName, "); sqlBuilder.append("sa.status_enum as statusEnum, "); sqlBuilder.append("sa.sub_status_enum as subStatusEnum, "); + sqlBuilder.append("sa.reason_for_block as reasonForBlock, "); sqlBuilder.append("sa.submittedon_date as submittedOnDate,"); sqlBuilder.append("sbu.username as submittedByUsername,"); sqlBuilder.append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname,"); @@ -874,6 +875,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum"); final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum); + final String reasonForBlock = rs.getString("reasonForBlock"); final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate"); final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive"); @@ -1022,7 +1024,6 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead BigDecimal availableBalance = accountBalance; if (availableBalance != null && onHoldFunds != null) { - availableBalance = availableBalance.subtract(onHoldFunds); } @@ -1052,12 +1053,13 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead } return SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, - productName, fieldOfficerId, fieldOfficerName, status, subStatus, 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); + productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, 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); } } @@ -1312,6 +1314,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final StringBuilder sqlBuilder = new StringBuilder(400); sqlBuilder.append("tr.id as transactionId, tr.transaction_type_enum as transactionType, "); sqlBuilder.append("tr.transaction_date as transactionDate, tr.amount as transactionAmount,"); + sqlBuilder.append(" tr.release_id_of_hold_amount as releaseTransactionId,"); + sqlBuilder.append(" tr.reason_for_block as reasonForBlock,"); sqlBuilder.append("tr.created_date as submittedOnDate,"); sqlBuilder.append(" au.username as submittedByUsername, "); sqlBuilder.append(" nt.note as transactionNote, "); @@ -1357,6 +1361,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final LocalDate date = JdbcSupport.getLocalDate(rs, "transactionDate"); final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); final BigDecimal amount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transactionAmount"); + final Long releaseTransactionId = rs.getLong("releaseTransactionId"); + final String reasonForBlock = rs.getString("reasonForBlock"); final BigDecimal outstandingChargeAmount = null; final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "runningBalance"); final boolean reversed = rs.getBoolean("reversed"); @@ -1416,7 +1422,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); + submittedByUsername, note, isReversal, originalTransactionId, releaseTransactionId, reasonForBlock); } } @@ -1619,6 +1625,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final BigDecimal savingsAmountOnHold = null; final SavingsAccountSubStatusEnumData subStatus = null; + final String reasonForBlock = null; final LocalDate lastActiveTransactionDate = null; final boolean isDormancyTrackingActive = false; final Integer daysToInactive = null; @@ -1628,13 +1635,13 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final SavingsAccountApplicationTimelineData timeline = SavingsAccountApplicationTimelineData.templateDefault(); final EnumOptionData depositType = null; return SavingsAccountData.instance(null, null, depositType, null, groupId, groupName, clientId, clientName, productId, - productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, 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); + productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, timeline, currency, + 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); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java index 2036b53d1..dab91c282 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -96,17 +96,17 @@ public interface SavingsAccountWritePlatformService { SavingsAccountData postInterest(SavingsAccountData account, boolean postInterestAs, LocalDate transactionDate, boolean backdatedTxnsAllowedTill); - CommandProcessingResult blockAccount(Long savingsId); + CommandProcessingResult blockAccount(Long savingsId, JsonCommand command); CommandProcessingResult unblockAccount(Long savingsId); CommandProcessingResult holdAmount(Long savingsId, JsonCommand command); - CommandProcessingResult blockCredits(Long savingsId); + CommandProcessingResult blockCredits(Long savingsId, JsonCommand command); CommandProcessingResult unblockCredits(Long savingsId); - CommandProcessingResult blockDebits(Long savingsId); + CommandProcessingResult blockDebits(Long savingsId, JsonCommand command); CommandProcessingResult unblockDebits(Long savingsId); 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 9d920fc2d..68acfaf16 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,6 +23,7 @@ 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.transactionAmountParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawBalanceParamName; @@ -53,6 +54,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.dataqueries.data.EntityTables; @@ -1762,7 +1764,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } @Override - public CommandProcessingResult blockAccount(final Long savingsId) { + public CommandProcessingResult blockAccount(final Long savingsId, final JsonCommand command) { this.context.authenticatedUser(); @@ -1770,6 +1772,11 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi checkClientOrGroupActive(account); final Map changes = account.block(); + + final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); + validateReasonForHold(reasonForBlock); + account.updateReason(reasonForBlock); + if (!changes.isEmpty()) { this.savingAccountRepositoryWrapper.save(account); @@ -1786,6 +1793,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi checkClientOrGroupActive(account); final Map changes = account.unblock(); + + account.updateReason(null); + if (!changes.isEmpty()) { this.savingAccountRepositoryWrapper.save(account); @@ -1804,12 +1814,29 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi checkClientOrGroupActive(account); - SavingsAccountTransaction transacton = this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(), + final BigDecimal amount = command.bigDecimalValueOfParameterNamed(transactionAmountParamName); + + Money runningBalance = Money.of(account.getCurrency(), account.getAccountBalance()); + if (account.getSavingsHoldAmount() != null) { + runningBalance = runningBalance.minus(account.getSavingsHoldAmount()).minus(amount); + } else { + runningBalance = runningBalance.minus(amount); + } + + account.holdAmount(amount); + + SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(), account, submittedBy, backdatedTxnsAllowedTill); - this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transacton.getTransactionLocalDate(), account); + transaction.updateRunningBalance(runningBalance); - this.savingsAccountTransactionRepository.saveAndFlush(transacton); + final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); + transaction.updateReason(reasonForBlock); + + account.getAccountBalance(); + this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionLocalDate(), account); + + this.savingsAccountTransactionRepository.saveAndFlush(transaction); if (backdatedTxnsAllowedTill) { // Check again whether transactions are modified @@ -1818,7 +1845,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi this.savingAccountRepositoryWrapper.saveAndFlush(account); - return new CommandProcessingResultBuilder().withEntityId(transacton.getId()).withOfficeId(account.officeId()) + return new CommandProcessingResultBuilder().withEntityId(transaction.getId()).withOfficeId(account.officeId()) .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).build(); } @@ -1830,6 +1857,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi SavingsAccountTransaction holdTransaction = this.savingsAccountTransactionRepository .findOneByIdAndSavingsAccountId(savingsTransactionId, savingsId); + holdTransaction.updateReason(null); + final SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator .validateReleaseAmountAndAssembleForm(holdTransaction, submittedBy); @@ -1837,6 +1866,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); checkClientOrGroupActive(account); + Money runningBalance = Money.of(account.getCurrency(), account.getAccountBalance()); + + Money savingsOnHold = Money.of(account.getCurrency(), account.getSavingsHoldAmount()); + + runningBalance = runningBalance.minus(savingsOnHold); + + runningBalance = runningBalance.plus(transaction.getAmount()); + transaction.updateRunningBalance(runningBalance); + this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionLocalDate(), account); account.releaseOnHoldAmount(transaction.getAmount()); @@ -1855,12 +1893,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override - public CommandProcessingResult blockCredits(final Long savingsId) { + public CommandProcessingResult blockCredits(final Long savingsId, final JsonCommand command) { this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); checkClientOrGroupActive(account); + final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); + validateReasonForHold(reasonForBlock); + account.updateReason(reasonForBlock); + final Map changes = account.blockCredits(account.getSubStatus()); if (!changes.isEmpty()) { @@ -1877,7 +1919,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); checkClientOrGroupActive(account); - + account.updateReason(null); final Map changes = account.unblockCredits(); if (!changes.isEmpty()) { @@ -1889,12 +1931,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override - public CommandProcessingResult blockDebits(final Long savingsId) { + public CommandProcessingResult blockDebits(final Long savingsId, final JsonCommand command) { this.context.authenticatedUser(); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); checkClientOrGroupActive(account); + final String reasonForBlock = command.stringValueOfParameterNamed(SavingsApiConstants.reasonForBlockParamName); + validateReasonForHold(reasonForBlock); + account.updateReason(reasonForBlock); + final Map changes = account.blockDebits(account.getSubStatus()); if (!changes.isEmpty()) { @@ -1912,6 +1958,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false); checkClientOrGroupActive(account); + account.updateReason(null); + final Map changes = account.unblockDebits(); if (!changes.isEmpty()) { @@ -1934,4 +1982,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } } + + private void validateReasonForHold(String reasonForBlock) { + if (StringUtils.isBlank(reasonForBlock)) { + throw new PlatformDataIntegrityException("Reason For Block is Mandatory", "error.msg.reason.for.block.mandatory"); + } + } } 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 8412d3ac3..bc73503ce 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 @@ -28,4 +28,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0009_hold_reason_savings_account.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0009_hold_reason_savings_account.xml new file mode 100644 index 000000000..d9e2d6755 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0009_hold_reason_savings_account.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 88465822d..ef2fa2fbb 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 @@ -2193,7 +2193,7 @@ public class ClientSavingsIntegrationTest { final String minBalanceForInterestCalculation = null; final String minRequiredBalance = null; - final String enforceMinRequiredBalance = "false"; + final String enforceMinRequiredBalance = "true"; final boolean allowOverdraft = false; final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); 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 733cf33b7..e0335b1bb 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 @@ -338,8 +338,8 @@ public class SavingsAccountHelper { public HashMap blockSavings(final Integer savingsID) { LOG.info("---------------------------------- BLOCKING SAVINGS ACCOUNT ----------------------------------"); Boolean isBlock = true; - return performSavingApplicationActions(createSavingsOperationURL(BLOCK_SAVINGS_COMMAND, savingsID), getActivatedSavingsAsJSON(), - isBlock); + return performSavingApplicationActions(createSavingsOperationURL(BLOCK_SAVINGS_COMMAND, savingsID), + getActivatedSavingsAsForHoldJSON(), isBlock); } public HashMap unblockSavings(final Integer savingsID) { @@ -353,7 +353,7 @@ public class SavingsAccountHelper { LOG.info("---------------------------------- BLOCKING DEBIT TRANSACTIONS ----------------------------------"); Boolean isBlock = true; return performSavingApplicationActions(createSavingsOperationURL(BLOCK_DEBITS_SAVINGS_COMMAND, savingsID), - getActivatedSavingsAsJSON(), isBlock); + getActivatedSavingsAsForHoldJSON(), isBlock); } public HashMap unblockDebit(final Integer savingsID) { @@ -367,7 +367,7 @@ public class SavingsAccountHelper { LOG.info("---------------------------------- BLOCKING CREDIT TRANSACTIONS ----------------------------------"); Boolean isBlock = true; return performSavingApplicationActions(createSavingsOperationURL(BLOCK_CREDITS_SAVINGS_COMMAND, savingsID), - getActivatedSavingsAsJSON(), isBlock); + getActivatedSavingsAsForHoldJSON(), isBlock); } public HashMap unblockCredit(final Integer savingsID) { @@ -437,6 +437,17 @@ public class SavingsAccountHelper { return savingsAccountActivateJson; } + private String getActivatedSavingsAsForHoldJSON() { + final HashMap map = new HashMap<>(); + map.put("locale", CommonConstants.LOCALE); + map.put("dateFormat", CommonConstants.DATE_FORMAT); + map.put("activatedOnDate", TRANSACTION_DATE); + map.put("reasonForBlock", "unUsualActivity"); + String savingsAccountActivateJson = new Gson().toJson(map); + LOG.info(savingsAccountActivateJson); + return savingsAccountActivateJson; + } + private String getSavingsTransactionJSON(final String amount, final String transactionDate) { final HashMap map = new HashMap<>(); map.put("locale", CommonConstants.LOCALE); @@ -455,6 +466,7 @@ public class SavingsAccountHelper { map.put("transactionDate", transactionDate); map.put("transactionAmount", amount); map.put("lien", lien); + map.put("reasonForBlock", "unUsualActivity"); String savingsAccountWithdrawalJson = new Gson().toJson(map); LOG.info(savingsAccountWithdrawalJson); return savingsAccountWithdrawalJson;