feat: 完成客户第12条需求+多系统改造

1. 新增预警表添加工单号字段,支持按工单号检索预警
2. 添加工单过滤在制品/绩效报表/预警中心查询
3. 补全WMS台账字段口径与权限配置
4. 修复备料单与数量不符处理流程
5. 新增配送进度/库位联动/自动出库功能
6. 修复AGV配送状态更新逻辑
7. 优化退料与库存管理细节
This commit is contained in:
SunYF
2026-09-19 17:04:58 +08:00
parent 62e45b7379
commit 05b0b8a909
89 changed files with 7912 additions and 429 deletions
+28
View File
@@ -0,0 +1,28 @@
package handler
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// MaterialDeliveryProgressHandler GET /api/v1/material-requests/progress?orderNo=&materialCode=&page=&pageSize=
//
// 配送进度(只读聚合):WMS 台账「需求/已发出/已退回/已消耗/净领用/缺口」
// + MES 备料单「已接料」→ 派生「在途 / 线边」。
// 不新增账本、不双写:两边各管各的真源字段,本接口只做合成展示。
func MaterialDeliveryProgressHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
data, err := logic.New(svcCtx).DeliveryProgress(r.Context(),
q.Get("orderNo"), q.Get("materialCode"),
atoiDefault(q.Get("page"), 1), atoiDefault(q.Get("pageSize"), 50))
if err != nil {
httpx.Fail(w, 3204, err.Error())
return
}
httpx.Ok(w, data)
}
}
+7
View File
@@ -24,6 +24,9 @@ var pathPermMap = map[string]string{
"POST:/api/v1/bom/item/delete": "produce.bom:edit",
"POST:/api/v1/bom/import": "produce.bom:edit",
"POST:/api/v1/material-requests/generate": "produce.material:generate",
"POST:/api/v1/material-requests/auto-outbound": "produce.material:outbound",
// 数量不符处理(produce.qty 菜单下:补发/退库/调整关闭)
"POST:/api/v1/qty-reports/handle": "produce.qty:handle",
"POST:/api/v1/plc/send-process": "produce.plc:send",
"POST:/api/v1/process-flows": "produce.processflow:add",
"POST:/api/v1/process-flows/status": "produce.processflow:edit",
@@ -36,6 +39,10 @@ var pathPermMap = map[string]string{
"POST:/api/v1/inspections/generate-inter-process": "sys.inspect:process",
// 质量检验处置(过程检/完工检不合格处置 退货/返修/退换)
"POST:/api/v1/quality/disposal": "produce.quality:edit",
// 附件中心(sys.attachment 菜单下):删除 / 归档清理属管理动作;
// 列表/预览/下载不设按钮门槛(有菜单权限即可查阅)
"POST:/api/v1/attachment/delete": "sys.attachment:manage",
"POST:/api/v1/attachment/archive": "sys.attachment:manage",
// 装机绑定(报工前扫料/撤销,属报工操作域)
"POST:/api/v1/binds": "produce.scan",
"POST:/api/v1/binds/remove": "produce.scan",
@@ -81,14 +81,15 @@ func ListAlertRulesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
}
// ListAlertsHandler GET /alerts?status=&type=&from=&to=&page=&pageSize=
// 预警消息收件箱:支持 状态/类型/时间范围(YYYY-MM-DD) 筛选;带 page 返回分页对象,否则返回全量数组(顶部铃铛未读数复用)。
// ListAlertsHandler GET /alerts?status=&type=&orderNo=&from=&to=&page=&pageSize=
// 预警消息收件箱:支持 状态/类型/工单号/时间范围(YYYY-MM-DD) 筛选;带 page 返回分页对象,否则返回全量数组(顶部铃铛未读数复用)。
func ListAlertsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
query := logic.AlertQuery{
Status: q.Get("status"),
Type: q.Get("type"),
OrderNo: q.Get("orderNo"),
From: q.Get("from"),
To: q.Get("to"),
Page: atoiDefault(q.Get("page"), 0),
@@ -218,12 +218,41 @@ func GenerateMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
}
// AutoOutboundHandler POST /material-requests/auto-outbound 备料自动出库(一键补出)。
// 日常由「生成备料单」自动触发(客户第1条:平时无需操作);库存补齐后库管可在此一键补出。
func AutoOutboundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
PlanDate string `json:"planDate"`
OrderNo string `json:"orderNo"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if req.PlanDate == "" && req.OrderNo == "" {
httpx.BadRequest(w, "planDate 与 orderNo 至少填一个")
return
}
done, short := logic.New(svcCtx).AutoOutboundRequests(r.Context(), req.PlanDate, req.OrderNo, operator(r, ""))
httpx.OkMessage(w, "自动出库完成", map[string]any{"done": done, "short": short})
}
}
func ListMaterialRequestsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
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("stationNo"))
data, err := logic.New(svcCtx).ListMaterialRequests(r.Context(), logic.MaterialRequestQuery{
OrderNo: q.Get("orderNo"),
PlanDate: q.Get("planDate"),
Status: q.Get("status"),
MaterialCode: q.Get("materialCode"),
MaterialName: q.Get("materialName"),
TargetDock: q.Get("targetDock"),
StationNo: q.Get("stationNo"),
Source: q.Get("source"),
Keyword: q.Get("keyword"),
})
if err != nil {
httpx.Fail(w, 3104, err.Error())
return
@@ -237,7 +266,12 @@ 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"), q.Get("stationNo"))
data, err := logic.New(svcCtx).ListMaterialRequests(r.Context(), logic.MaterialRequestQuery{
OrderNo: q.Get("orderNo"),
PlanDate: q.Get("planDate"),
Status: q.Get("status"),
StationNo: q.Get("stationNo"),
})
if err != nil {
httpx.Fail(w, 3105, err.Error())
return
@@ -286,7 +320,7 @@ func ListDocksHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
// ReceiveMaterialRequestHandler POST /material-requests/receive 接料确认(MES 备料页人工点「已接料」)。
// qty 可空(≤0 表示确认本批全部剩余);累加 sentQty 封顶 reqQty,达量自动 DONE
// qty 可空(≤0 表示确认本批全部剩余);累加 receivedQty(已接料),上限为已发出量
func ReceiveMaterialRequestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
@@ -110,7 +110,7 @@ func OrderQueryInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func InProcessInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
stationNo := atoiDefault(r.URL.Query().Get("stationNo"), 0)
data, err := logic.New(svcCtx).WorkpiecesInProcess(r.Context(), stationNo)
data, err := logic.New(svcCtx).WorkpiecesInProcess(r.Context(), stationNo, r.URL.Query().Get("orderNo"))
if err != nil {
httpx.Fail(w, 2006, err.Error())
return
@@ -271,10 +271,11 @@ func SuggestedDockCodesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
// InProcessHandler GET /workpieces/in-process 跨天在制品视图(停在各工位未完工工件)
// stationNo 可选:0=上线位, 13=下线位, 其余为物理工位;不传或<=0 返回全部。
// orderNo 可选:按工单号模糊过滤(客户第12条「记录查看加一项看工单号查询」)。
func InProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
stationNo, _ := strconv.Atoi(r.URL.Query().Get("stationNo"))
data, err := logic.New(svcCtx).WorkpiecesInProcess(r.Context(), stationNo)
data, err := logic.New(svcCtx).WorkpiecesInProcess(r.Context(), stationNo, r.URL.Query().Get("orderNo"))
if err != nil {
httpx.Fail(w, 3001, err.Error())
return
@@ -0,0 +1,78 @@
package handler
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// ============ 数量不符处理(MES 侧:唯一真源 + 处理闭环) ============
//
// 链路:工位终端上报(/api/internal/station/qty-report)→ 本页查询/处理
// - 补发 REPLENISH:生成 source=REFILL 补料单(少发时可用)
// - 退库 RETURN :调 WMS 建退库单(多发时可用)
// - 调整 ADJUST :双方确认后直接关闭,必须填说明
// ListQtyReportsHandler GET /api/v1/qty-reports?status=&stationNo=&materialCode=&orderNo=&page=&pageSize=
// 数量不符记录分页查询(真分页:后端 SQL 层分页)。
func ListQtyReportsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
data, err := logic.New(svcCtx).ListQtyReports(r.Context(), logic.QtyReportQuery{
Status: q.Get("status"),
StationNo: q.Get("stationNo"),
MaterialCode: q.Get("materialCode"),
OrderNo: q.Get("orderNo"),
Page: atoiDefault(q.Get("page"), 1),
PageSize: atoiDefault(q.Get("pageSize"), 20),
})
if err != nil {
httpx.Fail(w, 3201, err.Error())
return
}
httpx.Ok(w, data)
}
}
// QtyReportStatsHandler GET /api/v1/qty-reports/stats
// 数量不符概览:待处理 / 已补发 / 已退库 / 已关闭。
func QtyReportStatsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).QtyReportStats(r.Context())
if err != nil {
httpx.Fail(w, 3202, err.Error())
return
}
httpx.Ok(w, data)
}
}
// HandleQtyReportHandler POST /api/v1/qty-reports/handle
// body: { id, handleType(REPLENISH/RETURN/ADJUST), note }
func HandleQtyReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
ID int `json:"id"`
HandleType string `json:"handleType"`
Note string `json:"note"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if req.HandleType != "REPLENISH" && req.HandleType != "RETURN" && req.HandleType != "ADJUST" {
httpx.BadRequest(w, "处理方式仅支持 补发/退库/调整")
return
}
// 处理人取自鉴权中间件写入的用户名(无则留空,不阻断处理)
operator := r.Header.Get("X-Username")
data, err := logic.New(svcCtx).HandleQtyReport(r.Context(), req.ID, req.HandleType, req.Note, operator)
if err != nil {
httpx.Fail(w, 3203, err.Error())
return
}
httpx.OkMessage(w, "处理完成", data)
}
}
@@ -173,6 +173,13 @@ 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.MethodPost, Path: "/material-requests/auto-outbound", Handler: production.AutoOutboundHandler(serverCtx)},
// 配送进度(只读聚合:WMS 台账四段量 + MES 已接料 → 在途/线边)
{Method: http.MethodGet, Path: "/material-requests/progress", Handler: MaterialDeliveryProgressHandler(serverCtx)},
// 数量不符处理(MES 为唯一真源与处理侧;上报入口在 /api/internal/station/qty-report
{Method: http.MethodGet, Path: "/qty-reports", Handler: ListQtyReportsHandler(serverCtx)},
{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.MethodPost, Path: "/plc/send-process", Handler: production.PlcSendProcessHandler(serverCtx)},
+2 -2
View File
@@ -139,7 +139,7 @@ func StationReportInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func WorkloadInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), q.Get("from"), q.Get("to"),
data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), q.Get("orderNo"), q.Get("from"), q.Get("to"),
atoiDefault(q.Get("opPage"), 1), atoiDefault(q.Get("opPageSize"), 20),
atoiDefault(q.Get("stPage"), 1), atoiDefault(q.Get("stPageSize"), 20),
atoiDefault(q.Get("detPage"), 1), atoiDefault(q.Get("detPageSize"), 20))
@@ -209,7 +209,7 @@ func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
httpx.Fail(w, 2106, rerr.Error())
return
}
data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), startDate, endDate,
data, err := logic.New(svcCtx).Workload(r.Context(), q.Get("operator"), q.Get("stationNo"), q.Get("orderNo"), startDate, endDate,
1, 1<<30, 1, 1<<30, 1, 1<<30)
if err != nil {
httpx.Fail(w, 2105, err.Error())