From 7265606027113f0c35efd4933c44d2f0c46930f5 Mon Sep 17 00:00:00 2001 From: phandinhthe Date: Wed, 14 Jan 2026 12:38:13 +0700 Subject: [PATCH 1/3] **hotfix/support-customize-validthru-and-product-mapper-injection** - feat: Inject `ProductMapper` in `ProductIngestionSaga` and `BatchProductIngestionSaga`. - feat: Add custom mapper for `validThru` in `ProductMapper`. - test: Update tests to support `ProductMapper` injection. --- .../stream/product/mapping/ProductMapper.java | 11 +++++++++++ .../stream/product/BatchProductIngestionSaga.java | 5 +++-- .../backbase/stream/product/ProductIngestionSaga.java | 5 +++-- .../product/ProductIngestionSagaConfiguration.java | 8 ++++++-- .../stream/product/BatchProductIngestionSagaTest.java | 4 ++++ .../stream/product/ProductIngestionSagaTest.java | 4 ++++ 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java b/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java index dc9f53cd1..1891f107c 100644 --- a/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java +++ b/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java @@ -33,6 +33,7 @@ import com.backbase.stream.legalentity.model.TermDeposit; import com.backbase.stream.legalentity.model.TermUnit; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -98,6 +99,7 @@ public interface ProductMapper { @Mapping(source = "creditCard", qualifiedByName = "mapCreditCardNumber", target = ProductMapperConstants.NUMBER) @Mapping(source = "state.state", target = "state.externalId") @Mapping(source = ProductMapperConstants.EXTERNAL_PARENT_ID, target = "parentExternalId") + @Mapping(source = "validThru", target = "validThru", qualifiedByName = "mapValidThru") @InheritConfiguration ArrangementPost toPresentation(CreditCard creditCard); @@ -479,4 +481,13 @@ default String mapCreditCardNumber(CreditCard creditCard) { InterestPaymentFrequencyUnit mapTimeUnitV3ToInterestPaymentFrequencyUnit(com.backbase.dbs.arrangement.api.service.v3.model.TimeUnit timeUnit); com.backbase.dbs.arrangement.api.service.v3.model.TimeUnit mapTimeUnitV2ToTimeUnitV3(TimeUnit timeUnit); + + @Named("mapValidThru") + default LocalDate mapValidThru(OffsetDateTime validThru) { + if (validThru != null) { + return validThru.toLocalDate(); + } else { + return null; + } + } } diff --git a/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/BatchProductIngestionSaga.java b/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/BatchProductIngestionSaga.java index cffefd402..43d5722c7 100644 --- a/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/BatchProductIngestionSaga.java +++ b/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/BatchProductIngestionSaga.java @@ -22,6 +22,7 @@ import com.backbase.stream.loan.LoansSaga; import com.backbase.stream.loan.LoansTask; import com.backbase.stream.product.configuration.ProductIngestionSagaConfigurationProperties; +import com.backbase.stream.product.mapping.ProductMapper; import com.backbase.stream.product.service.ArrangementService; import com.backbase.stream.product.task.BatchProductGroupTask; import com.backbase.stream.product.task.ProductGroupTask; @@ -59,8 +60,8 @@ public class BatchProductIngestionSaga extends ProductIngestionSaga { public static final String BATCH_PRODUCT_GROUP = "batch-product-group"; - public BatchProductIngestionSaga(ArrangementService arrangementService, AccessGroupService accessGroupService, UserService userService, ProductIngestionSagaConfigurationProperties configurationProperties, LoansSaga loansSaga) { - super(arrangementService, accessGroupService, userService, configurationProperties, loansSaga); + public BatchProductIngestionSaga(ArrangementService arrangementService, AccessGroupService accessGroupService, UserService userService, ProductIngestionSagaConfigurationProperties configurationProperties, LoansSaga loansSaga, ProductMapper productMapper) { + super(arrangementService, accessGroupService, userService, configurationProperties, loansSaga, productMapper); } public Mono process(ProductGroupTask streamTask) { diff --git a/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSaga.java b/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSaga.java index b5c54338d..e99c0fef5 100644 --- a/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSaga.java +++ b/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSaga.java @@ -74,7 +74,7 @@ public class ProductIngestionSaga { public static final String REMOVED = "removed"; private static final String CREATED = "created"; - protected final ProductMapper productMapper = Mappers.getMapper(ProductMapper.class); + protected final ProductMapper productMapper; protected final BusinessFunctionGroupMapper businessFunctionGroupMapper = Mappers.getMapper(BusinessFunctionGroupMapper.class); @@ -84,12 +84,13 @@ public class ProductIngestionSaga { protected final ProductIngestionSagaConfigurationProperties configurationProperties; protected final LoansSaga loansSaga; - public ProductIngestionSaga(ArrangementService arrangementService, AccessGroupService accessGroupService, UserService userService, ProductIngestionSagaConfigurationProperties configurationProperties, LoansSaga loansSaga) { + public ProductIngestionSaga(ArrangementService arrangementService, AccessGroupService accessGroupService, UserService userService, ProductIngestionSagaConfigurationProperties configurationProperties, LoansSaga loansSaga, ProductMapper productMapper) { this.arrangementService = arrangementService; this.accessGroupService = accessGroupService; this.userService = userService; this.configurationProperties = configurationProperties; this.loansSaga = loansSaga; + this.productMapper = productMapper; } @ContinueSpan(log = "processProducts") diff --git a/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSagaConfiguration.java b/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSagaConfiguration.java index f71e0388e..9d8d309fd 100644 --- a/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSagaConfiguration.java +++ b/stream-product/product-ingestion-saga/src/main/java/com/backbase/stream/product/ProductIngestionSagaConfiguration.java @@ -3,9 +3,11 @@ import com.backbase.stream.loan.LoansSaga; import com.backbase.stream.product.configuration.ProductConfiguration; import com.backbase.stream.product.configuration.ProductIngestionSagaConfigurationProperties; +import com.backbase.stream.product.mapping.ProductMapper; import com.backbase.stream.product.service.ArrangementService; import com.backbase.stream.service.AccessGroupService; import com.backbase.stream.service.UserService; +import org.mapstruct.factory.Mappers; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -27,7 +29,8 @@ public ProductIngestionSaga productIngestionSaga(ArrangementService arrangementS accessGroupService, userService, configurationProperties, - loansSaga + loansSaga, + Mappers.getMapper(ProductMapper.class) ); } @@ -42,7 +45,8 @@ public BatchProductIngestionSaga batchProductIngestionSaga(ArrangementService ar accessGroupService, userService, configurationProperties, - loansSaga + loansSaga, + Mappers.getMapper(ProductMapper.class) ); } } diff --git a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java index fb23c94a4..a4e87e12a 100644 --- a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java +++ b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java @@ -17,6 +17,7 @@ import com.backbase.stream.loan.LoansSaga; import com.backbase.stream.loan.LoansTask; import com.backbase.stream.product.configuration.ProductIngestionSagaConfigurationProperties; +import com.backbase.stream.product.mapping.ProductMapper; import com.backbase.stream.product.service.ArrangementService; import com.backbase.stream.product.task.BatchProductGroupTask; import com.backbase.stream.service.AccessGroupService; @@ -25,9 +26,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -54,6 +57,7 @@ class BatchProductIngestionSagaTest { @BeforeEach void setUp() { + ReflectionTestUtils.setField(batchProductIngestionSaga, "productMapper", Mappers.getMapper(ProductMapper.class)); batchProductGroupTask = mockBatchProductGroupTask(); BatchResponseItem batchResponseItemExtended = new BatchResponseItem(); batchResponseItemExtended.setArrangementExternalId("resource_id"); diff --git a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java index 075cb9920..16d8ebdf6 100644 --- a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java +++ b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java @@ -10,6 +10,7 @@ import com.backbase.stream.legalentity.model.ProductGroup; import com.backbase.stream.loan.LoansSaga; import com.backbase.stream.product.configuration.ProductIngestionSagaConfigurationProperties; +import com.backbase.stream.product.mapping.ProductMapper; import com.backbase.stream.product.service.ArrangementService; import com.backbase.stream.product.task.ProductGroupTask; import com.backbase.stream.service.AccessGroupService; @@ -18,9 +19,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -46,6 +49,7 @@ class ProductIngestionSagaTest { @BeforeEach void setUp() { + ReflectionTestUtils.setField(productIngestionSaga, "productMapper", Mappers.getMapper(ProductMapper.class)); productGroupTask = mockProductGroupTask(); when(arrangementService.getArrangementInternalId(anyString())) .thenReturn(Mono.just("internal_id")); From 8f70af07c8fbd7d4f397987763fd56e66c33ea5c Mon Sep 17 00:00:00 2001 From: phandinhthe Date: Wed, 14 Jan 2026 14:45:22 +0700 Subject: [PATCH 2/3] **hotfix/support-customize-validthru-and-product-mapper-injection** feat(product-mapper): add `validThru` mapping with custom parser. - Implement `mapValidThru` method to handle date parsing for `validThru`. - Update `ProductMapper` to include `validThru` mappings. - Extend unit tests to cover `validThru` logic. --- .../stream/product/mapping/ProductMapper.java | 17 +++++++++++++++++ .../product/mapping/ProductMapperTest.java | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java b/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java index 1891f107c..ddd0af3d7 100644 --- a/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java +++ b/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java @@ -35,6 +35,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.OffsetDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Set; @@ -61,6 +62,7 @@ public interface ProductMapper { @Mapping(source = "state.externalStateId", target = "state.externalId") @Mapping(source = ProductMapperConstants.ACCOUNT_HOLDER_NAME, target = "accountHolder.names") @Mapping(source = ProductMapperConstants.EXTERNAL_PARENT_ID, target = "parentExternalId") + @Mapping(source = "validThru", target = "validThru", qualifiedByName = "mapValidThru") ArrangementPost toPresentation(Product product); @Mapping(source = ProductMapperConstants.EXTERNAL_ID, target = ProductMapperConstants.EXTERNAL_ARRANGEMENT_ID) @@ -132,11 +134,13 @@ public interface ProductMapper { @Mapping(source = "accountHolder.names", target = "accountHolderNames") @Mapping(source = "product.externalId", target = "externalProductId") @Mapping(source = "legalEntities", target = "legalEntityIds", qualifiedByName = "mapLegalEntitiesIdsSet") + @Mapping(source = "validThru", target = "validThru", qualifiedByName = "mapValidThru") ArrangementItem toArrangementItem(ArrangementPost arrangementItemPost); @Mapping(source = "externalId", target = "externalArrangementId") @Mapping(source = "state.externalId", target = "stateId") @Mapping(source = "accountHolder.names", target = "accountHolderNames") + @Mapping(source = "validThru", target = "validThru", qualifiedByName = "mapValidThru") ArrangementPutItem toArrangementItemPut(ArrangementPost arrangementItemPost); ArrangementItemBase toArrangementItemBase(ArrangementItemPostRequest arrangementItemPost); @@ -490,4 +494,17 @@ default LocalDate mapValidThru(OffsetDateTime validThru) { return null; } } + + @Named("mapValidThru") + default OffsetDateTime mapValidThru(String validThru) { + if (isBlank(validThru)) { + return null; + } + try { + LocalDate localDate = LocalDate.parse(validThru); + return localDate.atTime(0,0,0,0).atOffset(OffsetDateTime.now(ZoneId.of("UTC")).getOffset()); + } catch (java.time.format.DateTimeParseException e) { + return OffsetDateTime.parse(validThru, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")); + } + } } diff --git a/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java b/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java index 5afe2b561..bc422a5f8 100644 --- a/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java +++ b/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java @@ -100,7 +100,8 @@ private Product buildProduct() { .autoRenewalIndicator(true) .interestSettlementAccount("ISA") .outstandingPrincipalAmount(new BigDecimal("101")) - .monthlyInstalmentAmount(new BigDecimal("250")); + .monthlyInstalmentAmount(new BigDecimal("250")) + .validThru(OffsetDateTime.parse("2050-12-23T11:20:30.000001Z")); } private SavingsAccount buildSavingsAccount() { From 76c52400d83c419f7a4194885f2ca7866dc49012 Mon Sep 17 00:00:00 2001 From: phandinhthe Date: Wed, 14 Jan 2026 15:44:18 +0700 Subject: [PATCH 3/3] fix(hotfix/support-customize-validthru-and-product-mapper-injection): enhance ValidThru mappings and add ProductMapper mocks - Simplify `mapValidThru` logic in `ProductMapper` - Add `ProductMapper` mocks in `BatchProductIngestionSagaTest` and `ProductIngestionSagaTest` - Add test for `validThru` null case in `ProductMapperTest` --- .../stream/product/mapping/ProductMapper.java | 10 +--------- .../stream/product/mapping/ProductMapperTest.java | 12 ++++++++++++ .../product/BatchProductIngestionSagaTest.java | 2 ++ .../stream/product/ProductIngestionSagaTest.java | 2 ++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java b/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java index ddd0af3d7..e63e7016d 100644 --- a/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java +++ b/stream-product/product-core/src/main/java/com/backbase/stream/product/mapping/ProductMapper.java @@ -490,21 +490,13 @@ default String mapCreditCardNumber(CreditCard creditCard) { default LocalDate mapValidThru(OffsetDateTime validThru) { if (validThru != null) { return validThru.toLocalDate(); - } else { - return null; } + return null; } @Named("mapValidThru") default OffsetDateTime mapValidThru(String validThru) { - if (isBlank(validThru)) { - return null; - } - try { LocalDate localDate = LocalDate.parse(validThru); return localDate.atTime(0,0,0,0).atOffset(OffsetDateTime.now(ZoneId.of("UTC")).getOffset()); - } catch (java.time.format.DateTimeParseException e) { - return OffsetDateTime.parse(validThru, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'")); - } } } diff --git a/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java b/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java index bc422a5f8..97e73d2fe 100644 --- a/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java +++ b/stream-product/product-core/src/test/java/com/backbase/stream/product/mapping/ProductMapperTest.java @@ -276,6 +276,18 @@ void map_AccountArrangementItemBase_To_AccountArrangementItem() { Assertions.assertEquals(target.getAccountHolderNames(), source.getAccountHolder().getNames()); } + @Test + void map_AccountArrangementItemBase_To_AccountArrangementItem_when_validThruNull() { + Product product = buildProduct(); + product.setValidThru(null); + ArrangementPost source = productMapper.toPresentation(product); + ArrangementItem target = productMapper.toArrangementItem(source); + Assertions.assertEquals(target.getExternalArrangementId(), source.getExternalId()); + Assertions.assertNotNull(target.getState()); + Assertions.assertEquals(target.getState().getState(), source.getState().getExternalId()); + Assertions.assertEquals(target.getAccountHolderNames(), source.getAccountHolder().getNames()); + } + @Test void map_Product_To_AccountArrangementItem() { Product source = buildProduct(); diff --git a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java index a4e87e12a..bf41d5cc7 100644 --- a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java +++ b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/BatchProductIngestionSagaTest.java @@ -52,6 +52,8 @@ class BatchProductIngestionSagaTest { LoansSaga loansSaga; @Mock LoansApi loansApi; + @Mock + ProductMapper productMapper; BatchProductGroupTask batchProductGroupTask; diff --git a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java index 16d8ebdf6..4ccf085d4 100644 --- a/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java +++ b/stream-product/product-ingestion-saga/src/test/java/com/backbase/stream/product/ProductIngestionSagaTest.java @@ -44,6 +44,8 @@ class ProductIngestionSagaTest { LoansSaga loansSaga; @Mock LoansApi loansApi; + @Mock + ProductMapper productMapper; ProductGroupTask productGroupTask;