feat: 重构BOM架构,新增基础设置与虚拟工位管理功能
本次重构删除原有BOM物料清单表,改用工单工艺组合×工艺物料清单作为唯一用料来源;新增系统基础设置表支持WMS地址、日志保留天数等配置,新增虚拟工位作业管理后台接口与前端页签控制功能,同时优化工位号校验逻辑、事件日志自动清理与工单用料查询能力。
This commit is contained in:
@@ -89,18 +89,6 @@ func UploadPhotoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// QueryBomMaterialHandler GET /quality/bom-material?keyword= 按物料编码或名称/图号检索 BOM 料
|
||||
func QueryBomMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := logic.New(svcCtx).QueryBomMaterial(r.Context(), r.URL.Query().Get("keyword"))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2304, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// InspectionDisposalHandler POST /quality/disposal 质量检验处置(退货/返修/退换)
|
||||
func InspectionDisposalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -11,30 +11,29 @@ import (
|
||||
// pathPermMap 请求方法+路径 → 所需按钮级权限码(块2:后端接口兜底校验)。
|
||||
// 路径中的 :id 统一以 * 结尾匹配。
|
||||
var pathPermMap = map[string]string{
|
||||
"POST:/api/v1/product-types": "produce.product:add",
|
||||
"PUT:/api/v1/product-types": "produce.product:edit",
|
||||
"DELETE:/api/v1/product-types/*": "produce.product:delete",
|
||||
"POST:/api/v1/work-orders": "produce.workorder:add",
|
||||
"PUT:/api/v1/work-orders": "produce.workorder:edit",
|
||||
"POST:/api/v1/work-orders/status": "produce.workorder:edit",
|
||||
"POST:/api/v1/work-orders/auto-schedule": "produce.workorder:dailyplan",
|
||||
"DELETE:/api/v1/work-orders/*": "produce.workorder:delete",
|
||||
"POST:/api/v1/daily-plans": "produce.workorder:dailyplan",
|
||||
"PUT:/api/v1/bom": "produce.bom:edit",
|
||||
"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/product-types": "produce.product:add",
|
||||
"PUT:/api/v1/product-types": "produce.product:edit",
|
||||
"DELETE:/api/v1/product-types/*": "produce.product:delete",
|
||||
"POST:/api/v1/work-orders": "produce.workorder:add",
|
||||
"PUT:/api/v1/work-orders": "produce.workorder:edit",
|
||||
"POST:/api/v1/work-orders/status": "produce.workorder:edit",
|
||||
"POST:/api/v1/work-orders/auto-schedule": "produce.workorder:dailyplan",
|
||||
"DELETE:/api/v1/work-orders/*": "produce.workorder:delete",
|
||||
"POST:/api/v1/daily-plans": "produce.workorder:dailyplan",
|
||||
"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",
|
||||
"DELETE:/api/v1/process-flows/*": "produce.processflow:delete",
|
||||
"POST:/api/v1/process-flows/upload": "produce.processflow:upload",
|
||||
"POST:/api/v1/process-steps": "produce.processflow:edit",
|
||||
"POST:/api/v1/stations": "produce.station:edit",
|
||||
"DELETE:/api/v1/stations/*": "produce.station:delete",
|
||||
"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",
|
||||
"DELETE:/api/v1/process-flows/*": "produce.processflow:delete",
|
||||
"POST:/api/v1/process-flows/upload": "produce.processflow:upload",
|
||||
"POST:/api/v1/process-steps": "produce.processflow:edit",
|
||||
"POST:/api/v1/stations": "produce.station:edit",
|
||||
"DELETE:/api/v1/stations/*": "produce.station:delete",
|
||||
// 基础设置(WMS 地址/需求窗口/外推参数/日志保留)
|
||||
"PUT:/api/v1/settings": "sys.settings:edit",
|
||||
// 过程巡检汇总生成《工序间检验记录》(巡检终端操作域)
|
||||
"POST:/api/v1/inspections/generate-inter-process": "sys.inspect:process",
|
||||
// 质量检验处置(过程检/完工检不合格处置 退货/返修/退换)
|
||||
@@ -44,16 +43,16 @@ var pathPermMap = map[string]string{
|
||||
"POST:/api/v1/attachment/delete": "sys.attachment:manage",
|
||||
"POST:/api/v1/attachment/archive": "sys.attachment:manage",
|
||||
// 账号管理(拆分自原 sys.rbac:user;sys.account 菜单下增/改/删)
|
||||
"POST:/api/v1/users": "sys.account:add",
|
||||
"PUT:/api/v1/users": "sys.account:edit",
|
||||
"DELETE:/api/v1/users/*": "sys.account:delete",
|
||||
"POST:/api/v1/users": "sys.account:add",
|
||||
"PUT:/api/v1/users": "sys.account:edit",
|
||||
"DELETE:/api/v1/users/*": "sys.account:delete",
|
||||
// 角色管理(拆分自原 sys.rbac:role/perm;sys.role 菜单下增/改/删)
|
||||
"POST:/api/v1/roles": "sys.role:add",
|
||||
"PUT:/api/v1/roles": "sys.role:edit",
|
||||
"DELETE:/api/v1/roles/*": "sys.role:delete",
|
||||
"POST:/api/v1/permissions": "sys.role:perm",
|
||||
"PUT:/api/v1/permissions": "sys.role:perm",
|
||||
"DELETE:/api/v1/permissions/*": "sys.role:perm",
|
||||
"POST:/api/v1/roles": "sys.role:add",
|
||||
"PUT:/api/v1/roles": "sys.role:edit",
|
||||
"DELETE:/api/v1/roles/*": "sys.role:delete",
|
||||
"POST:/api/v1/permissions": "sys.role:perm",
|
||||
"PUT:/api/v1/permissions": "sys.role:perm",
|
||||
"DELETE:/api/v1/permissions/*": "sys.role:perm",
|
||||
}
|
||||
|
||||
// userHasPerm 校验当前登录用户是否拥有某权限码(SUPER_ADMIN 或 * 放行)。
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/bomitem"
|
||||
"bj_power_mes/ent/processflow"
|
||||
"bj_power_mes/ent/processstep"
|
||||
"bj_power_mes/ent/station"
|
||||
@@ -267,56 +266,43 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
|
||||
card.Stations = append(card.Stations, ms)
|
||||
}
|
||||
|
||||
// 5) 物料清单(按 BOM 名称分组;工艺清单经 flow_material + process_flow 映射)
|
||||
// 5) 物料清单(按工艺分组的工艺物料清单——唯一用料来源)
|
||||
if productCode != "" {
|
||||
// 物料编码 → 使用该物料的工艺名列表(工艺物料清单是装配绑定的唯一依据)
|
||||
flowNames := map[string][]string{}
|
||||
if fms, e := client.FlowMaterial.Query().All(ctx); e == nil {
|
||||
flowAll, _ := client.ProcessFlow.Query().All(ctx)
|
||||
nameById := map[int]string{}
|
||||
for _, f := range flowAll {
|
||||
nameById[f.ID] = f.Name
|
||||
}
|
||||
for _, fm := range fms {
|
||||
if n := nameById[fm.FlowId]; n != "" {
|
||||
flowNames[fm.MaterialCode] = append(flowNames[fm.MaterialCode], n)
|
||||
}
|
||||
}
|
||||
// 物料分组:按工艺分组的工艺物料清单(BOM 已删除,工艺物料清单是唯一用料来源)
|
||||
flowAll, _ := client.ProcessFlow.Query().All(ctx)
|
||||
nameById := map[int]string{}
|
||||
for _, f := range flowAll {
|
||||
nameById[f.ID] = f.Name
|
||||
}
|
||||
items, _ := client.BomItem.Query().
|
||||
Where(bomitem.ProductCode(productCode)).
|
||||
Order(ent.Asc(bomitem.FieldBomName), ent.Asc(bomitem.FieldMaterialCode)).All(ctx)
|
||||
groupIdx := map[string]int{}
|
||||
for _, it := range items {
|
||||
name := it.BomName
|
||||
if name == "" {
|
||||
name = "默认"
|
||||
if fms, e := client.FlowMaterial.Query().All(ctx); e == nil {
|
||||
groupIdx := map[int]int{}
|
||||
for _, fm := range fms {
|
||||
name := nameById[fm.FlowId]
|
||||
if name == "" {
|
||||
name = "未命名工艺"
|
||||
}
|
||||
gi, ok := groupIdx[fm.FlowId]
|
||||
if !ok {
|
||||
card.BomGroups = append(card.BomGroups, modelBomGroup{BomName: name})
|
||||
gi = len(card.BomGroups) - 1
|
||||
groupIdx[fm.FlowId] = gi
|
||||
}
|
||||
card.BomGroups[gi].Items = append(card.BomGroups[gi].Items, modelBomItem{
|
||||
MaterialCode: fm.MaterialCode,
|
||||
MaterialName: fm.MaterialName,
|
||||
Spec: fm.Spec,
|
||||
Unit: fm.Unit,
|
||||
ManageMode: manageModeCN(fm.ManageMode),
|
||||
FlowNames: name,
|
||||
UnitQty: f2s(fm.UnitQty),
|
||||
LossRate: f2s(fm.LossRate),
|
||||
RequiredQty: "-",
|
||||
})
|
||||
}
|
||||
gi, ok := groupIdx[name]
|
||||
if !ok {
|
||||
card.BomGroups = append(card.BomGroups, modelBomGroup{BomName: name})
|
||||
gi = len(card.BomGroups) - 1
|
||||
groupIdx[name] = gi
|
||||
}
|
||||
fnames := "-"
|
||||
if ns := flowNames[it.MaterialCode]; len(ns) > 0 {
|
||||
fnames = strings.Join(ns, "、")
|
||||
}
|
||||
card.BomGroups[gi].Items = append(card.BomGroups[gi].Items, modelBomItem{
|
||||
MaterialCode: it.MaterialCode,
|
||||
MaterialName: it.MaterialName,
|
||||
Spec: it.Spec,
|
||||
Unit: it.Unit,
|
||||
ManageMode: manageModeCN(it.ManageMode),
|
||||
FlowNames: fnames,
|
||||
UnitQty: f2s(it.UnitQty),
|
||||
LossRate: f2s(it.LossRate),
|
||||
RequiredQty: f2s(it.RequiredQty),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
card.Summary = fmt.Sprintf("共 %d 个工位、%d 项检验卡项、%d 份物料清单",
|
||||
card.Summary = fmt.Sprintf("共 %d 个工位、%d 项检验卡项、%d 个工艺物料分组",
|
||||
card.StationCount, len(card.CheckItems), len(card.BomGroups))
|
||||
return card, nil
|
||||
}
|
||||
|
||||
@@ -106,3 +106,9 @@ func BindPanelInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// BindPanelAdminHandler GET /api/v1/workpiece/bind-panel?sn=&flowId=
|
||||
// MES 管理后台「虚拟工位作业」页拉取绑定面板(与终端同逻辑,JWT 保护)
|
||||
func BindPanelAdminHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return BindPanelInternalHandler(svcCtx)
|
||||
}
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
package production
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/svc"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
// ---------- BOM ----------
|
||||
|
||||
// ImportBomHandler POST /bom/import 产品物料清单(BOM) Excel 导入。
|
||||
// 模板列:产品编号|物料清单名称|物料编码|名称|规格|单位|单台用量|损耗率|相关标准|备注(原「装配工位」列忽略)
|
||||
func ImportBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(svcCtx.Config.Upload.MaxMB << 20); err != nil {
|
||||
httpx.BadRequest(w, "上传文件过大或格式错误")
|
||||
return
|
||||
}
|
||||
f, _, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
httpx.BadRequest(w, "请选择 Excel 文件")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
xlsx, err := excelize.OpenReader(f)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3109, "Excel 解析失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
sheet := xlsx.GetSheetName(0)
|
||||
rows, err := xlsx.GetRows(sheet)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3110, "读取 Excel 失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
// 按 (产品编号, 物料清单名称) 分组,每组一次 SaveBom(覆盖式 upsert)
|
||||
type key struct{ pc, bn string }
|
||||
groups := map[key][]logic.BomItemReq{}
|
||||
order := []key{}
|
||||
count := 0
|
||||
for i, row := range rows {
|
||||
if i == 0 {
|
||||
continue // 跳过表头
|
||||
}
|
||||
if len(row) < 3 {
|
||||
continue
|
||||
}
|
||||
productCode := trimCell(row, 0)
|
||||
bomName := trimCell(row, 1)
|
||||
materialCode := trimCell(row, 2)
|
||||
if productCode == "" || materialCode == "" {
|
||||
continue
|
||||
}
|
||||
if bomName == "" {
|
||||
bomName = "默认"
|
||||
}
|
||||
item := logic.BomItemReq{
|
||||
ProductCode: productCode,
|
||||
BomName: bomName,
|
||||
MaterialCode: materialCode,
|
||||
MaterialName: trimCell(row, 3),
|
||||
Spec: trimCell(row, 4),
|
||||
Unit: trimCell(row, 5),
|
||||
ManageMode: "2",
|
||||
UnitQty: parseFloat(trimCell(row, 6)),
|
||||
LossRate: parseFloat(trimCell(row, 7)),
|
||||
RelatedStandard: trimCell(row, 9),
|
||||
}
|
||||
k := key{productCode, bomName}
|
||||
if _, ok := groups[k]; !ok {
|
||||
order = append(order, k)
|
||||
}
|
||||
groups[k] = append(groups[k], item)
|
||||
count++
|
||||
}
|
||||
if count == 0 {
|
||||
httpx.Fail(w, 3111, "未解析到有效物料清单行(至少需产品编号与图号)")
|
||||
return
|
||||
}
|
||||
for _, k := range order {
|
||||
if err := logic.New(svcCtx).SaveBom(r.Context(), k.pc, k.bn, groups[k], operator(r, "")); err != nil {
|
||||
httpx.Fail(w, 3112, "导入失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
httpx.OkMessage(w, "导入成功", map[string]any{"count": count})
|
||||
}
|
||||
}
|
||||
|
||||
func trimCell(row []string, i int) string {
|
||||
if i >= len(row) {
|
||||
return ""
|
||||
}
|
||||
return strconvTrimSpace(row[i])
|
||||
}
|
||||
|
||||
func strconvTrimSpace(s string) string {
|
||||
out := []rune{}
|
||||
for _, c := range s {
|
||||
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
|
||||
continue
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func parseFloat(s string) float64 {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func SaveBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
ProductCode string `json:"productCode"`
|
||||
BomName string `json:"bomName"` // 同型号多份 BOM 并存时的名称(空=「默认」)
|
||||
Items []logic.BomItemReq `json:"items"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if req.ProductCode == "" {
|
||||
httpx.BadRequest(w, "请选择产品编码")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).SaveBom(r.Context(), req.ProductCode, req.BomName, req.Items, operator(r, "")); err != nil {
|
||||
httpx.Fail(w, 3101, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "保存成功", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func ListBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := logic.New(svcCtx).ListBom(r.Context(), r.URL.Query().Get("productCode"), r.URL.Query().Get("bomName"))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3102, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// ListBomNamesHandler GET /bom/names?productCode= — 型号下并存的 BOM 名称列表(工单建单选 BOM 用)
|
||||
func ListBomNamesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := logic.New(svcCtx).ListBomNames(r.Context(), r.URL.Query().Get("productCode"))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3107, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteBomItemHandler POST /bom/item/delete {id} — 移除 BOM 中一条物料(按行主键)
|
||||
func DeleteBomItemHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Id int `json:"id"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil || req.Id <= 0 {
|
||||
httpx.BadRequest(w, "缺少 id")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).DeleteBomItem(r.Context(), req.Id, operator(r, "")); err != nil {
|
||||
httpx.Fail(w, 3108, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "已移除", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 备料单 ----------
|
||||
|
||||
// GenerateMaterialHandler 按日排产生成备料单(生成前校验 BOM 物料在 WMS 档案存在)
|
||||
func GenerateMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
PlanDate string `json:"planDate"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
count, missing, err := logic.New(svcCtx).GenerateMaterialRequest(r.Context(), req.PlanDate, operator(r, ""))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3103, err.Error())
|
||||
return
|
||||
}
|
||||
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})
|
||||
}
|
||||
}
|
||||
|
||||
// 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).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"),
|
||||
RequestNo: q.Get("requestNo"),
|
||||
})
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3104, err.Error())
|
||||
return
|
||||
}
|
||||
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(), 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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 接驳台 / 接料确认 ----------
|
||||
// 接驳台查询/维护接口见 dock.go(真源已迁 MES 本地 dock 表)。
|
||||
|
||||
// ReceiveMaterialRequestHandler POST /material-requests/receive 接料确认(MES 备料页人工点「已接料」)。
|
||||
// qty 可空(≤0 表示确认本批全部剩余);累加 receivedQty(已接料),上限为已发出量。
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package production
|
||||
package production
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/svc"
|
||||
)
|
||||
|
||||
@@ -31,3 +32,158 @@ func ListMaterialsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
httpx.Ok(w, list)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 备料单 ----------
|
||||
|
||||
// GenerateMaterialHandler 按日排产生成备料单(生成前校验 BOM 物料在 WMS 档案存在)
|
||||
func GenerateMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
PlanDate string `json:"planDate"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
count, missing, err := logic.New(svcCtx).GenerateMaterialRequest(r.Context(), req.PlanDate, operator(r, ""))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3103, err.Error())
|
||||
return
|
||||
}
|
||||
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})
|
||||
}
|
||||
}
|
||||
|
||||
// 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).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"),
|
||||
RequestNo: q.Get("requestNo"),
|
||||
})
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3104, err.Error())
|
||||
return
|
||||
}
|
||||
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(), 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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 接驳台 / 接料确认 ----------
|
||||
// 接驳台查询/维护接口见 dock.go(真源已迁 MES 本地 dock 表)。
|
||||
|
||||
// ReceiveMaterialRequestHandler POST /material-requests/receive 接料确认(MES 备料页人工点「已接料」)。
|
||||
// qty 可空(≤0 表示确认本批全部剩余);累加 receivedQty(已接料),上限为已发出量。
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,11 +154,11 @@ func DoneWorkpieceInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// BomInternalHandler BOM 物料清单(工位终端上线位「领料确认」按物料清单展示应领料)。
|
||||
// GET ?productCode=&bomName=,返回 []*ent.BomItem。
|
||||
func BomInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
// OrderUsageInternalHandler 工单单台用料(工位终端上线位「领料确认」按工单工艺组合的物料清单展示应领料)。
|
||||
// GET ?orderNo=,返回 []logic.UsageRow。BOM 已删除:用料唯一来源 = 工单工艺组合×工艺物料清单。
|
||||
func OrderUsageInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := logic.New(svcCtx).ListBom(r.Context(), r.URL.Query().Get("productCode"), r.URL.Query().Get("bomName"))
|
||||
data, err := logic.New(svcCtx).GetOrderUsage(r.Context(), r.URL.Query().Get("orderNo"))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2009, err.Error())
|
||||
return
|
||||
@@ -167,6 +167,11 @@ func BomInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// OrderUsageHandler GET /order-usage?orderNo= 工单单台用料(管理后台工单详情只读汇总展示)
|
||||
func OrderUsageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return OrderUsageInternalHandler(svcCtx)
|
||||
}
|
||||
|
||||
// ---------- 看板内部接口 ----------
|
||||
|
||||
// DashboardSnapshotInternalHandler 看板全量快照。
|
||||
|
||||
@@ -95,7 +95,7 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
Method: http.MethodPost, Path: "/api/internal/workpiece/done", Handler: internal(production.DoneWorkpieceInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/bom", Handler: internal(production.BomInternalHandler(serverCtx)),
|
||||
Method: http.MethodGet, Path: "/api/internal/order-usage", Handler: internal(production.OrderUsageInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/material-requests", Handler: internal(production.ListMaterialRequestsInternalHandler(serverCtx)),
|
||||
@@ -177,16 +177,12 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{Method: http.MethodPost, Path: "/daily-plans/:id/status", Handler: production.DailyPlanStatusHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/daily-plans/suggest-docks", Handler: production.SuggestedDockCodesHandler(serverCtx)},
|
||||
|
||||
{Method: http.MethodPut, Path: "/bom", Handler: production.SaveBomHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/bom", Handler: production.ListBomHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/bom/names", Handler: production.ListBomNamesHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/bom/item/delete", Handler: production.DeleteBomItemHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/bom/import", Handler: production.ImportBomHandler(serverCtx)},
|
||||
|
||||
{Method: http.MethodPost, Path: "/material-requests/generate", Handler: production.GenerateMaterialHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/material-requests", Handler: production.ListMaterialRequestsHandler(serverCtx)},
|
||||
// 物料档案(代理 WMS /api/internal/materials,WMS 是物料主数据源;WMS 不可达返回空列表)
|
||||
{Method: http.MethodGet, Path: "/materials", Handler: production.ListMaterialsHandler(serverCtx)},
|
||||
// 工单单台用料(BOM 已删除:工单详情页只读汇总 = 工单工艺组合×工艺物料清单)
|
||||
{Method: http.MethodGet, Path: "/order-usage", Handler: production.OrderUsageHandler(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 已接料 → 在途/线边)
|
||||
@@ -263,11 +259,21 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{Method: http.MethodPost, Path: "/inspections/upload", Handler: UploadPhotoHandler(serverCtx)},
|
||||
// P0-4:一键生成《工序间检验记录》(汇总该工单 PROCESS 类巡检,按步骤维度)
|
||||
// ---------- MES 质量检验(过程检/完工检,来料检在 WMS 已做仍落本表) ----------
|
||||
{Method: http.MethodGet, Path: "/quality/bom-material", Handler: QueryBomMaterialHandler(serverCtx)},
|
||||
|
||||
{Method: http.MethodPost, Path: "/quality/disposal", Handler: InspectionDisposalHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/quality/unqualified-report", Handler: UnqualifiedReportHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/inspections/generate-inter-process", Handler: GenerateInterProcessInspectionHandler(serverCtx)},
|
||||
|
||||
// ---------- 虚拟工位作业(管理后台,与终端同逻辑;虚拟工位无实体屏,手机/电脑登录操作) ----------
|
||||
{Method: http.MethodGet, Path: "/station/task", Handler: StationTaskAdminHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/station/report", Handler: StationReportAdminHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/workpiece/bind-panel", Handler: production.BindPanelAdminHandler(serverCtx)},
|
||||
|
||||
// ---------- 系统基础设置(系统管理员;保存即生效无需重启) ----------
|
||||
{Method: http.MethodGet, Path: "/settings", Handler: SettingsHandler(serverCtx)},
|
||||
{Method: http.MethodPut, Path: "/settings", Handler: SaveSettingsHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/settings/wms-test", Handler: WmsTestHandler(serverCtx)},
|
||||
|
||||
// ---------- 应急呼叫(D3,MES 前端亦可发起) ----------
|
||||
{Method: http.MethodPost, Path: "/alert/emergency", Handler: EmergencyCallHandler(serverCtx)},
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// SettingsHandler GET /api/v1/settings 基础设置 + 本机基础信息(只读)
|
||||
func SettingsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := logic.New(svcCtx).GetSettings(r.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2501, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// SaveSettingsHandler PUT /api/v1/settings 保存基础设置(WMS 地址热切换;sys.settings:edit 权限)
|
||||
func SaveSettingsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req logic.SettingsReq
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).SaveSettings(r.Context(), req, operator(r, "")); err != nil {
|
||||
httpx.Fail(w, 2502, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "已保存并生效", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// WmsTestHandler GET /api/v1/settings/wms-test?url= 测试 WMS 连通性(url 为空测当前生效地址)
|
||||
func WmsTestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
msg, err := logic.New(svcCtx).TestWms(r.Context(), r.URL.Query().Get("url"))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2503, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, msg, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// StartEventLogRetention 事件日志保留期清理:每小时检查一次(保留天数在「基础设置」页
|
||||
// 配置 eventlog.retention.days,0=不清理),删除超过保留期的操作日志。
|
||||
func StartEventLogRetention(ctx *svc.ServiceContext) {
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
days := logic.New(ctx).CfgInt(context.Background(), logic.CfgLogRetention, 0)
|
||||
if days <= 0 {
|
||||
continue
|
||||
}
|
||||
n, err := ctx.EventLog.CleanupBefore(context.Background(), time.Now().AddDate(0, 0, -days))
|
||||
if err != nil {
|
||||
logx.Errorf("事件日志清理失败: %v", err)
|
||||
continue
|
||||
}
|
||||
if n > 0 {
|
||||
logx.Infof("事件日志保留 %d 天:已清理 %d 条过期记录", days, n)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -105,7 +105,8 @@ func StationChangePasswordInternalHandler(svcCtx *svc.ServiceContext) http.Handl
|
||||
func StationTaskInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
stationNo := atoiDefault(r.URL.Query().Get("stationNo"), 0)
|
||||
if stationNo <= 0 {
|
||||
// 0=上线位为合法虚拟工位,仅负数视为未配置
|
||||
if stationNo < 0 {
|
||||
httpx.BadRequest(w, "缺少 stationNo 参数")
|
||||
return
|
||||
}
|
||||
@@ -118,6 +119,16 @@ func StationTaskInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// StationTaskAdminHandler GET /station/task?stationNo= 管理后台「虚拟工位作业」页拉取任务(与终端同逻辑)
|
||||
func StationTaskAdminHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return StationTaskInternalHandler(svcCtx)
|
||||
}
|
||||
|
||||
// StationReportAdminHandler POST /station/report 管理后台「虚拟工位作业」报工(与终端同逻辑,operator=登录账号)
|
||||
func StationReportAdminHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return StationReportInternalHandler(svcCtx)
|
||||
}
|
||||
|
||||
// StationReportInternalHandler 工位终端工艺报工(写工位实绩 + 步骤考核)
|
||||
func StationReportInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user