From fe3741c2cebfcceb4615f07dd0a7a7260410c149 Mon Sep 17 00:00:00 2001 From: ShruthiRajaram Date: Wed, 21 Jun 2017 13:21:36 +0530 Subject: [PATCH] block account/amount/credit/debit --- api-docs/apiLive.htm | 426 +++++++++++++++++- .../ClientSavingsIntegrationTest.java | 111 +++++ .../common/savings/SavingsAccountHelper.java | 85 +++- .../common/savings/SavingsStatusChecker.java | 25 + .../service/CommandWrapperBuilder.java | 73 +++ .../SavingsAccountTransactionType.java | 22 +- .../savings/SavingsApiConstants.java | 20 + ...SavingsAccountTransactionsApiResource.java | 11 +- .../api/SavingsAccountsApiResource.java | 22 +- .../data/SavingsAccountSubStatusEnumData.java | 11 +- .../data/SavingsAccountSummaryData.java | 4 +- ...avingsAccountTransactionDataValidator.java | 71 +++ .../SavingsAccountTransactionEnumData.java | 12 + .../savings/domain/SavingsAccount.java | 191 +++++++- .../SavingsAccountDomainServiceJpa.java | 4 + .../domain/SavingsAccountSubStatusEnum.java | 26 +- .../domain/SavingsAccountTransaction.java | 34 ++ .../SavingsAccountBlockedException.java | 29 ++ ...SavingsAccountCreditsBlockedException.java | 29 ++ .../SavingsAccountDebitsBlockedException.java | 30 ++ ...CreditsToSavingsAccountCommandHandler.java | 47 ++ ...ebitsFromSavingsAccountCommandHandler.java | 47 ++ .../BlockSavingsAccountCommandHandler.java | 47 ++ ...oldAmountSavingsAccountCommandHandler.java | 47 ++ ...aseAmountSavingsAccountCommandHandler.java | 48 ++ ...CreditsToSavingsAccountCommandHandler.java | 47 ++ ...ebitsFromSavingsAccountCommandHandler.java | 46 ++ .../UnblockSavingsAccountCommandHandler.java | 46 ++ ...DepositAccountReadPlatformServiceImpl.java | 3 +- ...SavingsAccountReadPlatformServiceImpl.java | 8 +- .../SavingsAccountWritePlatformService.java | 16 + ...WritePlatformServiceJpaRepositoryImpl.java | 137 ++++++ .../savings/service/SavingsEnumerations.java | 31 +- ..._savings_account_transaction_releaseId.sql | 44 ++ 34 files changed, 1824 insertions(+), 26 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountBlockedException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountCreditsBlockedException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/SavingsAccountDebitsBlockedException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockCreditsToSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockDebitsFromSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/BlockSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/HoldAmountSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ReleaseAmountSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UnblockCreditsToSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UnblockDebitsFromSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/UnblockSavingsAccountCommandHandler.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V329__savings_account_transaction_releaseId.sql diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 41ad457d9..bc0c43e2d 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -629,6 +629,54 @@ + + + savingsaccounts/{accountId}?command=block + Block savings account + + + + + + + savingsaccounts/{accountId}?command=unblock + Unblock savings account + + + + + + + savingsaccounts/{accountId}?command=blockCredit + Block Savings Account Credit Transactions + + + + + + + savingsaccounts/{accountId}?command=unblockCredit + Unblock Savings Account Credit Transactions + + + + + + + savingsaccounts/{accountId}?command=blockDebit + Block Savings Account Debit Transactions + + + + + + + savingsaccounts/{accountId}?command=unblockDebit + Unblock Savings Account Debit Transactions + + + + Savings Transactions savingsaccounts/{accountId}/transactions?command=deposit @@ -661,6 +709,22 @@ + + + savingsaccounts/{accountId}/transactions/{transactionId}?command=holdAmount + Hold Amount + + + + + + + savingsaccounts/{accountId}/transactions/{transactionId}?command=releaseAmount + Release Amount + + + + savingsaccounts/{accountId}/transactions/template @@ -26243,7 +26307,248 @@ Request Body: +  +
+
+

Block Savings Account

+

+ Blocks Savings account from all types of credit and debit transactions +

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=block + POST savingsaccount/1?command=block +Content-Type: application/json +Request Body: +{ +} + + +{ + "officeId":1, + "clientId":1, + "savingsId":1, + "resourceId":1, + "changes":{ + "subStatus":{ + "id":400, + "code":"SavingsAccountSubStatusEnum.block", + "value":"Block", + "none":false, + "inactive":false, + "dormant":false, + "escheat":false, + "block":true, + "blockCredit":false, + "blockDebit":false + } + } +} + +
+
+  +
+
+

Unblock Savings Account

+

+ Unblock a blocked account. On unblocking account, user can perform debit and credit transactions +

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=unblock + POST savingsaccount/1?command=unblock +Content-Type: application/json +Request Body: +{ +} + + +{ + "officeId":1, + "clientId":1, + "savingsId":1, + "resourceId":1, + "changes":{ + "subStatus":{ + "id":0, + "code":"SavingsAccountSubStatusEnum.none", + "value":"None", + "none":true, + "inactive":false, + "dormant":false, + "escheat":false, + "block":false, + "blockCredit":false, + "blockDebit":false + } + } +} + +
+
+ +  +
+
+

Block Savings Account Credit transactions

+

+ Savings account will be blocked from all types of credit transactions. +

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=blockCredit + POST savingsaccount/1?command=blockCredit +Content-Type: application/json +Request Body: +{ +} + + +{ + "officeId":1, + "clientId":1, + "savingsId":1, + "resourceId":1, + "changes":{ + "subStatus":{ + "id":500, + "code":"SavingsAccountSubStatusEnum.blockcredit", + "value":"BlockCredit", + "none":false, + "inactive":false, + "dormant":false, + "escheat":false, + "block":false, + "blockCredit":true, + "blockDebit":false + } + } +} + +
+
+ +  +
+
+

Unblock Savings Account Credit transactions

+

It unblocks the Saving account's credit operations. Now all types of credits can be transacted to Savings account +

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=unblockCredit + POST savingsaccount/1?command=unblockCredit +Content-Type: application/json +Request Body: +{ +} + + +{ + "officeId":1, + "clientId":1, + "savingsId":1, + "resourceId":1, + "changes":{ + "subStatus":{ + "id":0, + "code":"SavingsAccountSubStatusEnum.none", + "value":"None", + "none":true, + "inactive":false, + "dormant":false, + "escheat":false, + "block":false, + "blockCredit":false, + "blockDebit":false + } + } +} + +
+
+ + +  +
+
+

Block Savings Account Debit transactions

+

All types of debit operations from Savings account wil be blocked

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=blockDebit + POST savingsaccount/1?command=blockDebit +Content-Type: application/json +Request Body: +{ +} + + +{ + "officeId":1, + "clientId":1, + "savingsId":1, + "resourceId":1, + "changes":{ + "subStatus":{ + "id":600, + "code":"SavingsAccountSubStatusEnum.blockdebit", + "value":"BlockDebit", + "none":false, + "inactive":false, + "dormant":false, + "escheat":false, + "block":false, + "blockCredit":false, + "blockDebit":true + } + } +} + +
+
+ +  +
+
+

