Commit be4b7722 authored by Tong Li's avatar Tong Li

MP

parent d1745f27
package com.aps.controller; package com.aps.controller;
import com.aps.common.util.FlatParquetUtil; import com.aps.common.util.FlatParquetUtil;
import com.aps.common.util.ParamValidator;
import com.aps.common.util.R; import com.aps.common.util.R;
import com.aps.entity.ApsDemandOrder; import com.aps.entity.ApsDemandOrder;
import com.aps.entity.MaterialInfo; import com.aps.entity.MaterialInfo;
...@@ -13,8 +12,9 @@ import com.aps.macroplanner.data.MacroPlannerDataConverter; ...@@ -13,8 +12,9 @@ import com.aps.macroplanner.data.MacroPlannerDataConverter;
import com.aps.macroplanner.data.TestDataBuilder; import com.aps.macroplanner.data.TestDataBuilder;
import com.aps.macroplanner.output.ResultWriter; import com.aps.macroplanner.output.ResultWriter;
import com.aps.macroplanner.output.dto.*; import com.aps.macroplanner.output.dto.*;
import com.aps.service.MacroPlannerResultService;
import com.aps.service.MacroPlannerProductNetworkService; import com.aps.service.MacroPlannerProductNetworkService;
import com.aps.service.MacroPlannerResultService;
import com.aps.service.MacroPlannerProductNetworkService1;
import com.aps.service.MpPispipResultPersistenceService; import com.aps.service.MpPispipResultPersistenceService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.ortools.Loader; import com.google.ortools.Loader;
...@@ -456,7 +456,7 @@ public class MacroPlannerResultController { ...@@ -456,7 +456,7 @@ public class MacroPlannerResultController {
description = "成品→半成品→原材料完整BOM供应链树。" description = "成品→半成品→原材料完整BOM供应链树。"
+ "包含各层级的供应来源(含每周期产量)、消耗者、BOM子物料(含每周期消耗量)。" + "包含各层级的供应来源(含每周期产量)、消耗者、BOM子物料(含每周期消耗量)。"
+ "支持按 productId / spId / unitId / periodIndex 可选过滤。") + "支持按 productId / spId / unitId / periodIndex 可选过滤。")
public R<SupplyChainNode> getProductNetwork( public R<TraceNode> getProductNetwork(
@RequestParam("sceneId") @Parameter(description = "场景ID", required = true) String sceneId, @RequestParam("sceneId") @Parameter(description = "场景ID", required = true) String sceneId,
@RequestParam(value = "productId", required = false) @Parameter(description = "按产品ID过滤成品根节点(可选)") String productId, @RequestParam(value = "productId", required = false) @Parameter(description = "按产品ID过滤成品根节点(可选)") String productId,
@RequestParam(value = "spId", required = false) @Parameter(description = "按库存点ID过滤(可选)") String spId, @RequestParam(value = "spId", required = false) @Parameter(description = "按库存点ID过滤(可选)") String spId,
...@@ -464,7 +464,7 @@ public class MacroPlannerResultController { ...@@ -464,7 +464,7 @@ public class MacroPlannerResultController {
if (productId == null || productId.trim().isEmpty() || spId == null || spId.trim().isEmpty()) { if (productId == null || productId.trim().isEmpty() || spId == null || spId.trim().isEmpty()) {
return R.failed("productId 和 spId 不能为空"); return R.failed("productId 和 spId 不能为空");
} }
SupplyChainNode node = macroPlannerProductNetworkService.buildProductNetwork( TraceNode node = macroPlannerProductNetworkService.buildProductNetwork(
sceneId, productId.trim(), spId.trim(), period); sceneId, productId.trim(), spId.trim(), period);
if (node == null) { if (node == null) {
return R.failed("未找到场景 " + sceneId + " 中 " + productId + "@" + spId + " 的产品网络"); return R.failed("未找到场景 " + sceneId + " 中 " + productId + "@" + spId + " 的产品网络");
......
...@@ -36,7 +36,6 @@ import java.util.Map; ...@@ -36,7 +36,6 @@ import java.util.Map;
* 求解器被激励去增加生产来满足需求,而非简单地降低销售量。</p> * 求解器被激励去增加生产来满足需求,而非简单地降低销售量。</p>
* *
* @see BalanceConstraint 物料平衡约束(DemandSlack 在流入侧) * @see BalanceConstraint 物料平衡约束(DemandSlack 在流入侧)
* @see DemandVariableBuilder 需求变量定义(SalesDemandQty, DemandSlack)
*/ */
public class DemandSlackLinkageConstraint { public class DemandSlackLinkageConstraint {
......
...@@ -323,81 +323,82 @@ public class ResultWriter { ...@@ -323,81 +323,82 @@ public class ResultWriter {
private BomStructureData buildBomStructureData() { private BomStructureData buildBomStructureData() {
BomStructureData s = new BomStructureData(); BomStructureData s = new BomStructureData();
for (Product p : data.getProducts()) { // for (Product p : data.getProducts()) {
BomStructureData.ProductInfo pi = new BomStructureData.ProductInfo(); // BomStructureData.ProductInfo pi = new BomStructureData.ProductInfo();
pi.id = p.getId(); // pi.id = p.getId();
pi.name = p.getName(); // pi.name = p.getName();
pi.code = p.getCode(); // pi.code = p.getCode();
s.products.add(pi); // s.products.add(pi);
} // }
for (StockingPoint sp : data.getStockingPoints()) { // for (StockingPoint sp : data.getStockingPoints()) {
BomStructureData.StockingPointInfo si = new BomStructureData.StockingPointInfo(); // BomStructureData.StockingPointInfo si = new BomStructureData.StockingPointInfo();
si.id = sp.getId(); // si.id = sp.getId();
si.name = sp.getName(); // si.name = sp.getName();
s.stockingPoints.add(si); // s.stockingPoints.add(si);
} // }
// 产品→库存点映射 (通过 getStockingPointsForProduct 反推) // 产品→库存点映射 (通过 getStockingPointsForProduct 反推)
for (Product p : data.getProducts()) { // for (Product p : data.getProducts()) {
for (StockingPoint sp : data.getStockingPointsForProduct(p.getId())) { // for (StockingPoint sp : data.getStockingPointsForProduct(p.getId())) {
BomStructureData.ProductSpMappingInfo mi = new BomStructureData.ProductSpMappingInfo(); // BomStructureData.ProductSpMappingInfo mi = new BomStructureData.ProductSpMappingInfo();
mi.productId = p.getId(); // mi.productId = p.getId();
mi.spId = sp.getId(); // mi.spId = sp.getId();
s.productSpMappings.add(mi); // s.productSpMappings.add(mi);
} // }
} // }
for (Operation op : data.getOperations()) { // for (Operation op : data.getOperations()) {
BomStructureData.OperationInfo oi = new BomStructureData.OperationInfo(); // BomStructureData.OperationInfo oi = new BomStructureData.OperationInfo();
oi.id = op.getId(); // oi.id = op.getId();
oi.name = op.getName(); // oi.name = op.getName();
oi.leadTimeDays = op.getLeadTimeDays(); // oi.leadTimeDays = op.getLeadTimeDays();
oi.hasLotSize = op.hasLotSize(); // oi.hasLotSize = op.hasLotSize();
oi.lotSize = op.hasLotSize() ? op.getLotSize() : null; // oi.lotSize = op.hasLotSize() ? op.getLotSize() : null;
for (OperationOutput oo : op.getOutputs()) { // for (OperationOutput oo : op.getOutputs()) {
BomStructureData.OperationOutputInfo ooi = new BomStructureData.OperationOutputInfo(); // BomStructureData.OperationOutputInfo ooi = new BomStructureData.OperationOutputInfo();
ooi.productId = oo.getProductId(); // ooi.productId = oo.getProductId();
ooi.spId = oo.getSpId(); // ooi.spId = oo.getSpId();
oi.outputs.add(ooi); // oi.outputs.add(ooi);
} // }
for (UnitOperation uo : op.getUnitOperations()) { // for (UnitOperation uo : op.getUnitOperations()) {
BomStructureData.UnitOperationInfo uoi = new BomStructureData.UnitOperationInfo(); // BomStructureData.UnitOperationInfo uoi = new BomStructureData.UnitOperationInfo();
uoi.unitId = uo.getUnitId(); // uoi.unitId = uo.getUnitId();
uoi.unitName = uo.getUnitName(); // uoi.unitName = uo.getUnitName();
uoi.capacityCoeff = uo.getCapacityCoeff(); // uoi.capacityCoeff = uo.getCapacityCoeff();
oi.unitOperations.add(uoi); // oi.unitOperations.add(uoi);
} // }
s.operations.add(oi); // s.operations.add(oi);
} // }
for (OperationInput input : data.getOperationInputs()) { for (OperationInput input : data.getOperationInputs()) {
BomStructureData.OperationInputInfo ii = new BomStructureData.OperationInputInfo(); BomStructureData.OperationInputInfo ii = new BomStructureData.OperationInputInfo();
ii.operationId = input.getOperation().getId(); ii.operationId = input.getOperation().getId();
ii.inputProductId = input.getInputProduct().getId(); ii.inputProductId = input.getInputProduct().getId();
ii.inputProductCode = input.getInputProduct().getCode();
ii.inputSpId = input.getInputSp().getId(); ii.inputSpId = input.getInputSp().getId();
ii.inputSpName = input.getInputSp().getName(); ii.inputSpName = input.getInputSp().getName();
ii.factor = input.getFactor(); ii.factor = input.getFactor();
s.operationInputs.add(ii); s.operationInputs.add(ii);
} }
for (Period p : data.getPeriods()) { // for (Period p : data.getPeriods()) {
BomStructureData.PeriodInfo pi = new BomStructureData.PeriodInfo(); // BomStructureData.PeriodInfo pi = new BomStructureData.PeriodInfo();
pi.index = p.getIndex(); // pi.index = p.getIndex();
pi.name = p.getName(); // pi.name = p.getName();
pi.startDate = p.getStartDate() == null ? null : p.getStartDate().toString(); // pi.startDate = p.getStartDate() == null ? null : p.getStartDate().toString();
s.periods.add(pi); // s.periods.add(pi);
} // }
for (InitialInventory inv : data.getInitialInventories()) { // for (InitialInventory inv : data.getInitialInventories()) {
BomStructureData.InitialInventoryInfo ii = new BomStructureData.InitialInventoryInfo(); // BomStructureData.InitialInventoryInfo ii = new BomStructureData.InitialInventoryInfo();
ii.productId = inv.getProduct().getId(); // ii.productId = inv.getProduct().getId();
ii.spId = inv.getStockingPoint().getId(); // ii.spId = inv.getStockingPoint().getId();
ii.quantity = inv.getQuantity(); // ii.quantity = inv.getQuantity();
s.initialInventories.add(ii); // s.initialInventories.add(ii);
} // }
for (InTransitSupply its : data.getInTransitSupplies()) { // for (InTransitSupply its : data.getInTransitSupplies()) {
BomStructureData.InTransitSupplyInfo ti = new BomStructureData.InTransitSupplyInfo(); // BomStructureData.InTransitSupplyInfo ti = new BomStructureData.InTransitSupplyInfo();
ti.productId = its.getProduct().getId(); // ti.productId = its.getProduct().getId();
ti.spId = its.getStockingPoint().getId(); // ti.spId = its.getStockingPoint().getId();
ti.arrivalDate = its.getArrivalDate() == null ? null : its.getArrivalDate().toString(); // ti.arrivalDate = its.getArrivalDate() == null ? null : its.getArrivalDate().toString();
ti.quantity = its.getQuantity(); // ti.quantity = its.getQuantity();
s.inTransitSupplies.add(ti); // s.inTransitSupplies.add(ti);
} // }
return s; return s;
} }
......
...@@ -64,6 +64,7 @@ public class BomStructureData { ...@@ -64,6 +64,7 @@ public class BomStructureData {
public static class OperationInputInfo { public static class OperationInputInfo {
public String operationId; public String operationId;
public String inputProductId; public String inputProductId;
public String inputProductCode;
public String inputSpId; public String inputSpId;
public String inputSpName; public String inputSpName;
public double factor; public double factor;
......
package com.aps.macroplanner.output.dto;
/**
* 作者:佟礼
* 时间:2026-09-20
*/
import java.util.ArrayList;
import java.util.List;
/**
* 产品数量追溯网络节点。
*
* <pre>
* Product + Warehouse
* |
* | demand
* v
* Unit
* |
* | productionQty
* v
* BOM
* |
* v
* Product + Warehouse
* |
* v
* 最近历史 Output
* </pre>
*/
public class TraceNode {
/**
* 产品 ID
*/
public String productId;
/**
* 产品编码
*/
public String productCode;
/**
* 库房 / SP
*/
public String warehouseId;
/**
* 库房名称
*/
public String warehouseName;
/**
* 当前节点需求数量。
*
* 根节点:
* 成品需求量
*
* 子节点:
* Unit生产量 × BOM factor
*/
public double quantity;
/**
* BOM factor。
*
* 根节点一般为 1。
*/
public double factor = 1.0;
/**
* 当前节点匹配到的 Output 总数量。
*
* 注意:
* outputQty 可以 > quantity。
*/
public double outputQty;
/**
* 当前节点库存/库房已有供应量。
*
* 这里表示没有通过生产 Output 继续追溯的数量。
*/
public double inventorySupplyQty;
/**
* 当前节点匹配到的生产 Output。
*
* 一个产品可能对应多个 Output。
*/
// public final List<OutputNode> outputs = new ArrayList<>();
/**
* 当前节点对应的 Unit。
*
* 不同 Unit 必须独立。
*/
public final List<UnitNode> units = new ArrayList<>();
/**
* 实际生产 Output 节点。
*/
public static class OutputNode {
/**
* Output 对应的 dataKey。
*/
public String dataKey;
/**
* 产品 ID。
*/
public String productId;
/**
* 产品 Code。
*/
public String productCode;
/**
* 库房。
*/
public String warehouseId;
public String warehouseName;
/**
* Output 日期/期间。
*/
public Integer periodIndex;
/**
* Output 日期。
*
* 如果项目实际使用 LocalDate/LocalDateTime,
* 可以替换这里的类型。
*/
public String outputDate;
/**
* Output 实际数量。
*
* 可以大于当前 demand。
*/
public double outputQty;
/**
* 对应生产 Unit。
*/
public UnitNode unit;
}
/**
* 独立生产 Unit 节点。
*
* 每个 Unit 单独展开 BOM。
*/
public static class UnitNode {
public String unitId;
public String unitName;
/**
* 当前 Unit 的实际生产量。
*/
public double productionQty;
/**
* 产能占用。
*/
public double capacityUsed;
/**
* 工序。
*/
public String operationId;
public String operationName;
/**
* 当前 Unit 的 BOM 上游节点。
*/
public final List<TraceNode> children =
new ArrayList<>();
}
}
package com.aps.macroplanner.output.dto;
/**
* 作者:佟礼
* 时间:2026-09-20
* 数量追溯用的实际生产Output。
*
* OutputInfo + PeriodTaskResult 合并。
*/
public class TraceOutput {
public String productId;
public String warehouseId;
public String warehouseName;
public String dataKey;
public Integer periodIndex;
public double outputQty;
public String outputDate;
public String operationId;
public String operationName;
public String unitId;
public String unitName;
public double capacityUsed;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment