wanshenmean
昨天 5f1294d6dea53d286f5e7029839d37bf490e32bb
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Manual.cs
@@ -1,9 +1,13 @@
using System.ComponentModel;
using System.Reflection;
using Newtonsoft.Json;
using SqlSugar;
using WIDESEA_Common.Constants;
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Core;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.MES;
using WIDESEA_DTO.Task;
using WIDESEA_Model.Models;
@@ -39,6 +43,11 @@
                    case "移库":
                        taskType = TaskRelocationTypeEnum.Relocation.GetHashCode();
                        taskStatus = TaskRelocationStatusEnum.RelocationNew.GetHashCode();
                        break;
                    case "空箱入库":
                        taskType = TaskInboundTypeEnum.InEmpty.GetHashCode();
                        taskStatus = TaskInStatusEnum.InNew.GetHashCode();
                        break;
                    default:
@@ -92,7 +101,7 @@
                        wmsTaskDtos.ToJson());
                    if (!wcsResult.IsSuccess || !wcsResult.Data.Status)
                        return WebResponseContent.Instance.Error($"任务已创建但发送给WCS失败: {wcsResult.Data?.Message}");
                        return WebResponseContent.Instance.Error($"任务已创建但发送给WCS失败:{wcsResult.ErrorMessage}\r\n {wcsResult.Data?.Message}");
                    return WebResponseContent.Instance.OK($"手动创建任务成功,任务号: {taskNum}");
                });
