Skip to content

Commit

Permalink
Merge branch 'master' into MODORDERS-1207
Browse files Browse the repository at this point in the history
  • Loading branch information
JavokhirAbdullayev committed Feb 7, 2025
2 parents 35358fb + b05e8fd commit d558016
Show file tree
Hide file tree
Showing 19 changed files with 531 additions and 93 deletions.
52 changes: 49 additions & 3 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@
"orders-storage.alerts.item.put",
"orders-storage.alerts.item.delete",
"orders-storage.pieces.collection.get",
"orders-storage.pieces.item.post",
"orders-storage.pieces-batch.collection.post",
"orders-storage.po-lines.collection.get",
"finance.funds.collection.get",
"finance.funds.item.get",
Expand Down Expand Up @@ -628,6 +628,46 @@
"invoice.invoice-lines.item.put"
]
},
{
"methods": ["POST"],
"pathPattern": "/orders/pieces-batch",
"permissionsRequired": ["orders.pieces.collection.post"],
"modulePermissions": [
"acquisitions-units-storage.units.collection.get",
"acquisitions-units-storage.memberships.collection.get",
"configuration.entries.collection.get",
"inventory.items.item.put",
"inventory.items.collection.get",
"inventory-storage.holdings-sources.collection.get",
"inventory.instances.collection.get",
"inventory.instances.item.get",
"inventory.instances.item.post",
"inventory-storage.holdings.collection.get",
"inventory-storage.holdings.item.post",
"inventory-storage.holdings.item.get",
"inventory-storage.loan-types.collection.get",
"inventory-storage.items.item.post",
"inventory-storage.instance-types.collection.get",
"inventory-storage.instance-statuses.collection.get",
"inventory-storage.contributor-name-types.collection.get",
"finance.funds.budget.item.get",
"finance.fiscal-years.item.get",
"finance.transactions.batch.execute",
"finance-storage.ledgers.collection.get",
"orders-storage.pieces.collection.get",
"orders-storage.po-lines.item.get",
"orders-storage.po-lines.item.put",
"orders-storage.po-lines.collection.get",
"orders-storage.purchase-orders.item.get",
"orders-storage.titles.item.get",
"orders-storage.titles.item.put",
"orders-storage.alerts.item.get",
"orders-storage.reporting-codes.item.get",
"user-tenants.collection.get",
"consortia.sharing-instances.item.post",
"orders-storage.pieces-batch.collection.post"
]
},
{
"methods": ["PUT"],
"pathPattern": "/orders/pieces-batch/status",
Expand Down Expand Up @@ -1620,6 +1660,11 @@
"displayName": "orders - delete an existing piece record",
"description": "Delete an existing piece"
},
{
"permissionName": "orders.pieces.collection.post",
"displayName": "orders - batch create pieces",
"description": "Batch create pieces"
},
{
"permissionName": "orders.pieces.collection.put",
"displayName": "orders - batch update piece statuses",
Expand All @@ -1636,6 +1681,7 @@
"description" : "All permissions for the pieces",
"subPermissions" : [
"orders.pieces.collection.get",
"orders.pieces.collection.post",
"orders.pieces.collection.put",
"orders.pieces.item.post",
"orders.pieces.item.get",
Expand Down Expand Up @@ -2164,7 +2210,7 @@
"orders-storage.alerts.item.get",
"orders-storage.alerts.item.put",
"orders-storage.alerts.item.delete",
"orders-storage.pieces.item.post",
"orders-storage.pieces-batch.collection.post",
"orders-storage.pieces.item.get",
"orders-storage.pieces.item.delete",
"orders-storage.pieces.collection.get",
Expand Down Expand Up @@ -2239,7 +2285,7 @@
"orders-storage.po-lines.item.post",
"orders-storage.po-lines.item.put",
"orders-storage.po-lines.collection.get",
"orders-storage.pieces.item.post",
"orders-storage.pieces-batch.collection.post",
"orders-storage.pieces.collection.get",
"orders-storage.po-line-number.get",
"orders-storage.po-number.get",
Expand Down
25 changes: 25 additions & 0 deletions ramls/pieces-batch.raml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,34 @@ documentation:
content: <b>Endpoint for batch operations on Pieces</b>

types:
piece-collection: !include acq-models/mod-orders-storage/schemas/piece_collection.json
piece-batch-status-collection: !include acq-models/mod-orders/schemas/pieceStatusBatchCollection.json
errors: !include raml-util/schemas/errors.schema

traits:
validate: !include raml-util/traits/validation.raml

resourceTypes:
post-with-200: !include rtypes/post-json-200.raml

/orders/pieces-batch:
type:
post-with-200:
requestSchema: piece-collection
responseSchema: piece-collection
requestExample: !include acq-models/mod-orders-storage/examples/piece_collection.sample
responseExample: !include acq-models/mod-orders-storage/examples/piece_collection.sample
is: [ validate ]
post:
description: Create batch pieces
queryParameters:
createItem:
displayName: Should item will be created for piece
type: boolean
description: Should item will be created for piece
example: true
required: false
default: false
/status:
put:
description: Batch status update for pieces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ public BasePieceFlowHolder withPoLineOnly(CompositePoLine compositePoLineToSave)
return this;
}

public void withTitleInformation(Title title) {
public BasePieceFlowHolder withTitleInformation(Title title) {
this.title = title;
return this;
}

public CompositePurchaseOrder getOriginPurchaseOrder() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.folio.models.pieces;

import java.util.List;

import org.folio.rest.jaxrs.model.Piece;
import org.folio.rest.jaxrs.model.PieceCollection;

public class PieceBatchCreationHolder extends BasePieceFlowHolder {
private List<Piece> piecesToCreate;
private boolean createItem;

public PieceBatchCreationHolder withCreateItem(boolean createItem) {
this.createItem = createItem;
return this;
}

public boolean isCreateItem() {
return createItem;
}

public PieceBatchCreationHolder withPieceToCreate(List<Piece> piecesToCreate) {
this.piecesToCreate = piecesToCreate;
return this;
}

public PieceBatchCreationHolder withPieceToCreate(PieceCollection pieceCollection) {
this.piecesToCreate = pieceCollection.getPieces();
return this;
}

public List<Piece> getPiecesToCreate() {
return piecesToCreate;
}

@Override
public String getOrderLineId() {
if (piecesToCreate.isEmpty()) {
return null;
}
return piecesToCreate.get(0).getPoLineId();
}

@Override
public String getTitleId() {
if (piecesToCreate.isEmpty()) {
return null;
}
return piecesToCreate.get(0).getTitleId();
}
}
21 changes: 21 additions & 0 deletions src/main/java/org/folio/rest/core/RestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ public <T> Future<T> post(String endpoint, T entity, Class<T> responseType, Requ
.onFailure(log::error);
}

public <T> Future<T> postBatch(RequestEntry requestEntry, T entity, Class<T> responseType, RequestContext requestContext) {
return postBatch(requestEntry.buildEndpoint(), entity, responseType, requestContext);
}

public <T> Future<T> postBatch(String endpoint, T entity, Class<T> responseType, RequestContext requestContext) {
var caseInsensitiveHeader = convertToCaseInsensitiveMap(requestContext.getHeaders());
return getVertxWebClient(requestContext.getContext()).postAbs(buildAbsEndpoint(caseInsensitiveHeader, endpoint))
.putHeaders(caseInsensitiveHeader)
.sendJson(entity)
.compose(response -> {
if (response.statusCode() >= 200 && response.statusCode() < 300) {
return Future.succeededFuture(response);
} else {
String error = response.bodyAsString();
return Future.failedFuture(getHttpException(response.statusCode(), error));
}
})
.map(bufferHttpResponse -> bufferHttpResponse.bodyAsJsonObject().mapTo(responseType))
.onFailure(log::error);
}

public <T> Future<Void> postEmptyResponse(String endpoint, T entity, RequestContext requestContext) {
var caseInsensitiveHeader = convertToCaseInsensitiveMap(requestContext.getHeaders());
return getVertxWebClient(requestContext.getContext())
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/folio/rest/core/exceptions/ErrorCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ public enum ErrorCodes {
CANNOT_GROUP_PIECES_BY_VENDOR("cannotGroupPiecesByVendorMessage", "Cannot group pieces by vendor"),
CANNOT_CREATE_JOBS_AND_UPDATE_PIECES("cannotCreateJobsAndUpdatePieces", "Cannot create jobs and update pieces"),
CANNOT_FIND_PIECE_BY_ID("cannotFindPieceById", "Cannot find a piece by '%s' id"),
UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS("unableToGenerateClaimsForOrgNoIntegrationDetails", "Unable to generate claims for %s because no claim integrations exist");
UNABLE_TO_GENERATE_CLAIMS_FOR_ORG_NO_INTEGRATION_DETAILS("unableToGenerateClaimsForOrgNoIntegrationDetails", "Unable to generate claims for %s because no claim integrations exist"),
ALL_PIECES_MUST_HAVE_THE_SAME_POLINE_ID_AND_TITLE_ID("allPiecesMustHaveTheSamePoLineIdAndTitleId", "All pieces in the batch should have the same titleId and poLineId"),;

private final String code;
private final String description;
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/folio/rest/impl/PiecesAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.jaxrs.model.Piece;
import org.folio.rest.jaxrs.model.PieceBatchStatusCollection;
import org.folio.rest.jaxrs.model.PieceCollection;
import org.folio.rest.jaxrs.resource.OrdersPieces;
import org.folio.rest.jaxrs.resource.OrdersPiecesBatch;
import org.folio.rest.jaxrs.resource.OrdersPiecesRequests;
Expand Down Expand Up @@ -102,6 +103,14 @@ public void getOrdersPiecesRequests(List<String> pieceIds, String status, Map<St
.onFailure(fail -> handleErrorResponse(asyncResultHandler, fail));
}

@Override
public void postOrdersPiecesBatch(boolean createItem, PieceCollection pieceCollection, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
pieceCreateFlowManager.createPieces(pieceCollection, createItem, new RequestContext(vertxContext, okapiHeaders))
.onSuccess(pieces -> asyncResultHandler.handle(succeededFuture(buildCreatedResponse(pieces))))
.onFailure(t -> handleErrorResponse(asyncResultHandler, t));
}

@Override
public void putOrdersPiecesBatchStatus(PieceBatchStatusCollection pieceBatchStatusCollection, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.folio.rest.jaxrs.model.CompositePoLine;
import org.folio.rest.jaxrs.model.CompositePurchaseOrder;
import org.folio.rest.jaxrs.model.Piece;
import org.folio.rest.jaxrs.model.PieceCollection;
import org.folio.rest.jaxrs.model.Title;
import org.folio.service.ProtectionService;
import org.folio.service.inventory.InventoryHoldingManager;
Expand Down Expand Up @@ -98,23 +99,37 @@ private Future<List<Piece>> createPieces(OpenOrderPieceHolder holder, CompositeP
piecesToCreate.addAll(holder.getPiecesWithHoldingToProcess());
piecesToCreate.addAll(holder.getPiecesWithoutLocationId());

var piecesToCreateFutures = piecesToCreate.stream()
var preparedPieces = piecesToCreate.stream()
.map(piece -> piece.withTitleId(holder.getTitleId()))
.map(piece -> createPiece(piece, order, isInstanceMatchingDisabled, requestContext))
.toList();
return collectResultsOnSuccess(piecesToCreateFutures)
.recover(th -> {
logger.error("Piece creation failed", th);
throw new CompletionException("Piece creation error", th);

// Perform individual acq unit validations for each piece and open order inventory update operation before creating batch pieces
var validationFutures = preparedPieces.stream()
.map(piece -> openOrderUpdateInventory(piece, order, isInstanceMatchingDisabled, requestContext))
.toList();

logger.info("createPieces:: Passed acq unit validation and open order '{}' inventory update", order.getId());
return collectResultsOnSuccess(validationFutures)
.compose(validatedPieces ->
pieceStorageService.insertPiecesBatch(validatedPieces, requestContext)
.map(PieceCollection::getPieces)
)
.recover(th -> {
logger.error("Piece creation failed", th);
return Future.failedFuture(new CompletionException("Piece creation error", th));
});
}

public Future<Piece> createPiece(Piece piece, CompositePurchaseOrder order, boolean isInstanceMatchingDisabled, RequestContext requestContext) {
logger.debug("createPiece:: Creating piece - {}", piece.getId());
private Future<Piece> openOrderUpdateInventory(Piece piece, CompositePurchaseOrder order, boolean isInstanceMatchingDisabled, RequestContext requestContext) {
logger.debug("openOrderUpdateInventory:: Validating acq unit and updating order '{}' inventory - {}", order.getId(), piece.getId());
return titlesService.getTitleById(piece.getTitleId(), requestContext)
.compose(title -> protectionService.isOperationRestricted(title.getAcqUnitIds(), ProtectedOperationType.CREATE, requestContext))
.compose(v -> openOrderUpdateInventory(order, order.getCompositePoLines().get(0), piece, isInstanceMatchingDisabled, requestContext))
.compose(v -> pieceStorageService.insertPiece(piece, requestContext));
.compose(title ->
protectionService.isOperationRestricted(title.getAcqUnitIds(), ProtectedOperationType.CREATE, requestContext)
)
.compose(v ->
openOrderUpdateInventory(order, order.getCompositePoLines().get(0), piece, isInstanceMatchingDisabled, requestContext)
)
.map(v -> piece);
}

public Future<Piece> updatePiece(Piece piece, RequestContext requestContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ public Future<Piece> insertPiece(Piece piece, RequestContext requestContext) {
return restClient.post(requestEntry, piece, Piece.class, requestContext);
}

public Future<PieceCollection> insertPiecesBatch(List<Piece> pieces, RequestContext requestContext) {
var pieceCollection = new PieceCollection().withPieces(pieces).withTotalRecords(pieces.size());
RequestEntry requestEntry = new RequestEntry(PIECES_STORAGE_BATCH_ENDPOINT);
return restClient.postBatch(requestEntry, pieceCollection, PieceCollection.class, requestContext);
}

public Future<Void> deletePiece(String pieceId, RequestContext requestContext) {
RequestEntry requestEntry = new RequestEntry(PIECE_STORAGE_BY_ID_ENDPOINT).withId(pieceId);
return restClient.delete(requestEntry, requestContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.folio.service.pieces.flows;

import static org.folio.rest.core.exceptions.ErrorCodes.ALL_PIECES_MUST_HAVE_THE_SAME_POLINE_ID_AND_TITLE_ID;
import static org.folio.rest.core.exceptions.ErrorCodes.CREATE_ITEM_FOR_PIECE_IS_NOT_ALLOWED_ERROR;
import static org.folio.rest.core.exceptions.ErrorCodes.PIECE_DISPLAY_ON_HOLDINGS_IS_NOT_CONSISTENT;

Expand All @@ -8,6 +9,7 @@
import java.util.List;
import java.util.Optional;

import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -19,6 +21,7 @@
import org.folio.rest.jaxrs.model.Eresource;
import org.folio.rest.jaxrs.model.Error;
import org.folio.rest.jaxrs.model.Errors;
import org.folio.rest.jaxrs.model.Parameter;
import org.folio.rest.jaxrs.model.Physical;
import org.folio.rest.jaxrs.model.Piece;
import org.folio.service.pieces.validators.PieceValidatorUtil;
Expand Down Expand Up @@ -46,6 +49,18 @@ public void isPieceRequestValid(Piece pieceToCreate, CompositePurchaseOrder orig
}
}

public void isPieceBatchRequestValid(List<Piece> piecesToCreate, CompositePurchaseOrder originalOrder, CompositePoLine originPoLine, boolean isCreateItem) {
var titlePoLineIds = piecesToCreate.stream()
.collect(Collectors.groupingBy(piece -> piece.getTitleId() + ":" + piece.getPoLineId()));
if (titlePoLineIds.size() > 1) {
var param = new Parameter().withKey("titlePoLineIds").withValue(titlePoLineIds.keySet().toString());
var error = ALL_PIECES_MUST_HAVE_THE_SAME_POLINE_ID_AND_TITLE_ID.toError().withParameters(List.of(param));
logger.error("isPieceBatchRequestValid:: Validation Error {}", error.getMessage());
throw new HttpException(RestConstants.VALIDATION_ERROR, ALL_PIECES_MUST_HAVE_THE_SAME_POLINE_ID_AND_TITLE_ID);
}
piecesToCreate.forEach(piece -> isPieceRequestValid(piece, originalOrder, originPoLine, isCreateItem));
}

public static List<Error> validateItemCreateFlag(Piece pieceToCreate, CompositePoLine originPoLine, boolean createItem) {
if (createItem && !isCreateItemForPiecePossible(pieceToCreate, originPoLine)) {
String msg = String.format(CREATE_ITEM_FOR_PIECE_IS_NOT_ALLOWED_ERROR.getDescription(), pieceToCreate.getFormat(), originPoLine.getId());
Expand Down
Loading

0 comments on commit d558016

Please sign in to comment.