wanshenmean
2026-03-09 963aa00ec6b120d05dc702129b176c327213653b
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -1,13 +1,13 @@
using AutoMapper;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Configuration;
using SqlSugar;
using System.Text.Json;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_DTO;
using WIDESEA_DTO.Task;
using WIDESEA_IBasicService;
@@ -23,6 +23,8 @@
        private readonly IStockInfoService _stockInfoService;
        private readonly ILocationInfoService _locationInfoService;
        private readonly HttpClientHelper _httpClientHelper;
        private readonly IConfiguration _configuration;
        private readonly RoundRobinService _roundRobinService;
        public IRepository<Dt_Task> Repository => BaseDal;
@@ -40,13 +42,19 @@
            IMapper mapper,
            IStockInfoService stockInfoService,
            ILocationInfoService locationInfoService,
            HttpClientHelper httpClientHelper) : base(BaseDal)
            HttpClientHelper httpClientHelper,
            IConfiguration configuration,
            RoundRobinService roundRobinService) : base(BaseDal)
        {
            _mapper = mapper;
            _stockInfoService = stockInfoService;
            _locationInfoService = locationInfoService;
            _httpClientHelper = httpClientHelper;
            _configuration = configuration;
            _roundRobinService = roundRobinService;
        }
        #region WCS逻辑处理
        /// <summary>
        /// 创建任务(组盘入库任务、空托盘回库任务)
