AllocationServiceImpl.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.allocation.impl;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.ameba.annotation.Measured;
import org.ameba.annotation.TxService;
import org.openwms.core.units.api.Measurable;
import org.openwms.wms.inventory.PackagingUnit;
import org.openwms.wms.inventory.PackagingUnitService;
import org.openwms.wms.inventory.ProductService;
import org.openwms.wms.inventory.Reservation;
import org.openwms.wms.inventory.allocation.Allocation;
import org.openwms.wms.inventory.allocation.AllocationException;
import org.openwms.wms.inventory.allocation.AllocationService;
import org.openwms.wms.inventory.allocation.spi.AllocationRule;
import org.openwms.wms.inventory.allocation.spi.Allocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;

import java.util.Collections;
import java.util.List;
import java.util.UUID;

import static org.openwms.wms.InventoryLoggerCategories.ALLOCATION;

/**
 * A AllocationServiceImpl.
 *
 * @author Heiko Scherrer
 */
@Validated
@TxService
class AllocationServiceImpl implements AllocationService {

    private static final Logger ALLOCATION_LOGGER = LoggerFactory.getLogger(ALLOCATION);
    private final ProductService productService;
    private final PackagingUnitService packagingUnitService;
    private final Allocator allocation;

    AllocationServiceImpl(ProductService productService, PackagingUnitService packagingUnitService, Allocator allocation) {
        this.productService = productService;
        this.packagingUnitService = packagingUnitService;
        this.allocation = allocation;
    }

    /**
     * {@inheritDoc}
     */
    @Measured
    @Override
    public List<Allocation> allocate(@NotNull Measurable amount, @NotBlank String sku,
                                     List<String> sourceLocationGroupNames) {

        if (ALLOCATION_LOGGER.isDebugEnabled()) {
            ALLOCATION_LOGGER.debug("Trying to allocate an mount of [{}] for product [{}] in LocationGroups {}",
                    amount, sku, sourceLocationGroupNames);
        }
        var product = productService.findBySKUorThrow(sku);
        try {
            List<PackagingUnit> packagingUnits = allocation.allocate(new AllocationRule(amount, product, sourceLocationGroupNames));
            if (ALLOCATION_LOGGER.isDebugEnabled()) {
                ALLOCATION_LOGGER.debug("Found [{}] number of PackagingUnits for allocation", packagingUnits.size());
            }
            return packagingUnits.stream()
                    .filter(pu -> !pu.getQtyAvailable().isZero() && !pu.getQtyAvailable().isNegative())
                    .map(pu -> {
                        Measurable qtyReserved;
                        if (pu.getQtyAvailable().compareTo(amount) <= 0) {
                            qtyReserved = pu.getQtyAvailable();
                        } else {
                            qtyReserved = amount;
                        }
                        var reservation = new Reservation(qtyReserved, UUID.randomUUID().toString());
                        reservation.setPackagingUnit(pu);
                        ALLOCATION_LOGGER.debug("On PU [{}] the qtyAvailable is [{}], reservation has been added [{}]", pu.getPersistentKey(), pu.getQtyAvailable(), reservation);
                        pu.addReservation(reservation);
                        packagingUnitService.save(pu);
                        var builder = Allocation.AllocationBuilder.anAllocation()
                                .product(pu.getProduct())
                                .qtyAvailable(qtyReserved)
                                .reservationId(reservation.getReservedBy());
                        if (pu.hasLoadUnit()) {
                            builder.transportUnit(pu.getLoadUnit().getTransportUnit())
                                    .loadUnit(pu.getLoadUnit())
                                    .actualLocation(pu.getLoadUnit().getTransportUnit().getActualLocation());
                        } else {
                            builder.actualLocation(pu.getActualLocation());
                        }
                        return builder.build();
                    })
                    .toList();
        } catch (AllocationException e) {
            ALLOCATION_LOGGER.error(e.getMessage(), e);
            return Collections.emptyList();
        }
    }
}