diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 231310cf6..34da1913f 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -227,4 +227,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0209_transaction_summary_with_asset_owner_and_from_asset_owner_id_for_asset_sales.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0209_transaction_summary_with_asset_owner_and_from_asset_owner_id_for_asset_sales.xml new file mode 100644 index 000000000..db2db12d2 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0209_transaction_summary_with_asset_owner_and_from_asset_owner_id_for_asset_sales.xml @@ -0,0 +1,3120 @@ + + + + + + + report_name='Transaction Summary Report with Asset Owner' + + + + + + report_name='Transaction Summary Report with Asset Owner' + + + diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java index 54a000985..7262613a9 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java @@ -53,6 +53,7 @@ import java.util.Objects; import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; +import okhttp3.ResponseBody; import org.apache.fineract.accounting.common.AccountingConstants; import org.apache.fineract.accounting.journalentry.domain.JournalEntryType; import org.apache.fineract.client.models.ExternalAssetOwnerRequest; @@ -105,6 +106,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.lang.NonNull; +import retrofit2.Response; @SuppressWarnings("rawtypes") @ExtendWith({ ExternalEventsExtension.class }) @@ -1080,8 +1082,14 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNotNull(jsonPath.getString("columnHeaders[9].columnDisplayType")); assertFalse(jsonPath.getBoolean("columnHeaders[9].isColumnNullable")); + assertNotNull(jsonPath.getString("columnHeaders[10].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[10].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[10].isColumnNullable")); + assertNotNull(retrieveResponse.getContent().get(0).getOwner()); final var ownerId = retrieveResponse.getContent().get(0).getOwner().getExternalId(); + assertNotNull(retrieveResponse.getContent().get(2).getPreviousOwner()); + final var previousOwnerId = retrieveResponse.getContent().get(2).getPreviousOwner().getExternalId(); assertEquals("2020-03-03", jsonPath.getString("data[0].row[0]")); assertTrue(jsonPath.getString("data[0].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1093,6 +1101,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[0].row[7]")); assertEquals(9.68, jsonPath.getDouble("data[0].row[8]"), 0.01); assertEquals(ownerId, jsonPath.getString("data[0].row[9]")); + assertNull(jsonPath.getString("data[0].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[1].row[0]")); assertTrue(jsonPath.getString("data[1].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1104,6 +1113,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[1].row[7]")); assertEquals(-757.42, jsonPath.getDouble("data[1].row[8]"), 0.01); assertNull(jsonPath.getString("data[1].row[9]")); + assertEquals(previousOwnerId, jsonPath.getString("data[1].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[2].row[0]")); assertTrue(jsonPath.getString("data[2].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1115,6 +1125,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[2].row[7]")); assertEquals(-5.00, jsonPath.getDouble("data[2].row[8]"), 0.01); assertNull(jsonPath.getString("data[2].row[9]")); + assertEquals(previousOwnerId, jsonPath.getString("data[2].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[3].row[0]")); assertTrue(jsonPath.getString("data[3].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1126,6 +1137,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[3].row[7]")); assertEquals(-15000.00, jsonPath.getDouble("data[3].row[8]"), 0.01); assertNull(jsonPath.getString("data[3].row[9]")); + assertEquals(previousOwnerId, jsonPath.getString("data[3].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[4].row[0]")); assertTrue(jsonPath.getString("data[4].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1137,6 +1149,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[4].row[7]")); assertEquals(0.00, jsonPath.getDouble("data[4].row[8]"), 0.01); assertEquals(ownerId, jsonPath.getString("data[4].row[9]")); + assertNull(jsonPath.getString("data[4].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[5].row[0]")); assertTrue(jsonPath.getString("data[5].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1148,6 +1161,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[5].row[7]")); assertEquals(0.00, jsonPath.getDouble("data[5].row[8]"), 0.01); assertEquals(ownerId, jsonPath.getString("data[5].row[9]")); + assertNull(jsonPath.getString("data[5].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[6].row[0]")); assertTrue(jsonPath.getString("data[6].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1159,6 +1173,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[6].row[7]")); assertEquals(-5.00, jsonPath.getDouble("data[6].row[8]"), 0.01); assertEquals(ownerId, jsonPath.getString("data[6].row[9]")); + assertNull(jsonPath.getString("data[6].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[7].row[0]")); assertTrue(jsonPath.getString("data[7].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1170,6 +1185,7 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[7].row[7]")); assertEquals(0.00, jsonPath.getDouble("data[7].row[8]"), 0.01); assertEquals(ownerId, jsonPath.getString("data[7].row[9]")); + assertNull(jsonPath.getString("data[7].row[10]")); assertEquals("2020-03-03", jsonPath.getString("data[8].row[0]")); assertTrue(jsonPath.getString("data[8].row[1]").matches("^LOAN_PRODUCT_.{6}$")); @@ -1181,6 +1197,101 @@ public class InitiateExternalAssetOwnerTransferTest extends BaseLoanIntegrationT assertNull(jsonPath.getString("data[8].row[7]")); assertEquals(0.00, jsonPath.getDouble("data[8].row[8]"), 0.01); assertEquals(ownerId, jsonPath.getString("data[8].row[9]")); + assertNull(jsonPath.getString("data[8].row[10]")); + } finally { + ExternalEventHelper.deleteAllExternalEvents(REQUEST_SPEC, new ResponseSpecBuilder().expectStatusCode(Matchers.is(204)).build()); + cleanUpAndRestoreBusinessDate(); + } + } + + @Test + public void transactionSummaryReportWithAssetOwner_CheckFromAssetOwnerIdForBuyback() throws IOException { + try { + globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_AUTO_GENERATED_EXTERNAL_ID, true); + setInitialBusinessDate("2023-08-16"); + + ExternalEventHelper.deleteAllExternalEvents(REQUEST_SPEC, new ResponseSpecBuilder().expectStatusCode(Matchers.is(204)).build()); + ExternalEventHelper.changeEventState(REQUEST_SPEC, RESPONSE_SPEC, "LoanOwnershipTransferBusinessEvent", true); + + final Integer officeId = OFFICE_HELPER.createOffice("1 January 2020"); + final Integer clientID = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC, "1 January 2020", officeId.toString()); + final Integer loanID = createLoanForClient(clientID); + + // Create first sale transfer + final PostInitiateTransferResponse firstSaleTransferResponse = createSaleTransfer(loanID, "2023-08-16"); + validateResponse(firstSaleTransferResponse, loanID); + + // Verify the transfer is PENDING initially + getAndValidateExternalAssetOwnerTransferByLoan(loanID, + ExpectedExternalTransferData.expected(PENDING, firstSaleTransferResponse.getResourceExternalId(), "2023-08-16", + "2023-08-16", "9999-12-31", false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), + new BigDecimal("757.420000"), new BigDecimal("10.000000"), new BigDecimal("0.000000"), + new BigDecimal("0.000000"))); + + // Execute COB job on the next day to activate the transfer + updateBusinessDateAndExecuteCOBJob("2023-08-17"); + + // Verify the transfer is ACTIVE after COB job + getAndValidateExternalAssetOwnerTransferByLoan(loanID, + ExpectedExternalTransferData.expected(PENDING, firstSaleTransferResponse.getResourceExternalId(), "2023-08-16", + "2023-08-16", "2023-08-16", false, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), + new BigDecimal("757.420000"), new BigDecimal("10.000000"), new BigDecimal("0.000000"), + new BigDecimal("0.000000")), + ExpectedExternalTransferData.expected(ACTIVE, firstSaleTransferResponse.getResourceExternalId(), "2023-08-16", + "2023-08-17", "9999-12-31", true, new BigDecimal("15914.980000"), new BigDecimal("15000.000000"), + new BigDecimal("757.420000"), new BigDecimal("157.560000"), new BigDecimal("0.000000"), + new BigDecimal("0.000000"))); + + // Get the owner ID of the first transfer for later verification + PageExternalTransferData retrieveResponse = EXTERNAL_ASSET_OWNER_HELPER.retrieveTransfersByLoanId(loanID.longValue()); + assertNotNull(retrieveResponse.getContent().get(1).getOwner()); + final String firstOwnerId = retrieveResponse.getContent().get(1).getOwner().getExternalId(); + assertNull(retrieveResponse.getContent().get(1).getPreviousOwner(), "First sale transfer should not have previous_owner_id"); + + // Create buyback transfer + updateBusinessDateAndExecuteCOBJob("2023-08-18"); + final PostInitiateTransferResponse buybackTransferResponse = createBuybackTransfer(loanID, "2023-08-18"); + validateResponse(buybackTransferResponse, loanID); + + // Execute COB job to process buyback + updateBusinessDateAndExecuteCOBJob("2023-08-19"); + + // Verify buyback has previous_owner_id set + retrieveResponse = EXTERNAL_ASSET_OWNER_HELPER.retrieveTransfersByLoanId(loanID.longValue()); + assertNotNull(retrieveResponse.getContent().get(2).getPreviousOwner()); + assertEquals(firstOwnerId, retrieveResponse.getContent().get(2).getPreviousOwner().getExternalId(), + "Buyback transfer should have previous_owner_id set to first owner"); + + // Run report on settlement date of buyback to check that from_asset_owner_id is populated + final Response reportResult = reportHelper.runReport("Transaction Summary Report with Asset Owner", + Map.of("R_endDate", "2023-08-18", "R_officeId", officeId.toString(), "output-type", "CSV")); + + assertNotNull(reportResult.body()); + final String csvContent = reportResult.body().string(); + final JsonPath jsonPath = JsonPath.from(csvContent); + + // Find Asset Buyback entries to verify from_asset_owner_id is populated + final List> buybackRows = new ArrayList<>(); + final int dataSize = jsonPath.getInt("data.size()"); + for (int i = 0; i < dataSize; i++) { + final String transactionType = jsonPath.getString("data[" + i + "].row[2]"); + final String transactionDate = jsonPath.getString("data[" + i + "].row[0]"); + // Find Asset Buyback entries for the buyback date + if ("Asset Buyback".equals(transactionType) && "2023-08-18".equals(transactionDate)) { + buybackRows.add(jsonPath.getMap("data[" + i + "]")); + } + } + + // Verify that Asset Buyback entries exist + assertFalse(buybackRows.isEmpty(), "Asset Buyback entries should exist in the report"); + + // Verify that from_asset_owner_id is populated with previous_owner_id for buyback + for (Map row : buybackRows) { + final List rowData = (List) row.get("row"); + assertNotNull(rowData.get(10), "from_asset_owner_id should be populated for buyback transfer"); + assertEquals(firstOwnerId, rowData.get(10), + "from_asset_owner_id should equal the first owner's external ID for buyback transfer"); + } } finally { ExternalEventHelper.deleteAllExternalEvents(REQUEST_SPEC, new ResponseSpecBuilder().expectStatusCode(Matchers.is(204)).build()); cleanUpAndRestoreBusinessDate();