refactor: 重构产线工位与备料流程,移除冗余字段

1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段
2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据
3. 重构日排产为工位产量分配模式,替代原接驳台列表
4. 新增备料单工位级字段与分批出库支持
5. 移除定时同步可生产数量,改为WMS实时拉取接口
6. 过滤操作日志,排除登录/退出相关日志
7. 调整仪表盘工序展示逻辑为今日派工路线
8. 新增接驳台维护菜单与基础数据
This commit is contained in:
SunYF
2026-09-19 07:50:21 +08:00
parent 543655d441
commit 37f10d3a13
156 changed files with 4489 additions and 1285 deletions
+4 -19
View File
@@ -207,13 +207,14 @@ func buildCard(ctx context.Context, svcCtx *svc.ServiceContext, sn string) (*car
}
// hasTorqueStep 判断该工件加工路径上是否存在"拧紧采集"步骤(经 工位→流程→步骤 链)
// 路径由今日工位派工(station_process)派生
func hasTorqueStep(ctx context.Context, client *ent.Client, sn string, wp *ent.Workpiece) bool {
procCodes := parseCardSeq(wp.ProcessSeq)
if len(procCodes) == 0 {
route, err := logic.RouteStations(ctx, client, logic.TodayStr(), "")
if err != nil || len(route) == 0 {
return false
}
stations, err := client.Station.Query().
Where(station.StationNoIn(procCodes...), station.FlowIdGT(0)).All(ctx)
Where(station.StationNoIn(route...), station.FlowIdGT(0)).All(ctx)
if err != nil || len(stations) == 0 {
return false
}
@@ -229,22 +230,6 @@ func hasTorqueStep(ctx context.Context, client *ent.Client, sn string, wp *ent.W
return n > 0
}
// parseCardSeq 解析工单/工件工序组合 "1,3,5" → 工位号列表(含 0 号上线位)
func parseCardSeq(s string) []int {
out := []int{}
for _, p := range strings.Split(s, ",") {
p = strings.TrimSpace(p)
if p == "" {
continue
}
var n int
if _, err := fmt.Sscanf(p, "%d", &n); err == nil && n >= 0 && n <= logic.ProcessSeqMaxStation {
out = append(out, n)
}
}
return out
}
func itoaCard(n int) string { return fmt.Sprintf("%d", n) }
func renderProcessCard(t *cardTmpl) (string, error) {
@@ -21,6 +21,7 @@ import (
"bj_power_mes/ent/stepcriterion"
"bj_power_mes/ent/workorder"
"bj_power_mes/ent/workpieceprocess"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
@@ -157,8 +158,7 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
return nil, fmt.Errorf("产品型号不存在")
}
// 2) 工位组合:优先取该产品最近一张工单的工位组合;无工单则取全产线工位(升序
seq := []int{}
// 2) 产品最近工单(仅取工单号/计划时间用于卡头展示;路线不再存工单
if productCode != "" {
if wo, err := client.WorkOrder.Query().
Where(workorder.ProductCode(productCode)).
@@ -170,16 +170,19 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
if wo.PlanEnd != nil {
card.PlanEnd = wo.PlanEnd.Format("2006-01-02 15:04")
}
seq = parseCardSeq(wo.ProcessSeq)
card.SeqSource = "取自工单 " + wo.WorkOrderNo
}
}
// 3) 工位组合:由「今日工位派工(station_process)」派生;未派工则展示全产线已配置工艺流的工位
seq, _ := logic.RouteStations(ctx, client, logic.TodayStr(), "")
if len(seq) == 0 {
sts, _ := client.Station.Query().Order(ent.Asc(station.FieldStationNo)).All(ctx)
sts, _ := client.Station.Query().Where(station.FlowIdGT(0)).Order(ent.Asc(station.FieldStationNo)).All(ctx)
for _, st := range sts {
seq = append(seq, st.StationNo)
}
card.SeqSource = "该型号暂无工单,按全产线工位顺序展示"
card.SeqSource = "今日未派工,按全产线已配置工位顺序展示"
} else {
card.SeqSource = "取自今日工位派工路线"
}
sort.Ints(seq)
card.ProcessSeq = joinInts(seq)
@@ -223,7 +223,7 @@ func ListMaterialRequestsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
q := r.URL.Query()
data, err := logic.New(svcCtx).ListMaterialRequestsFiltered(r.Context(),
q.Get("orderNo"), q.Get("planDate"), q.Get("status"),
q.Get("materialCode"), q.Get("materialName"), q.Get("targetDock"))
q.Get("materialCode"), q.Get("materialName"), q.Get("targetDock"), q.Get("stationNo"))
if err != nil {
httpx.Fail(w, 3104, err.Error())
return
@@ -237,7 +237,7 @@ func ListMaterialRequestsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func ListMaterialRequestsInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
data, err := logic.New(svcCtx).ListMaterialRequests(r.Context(), q.Get("orderNo"), q.Get("planDate"), q.Get("status"))
data, err := logic.New(svcCtx).ListMaterialRequests(r.Context(), q.Get("orderNo"), q.Get("planDate"), q.Get("status"), q.Get("stationNo"))
if err != nil {
httpx.Fail(w, 3105, err.Error())
return
@@ -268,4 +268,59 @@ func MarkMaterialRequestStatusInternalHandler(svcCtx *svc.ServiceContext) http.H
}
httpx.Ok(w, nil)
}
}
// ---------- 接驳台 / 接料确认 ----------
// 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)
}
}
// ReceiveMaterialRequestHandler POST /material-requests/receive 接料确认(MES 备料页人工点「已接料」)。
// qty 可空(≤0 表示确认本批全部剩余);累加 sentQty 封顶 reqQty,达量自动 DONE。
func ReceiveMaterialRequestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
RequestNo string `json:"requestNo"`
Qty int `json:"qty"`
}
if err := httpx.ParseJSON(r, &req); err != nil || req.RequestNo == "" {
httpx.BadRequest(w, "缺少备料单号")
return
}
if err := logic.New(svcCtx).ReceiveMaterialRequest(r.Context(), req.RequestNo, req.Qty, operator(r, "")); err != nil {
httpx.Fail(w, 3114, err.Error())
return
}
httpx.OkMessage(w, "已接料", nil)
}
}
// StationReceiveMaterialInternalHandler POST /api/internal/station/receive-material 接料确认内部接口。
// 供工位终端代理调用(工人扫码接料主入口),X-API-TOKEN 保护。逻辑同 ReceiveMaterialRequestHandler。
func StationReceiveMaterialInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
RequestNo string `json:"requestNo"`
Qty int `json:"qty"`
}
if err := httpx.ParseJSON(r, &req); err != nil || req.RequestNo == "" {
httpx.BadRequest(w, "缺少备料单号")
return
}
if err := logic.New(svcCtx).ReceiveMaterialRequest(r.Context(), req.RequestNo, req.Qty, operator(r, "")); err != nil {
httpx.Fail(w, 3114, err.Error())
return
}
httpx.Ok(w, nil)
}
}
@@ -0,0 +1,22 @@
package production
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// ProducibleInternalHandler 内部接口:WMS 打开「可生产数量」时实时拉取。
// 不做定时/批量同步——WMS 需要时来拉一次,MES 现算现返(BOM 单台用量在 MES、可用量在 WMS)。
func ProducibleInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
res, err := logic.New(svcCtx).ComputeProducible(r.Context())
if err != nil {
httpx.Fail(w, 3401, err.Error())
return
}
httpx.Ok(w, res)
}
}
@@ -0,0 +1,69 @@
package production
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// ---------- 工位派工(工位↔工序,替代原工序组合串) ----------
// ListStationProcessHandler GET /station-process 查询某日班次全部工位派工(按工位号升序)
func ListStationProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
data, err := logic.New(svcCtx).ListStationProcess(r.Context(), q.Get("effectDate"), q.Get("shift"))
if err != nil {
httpx.Fail(w, 3301, err.Error())
return
}
httpx.Ok(w, data)
}
}
// SaveStationProcessHandler POST /station-process/save 保存单条工位派工
// 校验:同工位同天同班次唯一;已派工工位沿工位号必须单调不降、不可穿插。processCode=0 表示解除派工。
func SaveStationProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req logic.StationProcessReq
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if req.Operator == "" {
req.Operator = operator(r, "")
}
if err := logic.New(svcCtx).UpsertStationProcess(r.Context(), req); err != nil {
httpx.Fail(w, 3302, err.Error())
return
}
httpx.OkMessage(w, "保存成功", nil)
}
}
// RouteStationProcessHandler GET /station-process/route 派生路线(工位号列表/工序映射/串)
// 供流程卡、PLC 下发、备料单等依赖"今日路线"的模块读取,不再手填组合串。
func RouteStationProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
ctx := r.Context()
stations, err := logic.RouteStations(ctx, svcCtx.EntClient, q.Get("effectDate"), q.Get("shift"))
if err != nil {
httpx.Fail(w, 3303, err.Error())
return
}
procMap, err := logic.RouteStationProcessCodeMap(ctx, svcCtx.EntClient, q.Get("effectDate"), q.Get("shift"))
if err != nil {
httpx.Fail(w, 3303, err.Error())
return
}
str, _ := logic.RouteStationsStr(ctx, svcCtx.EntClient, q.Get("effectDate"), q.Get("shift"))
httpx.Ok(w, map[string]any{
"stations": stations,
"processByStation": procMap,
"routeStr": str,
})
}
}
@@ -111,6 +111,10 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
server.AddRoute(rest.Route{
Method: http.MethodPost, Path: "/api/internal/dock/pallet/set", Handler: internal(production.SetDockPalletInternalHandler(serverCtx)),
})
// 可生产数量:WMS 打开页面时实时拉取(不做定时推送)
server.AddRoute(rest.Route{
Method: http.MethodGet, Path: "/api/internal/producible", Handler: internal(production.ProducibleInternalHandler(serverCtx)),
})
// ---------- 产线点位/移料(供工位终端调用,X-API-TOKEN 保护)----------
server.AddRoute(rest.Route{
@@ -134,6 +138,9 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
server.AddRoute(rest.Route{
Method: http.MethodPost, Path: "/api/internal/line/occupy", Handler: internal(OccupyPointHandler(serverCtx)),
})
server.AddRoute(rest.Route{
Method: http.MethodPost, Path: "/api/internal/station/receive-material", Handler: internal(production.StationReceiveMaterialInternalHandler(serverCtx)),
})
// ---------- JWT 业务 API ----------
jwtRoutes := []rest.Route{
@@ -165,6 +172,8 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
{Method: http.MethodPost, Path: "/material-requests/generate", Handler: production.GenerateMaterialHandler(serverCtx)},
{Method: http.MethodGet, Path: "/material-requests", Handler: production.ListMaterialRequestsHandler(serverCtx)},
{Method: http.MethodPost, Path: "/material-requests/receive", Handler: production.ReceiveMaterialRequestHandler(serverCtx)},
{Method: http.MethodGet, Path: "/docks", Handler: production.ListDocksHandler(serverCtx)},
{Method: http.MethodPost, Path: "/plc/send-process", Handler: production.PlcSendProcessHandler(serverCtx)},
{Method: http.MethodGet, Path: "/plc/send-logs", Handler: production.PlcSendLogsHandler(serverCtx)},
@@ -247,6 +256,11 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
{Method: http.MethodGet, Path: "/process-card", Handler: ProcessCardHandler(serverCtx)},
// 产品型号级全景流程卡(D2):按型号聚合 工位组合→工艺流程/步骤→物料清单→检验卡项
{Method: http.MethodGet, Path: "/process-card/model", Handler: ModelProcessCardHandler(serverCtx)},
// ---------- 工位派工(工位↔工序,替代原工序组合串) ----------
{Method: http.MethodGet, Path: "/station-process", Handler: production.ListStationProcessHandler(serverCtx)},
{Method: http.MethodPost, Path: "/station-process/save", Handler: production.SaveStationProcessHandler(serverCtx)},
{Method: http.MethodGet, Path: "/station-process/route", Handler: production.RouteStationProcessHandler(serverCtx)},
}
server.AddRoutes(
rest.WithMiddlewares([]rest.Middleware{permissionGuard(serverCtx)}, jwtRoutes...),