feat: 重构BOM架构,新增基础设置与虚拟工位管理功能
本次重构删除原有BOM物料清单表,改用工单工艺组合×工艺物料清单作为唯一用料来源;新增系统基础设置表支持WMS地址、日志保留天数等配置,新增虚拟工位作业管理后台接口与前端页签控制功能,同时优化工位号校验逻辑、事件日志自动清理与工单用料查询能力。
This commit is contained in:
@@ -10,25 +10,47 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client WMS 内部接口客户端(半成品流转等)
|
||||
// baseURL 支持运行时热切换(系统管理员「基础设置」页保存 WMS 地址后 SetBaseURL 即生效),
|
||||
// 因此用 atomic.Value 存储,读侧无需加锁。
|
||||
type Client struct {
|
||||
baseURL string
|
||||
baseURL atomic.Value // string
|
||||
token string
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// defaultBaseURL 未配置 WMS 地址时的兜底值
|
||||
const defaultBaseURL = "http://127.0.0.1:9091"
|
||||
|
||||
func New(baseURL, token string) *Client {
|
||||
if baseURL == "" {
|
||||
baseURL = "http://127.0.0.1:9091"
|
||||
c := &Client{
|
||||
token: token,
|
||||
hc: &http.Client{Timeout: 5 * time.Second},
|
||||
}
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
token: token,
|
||||
hc: &http.Client{Timeout: 5 * time.Second},
|
||||
c.SetBaseURL(baseURL)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetBaseURL 热切换 WMS 地址;空串回退默认地址
|
||||
func (c *Client) SetBaseURL(u string) {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
u = defaultBaseURL
|
||||
}
|
||||
if !strings.HasSuffix(u, "/") {
|
||||
u += "/"
|
||||
}
|
||||
c.baseURL.Store(u)
|
||||
}
|
||||
|
||||
// BaseURL 当前生效的 WMS 地址(带尾斜杠)
|
||||
func (c *Client) BaseURL() string {
|
||||
return c.baseURL.Load().(string)
|
||||
}
|
||||
|
||||
// SemiInbound 调 WMS /api/internal/semi/inbound 半成品入库。
|
||||
@@ -55,7 +77,7 @@ func (c *Client) postSemi(ctx context.Context, path string, body any) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL()+path, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -98,7 +120,7 @@ func (c *Client) MaterialExists(ctx context.Context, codes []string) ([]string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/material/exists", bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL()+"/api/internal/material/exists", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,11 +150,11 @@ func (c *Client) MaterialExists(ctx context.Context, codes []string) ([]string,
|
||||
// 注:原「备注」随 WMS 物料档案「简称」一并删除(2026-09-19),
|
||||
// 改为与 WMS 必填口径一致的「规格 / 单位」。
|
||||
type ProductTypeRow struct {
|
||||
ID int `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Spec string `json:"spec"`
|
||||
Unit string `json:"unit"`
|
||||
ID int `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Spec string `json:"spec"`
|
||||
Unit string `json:"unit"`
|
||||
Category string `json:"category"`
|
||||
ManageMode int `json:"manageMode"` // 1结构件(批次)/2电气件(SN)
|
||||
IsActive bool `json:"isActive"`
|
||||
@@ -142,7 +164,7 @@ type ProductTypeRow struct {
|
||||
|
||||
// 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)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL()+path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,7 +200,7 @@ func (c *Client) putJSON(ctx context.Context, path string, body any) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, c.baseURL+path, bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, c.BaseURL()+path, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -271,7 +293,7 @@ func (c *Client) ExportProductTypes(ctx context.Context, code, name, category, i
|
||||
q += "isActive=" + isActive + "&"
|
||||
}
|
||||
q += "startDate=" + startDate + "&endDate=" + endDate
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+q, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL()+q, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -302,7 +324,7 @@ func (c *Client) CreateReturnOrder(ctx context.Context, orderNo, sn, materialCod
|
||||
// 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)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL()+"/api/display/overview", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -339,7 +361,7 @@ func (c *Client) StockAvailable(ctx context.Context, codes []string) (map[string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/stock/check", bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL()+"/api/internal/stock/check", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -372,8 +394,9 @@ func (c *Client) StockAvailable(ctx context.Context, codes []string) (map[string
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ProducibleItem 可生产数量快照行(MES 计算后推送 WMS 只读展示)
|
||||
// ProducibleItem 可生产数量行(按工单维度,MES 实时计算;原定时推送/快照已下线)
|
||||
type ProducibleItem struct {
|
||||
OrderNo string `json:"orderNo"`
|
||||
ProductCode string `json:"productCode"`
|
||||
ProductName string `json:"productName"`
|
||||
ProducibleQty int `json:"producibleQty"`
|
||||
@@ -395,7 +418,7 @@ func (c *Client) MaterialTypes(ctx context.Context, codes []string) (map[string]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/material/types", bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL()+"/api/internal/material/types", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -460,7 +483,7 @@ type StockBatch struct {
|
||||
// StockBatches 调 WMS /api/internal/stock/batches 取该物料可出库的批次(FIFO 升序)与可用 SN 列表。
|
||||
// 供「工单备料自动出库」分配用;WMS 只返回「合格」且在库的库存。
|
||||
func (c *Client) StockBatches(ctx context.Context, materialCode string) ([]StockBatch, []string, error) {
|
||||
u := c.baseURL + "/api/internal/stock/batches?materialCode=" + url.QueryEscape(materialCode)
|
||||
u := c.BaseURL() + "/api/internal/stock/batches?materialCode=" + url.QueryEscape(materialCode)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -529,7 +552,7 @@ func (c *Client) StockDeduct(ctx context.Context, orderNo, materialCode string,
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/stock/deduct", bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL()+"/api/internal/stock/deduct", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -615,7 +638,7 @@ func (c *Client) LedgerSync(ctx context.Context, orderNo, productCode, operator
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/internal/ledger/sync", bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL()+"/api/internal/ledger/sync", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -645,7 +668,7 @@ func (c *Client) post(ctx context.Context, path string, body any) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL()+path, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -729,7 +752,7 @@ func (c *Client) UpdateMaterial(ctx context.Context, id int, name, spec, unit, d
|
||||
"id": id, "name": name, "spec": spec, "unit": unit, "description": description,
|
||||
"itemType": itemType, "manageMode": manageMode, "templateCode": templateCode, "safetyStock": safetyStock,
|
||||
})
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, c.baseURL+"/api/internal/materials", bytes.NewReader(b))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, c.BaseURL()+"/api/internal/materials", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user