package wmsstock import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" ) // Client WMS 内部 API 客户端(X-API-TOKEN 写死 token 保护) type Client struct { BaseURL string Token string HTTP *http.Client } func New(baseURL, token string) *Client { return &Client{ BaseURL: baseURL, Token: token, HTTP: &http.Client{Timeout: 15 * time.Second}, } } // wmsResp WMS 统一响应 {code,message,data} type wmsResp struct { Code int `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data"` } func (c *Client) call(method, path string, body any) (json.RawMessage, error) { var rd io.Reader if body != nil { b, err := json.Marshal(body) if err != nil { return nil, err } rd = bytes.NewReader(b) } req, err := http.NewRequest(method, c.BaseURL+path, rd) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-API-TOKEN", c.Token) resp, err := c.HTTP.Do(req) if err != nil { return nil, fmt.Errorf("WMS 不可达(%s): %w", path, err) } defer resp.Body.Close() raw, _ := io.ReadAll(resp.Body) var wr wmsResp if err := json.Unmarshal(raw, &wr); err != nil { return nil, fmt.Errorf("WMS 响应解析失败(%s): %s", path, truncate(string(raw), 200)) } if wr.Code != 0 { return nil, fmt.Errorf("WMS 返回错误(%s): %s", path, wr.Message) } return wr.Data, nil } func truncate(s string, n int) string { if len(s) > n { return s[:n] } return s } // ============ 数据结构(与 C 的内部接口契约对齐)============ type CheckItem struct { MaterialCode string `json:"materialCode"` Qty int `json:"qty"` } type LockItem struct { MaterialCode string `json:"materialCode"` BatchNo string `json:"batchNo,omitempty"` Sn string `json:"sn,omitempty"` Qty int `json:"qty"` } type LedgerItem struct { MaterialCode string `json:"materialCode"` MaterialName string `json:"materialName"` TotalQty int `json:"totalQty"` } // ============ 业务方法 ============ // CheckStock 批量校验库存是否充足 func (c *Client) CheckStock(items []CheckItem) (json.RawMessage, error) { return c.call(http.MethodPost, "/api/internal/stock/check", map[string]any{"items": items}) } // LockStock 锁定库存(结构件锁批次量 / 精密件逐 SN) func (c *Client) LockStock(orderNo string, items []LockItem) (json.RawMessage, error) { return c.call(http.MethodPost, "/api/internal/stock/lock", map[string]any{"orderNo": orderNo, "items": items}) } // UnlockStock 解锁库存 func (c *Client) UnlockStock(orderNo string, lockIds []int) (json.RawMessage, error) { return c.call(http.MethodPost, "/api/internal/stock/unlock", map[string]any{"orderNo": orderNo, "lockIds": lockIds}) } // Deduct 出库扣减(领料出库,累计≤台账需求) func (c *Client) Deduct(req map[string]any) (json.RawMessage, error) { return c.call(http.MethodPost, "/api/internal/stock/deduct", req) } // QueryStock 库存查询(FIFO 分配用) func (c *Client) QueryStock(materialCode, zoneCode string) (json.RawMessage, error) { return c.call(http.MethodGet, "/api/internal/stock/query?materialCode="+materialCode+"&zoneCode="+zoneCode+"&pageSize=500", nil) } // LedgerSync 同步工单 BOM 台账到 WMS func (c *Client) LedgerSync(orderNo, productCode string, items []LedgerItem) (json.RawMessage, error) { return c.call(http.MethodPost, "/api/internal/ledger/sync", map[string]any{ "orderNo": orderNo, "productCode": productCode, "items": items, }) } // SemiInbound 半成品入库暂存 func (c *Client) SemiInbound(sn, materialCode, completedProcess, zoneCode, orderNo, operator string) (json.RawMessage, error) { return c.call(http.MethodPost, "/api/internal/semi/inbound", map[string]any{ "sn": sn, "materialCode": materialCode, "completedProcess": completedProcess, "zoneCode": zoneCode, "orderNo": orderNo, "operator": operator, }) } // SemiOutbound 半成品出库重上线 func (c *Client) SemiOutbound(sn, zoneCode, operator string) (json.RawMessage, error) { return c.call(http.MethodPost, "/api/internal/semi/outbound", map[string]any{ "sn": sn, "zoneCode": zoneCode, "operator": operator, }) } // DisplayOverview 库存总览(大屏用) func (c *Client) DisplayOverview() (json.RawMessage, error) { return c.call(http.MethodGet, "/api/internal/display/overview", nil) }