Files
bj_power/bj_power_mes/internal/handler/inspection.go
T
SunYF e31b85e87a refactor: 全链路统一将物料编码改称图号,优化入库流程
1.  统一全系统UI与文档中的"物料编码"为客户口径"图号",覆盖前端页面、后端导出表头与帮助文档
2.  优化入库模块:
    - 新增归属类型"其他"并完善注释
    - 入库时自动回写入库单批次号
    - 优化库位选择为三级联动下拉选择,替换原手动输入
    - 新增入库归属编号录入功能
3. 更新需求文档,同步完成图号全链路改造的验收标记
2026-09-17 18:27:07 +08:00

156 lines
5.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 (
"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(巡检拍照上传,返回文件名)
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
}
name, err := saveUploadFile(r, "file", svcCtx.Config.Upload.Dir)
if err != nil {
httpx.Fail(w, 2303, "照片保存失败:"+err.Error())
return
}
httpx.Ok(w, map[string]any{"filename": name, "url": "/api/v1/files/" + name})
}
}
// 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) {
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)
}
}