@@ -323,6 +332,254 @@
            return descAttr?.Description ?? taskStatus.ToString();
        }
        /// <summary>
        /// 组盘/拆盘操作并创建入库任务,完成后下发WCS
        /// </summary>
        /// <param name="dto">组盘/拆盘操作参数</param>
        /// <returns>操作结果</returns>
        public async Task<WebResponseContent> PalletOperationAndCreateTaskAsync(PalletOperationTaskDto dto)
        {
            try
            {
                // 1. 参数校验
                if (string.IsNullOrWhiteSpace(dto.PalletCode))
                    return WebResponseContent.Instance.Error("托盘号不能为空");
                if (string.IsNullOrWhiteSpace(dto.Action)
                    || (dto.Action != "组盘" && dto.Action != "拆盘"))
                    return WebResponseContent.Instance.Error("执行动作必须是'组盘'或'拆盘'");
                if (string.IsNullOrWhiteSpace(dto.LineId))
                    return WebResponseContent.Instance.Error("线体编号不能为空");
                if (string.IsNullOrWhiteSpace(dto.WarehouseCode))
                    return WebResponseContent.Instance.Error("仓库编号不能为空");
                if (string.IsNullOrWhiteSpace(dto.RobotName))
                    return WebResponseContent.Instance.Error("机械手名称不能为空");
                // 组盘时电芯列表必传
                if (dto.Action == "组盘" && (dto.Cells == null || !dto.Cells.Any()))
                    return WebResponseContent.Instance.Error("组盘时电芯列表不能为空");
                // 2. 根据仓库编号查询仓库信息,获取 WarehouseId、targetAddress 和 roadway
                var warehouse = _sqlSugarClient.Queryable<Dt_Warehouse>()
                    .First(w => w.WarehouseCode == dto.WarehouseCode);
                if (warehouse == null)
                    return WebResponseContent.Instance.Error($"未找到仓库编号为[{dto.WarehouseCode}]的仓库");
                int warehouseId = warehouse.WarehouseId;
                string targetAddress = warehouse.WarehouseCode;
                string roadway = warehouse.WarehouseCode;
                // 3. 根据动作类型执行不同流程
                if (dto.Action == "组盘")
                    return await ExecuteGroupPalletAsync(dto, warehouseId, targetAddress, roadway);
                return await ExecuteSplitPalletAsync(dto, warehouseId, targetAddress, roadway);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"组盘/拆盘操作异常: {ex.Message}");
            }
        }
        /// <summary>
        /// 执行组盘操作:添加库存 → 直接调用MES绑定 → 创建入库任务 → 下发WCS
        /// </summary>
        private async Task<WebResponseContent> ExecuteGroupPalletAsync(
            PalletOperationTaskDto dto, int warehouseId, string targetAddress, string roadway)
        {
            return await _unitOfWorkManage.BeginTranAsync(async () =>
            {
                // 1. 创建库存主记录(不绑定货位)
                var stockInfo = new Dt_StockInfo
                {
                    PalletCode = dto.PalletCode,
                    PalletType = 0,
                    WarehouseId = warehouseId,
                    LocationId = 0,
                    StockStatus = 0,
                    MesUploadStatus = 0
                };
                var stockId = await _stockInfoService.Repository.AddDataAsync(stockInfo);
                if (stockId <= 0)
                    return WebResponseContent.Instance.Error("创建库存记录失败");
                // 2. 批量创建库存明细
                var details = dto.Cells.Select((cell, i) => new Dt_StockInfoDetail
                {
                    StockId = (int)stockId,
                    MaterielCode = cell.SfcCode,
                    MaterielName = "",
                    OrderNo = "",
                    ProductionDate = DateTime.Now.ToString("yyyy-MM-dd"),
                    EffectiveDate = "",
                    SerialNumber = cell.SfcCode,
                    StockQuantity = 1,
                    OutboundQuantity = 0,
                    Status = 0,
                    InboundOrderRowNo = Convert.ToInt32(cell.Channel),
                    Remark = cell.Channel
                }).ToList();
                if (await _sqlSugarClient.Insertable(details).ExecuteCommandAsync() <= 0)
                    return WebResponseContent.Instance.Error("创建库存明细失败");
                // 3. 调用MES绑定
                var (equipmentCode, resourceCode, token) = ResolveMesConfig(dto.RobotName);
                var bindRequest = new BindContainerRequest
                {
                    ContainerCode = dto.PalletCode,
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    LocalTime = DateTime.Now,
                    OperationType = StockConstants.MES_BIND_OPERATION_TYPE,
                    ContainerSfcList = dto.Cells.Select((cell, i) => new ContainerSfcItem
                    {
                        Sfc = cell.SfcCode,
                        Location = cell.Channel
                    }).ToList()
                };
                var mesResult = string.IsNullOrWhiteSpace(token)
                    ? _mesService.BindContainer(bindRequest)
                    : _mesService.BindContainer(bindRequest, token);
                if (!CheckMesResult(mesResult, out var mesError))
                    return WebResponseContent.Instance.Error($"MES组盘绑定失败: {mesError}");
                // 4. 创建入库任务并下发WCS
                return await CreateTaskAndDispatchAsync(dto, warehouseId, targetAddress, roadway,
                    TaskInboundTypeEnum.Inbound, "组盘成功并创建入库任务");
            });
        }
        /// <summary>
        /// 执行拆盘操作:查询库存 → 直接调用MES解绑 → 清除库存货位 → 创建空托盘入库任务 → 下发WCS
        /// </summary>
        private async Task<WebResponseContent> ExecuteSplitPalletAsync(
            PalletOperationTaskDto dto, int warehouseId, string targetAddress, string roadway)
        {
            return await _unitOfWorkManage.BeginTranAsync(async () =>
            {
                // 1. 导航查询库存及明细
                var stockInfo = await _stockInfoService.Repository
                    .QueryDataNavFirstAsync(s => s.PalletCode == dto.PalletCode);
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error($"未找到托盘[{dto.PalletCode}]的库存记录");
                var sfcList = stockInfo.Details?.Select(d => d.SerialNumber).ToList()
                    ?? new List<string>();
                if (!sfcList.Any())
                    return WebResponseContent.Instance.Error($"托盘[{dto.PalletCode}]下无电芯数据");
                // 2. 调用MES解绑
                var (equipmentCode, resourceCode, token) = ResolveMesConfig(dto.RobotName);
                var unbindRequest = new UnBindContainerRequest
                {
                    EquipmentCode = equipmentCode,
                    ResourceCode = resourceCode,
                    LocalTime = DateTime.Now,
                    ContainCode = dto.PalletCode,
                    SfcList = sfcList
                };
                var mesResult = string.IsNullOrWhiteSpace(token)
                    ? _mesService.UnBindContainer(unbindRequest)
                    : _mesService.UnBindContainer(unbindRequest, token);
                if (!CheckMesResult(mesResult, out var mesError))
                    return WebResponseContent.Instance.Error($"MES拆盘解绑失败: {mesError}");
                // 3. 清除库存绑定的货位
                stockInfo.LocationId = 0;
                stockInfo.LocationCode = null;
                await _sqlSugarClient.Updateable(stockInfo)
                    .UpdateColumns(s => new { s.LocationId, s.LocationCode })
                    .ExecuteCommandAsync();
                // 4. 创建空托盘入库任务并下发WCS
                return await CreateTaskAndDispatchAsync(dto, warehouseId, targetAddress, roadway,
                    TaskInboundTypeEnum.InEmpty, "拆盘成功并创建空托盘入库任务");
            });
        }
        /// <summary>
        /// 解析MES设备配置,返回 (equipmentCode, resourceCode, token)
        /// </summary>
        private (string equipmentCode, string resourceCode, string token) ResolveMesConfig(string deviceName)
        {
            var config = _mesDeviceConfigService.GetByDeviceName(deviceName);
            return (
                config?.EquipmentCode ?? StockConstants.MES_EQUIPMENT_CODE,
                config?.ResourceCode ?? StockConstants.MES_RESOURCE_CODE,
                config?.Token
            );
        }
        /// <summary>
        /// 检查MES返回结果,失败时通过 error 输出错误信息
        /// </summary>
        private bool CheckMesResult(HttpResponseResult<MesResponse> result, out string error)
        {
            if (result?.Data != null && result.Data.IsSuccess)
            {
                error = null;
                return true;
            }
            error = result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误";
            return false;
        }
        /// <summary>
        /// 创建入库任务并下发WCS
        /// </summary>
        private async Task<WebResponseContent> CreateTaskAndDispatchAsync(
            PalletOperationTaskDto dto, int warehouseId, string targetAddress, string roadway,
            TaskInboundTypeEnum taskType, string successMessage)
        {
            int taskNum = await BaseDal.GetTaskNo();
            var task = new Dt_Task
            {
                TaskNum = taskNum,
                PalletCode = dto.PalletCode,
                SourceAddress = dto.LineId,
                TargetAddress = targetAddress,
                TaskType = taskType.GetHashCode(),
                TaskStatus = TaskInStatusEnum.InNew.GetHashCode(),
                Grade = dto.Grade,
                Roadway = roadway,
                WarehouseId = warehouseId,
                CurrentAddress = dto.LineId,
                NextAddress = targetAddress,
                Creater = "manual",
                CreateDate = DateTime.Now,
                ModifyDate = DateTime.Now
            };
            if (await BaseDal.AddDataAsync(task) <= 0)
                return WebResponseContent.Instance.Error("创建任务失败");
            var wmsTaskDto = new WMSTaskDTO
            {
                TaskNum = task.TaskNum,
                PalletCode = task.PalletCode,
                SourceAddress = task.SourceAddress,
                TargetAddress = task.TargetAddress,
                TaskType = task.TaskType,
                Roadway = task.Roadway,
                TaskStatus = task.TaskStatus,
                WarehouseId = task.WarehouseId
            };
            var wcsResult = _httpClientHelper.Post<WebResponseContent>(
                "http://localhost:9292/api/Task/ReceiveManualTask",
                new List<WMSTaskDTO> { wmsTaskDto }.ToJson());
            if (!wcsResult.IsSuccess || !wcsResult.Data.Status)
                return WebResponseContent.Instance.Error(
                    $"任务已创建但发送给WCS失败: {wcsResult.ErrorMessage}\r\n {wcsResult.Data?.Message}");
            return WebResponseContent.Instance.OK($"{successMessage},任务号: {taskNum}");
        }
        #endregion 手动任务
    }
}