Files
bj_power/bj_power_wms/internal/handler/dock.go
T
SunYF 37f10d3a13 refactor: 重构产线工位与备料流程,移除冗余字段
1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段
2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据
3. 重构日排产为工位产量分配模式,替代原接驳台列表
4. 新增备料单工位级字段与分批出库支持
5. 移除定时同步可生产数量,改为WMS实时拉取接口
6. 过滤操作日志,排除登录/退出相关日志
7. 调整仪表盘工序展示逻辑为今日派工路线
8. 新增接驳台维护菜单与基础数据
2026-09-19 07:50:21 +08:00

278 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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": "接驳台已删除"})
}
}