Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MODDCB-83 #64

Merged
merged 23 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1ab4d90
MODDCB-27 Implement status transition from CHECKED OUT to CHECKED IN …
Vignesh-kalyanasundaram Oct 3, 2023
1dd551d
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Oct 4, 2023
9f746cb
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Oct 19, 2023
bd94c57
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 2, 2023
200b2dd
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 3, 2023
ddfaecd
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 7, 2023
7f54b48
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 10, 2023
64b3ecc
MODDCB-14 Implement DCB transaction status transition from Open to Aw…
Vignesh-kalyanasundaram Nov 14, 2023
c45892c
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 15, 2023
3711aec
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 16, 2023
892f2ea
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 16, 2023
6e34640
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 21, 2023
2a749ee
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 22, 2023
8e40609
Merge branch 'master' of github.com:folio-org/mod-dcb
Vignesh-kalyanasundaram Nov 23, 2023
0fe06bf
Use Chain_of_Responsiblity to handle update API
Vignesh-kalyanasundaram Nov 29, 2023
7b15514
Implementing chain of responsibility
Vignesh-kalyanasundaram Dec 11, 2023
04d13e5
Merge branch 'master' of github.com:folio-org/mod-dcb into Chain_of_R…
Vignesh-kalyanasundaram Dec 11, 2023
c06b988
MODDCB-83 - Implementing chain of responsibility
Vignesh-kalyanasundaram Dec 11, 2023
62700f6
MODDCB-83 - Implementing chain of responsibility
Vignesh-kalyanasundaram Dec 11, 2023
58f7932
MODDCB-83 - Implementing chain of responsibility
Vignesh-kalyanasundaram Dec 11, 2023
5737918
MODDCB-83 - Implementing chain of responsibility
Vignesh-kalyanasundaram Dec 11, 2023
8c83ebd
MODDCB-83 - Implementing chain of responsibility
Vignesh-kalyanasundaram Dec 12, 2023
a8998e2
Merge branch 'master' of github.com:folio-org/mod-dcb into MODDCB-83
Vignesh-kalyanasundaram Dec 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import feign.FeignException;
import lombok.extern.log4j.Log4j2;
import org.folio.dcb.exception.ResourceAlreadyExistException;
import org.folio.dcb.exception.StatusException;
import org.folio.spring.exception.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
Expand Down Expand Up @@ -59,7 +60,8 @@ public Errors handleBadGatewayException(FeignException.BadGateway ex) {
MissingServletRequestParameterException.class,
MethodArgumentTypeMismatchException.class,
HttpMessageNotReadableException.class,
IllegalArgumentException.class
IllegalArgumentException.class,
StatusException.class
})
public Errors handleValidationErrors(Exception ex) {
logExceptionMessage(ex);
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/org/folio/dcb/domain/StatusProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.folio.dcb.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.folio.dcb.domain.dto.TransactionStatus.StatusEnum;

@AllArgsConstructor
@Data
public class StatusProcessor {

private StatusEnum currentStatus;
private StatusEnum nextStatus;
private boolean manual;
private StatusProcessor nextProcessor;

}
9 changes: 9 additions & 0 deletions src/main/java/org/folio/dcb/exception/StatusException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.folio.dcb.exception;

public class StatusException extends RuntimeException{

public StatusException(String errorMsg) {
super(errorMsg);
}

}
78 changes: 78 additions & 0 deletions src/main/java/org/folio/dcb/service/StatusProcessorService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.folio.dcb.service;

import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.folio.dcb.domain.StatusProcessor;
import org.folio.dcb.domain.dto.TransactionStatus;

import java.util.ArrayList;
import java.util.List;

import org.folio.dcb.exception.StatusException;
import org.springframework.stereotype.Component;

import static org.folio.dcb.domain.dto.TransactionStatus.StatusEnum.*;

@Data
@Component
@Log4j2
public class StatusProcessorService {
private StatusProcessor chain;

private static final String STATUS_TRANSITION_ERROR_MSG = "Status transition will not be possible from %s to %s";

public List<TransactionStatus.StatusEnum> lendingChainProcessor(TransactionStatus.StatusEnum fromStatus, TransactionStatus.StatusEnum toStatus) {
log.debug("lendingChainProcessor:: fetching list of statuses from {} to {}", fromStatus, toStatus);
StatusProcessorService startChain = new StatusProcessorService();
StatusProcessor closeProcessor = new StatusProcessor(ITEM_CHECKED_IN, CLOSED, true, null);
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);
startChain.setChain(openProcessor);
var statuses = process(startChain, fromStatus, toStatus);
log.info("lendingChainProcessor:: Following statuses needs to be transitioned {} ", statuses);
return statuses;
}

public List<TransactionStatus.StatusEnum> borrowingChainProcessor(TransactionStatus.StatusEnum fromStatus, TransactionStatus.StatusEnum toStatus) {
log.debug("borrowingChainProcessor:: fetching list of statuses from {} to {}", fromStatus, toStatus);
StatusProcessorService startChain = new StatusProcessorService();
StatusProcessor closeProcessor = new StatusProcessor(ITEM_CHECKED_IN, CLOSED, false, null);
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, false, awaitingPickupProcessor);
startChain.setChain(openProcessor);
var statuses = process(startChain, fromStatus, toStatus);
log.info("borrowingChainProcessor:: Following statuses needs to be transitioned {} ", statuses);
return statuses;
}

