编辑 | blame | 历史 | 原始文档

批量 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 实体
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
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
namespace WIDESEA_DTO.Stock
{
    /// <summary>
    /// 批量拆盘确认请求DTO
    /// </summary>
    public class SplitPalletConfirmRequestDto
    {
        /// <summary>
        /// 源托盘号
        /// </summary>
        public string PalletCode { get; set; }
    }
}
  • [ ] Step 2: 创建 GroupPalletConfirmRequestDto
namespace WIDESEA_DTO.Stock
{
    /// <summary>
    /// 批量组盘确认请求DTO
    /// </summary>
    public class GroupPalletConfirmRequestDto
    {
        /// <summary>
        /// 目标托盘号
        /// </summary>
        public string PalletCode { get; set; }
    }
}
  • [ ] Step 3: Commit
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 方法声明之后、接口结束 } 之前添加:

/// <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
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 类中添加:

using SqlSugar;
using WIDESEA_Model.Models;
using Newtonsoft.Json;

在类中添加属性:

/// <summary>
/// SqlSugar客户端(用于临时表操作)
/// </summary>
public ISqlSugarClient SqlSugarClient { get; }

构造函数中注入:

public StockService(
    ...,
    ISqlSugarClient sqlSugarClient)  // 添加到参数末尾
{
    ...
    SqlSugarClient = sqlSugarClient;
}
  • [ ] Step 2: 实现 SplitPalletConfirmAsync 方法

在类末尾(UpdateStockInfoAsync 方法之后、CreateDetailHistory 之前)添加:

/// <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
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 调用之前,添加:

// 幂等写入:检查临时表是否已有该托盘记录,无则写入
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
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 方法之后、类结束 } 之前添加:

/// <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:

using WIDESEA_DTO.Stock;
  • [ ] Step 2: Commit
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 脚本
-- 拆盘临时表:用于暂存拆盘任务电芯列表,供批量确认时使用
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
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 验证编译通过
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 中的临时表写入在事务外执行
  • [ ] GroupPalletConfirmAsyncDt_StockInfoDetail 查电芯,不查临时表
  • [ ] 两个新 Controller 方法均标记 [AllowAnonymous]
  • [ ] 数据库脚本含 IF NOT EXISTS 防止重复创建