LoadUnitServiceImpl.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.impl;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.ameba.annotation.Measured;
import org.ameba.annotation.TxService;
import org.ameba.exception.BadRequestException;
import org.ameba.exception.NotFoundException;
import org.ameba.exception.ResourceExistsException;
import org.ameba.i18n.Translator;
import org.openwms.wms.inventory.LoadUnit;
import org.openwms.wms.inventory.LoadUnitService;
import org.openwms.wms.inventory.LoadUnitType;
import org.openwms.wms.inventory.LoadUnitTypeService;
import org.openwms.wms.inventory.Product;
import org.openwms.wms.inventory.ProductService;
import org.openwms.wms.inventory.api.ProductVO;
import org.openwms.wms.inventory.events.LoadUnitEvent;
import org.openwms.wms.transport.TransportUnit;
import org.openwms.wms.transport.TransportUnitService;
import org.openwms.wms.transport.barcode.Barcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Optional;
import static java.lang.String.format;
import static org.openwms.wms.InventoryMessageCodes.LOAD_UNIT_ALREADY_ASSIGNED;
import static org.openwms.wms.InventoryMessageCodes.LOAD_UNIT_NOT_FOUND;
import static org.openwms.wms.InventoryMessageCodes.LOAD_UNIT_NOT_FOUND_BY_PK;
import static org.openwms.wms.InventoryMessageCodes.LOAD_UNIT_NOT_FOUND_ON_TU;
import static org.openwms.wms.InventoryMessageCodes.TRANSPORT_UNIT_ALREADY_DIVIDED;
/**
* A LoadUnitServiceImpl.
*
* @author Heiko Scherrer
*/
@Validated
@TxService
class LoadUnitServiceImpl implements LoadUnitService {
private static final Logger LOGGER = LoggerFactory.getLogger(LoadUnitServiceImpl.class);
private final Translator translator;
private final LoadUnitRepository repository;
private final PackagingUnitRepository packagingUnitRepository;
private final LoadUnitTypeService loadUnitTypeService;
private final ProductService productService;
private final TransportUnitService service;
private final ApplicationEventPublisher eventPublisher;
LoadUnitServiceImpl(Translator translator, LoadUnitRepository repository,
PackagingUnitRepository packagingUnitRepository,
LoadUnitTypeService loadUnitTypeRepository,
ProductService productService, TransportUnitService service,
ApplicationEventPublisher eventPublisher) {
this.translator = translator;
this.repository = repository;
this.packagingUnitRepository = packagingUnitRepository;
this.loadUnitTypeService = loadUnitTypeRepository;
this.productService = productService;
this.service = service;
this.eventPublisher = eventPublisher;
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public LoadUnit create(@NotBlank String transportUnitBK, @NotBlank String position, @NotBlank String loadUnitType) {
var loadUnit = findInternal(transportUnitBK, position);
if (loadUnit.isPresent()) {
throw new ResourceExistsException(format("LoadUnit [%s] already exists on TransportUnit [%s] and cannot be created", position, transportUnitBK));
}
var type = getLoadUnitType(loadUnitType);
var toCreate = createInternal(findTransportUnit(transportUnitBK), type, position);
return repository.save(toCreate);
}
private TransportUnit findTransportUnit(String transportUnitBK) {
return service.findOneByOrThrow(transportUnitBK);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public void divide(@NotBlank String transportUnitBK, @NotBlank String loadUnitType, int parts) {
List<LoadUnit> lus = repository.findByTransportUnit_transportUnitBK(Barcode.of(transportUnitBK));
if (!lus.isEmpty()) {
LOGGER.error("The TransportUnit with the BK [{}] has already [{}] LoadUnits on top and cannot be divided",
transportUnitBK, lus.size());
throw new ResourceExistsException(translator, TRANSPORT_UNIT_ALREADY_DIVIDED,
new String[]{transportUnitBK}, transportUnitBK);
}
TransportUnit tu = findTransportUnit(transportUnitBK);
LoadUnitType type = getLoadUnitType(loadUnitType);
for(int i = 1; i <= parts; i++) {
createInternal(tu, type, "" + i);
}
}
private LoadUnit createInternal(TransportUnit tu, LoadUnitType type, String position) {
LOGGER.debug("Creating LoadUnit [{}] in TU [{}] of type [{}]", position, tu.getTransportUnitBK(), type.getType());
LoadUnit loadUnit = new LoadUnit(tu, position, type);
LoadUnit saved = repository.saveAndFlush(loadUnit);
eventPublisher.publishEvent(new LoadUnitEvent(saved, LoadUnitEvent.TYPE.CREATED));
return saved;
}
private LoadUnitType getLoadUnitType(String loadUnitType) {
return loadUnitTypeService.findByType(loadUnitType).orElseThrow(
() -> new NotFoundException(format("LoadUnitType with type [%s] does not exist", loadUnitType))
);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public void load(@NotBlank String pKey, @NotNull @Valid ProductVO product) {
LoadUnit existing = findInternal(pKey);
if (existing.getProduct() != null) {
throw BadRequestException.createFromKey(translator,
LOAD_UNIT_ALREADY_ASSIGNED,
new String[]{ pKey, existing.getProduct().getSku() },
pKey, existing.getProduct().getSku());
}
Product vo = productService.findBySKU(product.getSku())
.orElseThrow(() -> new NotFoundException(format("Product with SKU [%s] does not exist", product.getSku())));
existing.assignProduct(vo);
}
private LoadUnit findInternal(String pKey) {
return repository.findBypKey(pKey).orElseThrow(() -> new NotFoundException(
translator,
LOAD_UNIT_NOT_FOUND,
new String[]{pKey},
pKey
));
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public Optional<List<LoadUnit>> find(@NotBlank String transportUnitBK) {
List<LoadUnit> loadUnits = repository.findByTransportUnit_transportUnitBK(Barcode.of(transportUnitBK));
return Optional.of(loadUnits);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public Optional<LoadUnit> findOptional(@NotBlank String transportUnitBK, @NotBlank String loadUnitPosition) {
return findInternal(transportUnitBK, loadUnitPosition);
}
private Optional<LoadUnit> findInternal(String transportUnitBK, String loadUnitPosition) {
return repository.findByTransportUnit_transportUnitBKAndPhysicalPosition(transportUnitBK, loadUnitPosition);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public LoadUnit find(@NotBlank String transportUnitBK, @NotBlank String loadUnitPosition) {
return repository.findByTransportUnit_transportUnitBKAndPhysicalPosition(transportUnitBK, loadUnitPosition)
.orElseThrow(() -> new NotFoundException(translator, LOAD_UNIT_NOT_FOUND_ON_TU,
new String[]{loadUnitPosition, transportUnitBK}, loadUnitPosition, transportUnitBK
));
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public LoadUnit save(@NotNull @Valid LoadUnit loadUnit) {
return repository.save(loadUnit);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public LoadUnit findByPKey(@NotBlank String pKey) {
return findInternal(pKey);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public LoadUnit findOrThrow(@NotNull Long pk) {
return repository.findById(pk).orElseThrow(() -> new NotFoundException(translator, LOAD_UNIT_NOT_FOUND_BY_PK,
new Long[]{pk}, pk));
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public boolean isLoadUnitEmpty(@NotBlank String transportUnitBK, @NotBlank String loadUnitPosition) {
return packagingUnitRepository.countPackagingUnits(transportUnitBK, loadUnitPosition) == 0L;
}
}