Commit 9e6f7171 authored by Tong Li's avatar Tong Li

MP-订单未满足原因

parent a8a8f6ea
......@@ -165,12 +165,30 @@ public class MacroPlannerResultController {
.skip((long)(pageNumber-1)*pageSize)
.limit(pageSize)
.collect(Collectors.toList());
if(SalesDemandSummaries!=null&&SalesDemandSummaries.size()>0)
{
List<String> SalesDemandids=SalesDemandSummaries.stream()
.map(SalesDemandResult::getSalesDemandId)
.collect(Collectors.toList());
List<RiskItem> Risks= getSalesDemandReason(sceneId,SalesDemandids);
if(Risks!=null&&Risks.size()>0) {
for (SalesDemandResult entry : SalesDemandSummaries) {
List<RiskItem> Risks1= Risks.stream()
.filter(t->t.getSalesDemandId().equals(entry.getSalesDemandId())&&t.getType()==1)
.collect(Collectors.toList());
entry.setUnmetReasons(Risks1);
List<RiskItem> Risks2= Risks.stream()
.filter(t->t.getSalesDemandId().equals(entry.getSalesDemandId())&&t.getType()==2)
.collect(Collectors.toList());
entry.setRisks(Risks2);
}
}
}
// List<Map<String, Object>> summaries = new ArrayList<>();
// for (SalesDemandResult entry : SalesDemandSummaries) {
//
// }
double totalDemand = lists.stream().mapToDouble(SalesDemandResult::getDemandQty).sum();
double totalFulfilled = lists.stream().mapToDouble(SalesDemandResult::getFulfilledQty).sum();
......@@ -188,32 +206,19 @@ public class MacroPlannerResultController {
return null;
}
}
private List<SalesDemandResult> getSalesDemandsResult(String sceneId,String salesDemandId) {
private List<RiskItem> getSalesDemandReason(String sceneId,List<String> salesDemandIds) {
try {
FlatParquetUtil parquetUtil = new FlatParquetUtil();
ResultWriter rw = new ResultWriter();
List<FlatParquetUtil.FilterCondition<?>> filters = new ArrayList<>();
filters.add(FlatParquetUtil.FilterCondition.eq("salesDemandId", salesDemandId));
List<FlatParquetUtil.SortCondition> sorts = Arrays.asList(
FlatParquetUtil.SortCondition.asc("periodIndex")
String salesDemandReasonPath = rw.getOptimizationSalesDemandReasons(sceneId);
);
FlatParquetUtil.QueryRequest request =
new FlatParquetUtil.QueryRequest(
FlatParquetUtil.PageRequest.of(1, 500),
filters,
sorts
);
FlatParquetUtil.PageResult<SalesDemandResult> result =
parquetUtil.queryPage(
rw.getOptimizationSalesDemand(sceneId),
SalesDemandResult.class,
request
);
List<SalesDemandResult> pispips = result.getRecords();
return pispips;
List<RiskItem> list= parquetUtil.readAll(salesDemandReasonPath,RiskItem.class,oi -> salesDemandIds.contains(oi.getSalesDemandId()));
return list;
} catch (Exception e) {
return null;
}
......@@ -678,25 +683,7 @@ public class MacroPlannerResultController {
}
}
private void normalizeUnmetReasons(List<SalesDemandResult> demands) {
for (SalesDemandResult demand : demands) {
List<String> reasons = demand.getUnmetReasons();
if (reasons == null || reasons.isEmpty()) {
continue;
}
List<String> normalized = new ArrayList<>();
for (String reason : reasons) {
if (reason != null && reason.startsWith("库存耗尽(") && reason.endsWith(")")) {
normalized.add("库存耗尽");
normalized.add(reason.substring("库存耗尽".length()));
} else {
normalized.add(reason);
}
}
reasons.clear();
reasons.addAll(normalized);
}
}
private void enrichPispipProductCodes(List<PispipResult> pispips) {
if (pispips == null || pispips.isEmpty()) {
......
......@@ -80,8 +80,8 @@ public class MacroPlannerDataConverterRunner {
optimizer.solve();
ResultWriter writer = new ResultWriter(optimizer.getModel(), optimizer.getData(), 1);
OptimizationResult result = writer.buildResult("Default");
boolean jsonPath = writer.saveResultToFile("Default", result);
OptimizationResult result = writer.buildResult(sceneId);
boolean jsonPath = writer.saveResultToFile(sceneId, result);
System.out.println("===== MACROPLANNER DATA CONVERTER RUNNER END =====");
} finally {
......
......@@ -142,6 +142,12 @@ public class ResultWriter {
String fileName =resultDir.getAbsolutePath()+ "\\salesdemand.parquet";
return fileName;
}
public String getOptimizationSalesDemandReasons(String sceneId) {
File resultDir = getResultDirectory(sceneId);
String fileName =resultDir.getAbsolutePath()+ "\\salesdemandReasons.parquet";
return fileName;
}
public String getOptimizationSalesDemandSummarie(String sceneId) {
File resultDir = getResultDirectory(sceneId);
String fileName =resultDir.getAbsolutePath()+ "\\salesdemandsummarie.parquet";
......@@ -212,6 +218,11 @@ public class ResultWriter {
writeLog("writesalesDemand");
String salesDemandPath = getOptimizationSalesDemand(sceneId);
parquetUtil.write(result.getSalesDemands(), salesDemandPath, SalesDemandResult.class);
String salesDemandReasonPath = getOptimizationSalesDemandReasons(sceneId);
parquetUtil.write(result.getSaleDemandReasons(), salesDemandReasonPath, RiskItem.class);
// List<PispipResult> pispips = parquetUtil.readAll(pispiPath, PispipResult.class);
String salesDemandSummariePath = getOptimizationSalesDemandSummarie(sceneId);
parquetUtil.write(result.getSaleSummarieDemands(), salesDemandSummariePath, SalesDemandResult.class);
......@@ -522,7 +533,9 @@ public class ResultWriter {
writeLog("PeriodTasks");
buildPeriodTasks(sceneId,result);
writeLog("SalesDemands");
buildSalesDemands(result);
List<RiskItem> unmetReasons=new ArrayList<>();
buildSalesDemands(result,unmetReasons);
result.setSaleDemandReasons(unmetReasons);
writeLog("Pispips");
buildPispips(result);
writeLog("UnitCapacitie");
......@@ -535,7 +548,7 @@ public class ResultWriter {
// KPI + 统计
writeLog("Kpi");
result.setKpis(buildKpis());
// result.setKpis(buildKpis());
// result.setStatistics(buildStatistics());
return result;
......@@ -706,7 +719,7 @@ public class ResultWriter {
// ==================== SalesDemandResult ====================
private void buildSalesDemands(OptimizationResult result) {
private void buildSalesDemands(OptimizationResult result,List<RiskItem> unmetReasons) {
for (SalesDemand sd : data.getSalesDemands()) {
SalesDemandResult sr = new SalesDemandResult();
sr.setSalesDemandId(sd.getDemandOrderId()==null? sd.getKey():sd.getDemandOrderId());
......@@ -733,10 +746,10 @@ public class ResultWriter {
sr.setDemandSlack(solutionValue(model.getDemandSlackVars(), invKey).setScale(3, RoundingMode.HALF_UP).doubleValue());
// 未完成原因分析
if (unmet > 0.001) {
// analyzeUnmetReasons(sd, sr.getUnmetReasons());
analyzeUnmetReasons(sd, unmetReasons);
}
// 风险分析
// analyzeRisks(sd, sr.getRisks());
analyzeRisks(sd, unmetReasons);
result.getSalesDemands().add(sr);
}
......@@ -775,7 +788,7 @@ public class ResultWriter {
* <li>无生产工序: 纯采购品, 依赖库存/在途</li>
* </ol>
*/
private void analyzeUnmetReasons(SalesDemand sd, java.util.List<String> reasons) {
private void analyzeUnmetReasons(SalesDemand sd, List<RiskItem> reasons) {
Product prod = sd.getProduct();
StockingPoint sp = sd.getStockingPoint();
Period p = sd.getPeriod();
......@@ -797,22 +810,25 @@ public class ResultWriter {
}
double available = openingInv + totalArrived.setScale(2, RoundingMode.HALF_UP).doubleValue();
if (available < demandQty) {
addReasonWithDetail(reasons, "库存耗尽",
addReasonWithDetail(reasons, sd,"库存耗尽",
"(可用" + fmt(available) + "件, 需求" + fmt(demandQty) + "件)");
}
// 2. 产能瓶颈: 所有生产该产品的单元满负荷 (利用率 > 95%)
boolean hasAnyUnit = false;
boolean allUnitsAtMax = true;
String unitnames="";
for (Operation op : data.getOperationsProducing(prod.getId(), sp.getId())) {
for (UnitOperation uo : op.getUnitOperations()) {
hasAnyUnit = true;
UnitPeriod up = data.getUnitPeriod(uo.getUnitId(), p);
if (up == null || up.isUnlimited()) continue;
BigDecimal totalUsed = BigDecimal.ZERO;
for (Operation unitOp : data.getOperationsByUnitId(uo.getUnitId())) {
for (UnitOperation unitUo : unitOp.getUnitOperations()) {
if (!unitUo.getUnitId().equals(uo.getUnitId())) continue;
unitnames+= unitUo.getUnitName()+";";
totalUsed =totalUsed.add( solutionValue(model.getPtQtyVars(),
unitOp.ptQtyKey(unitUo, p.getIndex())).multiply(BigDecimal.valueOf(unitUo.getCapacityCoeff()) ) );
}
......@@ -823,7 +839,8 @@ public class ResultWriter {
if (!allUnitsAtMax) break;
}
if (hasAnyUnit && allUnitsAtMax) {
reasons.add("产能瓶颈(所有产线满负荷)");
addReasonWithDetail(reasons,sd,"产能瓶颈(所有产线满负荷)",unitnames);
}
// 3. 原材料短缺: BOM 输入物料上期库存见底
......@@ -838,8 +855,12 @@ public class ResultWriter {
InventorySpec inputSpec = data.getInventorySpecFor(inputProd, inputSp, p);
double minInv = inputSpec != null ? inputSpec.getMinLevel() : 0;
if (prevInputInv <= minInv + 0.001) {
String r = "原材料短缺(" + inputProd.getId() + "库存见底)";
if (!reasons.contains(r)) reasons.add(r);
String r = "原材料短缺(" + inputProd.getCode() + "库存见底)";
if (!reasons.contains(r))
{
addReasonWithDetail(reasons,sd,"原材料短缺",r);
}
}
}
}
......@@ -854,8 +875,10 @@ public class ResultWriter {
totalProduced=totalProduced.add( solutionValue(model.getPtQtyVars(), op.ptQtyKey(uo, p.getIndex())));
}
if (totalProduced.doubleValue() > 0.001) {
reasons.add("提前期限制(生产" + fmt(totalProduced.doubleValue()) + "件, 需"
+ op.getLeadTimeDays() + "天后到货)");
String r="提前期限制(生产" + fmt(totalProduced.doubleValue()) + "件, 需"
+ op.getLeadTimeDays() + "天后到货)";
addReasonWithDetail(reasons,sd,"提前期限制",r);
}
}
}
......@@ -863,17 +886,23 @@ public class ResultWriter {
// 5. 无生产工序
boolean hasOperation = !data.getOperationsProducingProduct(prod.getId()).isEmpty();
if (!hasOperation) {
reasons.add("无生产工序(纯采购品, 依赖库存/在途)");
addReasonWithDetail(reasons,sd,"无生产工序","无生产工序(纯采购品, 依赖库存/在途)");
}
if (reasons.isEmpty()) {
reasons.add("供需缺口(可用库存不足)");
addReasonWithDetail(reasons,sd,"供需缺口","供需缺口(可用库存不足)");
}
}
private void addReasonWithDetail(java.util.List<String> reasons, String title, String detail) {
reasons.add(title);
reasons.add(detail);
private void addReasonWithDetail(java.util.List<RiskItem> reasons,SalesDemand sd, String title, String detail) {
addReasonWithDetail(reasons,sd,1,"",title,detail);
}
private void addReasonWithDetail(java.util.List<RiskItem> reasons,SalesDemand sd,int type,String level, String title, String detail) {
RiskItem reason=new RiskItem(sd.getDemandOrderId(),sd.getPeriod().getIndex(),type,level,title,detail);
reasons.add(reason);
}
/**
......@@ -898,7 +927,7 @@ public class ResultWriter {
double endingInv = solutionValue(model.getInvQtyVars(), invKey).setScale(3, RoundingMode.HALF_UP).doubleValue();
InventorySpec spec = data.getInventorySpecFor(prod, sp, p);
if (spec != null && spec.hasMinLevel() && endingInv < spec.getMinLevel() - 0.001) {
addRisk(risks, "高", "安全库存被消耗(期末" + fmt(endingInv) + "件 < 最小" + fmt(spec.getMinLevel()) + "件)");
addRisk(risks,sd, "高", "安全库存被消耗(期末" + fmt(endingInv) + "件 < 最小" + fmt(spec.getMinLevel()) + "件)");
}
// 2. 产线高负荷 (> 90%)
......@@ -919,7 +948,7 @@ public class ResultWriter {
.doubleValue()
: 0D; if (util > 0.90) {
String level = util > 0.95 ? "高" : "低";
addRisk(risks, level, "产线高负荷(" + uo.getUnitId() + "利用率"
addRisk(risks,sd, level, "产线高负荷(" + uo.getUnitId() + "利用率"
+ String.format("%.0f%%", util * 100) + ")");
}
}
......@@ -931,7 +960,7 @@ public class ResultWriter {
unitCount += op.getUnitOperations().size();
}
if (unitCount == 1) {
addRisk(risks, "低", "单一供应源(仅1条产线可生产)");
addRisk(risks,sd, "低", "单一供应源(仅1条产线可生产)");
}
// 4. 原材料提前期长: 关键 BOM 物料 leadTime 长
......@@ -941,7 +970,7 @@ public class ResultWriter {
Product inputProd = input.getInputProduct();
for (Operation upOp : data.getOperationsProducingProduct(inputProd.getId())) {
if (upOp.getLeadTimeDays() >= 2) {
addRisk(risks, "低", "原材料提前期长(" + inputProd.getId() + "采购需"
addRisk(risks, sd,"低", "原材料提前期长(" + inputProd.getId() + "采购需"
+ upOp.getLeadTimeDays() + "天)");
}
}
......@@ -952,16 +981,16 @@ public class ResultWriter {
double fulfilled = solutionValue(model.getSalesDemandQtyVars(), sd.getKey()).setScale(3, RoundingMode.HALF_UP).doubleValue();
double rate = sd.getQuantity() > 0 ? fulfilled / sd.getQuantity() : 1.0;
if (sd.getPriority() > 1.0 && rate < 0.999) {
addRisk(risks, "高", "高优先级需求满足率" + String.format("%.0f%%", rate * 100));
addRisk(risks,sd, "高", "高优先级需求满足率" + String.format("%.0f%%", rate * 100));
}
}
/** 按描述去重后添加风险项 */
private void addRisk(java.util.List<RiskItem> risks, String level, String description) {
private void addRisk(java.util.List<RiskItem> risks,SalesDemand sd, String level, String description) {
for (RiskItem r : risks) {
if (r.getDescription().equals(description)) return;
}
risks.add(new RiskItem(level, description));
addReasonWithDetail(risks,sd,2,level,"",description);
}
/** 格式化数值: 去掉多余小数位 */
private String fmt(double v) {
......@@ -1626,7 +1655,7 @@ public class ResultWriter {
jb.key("fulfillmentRate").val(sr.getFulfillmentRate());
jb.key("priority").val(sr.getPriority());
jb.key("unmetReasons").arr();
for (String r : sr.getUnmetReasons()) jb.val(r);
// for (String r : sr.getUnmetReasons()) jb.val(r);
jb.endArr();
jb.key("risks").arr();
for (RiskItem r : sr.getRisks()) {
......
......@@ -33,6 +33,8 @@ public class OptimizationResult {
private final List<SalesDemandResult> salesDemands = new ArrayList<>();
private List<SalesDemandResult> saleDemandSummaries = new ArrayList<>();
private List<RiskItem> saleDemandReasons = new ArrayList<>();
private final List<PispipResult> pispips = new ArrayList<>();
......@@ -76,8 +78,12 @@ public class OptimizationResult {
public List<SalesDemandResult> getSaleSummarieDemands() { return saleDemandSummaries; }
public List<RiskItem> getSaleDemandReasons() { return saleDemandReasons; }
public void setSaleSummarieDemands(List<SalesDemandResult> v) { saleDemandSummaries=v; }
public void setSaleDemandReasons(List<RiskItem> v) { saleDemandReasons=v; }
public List<PispipResult> getPispips() { return pispips; }
......
......@@ -2,6 +2,7 @@ package com.aps.macroplanner.output.dto;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 作者:佟礼
......@@ -12,16 +13,27 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*
* <p>level 取值: "高" / "低"。</p>
*/
@Data
public class RiskItem {
private final String level;
private final String description;
@JsonCreator
public RiskItem(@JsonProperty("level") String level, @JsonProperty("description") String description) {
private String salesDemandId;
private Integer periodIndex;
private Integer type;
private String level;
private String title;
private String description;
public RiskItem()
{}
public RiskItem(String salesDemandId,int periodIndex,int type,String level,String title, String description) {
this.salesDemandId=salesDemandId;
this.periodIndex=periodIndex;
this.type=type;
this.level = level;
this.title = title;
this.description = description;
}
public String getLevel() { return level; }
public String getDescription() { return description; }
}
......@@ -39,9 +39,9 @@ public class SalesDemandResult {
private Long categoryId;
/** 未完成原因分析 (仅当 unmetQty > 0 时有内容) */
private final List<String> unmetReasons = new ArrayList<>();
private List<RiskItem> unmetReasons = new ArrayList<>();
/** 风险分析 (即使满足也可能存在的供应链风险) */
private final List<RiskItem> risks = new ArrayList<>();
private List<RiskItem> risks = new ArrayList<>();
// ==================== Getters / Setters ====================
......@@ -88,8 +88,10 @@ public class SalesDemandResult {
public double getPriority() { return priority; }
public void setPriority(double v) { this.priority = v; }
public List<String> getUnmetReasons() { return unmetReasons; }
public List<RiskItem> getUnmetReasons() { return unmetReasons; }
public List<RiskItem> getRisks() { return risks; }
public void setUnmetReasons(List<RiskItem> v) { unmetReasons=v; }
public void setRisks(List<RiskItem> v) { risks=v; }
}
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