ProductServiceImpl.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.BusinessRuntimeException;
import org.ameba.exception.NotFoundException;
import org.ameba.i18n.Translator;
import org.openwms.core.listener.RemovalNotAllowedException;
import org.openwms.core.units.converter.Units;
import org.openwms.wms.inventory.Product;
import org.openwms.wms.inventory.ProductMapper;
import org.openwms.wms.inventory.ProductRepository;
import org.openwms.wms.inventory.ProductService;
import org.openwms.wms.inventory.events.ProductEvent;
import org.openwms.wms.registration.RegistrationService;
import org.openwms.wms.registration.events.EntityType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Optional;
import static org.openwms.wms.InventoryMessageCodes.PRODUCT_MULTIPLE_LABELS;
import static org.openwms.wms.InventoryMessageCodes.PRODUCT_NOT_FOUND_PKEY;
import static org.openwms.wms.InventoryMessageCodes.PRODUCT_NOT_FOUND_SKU;
/**
* A ProductServiceImpl.
*
* @author Heiko Scherrer
*/
@Validated
@TxService
class ProductServiceImpl implements ProductService {
private static final Logger LOGGER = LoggerFactory.getLogger(ProductServiceImpl.class);
private final Translator translator;
private final ProductMapper productMapper;
private final ProductRepository productRepository;
private final UomRelationRepository uomRelationRepository;
private final ApplicationEventPublisher publisher;
private RegistrationService registrationService;
ProductServiceImpl(Translator translator, ProductMapper productMapper, ProductRepository productRepository,
UomRelationRepository uomRelationRepository, ApplicationEventPublisher publisher) {
this.translator = translator;
this.productMapper = productMapper;
this.productRepository = productRepository;
this.uomRelationRepository = uomRelationRepository;
this.publisher = publisher;
}
@Autowired
public void setRegistrationService(RegistrationService registrationService) {
this.registrationService = registrationService;
}
protected ProductRepository getProductRepository() {
return productRepository;
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public @NotNull Product create(@Valid @NotNull Product product) {
return createInternal(product);
}
private Product createInternal(Product product) {
var existing = productRepository.findBySku(product.getSku());
if (existing.isPresent()) {
return existing.get();
}
var units = product.getUnits();
if (units != null && !units.isEmpty()) {
units.forEach(u -> u.setProduct(product));
}
var saved = productRepository.save(product);
publisher.publishEvent(new ProductEvent(saved, ProductEvent.TYPE.CREATED));
return saved;
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public void createAll(@NotNull List<Product> products) {
products.forEach(this::createInternal);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public @NotNull List<Product> findAll() {
return productRepository.findAll();
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public Product findBy(@NotBlank String pKey) {
return findByInternal(pKey);
}
private Product findByInternal(String pKey) {
return productRepository.findBypKey(pKey).orElseThrow(() -> new NotFoundException(translator, PRODUCT_NOT_FOUND_PKEY, new String[]{pKey}, pKey));
}
/**
* {@inheritDoc}
*/
@Override
@Measured
@Transactional(readOnly = true)
public Product findBySKUorThrow(@NotBlank String sku) {
return productRepository.findBySku(sku).orElseThrow(() -> new NotFoundException(translator, PRODUCT_NOT_FOUND_SKU, new String[]{sku}, sku));
}
/**
* {@inheritDoc}
*/
@Override
@Measured
@Transactional(readOnly = true)
public Product findByLabelOrSKUOrThrow(@NotBlank String bk) {
Product result;
var optProducts = productRepository.findByLabel(bk);
if (optProducts.isEmpty()) {
result = productRepository.findBySku(bk).orElseThrow(() -> new NotFoundException(translator, PRODUCT_NOT_FOUND_SKU, new String[]{bk}, bk));
} else {
if (optProducts.size() > 1) {
throw new BusinessRuntimeException(translator, PRODUCT_MULTIPLE_LABELS, new String[]{bk}, bk);
}
result = optProducts.get(0);
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
@Measured
@Transactional(readOnly = true)
public Optional<Product> findBySKU(@NotBlank String sku) {
return productRepository.findBySku(sku);
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public Optional<Product> findBySKUandUOM(@NotBlank String sku, @NotBlank String uom) {
var unit = Units.getMeasurableOptional(uom);
if (unit.isEmpty()) {
LOGGER.error("Unit with name [{}] does not exist", uom);
return Optional.empty();
}
var uomRelation = uomRelationRepository.findByProduct_SkuAndUnit(sku, unit.get());
if (uomRelation.isEmpty()) {
return Optional.empty();
}
return Optional.of(uomRelation.get().getProduct());
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public @NotNull Product update(@NotNull Product entity) {
var existingOne = findByInternal(entity.getPersistentKey());
productMapper.copy(entity, existingOne);
existingOne = productRepository.save(existingOne);
publisher.publishEvent(new ProductEvent(existingOne, ProductEvent.TYPE.UPDATED));
return existingOne;
}
/**
* {@inheritDoc}
*/
@Override
@Measured
public void delete(@NotBlank String pKey) {
var existingOne = findByInternal(pKey);
if (productRepository.productInUseByLU(existingOne) > 0) {
throw new RemovalNotAllowedException("Removal of Product is not allowed, because it is still used by one or more LoadUnits");
}
if (productRepository.productInUseByUomRelation(existingOne) > 0) {
throw new RemovalNotAllowedException("Removal of Product is not allowed, because it is still used in one or more UOM definitions");
}
if (productRepository.productInUseByPU(existingOne) > 0) {
throw new RemovalNotAllowedException("Removal of Product is not allowed, because it is still used by one or more PackagingUnits");
}
if (registrationService.registrarsExist(EntityType.PRODUCT)) {
registrationService.remove(EntityType.PRODUCT, pKey);
}
productRepository.delete(existingOne);
publisher.publishEvent(new ProductEvent(existingOne, ProductEvent.TYPE.DELETED));
}
}