// Package mesclient MES 内部接口客户端(备料单拉取/状态回写,AGV 配送用)。 package mesclient import ( "bytes" "context" "encoding/json" "errors" "fmt" "net/http" "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 } // ProducibleItem MES 实时计算的可生产数量行 type ProducibleItem struct { ProductCode string `json:"productCode"` ProductName string `json:"productName"` ProducibleQty int `json:"producibleQty"` ShortText string `json:"shortText"` ComputedAt int64 `json:"computedAt"` } // ProducibleDemand MES 实时计算的未来5天物料需求行 type ProducibleDemand struct { MaterialCode string `json:"materialCode"` Future5Qty int `json:"future5Qty"` } // ProducibleResult MES /api/internal/producible 返回体 type ProducibleResult struct { Items []ProducibleItem `json:"items"` Demands []ProducibleDemand `json:"demands"` ComputedAt int64 `json:"computedAt"` } // FetchProducible 实时向 MES 拉取「可生产数量 + 未来5天物料需求」。 // 两系统之间不做定时/批量同步:WMS 打开页面时按需拉一次,MES 现算现返。 func (c *Client) FetchProducible(ctx context.Context) (*ProducibleResult, error) { u := c.baseURL + "/api/internal/producible" 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 ProducibleResult `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) } return &env.Data, 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) }