Skip to content

Commit

Permalink
[MODORDERS-1146] Add link to original item for piece that is bound (#977
Browse files Browse the repository at this point in the history
)

* [MODORDERS-1146] Add endpoint for removing binding and separate original item and bind item

* [MODORDERS-1146] Misspell

* [MODORDERS-1146] Update acq-models pointer

* [MODORDERS-1146] Add unit tests and update remove process

* [MODORDERS-1146] Verify modified title in unit tests

* [MODORDERS-1146] Remove itemId from title when necessary

* [MODORDERS-1146] Remove unit tests for title bindItemIds

* [MODORDERS-1146] Add some logging

* [MODORDERS-1146] Remove only bindItemId from title when removing binding
  • Loading branch information
Saba-Zedginidze-EPAM authored Jul 12, 2024
1 parent 4362108 commit 8144e59
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 9 deletions.
8 changes: 8 additions & 0 deletions ramls/bind-pieces.raml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ resourceTypes:
is: [validate]
post:
description: bind pieces to item and connect that item to title
/{id}:
uriParameters:
id:
description: The UUID of a piece record
type: UUID
is: [ validate ]
delete:
description: Remove binding for a piece with given {id}
47 changes: 45 additions & 2 deletions src/main/java/org/folio/helper/BindHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -55,6 +56,10 @@ public BindHelper(BindPiecesCollection bindPiecesCollection,
bindPiecesCollection.getPoLineId(), bindPiecesCollection.getBindPieceIds().size());
}

public BindHelper(Map<String, String> okapiHeaders, Context ctx) {
super(okapiHeaders, ctx);
}

private Map<String, Map<String, BindPiecesCollection>> groupBindPieceByPoLineId(BindPiecesCollection bindPiecesCollection) {
String poLineId = bindPiecesCollection.getPoLineId();
Map<String, BindPiecesCollection> bindPieceMap = bindPiecesCollection.getBindPieceIds().stream()
Expand All @@ -66,6 +71,44 @@ private Map<String, Map<String, BindPiecesCollection>> groupBindPieceByPoLineId(
return Map.of(poLineId, bindPieceMap);
}

public Future<Void> removeBinding(String pieceId, RequestContext requestContext) {
logger.debug("removeBinding:: Removing binding for piece: {}", pieceId);
return pieceStorageService.getPieceById(pieceId, requestContext)
.compose(piece -> {
var bindItemId = piece.getBindItemId();
piece.withBindItemId(null).withIsBound(false);
return removeForbiddenEntities(piece, requestContext)
.compose(v -> getValidPieces(requestContext))
.compose(piecesGroupedByPoLine -> storeUpdatedPieceRecords(piecesGroupedByPoLine, requestContext))
.compose(piecesGroupedByPoLine -> clearTitleBindItemsIfNeeded(piece.getTitleId(), bindItemId, requestContext));
});
}

private Future<Void> removeForbiddenEntities(Piece piece, RequestContext requestContext) {
// Populate piecesByLineId used by removeForbiddenEntities and parent helper methods
piecesByLineId = Map.of(piece.getPoLineId(), Collections.singletonMap(piece.getId(), null));
return removeForbiddenEntities(requestContext);
}

private Future<Void> clearTitleBindItemsIfNeeded(String titleId, String bindItemId, RequestContext requestContext) {
String query = String.format("titleId==%s and bindItemId==%s and isBound==true", titleId, bindItemId);
return pieceStorageService.getPieces(0, 0, query, requestContext)
.compose(pieceCollection -> {
var totalRecords = pieceCollection.getTotalRecords();
if (totalRecords != 0) {
logger.info("clearTitleBindItemsIfNeeded:: Found '{}' piece(s) associated with bind item '{}'", totalRecords, bindItemId);
return Future.succeededFuture();
}
logger.info("clearTitleBindItemsIfNeeded:: Removing bind item '{}' from title '{}' as no associated piece(s) to the item was found", bindItemId, titleId);
return titlesService.getTitleById(titleId, requestContext)
.compose(title -> {
List<String> bindItemIds = new ArrayList<>(title.getBindItemIds());
bindItemIds.remove(bindItemId);
return titlesService.saveTitle(title.withBindItemIds(bindItemIds), requestContext);
});
});
}

public Future<BindPiecesResult> bindPieces(BindPiecesCollection bindPiecesCollection, RequestContext requestContext) {
return removeForbiddenEntities(requestContext)
.compose(vVoid -> processBindPieces(bindPiecesCollection, requestContext));
Expand Down Expand Up @@ -188,7 +231,7 @@ private Future<BindPiecesHolder> createItemForPieces(BindPiecesHolder holder, Re
inventoryItemRequestService.transferItemRequests(itemIds, newItemId, requestContext);
}
// Set new item ids for pieces and holder
holder.getPieces().forEach(piece -> piece.setItemId(newItemId));
holder.getPieces().forEach(piece -> piece.setBindItemId(newItemId));
return holder.withBindItemId(newItemId);
});
}
Expand Down Expand Up @@ -225,7 +268,7 @@ private Future<BindPiecesHolder> storeUpdatedPieces(BindPiecesHolder holder, Req
}

