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

MP

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