Skip to content

Commit

Permalink
Merge pull request #72 from folio-org/MODDCB-90
Browse files Browse the repository at this point in the history
MODDCB-90 Accept existing circulation request ID
  • Loading branch information
MagzhanArtykov authored Mar 22, 2024
2 parents dd51771 + bcdc3a4 commit c96a985
Show file tree
Hide file tree
Showing 15 changed files with 302 additions and 12 deletions.
27 changes: 26 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@
}
]
},
{
"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"
]
}
]
},
{
"id": "_tenant",
"version": "2.0",
Expand Down Expand Up @@ -180,7 +199,8 @@
"subPermissions": [
"dcb.transactions.post",
"dcb.transactions.put",
"dcb.transactions.get"
"dcb.transactions.get",
"dcb.ecs-request.transactions.post"
]
},
{
Expand All @@ -197,6 +217,11 @@
"permissionName": "dcb.transactions.put",
"displayName": "update transaction details",
"description": "update transaction details"
},
{
"permissionName": "dcb.ecs-request.transactions.post",
"displayName": "creates new ECS request transaction",
"description": "creates new ECS request transaction"
}
],
"launchDescriptor": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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<TransactionStatusResponse> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

public interface CirculationRequestService {
CirculationRequest getCancellationRequestIfOpenOrNull(String requestId);
CirculationRequest fetchRequestById(String requestId);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.folio.dcb.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
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.TransactionStatusResponse;
import org.folio.dcb.exception.ResourceAlreadyExistException;
import org.folio.dcb.repository.TransactionRepository;
import org.folio.dcb.service.CirculationRequestService;
import org.folio.dcb.service.EcsRequestTransactionsService;
import org.folio.dcb.utils.RequestStatus;
import org.springframework.stereotype.Service;
import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.LENDER;

@Service
@RequiredArgsConstructor
@Log4j2
public class EcsRequestTransactionsServiceImpl implements EcsRequestTransactionsService {

private final BaseLibraryService baseLibraryService;
private final TransactionRepository transactionRepository;
private final CirculationRequestService circulationRequestService;

@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 {
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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);
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,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));
}
}

}
26 changes: 26 additions & 0 deletions src/main/resources/swagger.api/dcb_transaction.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
components:
requestBodies:
TransactionStatusBody:
Expand Down Expand Up @@ -127,6 +147,12 @@ components:
schema:
type: string
required: true
ecsRequestTransactionId:
in: path
name: ecsRequestTransactionId
schema:
type: string
required: true
schemas:
InventoryItem:
$ref: 'schemas/InventoryItem.yaml#/InventoryItem'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ CirculationRequest:
type: string
enum:
- Item
- Title
requestDate:
description: Date the request was made
type: string
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/swagger.api/schemas/dcbTransaction.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.folio.dcb.controller;

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;

import java.util.UUID;

import static org.folio.dcb.domain.dto.DcbTransaction.RoleEnum.LENDER;
import static org.folio.dcb.utils.EntityUtils.CIRCULATION_REQUEST_ID;
import static org.folio.dcb.utils.EntityUtils.createEcsRequestTransactionByRole;
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;

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(createEcsRequestTransactionByRole(LENDER)))
.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(createEcsRequestTransactionByRole(LENDER)))
.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 checkErrorStatusForInvalidRequest() throws Exception {
DcbTransaction dcbTransaction = createEcsRequestTransactionByRole(LENDER);
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);
}
});
}
}
Loading

0 comments on commit c96a985

Please sign in to comment.