# 批量 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
{
///
/// 拆盘临时表 - 用于暂存拆盘任务电芯列表,供批量确认时使用
///
[SugarTable(nameof(Dt_SplitTemp), "拆盘临时表")]
public class Dt_SplitTemp
{
///
/// 主键
///
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键")]
public int Id { get; set; }
///
/// 托盘号
///
[SugarColumn(IsNullable = false, Length = 50, ColumnDescription = "托盘号")]
public string PalletCode { get; set; }
///
/// 电芯条码列表(JSON格式)
///
[SugarColumn(IsNullable = false, Length = -1, ColumnDescription = "电芯条码列表JSON")]
public string SfcList { get; set; }
///
/// 创建时间
///
[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 "
```
---
## 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
{
///
/// 批量拆盘确认请求DTO
///
public class SplitPalletConfirmRequestDto
{
///
/// 源托盘号
///
public string PalletCode { get; set; }
}
}
```
- [ ] **Step 2: 创建 GroupPalletConfirmRequestDto**
```csharp
namespace WIDESEA_DTO.Stock
{
///
/// 批量组盘确认请求DTO
///
public class GroupPalletConfirmRequestDto
{
///
/// 目标托盘号
///
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 "
```
---
## Task 3: 修改 IStockService 接口
**Files:**
- Modify: `WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs`(在接口末尾添加两个新方法)
- [ ] **Step 1: 在 IStockService 接口添加两个新方法声明**
在 `UpdateStockInfoAsync` 方法声明之后、接口结束 `}` 之前添加:
```csharp
///
/// 批量拆盘确认 - 一次性调用MES解绑整个托盘
///
/// 源托盘号
/// 操作结果
Task SplitPalletConfirmAsync(string palletCode);
///
/// 批量组盘确认 - 一次性调用MES绑定整个托盘
///
/// 目标托盘号
/// 操作结果
Task 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 "
```
---
## 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
///
/// SqlSugar客户端(用于临时表操作)
///
public ISqlSugarClient SqlSugarClient { get; }
```
构造函数中注入:
```csharp
public StockService(
...,
ISqlSugarClient sqlSugarClient) // 添加到参数末尾
{
...
SqlSugarClient = sqlSugarClient;
}
```
- [ ] **Step 2: 实现 SplitPalletConfirmAsync 方法**
在类末尾(`UpdateStockInfoAsync` 方法之后、`CreateDetailHistory` 之前)添加:
```csharp
///
/// 批量拆盘确认 - 一次性调用MES解绑整个托盘
///
/// 源托盘号
/// 操作结果
public async Task SplitPalletConfirmAsync(string palletCode)
{
WebResponseContent content = new WebResponseContent();
try
{
if (string.IsNullOrWhiteSpace(palletCode))
return content.Error("托盘号不能为空");
// 1. 从临时表读取电芯列表
var tempRecord = SqlSugarClient.Queryable()
.Where(t => t.PalletCode == palletCode)
.First();
if (tempRecord == null)
return content.Error("未找到拆盘临时记录,请先执行拆盘操作");
var sfcList = JsonConvert.DeserializeObject>(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().Where(t => t.PalletCode == palletCode).ExecuteCommand();
return content.OK("批量拆盘确认成功");
}
catch (Exception ex)
{
return content.Error($"批量拆盘确认失败: {ex.Message}");
}
}
///
/// 批量组盘确认 - 一次性调用MES绑定整个托盘
///
/// 目标托盘号
/// 操作结果
public async Task 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 "
```
---
## 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()
.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 "
```
---
## Task 6: 修改 StockController - 添加新路由
**Files:**
- Modify: `WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs`
- [ ] **Step 1: 在 StockController 添加两个新路由**
在 `UpdateStockInfoAsync` 方法之后、类结束 `}` 之前添加:
```csharp
///
/// 批量拆盘确认 - WCS拆盘任务全部取完时调用
///
/// 拆盘确认请求
/// 操作结果
[HttpPost("SplitPalletConfirm"), AllowAnonymous]
public async Task SplitPalletConfirm([FromBody] SplitPalletConfirmRequestDto dto)
{
return await Service.SplitPalletConfirmAsync(dto.PalletCode);
}
///
/// 批量组盘确认 - WCS组盘任务全部放完时调用
///
/// 组盘确认请求
/// 操作结果
[HttpPost("GroupPalletConfirm"), AllowAnonymous]
public async Task 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 "
```
---
## 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 "
```
---
## 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 防止重复创建