package handler import ( "errors" "net/http" "strings" "bj_power_wms/ent" "bj_power_wms/ent/dock" "bj_power_wms/internal/svc" ) // dockRow 接驳台返回 DTO(显式 json 标签,避免依赖 ent 默认编组)。 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"` } // listDocksInternalHandler 返回接驳台主数据列表(dock 真源在 WMS)。 // MES 工位维护页拉取此接口填充「送达接驳台」下拉,避免写死 DOCK01..20 留后患。 func listDocksInternalHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { list, err := ctx.EntClient.Dock.Query(). Order(ent.Asc("dock_code")).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "查询接驳台失败: "+err.Error()) return } rows := make([]dockRow, 0, len(list)) for _, d := range list { rows = append(rows, dockRow{ DockCode: d.DockCode, Name: d.Name, DockType: d.DockType, StationNo: d.StationNo, HasPallet: d.HasPallet, Status: d.Status, }) } ok(w, map[string]any{"list": rows}) } } // ---------- 接驳台主数据维护 ---------- // 接驳台是独立主数据:除产线工位旁,库房、缓存区等其他位置也有接驳台位。 // 类型:line=产线(绑定工位,与工位 1:1)/ store=库房 / other=其他(均不绑定工位)。 // dockMaintRow 接驳台维护页返回行(显式 json,避免 ent omitempty 吞掉 hasPallet=false) type dockMaintRow 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 dockMaintRowOf(d *ent.Dock) dockMaintRow { return dockMaintRow{ 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, } } // listDocksMaintHandler GET /api/docks?dockType=&keyword=&page=&pageSize= // 接驳台主数据分页查询(真分页在 SQL 层),默认按创建时间倒序。 func listDocksMaintHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() query := ctx.EntClient.Dock.Query() if typ := q.Get("dockType"); typ != "" { query = query.Where(dock.DockType(typ)) } if kw := strings.TrimSpace(q.Get("keyword")); kw != "" { query = query.Where(dock.Or( dock.DockCodeContainsFold(kw), dock.NameContainsFold(kw), )) } total, err := query.Count(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "查询接驳台失败: "+err.Error()) return } page := atoi(q.Get("page"), 1) size := atoi(q.Get("pageSize"), 20) if page < 1 { page = 1 } if size < 1 || size > 200 { size = 20 } list, err := query. Order(ent.Desc("created_at"), ent.Desc("id")). Offset((page - 1) * size).Limit(size).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "查询接驳台失败: "+err.Error()) return } rows := make([]dockMaintRow, 0, len(list)) for _, d := range list { rows = append(rows, dockMaintRowOf(d)) } ok(w, map[string]any{"list": rows, "total": total, "page": page, "pageSize": size}) } } // dockReq 接驳台保存请求 type dockReq 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"` } // validateDock 校验接驳台字段: // - 编码/名称必填,编码全库唯一; // - 类型仅 line/store/other; // - 产线接驳台必须绑定工位号(>0)且与工位 1:1;库房/其他接驳台不绑定工位(固定 0)。 func validateDock(ctx *svc.ServiceContext, req *dockReq, selfID int64) error { req.DockCode = strings.TrimSpace(req.DockCode) req.Name = strings.TrimSpace(req.Name) if req.DockCode == "" { return errors.New("接驳台编码必填") } if req.Name == "" { return errors.New("接驳台名称必填") } switch req.DockType { case "line", "store", "other": default: return errors.New("接驳台类型仅支持 产线/库房/其他") } if req.Status != "ENABLED" && req.Status != "DISABLED" { return errors.New("接驳台状态仅支持 启用/停用") } if req.DockType != "line" { req.StationNo = 0 } else if req.StationNo <= 0 { return errors.New("产线接驳台必须绑定工位号") } dupCode := ctx.EntClient.Dock.Query().Where(dock.DockCode(req.DockCode)) if selfID > 0 { dupCode = dupCode.Where(dock.IDNEQ(int(selfID))) } if exists, err := dupCode.Exist(ctx0()); err == nil && exists { return errors.New("接驳台编码已存在:" + req.DockCode) } if req.StationNo > 0 { q := ctx.EntClient.Dock.Query().Where(dock.StationNo(req.StationNo)) if selfID > 0 { q = q.Where(dock.IDNEQ(int(selfID))) } other, err := q.First(ctx0()) if err == nil && other != nil { return errors.New("该工位已绑定接驳台 " + other.DockCode + ",一个工位只能有一个接驳台") } } return nil } // createDockHandler POST /api/docks 新增接驳台 func createDockHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req dockReq if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") return } if req.DockType == "" { req.DockType = "line" } if req.Status == "" { req.Status = "ENABLED" } if err := validateDock(ctx, &req, 0); err != nil { fail(w, http.StatusBadRequest, err.Error()) return } _, err := ctx.EntClient.Dock.Create(). SetDockCode(req.DockCode).SetName(req.Name).SetDockType(req.DockType). SetStationNo(req.StationNo).SetHasPallet(false).SetStatus(req.Status). Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "新增接驳台失败: "+err.Error()) return } ctx.EventLog.Write(ctx0(), "dock.create", r.Header.Get("X-Username"), "dock", req.DockCode, "新增接驳台", map[string]any{"dockCode": req.DockCode, "dockType": req.DockType, "stationNo": req.StationNo}) ok(w, map[string]any{"message": "接驳台已新增"}) } } // updateDockHandler POST /api/docks/update 修改接驳台(编码不可改,是产线/AGV 任务的引用锚点) func updateDockHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req dockReq if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") return } if req.ID <= 0 { fail(w, http.StatusBadRequest, "缺少 id") return } old, err := ctx.EntClient.Dock.Get(ctx0(), int(req.ID)) if err != nil { fail(w, http.StatusBadRequest, "接驳台不存在") return } req.DockCode = old.DockCode if err := validateDock(ctx, &req, req.ID); err != nil { fail(w, http.StatusBadRequest, err.Error()) return } _, err = old.Update(). SetName(req.Name).SetDockType(req.DockType). SetStationNo(req.StationNo).SetStatus(req.Status).Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "修改接驳台失败: "+err.Error()) return } ctx.EventLog.Write(ctx0(), "dock.update", r.Header.Get("X-Username"), "dock", old.DockCode, "修改接驳台", map[string]any{"dockCode": old.DockCode, "dockType": req.DockType, "stationNo": req.StationNo}) ok(w, map[string]any{"message": "接驳台已保存"}) } } // deleteDockHandler POST /api/docks/delete 删除接驳台 // 正在占用(有托盘)的接驳台不允许删除,避免 AGV/产线任务指向已删除的接驳台。 func deleteDockHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { ID int64 `json:"id"` } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") return } if req.ID <= 0 { fail(w, http.StatusBadRequest, "缺少 id") return } d, err := ctx.EntClient.Dock.Get(ctx0(), int(req.ID)) if err != nil { fail(w, http.StatusBadRequest, "接驳台不存在") return } if d.HasPallet { fail(w, http.StatusBadRequest, "接驳台 "+d.DockCode+" 当前有托盘占用,请先处理托盘后再删除") return } if err := ctx.EntClient.Dock.DeleteOneID(d.ID).Exec(ctx0()); err != nil { fail(w, http.StatusInternalServerError, "删除接驳台失败: "+err.Error()) return } ctx.EventLog.Write(ctx0(), "dock.delete", r.Header.Get("X-Username"), "dock", d.DockCode, "删除接驳台", map[string]any{"dockCode": d.DockCode}) ok(w, map[string]any{"message": "接驳台已删除"}) } }