This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new c9a41794e8 FINERACT-2457: Share transaction chronological validation
incorrectly blocks new transactions based on rejected/reversed transactions
c9a41794e8 is described below
commit c9a41794e80c9f1474169df4042104b0a93028fc
Author: Wilfred Kigenyi <[email protected]>
AuthorDate: Tue Jan 27 23:45:43 2026 +0300
FINERACT-2457: Share transaction chronological validation incorrectly
blocks new transactions based on rejected/reversed transactions
---
.../serialization/ShareAccountDataSerializer.java | 54 ++-
.../shares/ShareAccountIntegrationTests.java | 415 +++++++++++++++++++++
2 files changed, 439 insertions(+), 30 deletions(-)
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 dc648318db..bb9477fd01 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
@@ -29,6 +29,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -706,16 +707,8 @@ public class ShareAccountDataSerializer {
}
}
boolean isTransactionBeforeExistingTransactions = false;
- Set<ShareAccountTransaction> 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 +725,23 @@ public class ShareAccountDataSerializer {
return actualChanges;
}
+ private boolean isTransactionBeforeExistingTransactions(LocalDate
requestedDate, boolean isTransactionBeforeExistingTransactions,
+ ShareAccount shareAccount) {
+ Collection<ShareAccountTransaction> 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<ShareAccountCharge> charges = account.getCharges();
BigDecimal totalChargeAmount = BigDecimal.ZERO;
@@ -863,16 +873,8 @@ public class ShareAccountDataSerializer {
baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(sharesRequested).notNull()
.longGreaterThanZero();
boolean isTransactionBeforeExistingTransactions = false;
- Set<ShareAccountTransaction> 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 +1020,8 @@ public class ShareAccountDataSerializer {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
boolean isTransactionBeforeExistingTransactions = false;
- Set<ShareAccountTransaction> 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 7826136e36..fbc2331c3f 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 class ShareAccountIntegrationTests {
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<String, Object> shareAccountData =
ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec,
+ responseSpec);
+ List<Map<String, Object>> transactions = (List<Map<String, Object>>)
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<Map<String, Object>>)
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<Map<String, Object>>)
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<String, Object> shareAccountData =
ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec,
+ responseSpec);
+ List<Map<String, Object>> transactions = (List<Map<String, Object>>)
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<Map<String, Object>>)
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<String, Object> 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<String, Object> statusMap = (Map<String, Object>)
shareAccountData.get("status");
+ Assertions.assertEquals("shareAccountStatusType.closed",
String.valueOf(statusMap.get("code")));
+
+ Map<String, Object> timelineMap = (Map<String, Object>)
shareAccountData.get("timeline");
+ List<Integer> closedDateList = (List<Integer>)
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<String, Object> shareAccountData =
ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec,
+ responseSpec);
+ List<Map<String, Object>> transactions = (List<Map<String, Object>>)
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<String, Object> 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<String, Object> shareAccountData =
ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec,
+ responseSpec);
+ List<Map<String, Object>> transactions = (List<Map<String, Object>>)
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<Map<String, Object>>)
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<Map<String, Object>>)
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<String, Object> transaction : transactions) {
+ Map<String, Object> transactionTypeMap = (Map<String, Object>)
transaction.get("type");
+ List<Integer> dateList = (List<Integer>)
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<String, Object> transactionStatusMap = (Map<String,
Object>) 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<String, Object> shareAccountData =
ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec,
+ responseSpec);
+ List<Map<String, Object>> transactions = (List<Map<String, Object>>)
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<String, Object> 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<String, Object> statusMap = (Map<String, Object>)
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 @@ public class ShareAccountIntegrationTests {
map.put("amount", amount);
return map;
}
+
+ private void updateShareAccountWithInitialData(Integer shareAccountId,
Integer requestedShares, String applicationDate) {
+ Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<Integer> 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<Map<String, Object>> transactions,
String transactionTypeCode, String expectedDate) {
+ DateFormat simple = new SimpleDateFormat("dd MMMM yyyy");
+ for (Map<String, Object> transaction : transactions) {
+ Map<String, Object> transactionTypeMap = (Map<String, Object>)
transaction.get("type");
+ List<Integer> dateList = (List<Integer>)
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<String, List<Map<String, Object>>> rejectMap = new HashMap<>();
+ List<Map<String, Object>> list = new ArrayList<>();
+ Map<String, Object> 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<Map<String, Object>>
transactions, String transactionTypeCode, String expectedDate,
+ String expectedStatus) {
+ DateFormat simple = new SimpleDateFormat("dd MMMM yyyy");
+ boolean transactionFound = false;
+
+ for (Map<String, Object> transaction : transactions) {
+ Map<String, Object> transactionTypeMap = (Map<String, Object>)
transaction.get("type");
+ List<Integer> dateList = (List<Integer>)
transaction.get("purchasedDate");
+ String transactionType = (String) transactionTypeMap.get("code");
+ String transactionDate = formatTransactionDate(dateList, simple);
+
+ if (transactionType.equals(transactionTypeCode) &&
transactionDate.equals(expectedDate)) {
+ Map<String, Object> transactionStatusMap = (Map<String,
Object>) 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<Map<String, Object>>
transactions, String transactionTypeCode, String expectedDate,
+ String expectedShares, String expectedStatus) {
+ DateFormat simple = new SimpleDateFormat("dd MMMM yyyy");
+ boolean transactionFound = false;
+
+ for (Map<String, Object> transaction : transactions) {
+ Map<String, Object> transactionTypeMap = (Map<String, Object>)
transaction.get("type");
+ List<Integer> dateList = (List<Integer>)
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<String, Object> transactionStatusMap = (Map<String,
Object>) 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<String, Object> shareAccountData =
ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec,
+ responseSpec);
+ Map<String, Object> statusMap = (Map<String, Object>)
shareAccountData.get("status");
+ Assertions.assertEquals("shareAccountStatusType.active",
String.valueOf(statusMap.get("code")));
+
+ return shareAccountId;
+ }
}