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

[MODORDERS-1146] Add link to original item for piece that is bound #977

Merged
merged 9 commits into from
Jul 12, 2024
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();
SerhiiNosko marked this conversation as resolved.
Show resolved Hide resolved
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
Loading