PackagingUnitFinderController.java
/*
* Copyright 2005-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openwms.wms.inventory.rest.pu;
import jakarta.validation.constraints.Pattern;
import org.ameba.http.MeasuredRestController;
import org.openwms.core.http.AbstractWebController;
import org.openwms.core.units.api.Measurable;
import org.openwms.wms.CycleAvoidingMappingContext;
import org.openwms.wms.inventory.PackagingUnit;
import org.openwms.wms.inventory.PackagingUnitFinder;
import org.openwms.wms.inventory.PackagingUnitMapper;
import org.openwms.wms.inventory.api.PackagingUnitVO;
import org.openwms.wms.inventory.api.ProductVO;
import org.openwms.wms.inventory.api.PusOnTU;
import org.openwms.wms.inventory.rest.LoadUnitController;
import org.openwms.wms.inventory.rest.ProductController;
import org.openwms.wms.inventory.spi.PackagingUnitTransformer;
import org.openwms.wms.transport.TransportUnitController;
import org.openwms.wms.transport.TransportUnitService;
import org.openwms.wms.transport.api.TransportUnitVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Arrays;
import java.util.List;
import static java.util.Arrays.asList;
import static org.openwms.wms.inventory.api.InventoryConstants.API_PACKAGING_UNITS;
import static org.openwms.wms.inventory.api.PackagingUnitApi.ACCEPT_HEADER_PU;
import static org.openwms.wms.transport.api.TransportUnitApi.API_TRANSPORT_UNITS;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
/**
* A PackagingUnitFinderController.
*
* @author Heiko Scherrer
*/
@Validated
@MeasuredRestController
class PackagingUnitFinderController extends AbstractWebController {
private static final Logger LOGGER = LoggerFactory.getLogger(PackagingUnitFinderController.class);
protected final PackagingUnitFinder finder;
protected final TransportUnitService transportUnitService;
protected final PackagingUnitMapper packagingUnitMapper;
protected final PackagingUnitTransformer packagingUnitTransformer;
protected PackagingUnitFinderController(PackagingUnitFinder finder, TransportUnitService transportUnitService,
PackagingUnitMapper packagingUnitMapper, @Autowired(required = false) PackagingUnitTransformer packagingUnitTransformer) {
this.finder = finder;
this.transportUnitService = transportUnitService;
this.packagingUnitMapper = packagingUnitMapper;
this.packagingUnitTransformer = packagingUnitTransformer;
}
@Transactional(readOnly = true)
@GetMapping(API_PACKAGING_UNITS + "/{pKey}")
public ResponseEntity<PackagingUnitVO> findByPKey(
@PathVariable("pKey") String pKey,
@RequestHeader(value = HttpHeaders.ACCEPT, required = false) String acceptHeader
) {
LOGGER.debug("Find PackagingUnit with pKey [{}]", pKey);
var eo = finder.findByPKey(pKey);
PackagingUnitVO result;
if (ACCEPT_HEADER_PU.equals(acceptHeader)) {
result = enrichWithLinks(eo).getFirst();
} else {
result = packagingUnitMapper.convert(eo, new CycleAvoidingMappingContext());
}
return ResponseEntity.ok(result);
}
@Transactional(readOnly = true)
@GetMapping(API_TRANSPORT_UNITS + "/{transportUnitBK}/load-units/packaging-units")
public ResponseEntity<List<PackagingUnitVO>> findPUOnTU(
@PathVariable("transportUnitBK") String transportUnitBK,
@RequestHeader(value = HttpHeaders.ACCEPT, required = false) String acceptHeader
) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Find PackagingUnits on TU [{}]", transportUnitBK);
}
var eos = finder.findOnTU(transportUnitBK);
if (ACCEPT_HEADER_PU.equals(acceptHeader)) {
return ResponseEntity.ok(enrichWithLinks(eos.toArray(new PackagingUnit[0])));
}
return ResponseEntity.ok(packagingUnitMapper.convert(eos, new CycleAvoidingMappingContext()));
}
@Transactional(readOnly = true)
@GetMapping("/v2/transport-units/{transportUnitBK}/load-units/packaging-units")
public ResponseEntity<PusOnTU> findOnTU(
@PathVariable("transportUnitBK") String transportUnitBK,
@RequestHeader(value = HttpHeaders.ACCEPT, required = false) String acceptHeader
) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Find PackagingUnits on TU [{}]", transportUnitBK);
}
var eos = finder.findOnTU(transportUnitBK);
var pusOnTU = new PusOnTU();
if (eos.isEmpty()) {
var transportUnit = transportUnitService.findOneByOrThrow(transportUnitBK);
var tu = new TransportUnitVO(transportUnit.getTransportUnitBK().getValue());
//tu.setActualLocationErpCode(transportUnit.getActualLocation().getErpCode());
pusOnTU.setTransportUnit(tu);
return ResponseEntity.ok(pusOnTU);
}
if (ACCEPT_HEADER_PU.equals(acceptHeader)) {
var tu = new TransportUnitVO(eos.get(0).getLoadUnit().getTransportUnit().getTransportUnitBK().getValue());
//tu.setActualLocationErpCode(eos.get(0).getLoadUnit().getTransportUnit().getActualLocation().getErpCode());
pusOnTU.setTransportUnit(tu);
pusOnTU.setPackagingUnits(enrichWithLinks(eos.toArray(new PackagingUnit[0])));
return ResponseEntity.ok(pusOnTU);
}
var pus = packagingUnitMapper.convert(eos, new CycleAvoidingMappingContext());
pusOnTU.setPackagingUnits(pus);
var tu = new TransportUnitVO(eos.get(0).getLoadUnit().getTransportUnit().getTransportUnitBK().getValue());
//tu.setActualLocationErpCode(eos.get(0).getLoadUnit().getTransportUnit().getActualLocation().getErpCode());
pusOnTU.setTransportUnit(tu);
return ResponseEntity.ok(pusOnTU);
}
@Transactional(readOnly = true)
@GetMapping(API_TRANSPORT_UNITS + "/{transportUnitBK}/load-units/{luPos}/packaging-units")
public ResponseEntity<List<PackagingUnitVO>> findOnTUandLU(
@PathVariable("transportUnitBK") String transportUnitBK,
@PathVariable("luPos") String luPos,
@RequestHeader(value = HttpHeaders.ACCEPT, required = false) String acceptHeader
) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Find PackagingUnits on TU [{}] and LU [{}]", transportUnitBK, luPos);
}
var eos = finder.findOnTUandLU(transportUnitBK, luPos);
if (ACCEPT_HEADER_PU.equals(acceptHeader)) {
return ResponseEntity.ok(enrichWithLinks(eos.toArray(new PackagingUnit[0])));
}
return ResponseEntity.ok(packagingUnitMapper.convert(eos, new CycleAvoidingMappingContext()));
}
@Transactional(readOnly = true)
@GetMapping(value = API_PACKAGING_UNITS, params = {"productPKey", "locationGroupName"})
public ResponseEntity<List<PackagingUnitVO>> findForProductInLG(
@RequestParam("productPKey") String productPKey,
@RequestParam("locationGroupName") String locationGroupName,
@RequestParam(value = "sortDirection", required = false) @Pattern(regexp = "^(asc|desc)$") String sortDirection,
@RequestParam(value = "sortProperty", required = false) String sortProperty,
@RequestHeader(value = HttpHeaders.ACCEPT, required = false) String acceptHeader
) {
LOGGER.debug("Find PackagingUnits of Product [{}] in LocationGroup [{}]", productPKey, locationGroupName);
var eos = finder.findOfProductInLG(productPKey, asList(locationGroupName), sortDirection, sortProperty);
if (ACCEPT_HEADER_PU.equals(acceptHeader)) {
return ResponseEntity.ok(enrichWithLinks(eos.toArray(new PackagingUnit[0])));
}
return ResponseEntity.ok(packagingUnitMapper.convert(eos, new CycleAvoidingMappingContext()));
}
@Transactional(readOnly = true)
@GetMapping(value = API_PACKAGING_UNITS + "/amount", params = {"locationGroupName"})
public ResponseEntity<Measurable> findQuantityInLG(
@RequestParam(value = "productPKey", required = false) String productPKey,
@RequestParam(value = "sku", required = false) String sku,
@RequestParam("locationGroupName") String locationGroupName
) {
if (productPKey != null && !productPKey.isEmpty()) {
var quantity = finder.findAmountOfProductByPKey(productPKey, locationGroupName);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Quantity of Product with pKey [{}] in LocationGroup [{}] is [{}]", productPKey, locationGroupName, quantity);
}
return ResponseEntity.ok(quantity);
} else if (sku != null && !sku.isEmpty()) {
var quantity = finder.findAmountOfProductBySKU(sku, locationGroupName);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Quantity of Product with sku [{}] in LocationGroup [{}] is [{}]", sku, locationGroupName, quantity);
}
return ResponseEntity.ok(quantity);
} else {
throw new IllegalArgumentException("Either the pKey or the SKU of the Product must be given");
}
}
@Transactional(readOnly = true)
@GetMapping(value = API_PACKAGING_UNITS + "/amount", params = {"sku", "erpCode"})
public ResponseEntity<Measurable> findQuantityOnLocation(
@RequestParam(value = "sku") String sku,
@RequestParam("erpCode") String erpCode
) {
var quantity = finder.findAmountOfProductBySKUOnLocation(sku, erpCode);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Quantity of Product with sku [{}] on Location [{}] is [{}]", sku, erpCode, quantity);
}
return ResponseEntity.ok(quantity);
}
@Transactional(readOnly = true)
@GetMapping(value = API_PACKAGING_UNITS, params = {"sku", "amount"})
public ResponseEntity<List<PackagingUnitVO>> findForProduct(
@RequestParam("sku") String sku,
@RequestParam("amount") String amount,
@RequestParam(value = "sortDirection", required = false) @Pattern(regexp = "^(asc|desc)$") String sortDirection,
@RequestParam(value = "sortProperty", required = false) String sortProperty,
@RequestHeader(value = HttpHeaders.ACCEPT, required = false) String acceptHeader
) {
int noResults;
if ("all".equalsIgnoreCase(amount) || "*".equals(amount) || "%".equals(amount)) {
noResults = Integer.MAX_VALUE;
} else {
noResults = Integer.parseInt(amount);
}
var eos = finder.findForSku(sku, noResults, sortDirection, sortProperty);
if (ACCEPT_HEADER_PU.equals(acceptHeader)) {
return ResponseEntity.ok(enrichWithLinks(eos.toArray(new PackagingUnit[0])));
}
return ResponseEntity.ok(packagingUnitMapper.convert(eos, new CycleAvoidingMappingContext()));
}
protected List<PackagingUnitVO> enrichWithLinks(PackagingUnit... eos) {
if (packagingUnitTransformer != null) {
return packagingUnitTransformer.enrichWithLinks(eos);
}
return Arrays.stream(eos).map(eo -> {
var vo = packagingUnitMapper.convert(eo, new CycleAvoidingMappingContext());
var product = eo.getProduct();
vo.product = ProductVO.newBuilder().sku(product.getSku()).baseUnit(product.getBaseUnit()).label(product.getLabel()).build();
if (eo.hasLoadUnit()) {
vo.add(WebMvcLinkBuilder.linkTo(methodOn(LoadUnitController.class).findByPKey(eo.getLoadUnit().getPersistentKey())).withRel("loadUnit"));
vo.add(linkTo(methodOn(TransportUnitController.class).findByPKey(eo.getLoadUnit().getTransportUnit().getPersistentKey())).withRel("transportUnit"));
}
vo.add(WebMvcLinkBuilder.linkTo(methodOn(ProductController.class).findByPKey(eo.getProduct().getPersistentKey())).withRel("product"));
return vo;
}).toList();
}
}