docs(test): 更新电表箱装配业务回归测试文档
- 将文档从代码审计任务转换为端到端业务回归测试链路 - 重新组织文档结构为链路总览、示例数据基线和详细步骤 - 添加完整的业务场景清单涵盖WMS/MES/工位终端三大系统 - 定义统一的测试数据包括产品型号、物料清单、工位派工等 - 提供详细的步骤断言和数量等式验证方法 - 建立贯穿全链路的数据流向和状态变更追踪体系
This commit is contained in:
@@ -187,6 +187,10 @@ var schemaPatchSQL = []string{
|
||||
`ALTER TABLE attachment ADD COLUMN IF NOT EXISTS deleted boolean NOT NULL DEFAULT false`,
|
||||
`ALTER TABLE attachment ADD COLUMN IF NOT EXISTS deleted_at bigint NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE attachment ADD COLUMN IF NOT EXISTS deleted_by varchar(64) NOT NULL DEFAULT ''`,
|
||||
|
||||
// ---- 接驳台主数据迁入 MES(2026-09-21,真源自 WMS 移入)----
|
||||
// 工位↔接驳台 1:1:部分唯一索引只约束已绑定工位(station_no>0)的行,库房/其他接驳台不参与。
|
||||
`CREATE UNIQUE INDEX IF NOT EXISTS ux_dock_station_no ON dock (station_no) WHERE station_no > 0`,
|
||||
}
|
||||
|
||||
// EnsureSchema ent 建表 + 幂等列补丁(只增,不删数据)
|
||||
|
||||
@@ -305,19 +305,7 @@ func MarkMaterialRequestStatusInternalHandler(svcCtx *svc.ServiceContext) http.H
|
||||
}
|
||||
|
||||
// ---------- 接驳台 / 接料确认 ----------
|
||||
|
||||
// ListDocksHandler GET /docks 拉取 WMS 接驳台主数据(dock 真源在 WMS)。
|
||||
// WMS 不可达降级为错误,前端仍可用写死兜底下拉。
|
||||
func ListDocksHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := svcCtx.Wms.ListDocks(r.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3113, "拉取接驳台失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, list)
|
||||
}
|
||||
}
|
||||
// 接驳台查询/维护接口见 dock.go(真源已迁 MES 本地 dock 表)。
|
||||
|
||||
// ReceiveMaterialRequestHandler POST /material-requests/receive 接料确认(MES 备料页人工点「已接料」)。
|
||||
// qty 可空(≤0 表示确认本批全部剩余);累加 receivedQty(已接料),上限为已发出量。
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package production
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/dock"
|
||||
"bj_power_mes/internal/svc"
|
||||
)
|
||||
|
||||
// 接驳台主数据(真源在 MES,2026-09-21 自 WMS 迁入):系统预置只读,不提供增删改。
|
||||
// 工位维护 / 排产推荐 / AGV 通讯均查本表;仓库不再维护接驳台、也不给接驳台派任务。
|
||||
|
||||
// dockRow 接驳台返回 DTO(显式 json 标签,避免 ent omitempty 吞掉 hasPallet=false)。
|
||||
type dockRow struct {
|
||||
ID int64 `json:"id"`
|
||||
DockCode string `json:"dockCode"`
|
||||
Name string `json:"name"`
|
||||
DockType string `json:"dockType"`
|
||||
StationNo int `json:"stationNo"`
|
||||
Status string `json:"status"`
|
||||
HasPallet bool `json:"hasPallet"`
|
||||
PalletRef string `json:"palletRef"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
}
|
||||
|
||||
func dockRowOf(d *ent.Dock) dockRow {
|
||||
return dockRow{
|
||||
ID: int64(d.ID),
|
||||
DockCode: d.DockCode,
|
||||
Name: d.Name,
|
||||
DockType: d.DockType,
|
||||
StationNo: d.StationNo,
|
||||
Status: d.Status,
|
||||
HasPallet: d.HasPallet,
|
||||
PalletRef: d.PalletRef,
|
||||
CreatedAt: d.CreatedAt.Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
// ListDocksHandler GET /docks 接驳台全量列表(工位/排产/AGV 下拉真源,读本地 dock 表)。
|
||||
func ListDocksHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := svcCtx.EntClient.Dock.Query().Order(ent.Asc("dockCode")).All(r.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3113, "查询接驳台失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
rows := make([]dockRow, 0, len(list))
|
||||
for _, d := range list {
|
||||
rows = append(rows, dockRowOf(d))
|
||||
}
|
||||
httpx.Ok(w, rows)
|
||||
}
|
||||
}
|
||||
|
||||
// ListDocksMaintHandler GET /docks/maint 接驳台维护页只读分页查询。
|
||||
// 编码精确 / 名称模糊 / 类型精确 / 状态精确各自独立筛选(真分页在 SQL 层)。
|
||||
func ListDocksMaintHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
query := svcCtx.EntClient.Dock.Query()
|
||||
if typ := strings.TrimSpace(q.Get("dockType")); typ != "" {
|
||||
query = query.Where(dock.DockTypeEQ(typ))
|
||||
}
|
||||
if code := strings.TrimSpace(q.Get("dockCode")); code != "" {
|
||||
query = query.Where(dock.DockCodeEqualFold(code))
|
||||
}
|
||||
if name := strings.TrimSpace(q.Get("name")); name != "" {
|
||||
query = query.Where(dock.NameContainsFold(name))
|
||||
}
|
||||
if status := strings.TrimSpace(q.Get("status")); status != "" {
|
||||
query = query.Where(dock.StatusEQ(status))
|
||||
}
|
||||
total, err := query.Count(r.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3113, "查询接驳台失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
page, _ := strconv.Atoi(q.Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
size, _ := strconv.Atoi(q.Get("pageSize"))
|
||||
if size < 1 || size > 200 {
|
||||
size = 20
|
||||
}
|
||||
list, err := query.
|
||||
Order(ent.Desc("createdAt"), ent.Desc("id")).
|
||||
Offset((page - 1) * size).Limit(size).All(r.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3113, "查询接驳台失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
rows := make([]dockRow, 0, len(list))
|
||||
for _, d := range list {
|
||||
rows = append(rows, dockRowOf(d))
|
||||
}
|
||||
httpx.Ok(w, map[string]any{"list": rows, "total": total, "page": page, "pageSize": size})
|
||||
}
|
||||
}
|
||||
|
||||
// ListDocksInternalHandler GET /api/internal/docks 供 WMS 拉取接驳台主数据
|
||||
// (WMS 出库目标工位下拉 / AGV 页接驳台列表;dock 真源已迁 MES)。X-API-TOKEN 保护。
|
||||
func ListDocksInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := svcCtx.EntClient.Dock.Query().Order(ent.Asc("dockCode")).All(r.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3113, "查询接驳台失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
rows := make([]dockRow, 0, len(list))
|
||||
for _, d := range list {
|
||||
rows = append(rows, dockRowOf(d))
|
||||
}
|
||||
httpx.Ok(w, map[string]any{"list": rows})
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,9 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/dock/pallet", Handler: internal(production.DockPalletInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/docks", Handler: internal(production.ListDocksInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/workpiece/bind-panel", Handler: internal(production.BindPanelInternalHandler(serverCtx)),
|
||||
})
|
||||
@@ -181,6 +184,7 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{Method: http.MethodGet, Path: "/qty-reports/stats", Handler: QtyReportStatsHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/qty-reports/handle", Handler: HandleQtyReportHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/docks", Handler: production.ListDocksHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/docks/maint", Handler: production.ListDocksMaintHandler(serverCtx)},
|
||||
|
||||
{Method: http.MethodPost, Path: "/plc/send-process", Handler: production.PlcSendProcessHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/plc/send-logs", Handler: production.PlcSendLogsHandler(serverCtx)},
|
||||
|
||||
@@ -80,6 +80,7 @@ func (s *Service) Seed(ctx context.Context) error {
|
||||
{"produce.torque", "拧紧查询", "MENU", "/torque"},
|
||||
{"produce.processflow", "工艺流程", "MENU", "/process-flow"},
|
||||
{"produce.station", "关联工位", "MENU", "/station"}, {"produce.performance", "绩效报表", "MENU", "/performance"},
|
||||
{"produce.dock", "接驳台维护", "MENU", "/dock"},
|
||||
{"produce.scan", "手动报工", "MENU", "/scan"},
|
||||
{"produce.trace", "工件追溯", "MENU", "/trace"},
|
||||
{"produce.wip", "在制品", "MENU", "/wip"},
|
||||
@@ -170,9 +171,39 @@ func (s *Service) Seed(ctx context.Context) error {
|
||||
|
||||
// 4. 初始化产线工位(内置只读主数据);工艺流程/工序步骤由用户在「工艺流程」页自建,不预置
|
||||
seedStations(ctx, s)
|
||||
// 5. 接驳台主数据(真源在 MES,2026-09-21 自 WMS 迁入);系统预置只读
|
||||
seedDocks(ctx, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// seedDocks 接驳台主数据:DOCK01~20(产线, DOCKn↔工位n) + DOCK21(库房收货)。
|
||||
// 系统预置只读主数据(不提供增删改),与工位种子同源一次性写入。
|
||||
func seedDocks(ctx context.Context, s *Service) {
|
||||
count, _ := s.ctx.EntClient.Dock.Query().Count(ctx)
|
||||
if count > 0 {
|
||||
return
|
||||
}
|
||||
for i := 1; i <= 20; i++ {
|
||||
code := "DOCK" + pad2(i)
|
||||
_, _ = s.ctx.EntClient.Dock.Create().
|
||||
SetDockCode(code).
|
||||
SetName("产线接驳台 " + code + "(工位" + strconv.Itoa(i) + ")").
|
||||
SetDockType("line").
|
||||
SetStationNo(i).
|
||||
SetHasPallet(false).
|
||||
SetStatus("ENABLED").
|
||||
Save(ctx)
|
||||
}
|
||||
_, _ = s.ctx.EntClient.Dock.Create().
|
||||
SetDockCode("DOCK21").
|
||||
SetName("库房收货接驳台").
|
||||
SetDockType("store").
|
||||
SetStationNo(0).
|
||||
SetHasPallet(false).
|
||||
SetStatus("ENABLED").
|
||||
Save(ctx)
|
||||
}
|
||||
|
||||
func seedStations(ctx context.Context, s *Service) {
|
||||
// 工位是产线固定物理工位,一律「内置工位」:需连 PLC、按工位号顺序流转、不可改类型、不可删除、仅可改名,
|
||||
// 属系统内置只读主数据(同 WMS 接驳台),故由种子初始化。12 个装配工位(1~10 带接驳台,11/12 产线末端无接驳台);
|
||||
|
||||
@@ -389,28 +389,6 @@ func (c *Client) MaterialTypes(ctx context.Context, codes []string) (map[string]
|
||||
return body.Data.Types, nil
|
||||
}
|
||||
|
||||
// DockRow 接驳台主数据行(WMS dock 真源)
|
||||
type DockRow struct {
|
||||
DockCode string `json:"dockCode"`
|
||||
Name string `json:"name"`
|
||||
DockType string `json:"dockType"`
|
||||
StationNo int `json:"stationNo"`
|
||||
HasPallet bool `json:"hasPallet"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ListDocks 拉取 WMS 接驳台主数据列表(MES 工位维护页「送达接驳台」下拉真源)。
|
||||
// WMS 不可达返回 err,由调用方降级为写死兜底。
|
||||
func (c *Client) ListDocks(ctx context.Context) ([]DockRow, error) {
|
||||
var data struct {
|
||||
List []DockRow `json:"list"`
|
||||
}
|
||||
if err := c.get(ctx, "/api/internal/docks", &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data.List, nil
|
||||
}
|
||||
|
||||
// BackflushItem 反冲单条物料(已含损耗的单件消耗 * 完成件数)
|
||||
type BackflushItem struct {
|
||||
MaterialCode string `json:"materialCode"`
|
||||
|
||||
Reference in New Issue
Block a user