diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java index 15ef932ae..ec00a4709 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java @@ -695,6 +695,10 @@ public final class ErrorMessageHelper { actual, expected); } + public static String wrongDataInExternalAssetOwnerLoanProductAttribute(String attributeKey, long loanProduct) { + return String.format("No attribute %s for loan product %s is found!", attributeKey, loanProduct); + } + public static String wrongData(String actual, String expected) { return String.format("Wrong data. Actual value is: %s - But expected value is: %s", actual, expected); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java index 9b1c390d3..710958559 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java @@ -413,6 +413,47 @@ public class EventCheckHelper { .isEqualTo(transferStatusReasonExpected); } + public void loanOwnershipTransferBusinessEventWithTypeCheck(Long loanId, ExternalTransferData transferData, String transferType, + String previousAssetOwner) throws IOException { + Response response = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null).execute(); + List content = response.body().getContent(); + Long transferId = transferData.getTransferId(); + String assetOwner = transferData.getOwner() == null ? null : transferData.getOwner().getExternalId(); + + ExternalTransferData filtered = content.stream().filter(t -> transferId.equals(t.getTransferId())).reduce((first, second) -> second) + .orElseThrow(() -> new IllegalStateException("No element found")); + + BigDecimal totalOutstandingBalanceAmountExpected = filtered.getDetails() == null ? null + : zeroConversion(filtered.getDetails().getTotalOutstanding()); + BigDecimal outstandingPrincipalPortionExpected = filtered.getDetails() == null ? null + : zeroConversion(filtered.getDetails().getTotalPrincipalOutstanding()); + BigDecimal outstandingFeePortionExpected = filtered.getDetails() == null ? null + : zeroConversion(filtered.getDetails().getTotalFeeChargesOutstanding()); + BigDecimal outstandingPenaltyPortionExpected = filtered.getDetails() == null ? null + : zeroConversion(filtered.getDetails().getTotalPenaltyChargesOutstanding()); + BigDecimal outstandingInterestPortionExpected = filtered.getDetails() == null ? null + : zeroConversion(filtered.getDetails().getTotalInterestOutstanding()); + BigDecimal overPaymentPortionExpected = filtered.getDetails() == null ? null + : zeroConversion(filtered.getDetails().getTotalOverpaid()); + + eventAssertion.assertEvent(LoanOwnershipTransferEvent.class, loanId).extractingData(LoanOwnershipTransferDataV1::getLoanId) + .isEqualTo(loanId).extractingData(LoanOwnershipTransferDataV1::getAssetOwnerExternalId) + .isEqualTo(filtered.getOwner().getExternalId()).extractingData(LoanOwnershipTransferDataV1::getTransferExternalId) + .isEqualTo(filtered.getTransferExternalId()).extractingData(LoanOwnershipTransferDataV1::getSettlementDate) + .isEqualTo(FORMATTER_EVENTS.format(filtered.getSettlementDate())) + .extractingBigDecimal(LoanOwnershipTransferDataV1::getTotalOutstandingBalanceAmount) + .isEqualTo(totalOutstandingBalanceAmountExpected) + .extractingBigDecimal(LoanOwnershipTransferDataV1::getOutstandingPrincipalPortion) + .isEqualTo(outstandingPrincipalPortionExpected).extractingBigDecimal(LoanOwnershipTransferDataV1::getOutstandingFeePortion) + .isEqualTo(outstandingFeePortionExpected).extractingBigDecimal(LoanOwnershipTransferDataV1::getOutstandingPenaltyPortion) + .isEqualTo(outstandingPenaltyPortionExpected) + .extractingBigDecimal(LoanOwnershipTransferDataV1::getOutstandingInterestPortion) + .isEqualTo(outstandingInterestPortionExpected).extractingBigDecimal(LoanOwnershipTransferDataV1::getOverPaymentPortion) + .isEqualTo(overPaymentPortionExpected).extractingData(LoanOwnershipTransferDataV1::getType).isEqualTo(transferType) + .extractingData(LoanOwnershipTransferDataV1::getAssetOwnerExternalId).isEqualTo(assetOwner) + .extractingData(LoanOwnershipTransferDataV1::getPreviousOwnerExternalId).isEqualTo(previousAssetOwner); + } + public void loanAccountSnapshotBusinessEventCheck(Long loanId, Long transferId) throws IOException { Response response = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null).execute(); List content = response.body().getContent(); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java index b9f9fea0d..42684603c 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java @@ -49,22 +49,33 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.models.CommandProcessingResult; import org.apache.fineract.client.models.ExternalAssetOwnerRequest; import org.apache.fineract.client.models.ExternalOwnerJournalEntryData; import org.apache.fineract.client.models.ExternalOwnerTransferJournalEntryData; import org.apache.fineract.client.models.ExternalTransferData; +import org.apache.fineract.client.models.ExternalTransferLoanProductAttributesData; +import org.apache.fineract.client.models.GetLoanProductsResponse; import org.apache.fineract.client.models.JournalEntryData; import org.apache.fineract.client.models.PageExternalTransferData; +import org.apache.fineract.client.models.PageExternalTransferLoanProductAttributesData; +import org.apache.fineract.client.models.PostExternalAssetOwnerLoanProductAttributeRequest; import org.apache.fineract.client.models.PostInitiateTransferResponse; import org.apache.fineract.client.models.PostLoansResponse; +import org.apache.fineract.client.models.PutExternalAssetOwnerLoanProductAttributeRequest; +import org.apache.fineract.client.services.ExternalAssetOwnerLoanProductAttributesApi; import org.apache.fineract.client.services.ExternalAssetOwnersApi; +import org.apache.fineract.client.services.LoanProductsApi; +import org.apache.fineract.client.services.LoansApi; import org.apache.fineract.client.util.JSON; import org.apache.fineract.test.data.AssetExternalizationErrorMessage; import org.apache.fineract.test.helper.ErrorHelper; import org.apache.fineract.test.helper.ErrorMessageHelper; import org.apache.fineract.test.helper.ErrorResponse; import org.apache.fineract.test.helper.Utils; +import org.apache.fineract.test.messaging.EventAssertion; import org.apache.fineract.test.messaging.event.EventCheckHelper; +import org.apache.fineract.test.messaging.event.assetexternalization.LoanOwnershipTransferEvent; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; @@ -78,6 +89,7 @@ public class AssetExternalizationStepDef extends AbstractStepDef { public static final String DEFAULT_LOCALE = "en"; public static final String TRANSACTION_TYPE_SALE = "sale"; public static final String TRANSACTION_TYPE_BUYBACK = "buyback"; + public static final String TRANSACTION_TYPE_INTERMEDIARY_SALE = "intermediarySale"; public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT_ASSET_EXT); private static final Gson GSON = new JSON().getGson(); @@ -87,6 +99,9 @@ public class AssetExternalizationStepDef extends AbstractStepDef { @Autowired private EventCheckHelper eventCheckHelper; + @Autowired + private EventAssertion eventAssertion; + @When("Admin makes asset externalization request by Loan ID with unique ownerExternalId, user-generated transferExternalId and the following data:") public void createAssetExternalizationRequestByLoanIdUserGeneratedExtId(DataTable table) throws IOException { // if user created transferExternalId previously, it will use that, otherwise create a new one @@ -130,7 +145,7 @@ public class AssetExternalizationStepDef extends AbstractStepDef { response.body().getResourceExternalId()); ErrorHelper.checkSuccessfulApiCall(response); - } else if (transferData.get(0).equals(TRANSACTION_TYPE_SALE)) { + } else if ((transferData.get(0).equals(TRANSACTION_TYPE_SALE) || transferData.get(0).equals(TRANSACTION_TYPE_INTERMEDIARY_SALE))) { String ownerExternalId; if (regenerateOwner) { ownerExternalId = Utils.randomNameGenerator(OWNER_EXTERNAL_ID_PREFIX, 3); @@ -151,6 +166,13 @@ public class AssetExternalizationStepDef extends AbstractStepDef { testContext().set(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE, response.body().getResourceExternalId()); testContext().set(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID, ownerExternalId); + if (transferData.get(0).equals(TRANSACTION_TYPE_INTERMEDIARY_SALE)) { + assertThat(ownerExternalId).isNotNull(); + testContext().set(TestContextKey.ASSET_EXTERNALIZATION_PREVIOUS_OWNER_EXTERNAL_ID, ownerExternalId); + testContext().set(TestContextKey.ASSET_EXTERNALIZATION_INTERMEDIARY_SALE_TRANSFER_EXTERNAL_ID_FROM_RESPONSE, + response.body().getResourceExternalId()); + } + ErrorHelper.checkSuccessfulApiCall(response); } else { @@ -327,18 +349,50 @@ public class AssetExternalizationStepDef extends AbstractStepDef { Integer numberOfElementsActual = body.getNumberOfElements(); List content = body.getContent(); - String ownerExternalIdStored = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID); String transferExternalId; + String ownerExternalIdStored = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID); + String ownerExternalId; + String previousAssetOwner; + String intermediarySaleAssetOwner = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_PREVIOUS_OWNER_EXTERNAL_ID); List> data = table.asLists(); for (int i = 1; i < data.size(); i++) { List expectedValues = data.get(i); String transactionType = expectedValues.get(5); - if (transactionType.equals(ExternalTransferData.StatusEnum.BUYBACK.getValue())) { - transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); - } else { - transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + String status = expectedValues.get(2); + + // in case transfer has no previous intermediarySale transfer + if (intermediarySaleAssetOwner == null) { + if (transactionType.equalsIgnoreCase(TRANSACTION_TYPE_BUYBACK) + && status.equals(ExternalTransferData.StatusEnum.BUYBACK.getValue())) { + previousAssetOwner = ownerExternalIdStored; + ownerExternalId = ownerExternalIdStored; + transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } else { // in case of sale or intermediarySale transfer + ownerExternalId = ownerExternalIdStored; + previousAssetOwner = null; + transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } + } else { // in case transfer has previous intermediarySale transfer + if (transactionType.equalsIgnoreCase(TRANSACTION_TYPE_SALE) + && (status.equals(ExternalTransferData.StatusEnum.ACTIVE.getValue()) + || status.equals(ExternalTransferData.StatusEnum.PENDING.getValue()))) { + ownerExternalId = ownerExternalIdStored; + previousAssetOwner = intermediarySaleAssetOwner; + transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } else if (transactionType.equalsIgnoreCase(TRANSACTION_TYPE_BUYBACK) + && (status.equals(ExternalTransferData.StatusEnum.BUYBACK.getValue()) + || status.equals(ExternalTransferData.StatusEnum.BUYBACK_INTERMEDIATE.getValue()))) { + ownerExternalId = ownerExternalIdStored; + previousAssetOwner = ownerExternalIdStored; + transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } else { + ownerExternalId = intermediarySaleAssetOwner; + previousAssetOwner = null; + transferExternalId = testContext() + .get(TestContextKey.ASSET_EXTERNALIZATION_INTERMEDIARY_SALE_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } } List> actualValuesList = content.stream().map(t -> { @@ -375,13 +429,15 @@ public class AssetExternalizationStepDef extends AbstractStepDef { : null); } actualValues.add(t.getOwner().getExternalId() == null ? null : t.getOwner().getExternalId()); + actualValues.add(t.getPreviousOwner() == null ? null : t.getPreviousOwner().getExternalId()); actualValues.add(loanId == null ? null : String.valueOf(t.getLoan().getLoanId())); actualValues.add(loanExternalId == null ? null : t.getLoan().getExternalId()); actualValues.add(t.getTransferExternalId()); return actualValues; }).collect(Collectors.toList()); - expectedValues.add(ownerExternalIdStored); + expectedValues.add(ownerExternalId); + expectedValues.add(previousAssetOwner); expectedValues.add(loanId == null ? null : String.valueOf(loanId)); expectedValues.add(loanExternalId); expectedValues.add(transferExternalId); @@ -581,6 +637,29 @@ public class AssetExternalizationStepDef extends AbstractStepDef { return result.getTransferId(); } + private ExternalTransferData getLastTransferByTransferType(Long loanId, String transferType) throws IOException { + String transferExternalId; + if (transferType.equalsIgnoreCase(TRANSACTION_TYPE_BUYBACK)) { + transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } else if (transferType.equalsIgnoreCase(TRANSACTION_TYPE_SALE)) { + transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } else if (transferType.equalsIgnoreCase(TRANSACTION_TYPE_INTERMEDIARY_SALE)) { + transferExternalId = testContext() + .get(TestContextKey.ASSET_EXTERNALIZATION_INTERMEDIARY_SALE_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); + } else { + transferExternalId = null; + } + + Response transfersResponse = externalAssetOwnersApi.getTransfers(null, loanId, null, null, null) + .execute(); + List content = transfersResponse.body().getContent(); + ExternalTransferData result = content.stream().filter(bizEvent -> bizEvent.getTransferExternalId().equals(transferExternalId)) + .toList().stream().reduce((first, second) -> second) + .orElseThrow(() -> new IllegalStateException("transfersResponse.body().getContent() is empty")); + + return result; + } + @Then("The asset external owner has the following OWNER Journal entries:") public void checkJournalEntriesOwner(DataTable table) throws IOException { String ownerExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID); @@ -635,6 +714,48 @@ public class AssetExternalizationStepDef extends AbstractStepDef { eventCheckHelper.loanOwnershipTransferBusinessEventWithStatusCheck(loanId, transferId, transferStatus, transferStatusReason); } + public String getPreviousAssetOwner(ExternalTransferData transferData, String transferType, boolean isIntermediarySaleTransfer) { + String previousAssetOwner; + String intermediarySaleAssetOwner = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_PREVIOUS_OWNER_EXTERNAL_ID); + String assetOwner = transferData.getOwner() == null ? null : transferData.getOwner().getExternalId(); + + if ((transferType.equalsIgnoreCase(TRANSACTION_TYPE_SALE) || transferType.equalsIgnoreCase(TRANSACTION_TYPE_BUYBACK)) + && isIntermediarySaleTransfer) { + previousAssetOwner = intermediarySaleAssetOwner; + } else if (transferType.equalsIgnoreCase(TRANSACTION_TYPE_BUYBACK)) { + previousAssetOwner = assetOwner; + } else { + // in case - transferType is sale(has no intermediarySale before) or intermediarySale + previousAssetOwner = null; + } + return previousAssetOwner; + } + + @Then("LoanOwnershipTransferBusinessEvent with transfer type: {string} and transfer asset owner is created") + public void loanOwnershipTransferBusinessEventCheck(String transferType) throws IOException { + Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.body().getLoanId(); + ExternalTransferData transferData = getLastTransferByTransferType(loanId, transferType); + String previousAssetOwner = getPreviousAssetOwner(transferData, transferType, false); + + eventCheckHelper.loanOwnershipTransferBusinessEventWithTypeCheck(loanId, transferData, transferType, previousAssetOwner); + } + + @Then("LoanOwnershipTransferBusinessEvent with transfer type: {string} and transfer asset owner based on intermediarySale is created") + public void loanOwnershipTransferBusinessEventCheckBasedOnIntermediarySale(String transferType) throws IOException { + Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.body().getLoanId(); + ExternalTransferData transferData = getLastTransferByTransferType(loanId, transferType); + String previousAssetOwner = getPreviousAssetOwner(transferData, transferType, true); + + eventCheckHelper.loanOwnershipTransferBusinessEventWithTypeCheck(loanId, transferData, transferType, previousAssetOwner); + } + + @Then("LoanOwnershipTransferBusinessEvent is not created on {string}") + public void loanOwnershipTransferBusinessEventIsNotRaised(String date) throws IOException { + eventAssertion.assertEventNotRaised(LoanOwnershipTransferEvent.class, em -> FORMATTER.format(em.getBusinessDate()).equals(date)); + } + @Then("LoanAccountSnapshotBusinessEvent is created") public void loanAccountSnapshotBusinessEventCheck() throws IOException { Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); @@ -783,6 +904,49 @@ public class AssetExternalizationStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(response); } + @Autowired + private LoansApi loansApi; + + @Autowired + private LoanProductsApi loanProductsApi; + + @Autowired + private ExternalAssetOwnerLoanProductAttributesApi externalAssetOwnerLoanProductAttributesApi; + + @When("Admin set external asset owner loan product attribute {string} value {string} for loan product {string}") + public void setAExternalAssetOwnerLoanProductAttribute(String externalAssetOwnerLoanProductAttributeKey, + String externalAssetOwnerLoanProductAttributeValue, String loanProductName) throws IOException { + Response> loanProducts = loanProductsApi.retrieveAllLoanProducts().execute(); + long loanProductId = loanProducts.body().stream().filter(loanProduct -> loanProduct.getName().equals(loanProductName)).findFirst() + .orElseThrow(() -> new RuntimeException("No loan product is found!")).getId(); + + Response getExternalAssetOwnerLoanProductAttribute = externalAssetOwnerLoanProductAttributesApi + .getExternalAssetOwnerLoanProductAttributes(loanProductId, externalAssetOwnerLoanProductAttributeKey).execute(); + PageExternalTransferLoanProductAttributesData getExternalAssetOwnerLoanProductAttributeBody = getExternalAssetOwnerLoanProductAttribute + .body(); + + if (getExternalAssetOwnerLoanProductAttributeBody.getTotalFilteredRecords() == 0) { + PostExternalAssetOwnerLoanProductAttributeRequest setLoanProductAttributeRequest = new PostExternalAssetOwnerLoanProductAttributeRequest() + .attributeKey(externalAssetOwnerLoanProductAttributeKey).attributeValue(externalAssetOwnerLoanProductAttributeValue); + Response updateLoanProductAttributerEsponse = externalAssetOwnerLoanProductAttributesApi + .postExternalAssetOwnerLoanProductAttribute(loanProductId, setLoanProductAttributeRequest).execute(); + ErrorHelper.checkSuccessfulApiCall(updateLoanProductAttributerEsponse); + } else { + List attributes = getExternalAssetOwnerLoanProductAttributeBody.getPageItems(); + assert attributes != null; + long attributeId = attributes.stream() + .filter(attribute -> attribute.getAttributeKey().equals(externalAssetOwnerLoanProductAttributeKey)).findFirst() + .orElseThrow(() -> new RuntimeException(ErrorMessageHelper + .wrongDataInExternalAssetOwnerLoanProductAttribute(externalAssetOwnerLoanProductAttributeKey, loanProductId))) + .getAttributeId(); + PutExternalAssetOwnerLoanProductAttributeRequest setLoanProductAttributeRequest = new PutExternalAssetOwnerLoanProductAttributeRequest() + .attributeKey(externalAssetOwnerLoanProductAttributeKey).attributeValue(externalAssetOwnerLoanProductAttributeValue); + Response updateLoanProductAttributerEsponse = externalAssetOwnerLoanProductAttributesApi + .updateLoanProductAttribute(loanProductId, attributeId, setLoanProductAttributeRequest).execute(); + ErrorHelper.checkSuccessfulApiCall(updateLoanProductAttributerEsponse); + } + } + @When("Admin makes asset externalization request for type {string} by Loan ID with unique ownerExternalId, force generated transferExternalId and without change test owner with following data:") public void createAssetExternalizationRequestByLoanIdUserGeneratedExtIdForceTransferIdNoTestOwner(String type, DataTable table) throws IOException { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index f7daecb2a..0a5904fcd 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -233,8 +233,10 @@ public abstract class TestContextKey { public static final String ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_FROM_RESPONSE = "assetExternalizationTransferExternalIdFromResponse"; public static final String ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE = "assetExternalizationSalesTransferExternalIdFromResponse"; public static final String ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_EXTERNAL_ID_FROM_RESPONSE = "assetExternalizationBuybackTransferExternalIdFromResponse"; + public static final String ASSET_EXTERNALIZATION_INTERMEDIARY_SALE_TRANSFER_EXTERNAL_ID_FROM_RESPONSE = "assetExternalizationIntermediarySaleTransferExternalIdFromResponse"; public static final String ASSET_EXTERNALIZATION_BUYBACK_TRANSFER_PREFIX = "assetExternalizationTransferPrefix"; public static final String ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID = "assetExternalizationOwnerExternalId"; + public static final String ASSET_EXTERNALIZATION_PREVIOUS_OWNER_EXTERNAL_ID = "assetExternalizationPreviousOwnerExternalId"; public static final String TRANSACTION_EVENT = "transactionEvent"; public static final String LOAN_WRITE_OFF_RESPONSE = "loanWriteOffResponse"; public static final String LOAN_DELINQUENCY_ACTION_RESPONSE = "loanDelinquencyActionResponse"; diff --git a/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature b/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature index c9a18bdfa..0635bde96 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/AssetExternalization.feature @@ -1309,6 +1309,8 @@ Feature: Asset Externalization | 2023-05-21 | 1 | PENDING | 2023-05-01 | 2023-05-10 | | 2023-05-21 | 1 | CANCELLED | 2023-05-01 | 2023-05-10 | + Then LoanOwnershipTransferBusinessEvent is not created on "21 May 2023" + @TestRailId:C2773 Scenario: Verify that active SALE can not be cancelled When Admin sets the business date to "1 May 2023" @@ -1332,6 +1334,8 @@ Feature: Asset Externalization | 2023-05-21 | 1 | ACTIVE | 2023-05-22 | 9999-12-31 | SALE | When Admin send "cancel" command on "SALE" transaction it will throw an error + Then LoanOwnershipTransferBusinessEvent with transfer type: "SALE" and transfer asset owner is created + @TestRailId:C2774 Scenario: Verify that Asset cannot be cancelled after SALE and BUYBACK is completed When Admin sets the business date to "1 May 2023" @@ -1500,6 +1504,7 @@ Feature: Asset Externalization | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | | 2023-05-21 | 1 | PENDING | 2023-05-01 | 2023-05-21 | SALE | | 2023-05-21 | 1 | ACTIVE | 2023-05-22 | 9999-12-31 | SALE | + Then LoanOwnershipTransferBusinessEvent with transfer type: "SALE" and transfer asset owner is created When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: | Transaction type | settlementDate | purchasePriceRatio | | buyback | 2023-05-30 | | @@ -1518,6 +1523,7 @@ Feature: Asset Externalization | 2023-05-21 | 1 | PENDING | 2023-05-01 | 2023-05-21 | SALE | | 2023-05-21 | 1 | ACTIVE | 2023-05-22 | 2023-05-25 | SALE | | 2023-05-30 | 1 | BUYBACK | 2023-05-22 | 2023-05-25 | BUYBACK | + Then LoanOwnershipTransferBusinessEvent with transfer type: "BUYBACK" and transfer asset owner is created @TestRailId:C2788 Scenario: Verify that when a loan with PENDING BUYBACK is overpaid BUYBACK transaction can be done successfully @@ -1856,3 +1862,160 @@ Feature: Asset Externalization | ASSET | 112601 | Loans Receivable | 250.05 | | true | | LIABILITY | 145023 | Suspense/Clearing account | | 250.05 | true | Given Global configuration "asset-externalization-of-non-active-loans" is enabled + + @TestRailId:C3991 + Scenario: Verify asset externalization previous owner for intermediarySale transfer with following SALES request - UC1 + When Admin set external asset owner loan product attribute "SETTLEMENT_MODEL" value "DELAYED_SETTLEMENT" for loan product "LP1_DUE_DATE" + When Admin sets the business date to "1 May 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_DUE_DATE | 01 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "1 May 2023" with "1000" amount and expected disbursement date on "1 May 2023" + When Admin successfully disburse the loan on "1 May 2023" with "1000" EUR transaction amount + Then Loan status will be "ACTIVE" + When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: + | Transaction type | settlementDate | purchasePriceRatio | + | intermediarySale | 2023-05-21 | 1 | + Then Asset externalization response has the correct Loan ID, transferExternalId + Then Fetching Asset externalization details by loan id gives numberOfElements: 1 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 9999-12-31 | INTERMEDIARYSALE | + When Admin sets the business date to "22 May 2023" + When Admin runs inline COB job for Loan + Then Fetching Asset externalization details by loan id gives numberOfElements: 2 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 9999-12-31 | INTERMEDIARYSALE | + Then LoanOwnershipTransferBusinessEvent with transfer type: "INTERMEDIARYSALE" and transfer asset owner is created + When Admin sets the business date to "14 June 2023" + When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: + | Transaction type | settlementDate | purchasePriceRatio | + | sale | 2023-06-14 | 1 | + Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 9999-12-31 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | PENDING | 2023-06-14 | 9999-12-31 | SALE | + When Admin sets the business date to "15 June 2023" + When Admin runs inline COB job for Loan + Then Fetching Asset externalization details by loan id gives numberOfElements: 4 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 2023-06-14 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | PENDING | 2023-06-14 | 2023-06-14 | SALE | + | 2023-06-14 | 1 | ACTIVE | 2023-06-15 | 9999-12-31 | SALE | + Then LoanOwnershipTransferBusinessEvent with transfer type: "SALE" and transfer asset owner based on intermediarySale is created + When Admin set external asset owner loan product attribute "SETTLEMENT_MODEL" value "DEFAULT_SETTLEMENT" for loan product "LP1_DUE_DATE" + + @TestRailId:C3992 + Scenario: Verify asset externalization previous owner for intermediarySale transfer with following SALES and BUYBACK requests - UC2 + When Admin set external asset owner loan product attribute "SETTLEMENT_MODEL" value "DELAYED_SETTLEMENT" for loan product "LP1_DUE_DATE" + When Admin sets the business date to "1 May 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_DUE_DATE | 01 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "1 May 2023" with "1000" amount and expected disbursement date on "1 May 2023" + When Admin successfully disburse the loan on "1 May 2023" with "1000" EUR transaction amount + Then Loan status will be "ACTIVE" + When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: + | Transaction type | settlementDate | purchasePriceRatio | + | intermediarySale | 2023-05-21 | 1 | + Then Asset externalization response has the correct Loan ID, transferExternalId + Then Fetching Asset externalization details by loan id gives numberOfElements: 1 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 9999-12-31 | INTERMEDIARYSALE | + When Admin sets the business date to "22 May 2023" + When Admin runs inline COB job for Loan + Then Fetching Asset externalization details by loan id gives numberOfElements: 2 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 9999-12-31 | INTERMEDIARYSALE | + Then LoanOwnershipTransferBusinessEvent with transfer type: "INTERMEDIARYSALE" and transfer asset owner is created + + When Admin sets the business date to "14 June 2023" + When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: + | Transaction type | settlementDate | purchasePriceRatio | + | sale | 2023-06-14 | 1 | + Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 9999-12-31 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | PENDING | 2023-06-14 | 9999-12-31 | SALE | + When Admin sets the business date to "15 June 2023" + When Admin runs inline COB job for Loan + Then Fetching Asset externalization details by loan id gives numberOfElements: 4 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 2023-06-14 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | PENDING | 2023-06-14 | 2023-06-14 | SALE | + | 2023-06-14 | 1 | ACTIVE | 2023-06-15 | 9999-12-31 | SALE | + Then LoanOwnershipTransferBusinessEvent with transfer type: "SALE" and transfer asset owner based on intermediarySale is created + + When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: + | Transaction type | settlementDate | purchasePriceRatio | + | buyback | 2023-06-16 | | + Then Fetching Asset externalization details by loan id gives numberOfElements: 5 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 2023-06-14 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | PENDING | 2023-06-14 | 2023-06-14 | SALE | + | 2023-06-14 | 1 | ACTIVE | 2023-06-15 | 9999-12-31 | SALE | + | 2023-06-16 | 1 | BUYBACK | 2023-06-15 | 9999-12-31 | BUYBACK | + When Admin sets the business date to "17 June 2023" + When Admin runs inline COB job for Loan + Then Fetching Asset externalization details by loan id gives numberOfElements: 5 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 2023-06-14 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | PENDING | 2023-06-14 | 2023-06-14 | SALE | + | 2023-06-14 | 1 | ACTIVE | 2023-06-15 | 2023-06-16 | SALE | + | 2023-06-16 | 1 | BUYBACK | 2023-06-15 | 2023-06-16 | BUYBACK | + Then LoanOwnershipTransferBusinessEvent with transfer type: "BUYBACK" and transfer asset owner is created + When Admin set external asset owner loan product attribute "SETTLEMENT_MODEL" value "DEFAULT_SETTLEMENT" for loan product "LP1_DUE_DATE" + + @TestRailId:C3993 + Scenario: Verify asset externalization previous owner for intermediarySale transfer with following BUYBACK requests - UC3 + When Admin set external asset owner loan product attribute "SETTLEMENT_MODEL" value "DELAYED_SETTLEMENT" for loan product "LP1_DUE_DATE" + When Admin sets the business date to "1 May 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_DUE_DATE | 01 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "1 May 2023" with "1000" amount and expected disbursement date on "1 May 2023" + When Admin successfully disburse the loan on "1 May 2023" with "1000" EUR transaction amount + Then Loan status will be "ACTIVE" + When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: + | Transaction type | settlementDate | purchasePriceRatio | + | intermediarySale | 2023-05-21 | 1 | + Then Asset externalization response has the correct Loan ID, transferExternalId + Then Fetching Asset externalization details by loan id gives numberOfElements: 1 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 9999-12-31 | INTERMEDIARYSALE | + When Admin sets the business date to "22 May 2023" + When Admin runs inline COB job for Loan + Then Fetching Asset externalization details by loan id gives numberOfElements: 2 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 9999-12-31 | INTERMEDIARYSALE | + Then LoanOwnershipTransferBusinessEvent with transfer type: "INTERMEDIARYSALE" and transfer asset owner is created + When Admin sets the business date to "14 June 2023" + When Admin makes asset externalization request by Loan ID with unique ownerExternalId, system-generated transferExternalId and the following data: + | Transaction type | settlementDate | purchasePriceRatio | + | buyback | 2023-06-14 | | + Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 9999-12-31 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | BUYBACK_INTERMEDIATE | 2023-06-14 | 9999-12-31 | BUYBACK | + When Admin sets the business date to "15 June 2023" + When Admin runs inline COB job for Loan + Then Fetching Asset externalization details by loan id gives numberOfElements: 3 with correct ownerExternalId and the following data: + | settlementDate | purchasePriceRatio | status | effectiveFrom | effectiveTo | Transaction type | + | 2023-05-21 | 1 | PENDING_INTERMEDIATE | 2023-05-01 | 2023-05-21 | INTERMEDIARYSALE | + | 2023-05-21 | 1 | ACTIVE_INTERMEDIATE | 2023-05-22 | 2023-06-14 | INTERMEDIARYSALE | + | 2023-06-14 | 1 | BUYBACK_INTERMEDIATE | 2023-06-14 | 2023-06-14 | BUYBACK | + Then LoanOwnershipTransferBusinessEvent with transfer type: "BUYBACK" and transfer asset owner based on intermediarySale is created + When Admin set external asset owner loan product attribute "SETTLEMENT_MODEL" value "DEFAULT_SETTLEMENT" for loan product "LP1_DUE_DATE" +