Commit 2275c355 authored by Tong Li's avatar Tong Li

MP

parent a1a0557e
...@@ -12,7 +12,7 @@ import com.aps.mapper.ErpPurchaseOrderMapper; ...@@ -12,7 +12,7 @@ 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.PlanResourceMapper; import com.aps.mapper.PlanResourceMapper;
import com.aps.mapper.ProdLaunchOrderMapper; import com.aps.mapper.ApsDemandOrderMapper;
import com.aps.mapper.PurchaseReceiptMapper; import com.aps.mapper.PurchaseReceiptMapper;
import com.aps.mapper.RoutingDetailEquipMapper; import com.aps.mapper.RoutingDetailEquipMapper;
import com.aps.mapper.RoutingHeaderMapper; import com.aps.mapper.RoutingHeaderMapper;
...@@ -30,6 +30,7 @@ import java.math.BigDecimal; ...@@ -30,6 +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.temporal.ChronoUnit;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -62,7 +63,7 @@ import java.util.stream.Collectors; ...@@ -62,7 +63,7 @@ import java.util.stream.Collectors;
* OperationInput ← Routingsupporting (仅 MP 原材料投料) * OperationInput ← Routingsupporting (仅 MP 原材料投料)
* InitialInventory ← Material.materialStocks (Stock) * InitialInventory ← Material.materialStocks (Stock)
* InTransitSupply ← Material.InTransit (MaterialSupply) * InTransitSupply ← Material.InTransit (MaterialSupply)
* SalesDemand ← ProdLaunchOrder * SalesDemand ← ApsDemandOrder
* </pre> * </pre>
*/ */
@Service @Service
...@@ -78,7 +79,7 @@ public class MacroPlannerDataConverter { ...@@ -78,7 +79,7 @@ public class MacroPlannerDataConverter {
// ==================== 注入的 Mapper ==================== // ==================== 注入的 Mapper ====================
@Autowired @Autowired
private ProdLaunchOrderMapper prodLaunchOrderMapper; private ApsDemandOrderMapper apsDemandOrderMapper;
@Autowired @Autowired
private RoutingHeaderMapper routingHeaderMapper; private RoutingHeaderMapper routingHeaderMapper;
@Autowired @Autowired
...@@ -143,15 +144,43 @@ public class MacroPlannerDataConverter { ...@@ -143,15 +144,43 @@ public class MacroPlannerDataConverter {
private ConvertContext loadRawData(String sceneId) { private ConvertContext loadRawData(String sceneId) {
ConvertContext ctx = new ConvertContext(); ConvertContext ctx = new ConvertContext();
// 1. 订单 (按 sceneId 过滤) // 0. 读取时间配置, 计算 horizonEnd
ctx.prodLaunchOrders = prodLaunchOrderMapper.selectList( // endCount = 期数 (与 periodDimension 结合决定实际天数)
new LambdaQueryWrapper<ProdLaunchOrder>() ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>());
.eq(ProdLaunchOrder::getSceneId, sceneId)); ctx.baseTime = (timeConfig != null && timeConfig.getBaseTime() != null)
log.info("加载订单: {} 条", ctx.prodLaunchOrders.size()); ? timeConfig.getBaseTime() : LocalDateTime.now();
// 2. 收集 materialIds ctx.periodDimension = "DAY";
Set<String> materialIds = ctx.prodLaunchOrders.stream() if (timeConfig != null && timeConfig.getPeriodDimension() != null
.map(ProdLaunchOrder::getMaterialId) && !timeConfig.getPeriodDimension().trim().isEmpty()) {
ctx.periodDimension = timeConfig.getPeriodDimension().trim().toUpperCase();
}
int periodCount = DEFAULT_PERIOD_COUNT;
if (timeConfig != null && timeConfig.getEndCount() != null) {
int ec = timeConfig.getEndCount().intValue();
if (ec > 0) {
periodCount = ec;
}
}
ctx.periodCount = periodCount;
LocalDate baseDate = ctx.baseTime.toLocalDate();
ctx.horizonEnd = baseDate.plusDays(calculateTotalDays(ctx.periodDimension, baseDate, periodCount));
LocalDateTime horizonEndDateTime = ctx.horizonEnd.atStartOfDay();
log.info("排产周期: dimension={}, periodCount={}, baseTime={}, horizonEnd={}",
ctx.periodDimension, periodCount, ctx.baseTime, ctx.horizonEnd);
// 1. 需求订单 (ApsDemandOrder, 按 deliverytime 时间范围过滤)
ctx.apsDemandOrders = apsDemandOrderMapper.selectList(
new LambdaQueryWrapper<ApsDemandOrder>()
.ge(ApsDemandOrder::getDeliverytime, ctx.baseTime)
.lt(ApsDemandOrder::getDeliverytime, horizonEndDateTime));
log.info("加载需求订单: {} 条 (时间范围过滤)", ctx.apsDemandOrders.size());
// 2. 收集 materialIds (从 ApsDemandOrder.mmid)
Set<String> materialIds = ctx.apsDemandOrders.stream()
.map(ApsDemandOrder::getMmid)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.distinct() .distinct()
.collect(Collectors.toSet()); .collect(Collectors.toSet());
...@@ -274,7 +303,7 @@ public class MacroPlannerDataConverter { ...@@ -274,7 +303,7 @@ public class MacroPlannerDataConverter {
} }
log.info("加载设备资源: {}, 设备信息: {}", ctx.planResources.size(), ctx.equipinfos.size()); log.info("加载设备资源: {}, 设备信息: {}", ctx.planResources.size(), ctx.equipinfos.size());
// 11. 设备产能日历: EquipShiftCapacity (每天一条, validTimePeriods=有效时间段JSON) // 11. 设备产能日历: EquipShiftCapacity (按时间范围过滤)
Set<Long> planResourceIds = ctx.planResources.stream() Set<Long> planResourceIds = ctx.planResources.stream()
.map(PlanResource::getId) .map(PlanResource::getId)
.filter(Objects::nonNull) .filter(Objects::nonNull)
...@@ -284,14 +313,11 @@ public class MacroPlannerDataConverter { ...@@ -284,14 +313,11 @@ public class MacroPlannerDataConverter {
ctx.equipShiftCapacities = equipShiftCapacityMapper.selectList( ctx.equipShiftCapacities = equipShiftCapacityMapper.selectList(
new LambdaQueryWrapper<EquipShiftCapacity>() new LambdaQueryWrapper<EquipShiftCapacity>()
.in(EquipShiftCapacity::getPlanResourceId, planResourceIds) .in(EquipShiftCapacity::getPlanResourceId, planResourceIds)
.ge(EquipShiftCapacity::getCapacityDate, ctx.baseTime)
.lt(EquipShiftCapacity::getCapacityDate, horizonEndDateTime)
.eq(EquipShiftCapacity::getIsDeleted, 0)); .eq(EquipShiftCapacity::getIsDeleted, 0));
} }
log.info("加载设备产能日历(EquipShiftCapacity): {} 条", ctx.equipShiftCapacities.size()); log.info("加载设备产能日历(EquipShiftCapacity): {} 条 (时间范围过滤)", ctx.equipShiftCapacities.size());
// 12. baseTime
ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>());
ctx.baseTime = (timeConfig != null && timeConfig.getBaseTime() != null)
? timeConfig.getBaseTime() : LocalDateTime.now();
return ctx; return ctx;
} }
...@@ -455,31 +481,8 @@ public class MacroPlannerDataConverter { ...@@ -455,31 +481,8 @@ public class MacroPlannerDataConverter {
* </p> * </p>
*/ */
private void buildDailyCapacityByUnitId(ConvertContext ctx) { private void buildDailyCapacityByUnitId(ConvertContext ctx) {
// 读取时间配置, 获取维度 + 计划周期 log.info("排产周期: dimension={}, periodCount={}, horizonEnd={}",
ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>()); ctx.periodDimension, ctx.periodCount, ctx.horizonEnd);
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 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);
// 按维度计算 periodCount
ctx.periodCount = calculatePeriodCount(ctx.periodDimension, baseDate, horizonEnd);
log.info("排产周期: dimension={}, periodCount={}, 总天数={}", ctx.periodDimension, ctx.periodCount, totalDays);
// planResourceId → PlanResource.id // planResourceId → PlanResource.id
Map<Long, PlanResource> prById = ctx.planResources.stream() Map<Long, PlanResource> prById = ctx.planResources.stream()
...@@ -557,32 +560,23 @@ public class MacroPlannerDataConverter { ...@@ -557,32 +560,23 @@ public class MacroPlannerDataConverter {
} }
/** /**
* 按维度计算周期数。 * 按维度将期数转换为实际天数。
* *
* @param dimension 维度: DAY / WEEK / MONTH * @param dimension 维度: DAY / WEEK / MONTH
* @param baseDate 起始日期 * @param baseDate 起始日期
* @param horizonEnd 计划截止日期 (不包含) * @param periodCount 期数 (endCount)
* @return 周期 * @return 实际天
*/ */
private static int calculatePeriodCount(String dimension, LocalDate baseDate, LocalDate horizonEnd) { private static int calculateTotalDays(String dimension, LocalDate baseDate, int periodCount) {
switch (dimension) { switch (dimension) {
case "WEEK": case "WEEK":
// 按周: 从 baseDate 开始, 每7天一周, 不足7天也算一周 return periodCount * 7;
long totalDays = java.time.temporal.ChronoUnit.DAYS.between(baseDate, horizonEnd);
return (int) Math.max(1, Math.ceil(totalDays / 7.0));
case "MONTH": case "MONTH":
// 按月: 从 baseDate 所在月开始, 跨越的自然月数 LocalDate end = baseDate.plusMonths(periodCount);
int months = 0; return (int) ChronoUnit.DAYS.between(baseDate, end);
LocalDate cursor = baseDate.withDayOfMonth(1);
while (cursor.isBefore(horizonEnd)) {
cursor = cursor.plusMonths(1);
months++;
}
return Math.max(1, months);
case "DAY": case "DAY":
default: default:
long days = java.time.temporal.ChronoUnit.DAYS.between(baseDate, horizonEnd); return periodCount;
return (int) Math.max(1, days);
} }
} }
...@@ -991,18 +985,7 @@ public class MacroPlannerDataConverter { ...@@ -991,18 +985,7 @@ public class MacroPlannerDataConverter {
LocalDate baseDate = ctx.baseTime.toLocalDate(); LocalDate baseDate = ctx.baseTime.toLocalDate();
int periodCount = ctx.periodCount; int periodCount = ctx.periodCount;
String dimension = ctx.periodDimension; String dimension = ctx.periodDimension;
LocalDate horizonEnd = ctx.horizonEnd;
// 计算 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 // 按维度创建 Period
switch (dimension) { switch (dimension) {
...@@ -1064,8 +1047,8 @@ public class MacroPlannerDataConverter { ...@@ -1064,8 +1047,8 @@ public class MacroPlannerDataConverter {
unitPeriods.add(new UnitPeriod(unitId, p, 0.0, periodMaxCapacity, false)); unitPeriods.add(new UnitPeriod(unitId, p, 0.0, periodMaxCapacity, false));
} }
} }
log.info("构建 Period: {} (dimension={}, 共{}天), UnitPeriod: {}", log.info("构建 Period: {} (dimension={}, horizonEnd={}), UnitPeriod: {}",
periods.size(), dimension, totalDays, unitPeriods.size()); periods.size(), dimension, horizonEnd, unitPeriods.size());
} }
/** DAY 维度: 每天一个 Period */ /** DAY 维度: 每天一个 Period */
...@@ -1117,40 +1100,89 @@ public class MacroPlannerDataConverter { ...@@ -1117,40 +1100,89 @@ public class MacroPlannerDataConverter {
if (periods.isEmpty()) { if (periods.isEmpty()) {
return; return;
} }
for (ProdLaunchOrder plo : ctx.prodLaunchOrders) { Period lastPeriod = periods.get(periods.size() - 1);
if (plo.getMaterialId() == null) {
for (ApsDemandOrder ado : ctx.apsDemandOrders) {
String materialId = ado.getMmid();
if (materialId == null) {
continue; continue;
} }
Product p = ctx.productByMaterialId.get(plo.getMaterialId()); Product p = ctx.productByMaterialId.get(materialId);
if (p == null) { if (p == null) {
continue; continue;
} }
StockingPoint sp = ctx.finalSpByMaterialId.get(plo.getMaterialId()); StockingPoint sp = ctx.finalSpByMaterialId.get(materialId);
if (sp == null) { if (sp == null) {
continue; continue;
} }
Period targetPeriod = null; double qty = ado.getQuantity() != null ? ado.getQuantity().doubleValue() : 0;
if (plo.getEndDate() != null) { if (qty <= 0) {
LocalDate dueDate = plo.getEndDate().toLocalDate(); continue;
for (Period per : periods) { }
if (per.contains(dueDate)) {
targetPeriod = per; double priority = 1;
break; if (ado.getPrioritry() != null) {
try {
priority = Double.parseDouble(ado.getPrioritry().trim());
} catch (NumberFormatException e) {
priority = 1;
} }
} }
// 确定需求时间范围: 优先用 [begintime, endtime), 否则用 deliverytime 作为单点
LocalDate orderStart, orderEnd;
if (ado.getBegintime() != null && ado.getEndtime() != null) {
orderStart = ado.getBegintime().toLocalDate();
orderEnd = ado.getEndtime().toLocalDate();
} else if (ado.getDeliverytime() != null) {
orderStart = ado.getDeliverytime().toLocalDate();
orderEnd = orderStart.plusDays(1);
} else {
// 无时间信息, 放入最后一个周期
salesDemands.add(new SalesDemand(p, sp, lastPeriod, qty, priority));
continue;
} }
if (targetPeriod == null) {
// 找不到匹配周期, 用最后一个周期 if (!orderStart.isBefore(orderEnd)) {
targetPeriod = periods.get(periods.size() - 1); orderEnd = orderStart.plusDays(1);
}
long totalRangeDays = ChronoUnit.DAYS.between(orderStart, orderEnd);
if (totalRangeDays <= 0) {
totalRangeDays = 1;
} }
double priority = plo.getOrderPriority() != null ? plo.getOrderPriority() : 1; // 遍历周期, 按重叠天数比例拆分需求量
salesDemands.add(new SalesDemand(p, sp, targetPeriod, plo.getQuantity(), priority)); boolean matched = false;
for (Period per : periods) {
long overlapDays = overlappingDays(per, orderStart, orderEnd);
if (overlapDays > 0) {
double periodQty = qty * overlapDays / totalRangeDays;
salesDemands.add(new SalesDemand(p, sp, per, periodQty, priority));
matched = true;
}
}
if (!matched) {
salesDemands.add(new SalesDemand(p, sp, lastPeriod, qty, priority));
}
} }
log.info("构建 SalesDemand: {}", salesDemands.size()); log.info("构建 SalesDemand: {}", salesDemands.size());
} }
/**
* 计算 Period 与日期范围的交集天数。
*/
private static long overlappingDays(Period period, LocalDate rangeStart, LocalDate rangeEnd) {
LocalDate periodStart = period.getStartDate();
LocalDate periodEnd = periodStart.plusDays((long) period.getDurationInDays());
LocalDate overlapStart = rangeStart.isAfter(periodStart) ? rangeStart : periodStart;
LocalDate overlapEnd = rangeEnd.isBefore(periodEnd) ? rangeEnd : periodEnd;
if (!overlapStart.isBefore(overlapEnd)) {
return 0;
}
return ChronoUnit.DAYS.between(overlapStart, overlapEnd);
}
// ---------- 步骤9: InventorySpec + SupplySpec (默认宽松) ---------- // ---------- 步骤9: InventorySpec + SupplySpec (默认宽松) ----------
private void fillInventorySpecsAndSupplySpecs(ConvertContext ctx) { private void fillInventorySpecsAndSupplySpecs(ConvertContext ctx) {
...@@ -1223,7 +1255,7 @@ public class MacroPlannerDataConverter { ...@@ -1223,7 +1255,7 @@ public class MacroPlannerDataConverter {
private static class ConvertContext { private static class ConvertContext {
// 数据库加载的原始实体 // 数据库加载的原始实体
List<ProdLaunchOrder> prodLaunchOrders = new ArrayList<>(); List<ApsDemandOrder> apsDemandOrders = new ArrayList<>();
List<RoutingHeader> routingHeaders = new ArrayList<>(); List<RoutingHeader> routingHeaders = new ArrayList<>();
List<RoutingDetail> routingDetails = new ArrayList<>(); List<RoutingDetail> routingDetails = new ArrayList<>();
List<Routingsupporting> routingsupportings = new ArrayList<>(); List<Routingsupporting> routingsupportings = new ArrayList<>();
...@@ -1238,6 +1270,8 @@ public class MacroPlannerDataConverter { ...@@ -1238,6 +1270,8 @@ public class MacroPlannerDataConverter {
List<PlanResource> planResources = new ArrayList<>(); List<PlanResource> planResources = new ArrayList<>();
List<EquipShiftCapacity> equipShiftCapacities = new ArrayList<>(); List<EquipShiftCapacity> equipShiftCapacities = new ArrayList<>();
LocalDateTime baseTime; LocalDateTime baseTime;
/** 计划截止日期 (不包含), 由 baseTime + periodDimension + periodCount 计算 */
LocalDate horizonEnd;
// 转换过程的中间映射 // 转换过程的中间映射
/** materialId → Material 业务对象 */ /** materialId → Material 业务对象 */
......
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