using Sln.Wcs.Business.Domain.Enum; using Sln.Wcs.Model.Domain; using Sln.Wcs.Serilog; namespace Sln.Wcs.Business; /// /// 接驳位拓扑驱动的任务创建服务 /// 根据起止楼栋/楼层/位置,自动生成 LiveTaskQueue 及其子任务明细序列 /// public class TaskCreateService { private readonly SerilogHelper _logger; public TaskCreateService(SerilogHelper logger) => _logger = logger; #region 拓扑配置 private record TopoNode(int Building, int Floor); /// /// 拓扑邻接表:(栋,层) → 可达的相邻节点 /// 所有楼栋 1~5F,本栋提升机可直达任意楼层(全连接) /// 跨栋仅 2F 连通: (13,2)↔(14,2) 廊桥交接, (14,2)↔(15,2) AGV直连 /// private static readonly Dictionary> Topology = BuildTopology(); private static Dictionary> BuildTopology() { var dict = new Dictionary>(); // 每个楼栋内全连接(提升机直达任意楼层) foreach (var building in new[] { 13, 14, 15 }) { for (int f1 = 1; f1 <= 5; f1++) { var neighbors = new List(); for (int f2 = 1; f2 <= 5; f2++) { if (f1 != f2) neighbors.Add(new(building, f2)); } dict[new(building, f1)] = neighbors; } } // 跨栋: 仅 2F 连通 dict[new(13, 2)].Add(new(14, 2)); // 13↔14 廊桥交接 dict[new(14, 2)].Add(new(13, 2)); dict[new(14, 2)].Add(new(15, 2)); // 14↔15 AGV直连 dict[new(15, 2)].Add(new(14, 2)); return dict; } #endregion /// /// 创建任务:输入楼栋/楼层/位置号,自动生成明细序列 /// /// 起始楼栋 (13/14/15) /// 起始楼层 /// 起始位置号,如 "01" /// 目标楼栋 (13/14/15) /// 目标楼层 /// 目标位置号,如 "02" /// 任务类型: 1-入库, 2-出库 /// 任务类别: 1-包材, 2-成品, 3-托盘 /// 托盘条码 /// 物料编码 /// 完整 LiveTaskQueue(含明细列表) public LiveTaskQueue CreateTask( int startBuilding, int startFloor, string startLocation, int endBuilding, int endFloor, string endLocation, int taskType, int taskCategory, string palletBarcode, string materialCode) { var startPoint = PosCode(startBuilding, startFloor, startLocation); var endPoint = PosCode(endBuilding, endFloor, endLocation); _logger.Info($"创建任务 - {startPoint} ({startBuilding}#_{startFloor}F) → {endPoint} ({endBuilding}#_{endFloor}F)"); // Step 1: BFS 找最短路径(节点序列) var nodes = FindPath(startBuilding, startFloor, endBuilding, endFloor); _logger.Info($"路由节点: {string.Join(" → ", nodes.Select(n => $"({n.Building},{n.Floor})"))}"); // Step 2: 沿节点路径逐段生成明细 // 规则: 同层移动=AGV, 跨层=提升机, 跨栋(13↔14)=廊桥交接两段AGV var details = new List(); int seq = 1; var taskCode = GenerateTaskCode(); string currentPos = startPoint; // 当前货物所在接驳位 for (int i = 0; i < nodes.Count - 1; i++) { var (b1, f1) = nodes[i]; var (b2, f2) = nodes[i + 1]; if (b1 != b2) // 跨栋 { if (IsBridgeRequired(b1, b2)) { // 13↔14: 廊桥交接 — 13# AGV 送到廊桥,等待 14# AGV details.Add(NewDetail(taskCode, palletBarcode, materialCode, taskType, taskCategory, seq++, currentPos, "BRIDGE_13_14", deviceType: 1)); currentPos = "BRIDGE_13_14"; } // 14↔15: 14# AGV 可直接驶入 15#,不生成过渡段,由下一步直达 } else if (f1 != f2) // 同栋跨层 → AGV到提升机入口 + 提升机 { var hoistEntry = HoistPoint(b1, f1); var hoistExit = HoistPoint(b2, f2); details.Add(NewDetail(taskCode, palletBarcode, materialCode, taskType, taskCategory, seq++, currentPos, hoistEntry, deviceType: 1)); details.Add(NewDetail(taskCode, palletBarcode, materialCode, taskType, taskCategory, seq++, hoistEntry, hoistExit, deviceType: 2)); currentPos = hoistExit; } } // Step 3: 最终 AGV 送达终点(同栋同层时直接一条 AGV) if (details.Count == 0 || currentPos != endPoint) { details.Add(NewDetail(taskCode, palletBarcode, materialCode, taskType, taskCategory, seq, currentPos, endPoint, deviceType: 1)); } _logger.Info($"生成 {details.Count} 条明细"); return new LiveTaskQueue { taskCode = taskCode, taskType = taskType, taskCategory = taskCategory, taskStatus = 1, startPoint = startPoint, endPoint = endPoint, palletBarcode = palletBarcode, materialCode = materialCode, taskSteps = details.Count, taskDetails = details, }; } #region 内部方法 /// BFS 最短路径 private static List FindPath(int b_s, int f_s, int b_e, int f_e) { var start = new TopoNode(b_s, f_s); var end = new TopoNode(b_e, f_e); if (start == end) return new List { start }; // 校验起止节点是否在拓扑中 if (!Topology.ContainsKey(start)) throw new InvalidOperationException($"起始位置不在拓扑中: {b_s}#_{f_s}F,可用节点: {string.Join(", ", Topology.Keys.Select(n => $"{n.Building}#_{n.Floor}F"))}"); if (!Topology.ContainsKey(end)) throw new InvalidOperationException($"目标位置不在拓扑中: {b_e}#_{f_e}F,可用节点: {string.Join(", ", Topology.Keys.Select(n => $"{n.Building}#_{n.Floor}F"))}"); var visited = new HashSet { start }; var queue = new Queue>(); queue.Enqueue(new List { start }); while (queue.Count > 0) { var path = queue.Dequeue(); var current = path[^1]; if (!Topology.TryGetValue(current, out var neighbors)) continue; foreach (var next in neighbors) { if (visited.Contains(next)) continue; var newPath = new List(path) { next }; if (next == end) return newPath; visited.Add(next); queue.Enqueue(newPath); } } throw new InvalidOperationException($"无路径: ({b_s},{f_s}) → ({b_e},{f_e})"); } /// 是否需要廊桥交接 private static bool IsBridgeRequired(int b1, int b2) => (b1 == 13 && b2 == 14) || (b1 == 14 && b2 == 13); /// 拼接位置编码: 楼栋#_L楼层_位置号 private static string PosCode(int building, int floor, string location) => $"{building}#_L{floor}_{location}"; /// 提升机接驳位 private static string HoistPoint(int building, int floor) => $"{building}#_L{floor}_HOIST"; /// 普通接驳位(非提升机、非廊桥) private static string DockingPoint(int building, int floor) => $"{building}#_L{floor}_01"; /// 生成任务编号 private static string GenerateTaskCode() => DateTime.Now.ToString("yyyyMMddHHmmss") + new Random().Next(10, 99); /// 创建一条 LiveTaskDetail private static LiveTaskDetail NewDetail( string taskCode, string palletBarcode, string materialCode, int taskType, int taskCategory, int seq, string startPoint, string endPoint, int deviceType) => new() { taskCode = taskCode, objId = seq, palletBarcode = palletBarcode, materialCode = materialCode, startPoint = startPoint, endPoint = endPoint, deviceType = deviceType, taskType = taskType, taskCategory = taskCategory, taskStatus = 1, }; #endregion }