@@ -55,27 +63,39 @@
        {
            try
            {
                WebResponseContent content = await GetTasksByPalletCodeAsync(taskDto.PalletCode);
                if (content.Status)
                {
                    return content;
                }
                if (string.IsNullOrWhiteSpace(taskDto.PalletCode) ||
                    string.IsNullOrWhiteSpace(taskDto.SourceAddress) ||
                    string.IsNullOrWhiteSpace(taskDto.TargetAddress) ||
                    string.IsNullOrWhiteSpace(taskDto.Roadway))
                {
                    return WebResponseContent.Instance.Error("Invalid task details.");
                    return WebResponseContent.Instance.Error("无效的任务详情");
                }
                if (taskDto.TaskType != TaskTypeEnum.Inbound && taskDto.TaskType != TaskTypeEnum.InEmpty)
                {
                    return WebResponseContent.Instance.Error("Invalid task details.");
                    return WebResponseContent.Instance.Error("无效的任务详情");
                }
                // 使用 switch 表达式映射任务类型
                int taskInboundType = taskDto.TaskType switch
                {
                    TaskTypeEnum.Inbound => TaskInboundTypeEnum.Inbound.GetHashCode(),
                    TaskTypeEnum.InEmpty => TaskInboundTypeEnum.InEmpty.GetHashCode(),
                    _ => 0 // 理论上不会走到这里,因为已经验证过了
                };
                var task = new Dt_Task
                {
                    TaskNum = 0,
                    TaskNum = await BaseDal.GetTaskNo(),
                    PalletCode = taskDto.PalletCode,
                    PalletType = taskDto.PalletType,
                    Roadway = taskDto.Roadway,
                    TaskType = taskDto.TaskType.GetHashCode(),
                    TaskStatus = TaskStatusEnum.New.GetHashCode(),
                    TaskType = taskInboundType,
                    TaskStatus = TaskInStatusEnum.InNew.GetHashCode(),
                    SourceAddress = taskDto.SourceAddress,
                    TargetAddress = taskDto.TargetAddress,
                    CurrentAddress = taskDto.SourceAddress,
@@ -96,7 +116,6 @@
                return WebResponseContent.Instance.Error($"任务创建失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 根据指定的任务详情异步创建新的出库任务
@@ -128,14 +147,13 @@
                var result = await BaseDal.AddDataAsync(taskList) > 0;
                var wmstaskDto = result ? _mapper.Map<WMSTaskDTO>(taskList) : null;
                return WebResponseContent.Instance.OK(result ? "任务创建成功" : "任务创建失败", wmstaskDto);
                return WebResponseContent.Instance.OK(result ? "任务创建成功" : "任务创建失败", wmstaskDto ?? new object());
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"任务创建失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 获取可入库货位
@@ -151,10 +169,10 @@
                if (locationInfo == null) return WebResponseContent.Instance.Error("未找到对应的货位");
                locationInfo.LocationStatus = LocationStatusEnum.FreeLock.GetHashCode();
                task.CurrentAddress = taskDto.SourceAddress;
                task.CurrentAddress = task.SourceAddress;
                task.NextAddress = locationInfo.LocationCode;
                task.TargetAddress = taskDto.TargetAddress;
                task.TaskStatus = TaskStatusEnum.Line_Finish.GetHashCode();
                task.TargetAddress = locationInfo.LocationCode;
                task.TaskStatus = TaskInStatusEnum.Line_InFinish.GetHashCode();
                var updateResult = await BaseDal.UpdateDataAsync(task);
                var locationResult = await _locationInfoService.UpdateLocationInfoAsync(locationInfo);
@@ -250,6 +268,296 @@
                return WebResponseContent.Instance.Error($"完成任务失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 创建空托盘入库任务
        /// </summary>
        /// <param name="taskDto"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> CreateTaskInboundTrayAsync(CreateTaskDto taskDto)
        {
            try
            {
                WebResponseContent content = await GetTasksByPalletCodeAsync(taskDto.PalletCode);
                if (content.Status)
                {
                    return content;
                }
                //var tasks = await BaseDal.QueryAsync(s => s.PalletCode == palletCode);
                //if (tasks == null || !tasks.Any())
                //    return WebResponseContent.Instance.Error("未找到对应的任务");
                //var taskDtos = _mapper.Map<List<WMSTaskDTO>>(tasks);
                return WebResponseContent.Instance.OK("查询成功"/*, taskDtos*/);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"查询任务失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 创建空托盘出库任务
        /// </summary>
        /// <param name="taskDto"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> GetOutBoundTrayTaskAsync(CreateTaskDto taskDto)
        {
            try
            {
                var stockInfo = await _stockInfoService.Repository.QueryFirstAsync(x => x.LocationDetails.WarehouseId == taskDto.WarehouseId && x.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode() && x.StockStatus == StockStatusEmun.空托盘库存.GetHashCode());
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的库存信息");
                var task = new Dt_Task()
                {
                    WarehouseId = stockInfo.LocationDetails.WarehouseId,
                    PalletCode = stockInfo.PalletCode,
                    PalletType = stockInfo.PalletType,
                    SourceAddress = stockInfo.LocationCode,
                    CurrentAddress = stockInfo.LocationCode,
                    NextAddress = taskDto.TargetAddress,
                    TargetAddress = taskDto.TargetAddress,
                    Roadway = stockInfo.LocationDetails.RoadwayNo,
                    TaskType = TaskTypeEnum.OutEmpty.GetHashCode(),
                    TaskStatus = TaskStatusEnum.New.GetHashCode(),
                    Grade = 1,
                    TaskNum = await BaseDal.GetTaskNo(),
                    Creater = "system",
                };
                var taskDtos = _mapper.Map<List<WMSTaskDTO>>(task);
                return WebResponseContent.Instance.OK("查询成功", taskDtos);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"查询任务失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 修改任务状态(根据任务ID修改为指定状态)
        /// </summary>
        /// <param name="taskId"></param>
        /// <param name="newStatus"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> UpdateTaskByStatusAsync(int taskId, int newStatus)
        {
            try
            {
                var tasks = await BaseDal.QueryFirstAsync(s => s.TaskNum == taskId);
                if (tasks == null)
                    return WebResponseContent.Instance.Error("未找到对应的任务");
                tasks.TaskStatus = newStatus;
                await BaseDal.UpdateDataAsync(tasks);
                return WebResponseContent.Instance.OK("修改成功", tasks);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"修改失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 查找托盘是否有任务
        /// </summary>
        /// <param name="palletCode"></param>
        /// <returns></returns>
        private async Task<WebResponseContent> GetTasksByPalletCodeAsync(string palletCode)
        {
            try
            {
                var tasks = await BaseDal.QueryFirstAsync(s => s.PalletCode == palletCode);
                if (tasks == null)
                    return WebResponseContent.Instance.Error("未找到对应的任务");
                var taskDtos = _mapper.Map<List<WMSTaskDTO>>(tasks);
                return WebResponseContent.Instance.OK("查询成功", taskDtos);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"查询任务失败: {ex.Message}");
            }
        }
        /// <summary>
        /// 根据巷道确定目标地址(支持多出库口轮询)
        /// </summary>
        private string DetermineTargetAddress(string roadway, Dictionary<string, List<string>> addressMap)
        {
            if (string.IsNullOrWhiteSpace(roadway))
                return "10080";
            // 查找匹配的巷道前缀
            string matchedPrefix = null;
            foreach (var kvp in addressMap)
            {
                if (roadway.Contains(kvp.Key))
                {
                    matchedPrefix = kvp.Key;
                    break;
                }
            }
            if (matchedPrefix == null)
                return "10080";
            var addresses = addressMap[matchedPrefix];
            if (addresses == null || addresses.Count == 0)
                return "10080";
            // 单个地址,直接返回
            if (addresses.Count == 1)
                return addresses[0];
            // 多个地址,使用轮询服务
            return _roundRobinService.GetNextAddress(matchedPrefix, addresses);
        }
        /// <summary>
        /// 自动创建出库任务 - 查询到期库存并创建任务
        /// </summary>
        public async Task<WebResponseContent> CreateAutoOutboundTasksAsync()
        {
            try
            {
                // 1. 查询到期库存
                var expiredStocks = await _stockInfoService.Repository
                    .QueryDataAsync(s => s.OutboundDate <= DateTime.Now
                        && s.StockStatus == StockStatusEmun.入库完成.GetHashCode());
                if (expiredStocks == null || !expiredStocks.Any())
                {
                    return WebResponseContent.Instance.OK("无到期库存需要处理");
                }
                // 批量加载位置详情(优化 N+1 查询问题)
                var locationIds = expiredStocks
                    .Where(s => s.LocationId > 0)
                    .Select(s => s.LocationId)
                    .Distinct()
                    .Cast<object>()
                    .ToList();
                if (locationIds.Any())
                {
                    var locations = await _locationInfoService.Repository
                        .QureyDataByIdsAsync(locationIds);
                    // 创建位置字典以便快速查找
                    var locationDict = locations.ToDictionary(l => l.Id, l => l);
                    // 为每个库存关联位置详情
                    foreach (var stock in expiredStocks)
                    {
                        if (stock.LocationId > 0 && locationDict.ContainsKey(stock.LocationId))
                        {
                            stock.LocationDetails = locationDict[stock.LocationId];
                        }
                    }
                }
                // 过滤有位置且位置有库存的记录
                expiredStocks = expiredStocks
                    .Where(s => s.LocationDetails != null
                        && s.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode())
                    .ToList();
                if (!expiredStocks.Any())
                {
                    return WebResponseContent.Instance.OK("无符合条件的到期库存");
                }
                // 2. 检查已存在的任务
                var palletCodes = expiredStocks.Select(s => s.PalletCode).ToList();
                var existingTasks = await Repository.QueryDataAsync(t =>
                    palletCodes.Contains(t.PalletCode)
                    && (t.TaskStatus == TaskStatusEnum.New.GetHashCode()
                        || t.TaskStatus == TaskStatusEnum.SC_Executing.GetHashCode()
                        || t.TaskStatus == TaskInStatusEnum.InNew.GetHashCode()));
                var processedPallets = existingTasks.Select(t => t.PalletCode).ToHashSet();
                // 3. 筛选需要处理的库存
                var stocksToProcess = expiredStocks
                    .Where(s => !processedPallets.Contains(s.PalletCode))
                    .ToList();
                if (!stocksToProcess.Any())
                {
                    return WebResponseContent.Instance.OK("所有到期库存已存在任务");
                }
                // 4. 获取配置的目标地址映射
                var targetAddressMap = _configuration.GetSection("AutoOutboundTask:TargetAddresses")
                    .Get<Dictionary<string, string>>()
                    ?? new Dictionary<string, string>();
                // 5. 批量创建任务
                var taskList = new List<Dt_Task>();
                foreach (var stock in stocksToProcess)
                {
                    // 根据巷道确定目标地址
                    var targetAddress = DetermineTargetAddress(
                        stock.LocationDetails?.RoadwayNo ?? "",
                        targetAddressMap);
                    var task = new Dt_Task
                    {
                        WarehouseId = stock.WarehouseId,
                        PalletCode = stock.PalletCode,
                        PalletType = stock.PalletType,
                        SourceAddress = stock.LocationCode,
                        CurrentAddress = stock.LocationCode,
                        NextAddress = targetAddress,
                        TargetAddress = targetAddress,
                        Roadway = stock.LocationDetails?.RoadwayNo ?? "",
                        TaskType = TaskTypeEnum.Outbound.GetHashCode(),
                        TaskStatus = TaskStatusEnum.New.GetHashCode(),
                        Grade = 1,
                        TaskNum = 0,  // 使用 0 让数据库自动生成任务号
                        Creater = "system_auto"
                    };
                    taskList.Add(task);
                }
                var addResult = await BaseDal.AddDataAsync(taskList) > 0;
                if (!addResult)
                {
                    return WebResponseContent.Instance.Error($"批量创建任务失败,共 {taskList.Count} 个任务");
                }
                // 6. 通知 WCS(异步,不影响主流程)
                _ = Task.Run(() =>
                {
                    foreach (var task in taskList)
                    {
                        try
                        {
                            var wmstaskDto = _mapper.Map<WMSTaskDTO>(task);
                            _httpClientHelper.Post<WebResponseContent>(
                                "http://logistics-service/api/logistics/notifyoutbound",
                                JsonSerializer.Serialize(wmstaskDto));
                        }
                        catch (Exception ex)
                        {
                            // WCS 通知失败不影响任务创建,记录日志即可
                            Console.WriteLine($"WCS 通知失败,任务编号: {task.TaskNum}, 错误: {ex.Message}");
                        }
                    }
                });
                return WebResponseContent.Instance.OK($"成功创建 {taskList.Count} 个出库任务", taskList.Count);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"自动创建出库任务失败: {ex.Message}");
            }
        }
        #endregion WCS逻辑处理
        #region 分容柜接口
@@ -378,7 +686,6 @@
                    var result = await BaseDal.AddDataAsync(taskList) > 0;
                    var wmstaskDto = result ? _mapper.Map<WMSTaskDTO>(taskList) : null;
                    var httpResponse = _httpClientHelper.Post<WebResponseContent>("http://logistics-service/api/logistics/notifyoutbound", JsonSerializer.Serialize(wmstaskDto)).Data;
                    if (result && httpResponse != null)
                    {
@@ -433,6 +740,6 @@
            }
        }
        #endregion
        #endregion 分容柜接口
    }
}
}