Commit a1a0557e authored by Tong Li's avatar Tong Li

MP

parent 5a208354
...@@ -17,4 +17,7 @@ public class ApsTimeConfig { ...@@ -17,4 +17,7 @@ public class ApsTimeConfig {
private BigDecimal startCount; private BigDecimal startCount;
private BigDecimal endCount; private BigDecimal endCount;
private LocalDateTime deadlineTime; private LocalDateTime deadlineTime;
/** 周期维度: DAY(天) / WEEK(周) / MONTH(月), 默认 DAY */
private String periodDimension;
} }
\ No newline at end of file
...@@ -4,14 +4,14 @@ import com.aps.common.util.ParamValidator; ...@@ -4,14 +4,14 @@ import com.aps.common.util.ParamValidator;
import com.aps.entity.*; import com.aps.entity.*;
import com.aps.entity.basic.Material; import com.aps.entity.basic.Material;
import com.aps.entity.basic.MaterialSupply; import com.aps.entity.basic.MaterialSupply;
import com.aps.mapper.EquipCapacityDefMapper; import com.aps.mapper.EquipShiftCapacityMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.aps.mapper.EquipinfoMapper; import com.aps.mapper.EquipinfoMapper;
import com.aps.mapper.ErpPurchaseOrderMapper; import com.aps.mapper.ErpPurchaseOrderMapper;
import com.aps.mapper.MaterialInfoMapper; import com.aps.mapper.MaterialInfoMapper;
import com.aps.mapper.MaterialPurchaseMapper; import com.aps.mapper.MaterialPurchaseMapper;
import com.aps.mapper.MesShiftWorkSchedMapper;
import com.aps.mapper.PlanResourceMapper; import com.aps.mapper.PlanResourceMapper;
import com.aps.mapper.ProdEquipSpecialCalMapper;
import com.aps.mapper.ProdLaunchOrderMapper; import com.aps.mapper.ProdLaunchOrderMapper;
import com.aps.mapper.PurchaseReceiptMapper; import com.aps.mapper.PurchaseReceiptMapper;
import com.aps.mapper.RoutingDetailEquipMapper; import com.aps.mapper.RoutingDetailEquipMapper;
...@@ -30,19 +30,7 @@ import java.math.BigDecimal; ...@@ -30,19 +30,7 @@ import java.math.BigDecimal;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
...@@ -114,11 +102,7 @@ public class MacroPlannerDataConverter { ...@@ -114,11 +102,7 @@ public class MacroPlannerDataConverter {
@Autowired @Autowired
private PlanResourceMapper planResourceMapper; private PlanResourceMapper planResourceMapper;
@Autowired @Autowired
private ProdEquipSpecialCalMapper prodEquipSpecialCalMapper; private EquipShiftCapacityMapper equipShiftCapacityMapper;
@Autowired
private MesShiftWorkSchedMapper mesShiftWorkSchedMapper;
@Autowired
private EquipCapacityDefMapper equipCapacityDefMapper;
// ==================== 注入的 Service ==================== // ==================== 注入的 Service ====================
@Autowired @Autowired
...@@ -290,22 +274,19 @@ public class MacroPlannerDataConverter { ...@@ -290,22 +274,19 @@ public class MacroPlannerDataConverter {
} }
log.info("加载设备资源: {}, 设备信息: {}", ctx.planResources.size(), ctx.equipinfos.size()); log.info("加载设备资源: {}, 设备信息: {}", ctx.planResources.size(), ctx.equipinfos.size());
// 11. 设备日历: ProdEquipSpecialCal + MesShiftWorkSched + EquipCapacityDef // 11. 设备产能日历: EquipShiftCapacity (每天一条, validTimePeriods=有效时间段JSON)
ctx.prodEquipSpecialCals = prodEquipSpecialCalMapper.selectList( Set<Long> planResourceIds = ctx.planResources.stream()
new LambdaQueryWrapper<ProdEquipSpecialCal>() .map(PlanResource::getId)
.eq(ProdEquipSpecialCal::getSceneId, sceneId)); .filter(Objects::nonNull)
ctx.mesShiftWorkScheds = mesShiftWorkSchedMapper.selectList( .map(Long::valueOf)
new LambdaQueryWrapper<MesShiftWorkSched>() .collect(Collectors.toSet());
.eq(MesShiftWorkSched::getIsdeleted, 0)); if (!planResourceIds.isEmpty()) {
// EquipCapacityDef 按 PlanResource.referenceId 筛选 ctx.equipShiftCapacities = equipShiftCapacityMapper.selectList(
if (!equipinfoIds.isEmpty()) { new LambdaQueryWrapper<EquipShiftCapacity>()
ctx.equipCapacityDefs = equipCapacityDefMapper.selectList( .in(EquipShiftCapacity::getPlanResourceId, planResourceIds)
new LambdaQueryWrapper<EquipCapacityDef>() .eq(EquipShiftCapacity::getIsDeleted, 0));
.in(EquipCapacityDef::getReferenceId, equipinfoIds)
.eq(EquipCapacityDef::getIsDeleted, 0));
} }
log.info("加载设备日历: 特殊日历={}, 班次模板={}, 产能定义={}", log.info("加载设备产能日历(EquipShiftCapacity): {} 条", ctx.equipShiftCapacities.size());
ctx.prodEquipSpecialCals.size(), ctx.mesShiftWorkScheds.size(), ctx.equipCapacityDefs.size());
// 12. baseTime // 12. baseTime
ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>()); ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>());
...@@ -460,212 +441,164 @@ public class MacroPlannerDataConverter { ...@@ -460,212 +441,164 @@ public class MacroPlannerDataConverter {
} }
} }
// ==================== 步骤3: 结合设备日历计算每个 PlanResource (设备) 的日产能 ==================== // ==================== 步骤3: 从 EQUIP_SHIFT_CAPACITY 读取设备日产能 ====================
/** /**
* 按 PlanResource.id 计算每台设备的日产能 (小时), 结合班次+节假日+效率系数。 * 从 EquipShiftCapacity.validTimePeriods 解析每台设备每天的可用时间段, 计算日产能。
* *
* <p>映射链路:</p> * <p>validTimePeriods 格式: [{"StartTime":"2026-04-09 08:00:00","EndTime":"2026-04-09 18:00:00"}]
* <pre> * 每天一条记录, 直接求和得到日可用小时数。</p>
* RoutingDetailEquip.equipId → PlanResource.id → unitId = "EQUIP_" + id
* PlanResource.referenceId → Equipinfo.id → Equipinfo.xxx
* ProdEquipSpecialCal(referenceType=1) → MesShiftWorkSched → 班次时间
* ProdEquipSpecialCal(referenceType=2) → 节假日日期
* EquipCapacityDef → 效率系数
* </pre>
* *
* <p>结果存储到 {@code ctx.dailyCapacityByUnitId} 和节假日映射。</p> * <p>结果:
* ctx.dailyCapacityByUnitId: unitId → 平均日产能 (小时, 含效率系数)
* ctx.holidayDatesByUnitId: unitId → 日产能为0的日期 (视为节假日)
* </p>
*/ */
private void buildDailyCapacityByUnitId(ConvertContext ctx) { private void buildDailyCapacityByUnitId(ConvertContext ctx) {
// 计算排产周期数 (从 ApsTimeConfig) // 读取时间配置, 获取维度 + 计划周期
ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>()); ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>());
LocalDate baseDate = ctx.baseTime.toLocalDate();
// 维度: 默认 DAY
ctx.periodDimension = "DAY";
if (timeConfig != null && timeConfig.getPeriodDimension() != null
&& !timeConfig.getPeriodDimension().trim().isEmpty()) {
ctx.periodDimension = timeConfig.getPeriodDimension().trim().toUpperCase();
}
// 时间范围: horizonSeconds = endCount - startCount (秒)
long horizonSeconds = 86400 * DEFAULT_PERIOD_COUNT;
if (timeConfig != null && timeConfig.getStartCount() != null && timeConfig.getEndCount() != null) { if (timeConfig != null && timeConfig.getStartCount() != null && timeConfig.getEndCount() != null) {
long horizonSeconds = timeConfig.getEndCount().longValue() long diff = timeConfig.getEndCount().longValue() - timeConfig.getStartCount().longValue();
- timeConfig.getStartCount().longValue(); if (diff > 0) {
if (horizonSeconds > 0) { horizonSeconds = diff;
ctx.periodCount = (int) Math.max(1, horizonSeconds / 86400);
} }
} }
log.info("排产周期数: {}", ctx.periodCount); int totalDays = (int) Math.max(1, horizonSeconds / 86400);
LocalDate horizonEnd = baseDate.plusDays(totalDays);
// 构建索引
Map<Integer, PlanResource> planResourceById = ctx.planResources.stream()
.collect(Collectors.toMap(PlanResource::getId, pr -> pr, (a, b) -> a));
Map<Integer, Equipinfo> equipinfoById = ctx.equipinfos.stream()
.collect(Collectors.toMap(Equipinfo::getId, e -> e, (a, b) -> a));
// MesShiftWorkSched: weekWorkSchedId → List
Map<Integer, List<MesShiftWorkSched>> shiftSchedByWeekId = ctx.mesShiftWorkScheds.stream()
.filter(s -> s.getWeekWorkSchedId() != null)
.collect(Collectors.groupingBy(MesShiftWorkSched::getWeekWorkSchedId));
// ProdEquipSpecialCal: planResourceId → List (referenceType=1 班次)
Map<Long, List<ProdEquipSpecialCal>> shiftCalsByPrId = ctx.prodEquipSpecialCals.stream()
.filter(c -> c.getPlanResourceId() != null && c.getReferenceType() != null
&& c.getReferenceType() == 1)
.collect(Collectors.groupingBy(ProdEquipSpecialCal::getPlanResourceId));
// ProdEquipSpecialCal: planResourceId → List (referenceType=2 节假日)
Map<Long, List<ProdEquipSpecialCal>> holidayCalsByPrId = ctx.prodEquipSpecialCals.stream()
.filter(c -> c.getPlanResourceId() != null && c.getReferenceType() != null
&& c.getReferenceType() == 2)
.collect(Collectors.groupingBy(ProdEquipSpecialCal::getPlanResourceId));
// EquipCapacityDef: equipinfo.id → EquipCapacityDef
Map<Long, EquipCapacityDef> capDefByRefId = ctx.equipCapacityDefs.stream()
.filter(d -> d.getReferenceId() != null)
.collect(Collectors.toMap(EquipCapacityDef::getReferenceId, d -> d, (a, b) -> a));
double DEFAULT_DAILY_HOURS = 16.0; // 按维度计算 periodCount
ctx.periodCount = calculatePeriodCount(ctx.periodDimension, baseDate, horizonEnd);
log.info("排产周期: dimension={}, periodCount={}, 总天数={}", ctx.periodDimension, ctx.periodCount, totalDays);
// 收集所有 unitId (从 RoutingDetailEquip) // planResourceId → PlanResource.id
Set<String> allUnitIds = new HashSet<>(); Map<Long, PlanResource> prById = ctx.planResources.stream()
for (RoutingDetailEquip rde : ctx.routingDetailEquips) { .collect(Collectors.toMap(p -> Long.valueOf(p.getId()), p -> p, (a, b) -> a));
if (rde.getEquipId() != null) {
allUnitIds.add("EQUIP_" + rde.getEquipId());
}
}
for (String unitId : allUnitIds) { ObjectMapper mapper = new ObjectMapper();
String idStr = unitId.substring(6); // 去掉 "EQUIP_" 前缀 for (EquipShiftCapacity esc : ctx.equipShiftCapacities) {
long planResourceId; Long prId = esc.getPlanResourceId();
try { if (prId == null || esc.getCapacityDate() == null) {
planResourceId = Long.parseLong(idStr);
} catch (NumberFormatException e) {
ctx.dailyCapacityByUnitId.put(unitId, DEFAULT_DAILY_HOURS);
continue; continue;
} }
PlanResource pr = prById.get(prId);
PlanResource pr = planResourceById.get(planResourceId);
if (pr == null) { if (pr == null) {
ctx.dailyCapacityByUnitId.put(unitId, DEFAULT_DAILY_HOURS);
continue; continue;
} }
String unitId = "EQUIP_" + pr.getId();
Equipinfo eq = (pr.getReferenceId() != null) ? equipinfoById.get(pr.getReferenceId()) : null; double dailyHours = 0.0;
if (esc.getValidTimePeriods() != null && !esc.getValidTimePeriods().trim().isEmpty()) {
// 效率系数: EquipCapacityDef > ProdEquipSpecialCal > 默认 1.0 try {
double efficiency = 1.0; List<Map<String, String>> periods = mapper.readValue(
if (eq != null) { esc.getValidTimePeriods(),
EquipCapacityDef capDef = capDefByRefId.get(eq.getId().longValue()); new TypeReference<List<Map<String, String>>>() {});
if (capDef != null && capDef.getEfficiencyCoeff() != null) { for (Map<String, String> tp : periods) {
efficiency = capDef.getEfficiencyCoeff(); String startStr = tp.get("StartTime");
} String endStr = tp.get("EndTime");
} if (startStr != null && endStr != null) {
if (efficiency == 1.0) { LocalDateTime start = LocalDateTime.parse(startStr.replace(" ", "T"));
List<ProdEquipSpecialCal> shiftCals = shiftCalsByPrId.getOrDefault(pr.getId(), Collections.emptyList()); LocalDateTime end = LocalDateTime.parse(endStr.replace(" ", "T"));
for (ProdEquipSpecialCal cal : shiftCals) { double hours = Duration.between(start, end).toMinutes() / 60.0;
if ( cal.getEfficiencyCoeff() > 0) { dailyHours += Math.max(0, hours);
efficiency = cal.getEfficiencyCoeff();
break;
}
}
}
// 收集班次时间
List<MesShiftWorkSched> allShifts = new ArrayList<>();
List<ProdEquipSpecialCal> shiftCals = shiftCalsByPrId.getOrDefault(pr.getId(), Collections.emptyList());
for (ProdEquipSpecialCal cal : shiftCals) {
if (cal.getReferenceId() != null) {
List<MesShiftWorkSched> scheds = shiftSchedByWeekId.get(cal.getReferenceId().intValue());
if (scheds != null) {
allShifts.addAll(scheds);
}
} }
} }
// 备选: 通过 PlanResource.workSchedId } catch (Exception e) {
if (allShifts.isEmpty() && pr.getWorkSchedId() != null) { log.warn("解析 validTimePeriods 失败: unitId={}, date={}, json={}",
List<MesShiftWorkSched> scheds = shiftSchedByWeekId.get(pr.getWorkSchedId().intValue()); unitId, esc.getCapacityDate(), esc.getValidTimePeriods(), e);
if (scheds != null) {
allShifts.addAll(scheds);
} }
} }
double dailyHours; // 应用效率系数
if (!allShifts.isEmpty()) { double efficiency = 1.0;
dailyHours = computeDailyHoursFromShifts(allShifts); if (esc.getEfficiencyCoeff() != null && esc.getEfficiencyCoeff() > 0) {
} else { efficiency = esc.getEfficiencyCoeff();
// 无班次: 使用 capabilityValue 回退
if (pr.getCapabilityValue() != null) {
dailyHours = pr.getCapabilityValue().doubleValue();
} else if (eq != null && eq.getCapabilityValue() != null) {
dailyHours = eq.getCapabilityValue().doubleValue();
} else {
dailyHours = DEFAULT_DAILY_HOURS;
}
} }
dailyHours *= efficiency; dailyHours *= efficiency;
ctx.dailyCapacityByUnitId.put(unitId, Math.max(0, dailyHours));
ctx.dailyHoursByUnitId
.computeIfAbsent(unitId, k -> new LinkedHashMap<>())
.put(esc.getCapacityDate().toLocalDate(), dailyHours);
} }
// 构建节假日映射: unitId → Set<LocalDate> // 汇总: 平均日产能 + 节假日
for (Map.Entry<Long, List<ProdEquipSpecialCal>> entry : holidayCalsByPrId.entrySet()) { for (Map.Entry<String, Map<LocalDate, Double>> entry : ctx.dailyHoursByUnitId.entrySet()) {
String unitId = "EQUIP_" + entry.getKey(); String unitId = entry.getKey();
Set<LocalDate> dates = ctx.holidayDatesByUnitId.computeIfAbsent(unitId, k -> new HashSet<>()); double total = 0;
for (ProdEquipSpecialCal cal : entry.getValue()) { int nonZeroDays = 0;
if (cal.getStartDate() == null || cal.getEndDate() == null) { Set<LocalDate> holidayDates = new HashSet<>();
continue; for (Map.Entry<LocalDate, Double> de : entry.getValue().entrySet()) {
if (de.getValue() <= 0) {
holidayDates.add(de.getKey());
} else {
total += de.getValue();
nonZeroDays++;
} }
LocalDate start = cal.getStartDate().toLocalDate();
LocalDate end = cal.getEndDate().toLocalDate();
for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
dates.add(d);
} }
double avgDaily = nonZeroDays > 0 ? total / nonZeroDays : 16.0;
ctx.dailyCapacityByUnitId.put(unitId, avgDaily);
if (!holidayDates.isEmpty()) {
ctx.holidayDatesByUnitId.put(unitId, holidayDates);
} }
} }
log.info("构建日产能(含日历+节假日): {} 台设备, {} 台有节假日, 默认={}h", log.info("设备日产能(EquipShiftCapacity): {} 台设备, {} 台有节假日, 默认={}h",
ctx.dailyCapacityByUnitId.size(), ctx.holidayDatesByUnitId.size(), DEFAULT_DAILY_HOURS); ctx.dailyCapacityByUnitId.size(), ctx.holidayDatesByUnitId.size(), 16.0);
} }
/** /**
* 从 MesShiftWorkSched 列表计算平均日工作小时数。 * 按维度计算周期数。
* 遍历每周 7 天, 累加匹配的班次时间, 除以 7 得到日均值。 *
* @param dimension 维度: DAY / WEEK / MONTH
* @param baseDate 起始日期
* @param horizonEnd 计划截止日期 (不包含)
* @return 周期数
*/ */
private double computeDailyHoursFromShifts(List<MesShiftWorkSched> shifts) { private static int calculatePeriodCount(String dimension, LocalDate baseDate, LocalDate horizonEnd) {
double[] hoursByDay = new double[7]; // 0=Sunday, 1=Monday, ..., 6=Saturday switch (dimension) {
for (MesShiftWorkSched s : shifts) { case "WEEK":
if (s.getShiftStart() == null || s.getShiftEnd() == null) { // 按周: 从 baseDate 开始, 每7天一周, 不足7天也算一周
continue; long totalDays = java.time.temporal.ChronoUnit.DAYS.between(baseDate, horizonEnd);
} return (int) Math.max(1, Math.ceil(totalDays / 7.0));
LocalTime startTime = s.getShiftStart().toLocalTime(); case "MONTH":
LocalTime endTime = s.getShiftEnd().toLocalTime(); // 按月: 从 baseDate 所在月开始, 跨越的自然月数
double shiftHours = calculateShiftHours(startTime, endTime); int months = 0;
LocalDate cursor = baseDate.withDayOfMonth(1);
int startDay = s.getStartWeekDay() != null ? s.getStartWeekDay() : 1; while (cursor.isBefore(horizonEnd)) {
int endDay = s.getEndWeekDay() != null ? s.getEndWeekDay() : startDay; cursor = cursor.plusMonths(1);
int mappedStart = (startDay % 7); // 数据库: 1=Mon→1, 7=Sun→0 months++;
int mappedEnd = (endDay % 7); }
if (mappedStart == mappedEnd) { return Math.max(1, months);
hoursByDay[mappedStart] += shiftHours; case "DAY":
} else { default:
int day = mappedStart; long days = java.time.temporal.ChronoUnit.DAYS.between(baseDate, horizonEnd);
while (day != mappedEnd) { return (int) Math.max(1, days);
hoursByDay[day] += shiftHours;
day = (day + 1) % 7;
} }
hoursByDay[mappedEnd] += shiftHours;
}
}
double totalWeekHours = 0;
for (double h : hoursByDay) {
totalWeekHours += h;
}
return totalWeekHours / 7.0;
} }
/** /**
* 计算班次时长 (小时), 处理跨天 (如 22:00-06:00)。 * 计算给定日期范围内的有效工作日数 (不含周日)。
*/ */
private double calculateShiftHours(LocalTime start, LocalTime end) { private static int calculateWorkDays(LocalDate start, LocalDate endExclusive) {
long minutes; int count = 0;
if (end.isAfter(start)) { LocalDate d = start;
minutes = Duration.between(start, end).toMinutes(); while (d.isBefore(endExclusive)) {
} else { if (d.getDayOfWeek().getValue() != 7) { // 非周日
minutes = Duration.between(start, LocalTime.MAX).toMinutes() count++;
+ Duration.between(LocalTime.MIN, end).toMinutes(); }
d = d.plusDays(1);
} }
return minutes / 60.0; return count;
} }
// ==================== Sink: 继承 TestDataBuilder, 直接操作 protected 字段 ==================== // ==================== Sink: 继承 TestDataBuilder, 直接操作 protected 字段 ====================
...@@ -916,7 +849,6 @@ public class MacroPlannerDataConverter { ...@@ -916,7 +849,6 @@ public class MacroPlannerDataConverter {
// 根据 MATERIAL_PURCHASE 创建供应商 unit: // 根据 MATERIAL_PURCHASE 创建供应商 unit:
// - 有 MATERIAL_PURCHASE → 每供应商一个 Operation, leadTimeDays = purchaseCycle // - 有 MATERIAL_PURCHASE → 每供应商一个 Operation, leadTimeDays = purchaseCycle
// - 无 MATERIAL_PURCHASE → 通用供应商 unit, relativeDuration=1, leadTimeDays=0, UnitPeriod 无限 // - 无 MATERIAL_PURCHASE → 通用供应商 unit, relativeDuration=1, leadTimeDays=0, UnitPeriod 无限
for (Material m : ctx.materialByMaterialId.values()) { for (Material m : ctx.materialByMaterialId.values()) {
if (!"MP".equals(m.getMaterialTypeName())) { if (!"MP".equals(m.getMaterialTypeName())) {
continue; continue;
...@@ -969,7 +901,6 @@ public class MacroPlannerDataConverter { ...@@ -969,7 +901,6 @@ public class MacroPlannerDataConverter {
operations.add(procureOp); operations.add(procureOp);
log.info("为 MP 原材料 {} 创建通用采购 Operation (无供应商, 无限产能)", m.getId()); log.info("为 MP 原材料 {} 创建通用采购 Operation (无供应商, 无限产能)", m.getId());
} }
} }
} }
log.info("构建 OperationInput: {}", operationInputs.size()); log.info("构建 OperationInput: {}", operationInputs.size());
...@@ -1054,15 +985,37 @@ public class MacroPlannerDataConverter { ...@@ -1054,15 +985,37 @@ public class MacroPlannerDataConverter {
initialInventories.size(), inTransitSupplies.size()); initialInventories.size(), inTransitSupplies.size());
} }
// ---------- 步骤7: Period + UnitPeriod (结合设备日历和周期长度) ---------- // ---------- 步骤7: Period + UnitPeriod (按维度创建周期, 聚合日产能) ----------
private void fillPeriodsAndUnitPeriods(ConvertContext ctx) { private void fillPeriodsAndUnitPeriods(ConvertContext ctx) {
LocalDate baseDate = ctx.baseTime.toLocalDate(); LocalDate baseDate = ctx.baseTime.toLocalDate();
int periodCount = ctx.periodCount; int periodCount = ctx.periodCount;
String dimension = ctx.periodDimension;
// 创建周期 (1天/周期) // 计算 horizonEnd (与 buildDailyCapacityByUnitId 中一致)
for (int i = 0; i < periodCount; i++) { ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>());
periods.add(new Period(i, "P" + (i + 1), 1.0, baseDate.plusDays(i))); long horizonSeconds = 86400 * DEFAULT_PERIOD_COUNT;
if (timeConfig != null && timeConfig.getStartCount() != null && timeConfig.getEndCount() != null) {
long diff = timeConfig.getEndCount().longValue() - timeConfig.getStartCount().longValue();
if (diff > 0) {
horizonSeconds = diff;
}
}
int totalDays = (int) Math.max(1, horizonSeconds / 86400);
LocalDate horizonEnd = baseDate.plusDays(totalDays);
// 按维度创建 Period
switch (dimension) {
case "WEEK":
createWeekPeriods(baseDate, periodCount, horizonEnd);
break;
case "MONTH":
createMonthPeriods(baseDate, periodCount, horizonEnd);
break;
case "DAY":
default:
createDayPeriods(baseDate, periodCount);
break;
} }
// 收集所有 unitId (遍历每个 Operation 的每个 UnitOperation) // 收集所有 unitId (遍历每个 Operation 的每个 UnitOperation)
...@@ -1080,35 +1033,82 @@ public class MacroPlannerDataConverter { ...@@ -1080,35 +1033,82 @@ public class MacroPlannerDataConverter {
LocalDate periodEnd = periodStart.plusDays((long) p.getDurationInDays()); LocalDate periodEnd = periodStart.plusDays((long) p.getDurationInDays());
for (String unitId : unitIds) { for (String unitId : unitIds) {
// 无限产能设备 (通用供应商): 直接创建 unlimited UnitPeriod
// 无限产能设备 (通用供应商): 无需计算, 直接创建 unlimited UnitPeriod
if (ctx.unlimitedUnitIds.contains(unitId)) { if (ctx.unlimitedUnitIds.contains(unitId)) {
unitPeriods.add(UnitPeriod.unlimited(unitId, p)); unitPeriods.add(UnitPeriod.unlimited(unitId, p));
continue; continue;
} }
double dailyCapacity = ctx.dailyCapacityByUnitId.getOrDefault(unitId, DEFAULT_DAILY_HOURS); double periodMaxCapacity;
Map<LocalDate, Double> hourlyData = ctx.dailyHoursByUnitId.get(unitId);
// 节假日扣减 if (hourlyData != null && !hourlyData.isEmpty()) {
Set<LocalDate> holidays = ctx.holidayDatesByUnitId.getOrDefault(unitId, Collections.emptySet()); // 有 EquipShiftCapacity 数据: 按实际日产能加和
long holidaysInPeriod = 0; double totalHours = 0;
for (LocalDate h : holidays) { for (Map.Entry<LocalDate, Double> e : hourlyData.entrySet()) {
if (!h.isBefore(periodStart) && h.isBefore(periodEnd)) { if (!e.getKey().isBefore(periodStart) && e.getKey().isBefore(periodEnd)) {
holidaysInPeriod++; totalHours += e.getValue();
} }
} }
long periodDays = (long) p.getDurationInDays(); periodMaxCapacity = totalHours;
long effectiveDays = Math.max(0, periodDays - holidaysInPeriod); } else {
double periodMaxCapacity = dailyCapacity * effectiveDays; // 无 EquipShiftCapacity 数据: 使用平均日产能 × 工作日天数
double dailyCapacity = ctx.dailyCapacityByUnitId.getOrDefault(unitId, DEFAULT_DAILY_HOURS);
int workDays = calculateWorkDays(periodStart, periodEnd);
periodMaxCapacity = dailyCapacity * workDays;
}
if (periodMaxCapacity <= 0) { if (periodMaxCapacity <= 0) {
periodMaxCapacity = DEFAULT_DAILY_HOURS * effectiveDays; int workDays = calculateWorkDays(periodStart, periodEnd);
periodMaxCapacity = DEFAULT_DAILY_HOURS * Math.max(1, workDays);
} }
unitPeriods.add(new UnitPeriod(unitId, p, 0.0, periodMaxCapacity, false)); unitPeriods.add(new UnitPeriod(unitId, p, 0.0, periodMaxCapacity, false));
} }
} }
log.info("构建 Period: {} (共{}天), UnitPeriod: {} (基于设备日历+节假日)", log.info("构建 Period: {} (dimension={}, 共{}天), UnitPeriod: {}",
periods.size(), periodCount, unitPeriods.size()); periods.size(), dimension, totalDays, unitPeriods.size());
}
/** DAY 维度: 每天一个 Period */
private void createDayPeriods(LocalDate baseDate, int periodCount) {
for (int i = 0; i < periodCount; i++) {
periods.add(new Period(i, formatDayName(baseDate.plusDays(i)), 1.0, baseDate.plusDays(i)));
}
}
/** WEEK 维度: 每周一个 Period (周一起始, 7天, 最后一周可能不足7天) */
private void createWeekPeriods(LocalDate baseDate, int periodCount, LocalDate horizonEnd) {
for (int i = 0; i < periodCount; i++) {
LocalDate weekStart = baseDate.plusDays(i * 7L);
LocalDate weekEnd = weekStart.plusDays(7);
if (weekEnd.isAfter(horizonEnd)) {
weekEnd = horizonEnd;
}
long durationDays = java.time.temporal.ChronoUnit.DAYS.between(weekStart, weekEnd);
periods.add(new Period(i, "W" + (i + 1) + "(" + weekStart + ")", (double) durationDays, weekStart));
}
}
/** MONTH 维度: 每月一个 Period (按自然月, 首尾月可能不足月) */
private void createMonthPeriods(LocalDate baseDate, int periodCount, LocalDate horizonEnd) {
LocalDate cursor = baseDate.withDayOfMonth(1);
for (int i = 0; i < periodCount; i++) {
LocalDate monthStart = cursor;
LocalDate monthEnd = cursor.plusMonths(1);
if (monthEnd.isAfter(horizonEnd)) {
monthEnd = horizonEnd;
}
long durationDays = java.time.temporal.ChronoUnit.DAYS.between(monthStart, monthEnd);
periods.add(new Period(i, formatMonthName(monthStart), (double) durationDays, monthStart));
cursor = cursor.plusMonths(1);
}
}
private static String formatDayName(LocalDate date) {
return String.format("D%02d(%02d-%02d)", date.getDayOfYear(), date.getMonthValue(), date.getDayOfMonth());
}
private static String formatMonthName(LocalDate date) {
return String.format("M%d-%02d", date.getYear(), date.getMonthValue());
} }
// ---------- 步骤8: SalesDemand (从订单推导) ---------- // ---------- 步骤8: SalesDemand (从订单推导) ----------
...@@ -1236,9 +1236,7 @@ public class MacroPlannerDataConverter { ...@@ -1236,9 +1236,7 @@ public class MacroPlannerDataConverter {
List<RoutingDetailEquip> routingDetailEquips = new ArrayList<>(); List<RoutingDetailEquip> routingDetailEquips = new ArrayList<>();
List<Equipinfo> equipinfos = new ArrayList<>(); List<Equipinfo> equipinfos = new ArrayList<>();
List<PlanResource> planResources = new ArrayList<>(); List<PlanResource> planResources = new ArrayList<>();
List<ProdEquipSpecialCal> prodEquipSpecialCals = new ArrayList<>(); List<EquipShiftCapacity> equipShiftCapacities = new ArrayList<>();
List<MesShiftWorkSched> mesShiftWorkScheds = new ArrayList<>();
List<EquipCapacityDef> equipCapacityDefs = new ArrayList<>();
LocalDateTime baseTime; LocalDateTime baseTime;
// 转换过程的中间映射 // 转换过程的中间映射
...@@ -1254,15 +1252,17 @@ public class MacroPlannerDataConverter { ...@@ -1254,15 +1252,17 @@ public class MacroPlannerDataConverter {
Map<Long, Operation> operationByRoutingDetailId = new HashMap<>(); Map<Long, Operation> operationByRoutingDetailId = new HashMap<>();
/** materialId → 成品/半成品库 StockingPoint (首个库存点) */ /** materialId → 成品/半成品库 StockingPoint (首个库存点) */
Map<String, StockingPoint> finalSpByMaterialId = new HashMap<>(); Map<String, StockingPoint> finalSpByMaterialId = new HashMap<>();
/** 周期维度: DAY/WEEK/MONTH, 默认 DAY */
String periodDimension = "DAY";
/** unitId ("EQUIP_" + PlanResource.id) → 日产能 (小时), 含日历+效率系数 */ /** unitId ("EQUIP_" + PlanResource.id) → 日产能 (小时), 含日历+效率系数 */
Map<String, Double> dailyCapacityByUnitId = new HashMap<>(); Map<String, Double> dailyCapacityByUnitId = new HashMap<>();
/** unitId → 每天实际产能 (LocalDate → 小时), 从 EquipShiftCapacity 解析 */
Map<String, Map<LocalDate, Double>> dailyHoursByUnitId = new HashMap<>();
/** unitId → 节假日日期集合 */ /** unitId → 节假日日期集合 */
Map<String, Set<LocalDate>> holidayDatesByUnitId = new HashMap<>(); Map<String, Set<LocalDate>> holidayDatesByUnitId = new HashMap<>();
/** unitId 集合: 产能无上限的设备 (如通用供应商, 无 MATERIAL_PURCHASE 时) */ /** unitId 集合: 产能无上限的设备 (如通用供应商, 无 MATERIAL_PURCHASE 时) */
Set<String> unlimitedUnitIds = new HashSet<>(); Set<String> unlimitedUnitIds = new HashSet<>();
/** 排产周期数, 从 ApsTimeConfig 计算, 维度为 DAY 时是天数, WEEK 时是周数, MONTH 时是月数 */
/** 排产周期数 (天), 从 ApsTimeConfig 计算 */
int periodCount = 7; int periodCount = 7;
} }
} }
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