Files
bj_power/bj_power_mes/internal/wmsclient/client.go
T
SunYF e852c7cd37 chore: 批量完成项目多模块迭代优化
1. 废弃半成品库存表并统一库存主表
2. 修复列表排序与搜索大小写不敏感问题
3. 新增用户最近登录时间、工单BOM名称字段
4. 完善角色管理、工位选择器功能
5. 增加拧紧数据补录、成品自动回流WMS能力
6. 统一导出时间范围校验规则
7. 丰富物料/备料单筛选条件
8. 调整菜单与权限命名适配产品型号业务
2026-09-08 11:44:04 +08:00

295 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package wmsclient
import (
"fmt"
"bytes"
"context"
"encoding/json"
"net/http"
"time"
)
// Client WMS 内部接口客户端(半成品流转等)
type Client struct {
baseURL string
token string
hc *http.Client
}
func New(baseURL, token string) *Client {
if baseURL == "" {
baseURL = "http://127.0.0.1:9091"
}
return &Client{
baseURL: baseURL,
token: token,
hc: &http.Client{Timeout: 5 * time.Second},
}
}
// SemiInbound 调 WMS /api/internal/semi/inbound 半成品入库
func (c *Client) SemiInbound(ctx context.Context, sn, orderNo string, doneProcessCodes []int, operator string) error {
return c.post(ctx, "/api/internal/semi/inbound", map[string]any{
"sn": sn, "orderNo": orderNo, "doneProcessCodes": doneProcessCodes, "operator": operator,
})
}
// SemiOutbound 调 WMS /api/internal/semi/outbound 半成品出库(重上线)
func (c *Client) SemiOutbound(ctx context.Context, sn, orderNo string, doneProcessCodes []int, operator string) error {
return c.post(ctx, "/api/internal/semi/outbound", map[string]any{
"sn": sn, "orderNo": orderNo, "doneProcessCodes": doneProcessCodes, "operator": operator,
})
}
// FinishedInbound 成品完工回流入库:调 WMS /api/internal/finished/inbound。
// 成品 SN 由 MES 系统生成(工件SN),统一库存主表 category=3;失败返回 err 由调用方降级。
func (c *Client) FinishedInbound(ctx context.Context, productCode, productName, sn, orderNo, operator string) error {
return c.post(ctx, "/api/internal/finished/inbound", map[string]any{
"productCode": productCode, "productName": productName, "sn": sn,
"orderNo": orderNo, "snSource": "AUTO", "operator": operator,
})
}
// MaterialExists 批量校验物料编码是否存在于 WMS 物料档案。
// 返回 WMS 中不存在的编码列表(全部存在则为空)。WMS 响应 {code,message,data:{missing:[]}}。
// 失败(网络/超时/非0响应)返回 err,由调用方决定降级策略。
func (c *Client) MaterialExists(ctx context.Context, codes []string) ([]string, error) {
b, err := json.Marshal(map[string]any{"codes": codes})
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/material/exists", bytes.NewReader(b))
if err != nil {
return nil, 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 nil, err
}
defer resp.Body.Close()
var body struct {
Code int `json:"code"`
Data struct {
Missing []string `json:"missing"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return nil, err
}
if resp.StatusCode >= 300 || body.Code != 0 {
return nil, &RespError{Status: resp.StatusCode}
}
return body.Data.Missing, nil
}
// ProductTypeRow 产品型号档案行(WMS materials item_type=3
type ProductTypeRow struct {
ID int `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
Category string `json:"category"`
Remark string `json:"remark"`
IsActive bool `json:"isActive"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
// get 调 WMS GET 接口并解出 {code,data}
func (c *Client) get(ctx context.Context, path string, out any) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil)
if err != nil {
return err
}
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}
}
var body struct {
Code int `json:"code"`
Data any `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return err
}
if body.Code != 0 {
return &RespError{Status: resp.StatusCode}
}
if out != nil && body.Data != nil {
b, _ := json.Marshal(body.Data)
return json.Unmarshal(b, out)
}
return nil
}
// putJSON 调 WMS PUT 接口
func (c *Client) putJSON(ctx context.Context, path string, body any) error {
b, err := json.Marshal(body)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPut, c.baseURL+path, 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
}
// ProductTypes 全量产品型号(item_type=3id asc,新建在底部)。keyword/isActive 可空。
func (c *Client) ProductTypes(ctx context.Context, keyword, isActive string) ([]ProductTypeRow, error) {
q := "/api/internal/product-types?"
if keyword != "" {
q += "keyword=" + keyword + "&"
}
if isActive != "" {
q += "isActive=" + isActive
}
var list []ProductTypeRow
if err := c.get(ctx, q, &list); err != nil {
return nil, err
}
return list, nil
}
// ProductTypePage 分页+搜索(产品型号管理页真分页)
func (c *Client) ProductTypePage(ctx context.Context, keyword, isActive string, page, pageSize int) (int64, []ProductTypeRow, error) {
q := fmt.Sprintf("/api/internal/product-types?page=%d&pageSize=%d", page, pageSize)
if keyword != "" {
q += "&keyword=" + keyword
}
if isActive != "" {
q += "&isActive=" + isActive
}
var data struct {
Total int64 `json:"total"`
List []ProductTypeRow `json:"list"`
}
if err := c.get(ctx, q, &data); err != nil {
return 0, nil, err
}
return data.Total, data.List, nil
}
// CreateProductType 新建产品型号(code 可空由 WMS 自动生成 PROD-日期-流水)
func (c *Client) CreateProductType(ctx context.Context, code, name, category, remark string, isActive bool) error {
return c.post(ctx, "/api/internal/product-types", map[string]any{
"code": code, "name": name, "category": category, "remark": remark, "isActive": isActive,
})
}
// UpdateProductType 修改产品型号(编码不可改)
func (c *Client) UpdateProductType(ctx context.Context, id int, name, category, remark string, isActive bool) error {
return c.putJSON(ctx, "/api/internal/product-types", map[string]any{
"id": id, "name": name, "category": category, "remark": remark, "isActive": isActive,
})
}
// DeleteProductType 删除产品型号(引用保护由调用方与 WMS 双侧校验)
func (c *Client) DeleteProductType(ctx context.Context, id int) error {
return c.post(ctx, "/api/internal/product-types/delete", map[string]any{"id": id})
}
// ExportProductTypes 拉取产品型号导出 xlsx 字节流(WMS 端执行统一导出时间规则)
func (c *Client) ExportProductTypes(ctx context.Context, keyword, isActive, startDate, endDate string) ([]byte, error) {
q := "/api/internal/product-types/export?"
if keyword != "" {
q += "keyword=" + keyword + "&"
}
if isActive != "" {
q += "isActive=" + isActive + "&"
}
q += "startDate=" + startDate + "&endDate=" + endDate
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+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()
if resp.StatusCode >= 300 {
return nil, &RespError{Status: resp.StatusCode}
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(resp.Body); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// DisplayOverview 拉取 WMS 免登录看板总览 /api/display/overview。
// 用于看板「仓储动态」屏。WMS 不可达时返回 err,由调用方降级为空对象(看板其余屏不受影响)。
func (c *Client) DisplayOverview(ctx context.Context) (map[string]any, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/display/overview", 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()
if resp.StatusCode >= 300 {
return nil, &RespError{Status: resp.StatusCode}
}
var body struct {
Code int `json:"code"`
Data map[string]any `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return nil, err
}
if body.Data == nil {
return map[string]any{}, nil
}
return body.Data, nil
}
func (c *Client) post(ctx context.Context, path string, body any) error {
b, err := json.Marshal(body)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, 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 "wms call failed with status " + string(rune('0'+e.Status))
}