145 lines
4.3 KiB
Go
145 lines
4.3 KiB
Go
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)
|
||
|
|
}
|