// Package mesclient MES 内部接口客户端(备料单拉取/状态回写,AGV 配送用)。 package mesclient import ( "bytes" "context" "encoding/json" "errors" "fmt" "net/http" "strconv" "time" ) // Client MES 内部接口客户端 type Client struct { baseURL string token string hc *http.Client } func New(baseURL, token string) *Client { if baseURL == "" { baseURL = "http://127.0.0.1:8888" } return &Client{ baseURL: baseURL, token: token, hc: &http.Client{Timeout: 6 * time.Second}, } } // MaterialRequest MES 备料单行 type MaterialRequest struct { ID int `json:"id"` RequestNo string `json:"requestNo"` OrderNo string `json:"orderNo"` PlanDate string `json:"planDate"` MaterialCode string `json:"materialCode"` MaterialName string `json:"materialName"` Unit string `json:"unit"` ManageMode string `json:"manageMode"` ReqQty float64 `json:"reqQty"` Status string `json:"status"` TargetDock string `json:"targetDock"` Operator string `json:"operator"` } // ListMaterialRequests 拉取备料单(含 target_dock) // status 空=全部;传 PENDING 可只取待发料。 func (c *Client) ListMaterialRequests(ctx context.Context, status string) ([]MaterialRequest, error) { u := c.baseURL + "/api/internal/material-requests" q := "" if status != "" { q = "?status=" + status } req, err := http.NewRequestWithContext(ctx, http.MethodGet, u+q, nil) if err != nil { return nil, err } req.Header.Set("X-API-TOKEN", c.token) resp, err := c.hc.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var env struct { Code int `json:"code"` Data []MaterialRequest `json:"data"` Msg string `json:"message"` } dec := json.NewDecoder(resp.Body) // MES 的 data 可能是 [object] 数组 if err := dec.Decode(&env); err != nil { return nil, err } if env.Data == nil { return []MaterialRequest{}, nil } return env.Data, nil } // MaterialDemandRow MES 排产物料需求行(按物料汇总,未来 N 天) // WMS「缺料补货」视图拉此数据,对比本地可用库存得出排产缺料量。 type MaterialDemandRow struct { MaterialCode string `json:"materialCode"` MaterialName string `json:"materialName"` Unit string `json:"unit"` DemandQty int `json:"demandQty"` // 未来 N 天排产总需求(台数×单台用量,向上取整) StartDate string `json:"startDate"` // 需求窗口起(YYYY-MM-DD) EndDate string `json:"endDate"` // 需求窗口止(YYYY-MM-DD) } // FetchMaterialDemand 实时向 MES 拉取「排产物料需求量」(供 WMS 缺料补货视图)。 // 两系统之间不做定时/批量同步:WMS 打开页面时按需拉一次,MES 现算现返。 // days 不传默认 5(与历史未来5天口径一致);MES 内部 /api/internal/material-demand 支持 ?days 覆盖。 func (c *Client) FetchMaterialDemand(ctx context.Context, days int) ([]MaterialDemandRow, error) { u := c.baseURL + "/api/internal/material-demand" if days > 0 { u += "?days=" + strconv.Itoa(days) } req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return nil, err } req.Header.Set("X-API-TOKEN", c.token) resp, err := c.hc.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var env struct { Code int `json:"code"` Data []MaterialDemandRow `json:"data"` Msg string `json:"message"` } if err := json.NewDecoder(resp.Body).Decode(&env); err != nil { return nil, err } if env.Code != 0 { msg := env.Msg if msg == "" { msg = "MES 返回失败" } return nil, errors.New(msg) } if env.Data == nil { return []MaterialDemandRow{}, nil } return env.Data, nil } // DockRow MES 接驳台主数据行(dock 真源已迁 MES,WMS 仅只读取用)。 type DockRow struct { ID int64 `json:"id"` DockCode string `json:"dockCode"` Name string `json:"name"` DockType string `json:"dockType"` StationNo int `json:"stationNo"` Status string `json:"status"` HasPallet bool `json:"hasPallet"` PalletRef string `json:"palletRef"` } // ListDocks 拉取 MES 接驳台主数据(dock 真源在 MES)。 // WMS 出库目标工位下拉 / AGV 页接驳台列表均读此接口,仓库不再本地维护 dock 表。 func (c *Client) ListDocks(ctx context.Context) ([]DockRow, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/internal/docks", nil) if err != nil { return nil, err } req.Header.Set("X-API-TOKEN", c.token) resp, err := c.hc.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var env struct { Code int `json:"code"` Data struct { List []DockRow `json:"list"` } `json:"data"` Msg string `json:"message"` } if err := json.NewDecoder(resp.Body).Decode(&env); err != nil { return nil, err } if env.Code != 0 { msg := env.Msg if msg == "" { msg = "MES 返回失败" } return nil, errors.New(msg) } if env.Data.List == nil { return []DockRow{}, nil } return env.Data.List, nil } // MarkStatus 回写备料单状态(DELIVERING / DONE) func (c *Client) MarkStatus(ctx context.Context, requestNo, status string) error { b, _ := json.Marshal(map[string]string{"requestNo": requestNo, "status": status}) req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/material-request/status", 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() if resp.StatusCode >= 300 { return &RespError{Status: resp.StatusCode} } return nil } type RespError struct { Status int } func (e *RespError) Error() string { return "mes call failed with status " + fmt.Sprintf("%d", e.Status) }