Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs
@@ -82,7 +82,17 @@ /// <summary> /// 空æçåºåºå®æï¼æ ¹æ®ä»»å¡å·åæçå·éç¥WMS空æçåºåºå®æï¼ /// </summary> OutboundFinishTaskTray OutboundFinishTaskTray, /// <summary> /// æ¹éæç确认 /// </summary> SplitPalletConfirm, /// <summary> /// æ¹éç»ç确认 /// </summary> GroupPalletConfirm #endregion WMSæ¥å£ } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/HostedService/ApiRouteCacheWarmupHostedService.cs
@@ -28,7 +28,9 @@ (nameof(ConfigKey.CreateRobotGroupPalletTask), "Task/CreateRobotGroupPalletTask"), (nameof(ConfigKey.CreateRobotChangePalletTask), "Task/CreateRobotChangePalletTask"), (nameof(ConfigKey.CreateRobotSplitPalletTask), "Task/CreateRobotSplitPalletTask"), (nameof(ConfigKey.OutboundFinishTaskTray), "Task/OutboundFinishTaskTray") (nameof(ConfigKey.OutboundFinishTaskTray), "Task/OutboundFinishTaskTray"), (nameof(ConfigKey.SplitPalletConfirm), "Stock/SplitPalletConfirm"), (nameof(ConfigKey.GroupPalletConfirm), "Stock/GroupPalletConfirm") }; private readonly ICacheService _cache; @@ -53,7 +55,7 @@ warmedCount++; } _logger.LogInformation("ï¼APIè·¯ç±ç¼åé¢ç宿ã计æ°={Count}", warmedCount); _logger.LogInformation("��API·�ɻ���Ԥ����ɡ�����={Count}", warmedCount); return Task.CompletedTask; } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotBarcodeGenerator.cs
@@ -1,36 +1,27 @@ using Masuit.Tools; using WIDESEAWCS_QuartzJob; namespace WIDESEAWCS_Tasks { /// <summary> /// æºæ¢°ææ¡ç çæå¨ - è´è´£çææçæ¡ç /// æºæ¢°ææ¡ç 读åå¨ - è´è´£è¯»åçµè¯æ¡ç /// </summary> /// <remarks> /// æ¡ç æ ¼å¼ï¼åç¼ï¼å¯éï¼+ æ¥æï¼yyyyMMddï¼+ æ¶é´ï¼HHmmssï¼+ éæºæ°ï¼100-999ï¼ /// ä¾å¦ï¼TRAY20260326093045123 /// </remarks> public static class RobotBarcodeGenerator { /// <summary> /// çææçæ¡ç /// 读åçº¿ä½æ¡ç /// </summary> /// <param name="prefix">æ¡ç åç¼ï¼é»è®¤ä¸ºç©ºå符串ï¼ä¾å¦ "TRAY"</param> /// <returns>çæçæ¡ç åç¬¦ä¸²ï¼æ ¼å¼ï¼åç¼+æ¥æ+æ¶é´+éæºæ°</returns> /// <param name="prefix">DBç¹ä½ï¼ä¾å¦ "DB40.990"</param> /// <returns>读åå°ççµè¯æ¡ç </returns> public static string GenerateTrayBarcode(string prefix = "") { // è·åå½åæ¥æï¼æ ¼å¼å为 yyyyMMddï¼8使°åï¼ // ä¾å¦ï¼20260326 string datePart = DateTime.Now.ToString("yyyyMMdd"); // è·åå½åæ¶é´ï¼æ¶åç§ï¼ï¼æ ¼å¼å为 HHmmssï¼6使°åï¼ // ä¾å¦ï¼093045 表示 09:30:45 string timePart = DateTime.Now.ToString("HHmmss"); // çæ3ä½éæºæ°ï¼èå´ 100-999ï¼ç¡®ä¿æ¡ç å¯ä¸æ§ // ä½¿ç¨ Random.Shared è·å线ç¨å®å ¨çéæºæ°çæå¨ string randomPart = Random.Shared.Next(100, 1000).ToString(); // ç»åææé¨åï¼åç¼ + æ¥æ + æ¶é´ + éæºæ° // 妿åç¼ä¸ºç©ºï¼åç´æ¥è¿åæ¥æ+æ¶é´+éæºæ°çç»å return prefix + datePart + timePart + randomPart; var device = Storage.Devices.Where(d => d.DeviceName == "Aåº_䏿³¨è¾é线").FirstOrDefault(); if (!device.IsNullOrEmpty() && device != null && device.Communicator.IsConnected) { var trayBarcode = device.Communicator.Read<string>(prefix); return trayBarcode; } return ""; } } } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotSocketState.cs
@@ -203,5 +203,13 @@ /// 4: æ¾åçµè¯å°5å·ä½ï¼æµåB Phase2ï¼ /// </remarks> public int ChangePalletPhase { get; set; } /// <summary> /// æ¯å¦æ«ç NG /// </summary> /// <remarks> /// æå¸¦çº¿ä¸çµè¯æ«ç æ¯å¦NGã /// </remarks> public bool IsScanNG { get; set; } = false; } } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -164,7 +164,8 @@ /// </remarks> /// <param name="task">è¦ä¸åçä»»å¡å¯¹è±¡</param> /// <param name="state">æºå¨äººå½åç¶æ</param> public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state) /// <param name="isScanNG">æ¯å¦æ«ç NG</param> public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state, bool isScanNG) { // æå»ºåè´§æä»¤ï¼æ ¼å¼ï¼Pickbattery,{æºå°å} string taskString = $"Pickbattery,{task.RobotSourceAddress}"; @@ -183,6 +184,11 @@ // å°ä»»å¡å ³èå°ç¶æå¯¹è±¡ state.CurrentTask = task; if(isScanNG) { state.IsScanNG = true; } // ä¿æåè¯ä¹ï¼ä» å¨ç¶æå®å ¨åå ¥æåååæ´æ°ä»»å¡ç¶æ // è¿æ ·å¯ä»¥ç¡®ä¿ç¶æåä»»å¡è®°å½çä¸è´æ§ @@ -514,37 +520,37 @@ } // è§£æè¿åçä»»å¡ä¿¡æ¯ var taskInfos = JsonConvert.DeserializeObject<List<Dt_Task>>(content.Data.ToJson() ?? string.Empty) ?? new List<Dt_Task>(); var taskInfo = taskInfos.FirstOrDefault(); //var taskInfos = JsonConvert.DeserializeObject<List<Dt_Task>>(content.Data.ToJson() ?? string.Empty) ?? new List<Dt_Task>(); //var taskInfo = taskInfos.FirstOrDefault(); // è·åæºå°å string sourceAddress = taskDTO.SourceAddress; //// è·åæºå°å //string sourceAddress = taskDTO.SourceAddress; // æ¥æ¾æºå°å对åºçè¾éçº¿è®¾å¤ IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress)); //// æ¥æ¾æºå°å对åºçè¾éçº¿è®¾å¤ //IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress)); if (device != null) { // å°è®¾å¤è½¬æ¢ä¸ºè¾é线类å CommonConveyorLine conveyorLine = (CommonConveyorLine)device; //if (device != null) //{ // // å°è®¾å¤è½¬æ¢ä¸ºè¾é线类å // CommonConveyorLine conveyorLine = (CommonConveyorLine)device; // 设置è¾é线çç®æ å°å conveyorLine.SetValue(ConveyorLineDBNameNew.Target, taskInfo.NextAddress, sourceAddress); // // 设置è¾é线çç®æ å°å // conveyorLine.SetValue(ConveyorLineDBNameNew.Target, taskInfo.NextAddress, sourceAddress); // 设置è¾é线çä»»å¡å· conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, taskInfo.TaskNum, sourceAddress); // // 设置è¾é线çä»»å¡å· // conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, taskInfo.TaskNum, sourceAddress); // 触åè¾é线å¼å§æ§è¡ï¼åå ¥ WCS_ACK = 1ï¼ conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, sourceAddress); // // 触åè¾é线å¼å§æ§è¡ï¼åå ¥ WCS_ACK = 1ï¼ // conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, sourceAddress); // æ´æ°ä»»å¡ç¶æå°ä¸ä¸é¶æ®µ if (_taskService.UpdateTaskStatusToNext(taskInfo).Status) { _logger.LogInformation("HandleInboundTaskAsyncï¼å ¥åºä»»å¡å¤çæåï¼ä»»å¡å·: {TaskNum}", taskInfo.TaskNum); QuartzLogger.Info($"HandleInboundTaskAsyncï¼å ¥åºä»»å¡å¤çæåï¼ä»»å¡å·: {taskInfo.TaskNum}", state.RobotCrane?.DeviceName ?? "Unknown"); return true; } } // // æ´æ°ä»»å¡ç¶æå°ä¸ä¸é¶æ®µ // if (_taskService.UpdateTaskStatusToNext(taskInfo).Status) // { // _logger.LogInformation("HandleInboundTaskAsyncï¼å ¥åºä»»å¡å¤çæåï¼ä»»å¡å·: {TaskNum}", taskInfo.TaskNum); // QuartzLogger.Info($"HandleInboundTaskAsyncï¼å ¥åºä»»å¡å¤çæåï¼ä»»å¡å·: {taskInfo.TaskNum}", state.RobotCrane?.DeviceName ?? "Unknown"); // return true; // } //} return false; } @@ -632,5 +638,33 @@ { return _httpClientHelper.Post<WebResponseContent>(configKey, stockDTO.ToJson()); } /// <summary> /// è°ç¨æ¹éæç确认 API /// </summary> /// <remarks> /// 彿çä»»å¡å ¨é¨å宿¶è°ç¨ï¼ä¸æ¬¡æ§ä¸ä¼ æ´ä¸ªæççè§£ç»æ°æ®å° MESã /// </remarks> /// <param name="palletCode">æºæçå·</param> /// <returns>HTTP ååºç»æ</returns> public HttpResponseResult<WebResponseContent> PostSplitPalletConfirmAsync(string palletCode) { var request = new { PalletCode = palletCode }; return _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.SplitPalletConfirm), request.ToJson()); } /// <summary> /// è°ç¨æ¹éç»ç确认 API /// </summary> /// <remarks> /// å½ç»çä»»å¡å ¨é¨æ¾å®æ¶è°ç¨ï¼ä¸æ¬¡æ§ä¸ä¼ æ´ä¸ªæççç»å®æ°æ®å° MESã /// </remarks> /// <param name="palletCode">ç®æ æçå·</param> /// <returns>HTTP ååºç»æ</returns> public HttpResponseResult<WebResponseContent> PostGroupPalletConfirmAsync(string palletCode) { var request = new { PalletCode = palletCode }; return _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.GroupPalletConfirm), request.ToJson()); } } } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
@@ -145,20 +145,27 @@ // 仿°æ®åºéæ°æ¥è¯¢å½åä»»å¡ï¼ç¡®ä¿è·åææ°ç¶æï¼ var task = await _robotTaskService.Repository.QueryFirstAsync(x => x.RobotTaskId == state.CurrentTask.RobotTaskId); // æ ¹æ®å½ä»¤åç¼ååå¤ç if (cmd.StartsWith("pickfinished")) if (task != null) { // å¤çåè´§å®æ await HandlePickFinishedAsync(state, positions, task); } else if (cmd.StartsWith("putfinished")) { // å¤çæ¾è´§å®æ await HandlePutFinishedAsync(state, positions, task); } // æ ¹æ®å½ä»¤åç¼ååå¤ç if (cmd.StartsWith("pickfinished")) { // å¤çåè´§å®æ await HandlePickFinishedAsync(state, positions, task); } else if (cmd.StartsWith("putfinished")) { // å¤çæ¾è´§å®æ await HandlePutFinishedAsync(state, positions, task); } // åååæ¶æ¯å°å®¢æ·ç«¯ï¼ä¿æåæè¡ä¸ºï¼ await _socketClientGateway.SendMessageAsync(client, message); // åååæ¶æ¯å°å®¢æ·ç«¯ï¼ä¿æåæè¡ä¸ºï¼ await _socketClientGateway.SendMessageAsync(client, message); } else { Console.WriteLine($"RobotJob HandleAsync Warning: Current task not found for RobotTaskId {state.CurrentTask.RobotTaskId}"); } } catch (Exception ex) { @@ -306,9 +313,12 @@ putSuccess = result.Data.Status && result.IsSuccess; // å¢å ä»»å¡è®¡æ° state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; if (!state.IsScanNG) { state.RobotTaskTotalNum += positions.Length; if (task != null) task.RobotTaskTotalNum -= positions.Length; } } } @@ -317,6 +327,7 @@ { // æ´æ°å½åå¨ä½ä¸º"æ¾è´§å®æ" state.CurrentAction = "PutFinished"; state.IsScanNG = false; // éç»ç任塿¶å¢å 计æ°ï¼ç»çä»»å¡å·²å¨ä¸é¢éå¢ï¼ if (!state.IsGroupPallet) @@ -325,18 +336,18 @@ if (task != null) task.RobotTaskTotalNum -= positions.Length; } } // 妿任å¡åå¨ if (task != null) { // æ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ¾è´§å®æ" task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode(); // å®å ¨æ´æ°ç¶æå° Redis if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) // 妿任å¡åå¨ if (task != null) { await _robotTaskService.Repository.UpdateDataAsync(task); // æ´æ°ä»»å¡ç¶æä¸º"æºå¨äººæ¾è´§å®æ" task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode(); // å®å ¨æ´æ°ç¶æå° Redis if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) { await _robotTaskService.Repository.UpdateDataAsync(task); } } } } Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -143,6 +143,14 @@ { if (state.ChangePalletPhase == 0) { // è°ç¨æ¹éæç确认æ¥å£ï¼æ¢çåå®é¶æ®µï¼ var sourcePallet = state.CurrentTask.RobotSourceAddressPalletCode; var confirmResult = _taskProcessor.PostSplitPalletConfirmAsync(sourcePallet); if (!confirmResult.IsSuccess) { QuartzLogger.Error($"æ¹éæç确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown"); } // ææé¶æ®µå®æï¼å¤çå ¥åº if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) { @@ -165,6 +173,14 @@ // æçä»»å¡ï¼ç´æ¥å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.SplitPallet) { // è°ç¨æ¹éæç确认æ¥å£ var sourcePallet = state.CurrentTask.RobotSourceAddressPalletCode; var confirmResult = _taskProcessor.PostSplitPalletConfirmAsync(sourcePallet); if (!confirmResult.IsSuccess) { QuartzLogger.Error($"æ¹éæç确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown"); } if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true)) { // å ¥åºæåï¼å é¤ä»»å¡è®°å½ @@ -198,6 +214,14 @@ { if (state.ChangePalletPhase == 0) { // è°ç¨æ¹éç»ç确认æ¥å£ï¼æ¢çæ¾å®é¶æ®µï¼ var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode; var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet); if (!confirmResult.IsSuccess) { QuartzLogger.Error($"æ¹éç»ç确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown"); } // ææé¶æ®µå®æï¼å¤çå ¥åº if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) { @@ -226,6 +250,14 @@ // ç»çä»»å¡ï¼ç´æ¥å¤çå ¥åº if (robotTaskType == RobotTaskTypeEnum.GroupPallet) { // è°ç¨æ¹éç»ç确认æ¥å£ var targetPallet = state.CurrentTask.RobotTargetAddressPalletCode; var confirmResult = _taskProcessor.PostGroupPalletConfirmAsync(targetPallet); if (!confirmResult.IsSuccess) { QuartzLogger.Error($"æ¹éç»ç确认失败: {confirmResult.ErrorMessage}", state.RobotCrane?.DeviceName ?? "Unknown"); } // å¤çå ¥åºä»»å¡åä¼ // useSourceAddress: false 表示使ç¨ç®æ å°åï¼ç»çåºæ¯ï¼ if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false)) Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -5,6 +5,7 @@ using WIDESEAWCS_Core.LogHelper; using WIDESEAWCS_ITaskInfoService; using WIDESEAWCS_Model.Models; using WIDESEAWCS_Tasks.SocketServer; using WIDESEAWCS_Tasks.Workflow.Abstractions; namespace WIDESEAWCS_Tasks.Workflow @@ -163,11 +164,12 @@ { string taskString; var state = _stateManager.GetState(ipAddress); // æ¢çä»»å¡ä½¿ç¨æ¹æ¬¡æ ¼å¼ if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) { int targetNormalCount = task.RobotTaskTotalNum; var state = _stateManager.GetState(ipAddress); int currentCompletedCount = state?.RobotTaskTotalNum ?? 0; bool isFlowA = task.RobotSourceAddressLineCode is "11001" or "11010"; @@ -229,7 +231,20 @@ else { // 鿢çä»»å¡ï¼ä½¿ç¨åææ ¼å¼ taskString = $"Putbattery,{task.RobotTargetAddress}"; if (state != null && state.IsGroupPallet && task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode()) { // ç»çä»»å¡ï¼æ¾è´§é夿æ¯å¦NGï¼å¦æNGåæ¾å°NGå£ if (state.IsScanNG) { taskString = $"Putbattery,NG"; } else { taskString = $"Putbattery,{task.RobotTargetAddress}"; } } else taskString = $"Putbattery,{task.RobotTargetAddress}"; } bool result = await _clientManager.SendToClientAsync(ipAddress, taskString); @@ -301,46 +316,49 @@ // 妿æ¯ç»çä»»å¡ if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode()) { // çææçæ¡ç åç¼ const string prefix = "TRAY"; // çæä¸¤ä¸ªæçæ¡ç ï¼ç¨äºç»çæä½ï¼ï¼æµè¯ç¨ï¼åç»è¯»åçº¿ä½æ¡ç ï¼ string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix); string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix); // 读å线ä½çµè¯æ¡ç string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.990"); string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode("DB40.1020"); // 妿æ¡ç çææå if (!string.IsNullOrEmpty(trayBarcode1) && !string.IsNullOrEmpty(trayBarcode2)) { if(stateForUpdate.CellBarcode.Contains(trayBarcode1)|| stateForUpdate.CellBarcode.Contains(trayBarcode2)) if (stateForUpdate.CellBarcode.Contains(trayBarcode1) || stateForUpdate.CellBarcode.Contains(trayBarcode2)) { _logger.LogError("HandlePutFinishedStateAsyncï¼çæçæçæ¡ç å·²åå¨ï¼å¯è½åå¨éå¤ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Error($"çæçæçæ¡ç å·²åå¨ï¼å¯è½åå¨éå¤", stateForUpdate.RobotCrane.DeviceName); _logger.LogError("HandlePutFinishedStateAsyncï¼è¯»åçæçæ¡ç å·²åå¨ï¼å¯è½åå¨éå¤ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Error($"读åçæçæ¡ç å·²åå¨ï¼å¯è½åå¨éå¤", stateForUpdate.RobotCrane.DeviceName); // æ¡ç éå¤ï¼è®°å½é误æ¥å¿å¹¶åæ¢åç»æä½(åç»æ¾è´§æ¶ä¼ç¨å°è¿äºæ¡ç ä¿¡æ¯ï¼ä¾åç»æ¾è´§æ¶ä½¿ç¨ï¼è°è¯åå¯è½ä¼åæ¶æ¤é»è¾) return; // åéåè´§æä»¤ æ è®°æ«ç NGï¼æ¾è´§æ¶ä¸ä½¿ç¨è¿äºæ¡ç ï¼å¹¶æ¾å ¥NGå£ await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true); } else { _logger.LogInformation("HandlePutFinishedStateAsyncï¼çæçæçæ¡ç å¯ä¸ï¼ç»§ç»æ§è¡ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Info($"çæçæçæ¡ç å¯ä¸ï¼ç»§ç»æ§è¡", stateForUpdate.RobotCrane.DeviceName); _logger.LogInformation("HandlePutFinishedStateAsyncï¼è¯»åçæçæ¡ç å¯ä¸ï¼ç»§ç»æ§è¡ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Info($"读åçæçæ¡ç å¯ä¸ï¼ç»§ç»æ§è¡", stateForUpdate.RobotCrane.DeviceName); // å°æ¡ç æ·»å å°ç¶æä¸ï¼ä¾åç»æ¾è´§æ¶ä½¿ç¨ stateForUpdate.CellBarcode.Add(trayBarcode1); stateForUpdate.CellBarcode.Add(trayBarcode2); } // è®°å½æ¥å¿ï¼çææçæ¡ç æå _logger.LogInformation("HandlePutFinishedStateAsyncï¼çææçæ¡ç æå: {Barcode1}+{Barcode2}ï¼ä»»å¡å·: {TaskNum}", trayBarcode1, trayBarcode2, task.RobotTaskNum); QuartzLogger.Info($"çææçæ¡ç æå: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName); // è®°å½æ¥å¿ï¼è¯»åæçæ¡ç æå _logger.LogInformation("HandlePutFinishedStateAsyncï¼è¯»åæçæ¡ç æå: {Barcode1}+{Barcode2}ï¼ä»»å¡å·: {TaskNum}", trayBarcode1, trayBarcode2, task.RobotTaskNum); QuartzLogger.Info($"读åæçæ¡ç æå: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName); // åéåè´§æä»¤ await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false); } else { // æ¡ç çæå¤±è´¥ï¼è®°å½é误æ¥å¿ _logger.LogError("HandlePutFinishedStateAsyncï¼çææçæ¡ç 失败ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Error($"çææçæ¡ç 失败", stateForUpdate.RobotCrane.DeviceName); // æ¡ç 读å失败ï¼è®°å½é误æ¥å¿ _logger.LogError("HandlePutFinishedStateAsyncï¼è¯»åæçæ¡ç 失败ï¼ä»»å¡å·: {TaskNum}", task.RobotTaskNum); QuartzLogger.Error($"读åæçæ¡ç 失败", stateForUpdate.RobotCrane.DeviceName); // åéåè´§æä»¤ æ è®°æ«ç NGï¼æ¾è´§æ¶ä¸ä½¿ç¨è¿äºæ¡ç ï¼å¹¶æ¾å ¥NGå£ await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, true); } } else if (task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()) @@ -355,7 +373,7 @@ // ç®æ æ°é为48ï¼ç´æ¥èµ°åæé»è¾ï¼ä¸è¿å ¥æ¹æ¬¡æ¨¡å¼ if (targetNormalCount == targetTotal) { await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false); return; } @@ -524,7 +542,7 @@ else { // éç»çä»»å¡ï¼ç´æ¥åéåè´§æä»¤ await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate); await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate, false); } } } Code/WMS/WIDESEA_WMSServer/Database/Scripts/20260416_Dt_SplitTemp.sql
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,15 @@ -- æç临æ¶è¡¨ï¼ç¨äºæåæçä»»å¡çµè¯å表ï¼ä¾æ¹é确认æ¶ä½¿ç¨ IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Dt_SplitTemp]') AND type in (N'U')) BEGIN CREATE TABLE [dbo].[Dt_SplitTemp]( [Id] [int] IDENTITY(1,1) NOT NULL, [PalletCode] [nvarchar](50) NOT NULL, [SfcList] [nvarchar](max) NOT NULL, [CreateTime] [datetime] NOT NULL DEFAULT GETDATE(), CONSTRAINT [PK_Dt_SplitTemp] PRIMARY KEY CLUSTERED ([Id] ASC) ); -- å¯ä¸ç´¢å¼é²æ¢å䏿çéå¤åå ¥ CREATE UNIQUE NONCLUSTERED INDEX [IX_Dt_SplitTemp_PalletCode] ON [dbo].[Dt_SplitTemp]([PalletCode] ASC); END GO Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/GroupPalletConfirmRequestDto.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,13 @@ namespace WIDESEA_DTO.Stock { /// <summary> /// æ¹éç»ç确认请æ±DTO /// </summary> public class GroupPalletConfirmRequestDto { /// <summary> /// ç®æ æçå· /// </summary> public string PalletCode { get; set; } } } Code/WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/SplitPalletConfirmRequestDto.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,13 @@ namespace WIDESEA_DTO.Stock { /// <summary> /// æ¹éæç确认请æ±DTO /// </summary> public class SplitPalletConfirmRequestDto { /// <summary> /// æºæçå· /// </summary> public string PalletCode { get; set; } } } Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs
@@ -55,5 +55,19 @@ /// <param name="stock">åºåä¿¡æ¯æ°æ®ä¼ è¾å¯¹è±¡</param> /// <returns>æä½ç»æ</returns> Task<WebResponseContent> UpdateStockInfoAsync(StockInfoDTO stock); /// <summary> /// æ¹éæç确认 - 䏿¬¡æ§è°ç¨MESè§£ç»æ´ä¸ªæç /// </summary> /// <param name="palletCode">æºæçå·</param> /// <returns>æä½ç»æ</returns> Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode); /// <summary> /// æ¹éç»ç确认 - 䏿¬¡æ§è°ç¨MESç»å®æ´ä¸ªæç /// </summary> /// <param name="palletCode">ç®æ æçå·</param> /// <returns>æä½ç»æ</returns> Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode); } } Code/WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_SplitTemp.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,36 @@ using SqlSugar; using WIDESEA_Core.DB.Models; namespace WIDESEA_Model.Models { /// <summary> /// æç临æ¶è¡¨ - ç¨äºæåæçä»»å¡çµè¯å表ï¼ä¾æ¹é确认æ¶ä½¿ç¨ /// </summary> [SugarTable(nameof(Dt_SplitTemp), "æç临æ¶è¡¨")] public class Dt_SplitTemp { /// <summary> /// ä¸»é® /// </summary> [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主é®")] public int Id { get; set; } /// <summary> /// æçå· /// </summary> [SugarColumn(IsNullable = false, Length = 50, ColumnDescription = "æçå·")] public string PalletCode { get; set; } /// <summary> /// çµè¯æ¡ç å表ï¼JSONæ ¼å¼ï¼ /// </summary> [SugarColumn(IsNullable = false, Length = -1, ColumnDescription = "çµè¯æ¡ç å表JSON")] public string SfcList { get; set; } /// <summary> /// å建æ¶é´ /// </summary> [SugarColumn(IsNullable = false, ColumnDescription = "å建æ¶é´")] public DateTime CreateTime { get; set; } = DateTime.Now; } } Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
@@ -1,4 +1,5 @@ using SqlSugar; using Newtonsoft.Json; using SqlSugar; using WIDESEA_Common.Constants; using WIDESEA_Common.StockEnum; using WIDESEA_Core; @@ -41,6 +42,11 @@ public IWarehouseService _warehouseService { get; } /// <summary> /// SqlSugar客æ·ç«¯ï¼ç¨äºä¸´æ¶è¡¨æä½ï¼ /// </summary> public ISqlSugarClient SqlSugarClient { get; } /// <summary> /// Mesæ¥å£æå¡ /// </summary> public IMesService _mesService { get; } @@ -58,7 +64,8 @@ IStockInfoDetail_HtyService stockInfoDetail_HtyService, IStockInfo_HtyService stockInfo_HtyService, IMesService mesService, IWarehouseService warehouseService) IWarehouseService warehouseService, ISqlSugarClient sqlSugarClient) { StockInfoDetailService = stockInfoDetailService; StockInfoService = stockInfoService; @@ -66,6 +73,7 @@ StockInfo_HtyService = stockInfo_HtyService; _mesService = mesService; _warehouseService = warehouseService; SqlSugarClient = sqlSugarClient; } /// <summary> @@ -179,11 +187,11 @@ result = StockInfoService.Repository.AddData(entity, x => x.Details); if (!result) return content.Error("ç»ç失败"); //var mesResult = _mesService.BindContainer(bindRequest); //if (mesResult == null || mesResult.Data == null || !mesResult.Data.IsSuccess) //{ // return content.Error($"ç»çæåï¼ä½MESç»å®å¤±è´¥: {mesResult?.Data?.Msg ?? mesResult?.ErrorMessage ?? "æªç¥é误"}"); //} var mesResult = _mesService.BindContainer(bindRequest); if (mesResult == null || mesResult.Data == null || !mesResult.Data.IsSuccess) { return content.Error($"ç»çæåï¼ä½MESç»å®å¤±è´¥: {mesResult?.Data?.Msg ?? mesResult?.ErrorMessage ?? "æªç¥é误"}"); } return content.OK("ç»çæå"); }); } @@ -303,6 +311,30 @@ { if (stock == null || string.IsNullOrWhiteSpace(stock.SourcePalletNo)) return content.Error("æºæçå·ä¸è½ä¸ºç©º"); // å¹çåå ¥ï¼æ£æ¥ä¸´æ¶è¡¨æ¯å¦å·²æè¯¥æçè®°å½ï¼æ ååå ¥ var existingTemp = SqlSugarClient.Queryable<Dt_SplitTemp>() .Where(t => t.PalletCode == stock.SourcePalletNo) .First(); if (existingTemp == null) { // æ¥è¯¢è¯¥æçå½åææçµè¯ï¼åå ¥ä¸´æ¶è¡¨ var sourceStockForTemp = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.SourcePalletNo); if (sourceStockForTemp != null) { var allDetails = StockInfoDetailService.Repository.QueryData(d => d.StockId == sourceStockForTemp.Id); if (allDetails != null && allDetails.Any()) { var sfcListJson = JsonConvert.SerializeObject(allDetails.Select(d => d.SerialNumber).ToList()); await SqlSugarClient.Insertable(new Dt_SplitTemp { PalletCode = stock.SourcePalletNo, SfcList = sfcListJson, CreateTime = DateTime.Now }).ExecuteCommandAsync(); } } } return await ExecuteWithinTransactionAsync(async () => { @@ -426,5 +458,105 @@ OutboundDate = s.OutboundDate }).ToList(); } /// <summary> /// æ¹éæç确认 - 䏿¬¡æ§è°ç¨MESè§£ç»æ´ä¸ªæç /// </summary> /// <param name="palletCode">æºæçå·</param> /// <returns>æä½ç»æ</returns> public async Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode) { WebResponseContent content = new WebResponseContent(); try { if (string.IsNullOrWhiteSpace(palletCode)) return content.Error("æçå·ä¸è½ä¸ºç©º"); // 1. ä»ä¸´æ¶è¡¨è¯»åçµè¯å表 var tempRecord = SqlSugarClient.Queryable<Dt_SplitTemp>() .Where(t => t.PalletCode == palletCode) .First(); if (tempRecord == null) return content.Error("æªæ¾å°æç临æ¶è®°å½ï¼è¯·å æ§è¡æçæä½"); var sfcList = JsonConvert.DeserializeObject<List<string>>(tempRecord.SfcList); if (sfcList == null || !sfcList.Any()) return content.Error("临æ¶è¡¨ä¸çµè¯å表为空"); // 2. è°ç¨MESè§£ç» var unbindRequest = new UnBindContainerRequest { EquipmentCode = StockConstants.MES_EQUIPMENT_CODE, ResourceCode = StockConstants.MES_RESOURCE_CODE, LocalTime = DateTime.Now, ContainCode = palletCode, SfcList = sfcList }; var unbindResult = _mesService.UnBindContainer(unbindRequest); if (unbindResult == null || unbindResult.Data == null || !unbindResult.Data.IsSuccess) { return content.Error($"MESè§£ç»å¤±è´¥: {unbindResult?.Data?.Msg ?? unbindResult?.ErrorMessage ?? "æªç¥é误"}"); } // 3. å é¤ä¸´æ¶è¡¨è®°å½ await SqlSugarClient.Deleteable<Dt_SplitTemp>().Where(t => t.PalletCode == palletCode).ExecuteCommandAsync(); return content.OK("æ¹éæç确认æå"); } catch (Exception ex) { return content.Error($"æ¹éæç确认失败: {ex.Message}"); } } /// <summary> /// æ¹éç»ç确认 - 䏿¬¡æ§è°ç¨MESç»å®æ´ä¸ªæç /// </summary> /// <param name="palletCode">ç®æ æçå·</param> /// <returns>æä½ç»æ</returns> public async Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode) { WebResponseContent content = new WebResponseContent(); try { if (string.IsNullOrWhiteSpace(palletCode)) return content.Error("æçå·ä¸è½ä¸ºç©º"); // 1. æ¥è¯¢è¯¥æçä¸çææçµè¯æç» var stockInfo = StockInfoService.Repository.QueryFirst(s => s.PalletCode == palletCode); if (stockInfo == null) return content.Error("æçä¸åå¨"); var details = StockInfoDetailService.Repository.QueryData(d => d.StockId == stockInfo.Id); if (details == null || !details.Any()) return content.Error("æç䏿 çµè¯æ°æ®"); // 2. è°ç¨MESç»å® var bindRequest = new BindContainerRequest { ContainerCode = palletCode, EquipmentCode = StockConstants.MES_EQUIPMENT_CODE, ResourceCode = StockConstants.MES_RESOURCE_CODE, LocalTime = DateTime.Now, OperationType = StockConstants.MES_BIND_OPERATION_TYPE, ContainerSfcList = details.Select(d => new ContainerSfcItem { Sfc = d.SerialNumber, Location = d.InboundOrderRowNo.ToString() }).ToList() }; var bindResult = _mesService.BindContainer(bindRequest); if (bindResult == null || bindResult.Data == null || !bindResult.Data.IsSuccess) { return content.Error($"MESç»å®å¤±è´¥: {bindResult?.Data?.Msg ?? bindResult?.ErrorMessage ?? "æªç¥é误"}"); } return content.OK("æ¹éç»ç确认æå"); } catch (Exception ex) { return content.Error($"æ¹éç»ç确认失败: {ex.Message}"); } } } } Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs
@@ -63,5 +63,27 @@ { return await Service.UpdateStockInfoAsync(stock); } /// <summary> /// æ¹éæç确认 - WCSæçä»»å¡å ¨é¨å宿¶è°ç¨ /// </summary> /// <param name="dto">æç确认请æ±</param> /// <returns>æä½ç»æ</returns> [HttpPost("SplitPalletConfirm"), AllowAnonymous] public async Task<WebResponseContent> SplitPalletConfirm([FromBody] SplitPalletConfirmRequestDto dto) { return await Service.SplitPalletConfirmAsync(dto.PalletCode); } /// <summary> /// æ¹éç»ç确认 - WCSç»çä»»å¡å ¨é¨æ¾å®æ¶è°ç¨ /// </summary> /// <param name="dto">ç»ç确认请æ±</param> /// <returns>æä½ç»æ</returns> [HttpPost("GroupPalletConfirm"), AllowAnonymous] public async Task<WebResponseContent> GroupPalletConfirm([FromBody] GroupPalletConfirmRequestDto dto) { return await Service.GroupPalletConfirmAsync(dto.PalletCode); } } } Code/docs/superpowers/plans/2026-04-16-BatchMesBinding-Plan.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,485 @@ # æ¹é MES ç»å®ä¸è§£ç»æ¥å£å®æ½è®¡å > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** æ°å¢ä¸¤ä¸ª WMS æ¥å£ï¼SplitPalletConfirmãGroupPalletConfirmï¼ï¼ä¾ WCS å¨ä»»å¡é¶æ®µå®ææ¶ä¸æ¬¡æ§ä¸ä¼ æç级å«ç MES ç»å®/è§£ç»æ°æ®ï¼åå° MES è°ç¨æ¬¡æ°ã **Architecture:** - `Dt_SplitTemp` 临æ¶è¡¨ï¼æçå¼å§æ¶å¹çåå ¥æççµè¯å表ï¼Confirm æ¶è¯»åå¹¶å é¤ - `SplitPalletAsync` æ¹é ï¼æ¯æ¬¡è°ç¨æ¶æ£æ¥ä¸´æ¶è¡¨ï¼æ è®°å½ååå ¥ï¼æè®°å½åè·³è¿ - `SplitPalletConfirm`ï¼ä»ä¸´æ¶è¡¨è¯»åçµè¯ â è°ç¨ MES UnBindContainer â å é¤ä¸´æ¶è¡¨è®°å½ - `GroupPalletConfirm`ï¼ææçå·æ¥ Dt_StockInfoDetail â è°ç¨ MES BindContainer **Tech Stack:** .NET 6/8, C#, SqlSugar ORM, ASP.NET Core WebAPI --- ## æä»¶åæ´æ¦è§ | æä½ | æä»¶ | |------|------| | æ°å¢ | `WIDESEA_Model/Models/Stock/Dt_SplitTemp.cs` | | æ°å¢ | `WIDESEA_DTO/Stock/SplitPalletConfirmRequestDto.cs` | | æ°å¢ | `WIDESEA_DTO/Stock/GroupPalletConfirmRequestDto.cs` | | ä¿®æ¹ | `WIDESEA_IStockService/IStockService.cs` | | ä¿®æ¹ | `WIDESEA_StockService/StockService.cs` | | ä¿®æ¹ | `WIDESEA_WMSServer/Controllers/Stock/StockController.cs` | | ä¿®æ¹ | æ°æ®åºï¼æ°å¢ `Dt_SplitTemp` 表 | --- ## Task 1: æ°å»ºä¸´æ¶è¡¨å®ä½ Dt_SplitTemp **Files:** - Create: `WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_SplitTemp.cs` - [ ] **Step 1: å建 Dt_SplitTemp å®ä½** ```csharp using SqlSugar; using WIDESEA_Core.DB.Models; namespace WIDESEA_Model.Models { /// <summary> /// æç临æ¶è¡¨ - ç¨äºæåæçä»»å¡çµè¯å表ï¼ä¾æ¹é确认æ¶ä½¿ç¨ /// </summary> [SugarTable(nameof(Dt_SplitTemp), "æç临æ¶è¡¨")] public class Dt_SplitTemp { /// <summary> /// ä¸»é® /// </summary> [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主é®")] public int Id { get; set; } /// <summary> /// æçå· /// </summary> [SugarColumn(IsNullable = false, Length = 50, ColumnDescription = "æçå·")] public string PalletCode { get; set; } /// <summary> /// çµè¯æ¡ç å表ï¼JSONæ ¼å¼ï¼ /// </summary> [SugarColumn(IsNullable = false, Length = -1, ColumnDescription = "çµè¯æ¡ç å表JSON")] public string SfcList { get; set; } /// <summary> /// å建æ¶é´ /// </summary> [SugarColumn(IsNullable = false, ColumnDescription = "å建æ¶é´")] public DateTime CreateTime { get; set; } = DateTime.Now; } } ``` - [ ] **Step 2: Commit** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_SplitTemp.cs git commit -m "feat(Stock): æ°å¢Dt_SplitTempæç临æ¶è¡¨å®ä½ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` --- ## Task 2: æ°å»ºè¯·æ± DTO **Files:** - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/SplitPalletConfirmRequestDto.cs` - Create: `WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/GroupPalletConfirmRequestDto.cs` - [ ] **Step 1: å建 SplitPalletConfirmRequestDto** ```csharp namespace WIDESEA_DTO.Stock { /// <summary> /// æ¹éæç确认请æ±DTO /// </summary> public class SplitPalletConfirmRequestDto { /// <summary> /// æºæçå· /// </summary> public string PalletCode { get; set; } } } ``` - [ ] **Step 2: å建 GroupPalletConfirmRequestDto** ```csharp namespace WIDESEA_DTO.Stock { /// <summary> /// æ¹éç»ç确认请æ±DTO /// </summary> public class GroupPalletConfirmRequestDto { /// <summary> /// ç®æ æçå· /// </summary> public string PalletCode { get; set; } } } ``` - [ ] **Step 3: Commit** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/SplitPalletConfirmRequestDto.cs WMS/WIDESEA_WMSServer/WIDESEA_DTO/Stock/GroupPalletConfirmRequestDto.cs git commit -m "feat(DTO): æ°å¢æ¹éç»çæç确认请æ±DTO Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` --- ## Task 3: ä¿®æ¹ IStockService æ¥å£ **Files:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs`ï¼å¨æ¥å£æ«å°¾æ·»å ä¸¤ä¸ªæ°æ¹æ³ï¼ - [ ] **Step 1: å¨ IStockService æ¥å£æ·»å ä¸¤ä¸ªæ°æ¹æ³å£°æ** å¨ `UpdateStockInfoAsync` æ¹æ³å£°æä¹åãæ¥å£ç»æ `}` ä¹åæ·»å ï¼ ```csharp /// <summary> /// æ¹éæç确认 - 䏿¬¡æ§è°ç¨MESè§£ç»æ´ä¸ªæç /// </summary> /// <param name="palletCode">æºæçå·</param> /// <returns>æä½ç»æ</returns> Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode); /// <summary> /// æ¹éç»ç确认 - 䏿¬¡æ§è°ç¨MESç»å®æ´ä¸ªæç /// </summary> /// <param name="palletCode">ç®æ æçå·</param> /// <returns>æä½ç»æ</returns> Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode); ``` - [ ] **Step 2: Commit** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs git commit -m "feat(IStockService): æ°å¢SplitPalletConfirmAsyncåGroupPalletConfirmAsyncæ¥å£ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` --- ## Task 4: ä¿®æ¹ StockService å®ç° - SplitPalletConfirmAsync å GroupPalletConfirmAsync **Files:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockService.cs` - [ ] **Step 1: æ·»å ISqlSugarClient æ³¨å ¥å Dt_SplitTemp å®ä½** å¨ `StockService` ç±»ä¸æ·»å ï¼ ```csharp using SqlSugar; using WIDESEA_Model.Models; using Newtonsoft.Json; ``` å¨ç±»ä¸æ·»å 屿§ï¼ ```csharp /// <summary> /// SqlSugar客æ·ç«¯ï¼ç¨äºä¸´æ¶è¡¨æä½ï¼ /// </summary> public ISqlSugarClient SqlSugarClient { get; } ``` æé 彿°ä¸æ³¨å ¥ï¼ ```csharp public StockService( ..., ISqlSugarClient sqlSugarClient) // æ·»å å°åæ°æ«å°¾ { ... SqlSugarClient = sqlSugarClient; } ``` - [ ] **Step 2: å®ç° SplitPalletConfirmAsync æ¹æ³** å¨ç±»æ«å°¾ï¼`UpdateStockInfoAsync` æ¹æ³ä¹åã`CreateDetailHistory` ä¹åï¼æ·»å ï¼ ```csharp /// <summary> /// æ¹éæç确认 - 䏿¬¡æ§è°ç¨MESè§£ç»æ´ä¸ªæç /// </summary> /// <param name="palletCode">æºæçå·</param> /// <returns>æä½ç»æ</returns> public async Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode) { WebResponseContent content = new WebResponseContent(); try { if (string.IsNullOrWhiteSpace(palletCode)) return content.Error("æçå·ä¸è½ä¸ºç©º"); // 1. ä»ä¸´æ¶è¡¨è¯»åçµè¯å表 var tempRecord = SqlSugarClient.Queryable<Dt_SplitTemp>() .Where(t => t.PalletCode == palletCode) .First(); if (tempRecord == null) return content.Error("æªæ¾å°æç临æ¶è®°å½ï¼è¯·å æ§è¡æçæä½"); var sfcList = JsonConvert.DeserializeObject<List<string>>(tempRecord.SfcList); if (sfcList == null || !sfcList.Any()) return content.Error("临æ¶è¡¨ä¸çµè¯å表为空"); // 2. è°ç¨MESè§£ç» var unbindRequest = new UnBindContainerRequest { EquipmentCode = StockConstants.MES_EQUIPMENT_CODE, ResourceCode = StockConstants.MES_RESOURCE_CODE, LocalTime = DateTime.Now, ContainCode = palletCode, SfcList = sfcList }; var unbindResult = _mesService.UnBindContainer(unbindRequest); if (unbindResult == null || unbindResult.Data == null || !unbindResult.Data.IsSuccess) { return content.Error($"MESè§£ç»å¤±è´¥: {unbindResult?.Data?.Msg ?? unbindResult?.ErrorMessage ?? "æªç¥é误"}"); } // 3. å é¤ä¸´æ¶è¡¨è®°å½ SqlSugarClient.Deleteable<Dt_SplitTemp>().Where(t => t.PalletCode == palletCode).ExecuteCommand(); return content.OK("æ¹éæç确认æå"); } catch (Exception ex) { return content.Error($"æ¹éæç确认失败: {ex.Message}"); } } /// <summary> /// æ¹éç»ç确认 - 䏿¬¡æ§è°ç¨MESç»å®æ´ä¸ªæç /// </summary> /// <param name="palletCode">ç®æ æçå·</param> /// <returns>æä½ç»æ</returns> public async Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode) { WebResponseContent content = new WebResponseContent(); try { if (string.IsNullOrWhiteSpace(palletCode)) return content.Error("æçå·ä¸è½ä¸ºç©º"); // 1. æ¥è¯¢è¯¥æçä¸çææçµè¯æç» var stockInfo = StockInfoService.Repository.QueryFirst(s => s.PalletCode == palletCode); if (stockInfo == null) return content.Error("æçä¸åå¨"); var details = StockInfoDetailService.Repository.QueryData(d => d.StockId == stockInfo.Id); if (!details.Any()) return content.Error("æç䏿 çµè¯æ°æ®"); // 2. è°ç¨MESç»å® var bindRequest = new BindContainerRequest { ContainerCode = palletCode, EquipmentCode = StockConstants.MES_EQUIPMENT_CODE, ResourceCode = StockConstants.MES_RESOURCE_CODE, LocalTime = DateTime.Now, OperationType = StockConstants.MES_BIND_OPERATION_TYPE, ContainerSfcList = details.Select(d => new ContainerSfcItem { Sfc = d.SerialNumber, Location = d.InboundOrderRowNo.ToString() }).ToList() }; var bindResult = _mesService.BindContainer(bindRequest); if (bindResult == null || bindResult.Data == null || !bindResult.Data.IsSuccess) { return content.Error($"MESç»å®å¤±è´¥: {bindResult?.Data?.Msg ?? bindResult?.ErrorMessage ?? "æªç¥é误"}"); } return content.OK("æ¹éç»ç确认æå"); } catch (Exception ex) { return content.Error($"æ¹éç»ç确认失败: {ex.Message}"); } } ``` - [ ] **Step 3: Commit** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockService.cs git commit -m "feat(StockService): å®ç°SplitPalletConfirmAsyncåGroupPalletConfirmAsync Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` --- ## Task 5: ä¿®æ¹ SplitPalletAsync - æ·»å 临æ¶è¡¨å¹çåå ¥é»è¾ **Files:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockService.cs` - [ ] **Step 1: å¨ SplitPalletAsync æ¹æ³å¼å¤´æ·»å 临æ¶è¡¨åå ¥é»è¾** å¨ `SplitPalletAsync` æ¹æ³ç `try` åå¼å¤´ï¼`if (stock == null ...` ä¹åï¼ãå¨äºå¡ `ExecuteWithinTransactionAsync` è°ç¨ä¹åï¼æ·»å ï¼ ```csharp // å¹çåå ¥ï¼æ£æ¥ä¸´æ¶è¡¨æ¯å¦å·²æè¯¥æçè®°å½ï¼æ ååå ¥ var existingTemp = SqlSugarClient.Queryable<Dt_SplitTemp>() .Where(t => t.PalletCode == stock.SourcePalletNo) .First(); if (existingTemp == null) { // æ¥è¯¢è¯¥æçå½åææçµè¯ï¼åå ¥ä¸´æ¶è¡¨ var sourceStockForTemp = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.SourcePalletNo); if (sourceStockForTemp != null) { var allDetails = StockInfoDetailService.Repository.QueryData(d => d.StockId == sourceStockForTemp.Id); if (allDetails.Any()) { var sfcListJson = JsonConvert.SerializeObject(allDetails.Select(d => d.SerialNumber).ToList()); SqlSugarClient.Insertable(new Dt_SplitTemp { PalletCode = stock.SourcePalletNo, SfcList = sfcListJson, CreateTime = DateTime.Now }).ExecuteCommand(); } } } ``` 注æï¼è¿æ®µä»£ç å¨ `return await ExecuteWithinTransactionAsync(...)` ä¹åæ§è¡ï¼ä¸å¨äºå¡å ã - [ ] **Step 2: Commit** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockService.cs git commit -m "feat(SplitPalletAsync): æ·»å 临æ¶è¡¨å¹çåå ¥é»è¾ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` --- ## Task 6: ä¿®æ¹ StockController - æ·»å æ°è·¯ç± **Files:** - Modify: `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs` - [ ] **Step 1: å¨ StockController æ·»å 两个æ°è·¯ç±** å¨ `UpdateStockInfoAsync` æ¹æ³ä¹åãç±»ç»æ `}` ä¹åæ·»å ï¼ ```csharp /// <summary> /// æ¹éæç确认 - WCSæçä»»å¡å ¨é¨å宿¶è°ç¨ /// </summary> /// <param name="dto">æç确认请æ±</param> /// <returns>æä½ç»æ</returns> [HttpPost("SplitPalletConfirm"), AllowAnonymous] public async Task<WebResponseContent> SplitPalletConfirm([FromBody] SplitPalletConfirmRequestDto dto) { return await Service.SplitPalletConfirmAsync(dto.PalletCode); } /// <summary> /// æ¹éç»ç确认 - WCSç»çä»»å¡å ¨é¨æ¾å®æ¶è°ç¨ /// </summary> /// <param name="dto">ç»ç确认请æ±</param> /// <returns>æä½ç»æ</returns> [HttpPost("GroupPalletConfirm"), AllowAnonymous] public async Task<WebResponseContent> GroupPalletConfirm([FromBody] GroupPalletConfirmRequestDto dto) { return await Service.GroupPalletConfirmAsync(dto.PalletCode); } ``` 忶卿件顶鍿·»å usingï¼ ```csharp using WIDESEA_DTO.Stock; ``` - [ ] **Step 2: Commit** ```bash git add WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs git commit -m "feat(StockController): æ°å¢SplitPalletConfirmåGroupPalletConfirmæ¥å£è·¯ç± Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` --- ## Task 7: æ°æ®åºåæ´èæ¬ **Files:** - Create: `WMS/WIDESEA_WMSServer/Database/Scripts/20260416_Dt_SplitTemp.sql` - [ ] **Step 1: å建临æ¶è¡¨ DDL èæ¬** ```sql -- æç临æ¶è¡¨ï¼ç¨äºæåæçä»»å¡çµè¯å表ï¼ä¾æ¹é确认æ¶ä½¿ç¨ IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Dt_SplitTemp]') AND type in (N'U')) BEGIN CREATE TABLE [dbo].[Dt_SplitTemp]( [Id] [int] IDENTITY(1,1) NOT NULL, [PalletCode] [nvarchar](50) NOT NULL, [SfcList] [nvarchar](max) NOT NULL, [CreateTime] [datetime] NOT NULL DEFAULT GETDATE(), CONSTRAINT [PK_Dt_SplitTemp] PRIMARY KEY CLUSTERED ([Id] ASC) ); -- å¯éï¼æ·»å å¯ä¸ç´¢å¼é²æ¢å䏿çéå¤åå ¥ CREATE UNIQUE NONCLUSTERED INDEX [IX_Dt_SplitTemp_PalletCode] ON [dbo].[Dt_SplitTemp]([PalletCode] ASC); END GO ``` - [ ] **Step 2: Commit** ```bash git add WMS/WIDESEA_WMSServer/Database/Scripts/20260416_Dt_SplitTemp.sql git commit -m "feat(db): æ°å¢Dt_SplitTempæç临æ¶è¡¨ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" ``` --- ## Task 8: æå»ºéªè¯ - [ ] **Step 1: è¿è¡ dotnet build éªè¯ç¼è¯éè¿** ```bash cd D:\Git\ShanMeiXinNengYuan\Code\WMS\WIDESEA_WMSServer dotnet build WIDESEA_WMSServer.sln ``` Expected: Build succeeded with no errors. --- ## èªæ£æ¸ å - [ ] ææ public æ¹æ³åæ XML ææ¡£æ³¨é - [ ] `Dt_SplitTemp.SfcList` ä½¿ç¨ `nvarchar(max)` åå¨ JSON - [ ] `SplitPalletConfirmAsync` 读å临æ¶è¡¨åå é¤è®°å½ - [ ] `SplitPalletAsync` ä¸ç临æ¶è¡¨åå ¥å¨äºå¡å¤æ§è¡ - [ ] `GroupPalletConfirmAsync` ä» `Dt_StockInfoDetail` æ¥çµè¯ï¼ä¸æ¥ä¸´æ¶è¡¨ - [ ] ä¸¤ä¸ªæ° Controller æ¹æ³åæ è®° `[AllowAnonymous]` - [ ] æ°æ®åºèæ¬å« IF NOT EXISTS 鲿¢éå¤å建 Code/docs/superpowers/specs/2026-04-16-BatchMesBinding-Design.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,106 @@ # æ¹é MES ç»å®ä¸è§£ç»æ¥å£è®¾è®¡ ## èæ¯ å½å `StockSerivce` ä¸ `GroupPalletAsync`ã`ChangePalletAsync`ã`SplitPalletAsync` æ¯æ¬¡è°ç¨é½ä¼è§¦å䏿¬¡ MES æ¥å£ã WCS æºå¨äººä»»å¡ææ¹æ¬¡åæ¾ï¼ä¸ä¸ªæçå¯è½éè¦å¤æ¬¡ MES è°ç¨ï¼å¦æ¢çéè¦å è§£ç»åç»å®ï¼ã为åå° MES è°ç¨æ¬¡æ°ï¼æ°å¢ä¸¤ä¸ªæ¹é确认æ¥å£ä¾ WCS å¨ä»»å¡é¶æ®µå®ææ¶ä¸æ¬¡æ§ä¸ä¼ æç级å«çç»å®/è§£ç»æ°æ®ã ## æ°å¢æ¥å£ ### 1. SplitPalletConfirm â æ¹éæç确认 **è§¦åæ¶æº**ï¼WCS æçä»»å¡/æ¢çä»»å¡å ¨é¨åå® **WCS è°ç¨æ¹å¼**ï¼`POST /api/Stock/SplitPalletConfirm` **请æ±åæ°**ï¼ ```csharp public class SplitPalletConfirmRequest { /// <summary> /// æºæçå· /// </summary> public string PalletCode { get; set; } } ``` **å¤çæµç¨**ï¼ 1. WMS æ ¹æ® `PalletCode` ä»ä¸´æ¶è¡¨ `Dt_SplitTemp` 读åé¢åçµè¯å表 2. è°ç¨ MES `UnBindContainer`ï¼ä¸æ¬¡æ§ä¸ä¼ æ´æçµè¯ï¼ 3. å é¤ä¸´æ¶è¡¨ `Dt_SplitTemp` ä¸å¯¹åºè®°å½ **临æ¶è¡¨åå ¥æ¶æº**ï¼`SplitPalletAsync` æ¯æ¬¡è¢«è°ç¨æ¶ï¼å æ£æ¥ `Dt_SplitTemp` 䏿¯å¦åå¨è¯¥æçè®°å½ï¼ä¸åå¨åå°å½åæç对åºçææçµè¯æ¡ç åå ¥ä¸´æ¶è¡¨ï¼å·²åå¨åè·³è¿åå ¥ã **临æ¶è¡¨ç»æ**ï¼`Dt_SplitTemp`ï¼ï¼ | åæ®µ | ç±»å | 说æ | |------|------|------| | Id | int | ä¸»é® | | PalletCode | string | æçå· | | SfcList | string | çµè¯æ¡ç å表ï¼JSONæ°ç»ï¼ | | CreateTime | DateTime | å建æ¶é´ | --- ### 2. GroupPalletConfirm â æ¹éç»ç确认 **è§¦åæ¶æº**ï¼WCS ç»çä»»å¡/æ¢çä»»å¡å ¨é¨æ¾å® **WCS è°ç¨æ¹å¼**ï¼`POST /api/Stock/GroupPalletConfirm` **请æ±åæ°**ï¼ ```csharp public class GroupPalletConfirmRequest { /// <summary> /// ç®æ æçå· /// </summary> public string PalletCode { get; set; } } ``` **å¤çæµç¨**ï¼ 1. WMS æ ¹æ® `PalletCode` æ¥è¯¢ `Dt_StockInfoDetail` ä¸è¯¥æçä¸çææçµè¯æç» 2. è°ç¨ MES `BindContainer`ï¼ä¸æ¬¡æ§ä¸ä¼ æ´æçµè¯ç»å®ï¼ 3. è¿åç»æ **注æ**ï¼çµè¯æ°æ®å¨ç»ç任塿¾è´§è¿ç¨ä¸å·²ç± WCS éè¿å ¶ä»æ¥å£åå ¥ `Dt_StockInfoDetail`ï¼WMS ä¸éè¦é¢å¤åå¨ --- ## æ¢çä»»å¡å®æ´æµç¨ ``` æ¢çä»»å¡ï¼ å ¨é¨åå® â SplitPalletConfirm(æºæç) â MES UnBindContainer å ¨é¨æ¾å® â GroupPalletConfirm(ç®æ æç) â MES BindContainer ``` ## ç°ææ¥å£å¤ç - `GroupPalletAsync`ã`ChangePalletAsync`ã`SplitPalletAsync` ä¿ç - æ°æ¥å£ä¸ç°ææ¥å£å¹¶åï¼WCS æ ¹æ®ä»»å¡åºæ¯éæ©è°ç¨ - ç°ææ¥å£ç»§ç»æ¿æ 忬¡/éæ¹éåºæ¯ç MES è°ç¨ ## WCS ä¾§æ¹é è¦ç¹ - æç/æ¢çä»»å¡å¼å§æ¶ï¼WCS è°ç¨ç°æ `SplitPalletAsync` æ¥å£ï¼WMS å¨ `SplitPalletAsync` å é¨å æ£æ¥ `Dt_SplitTemp` æ¯å¦å·²æè¯¥æçè®°å½ï¼æ ååå ¥ï¼æåè·³è¿ï¼å¹çåå ¥ï¼ - ç»çä»»å¡å ¨é¨æ¾å®æ¶è°ç¨ `GroupPalletConfirm` - æ¢çä»»å¡å ¨é¨å宿¶è°ç¨ `SplitPalletConfirm`ï¼å ¨é¨æ¾å®æ¶è°ç¨ `GroupPalletConfirm` ## æä»¶åæ´ | æä½ | æä»¶ | |------|------| | æ°å¢ | `WIDESEA_DTO/Stock/SplitPalletConfirmRequestDto.cs` | | æ°å¢ | `WIDESEA_DTO/Stock/GroupPalletConfirmRequestDto.cs` | | æ°å¢ | `WIDESEA_Model/Models/Dt_SplitTemp.cs` | | ä¿®æ¹ | `WIDESEA_IStockService/IStockService.cs`ï¼æ°å¢æ¥å£å®ä¹ï¼ | | ä¿®æ¹ | `WIDESEA_StockService/StockService.cs`ï¼å®ç°æ¹é确认é»è¾ï¼ | | ä¿®æ¹ | `WIDESEA_WMSServer/Controllers/Stock/StockInfoDetailController.cs`ï¼æ°å¢ API è·¯ç±ï¼ | | ä¿®æ¹ | æ°æ®åºï¼æ°å¢ `Dt_SplitTemp` 表 | ## é£é©ä¸çº¦æ - 临æ¶è¡¨ `Dt_SplitTemp` éè¦ææ¸ çæºå¶ï¼é²æ¢å¼å¸¸æ åµä¸æ°æ®æ®ç - MES æ¥å£è°ç¨å¤±è´¥æ¶ï¼ä¸´æ¶è¡¨æ°æ®ä¸åæ»ï¼ä¸æ¬¡éè¯æ¶å¯è½éå¤è§£ç»ï¼é MES ä¾§å¹çæ¯æ