From 85ac079193a191ac7dfe6b56e11f32978967c895 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Tue, 10 Dec 2024 17:44:15 +0200 Subject: [PATCH 01/16] MODTLR-112 Loan event listener --- .../listener/kafka/KafkaEventListener.java | 67 ++++++++-------- .../folio/repository/EcsTlrRepository.java | 1 + .../folio/service/impl/LoanEventHandler.java | 79 +++++++++++++++++++ .../listener/KafkaEventListenerTest.java | 7 +- 4 files changed, 120 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/folio/service/impl/LoanEventHandler.java diff --git a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java index f6e7b840..9e3bad58 100644 --- a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java +++ b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java @@ -5,12 +5,14 @@ import java.nio.charset.StandardCharsets; import java.util.Optional; +import org.folio.domain.dto.Loan; import org.folio.domain.dto.Request; import org.folio.domain.dto.RequestsBatchUpdate; import org.folio.domain.dto.User; import org.folio.domain.dto.UserGroup; import org.folio.exception.KafkaEventDeserializationException; import org.folio.service.KafkaEventHandler; +import org.folio.service.impl.LoanEventHandler; import org.folio.service.impl.RequestBatchUpdateEventHandler; import org.folio.service.impl.RequestEventHandler; import org.folio.service.impl.UserEventHandler; @@ -34,18 +36,20 @@ public class KafkaEventListener { private static final ObjectMapper objectMapper = new ObjectMapper(); private final RequestEventHandler requestEventHandler; + private final LoanEventHandler loanEventHandler; private final UserGroupEventHandler userGroupEventHandler; private final UserEventHandler userEventHandler; private final SystemUserScopedExecutionService systemUserScopedExecutionService; private final RequestBatchUpdateEventHandler requestBatchEventHandler; - public KafkaEventListener(@Autowired RequestEventHandler requestEventHandler, - @Autowired RequestBatchUpdateEventHandler requestBatchEventHandler, - @Autowired SystemUserScopedExecutionService systemUserScopedExecutionService, - @Autowired UserGroupEventHandler userGroupEventHandler, - @Autowired UserEventHandler userEventHandler) { + @Autowired + public KafkaEventListener(RequestEventHandler requestEventHandler, + LoanEventHandler loanEventHandler, RequestBatchUpdateEventHandler requestBatchEventHandler, + SystemUserScopedExecutionService systemUserScopedExecutionService, + UserGroupEventHandler userGroupEventHandler, UserEventHandler userEventHandler) { this.requestEventHandler = requestEventHandler; + this.loanEventHandler = loanEventHandler; this.systemUserScopedExecutionService = systemUserScopedExecutionService; this.userGroupEventHandler = userGroupEventHandler; this.requestBatchEventHandler = requestBatchEventHandler; @@ -57,32 +61,23 @@ public KafkaEventListener(@Autowired RequestEventHandler requestEventHandler, groupId = "${spring.kafka.consumer.group-id}" ) public void handleRequestEvent(String eventString, MessageHeaders messageHeaders) { - log.debug("handleRequestEvent:: event: {}", () -> eventString); - KafkaEvent event = deserialize(eventString, messageHeaders, Request.class); - log.info("handleRequestEvent:: event received: {}", event::getId); - handleEvent(event, requestEventHandler); - log.info("handleRequestEvent:: event consumed: {}", event::getId); + handleEvent(eventString, requestEventHandler, messageHeaders, Request.class); } @KafkaListener( - topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.request-queue-reordering", + topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.loan", groupId = "${spring.kafka.consumer.group-id}" ) - public void handleRequestBatchUpdateEvent(String eventString, MessageHeaders messageHeaders) { - log.debug("handleRequestBatchUpdateEvent:: event: {}", () -> eventString); - KafkaEvent event = deserialize(eventString, messageHeaders, RequestsBatchUpdate.class); - log.info("handleRequestBatchUpdateEvent:: event received: {}", event::getId); - handleEvent(event, requestBatchEventHandler); - log.info("handleRequestBatchUpdateEvent:: event consumed: {}", event::getId); + public void handleLoanEvent(String eventString, MessageHeaders messageHeaders) { + handleEvent(eventString, loanEventHandler, messageHeaders, Loan.class); } - private void handleEvent(KafkaEvent event, KafkaEventHandler handler) { - try { - systemUserScopedExecutionService.executeAsyncSystemUserScoped(CENTRAL_TENANT_ID, - () -> handler.handle(event)); - } catch (Exception e) { - log.error("handleEvent:: Failed to handle Kafka event in tenant {}", CENTRAL_TENANT_ID); - } + @KafkaListener( + topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.request-queue-reordering", + groupId = "${spring.kafka.consumer.group-id}" + ) + public void handleRequestBatchUpdateEvent(String eventString, MessageHeaders messageHeaders) { + handleEvent(eventString, requestBatchEventHandler, messageHeaders, RequestsBatchUpdate.class); } @KafkaListener( @@ -90,11 +85,7 @@ private void handleEvent(KafkaEvent event, KafkaEventHandler handler) groupId = "${spring.kafka.consumer.group-id}" ) public void handleUserGroupEvent(String eventString, MessageHeaders messageHeaders) { - KafkaEvent event = deserialize(eventString, messageHeaders, UserGroup.class); - - log.info("handleUserGroupEvent:: event received: {}", event::getId); - log.debug("handleUserGroupEvent:: event: {}", () -> event); - handleEvent(event, userGroupEventHandler); + handleEvent(eventString, userGroupEventHandler, messageHeaders, UserGroup.class); } @KafkaListener( @@ -102,10 +93,22 @@ public void handleUserGroupEvent(String eventString, MessageHeaders messageHeade groupId = "${spring.kafka.consumer.group-id}" ) public void handleUserEvent(String eventString, MessageHeaders messageHeaders) { - KafkaEvent event = deserialize(eventString, messageHeaders, User.class); + handleEvent(eventString, userEventHandler, messageHeaders, User.class); + } + + private void handleEvent(String eventString, KafkaEventHandler handler, + MessageHeaders messageHeaders, Class payloadType) { - log.info("handleUserEvent:: event received: {}", event::getId); - handleEvent(event, userEventHandler); + log.debug("handleEvent:: event: {}", () -> eventString); + KafkaEvent event = deserialize(eventString, messageHeaders, payloadType); + log.info("handleEvent:: event received: {}", event::getId); + try { + systemUserScopedExecutionService.executeAsyncSystemUserScoped(CENTRAL_TENANT_ID, + () -> handler.handle(event)); + } catch (Exception e) { + log.error("handleEvent:: failed to handle event in tenant {}", CENTRAL_TENANT_ID); + } + log.info("handleEvent:: event consumed: {}", event::getId); } private static KafkaEvent deserialize(String eventString, MessageHeaders messageHeaders, diff --git a/src/main/java/org/folio/repository/EcsTlrRepository.java b/src/main/java/org/folio/repository/EcsTlrRepository.java index 1679554a..b460aa52 100644 --- a/src/main/java/org/folio/repository/EcsTlrRepository.java +++ b/src/main/java/org/folio/repository/EcsTlrRepository.java @@ -14,4 +14,5 @@ public interface EcsTlrRepository extends JpaRepository { Optional findByPrimaryRequestId(UUID primaryRequestId); Optional findByInstanceId(UUID instanceId); List findByPrimaryRequestIdIn(List primaryRequestIds); + ListfindByItemIdAndRequesterId(UUID itemId, UUID requesterId); } diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java new file mode 100644 index 00000000..f5cd0a1c --- /dev/null +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -0,0 +1,79 @@ +package org.folio.service.impl; + +import static org.folio.support.KafkaEvent.EventType.UPDATED; + +import java.util.Optional; +import java.util.UUID; + +import org.folio.domain.dto.Loan; +import org.folio.domain.entity.EcsTlrEntity; +import org.folio.repository.EcsTlrRepository; +import org.folio.service.DcbService; +import org.folio.service.KafkaEventHandler; +import org.folio.service.RequestService; +import org.folio.service.ServicePointService; +import org.folio.spring.service.SystemUserScopedExecutionService; +import org.folio.support.KafkaEvent; +import org.springframework.stereotype.Service; + +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@AllArgsConstructor +@Service +@Log4j2 +public class LoanEventHandler implements KafkaEventHandler { + private static final String LOAN_ACTION_CHECKED_IN = "checkedin"; + + private final DcbService dcbService; + private final EcsTlrRepository ecsTlrRepository; + private final SystemUserScopedExecutionService executionService; + private final ServicePointService servicePointService; + private final RequestService requestService; + + @Override + public void handle(KafkaEvent event) { + log.info("handle:: processing loan event: {}", event::getId); + if (event.getType() == UPDATED) { + handleUpdateEvent(event); + } else { + log.info("handle:: ignoring event {} of unsupported type: {}", event::getId, event::getType); + } + log.info("handle:: loan event processed: {}", event::getId); + } + + private void handleUpdateEvent(KafkaEvent event) { + Loan loan = event.getData().getNewVersion(); + String loanAction = loan.getAction(); + log.info("handle:: loan action: {}", loanAction); + if (LOAN_ACTION_CHECKED_IN.equals(loanAction)) { + log.info("handleUpdateEvent:: processing check-in event: {}", event::getId); + handleCheckInEvent(loan); + } else { + log.info("handleUpdateEvent:: unsupported loan action: {}", loanAction); + } + } + + private void handleCheckInEvent(Loan loan) { + findEcsTlr(loan) + .ifPresent(this::updateEcsTlrOnCheckIn); + } + + private Optional findEcsTlr(Loan loan) { + log.info("findEcsTlr:: searching ECS TLR for loan {}", loan::getId); + return ecsTlrRepository.findByItemIdAndRequesterId(UUID.fromString(loan.getItemId()), + UUID.fromString(loan.getUserId())) + .stream() + .filter(this::isForLoan) + .findFirst(); + } + + private boolean isForLoan(EcsTlrEntity ecsTlr) { + return true; + } + + private void updateEcsTlrOnCheckIn(EcsTlrEntity ecsTlr) { + + } + +} diff --git a/src/test/java/org/folio/listener/KafkaEventListenerTest.java b/src/test/java/org/folio/listener/KafkaEventListenerTest.java index 759f8272..fe5f8f5d 100644 --- a/src/test/java/org/folio/listener/KafkaEventListenerTest.java +++ b/src/test/java/org/folio/listener/KafkaEventListenerTest.java @@ -8,6 +8,7 @@ import java.util.Map; import org.folio.listener.kafka.KafkaEventListener; +import org.folio.service.impl.LoanEventHandler; import org.folio.service.impl.RequestBatchUpdateEventHandler; import org.folio.service.impl.RequestEventHandler; import org.folio.service.impl.UserEventHandler; @@ -24,6 +25,8 @@ class KafkaEventListenerTest { @Mock RequestEventHandler requestEventHandler; @Mock + LoanEventHandler loanEventHandler; + @Mock RequestBatchUpdateEventHandler requestBatchEventHandler; @Mock SystemUserScopedExecutionService systemUserScopedExecutionService; @@ -37,8 +40,8 @@ void shouldHandleExceptionInEventHandler() { doThrow(new NullPointerException("NPE")).when(systemUserScopedExecutionService) .executeAsyncSystemUserScoped(any(), any()); KafkaEventListener kafkaEventListener = new KafkaEventListener(requestEventHandler, - requestBatchEventHandler, systemUserScopedExecutionService, userGroupEventHandler, - userEventHandler); + loanEventHandler, requestBatchEventHandler, systemUserScopedExecutionService, + userGroupEventHandler, userEventHandler); kafkaEventListener.handleRequestEvent("{}", new MessageHeaders(Map.of(TENANT, "default".getBytes()))); From f54a1fabeac10e83c57ea5fb71dff9fa995eae94 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Thu, 12 Dec 2024 11:04:14 +0200 Subject: [PATCH 02/16] MODTLR-112 Loan event handler --- .../folio/service/impl/LoanEventHandler.java | 120 ++++++++++++++---- 1 file changed, 98 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java index f5cd0a1c..36c14f0a 100644 --- a/src/main/java/org/folio/service/impl/LoanEventHandler.java +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -1,18 +1,25 @@ package org.folio.service.impl; +import static org.folio.domain.dto.TransactionStatusResponse.RoleEnum.BORROWING_PICKUP; +import static org.folio.domain.dto.TransactionStatusResponse.RoleEnum.LENDER; +import static org.folio.domain.dto.TransactionStatusResponse.RoleEnum.PICKUP; +import static org.folio.domain.dto.TransactionStatusResponse.StatusEnum.CLOSED; +import static org.folio.domain.dto.TransactionStatusResponse.StatusEnum.ITEM_CHECKED_IN; +import static org.folio.domain.dto.TransactionStatusResponse.StatusEnum.ITEM_CHECKED_OUT; import static org.folio.support.KafkaEvent.EventType.UPDATED; -import java.util.Optional; +import java.util.Collection; +import java.util.EnumSet; import java.util.UUID; import org.folio.domain.dto.Loan; +import org.folio.domain.dto.TransactionStatus.StatusEnum; +import org.folio.domain.dto.TransactionStatusResponse; +import org.folio.domain.dto.TransactionStatusResponse.RoleEnum; import org.folio.domain.entity.EcsTlrEntity; import org.folio.repository.EcsTlrRepository; import org.folio.service.DcbService; import org.folio.service.KafkaEventHandler; -import org.folio.service.RequestService; -import org.folio.service.ServicePointService; -import org.folio.spring.service.SystemUserScopedExecutionService; import org.folio.support.KafkaEvent; import org.springframework.stereotype.Service; @@ -24,12 +31,11 @@ @Log4j2 public class LoanEventHandler implements KafkaEventHandler { private static final String LOAN_ACTION_CHECKED_IN = "checkedin"; + private static final EnumSet RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN = + EnumSet.of(ITEM_CHECKED_OUT, ITEM_CHECKED_IN, CLOSED); private final DcbService dcbService; private final EcsTlrRepository ecsTlrRepository; - private final SystemUserScopedExecutionService executionService; - private final ServicePointService servicePointService; - private final RequestService requestService; @Override public void handle(KafkaEvent event) { @@ -47,31 +53,101 @@ private void handleUpdateEvent(KafkaEvent event) { String loanAction = loan.getAction(); log.info("handle:: loan action: {}", loanAction); if (LOAN_ACTION_CHECKED_IN.equals(loanAction)) { - log.info("handleUpdateEvent:: processing check-in event: {}", event::getId); - handleCheckInEvent(loan); + log.info("handleUpdateEvent:: processing loan check-in event: {}", event::getId); + handleCheckInEvent(event); } else { - log.info("handleUpdateEvent:: unsupported loan action: {}", loanAction); + log.info("handleUpdateEvent:: ignoring loan update event with unsupported loan action: {}", loanAction); } } - private void handleCheckInEvent(Loan loan) { - findEcsTlr(loan) - .ifPresent(this::updateEcsTlrOnCheckIn); + private void handleCheckInEvent(KafkaEvent event) { + updateEcsTlrForLoan(event.getData().getNewVersion(), event.getTenant()); } - private Optional findEcsTlr(Loan loan) { - log.info("findEcsTlr:: searching ECS TLR for loan {}", loan::getId); - return ecsTlrRepository.findByItemIdAndRequesterId(UUID.fromString(loan.getItemId()), - UUID.fromString(loan.getUserId())) - .stream() - .filter(this::isForLoan) - .findFirst(); + private void updateEcsTlrForLoan(Loan loan, String tenantId) { + Collection ecsTlrs = findEcsTlrs(loan); + for (EcsTlrEntity ecsTlr : ecsTlrs) { + log.info("updateEcsTlrForLoan:: checking ECS TLR {}", ecsTlr::getId); + + String primaryTenantId = ecsTlr.getPrimaryRequestTenantId(); + String secondaryTenantId = ecsTlr.getSecondaryRequestTenantId(); + String intermediateTenantId = ecsTlr.getIntermediateRequestTenantId(); + + UUID primaryTransactionId = ecsTlr.getPrimaryRequestDcbTransactionId(); + UUID secondaryTransactionId = ecsTlr.getSecondaryRequestDcbTransactionId(); + UUID intermediateTransactionId = ecsTlr.getIntermediateRequestDcbTransactionId(); + + if (primaryTransactionId == null || secondaryTransactionId == null) { + log.info("updateEcsTlrForLoan:: ECS TLR does not have primary and/or secondary transaction, skipping"); + continue; + } + + boolean eventTenantIdIsPrimaryTenantId = tenantId.equals(primaryTenantId); + boolean eventTenantIdIsSecondaryTenantId = tenantId.equals(secondaryTenantId); + if (!(eventTenantIdIsPrimaryTenantId || eventTenantIdIsSecondaryTenantId)) { + log.info("updateEcsTlrForLoan:: event tenant ID does not match ECS TLR's primary/secondary request tenant ID, skipping"); + continue; + } + + TransactionStatusResponse primaryTransaction = dcbService.getTransactionStatus( + primaryTransactionId, primaryTenantId); + TransactionStatusResponse.StatusEnum primaryTransactionStatus = primaryTransaction.getStatus(); + RoleEnum primaryTransactionRole = primaryTransaction.getRole(); + log.info("updateEcsTlrForLoan:: primary request transaction: status={}, role={}", + primaryTransactionStatus, primaryTransactionRole); + if (!RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN.contains(primaryTransactionStatus)) { + log.info("updateEcsTlrForLoan:: irrelevant primary request transaction status: {}", + primaryTransaction); + continue; + } + + TransactionStatusResponse secondaryTransaction = dcbService.getTransactionStatus( + secondaryTransactionId, secondaryTenantId); + TransactionStatusResponse.StatusEnum secondaryTransactionStatus = secondaryTransaction.getStatus(); + RoleEnum secondaryTransactionRole = secondaryTransaction.getRole(); + log.info("updateEcsTlrForLoan:: secondary request transaction: status={}, role={}", + secondaryTransactionStatus, secondaryTransactionRole); + if (!RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN.contains(secondaryTransactionStatus)) { + log.info("updateEcsTlrForLoan:: irrelevant secondary request transaction status: {}", + secondaryTransactionStatus); + continue; + } + + if (eventTenantIdIsPrimaryTenantId && + (primaryTransactionRole == BORROWING_PICKUP || primaryTransactionRole == PICKUP) && + (primaryTransactionStatus == ITEM_CHECKED_OUT || primaryTransactionStatus == ITEM_CHECKED_IN) && + secondaryTransactionRole == LENDER && secondaryTransactionStatus == ITEM_CHECKED_OUT) { + + log.info("updateEcsTlrForLoan:: check-in happened in primary request tenant ({}), updating transactions", primaryTenantId); + dcbService.updateTransactionStatus(primaryTransactionId, StatusEnum.ITEM_CHECKED_IN, primaryTenantId); + dcbService.updateTransactionStatus(secondaryTransactionId, StatusEnum.ITEM_CHECKED_IN, secondaryTenantId); + dcbService.updateTransactionStatus(intermediateTransactionId, StatusEnum.ITEM_CHECKED_IN, intermediateTenantId); + return; + } + else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER && + (secondaryTransactionStatus == ITEM_CHECKED_IN || secondaryTransactionStatus == CLOSED) && + (primaryTransactionRole == BORROWING_PICKUP || primaryTransactionRole == PICKUP) && + primaryTransactionStatus == ITEM_CHECKED_IN) { + + log.info("updateEcsTlrForLoan:: check-in happened in secondary request tenant ({}), updating transactions", secondaryTenantId); + dcbService.updateTransactionStatus(primaryTransactionId, StatusEnum.CLOSED, primaryTenantId); + dcbService.updateTransactionStatus(secondaryTransactionId, StatusEnum.CLOSED, secondaryTenantId); + dcbService.updateTransactionStatus(intermediateTransactionId, StatusEnum.CLOSED, intermediateTenantId); + return; + } + } + log.info("updateEcsTlrForLoan:: ECS TLR for loan {} in tenant {} was not found", loan.getId(), tenantId); } - private boolean isForLoan(EcsTlrEntity ecsTlr) { - return true; + + + private Collection findEcsTlrs(Loan loan) { + log.info("findEcsTlr:: searching ECS TLRs for loan {}", loan::getId); + return ecsTlrRepository.findByItemIdAndRequesterId(UUID.fromString(loan.getItemId()), + UUID.fromString(loan.getUserId())); } + private void updateEcsTlrOnCheckIn(EcsTlrEntity ecsTlr) { } From ef5c55dfd659495fb092a39a012b26f5302ba4a0 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Thu, 12 Dec 2024 11:18:08 +0200 Subject: [PATCH 03/16] MODTLR-112 Add loan schema --- src/main/resources/swagger.api/ecs-tlr.yaml | 2 + .../resources/swagger.api/schemas/loan.json | 166 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 src/main/resources/swagger.api/schemas/loan.json diff --git a/src/main/resources/swagger.api/ecs-tlr.yaml b/src/main/resources/swagger.api/ecs-tlr.yaml index dd60c16e..b1d4460c 100644 --- a/src/main/resources/swagger.api/ecs-tlr.yaml +++ b/src/main/resources/swagger.api/ecs-tlr.yaml @@ -103,6 +103,8 @@ components: $ref: 'schemas/request.json' requests: $ref: 'schemas/requests.json' + loan: + $ref: 'schemas/loan.json' searchInstancesResponse: $ref: 'schemas/search/searchInstancesResponse.yaml' searchItemResponse: diff --git a/src/main/resources/swagger.api/schemas/loan.json b/src/main/resources/swagger.api/schemas/loan.json new file mode 100644 index 00000000..1ab9653f --- /dev/null +++ b/src/main/resources/swagger.api/schemas/loan.json @@ -0,0 +1,166 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "Loan", + "description": "Links the item with the patron and applies certain conditions based on policies", + "properties": { + "id": { + "description": "Unique ID (generated UUID) of the loan", + "type": "string" + }, + "userId": { + "description": "ID of the patron the item was lent to. Required for open loans, not required for closed loans (for anonymization).", + "type": "string" + }, + "proxyUserId": { + "description": "ID of the user representing a proxy for the patron", + "type": "string", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "itemId": { + "description": "ID of the item lent to the patron", + "type": "string" + }, + "itemEffectiveLocationIdAtCheckOut": { + "description": "The effective location, at the time of checkout, of the item loaned to the patron.", + "type": "string", + "$ref": "uuid.json" + }, + "status": { + "description": "Overall status of the loan", + "type": "object", + "properties": { + "name": { + "description": "Name of the status (currently can be any value, values commonly used are Open and Closed)", + "type": "string" + } + } + }, + "loanDate": { + "description": "Date time when the loan began (typically represented according to rfc3339 section-5.6. Has not had the date-time format validation applied as was not supported at point of introduction and would now be a breaking change)", + "type": "string" + }, + "dueDate": { + "description": "Date time when the item is due to be returned", + "type": "string", + "format": "date-time" + }, + "returnDate": { + "description": "Date time when the item is returned and the loan ends (typically represented according to rfc3339 section-5.6. Has not had the date-time format validation applied as was not supported at point of introduction and would now be a breaking change)", + "type": "string" + }, + "systemReturnDate" : { + "description": "Date time when the returned item is actually processed", + "type": "string", + "format": "date-time" + }, + "action": { + "description": "Last action performed on a loan (currently can be any value, values commonly used are checkedout and checkedin)", + "type": "string" + }, + "actionComment": { + "description": "Comment to last action performed on a loan", + "type": "string" + }, + "itemStatus": { + "description": "Last item status used in relation to this loan (currently can be any value, values commonly used are Checked out and Available)", + "type": "string" + }, + "renewalCount": { + "description": "Count of how many times a loan has been renewed (incremented by the client)", + "type": "integer" + }, + "loanPolicyId": { + "description": "ID of last policy used in relation to this loan", + "type": "string" + }, + "checkoutServicePointId": { + "description": "ID of the Service Point where the last checkout occured", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", + "type": "string" + }, + "checkinServicePointId": { + "description": "ID of the Service Point where the last checkin occured", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", + "type": "string" + }, + "patronGroupIdAtCheckout": { + "description": "Patron Group Id at checkout", + "type": "string" + }, + "dueDateChangedByRecall": { + "description": "Indicates whether or not this loan had its due date modified by a recall on the loaned item", + "type": "boolean" + }, + "isDcb": { + "description": "Indicates whether or not this loan is associated for DCB use case", + "type": "boolean" + }, + "declaredLostDate" : { + "description": "Date and time the item was declared lost during this loan", + "type": "string", + "format": "date-time" + }, + "claimedReturnedDate": { + "description": "Date and time the item was claimed returned for this loan", + "type": "string", + "format": "date-time" + }, + "overdueFinePolicyId": { + "description": "ID of overdue fines policy at the time the item is check-in or renewed", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", + "type": "string" + }, + "lostItemPolicyId": { + "description": "ID of lost item policy which determines when the item ages to lost and the associated fees or the associated fees if the patron declares the item lost.", + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$", + "type": "string" + }, + "metadata": { + "description": "Metadata about creation and changes to loan, provided by the server (client should not provide)", + "type": "object", + "$ref": "metadata.json" + }, + "agedToLostDelayedBilling": { + "description": "Aged to Lost Delayed Billing processing", + "type": "object", + "properties": { + "lostItemHasBeenBilled": { + "description": "Indicates if the aged to lost fee has been billed (for use where delayed billing is set up)", + "type": "boolean" + }, + "dateLostItemShouldBeBilled": { + "description": "Indicates when the aged to lost fee should be billed (for use where delayed billing is set up)", + "type": "string", + "format": "date-time" + }, + "agedToLostDate": { + "description": "Date and time the item was aged to lost for this loan", + "type": "string", + "format": "date-time" + } + } + }, + "reminders" : { + "description": "Information about reminders for overdue loan", + "type": "object", + "properties": { + "lastFeeBilled": { + "description": "Information about the most recent reminder fee billing", + "type": "object", + "properties": { + "number": { + "description": "Last reminder fee billed, sequence number", + "type": "integer" + }, + "date": { + "description": "Last reminder fee billed, date", + "type": "string", + "format": "date-time" + } + } + } + } + } + } +} \ No newline at end of file From 24cd1e16b6ded28ecda22879dbdc80b515e84f32 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Fri, 13 Dec 2024 13:40:19 +0200 Subject: [PATCH 04/16] MODTLR-112 Only attempt allowed transaction status changes --- .../java/org/folio/service/DcbService.java | 3 +- .../folio/service/impl/DcbServiceImpl.java | 106 ++++++++++++++++-- .../folio/service/impl/LoanEventHandler.java | 8 +- .../service/impl/RequestEventHandler.java | 49 +------- 4 files changed, 99 insertions(+), 67 deletions(-) diff --git a/src/main/java/org/folio/service/DcbService.java b/src/main/java/org/folio/service/DcbService.java index 7b8cb372..1f2e3df0 100644 --- a/src/main/java/org/folio/service/DcbService.java +++ b/src/main/java/org/folio/service/DcbService.java @@ -13,7 +13,6 @@ public interface DcbService { void createBorrowerTransaction(EcsTlrEntity ecsTlr, Request request); void createBorrowingPickupTransaction(EcsTlrEntity ecsTlr, Request request); void createPickupTransaction(EcsTlrEntity ecsTlr, Request request); + void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr); TransactionStatusResponse getTransactionStatus(UUID transactionId, String tenantId); - TransactionStatusResponse updateTransactionStatus(UUID transactionId, - TransactionStatus.StatusEnum newStatus, String tenantId); } diff --git a/src/main/java/org/folio/service/impl/DcbServiceImpl.java b/src/main/java/org/folio/service/impl/DcbServiceImpl.java index 99017e46..57510263 100644 --- a/src/main/java/org/folio/service/impl/DcbServiceImpl.java +++ b/src/main/java/org/folio/service/impl/DcbServiceImpl.java @@ -4,6 +4,13 @@ import static org.folio.domain.dto.DcbTransaction.RoleEnum.BORROWING_PICKUP; import static org.folio.domain.dto.DcbTransaction.RoleEnum.LENDER; import static org.folio.domain.dto.DcbTransaction.RoleEnum.PICKUP; +import static org.folio.domain.dto.TransactionStatus.StatusEnum.AWAITING_PICKUP; +import static org.folio.domain.dto.TransactionStatus.StatusEnum.CANCELLED; +import static org.folio.domain.dto.TransactionStatus.StatusEnum.CLOSED; +import static org.folio.domain.dto.TransactionStatus.StatusEnum.CREATED; +import static org.folio.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_IN; +import static org.folio.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_OUT; +import static org.folio.domain.dto.TransactionStatus.StatusEnum.OPEN; import java.util.UUID; @@ -14,6 +21,7 @@ import org.folio.domain.dto.DcbTransaction.RoleEnum; import org.folio.domain.dto.Request; import org.folio.domain.dto.TransactionStatus; +import org.folio.domain.dto.TransactionStatus.StatusEnum; import org.folio.domain.dto.TransactionStatusResponse; import org.folio.domain.entity.EcsTlrEntity; import org.folio.service.DcbService; @@ -21,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import feign.FeignException; import lombok.extern.log4j.Log4j2; @Service @@ -114,18 +123,6 @@ public TransactionStatusResponse getTransactionStatus(UUID transactionId, String () -> dcbTransactionClient.getDcbTransactionStatus(transactionId.toString())); } - @Override - public TransactionStatusResponse updateTransactionStatus(UUID transactionId, - TransactionStatus.StatusEnum newStatus, String tenantId) { - - log.info("updateTransactionStatus:: transactionId={}, newStatus={}, tenantId={}", - transactionId, newStatus, tenantId); - - return executionService.executeSystemUserScoped(tenantId, - () -> dcbTransactionClient.changeDcbTransactionStatus( - transactionId.toString(), new TransactionStatus().status(newStatus))); - } - @Override public void createTransactions(EcsTlrEntity ecsTlr, Request secondaryRequest) { log.info("createTransactions:: creating transactions for ECS TLR {}", ecsTlr::getId); @@ -143,4 +140,89 @@ public void createTransactions(EcsTlrEntity ecsTlr, Request secondaryRequest) { } } + @Override + public void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr) { + log.info("updateTransactionStatuses:: updating primary transaction status to {}", newStatus::getValue); + updateTransactionStatus(ecsTlr.getPrimaryRequestDcbTransactionId(), newStatus, + ecsTlr.getPrimaryRequestTenantId()); + + log.info("updateTransactionStatuses:: updating intermediate transaction status to {}", newStatus::getValue); + updateTransactionStatus(ecsTlr.getIntermediateRequestDcbTransactionId(), newStatus, + ecsTlr.getIntermediateRequestTenantId()); + + log.info("updateTransactionStatuses:: updating secondary transaction status to {}", newStatus::getValue); + updateTransactionStatus(ecsTlr.getSecondaryRequestDcbTransactionId(), newStatus, + ecsTlr.getSecondaryRequestTenantId()); + } + + private void updateTransactionStatus(UUID transactionId, StatusEnum newStatus, String tenantId) { + if (transactionId == null) { + log.info("updateTransactionStatus:: transaction ID is null, doing nothing"); + return; + } + if (tenantId == null) { + log.info("updateTransactionStatus:: tenant ID is null, doing nothing"); + return; + } + + try { + if (isTransactionStatusChangeAllowed(transactionId, newStatus, tenantId)) { + log.info("updateTransactionStatus: changing status of transaction {} in tenant {} to {}", + transactionId, tenantId, newStatus.getValue()); + + executionService.executeSystemUserScoped(tenantId, + () -> dcbTransactionClient.changeDcbTransactionStatus(transactionId.toString(), + new TransactionStatus().status(newStatus))); + } + } catch (FeignException.NotFound e) { + log.error("updateTransactionStatus:: transaction {} not found: {}", transactionId, e.getMessage()); + } catch (Exception e) { + log.error("updateTransactionStatus:: failed to update transaction status: {}", e::getMessage); + log.debug("updateTransactionStatus:: ", e); + } + } + + private boolean isTransactionStatusChangeAllowed(UUID transactionId, StatusEnum newStatus, + String tenantId) { + + TransactionStatusResponse transaction = getTransactionStatus(transactionId, tenantId); + RoleEnum transactionRole = RoleEnum.fromValue(transaction.getRole().getValue()); + StatusEnum currentStatus = StatusEnum.fromValue(transaction.getStatus().getValue()); + + return isTransactionStatusChangeAllowed(transactionRole, currentStatus, newStatus); + } + + private static boolean isTransactionStatusChangeAllowed(RoleEnum role, StatusEnum oldStatus, + StatusEnum newStatus) { + + log.info("isTransactionStatusChangeAllowed:: role={}, oldStatus={}, newStatus={}", role, + oldStatus, newStatus); + + boolean isStatusChangeAllowed = false; + + if (role == LENDER) { + isStatusChangeAllowed = newStatus == CANCELLED || + (oldStatus == CREATED && newStatus == OPEN) || + (oldStatus == OPEN && newStatus == AWAITING_PICKUP) || + (oldStatus == AWAITING_PICKUP && newStatus == ITEM_CHECKED_OUT) || + (oldStatus == ITEM_CHECKED_OUT && newStatus == ITEM_CHECKED_IN); + } + else if (role == BORROWER) { + isStatusChangeAllowed = newStatus == CANCELLED || + (oldStatus == CREATED && newStatus == OPEN) || + (oldStatus == OPEN && newStatus == AWAITING_PICKUP) || + (oldStatus == AWAITING_PICKUP && newStatus == ITEM_CHECKED_OUT) || + (oldStatus == ITEM_CHECKED_OUT && newStatus == ITEM_CHECKED_IN) || + (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED); + } + else if (role == BORROWING_PICKUP || role == PICKUP) { + isStatusChangeAllowed = newStatus == CANCELLED || + (oldStatus == CREATED && newStatus == OPEN) || + (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED); + } + log.info("isTransactionStatusChangeAllowed:: transaction status change from {} to {} is allowed: {}", + oldStatus, newStatus, isStatusChangeAllowed); + return isStatusChangeAllowed; + } + } diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java index 36c14f0a..98904ade 100644 --- a/src/main/java/org/folio/service/impl/LoanEventHandler.java +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -119,9 +119,7 @@ private void updateEcsTlrForLoan(Loan loan, String tenantId) { secondaryTransactionRole == LENDER && secondaryTransactionStatus == ITEM_CHECKED_OUT) { log.info("updateEcsTlrForLoan:: check-in happened in primary request tenant ({}), updating transactions", primaryTenantId); - dcbService.updateTransactionStatus(primaryTransactionId, StatusEnum.ITEM_CHECKED_IN, primaryTenantId); - dcbService.updateTransactionStatus(secondaryTransactionId, StatusEnum.ITEM_CHECKED_IN, secondaryTenantId); - dcbService.updateTransactionStatus(intermediateTransactionId, StatusEnum.ITEM_CHECKED_IN, intermediateTenantId); + dcbService.updateTransactionStatuses(StatusEnum.ITEM_CHECKED_IN, ecsTlr); return; } else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER && @@ -130,9 +128,7 @@ else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER primaryTransactionStatus == ITEM_CHECKED_IN) { log.info("updateEcsTlrForLoan:: check-in happened in secondary request tenant ({}), updating transactions", secondaryTenantId); - dcbService.updateTransactionStatus(primaryTransactionId, StatusEnum.CLOSED, primaryTenantId); - dcbService.updateTransactionStatus(secondaryTransactionId, StatusEnum.CLOSED, secondaryTenantId); - dcbService.updateTransactionStatus(intermediateTransactionId, StatusEnum.CLOSED, intermediateTenantId); + dcbService.updateTransactionStatuses(StatusEnum.CLOSED, ecsTlr); return; } } diff --git a/src/main/java/org/folio/service/impl/RequestEventHandler.java b/src/main/java/org/folio/service/impl/RequestEventHandler.java index f8087ace..e367cc41 100644 --- a/src/main/java/org/folio/service/impl/RequestEventHandler.java +++ b/src/main/java/org/folio/service/impl/RequestEventHandler.java @@ -30,7 +30,6 @@ import org.folio.support.KafkaEvent; import org.springframework.stereotype.Service; -import feign.FeignException; import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -149,7 +148,7 @@ private static Optional determineNewTransactionSta oldRequestStatus, newRequestStatus); if (newRequestStatus == oldRequestStatus) { - log.info("determineNewTransactionStatus:: request status did not change"); + log.info("determineNewTransactionStatus:: request status did not change, doing nothing"); return Optional.empty(); } @@ -171,51 +170,7 @@ private static Optional determineNewTransactionSta private void updateTransactionStatuses(KafkaEvent event, EcsTlrEntity ecsTlr) { determineNewTransactionStatus(event) - .ifPresent(newStatus -> updateTransactionStatuses(newStatus, ecsTlr)); - } - - private void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr) { - log.info("updateTransactionStatuses:: updating primary transaction status to {}", newStatus::getValue); - updateTransactionStatus(ecsTlr.getPrimaryRequestDcbTransactionId(), newStatus, - ecsTlr.getPrimaryRequestTenantId()); - - log.info("updateTransactionStatuses:: updating intermediate transaction status to {}", newStatus::getValue); - updateTransactionStatus(ecsTlr.getIntermediateRequestDcbTransactionId(), newStatus, - ecsTlr.getIntermediateRequestTenantId()); - - log.info("updateTransactionStatuses:: updating secondary transaction status to {}", newStatus::getValue); - updateTransactionStatus(ecsTlr.getSecondaryRequestDcbTransactionId(), newStatus, - ecsTlr.getSecondaryRequestTenantId()); - } - - private void updateTransactionStatus(UUID transactionId, - TransactionStatus.StatusEnum newStatus, String tenantId) { - - if (transactionId == null) { - log.info("updateTransactionStatus:: transaction ID is null, doing nothing"); - return; - } - if (tenantId == null) { - log.info("updateTransactionStatus:: tenant ID is null, doing nothing"); - return; - } - - try { - var currentStatus = dcbService.getTransactionStatus(transactionId, tenantId).getStatus(); - log.info("updateTransactionStatus:: current transaction status: {}", currentStatus); - if (newStatus.getValue().equals(currentStatus.getValue())) { - log.info("updateTransactionStatus:: transaction status did not change, doing nothing"); - return; - } - log.info("updateTransactionStatus: changing status of transaction {} in tenant {} from {} to {}", - transactionId, tenantId, currentStatus.getValue(), newStatus.getValue()); - dcbService.updateTransactionStatus(transactionId, newStatus, tenantId); - } catch (FeignException.NotFound e) { - log.error("updateTransactionStatus:: transaction {} not found: {}", transactionId, e.getMessage()); - } catch (Exception e) { - log.error("updateTransactionStatus:: failed to update transaction status: {}", e::getMessage); - log.debug("updateTransactionStatus:: ", e); - } + .ifPresent(newStatus -> dcbService.updateTransactionStatuses(newStatus, ecsTlr)); } private void propagateChangesFromPrimaryToSecondaryRequest(EcsTlrEntity ecsTlr, From 615cf458ad22b8298304a97bd44904d1fd943f04 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Fri, 13 Dec 2024 17:42:55 +0200 Subject: [PATCH 05/16] MODTLR-112 Only attempt allowed transaction status changes --- .../listener/kafka/KafkaEventListener.java | 2 +- .../folio/service/impl/DcbServiceImpl.java | 21 +++++++++---------- .../folio/service/impl/LoanEventHandler.java | 10 ++------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java index 9e3bad58..64d393fb 100644 --- a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java +++ b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java @@ -106,7 +106,7 @@ private void handleEvent(String eventString, KafkaEventHandler handler, systemUserScopedExecutionService.executeAsyncSystemUserScoped(CENTRAL_TENANT_ID, () -> handler.handle(event)); } catch (Exception e) { - log.error("handleEvent:: failed to handle event in tenant {}", CENTRAL_TENANT_ID); + log.error("handleEvent:: failed to handle event {}", event.getId(), e); } log.info("handleEvent:: event consumed: {}", event::getId); } diff --git a/src/main/java/org/folio/service/impl/DcbServiceImpl.java b/src/main/java/org/folio/service/impl/DcbServiceImpl.java index 57510263..803da05f 100644 --- a/src/main/java/org/folio/service/impl/DcbServiceImpl.java +++ b/src/main/java/org/folio/service/impl/DcbServiceImpl.java @@ -201,27 +201,26 @@ private static boolean isTransactionStatusChangeAllowed(RoleEnum role, StatusEnu boolean isStatusChangeAllowed = false; if (role == LENDER) { - isStatusChangeAllowed = newStatus == CANCELLED || - (oldStatus == CREATED && newStatus == OPEN) || + isStatusChangeAllowed = (oldStatus == CREATED && newStatus == OPEN) || (oldStatus == OPEN && newStatus == AWAITING_PICKUP) || (oldStatus == AWAITING_PICKUP && newStatus == ITEM_CHECKED_OUT) || - (oldStatus == ITEM_CHECKED_OUT && newStatus == ITEM_CHECKED_IN); + (oldStatus == ITEM_CHECKED_OUT && newStatus == ITEM_CHECKED_IN) || + (oldStatus != CANCELLED && newStatus == CANCELLED); } else if (role == BORROWER) { - isStatusChangeAllowed = newStatus == CANCELLED || - (oldStatus == CREATED && newStatus == OPEN) || + isStatusChangeAllowed = (oldStatus == CREATED && newStatus == OPEN) || (oldStatus == OPEN && newStatus == AWAITING_PICKUP) || (oldStatus == AWAITING_PICKUP && newStatus == ITEM_CHECKED_OUT) || (oldStatus == ITEM_CHECKED_OUT && newStatus == ITEM_CHECKED_IN) || - (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED); + (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED) || + (oldStatus != CANCELLED && newStatus == CANCELLED); } else if (role == BORROWING_PICKUP || role == PICKUP) { - isStatusChangeAllowed = newStatus == CANCELLED || - (oldStatus == CREATED && newStatus == OPEN) || - (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED); + isStatusChangeAllowed = (oldStatus == CREATED && newStatus == OPEN) || + (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED) || + (oldStatus != CANCELLED && newStatus == CANCELLED); } - log.info("isTransactionStatusChangeAllowed:: transaction status change from {} to {} is allowed: {}", - oldStatus, newStatus, isStatusChangeAllowed); + log.info("isTransactionStatusChangeAllowed:: status change is allowed: {}", isStatusChangeAllowed); return isStatusChangeAllowed; } diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java index 98904ade..4044e2fc 100644 --- a/src/main/java/org/folio/service/impl/LoanEventHandler.java +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -131,21 +131,15 @@ else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER dcbService.updateTransactionStatuses(StatusEnum.CLOSED, ecsTlr); return; } + log.info("updateEcsTlrForLoan:: ECS TLR {} was not updated", ecsTlr::getId); } - log.info("updateEcsTlrForLoan:: ECS TLR for loan {} in tenant {} was not found", loan.getId(), tenantId); + log.info("updateEcsTlrForLoan:: suitable ECS TLR for loan {} in tenant {} was not found", loan.getId(), tenantId); } - - private Collection findEcsTlrs(Loan loan) { log.info("findEcsTlr:: searching ECS TLRs for loan {}", loan::getId); return ecsTlrRepository.findByItemIdAndRequesterId(UUID.fromString(loan.getItemId()), UUID.fromString(loan.getUserId())); } - - private void updateEcsTlrOnCheckIn(EcsTlrEntity ecsTlr) { - - } - } From 506a578637ce8629f28ddbf6ecb4a68feab5179b Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Tue, 24 Dec 2024 13:48:13 +0200 Subject: [PATCH 06/16] MODTLR-112 Test for transaction status update --- .../java/org/folio/service/DcbService.java | 2 + .../folio/service/impl/DcbServiceImpl.java | 3 +- .../folio/service/impl/LoanEventHandler.java | 34 +++--- .../org/folio/service/DcbServiceTest.java | 103 ++++++++++++++++++ 4 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 src/test/java/org/folio/service/DcbServiceTest.java diff --git a/src/main/java/org/folio/service/DcbService.java b/src/main/java/org/folio/service/DcbService.java index 1f2e3df0..5bf75405 100644 --- a/src/main/java/org/folio/service/DcbService.java +++ b/src/main/java/org/folio/service/DcbService.java @@ -15,4 +15,6 @@ public interface DcbService { void createPickupTransaction(EcsTlrEntity ecsTlr, Request request); void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr); TransactionStatusResponse getTransactionStatus(UUID transactionId, String tenantId); + void updateTransactionStatus(UUID transactionId, TransactionStatus.StatusEnum newStatus, + String tenantId); } diff --git a/src/main/java/org/folio/service/impl/DcbServiceImpl.java b/src/main/java/org/folio/service/impl/DcbServiceImpl.java index 803da05f..3408e23f 100644 --- a/src/main/java/org/folio/service/impl/DcbServiceImpl.java +++ b/src/main/java/org/folio/service/impl/DcbServiceImpl.java @@ -155,7 +155,8 @@ public void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, Ec ecsTlr.getSecondaryRequestTenantId()); } - private void updateTransactionStatus(UUID transactionId, StatusEnum newStatus, String tenantId) { + @Override + public void updateTransactionStatus(UUID transactionId, StatusEnum newStatus, String tenantId) { if (transactionId == null) { log.info("updateTransactionStatus:: transaction ID is null, doing nothing"); return; diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java index 4044e2fc..1e21c8ef 100644 --- a/src/main/java/org/folio/service/impl/LoanEventHandler.java +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -31,8 +31,8 @@ @Log4j2 public class LoanEventHandler implements KafkaEventHandler { private static final String LOAN_ACTION_CHECKED_IN = "checkedin"; - private static final EnumSet RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN = - EnumSet.of(ITEM_CHECKED_OUT, ITEM_CHECKED_IN, CLOSED); + private static final EnumSet + RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN = EnumSet.of(ITEM_CHECKED_OUT, ITEM_CHECKED_IN, CLOSED); private final DcbService dcbService; private final EcsTlrRepository ecsTlrRepository; @@ -61,31 +61,28 @@ private void handleUpdateEvent(KafkaEvent event) { } private void handleCheckInEvent(KafkaEvent event) { - updateEcsTlrForLoan(event.getData().getNewVersion(), event.getTenant()); + updateEcsTlr(event.getData().getNewVersion(), event.getTenant()); } - private void updateEcsTlrForLoan(Loan loan, String tenantId) { + private void updateEcsTlr(Loan loan, String tenantId) { Collection ecsTlrs = findEcsTlrs(loan); for (EcsTlrEntity ecsTlr : ecsTlrs) { - log.info("updateEcsTlrForLoan:: checking ECS TLR {}", ecsTlr::getId); - + log.info("updateEcsTlr:: checking ECS TLR {}", ecsTlr::getId); String primaryTenantId = ecsTlr.getPrimaryRequestTenantId(); String secondaryTenantId = ecsTlr.getSecondaryRequestTenantId(); - String intermediateTenantId = ecsTlr.getIntermediateRequestTenantId(); - UUID primaryTransactionId = ecsTlr.getPrimaryRequestDcbTransactionId(); UUID secondaryTransactionId = ecsTlr.getSecondaryRequestDcbTransactionId(); - UUID intermediateTransactionId = ecsTlr.getIntermediateRequestDcbTransactionId(); if (primaryTransactionId == null || secondaryTransactionId == null) { - log.info("updateEcsTlrForLoan:: ECS TLR does not have primary and/or secondary transaction, skipping"); + log.info("updateEcsTlr:: ECS TLR does not have primary/secondary transaction, skipping"); continue; } boolean eventTenantIdIsPrimaryTenantId = tenantId.equals(primaryTenantId); boolean eventTenantIdIsSecondaryTenantId = tenantId.equals(secondaryTenantId); if (!(eventTenantIdIsPrimaryTenantId || eventTenantIdIsSecondaryTenantId)) { - log.info("updateEcsTlrForLoan:: event tenant ID does not match ECS TLR's primary/secondary request tenant ID, skipping"); + log.info("updateEcsTlr:: event tenant ID does not match ECS TLR's primary/secondary request " + + "tenant ID, skipping"); continue; } @@ -93,7 +90,7 @@ private void updateEcsTlrForLoan(Loan loan, String tenantId) { primaryTransactionId, primaryTenantId); TransactionStatusResponse.StatusEnum primaryTransactionStatus = primaryTransaction.getStatus(); RoleEnum primaryTransactionRole = primaryTransaction.getRole(); - log.info("updateEcsTlrForLoan:: primary request transaction: status={}, role={}", + log.info("updateEcsTlr:: primary request transaction: status={}, role={}", primaryTransactionStatus, primaryTransactionRole); if (!RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN.contains(primaryTransactionStatus)) { log.info("updateEcsTlrForLoan:: irrelevant primary request transaction status: {}", @@ -105,10 +102,10 @@ private void updateEcsTlrForLoan(Loan loan, String tenantId) { secondaryTransactionId, secondaryTenantId); TransactionStatusResponse.StatusEnum secondaryTransactionStatus = secondaryTransaction.getStatus(); RoleEnum secondaryTransactionRole = secondaryTransaction.getRole(); - log.info("updateEcsTlrForLoan:: secondary request transaction: status={}, role={}", + log.info("updateEcsTlr:: secondary request transaction: status={}, role={}", secondaryTransactionStatus, secondaryTransactionRole); if (!RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN.contains(secondaryTransactionStatus)) { - log.info("updateEcsTlrForLoan:: irrelevant secondary request transaction status: {}", + log.info("updateEcsTlr:: irrelevant secondary request transaction status: {}", secondaryTransactionStatus); continue; } @@ -118,7 +115,8 @@ private void updateEcsTlrForLoan(Loan loan, String tenantId) { (primaryTransactionStatus == ITEM_CHECKED_OUT || primaryTransactionStatus == ITEM_CHECKED_IN) && secondaryTransactionRole == LENDER && secondaryTransactionStatus == ITEM_CHECKED_OUT) { - log.info("updateEcsTlrForLoan:: check-in happened in primary request tenant ({}), updating transactions", primaryTenantId); + log.info("updateEcsTlr:: check-in happened in primary request tenant ({}), updating transactions", + primaryTenantId); dcbService.updateTransactionStatuses(StatusEnum.ITEM_CHECKED_IN, ecsTlr); return; } @@ -127,13 +125,13 @@ else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER (primaryTransactionRole == BORROWING_PICKUP || primaryTransactionRole == PICKUP) && primaryTransactionStatus == ITEM_CHECKED_IN) { - log.info("updateEcsTlrForLoan:: check-in happened in secondary request tenant ({}), updating transactions", secondaryTenantId); + log.info("updateEcsTlr:: check-in happened in secondary request tenant ({}), updating transactions", secondaryTenantId); dcbService.updateTransactionStatuses(StatusEnum.CLOSED, ecsTlr); return; } - log.info("updateEcsTlrForLoan:: ECS TLR {} was not updated", ecsTlr::getId); + log.info("updateEcsTlr:: ECS TLR {} was not updated", ecsTlr::getId); } - log.info("updateEcsTlrForLoan:: suitable ECS TLR for loan {} in tenant {} was not found", loan.getId(), tenantId); + log.info("updateEcsTlr:: suitable ECS TLR for loan {} in tenant {} was not found", loan.getId(), tenantId); } private Collection findEcsTlrs(Loan loan) { diff --git a/src/test/java/org/folio/service/DcbServiceTest.java b/src/test/java/org/folio/service/DcbServiceTest.java new file mode 100644 index 00000000..3b7eb402 --- /dev/null +++ b/src/test/java/org/folio/service/DcbServiceTest.java @@ -0,0 +1,103 @@ +package org.folio.service; + +import static java.util.UUID.randomUUID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.UUID; +import java.util.concurrent.Callable; + +import org.folio.client.feign.DcbTransactionClient; +import org.folio.domain.dto.TransactionStatus; +import org.folio.domain.dto.TransactionStatusResponse; +import org.folio.service.impl.DcbServiceImpl; +import org.folio.spring.service.SystemUserScopedExecutionService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class DcbServiceTest { + + @Mock + private DcbTransactionClient dcbTransactionClient; + @Mock + private SystemUserScopedExecutionService executionService; + @InjectMocks + private DcbServiceImpl dcbService; + + @BeforeEach + public void setup() { + // Bypass the use of system user and return the result of Callable immediately + when(executionService.executeSystemUserScoped(any(String.class), any(Callable.class))) + .thenAnswer(invocation -> invocation.getArgument(1, Callable.class).call()); + } + + @ParameterizedTest + @CsvSource(value = { + "PICKUP, CREATED, OPEN, true", + "PICKUP, OPEN, AWAITING_PICKUP, false", + "PICKUP, AWAITING_PICKUP, ITEM_CHECKED_OUT, false", + "PICKUP, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, false", + "PICKUP, ITEM_CHECKED_IN, CLOSED, true", + "PICKUP, OPEN, CANCELLED, true", + + "BORROWING-PICKUP, CREATED, OPEN, true", + "BORROWING-PICKUP, OPEN, AWAITING_PICKUP, false", + "BORROWING-PICKUP, AWAITING_PICKUP, ITEM_CHECKED_OUT, false", + "BORROWING-PICKUP, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, false", + "BORROWING-PICKUP, ITEM_CHECKED_IN, CLOSED, true", + "BORROWING-PICKUP, OPEN, CANCELLED, true", + + "BORROWER, CREATED, OPEN, true", + "BORROWER, OPEN, AWAITING_PICKUP, true", + "BORROWER, AWAITING_PICKUP, ITEM_CHECKED_OUT, true", + "BORROWER, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, true", + "BORROWER, ITEM_CHECKED_IN, CLOSED, true", + "BORROWER, OPEN, CANCELLED, true", + + "LENDER, CREATED, OPEN, true", + "LENDER, OPEN, AWAITING_PICKUP, true", + "LENDER, AWAITING_PICKUP, ITEM_CHECKED_OUT, true", + "LENDER, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, true", + "LENDER, ITEM_CHECKED_IN, CLOSED, false", + "LENDER, OPEN, CANCELLED, true", + }) + void updateTransactionStatusesUpdatesAllTransactions(String role, String oldStatus, + String newStatus, boolean transactionUpdateIsExpected) { + + String transactionId = randomUUID().toString(); + TransactionStatus newTransactionStatus = new TransactionStatus().status( + TransactionStatus.StatusEnum.fromValue(newStatus)); + + TransactionStatusResponse mockGetStatusResponse = buildTransactionStatusResponse(role, oldStatus); + TransactionStatusResponse mockUpdateStatusResponse = buildTransactionStatusResponse(role, newStatus); + + when(dcbTransactionClient.getDcbTransactionStatus(transactionId)) + .thenReturn(mockGetStatusResponse); + + if (transactionUpdateIsExpected) { + when(dcbTransactionClient.changeDcbTransactionStatus(transactionId, newTransactionStatus)) + .thenReturn(mockUpdateStatusResponse); + } + + dcbService.updateTransactionStatus(UUID.fromString(transactionId), + newTransactionStatus.getStatus(), "test_tenant"); + + verify(dcbTransactionClient, times(transactionUpdateIsExpected ? 1 : 0)) + .changeDcbTransactionStatus(transactionId, newTransactionStatus); + } + + private static TransactionStatusResponse buildTransactionStatusResponse(String role, String status) { + return new TransactionStatusResponse() + .role(TransactionStatusResponse.RoleEnum.fromValue(role)) + .status(TransactionStatusResponse.StatusEnum.fromValue(status)); + } + +} \ No newline at end of file From 0425d699f89ea9cca6e2f8089846f5861822eda4 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Tue, 24 Dec 2024 15:40:05 +0200 Subject: [PATCH 07/16] MODTLR-112 Loan event handler tests --- .../folio/service/LoanEventHandlerTest.java | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 src/test/java/org/folio/service/LoanEventHandlerTest.java diff --git a/src/test/java/org/folio/service/LoanEventHandlerTest.java b/src/test/java/org/folio/service/LoanEventHandlerTest.java new file mode 100644 index 00000000..284ecdbd --- /dev/null +++ b/src/test/java/org/folio/service/LoanEventHandlerTest.java @@ -0,0 +1,199 @@ +package org.folio.service; + +import static java.util.Collections.emptyList; +import static java.util.UUID.randomUUID; +import static org.folio.support.KafkaEvent.EventType.UPDATED; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; + +import org.folio.domain.dto.Loan; +import org.folio.domain.dto.TransactionStatus; +import org.folio.domain.dto.TransactionStatusResponse; +import org.folio.domain.entity.EcsTlrEntity; +import org.folio.repository.EcsTlrRepository; +import org.folio.service.impl.LoanEventHandler; +import org.folio.support.KafkaEvent; +import org.folio.support.KafkaEvent.EventType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class LoanEventHandlerTest { + + private static final EnumSet SUPPORTED_EVENT_TYPES = EnumSet.of(UPDATED); + + @Mock + private DcbService dcbService; + @Mock + private EcsTlrRepository ecsTlrRepository; + @InjectMocks + private LoanEventHandler loanEventHandler; + + @ParameterizedTest + @EnumSource(EventType.class) + void eventsOfUnsupportedTypesAreIgnored(EventType eventType) { + if (!SUPPORTED_EVENT_TYPES.contains(eventType)) { + loanEventHandler.handle(new KafkaEvent<>(null, null, eventType, 0L, null, null)); + verifyNoInteractions(ecsTlrRepository, dcbService); + } + } + + @Test + void updateEventForLoanWithUnsupportedActionInIgnored() { + Loan loan = new Loan().action("random_action"); + KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED, + 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant"); + loanEventHandler.handle(event); + verifyNoInteractions(ecsTlrRepository, dcbService); + } + + @Test + void checkInEventIsIgnoredWhenEcsTlrForUpdatedLoanIsNotFound() { + UUID itemId = randomUUID(); + UUID userId = randomUUID(); + Loan loan = new Loan() + .id(randomUUID().toString()) + .action("checkedin") + .itemId(itemId.toString()) + .userId(userId.toString()); + + when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + .thenReturn(emptyList()); + + KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED, + 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant"); + loanEventHandler.handle(event); + + verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verifyNoInteractions(dcbService); + } + + @Test + void checkInEventIsIgnoredWhenEcsTlrDoesNotContainsNoTransactionIds() { + UUID itemId = randomUUID(); + UUID userId = randomUUID(); + Loan loan = new Loan() + .id(randomUUID().toString()) + .action("checkedin") + .itemId(itemId.toString()) + .userId(userId.toString()); + + when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + .thenReturn(List.of(new EcsTlrEntity())); + + KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED, + 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant"); + loanEventHandler.handle(event); + + verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verifyNoInteractions(dcbService); + } + + @Test + void checkInEventIsIgnoredWhenEventTenantDoesNotMatchEcsRequestTransactionTenants() { + UUID itemId = randomUUID(); + UUID userId = randomUUID(); + Loan loan = new Loan() + .id(randomUUID().toString()) + .action("checkedin") + .itemId(itemId.toString()) + .userId(userId.toString()); + + EcsTlrEntity ecsTlr = new EcsTlrEntity(); + ecsTlr.setPrimaryRequestTenantId("borrowing_tenant"); + ecsTlr.setSecondaryRequestTenantId("lending_tenant"); + ecsTlr.setPrimaryRequestDcbTransactionId(randomUUID()); + ecsTlr.setSecondaryRequestDcbTransactionId(randomUUID()); + + when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + .thenReturn(List.of(ecsTlr)); + + KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED, + 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant"); + loanEventHandler.handle(event); + + verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verifyNoInteractions(dcbService); + } + + @ParameterizedTest + @CsvSource(value = { + "BORROWING-PICKUP, ITEM_CHECKED_OUT, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN", + "BORROWING-PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN", + "PICKUP, ITEM_CHECKED_OUT, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN", + "PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN", + + "BORROWING-PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_IN, lending_tenant, CLOSED", + "BORROWING-PICKUP, ITEM_CHECKED_IN, LENDER, CLOSED, lending_tenant, CLOSED", + "PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_IN, lending_tenant, CLOSED", + "PICKUP, ITEM_CHECKED_IN, LENDER, CLOSED, lending_tenant, CLOSED" + }) + void checkInEventIsHandled(String primaryTransactionRole, String primaryTransactionStatus, + String secondaryTransactionRole, String secondaryTransactionStatus, String eventTenant, + String expectedNewTransactionStatus) { + + String primaryRequestTenant = "borrowing_tenant"; + String secondaryRequestTenant = "lending_tenant"; + UUID primaryTransactionId = randomUUID(); + UUID secondaryTransactionId = randomUUID(); + UUID itemId = randomUUID(); + UUID userId = randomUUID(); + Loan loan = new Loan() + .action("checkedin") + .itemId(itemId.toString()) + .userId(userId.toString()); + + EcsTlrEntity mockEcsTlr = new EcsTlrEntity(); + mockEcsTlr.setId(randomUUID()); + mockEcsTlr.setPrimaryRequestTenantId(primaryRequestTenant); + mockEcsTlr.setSecondaryRequestTenantId(secondaryRequestTenant); + mockEcsTlr.setPrimaryRequestDcbTransactionId(primaryTransactionId); + mockEcsTlr.setSecondaryRequestDcbTransactionId(secondaryTransactionId); + + when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + .thenReturn(List.of(mockEcsTlr)); + + TransactionStatusResponse mockPrimaryTransactionResponse = buildTransactionStatusResponse( + primaryTransactionRole, primaryTransactionStatus); + TransactionStatusResponse mockSecondaryTransactionResponse = buildTransactionStatusResponse( + secondaryTransactionRole, secondaryTransactionStatus); + + when(dcbService.getTransactionStatus(primaryTransactionId, primaryRequestTenant)) + .thenReturn(mockPrimaryTransactionResponse); + when(dcbService.getTransactionStatus(secondaryTransactionId, secondaryRequestTenant)) + .thenReturn(mockSecondaryTransactionResponse); + + TransactionStatus.StatusEnum expectedNewStatus = TransactionStatus.StatusEnum.fromValue( + expectedNewTransactionStatus); + doNothing().when(dcbService).updateTransactionStatuses(expectedNewStatus, mockEcsTlr); + + KafkaEvent.EventData eventData = new KafkaEvent.EventData<>(loan, loan); + KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), eventTenant, + UPDATED, 0L, eventData, eventTenant); + + loanEventHandler.handle(event); + + verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verify(dcbService).getTransactionStatus(primaryTransactionId, primaryRequestTenant); + verify(dcbService).getTransactionStatus(secondaryTransactionId, secondaryRequestTenant); + verify(dcbService).updateTransactionStatuses(expectedNewStatus, mockEcsTlr); + } + + private static TransactionStatusResponse buildTransactionStatusResponse(String role, String status) { + return new TransactionStatusResponse() + .role(TransactionStatusResponse.RoleEnum.fromValue(role)) + .status(TransactionStatusResponse.StatusEnum.fromValue(status)); + } +} From 7bdb068e95dde0c4ac37b16baffa4ebd0ea7e3c3 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Thu, 26 Dec 2024 15:42:40 +0200 Subject: [PATCH 08/16] MODTLR-118 Remove hardcoded central tenant ID --- src/main/java/org/folio/domain/Constants.java | 13 ----- .../listener/kafka/KafkaEventListener.java | 54 +++++++++--------- .../service/impl/RequestServiceImpl.java | 2 +- src/test/java/org/folio/api/BaseIT.java | 56 +++++++++++++------ .../controller/KafkaEventListenerTest.java | 11 ++-- .../listener/KafkaEventListenerTest.java | 9 ++- .../folio/service/LoanEventHandlerTest.java | 2 +- .../RequestBatchUpdateEventHandlerTest.java | 10 ++-- .../service/RequestEventHandlerTest.java | 4 +- .../folio/service/UserEventHandlerTest.java | 4 +- .../service/UserGroupEventHandlerTest.java | 6 +- 11 files changed, 89 insertions(+), 82 deletions(-) delete mode 100644 src/main/java/org/folio/domain/Constants.java diff --git a/src/main/java/org/folio/domain/Constants.java b/src/main/java/org/folio/domain/Constants.java deleted file mode 100644 index 35a1ad8e..00000000 --- a/src/main/java/org/folio/domain/Constants.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.folio.domain; - -import static org.folio.domain.dto.Request.RequestTypeEnum.HOLD; - -import org.folio.domain.dto.Request; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class Constants { - public static final String CENTRAL_TENANT_ID = "consortium"; - public static final Request.RequestTypeEnum PRIMARY_REQUEST_TYPE = HOLD; -} diff --git a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java index 64d393fb..4d3bf60a 100644 --- a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java +++ b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java @@ -1,8 +1,7 @@ package org.folio.listener.kafka; -import static org.folio.domain.Constants.CENTRAL_TENANT_ID; - import java.nio.charset.StandardCharsets; +import java.util.Map; import java.util.Optional; import org.folio.domain.dto.Loan; @@ -12,27 +11,33 @@ import org.folio.domain.dto.UserGroup; import org.folio.exception.KafkaEventDeserializationException; import org.folio.service.KafkaEventHandler; +import org.folio.service.UserTenantsService; import org.folio.service.impl.LoanEventHandler; import org.folio.service.impl.RequestBatchUpdateEventHandler; import org.folio.service.impl.RequestEventHandler; import org.folio.service.impl.UserEventHandler; import org.folio.service.impl.UserGroupEventHandler; +import org.folio.spring.DefaultFolioExecutionContext; +import org.folio.spring.FolioExecutionContext; +import org.folio.spring.FolioModuleMetadata; import org.folio.spring.integration.XOkapiHeaders; +import org.folio.spring.scope.FolioExecutionContextSetter; import org.folio.spring.service.SystemUserScopedExecutionService; import org.folio.support.KafkaEvent; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.handler.annotation.Headers; import org.springframework.stereotype.Component; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @Component @Log4j2 +@RequiredArgsConstructor public class KafkaEventListener { private static final ObjectMapper objectMapper = new ObjectMapper(); private final RequestEventHandler requestEventHandler; @@ -41,26 +46,14 @@ public class KafkaEventListener { private final UserEventHandler userEventHandler; private final SystemUserScopedExecutionService systemUserScopedExecutionService; private final RequestBatchUpdateEventHandler requestBatchEventHandler; - - @Autowired - public KafkaEventListener(RequestEventHandler requestEventHandler, - LoanEventHandler loanEventHandler, RequestBatchUpdateEventHandler requestBatchEventHandler, - SystemUserScopedExecutionService systemUserScopedExecutionService, - UserGroupEventHandler userGroupEventHandler, UserEventHandler userEventHandler) { - - this.requestEventHandler = requestEventHandler; - this.loanEventHandler = loanEventHandler; - this.systemUserScopedExecutionService = systemUserScopedExecutionService; - this.userGroupEventHandler = userGroupEventHandler; - this.requestBatchEventHandler = requestBatchEventHandler; - this.userEventHandler = userEventHandler; - } + private final UserTenantsService userTenantsService; + private final FolioModuleMetadata folioModuleMetadata; @KafkaListener( topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.request", groupId = "${spring.kafka.consumer.group-id}" ) - public void handleRequestEvent(String eventString, MessageHeaders messageHeaders) { + public void handleRequestEvent(String eventString, @Headers Map messageHeaders) { handleEvent(eventString, requestEventHandler, messageHeaders, Request.class); } @@ -68,7 +61,7 @@ public void handleRequestEvent(String eventString, MessageHeaders messageHeaders topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.loan", groupId = "${spring.kafka.consumer.group-id}" ) - public void handleLoanEvent(String eventString, MessageHeaders messageHeaders) { + public void handleLoanEvent(String eventString, @Headers Map messageHeaders) { handleEvent(eventString, loanEventHandler, messageHeaders, Loan.class); } @@ -76,7 +69,7 @@ public void handleLoanEvent(String eventString, MessageHeaders messageHeaders) { topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.request-queue-reordering", groupId = "${spring.kafka.consumer.group-id}" ) - public void handleRequestBatchUpdateEvent(String eventString, MessageHeaders messageHeaders) { + public void handleRequestBatchUpdateEvent(String eventString, @Headers Map messageHeaders) { handleEvent(eventString, requestBatchEventHandler, messageHeaders, RequestsBatchUpdate.class); } @@ -84,7 +77,7 @@ public void handleRequestBatchUpdateEvent(String eventString, MessageHeaders mes topicPattern = "${folio.environment}\\.\\w+\\.users\\.userGroup", groupId = "${spring.kafka.consumer.group-id}" ) - public void handleUserGroupEvent(String eventString, MessageHeaders messageHeaders) { + public void handleUserGroupEvent(String eventString, @Headers Map messageHeaders) { handleEvent(eventString, userGroupEventHandler, messageHeaders, UserGroup.class); } @@ -92,18 +85,23 @@ public void handleUserGroupEvent(String eventString, MessageHeaders messageHeade topicPattern = "${folio.environment}\\.\\w+\\.users\\.users", groupId = "${spring.kafka.consumer.group-id}" ) - public void handleUserEvent(String eventString, MessageHeaders messageHeaders) { + public void handleUserEvent(String eventString, @Headers Map messageHeaders) { handleEvent(eventString, userEventHandler, messageHeaders, User.class); } private void handleEvent(String eventString, KafkaEventHandler handler, - MessageHeaders messageHeaders, Class payloadType) { + Map messageHeaders, Class payloadType) { log.debug("handleEvent:: event: {}", () -> eventString); KafkaEvent event = deserialize(eventString, messageHeaders, payloadType); log.info("handleEvent:: event received: {}", event::getId); - try { - systemUserScopedExecutionService.executeAsyncSystemUserScoped(CENTRAL_TENANT_ID, + + FolioExecutionContext context = DefaultFolioExecutionContext.fromMessageHeaders( + folioModuleMetadata, messageHeaders); + + try (FolioExecutionContextSetter contextSetter = new FolioExecutionContextSetter(context)) { + String centralTenantId = userTenantsService.getCentralTenantId(); + systemUserScopedExecutionService.executeAsyncSystemUserScoped(centralTenantId, () -> handler.handle(event)); } catch (Exception e) { log.error("handleEvent:: failed to handle event {}", event.getId(), e); @@ -111,7 +109,7 @@ private void handleEvent(String eventString, KafkaEventHandler handler, log.info("handleEvent:: event consumed: {}", event::getId); } - private static KafkaEvent deserialize(String eventString, MessageHeaders messageHeaders, + private static KafkaEvent deserialize(String eventString, Map messageHeaders, Class dataType) { try { @@ -128,7 +126,7 @@ private static KafkaEvent deserialize(String eventString, MessageHeaders } } - private static String getHeaderValue(MessageHeaders headers, String headerName) { + private static String getHeaderValue(Map headers, String headerName) { log.debug("getHeaderValue:: headers: {}, headerName: {}", () -> headers, () -> headerName); var headerValue = headers.get(headerName); var value = headerValue == null diff --git a/src/main/java/org/folio/service/impl/RequestServiceImpl.java b/src/main/java/org/folio/service/impl/RequestServiceImpl.java index 313d4b8e..242c7c61 100644 --- a/src/main/java/org/folio/service/impl/RequestServiceImpl.java +++ b/src/main/java/org/folio/service/impl/RequestServiceImpl.java @@ -234,7 +234,7 @@ public CirculationItem createCirculationItem(Request request, String inventoryTe .effectiveLocationId(item.getEffectiveLocationId()) .lendingLibraryCode("TEST_CODE"); - log.info("createCirculationItem:: creating circulation item {}", circulationItem.toString()); + log.info("createCirculationItem:: creating circulation item {}", itemId); return circulationItemClient.createCirculationItem(itemId, circulationItem); } diff --git a/src/test/java/org/folio/api/BaseIT.java b/src/test/java/org/folio/api/BaseIT.java index 4e905b89..f836fe5d 100644 --- a/src/test/java/org/folio/api/BaseIT.java +++ b/src/test/java/org/folio/api/BaseIT.java @@ -1,5 +1,6 @@ package org.folio.api; +import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -16,6 +17,8 @@ import org.apache.kafka.clients.admin.KafkaAdminClient; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.header.Header; +import org.apache.kafka.common.header.internals.RecordHeader; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; import org.folio.spring.integration.XOkapiHeaders; @@ -35,7 +38,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.messaging.MessageHeaders; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.DynamicPropertyRegistry; @@ -74,6 +76,7 @@ public class BaseIT { private static final String FOLIO_ENVIRONMENT = "folio"; protected static final String HEADER_TENANT = "x-okapi-tenant"; + protected static final String USED_ID = "08d51c7a-0f36-4f3d-9e35-d285612a23df"; protected static final String TOKEN = "test_token"; protected static final String TENANT_ID_CONSORTIUM = "consortium"; // central tenant protected static final String TENANT_ID_UNIVERSITY = "university"; @@ -156,22 +159,48 @@ public static String getOkapiUrl() { protected static void setUpTenant(MockMvc mockMvc) { mockMvc.perform(MockMvcRequestBuilders.post("/_/tenant") .content(asJsonString(new TenantAttributes().moduleTo("mod-tlr"))) - .headers(defaultHeaders()) + .headers(defaultHeadersForRequest()) .contentType(APPLICATION_JSON)).andExpect(status().isNoContent()); } - public static HttpHeaders defaultHeaders() { + public static HttpHeaders defaultHeadersForRequest() { final HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(APPLICATION_JSON); - httpHeaders.add(XOkapiHeaders.TENANT, TENANT_ID_CONSORTIUM); - httpHeaders.add(XOkapiHeaders.URL, wireMockServer.baseUrl()); - httpHeaders.add(XOkapiHeaders.TOKEN, TOKEN); - httpHeaders.add(XOkapiHeaders.USER_ID, "08d51c7a-0f36-4f3d-9e35-d285612a23df"); - + buildHeaders().forEach(httpHeaders::add); return httpHeaders; } + protected static Collection
buildHeadersForKafkaProducer(String tenant) { + return buildKafkaHeaders(tenant) + .entrySet() + .stream() + .map(entry -> new RecordHeader(entry.getKey(), (byte[]) entry.getValue())) + .collect(toList()); + } + + protected static Map buildKafkaHeaders(String tenantId) { + Map headers = buildHeaders(tenantId); + headers.put("folio.tenantId", tenantId); + + return headers.entrySet() + .stream() + .collect(toMap(Map.Entry::getKey, entry -> entry.getValue().getBytes())); + } + + protected static Map buildHeaders() { + return buildHeaders(TENANT_ID_CONSORTIUM); + } + + protected static Map buildHeaders(String tenantId) { + Map headers = new HashMap<>(); + headers.put(XOkapiHeaders.TENANT, tenantId); + headers.put(XOkapiHeaders.URL, wireMockServer.baseUrl()); + headers.put(XOkapiHeaders.TOKEN, TOKEN); + headers.put(XOkapiHeaders.USER_ID, USED_ID); + headers.put(XOkapiHeaders.REQUEST_ID, randomId()); + return headers; + } + @SneakyThrows public static String asJsonString(Object value) { return OBJECT_MAPPER.writeValueAsString(value); @@ -228,7 +257,7 @@ protected static String randomId() { } private static Map> buildDefaultHeaders() { - return new HashMap<>(defaultHeaders().entrySet() + return new HashMap<>(defaultHeadersForRequest().entrySet() .stream() .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))); } @@ -260,11 +289,4 @@ private static String buildTopicName(String env, String tenant, String module, S return String.format("%s.%s.%s.%s", env, tenant, module, objectType); } - protected MessageHeaders getMessageHeaders(String tenantName, String tenantId) { - Map header = new HashMap<>(); - header.put(XOkapiHeaders.TENANT, tenantName.getBytes()); - header.put("folio.tenantId", tenantId); - - return new MessageHeaders(header); - } } diff --git a/src/test/java/org/folio/controller/KafkaEventListenerTest.java b/src/test/java/org/folio/controller/KafkaEventListenerTest.java index 8e896c79..3462e274 100644 --- a/src/test/java/org/folio/controller/KafkaEventListenerTest.java +++ b/src/test/java/org/folio/controller/KafkaEventListenerTest.java @@ -27,8 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.Date; -import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -37,7 +37,7 @@ import org.apache.kafka.clients.consumer.OffsetAndMetadata; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.TopicPartition; -import org.apache.kafka.common.header.internals.RecordHeader; +import org.apache.kafka.common.header.Header; import org.awaitility.Awaitility; import org.folio.api.BaseIT; import org.folio.domain.dto.DcbItem; @@ -622,11 +622,8 @@ private void publishEvent(String tenant, String topic, KafkaEvent event) @SneakyThrows private void publishEvent(String tenant, String topic, String payload) { - kafkaTemplate.send(new ProducerRecord<>(topic, 0, randomId(), payload, - List.of( - new RecordHeader(XOkapiHeaders.TENANT, tenant.getBytes()), - new RecordHeader("folio.tenantId", randomId().getBytes()) - ))) + Collection
headers = buildHeadersForKafkaProducer(tenant); + kafkaTemplate.send(new ProducerRecord<>(topic, 0, randomId(), payload, headers)) .get(10, SECONDS); } diff --git a/src/test/java/org/folio/listener/KafkaEventListenerTest.java b/src/test/java/org/folio/listener/KafkaEventListenerTest.java index fe5f8f5d..f6783011 100644 --- a/src/test/java/org/folio/listener/KafkaEventListenerTest.java +++ b/src/test/java/org/folio/listener/KafkaEventListenerTest.java @@ -8,6 +8,7 @@ import java.util.Map; import org.folio.listener.kafka.KafkaEventListener; +import org.folio.service.UserTenantsService; import org.folio.service.impl.LoanEventHandler; import org.folio.service.impl.RequestBatchUpdateEventHandler; import org.folio.service.impl.RequestEventHandler; @@ -16,6 +17,7 @@ import org.folio.spring.service.SystemUserScopedExecutionService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.messaging.MessageHeaders; @@ -34,14 +36,15 @@ class KafkaEventListenerTest { UserGroupEventHandler userGroupEventHandler; @Mock UserEventHandler userEventHandler; + @Mock + UserTenantsService userTenantsService; + @InjectMocks + KafkaEventListener kafkaEventListener; @Test void shouldHandleExceptionInEventHandler() { doThrow(new NullPointerException("NPE")).when(systemUserScopedExecutionService) .executeAsyncSystemUserScoped(any(), any()); - KafkaEventListener kafkaEventListener = new KafkaEventListener(requestEventHandler, - loanEventHandler, requestBatchEventHandler, systemUserScopedExecutionService, - userGroupEventHandler, userEventHandler); kafkaEventListener.handleRequestEvent("{}", new MessageHeaders(Map.of(TENANT, "default".getBytes()))); diff --git a/src/test/java/org/folio/service/LoanEventHandlerTest.java b/src/test/java/org/folio/service/LoanEventHandlerTest.java index 284ecdbd..a9afe582 100644 --- a/src/test/java/org/folio/service/LoanEventHandlerTest.java +++ b/src/test/java/org/folio/service/LoanEventHandlerTest.java @@ -30,7 +30,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class LoanEventHandlerTest { +class LoanEventHandlerTest { private static final EnumSet SUPPORTED_EVENT_TYPES = EnumSet.of(UPDATED); diff --git a/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java b/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java index 8425f55c..2de1dcd0 100644 --- a/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java +++ b/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java @@ -116,7 +116,7 @@ void shouldReorderTwoSecondaryRequestsWhenPrimaryRequestsReordered() { null, new RequestsBatchUpdate() .instanceId(instanceId) .requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))), - getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID)); + buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(requestService, times(1)).reorderRequestsQueueForInstance( instanceId, firstTenant, reorderQueue); @@ -193,7 +193,7 @@ void shouldReorderThreeSecondaryRequestsWhenPrimaryRequestsReordered() { null, new RequestsBatchUpdate() .instanceId(instanceId) .requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))), - getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID)); + buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(requestService, times(1)).reorderRequestsQueueForInstance( instanceId, firstTenant, reorderQueue); @@ -250,7 +250,7 @@ void shouldNotReorderSecondaryRequestsWhenPrimaryRequestsOrderIsUnchanged() { null, new RequestsBatchUpdate() .instanceId(instanceId) .requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))), - getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID)); + buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(requestService, times(0)).reorderRequestsQueueForInstance( eq(instanceId), eq(firstTenant), any()); @@ -308,7 +308,7 @@ void shouldNotReorderSecondaryRequestsWhenPrimaryRequestsAreNullOrEmtpy( null, new RequestsBatchUpdate() .instanceId(instanceId) .requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))), - getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID)); + buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(requestService, times(0)).reorderRequestsQueueForInstance( eq(instanceId), eq(firstTenant), any()); @@ -377,7 +377,7 @@ null, new RequestsBatchUpdate() .instanceId(instanceId) .itemId(itemId) .requestLevel(RequestsBatchUpdate.RequestLevelEnum.ITEM))), - getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID)); + buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(requestService, times(1)).reorderRequestsQueueForItem( itemId, firstTenant, reorderQueue); diff --git a/src/test/java/org/folio/service/RequestEventHandlerTest.java b/src/test/java/org/folio/service/RequestEventHandlerTest.java index 70c2615b..a1172aee 100644 --- a/src/test/java/org/folio/service/RequestEventHandlerTest.java +++ b/src/test/java/org/folio/service/RequestEventHandlerTest.java @@ -8,7 +8,6 @@ import static org.mockito.Mockito.when; import java.util.Optional; -import java.util.UUID; import org.folio.api.BaseIT; import org.folio.listener.kafka.KafkaEventListener; @@ -35,8 +34,7 @@ class RequestEventHandlerTest extends BaseIT { void handleRequestUpdateTest() { when(ecsTlrRepository.findBySecondaryRequestId(any())).thenReturn(Optional.of(getEcsTlrEntity())); doNothing().when(dcbService).createLendingTransaction(any()); - eventListener.handleRequestEvent(REQUEST_UPDATE_EVENT_SAMPLE, getMessageHeaders( - TENANT_ID_CONSORTIUM, UUID.randomUUID().toString())); + eventListener.handleRequestEvent(REQUEST_UPDATE_EVENT_SAMPLE, buildKafkaHeaders(TENANT_ID_CONSORTIUM)); verify(ecsTlrRepository).findBySecondaryRequestId(any()); } } diff --git a/src/test/java/org/folio/service/UserEventHandlerTest.java b/src/test/java/org/folio/service/UserEventHandlerTest.java index 94569125..aef0b12a 100644 --- a/src/test/java/org/folio/service/UserEventHandlerTest.java +++ b/src/test/java/org/folio/service/UserEventHandlerTest.java @@ -23,6 +23,7 @@ void handleUserUpdatingEventShouldUpdateUserForAllDataTenants() { when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant()); when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection()); when(userService.update(any(User.class))).thenReturn(new User()); + when(userTenantsService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); doAnswer(invocation -> { ((Runnable) invocation.getArguments()[1]).run(); @@ -30,8 +31,7 @@ void handleUserUpdatingEventShouldUpdateUserForAllDataTenants() { }).when(systemUserScopedExecutionService).executeAsyncSystemUserScoped(anyString(), any(Runnable.class)); - eventListener.handleUserEvent(USER_UPDATING_EVENT_SAMPLE, - getMessageHeaders(TENANT, TENANT_ID)); + eventListener.handleUserEvent(USER_UPDATING_EVENT_SAMPLE, buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(systemUserScopedExecutionService, times(3)) .executeAsyncSystemUserScoped(anyString(), any(Runnable.class)); diff --git a/src/test/java/org/folio/service/UserGroupEventHandlerTest.java b/src/test/java/org/folio/service/UserGroupEventHandlerTest.java index 6b92c7f5..70c6cee3 100644 --- a/src/test/java/org/folio/service/UserGroupEventHandlerTest.java +++ b/src/test/java/org/folio/service/UserGroupEventHandlerTest.java @@ -30,6 +30,7 @@ void handleUserGroupCreatingEventShouldCreateUserGroupForAllDataTenants() { when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant()); when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection()); when(userGroupService.create(any(UserGroup.class))).thenReturn(new UserGroup()); + when(userTenantsService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); doAnswer(invocation -> { ((Runnable) invocation.getArguments()[1]).run(); @@ -38,7 +39,7 @@ void handleUserGroupCreatingEventShouldCreateUserGroupForAllDataTenants() { any(Runnable.class)); eventListener.handleUserGroupEvent(USER_GROUP_CREATING_EVENT_SAMPLE, - getMessageHeaders(TENANT, TENANT_ID)); + buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(systemUserScopedExecutionService, times(3)).executeAsyncSystemUserScoped(anyString(), any(Runnable.class)); @@ -50,6 +51,7 @@ void handleUserGroupUpdatingEventShouldUpdateUserGroupForAllDataTenants() { when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant()); when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection()); when(userGroupService.update(any(UserGroup.class))).thenReturn(new UserGroup()); + when(userTenantsService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); doAnswer(invocation -> { ((Runnable) invocation.getArguments()[1]).run(); @@ -58,7 +60,7 @@ void handleUserGroupUpdatingEventShouldUpdateUserGroupForAllDataTenants() { any(Runnable.class)); eventListener.handleUserGroupEvent(USER_GROUP_UPDATING_EVENT_SAMPLE, - getMessageHeaders(TENANT, TENANT_ID)); + buildKafkaHeaders(CENTRAL_TENANT_ID)); verify(systemUserScopedExecutionService, times(3)) .executeAsyncSystemUserScoped(anyString(), any(Runnable.class)); From f179623688de3a5035362a73edc636aa4b430d3a Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Thu, 26 Dec 2024 19:07:55 +0200 Subject: [PATCH 09/16] MODTLR-118 Get central tenant ID from consortia configuration --- .../feign/ConsortiaConfigurationClient.java | 13 ++++++++++++ .../listener/kafka/KafkaEventListener.java | 5 +++-- .../org/folio/service/ConsortiaService.java | 1 + .../service/impl/ConsortiaServiceImpl.java | 13 ++++++++++++ src/main/resources/swagger.api/ecs-tlr.yaml | 6 ++++-- .../consortia/consortiaConfiguration.yaml | 9 +++++++++ .../schemas/{ => consortia}/tenant.yaml | 0 .../listener/KafkaEventListenerTest.java | 3 ++- .../folio/service/UserEventHandlerTest.java | 2 +- .../service/UserGroupEventHandlerTest.java | 4 ++-- .../mappings/consortiaConfiguration.json | 20 +++++++++++++++++++ 11 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/folio/client/feign/ConsortiaConfigurationClient.java create mode 100644 src/main/resources/swagger.api/schemas/consortia/consortiaConfiguration.yaml rename src/main/resources/swagger.api/schemas/{ => consortia}/tenant.yaml (100%) create mode 100644 src/test/resources/mappings/consortiaConfiguration.json diff --git a/src/main/java/org/folio/client/feign/ConsortiaConfigurationClient.java b/src/main/java/org/folio/client/feign/ConsortiaConfigurationClient.java new file mode 100644 index 00000000..d9602ae5 --- /dev/null +++ b/src/main/java/org/folio/client/feign/ConsortiaConfigurationClient.java @@ -0,0 +1,13 @@ +package org.folio.client.feign; + +import org.folio.domain.dto.ConsortiaConfiguration; +import org.folio.spring.config.FeignClientConfiguration; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +@FeignClient(name = "consortia-configuration", url = "consortia-configuration", configuration = FeignClientConfiguration.class) +public interface ConsortiaConfigurationClient { + + @GetMapping + ConsortiaConfiguration getConfiguration(); +} diff --git a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java index 4d3bf60a..544e8197 100644 --- a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java +++ b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java @@ -10,6 +10,7 @@ import org.folio.domain.dto.User; import org.folio.domain.dto.UserGroup; import org.folio.exception.KafkaEventDeserializationException; +import org.folio.service.ConsortiaService; import org.folio.service.KafkaEventHandler; import org.folio.service.UserTenantsService; import org.folio.service.impl.LoanEventHandler; @@ -46,7 +47,7 @@ public class KafkaEventListener { private final UserEventHandler userEventHandler; private final SystemUserScopedExecutionService systemUserScopedExecutionService; private final RequestBatchUpdateEventHandler requestBatchEventHandler; - private final UserTenantsService userTenantsService; + private final ConsortiaService consortiaService; private final FolioModuleMetadata folioModuleMetadata; @KafkaListener( @@ -100,7 +101,7 @@ private void handleEvent(String eventString, KafkaEventHandler handler, folioModuleMetadata, messageHeaders); try (FolioExecutionContextSetter contextSetter = new FolioExecutionContextSetter(context)) { - String centralTenantId = userTenantsService.getCentralTenantId(); + String centralTenantId = consortiaService.getCentralTenantId(); systemUserScopedExecutionService.executeAsyncSystemUserScoped(centralTenantId, () -> handler.handle(event)); } catch (Exception e) { diff --git a/src/main/java/org/folio/service/ConsortiaService.java b/src/main/java/org/folio/service/ConsortiaService.java index 562d9749..f676f228 100644 --- a/src/main/java/org/folio/service/ConsortiaService.java +++ b/src/main/java/org/folio/service/ConsortiaService.java @@ -8,4 +8,5 @@ public interface ConsortiaService { TenantCollection getAllConsortiumTenants(String consortiumId); Collection getAllConsortiumTenants(); + String getCentralTenantId(); } diff --git a/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java b/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java index e328de0b..d26f6c9a 100644 --- a/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java +++ b/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java @@ -6,6 +6,8 @@ import java.util.Optional; import org.folio.client.feign.ConsortiaClient; +import org.folio.client.feign.ConsortiaConfigurationClient; +import org.folio.domain.dto.ConsortiaConfiguration; import org.folio.domain.dto.Tenant; import org.folio.domain.dto.TenantCollection; import org.folio.domain.dto.UserTenant; @@ -21,6 +23,7 @@ @RequiredArgsConstructor public class ConsortiaServiceImpl implements ConsortiaService { private final ConsortiaClient consortiaClient; + private final ConsortiaConfigurationClient consortiaConfigurationClient; private final UserTenantsService userTenantsService; @Override @@ -40,4 +43,14 @@ public Collection getAllConsortiumTenants() { log.info("getAllConsortiumTenants:: found {} consortium tenants", tenants::size); return tenants; } + + @Override + public String getCentralTenantId() { + log.info("getCentralTenantId:: resolving central tenant ID"); + String centralTenantId = Optional.ofNullable(consortiaConfigurationClient.getConfiguration()) + .map(ConsortiaConfiguration::getCentralTenantId) + .orElseThrow(); + log.info("getCentralTenantId: central tenant ID: {}", centralTenantId); + return centralTenantId; + } } diff --git a/src/main/resources/swagger.api/ecs-tlr.yaml b/src/main/resources/swagger.api/ecs-tlr.yaml index b1d4460c..52bc6adb 100644 --- a/src/main/resources/swagger.api/ecs-tlr.yaml +++ b/src/main/resources/swagger.api/ecs-tlr.yaml @@ -90,9 +90,11 @@ components: transactionStatusResponse: $ref: 'schemas/transactionStatusResponse.yaml#/TransactionStatusResponse' tenant: - $ref: 'schemas/tenant.yaml#/Tenant' + $ref: 'schemas/consortia/tenant.yaml#/Tenant' tenants: - $ref: 'schemas/tenant.yaml#/TenantCollection' + $ref: 'schemas/consortia/tenant.yaml#/TenantCollection' + consortiaConfiguration: + $ref: 'schemas/consortia/consortiaConfiguration.yaml#/ConsortiaConfiguration' publicationRequest: $ref: 'schemas/publication.yaml#/PublicationRequest' publicationResponse: diff --git a/src/main/resources/swagger.api/schemas/consortia/consortiaConfiguration.yaml b/src/main/resources/swagger.api/schemas/consortia/consortiaConfiguration.yaml new file mode 100644 index 00000000..10b01b6d --- /dev/null +++ b/src/main/resources/swagger.api/schemas/consortia/consortiaConfiguration.yaml @@ -0,0 +1,9 @@ +ConsortiaConfiguration: + type: "object" + description: "Consortia Configuration" + properties: + id: + type: "string" + format: "uuid" + centralTenantId: + type: "string" \ No newline at end of file diff --git a/src/main/resources/swagger.api/schemas/tenant.yaml b/src/main/resources/swagger.api/schemas/consortia/tenant.yaml similarity index 100% rename from src/main/resources/swagger.api/schemas/tenant.yaml rename to src/main/resources/swagger.api/schemas/consortia/tenant.yaml diff --git a/src/test/java/org/folio/listener/KafkaEventListenerTest.java b/src/test/java/org/folio/listener/KafkaEventListenerTest.java index f6783011..3ebae521 100644 --- a/src/test/java/org/folio/listener/KafkaEventListenerTest.java +++ b/src/test/java/org/folio/listener/KafkaEventListenerTest.java @@ -8,6 +8,7 @@ import java.util.Map; import org.folio.listener.kafka.KafkaEventListener; +import org.folio.service.ConsortiaService; import org.folio.service.UserTenantsService; import org.folio.service.impl.LoanEventHandler; import org.folio.service.impl.RequestBatchUpdateEventHandler; @@ -37,7 +38,7 @@ class KafkaEventListenerTest { @Mock UserEventHandler userEventHandler; @Mock - UserTenantsService userTenantsService; + ConsortiaService consortiaService; @InjectMocks KafkaEventListener kafkaEventListener; diff --git a/src/test/java/org/folio/service/UserEventHandlerTest.java b/src/test/java/org/folio/service/UserEventHandlerTest.java index aef0b12a..7e7f4035 100644 --- a/src/test/java/org/folio/service/UserEventHandlerTest.java +++ b/src/test/java/org/folio/service/UserEventHandlerTest.java @@ -23,7 +23,7 @@ void handleUserUpdatingEventShouldUpdateUserForAllDataTenants() { when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant()); when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection()); when(userService.update(any(User.class))).thenReturn(new User()); - when(userTenantsService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); + when(consortiaService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); doAnswer(invocation -> { ((Runnable) invocation.getArguments()[1]).run(); diff --git a/src/test/java/org/folio/service/UserGroupEventHandlerTest.java b/src/test/java/org/folio/service/UserGroupEventHandlerTest.java index 70c6cee3..5b6c227f 100644 --- a/src/test/java/org/folio/service/UserGroupEventHandlerTest.java +++ b/src/test/java/org/folio/service/UserGroupEventHandlerTest.java @@ -30,7 +30,7 @@ void handleUserGroupCreatingEventShouldCreateUserGroupForAllDataTenants() { when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant()); when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection()); when(userGroupService.create(any(UserGroup.class))).thenReturn(new UserGroup()); - when(userTenantsService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); + when(consortiaService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); doAnswer(invocation -> { ((Runnable) invocation.getArguments()[1]).run(); @@ -51,7 +51,7 @@ void handleUserGroupUpdatingEventShouldUpdateUserGroupForAllDataTenants() { when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant()); when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection()); when(userGroupService.update(any(UserGroup.class))).thenReturn(new UserGroup()); - when(userTenantsService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); + when(consortiaService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID); doAnswer(invocation -> { ((Runnable) invocation.getArguments()[1]).run(); diff --git a/src/test/resources/mappings/consortiaConfiguration.json b/src/test/resources/mappings/consortiaConfiguration.json new file mode 100644 index 00000000..b978e6df --- /dev/null +++ b/src/test/resources/mappings/consortiaConfiguration.json @@ -0,0 +1,20 @@ +{ + "mappings": [ + { + "request": { + "method": "GET", + "url": "/consortia-configuration" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "id": "0bc8835b-1233-48ba-bc75-979cb04dc06e", + "centralTenantId": "consortium" + } + } + } + ] +} From 9da38ade5c96d5064e7c1f36b670d6043ebd7396 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Thu, 26 Dec 2024 19:38:04 +0200 Subject: [PATCH 10/16] MODTLR-118 Add logging --- .../org/folio/service/impl/ConsortiaServiceImpl.java | 2 +- .../java/org/folio/service/impl/LoanEventHandler.java | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java b/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java index d26f6c9a..cbc145ef 100644 --- a/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java +++ b/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java @@ -50,7 +50,7 @@ public String getCentralTenantId() { String centralTenantId = Optional.ofNullable(consortiaConfigurationClient.getConfiguration()) .map(ConsortiaConfiguration::getCentralTenantId) .orElseThrow(); - log.info("getCentralTenantId: central tenant ID: {}", centralTenantId); + log.info("getCentralTenantId:: central tenant ID: {}", centralTenantId); return centralTenantId; } } diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java index 1e21c8ef..8aa967b0 100644 --- a/src/main/java/org/folio/service/impl/LoanEventHandler.java +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -10,6 +10,7 @@ import java.util.Collection; import java.util.EnumSet; +import java.util.List; import java.util.UUID; import org.folio.domain.dto.Loan; @@ -135,9 +136,13 @@ else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER } private Collection findEcsTlrs(Loan loan) { - log.info("findEcsTlr:: searching ECS TLRs for loan {}", loan::getId); - return ecsTlrRepository.findByItemIdAndRequesterId(UUID.fromString(loan.getItemId()), - UUID.fromString(loan.getUserId())); + log.info("findEcsTlrs:: searching ECS TLRs: loanId={}, itemId={}, userId={}", loan::getId, + loan::getItemId, loan::getId); + List ecsTlrs = ecsTlrRepository.findByItemIdAndRequesterId( + UUID.fromString(loan.getItemId()), UUID.fromString(loan.getUserId())); + log.info("findEcsTlrs:: found {} ECS TLRs", ecsTlrs::size); + + return ecsTlrs; } } From 15561ddf1e702447d92fa390f605dc1cca6dcd35 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Fri, 27 Dec 2024 13:43:30 +0200 Subject: [PATCH 11/16] MODTLR-118 Find ECS TLR by itemId only --- .../java/org/folio/listener/kafka/KafkaEventListener.java | 1 - src/main/java/org/folio/repository/EcsTlrRepository.java | 2 +- .../java/org/folio/service/impl/LoanEventHandler.java | 8 +++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java index 544e8197..bc87dfed 100644 --- a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java +++ b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java @@ -12,7 +12,6 @@ import org.folio.exception.KafkaEventDeserializationException; import org.folio.service.ConsortiaService; import org.folio.service.KafkaEventHandler; -import org.folio.service.UserTenantsService; import org.folio.service.impl.LoanEventHandler; import org.folio.service.impl.RequestBatchUpdateEventHandler; import org.folio.service.impl.RequestEventHandler; diff --git a/src/main/java/org/folio/repository/EcsTlrRepository.java b/src/main/java/org/folio/repository/EcsTlrRepository.java index b460aa52..4574ca90 100644 --- a/src/main/java/org/folio/repository/EcsTlrRepository.java +++ b/src/main/java/org/folio/repository/EcsTlrRepository.java @@ -14,5 +14,5 @@ public interface EcsTlrRepository extends JpaRepository { Optional findByPrimaryRequestId(UUID primaryRequestId); Optional findByInstanceId(UUID instanceId); List findByPrimaryRequestIdIn(List primaryRequestIds); - ListfindByItemIdAndRequesterId(UUID itemId, UUID requesterId); + ListfindByItemId(UUID itemId); } diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java index 8aa967b0..2f996126 100644 --- a/src/main/java/org/folio/service/impl/LoanEventHandler.java +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -130,16 +130,14 @@ else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER dcbService.updateTransactionStatuses(StatusEnum.CLOSED, ecsTlr); return; } - log.info("updateEcsTlr:: ECS TLR {} was not updated", ecsTlr::getId); + log.info("updateEcsTlr:: ECS TLR {} does not match loan update event, skipping", ecsTlr::getId); } log.info("updateEcsTlr:: suitable ECS TLR for loan {} in tenant {} was not found", loan.getId(), tenantId); } private Collection findEcsTlrs(Loan loan) { - log.info("findEcsTlrs:: searching ECS TLRs: loanId={}, itemId={}, userId={}", loan::getId, - loan::getItemId, loan::getId); - List ecsTlrs = ecsTlrRepository.findByItemIdAndRequesterId( - UUID.fromString(loan.getItemId()), UUID.fromString(loan.getUserId())); + log.info("findEcsTlrs:: searching ECS TLRs for item {}", loan::getItemId); + List ecsTlrs = ecsTlrRepository.findByItemId(UUID.fromString(loan.getItemId())); log.info("findEcsTlrs:: found {} ECS TLRs", ecsTlrs::size); return ecsTlrs; From 7b407a5905bb1b9d441e4e31d857a583b422a497 Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Fri, 27 Dec 2024 13:48:13 +0200 Subject: [PATCH 12/16] MODTLR-118 Fix compilation --- .../org/folio/service/LoanEventHandlerTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/folio/service/LoanEventHandlerTest.java b/src/test/java/org/folio/service/LoanEventHandlerTest.java index a9afe582..294e4adc 100644 --- a/src/test/java/org/folio/service/LoanEventHandlerTest.java +++ b/src/test/java/org/folio/service/LoanEventHandlerTest.java @@ -69,14 +69,14 @@ void checkInEventIsIgnoredWhenEcsTlrForUpdatedLoanIsNotFound() { .itemId(itemId.toString()) .userId(userId.toString()); - when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + when(ecsTlrRepository.findByItemId(itemId)) .thenReturn(emptyList()); KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED, 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant"); loanEventHandler.handle(event); - verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verify(ecsTlrRepository).findByItemId(itemId); verifyNoInteractions(dcbService); } @@ -90,14 +90,14 @@ void checkInEventIsIgnoredWhenEcsTlrDoesNotContainsNoTransactionIds() { .itemId(itemId.toString()) .userId(userId.toString()); - when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + when(ecsTlrRepository.findByItemId(itemId)) .thenReturn(List.of(new EcsTlrEntity())); KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED, 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant"); loanEventHandler.handle(event); - verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verify(ecsTlrRepository).findByItemId(itemId); verifyNoInteractions(dcbService); } @@ -117,14 +117,14 @@ void checkInEventIsIgnoredWhenEventTenantDoesNotMatchEcsRequestTransactionTenant ecsTlr.setPrimaryRequestDcbTransactionId(randomUUID()); ecsTlr.setSecondaryRequestDcbTransactionId(randomUUID()); - when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + when(ecsTlrRepository.findByItemId(itemId)) .thenReturn(List.of(ecsTlr)); KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED, 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant"); loanEventHandler.handle(event); - verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verify(ecsTlrRepository).findByItemId(itemId); verifyNoInteractions(dcbService); } @@ -162,7 +162,7 @@ void checkInEventIsHandled(String primaryTransactionRole, String primaryTransact mockEcsTlr.setPrimaryRequestDcbTransactionId(primaryTransactionId); mockEcsTlr.setSecondaryRequestDcbTransactionId(secondaryTransactionId); - when(ecsTlrRepository.findByItemIdAndRequesterId(itemId, userId)) + when(ecsTlrRepository.findByItemId(itemId)) .thenReturn(List.of(mockEcsTlr)); TransactionStatusResponse mockPrimaryTransactionResponse = buildTransactionStatusResponse( @@ -185,7 +185,7 @@ void checkInEventIsHandled(String primaryTransactionRole, String primaryTransact loanEventHandler.handle(event); - verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); + verify(ecsTlrRepository).findByItemId(itemId); verify(dcbService).getTransactionStatus(primaryTransactionId, primaryRequestTenant); verify(dcbService).getTransactionStatus(secondaryTransactionId, secondaryRequestTenant); verify(dcbService).updateTransactionStatuses(expectedNewStatus, mockEcsTlr); From 74deb248783409b3c006fcd86cc729a085226f8e Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Mon, 30 Dec 2024 13:12:33 +0200 Subject: [PATCH 13/16] MODTLR-118 Remove unused import --- src/test/java/org/folio/listener/KafkaEventListenerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/folio/listener/KafkaEventListenerTest.java b/src/test/java/org/folio/listener/KafkaEventListenerTest.java index 3ebae521..f1e698e5 100644 --- a/src/test/java/org/folio/listener/KafkaEventListenerTest.java +++ b/src/test/java/org/folio/listener/KafkaEventListenerTest.java @@ -9,7 +9,6 @@ import org.folio.listener.kafka.KafkaEventListener; import org.folio.service.ConsortiaService; -import org.folio.service.UserTenantsService; import org.folio.service.impl.LoanEventHandler; import org.folio.service.impl.RequestBatchUpdateEventHandler; import org.folio.service.impl.RequestEventHandler; From d361e85d4a680665f15237555c5d46a59a2b95ea Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Mon, 30 Dec 2024 13:15:05 +0200 Subject: [PATCH 14/16] MODTLR-118 Fix code smells --- src/main/java/org/folio/repository/EcsTlrRepository.java | 2 +- src/test/java/org/folio/service/LoanEventHandlerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/folio/repository/EcsTlrRepository.java b/src/main/java/org/folio/repository/EcsTlrRepository.java index b460aa52..c80cce38 100644 --- a/src/main/java/org/folio/repository/EcsTlrRepository.java +++ b/src/main/java/org/folio/repository/EcsTlrRepository.java @@ -14,5 +14,5 @@ public interface EcsTlrRepository extends JpaRepository { Optional findByPrimaryRequestId(UUID primaryRequestId); Optional findByInstanceId(UUID instanceId); List findByPrimaryRequestIdIn(List primaryRequestIds); - ListfindByItemIdAndRequesterId(UUID itemId, UUID requesterId); + List findByItemIdAndRequesterId(UUID itemId, UUID requesterId); } diff --git a/src/test/java/org/folio/service/LoanEventHandlerTest.java b/src/test/java/org/folio/service/LoanEventHandlerTest.java index 284ecdbd..a9afe582 100644 --- a/src/test/java/org/folio/service/LoanEventHandlerTest.java +++ b/src/test/java/org/folio/service/LoanEventHandlerTest.java @@ -30,7 +30,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class LoanEventHandlerTest { +class LoanEventHandlerTest { private static final EnumSet SUPPORTED_EVENT_TYPES = EnumSet.of(UPDATED); From 33aa03a3f5655bb324f76492f6d53d2d745e1abd Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Mon, 30 Dec 2024 14:13:58 +0200 Subject: [PATCH 15/16] MODTLR-118 Fix incorrect method declaration --- src/main/java/org/folio/repository/EcsTlrRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/folio/repository/EcsTlrRepository.java b/src/main/java/org/folio/repository/EcsTlrRepository.java index c80cce38..4c45fde6 100644 --- a/src/main/java/org/folio/repository/EcsTlrRepository.java +++ b/src/main/java/org/folio/repository/EcsTlrRepository.java @@ -14,5 +14,5 @@ public interface EcsTlrRepository extends JpaRepository { Optional findByPrimaryRequestId(UUID primaryRequestId); Optional findByInstanceId(UUID instanceId); List findByPrimaryRequestIdIn(List primaryRequestIds); - List findByItemIdAndRequesterId(UUID itemId, UUID requesterId); + List findByItemId(UUID itemId); } From 95f6211371c27fe509aa3aa222b534e81110889b Mon Sep 17 00:00:00 2001 From: Oleksandr Vidinieiev Date: Mon, 30 Dec 2024 15:19:28 +0200 Subject: [PATCH 16/16] MODTLR-118 Post-merge fixes --- src/main/java/org/folio/service/impl/LoanEventHandler.java | 5 +++++ src/test/java/org/folio/service/LoanEventHandlerTest.java | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java index 617a5eba..2f996126 100644 --- a/src/main/java/org/folio/service/impl/LoanEventHandler.java +++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java @@ -136,6 +136,11 @@ else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER } private Collection findEcsTlrs(Loan loan) { + log.info("findEcsTlrs:: searching ECS TLRs for item {}", loan::getItemId); + List ecsTlrs = ecsTlrRepository.findByItemId(UUID.fromString(loan.getItemId())); + log.info("findEcsTlrs:: found {} ECS TLRs", ecsTlrs::size); + + return ecsTlrs; } } diff --git a/src/test/java/org/folio/service/LoanEventHandlerTest.java b/src/test/java/org/folio/service/LoanEventHandlerTest.java index 11abc850..294e4adc 100644 --- a/src/test/java/org/folio/service/LoanEventHandlerTest.java +++ b/src/test/java/org/folio/service/LoanEventHandlerTest.java @@ -77,7 +77,6 @@ void checkInEventIsIgnoredWhenEcsTlrForUpdatedLoanIsNotFound() { loanEventHandler.handle(event); verify(ecsTlrRepository).findByItemId(itemId); - verify(ecsTlrRepository).findByItemIdAndRequesterId(itemId, userId); verifyNoInteractions(dcbService); }