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 6d9c8aaf193..8f7b9c64019 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 @@ -166,4 +166,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0148_transaction_summary_with_asset_owner_report_performance_improvement.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0148_transaction_summary_with_asset_owner_report_performance_improvement.xml new file mode 100644 index 00000000000..1972a127512 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0148_transaction_summary_with_asset_owner_report_performance_improvement.xml @@ -0,0 +1,1168 @@ + + + + + + + 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 dcab0859895..bee35a68a65 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 @@ -28,6 +28,7 @@ import static org.apache.fineract.client.models.ExternalTransferData.SubStatusEnum.UNSOLD; import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -39,6 +40,7 @@ import io.restassured.path.json.JsonPath; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; +import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -46,6 +48,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -63,12 +66,14 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO; +import org.apache.fineract.integrationtests.client.IntegrationTest; import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.BusinessStepHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CollateralManagementHelper; import org.apache.fineract.integrationtests.common.ExternalAssetOwnerHelper; import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.OfficeHelper; import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; @@ -88,14 +93,11 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @SuppressWarnings("rawtypes") @ExtendWith({ LoanTestLifecycleExtension.class, ExternalEventsExtension.class }) -public class InitiateExternalAssetOwnerTransferTest { +public class InitiateExternalAssetOwnerTransferTest extends IntegrationTest { - private static final Logger LOG = LoggerFactory.getLogger(InitiateExternalAssetOwnerTransferTest.class); private static ResponseSpecification RESPONSE_SPEC; private static RequestSpecification REQUEST_SPEC; private static Account ASSET_ACCOUNT; @@ -108,9 +110,10 @@ public class InitiateExternalAssetOwnerTransferTest { private static ExternalAssetOwnerHelper EXTERNAL_ASSET_OWNER_HELPER; private static LoanTransactionHelper LOAN_TRANSACTION_HELPER; private static SchedulerJobHelper SCHEDULER_JOB_HELPER; + private static OfficeHelper OFFICE_HELPER; private static LocalDate TODAYS_DATE; public String ownerExternalId; - private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); + private final DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); @BeforeAll public static void setupInvestorBusinessStep() { @@ -123,6 +126,7 @@ public static void setupInvestorBusinessStep() { SCHEDULER_JOB_HELPER = new SchedulerJobHelper(REQUEST_SPEC); FINANCIAL_ACTIVITY_ACCOUNT_HELPER = new FinancialActivityAccountHelper(REQUEST_SPEC); LOAN_TRANSACTION_HELPER = new LoanTransactionHelper(REQUEST_SPEC, RESPONSE_SPEC); + OFFICE_HELPER = new OfficeHelper(REQUEST_SPEC, RESPONSE_SPEC); TODAYS_DATE = Utils.getLocalDateOfTenant(); new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", @@ -936,6 +940,248 @@ public void saleExceptionHandling() { } } + @Test + public void transactionSummaryReportWithAssetOwner() throws IOException { + try { + GlobalConfigurationHelper.manageConfigurations(REQUEST_SPEC, RESPONSE_SPEC, + GlobalConfigurationHelper.ENABLE_AUTOGENERATED_EXTERNAL_ID, true); + setInitialBusinessDate("2020-03-02"); + + ExternalEventHelper.deleteAllExternalEvents(REQUEST_SPEC, new ResponseSpecBuilder().expectStatusCode(Matchers.is(204)).build()); + ExternalEventHelper.changeEventState(REQUEST_SPEC, RESPONSE_SPEC, "LoanOwnershipTransferBusinessEvent", true); + + final var officeId = OFFICE_HELPER.createOffice("1 January 2020"); + final var clientID = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC, "1 January 2020", officeId.toString()); + final var loanID = createLoanForClient(clientID); + addPenaltyForLoan(loanID, "10"); + + final var saleTransferResponse = createSaleTransfer(loanID, "2020-03-02"); + validateResponse(saleTransferResponse, loanID); + getAndValidateExternalAssetOwnerTransferByLoan(loanID, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-02", + "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"))); + getAndValidateThereIsNoActiveMapping(saleTransferResponse.getResourceExternalId()); + var retrieveResponse = EXTERNAL_ASSET_OWNER_HELPER.retrieveTransfersByLoanId(loanID.longValue()); + retrieveResponse.getContent().forEach(transfer -> getAndValidateThereIsNoJournalEntriesForTransfer(transfer.getTransferId())); + + updateBusinessDateAndExecuteCOBJob("2020-03-03"); + getAndValidateExternalAssetOwnerTransferByLoan(loanID, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-02", + "2020-03-02", 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, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-03", + "9999-12-31", true, new BigDecimal("15767.420000"), new BigDecimal("15000.000000"), + new BigDecimal("757.420000"), new BigDecimal("10.000000"), new BigDecimal("0.000000"), + new BigDecimal("0.000000"))); + + final var allExternalEvents = ExternalEventHelper.getAllExternalEvents(REQUEST_SPEC, RESPONSE_SPEC); + Assertions.assertEquals(1, allExternalEvents.size()); + Assertions.assertEquals("LoanOwnershipTransferBusinessEvent", allExternalEvents.get(0).getType()); + Assertions.assertEquals(Long.valueOf(loanID), allExternalEvents.get(0).getAggregateRootId()); + + ExternalEventHelper.deleteAllExternalEvents(REQUEST_SPEC, new ResponseSpecBuilder().expectStatusCode(Matchers.is(204)).build()); + ExternalEventHelper.changeEventState(REQUEST_SPEC, RESPONSE_SPEC, "LoanOwnershipTransferBusinessEvent", true); + + getAndValidateThereIsActiveMapping(loanID); + + final var expectedDate = LocalDate.of(2020, 3, 2); + final var initial = 0; + + final var buybackTransferResponse = createBuybackTransfer(loanID, "2020-03-03"); + validateResponse(buybackTransferResponse, loanID); + getAndValidateExternalAssetOwnerTransferByLoan(loanID, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-02", + "2020-03-02", 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, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-03", + "9999-12-31", true, 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(BUYBACK, buybackTransferResponse.getResourceExternalId(), "2020-03-03", + "2020-03-03", "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"))); + getAndValidateThereIsActiveMapping(loanID); + retrieveResponse = EXTERNAL_ASSET_OWNER_HELPER.retrieveTransfersByLoanId(loanID.longValue()); + getAndValidateThereIsNoJournalEntriesForTransfer(retrieveResponse.getContent().get(initial + 2).getTransferId()); + + LOAN_TRANSACTION_HELPER.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy") + .transactionDate(dateFormatter.format(expectedDate)).locale("en").transactionAmount(5.0)); + + updateBusinessDateAndExecuteCOBJob("2020-03-04"); + getAndValidateExternalAssetOwnerTransferByLoan(loanID, + ExpectedExternalTransferData.expected(PENDING, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-02", + "2020-03-02", 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, saleTransferResponse.getResourceExternalId(), "2020-03-02", "2020-03-03", + "2020-03-03", true, 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(BUYBACK, buybackTransferResponse.getResourceExternalId(), "2020-03-03", + "2020-03-03", "2020-03-03", true, new BigDecimal("15762.420000"), new BigDecimal("15000.000000"), + new BigDecimal("757.420000"), new BigDecimal("5.000000"), new BigDecimal("0.000000"), + new BigDecimal("0.000000"))); + getAndValidateThereIsNoActiveMapping(saleTransferResponse.getResourceExternalId()); + retrieveResponse = EXTERNAL_ASSET_OWNER_HELPER.retrieveTransfersByLoanId(loanID.longValue()); + + final var reportResult = fineract().reportsRun.runReportGetFile("Transaction Summary Report with Asset Owner", + Map.of("R_endDate", "2020-03-03", "R_officeId", officeId.toString(), "output-type", "CSV"), false).execute(); + + assertNotNull(reportResult.body()); + final var csvContent = reportResult.body().string(); + final var jsonPath = JsonPath.from(csvContent); + + assertNotNull(jsonPath.getString("columnHeaders[0].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[0].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[0].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[1].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[1].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[1].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[2].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[2].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[2].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[3].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[3].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[3].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[4].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[4].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[4].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[5].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[5].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[5].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[6].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[6].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[6].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[7].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[7].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[7].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[8].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[8].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[8].isColumnNullable")); + + assertNotNull(jsonPath.getString("columnHeaders[9].columnType")); + assertNotNull(jsonPath.getString("columnHeaders[9].columnDisplayType")); + assertFalse(jsonPath.getBoolean("columnHeaders[9].isColumnNullable")); + + assertNotNull(retrieveResponse.getContent().get(0).getOwner()); + final var ownerId = retrieveResponse.getContent().get(0).getOwner().getExternalId(); + + assertEquals("2020-03-03", jsonPath.getString("data[0].row[0]")); + assertTrue(jsonPath.getString("data[0].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Apply Charges", jsonPath.getString("data[0].row[2]")); + assertNull(jsonPath.getString("data[0].row[3]")); + assertEquals("", jsonPath.getString("data[0].row[4]")); + assertFalse(jsonPath.getBoolean("data[0].row[5]")); + assertEquals("Interest", jsonPath.getString("data[0].row[6]")); + 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]")); + + assertEquals("2020-03-03", jsonPath.getString("data[1].row[0]")); + assertTrue(jsonPath.getString("data[1].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Asset Buyback", jsonPath.getString("data[1].row[2]")); + assertNull(jsonPath.getString("data[1].row[3]")); + assertEquals("", jsonPath.getString("data[1].row[4]")); + assertFalse(jsonPath.getBoolean("data[1].row[5]")); + assertEquals("Interest", jsonPath.getString("data[1].row[6]")); + 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("2020-03-03", jsonPath.getString("data[2].row[0]")); + assertTrue(jsonPath.getString("data[2].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Asset Buyback", jsonPath.getString("data[2].row[2]")); + assertNull(jsonPath.getString("data[2].row[3]")); + assertEquals("", jsonPath.getString("data[2].row[4]")); + assertFalse(jsonPath.getBoolean("data[2].row[5]")); + assertEquals("Penalty", jsonPath.getString("data[2].row[6]")); + 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("2020-03-03", jsonPath.getString("data[3].row[0]")); + assertTrue(jsonPath.getString("data[3].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Asset Buyback", jsonPath.getString("data[3].row[2]")); + assertNull(jsonPath.getString("data[3].row[3]")); + assertEquals("", jsonPath.getString("data[3].row[4]")); + assertFalse(jsonPath.getBoolean("data[3].row[5]")); + assertEquals("Principal", jsonPath.getString("data[3].row[6]")); + 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("2020-03-03", jsonPath.getString("data[4].row[0]")); + assertTrue(jsonPath.getString("data[4].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Repayment", jsonPath.getString("data[4].row[2]")); + assertNull(jsonPath.getString("data[4].row[3]")); + assertEquals("", jsonPath.getString("data[4].row[4]")); + assertFalse(jsonPath.getBoolean("data[4].row[5]")); + assertEquals("Fees", jsonPath.getString("data[4].row[6]")); + 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]")); + + assertEquals("2020-03-03", jsonPath.getString("data[5].row[0]")); + assertTrue(jsonPath.getString("data[5].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Repayment", jsonPath.getString("data[5].row[2]")); + assertNull(jsonPath.getString("data[5].row[3]")); + assertEquals("", jsonPath.getString("data[5].row[4]")); + assertFalse(jsonPath.getBoolean("data[5].row[5]")); + assertEquals("Interest", jsonPath.getString("data[5].row[6]")); + 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]")); + + assertEquals("2020-03-03", jsonPath.getString("data[6].row[0]")); + assertTrue(jsonPath.getString("data[6].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Repayment", jsonPath.getString("data[6].row[2]")); + assertNull(jsonPath.getString("data[6].row[3]")); + assertEquals("", jsonPath.getString("data[6].row[4]")); + assertFalse(jsonPath.getBoolean("data[6].row[5]")); + assertEquals("Penalty", jsonPath.getString("data[6].row[6]")); + 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]")); + + assertEquals("2020-03-03", jsonPath.getString("data[7].row[0]")); + assertTrue(jsonPath.getString("data[7].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Repayment", jsonPath.getString("data[7].row[2]")); + assertNull(jsonPath.getString("data[7].row[3]")); + assertEquals("", jsonPath.getString("data[7].row[4]")); + assertFalse(jsonPath.getBoolean("data[7].row[5]")); + assertEquals("Principal", jsonPath.getString("data[7].row[6]")); + 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]")); + + assertEquals("2020-03-03", jsonPath.getString("data[8].row[0]")); + assertTrue(jsonPath.getString("data[8].row[1]").matches("^LOAN_PRODUCT_.{6}$")); + assertEquals("Repayment", jsonPath.getString("data[8].row[2]")); + assertNull(jsonPath.getString("data[8].row[3]")); + assertEquals("", jsonPath.getString("data[8].row[4]")); + assertFalse(jsonPath.getBoolean("data[8].row[5]")); + assertEquals("Unallocated Credit (UNC)", jsonPath.getString("data[8].row[6]")); + 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]")); + } finally { + cleanUpAndRestoreBusinessDate(); + } + } + private void updateBusinessDateAndExecuteCOBJob(String date) { BusinessDateHelper.updateBusinessDate(REQUEST_SPEC, RESPONSE_SPEC, BUSINESS_DATE, LocalDate.parse(date)); SCHEDULER_JOB_HELPER.executeAndAwaitJob("Loan COB");