Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,25 @@
}
]
},
{
"id": "orders.holding-detail",
"version": "1.0",
"handlers": [
{
"methods": [
"POST"
],
"pathPattern": "/orders/holding-detail",
"permissionsRequired": [
"orders.holding-detail.item.post"
],
"modulePermissions": [
"orders-storage.pieces.collection.get",
"inventory-storage.items.collection.get"
]
}
]
},
{
"id": "orders.export-history",
"version": "1.0",
Expand Down Expand Up @@ -2089,6 +2108,11 @@
"displayName" : "orders holding-summary get",
"description" : "Holding summary"
},
{
"permissionName": "orders.holding-detail.item.post",
"displayName" : "orders holding-detail post",
"description" : "Holding detail"
},
{
"permissionName" : "orders.routing-lists.collection.get",
"displayName" : "orders routing-list collection get",
Expand Down Expand Up @@ -2199,6 +2223,7 @@
"orders.fiscal-years.collection.get",
"orders.rollover.item.post",
"orders.holding-summary.collection.get",
"orders.holding-detail.item.post",
"orders.acquisition-methods.all",
"orders.export-history.all",
"orders.routing-lists.all",
Expand Down
39 changes: 39 additions & 0 deletions ramls/holding-detail.raml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#%RAML 1.0
title: "Holding detail"
baseUri: http://github.com/org/folio/mod-orders
version: v1
protocols: [ HTTP, HTTPS ]

documentation:
- title: Holding detail API
content: <b>API retrieve to holding detail.</b>

types:
holding-detail-collection: !include acq-models/mod-orders/schemas/holdingDetailCollection.json
holding-detail-results: !include acq-models/mod-orders/schemas/holdingDetailResults.json
errors: !include raml-util/schemas/errors.schema
UUID:
type: string
pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$

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

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

/orders/holding-detail:
displayName: Holding detail
description: |
Holding detail. The endpoint is used to:
- Accept a list of holding ids
- Retrieve linked details, i.e. pieces and items by each holding id
type:
post-with-200:
requestSchema: holding-detail-collection
responseSchema: holding-detail-results
requestExample: !include acq-models/mod-orders/examples/holdingDetailCollection.sample
responseExample: !include acq-models/mod-orders/examples/holdingDetailResults.sample
is: [validate]
post:
description: Holding detail
2 changes: 2 additions & 0 deletions ramls/titles.raml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ annotationTypes:
type: string
fqm-visible-by-default:
type: boolean
fqm-value-getter:
type: string

traits:
pageable: !include raml-util/traits/pageable.raml
Expand Down
2 changes: 2 additions & 0 deletions ramls/wrapper-pieces.raml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ annotationTypes:
type: string
fqm-visible-by-default:
type: boolean
fqm-value-getter:
type: string

traits:
orderable: !include raml-util/traits/orderable.raml
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/folio/config/ApplicationConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.folio.service.caches.ExportConfigsCache;
import org.folio.service.caches.InventoryCache;
import org.folio.service.dataexport.ExportConfigsRetriever;
import org.folio.service.orders.HoldingDetailService;
import org.folio.service.settings.CommonSettingsRetriever;
import org.folio.service.consortium.ConsortiumConfigurationService;
import org.folio.service.consortium.ConsortiumUserTenantsRetriever;
Expand Down Expand Up @@ -420,6 +421,11 @@ HoldingsSummaryService holdingsSummaryService(PurchaseOrderStorageService purcha
return new HoldingsSummaryService(purchaseOrderStorageService, purchaseOrderLineService, pieceStorageService);
}

@Bean
HoldingDetailService holdingsDetailService(PieceStorageService pieceStorageService, InventoryItemManager inventoryItemManager) {
return new HoldingDetailService(pieceStorageService, inventoryItemManager);
}

@Bean
CompositeOrderDynamicDataPopulateService totalExpendedPopulateService(TransactionService transactionService, InvoiceService invoiceService,
InvoiceLineService invoiceLineService, FiscalYearService fiscalYearService,
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/folio/models/HoldingDetailHolder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.folio.models;

import org.folio.rest.jaxrs.model.ItemsDetail;
import org.folio.rest.jaxrs.model.PiecesDetail;

import java.util.List;

public record HoldingDetailHolder(String holdingId, List<PiecesDetail> pieces, List<ItemsDetail> items) {
}
38 changes: 38 additions & 0 deletions src/main/java/org/folio/rest/impl/HoldingDetailAPI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.folio.rest.impl;

import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import org.folio.rest.annotations.Validate;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.jaxrs.model.HoldingDetailCollection;
import org.folio.rest.jaxrs.resource.OrdersHoldingDetail;
import org.folio.service.orders.HoldingDetailService;
import org.folio.spring.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;

import javax.ws.rs.core.Response;
import java.util.Map;

import static io.vertx.core.Future.succeededFuture;

public class HoldingDetailAPI extends BaseApi implements OrdersHoldingDetail {

@Autowired
private HoldingDetailService holdingDetailService;

public HoldingDetailAPI() {
SpringContextUtil.autowireDependencies(this, Vertx.currentContext());
}

@Override
@Validate
public void postOrdersHoldingDetail(HoldingDetailCollection entity, Map<String, String> okapiHeaders,
Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
var requestContext = new RequestContext(vertxContext, okapiHeaders);
holdingDetailService.postOrdersHoldingDetail(entity.getHoldingIds(), requestContext)
.onSuccess(holdingSummary -> asyncResultHandler.handle(succeededFuture(this.buildOkResponse(holdingSummary))))
.onFailure(t -> handleErrorResponse(asyncResultHandler, t));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import static org.folio.service.inventory.InventoryUtils.INVENTORY_LOOKUP_ENDPOINTS;

public class InventoryHoldingManager {

private static final Logger logger = LogManager.getLogger(InventoryHoldingManager.class);

public static final String ID = "id";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import static org.folio.service.inventory.InventoryUtils.isPurchaseOrderClosedOrPoLineCancelled;

public class InventoryItemManager {

private static final Logger logger = LogManager.getLogger(InventoryItemManager.class);

public static final String ID = "id";
Expand Down
151 changes: 151 additions & 0 deletions src/main/java/org/folio/service/orders/HoldingDetailService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package org.folio.service.orders;

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.folio.models.HoldingDetailHolder;
import org.folio.rest.core.models.RequestContext;
import org.folio.rest.jaxrs.model.HoldingDetailResults;
import org.folio.rest.jaxrs.model.HoldingDetailResultsProperty;
import org.folio.rest.jaxrs.model.ItemsDetail;
import org.folio.rest.jaxrs.model.ItemsDetailCollection;
import org.folio.rest.jaxrs.model.Piece;
import org.folio.rest.jaxrs.model.PiecesDetail;
import org.folio.rest.jaxrs.model.PiecesDetailCollection;
import org.folio.service.inventory.InventoryItemManager;
import org.folio.service.pieces.PieceStorageService;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.folio.orders.utils.HelperUtils.collectResultsOnSuccess;
import static org.folio.orders.utils.RequestContextUtil.createContextWithNewTenantId;
import static org.folio.service.inventory.InventoryItemManager.ID;

@Log4j2
public class HoldingDetailService {

private final PieceStorageService pieceStorageService;
private final InventoryItemManager inventoryItemManager;

public HoldingDetailService(PieceStorageService pieceStorageService, InventoryItemManager inventoryItemManager) {
this.pieceStorageService = pieceStorageService;
this.inventoryItemManager = inventoryItemManager;
}

public Future<HoldingDetailResults> postOrdersHoldingDetail(List<String> holdingIds, RequestContext requestContext) {
if (CollectionUtils.isEmpty(holdingIds)) {
log.info("postOrdersHoldingDetail:: No holding ids were passed");
return Future.succeededFuture(new HoldingDetailResults());
}
return pieceStorageService.getPiecesByHoldingIds(holdingIds, requestContext)
.compose(pieces -> {
if (CollectionUtils.isEmpty(pieces)) {
log.info("postOrdersHoldingDetail:: No pieces were found by holding ids={}", holdingIds);
return Future.succeededFuture(new HoldingDetailResults());
}
var holdersFutures = new ArrayList<Future<HoldingDetailHolder>>();
groupPiecesByTenantIdAndHoldingId(pieces)
.forEach((tenantId, groupedPiecesByHoldingId) -> {
if (Objects.nonNull(groupedPiecesByHoldingId)) {
groupedPiecesByHoldingId.forEach((holdingId, groupedPieces) -> {
log.info("postOrdersHoldingDetail:: Processing holding detail by tenant={}, holding id={}, pieces={}", getTenantId(tenantId), holdingId, groupedPieces.size());
var piecesDetail = createPieceDetail(groupedPieces);
var holdersFuture = getHolderFuture(tenantId, holdingId, piecesDetail, requestContext);
holdersFutures.add(holdersFuture);
});
}
});
return collectResultsOnSuccess(holdersFutures)
.map(this::createHoldingDetailResults);
})
.recover(throwable -> {
log.error("postOrdersHoldingDetail:: Error processing holding details for holding ids={}", holdingIds, throwable);
return Future.failedFuture(throwable);
});
}

protected Map<String, Map<String, List<Piece>>> groupPiecesByTenantIdAndHoldingId(List<Piece> pieces) {
if (Objects.isNull(pieces)) {
return Collections.emptyMap();
}
return pieces.stream()
.filter(Objects::nonNull)
.filter(piece -> Objects.nonNull(piece.getHoldingId()))
.map(piece -> Objects.nonNull(piece.getReceivingTenantId()) ? piece : piece.withReceivingTenantId(""))
.collect(Collectors.groupingBy(Piece::getReceivingTenantId, Collectors.groupingBy(Piece::getHoldingId)));
}

protected Future<HoldingDetailHolder> getHolderFuture(String tenantId, String holdingId, List<PiecesDetail> piecesDetail,
RequestContext requestContext) {
var localRequestContext = StringUtils.isNotBlank(tenantId) ? createContextWithNewTenantId(requestContext, tenantId) : requestContext;
return inventoryItemManager.getItemsByHoldingId(holdingId, localRequestContext)
.map(items -> {
if (Objects.isNull(items)) {
return Collections.<ItemsDetail>emptyList();
}
return items.stream()
.filter(Objects::nonNull)
.map(item -> createItemDetail(tenantId, item))
.toList();
})
.map(itemsDetail -> {
var finalPiecesDetail = Objects.nonNull(piecesDetail) ? piecesDetail : Collections.<PiecesDetail>emptyList();
return new HoldingDetailHolder(holdingId, finalPiecesDetail, itemsDetail);
});
}

private List<PiecesDetail> createPieceDetail(List<Piece> groupedPieces) {
if (CollectionUtils.isEmpty(groupedPieces)) {
return Collections.emptyList();
}
return groupedPieces.stream()
.filter(Objects::nonNull)
.map(piece -> new PiecesDetail()
.withId(piece.getId())
.withItemId(piece.getItemId())
.withTenantId(getTenantId(piece.getReceivingTenantId())))
.toList();
}

private ItemsDetail createItemDetail(String tenantId, JsonObject item) {
return new ItemsDetail()
.withId(item.getString(ID))
.withTenantId(getTenantId(tenantId));
}

private String getTenantId(String tenantId) {
return StringUtils.isNotBlank(tenantId) ? tenantId : null;
}

private HoldingDetailResults createHoldingDetailResults(List<HoldingDetailHolder> holders) {
if (CollectionUtils.isEmpty(holders)) {
log.info("createHoldingDetailResults:: No detail holders were generated");
return new HoldingDetailResults();
}
var holdingDetailResults = new HoldingDetailResults();
holders.forEach(holder -> {
var piecesDetailCollection = new PiecesDetailCollection();
var itemsDetailCollection = new ItemsDetailCollection();
if (Objects.nonNull(holder.pieces())) {
piecesDetailCollection.withPiecesDetail(holder.pieces())
.withTotalRecords(holder.pieces().size());
}
if (Objects.nonNull(holder.items())) {
itemsDetailCollection.withItemsDetail(holder.items())
.withTotalRecords(holder.items().size());
}
var holdingDetailProperty = new HoldingDetailResultsProperty()
.withPiecesDetailCollection(piecesDetailCollection)
.withItemsDetailCollection(itemsDetailCollection);
holdingDetailResults.withAdditionalProperty(holder.holdingId(), holdingDetailProperty);
});
return holdingDetailResults;
}
}
5 changes: 5 additions & 0 deletions src/test/java/org/folio/ApiTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import org.folio.service.orders.CompositeOrderRetrieveHolderBuilderTest;
import org.folio.service.orders.CompositeOrderTotalFieldsPopulateServiceTest;
import org.folio.service.orders.FundsDistributionServiceTest;
import org.folio.service.orders.HoldingDetailServiceTest;
import org.folio.service.orders.OrderFiscalYearServiceTest;
import org.folio.service.orders.OrderInvoiceRelationServiceTest;
import org.folio.service.orders.OrderReEncumberServiceTest;
Expand Down Expand Up @@ -374,6 +375,10 @@ class HoldingsSummaryAPITestNested extends HoldingsSummaryAPITest {
class HoldingsSummaryServiceTestNested extends HoldingsSummaryServiceTest {
}

@Nested
class HoldingDetailServiceTestNested extends HoldingDetailServiceTest {
}

@Nested
class ExpenseClassValidationServiceTestNested extends ExpenseClassValidationServiceTest {
}
Expand Down
Loading