You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Optaplanner/Timefold订单分配场景下库存管理方案问询

库存管理替代实现方案(Optaplanner/Timefold)

方案1:将库存抽象为独立ProblemFact实体

把库存定义成单独的Stock实体,替代原有的Map结构,更贴合Optaplanner的问题事实设计规范,也方便后续扩展库存相关属性(如库存地点、有效期等)。

@ProblemFact
public class Stock {
    private Product product;
    private Integer availableBoxes;

    // 构造器、getter/setter方法
}

修改Solution类,将库存集合标记为问题事实:

@Solution
public class Solution {
    @PlanningEntityCollectionProperty
    private List<Order> orders;
    @ProblemFactCollectionProperty
    private List<Vehicle> vehicles;
    @ProblemFactCollectionProperty
    private List<Stock> stockList;

    // 辅助方法:快速查询指定产品的可用库存
    public Integer getAvailableStock(Product product) {
        return stockList.stream()
                .filter(s -> s.getProduct().equals(product))
                .findFirst()
                .map(Stock::getAvailableBoxes)
                .orElse(0);
    }
}

这种方式结构清晰,约束编写时可直接基于问题事实集合做累计计算。

方案2:用Shadow变量自动跟踪库存消耗

若需实时维护每个产品的已消耗库存,可为Stock实体添加影子变量,由框架自动更新,避免手动遍历订单计算累计值。

首先在Stock中定义影子变量:

@ProblemFact
public class Stock {
    private Product product;
    private Integer availableBoxes;
    @ShadowVariable(sourceVariableName = "vehicle", 
                    variableListenerClass = StockConsumptionListener.class)
    private Integer consumedBoxes;

    // 计算剩余库存的辅助方法
    public Integer getRemainingBoxes() {
        return availableBoxes - (consumedBoxes == null ? 0 : consumedBoxes);
    }

    // getter/setter、构造器
}

实现VariableListener处理库存消耗的更新逻辑:

public class StockConsumptionListener implements VariableListener<Order> {
    @Override
    public void afterVariableChanged(ScoreDirector scoreDirector, Order order) {
        Product product = order.getProduct();
        Stock stock = findStockForProduct(scoreDirector, product);
        if (stock == null) return;

        // 扣除旧分配带来的库存消耗(若之前已分配车辆)
        Order oldOrder = (Order) scoreDirector.getWorkingObject(order);
        if (oldOrder.getVehicle() != null) {
            scoreDirector.beforeVariableChanged(stock, "consumedBoxes");
            stock.setConsumedBoxes(stock.getConsumedBoxes() - oldOrder.getAmountOfBoxes());
            scoreDirector.afterVariableChanged(stock, "consumedBoxes");
        }

        // 添加新分配带来的库存消耗
        if (order.getVehicle() != null) {
            scoreDirector.beforeVariableChanged(stock, "consumedBoxes");
            stock.setConsumedBoxes(stock.getConsumedBoxes() + order.getAmountOfBoxes());
            scoreDirector.afterVariableChanged(stock, "consumedBoxes");
        }
    }

    private Stock findStockForProduct(ScoreDirector scoreDirector, Product product) {
        Solution solution = (Solution) scoreDirector.getWorkingSolution();
        return solution.getStockList().stream()
                .filter(s -> s.getProduct().equals(product))
                .findFirst()
                .orElse(null);
    }

    // 实现其他必需的VariableListener方法(beforeVariableChanged等,可留空或做对应处理)
}

这种方案下,库存消耗由框架自动维护,约束只需判断stock.getRemainingBoxes() >= 0即可,无需手动计算累计值。

方案3:在约束中直接计算累计消耗(无需修改现有结构)

如果不想改动当前实体结构,可直接在约束定义里用Stream API计算指定产品的总消耗,再与库存对比:

public class OrderAssignmentConstraints {
    @Constraint
    public Constraint stockAvailability(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(Order.class)
                .groupBy(Order::getProduct, sum(Order::getAmountOfBoxes))
                .join(Stock.class, Joiners.equal(Function.identity(), Stock::getProduct))
                .filter((productTotalConsumed, stock) -> productTotalConsumed.getSecond() > stock.getAvailableBoxes())
                .penalize("Exceeded stock", HardSoftScore.ONE_HARD,
                        (productTotalConsumed, stock) -> productTotalConsumed.getSecond() - stock.getAvailableBoxes());
    }
}

如果仍用原有的Map<Product, Integer>存储库存,可调整约束逻辑直接从Solution中获取库存值:

return constraintFactory.forEach(Order.class)
        .groupBy(Order::getProduct, sum(Order::getAmountOfBoxes))
        .filter((product, totalConsumed) -> totalConsumed > getStockFromSolution(constraintFactory, product))
        .penalize("Exceeded stock", HardSoftScore.ONE_HARD,
                (product, totalConsumed) -> totalConsumed - getStockFromSolution(constraintFactory, product));
}

private Integer getStockFromSolution(ConstraintFactory constraintFactory, Product product) {
    Solution solution = (Solution) constraintFactory.getSolutionDescriptor().getSolutionClass()
            .cast(constraintFactory.getScoreDirector().getWorkingSolution());
    return solution.getStock().getOrDefault(product, 0);
}

关于ValueRangeProviders的说明

ValueRangeProviders的作用是给PlanningVariable提供可选值范围(比如车辆的备选列表),而库存管理属于约束校验范畴,并非变量取值范围的定义,所以不适合用它来实现库存逻辑,这也是你之前尝试没有进展的原因。


内容的提问来源于stack exchange,提问作者Gustavo André

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 17:23:20