Files
bj_power/bj_power_mes/internal/handler/inspection.go
T
SunYF f274da044c feat: 重构BOM架构,新增基础设置与虚拟工位管理功能
本次重构删除原有BOM物料清单表,改用工单工艺组合×工艺物料清单作为唯一用料来源;新增系统基础设置表支持WMS地址、日志保留天数等配置,新增虚拟工位作业管理后台接口与前端页签控制功能,同时优化工位号校验逻辑、事件日志自动清理与工单用料查询能力。
2026-09-23 11:41:28 +08:00

146 lines
5.7 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 (
"net/http"
"net/url"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
"github.com/xuri/excelize/v2"
)
// CreateInspectionHandler POST /inspectionsPAD 巡检终端提交记录)
func CreateInspectionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req logic.InspectionReq
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
// 按钮级权限:按记录类型校验(块2/8)
categoryPerm := map[string]string{
"CHECKIN": "sys.inspect:checkin",
"POINT": "sys.inspect:point",
"PROCESS": "sys.inspect:process",
"DONE": "sys.inspect:done",
"ALARM": "sys.inspect:alarm",
}
// 质量检验三类(过程检 QC_PROCESS / 完工检 FINAL / 来料检 INCOMING)与 PAD 巡检共用本接口,
// 持有质量检验录入权限 produce.quality:edit 即放行,避免被巡检按钮权限误拦截。
// 注:PAD 巡检「过程巡检」仍用 PROCESS(走下方 categoryPerm 的 sys.inspect:process),与质检隔离(P1-8)。
qualityCats := map[string]bool{"QC_PROCESS": true, "FINAL": true, "INCOMING": true}
if qualityCats[req.Category] && userHasPerm(r, svcCtx, "produce.quality:edit") {
// 质检录入已校验,跳过巡检按钮权限
} else if code, ok := categoryPerm[req.Category]; ok && !userHasPerm(r, svcCtx, code) {
httpx.FailHTTP(w, http.StatusForbidden, "无操作权限:"+code)
return
}
if err := logic.New(svcCtx).CreateInspection(r.Context(), req, operator(r, "")); err != nil {
httpx.Fail(w, 2301, err.Error())
return
}
httpx.OkMessage(w, "巡检记录已提交", nil)
}
}
// ListInspectionsHandler GET /inspections?category=&operator=&from=&to=&materialCode=&materialName=&reportNo=&page=&pageSize=
// 返回 {list, total},后端真分页(created_at desc / id desc,最新置顶)
// materialCode/materialName/reportNo:质量检验模糊查询(每框内模糊、多框取交集)
func ListInspectionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
page := atoiDefault(q.Get("page"), 1)
pageSize := atoiDefault(q.Get("pageSize"), 50)
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 1000 {
pageSize = 50
}
data, total, err := logic.New(svcCtx).ListInspectionsPaged(r.Context(),
q.Get("category"), q.Get("operator"), q.Get("from"), q.Get("to"),
q.Get("orderNo"), q.Get("stationNo"),
q.Get("materialCode"), q.Get("materialName"), q.Get("reportNo"), page, pageSize)
if err != nil {
httpx.Fail(w, 2302, err.Error())
return
}
httpx.Ok(w, map[string]any{"list": data, "total": total})
}
}
// UploadPhotoHandler POST /inspections/upload(巡检/异常拍照上传)
// 统一存储:<Upload.Dir>/年/月/日/INSPECTION_PHOTO/<uuid>.<ext>;返回相对路径 + 免鉴权直链。
func UploadPhotoHandler(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
}
res, err := saveTypedUpload(r, "file", svcCtx.Config.Upload.Dir, "inspection", FileTypeInspectionPhoto,
uploadMaxBytes(svcCtx.Config.Upload.MaxSize, svcCtx.Config.Upload.MaxMB, FileTypeInspectionPhoto))
if err != nil {
httpx.Fail(w, 2303, "照片保存失败:"+err.Error())
return
}
httpx.Ok(w, map[string]any{"filename": res.RelPath, "url": "/api/v1/files/" + res.RelPath})
}
}
// InspectionDisposalHandler POST /quality/disposal 质量检验处置(退货/返修/退换)
func InspectionDisposalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Id int `json:"id"`
DisposalType string `json:"disposalType"`
DisposalRemark string `json:"disposalRemark"`
}
if err := httpx.ParseJSON(r, &req); err != nil || req.Id <= 0 {
httpx.BadRequest(w, "缺少 id")
return
}
if err := logic.New(svcCtx).SetInspectionDisposal(r.Context(), req.Id, req.DisposalType, req.DisposalRemark, operator(r, "")); err != nil {
httpx.Fail(w, 2305, err.Error())
return
}
httpx.OkMessage(w, "处置已记录", nil)
}
}
// UnqualifiedReportHandler GET /quality/unqualified-report?id= 一键生成《不合格单》xlsx 模板(预填检验数据)
func UnqualifiedReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := atoiDefault(r.URL.Query().Get("id"), 0)
if id <= 0 {
httpx.Fail(w, 2306, "缺少 id")
return
}
rec, err := logic.New(svcCtx).GetInspectionRecord(r.Context(), id)
if err != nil {
httpx.Fail(w, 2307, err.Error())
return
}
f := excelize.NewFile()
sheet := "不合格单"
f.SetSheetName("Sheet1", sheet)
headers := []string{"报检单号", "图号", "物料名称", "生产厂家", "检验单号", "结论", "处置方式", "处置说明", "记录时间"}
for c, h := range headers {
cell, _ := excelize.CoordinatesToCellName(c+1, 1)
_ = f.SetCellValue(sheet, cell, h)
}
row := []any{
rec.ReportNo, rec.MaterialCode, rec.MaterialName, rec.Manufacturer, rec.InspectionNo,
rec.Result, rec.DisposalType, rec.DisposalRemark, rec.CreatedAt.Format("2006-01-02 15:04:05"),
}
for c, v := range row {
cell, _ := excelize.CoordinatesToCellName(c+1, 2)
_ = f.SetCellValue(sheet, cell, v)
}
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+url.PathEscape("不合格单.xlsx"))
_, _ = f.WriteTo(w)
}
}