| | |
| | | using AutoMapper; |
| | | using Microsoft.AspNetCore.Components.Forms; |
| | | using Microsoft.Extensions.Configuration; |
| | | using SqlSugar; |
| | | using System.Text.Json; |
| | | using WIDESEA_Common.LocationEnum; |
| | |
| | | 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; |
| | |
| | | private readonly IStockInfoService _stockInfoService; |
| | | private readonly ILocationInfoService _locationInfoService; |
| | | private readonly HttpClientHelper _httpClientHelper; |
| | | private readonly IConfiguration _configuration; |
| | | |
| | | public IRepository<Dt_Task> Repository => BaseDal; |
| | | |
| | |
| | | IMapper mapper, |
| | | IStockInfoService stockInfoService, |
| | | ILocationInfoService locationInfoService, |
| | | HttpClientHelper httpClientHelper) : base(BaseDal) |
| | | HttpClientHelper httpClientHelper, |
| | | IConfiguration configuration) : base(BaseDal) |
| | | { |
| | | _mapper = mapper; |
| | | _stockInfoService = stockInfoService; |
| | | _locationInfoService = locationInfoService; |
| | | _httpClientHelper = httpClientHelper; |
| | | _configuration = configuration; |
| | | } |
| | | |
| | | #region WCS逻辑处理 |
| | | |
| | | /// <summary> |
| | | /// 创建任务(组盘入库任务、空托盘回库任务) |
| | |
| | | { |
| | | 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("无效的任务详情"); |
| | |
| | | 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, |
| | |
| | | task.CurrentAddress = task.SourceAddress; |
| | | task.NextAddress = locationInfo.LocationCode; |
| | | task.TargetAddress = locationInfo.LocationCode; |
| | | task.TaskStatus = TaskStatusEnum.Line_Finish.GetHashCode(); |
| | | task.TaskStatus = TaskInStatusEnum.Line_InFinish.GetHashCode(); |
| | | |
| | | var updateResult = await BaseDal.UpdateDataAsync(task); |
| | | var locationResult = await _locationInfoService.UpdateLocationInfoAsync(locationInfo); |
| | |
| | | { |
| | | 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("未找到对应的任务"); |
| | |
| | | { |
| | | 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) |
| | | if (stockInfo == null) |
| | | return WebResponseContent.Instance.Error("未找到对应的库存信息"); |
| | | |
| | | var task = new Dt_Task() |
| | |
| | | TaskNum = await BaseDal.GetTaskNo(), |
| | | Creater = "system", |
| | | }; |
| | | var taskDtos = _mapper.Map<List<WMSTaskDTO>>(task); |
| | | 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, string> addressMap) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(roadway)) |
| | | return "10080"; // 默认地址 |
| | | |
| | | foreach (var kvp in addressMap) |
| | | { |
| | | if (roadway.Contains(kvp.Key)) |
| | | return kvp.Value; |
| | | } |
| | | |
| | | return "10080"; // 默认地址 |
| | | } |
| | | |
| | | /// <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 分容柜接口 |
| | | |
| | |
| | | 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) |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | #endregion 分容柜接口 |
| | | } |
| | | } |
| | | } |