diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java index dc648318db0..12683d110bd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java @@ -27,14 +27,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; @@ -706,16 +699,8 @@ public Map validateAndApplyAddtionalShares(JsonCommand jsonComma } } boolean isTransactionBeforeExistingTransactions = false; - Set transactions = account.getShareAccountTransactions(); - for (ShareAccountTransaction transaction : transactions) { - if (!transaction.isChargeTransaction()) { - LocalDate transactionDate = transaction.getPurchasedDate(); - if (DateUtils.isBefore(requestedDate, transactionDate)) { - isTransactionBeforeExistingTransactions = true; - break; - } - } - } + isTransactionBeforeExistingTransactions = isTransactionBeforeExistingTransactions(requestedDate, + isTransactionBeforeExistingTransactions, account); if (isTransactionBeforeExistingTransactions) { baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname).value(requestedDate) .failWithCodeNoParameterAddedToErrorCode("purchase.transaction.date.cannot.be.before.existing.transactions"); @@ -732,6 +717,23 @@ public Map validateAndApplyAddtionalShares(JsonCommand jsonComma return actualChanges; } + private boolean isTransactionBeforeExistingTransactions(LocalDate requestedDate, boolean isTransactionBeforeExistingTransactions, + ShareAccount shareAccount) { + Collection activeTransactions = shareAccount.getShareAccountTransactions().stream() + .filter(tr -> tr.isActive() && !tr.isChargeTransaction() && !tr.isPurchaseRejectedTransaction()).toList(); + + for (ShareAccountTransaction transaction : activeTransactions) { + if (!transaction.isChargeTransaction() && transaction.isActive() && !transaction.isPurchaseRejectedTransaction()) { + LocalDate transactionDate = transaction.getPurchasedDate(); + if (DateUtils.isBefore(requestedDate, transactionDate)) { + isTransactionBeforeExistingTransactions = true; + break; + } + } + } + return isTransactionBeforeExistingTransactions; + } + private void handleAdditionalSharesChargeTransactions(final ShareAccount account, final ShareAccountTransaction purchaseTransaction) { Set charges = account.getCharges(); BigDecimal totalChargeAmount = BigDecimal.ZERO; @@ -863,16 +865,8 @@ public Map validateAndRedeemShares(JsonCommand jsonCommand, Shar baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(sharesRequested).notNull() .longGreaterThanZero(); boolean isTransactionBeforeExistingTransactions = false; - Set transactions = account.getShareAccountTransactions(); - for (ShareAccountTransaction transaction : transactions) { - if (!transaction.isChargeTransaction() && transaction.isActive()) { - LocalDate transactionDate = transaction.getPurchasedDate(); - if (DateUtils.isBefore(requestedDate, transactionDate)) { - isTransactionBeforeExistingTransactions = true; - break; - } - } - } + isTransactionBeforeExistingTransactions = isTransactionBeforeExistingTransactions(requestedDate, + isTransactionBeforeExistingTransactions, account); if (isTransactionBeforeExistingTransactions) { baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname).value(requestedDate) .failWithCodeNoParameterAddedToErrorCode("redeem.transaction.date.cannot.be.before.existing.transactions"); @@ -1018,16 +1012,8 @@ public Map validateAndClose(JsonCommand jsonCommand, ShareAccoun throw new PlatformApiDataValidationException(dataValidationErrors); } boolean isTransactionBeforeExistingTransactions = false; - Set transactions = account.getShareAccountTransactions(); - for (ShareAccountTransaction transaction : transactions) { - if (!transaction.isChargeTransaction()) { - LocalDate transactionDate = transaction.getPurchasedDate(); - if (DateUtils.isBefore(closedDate, transactionDate)) { - isTransactionBeforeExistingTransactions = true; - break; - } - } - } + isTransactionBeforeExistingTransactions = isTransactionBeforeExistingTransactions(closedDate, + isTransactionBeforeExistingTransactions, account); if (isTransactionBeforeExistingTransactions) { baseDataValidator.reset().parameter(ShareAccountApiConstants.closeddate_paramname).value(closedDate) .failWithCodeNoParameterAddedToErrorCode("share.account.cannot.be.closed.before.existing.transactions"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java index 7826136e36e..fbc2331c3f7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java @@ -983,6 +983,285 @@ public void testCreateShareAccountWithCharges() { Assertions.assertEquals("0", String.valueOf(summaryMap.get("totalPendingForApprovalShares"))); } + // Refactored Test 1 + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAdditionalSharesAfterRejectedTransaction() { + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 15 April 2016 + applyAdditionalShares(shareAccountId, "15 April 2016", "20"); + + // Retrieve transactions and find the additional shares request + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + Assertions.assertNotNull(transactions); + + // Find and reject the additional shares request (15 April 2016) + String additionalSharesRequestId = findTransactionId(transactions, "purchasedSharesType.purchased", "15 April 2016"); + Assertions.assertNotNull(additionalSharesRequestId, "Additional shares request for 15 April 2016 should exist"); + + // Reject the additional shares request + rejectAdditionalSharesRequest(shareAccountId, additionalSharesRequestId); + + // Verify transaction is rejected + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionStatus(transactions, "purchasedSharesType.purchased", "15 April 2016", "purchasedSharesStatusType.rejected"); + + // Now try to apply additional shares with a date BEFORE the rejected transaction (10 April 2016) + // This should succeed because rejected transactions should be ignored in chronological validation + applyAdditionalShares(shareAccountId, "10 April 2016", "15"); + + // Verify the new transaction was successfully added + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionWithShares(transactions, "purchasedSharesType.purchased", "10 April 2016", "15", + "purchasedSharesStatusType.applied"); + } + + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAccountClosureBeforeRejectedTransaction() { + // FINERACT-2457: Account closure validation should ignore rejected/reversed transactions + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 20 May 2016 + applyAdditionalShares(shareAccountId, "20 May 2016", "30"); + + // Retrieve transactions and find the additional shares request + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + Assertions.assertNotNull(transactions); + + // Find and reject the additional shares request (20 May 2016) + String additionalSharesRequestId = findTransactionId(transactions, "purchasedSharesType.purchased", "20 May 2016"); + Assertions.assertNotNull(additionalSharesRequestId, "Additional shares request for 20 May 2016 should exist"); + + // Reject the additional shares request + rejectAdditionalSharesRequest(shareAccountId, additionalSharesRequestId); + + // Verify transaction is rejected + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionStatus(transactions, "purchasedSharesType.purchased", "20 May 2016", "purchasedSharesStatusType.rejected"); + + // Now try to close the account with a date BEFORE the rejected transaction (15 May 2016) + // This should succeed because rejected transactions should be ignored in chronological validation + Map closeAccountMap = new HashMap<>(); + closeAccountMap.put("note", "Share Account Close Note"); + closeAccountMap.put("dateFormat", "dd MMMM yyyy"); + closeAccountMap.put("closedDate", "15 May 2016"); + closeAccountMap.put("locale", "en"); + String closeJson = new Gson().toJson(closeAccountMap); + ShareAccountTransactionHelper.postCommand("close", shareAccountId, closeJson, requestSpec, responseSpec); + + // Verify the account was successfully closed + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + Map statusMap = (Map) shareAccountData.get("status"); + Assertions.assertEquals("shareAccountStatusType.closed", String.valueOf(statusMap.get("code"))); + + Map timelineMap = (Map) shareAccountData.get("timeline"); + List closedDateList = (List) timelineMap.get("closedDate"); + LocalDate closedDate = LocalDate.of(closedDateList.get(0), closedDateList.get(1), closedDateList.get(2)); + Assertions.assertEquals("15 May 2016", closedDate.format(Utils.dateFormatter)); + } + + // Additional Test 1: Verify original validation still works (Negative Test) + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAdditionalSharesBeforeActiveTransactionShouldFail() { + // FINERACT-2457: Verify that the fix didn't break the original chronological validation + // Transactions BEFORE active/approved transactions should still be REJECTED + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 15 April 2016 (this remains active/approved) + applyAdditionalShares(shareAccountId, "15 April 2016", "20"); + + // Verify the transaction exists and is in applied/active state + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + Assertions.assertNotNull(transactions); + + String transactionId = findTransactionId(transactions, "purchasedSharesType.purchased", "15 April 2016"); + Assertions.assertNotNull(transactionId, "Transaction for 15 April 2016 should exist"); + + // Try to apply additional shares BEFORE the active transaction (10 April 2016) + // This should FAIL because the April 15 transaction is ACTIVE/APPROVED + Map additionalSharesRequestMap = new HashMap<>(); + additionalSharesRequestMap.put("requestedDate", "10 April 2016"); + additionalSharesRequestMap.put("dateFormat", "dd MMMM yyyy"); + additionalSharesRequestMap.put("locale", "en"); + additionalSharesRequestMap.put("requestedShares", "15"); + String additionalSharesRequestJson = new Gson().toJson(additionalSharesRequestMap); + + ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build(); + + try { + ShareAccountTransactionHelper.postCommand("applyadditionalshares", shareAccountId, additionalSharesRequestJson, requestSpec, + errorResponse); + + } catch (Exception e) { + Assertions.assertTrue( + e.getMessage().contains("chronological") || e.getMessage().contains("date") || e.getMessage().contains("before"), + "Error message should indicate chronological validation failure"); + } + } + + // Additional Test 3: Test edge case - transaction on same date as rejected transaction + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAdditionalSharesOnSameDateAsRejectedTransaction() { + // FINERACT-2457: Test behavior when applying shares on the SAME date as a rejected transaction + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply and reject shares on 15 April 2016 + applyAdditionalShares(shareAccountId, "15 April 2016", "20"); + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + String txId = findTransactionId(transactions, "purchasedSharesType.purchased", "15 April 2016"); + Assertions.assertNotNull(txId); + rejectAdditionalSharesRequest(shareAccountId, txId); + + // Verify transaction is rejected + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionStatus(transactions, "purchasedSharesType.purchased", "15 April 2016", "purchasedSharesStatusType.rejected"); + + // Try to apply shares on the SAME date as the rejected transaction + // This should succeed since rejected transactions are ignored + applyAdditionalShares(shareAccountId, "15 April 2016", "15"); + + // Verify the new transaction was successfully added + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + + // Count how many transactions exist for 15 April 2016 + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + int countForDate = 0; + int appliedCountForDate = 0; + + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals("purchasedSharesType.purchased") && transactionDate.equals("15 April 2016")) { + countForDate++; + Map transactionStatusMap = (Map) transaction.get("status"); + String status = String.valueOf(transactionStatusMap.get("code")); + if (status.equals("purchasedSharesStatusType.applied")) { + appliedCountForDate++; + Assertions.assertEquals("15", String.valueOf(transaction.get("numberOfShares"))); + } + } + } + + // Should have 2 transactions for this date: 1 rejected, 1 applied + Assertions.assertEquals(2, countForDate, "Should have 2 transactions for 15 April 2016"); + Assertions.assertEquals(1, appliedCountForDate, "Should have 1 applied transaction for 15 April 2016"); + } + + // Additional Test 4: Test account closure before active transaction should still fail + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAccountClosureBeforeActiveTransactionShouldFail() { + // FINERACT-2457: Verify that closing account before active transactions is still blocked + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 20 May 2016 (and keep it active/approved) + applyAdditionalShares(shareAccountId, "20 May 2016", "30"); + + // Verify the transaction exists and is active + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + String transactionId = findTransactionId(transactions, "purchasedSharesType.purchased", "20 May 2016"); + Assertions.assertNotNull(transactionId, "Transaction for 20 May 2016 should exist"); + + // Try to close account on 15 May 2016 (before the active transaction) + // This should FAIL + Map closeAccountMap = new HashMap<>(); + closeAccountMap.put("note", "Share Account Close Note"); + closeAccountMap.put("dateFormat", "dd MMMM yyyy"); + closeAccountMap.put("closedDate", "15 May 2016"); + closeAccountMap.put("locale", "en"); + String closeJson = new Gson().toJson(closeAccountMap); + + ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build(); + + try { + ShareAccountTransactionHelper.postCommand("close", shareAccountId, closeJson, requestSpec, errorResponse); + + // If we get here, the validation worked correctly (request was rejected) + // This is the expected behavior + } catch (Exception e) { + // Depending on the framework, the error might be thrown as an exception + // We expect this to fail, so this is acceptable + Assertions + .assertTrue( + e.getMessage().contains("chronological") || e.getMessage().contains("date") || e.getMessage().contains("before") + || e.getMessage().contains("transaction"), + "Error message should indicate chronological validation failure"); + } + + // Verify account is still active (not closed) + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + Map statusMap = (Map) shareAccountData.get("status"); + Assertions.assertEquals("shareAccountStatusType.active", String.valueOf(statusMap.get("code")), + "Account should still be active since closure was rejected"); + } + private Integer createShareProduct() { String shareProductJson = shareProductHelper.build(); return ShareProductTransactionHelper.createShareProduct(shareProductJson, requestSpec, responseSpec); @@ -1009,4 +1288,140 @@ private Map createCharge(final Integer chargeId, String amount) map.put("amount", amount); return map; } + + private void updateShareAccountWithInitialData(Integer shareAccountId, Integer requestedShares, String applicationDate) { + Map shareAccountDataForUpdate = new HashMap<>(); + shareAccountDataForUpdate.put("requestedShares", requestedShares); + shareAccountDataForUpdate.put("applicationDate", applicationDate); + shareAccountDataForUpdate.put("dateFormat", "dd MMMM yyyy"); + shareAccountDataForUpdate.put("locale", "en_GB"); + String updateShareAccountJsonString = new Gson().toJson(shareAccountDataForUpdate); + ShareAccountTransactionHelper.updateShareAccount(shareAccountId, updateShareAccountJsonString, requestSpec, responseSpec); + } + + private void approveShareAccount(Integer shareAccountId, String approvalDate) { + Map approveMap = new HashMap<>(); + approveMap.put("note", "Share Account Approval Note"); + approveMap.put("dateFormat", "dd MMMM yyyy"); + approveMap.put("approvedDate", approvalDate); + approveMap.put("locale", "en"); + String approve = new Gson().toJson(approveMap); + ShareAccountTransactionHelper.postCommand("approve", shareAccountId, approve, requestSpec, responseSpec); + } + + private void activateShareAccount(Integer shareAccountId, String activationDate) { + Map activateMap = new HashMap<>(); + activateMap.put("dateFormat", "dd MMMM yyyy"); + activateMap.put("activatedDate", activationDate); + activateMap.put("locale", "en"); + String activateJson = new Gson().toJson(activateMap); + ShareAccountTransactionHelper.postCommand("activate", shareAccountId, activateJson, requestSpec, responseSpec); + } + + private void applyAdditionalShares(Integer shareAccountId, String requestedDate, String requestedShares) { + Map additionalSharesRequestMap = new HashMap<>(); + additionalSharesRequestMap.put("requestedDate", requestedDate); + additionalSharesRequestMap.put("dateFormat", "dd MMMM yyyy"); + additionalSharesRequestMap.put("locale", "en"); + additionalSharesRequestMap.put("requestedShares", requestedShares); + String additionalSharesRequestJson = new Gson().toJson(additionalSharesRequestMap); + ShareAccountTransactionHelper.postCommand("applyadditionalshares", shareAccountId, additionalSharesRequestJson, requestSpec, + responseSpec); + } + + private String formatTransactionDate(List dateList, DateFormat formatter) { + Calendar cal = Calendar.getInstance(); + cal.set(dateList.get(0), dateList.get(1) - 1, dateList.get(2)); + Date date = cal.getTime(); + return formatter.format(date); + } + + private String findTransactionId(List> transactions, String transactionTypeCode, String expectedDate) { + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals(transactionTypeCode) && transactionDate.equals(expectedDate)) { + return String.valueOf(transaction.get("id")); + } + } + return null; + } + + private void rejectAdditionalSharesRequest(Integer shareAccountId, String transactionId) { + Map>> rejectMap = new HashMap<>(); + List> list = new ArrayList<>(); + Map idsMap = new HashMap<>(); + idsMap.put("id", transactionId); + list.add(idsMap); + rejectMap.put("requestedShares", list); + String rejectJson = new Gson().toJson(rejectMap); + ShareAccountTransactionHelper.postCommand("rejectadditionalshares", shareAccountId, rejectJson, requestSpec, responseSpec); + } + + private void verifyTransactionStatus(List> transactions, String transactionTypeCode, String expectedDate, + String expectedStatus) { + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + boolean transactionFound = false; + + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals(transactionTypeCode) && transactionDate.equals(expectedDate)) { + Map transactionStatusMap = (Map) transaction.get("status"); + Assertions.assertEquals(expectedStatus, String.valueOf(transactionStatusMap.get("code"))); + transactionFound = true; + break; + } + } + + Assertions.assertTrue(transactionFound, + String.format("Transaction with type %s for %s should exist", transactionTypeCode, expectedDate)); + } + + private void verifyTransactionWithShares(List> transactions, String transactionTypeCode, String expectedDate, + String expectedShares, String expectedStatus) { + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + boolean transactionFound = false; + + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals(transactionTypeCode) && transactionDate.equals(expectedDate)) { + Assertions.assertEquals(expectedShares, String.valueOf(transaction.get("numberOfShares"))); + Map transactionStatusMap = (Map) transaction.get("status"); + Assertions.assertEquals(expectedStatus, String.valueOf(transactionStatusMap.get("code"))); + transactionFound = true; + break; + } + } + + Assertions.assertTrue(transactionFound, String.format("Transaction for %s should be successfully created", expectedDate)); + } + + private Integer setupAndActivateShareAccount(Integer clientId, Integer productId, Integer savingsAccountId, String initialDate) { + final Integer shareAccountId = createShareAccount(clientId, productId, savingsAccountId); + Assertions.assertNotNull(shareAccountId); + + updateShareAccountWithInitialData(shareAccountId, 25, initialDate); + approveShareAccount(shareAccountId, initialDate); + activateShareAccount(shareAccountId, initialDate); + + // Verify account is active + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + Map statusMap = (Map) shareAccountData.get("status"); + Assertions.assertEquals("shareAccountStatusType.active", String.valueOf(statusMap.get("code"))); + + return shareAccountId; + } }