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

Merge remote-tracking branch 'origin/master' into tl

# Conflicts:
#	src/main/java/com/aps/service/Algorithm/GeneticDecoder.java
#	src/main/java/com/aps/service/plan/PlanResultService.java
#	src/test/java/com/aps/demo/PlanResultServiceTest.java
parents 6abaed14 f0698511
......@@ -133,6 +133,28 @@ public class SwaggerMapParamConfig {
));
break;
case "insertOrder":
properties.put("sceneId", new StringSchema().description("场景ID").example("B571EF6682DB463AB2977B1055A74112"));
properties.put("orderCode", new StringSchema().description("订单编码").example("ORDER-2026-001"));
properties.put("materialId", new StringSchema().description("物料ID").example("MAT001"));
properties.put("startDate", new StringSchema().description("开始时间(ISO格式)").example("2026-03-10T08:00:00"));
properties.put("endDate", new StringSchema().description("结束时间(ISO格式)").example("2026-03-20T18:00:00"));
properties.put("priority", new StringSchema().description("优先级").example("1"));
properties.put("quantity", new StringSchema().description("数量").example("100.0"));
examples.put("插单示例", createExample(
"向现有场景添加新订单",
"{\n" +
" \"sceneId\": \"B571EF6682DB463AB2977B1055A74112\",\n" +
" \"orderCode\": \"ORDER-2026-001\",\n" +
" \"materialId\": \"MAT001\",\n" +
" \"startDate\": \"2026-03-10T08:00:00\",\n" +
" \"endDate\": \"2026-03-20T18:00:00\",\n" +
" \"priority\": 1,\n" +
" \"quantity\": 100.0\n" +
"}"
));
break;
case "getResourceGantt":
case "getProductGantt":
......
......@@ -187,4 +187,30 @@ public class LanuchController {
return lanuchService.savePlan(sceneId);
}
/**
* 插单:向现有场景添加新订单
*/
@PostMapping("/insertOrder")
@Operation(summary = "插单", description = "向现有场景添加新订单,订单ID会自动生成UUID格式")
public R<String> insertOrder(@RequestBody Map<String, Object> params) {
String sceneId = (String) params.get("sceneId");
String orderCode = (String) params.get("orderCode");
String materialId = (String) params.get("materialId");
// 解析时间参数
String startDateStr = (String) params.get("startDate");
String endDateStr = (String) params.get("endDate");
java.time.LocalDateTime startDate = java.time.LocalDateTime.parse(startDateStr);
java.time.LocalDateTime endDate = java.time.LocalDateTime.parse(endDateStr);
// 解析优先级和数量
Integer priority = params.get("priority") != null ?
Integer.valueOf(params.get("priority").toString()) : 1;
Double quantity = params.get("quantity") != null ?
Double.valueOf(params.get("quantity").toString()) : 0.0;
return lanuchService.insertOrder(sceneId, orderCode, materialId,
startDate, endDate, priority, quantity);
}
}
\ No newline at end of file
......@@ -400,10 +400,74 @@ public class ResourceGanttController {
return R.ok("复制成功");
}
@PostMapping("/orderInsert")
@Operation(summary = "订单插单", description = "在指定订单后插入新订单")
public R<String> insertOrder(@RequestBody Map<String, Object> params) {
log.info("insertOrder 请求参数: {}", params);
String sceneId = ParamValidator.getString(params, "sceneId", "场景ID");
String afterOrderId = ParamValidator.getString(params, "afterOrderId", "插入位置订单ID");
// 获取新订单信息
@SuppressWarnings("unchecked")
Map<String, Object> newOrderData = (Map<String, Object>) params.get("newOrder");
ParamValidator.validateSceneExists(sceneService, sceneId);
Chromosome result = planResultService.InsertOrder(sceneId, afterOrderId, newOrderData);
return R.ok("插单成功");
}
@PostMapping("/orderInsertAuto")
@Operation(
summary = "自动插单",
description = "创建新工单并按基准时间+冻结期自动排程。若锚点被占用,则占位工单及后续工单后移;若前面有空挡,新工单自动前移到设备最早可用时间",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "自动插单参数",
required = true,
content = @io.swagger.v3.oas.annotations.media.Content(
mediaType = "application/json",
examples = {
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "自动插单示例",
summary = "基础示例",
value = "{\n" +
" \"sceneId\": \"B571EF6682DB463AB2977B1055A74112\",\n" +
" \"newOrder\": {\n" +
" \"orderCode\": \"AUTO_20260326_001\",\n" +
" \"materialId\": \"d5d0dd08-cfb5-2b45-85e9-aba470895423\",\n" +
" \"quantity\": 610000\n" +
" }\n" +
"}"
),
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "大批量示例",
summary = "大批量订单",
value = "{\n" +
" \"sceneId\": \"B571EF6682DB463AB2977B1055A74112\",\n" +
" \"newOrder\": {\n" +
" \"orderCode\": \"AUTO_20260326_002\",\n" +
" \"materialId\": \"cbd0dd08-49b1-0146-83bd-ec6185316c16\",\n" +
" \"quantity\": 1200000\n" +
" }\n" +
"}"
)
}
)
)
)
public R<String> insertOrderAuto(@RequestBody Map<String, Object> params) {
log.info("insertOrderAuto 请求参数: {}", params);
String sceneId = ParamValidator.getString(params, "sceneId", "场景ID");
@SuppressWarnings("unchecked")
Map<String, Object> newOrderData = (Map<String, Object>) params.get("newOrder");
ParamValidator.validateSceneExists(sceneService, sceneId);
planResultService.InsertOrderAuto(sceneId, newOrderData);
return R.ok("自动插单成功");
}
@PostMapping("/ordermerge")
......@@ -1015,7 +1079,7 @@ public class ResourceGanttController {
)
)
public R<SupplyRelationResponse> getSupplyRelation(@RequestBody Map<String, Object> params) {
public R<List<Object>> getSupplyRelation(@RequestBody Map<String, Object> params) {
log.info("getSupplyRelation 请求参数: {}", params);
// 提取参数
String sceneId = ParamValidator.getString(params, "sceneId", "场景ID");
......@@ -1024,7 +1088,7 @@ public class ResourceGanttController {
// 调用服务获取供给关系
List<Object> data = planResultService.getSupplyRelation(sceneId, entryId, sceneService);
return R.ok(SupplyRelationResponse.success(data));
return R.ok(data);
}
......
......@@ -51,4 +51,6 @@ public class ScheduleResultDetail {
public int getProcessingTime() {
return EndTime - StartTime; // 绝对处理时间(分钟)
}
}
......@@ -66,4 +66,6 @@ private Long issplit;
private Long parentid;
private Long statusOrtems;
private LocalDateTime actualEndTimeOrtems;
private String sceneId;
}
\ No newline at end of file
......@@ -183,4 +183,9 @@ public class Order {
private double changeoverCost;
private int changeoverPriority;
/**
* 多级排序路径(用于精确字典序排序,避免浮点精度问题)
*/
private List<Integer> sortKey;
}
\ No newline at end of file
......@@ -416,6 +416,15 @@ public class GeneticAlgorithm {
chromosome.setResultOld(new CopyOnWriteArrayList<>());
}
// 加载锁定工单到ResultOld
List<GAScheduleResult> lockedOrders = GlobalCacheUtil.get("locked_orders_" + sceneId);
if (lockedOrders != null && !lockedOrders.isEmpty()) {
chromosome.setResultOld(ProductionDeepCopyUtil.deepCopyList(lockedOrders, GAScheduleResult.class));
FileHelper.writeLogFile("将 " + lockedOrders.size() + " 个锁定工单加载到初始种群中");
} else {
chromosome.setResultOld(new CopyOnWriteArrayList<>());
}
decoder.decodeChromosomeWithCache(chromosome);
if (chromosome.getFitness() == 0) {
chromosome.setFitness(_fitnessCalculator.calculateFitness(chromosome, _objectiveWeights));
......
......@@ -87,6 +87,11 @@ public class OrderSortService {
return;
}
log.debug("[OrderSort] assignPriority start: orderCount={}, enabled={}, conditionCount={}",
orders.size(),
rule.isEnabled(),
rule.getConditions() == null ? 0 : rule.getConditions().size());
List<Order> sortedOrders = assignPriorityValues(new ArrayList<>(orders), rule);
orders.clear();
orders.addAll(sortedOrders);
......@@ -442,7 +447,7 @@ public class OrderSortService {
private void processConditionsWithMinimizeChangeover(List<Order> orders,
List<OrderSortRule.SortCondition> conditions,
int conditionIndex,
Map<Integer, List<Integer>> priorityPaths) {
Map<String, List<Integer>> priorityPaths) {
// 递归终止:处理完所有条件后停止
if (conditionIndex >= conditions.size() || CollectionUtils.isEmpty(orders)) {
return;
......@@ -455,20 +460,48 @@ public class OrderSortService {
// 应用最小化换线策略
List<Order> optimizedOrders = sortByMinimizeChangeover(orders);
// ========== 换线明细日志 ==========
double totalCost = 0.0;
StringBuilder costLog = new StringBuilder();
costLog.append("[换线明细] 序列顺序:\n");
for (int i = 0; i < optimizedOrders.size(); i++) {
Order order = optimizedOrders.get(i);
double costToPrev = (i == 0) ? 0.0 : calculateChangeoverCost(optimizedOrders.get(i - 1), order);
StringBuilder flags = new StringBuilder();
if (i > 0) {
Order prev = optimizedOrders.get(i - 1);
if (!Objects.equals(prev.getRoutingId(), order.getRoutingId())) flags.append("routing变 ");
if (!Objects.equals(prev.getSerie(), order.getSerie())) flags.append("serie变 ");
if (!Objects.equals(prev.getMaterialCode(), order.getMaterialCode())) flags.append("material变");
}
costLog.append(String.format(" %2d. %s, routingId=%s, serie=%s, materialCode=%s, cost=%.0f %s\n",
(i + 1),
order.getOrderCode(),
order.getRoutingId(),
order.getSerie(),
order.getMaterialCode(),
costToPrev,
flags.toString()));
totalCost += costToPrev;
}
costLog.append(String.format("[换线明细] 总换线成本 = %.0f\n", totalCost));
log.debug(costLog.toString());
// ==================================
// 为最小化换线后的订单分配层级序号
// 相同成本的订单分配相同的序号
for (Order order : optimizedOrders) {
priorityPaths.get(order.getId()).add(order.getChangeoverPriority());
priorityPaths.get(order.getOrderId()).add(order.getChangeoverPriority());
}
// 按优先级分组顺序处理
Map<Integer, List<Order>> priorityGroups = new HashMap<>();
for (Order order : optimizedOrders) {
int priority = order.getChangeoverPriority();
priorityGroups.computeIfAbsent(priority, k -> new ArrayList<>()).add(order);
}
// 按优先级分组顺序处理
List<Integer> sortedPriorities = priorityGroups.keySet().stream()
.sorted()
.collect(Collectors.toList());
......@@ -509,7 +542,7 @@ public class OrderSortService {
// 分配当前层级序号(1、2、3...)
int levelNumber = groupIndex + 1;
groupOrders.forEach(order -> priorityPaths.get(order.getId()).add(levelNumber));
groupOrders.forEach(order -> priorityPaths.get(order.getOrderId()).add(levelNumber));
// 递归处理下一级条件
processConditionsWithMinimizeChangeover(groupOrders, conditions, conditionIndex + 1, priorityPaths);
......@@ -529,19 +562,13 @@ public class OrderSortService {
.collect(Collectors.toList());
// 为每个订单初始化优先级路径
Map<Integer, List<Integer>> priorityPaths = new HashMap<>();
currentOrders.forEach(order -> priorityPaths.put(order.getId(), new ArrayList<>()));
Map<String, List<Integer>> priorityPaths = new HashMap<>();
currentOrders.forEach(order -> priorityPaths.put(order.getOrderId(), new ArrayList<>()));
// 处理排序条件
processConditionsWithMinimizeChangeover(currentOrders, conditions, 0, priorityPaths);
// 将路径转换为最终格式
convertPriorityPathsToNumeric(currentOrders, priorityPaths);
// 按优先级升序排序(数值越小越优先)
return currentOrders.stream()
.sorted(Comparator.comparingDouble(Order::getActualPriority))
.collect(Collectors.toList());
return applyPriorityFromPaths(currentOrders, priorityPaths);
}
/**
......@@ -596,6 +623,7 @@ public class OrderSortService {
if (hasMinimizeChangeover) {
// 仅使用最小化换线策略
return sortWithConfiguredStrategy(orders,rule);
} else {
......@@ -605,19 +633,28 @@ public class OrderSortService {
.collect(Collectors.toList());
// 为每个订单初始化优先级路径
Map<Integer, List<Integer>> priorityPaths = new HashMap<>();
orders.forEach(order -> priorityPaths.put(order.getId(), new ArrayList<>()));
Map<String, List<Integer>> priorityPaths = new HashMap<>();
orders.forEach(order -> priorityPaths.put(order.getOrderId(), new ArrayList<>()));
// 递归分配层级化优先级(核心逻辑)
assignHierarchicalPriority(orders, sortedConditions, 0, priorityPaths);
// 将路径转换为最终格式
convertPriorityPathsToNumeric(orders, priorityPaths);
List<Order> orders1 = applyPriorityFromPaths(orders, priorityPaths);
// 按优先级升序排序(数值越小越优先)
return orders.stream()
.sorted(Comparator.comparingDouble(Order::getActualPriority))
.collect(Collectors.toList());
// ========== 订单优先级信息(debug)==========
log.debug("[OrderSort] ========== 订单优先级信息 ==========");
for (Order order : orders1) {
log.debug("[OrderSort] 订单编码:{}, 开始时间:{}, 结束时间:{}, 真实优先级:{}, sortKey:{}",
order.getOrderCode(),
order.getStartDate(),
order.getDueDate(),
order.getActualPriority(),
order.getSortKey());
}
log.debug("[OrderSort] ====================================");
// ==========================================
return orders1;
}
}
......@@ -627,7 +664,7 @@ public class OrderSortService {
private void assignHierarchicalPriority(List<Order> orders,
List<OrderSortRule.SortCondition> conditions,
int conditionIndex,
Map<Integer, List<Integer>> priorityPaths) {
Map<String, List<Integer>> priorityPaths) {
// 递归终止:处理完所有条件后停止,不再添加额外序号
if (conditionIndex >= conditions.size() || CollectionUtils.isEmpty(orders)) {
return;
......@@ -660,7 +697,7 @@ public class OrderSortService {
// 分配当前层级序号(1、2、3...)
int levelNumber = groupIndex + 1;
groupOrders.forEach(order -> priorityPaths.get(order.getId()).add(levelNumber));
groupOrders.forEach(order -> priorityPaths.get(order.getOrderId()).add(levelNumber));
// 递归处理下一级条件
assignHierarchicalPriority(groupOrders, conditions, conditionIndex + 1, priorityPaths);
......@@ -668,13 +705,17 @@ public class OrderSortService {
}
/**
* 路径转数值:严格匹配条件数格式
* 3个条件 → [1,1,1] → 1.11
* 4个条件 → [1,1,1,1] → 1.111
* 路径转数值:拼接式小数编码
* [1, 1, 1] → 1.11
* [1, 1, 20] → 1.120
* [1, 20, 20] → 1.2020
*
* 注意:此编码假设每层分组数不超过9(或不会连续出现导致歧义的组合)
* 如 [1,12,3] 和 [1,1,23] 都会变成 1.123,会冲突!
*/
private void convertPriorityPathsToNumeric(List<Order> orders, Map<Integer, List<Integer>> priorityPaths) {
private void convertPriorityPathsToNumeric(List<Order> orders, Map<String, List<Integer>> priorityPaths) {
for (Order order : orders) {
List<Integer> path = priorityPaths.get(order.getId());
List<Integer> path = priorityPaths.get(order.getOrderId());
if (CollectionUtils.isEmpty(path)) {
order.setActualPriority(0.0);
continue;
......@@ -685,7 +726,7 @@ public class OrderSortService {
StringBuilder sb = new StringBuilder();
sb.append(path.get(0));
// 后续部分作为小数(严格按照路径长度)
// 后续部分直接拼接,不补零
if (path.size() > 1) {
sb.append(".");
for (int i = 1; i < path.size(); i++) {
......@@ -703,6 +744,42 @@ public class OrderSortService {
}
}
/**
* 将 priorityPaths 应用到订单:写入 sortKey,字典序排序,赋整数 actualPriority
*/
private List<Order> applyPriorityFromPaths(List<Order> orders, Map<String, List<Integer>> priorityPaths) {
// Step1: 写入 sortKey
orders.forEach(order -> order.setSortKey(priorityPaths.get(order.getOrderId())));
// Step2: 按 sortKey 字典序排序
orders.sort((o1, o2) -> {
List<Integer> p1 = o1.getSortKey();
List<Integer> p2 = o2.getSortKey();
if (p1 == null && p2 == null) return 0;
if (p1 == null) return 1;
if (p2 == null) return -1;
for (int i = 0; i < Math.min(p1.size(), p2.size()); i++) {
int cmp = Integer.compare(p1.get(i), p2.get(i));
if (cmp != 0) return cmp;
}
return Integer.compare(p1.size(), p2.size());
});
// Step3: 赋整数排名,相同 sortKey 赋相同值
int rank = 1;
for (int i = 0; i < orders.size(); i++) {
if (i > 0) {
List<Integer> prevKey = orders.get(i - 1).getSortKey();
List<Integer> currKey = orders.get(i).getSortKey();
boolean same = (prevKey == null && currKey == null)
|| (prevKey != null && currKey != null && prevKey.equals(currKey));
if (!same) {
rank++;
}
}
orders.get(i).setActualPriority(rank);
}
return orders;
}
/**
* 创建单个条件的比较器
*/
......
package com.aps.service.Algorithm;
import com.aps.common.util.ProductionDeepCopyUtil;
import com.aps.entity.*;
import com.aps.entity.Algorithm.*;
import com.aps.entity.Algorithm.IDAndChildID.GroupResult;
import com.aps.entity.Algorithm.IDAndChildID.NodeInfo;
......@@ -11,6 +12,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.ScopeMetadata;
import javax.xml.transform.Result;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
......@@ -922,10 +924,640 @@ if(targetOp.getSequence()>1) {
}
}
chromosome.setOrders(new CopyOnWriteArrayList<>(orders));
redecode(chromosome, chromosome.getBaseTime(), globalParam);
}
/**
* 插入新订单到排产中
* 将新订单排在指定订单后面
*
* @param chromosome 染色体对象
* @param afterOrderId 插入位置订单ID(新订单将排在此订单后面)
* @param newOrderId 新订单ID
* @param newLaunchOrder 新订单对象
* @param newProcessExecs 新订单的工序列表
* @param globalParam 全局参数
*/
public void InsertOrder(Chromosome chromosome, String afterOrderId, String newOrderId,
ProdLaunchOrder newLaunchOrder, List<ProdProcessExec> newProcessExecs,
GlobalParam globalParam) {
List<Entry> allOperations = chromosome.getAllOperations();
List<Order> orders = chromosome.getOrders();
List<GroupResult> OperatRels = chromosome.getOperatRel();
// 1. 创建新的Order对象
Order newOrder = new Order();
newOrder.setOrderId(newOrderId);
newOrder.setOrderCode(newLaunchOrder.getOrderCode());
newOrder.setQuantity(newLaunchOrder.getQuantity());
newOrder.setPriority(newLaunchOrder.getOrderPriority() != null ? newLaunchOrder.getOrderPriority() : 1);
newOrder.setMaterialId(newLaunchOrder.getMaterialId());
newOrder.setMaterialCode(newLaunchOrder.getMaterialCode());
newOrder.setMaterialName(newLaunchOrder.getMaterialName());
newOrder.setStartDate(newLaunchOrder.getStartDate());
newOrder.setDueDate(newLaunchOrder.getEndDate());
newOrder.setRoutingId(newLaunchOrder.getRoutingId());
newOrder.setRoutingCode(newLaunchOrder.getRoutingCode());
newOrder.setSerie(newLaunchOrder.getSerie());
// 获取当前最大的Order ID
int maxOrderId = orders.stream()
.mapToInt(Order::getId)
.max()
.orElse(0) + 1;
newOrder.setId(maxOrderId);
orders.add(newOrder);
// 2. 创建新订单的Entry对象
int maxGroupId = OperatRels.size();
int newGroupId = maxGroupId + 1;
List<Entry> newEntrys = new ArrayList<>();
List<String> newIdList = new ArrayList<>();
List<String> newChildIdList = new ArrayList<>();
// 从ProdProcessExec创建Entry
for (ProdProcessExec processExec : newProcessExecs) {
Entry newEntry = new Entry();
newEntry.setExecId(processExec.getExecId());
newEntry.setOrderId(newOrderId);
newEntry.setOrderCode(newLaunchOrder.getOrderCode());
newEntry.setGroupId(newGroupId);
newEntry.setQuantity(newLaunchOrder.getQuantity());
newEntry.setSequence(processExec.getTaskSeq() != null ? processExec.getTaskSeq().intValue() : 1);
newEntry.setTaskSeq(processExec.getTaskSeq()); // 设置taskSeq字段
newEntry.setState(2);
// 设置工序相关信息
newEntry.setRoutingDetailId(processExec.getRoutingDetailId());
newEntry.setRoutingId(processExec.getRoutingId());
newEntry.setRoutingCode(processExec.getRoutingCode());
newEntry.setRoutingName(processExec.getRoutingName());
newEntry.setRoutingDetailName(processExec.getRoutingDetailName());
// 设置产品相关信息
newEntry.setProductId(newLaunchOrder.getMaterialId());
newEntry.setProductCode(newLaunchOrder.getMaterialCode());
newEntry.setProductName(newLaunchOrder.getMaterialName());
// 设置部门信息
if (processExec.getDepartmentId() != null) {
newEntry.setDepartmentId(processExec.getDepartmentId());
}
// 设置中断属性
newEntry.setIsInterrupt(processExec.getCanInterrupt());
// 设置设备类型信息
newEntry.setEquipTypeID(processExec.getMachineId());
// 使用runtime字段
if (processExec.getRuntime() != null) {
newEntry.setRuntime(processExec.getRuntime());
}
newEntry.setPriority(newLaunchOrder.getOrderPriority() != null ? newLaunchOrder.getOrderPriority() : 1);
// 设置其他时间相关字段
if (processExec.getSetupTime() != null) {
newEntry.setSetupTime(processExec.getSetupTime());
}
newEntry.setChangeLineTime(processExec.getChangeLineTime());
newEntry.setConstTime(processExec.getConstTime());
newEntry.setPreTime(processExec.getPreprocessingTime());
newEntry.setTeardownTime(processExec.getPostprocessingTime());
// 设置设备相关信息
newEntry.setEquipTypeName(processExec.getEquipTypeName());
newEntry.setEquipTypeCode(processExec.getEquipTypeCode());
// 设备配置将在后面从插入位置的订单复制,这里先设置为空列表
newEntry.setMachineOptions(new ArrayList<>());
// 设置默认选中的设备ID(将在后面从插入位置订单复制)
newEntry.setSelectMachineID(null);
newEntrys.add(newEntry);
}
// 3. 建立工序之间的依赖关系
for (int i = 0; i < newEntrys.size(); i++) {
Entry entry = newEntrys.get(i);
if (i == 0) {
// 第一道工序,没有前置工序
newIdList.add(entry.getExecId());
newChildIdList.add(i + 1 < newEntrys.size() ? newEntrys.get(i + 1).getExecId() : "");
} else if (i == newEntrys.size() - 1) {
// 最后一道工序,没有后续工序
newIdList.add(entry.getExecId());
newChildIdList.add("");
} else {
// 中间工序
newIdList.add(entry.getExecId());
newChildIdList.add(newEntrys.get(i + 1).getExecId());
}
}
// 4. 添加新数据到OperatRels
OperatRels = IdGroupingWithDualSerial.addNewDataWithIsolatedGroup(OperatRels, newIdList, newChildIdList);
chromosome.setOperatRel(new CopyOnWriteArrayList<>(OperatRels));
// 5. 更新全局ID和Entry信息
int globalOpId = chromosome.getGlobalOpList().stream()
.mapToInt(GlobalOperationInfo::getGlobalOpId)
.max()
.orElse(0) + 1;
GroupResult newGroupResult = OperatRels.get(OperatRels.size() - 1);
List<NodeInfo> nodeInfoList = newGroupResult.getNodeInfoList();
for (NodeInfo nodeInfo : nodeInfoList) {
Entry entry = newEntrys.stream()
.filter(t -> t.getExecId().equals(nodeInfo.getOriginalId()))
.findFirst()
.orElse(null);
if (entry != null) {
entry.setId(nodeInfo.getGlobalSerial());
entry.setGroupId(newGroupId);
entry.setSequence(nodeInfo.getGroupSerial());
// 设置前置和后续工序依赖
if (nodeInfo.getNewParentIds() != null) {
List<OperationDependency> dependencies = new ArrayList<>();
for (int id : nodeInfo.getNewParentIds()) {
OperationDependency od = new OperationDependency();
od.setPrevOperationId(id);
dependencies.add(od);
}
entry.setPrevEntryIds(dependencies);
}
if (nodeInfo.getNewChildIds() != null) {
List<OperationDependency> dependencies = new ArrayList<>();
for (int id : nodeInfo.getNewChildIds()) {
OperationDependency od = new OperationDependency();
od.setNextOperationId(id);
dependencies.add(od);
}
entry.setNextEntryIds(dependencies);
}
// 添加到allOperations
chromosome.getAllOperations().add(entry);
// 添加到全局操作列表
GlobalOperationInfo info = new GlobalOperationInfo();
info.setGlobalOpId(globalOpId);
info.setGroupId(entry.getGroupId());
info.setSequence(entry.getSequence());
info.setOp(entry);
chromosome.getGlobalOpList().add(info);
globalOpId++;
// 添加默认的机器选择(第一个可选设备)
chromosome.getMachineSelection().add(1);
}
}
// 6. 将新订单插入到指定订单后面的位置,并使用相同的设备配置
List<Entry> afterOrderOps = allOperations.stream()
.filter(o -> o.getOrderId().equals(afterOrderId))
.sorted(Comparator.comparing(Entry::getSequence))
.collect(Collectors.toList());
if (!afterOrderOps.isEmpty()) {
Entry lastOpOfAfterOrder = afterOrderOps.get(afterOrderOps.size() - 1);
List<Integer> operationSequencing = chromosome.getOperationSequencing();
// 找到afterOrder最后一道工序在operationSequencing中的位置
OptionalInt operationIndex = IntStream.range(0, operationSequencing.size())
.filter(i -> operationSequencing.get(i).equals(lastOpOfAfterOrder.getGroupId()))
.findFirst();
if (operationIndex.isPresent()) {
int insertPosition = operationIndex.getAsInt() + 1;
// 在该位置后插入新订单的所有工序
for (int i = 0; i < newEntrys.size(); i++) {
chromosome.getOperationSequencing().add(insertPosition + i, newGroupId);
}
} else {
// 如果找不到位置,就添加到末尾
for (int i = 0; i < newEntrys.size(); i++) {
chromosome.getOperationSequencing().add(newGroupId);
}
}
// 使用插入位置订单的设备配置来更新新订单的工序
// 这样可以确保新订单使用相同的生产线和设备
for (int i = 0; i < newEntrys.size() && i < afterOrderOps.size(); i++) {
Entry newEntry = newEntrys.get(i);
Entry referenceEntry = afterOrderOps.get(i);
// 复制设备配置
if (referenceEntry.getMachineOptions() != null && !referenceEntry.getMachineOptions().isEmpty()) {
newEntry.setMachineOptions(new ArrayList<>(referenceEntry.getMachineOptions()));
newEntry.setSelectMachineID(referenceEntry.getSelectMachineID());
newEntry.setEquipCode(referenceEntry.getEquipCode());
newEntry.setEquipName(referenceEntry.getEquipName());
}
}
} else {
// 如果找不到afterOrder,就添加到末尾
for (int i = 0; i < newEntrys.size(); i++) {
chromosome.getOperationSequencing().add(newGroupId);
}
}
// 临时设置isCheckSf为false,避免CreateNewOpSequence重新生成operationSequencing
boolean originalIsCheckSf = globalParam.isIsCheckSf();
globalParam.setIsCheckSf(false);
// 7. 重新解码,重新计算排产结果
redecode(chromosome, chromosome.getBaseTime(), globalParam);
// 恢复isCheckSf的原始值
globalParam.setIsCheckSf(originalIsCheckSf);
}
public void InsertOrderAuto(Chromosome chromosome, String newOrderId,
ProdLaunchOrder newLaunchOrder, List<ProdProcessExec> newProcessExecs,
List<ProdEquipment> newProdEquipments,
LocalDateTime anchorTime, GlobalParam globalParam) {
List<Order> orders = chromosome.getOrders();
List<GroupResult> OperatRels = chromosome.getOperatRel();
Order newOrder = new Order();
newOrder.setOrderId(newOrderId);
newOrder.setOrderCode(newLaunchOrder.getOrderCode());
newOrder.setQuantity(newLaunchOrder.getQuantity());
newOrder.setPriority(newLaunchOrder.getOrderPriority() != null ? newLaunchOrder.getOrderPriority() : 1);
newOrder.setMaterialId(newLaunchOrder.getMaterialId());
newOrder.setMaterialCode(newLaunchOrder.getMaterialCode());
newOrder.setMaterialName(newLaunchOrder.getMaterialName());
newOrder.setStartDate(newLaunchOrder.getStartDate());
newOrder.setDueDate(newLaunchOrder.getEndDate());
newOrder.setRoutingId(newLaunchOrder.getRoutingId());
newOrder.setRoutingCode(newLaunchOrder.getRoutingCode());
newOrder.setSerie(newLaunchOrder.getSerie());
int maxOrderId = orders.stream().mapToInt(Order::getId).max().orElse(0) + 1;
newOrder.setId(maxOrderId);
orders.add(newOrder);
int maxGroupId = OperatRels.size();
int newGroupId = maxGroupId + 1;
List<Entry> newEntrys = new ArrayList<>();
List<String> newIdList = new ArrayList<>();
List<String> newChildIdList = new ArrayList<>();
for (ProdProcessExec processExec : newProcessExecs) {
Entry newEntry = new Entry();
newEntry.setExecId(processExec.getExecId());
newEntry.setOrderId(newOrderId);
newEntry.setOrderCode(newLaunchOrder.getOrderCode());
newEntry.setGroupId(newGroupId);
newEntry.setQuantity(newLaunchOrder.getQuantity());
newEntry.setSequence(processExec.getTaskSeq() != null ? processExec.getTaskSeq().intValue() : 1);
newEntry.setTaskSeq(processExec.getTaskSeq());
newEntry.setState(2);
newEntry.setRoutingDetailId(processExec.getRoutingDetailId());
newEntry.setRoutingId(processExec.getRoutingId());
newEntry.setRoutingCode(processExec.getRoutingCode());
newEntry.setRoutingName(processExec.getRoutingName());
newEntry.setRoutingDetailName(processExec.getRoutingDetailName());
newEntry.setProductId(newLaunchOrder.getMaterialId());
newEntry.setProductCode(newLaunchOrder.getMaterialCode());
newEntry.setProductName(newLaunchOrder.getMaterialName());
if (processExec.getDepartmentId() != null) {
newEntry.setDepartmentId(processExec.getDepartmentId());
}
newEntry.setIsInterrupt(processExec.getCanInterrupt());
newEntry.setEquipTypeID(processExec.getMachineId());
if (processExec.getRuntime() != null) {
newEntry.setRuntime(processExec.getRuntime());
}
if (processExec.getSingleOut() != null) {
newEntry.setSingleOut(processExec.getSingleOut());
}
newEntry.setPriority(newLaunchOrder.getOrderPriority() != null ? newLaunchOrder.getOrderPriority() : 1);
if (processExec.getSetupTime() != null) {
newEntry.setSetupTime(processExec.getSetupTime());
}
newEntry.setChangeLineTime(processExec.getChangeLineTime());
newEntry.setConstTime(processExec.getConstTime());
newEntry.setPreTime(processExec.getPreprocessingTime());
newEntry.setTeardownTime(processExec.getPostprocessingTime());
newEntry.setEquipTypeName(processExec.getEquipTypeName());
newEntry.setEquipTypeCode(processExec.getEquipTypeCode());
// 与 InitEntrys 对齐:设备来源于 prod_equipment(真实设备ID)
List<MachineOption> machineOptions = newProdEquipments.stream()
.filter(pe -> pe.getExecId() != null && pe.getExecId().equals(processExec.getExecId()))
.map(pe -> {
MachineOption mo = new MachineOption();
mo.setMachineId(pe.getEquipId());
mo.setRuntime(pe.getRuntime() != null ? pe.getRuntime() : BigDecimal.ZERO);
mo.setSingleOut(pe.getSingleOut() != null ? pe.getSingleOut() : BigDecimal.ONE);
mo.setEquipCode(pe.getEquipCode());
mo.setEquipName(pe.getEquipName());
mo.setResourceCode(pe.getResourceCode());
mo.setProcessingTime(pe.getSpeed() != null ? pe.getSpeed() : 0d);
return mo;
})
.collect(Collectors.toList());
if (machineOptions.isEmpty()) {
throw new RuntimeException("自动插单失败:工序[" + processExec.getExecId() + "]没有可用设备");
}
newEntry.setMachineOptions(machineOptions);
newEntry.setSelectMachineID(null); // 后面按最优序号回填
newEntrys.add(newEntry);
}
// 建工序链
for (int i = 0; i < newEntrys.size(); i++) {
Entry entry = newEntrys.get(i);
if (i == 0) {
newIdList.add(entry.getExecId());
newChildIdList.add(i + 1 < newEntrys.size() ? newEntrys.get(i + 1).getExecId() : "");
} else if (i == newEntrys.size() - 1) {
newIdList.add(entry.getExecId());
newChildIdList.add("");
} else {
newIdList.add(entry.getExecId());
newChildIdList.add(newEntrys.get(i + 1).getExecId());
}
}
OperatRels = IdGroupingWithDualSerial.addNewDataWithIsolatedGroup(OperatRels, newIdList, newChildIdList);
chromosome.setOperatRel(new CopyOnWriteArrayList<>(OperatRels));
int globalOpId = chromosome.getGlobalOpList().stream()
.mapToInt(GlobalOperationInfo::getGlobalOpId)
.max()
.orElse(0) + 1;
GroupResult newGroupResult = OperatRels.get(OperatRels.size() - 1);
List<NodeInfo> nodeInfoList = newGroupResult.getNodeInfoList();
Entry firstEntry = null;
for (NodeInfo nodeInfo : nodeInfoList) {
Entry entry = newEntrys.stream()
.filter(t -> t.getExecId().equals(nodeInfo.getOriginalId()))
.findFirst()
.orElse(null);
if (entry != null) {
entry.setId(nodeInfo.getGlobalSerial());
entry.setGroupId(newGroupId);
entry.setSequence(nodeInfo.getGroupSerial());
if (nodeInfo.getNewParentIds() != null) {
List<OperationDependency> dependencies = new ArrayList<>();
for (int id : nodeInfo.getNewParentIds()) {
OperationDependency od = new OperationDependency();
od.setPrevOperationId(id);
dependencies.add(od);
}
entry.setPrevEntryIds(dependencies);
}
if (nodeInfo.getNewChildIds() != null) {
List<OperationDependency> dependencies = new ArrayList<>();
for (int id : nodeInfo.getNewChildIds()) {
OperationDependency od = new OperationDependency();
od.setNextOperationId(id);
dependencies.add(od);
}
entry.setNextEntryIds(dependencies);
}
chromosome.getAllOperations().add(entry);
GlobalOperationInfo info = new GlobalOperationInfo();
info.setGlobalOpId(globalOpId);
info.setGroupId(entry.getGroupId());
info.setSequence(entry.getSequence());
info.setOp(entry);
chromosome.getGlobalOpList().add(info);
int bestSeq = calcBestMachineSeq(entry, chromosome, anchorTime);
chromosome.getMachineSelection().add(bestSeq);
entry.setSelectMachineID(entry.getMachineOptions().get(bestSeq - 1).getMachineId());
if (entry.getSequence() == 1) {
firstEntry = entry;
}
globalOpId++;
}
}
for (int i = 0; i < newEntrys.size(); i++) {
chromosome.getOperationSequencing().add(newGroupId);
}
int firstOperationId = firstEntry != null ? firstEntry.getId() : -1;
boolean originalIsCheckSf = globalParam.isIsCheckSf();
globalParam.setIsCheckSf(false);
// 第一次解码:先拿到首工序所在设备
if (anchorTime != null && firstEntry != null) {
firstEntry.setDesignatedStartTime(anchorTime);
}
redecode(chromosome, chromosome.getBaseTime(), globalParam);
// 第二次调整:按冻结锚点插位
if (anchorTime != null && firstOperationId > 0) {
GAScheduleResult firstResult = chromosome.getResult().stream()
.filter(r -> r.getOperationId() == firstOperationId)
.findFirst()
.orElse(null);
if (firstResult != null) {
long machineId = firstResult.getMachineId();
int anchorSeconds = (int) java.time.temporal.ChronoUnit.SECONDS.between(chromosome.getBaseTime(), anchorTime);
int firstDuration = Math.max(1, firstResult.getEndTime() - firstResult.getStartTime());
// 该设备老任务(不含新工单)
List<GAScheduleResult> machineOldResults = chromosome.getResult().stream()
.filter(r -> r.getMachineId() == machineId)
.filter(r -> r.getGroupId() != firstResult.getGroupId())
.sorted(Comparator.comparingInt(GAScheduleResult::getStartTime))
.collect(Collectors.toList());
// 锚点处占位任务(跨锚点)
GAScheduleResult pivot = machineOldResults.stream()
.filter(r -> r.getStartTime() <= anchorSeconds && r.getEndTime() > anchorSeconds)
.findFirst()
.orElse(null);
// 若锚点没被跨,则取锚点后第一任务
if (pivot == null) {
pivot = machineOldResults.stream()
.filter(r -> r.getStartTime() >= anchorSeconds)
.findFirst()
.orElse(null);
}
// 锚点前最后一单
GAScheduleResult prev = machineOldResults.stream()
.filter(r -> r.getEndTime() <= anchorSeconds)
.reduce((a, b) -> b)
.orElse(null);
int newDesignatedStart;
if (pivot != null) {
if (pivot.getStartTime() <= anchorSeconds && pivot.getEndTime() > anchorSeconds) {
// 锚点被占:应从占位任务结束后开始
newDesignatedStart = pivot.getEndTime();
} else {
// 锚点后有任务:插到该任务前
newDesignatedStart = pivot.getStartTime();
}
} else {
// 锚点后无任务:从锚点或锚点前最后一单结束后开始
newDesignatedStart = prev != null ? Math.max(anchorSeconds, prev.getEndTime()) : anchorSeconds;
}
// 不能早于冻结边界
if (newDesignatedStart < anchorSeconds) {
newDesignatedStart = anchorSeconds;
}
// 锁新工单首工序起点
Entry firstEntryRef = chromosome.getAllOperations().stream()
.filter(e -> e.getId() == firstOperationId)
.findFirst()
.orElse(null);
if (firstEntryRef != null) {
firstEntryRef.setDesignatedStartTime(chromosome.getBaseTime().plusSeconds(newDesignatedStart));
}
GAScheduleResult firstLock = chromosome.getResult().stream()
.filter(r -> r.getOperationId() == firstOperationId)
.findFirst()
.orElse(null);
if (firstLock != null) {
firstLock.setDesignatedStartTime(newDesignatedStart);
firstLock.setLockStartTime(1);
}
// 仅后移 newDesignatedStart 及其后的老任务
int finalNewDesignatedStart = newDesignatedStart;
List<GAScheduleResult> needShift = machineOldResults.stream()
.filter(r -> r.getStartTime() >= finalNewDesignatedStart)
.collect(Collectors.toList());
for (GAScheduleResult oldResult : needShift) {
oldResult.setDesignatedStartTime(oldResult.getStartTime() + firstDuration);
oldResult.setLockStartTime(1);
}
redecode(chromosome, chromosome.getBaseTime(), globalParam);
}
}
globalParam.setIsCheckSf(originalIsCheckSf);
}
private int calcBestMachineSeq(Entry entry, Chromosome chromosome, LocalDateTime anchorTime) {
List<MachineOption> rawOptions = entry.getMachineOptions();
if (rawOptions == null || rawOptions.isEmpty()) {
throw new RuntimeException("工序无可选设备: " + entry.getExecId());
}
// 只保留当前排产设备池中的设备
Set<Long> availableMachineIds = chromosome.getMachines().stream()
.map(Machine::getId)
.collect(Collectors.toSet());
List<MachineOption> options = rawOptions.stream()
.filter(o -> availableMachineIds.contains(o.getMachineId()))
.collect(Collectors.toList());
if (options.isEmpty()) {
throw new RuntimeException("工序无可用设备(不在当前排产设备池): " + entry.getExecId());
}
Map<Long, Integer> machineEnd = chromosome.getResult().stream()
.collect(Collectors.groupingBy(
GAScheduleResult::getMachineId,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(GAScheduleResult::getEndTime)),
o -> o.map(GAScheduleResult::getEndTime).orElse(0)
)
));
int anchorSec = 0;
if (anchorTime != null && chromosome.getBaseTime() != null) {
anchorSec = (int) java.time.temporal.ChronoUnit.SECONDS.between(chromosome.getBaseTime(), anchorTime);
if (anchorSec < 0) {
anchorSec = 0;
}
}
int bestIdx = 0;
double bestScore = Double.MAX_VALUE;
for (int i = 0; i < options.size(); i++) {
MachineOption op = options.get(i);
int ready = Math.max(anchorSec, machineEnd.getOrDefault(op.getMachineId(), 0));
double duration;
if (entry.getConstTime() == 1) {
duration = Math.max(0d, op.getProcessingTime());
} else {
double runtime = op.getRuntime() != null ? op.getRuntime().doubleValue() : 0d;
double singleOut = op.getSingleOut() != null ? op.getSingleOut().doubleValue() : 1d;
if (singleOut <= 0) {
singleOut = 1d;
}
duration = runtime / singleOut * entry.getQuantity();
}
double score = ready + duration;
if (score < bestScore) {
bestScore = score;
bestIdx = i;
}
}
// 注意:这里返回的是在rawOptions中的序号(machineSelection要求按entry.machineOptions顺序)
Long bestMachineId = options.get(bestIdx).getMachineId();
int rawIndex = IntStream.range(0, rawOptions.size())
.filter(i -> rawOptions.get(i).getMachineId() == bestMachineId)
.findFirst()
.orElse(0);
return rawIndex + 1;
}
public void MergeOrder(Chromosome chromosome, String sourceorderId,String targetorderId, GlobalParam globalParam) {
List<Entry> allOperations = chromosome.getAllOperations();
List<GlobalOperationInfo> globalOpList= chromosome.getGlobalOpList();
......
......@@ -3,6 +3,7 @@ package com.aps.service;
import com.aps.common.util.R;
import com.aps.entity.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
......@@ -67,4 +68,20 @@ public interface LanuchService {
*/
R<String> savePlan(String sceneId);
/**
* 插单功能:向现有场景添加新订单
* orderId 会在方法内部自动生成 UUID
*
* @param sceneId 场景ID
* @param orderCode 订单编码
* @param materialId 物料ID
* @param startDate 开始时间
* @param endDate 结束时间
* @param priority 优先级
* @param quantity 数量
* @return 结果
*/
R<String> insertOrder(String sceneId, String orderCode, String materialId,
LocalDateTime startDate, LocalDateTime endDate, Integer priority, Double quantity);
}
\ No newline at end of file
......@@ -1169,6 +1169,13 @@ public class ChromosomeDataService {
config.setEntityName(entityName);
config.setDataSource(DataSourceType.FILE);
config.setFieldName("Machines");
}
// 特殊处理:当实体是Order时,映射到orders字段
else if ("order".equalsIgnoreCase(key)) {
config = new EntityConfig();
config.setEntityName(entityName);
config.setDataSource(DataSourceType.FILE);
config.setFieldName("orders");
} else {
// 自动创建数据库配置(默认行为)
config = createDefaultDbConfig(entityName);
......
......@@ -64,6 +64,8 @@ public class LanuchServiceImpl implements LanuchService {
private final PlanResourceService planResourceService;
private final OrderMaterialRequirementService orderMaterialRequirementService;
private final EquipinfoService equipinfoService;
private final MaterialInfoService materialInfoService;
private final RoutingHeaderService routingHeaderService;
/**
* 生成场景数据
*
......@@ -113,12 +115,22 @@ public class LanuchServiceImpl implements LanuchService {
if (apsOrderIds.isEmpty()) {
apsOrders = new ArrayList<>();
} else {
apsOrders = apsOrderService.lambdaQuery()
// Oracle IN子句限制最多1000个值,需要分批查询
apsOrders = new ArrayList<>();
int batchSize = 1000;
for (int i = 0; i < apsOrderIds.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, apsOrderIds.size());
List<String> batchIds = apsOrderIds.subList(i, endIndex);
List<ApsOrder> batchOrders = apsOrderService.lambdaQuery()
.eq(ApsOrder::getIsdeleted, 0)
.eq(ApsOrder::getStatus, 4)
// .eq(ApsOrder::getCreatoruserid, username)
.in(ApsOrder::getId, apsOrderIds)
.in(ApsOrder::getId, batchIds)
.list();
apsOrders.addAll(batchOrders);
}
}
if (CollectionUtils.isEmpty(apsOrders)) {
throw new SceneGenerationException("工单列表不能为空");
......@@ -243,52 +255,157 @@ public class LanuchServiceImpl implements LanuchService {
mesOrder.setTaskType(prodLaunchOrder.getFinishOrderId() != null ? "半成品" : "成品");
mesOrderList.add(mesOrder);
}
mesOrderService.remove(new LambdaQueryWrapper<>());
boolean saved = mesOrderService.saveBatch(mesOrderList);
// 更新或插入 mes_order 数据
List<String> orderIds = prodLanuchList.stream().map(Order::getOrderId).distinct().collect(Collectors.toList());
if (saved) {
log.info("插入 {} 条数据到mes_order", mesOrderList.size());
// 查询已存在的 mes_order 记录
List<MesOrder> existingMesOrders = mesOrderService.lambdaQuery()
.in(MesOrder::getMesCode, orderIds)
.list();
Map<String, MesOrder> existingMesOrderMap = existingMesOrders.stream()
.collect(Collectors.toMap(MesOrder::getMesCode, order -> order));
List<MesOrder> toUpdate = new ArrayList<>();
List<MesOrder> toInsert = new ArrayList<>();
for (Order prodLaunchOrder : prodLanuchList) {
MesOrder existingOrder = existingMesOrderMap.get(prodLaunchOrder.getOrderId());
MesOrder mesOrder;
if (existingOrder != null) {
// 更新现有记录
mesOrder = existingOrder;
mesOrder.setLastModifierUserId(Long.valueOf(sceneConfig.getCreateUser()));
mesOrder.setLastModificationTime(LocalDateTime.now());
toUpdate.add(mesOrder);
} else {
log.error("插入失败");
throw new RuntimeException("插入mes_order失败");
// 创建新记录
mesOrder = new MesOrder();
mesOrder.setCreatorUserId(Long.valueOf(sceneConfig.getCreateUser()));
mesOrder.setCreationTime(LocalDateTime.now());
toInsert.add(mesOrder);
}
// 设置部门信息(使用upId字段存储部门ID)
Integer departmentId = orderDepartmentMap.get(prodLaunchOrder.getOrderId());
if (departmentId != null) {
mesOrder.setUpId(Long.valueOf(departmentId));
}
mesOrder.setMesCode(prodLaunchOrder.getOrderId());
mesOrder.setMaterialId(prodLaunchOrder.getMaterialId());
mesOrder.setQuantity(prodLaunchOrder.getQuantity());
mesOrder.setRoutingId(Long.valueOf(prodLaunchOrder.getRoutingId()));
mesOrder.setDemandStartDate(prodLaunchOrder.getStartDate());
mesOrder.setDemandFinishDate(prodLaunchOrder.getDueDate());
mesOrder.setDrawnNumber(prodLaunchOrder.getMaterialCode());
mesOrder.setOrderCode(prodLaunchOrder.getOrderCode());
mesOrder.setStatus(12L);
mesOrder.setIsDeleted(0L);
mesOrder.setTaskType(prodLaunchOrder.getFinishOrderId() != null ? "半成品" : "成品");
}
// 批量更新和插入
boolean updateSuccess = true;
boolean insertSuccess = true;
if (!toUpdate.isEmpty()) {
updateSuccess = mesOrderService.updateBatchById(toUpdate);
log.info("更新 {} 条 mes_order 记录", toUpdate.size());
}
if (!toInsert.isEmpty()) {
insertSuccess = mesOrderService.saveBatch(toInsert);
log.info("插入 {} 条 mes_order 记录", toInsert.size());
}
if (!updateSuccess || !insertSuccess) {
log.error("mes_order 操作失败");
throw new RuntimeException("mes_order 操作失败");
}
// 构建 orderId -> mes_order.id 映射,供 dispatch 的 PART_TASK_READY_ID 使用
List<String> orderIds = prodLanuchList.stream().map(Order::getOrderId).distinct().collect(Collectors.toList());
List<MesOrder> savedMesOrders = mesOrderService.lambdaQuery()
List<MesOrder> allMesOrders = mesOrderService.lambdaQuery()
.in(MesOrder::getMesCode, orderIds)
.list();
Map<String, Long> mesOrderIdByOrderId = savedMesOrders.stream()
Map<String, Long> mesOrderIdByOrderId = allMesOrders.stream()
.collect(Collectors.toMap(MesOrder::getMesCode, MesOrder::getId, (a, b) -> a));
// List<ProdProcessExec> processExecList = prodProcessExecService.lambdaQuery()
// .eq(ProdProcessExec::getSceneId, sceneId)
// .list();
List<Entry> entrys= chromosome.getAllOperations();
// 查询全表中已存在的、未删除的 dispatch 记录(不再按场景区分)
List<Dispatch> existingDispatches = dispatchService.lambdaQuery()
.eq(Dispatch::getIsDeleted, 0L)
.list();
// 构建现有记录的映射 (mesCode + routingDetailId 作为全局唯一标识)
Map<String, Dispatch> existingDispatchMap = existingDispatches.stream()
.collect(Collectors.toMap(
dispatch -> dispatch.getMesCode() + "_" + dispatch.getRoutingDetailId(),
dispatch -> dispatch,
(existing, replacement) -> existing
));
List<Dispatch> toUpdateDispatches = new ArrayList<>();
List<Dispatch> toInsertDispatches = new ArrayList<>();
// 记录已经处理过的 key,避免同一个 MES_CODE + ROUTING_DETAIL_ID 在一次下发中生成多条记录
Set<String> processedDispatchKeys = new HashSet<>();
// 遍历GAScheduleResult结果并转换为dispatch
List<Dispatch> dispatches = new ArrayList<>();
for (GAScheduleResult gaResult : chromosome.getResult()) {
Entry entry1 = entrys.stream()
.filter(entry -> entry.getExecId().equals(gaResult.getExecId()))
.findFirst()
.orElse(null);
Dispatch dispatch = new Dispatch();
if (entry1 == null) {
log.warn("未找到对应的Entry,ExecId: {}", gaResult.getExecId());
continue;
}
// 只根据 MES_CODE(=orderId) + ROUTING_DETAIL_ID 唯一确定一条 dispatch
String dispatchKey = gaResult.getOrderId() + "_" + entry1.getRoutingDetailId();
// 如果本次下发中已经处理过这个 key,则直接跳过,避免生成重复数据
if (processedDispatchKeys.contains(dispatchKey)) {
continue;
}
Dispatch existingDispatch = existingDispatchMap.get(dispatchKey);
Dispatch dispatch;
if (existingDispatch != null) {
// 更新现有记录
dispatch = existingDispatch;
dispatch.setLastModifierUserId(Long.parseLong(sceneConfig.getCreateUser()));
dispatch.setLastModificationTime(LocalDateTime.now());
toUpdateDispatches.add(dispatch);
} else {
// 创建新记录
dispatch = new Dispatch();
dispatch.setCreatorUserId(Long.parseLong(sceneConfig.getCreateUser()));
dispatch.setCreationTime(LocalDateTime.now());
dispatch.setExecuteId((long) entry1.getId());
dispatch.setRoutingDetailId(entry1.getRoutingDetailId());
toInsertDispatches.add(dispatch);
}
// 更新相关字段
dispatch.setENof(gaResult.getOrderId());
dispatch.setQuantity(gaResult.getQuantity());
// 设置场景ID
dispatch.setSceneId(sceneId);
// 设置设备ID(从排产结果中获取)
dispatch.setEquipId(gaResult.getMachineId());
LocalDateTime baseTime = chromosome.getBaseTime() != null ? chromosome.getBaseTime() : LocalDateTime.now();
dispatch.setBeginTime(baseTime.plusSeconds(gaResult.getStartTime()));
dispatch.setEndTime(baseTime.plusSeconds(gaResult.getEndTime()));
dispatch.setENof(gaResult.getOrderId());
dispatch.setCreatorUserId(Long.parseLong(sceneConfig.getCreateUser()));
// 设置状态等其他字段
dispatch.setTaskSeq(entry1.getTaskSeq());
dispatch.setMesCode(gaResult.getOrderId());
dispatch.setRoutingDetailId(entry1.getRoutingDetailId());
dispatch.setOpe(entry1.getRoutingDetailName());
dispatch.setRoutingDetailId(entry1.getRoutingDetailId());
dispatch.setIsDeleted(0L);
dispatch.setStatus(12L);
// PART_TASK_READY_ID:取对应 mes_order 的主键 id(按 orderId/mesCode 关联)
......@@ -298,14 +415,46 @@ public class LanuchServiceImpl implements LanuchService {
}
dispatch.setShopid(entry1.getDepartmentId());
// 添加到列表中
dispatches.add(dispatch);
// 标记该 key 已处理,后续相同 key 不再生成新记录
processedDispatchKeys.add(dispatchKey);
// 验证时间冲突:查所有场景该设备的dispatch,防止跨场景占用同一时间段
List<Dispatch> equipDispatches = dispatchService.lambdaQuery()
.eq(Dispatch::getIsDeleted, 0L)
.eq(Dispatch::getEquipId, dispatch.getEquipId())
.list();
for (Dispatch existing : equipDispatches) {
if (existing.getMesCode() != null
&& existing.getMesCode().equals(dispatch.getMesCode())) {
continue; // 同一工单,允许
}
if (dispatch.getBeginTime() != null && dispatch.getEndTime() != null
&& existing.getBeginTime() != null && existing.getEndTime() != null
&& dispatch.getBeginTime().isBefore(existing.getEndTime())
&& dispatch.getEndTime().isAfter(existing.getBeginTime())) {
throw new RuntimeException(String.format(
"下发失败:设备%d在%s-%s已被工单%s占用,新工单%s无法下发",
dispatch.getEquipId(), existing.getBeginTime(), existing.getEndTime(),
existing.getMesCode(), dispatch.getMesCode()));
}
}
}
// 批量更新和插入 dispatch
boolean updateDispatchSuccess = true;
boolean insertDispatchSuccess = true;
if (!toUpdateDispatches.isEmpty()) {
updateDispatchSuccess = dispatchService.updateBatchById(toUpdateDispatches);
log.info("更新 {} 条 dispatch 记录", toUpdateDispatches.size());
}
dispatchService.remove(new LambdaQueryWrapper<>());
// 批量保存到数据库
if (!toInsertDispatches.isEmpty()) {
insertDispatchSuccess = dispatchService.saveBatch(toInsertDispatches);
log.info("插入 {} 条 dispatch 记录", toInsertDispatches.size());
}
boolean savedDispatch = dispatchService.saveOrUpdateBatch(dispatches);
boolean savedDispatch = updateDispatchSuccess && insertDispatchSuccess;
// 构建ExecId到GAScheduleResult的映射,用于获取工序开始时间
Map<String, GAScheduleResult> execIdToScheduleMap = new HashMap<>();
......@@ -333,7 +482,7 @@ public class LanuchServiceImpl implements LanuchService {
if (operationStartTime != null) {
omr.setShortageTime(operationStartTime);
}
if (omr.getQuantity() >0) {
if (omr.getQuantity() > 0) {
orderMaterials.add(omr);
}
}
......@@ -341,13 +490,12 @@ public class LanuchServiceImpl implements LanuchService {
}
}
// 先删除表中所有数据
orderMaterialRequirementService.remove(new LambdaQueryWrapper<>());
// 再批量插入新数据
orderMaterialRequirementService.saveBatch(orderMaterials);
System.out.println("保存物料需求成功");
log.info("保存物料需求成功");
if (savedDispatch) {
// 清理历史版本的排产结果文件,只留下当前版本
boolean cleanupSuccess = sceneService.cleanupHistoricalVersions(sceneId);
......@@ -692,19 +840,33 @@ public class LanuchServiceImpl implements LanuchService {
}
public List<RoutingDetail> getRoutingDetails(List<Long> routingHeaderIds) {
if (routingHeaderIds.isEmpty()) {
return new ArrayList<>();
}
// Oracle IN子句限制最多1000个值,需要分批查询
List<RoutingDetail> allRoutingDetails = new ArrayList<>();
int batchSize = 1000;
for (int i = 0; i < routingHeaderIds.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, routingHeaderIds.size());
List<Long> batchIds = routingHeaderIds.subList(i, endIndex);
LambdaQueryWrapper<RoutingDetail> wrapper = new LambdaQueryWrapper<>();
wrapper.in(RoutingDetail::getRoutingHeaderId, routingHeaderIds)
wrapper.in(RoutingDetail::getRoutingHeaderId, batchIds)
.eq(RoutingDetail::getIsDeleted, 0) // 添加 is_deleted=0 过滤条件
.orderByAsc(RoutingDetail::getTaskSeq);
List<RoutingDetail> routingDetails = routingDetailMapper.selectList(wrapper);
List<RoutingDetail> batchDetails = routingDetailMapper.selectList(wrapper);
allRoutingDetails.addAll(batchDetails);
}
if (CollectionUtils.isEmpty(routingDetails)) {
if (CollectionUtils.isEmpty(allRoutingDetails)) {
log.error("工艺下无工序信息: {}", routingHeaderIds);
throw new RuntimeException("工艺下无工序信息: " + routingHeaderIds);
}
return routingDetails;
return allRoutingDetails;
}
/**
......@@ -1510,4 +1672,103 @@ public class LanuchServiceImpl implements LanuchService {
throw e;
}
}
/**
* 插单功能:向现有场景添加新订单
*
* @param sceneId 场景ID
* @param orderCode 订单编码
* @param materialId 物料ID
* @param startDate 开始时间
* @param endDate 结束时间
* @param priority 优先级
* @param quantity 数量
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<String> insertOrder(String sceneId, String orderCode, String materialId,
LocalDateTime startDate, LocalDateTime endDate,
Integer priority, Double quantity) {
// 1. 参数校验
if (sceneId == null || sceneId.trim().isEmpty()) {
throw new IllegalArgumentException("场景ID不能为空");
}
if (orderCode == null || orderCode.trim().isEmpty()) {
throw new IllegalArgumentException("订单编码不能为空");
}
if (materialId == null || materialId.trim().isEmpty()) {
throw new IllegalArgumentException("物料ID不能为空");
}
if (quantity == null || quantity <= 0) {
throw new IllegalArgumentException("数量必须大于0");
}
// 2. 检查场景是否存在
ProdSceneConfig sceneConfig = prodSceneConfigService.lambdaQuery()
.eq(ProdSceneConfig::getSceneId, sceneId)
.one();
if (sceneConfig == null) {
throw new RuntimeException("场景不存在");
}
// 3. 根据物料ID查询物料信息(使用 MaterialInfo 表)
MaterialInfo material = materialInfoService.lambdaQuery()
.eq(MaterialInfo::getId, materialId)
.eq(MaterialInfo::getIsdeleted, 0)
.one();
if (material == null) {
throw new RuntimeException("物料不存在");
}
// 4. 查询工艺路线(按创建时间倒序,获取最新的)
RoutingHeader routingHeader = routingHeaderService.lambdaQuery()
.eq(RoutingHeader::getMaterialId, materialId)
.eq(RoutingHeader::getIsDeleted, 0)
.orderByDesc(RoutingHeader::getCreationTime)
.last("FETCH FIRST 1 ROWS ONLY")
.one();
if (routingHeader == null) {
throw new RuntimeException("物料没有配置工艺路线");
}
log.info("查询到物料 {} 的最新工艺路线: ID={}, Code={}, Version={}, CreationTime={}",
materialId, routingHeader.getId(), routingHeader.getCode(),
routingHeader.getVersion(), routingHeader.getCreationTime());
// 5. 生成订单ID(UUID格式)
String orderId = java.util.UUID.randomUUID().toString();
// 6. 创建 ProdLaunchOrder
ProdLaunchOrder launchOrder = new ProdLaunchOrder();
launchOrder.setOrderId(orderId);
launchOrder.setSceneId(sceneId);
launchOrder.setOrderCode(orderCode);
launchOrder.setMaterialId(materialId);
launchOrder.setMaterialCode(material.getCode());
launchOrder.setMaterialName(material.getName());
launchOrder.setStartDate(startDate);
launchOrder.setEndDate(endDate);
launchOrder.setOrderPriority(priority != null ? priority : 1);
launchOrder.setQuantity(quantity);
launchOrder.setRoutingId(routingHeader.getId());
launchOrder.setRoutingCode(routingHeader.getCode());
launchOrder.setCreateUser(sceneConfig.getCreateUser());
launchOrder.setOrderStatus("S"); // S未启动
// 7. 保存到 PROD_LAUNCH_ORDER 表
prodLaunchOrderService.save(launchOrder);
log.info("成功创建插单订单,订单ID: {}, 订单编码: {}", orderId, orderCode);
// 8. 转换工单于工序执行表(参考 launchPlan 方法)
List<ProdLaunchOrder> orderList = new ArrayList<>();
orderList.add(launchOrder);
convertToProcessExecBatch(orderList, sceneId);
// 9. 生成工序关联关系(参考 launchPlan 方法)
generateProcessRelationsBatch(orderList, sceneId);
log.info("插单成功,场景ID: {}, 订单编码: {}", sceneId, orderCode);
return R.ok("插单成功,订单ID: " + orderId);
}
}
package com.aps.service.plan;
import com.aps.common.util.ProductionDeepCopyUtil;
import com.aps.common.util.redis.RedisUtils;
import com.aps.entity.Algorithm.*;
import com.aps.entity.ApsTimeConfig;
import com.aps.entity.Dispatch;
import com.aps.entity.MesShiftWorkSched;
import com.aps.entity.PlanResource;
import com.aps.entity.Schedule.SceneChromsome;
import com.aps.entity.basic.*;
import com.aps.service.ApsTimeConfigService;
import com.aps.service.DispatchService;
import com.aps.service.MesShiftWorkSchedService;
import com.aps.service.PlanResourceService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
* 锁定期工单处理服务
* 负责处理锁定期工单的加载、复制和添加到排产结果中
*/
@Slf4j
@Service
public class LockedOrderProcessorService {
@Autowired
private ApsTimeConfigService apsTimeConfigService;
@Autowired
private DispatchService dispatchService;
@Autowired
private SceneService sceneService;
@Autowired
private PlanResourceService planResourceService;
@Autowired
private MesShiftWorkSchedService mesShiftWorkSchedService;
@Autowired
private RedisUtils redisUtils;
/**
* 添加锁定期工单到调度结果中
* 从上一次排产结果中获取冻结期内的工单,保持时间、数量、设备、Entry等信息不变
*
* @param chromosome 染色体对象
*/
public void addLockedOrdersToResult(Chromosome chromosome) {
try {
// 1. 获取锁定期配置
LockPeriodConfig config = getLockPeriodConfig();
if (config == null) {
return;
}
log.debug("锁定期范围: {} 到 {} (冻结期: {}秒)", config.lockStartTime, config.lockEndTime, config.freezeSeconds);
// 2. 从已下发场景获取锁定期工单
LockedOrdersData lockedData = loadLockedOrdersFromScenes(config, chromosome.getBaseTime(), chromosome.getScenarioID());
// // 3. 如果无法从已下发场景获取,则从Dispatch表创建
// if (lockedData.results.isEmpty()) {
// lockedData = createLockedOrdersFromDispatch(config, chromosome.getBaseTime());
// }
// 4. 添加锁定期工单到chromosome
addLockedOrdersToChromosome(chromosome, lockedData, config);
} catch (Exception e) {
log.error("添加锁定期工单时发生错误: {}", e.getMessage(), e);
}
}
/**
* 获取锁定期配置
*/
private LockPeriodConfig getLockPeriodConfig() {
ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>());
if (timeConfig == null || timeConfig.getBaseTime() == null) {
log.warn("未找到ApsTimeConfig配置或baseTime为空,无法添加锁定期工单");
return null;
}
LocalDateTime baseTime = timeConfig.getBaseTime();
long freezeSeconds = timeConfig.getFreezeDate() != null ? timeConfig.getFreezeDate().longValue() : 0;
if (freezeSeconds <= 0) {
log.debug("冻结期秒数为{},跳过添加锁定期工单", freezeSeconds);
return null;
}
LockPeriodConfig config = new LockPeriodConfig();
config.baseTime = baseTime;
config.freezeSeconds = freezeSeconds;
config.lockStartTime = baseTime;
config.lockEndTime = baseTime.plusSeconds(freezeSeconds);
return config;
}
/**
* 从已下发场景加载锁定期工单
*/
private LockedOrdersData loadLockedOrdersFromScenes(LockPeriodConfig config, LocalDateTime newBaseTime, String newSceneId) {
LockedOrdersData data = new LockedOrdersData();
try {
// 查询锁定期内的Dispatch记录
List<Dispatch> frozenDispatches = queryFrozenDispatches(config);
if (frozenDispatches.isEmpty()) {
log.debug("冻结期内没有工单需要添加");
return data;
}
log.debug("查询到冻结期内的工序数: {}", frozenDispatches.size());
// 获取所有不同的sceneId
Set<String> dispatchSceneIds = frozenDispatches.stream()
.map(Dispatch::getSceneId)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
if (dispatchSceneIds.isEmpty()) {
log.warn("Dispatch表中的工单没有sceneId信息");
return data;
}
log.debug("锁定期工单来自 {} 个已下发场景: {}", dispatchSceneIds.size(), dispatchSceneIds);
// 遍历每个场景,加载锁定期工单
for (String sceneId : dispatchSceneIds) {
processSceneForLockedOrders(sceneId, frozenDispatches, config, newBaseTime, data, newSceneId);
}
log.debug("从 {} 个已下发场景排产结果中获取到 {} 个锁定期工单", dispatchSceneIds.size(), data.results.size());
} catch (Exception ex) {
log.error("从已下发场景排产结果获取锁定期工单失败: {}", ex.getMessage(), ex);
}
return data;
}
/**
* 处理单个场景的锁定期工单
*/
private void processSceneForLockedOrders(String sceneId, List<Dispatch> allDispatches,
LockPeriodConfig config, LocalDateTime newBaseTime,
LockedOrdersData data, String newSceneId) {
try {
log.debug("处理场景: {}", sceneId);
// 加载场景的排产结果
Chromosome dispatchChromosome = loadSceneChromosome(sceneId);
if (dispatchChromosome == null || dispatchChromosome.getResult() == null) {
return;
}
LocalDateTime dispatchBaseTime = dispatchChromosome.getBaseTime();
if (dispatchBaseTime == null) {
log.warn("场景 {} 的baseTime为空,无法计算时间偏移", sceneId);
return;
}
log.debug("场景 {} - 已下发场景baseTime: {}, 新场景baseTime: {}", sceneId, dispatchBaseTime, newBaseTime);
// 筛选属于当前场景的Dispatch记录
List<Dispatch> currentSceneDispatches = allDispatches.stream()
.filter(d -> sceneId.equals(d.getSceneId()))
.collect(Collectors.toList());
// 找出锁定期内的订单ID
Set<String> lockedOrderIds = findLockedOrderIds(dispatchChromosome, currentSceneDispatches,
config, dispatchBaseTime, sceneId);
log.debug("场景 {} 中发现 {} 个订单在锁定期内有工序: {}", sceneId, lockedOrderIds.size(), lockedOrderIds);
// 复制锁定期订单的所有工序
copyLockedOrders(dispatchChromosome, currentSceneDispatches, lockedOrderIds,
dispatchBaseTime, newBaseTime, sceneId, data, config);
// 直接从已下发场景匹配Entry信息,避免ID冲突
matchLockedEntriesFromDispatchScene(dispatchChromosome, data, sceneId, newSceneId);
} catch (Exception ex) {
log.error("处理场景 {} 的排产结果失败: {}", sceneId, ex.getMessage(), ex);
}
}
/**
* 直接从已下发场景匹配锁定期工单的Entry信息,避免ID冲突
*/
private void matchLockedEntriesFromDispatchScene(Chromosome dispatchChromosome, LockedOrdersData data, String oldSceneId, String newSceneId) {
if (dispatchChromosome.getAllOperations() == null) {
return;
}
log.debug("场景 {} 中有 {} 个Entry,开始匹配锁定期工单的Entry信息",
oldSceneId, dispatchChromosome.getAllOperations().size());
for (GAScheduleResult result : data.results) {
log.debug("处理锁定期工单: ExecId={}, OperationId={}", result.getExecId(), result.getOperationId());
// 直接从已下发场景的allOperations中查找Entry
Entry entry = dispatchChromosome.getAllOperations().stream()
.filter(e -> e.getExecId().equals(result.getExecId()))
.findFirst()
.orElse(null);
if (entry != null) {
// 检查原始Entry的machineOptions
log.debug("原始Entry {} 的machineOptions数量: {}",
entry.getExecId(),
entry.getMachineOptions() != null ? entry.getMachineOptions().size() : "null");
if (entry.getMachineOptions() != null && !entry.getMachineOptions().isEmpty()) {
for (int i = 0; i < entry.getMachineOptions().size(); i++) {
MachineOption option = entry.getMachineOptions().get(i);
log.debug(" machineOption[{}]: machineId={}, equipCode={}, equipName={}",
i, option.getMachineId(), option.getEquipCode(), option.getEquipName());
}
}
// 创建Entry副本,保持原有的machineOptions和设备信息
Entry lockedEntry;
try {
lockedEntry = ProductionDeepCopyUtil.deepCopy(entry);
log.debug("深拷贝后Entry {} 的machineOptions数量: {}, equipCode={}, equipName={}",
lockedEntry.getExecId(),
lockedEntry.getMachineOptions() != null ? lockedEntry.getMachineOptions().size() : "null",
lockedEntry.getEquipCode(), lockedEntry.getEquipName());
} catch (Exception e) {
log.error("深拷贝Entry失败: {}", e.getMessage(), e);
lockedEntry = entry; // 失败时使用原始Entry
}
// 确保selectMachineID设置正确
lockedEntry.setSelectMachineID(result.getMachineId());
// 修复:不再过滤machineOptions,保持原有的所有设备选项
// 原因:过滤machineOptions会导致在修改订单数量时,重新解码时无法正确处理
// 解码器会根据所有可用的machineOptions进行调度,而不是被限制在单一设备
// 这样可以避免因数量改变导致的时间冲突问题
log.debug("锁定期Entry {} 保留所有machineOptions: 数量={}, equipCode={}, equipName={}",
lockedEntry.getExecId(),
lockedEntry.getMachineOptions() != null ? lockedEntry.getMachineOptions().size() : "null",
lockedEntry.getEquipCode(), lockedEntry.getEquipName());
// 重要:更新sceneId为新场景ID,确保查询时能正确匹配
if (newSceneId != null) {
String originalSceneId = lockedEntry.getSceneId();
lockedEntry.setSceneId(newSceneId);
log.debug("更新锁定期Entry的sceneId: {} -> {}", originalSceneId, newSceneId);
}
data.entries.put(result.getExecId(), lockedEntry);
log.debug("从场景 {} 匹配到锁定期Entry: ExecId={}, 设备ID={}, equipCode={}, equipName={}, machineOptions数量={}",
oldSceneId, result.getExecId(), result.getMachineId(),
lockedEntry.getEquipCode(), lockedEntry.getEquipName(),
lockedEntry.getMachineOptions() != null ? lockedEntry.getMachineOptions().size() : "null");
} else {
log.warn("在场景 {} 中未找到工序 {} 对应的Entry", oldSceneId, result.getExecId());
}
}
log.debug("场景 {} 成功匹配 {} 个Entry信息", oldSceneId, data.entries.size());
}
/**
* 找出锁定期内的订单ID
*/
private Set<String> findLockedOrderIds(Chromosome chromosome, List<Dispatch> dispatches,
LockPeriodConfig config, LocalDateTime baseTime, String sceneId) {
Set<String> lockedOrderIds = new HashSet<>();
log.debug("场景 {} 开始分析工序时间,锁定期范围: {} 到 {}", sceneId, config.lockStartTime, config.lockEndTime);
for (GAScheduleResult result : chromosome.getResult()) {
LocalDateTime startTime = baseTime.plusSeconds(result.getStartTime());
log.debug("分析工序: ExecId={}, OrderId={}, 原始StartTime={}秒, 绝对开始时间={}",
result.getExecId(), result.getOrderId(), result.getStartTime(), startTime);
// 检查工序是否在锁定期内
if (!startTime.isBefore(config.lockStartTime) && !startTime.isAfter(config.lockEndTime)) {
log.debug("工序在锁定期内: ExecId={}, OrderId={}, 开始时间={}",
result.getExecId(), result.getOrderId(), startTime);
// 通过订单号匹配Dispatch记录
boolean hasValidDispatch = dispatches.stream()
.anyMatch(d -> d.getMesCode() != null &&
d.getMesCode().equals(result.getOrderId()) &&
d.getBeginTime() != null &&
!d.getBeginTime().isBefore(config.lockStartTime) &&
!d.getBeginTime().isAfter(config.lockEndTime));
if (hasValidDispatch && result.getOrderId() != null) {
lockedOrderIds.add(result.getOrderId());
log.debug("订单 {} 被标记为锁定期订单(通过订单号匹配)", result.getOrderId());
} else {
log.warn("工序 {} 在锁定期内但没有有效的Dispatch记录(订单号: {})",
result.getExecId(), result.getOrderId());
}
}
}
return lockedOrderIds;
}
/**
* 复制锁定期订单的所有工序
*/
private void copyLockedOrders(Chromosome chromosome, List<Dispatch> dispatches,
Set<String> lockedOrderIds, LocalDateTime oldBaseTime,
LocalDateTime newBaseTime, String sceneId, LockedOrdersData data,
LockPeriodConfig config) {
for (GAScheduleResult result : chromosome.getResult()) {
if (!lockedOrderIds.contains(result.getOrderId())) {
continue;
}
// 检查是否有对应的Dispatch记录
boolean hasDispatch = dispatches.stream()
.anyMatch(d -> d.getMesCode() != null && d.getMesCode().equals(result.getOrderId()));
if (!hasDispatch) {
log.warn("订单 {} 的工序 {} 没有对应的Dispatch记录", result.getOrderId(), result.getExecId());
continue;
}
// 深拷贝工单并转换时间
GAScheduleResult lockedResult = copyGAScheduleResult(result);
lockedResult.setIsLocked(false); // 重要:标记为锁定工单,确保重新解码时不被清除
// 转换时间:从旧baseTime转换到新baseTime
LocalDateTime prevStartTime = oldBaseTime.plusSeconds(result.getStartTime());
LocalDateTime prevEndTime = oldBaseTime.plusSeconds(result.getEndTime());
int newStartTime = (int) java.time.temporal.ChronoUnit.SECONDS.between(newBaseTime, prevStartTime);
int newEndTime = (int) java.time.temporal.ChronoUnit.SECONDS.between(newBaseTime, prevEndTime);
lockedResult.setStartTime(newStartTime);
lockedResult.setEndTime(newEndTime);
data.results.add(lockedResult);
data.machineIds.add(result.getMachineId());
boolean isInLockPeriod = !prevStartTime.isBefore(config.lockStartTime) &&
!prevStartTime.isAfter(config.lockEndTime);
log.debug("从场景 {} 获取订单 {} 的工序: ExecId={}, OrderCode={}, MachineId={}, 原始时间=[{} 到 {}], 新相对时间=[{} 到 {}]秒, 是否在锁定期内={}",
sceneId, result.getOrderId(), lockedResult.getExecId(), lockedResult.getOrderCode(),
lockedResult.getMachineId(), prevStartTime, prevEndTime, newStartTime, newEndTime, isInLockPeriod);
}
}
/**
* 匹配锁定期工单的Entry信息
*/
private void matchLockedEntries(Chromosome chromosome, LockedOrdersData data, String sceneId) {
if (chromosome.getAllOperations() == null) {
return;
}
log.debug("场景 {} 中有 {} 个Entry,开始匹配锁定期工单的Entry信息",
sceneId, chromosome.getAllOperations().size());
for (GAScheduleResult result : data.results) {
log.debug("处理锁定期工单: ExecId={}, OperationId={}", result.getExecId(), result.getOperationId());
Entry entry = null;
if (result.getOperationId() > 0) {
entry = chromosome.getAllOperations().stream()
.filter(e -> e.getId() == result.getOperationId())
.findFirst()
.orElse(null);
}
// 如果通过OperationId找不到,尝试通过ExecId匹配
if (entry == null) {
entry = chromosome.getAllOperations().stream()
.filter(e -> e.getExecId().equals(result.getExecId()))
.findFirst()
.orElse(null);
if (entry != null) {
result.setOperationId(entry.getId());
log.debug("通过ExecId匹配到Entry并修正OperationId: ExecId={}, EntryId={}",
result.getExecId(), entry.getId());
}
}
if (entry != null) {
data.entries.put(result.getExecId(), entry);
} else {
log.warn("未找到工序 {} 对应的Entry", result.getExecId());
}
}
log.debug("场景 {} 成功匹配 {} 个Entry信息", sceneId, data.entries.size());
}
/**
* 从Dispatch表创建锁定期工单
*/
private LockedOrdersData createLockedOrdersFromDispatch(LockPeriodConfig config, LocalDateTime baseTime) {
LockedOrdersData data = new LockedOrdersData();
log.debug("从Dispatch表查询锁定期工单");
List<Dispatch> frozenDispatches = queryFrozenDispatches(config);
if (frozenDispatches.isEmpty()) {
log.debug("冻结期内没有工单需要添加");
return data;
}
log.debug("查询到冻结期内的工序数: {}", frozenDispatches.size());
for (Dispatch dispatch : frozenDispatches) {
GAScheduleResult result = createResultFromDispatch(dispatch, baseTime);
data.results.add(result);
data.machineIds.add(result.getMachineId());
}
return data;
}
/**
* 从Dispatch记录创建GAScheduleResult
*/
private GAScheduleResult createResultFromDispatch(Dispatch dispatch, LocalDateTime baseTime) {
GAScheduleResult result = new GAScheduleResult();
log.debug("处理Dispatch记录: id={}, executeId={}, mesCode={}, equipId={}, beginTime={}, endTime={}",
dispatch.getId(), dispatch.getExecuteId(), dispatch.getMesCode(),
dispatch.getEquipId(), dispatch.getBeginTime(), dispatch.getEndTime());
// 设置基本信息
result.setOperationId(dispatch.getExecuteId() != null ? dispatch.getExecuteId().intValue() : 0);
result.setExecId(dispatch.getExecuteId() != null ? dispatch.getExecuteId().toString() : "");
result.setOrderId(dispatch.getMesCode() != null ? dispatch.getMesCode() : "");
result.setOrderCode(dispatch.getMesCode());
result.setMachineId(dispatch.getEquipId() != null ? dispatch.getEquipId() : 0L);
result.setQuantity(dispatch.getQuantity() != null ? dispatch.getQuantity() : 0.0);
result.setProductId("");
result.setProductName("");
result.setProductCode("");
log.debug("从Dispatch创建锁定期工单: executeId={}, OperationId={}, mesCode={}",
dispatch.getExecuteId(), result.getOperationId(), dispatch.getMesCode());
// 计算相对时间
if (dispatch.getBeginTime() != null && dispatch.getEndTime() != null) {
int startTime = (int) java.time.temporal.ChronoUnit.SECONDS.between(baseTime, dispatch.getBeginTime());
int endTime = (int) java.time.temporal.ChronoUnit.SECONDS.between(baseTime, dispatch.getEndTime());
log.debug("锁定期工单时间计算: ExecId={}, Dispatch时间=[{} 到 {}], baseTime={}, 相对时间=[{} 到 {}]秒",
dispatch.getExecuteId(), dispatch.getBeginTime(), dispatch.getEndTime(),
baseTime, startTime, endTime);
result.setStartTime(startTime);
result.setEndTime(endTime);
result.setProcessingTime((double) (endTime - startTime) / 60.0);
}
// 设置其他信息
result.setIsLocked(true);
result.setTeardownTime(0);
result.setChangeOverTime(0);
result.setPreTime(0);
result.setSeq(dispatch.getTaskSeq() != null ? dispatch.getTaskSeq().intValue() : 0);
result.setBomTime(0);
result.setDesignatedStartTime(-1);
result.setLockStartTime(0);
// 关键修复:为dispatch工单设置forcedMachineId,确保重新解码时使用这个设备
// 这样dispatch工单的设备就不会被改变
result.setForcedMachineId(dispatch.getEquipId());
// 创建GeneDetails
List<ScheduleResultDetail> geneDetails = new ArrayList<>();
ScheduleResultDetail detail = new ScheduleResultDetail();
detail.setStartTime(result.getStartTime());
detail.setEndTime(result.getEndTime());
detail.setQuantity(dispatch.getQuantity() != null ? dispatch.getQuantity() : 0.0);
geneDetails.add(detail);
result.setGeneDetails(geneDetails);
return result;
}
/**
* 将锁定期工单添加到chromosome
*/
private void addLockedOrdersToChromosome(Chromosome chromosome, LockedOrdersData data, LockPeriodConfig config) {
// 1. 添加到result列表
if (chromosome.getResult() == null) {
chromosome.setResult(new CopyOnWriteArrayList<>());
}
int beforeSize = chromosome.getResult().size();
chromosome.getResult().addAll(data.results);
log.debug("成功添加 {} 个锁定期工单到调度结果中,result大小从{}变为{}",
data.results.size(), beforeSize, chromosome.getResult().size());
// 1.5. 关键修复:将锁定期工单添加到ResultOld中,确保重新解码时能获取正确的设备ID
if (chromosome.getResultOld() == null) {
chromosome.setResultOld(new CopyOnWriteArrayList<>());
}
// 深拷贝锁定期工单的GAScheduleResult到ResultOld
for (GAScheduleResult result : data.results) {
try {
GAScheduleResult resultCopy = ProductionDeepCopyUtil.deepCopy(result);
chromosome.getResultOld().add(resultCopy);
log.debug("添加锁定期工单到ResultOld: OperationId={}, MachineId={}, IsLocked={}",
resultCopy.getOperationId(), resultCopy.getMachineId(), resultCopy.isIsLocked());
} catch (Exception e) {
log.error("深拷贝GAScheduleResult失败: {}", e.getMessage(), e);
chromosome.getResultOld().add(result); // 失败时使用原始对象
}
}
log.debug("成功添加 {} 个锁定期工单到ResultOld中", data.results.size());
// 2. 复制锁定期订单的Order对象
copyLockedOrderObjects(chromosome, data, config);
// 3. 添加Entry到allOperations,并重新分配ID避免冲突
if (!data.entries.isEmpty() && chromosome.getAllOperations() != null) {
int beforeEntrySize = chromosome.getAllOperations().size();
// 获取当前最大的Entry ID,避免ID冲突
int maxExistingId = chromosome.getAllOperations().stream()
.mapToInt(Entry::getId)
.max()
.orElse(0);
int nextAvailableId = maxExistingId + 1;
Map<Integer, Integer> lockedEntryIdMapping = new HashMap<>();
// 为锁定期Entry重新分配ID
for (Entry lockedEntry : data.entries.values()) {
int oldId = lockedEntry.getId();
lockedEntryIdMapping.put(oldId, nextAvailableId);
lockedEntry.setId(nextAvailableId);
// 更新对应的GAScheduleResult的operationId(包括result和resultOld)
int finalNextAvailableId = nextAvailableId;
// 更新result中的operationId
data.results.stream()
.filter(result -> result.getOperationId() == oldId)
.forEach(result -> result.setOperationId(finalNextAvailableId));
// 关键修复:同时更新resultOld中的operationId
if (chromosome.getResultOld() != null) {
chromosome.getResultOld().stream()
.filter(result -> result.getOperationId() == oldId)
.forEach(result -> {
result.setOperationId(finalNextAvailableId);
log.debug("更新ResultOld中的operationId: {} -> {}, ExecId={}",
oldId, finalNextAvailableId, result.getExecId());
});
}
log.debug("锁定期Entry ID重新分配: {} -> {}, ExecId={}",
oldId, nextAvailableId, lockedEntry.getExecId());
nextAvailableId++;
}
// 同步更新锁定期Entry内部依赖中的工序ID引用(仅修正本批次重分配的ID)
for (Entry lockedEntry : data.entries.values()) {
if (lockedEntry.getPrevEntryIds() != null) {
for (OperationDependency dep : lockedEntry.getPrevEntryIds()) {
if (dep == null) {
continue;
}
Integer newPrev = lockedEntryIdMapping.get(dep.getPrevOperationId());
if (newPrev != null) {
dep.setPrevOperationId(newPrev);
}
Integer newNext = lockedEntryIdMapping.get(dep.getNextOperationId());
if (newNext != null) {
dep.setNextOperationId(newNext);
}
}
}
if (lockedEntry.getNextEntryIds() != null) {
for (OperationDependency dep : lockedEntry.getNextEntryIds()) {
if (dep == null) {
continue;
}
Integer newPrev = lockedEntryIdMapping.get(dep.getPrevOperationId());
if (newPrev != null) {
dep.setPrevOperationId(newPrev);
}
Integer newNext = lockedEntryIdMapping.get(dep.getNextOperationId());
if (newNext != null) {
dep.setNextOperationId(newNext);
}
}
}
}
// 同步更新锁定期Entry内部依赖中的工序ID引用(仅修正本批次重分配的ID)
for (Entry lockedEntry : data.entries.values()) {
if (lockedEntry.getPrevEntryIds() != null) {
for (OperationDependency dep : lockedEntry.getPrevEntryIds()) {
if (dep == null) {
continue;
}
Integer newPrev = lockedEntryIdMapping.get(dep.getPrevOperationId());
if (newPrev != null) {
dep.setPrevOperationId(newPrev);
}
Integer newNext = lockedEntryIdMapping.get(dep.getNextOperationId());
if (newNext != null) {
dep.setNextOperationId(newNext);
}
}
}
if (lockedEntry.getNextEntryIds() != null) {
for (OperationDependency dep : lockedEntry.getNextEntryIds()) {
if (dep == null) {
continue;
}
Integer newPrev = lockedEntryIdMapping.get(dep.getPrevOperationId());
if (newPrev != null) {
dep.setPrevOperationId(newPrev);
}
Integer newNext = lockedEntryIdMapping.get(dep.getNextOperationId());
if (newNext != null) {
dep.setNextOperationId(newNext);
}
}
}
}
chromosome.getAllOperations().addAll(data.entries.values());
log.debug("成功添加 {} 个锁定期Entry到allOperations中,大小从{}变为{},ID范围: {}-{}",
data.entries.size(), beforeEntrySize, chromosome.getAllOperations().size(),
maxExistingId + 1, nextAvailableId - 1);
}
// 4. 确保锁定期设备存在
ensureLockedMachines(chromosome, data.machineIds, config);
// 5. 将锁定期订单添加到operationSequencing中,确保它们会被解码处理
if (!data.entries.isEmpty() && chromosome.getOperationSequencing() != null) {
List<Integer> currentSequencing = new ArrayList<>(chromosome.getOperationSequencing());
// 获取所有锁定期订单的ID
Set<String> lockedOrderIds = data.entries.values().stream()
.map(Entry::getOrderId)
.collect(Collectors.toSet());
// 为每个锁定期订单添加其所有工序到排产序列
for (String orderId : lockedOrderIds) {
// 找到该订单的所有工序,按sequence排序
List<Entry> orderEntries = data.entries.values().stream()
.filter(entry -> orderId.equals(entry.getOrderId()))
.sorted(Comparator.comparing(Entry::getSequence))
.collect(Collectors.toList());
// 将每个工序的groupId添加到operationSequencing
for (Entry entry : orderEntries) {
currentSequencing.add(entry.getGroupId());
log.debug("添加锁定期工序到排产序列: OrderId={}, GroupId={}, Sequence={}",
orderId, entry.getGroupId(), entry.getSequence());
}
}
chromosome.setOperationSequencing(currentSequencing);
log.debug("更新operationSequencing,从{}个工序增加到{}个工序",
chromosome.getOperationSequencing().size() - (currentSequencing.size() - chromosome.getOperationSequencing().size()),
currentSequencing.size());
}
// 6. 关键修复:将锁定期Entry添加到globalOpList中,确保重新解码时不会丢失
if (!data.entries.isEmpty() && chromosome.getGlobalOpList() != null) {
// 获取当前最大的globalOpId
int maxGlobalOpId = chromosome.getGlobalOpList().stream()
.mapToInt(GlobalOperationInfo::getGlobalOpId)
.max()
.orElse(-1);
int nextGlobalOpId = maxGlobalOpId + 1;
// 为每个锁定期Entry创建GlobalOperationInfo并添加到globalOpList
List<Entry> sortedEntries = data.entries.values().stream()
.sorted(Comparator.comparing(Entry::getGroupId)
.thenComparing(Entry::getSequence))
.collect(Collectors.toList());
for (Entry lockedEntry : sortedEntries) {
GlobalOperationInfo info = new GlobalOperationInfo();
info.setGlobalOpId(nextGlobalOpId);
info.setGroupId(lockedEntry.getGroupId());
info.setSequence(lockedEntry.getSequence());
info.setOp(lockedEntry);
chromosome.getGlobalOpList().add(info);
log.debug("添加锁定期Entry到globalOpList: GlobalOpId={}, EntryId={}, OrderCode={}, GroupId={}, Sequence={}, EquipCode={}",
nextGlobalOpId, lockedEntry.getId(), lockedEntry.getOrderCode(),
lockedEntry.getGroupId(), lockedEntry.getSequence(), lockedEntry.getEquipCode());
nextGlobalOpId++;
}
log.debug("成功添加 {} 个锁定期Entry到globalOpList中,globalOpList大小变为{}",
sortedEntries.size(), chromosome.getGlobalOpList().size());
}
// 7. 关键修复:同步更新machineSelection,确保长度与globalOpList一致
if (!data.entries.isEmpty() && chromosome.getMachineSelection() != null) {
List<Integer> machineSelection = chromosome.getMachineSelection();
// 为每个锁定期Entry添加机器选择索引
// 锁定期工序的机器已经确定,所以机器选择索引设为1(表示使用machineOptions中的第一个,也是唯一的机器)
// 注意:machineSelection的索引是从1开始的,不是从0开始
List<Entry> sortedEntries = data.entries.values().stream()
.sorted(Comparator.comparing(Entry::getGroupId)
.thenComparing(Entry::getSequence))
.collect(Collectors.toList());
for (Entry lockedEntry : sortedEntries) {
// 锁定期工序的机器已经确定,machineSelection设为1(索引从1开始)
machineSelection.add(1);
log.debug("添加锁定期Entry的机器选择: EntryId={}, OrderCode={}, EquipCode={}, MachineSelectionIndex=1",
lockedEntry.getId(), lockedEntry.getOrderCode(), lockedEntry.getEquipCode());
}
log.debug("成功添加 {} 个锁定期Entry的机器选择到machineSelection中,machineSelection大小变为{}",
sortedEntries.size(), machineSelection.size());
// 验证:确保machineSelection长度与globalOpList长度一致
if (chromosome.getGlobalOpList().size() != machineSelection.size()) {
log.error("警告:machineSelection长度({})与globalOpList长度({})不一致!",
machineSelection.size(), chromosome.getGlobalOpList().size());
} else {
log.debug("验证通过:machineSelection长度({})与globalOpList长度({})一致",
machineSelection.size(), chromosome.getGlobalOpList().size());
}
}
}
/**
* 复制锁定期订单的Order对象
*/
private void copyLockedOrderObjects(Chromosome chromosome, LockedOrdersData data, LockPeriodConfig config) {
if (data.results.isEmpty()) {
return;
}
if (chromosome.getOrders() == null) {
chromosome.setOrders(new CopyOnWriteArrayList<>());
}
int beforeOrderSize = chromosome.getOrders().size();
// 收集锁定期订单ID
Set<String> lockedOrderIds = data.results.stream()
.map(GAScheduleResult::getOrderId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
log.debug("需要复制的锁定期订单ID: {}", lockedOrderIds);
// 用于存储OrderId到新Order.id的映射
Map<String, Integer> orderIdToNewGroupId = new HashMap<>();
try {
// 查询锁定期Dispatch记录
List<Dispatch> frozenDispatches = queryFrozenDispatches(config);
log.debug("查询到 {} 个锁定期Dispatch记录", frozenDispatches.size());
Set<String> dispatchSceneIds = frozenDispatches.stream()
.map(Dispatch::getSceneId)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
log.debug("锁定期Dispatch记录来自 {} 个场景: {}", dispatchSceneIds.size(), dispatchSceneIds);
// 从每个场景复制订单,并收集Order ID映射
for (String sceneId : dispatchSceneIds) {
Map<String, Integer> sceneMapping = copyOrdersFromScene(chromosome, sceneId, lockedOrderIds);
orderIdToNewGroupId.putAll(sceneMapping);
}
log.debug("Order ID映射: {}", orderIdToNewGroupId);
// 更新Entry的groupId
updateEntryGroupIds(data, orderIdToNewGroupId);
// 更新GAScheduleResult的groupId
updateResultGroupIds(data, orderIdToNewGroupId);
} catch (Exception e) {
log.error("复制锁定期订单失败: {}", e.getMessage(), e);
}
log.debug("成功复制锁定期订单到Orders列表,大小从{}变为{}", beforeOrderSize, chromosome.getOrders().size());
}
/**
* 从场景复制订单,并返回OrderId到新Order.id的映射
* @return Map<OrderId, 新Order.id>
*/
private Map<String, Integer> copyOrdersFromScene(Chromosome chromosome, String sceneId, Set<String> lockedOrderIds) {
Map<String, Integer> orderIdToNewGroupId = new HashMap<>();
try {
log.debug("开始从场景 {} 加载Chromosome", sceneId);
Chromosome dispatchChromosome = sceneService.loadChromosomeFromFile(sceneId);
if (dispatchChromosome == null) {
log.warn("场景 {} 加载失败,Chromosome为null", sceneId);
return orderIdToNewGroupId;
}
log.debug("场景 {} 加载成功,Orders数量: {}", sceneId,
dispatchChromosome.getOrders() != null ? dispatchChromosome.getOrders().size() : 0);
if (dispatchChromosome.getOrders() == null) {
log.warn("场景 {} 的Orders为null", sceneId);
return orderIdToNewGroupId;
}
// 筛选锁定期订单
List<Order> lockedOrders = dispatchChromosome.getOrders().stream()
.filter(order -> lockedOrderIds.contains(order.getOrderId()))
.collect(Collectors.toList());
log.debug("场景 {} 中找到 {} 个匹配的锁定期订单", sceneId, lockedOrders.size());
// 获取当前最大的Order ID
int maxOrderId = chromosome.getOrders().stream()
.mapToInt(Order::getId)
.max()
.orElse(0);
log.debug("当前最大Order ID: {}", maxOrderId);
// 复制订单(避免重复)
for (Order lockedOrder : lockedOrders) {
boolean exists = chromosome.getOrders().stream()
.anyMatch(order -> lockedOrder.getOrderId().equals(order.getOrderId()));
if (!exists) {
// 为dispatch工单分配新的Order ID
int oldOrderId = lockedOrder.getId();
int newOrderId = ++maxOrderId;
lockedOrder.setId(newOrderId);
// 记录映射关系
orderIdToNewGroupId.put(lockedOrder.getOrderId(), newOrderId);
log.debug("从场景 {} 复制锁定期订单: OrderId={}, OrderCode={}, 旧Order.id={}, 新Order.id={}",
sceneId, lockedOrder.getOrderId(), lockedOrder.getOrderCode(), oldOrderId, newOrderId);
chromosome.getOrders().add(lockedOrder);
} else {
log.warn("订单 {} 已存在,跳过复制", lockedOrder.getOrderId());
}
}
} catch (Exception ex) {
log.error("从场景 {} 复制锁定期订单失败: {}", sceneId, ex.getMessage(), ex);
}
return orderIdToNewGroupId;
}
/**
* 更新Entry的groupId
*/
private void updateEntryGroupIds(LockedOrdersData data, Map<String, Integer> orderIdToNewGroupId) {
if (orderIdToNewGroupId.isEmpty()) {
return;
}
log.debug("开始更新Entry的groupId,映射数量: {}", orderIdToNewGroupId.size());
for (Entry entry : data.entries.values()) {
String orderId = entry.getOrderId();
if (orderIdToNewGroupId.containsKey(orderId)) {
int oldGroupId = entry.getGroupId();
int newGroupId = orderIdToNewGroupId.get(orderId);
entry.setGroupId(newGroupId);
log.debug("更新Entry的groupId: ExecId={}, OrderId={}, 旧groupId={}, 新groupId={}",
entry.getExecId(), orderId, oldGroupId, newGroupId);
}
}
log.debug("Entry的groupId更新完成");
}
/**
* 更新GAScheduleResult的groupId
*/
private void updateResultGroupIds(LockedOrdersData data, Map<String, Integer> orderIdToNewGroupId) {
if (orderIdToNewGroupId.isEmpty()) {
return;
}
log.debug("开始更新GAScheduleResult的groupId,映射数量: {}", orderIdToNewGroupId.size());
for (GAScheduleResult result : data.results) {
String orderId = result.getOrderId();
if (orderIdToNewGroupId.containsKey(orderId)) {
int oldGroupId = result.getGroupId();
int newGroupId = orderIdToNewGroupId.get(orderId);
result.setGroupId(newGroupId);
log.debug("更新GAScheduleResult的groupId: ExecId={}, OrderId={}, 旧groupId={}, 新groupId={}",
result.getExecId(), orderId, oldGroupId, newGroupId);
}
}
log.debug("GAScheduleResult的groupId更新完成");
}
/**
* 确保锁定期设备存在于initMachines中
*/
private void ensureLockedMachines(Chromosome chromosome, Set<Long> lockedMachineIds, LockPeriodConfig config) {
if (chromosome.getInitMachines() == null || lockedMachineIds.isEmpty()) {
return;
}
Set<Long> existingMachineIds = chromosome.getInitMachines().stream()
.map(Machine::getId)
.collect(Collectors.toSet());
Set<Long> missingMachineIds = lockedMachineIds.stream()
.filter(id -> id != null && id > 0 && !existingMachineIds.contains(id))
.collect(Collectors.toSet());
if (missingMachineIds.isEmpty()) {
return;
}
log.debug("锁定期工单使用的设备不在本次排产中,需要从已下发场景获取设备信息: {}", missingMachineIds);
try {
List<Dispatch> frozenDispatches = queryFrozenDispatches(config);
Set<String> dispatchSceneIds = frozenDispatches.stream()
.map(Dispatch::getSceneId)
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
// 从各个场景查找设备
for (String sceneId : dispatchSceneIds) {
if (missingMachineIds.isEmpty()) {
break;
}
loadMachinesFromScene(chromosome, sceneId, missingMachineIds);
}
// 如果还有设备没找到,创建默认设备
if (!missingMachineIds.isEmpty()) {
log.warn("在所有已下发场景中都没有找到锁定期设备,将创建默认设备信息: {}", missingMachineIds);
createDefaultMachines(chromosome, missingMachineIds);
}
} catch (Exception ex) {
log.error("从已下发场景排产结果获取设备信息失败: {}", ex.getMessage(), ex);
createDefaultMachines(chromosome, missingMachineIds);
}
}
/**
* 从指定场景加载设备信息
*/
private void loadMachinesFromScene(Chromosome chromosome, String sceneId, Set<Long> missingMachineIds) {
try {
log.debug("从场景 {} 获取设备信息", sceneId);
Chromosome dispatchChromosome = loadSceneChromosome(sceneId);
if (dispatchChromosome == null || dispatchChromosome.getInitMachines() == null) {
return;
}
List<Machine> foundMachines = dispatchChromosome.getInitMachines().stream()
.filter(m -> missingMachineIds.contains(m.getId()))
.collect(Collectors.toList());
if (!foundMachines.isEmpty()) {
log.debug("从场景 {} 的排产结果中找到 {} 个锁定期设备", sceneId, foundMachines.size());
chromosome.getInitMachines().addAll(foundMachines);
Set<Long> foundIds = foundMachines.stream()
.map(Machine::getId)
.collect(Collectors.toSet());
missingMachineIds.removeAll(foundIds);
}
} catch (Exception ex) {
log.error("从场景 {} 获取设备信息失败: {}", sceneId, ex.getMessage(), ex);
}
}
/**
* 加载场景的Chromosome
*/
private Chromosome loadSceneChromosome(String sceneId) {
try {
SceneChromsome sceneChromsome = (SceneChromsome) redisUtils.get("SceneId." + sceneId);
if (sceneChromsome == null) {
log.warn("未找到场景 {} 的版本信息", sceneId);
return null;
}
int version = sceneChromsome.getVersion();
File file = new File("result", "chromosome_result_" + sceneId + "_" + version + "_.json");
if (!file.exists()) {
log.warn("场景 {} 的排产结果文件不存在: {}", sceneId, file.getAbsolutePath());
return null;
}
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper.readValue(file, Chromosome.class);
} catch (Exception ex) {
log.error("加载场景 {} 失败: {}", sceneId, ex.getMessage(), ex);
return null;
}
}
/**
* 查询冻结期内的Dispatch记录
*/
private List<Dispatch> queryFrozenDispatches(LockPeriodConfig config) {
return dispatchService.lambdaQuery()
.ge(Dispatch::getBeginTime, config.lockStartTime)
.le(Dispatch::getBeginTime, config.lockEndTime)
.eq(Dispatch::getIsDeleted, 0L)
.orderBy(true, true, Dispatch::getMesCode, Dispatch::getTaskSeq)
.list();
}
/**
* 为锁定期工单创建默认设备信息
* @param chromosome 染色体对象
* @param machineIds 需要创建的设备ID集合
*/
private void createDefaultMachines(Chromosome chromosome, Set<Long> machineIds) {
try {
// 查询这些设备的信息
List<PlanResource> planResources = planResourceService.lambdaQuery()
.eq(PlanResource::getIsdeleted, 0)
.in(PlanResource::getId, machineIds.stream()
.map(Long::intValue)
.collect(Collectors.toList()))
.list();
// 查询班次信息
List<MesShiftWorkSched> allShiftWorkScheds = mesShiftWorkSchedService.lambdaQuery()
.eq(MesShiftWorkSched::getIsdeleted, 0)
.list();
// 创建Machine对象并添加到initMachines
for (Long machineId : machineIds) {
Machine machine = new Machine();
machine.setId(machineId);
PlanResource planResource = planResources.stream()
.filter(pr -> pr.getId() == machineId.intValue())
.findFirst()
.orElse(null);
if (planResource != null) {
machine.setCode(planResource.getReferenceCode());
machine.setName(planResource.getTitle());
machine.setDepartment(planResource.getDepartTitle());
log.debug("为设备 {} 查询班次信息,workSchedId: {}", machineId, planResource.getWorkSchedId());
// 查询设备的真实班次信息
List<MesShiftWorkSched> shiftWorkScheds = allShiftWorkScheds.stream()
.filter(t -> (long) t.getWeekWorkSchedId() == planResource.getWorkSchedId())
.collect(Collectors.toList());
log.debug("设备 {} 找到 {} 个班次记录", machineId, shiftWorkScheds.size());
if (!shiftWorkScheds.isEmpty()) {
// 使用真实的班次信息
List<Shift> shifts = mergeShiftData(shiftWorkScheds);
for (Shift shift : shifts) {
shift.setMachineId(machineId);
shift.setSpecial(false);
shift.setStartDate(LocalDateTime.of(2000, 1, 1, 0, 0, 0));
shift.setEndDate(LocalDateTime.of(2000, 1, 1, 0, 0, 0));
}
machine.setShifts(shifts);
log.debug("为设备 {} 设置真实班次信息,共 {} 个班次", machineId, shifts.size());
} else {
// 如果没有找到班次信息,使用默认24小时班次
machine.setShifts(createDefault24HourShift(machineId));
log.warn("设备 {} 没有找到班次信息,使用默认24小时班次", machineId);
}
} else {
machine.setCode("LOCKED-" + machineId);
machine.setName("锁定期设备-" + machineId);
// 使用默认24小时班次
machine.setShifts(createDefault24HourShift(machineId));
log.warn("设备 {} 没有找到PlanResource信息,使用默认24小时班次", machineId);
}
chromosome.getInitMachines().add(machine);
}
log.debug("成功创建 {} 个默认锁定期设备", machineIds.size());
} catch (Exception e) {
log.error("创建默认设备信息失败: {}", e.getMessage(), e);
}
}
/**
* 创建默认的24小时班次
* @param machineId 设备ID
* @return 24小时班次列表
*/
private List<Shift> createDefault24HourShift(Long machineId) {
List<Shift> shifts = new ArrayList<>();
Shift shift = new Shift();
shift.setMachineId(machineId);
shift.setStartTime(LocalTime.of(0, 0, 0));
shift.setEndTime(LocalTime.of(23, 59, 59));
HashSet<Integer> days = new HashSet<>();
for (int i = 0; i <= 6; i++) {
days.add(i);
}
shift.setDays(days);
shift.setSpecial(false);
shift.setStartDate(LocalDateTime.of(2000, 1, 1, 0, 0, 0));
shift.setEndDate(LocalDateTime.of(2000, 1, 1, 0, 0, 0));
shifts.add(shift);
return shifts;
}
/**
* 合并班次数据
* @param shiftWorkScheds 班次工作计划列表
* @return 合并后的班次列表
*/
private List<Shift> mergeShiftData(List<MesShiftWorkSched> shiftWorkScheds) {
// 这里需要根据实际的MesShiftWorkSched结构来实现班次合并逻辑
// 暂时返回默认24小时班次,实际项目中需要根据具体需求实现
List<Shift> shifts = new ArrayList<>();
// 按班次分组并合并
Map<String, List<MesShiftWorkSched>> shiftGroups = shiftWorkScheds.stream()
.collect(Collectors.groupingBy(s -> s.getShiftName() != null ? s.getShiftName() : "默认班次"));
for (Map.Entry<String, List<MesShiftWorkSched>> entry : shiftGroups.entrySet()) {
Shift shift = new Shift();
List<MesShiftWorkSched> schedList = entry.getValue();
if (!schedList.isEmpty()) {
MesShiftWorkSched firstSched = schedList.get(0);
// 设置班次时间(需要根据实际字段调整)
if (firstSched.getShiftStart() != null && firstSched.getShiftEnd() != null) {
shift.setStartTime(firstSched.getShiftStart().toLocalTime());
shift.setEndTime(firstSched.getShiftEnd().toLocalTime());
} else {
// 默认24小时
shift.setStartTime(LocalTime.of(0, 0, 0));
shift.setEndTime(LocalTime.of(23, 59, 59));
}
// 设置工作日(需要根据实际字段调整)
HashSet<Integer> days = new HashSet<>();
for (int i = 0; i <= 6; i++) {
days.add(i);
}
shift.setDays(days);
}
shifts.add(shift);
}
return shifts.isEmpty() ? createDefault24HourShift(0L) : shifts;
}
/**
* 深拷贝 GAScheduleResult 对象
*
* @param source 源对象
* @return 拷贝后的新对象
*/
private GAScheduleResult copyGAScheduleResult(GAScheduleResult source) {
if (source == null) {
return null;
}
GAScheduleResult copy = new GAScheduleResult();
// 拷贝基本字段
copy.setGroupId(source.getGroupId());
copy.setOperationId(source.getOperationId());
copy.setExecId(source.getExecId());
copy.setOrderId(source.getOrderId());
copy.setOrderCode(source.getOrderCode());
copy.setProductId(source.getProductId());
copy.setProductName(source.getProductName());
copy.setProductCode(source.getProductCode());
copy.setMachineId(source.getMachineId());
copy.setStartTime(source.getStartTime());
copy.setEndTime(source.getEndTime());
copy.setTeardownTime(source.getTeardownTime());
copy.setQuantity(source.getQuantity());
copy.setDesignatedStartTime(source.getDesignatedStartTime());
copy.setLockStartTime(source.getLockStartTime());
copy.setForcedMachineId(source.getForcedMachineId());
copy.setIsLocked(source.isIsLocked());
copy.setOneTime(source.getOneTime());
copy.setProcessingTime(source.getProcessingTime());
copy.setChangeOverTime(source.getChangeOverTime());
copy.setPreTime(source.getPreTime());
copy.setSeq(source.getSeq());
copy.setBomTime(source.getBomTime());
// 深拷贝 GeneDetails 列表
if (source.getGeneDetails() != null) {
List<ScheduleResultDetail> detailsCopy = new ArrayList<>();
for (ScheduleResultDetail detail : source.getGeneDetails()) {
ScheduleResultDetail detailCopy = new ScheduleResultDetail();
detailCopy.setKey(detail.getKey());
detailCopy.setStartTime(detail.getStartTime());
detailCopy.setEndTime(detail.getEndTime());
detailCopy.setOneTime(detail.getOneTime());
detailCopy.setQuantity(detail.getQuantity());
// 深拷贝 usedSegment
if (detail.getUsedSegment() != null) {
List<com.aps.entity.basic.TimeSegment> segmentsCopy = new ArrayList<>(detail.getUsedSegment());
detailCopy.setUsedSegment(segmentsCopy);
}
detailsCopy.add(detailCopy);
}
copy.setGeneDetails(detailsCopy);
}
// 深拷贝 TargetFinishedOperationId 列表
if (source.getTargetFinishedOperationId() != null) {
copy.setTargetFinishedOperationId(new ArrayList<>(source.getTargetFinishedOperationId()));
}
return copy;
}
/**
* 在排产前标记锁定期工单占用的设备时间段
* 将锁定期工单占用的设备时间段标记为不可用,避免新工单与锁定期工单冲突
* 核心逻辑:拆分设备的可用时间段,将锁定期工单占用的部分标记为isUsed=true
*
* @param machines 设备列表
* @param baseTime 基准时间
*/
public void markLockedOrdersOccupiedTime(List<Machine> machines, LocalDateTime baseTime) {
try {
// 1. 获取时间配置
LockPeriodConfig config = getLockPeriodConfig();
if (config == null) {
log.debug("未找到锁定期配置,跳过标记锁定期设备占用");
return;
}
log.debug("开始标记锁定期设备占用时间段,锁定期范围: {} 到 {}", config.lockStartTime, config.lockEndTime);
// 2. 从Dispatch表查询锁定期内的工单
List<Dispatch> frozenDispatches = dispatchService.lambdaQuery()
.ge(Dispatch::getBeginTime, config.lockStartTime)
.le(Dispatch::getBeginTime, config.lockEndTime)
.eq(Dispatch::getIsDeleted, 0L)
.isNotNull(Dispatch::getEquipId)
.isNotNull(Dispatch::getBeginTime)
.isNotNull(Dispatch::getEndTime)
.list();
if (frozenDispatches.isEmpty()) {
log.debug("没有锁定期工单需要标记设备占用");
return;
}
log.debug("查询到 {} 个锁定期工单,开始标记设备占用", frozenDispatches.size());
// 3. 按设备分组
Map<Long, List<Dispatch>> dispatchByMachine = frozenDispatches.stream()
.collect(Collectors.groupingBy(Dispatch::getEquipId));
// 4. 为每个设备标记占用时间段
int markedCount = 0;
for (Map.Entry<Long, List<Dispatch>> entry : dispatchByMachine.entrySet()) {
Long machineId = entry.getKey();
List<Dispatch> dispatches = entry.getValue();
// 查找对应的Machine对象
Machine machine = machines.stream()
.filter(m -> m.getId() == machineId)
.findFirst()
.orElse(null);
if (machine == null) {
log.warn("未找到设备 {},跳过标记", machineId);
continue;
}
// 为该设备的每个锁定期工单创建占用时间段
for (Dispatch dispatch : dispatches) {
LocalDateTime dispatchStart = dispatch.getBeginTime();
LocalDateTime dispatchEnd = dispatch.getEndTime();
// 只标记结束时间在baseTime之后的工单(这些会占用未来的设备时间)
if (dispatchEnd.isAfter(baseTime)) {
// 如果开始时间在baseTime之前,则从baseTime开始标记
LocalDateTime occupyStart = dispatchStart.isBefore(baseTime) ? baseTime : dispatchStart;
LocalDateTime occupyEnd = dispatchEnd;
// 初始化设备的可用时间段列表
if (machine.getAvailability() == null) {
machine.setAvailability(new CopyOnWriteArrayList<>());
}
// 关键逻辑:拆分设备的可用时间段
// 将与锁定期工单重叠的时间段拆分成三部分:
// 1. 锁定期之前的部分(isUsed=false)
// 2. 锁定期占用的部分(isUsed=true)
// 3. 锁定期之后的部分(isUsed=false)
splitAndMarkAvailabilitySegments(machine, occupyStart, occupyEnd);
markedCount++;
log.debug("标记设备 {} 的锁定期占用时间段: {} 到 {}, 订单: {}",
machineId, occupyStart, occupyEnd, dispatch.getMesCode());
}
}
}
log.debug("成功标记 {} 个锁定期工单的设备占用时间段", markedCount);
// 5. 打印设备的可用时间段信息用于调试
for (Machine machine : machines) {
if (machine.getAvailability() != null && !machine.getAvailability().isEmpty()) {
log.debug("设备 {} 的availability中有 {} 个时间段,前3个:", machine.getId(), machine.getAvailability().size());
for (int i = 0; i < Math.min(3, machine.getAvailability().size()); i++) {
TimeSegment seg = machine.getAvailability().get(i);
log.debug(" [{}] 类型={}, 开始={}, 结束={}, isUsed={}",
i, seg.getType(), seg.getStart(), seg.getEnd(), seg.isUsed());
}
}
}
} catch (Exception e) {
log.error("标记锁定期设备占用时间段失败: {}", e.getMessage(), e);
}
}
/**
* 拆分并标记设备的可用时间段
* 将与锁定期工单重叠的时间段拆分,并标记占用部分为isUsed=true
*
* @param machine 设备对象
* @param occupyStart 锁定期工单开始时间
* @param occupyEnd 锁定期工单结束时间
*/
private void splitAndMarkAvailabilitySegments(Machine machine, LocalDateTime occupyStart, LocalDateTime occupyEnd) {
if (machine.getAvailability() == null || machine.getAvailability().isEmpty()) {
// 如果没有可用时间段,直接添加占用段
TimeSegment occupiedSegment = new TimeSegment();
occupiedSegment.setStart(occupyStart);
occupiedSegment.setEnd(occupyEnd);
occupiedSegment.setType(SegmentType.MAINTENANCE);
occupiedSegment.setUsed(true);
occupiedSegment.setHoliday(false);
occupiedSegment.setEfficiency(1.0);
machine.getAvailability().add(occupiedSegment);
return;
}
// 处理现有的可用时间段
List<TimeSegment> segmentsToProcess = new ArrayList<>(machine.getAvailability());
machine.getAvailability().clear();
for (TimeSegment segment : segmentsToProcess) {
// 检查时间段与锁定期工单是否有重叠
if (segment.getStart().isBefore(occupyEnd) && segment.getEnd().isAfter(occupyStart)) {
// 有重叠,需要拆分
// 1. 前半部分(锁定期之前)
if (segment.getStart().isBefore(occupyStart)) {
TimeSegment beforeSegment = new TimeSegment();
beforeSegment.setStart(segment.getStart());
beforeSegment.setEnd(occupyStart);
beforeSegment.setType(segment.getType());
beforeSegment.setUsed(false); // 未被占用
beforeSegment.setHoliday(segment.isHoliday());
beforeSegment.setEfficiency(segment.getEfficiency());
beforeSegment.setKey(segment.getKey());
machine.getAvailability().add(beforeSegment);
log.debug("添加前半部分时间段: {} 到 {}, isUsed=false", beforeSegment.getStart(), beforeSegment.getEnd());
}
// 2. 中间部分(锁定期占用)
TimeSegment occupiedSegment = new TimeSegment();
occupiedSegment.setStart(occupyStart);
occupiedSegment.setEnd(occupyEnd);
occupiedSegment.setType(SegmentType.MAINTENANCE);
occupiedSegment.setUsed(true); // 被占用
occupiedSegment.setHoliday(false);
occupiedSegment.setEfficiency(segment.getEfficiency());
machine.getAvailability().add(occupiedSegment);
log.debug("添加占用部分时间段: {} 到 {}, isUsed=true", occupiedSegment.getStart(), occupiedSegment.getEnd());
// 3. 后半部分(锁定期之后)
if (segment.getEnd().isAfter(occupyEnd)) {
TimeSegment afterSegment = new TimeSegment();
afterSegment.setStart(occupyEnd);
afterSegment.setEnd(segment.getEnd());
afterSegment.setType(segment.getType());
afterSegment.setUsed(false); // 未被占用
afterSegment.setHoliday(segment.isHoliday());
afterSegment.setEfficiency(segment.getEfficiency());
afterSegment.setKey(segment.getKey());
machine.getAvailability().add(afterSegment);
log.debug("添加后半部分时间段: {} 到 {}, isUsed=false", afterSegment.getStart(), afterSegment.getEnd());
}
} else {
// 没有重叠,保持原样
segment.setUsed(false); // 确保未被占用的段标记为false
machine.getAvailability().add(segment);
log.debug("保持原有时间段: {} 到 {}, isUsed=false", segment.getStart(), segment.getEnd());
}
}
// 按开始时间排序
machine.getAvailability().sort(Comparator.comparing(TimeSegment::getStart));
}
/**
* 锁定期配置类
*/
private static class LockPeriodConfig {
LocalDateTime baseTime;
long freezeSeconds;
LocalDateTime lockStartTime;
LocalDateTime lockEndTime;
}
/**
* 锁定期工单数据类
*/
private static class LockedOrdersData {
List<GAScheduleResult> results = new ArrayList<>();
Map<String, Entry> entries = new HashMap<>();
Set<Long> machineIds = new HashSet<>();
}
}
\ No newline at end of file
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