Unblock Savings Account debit transactions

+

It unblocks the Saving account's debit operations. Now all types of debits can be transacted from Savings account +

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/{savingsId}?command=unblockDebit + POST savingsaccount/1?command=unblockDebit +Content-Type: application/json +Request Body: +{ +} + + +{ + "officeId":1, + "clientId":1, + "savingsId":1, + "resourceId":1, + "changes":{ + "subStatus":{ + "id":0, + "code":"SavingsAccountSubStatusEnum.none", + "value":"None", + "none":true, + "inactive":false, + "dormant":false, + "escheat":false, + "block":false, + "blockCredit":false, + "blockDebit":false + } + } +} + +
+
 
@@ -26417,7 +26722,8 @@ Request Body: "nameCode": "currency.USD", "displayLabel": "US Dollar ($)" }, - "accountBalance": 0 + "accountBalance": 0, + "availableBalance": 0 } } @@ -26787,6 +27093,60 @@ No Request Body:
+  +
+
+

Hold Amount

+

Hold amount enables the user to hold/block/lien the specified amount from the account

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/4/transactions?command=holdAmount + POST savingsaccounts/4/transactions?command=holdAmount +Content-Type: application/json +Request Body: +{ + transactionDate:"15 June 2017", + transactionAmount:"200", + locale:"en", + dateFormat:"dd MMMM yyyy" +} + + +{ + "officeId":1, + "clientId":2, + "savingsId":4, + "resourceId":35 +} + +
+
+  +
+
+

Release Amount

+

Release amount removes the hold/block/lien already placed on an amount in the account.

