refactor: 清理MES端本地product_type冗余代码,对齐WMS主数据架构
1. 移除MES端本地product_type实体、schema及相关CRUD代码,改为通过WMS内部API代理获取物料主数据 2. 更新物料管理粒度逻辑,新增"其他"类型(3)支持,同步更新入库校验规则 3. 修复前端物料选择、库存筛选等页面的类型判断逻辑,统一使用manage_mode区分管理类型 4. 清理静态资源文件冗余的样式代码,调整前端页面展示逻辑 5. 更新帮助文档,修正物料类型的说明描述
This commit is contained in:
@@ -170,12 +170,9 @@ var schemaPatchSQL = []string{
|
||||
// 日排产各工位产量分配
|
||||
`ALTER TABLE daily_plan ADD COLUMN IF NOT EXISTS station_plan_qty jsonb NOT NULL DEFAULT '{}'::jsonb`,
|
||||
|
||||
// ---- 物料档案「简称」删除(2026-09-19 用户裁决)----
|
||||
// 本地 product_type 仅作 WMS 不可达时的离线兜底缓存:原 remark(备注) 一并删除,
|
||||
// 改存 spec/unit,与 WMS 物料档案必填口径(图号/名称/规格/单位/品类/类型)保持一致。
|
||||
`ALTER TABLE product_type ADD COLUMN IF NOT EXISTS spec varchar(128) NOT NULL DEFAULT ''`,
|
||||
`ALTER TABLE product_type ADD COLUMN IF NOT EXISTS unit varchar(32) NOT NULL DEFAULT ''`,
|
||||
`ALTER TABLE product_type DROP COLUMN IF EXISTS remark`,
|
||||
// ---- 物料档案 product_type 本地表清理(2026-09-21 方案 A:纯 WMS 代理,删除本地双写/缓存表)----
|
||||
// ent 已移除 product_type 实体,Schema.Create 不再建此表;此处清理存量库遗留表。
|
||||
`DROP TABLE IF EXISTS product_type`,
|
||||
|
||||
// ---- 附件统一存储方案(2026-09-19):年/月/日/文件类型/uuid.ext ----
|
||||
`ALTER TABLE attachment ADD COLUMN IF NOT EXISTS file_type varchar(32) NOT NULL DEFAULT 'OTHER'`,
|
||||
|
||||
@@ -16,12 +16,12 @@ import (
|
||||
"bj_power_mes/ent/bomitem"
|
||||
"bj_power_mes/ent/processflow"
|
||||
"bj_power_mes/ent/processstep"
|
||||
"bj_power_mes/ent/producttype"
|
||||
"bj_power_mes/ent/station"
|
||||
"bj_power_mes/ent/stepcriterion"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/ent/workpieceprocess"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/wmsclient"
|
||||
"bj_power_mes/internal/svc"
|
||||
)
|
||||
|
||||
@@ -135,13 +135,13 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
|
||||
client := svcCtx.EntClient
|
||||
card := &modelCard{GeneratedAt: nowText()}
|
||||
|
||||
// 1) 定位产品型号
|
||||
var pt *ent.ProductType
|
||||
// 1) 定位产品型号(实时代理 WMS,成品档案 item_type=3;WMS 不可达时降级用 code 兜底)
|
||||
var pt *wmsclient.ProductTypeRow
|
||||
if productTypeId > 0 {
|
||||
pt, _ = client.ProductType.Get(ctx, productTypeId)
|
||||
pt, _ = logic.New(svcCtx).ProductTypeByID(ctx, productTypeId)
|
||||
}
|
||||
if pt == nil && productCode != "" {
|
||||
pt, _ = client.ProductType.Query().Where(producttype.Code(productCode)).First(ctx)
|
||||
pt, _ = logic.New(svcCtx).ProductTypeByCode(ctx, productCode)
|
||||
}
|
||||
switch {
|
||||
case pt != nil:
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"bj_power_mes/ent/dailyplan"
|
||||
"bj_power_mes/ent/producttype"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/internal/wmsclient"
|
||||
)
|
||||
@@ -100,8 +99,8 @@ func (s *Service) ComputeProducible(ctx context.Context) (*ProducibleResult, err
|
||||
|
||||
// 4) 产品名称映射(product_type 优先,work_order 兜底)
|
||||
nameMap := map[string]string{}
|
||||
if pts, e := s.ctx.EntClient.ProductType.Query().All(ctx); e == nil {
|
||||
for _, p := range pts {
|
||||
if rows, e := s.ctx.Wms.ProductTypes(ctx, ""); e == nil {
|
||||
for _, p := range rows {
|
||||
nameMap[p.Code] = p.Name
|
||||
}
|
||||
}
|
||||
@@ -185,11 +184,11 @@ func (s *Service) dropInactiveProducts(ctx context.Context, chosen map[string][]
|
||||
if len(chosen) == 0 {
|
||||
return
|
||||
}
|
||||
pts, err := s.ctx.EntClient.ProductType.Query().Where(producttype.IsActiveEQ(false)).All(ctx)
|
||||
rows, err := s.ctx.Wms.ProductTypes(ctx, "false")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, p := range pts {
|
||||
for _, p := range rows {
|
||||
delete(chosen, p.Code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,14 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/bomitem"
|
||||
"bj_power_mes/ent/producttype"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/internal/wmsclient"
|
||||
)
|
||||
|
||||
// 产品型号(成品档案):编码主数据单一来源 = WMS(materials item_type=3)。
|
||||
// MES 本页改为实时代理 WMS;WMS 不可达时降级读本地 product_types 只读缓存(仅查询,
|
||||
// 写操作必须 WMS 可用),前端以 wmsOnline 标识提示。
|
||||
// MES 本页实时代理 WMS;不再保留本地 product_types 缓存/双写(方案 A:WMS 不可达即报错,
|
||||
// 不降级读本地缓存,避免给界面喂空/陈旧数据)。
|
||||
|
||||
type ProductTypeReq struct {
|
||||
Id int `json:"id"`
|
||||
@@ -69,7 +67,7 @@ func (s *Service) UpdateProductType(ctx context.Context, req ProductTypeReq) err
|
||||
// DeleteProductType 删除保护:被工单/BOM 引用过的型号禁删(MES 侧引用校验,
|
||||
// WMS 侧库存引用由 WMS 内部接口再校验一层)
|
||||
func (s *Service) DeleteProductType(ctx context.Context, id int) error {
|
||||
row, err := s.productTypeByID(ctx, id)
|
||||
row, err := s.ProductTypeByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,84 +86,50 @@ func (s *Service) DeleteProductType(ctx context.Context, id int) error {
|
||||
}
|
||||
|
||||
// ListProductTypes 全量列表(下拉/兼容形态,id asc 新建在底部)。
|
||||
// degraded=false 表示 WMS 不可达、数据来自本地只读缓存。
|
||||
// 纯 WMS 代理:WMS 不可达直接报错(不再降级本地缓存)。degraded 恒为 true(仅兼容前端字段)。
|
||||
func (s *Service) ListProductTypes(ctx context.Context) ([]wmsclient.ProductTypeRow, bool, error) {
|
||||
list, err := s.ctx.Wms.ProductTypes(ctx, "")
|
||||
if err == nil {
|
||||
return list, true, nil
|
||||
if err != nil {
|
||||
return nil, false, errors.New("WMS 不可达,无法读取成品档案:" + err.Error())
|
||||
}
|
||||
fallback, ferr := s.localProductTypes(ctx, "", "", "")
|
||||
if ferr != nil {
|
||||
return nil, false, errors.New("WMS 不可达且本地缓存读取失败")
|
||||
}
|
||||
return fallback, false, nil
|
||||
return list, true, nil
|
||||
}
|
||||
|
||||
// ListProductTypesPage 分页+搜索(管理页真分页)。
|
||||
// 2026-09-08 禁止关键字混搜:编码/名称/类别各自独立模糊。
|
||||
// 纯 WMS 代理:WMS 不可达直接报错。wmsOnline 恒为 true(仅兼容前端字段)。
|
||||
func (s *Service) ListProductTypesPage(ctx context.Context, code, name, category, isActive string, page, pageSize int) (int64, []wmsclient.ProductTypeRow, bool, error) {
|
||||
total, list, err := s.ctx.Wms.ProductTypePage(ctx, code, name, category, isActive, page, pageSize)
|
||||
if err == nil {
|
||||
return total, list, true, nil
|
||||
if err != nil {
|
||||
return 0, nil, false, errors.New("WMS 不可达,无法读取成品档案:" + err.Error())
|
||||
}
|
||||
fallback, ferr := s.localProductTypes(ctx, code, name, category)
|
||||
if ferr != nil {
|
||||
return 0, nil, false, errors.New("WMS 不可达且本地缓存读取失败")
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
if start > len(fallback) {
|
||||
start = len(fallback)
|
||||
}
|
||||
end := start + pageSize
|
||||
if end > len(fallback) {
|
||||
end = len(fallback)
|
||||
}
|
||||
return int64(len(fallback)), fallback[start:end], false, nil
|
||||
return total, list, true, nil
|
||||
}
|
||||
|
||||
func (s *Service) productTypeByID(ctx context.Context, id int) (*wmsclient.ProductTypeRow, error) {
|
||||
// ProductTypeByID 从 WMS 取成品档案(item_type=3)按 id;WMS 不可达即报错。
|
||||
func (s *Service) ProductTypeByID(ctx context.Context, id int) (*wmsclient.ProductTypeRow, error) {
|
||||
list, err := s.ctx.Wms.ProductTypes(ctx, "")
|
||||
if err == nil {
|
||||
for i := range list {
|
||||
if list[i].ID == id {
|
||||
return &list[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("物料档案不存在")
|
||||
}
|
||||
// 降级:本地缓存
|
||||
pt, err := s.ctx.EntClient.ProductType.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, errors.New("物料档案不存在(WMS 不可达,无法校验引用)")
|
||||
return nil, errors.New("WMS 不可达,无法读取成品档案:" + err.Error())
|
||||
}
|
||||
return &wmsclient.ProductTypeRow{ID: pt.ID, Code: pt.Code, Name: pt.Name}, nil
|
||||
for i := range list {
|
||||
if list[i].ID == id {
|
||||
return &list[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("物料档案不存在")
|
||||
}
|
||||
|
||||
// localProductTypes 本地只读缓存过滤(降级用):编码/名称/类别各自独立模糊匹配
|
||||
func (s *Service) localProductTypes(ctx context.Context, code, name, category string) ([]wmsclient.ProductTypeRow, error) {
|
||||
list, err := s.ctx.EntClient.ProductType.Query().Order(ent.Asc(producttype.FieldID)).All(ctx)
|
||||
// ProductTypeByCode 从 WMS 取成品档案按 code。
|
||||
func (s *Service) ProductTypeByCode(ctx context.Context, code string) (*wmsclient.ProductTypeRow, error) {
|
||||
list, err := s.ctx.Wms.ProductTypes(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.New("WMS 不可达,无法读取成品档案:" + err.Error())
|
||||
}
|
||||
codeKw := strings.ToLower(strings.TrimSpace(code))
|
||||
nameKw := strings.ToLower(strings.TrimSpace(name))
|
||||
catKw := strings.ToLower(strings.TrimSpace(category))
|
||||
rows := make([]wmsclient.ProductTypeRow, 0, len(list))
|
||||
for _, p := range list {
|
||||
if codeKw != "" && !strings.Contains(strings.ToLower(p.Code), codeKw) {
|
||||
continue
|
||||
for i := range list {
|
||||
if list[i].Code == code {
|
||||
return &list[i], nil
|
||||
}
|
||||
if nameKw != "" && !strings.Contains(strings.ToLower(p.Name), nameKw) {
|
||||
continue
|
||||
}
|
||||
if catKw != "" && !strings.Contains(strings.ToLower(p.Category), catKw) {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, wmsclient.ProductTypeRow{
|
||||
ID: p.ID, Code: p.Code, Name: p.Name, Spec: p.Spec, Unit: p.Unit,
|
||||
Category: p.Category, IsActive: p.IsActive,
|
||||
CreatedAt: p.CreatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
return rows, nil
|
||||
return nil, errors.New("物料档案不存在")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package wmsclient
|
||||
package wmsclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -630,3 +632,80 @@ type RespError struct {
|
||||
func (e *RespError) Error() string {
|
||||
return "wms call failed with status " + string(rune('0'+e.Status))
|
||||
}
|
||||
|
||||
// MaterialRow WMS 物料档案行(代理 WMS materials 表)
|
||||
type MaterialRow struct {
|
||||
ID int `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Spec string `json:"spec"`
|
||||
Unit string `json:"unit"`
|
||||
Description string `json:"description"`
|
||||
ItemType int `json:"itemType"`
|
||||
ManageMode int `json:"manageMode"`
|
||||
TemplateCode string `json:"templateCode"`
|
||||
SafetyStock int `json:"safetyStock"`
|
||||
IsActive bool `json:"isActive"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// MaterialPage 分页查询 WMS 物料(GET /api/internal/materials)
|
||||
func (c *Client) MaterialPage(ctx context.Context, code, name, spec string, itemType, manageMode, page, pageSize int) (int64, []MaterialRow, error) {
|
||||
q := "/api/internal/materials?page=" + strconv.Itoa(page) + "&pageSize=" + strconv.Itoa(pageSize)
|
||||
if code != "" {
|
||||
q += "&materialCode=" + url.QueryEscape(code)
|
||||
}
|
||||
if name != "" {
|
||||
q += "&name=" + url.QueryEscape(name)
|
||||
}
|
||||
if spec != "" {
|
||||
q += "&spec=" + url.QueryEscape(spec)
|
||||
}
|
||||
if itemType > 0 {
|
||||
q += "&itemType=" + strconv.Itoa(itemType)
|
||||
}
|
||||
if manageMode > 0 {
|
||||
q += "&manageMode=" + strconv.Itoa(manageMode)
|
||||
}
|
||||
var out struct {
|
||||
Total int64 `json:"total"`
|
||||
List []MaterialRow `json:"list"`
|
||||
}
|
||||
if err := c.get(ctx, q, &out); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return out.Total, out.List, nil
|
||||
}
|
||||
|
||||
// CreateMaterial 调 WMS 创建物料(POST /api/internal/materials)
|
||||
func (c *Client) CreateMaterial(ctx context.Context, code, name, spec, unit, description string, itemType, manageMode, safetyStock int, templateCode string) error {
|
||||
return c.post(ctx, "/api/internal/materials", map[string]any{
|
||||
"code": code, "name": name, "spec": spec, "unit": unit, "description": description,
|
||||
"itemType": itemType, "manageMode": manageMode, "templateCode": templateCode, "safetyStock": safetyStock,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateMaterial 调 WMS 更新物料(PUT /api/internal/materials)
|
||||
func (c *Client) UpdateMaterial(ctx context.Context, id int, name, spec, unit, description string, itemType, manageMode, safetyStock int, templateCode string) error {
|
||||
b, _ := json.Marshal(map[string]any{
|
||||
"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))
|
||||
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 >= 400 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("WMS PUT /api/internal/materials %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user