private List<TransactionStatus.StatusEnum> process(StatusProcessorService statusProcessorService, TransactionStatus.StatusEnum fromStatus, TransactionStatus.StatusEnum toStatus) {
if (fromStatus.ordinal() >= toStatus.ordinal()) {
throw new StatusException(String.format(STATUS_TRANSITION_ERROR_MSG, fromStatus, toStatus));
}

List<TransactionStatus.StatusEnum> transactionStatuses = new ArrayList<>();
if (CANCELLED == toStatus) {
transactionStatuses.add(CANCELLED);
return transactionStatuses;
}

var processor = statusProcessorService.getChain();
while (processor != null) {
if (processor.getCurrentStatus().ordinal() >= fromStatus.ordinal() &&
processor.getNextStatus().ordinal() <= toStatus.ordinal()) {
if (processor.isManual()) {
throw new StatusException(String.format(STATUS_TRANSITION_ERROR_MSG, fromStatus, toStatus));
} else {
transactionStatuses.add(processor.getNextStatus());
}
}
processor = processor.getNextProcessor();
}
return transactionStatuses;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import org.folio.dcb.domain.dto.TransactionStatusResponse;
import org.folio.dcb.domain.entity.TransactionEntity;
import org.folio.dcb.exception.ResourceAlreadyExistException;
import org.folio.dcb.exception.StatusException;
import org.folio.dcb.repository.TransactionRepository;
import org.folio.dcb.service.LibraryService;
import org.folio.dcb.service.ServicePointService;
import org.folio.dcb.service.StatusProcessorService;
import org.folio.dcb.service.TransactionsService;
import org.folio.spring.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Qualifier;
Expand All @@ -31,6 +33,7 @@ public class TransactionsServiceImpl implements TransactionsService {
private final LibraryService borrowingLibraryService;
private final TransactionRepository transactionRepository;
private final ServicePointService servicePointService;
private final StatusProcessorService statusProcessorService;

@Override
public TransactionStatusResponse createCirculationRequest(String dcbTransactionId, DcbTransaction dcbTransaction) {
Expand All @@ -50,21 +53,23 @@ public TransactionStatusResponse createCirculationRequest(String dcbTransactionI
public TransactionStatusResponse updateTransactionStatus(String dcbTransactionId, TransactionStatus transactionStatus) {
return transactionRepository.findById(dcbTransactionId).map(dcbTransaction -> {
if (dcbTransaction.getStatus() == transactionStatus.getStatus()) {
throw new IllegalArgumentException(String.format(
throw new StatusException(String.format(
"Current transaction status equal to new transaction status: dcbTransactionId: %s, status: %s", dcbTransactionId, transactionStatus.getStatus()
));
} else if (transactionStatus.getStatus() == TransactionStatus.StatusEnum.CANCELLED
&& (dcbTransaction.getStatus() == TransactionStatus.StatusEnum.ITEM_CHECKED_IN ||
dcbTransaction.getStatus() == TransactionStatus.StatusEnum.ITEM_CHECKED_OUT)) {
throw new IllegalArgumentException(String.format(
throw new StatusException(String.format(
"Cannot cancel transaction dcbTransactionId: %s. Transaction already in status: %s: ", dcbTransactionId, dcbTransaction.getStatus()
));
}
switch (dcbTransaction.getRole()) {
case LENDER -> lendingLibraryService.updateTransactionStatus(dcbTransaction, transactionStatus);
case LENDER -> statusProcessorService.lendingChainProcessor(dcbTransaction.getStatus(), transactionStatus.getStatus())
.forEach(statusEnum -> lendingLibraryService.updateTransactionStatus(dcbTransaction, TransactionStatus.builder().status(statusEnum).build()));
case BORROWING_PICKUP -> borrowingPickupLibraryService.updateTransactionStatus(dcbTransaction, transactionStatus);
case PICKUP -> pickupLibraryService.updateTransactionStatus(dcbTransaction, transactionStatus);
case BORROWER -> borrowingLibraryService.updateTransactionStatus(dcbTransaction, transactionStatus);
case BORROWER -> statusProcessorService.borrowingChainProcessor(dcbTransaction.getStatus(), transactionStatus.getStatus())
.forEach(statusEnum -> borrowingLibraryService.updateTransactionStatus(dcbTransaction, TransactionStatus.builder().status(statusEnum).build()));
}

return TransactionStatusResponse.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,27 @@ void transactionStatusUpdateFromOpenToAwaitingTest() throws Exception {
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("AWAITING_PICKUP"));
}

@Test
void transactionStatusUpdateFromOpenToCheckedInTest() throws Exception {

var transactionID = UUID.randomUUID().toString();
var dcbTransaction = createTransactionEntity();
dcbTransaction.setStatus(TransactionStatus.StatusEnum.OPEN);
dcbTransaction.setRole(LENDER);
dcbTransaction.setId(transactionID);

systemUserScopedExecutionService.executeAsyncSystemUserScoped(TENANT, () -> transactionRepository.save(dcbTransaction));

this.mockMvc.perform(
put("/transactions/" + transactionID + "/status")
.content(asJsonString(createTransactionStatus(TransactionStatus.StatusEnum.ITEM_CHECKED_IN)))
.headers(defaultHeaders())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("ITEM_CHECKED_IN"));
}
@Test
void transactionStatusUpdateFromCreatedToOpenForPickupLibTest() throws Exception {

Expand Down Expand Up @@ -368,6 +389,26 @@ void transactionStatusUpdateFromAwaitingPickupToItemCheckedOut() throws Exceptio
.andExpect(jsonPath("$.status").value("ITEM_CHECKED_OUT"));
}

@Test
void transactionStatusUpdateFromCreatedToItemClosed() throws Exception {
var transactionID = UUID.randomUUID().toString();
var dcbTransaction = createTransactionEntity();
dcbTransaction.setStatus(TransactionStatus.StatusEnum.CREATED);
dcbTransaction.setRole(BORROWER);
dcbTransaction.setId(transactionID);

systemUserScopedExecutionService.executeAsyncSystemUserScoped(TENANT, () -> transactionRepository.save(dcbTransaction));

this.mockMvc.perform(
put("/transactions/" + transactionID + "/status")
.content(asJsonString(createTransactionStatus(TransactionStatus.StatusEnum.CLOSED)))
.headers(defaultHeaders())
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("CLOSED"));
}

@Test
void transactionStatusUpdateFromItemCheckedOutToItemCheckedIn() throws Exception {
var transactionID = UUID.randomUUID().toString();
Expand Down
Loading