feat: 完成客户第12条需求+多系统改造

1. 新增预警表添加工单号字段,支持按工单号检索预警
2. 添加工单过滤在制品/绩效报表/预警中心查询
3. 补全WMS台账字段口径与权限配置
4. 修复备料单与数量不符处理流程
5. 新增配送进度/库位联动/自动出库功能
6. 修复AGV配送状态更新逻辑
7. 优化退料与库存管理细节
This commit is contained in:
SunYF
2026-09-19 17:04:58 +08:00
parent 62e45b7379
commit 05b0b8a909
89 changed files with 7912 additions and 429 deletions
+142
View File
@@ -430,6 +430,148 @@ func (c *Client) Backflush(ctx context.Context, orderNo string, stationNo, proce
})
}
// StockBatch WMS 可用批次(结构件)
type StockBatch struct {
BatchNo string `json:"batchNo"`
Quantity int `json:"quantity"`
LockedQty int `json:"lockedQty"`
Avail int `json:"avail"`
ZoneCode string `json:"zoneCode"`
QualityStatus string `json:"qualityStatus"`
}
// StockBatches 调 WMS /api/internal/stock/batches 取该物料可出库的批次(FIFO 升序)与可用 SN 列表。
// 供「工单备料自动出库」分配用;WMS 只返回「合格」且在库的库存。
func (c *Client) StockBatches(ctx context.Context, materialCode string) ([]StockBatch, []string, error) {
u := c.baseURL + "/api/internal/stock/batches?materialCode=" + url.QueryEscape(materialCode)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("X-API-TOKEN", c.token)
resp, err := c.hc.Do(req)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
var body struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
Batches []StockBatch `json:"batches"`
SnList []string `json:"snList"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return nil, nil, err
}
if resp.StatusCode >= 300 || body.Code != 0 {
return nil, nil, errors.New("查询 WMS 可用库存失败: " + body.Message)
}
return body.Data.Batches, body.Data.SnList, nil
}
// StockDeductOpts 备料出库可选参数(一个字段一个语义,避免位置参数越加越长)。
type StockDeductOpts struct {
BatchNo string // 批次出库(结构件),与 SnList 二选一
SnList []string // SN 出库(电气件),与 BatchNo 二选一
Qty int // 出库数量
Operator string
TargetDock string
ZoneCode string
OutboundType string // workorder(备料,默认) / refill(补料)
NeedAgv bool // 是否叫 AGV 配送到接驳台
OverReason string // 超出 BOM 需求时的超领原因(损耗/报废/返修/其他);空=超领将被 WMS 拦截
}
// StockDeduct 调 WMS /api/internal/stock/deduct 执行一笔备料出库(批次或 SN 二选一)。
// 返回该笔出库单号与工单台账状态(等于「领料完结」表示该物料已领完)。
// 失败返回 err(含 WMS 业务错误文案,由调用方降级为保持待备料并预警)。
func (c *Client) StockDeduct(ctx context.Context, orderNo, materialCode string, o StockDeductOpts) (string, string, error) {
payload := map[string]any{
"orderNo": orderNo, "materialCode": materialCode,
"qty": o.Qty, "operator": o.Operator,
"targetDock": o.TargetDock, "zoneCode": o.ZoneCode,
}
if o.BatchNo != "" {
payload["batchNo"] = o.BatchNo
}
if len(o.SnList) > 0 {
payload["snList"] = o.SnList
}
if o.OutboundType != "" {
payload["outboundType"] = o.OutboundType
}
if o.NeedAgv {
payload["needAgv"] = true
}
if o.OverReason != "" {
payload["overReason"] = o.OverReason
}
b, err := json.Marshal(payload)
if err != nil {
return "", "", err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/stock/deduct", bytes.NewReader(b))
if err != nil {
return "", "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-TOKEN", c.token)
resp, err := c.hc.Do(req)
if err != nil {
return "", "", err
}
defer resp.Body.Close()
var body struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
OutboundNo string `json:"outboundNo"`
Status string `json:"status"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return "", "", err
}
if resp.StatusCode >= 300 || body.Code != 0 {
return "", "", errors.New(body.Message)
}
return body.Data.OutboundNo, body.Data.Status, nil
}
// LedgerProgressRow 工单台账配送进度行(WMS 侧四段量)。
// 在途 / 线边由 MES 用本地已接料量派生(在途 = outQty receivedQty;线边 = receivedQty consumedQty),
// WMS 不猜 MES 的接料量,避免跨系统重复记账。
type LedgerProgressRow struct {
OrderNo string `json:"orderNo"`
MaterialCode string `json:"materialCode"`
MaterialName string `json:"materialName"`
TargetStation string `json:"targetStation"`
TotalQty int `json:"totalQty"`
OutQty int `json:"outQty"`
ReturnedQty int `json:"returnedQty"`
ConsumedQty int `json:"consumedQty"`
NetOut int `json:"netOut"`
Gap int `json:"gap"`
Status string `json:"status"`
CompletedAt int64 `json:"completedAt"`
}
// LedgerProgress 拉取 WMS 工单物料台账配送进度(只读聚合,WMS 是台账唯一真源)。
func (c *Client) LedgerProgress(ctx context.Context, orderNo, materialCode string, page, pageSize int) ([]LedgerProgressRow, int, error) {
u := fmt.Sprintf("/api/internal/ledger/progress?orderNo=%s&materialCode=%s&page=%d&pageSize=%d",
url.QueryEscape(orderNo), url.QueryEscape(materialCode), page, pageSize)
var out struct {
Total int `json:"total"`
List []LedgerProgressRow `json:"list"`
}
if err := c.get(ctx, u, &out); err != nil {
return nil, 0, err
}
return out.List, out.Total, nil
}
func (c *Client) post(ctx context.Context, path string, body any) error {
b, err := json.Marshal(body)
if err != nil {