You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
8.8 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Sln.Wcs.Business.Domain.Enum;
using Sln.Wcs.Model.Domain;
using Sln.Wcs.Serilog;
namespace Sln.Wcs.Business;
/// <summary>
/// 接驳位拓扑驱动的任务创建服务
/// 根据起止楼栋/楼层/位置,自动生成 LiveTaskQueue 及其子任务明细序列
/// </summary>
public class TaskCreateService
{
private readonly SerilogHelper _logger;
public TaskCreateService(SerilogHelper logger) => _logger = logger;
#region 拓扑配置
private record TopoNode(int Building, int Floor);
/// <summary>
/// 拓扑邻接表:(栋,层) → 可达的相邻节点
/// 所有楼栋 1~5F本栋提升机可直达任意楼层全连接
/// 跨栋仅 2F 连通: (13,2)↔(14,2) 廊桥交接, (14,2)↔(15,2) AGV直连
/// </summary>
private static readonly Dictionary<TopoNode, List<TopoNode>> Topology = BuildTopology();
private static Dictionary<TopoNode, List<TopoNode>> BuildTopology()
{
var dict = new Dictionary<TopoNode, List<TopoNode>>();
// 每个楼栋内全连接(提升机直达任意楼层)
foreach (var building in new[] { 13, 14, 15 })
{
for (int f1 = 1; f1 <= 5; f1++)
{
var neighbors = new List<TopoNode>();
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
/// <summary>
/// 创建任务:输入楼栋/楼层/位置号,自动生成明细序列
/// </summary>
/// <param name="startBuilding">起始楼栋 (13/14/15)</param>
/// <param name="startFloor">起始楼层</param>
/// <param name="startLocation">起始位置号,如 "01"</param>
/// <param name="endBuilding">目标楼栋 (13/14/15)</param>
/// <param name="endFloor">目标楼层</param>
/// <param name="endLocation">目标位置号,如 "02"</param>
/// <param name="taskType">任务类型: 1-入库, 2-出库</param>
/// <param name="taskCategory">任务类别: 1-包材, 2-成品, 3-托盘</param>
/// <param name="palletBarcode">托盘条码</param>
/// <param name="materialCode">物料编码</param>
/// <returns>完整 LiveTaskQueue含明细列表</returns>
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<LiveTaskDetail>();
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 内部方法
/// <summary>BFS 最短路径</summary>
private static List<TopoNode> 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<TopoNode> { 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<TopoNode> { start };
var queue = new Queue<List<TopoNode>>();
queue.Enqueue(new List<TopoNode> { 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<TopoNode>(path) { next };
if (next == end) return newPath;
visited.Add(next);
queue.Enqueue(newPath);
}
}
throw new InvalidOperationException($"无路径: ({b_s},{f_s}) → ({b_e},{f_e})");
}
/// <summary>是否需要廊桥交接</summary>
private static bool IsBridgeRequired(int b1, int b2) =>
(b1 == 13 && b2 == 14) || (b1 == 14 && b2 == 13);
/// <summary>拼接位置编码: 楼栋#_L楼层_位置号</summary>
private static string PosCode(int building, int floor, string location) =>
$"{building}#_L{floor}_{location}";
/// <summary>提升机接驳位</summary>
private static string HoistPoint(int building, int floor) =>
$"{building}#_L{floor}_HOIST";
/// <summary>普通接驳位(非提升机、非廊桥)</summary>
private static string DockingPoint(int building, int floor) =>
$"{building}#_L{floor}_01";
/// <summary>生成任务编号</summary>
private static string GenerateTaskCode() =>
DateTime.Now.ToString("yyyyMMddHHmmss") + new Random().Next(10, 99);
/// <summary>创建一条 LiveTaskDetail</summary>
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
}