feat: 重构BOM架构,新增基础设置与虚拟工位管理功能

本次重构删除原有BOM物料清单表,改用工单工艺组合×工艺物料清单作为唯一用料来源;新增系统基础设置表支持WMS地址、日志保留天数等配置,新增虚拟工位作业管理后台接口与前端页签控制功能,同时优化工位号校验逻辑、事件日志自动清理与工单用料查询能力。
This commit is contained in:
SunYF
2026-09-23 11:41:28 +08:00
parent d3eda6b588
commit f274da044c
74 changed files with 3637 additions and 5586 deletions
+49 -26
View File
@@ -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
}