+
+
+ POST https://Domain Name/api/v1/savingsaccounts/4/transactions/35?command=releaseAmount + POST savingsaccounts/4/transactions/35?command=releaseAmount +Content-Type: application/json +No Request Body: +{ + +} + + +{ + "officeId":1, + "clientId":2, + "savingsId":4, + "resourceId":36 +} + +
+
+ @@ -48361,6 +48721,54 @@ No Request Body: + + + savingsaccounts/{accountId}?command=block + Block savings account + + + + + + + savingsaccounts/{accountId}?command=unblock + UnBlock savings account + + + + + + + savingsaccounts/{accountId}?command=blockCredit + Block Savings Account Credit transactions + + + + + + + savingsaccounts/{accountId}?command=unblockCredit + Unblock Savings Account Credit transactions + + + + + + + savingsaccounts/{accountId}?command=blockDebit + Block Savings Account Debit transactions + + + + + + + savingsaccounts/{accountId}?command=unblockDebit + Unblock Savings Account Debit transactions + + + + Savings Transactions savingsaccounts/{accountId}/transactions?command=deposit @@ -48393,6 +48801,22 @@ No Request Body: + + + savingsaccounts/{accountId}/transactions/{transactionId}?command=holdAmount + Hold Amount + + + + + + + savingsaccounts/{accountId}/transactions/{transactionId}?command=releaseAmount + Release Amount + + + + savingsaccounts/{accountId}/transactions/template diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index 7a49c762b..b279163e1 100755 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -2115,4 +2115,115 @@ public class ClientSavingsIntegrationTest { * assertEquals("Verifying Interest Calculation", new Float("238.3399"), * savingsInterest); } */ + + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountBlockStatus() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec, + new ResponseSpecBuilder().build()); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assert.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assert.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); + + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + Float balance = new Float(MINIMUM_OPENING_BALANCE); + + savingsStatusHashMap = this.savingsAccountHelper.blockSavings(savingsId); + SavingsStatusChecker.verifySavingsSubStatusblock(savingsStatusHashMap); + + List error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "100", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("error.msg.saving.account.blocked.transaction.not.allowed", + error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "100", SavingsAccountHelper.TRANSACTION_DATE, + CommonConstants.RESPONSE_ERROR); + assertEquals("error.msg.saving.account.blocked.transaction.not.allowed", + error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + savingsStatusHashMap = this.savingsAccountHelper.unblockSavings(savingsId); + SavingsStatusChecker.verifySavingsSubStatusIsNone(savingsStatusHashMap); + Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + HashMap depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId); + balance += new Float(DEPOSIT_AMOUNT); + assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount")); + + savingsStatusHashMap = this.savingsAccountHelper.blockDebit(savingsId); + SavingsStatusChecker.verifySavingsSubStatusIsDebitBlocked(savingsStatusHashMap); + error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "100", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("error.msg.savings.account.debit.transaction.not.allowed", + error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId); + balance += new Float(DEPOSIT_AMOUNT); + assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount")); + + savingsStatusHashMap = this.savingsAccountHelper.unblockDebit(savingsId); + SavingsStatusChecker.verifySavingsSubStatusIsNone(savingsStatusHashMap); + Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, WITHDRAW_AMOUNT, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + HashMap withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId); + balance -= new Float(WITHDRAW_AMOUNT); + assertEquals("Verifying Withdrawal Amount", new Float(WITHDRAW_AMOUNT), withdrawTransaction.get("amount")); + + savingsStatusHashMap = this.savingsAccountHelper.blockCredit(savingsId); + SavingsStatusChecker.verifySavingsSubStatusIsCreditBlocked(savingsStatusHashMap); + error = (List) savingsAccountHelperValidationError.depositToSavingsAccount(savingsId, "100", SavingsAccountHelper.TRANSACTION_DATE, + CommonConstants.RESPONSE_ERROR); + assertEquals("error.msg.savings.account.credit.transaction.not.allowed", + error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, WITHDRAW_AMOUNT, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId); + balance -= new Float(WITHDRAW_AMOUNT); + assertEquals("Verifying Withdrawal Amount", new Float(WITHDRAW_AMOUNT), withdrawTransaction.get("amount")); + + savingsStatusHashMap = this.savingsAccountHelper.unblockCredit(savingsId); + SavingsStatusChecker.verifySavingsSubStatusIsNone(savingsStatusHashMap); + depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT, + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId); + balance += new Float(DEPOSIT_AMOUNT); + assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount")); + + Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(balance - 100), + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "300", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance", + error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + + Integer releaseTransactionId = this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId); + withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "300", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId); + balance -= new Float("300"); + assertEquals("Verifying Withdrawal Amount", new Float("300"), withdrawTransaction.get("amount")); + + } } \ No newline at end of file diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index 0b24cb80e..c2cd4f92a 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -55,6 +55,15 @@ public class SavingsAccountHelper { private static final String WITHDRAW_SAVINGS_COMMAND = "withdrawal"; private static final String MODIFY_TRASACTION_COMMAND = "modify"; private static final String UNDO_TRASACTION_COMMAND = "undo"; + + private static final String BLOCK_SAVINGS_COMMAND = "block"; + private static final String UNBLOCK_SAVINGS_COMMAND = "unblock"; + private static final String BLOCK_DEBITS_SAVINGS_COMMAND = "blockDebit"; + private static final String UNBLOCK_DEBITS_SAVINGS_COMMAND = "unblockDebit"; + private static final String BLOCK_CREDITS_SAVINGS_COMMAND = "blockCredit"; + private static final String UNBLOCK_CREDITS_SAVINGS_COMMAND = "unblockCredit"; + private static final String HOLD_AMOUNT_SAVINGS_COMMAND = "holdAmount"; + private static final String RELEASE_AMOUNT_SAVINGS_COMMAND = "releaseAmount"; public static final String CREATED_DATE = "08 January 2013"; public static final String CREATED_DATE_PLUS_ONE = "09 January 2013"; @@ -64,6 +73,7 @@ public class SavingsAccountHelper { public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL"; public static final String DATE_TIME_FORMAT = "dd MMMM yyyy HH:mm"; + private static final Boolean isBlock = false; public SavingsAccountHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { this.requestSpec = requestSpec; @@ -138,20 +148,20 @@ public class SavingsAccountHelper { System.out.println("--------------------------------- APPROVING SAVINGS APPLICATION ------------------------------------"); final String savingsOperationURL = createSavingsOperationURL(APPROVE_SAVINGS_COMMAND, savingsID); if (approvalDate == null || approvalDate == "") - return performSavingApplicationActions(savingsOperationURL, getApproveSavingsAsJSON()); - return performSavingApplicationActions(savingsOperationURL, getApproveSavingsAsJsonOnDate(approvalDate)); + return performSavingApplicationActions(savingsOperationURL, getApproveSavingsAsJSON(), isBlock); + return performSavingApplicationActions(savingsOperationURL, getApproveSavingsAsJsonOnDate(approvalDate), isBlock); } public HashMap undoApproval(final Integer savingsID) { System.out.println("--------------------------------- UNDO APPROVING SAVINGS APPLICATION -------------------------------"); final String undoBodyJson = "{'note':'UNDO APPROVAL'}"; - return performSavingApplicationActions(createSavingsOperationURL(UNDO_APPROVAL_SAVINGS_COMMAND, savingsID), undoBodyJson); + return performSavingApplicationActions(createSavingsOperationURL(UNDO_APPROVAL_SAVINGS_COMMAND, savingsID), undoBodyJson, isBlock); } public HashMap rejectApplication(final Integer savingsID) { System.out.println("--------------------------------- REJECT SAVINGS APPLICATION -------------------------------"); return performSavingApplicationActions(createSavingsOperationURL(REJECT_SAVINGS_COMMAND, savingsID), - getRejectedSavingsAsJSON(CREATED_DATE_PLUS_ONE)); + getRejectedSavingsAsJSON(CREATED_DATE_PLUS_ONE), isBlock); } public List rejectApplicationWithErrorCode(final Integer savingsId, final String date) { @@ -163,18 +173,18 @@ public class SavingsAccountHelper { public HashMap withdrawApplication(final Integer savingsID) { System.out.println("--------------------------------- Withdraw SAVINGS APPLICATION -------------------------------"); return performSavingApplicationActions(createSavingsOperationURL(WITHDRAWN_BY_CLIENT_SAVINGS_COMMAND, savingsID), - getWithdrawnSavingsAsJSON()); + getWithdrawnSavingsAsJSON(), isBlock); } public HashMap activateSavings(final Integer savingsID) { System.out.println("---------------------------------- ACTIVATING SAVINGS APPLICATION ----------------------------------"); - return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID), getActivatedSavingsAsJSON()); + return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID), getActivatedSavingsAsJSON(), isBlock); } public HashMap closeSavingsAccount(final Integer savingsID, String withdrawBalance) { System.out.println("---------------------------------- CLOSE SAVINGS APPLICATION ----------------------------------"); return performSavingApplicationActions(createSavingsOperationURL(CLOSE_SAVINGS_COMMAND, savingsID), - getCloseAccountJSON(withdrawBalance, LAST_TRANSACTION_DATE)); + getCloseAccountJSON(withdrawBalance, LAST_TRANSACTION_DATE), isBlock); } public Object deleteSavingsApplication(final Integer savingsId, final String jsonAttributeToGetBack) { @@ -251,6 +261,60 @@ public class SavingsAccountHelper { return Utils.performServerDelete(this.requestSpec, this.responseSpec, SAVINGS_ACCOUNT_URL + "/" + savingsId + "/charges/" + chargeId + "?" + Utils.TENANT_IDENTIFIER, CommonConstants.RESPONSE_RESOURCE_ID); } + + public HashMap blockSavings(final Integer savingsID) { + System.out.println("---------------------------------- BLOCKING SAVINGS ACCOUNT ----------------------------------"); + Boolean isBlock = true; + return performSavingApplicationActions(createSavingsOperationURL(BLOCK_SAVINGS_COMMAND, savingsID), + getActivatedSavingsAsJSON(), isBlock); + } + + public HashMap unblockSavings(final Integer savingsID) { + System.out.println("---------------------------------- UNBLOCKING SAVINGS ACCOUNT ----------------------------------"); + Boolean isBlock = true; + return performSavingApplicationActions(createSavingsOperationURL(UNBLOCK_SAVINGS_COMMAND, savingsID), + getActivatedSavingsAsJSON(), isBlock); + } + + public HashMap blockDebit(final Integer savingsID) { + System.out.println("---------------------------------- BLOCKING DEBIT TRANSACTIONS ----------------------------------"); + Boolean isBlock = true; + return performSavingApplicationActions(createSavingsOperationURL(BLOCK_DEBITS_SAVINGS_COMMAND, savingsID), + getActivatedSavingsAsJSON(), isBlock); + } + + public HashMap unblockDebit(final Integer savingsID) { + System.out.println("---------------------------------- UNBLOCKING DEBIT TRANSACTIONS ----------------------------------"); + Boolean isBlock = true; + return performSavingApplicationActions(createSavingsOperationURL(UNBLOCK_DEBITS_SAVINGS_COMMAND, savingsID), + getActivatedSavingsAsJSON(), isBlock); + } + + public HashMap blockCredit(final Integer savingsID) { + System.out.println("---------------------------------- BLOCKING CREDIT TRANSACTIONS ----------------------------------"); + Boolean isBlock = true; + return performSavingApplicationActions(createSavingsOperationURL(BLOCK_CREDITS_SAVINGS_COMMAND, savingsID), + getActivatedSavingsAsJSON(), isBlock); + } + + public HashMap unblockCredit(final Integer savingsID) { + System.out.println("---------------------------------- UNBLOCKING CREDIT TRANSACTIONS ----------------------------------"); + Boolean isBlock = true; + return performSavingApplicationActions(createSavingsOperationURL(UNBLOCK_CREDITS_SAVINGS_COMMAND, savingsID), + getActivatedSavingsAsJSON(), isBlock); + } + + public Object holdAmountInSavingsAccount(final Integer savingsID, final String amount, String date, String jsonAttributeToGetback) { + System.out.println("--------------------------------- SAVINGS TRANSACTION HOLD AMOUNT--------------------------------"); + return performSavingActions(createSavingsTransactionURL(HOLD_AMOUNT_SAVINGS_COMMAND, savingsID), + getSavingsTransactionJSON(amount, date), jsonAttributeToGetback); + } + + public Integer releaseAmount(final Integer savingsId, final Integer transactionId) { + System.out.println("\n--------------------------------- SAVINGS TRANSACTION RELEASE AMOUNT--------------------------------"); + return (Integer) performSavingActions(createAdjustTransactionURL(RELEASE_AMOUNT_SAVINGS_COMMAND, savingsId, transactionId), + getSavingsTransactionJSON("1000", LAST_TRANSACTION_DATE), CommonConstants.RESPONSE_RESOURCE_ID); + } private String getApproveSavingsAsJSON() { return getApproveSavingsAsJsonOnDate(CREATED_DATE_PLUS_ONE); @@ -454,12 +518,15 @@ public class SavingsAccountHelper { return response; } - private HashMap performSavingApplicationActions(final String postURLForSavingsTransaction, final String jsonToBeSent) { + private HashMap performSavingApplicationActions(final String postURLForSavingsTransaction, final String jsonToBeSent, final Boolean isBlock) { HashMap status = null; final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForSavingsTransaction, jsonToBeSent, CommonConstants.RESPONSE_CHANGES); if (response != null) { status = (HashMap) response.get("status"); + if (isBlock != null && isBlock) { + status = (HashMap) response.get("subStatus"); + } } return status; } @@ -510,7 +577,7 @@ public class SavingsAccountHelper { public HashMap activateSavingsAccount(Integer savingsID, String activationDate) { return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID), - getAccountActivationJSON(activationDate)); + getAccountActivationJSON(activationDate), isBlock); } public Object inactivateCharge(final Integer chargeId, final Integer savingsId, final String jsonAttributeToGetBack) { diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsStatusChecker.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsStatusChecker.java index 3c6c387c5..4a7f6368b 100755 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsStatusChecker.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsStatusChecker.java @@ -113,5 +113,30 @@ public class SavingsStatusChecker { assertTrue("UNEXPECTED SAVINGS ACCOUNT SUB STATUS", getStatus(savingsStatusHashMap, "none")); System.out.println("Savings Application Sub Status:" + savingsStatusHashMap + "\n"); } + + public static void verifySavingsSubStatusblock(HashMap savingsStatusHashMap) { + System.out.println( + "\n-------------------------------------- VERIFYING SAVINGS ACCOUNT IS BLOCKED ------------------------------------"); + assertTrue("block", getStatus(savingsStatusHashMap, "block")); + System.out.println("Savings Application Sub Status:" + savingsStatusHashMap + "\n"); + } + + public static void verifySavingsSubStatusIsNone(HashMap savingsStatusHashMap) { + System.out.println("\n------------------------- VERIFYING SAVINGS ACCOUNT IS NOT BLOCKED FOR ANY TYPE OF TRANSACTIONS ---------------------------"); + assertTrue("none", getStatus(savingsStatusHashMap, "none")); + System.out.println("Savings Application Sub Status:" + savingsStatusHashMap + "\n"); + } + + public static void verifySavingsSubStatusIsDebitBlocked(HashMap savingsStatusHashMap) { + System.out.println("\n--------------------- VERIFYING SAVINGS APPLICATION IS BLOCKED FOR DEBIT TRANSACTIONS ---------------------"); + assertTrue("status is blockDebit", getStatus(savingsStatusHashMap, "blockDebit")); + System.out.println("Savings Application Sub Status:" + savingsStatusHashMap + "\n"); + } + + public static void verifySavingsSubStatusIsCreditBlocked(HashMap savingsStatusHashMap) { + System.out.println("\n---------------------- VERIFYING SAVINGS APPLICATION IS BLOCKED FOR CREDIT TRANSACTIONS ---------------"); + assertTrue("blockCredit ", getStatus(savingsStatusHashMap, "blockCredit")); + System.out.println("Savings Application Sub Status:" + savingsStatusHashMap + "\n"); + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 5bcd21a46..46c585bd5 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -2904,4 +2904,77 @@ public class CommandWrapperBuilder { this.href = "/smscampaigns/"+resourceId; return this; } + + public CommandWrapperBuilder holdAmount(final Long accountId) { + this.actionName = "HOLDAMOUNT"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "/transactions?command=holdAmount"; + return this; + } + + public CommandWrapperBuilder releaseAmount(final Long accountId, final Long transactionId) { + this.actionName = "RELEASEAMOUNT"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = null; + this.savingsId = accountId; + this.transactionId = transactionId.toString(); + this.href = "/savingsaccounts/" + accountId + "/transactions/" + transactionId + "?command=releaseAmount"; + return this; + } + + public CommandWrapperBuilder blockDebitsFromSavingsAccount(final Long accountId) { + this.actionName = "BLOCKDEBIT"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=blockDebit"; + return this; + } + + public CommandWrapperBuilder unblockDebitsFromSavingsAccount(final Long accountId) { + this.actionName = "UNBLOCKDEBIT"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=unblockDebit"; + return this; + } + + public CommandWrapperBuilder blockCreditsToSavingsAccount(final Long accountId) { + this.actionName = "BLOCKCREDIT"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=blockCredit"; + return this; + } + + public CommandWrapperBuilder unblockCreditsToSavingsAccount(final Long accountId) { + this.actionName = "UNBLOCKCREDIT"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=unblockCredit"; + return this; + } + + public CommandWrapperBuilder blockSavingsAccount(final Long accountId) { + this.actionName = "BLOCK"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=block"; + return this; + } + + public CommandWrapperBuilder unblockSavingsAccount(final Long accountId) { + this.actionName = "UNBLOCK"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=unblock"; + return this; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java index 85b267d94..dc51e1f91 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsAccountTransactionType.java @@ -41,7 +41,9 @@ public enum SavingsAccountTransactionType { REJECT_TRANSFER(15, "savingsAccountTransactionType.rejectTransfer"), WRITTEN_OFF(16, "savingsAccountTransactionType.writtenoff"), // OVERDRAFT_INTEREST(17, "savingsAccountTransactionType.overdraftInterest"), // WITHHOLD_TAX(18, "savingsAccountTransactionType.withholdTax"), - ESCHEAT(19, "savingsAccountTransactionType.escheat"); + ESCHEAT(19, "savingsAccountTransactionType.escheat"), + AMOUNT_HOLD(20, "savingsAccountTransactionType.onHold"), + AMOUNT_RELEASE(21, "savingsAccountTransactionType.release"); private final Integer value; private final String code; @@ -113,6 +115,12 @@ public enum SavingsAccountTransactionType { case 19: savingsAccountTransactionType = SavingsAccountTransactionType.ESCHEAT; break; + case 20: + savingsAccountTransactionType = SavingsAccountTransactionType.AMOUNT_HOLD; + break; + case 21: + savingsAccountTransactionType = SavingsAccountTransactionType.AMOUNT_RELEASE; + break; } return savingsAccountTransactionType; } @@ -188,12 +196,20 @@ public enum SavingsAccountTransactionType { public boolean isEscheat() { return this.value.equals(SavingsAccountTransactionType.ESCHEAT.getValue()); } + + public boolean isAmountOnHold() { + return this.value.equals(SavingsAccountTransactionType.AMOUNT_HOLD.getValue()); + } + + public boolean isAmountRelease() { + return this.value.equals(SavingsAccountTransactionType.AMOUNT_RELEASE.getValue()); + } public boolean isDebit() { - return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge() || isIncomeFromInterest() || isWithHoldTax() || isEscheat(); + return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isPayCharge() || isIncomeFromInterest() || isWithHoldTax() || isEscheat() || isAmountOnHold(); } public boolean isCredit() { - return isDeposit() || isInterestPosting() || isDividendPayout(); + return isDeposit() || isInterestPosting() || isDividendPayout() || isAmountRelease(); } } \ No newline at end of file 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 741683168..1b8aa53aa 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 @@ -50,6 +50,12 @@ public class SavingsApiConstants { public static final String payChargeTransactionAction = ".paycharge"; public static final String waiveChargeTransactionAction = ".waivecharge"; public static final String updateMaturityDetailsAction = ".updateMaturityDetails"; + public static final String blockAction = ".block"; + public static final String unblockAction = ".unblock"; + public static final String blockCreditsAction = ".blockCredits"; + public static final String unblockCreditsAction = ".unblockCredits"; + public static final String blockDebitsAction = ".blockDebits"; + public static final String unblockDebitsAction = ".unblockDebits"; // command public static final String COMMAND_UNDO_TRANSACTION = "undo"; @@ -57,6 +63,14 @@ public class SavingsApiConstants { public static final String COMMAND_WAIVE_CHARGE = "waive"; public static final String COMMAND_PAY_CHARGE = "paycharge"; public static final String COMMAND_INACTIVATE_CHARGE = "inactivate"; + public static final String COMMAND_HOLD_AMOUNT = "holdAmount"; + public static final String COMMAND_RELEASE_AMOUNT = "releaseAmount"; + public static final String COMMAND_UNBLOCK_ACCOUNT= "unblock"; + public static final String COMMAND_BLOCK_ACCOUNT = "block"; + public static final String COMMAND_BLOCK_CREDIT = "blockCredit"; + public static final String COMMAND_BLOCK_DEBIT = "blockDebit"; + public static final String COMMAND_UNBLOCK_DEBIT = "unblockDebit"; + public static final String COMMAND_UNBLOCK_CREDIT = "unblockCredit"; // general public static final String localeParamName = "locale"; @@ -69,6 +83,7 @@ public class SavingsApiConstants { public static final String accountNoParamName = "accountNo"; public static final String externalIdParamName = "externalId"; public static final String statusParamName = "status"; + public static final String subStatusParamName = "subStatus"; public static final String clientIdParamName = "clientId"; public static final String groupIdParamName = "groupId"; public static final String productIdParamName = "productId"; @@ -169,4 +184,9 @@ public class SavingsApiConstants { public static final String daysToEscheatParamName = "daysToEscheat"; public static final String datatables = "datatables"; + + public static final String ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE = "not.in.active.state"; + + public static final Set SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(transactionDateParamName, dateFormatParamName, localeParamName, transactionAmountParamName)); } \ No newline at end of file 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 5204db250..a50a08396 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 @@ -145,11 +145,14 @@ public class SavingsAccountTransactionsApiResource { } else if (is(commandParam, "postInterestAsOn")) { final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(savingsId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_HOLD_AMOUNT)) { + final CommandWrapper commandRequest = builder.holdAmount(savingsId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { // - throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "deposit", "withdrawal" }); + throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "deposit", "withdrawal", SavingsApiConstants.COMMAND_HOLD_AMOUNT }); } return this.toApiJsonSerializer.serialize(result); @@ -183,11 +186,15 @@ public class SavingsAccountTransactionsApiResource { } else if (is(commandParam, SavingsApiConstants.COMMAND_ADJUST_TRANSACTION)) { final CommandWrapper commandRequest = builder.adjustSavingsAccountTransaction(savingsId, transactionId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam,SavingsApiConstants.COMMAND_RELEASE_AMOUNT)) { + final CommandWrapper commandRequest = builder.releaseAmount(savingsId, transactionId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { // - throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "undo" }); + throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { SavingsApiConstants.COMMAND_UNDO_TRANSACTION, + SavingsApiConstants.COMMAND_ADJUST_TRANSACTION, SavingsApiConstants.COMMAND_RELEASE_AMOUNT}); } return this.toApiJsonSerializer.serialize(result); 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 8a3ecc10d..05feb6374 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 @@ -279,13 +279,33 @@ public class SavingsAccountsApiResource { final CommandWrapper commandRequest = builder.unassignSavingsOfficer(accountId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); + } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_DEBIT)) { + final CommandWrapper commandRequest = builder.blockDebitsFromSavingsAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT)) { + final CommandWrapper commandRequest = builder.unblockDebitsFromSavingsAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_CREDIT)) { + final CommandWrapper commandRequest = builder.blockCreditsToSavingsAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT)) { + 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(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) { + final CommandWrapper commandRequest = builder.withNoJsonBody().unblockSavingsAccount(accountId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { // throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", "postInterest", "close", "assignSavingsOfficer", - "unassignSavingsOfficer" }); + "unassignSavingsOfficer", SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT, + SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT, + SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT }); } return this.toApiJsonSerializer.serialize(result); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSubStatusEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSubStatusEnumData.java index 498111355..3a7347aab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSubStatusEnumData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSubStatusEnumData.java @@ -36,9 +36,15 @@ public class SavingsAccountSubStatusEnumData { private final boolean dormant; @SuppressWarnings("unused") private final boolean escheat; + @SuppressWarnings("unused") + private final boolean block; + @SuppressWarnings("unused") + private final boolean blockCredit; + @SuppressWarnings("unused") + private final boolean blockDebit; public SavingsAccountSubStatusEnumData(final Long id, final String code, final String value, final boolean none, - final boolean inactive, final boolean dormant, final boolean escheat) { + final boolean inactive, final boolean dormant, final boolean escheat, final boolean block, final boolean blockCredit, final boolean blockDebit) { this.id = id; this.code = code; this.value = value; @@ -46,6 +52,9 @@ public class SavingsAccountSubStatusEnumData { this.inactive = inactive; this.dormant = dormant; this.escheat = escheat; + this.block = block; + this.blockCredit = blockCredit; + this.blockDebit = blockDebit; } public Long id() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java index 21d23b763..a3637c712 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java @@ -43,12 +43,13 @@ public class SavingsAccountSummaryData { private final BigDecimal totalWithholdTax; private final BigDecimal interestNotPosted; private final LocalDate lastInterestCalculationDate; + private final BigDecimal availableBalance; public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals, final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned, final BigDecimal totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge, final BigDecimal totalPenaltyCharge, final BigDecimal totalOverdraftInterestDerived,final BigDecimal totalWithholdTax, - final BigDecimal interestNotPosted, final LocalDate lastInterestCalculationDate) { + final BigDecimal interestNotPosted, final LocalDate lastInterestCalculationDate, final BigDecimal availableBalance) { this.currency = currency; this.totalDeposits = totalDeposits; this.totalWithdrawals = totalWithdrawals; @@ -63,5 +64,6 @@ public class SavingsAccountSummaryData { this.totalWithholdTax = totalWithholdTax; this.interestNotPosted = interestNotPosted; this.lastInterestCalculationDate = lastInterestCalculationDate; + this.availableBalance = availableBalance; } } \ No newline at end of file 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 936ba9d76..fcd7a8878 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 @@ -29,6 +29,8 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transact 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.withdrawBalanceParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -38,6 +40,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Date; import org.apache.commons.lang.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -46,10 +49,16 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; 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.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.SavingsAccountTransaction; +import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.apache.fineract.organisation.monetary.domain.Money; import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; @@ -160,6 +169,68 @@ public class SavingsAccountTransactionDataValidator { } } + + public SavingsAccountTransaction validateHoldAndAssembleForm(final String json, final SavingsAccount account, final AppUser createdUser) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transactionAmountParamName, element); + baseDataValidator.reset().parameter(transactionAmountParamName).value(amount).notNull().positiveAmount(); + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transactionDateParamName, element); + baseDataValidator.reset().parameter(transactionDateParamName).value(transactionDate).notNull(); + boolean isActive = account.isActive(); + + if (!isActive) { + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + } + account.holdFunds(amount); + if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId()); + baseDataValidator.failWithCode("validation.msg.savingsaccount.insufficient balance", "Insufficient balance"); + } + LocalDate lastTransactionDate = account.retrieveLastTransactionDate(); + // compare two dates now + if (lastTransactionDate != null && transactionDate.isBefore(lastTransactionDate)) { + baseDataValidator.parameter(SavingsApiConstants.dateParamName).value(lastTransactionDate).failWithCode( + "validation.msg.date.can.not.be.before.last.transaction.date", "Amount can be put on hold only after last transaction"); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + final PaymentDetail paymentDetails = null; + Date createdDate = DateUtils.getDateOfTenant(); + SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails, + transactionDate, Money.of(account.getCurrency(), amount), createdDate, createdUser); + return transaction; + } + + public SavingsAccountTransaction validateReleaseAmountAndAssembleForm(final SavingsAccountTransaction holdTransaction, final AppUser createdUser) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); + + if (holdTransaction == null) { + baseDataValidator.failWithCode("validation.msg.validation.errors.exist", "Transaction not found"); + } else if (holdTransaction.getReleaseIdOfHoldAmountTransaction() != null) { + baseDataValidator.parameter(SavingsApiConstants.amountParamName).value(holdTransaction.getAmount()).failWithCode("validation.msg.amount.is.not.on.hold", + "Transaction amount is not on hold"); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + Date createdDate = DateUtils.getDateOfTenant(); + LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); + SavingsAccountTransaction transaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate, + createdUser); + return transaction; + } private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java index 2417a0229..02651ce57 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java @@ -44,6 +44,8 @@ public class SavingsAccountTransactionEnumData { private final boolean overdraftFee = true; private final boolean withholdTax; private final boolean escheat; + private final boolean amountHold; + private final boolean amountRelease; public SavingsAccountTransactionEnumData(final Long id, final String code, final String value) { this.id = id; @@ -64,6 +66,8 @@ public class SavingsAccountTransactionEnumData { this.overdraftInterest = Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id); this.withholdTax = Long.valueOf(SavingsAccountTransactionType.WITHHOLD_TAX.getValue()).equals(this.id); this.escheat = Long.valueOf(SavingsAccountTransactionType.ESCHEAT.getValue()).equals(this.id); + this.amountHold = Long.valueOf(SavingsAccountTransactionType.AMOUNT_HOLD.getValue()).equals(this.id); + this.amountRelease = Long.valueOf(SavingsAccountTransactionType.AMOUNT_RELEASE.getValue()).equals(this.id); // this.overdraftFee = // Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id); } @@ -139,5 +143,13 @@ public class SavingsAccountTransactionEnumData { public boolean isEscheat() { return this.escheat; } + + public boolean isAmountOnHold() { + return this.amountHold; + } + + public boolean isAmountRelease() { + return this.amountRelease; + } } \ No newline at end of file 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 9825d814f..f904b0950 100755 --- 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 @@ -100,6 +100,9 @@ import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO; import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod; import org.apache.fineract.portfolio.savings.exception.InsufficientAccountBalanceException; +import org.apache.fineract.portfolio.savings.exception.SavingsAccountBlockedException; +import org.apache.fineract.portfolio.savings.exception.SavingsAccountCreditsBlockedException; +import org.apache.fineract.portfolio.savings.exception.SavingsAccountDebitsBlockedException; import org.apache.fineract.portfolio.savings.exception.SavingsAccountTransactionNotFoundException; import org.apache.fineract.portfolio.savings.exception.SavingsActivityPriorToClientTransferException; import org.apache.fineract.portfolio.savings.exception.SavingsOfficerAssignmentDateException; @@ -1053,7 +1056,9 @@ public class SavingsAccount extends AbstractPersistableCustom { runningBalance = runningBalance.plus(transaction.getAmount(this.currency)); } else if (transaction.isNotReversed() && transaction.isDebit()) { runningBalance = runningBalance.minus(transaction.getAmount(this.currency)); - } else { + } else if(transaction.isAmountOnHold() && transaction.getReleaseIdOfHoldAmountTransaction() == null){ + runningBalance = runningBalance.minus(transaction.getAmount(this.currency)); + }else { continue; } @@ -2822,4 +2827,188 @@ public class SavingsAccount extends AbstractPersistableCustom { this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, transactions); } + public Map block() { + + final Map actualChanges = new LinkedHashMap<>(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(depositAccountType().resourceName() + SavingsApiConstants.blockAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + + this.sub_status = SavingsAccountSubStatusEnum.BLOCK.getValue(); + actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status)); + + return actualChanges; + } + + public Map unblock() { + + final Map actualChanges = new LinkedHashMap<>(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(depositAccountType().resourceName() + SavingsApiConstants.unblockAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + + } + + final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.sub_status); + if (!SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus)) { + baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName) + .failWithCodeNoParameterAddedToErrorCode("not.in.blocked.state"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue(); + actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status)); + return actualChanges; + } + + public Map blockCredits(Integer currentSubstatus) { + + final Map actualChanges = new LinkedHashMap<>(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(depositAccountType().resourceName() + SavingsApiConstants.blockCreditsAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + } + if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus)) + || SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) { + + baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName).value(SavingsAccountSubStatusEnum.fromInt(currentSubstatus)) + .failWithCodeNoParameterAddedToErrorCode("currently.set"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + this.sub_status = SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue(); + actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status)); + + return actualChanges; + } + + public Map unblockCredits() { + + final Map actualChanges = new LinkedHashMap<>(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(depositAccountType().resourceName() + SavingsApiConstants.unblockCreditsAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + } + + final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.sub_status); + if (!SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(currentSubStatus)) { + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode("credits.are.not.blocked"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue(); + actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status)); + return actualChanges; + } + + public Map blockDebits(Integer currentSubstatus) { + + final Map actualChanges = new LinkedHashMap<>(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(depositAccountType().resourceName() + SavingsApiConstants.blockDebitsAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + + } + if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus)) + || SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) { + + baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName).value(SavingsAccountSubStatusEnum.fromInt(currentSubstatus)) + .failWithCodeNoParameterAddedToErrorCode("currently.set"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + + this.sub_status = SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue(); + actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status)); + + return actualChanges; + } + + public Map unblockDebits() { + + final Map actualChanges = new LinkedHashMap<>(); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(depositAccountType().resourceName() + SavingsApiConstants.unblockDebitsAction); + + final SavingsAccountStatusType currentStatus = SavingsAccountStatusType.fromInt(this.status); + if (!SavingsAccountStatusType.ACTIVE.hasStateOf(currentStatus)) { + + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + + } + + final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.sub_status); + if (!SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(currentSubStatus)) { + baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName) + .failWithCodeNoParameterAddedToErrorCode("debits.are.not.blocked"); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue(); + actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status)); + return actualChanges; + } + + public Integer getSubStatus() { + return this.sub_status; + } + + public void validateForAccountBlock() { + final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.getSubStatus()); + if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus)) { throw new SavingsAccountBlockedException(this.getId()); } + } + + public void validateForDebitBlock() { + final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.getSubStatus()); + if (SavingsAccountSubStatusEnum.BLOCK_DEBIT + .hasStateOf(currentSubStatus)) { throw new SavingsAccountDebitsBlockedException(this.getId()); } + } + + public void validateForCreditBlock() { + final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.getSubStatus()); + if (SavingsAccountSubStatusEnum.BLOCK_CREDIT + .hasStateOf(currentSubStatus)) { throw new SavingsAccountCreditsBlockedException(this.getId()); } + } + + public LocalDate retrieveLastTransactionDate() { + final List transactionsSortedByDate = retreiveListOfTransactions(); + SavingsAccountTransaction lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1); + return lastTransaction.transactionLocalDate(); + } + + } \ No newline at end of file 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 32fdcd25d..b3ea4db0e 100755 --- 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 @@ -86,6 +86,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final SavingsTransactionBooleanValues transactionBooleanValues) { AppUser user = getAppUserIfPresent(); + account.validateForAccountBlock(); + account.validateForDebitBlock(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); @@ -148,6 +150,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final boolean isAccountTransfer, final boolean isRegularTransaction, final SavingsAccountTransactionType savingsAccountTransactionType) { AppUser user = getAppUserIfPresent(); + account.validateForAccountBlock(); + account.validateForCreditBlock(); final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSubStatusEnum.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSubStatusEnum.java index aefe18353..889a69a0e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSubStatusEnum.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSubStatusEnum.java @@ -26,7 +26,10 @@ public enum SavingsAccountSubStatusEnum { NONE(0, "SavingsAccountSubStatusEnum.none"), // INACTIVE(100, "SavingsAccountSubStatusEnum.inactive"), // DORMANT(200, "SavingsAccountSubStatusEnum.dormant"), - ESCHEAT(300,"SavingsAccountSubStatusEnum.escheat"); + ESCHEAT(300,"SavingsAccountSubStatusEnum.escheat"), + BLOCK(400, "SavingsAccountSubStatusEnum.block"), + BLOCK_CREDIT(500, "SavingsAccountSubStatusEnum.blockCredit"), + BLOCK_DEBIT(600, "SavingsAccountSubStatusEnum.blockDebit"); private final Integer value; private final String code; @@ -45,6 +48,15 @@ public enum SavingsAccountSubStatusEnum { case 300: enumeration = SavingsAccountSubStatusEnum.ESCHEAT; break; + case 400: + enumeration = SavingsAccountSubStatusEnum.BLOCK; + break; + case 500: + enumeration = SavingsAccountSubStatusEnum.BLOCK_CREDIT; + break; + case 600: + enumeration = SavingsAccountSubStatusEnum.BLOCK_DEBIT; + break; } } return enumeration; @@ -82,4 +94,16 @@ public enum SavingsAccountSubStatusEnum { public boolean isSubStatusEscheat() { return this.value.equals(SavingsAccountSubStatusEnum.ESCHEAT.getValue()); } + + public boolean isSubStatusAccountBlocked() { + return this.value.equals(SavingsAccountSubStatusEnum.BLOCK.getValue()); + } + + public boolean isSubStatusCreditBlocked() { + return this.value.equals(SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue()); + } + + public boolean isSubStatusDebitBlocked() { + return this.value.equals(SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue()); + } } \ No newline at end of file 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 d5c5c1266..12de52762 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 @@ -121,6 +121,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom taxDetails = new ArrayList<>(); + + @Column(name = "release_id_of_hold_amount", length = 20) + private Long releaseIdOfHoldAmountTransaction; protected SavingsAccountTransaction() { this.dateOf = null; @@ -289,6 +292,21 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom changes = account.block(); + if (!changes.isEmpty()) { + + this.savingAccountRepositoryWrapper.save(account); + } + return new CommandProcessingResultBuilder().withEntityId(savingsId).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).with(changes).build(); + } + + @Override + public CommandProcessingResult unblockAccount(final Long savingsId) { + this.context.authenticatedUser(); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); + + final Map changes = account.unblock(); + if (!changes.isEmpty()) { + + this.savingAccountRepositoryWrapper.save(account); + } + return new CommandProcessingResultBuilder().withEntityId(savingsId).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).with(changes).build(); + } + + @Transactional + @Override + public CommandProcessingResult holdAmount(final Long savingsId, final JsonCommand command) { + + final AppUser submittedBy = this.context.authenticatedUser(); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); + + SavingsAccountTransaction transacton = this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(), + account, submittedBy); + + this.savingsAccountTransactionRepository.save(transacton); + this.savingAccountRepositoryWrapper.saveAndFlush(account); + + return new CommandProcessingResultBuilder().withEntityId(transacton.getId()).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).build(); + } + + @Transactional + @Override + public CommandProcessingResult releaseAmount(final Long savingsId, final Long savingsTransactionId) { + + final AppUser submittedBy = this.context.authenticatedUser(); + SavingsAccountTransaction holdTransaction = this.savingsAccountTransactionRepository + .findOneByIdAndSavingsAccountId(savingsTransactionId, savingsId); + + final SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator + .validateReleaseAmountAndAssembleForm(holdTransaction, submittedBy); + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); + account.releaseFunds(transaction.getAmount()); + + this.savingsAccountTransactionRepository.save(transaction); + holdTransaction.updateReleaseId(transaction.getId()); + this.savingAccountRepositoryWrapper.save(account); + + return new CommandProcessingResultBuilder().withEntityId(transaction.getId()).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(account.getId()).build(); + } + + @Override + public CommandProcessingResult blockCredits(final Long savingsId) { + this.context.authenticatedUser(); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); + + final Map changes = account.blockCredits(account.getSubStatus()); + if (!changes.isEmpty()) { + + this.savingAccountRepositoryWrapper.save(account); + } + return new CommandProcessingResultBuilder().withEntityId(savingsId).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).with(changes).build(); + } + + @Override + public CommandProcessingResult unblockCredits(final Long savingsId) { + this.context.authenticatedUser(); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); + + final Map changes = account.unblockCredits(); + if (!changes.isEmpty()) { + + this.savingAccountRepositoryWrapper.save(account); + } + return new CommandProcessingResultBuilder().withEntityId(savingsId).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).with(changes).build(); + } + + @Override + public CommandProcessingResult blockDebits(final Long savingsId) { + this.context.authenticatedUser(); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); + + final Map changes = account.blockDebits(account.getSubStatus()); + if (!changes.isEmpty()) { + + this.savingAccountRepositoryWrapper.save(account); + } + return new CommandProcessingResultBuilder().withEntityId(savingsId).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).with(changes).build(); + } + + @Override + public CommandProcessingResult unblockDebits(final Long savingsId) { + this.context.authenticatedUser(); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + checkClientOrGroupActive(account); + + final Map changes = account.unblockDebits(); + if (!changes.isEmpty()) { + + this.savingAccountRepositoryWrapper.save(account); + } + return new CommandProcessingResultBuilder().withEntityId(savingsId).withOfficeId(account.officeId()) + .withClientId(account.clientId()).withGroupId(account.groupId()).withSavingsId(savingsId).with(changes).build(); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java index 9bc45661d..d74e4a8ef 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java @@ -193,6 +193,14 @@ public class SavingsEnumerations { optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.ESCHEAT.getValue().longValue(), SavingsAccountTransactionType.ESCHEAT.getCode(), "Escheat"); break; + case AMOUNT_HOLD: + optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.AMOUNT_HOLD.getValue().longValue(), + SavingsAccountTransactionType.AMOUNT_HOLD.getCode(), "Amount on hold"); + break; + case AMOUNT_RELEASE: + optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.AMOUNT_RELEASE.getValue().longValue(), + SavingsAccountTransactionType.AMOUNT_RELEASE.getCode(), "Release Amount"); + break; } return optionData; } @@ -294,22 +302,37 @@ public class SavingsEnumerations { final boolean inactive = type.isSubStatusInactive(); final boolean dormant = type.isSubStatusDormant(); final boolean escheat = type.isSubStatusEscheat(); + final boolean block = type.isSubStatusAccountBlocked(); + final boolean blockCredit = type.isSubStatusCreditBlocked(); + final boolean blockDebit = type.isSubStatusDebitBlocked(); SavingsAccountSubStatusEnumData optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.NONE.getValue().longValue(), - SavingsAccountSubStatusEnum.NONE.getCode(), "None", true, inactive, dormant, escheat); + SavingsAccountSubStatusEnum.NONE.getCode(), "None", true, inactive, dormant, escheat, block, blockCredit, blockDebit); switch (type) { case INACTIVE: optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.INACTIVE.getValue().longValue(), - SavingsAccountSubStatusEnum.INACTIVE.getCode(), "Inactive", none, inactive, dormant, escheat); + SavingsAccountSubStatusEnum.INACTIVE.getCode(), "Inactive", none, inactive, dormant, escheat, block, blockCredit, blockDebit); break; case DORMANT: optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.DORMANT.getValue().longValue(), - SavingsAccountSubStatusEnum.DORMANT.getCode(), "Dormant", none, inactive, dormant, escheat); + SavingsAccountSubStatusEnum.DORMANT.getCode(), "Dormant", none, inactive, dormant, escheat, block, blockCredit, blockDebit); break; case ESCHEAT: optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.ESCHEAT.getValue().longValue(), - SavingsAccountSubStatusEnum.ESCHEAT.getCode(), "Escheat", none, inactive, dormant, escheat); + SavingsAccountSubStatusEnum.ESCHEAT.getCode(), "Escheat", none, inactive, dormant, escheat, block, blockCredit, blockDebit); + break; + case BLOCK: + optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.BLOCK.getValue().longValue(), + SavingsAccountSubStatusEnum.BLOCK.getCode(), "Block", none, inactive, dormant, escheat, block, blockCredit, blockDebit); + break; + case BLOCK_CREDIT: + optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue().longValue(), + SavingsAccountSubStatusEnum.BLOCK_CREDIT.getCode(), "BlockCredit", none, inactive, dormant, escheat, block, blockCredit, blockDebit); + break; + case BLOCK_DEBIT: + optionData = new SavingsAccountSubStatusEnumData(SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue().longValue(), + SavingsAccountSubStatusEnum.BLOCK_DEBIT.getCode(), "BlockDebit", none, inactive, dormant, escheat, block, blockCredit, blockDebit); break; default: break; diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V329__savings_account_transaction_releaseId.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V329__savings_account_transaction_releaseId.sql new file mode 100644 index 000000000..cf9e24c70 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V329__savings_account_transaction_releaseId.sql @@ -0,0 +1,44 @@ +-- +-- 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. +-- + + -- permissions +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES +('transaction_savings', 'HOLDAMOUNT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'HOLDAMOUNT', 0), +('transaction_savings', 'HOLDAMOUNT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'HOLDAMOUNT_CHECKER', 0), +('transaction_savings', 'BLOCKDEBIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'BLOCKDEBIT', 0), +('transaction_savings', 'BLOCKDEBIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'BLOCKDEBIT_CHECKER', 0), +('transaction_savings', 'UNBLOCKDEBIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UNBLOCKDEBIT', 0), +('transaction_savings', 'UNBLOCKDEBIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UNBLOCKDEBIT_CHECKER', 0), +('transaction_savings', 'BLOCKCREDIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'BLOCKCREDIT', 0), +('transaction_savings', 'BLOCKCREDIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'BLOCKCREDIT_CHECKER', 0), +('transaction_savings', 'UNBLOCKCREDIT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UNBLOCKCREDIT', 0), +('transaction_savings', 'UNBLOCKCREDIT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UNBLOCKCREDIT_CHECKER', 0), +('transaction_savings', 'BLOCK_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'BLOCK', 0), +('transaction_savings', 'BLOCK_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'BLOCK_CHECKER', 0), +('transaction_savings', 'UNBLOCK_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'UNBLOCK', 0), +('transaction_savings', 'UNBLOCK_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'UNBLOCK_CHECKER', 0), +('transaction_savings', 'RELEASEAMOUNT_SAVINGSACCOUNT', 'SAVINGSACCOUNT', 'RELEASEAMOUNT', 0), +('transaction_savings', 'RELEASEAMOUNT_SAVINGSACCOUNT_CHECKER', 'SAVINGSACCOUNT', 'RELEASEAMOUNT_CHECKER', 0); + +-- modify `m_savings_account_transaction` + +ALTER TABLE `m_savings_account_transaction` ADD COLUMN `release_id_of_hold_amount` BIGINT(20) NULL DEFAULT NULL; + + +