diff --git a/NEWS.md b/NEWS.md
index 18d5c1f0..a4ffae8b 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,10 @@
+## v1.2.3 2024-12-02
+* MODDCB-90: Accept existing circulation request ID
+* MODDCB-105: Accept existing circulation request ID (borrowing transaction)
+* MODDCB-111: Allow manual transaction status change from CREATED to OPEN
+* MODDCB-117: Add ecsRequestPhase to the circulation request schema
+* MODDCB-124: Merge esc-tlr feature branch into master
+
## v1.2.2 2024-11-20
* MODDCB-145: Issue with spaces in service point name
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index a103410c..1b52f2b1 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -144,6 +144,33 @@
}
]
},
+ {
+ "id": "ecs-request-transactions",
+ "version": "1.0",
+ "handlers": [
+ {
+ "methods": [
+ "POST"
+ ],
+ "pathPattern": "/ecs-request-transactions/{ecsRequestTransactionId}",
+ "permissionsRequired": [
+ "dcb.ecs-request.transactions.post"
+ ],
+ "modulePermissions": [
+ "circulation-storage.requests.item.get",
+ "circulation-storage.requests.collection.get",
+ "circulation.requests.item.put",
+ "inventory-storage.items.item.get",
+ "inventory-storage.items.collection.get",
+ "circulation-item.item.post",
+ "circulation-item.collection.get",
+ "circulation-item.item.get",
+ "inventory-storage.material-types.collection.get",
+ "inventory-storage.loan-types.collection.get"
+ ]
+ }
+ ]
+ },
{
"id": "_tenant",
"version": "2.0",
@@ -204,7 +231,8 @@
"dcb.transactions.post",
"dcb.transactions.put",
"dcb.transactions.get",
- "dcb.transactions.collection.get"
+ "dcb.transactions.collection.get",
+ "dcb.ecs-request.transactions.post"
]
},
{
@@ -226,6 +254,11 @@
"permissionName": "dcb.transactions.collection.get",
"displayName": "get updated transaction detail list",
"description": "get list of transaction updated between a given query range"
+ },
+ {
+ "permissionName": "dcb.ecs-request.transactions.post",
+ "displayName": "creates new ECS request transaction",
+ "description": "creates new ECS request transaction"
}
],
"metadata": {
diff --git a/pom.xml b/pom.xml
index 54fe3d4b..8649c093 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
org.folio
mod-dcb
mod-dcb
- 1.2.3-SNAPSHOT
+ 1.2.4-SNAPSHOT
Manage DCB related transactions in folio
jar
diff --git a/src/main/java/org/folio/dcb/client/feign/CirculationClient.java b/src/main/java/org/folio/dcb/client/feign/CirculationClient.java
index 40340069..8b21366f 100644
--- a/src/main/java/org/folio/dcb/client/feign/CirculationClient.java
+++ b/src/main/java/org/folio/dcb/client/feign/CirculationClient.java
@@ -22,5 +22,6 @@ public interface CirculationClient {
void checkOutByBarcode(@RequestBody CheckOutRequest checkOutRequest);
@PutMapping("/requests/{requestId}")
- CirculationRequest cancelRequest(@PathVariable("requestId") String requestId, @RequestBody CirculationRequest circulationRequest);
+ CirculationRequest updateRequest(@PathVariable("requestId") String requestId,
+ @RequestBody CirculationRequest circulationRequest);
}
diff --git a/src/main/java/org/folio/dcb/controller/EcsRequestTransactionsApiController.java b/src/main/java/org/folio/dcb/controller/EcsRequestTransactionsApiController.java
new file mode 100644
index 00000000..e85b53b7
--- /dev/null
+++ b/src/main/java/org/folio/dcb/controller/EcsRequestTransactionsApiController.java
@@ -0,0 +1,39 @@
+package org.folio.dcb.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+import org.folio.dcb.domain.dto.DcbTransaction;
+import org.folio.dcb.domain.dto.TransactionStatusResponse;
+import org.folio.dcb.rest.resource.EcsRequestTransactionsApi;
+import org.folio.dcb.service.EcsRequestTransactionsService;
+import org.folio.dcb.service.TransactionAuditService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@Log4j2
+@RequiredArgsConstructor
+public class EcsRequestTransactionsApiController implements EcsRequestTransactionsApi {
+
+ private final EcsRequestTransactionsService ecsRequestTransactionsService;
+ private final TransactionAuditService transactionAuditService;
+
+ @Override
+ public ResponseEntity createEcsRequestTransactions(
+ String ecsRequestTransactionId, DcbTransaction dcbTransaction) {
+
+ log.info("createEcsRequestTransactions:: creating ECS Request Transaction {} with ID {}",
+ dcbTransaction, ecsRequestTransactionId);
+ TransactionStatusResponse transactionStatusResponse;
+ try {
+ transactionStatusResponse = ecsRequestTransactionsService.createEcsRequestTransactions(
+ ecsRequestTransactionId, dcbTransaction);
+ } catch (Exception ex) {
+ transactionAuditService.logErrorIfTransactionAuditNotExists(ecsRequestTransactionId,
+ dcbTransaction, ex.getMessage());
+ throw ex;
+ }
+ return ResponseEntity.status(HttpStatus.CREATED).body(transactionStatusResponse);
+ }
+}
diff --git a/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java b/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java
index 0011f62d..49069a2a 100644
--- a/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java
+++ b/src/main/java/org/folio/dcb/listener/kafka/CirculationEventListener.java
@@ -71,7 +71,7 @@ public void handleLoanEvent(String data, MessageHeaders messageHeaders) {
public void handleRequestEvent(String data, MessageHeaders messageHeaders) {
String tenantId = getHeaderValue(messageHeaders, XOkapiHeaders.TENANT, null).get(0);
var eventData = parseRequestEvent(data);
- if (Objects.nonNull(eventData) && eventData.isDcb() ) {
+ if (Objects.nonNull(eventData)) {
log.debug("dcb flow for a request event");
String requestId = eventData.getRequestId();
if (Objects.nonNull(requestId)) {
diff --git a/src/main/java/org/folio/dcb/service/CirculationRequestService.java b/src/main/java/org/folio/dcb/service/CirculationRequestService.java
index d4624e78..6cfde41f 100644
--- a/src/main/java/org/folio/dcb/service/CirculationRequestService.java
+++ b/src/main/java/org/folio/dcb/service/CirculationRequestService.java
@@ -4,4 +4,5 @@
public interface CirculationRequestService {
CirculationRequest getCancellationRequestIfOpenOrNull(String requestId);
+ CirculationRequest fetchRequestById(String requestId);
}
diff --git a/src/main/java/org/folio/dcb/service/EcsRequestTransactionsService.java b/src/main/java/org/folio/dcb/service/EcsRequestTransactionsService.java
new file mode 100644
index 00000000..dcb44c41
--- /dev/null
+++ b/src/main/java/org/folio/dcb/service/EcsRequestTransactionsService.java
@@ -0,0 +1,9 @@
+package org.folio.dcb.service;
+
+import org.folio.dcb.domain.dto.DcbTransaction;
+import org.folio.dcb.domain.dto.TransactionStatusResponse;
+
+public interface EcsRequestTransactionsService {
+ TransactionStatusResponse createEcsRequestTransactions(String ecsRequestTransactionsId,
+ DcbTransaction dcbTransaction);
+}
diff --git a/src/main/java/org/folio/dcb/service/RequestService.java b/src/main/java/org/folio/dcb/service/RequestService.java
index 0ac6842f..49358cb8 100644
--- a/src/main/java/org/folio/dcb/service/RequestService.java
+++ b/src/main/java/org/folio/dcb/service/RequestService.java
@@ -12,4 +12,5 @@ public interface RequestService {
*/
CirculationRequest createPageItemRequest(User user, DcbItem dcbItem, String pickupServicePointId);
CirculationRequest createHoldItemRequest(User user, DcbItem dcbItem, String pickupServicePointId);
+ void updateCirculationRequest(CirculationRequest circulationRequest);
}
diff --git a/src/main/java/org/folio/dcb/service/StatusProcessorService.java b/src/main/java/org/folio/dcb/service/StatusProcessorService.java
index f76b6f4c..583a1b6a 100644
--- a/src/main/java/org/folio/dcb/service/StatusProcessorService.java
+++ b/src/main/java/org/folio/dcb/service/StatusProcessorService.java
@@ -28,7 +28,7 @@ public List lendingChainProcessor(TransactionStatu
StatusProcessor checkInProcessor = new StatusProcessor(ITEM_CHECKED_OUT, ITEM_CHECKED_IN, false, closeProcessor);
StatusProcessor checkoutProcessor = new StatusProcessor(AWAITING_PICKUP, ITEM_CHECKED_OUT, false, checkInProcessor);
StatusProcessor awaitingPickupProcessor = new StatusProcessor(OPEN, AWAITING_PICKUP, false, checkoutProcessor);
- StatusProcessor openProcessor = new StatusProcessor(CREATED, OPEN, true, awaitingPickupProcessor);
+ StatusProcessor openProcessor = new StatusProcessor(CREATED, OPEN, false, awaitingPickupProcessor);
startChain.setChain(openProcessor);
var statuses = process(startChain, fromStatus, toStatus);
log.info("lendingChainProcessor:: Following statuses needs to be transitioned {} ", statuses);
diff --git a/src/main/java/org/folio/dcb/service/impl/CirculationRequestServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/CirculationRequestServiceImpl.java
index 64700355..51410422 100644
--- a/src/main/java/org/folio/dcb/service/impl/CirculationRequestServiceImpl.java
+++ b/src/main/java/org/folio/dcb/service/impl/CirculationRequestServiceImpl.java
@@ -22,7 +22,7 @@ public class CirculationRequestServiceImpl implements CirculationRequestService
private final CirculationRequestClient circulationRequestClient;
private final FolioExecutionContext folioExecutionContext;
- private CirculationRequest fetchRequestById(String requestId) {
+ public CirculationRequest fetchRequestById(String requestId) {
log.info("fetchRequestById:: fetching request for id {} ", requestId);
try {
return circulationRequestClient.fetchRequestById(requestId);
diff --git a/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java
index 9c88acd2..9934385f 100644
--- a/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java
+++ b/src/main/java/org/folio/dcb/service/impl/CirculationServiceImpl.java
@@ -46,7 +46,7 @@ public void cancelRequest(TransactionEntity dcbTransaction) {
CirculationRequest request = circulationStorageService.getCancellationRequestIfOpenOrNull(dcbTransaction.getRequestId().toString());
if (request != null){
try {
- circulationClient.cancelRequest(request.getId(), request);
+ circulationClient.updateRequest(request.getId(), request);
} catch (FeignException e) {
log.warn("cancelRequest:: error cancelling request using request id {} ", dcbTransaction.getRequestId(), e);
throw new CirculationRequestException(String.format("Error cancelling request using request id %s", dcbTransaction.getRequestId()));
diff --git a/src/main/java/org/folio/dcb/service/impl/EcsRequestTransactionsServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/EcsRequestTransactionsServiceImpl.java
new file mode 100644
index 00000000..722660b2
--- /dev/null
+++ b/src/main/java/org/folio/dcb/service/impl/EcsRequestTransactionsServiceImpl.java
@@ -0,0 +1,119 @@
+package org.folio.dcb.service.impl;
+
+import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.BORROWER;
+import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.LENDER;
+
+import java.util.UUID;
+
+import org.folio.dcb.domain.dto.CirculationItem;
+import org.folio.dcb.domain.dto.CirculationRequest;
+import org.folio.dcb.domain.dto.DcbItem;
+import org.folio.dcb.domain.dto.DcbPatron;
+import org.folio.dcb.domain.dto.DcbPickup;
+import org.folio.dcb.domain.dto.DcbTransaction;
+import org.folio.dcb.domain.dto.Item;
+import org.folio.dcb.domain.dto.TransactionStatusResponse;
+import org.folio.dcb.exception.ResourceAlreadyExistException;
+import org.folio.dcb.repository.TransactionRepository;
+import org.folio.dcb.service.CirculationItemService;
+import org.folio.dcb.service.CirculationRequestService;
+import org.folio.dcb.service.EcsRequestTransactionsService;
+import org.folio.dcb.service.RequestService;
+import org.folio.dcb.utils.RequestStatus;
+import org.springframework.stereotype.Service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Service
+@RequiredArgsConstructor
+@Log4j2
+public class EcsRequestTransactionsServiceImpl implements EcsRequestTransactionsService {
+
+ private final BaseLibraryService baseLibraryService;
+ private final TransactionRepository transactionRepository;
+ private final RequestService requestService;
+ private final CirculationRequestService circulationRequestService;
+ private final CirculationItemService circulationItemService;
+
+ @Override
+ public TransactionStatusResponse createEcsRequestTransactions(String ecsRequestTransactionsId,
+ DcbTransaction dcbTransaction) {
+
+ log.info("createEcsRequestTransactions:: creating new transaction request for role {} ",
+ dcbTransaction.getRole());
+ checkEcsRequestTransactionExistsAndThrow(ecsRequestTransactionsId);
+ CirculationRequest circulationRequest = circulationRequestService.fetchRequestById(
+ dcbTransaction.getRequestId());
+ if (circulationRequest != null && RequestStatus.isRequestOpen(
+ RequestStatus.from(circulationRequest.getStatus()))) {
+ if (dcbTransaction.getRole() == LENDER) {
+ createLenderEcsRequestTransactions(ecsRequestTransactionsId, dcbTransaction, circulationRequest);
+ } else if(dcbTransaction.getRole() == BORROWER) {
+ createBorrowerEcsRequestTransactions(ecsRequestTransactionsId, dcbTransaction,
+ circulationRequest);
+ } else {
+ throw new IllegalArgumentException("Unimplemented role: " + dcbTransaction.getRole());
+ }
+ return TransactionStatusResponse.builder()
+ .status(TransactionStatusResponse.StatusEnum.CREATED)
+ .item(dcbTransaction.getItem())
+ .patron(dcbTransaction.getPatron())
+ .build();
+ } else {
+ throw new IllegalArgumentException("Unable to create ECS transaction as could not find open request");
+ }
+ }
+
+ private void checkEcsRequestTransactionExistsAndThrow(String dcbTransactionId) {
+ if (transactionRepository.existsById(dcbTransactionId)) {
+ throw new ResourceAlreadyExistException(
+ String.format("unable to create ECS transaction with ID %s as it already exists",
+ dcbTransactionId));
+ }
+ }
+
+ private void createLenderEcsRequestTransactions(String ecsRequestTransactionsId,
+ DcbTransaction dcbTransaction, CirculationRequest circulationRequest) {
+
+ dcbTransaction.setItem(DcbItem.builder()
+ .id(String.valueOf(circulationRequest.getItemId()))
+ .barcode(circulationRequest.getItem().getBarcode())
+ .build());
+ dcbTransaction.setPatron(DcbPatron.builder()
+ .id(String.valueOf(circulationRequest.getRequesterId()))
+ .barcode(circulationRequest.getRequester().getBarcode())
+ .build());
+ dcbTransaction.setPickup(DcbPickup.builder()
+ .servicePointId(String.valueOf(circulationRequest.getPickupServicePointId()))
+ .build());
+ baseLibraryService.saveDcbTransaction(ecsRequestTransactionsId, dcbTransaction,
+ dcbTransaction.getRequestId());
+ }
+
+ private void createBorrowerEcsRequestTransactions(String ecsRequestTransactionsId,
+ DcbTransaction dcbTransaction, CirculationRequest circulationRequest) {
+
+ var itemVirtual = dcbTransaction.getItem();
+ if (itemVirtual == null) {
+ throw new IllegalArgumentException("Item is required for borrower transaction");
+ }
+ baseLibraryService.checkItemExistsInInventoryAndThrow(itemVirtual.getBarcode());
+ CirculationItem item = circulationItemService.checkIfItemExistsAndCreate(itemVirtual, circulationRequest.getPickupServicePointId());
+ circulationRequest.setItemId(UUID.fromString(item.getId()));
+ circulationRequest.setItem(Item.builder()
+ .barcode(item.getBarcode())
+ .build());
+ circulationRequest.setHoldingsRecordId(UUID.fromString(item.getHoldingsRecordId()));
+ requestService.updateCirculationRequest(circulationRequest);
+ dcbTransaction.setPatron(DcbPatron.builder()
+ .id(String.valueOf(circulationRequest.getRequesterId()))
+ .barcode(circulationRequest.getRequester().getBarcode())
+ .build());
+ dcbTransaction.setPickup(DcbPickup.builder()
+ .servicePointId(String.valueOf(circulationRequest.getPickupServicePointId()))
+ .build());
+ baseLibraryService.saveDcbTransaction(ecsRequestTransactionsId, dcbTransaction,
+ dcbTransaction.getRequestId());
+ }
+}
diff --git a/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java
index 56d38bff..de65af3e 100644
--- a/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java
+++ b/src/main/java/org/folio/dcb/service/impl/LendingLibraryServiceImpl.java
@@ -17,6 +17,7 @@
import org.springframework.stereotype.Service;
import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.AWAITING_PICKUP;
+import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.CREATED;
import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_IN;
import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_OUT;
import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.OPEN;
@@ -61,7 +62,9 @@ public void updateTransactionStatus(TransactionEntity dcbTransaction, Transactio
log.debug("updateTransactionStatus:: Updating dcbTransaction {} to status {} ", dcbTransaction, transactionStatus);
var currentStatus = dcbTransaction.getStatus();
var requestedStatus = transactionStatus.getStatus();
- if (OPEN == currentStatus && AWAITING_PICKUP == requestedStatus) {
+ if (CREATED == currentStatus && OPEN == requestedStatus) {
+ updateTransactionEntity(dcbTransaction, requestedStatus);
+ } else if (OPEN == currentStatus && AWAITING_PICKUP == requestedStatus) {
log.info("updateTransactionStatus:: Checking in item by barcode: {} ", dcbTransaction.getItemBarcode());
circulationService.checkInByBarcode(dcbTransaction);
updateTransactionEntity(dcbTransaction, requestedStatus);
diff --git a/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java
index 65c1f8a3..f8fe764f 100644
--- a/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java
+++ b/src/main/java/org/folio/dcb/service/impl/RequestServiceImpl.java
@@ -48,6 +48,13 @@ public CirculationRequest createHoldItemRequest(User user, DcbItem item, String
return circulationClient.createRequest(circulationRequest);
}
+ @Override
+ public void updateCirculationRequest(CirculationRequest circulationRequest) {
+ log.debug("updateCirculationRequest:: updating circulation request with id {}",
+ circulationRequest.getId());
+ circulationClient.updateRequest(circulationRequest.getId(), circulationRequest);
+ }
+
private CirculationRequest createCirculationRequest(CirculationRequest.RequestTypeEnum type, User user, DcbItem item, String holdingsId, String instanceId, String pickupServicePointId) {
return CirculationRequest.builder()
.id(UUID.randomUUID().toString())
diff --git a/src/main/java/org/folio/dcb/service/impl/TransactionAuditServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/TransactionAuditServiceImpl.java
index 5b6a64b1..ba2a9888 100644
--- a/src/main/java/org/folio/dcb/service/impl/TransactionAuditServiceImpl.java
+++ b/src/main/java/org/folio/dcb/service/impl/TransactionAuditServiceImpl.java
@@ -4,8 +4,6 @@
import lombok.extern.log4j.Log4j2;
import org.folio.dcb.domain.dto.DcbTransaction;
import org.folio.dcb.domain.entity.TransactionAuditEntity;
-import org.folio.dcb.domain.entity.TransactionEntity;
-import org.folio.dcb.domain.mapper.TransactionMapper;
import org.folio.dcb.repository.TransactionAuditRepository;
import org.folio.dcb.service.TransactionAuditService;
import org.springframework.stereotype.Service;
@@ -20,7 +18,6 @@ public class TransactionAuditServiceImpl implements TransactionAuditService {
private static final String DUPLICATE_ERROR_ACTION = "DUPLICATE_ERROR";
private static final String DUPLICATE_ERROR_TRANSACTION_ID = "-1";
- private final TransactionMapper transactionMapper;
private final TransactionAuditRepository transactionAuditRepository;
@Override
public void logErrorIfTransactionAuditExists(String dcbTransactionId, String errorMsg) {
@@ -44,8 +41,7 @@ public void logErrorIfTransactionAuditExists(String dcbTransactionId, String err
@Override
public void logErrorIfTransactionAuditNotExists(String dcbTransactionId, DcbTransaction dcbTransaction, String errorMsg) {
TransactionAuditEntity auditExisting = transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(dcbTransactionId).orElse(null);
- TransactionEntity transactionMapped = transactionMapper.mapToEntity(dcbTransactionId, dcbTransaction);
- TransactionAuditEntity auditError = generateTrnAuditEntityByTrnEntityWithError(dcbTransactionId, transactionMapped, errorMsg);
+ TransactionAuditEntity auditError = generateTrnAuditEntityByTrnEntityWithError(dcbTransactionId, dcbTransaction, errorMsg);
if (auditExisting != null) {
log.debug("logTheErrorForNotExistedTransactionAudit:: dcbTransactionId = {}, dcbTransaction = {}, err = {}", dcbTransactionId, dcbTransaction, errorMsg);
@@ -68,7 +64,7 @@ private TransactionAuditEntity generateTrnAuditEntityFromTheFoundOneWithError(Tr
return auditError;
}
- private TransactionAuditEntity generateTrnAuditEntityByTrnEntityWithError(String dcbTransactionId, TransactionEntity trnE, String errorMsg) {
+ private TransactionAuditEntity generateTrnAuditEntityByTrnEntityWithError(String dcbTransactionId, DcbTransaction trnE, String errorMsg) {
String errorMessage = String.format("dcbTransactionId = %s; dcb transaction content = %s; error message = %s.", dcbTransactionId, trnE.toString(), errorMsg);
TransactionAuditEntity auditError = new TransactionAuditEntity();
diff --git a/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java b/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java
index d697931a..64b41d20 100644
--- a/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java
+++ b/src/main/java/org/folio/dcb/service/impl/TransactionsServiceImpl.java
@@ -126,9 +126,10 @@ public TransactionEntity getTransactionEntityOrThrow(String dcbTransactionId) {
}
private void checkTransactionExistsAndThrow(String dcbTransactionId) {
- if(transactionRepository.existsById(dcbTransactionId)) {
+ if (transactionRepository.existsById(dcbTransactionId)) {
throw new ResourceAlreadyExistException(
String.format("unable to create transaction with id %s as it already exists", dcbTransactionId));
}
}
+
}
diff --git a/src/main/resources/swagger.api/dcb_transaction.yaml b/src/main/resources/swagger.api/dcb_transaction.yaml
index 21c3d176..5871cfa8 100644
--- a/src/main/resources/swagger.api/dcb_transaction.yaml
+++ b/src/main/resources/swagger.api/dcb_transaction.yaml
@@ -57,6 +57,26 @@ paths:
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalServerError'
+ /ecs-request-transactions/{ecsRequestTransactionId}:
+ description: ECS TLR Transaction endpoint
+ post:
+ description: Create transaction for existing circulation TLR
+ operationId: createEcsRequestTransactions
+ tags:
+ - ecs-tlr-transaction
+ parameters:
+ - $ref: '#/components/parameters/ecsRequestTransactionId'
+ requestBody:
+ $ref: "#/components/requestBodies/DCBTransaction"
+ responses:
+ '201':
+ $ref: '#/components/responses/TransactionStatusResponse'
+ '400':
+ $ref: '#/components/responses/BadRequest'
+ '409':
+ $ref: '#/components/responses/Conflict'
+ '500':
+ $ref: '#/components/responses/InternalServerError'
/transactions/status:
parameters:
- $ref: '#/components/parameters/fromDate'
@@ -151,6 +171,12 @@ components:
schema:
type: string
required: true
+ ecsRequestTransactionId:
+ in: path
+ name: ecsRequestTransactionId
+ schema:
+ type: string
+ required: true
fromDate:
in: query
name: fromDate
diff --git a/src/main/resources/swagger.api/schemas/CirculationRequest.yaml b/src/main/resources/swagger.api/schemas/CirculationRequest.yaml
index 8634f6b0..a8fb0c56 100644
--- a/src/main/resources/swagger.api/schemas/CirculationRequest.yaml
+++ b/src/main/resources/swagger.api/schemas/CirculationRequest.yaml
@@ -12,11 +12,18 @@ CirculationRequest:
- Hold
- Recall
- Page
+ ecsRequestPhase:
+ description: Stage in ECS request process, absence of this field means this is a single-tenant request
+ type: string
+ enum:
+ - Primary
+ - Secondary
requestLevel:
description: Level of the request - Item or Title
type: string
enum:
- Item
+ - Title
requestDate:
description: Date the request was made
type: string
@@ -43,6 +50,9 @@ CirculationRequest:
status:
description: Status of the request
type: string
+ position:
+ description: Position of the request in the queue
+ type: integer
cancellationReasonId:
description: The id of the request reason
type: string
diff --git a/src/main/resources/swagger.api/schemas/dcbTransaction.yaml b/src/main/resources/swagger.api/schemas/dcbTransaction.yaml
index 02d16935..8c6be9c2 100644
--- a/src/main/resources/swagger.api/schemas/dcbTransaction.yaml
+++ b/src/main/resources/swagger.api/schemas/dcbTransaction.yaml
@@ -7,6 +7,9 @@ DcbTransaction:
$ref: 'dcbPatron.yaml#/DcbPatron'
pickup:
$ref: 'dcbPickup.yaml#/DcbPickup'
+ requestId:
+ description: ID of the existing circulation TLR
+ type: string
role:
type: string
enum:
diff --git a/src/test/java/org/folio/dcb/controller/EcsRequestTransactionsApiControllerTest.java b/src/test/java/org/folio/dcb/controller/EcsRequestTransactionsApiControllerTest.java
new file mode 100644
index 00000000..3b30281f
--- /dev/null
+++ b/src/test/java/org/folio/dcb/controller/EcsRequestTransactionsApiControllerTest.java
@@ -0,0 +1,106 @@
+package org.folio.dcb.controller;
+
+import static org.folio.dcb.utils.EntityUtils.CIRCULATION_REQUEST_ID;
+import static org.folio.dcb.utils.EntityUtils.createBorrowingEcsRequestTransactionByRole;
+import static org.folio.dcb.utils.EntityUtils.createLendingEcsRequestTransactionByRole;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.UUID;
+
+import org.folio.dcb.domain.dto.DcbTransaction;
+import org.folio.dcb.domain.entity.TransactionAuditEntity;
+import org.folio.dcb.repository.TransactionAuditRepository;
+import org.folio.dcb.repository.TransactionRepository;
+import org.folio.spring.service.SystemUserScopedExecutionService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+
+class EcsRequestTransactionsApiControllerTest extends BaseIT {
+
+ private static final String TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION = "DUPLICATE_ERROR";
+ private static final String DUPLICATE_ERROR_TRANSACTION_ID = "-1";
+
+ @Autowired
+ private TransactionRepository transactionRepository;
+ @Autowired
+ private TransactionAuditRepository transactionAuditRepository;
+ @Autowired
+ private SystemUserScopedExecutionService systemUserScopedExecutionService;
+
+ @Test
+ void createLendingEcsRequestTest() throws Exception {
+ removeExistedTransactionFromDbIfSoExists();
+
+ this.mockMvc.perform(
+ post("/ecs-request-transactions/" + CIRCULATION_REQUEST_ID)
+ .content(asJsonString(createLendingEcsRequestTransactionByRole()))
+ .headers(defaultHeaders())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isCreated());
+
+ //Trying to create another transaction with same transaction id
+ this.mockMvc.perform(
+ post("/ecs-request-transactions/" + CIRCULATION_REQUEST_ID)
+ .content(asJsonString(createLendingEcsRequestTransactionByRole()))
+ .headers(defaultHeaders())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpectAll(status().is4xxClientError(),
+ jsonPath("$.errors[0].code", is("DUPLICATE_ERROR")));
+
+ // check for DUPLICATE_ERROR propagated into transactions_audit.
+ systemUserScopedExecutionService.executeAsyncSystemUserScoped(
+ TENANT,
+ () -> {
+ TransactionAuditEntity auditExisting = transactionAuditRepository
+ .findLatestTransactionAuditEntityByDcbTransactionId(CIRCULATION_REQUEST_ID)
+ .orElse(null);
+ Assertions.assertNotNull(auditExisting);
+ Assertions.assertNotEquals(TRANSACTION_AUDIT_DUPLICATE_ERROR_ACTION,
+ auditExisting.getAction());
+ Assertions.assertNotEquals(DUPLICATE_ERROR_TRANSACTION_ID,
+ auditExisting.getTransactionId());
+ }
+ );
+ }
+
+ @Test
+ void createBorrowingEcsRequestTest() throws Exception {
+ removeExistedTransactionFromDbIfSoExists();
+
+ this.mockMvc.perform(
+ post("/ecs-request-transactions/" + CIRCULATION_REQUEST_ID)
+ .content(asJsonString(createBorrowingEcsRequestTransactionByRole()))
+ .headers(defaultHeaders())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isCreated());
+ }
+
+ @Test
+ void checkErrorStatusForInvalidRequest() throws Exception {
+ DcbTransaction dcbTransaction = createLendingEcsRequestTransactionByRole();
+ dcbTransaction.setRequestId(UUID.randomUUID().toString());
+ this.mockMvc.perform(
+ post("/ecs-request-transactions/" + CIRCULATION_REQUEST_ID)
+ .content(asJsonString(dcbTransaction))
+ .headers(defaultHeaders())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpectAll(status().is4xxClientError());
+ }
+
+ private void removeExistedTransactionFromDbIfSoExists() {
+ systemUserScopedExecutionService.executeAsyncSystemUserScoped(TENANT, () -> {
+ if (transactionRepository.existsById(CIRCULATION_REQUEST_ID)) {
+ transactionRepository.deleteById(CIRCULATION_REQUEST_ID);
+ }
+ });
+ }
+}
diff --git a/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java b/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java
index 029db24a..5edbc7ff 100644
--- a/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java
+++ b/src/test/java/org/folio/dcb/controller/TransactionApiControllerTest.java
@@ -591,6 +591,26 @@ void transactionStatusUpdateFromCreatedToItemClosed() throws Exception {
.andExpect(jsonPath("$.status").value("CLOSED"));
}
+ @Test
+ void lenderTransactionStatusUpdateFromCreatedToOpen() throws Exception {
+ var transactionID = UUID.randomUUID().toString();
+ var dcbTransaction = createTransactionEntity();
+ dcbTransaction.setStatus(TransactionStatus.StatusEnum.CREATED);
+ dcbTransaction.setRole(LENDER);
+ dcbTransaction.setId(transactionID);
+
+ systemUserScopedExecutionService.executeAsyncSystemUserScoped(TENANT, () -> transactionRepository.save(dcbTransaction));
+
+ this.mockMvc.perform(
+ put("/transactions/" + transactionID + "/status")
+ .content(asJsonString(createTransactionStatus(TransactionStatus.StatusEnum.OPEN)))
+ .headers(defaultHeaders())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.status").value("OPEN"));
+ }
+
@Test
void transactionStatusUpdateFromItemCheckedOutToItemCheckedIn() throws Exception {
var transactionID = UUID.randomUUID().toString();
diff --git a/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java b/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java
index 37f1ccd1..ba2ac32f 100644
--- a/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java
+++ b/src/test/java/org/folio/dcb/listener/CirculationRequestEventListenerTest.java
@@ -91,7 +91,7 @@ void handleCancelRequestTest() {
MessageHeaders messageHeaders = getMessageHeaders();
when(transactionRepository.findTransactionByRequestIdAndStatusNotInClosed(any())).thenReturn(Optional.of(transactionEntity));
eventListener.handleRequestEvent(REQUEST_CANCEL_EVENT_SAMPLE, messageHeaders);
- Mockito.verify(transactionRepository, times(0)).save(any());
+ Mockito.verify(transactionRepository, times(1)).save(any());
}
@Test
@@ -118,7 +118,7 @@ void handleOpenRequestTest() {
when(circulationItemService.fetchItemById(anyString())).thenReturn(circulationItem);
MessageHeaders messageHeaders = getMessageHeaders();
eventListener.handleRequestEvent(CHECK_IN_TRANSIT_EVENT_SAMPLE, messageHeaders);
- Mockito.verify(transactionRepository, times(0)).save(any());
+ Mockito.verify(transactionRepository, times(1)).save(any());
}
@Test
diff --git a/src/test/java/org/folio/dcb/service/CirculationServiceTest.java b/src/test/java/org/folio/dcb/service/CirculationServiceTest.java
index 59c2a93f..eb461539 100644
--- a/src/test/java/org/folio/dcb/service/CirculationServiceTest.java
+++ b/src/test/java/org/folio/dcb/service/CirculationServiceTest.java
@@ -48,13 +48,13 @@ void checkInByBarcodeWithServicePointTest(){
void cancelRequestTest() {
when(circulationRequestService.getCancellationRequestIfOpenOrNull(anyString())).thenReturn(createCirculationRequest());
circulationService.cancelRequest(createTransactionEntity());
- verify(circulationClient).cancelRequest(anyString(), any());
+ verify(circulationClient).updateRequest(anyString(), any());
}
@Test
void shouldThrowExceptionWhenRequestIsNotUpdated() {
when(circulationRequestService.getCancellationRequestIfOpenOrNull(anyString())).thenReturn(createCirculationRequest());
- when(circulationClient.cancelRequest(anyString(), any())).thenThrow(FeignException.BadRequest.class);
+ when(circulationClient.updateRequest(anyString(), any())).thenThrow(FeignException.BadRequest.class);
assertThrows(CirculationRequestException.class, () -> circulationService.cancelRequest(createTransactionEntity()));
}
diff --git a/src/test/java/org/folio/dcb/service/StatusProcessorServiceTest.java b/src/test/java/org/folio/dcb/service/StatusProcessorServiceTest.java
index 30a527a9..8b9d2bb1 100644
--- a/src/test/java/org/folio/dcb/service/StatusProcessorServiceTest.java
+++ b/src/test/java/org/folio/dcb/service/StatusProcessorServiceTest.java
@@ -45,14 +45,6 @@ void lendingChainProcessorTest() {
@Test
void lendingChainProcessorErrorTest() {
- assertThrows(StatusException.class, () -> statusProcessorService.lendingChainProcessor(TransactionStatus.StatusEnum.CREATED, TransactionStatus.StatusEnum.OPEN));
-
- assertThrows(StatusException.class, () -> statusProcessorService.lendingChainProcessor(TransactionStatus.StatusEnum.CREATED, TransactionStatus.StatusEnum.AWAITING_PICKUP));
-
- assertThrows(StatusException.class, () -> statusProcessorService.lendingChainProcessor(TransactionStatus.StatusEnum.CREATED, TransactionStatus.StatusEnum.ITEM_CHECKED_OUT));
-
- assertThrows(StatusException.class, () -> statusProcessorService.lendingChainProcessor(TransactionStatus.StatusEnum.CREATED, TransactionStatus.StatusEnum.ITEM_CHECKED_IN));
-
assertThrows(StatusException.class, () -> statusProcessorService.lendingChainProcessor(TransactionStatus.StatusEnum.CREATED, TransactionStatus.StatusEnum.CLOSED));
assertThrows(StatusException.class, () -> statusProcessorService.lendingChainProcessor(TransactionStatus.StatusEnum.CREATED, TransactionStatus.StatusEnum.CREATED));
diff --git a/src/test/java/org/folio/dcb/service/TransactionAuditServiceTest.java b/src/test/java/org/folio/dcb/service/TransactionAuditServiceTest.java
index 0f285daa..48606e31 100644
--- a/src/test/java/org/folio/dcb/service/TransactionAuditServiceTest.java
+++ b/src/test/java/org/folio/dcb/service/TransactionAuditServiceTest.java
@@ -15,7 +15,6 @@
import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.LENDER;
import static org.folio.dcb.utils.EntityUtils.DCB_TRANSACTION_ID;
import static org.folio.dcb.utils.EntityUtils.createDcbTransactionByRole;
-import static org.folio.dcb.utils.EntityUtils.createTransactionEntity;
import static org.folio.dcb.utils.EntityUtils.createTransactionAuditEntity;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
@@ -41,11 +40,9 @@ void logTheErrorForExistedTransactionAuditTest() {
}
@Test
void logTheErrorForNotExistedTransactionAuditTest() {
- when(transactionMapper.mapToEntity(any(), any())).thenReturn(createTransactionEntity());
when(transactionAuditRepository.findLatestTransactionAuditEntityByDcbTransactionId(any()))
.thenReturn(Optional.empty());
transactionAuditService.logErrorIfTransactionAuditNotExists(DCB_TRANSACTION_ID, createDcbTransactionByRole(LENDER), "error_message");
- Mockito.verify(transactionMapper, times(1)).mapToEntity(any(), any());
Mockito.verify(transactionAuditRepository, times(1)).save(any());
}
diff --git a/src/test/java/org/folio/dcb/utils/EntityUtils.java b/src/test/java/org/folio/dcb/utils/EntityUtils.java
index e27985c4..48d1b1e1 100644
--- a/src/test/java/org/folio/dcb/utils/EntityUtils.java
+++ b/src/test/java/org/folio/dcb/utils/EntityUtils.java
@@ -60,6 +60,7 @@ public class EntityUtils {
public static String DCB_TYPE_USER_ID = "910c512c-ebc5-40c6-96a5-a20bfd81e154";
public static String EXISTED_INVENTORY_ITEM_BARCODE = "INVENTORY_ITEM";
public static String PATRON_TYPE_USER_ID = "18c1741d-e678-4c8e-9fe7-cfaeefab5eea";
+ public static String REQUEST_ID = "398501a2-5c97-4ba6-9ee7-d1cd6433cb98";
public static DcbTransaction createDcbTransactionByRole(DcbTransaction.RoleEnum role) {
return DcbTransaction.builder()
@@ -74,6 +75,23 @@ public static DcbTransaction createDcbTransactionByRole(DcbTransaction.RoleEnum
.build();
}
+ public static DcbTransaction createLendingEcsRequestTransactionByRole() {
+ return DcbTransaction.builder()
+ .requestId(REQUEST_ID)
+ .role(DcbTransaction.RoleEnum.LENDER)
+ .pickup(createDcbPickup())
+ .build();
+ }
+
+ public static DcbTransaction createBorrowingEcsRequestTransactionByRole() {
+ return DcbTransaction.builder()
+ .requestId(REQUEST_ID)
+ .item(createDcbItem())
+ .role(DcbTransaction.RoleEnum.BORROWER)
+ .pickup(createDcbPickup())
+ .build();
+ }
+
public static org.folio.dcb.domain.dto.ServicePointRequest createServicePointRequest() {
return org.folio.dcb.domain.dto.ServicePointRequest.builder()
.id(PICKUP_SERVICE_POINT_ID)
diff --git a/src/test/resources/mappings/circulation.json b/src/test/resources/mappings/circulation.json
index ac492f17..00ec8fc2 100644
--- a/src/test/resources/mappings/circulation.json
+++ b/src/test/resources/mappings/circulation.json
@@ -13,6 +13,18 @@
}
}
},
+ {
+ "request": {
+ "method": "PUT",
+ "url": "/circulation/requests/398501a2-5c97-4ba6-9ee7-d1cd6433cb98"
+ },
+ "response": {
+ "status": 204,
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ }
+ },
{
"request": {
"method": "POST",
diff --git a/src/test/resources/mappings/requests.json b/src/test/resources/mappings/requests.json
new file mode 100644
index 00000000..c981dff1
--- /dev/null
+++ b/src/test/resources/mappings/requests.json
@@ -0,0 +1,29 @@
+{
+ "mappings": [
+ {
+ "request": {
+ "method": "GET",
+ "url": "/request-storage/requests/398501a2-5c97-4ba6-9ee7-d1cd6433cb98"
+ },
+ "response": {
+ "status": 200,
+ "body": "{\n \"id\" : \"398501a2-5c97-4ba6-9ee7-d1cd6433cb98\",\n \"requestLevel\" : \"Item\",\n \"requestType\" : \"Page\",\n \"requestDate\" : \"2024-03-07T13:54:08.655+00:00\",\n \"requesterId\" : \"2205005b-ca51-4a04-87fd-938eefa8f6de\",\n \"instanceId\" : \"5bf370e0-8cca-4d9c-82e4-5170ab2a0a39\",\n \"holdingsRecordId\" : \"e3ff6133-b9a2-4d4c-a1c9-dc1867d4df19\",\n \"itemId\" : \"100d10bf-2f06-4aa0-be15-0b95b2d9f9e3\",\n \"status\" : \"Open - Not yet filled\",\n \"position\" : 1,\n \"instance\" : {\n \"title\" : \"A semantic web primer\",\n \"identifiers\" : [ {\n \"identifierTypeId\" : \"8261054f-be78-422d-bd51-4ed9f33c3422\",\n \"value\" : \"0262012103\"\n }, {\n \"identifierTypeId\" : \"8261054f-be78-422d-bd51-4ed9f33c3422\",\n \"value\" : \"9780262012102\"\n }, {\n \"identifierTypeId\" : \"c858e4f2-2b6b-4385-842b-60732ee14abb\",\n \"value\" : \"2003065165\"\n } ],\n \"contributorNames\" : [ {\n \"name\" : \"Antoniou, Grigoris\"\n }, {\n \"name\" : \"Van Harmelen, Frank\"\n } ],\n \"publication\" : [ {\n \"publisher\" : \"MIT Press\",\n \"place\" : \"Cambridge, Mass. \",\n \"dateOfPublication\" : \"c2004\",\n \"role\" : \"Publisher\"\n } ]\n },\n \"item\" : {\n \"barcode\" : \"90000\",\n \"location\" : {\n \"name\" : \"Annex\",\n \"libraryName\" : \"Datalogisk Institut\",\n \"code\" : \"KU/CC/DI/A\"\n },\n \"enumeration\" : \"\",\n \"status\" : \"Paged\",\n \"callNumber\" : \"TK5105.88815 . A58 2004 FT MEADE\",\n \"callNumberComponents\" : {\n \"callNumber\" : \"TK5105.88815 . A58 2004 FT MEADE\"\n }\n },\n \"requester\" : {\n \"lastName\" : \"rick\",\n \"firstName\" : \"psych\",\n \"barcode\" : \"123\",\n \"patronGroup\" : {\n \"id\" : \"3684a786-6671-4268-8ed0-9db82ebca60b\",\n \"group\" : \"staff\",\n \"desc\" : \"Staff Member\"\n },\n \"patronGroupId\" : \"3684a786-6671-4268-8ed0-9db82ebca60b\"\n },\n \"fulfillmentPreference\" : \"Hold Shelf\",\n \"pickupServicePointId\" : \"3a40852d-49fd-4df2-a1f9-6e2641a6e91f\",\n \"metadata\" : {\n \"createdDate\" : \"2024-03-07T13:54:13.484+00:00\",\n \"createdByUserId\" : \"5600bae3-4ca8-42dd-bef5-4502aaea6dc7\",\n \"updatedDate\" : \"2024-03-07T13:54:14.768+00:00\",\n \"updatedByUserId\" : \"5600bae3-4ca8-42dd-bef5-4502aaea6dc7\"\n },\n \"pickupServicePoint\" : {\n \"name\" : \"Circ Desk 1\",\n \"code\" : \"cd1\",\n \"discoveryDisplayName\" : \"Circulation Desk -- Hallway\",\n \"description\" : null,\n \"shelvingLagTime\" : null,\n \"pickupLocation\" : true\n }\n }",
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ }
+ },
+ {
+ "request": {
+ "method": "PUT",
+ "url": "/request-storage/requests/398501a2-5c97-4ba6-9ee7-d1cd6433cb98"
+ },
+ "response": {
+ "status": 204,
+ "headers": {
+ "Content-Type": "application/json"
+ }
+ }
+ }
+ ]
+}