private Future<BindPiecesHolder> updateTitleWithBindItems(BindPiecesHolder holder, RequestContext requestContext) {
var itemIds = holder.getPieces().map(Piece::getItemId).distinct().toList();
var itemIds = holder.getPieces().map(Piece::getBindItemId).distinct().toList();
return titlesService.getTitlesByQuery(String.format(TITLE_BY_POLINE_QUERY, holder.getPoLineId()), requestContext)
.map(titles -> updateTitle(titles, itemIds, requestContext))
.map(v -> holder);
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/folio/rest/impl/ReceivingAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ public void postOrdersBindPieces(BindPiecesCollection entity, Map<String, String
.onFailure(t -> handleErrorResponse(asyncResultHandler, helper, t));
}

@Override
public void deleteOrdersBindPiecesById(String id, Map<String, String> okapiHeaders, Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
logger.info("Removing binding for piece: {}", id);
BindHelper helper = new BindHelper(okapiHeaders, vertxContext);
helper.removeBinding(id, new RequestContext(vertxContext, okapiHeaders))
.onSuccess(s -> asyncResultHandler.handle(succeededFuture(helper.buildNoContentResponse())))
.onFailure(t -> handleErrorResponse(asyncResultHandler, helper, t));
}

@Override
@Validate
public void getOrdersReceivingHistory(String totalRecords, int offset, int limit, String query, Map<String, String> okapiHeaders,
Expand Down
1 change: 1 addition & 0 deletions src/test/java/org/folio/TestConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ private TestConstants() {}
public static final String ORDERS_CHECKIN_ENDPOINT = "/orders/check-in";
public static final String ORDERS_EXPECT_ENDPOINT = "/orders/expect";
public static final String ORDERS_BIND_ENDPOINT = "/orders/bind-pieces";
public static final String ORDERS_BIND_ID_ENDPOINT = "/orders/bind-pieces/%s";
public static final String PO_LINE_NUMBER_VALUE = "1";

public static final String BAD_QUERY = "unprocessableQuery";
Expand Down
103 changes: 97 additions & 6 deletions src/test/java/org/folio/rest/impl/CheckinReceivingApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.folio.rest.jaxrs.model.ReceivingItemResult;
import org.folio.rest.jaxrs.model.ReceivingResult;
import org.folio.rest.jaxrs.model.ReceivingResults;
import org.folio.rest.jaxrs.model.Title;
import org.folio.rest.jaxrs.model.ToBeCheckedIn;
import org.folio.rest.jaxrs.model.ToBeExpected;
import org.junit.jupiter.api.AfterAll;
Expand All @@ -42,6 +43,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -55,12 +57,14 @@

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.folio.RestTestUtils.prepareHeaders;
import static org.folio.RestTestUtils.verifyDeleteResponse;
import static org.folio.RestTestUtils.verifyPostResponse;
import static org.folio.TestConfig.clearServiceInteractions;
import static org.folio.TestConfig.initSpringContext;
import static org.folio.TestConfig.isVerticleNotDeployed;
import static org.folio.TestConstants.EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10;
import static org.folio.TestConstants.ORDERS_BIND_ENDPOINT;
import static org.folio.TestConstants.ORDERS_BIND_ID_ENDPOINT;
import static org.folio.TestConstants.ORDERS_CHECKIN_ENDPOINT;
import static org.folio.TestConstants.ORDERS_EXPECT_ENDPOINT;
import static org.folio.TestConstants.ORDERS_RECEIVING_ENDPOINT;
Expand Down Expand Up @@ -108,6 +112,7 @@
import static org.folio.rest.impl.MockServer.getPieceUpdates;
import static org.folio.rest.impl.MockServer.getPoLineSearches;
import static org.folio.rest.impl.MockServer.getPoLineUpdates;
import static org.folio.rest.impl.MockServer.getUpdatedTitles;
import static org.folio.rest.jaxrs.model.ProcessingStatus.Type.SUCCESS;
import static org.folio.rest.jaxrs.model.ReceivedItem.ItemStatus.ON_ORDER;
import static org.folio.service.inventory.InventoryItemManager.COPY_NUMBER;
Expand Down Expand Up @@ -1035,12 +1040,15 @@ void testBindPiecesToTitleWithItem() {
var order = getMinimalContentCompositePurchaseOrder()
.withWorkflowStatus(CompositePurchaseOrder.WorkflowStatus.OPEN);
var poLine = getMinimalContentCompositePoLine(order.getId());
var title = getTitle(poLine);
var bindingPiece1 = getMinimalContentPiece(poLine.getId())
.withTitleId(title.getId())
.withHoldingId(holdingId)
.withReceivingStatus(receivingStatus)
.withFormat(format);
var bindingPiece2 = getMinimalContentPiece(poLine.getId())
.withId(UUID.randomUUID().toString())
.withTitleId(title.getId())
.withHoldingId(holdingId)
.withReceivingStatus(receivingStatus)
.withFormat(format);
Expand All @@ -1049,7 +1057,7 @@ void testBindPiecesToTitleWithItem() {
addMockEntry(PO_LINES_STORAGE, poLine);
addMockEntry(PIECES_STORAGE, bindingPiece1);
addMockEntry(PIECES_STORAGE, bindingPiece2);
addMockEntry(TITLES, getTitle(poLine));
addMockEntry(TITLES, title);

var pieceIds = List.of(bindingPiece1.getId(), bindingPiece2.getId());
var bindPiecesCollection = new BindPiecesCollection()
Expand All @@ -1072,13 +1080,28 @@ void testBindPiecesToTitleWithItem() {
assertThat(pieceUpdates, notNullValue());
assertThat(pieceUpdates, hasSize(bindPiecesCollection.getBindPieceIds().size()));

var pieceList = pieceUpdates.stream().filter(pol -> {
Piece piece = pol.mapTo(Piece.class);
String pieceId = piece.getId();
return Objects.equals(bindingPiece1.getId(), pieceId) || Objects.equals(bindingPiece2.getId(), pieceId);
}).toList();
var pieceList = pieceUpdates.stream()
.map(json -> json.mapTo(Piece.class))
.filter(piece -> pieceIds.contains(piece.getId()))
.filter(piece -> piece.getBindItemId().equals(newItemId))
.toList();
assertThat(pieceList.size(), is(2));

var titleUpdates = getUpdatedTitles();
assertThat(titleUpdates, notNullValue());
assertThat(titleUpdates, hasSize(1));

var updatedTitleOpt = titleUpdates.stream()
.map(json -> json.mapTo(Title.class))
.filter(updatedTitle -> updatedTitle.getId().equals(pieceList.get(0).getTitleId()))
.filter(updatedTitle -> updatedTitle.getId().equals(pieceList.get(1).getTitleId()))
.findFirst();

assertThat(updatedTitleOpt.isPresent(), is(true));
var titleBindItemIds = updatedTitleOpt.get().getBindItemIds();
assertThat(titleBindItemIds, hasSize(1));
assertThat(titleBindItemIds.get(0), is(newItemId));

var createdHoldings = getCreatedHoldings();
assertThat(createdHoldings, nullValue());

Expand Down Expand Up @@ -1291,6 +1314,74 @@ void testBindExpectedPieces() {
assertThat(errors.get(0).getMessage(), equalTo(PIECES_MUST_HAVE_RECEIVED_STATUS.getDescription()));
}

@Test
void testRemovePieceBinding() {
logger.info("=== Test DELETE Remove binding");

var holdingId = "849241fa-4a14-4df5-b951-846dcd6cfc4d";
var order = getMinimalContentCompositePurchaseOrder()
.withWorkflowStatus(CompositePurchaseOrder.WorkflowStatus.OPEN);
var poLine = getMinimalContentCompositePoLine(order.getId());
var title = getTitle(poLine);
var bindPiece1 = getMinimalContentPiece(poLine.getId())
.withTitleId(title.getId())
.withHoldingId(holdingId)
.withReceivingStatus(Piece.ReceivingStatus.RECEIVED)
.withFormat(Piece.Format.PHYSICAL);
var bindPiece2 = getMinimalContentPiece(poLine.getId())
.withId(UUID.randomUUID().toString())
.withTitleId(title.getId())
.withHoldingId(holdingId)
.withReceivingStatus(Piece.ReceivingStatus.RECEIVED)
.withFormat(Piece.Format.PHYSICAL);
var bindPieceIds = List.of(bindPiece1.getId(), bindPiece2.getId());

addMockEntry(PURCHASE_ORDER_STORAGE, order);
addMockEntry(PO_LINES_STORAGE, poLine);
addMockEntry(PIECES_STORAGE, bindPiece1);
addMockEntry(PIECES_STORAGE, bindPiece2);
addMockEntry(TITLES, title);

var bindPiecesCollection = new BindPiecesCollection()
.withPoLineId(poLine.getId())
.withBindItem(getMinimalContentBindItem()
.withLocationId(null)
.withHoldingId(holdingId))
.withBindPieceIds(bindPieceIds);

var bindResponse = verifyPostResponse(ORDERS_BIND_ENDPOINT, JsonObject.mapFrom(bindPiecesCollection).encode(),
prepareHeaders(EXIST_CONFIG_X_OKAPI_TENANT_LIMIT_10), APPLICATION_JSON, HttpStatus.HTTP_OK.toInt())
.as(BindPiecesResult.class);

assertThat(bindResponse.getPoLineId(), is(poLine.getId()));
assertThat(bindResponse.getBoundPieceIds(), hasSize(2));
assertThat(bindResponse.getBoundPieceIds(), is(bindPieceIds));
assertThat(bindResponse.getItemId(), notNullValue());

var bindItemId = bindResponse.getItemId();
var url = String.format(ORDERS_BIND_ID_ENDPOINT, bindPiece1.getId());
verifyDeleteResponse(url, "", HttpStatus.HTTP_NO_CONTENT.toInt());

var pieceUpdates = getPieceUpdates();
assertThat(pieceUpdates, notNullValue());
assertThat(pieceUpdates, hasSize(3));

var pieceList = pieceUpdates.stream()
.map(json -> json.mapTo(Piece.class))
.filter(piece -> Objects.equals(bindPiece1.getId(), piece.getId()))
.sorted(Comparator.comparing(Piece::getIsBound))
.toList();
assertThat(pieceList.size(), is(2));

var pieceBefore = pieceList.get(1);
assertThat(pieceBefore.getIsBound(), is(true));
assertThat(pieceBefore.getBindItemId(), notNullValue());

var pieceAfter = pieceList.get(0);
assertThat(pieceAfter.getIsBound(), is(false));
assertThat(pieceAfter.getBindItemId(), nullValue());
}

@Test
void testPostReceivingWithErrorSearchingForPiece() {
logger.info("=== Test POST Receiving - Receive resources with error searching for piece");
Expand Down

0 comments on commit 8144e59

Please sign in to comment.