wanshenmean
10 小时以前 406a7dd7ed6d1215b739a80453e2bbde0f8af643
docs: add MES upload status tracking design spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
已添加1个文件
288 ■■■■■ 文件已修改
Code/docs/superpowers/specs/2026-04-20-MES上传状态与异步上传设计.md 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/docs/superpowers/specs/2026-04-20-MESÉÏ´«×´Ì¬ÓëÒì²½ÉÏ´«Éè¼Æ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,288 @@
# MES ä¸Šä¼ çŠ¶æ€è·Ÿè¸ªä¸Žå¼‚æ­¥ä¸Šä¼ è®¾è®¡
> **Date:** 2026-04-20
> **Author:** Claude
## 1. ç›®æ ‡
在库存表添加 MES ä¸Šä¼ çŠ¶æ€å­—æ®µï¼Œé€šè¿‡å¼‚æ­¥æ–¹å¼ä¸Šä¼  MES æ•°æ®ï¼Œä¸å¹²æ‰°ä¸»ä¸šåŠ¡é€»è¾‘ï¼Œæ‰€æœ‰ MES è°ƒç”¨å‡è®°å½•详细日志。
## 2. æ•°æ®åº“变更
### 2.1 Dt_StockInfo è¡¨æ–°å¢žå­—段
```sql
ALTER TABLE [dbo].[Dt_StockInfo] ADD [MesUploadStatus] TINYINT NOT NULL DEFAULT 0;
GO
```
### 2.2 æžšä¸¾å®šä¹‰
| å€¼ | å«ä¹‰ |
|----|------|
| 0 | æœªä¸Šä¼ ï¼ˆä»Žæœªè°ƒç”¨è¿‡MES) |
| 1 | ç»„盘上传成功 |
| 2 | ç»„盘上传失败 |
| 3 | æ‹†ç›˜ä¸Šä¼ æˆåŠŸ |
| 4 | æ‹†ç›˜ä¸Šä¼ å¤±è´¥ |
| 5 | è¿›ç«™ä¸Šä¼ æˆåŠŸ |
| 6 | è¿›ç«™ä¸Šä¼ å¤±è´¥ |
| 7 | å‡ºç«™ä¸Šä¼ æˆåŠŸ |
| 8 | å‡ºç«™ä¸Šä¼ å¤±è´¥ |
| 9 | NG上报成功 |
| 10 | NG上报失败 |
在 `WIDESEA_Common` é¡¹ç›®æ–°å¢žæžšä¸¾ç±» `MesUploadStatusEnum`。
## 3. åŽç«¯è®¾è®¡
### 3.1 æ ¸å¿ƒå¼‚步方法
在 `WIDESEA_TaskInfoService/TaskService.cs` ä¸­æ–°å¢ž `MesUploadAsync` ç§æœ‰å¼‚步方法:
```csharp
/// <summary>
/// å¼‚步执行MES上传 - ä¸é˜»å¡žä¸»ä¸šåŠ¡é€»è¾‘
/// </summary>
/// <param name="palletCode">托盘号</param>
/// <param name="mesType">MES操作类型枚举</param>
/// <param name="uploadFunc">具体的MES调用函数</param>
private async Task MesUploadAsync(string palletCode, MesUploadStatusEnum mesType, Func<Task<HttpResponseResult<MesResponse>>> uploadFunc)
{
    var stopwatch = Stopwatch.StartNew();
    string requestJson = "";
    string responseJson = "";
    bool isSuccess = false;
    string errorMessage = "";
    try
    {
        // è®°å½•请求日志
        var mesLog = new MesApiLogDto
        {
            PalletCode = palletCode,
            ApiType = mesType.ToString(),
            RequestTime = DateTime.Now
        };
        // è°ƒç”¨MES
        var result = await uploadFunc();
        stopwatch.Stop();
        isSuccess = result?.Data?.IsSuccess ?? false;
        errorMessage = result?.Data?.Msg ?? result?.ErrorMessage ?? "未知错误";
        responseJson = JsonConvert.SerializeObject(result);
        // æ›´æ–°åº“存表状态
        var uploadStatus = isSuccess ? (int)mesType : (int)mesType + 1; // å¥‡æ•°=成功,偶数=失败
        await _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus);
        // è®°å½•日志
        mesLog.ResponseTime = DateTime.Now;
        mesLog.Duration = stopwatch.ElapsedMilliseconds;
        mesLog.RequestParams = requestJson;
        mesLog.ResponseResult = responseJson;
        mesLog.Status = isSuccess ? "成功" : "失败";
        mesLog.ErrorMessage = errorMessage;
        mesLog.CreateTime = DateTime.Now;
        _ = _mesLogService.LogAsync(mesLog); // ä¸ç­‰å¾…
    }
    catch (Exception ex)
    {
        stopwatch.Stop();
        errorMessage = ex.Message;
        // æ›´æ–°çŠ¶æ€ä¸ºå¤±è´¥
        var uploadStatus = (int)mesType + 1;
        _stockInfoService.UpdateMesUploadStatusAsync(palletCode, uploadStatus).ConfigureAwait(false);
        // è®°å½•异常日志
        var mesLog = new MesApiLogDto
        {
            PalletCode = palletCode,
            ApiType = mesType.ToString(),
            RequestTime = DateTime.Now,
            ResponseTime = DateTime.Now,
            Duration = stopwatch.ElapsedMilliseconds,
            RequestParams = requestJson,
            ResponseResult = responseJson,
            Status = "失败",
            ErrorMessage = errorMessage,
            CreateTime = DateTime.Now
        };
        _ = _mesLogService.LogAsync(mesLog); // ä¸ç­‰å¾…
    }
}
```
### 3.2 è°ƒç”¨æ–¹å¼
在各个 MES æ“ä½œå¤„统一使用 `Task.Run` + `MesUploadAsync`:
```csharp
// ç»„盘确认
public async Task<WebResponseContent> GroupPalletConfirmAsync(string palletCode, string deviceName)
{
    // ... ä¸šåŠ¡é€»è¾‘ ...
    // å¼‚æ­¥MES上传,不阻塞主逻辑
    _ = Task.Run(() => MesUploadAsync(palletCode, MesUploadStatusEnum.GroupPalletSuccess, async () =>
    {
        return await _mesService.BindContainerAsync(bindRequest, token);
    }));
    return content.OK("组盘成功");
}
// æ‹†ç›˜ç¡®è®¤
public async Task<WebResponseContent> SplitPalletConfirmAsync(string palletCode, string deviceName)
{
    // ... ä¸šåŠ¡é€»è¾‘ ...
    _ = Task.Run(() => MesUploadAsync(palletCode, MesUploadStatusEnum.SplitPalletSuccess, async () =>
    {
        return await _mesService.UnBindContainerAsync(unbindRequest, token);
    }));
    return content.OK("拆盘成功");
}
```
### 3.3 IStockInfoService æ–°å¢žæ–¹æ³•
在 `IStockInfoService` æŽ¥å£æ–°å¢žï¼š
```csharp
/// <summary>
/// æ›´æ–°MES上传状态
/// </summary>
/// <param name="palletCode">托盘号</param>
/// <param name="status">状态值</param>
Task<bool> UpdateMesUploadStatusAsync(string palletCode, int status);
```
在 `StockInfoService` ä¸­å®žçŽ°è¯¥æ–¹æ³•ã€‚
### 3.4 æ—¥å¿—记录规范
所有 MES è°ƒç”¨ç»Ÿä¸€è®°å½•以下字段:
| å­—段 | è¯´æ˜Ž |
|------|------|
| PalletCode | æ‰˜ç›˜å· |
| ApiType | æŽ¥å£ç±»åž‹ï¼ˆç»„盘/拆盘/进站/出站/NG上报) |
| RequestTime | è¯·æ±‚æ—¶é—´ |
| ResponseTime | å“åº”æ—¶é—´ |
| Duration | è€—æ—¶(ms) |
| RequestParams | è¯·æ±‚JSON |
| ResponseResult | å“åº”JSON |
| Status | æˆåŠŸ/失败 |
| ErrorMessage | å¤±è´¥åŽŸå›  |
## 4. å‰ç«¯å˜æ›´
### 4.1 åº“存页面操作列新增按钮
在 `WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx` ä¸­æ‰©å±•操作列:
```jsx
let extension = {
  components: {
    gridHeader: "",
    gridBody: "",
    gridFooter: "",
    modelHeader: "",
    modelBody: "",
    modelFooter: "",
  },
  tableAction: "stock",
  buttons: {
    view: ["Export"],
    box: []
  },
  methods: {
    onInit() {
      return true;
    },
    onInited() {
      // æ³¨å…¥ç»„盘/拆盘按钮
      this.editTableButtons = [
        { name: "组盘", onClick: this.onGroupPallet },
        { name: "拆盘", onClick: this.onSplitPallet }
      ];
      return true;
    },
    async onGroupPallet({ row }) {
      // è°ƒç”¨ç»„盘接口
      let result = await this.$api.post("/Stock/GroupPalletConfirm", { palletCode: row.palletCode });
      if (result.status) {
        this.$Message.success("组盘成功");
        this.$refs.grid.search();
      } else {
        this.$Message.error(result.message || "组盘失败");
      }
    },
    async onSplitPallet({ row }) {
      // è°ƒç”¨æ‹†ç›˜æŽ¥å£
      let result = await this.$api.post("/Stock/SplitPalletConfirm", { palletCode: row.palletCode });
      if (result.status) {
        this.$Message.success("拆盘成功");
        this.$refs.grid.search();
      } else {
        this.$Message.error(result.message || "拆盘失败");
      }
    }
  },
};
export default extension;
```
### 4.2 åº“存列表新增状态列
在 `WMS/WIDESEA_WMSClient/src/views/stock/stock.vue` çš„ `columns` ä¸­æ–°å¢žï¼š
```vue
{ field: "mesUploadStatus", title: "MES状态", type: "int", width: 120, bind: { key: "mesUploadStatusEnum", data: [] } }
```
### 4.3 å­—典配置
在 `mesUploadStatusEnum` å­—典中添加:
| value | label |
|-------|-------|
| 0 | æœªä¸Šä¼  |
| 1 | ç»„盘成功 |
| 2 | ç»„盘失败 |
| 3 | æ‹†ç›˜æˆåŠŸ |
| 4 | æ‹†ç›˜å¤±è´¥ |
| 5 | è¿›ç«™æˆåŠŸ |
| 6 | è¿›ç«™å¤±è´¥ |
| 7 | å‡ºç«™æˆåŠŸ |
| 8 | å‡ºç«™å¤±è´¥ |
| 9 | NG上报成功 |
| 10 | NG上报失败 |
## 5. æ–‡ä»¶å˜æ›´æ¸…单
| æ“ä½œ | æ–‡ä»¶ |
|------|------|
| æ–°å¢ž | `WMS/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/MesUploadStatusEnum.cs` |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfo.cs` - æ–°å¢ž MesUploadStatus å­—段 |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockInfoService.cs` - æ–°å¢ž UpdateMesUploadStatusAsync |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs` - å®žçް UpdateMesUploadStatusAsync |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs` - æ–°å¢ž MesUploadAsync + å„处调用 |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSClient/src/extension/stock/stock.jsx` - æ–°å¢žç»„盘/拆盘按钮 |
| ä¿®æ”¹ | `WMS/WIDESEA_WMSClient/src/views/stock/stock.vue` - æ–°å¢žMES状态列 |
| æ–°å¢ž | æ•°æ®åº“变更脚本 |
## 6. è‡ªæ£€æ¸…单
- [ ] `MesUploadStatusEnum` æžšä¸¾å€¼å¥‡æ•°ä¸ºæˆåŠŸï¼Œå¶æ•°ä¸ºå¤±è´¥ï¼Œ+1运算正确
- [ ] `MesUploadAsync` æ–¹æ³•内所有 MES è°ƒç”¨è®°å½•详细日志(请求JSON、响应JSON、耗时、错误原因)
- [ ] ç»„盘/拆盘/进站/出站/NG上报均通过 `Task.Run` å¼‚步执行,不阻塞主逻辑
- [ ] å‰ç«¯ç»„盘/拆盘按钮调用 `GroupPalletConfirmAsync` / `SplitPalletConfirmAsync`
- [ ] åº“存列表正确显示 `mesUploadStatus` å­—段
- [ ] æ‰€æœ‰ public æ–¹æ³•均有 XML æ–‡æ¡£æ³¨é‡Š