feat: 完成产线与仓储系统多模块迭代升级

本次迭代覆盖MES与WMS核心业务:
1. 新增接驳台托盘传感器读取与AGV对接能力
2. 完善工单排产、备料流程与权限体系拆分
3. 优化看板接口与前端路由、样式
4. 新增操作日志、库存盘点与角色保护逻辑
5. 修复代理地址、BOM保存等已知问题
This commit is contained in:
SunYF
2026-09-04 14:18:53 +08:00
parent c1fc6d32b2
commit b6799c9de4
138 changed files with 20613 additions and 2220 deletions
@@ -45,6 +45,7 @@ func ListBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
// ---------- 备料单 ----------
// GenerateMaterialHandler 按日排产生成备料单(生成前校验 BOM 物料在 WMS 档案存在)
func GenerateMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
@@ -54,12 +55,17 @@ func GenerateMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
httpx.BadRequest(w, "请求体解析失败")
return
}
n, err := logic.New(svcCtx).GenerateMaterialRequest(r.Context(), req.PlanDate, operator(r, ""))
count, missing, err := logic.New(svcCtx).GenerateMaterialRequest(r.Context(), req.PlanDate, operator(r, ""))
if err != nil {
httpx.Fail(w, 3103, err.Error())
return
}
httpx.OkMessage(w, "生成完成", map[string]int{"count": n})
if len(missing) > 0 {
// 有物料在 WMS 档案不存在:不生成,把缺失列表返回给前端弹窗
httpx.Ok(w, map[string]any{"count": 0, "missing": missing, "blocked": true})
return
}
httpx.OkMessage(w, "生成完成", map[string]any{"count": count, "blocked": false})
}
}
@@ -73,4 +79,42 @@ func ListMaterialRequestsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
httpx.Ok(w, data)
}
}
// ListMaterialRequestsInternalHandler 内部接口:供 WMS 拉取备料单(含 target_dock),X-API-TOKEN 保护。
// WMS AGV 配送页据此展示待发料行,仓管确认 target_dock 后调用 WMS AGV 下发。
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"))
if err != nil {
httpx.Fail(w, 3105, err.Error())
return
}
httpx.Ok(w, data)
}
}
// MarkMaterialRequestStatusInternalHandler 内部接口:WMS 下发 AGV 后回写备料单状态(DELIVERING/DONE)。
// 供 WMS AGV 任务推进状态时联动 MES 备料单,保持一致。
func MarkMaterialRequestStatusInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
RequestNo string `json:"requestNo"`
Status string `json:"status"`
}
if err := httpx.ParseJSON(r, &req); err != nil || req.RequestNo == "" {
httpx.BadRequest(w, "请求体缺失")
return
}
if req.Status != "DELIVERING" && req.Status != "DONE" {
httpx.Fail(w, 3106, "非法状态")
return
}
if err := logic.New(svcCtx).SetMaterialRequestStatus(r.Context(), req.RequestNo, req.Status); err != nil {
httpx.Fail(w, 3106, err.Error())
return
}
httpx.Ok(w, nil)
}
}
@@ -0,0 +1,47 @@
package production
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/svc"
)
// DockPalletInternalHandler 内部接口:读某接驳台是否有托盘(PLC 托盘传感器)。
// WMS AGV 到达目的地前调用,确认接驳台空闲。
func DockPalletInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
dockCode := r.URL.Query().Get("dockCode")
if dockCode == "" {
httpx.BadRequest(w, "缺少 dockCode")
return
}
has, err := svcCtx.PLC.Get().QueryDockPallet(r.Context(), dockCode)
if err != nil {
httpx.Fail(w, 3201, err.Error())
return
}
httpx.Ok(w, map[string]any{"dockCode": dockCode, "hasPallet": has})
}
}
// SetDockPalletInternalHandler 内部接口:mock 联调用,手动置位某接驳台是否有托盘。
// 仅在 mock 模式下生效(真实 PLC 模式忽略,读的是真实传感器)。
func SetDockPalletInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
DockCode string `json:"dockCode"`
HasPallet bool `json:"hasPallet"`
}
if err := httpx.ParseJSON(r, &req); err != nil || req.DockCode == "" {
httpx.BadRequest(w, "请求体缺失")
return
}
svcCtx.PLC.SetMockDockPallet(req.DockCode, req.HasPallet)
if !svcCtx.PLC.IsMock() {
httpx.OkMessage(w, "真实 PLC 模式忽略手动置位", nil)
return
}
httpx.Ok(w, nil)
}
}
@@ -105,9 +105,12 @@ func OrderQueryInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
// ---------- 看板内部接口 ----------
func DashboardOverviewInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
// DashboardSnapshotInternalHandler 看板全量快照。
// 结构与本项目看板前端 src/types.ts 严格对齐(见 internal/logic/dashboard_front.go),
// 由 DashboardSnapshotInternalHandler 一次性返回,前端不再分接口拼装。
func DashboardSnapshotInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).DashboardOverview(r.Context())
data, err := logic.New(svcCtx).DashboardFrontSnapshot(r.Context())
if err != nil {
httpx.Fail(w, 2005, err.Error())
return
@@ -115,47 +118,3 @@ func DashboardOverviewInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFu
httpx.Ok(w, data)
}
}
func DashboardEquipmentInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).DashboardEquipment(r.Context())
if err != nil {
httpx.Fail(w, 2006, err.Error())
return
}
httpx.Ok(w, data)
}
}
func DashboardProgressInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).DashboardProgress(r.Context())
if err != nil {
httpx.Fail(w, 2007, err.Error())
return
}
httpx.Ok(w, data)
}
}
func DashboardAlarmsInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).DashboardAlarms(r.Context())
if err != nil {
httpx.Fail(w, 2008, err.Error())
return
}
httpx.Ok(w, data)
}
}
func DashboardTrendsInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).DashboardTrends(r.Context())
if err != nil {
httpx.Fail(w, 2009, err.Error())
return
}
httpx.Ok(w, data)
}
}
@@ -127,18 +127,19 @@ func GetWorkOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
}
// SetWorkOrderStatusHandler POST /work-orders/status(工单状态流转)
// SetWorkOrderStatusHandler POST /work-orders/status(工单状态流转,可选原因
func SetWorkOrderStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Id int `json:"id"`
Status string `json:"status"`
Reason string `json:"reason"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if err := logic.New(svcCtx).SetWorkOrderStatus(r.Context(), req.Id, req.Status, operator(r, "")); err != nil {
if err := logic.New(svcCtx).SetWorkOrderStatus(r.Context(), req.Id, req.Status, req.Reason, operator(r, "")); err != nil {
httpx.Fail(w, 3008, err.Error())
return
}
@@ -201,3 +202,16 @@ func DeleteDailyPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
httpx.Ok(w, nil)
}
}
// SuggestedDockCodesHandler 查某工单「上次使用的接驳台」,供排产默认推荐
func SuggestedDockCodesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderNo := r.URL.Query().Get("orderNo")
if orderNo == "" {
httpx.BadRequest(w, "缺少工单号")
return
}
docks := logic.New(svcCtx).SuggestedDockCodes(r.Context(), orderNo)
httpx.Ok(w, map[string]any{"dockCodes": docks})
}
}
@@ -2,6 +2,7 @@ package production
import (
"net/http"
"strconv"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
@@ -114,8 +115,10 @@ func ListEventLogsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
q := r.URL.Query()
page := atoiDefault(q.Get("page"), 1)
pageSize := atoiDefault(q.Get("pageSize"), 20)
fromMs, _ := strconv.ParseInt(q.Get("from"), 10, 64)
toMs, _ := strconv.ParseInt(q.Get("to"), 10, 64)
list, total, err := logic.New(svcCtx).ListEventLogs(r.Context(),
q.Get("orderNo"), q.Get("operator"), q.Get("eventType"), page, pageSize)
q.Get("orderNo"), q.Get("operator"), q.Get("eventType"), fromMs, toMs, page, pageSize)
if err != nil {
httpx.Fail(w, 3308, err.Error())
return