Commit a1a0557e authored by Tong Li's avatar Tong Li

MP

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