feat(mes): 添加定时同步可生产数量到WMS及BOM项相关标准字段
- 在main.go中添加定时任务每10分钟同步可生产数量到WMS系统 - 为BomItem实体添加relatedStandard字段及相关CRUD方法 - 为InspectionRecord实体添加reportNo、materialCode、materialName等字段 - 更新ent schema确保新字段的验证和默认值设置 - 添加必要的数据库迁移和字段映射逻辑
This commit is contained in:
@@ -2,10 +2,13 @@ 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 /inspections(PAD 巡检终端提交记录)
|
||||
@@ -24,7 +27,13 @@ func CreateInspectionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
"DONE": "sys.inspect:done",
|
||||
"ALARM": "sys.inspect:alarm",
|
||||
}
|
||||
if code, ok := categoryPerm[req.Category]; ok && !userHasPerm(r, svcCtx, code) {
|
||||
// 质量检验三类(过程检 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
|
||||
}
|
||||
@@ -36,8 +45,9 @@ func CreateInspectionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// ListInspectionsHandler GET /inspections?category=&operator=&from=&to=&page=&pageSize=
|
||||
// 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()
|
||||
@@ -51,7 +61,8 @@ func ListInspectionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
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"), page, pageSize)
|
||||
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
|
||||
@@ -75,3 +86,70 @@ func UploadPhotoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ var pathPermMap = map[string]string{
|
||||
"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/plc/send-process": "produce.plc:send",
|
||||
"POST:/api/v1/process-flows": "produce.processflow:add",
|
||||
@@ -33,6 +35,8 @@ var pathPermMap = map[string]string{
|
||||
"POST:/api/v1/qty-reports": "produce.qtyreport",
|
||||
// 过程巡检汇总生成《工序间检验记录》(巡检终端操作域)
|
||||
"POST:/api/v1/inspections/generate-inter-process": "sys.inspect:process",
|
||||
// 质量检验处置(过程检/完工检不合格处置 退货/返修/退换)
|
||||
"POST:/api/v1/quality/disposal": "produce.quality:edit",
|
||||
// 装机绑定(报工前扫料/撤销,属报工操作域)
|
||||
"POST:/api/v1/binds": "produce.scan",
|
||||
"POST:/api/v1/binds/remove": "produce.scan",
|
||||
|
||||
@@ -2,14 +2,132 @@ 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 = "默认"
|
||||
}
|
||||
pc := 0
|
||||
if v := trimCell(row, 8); v != "" {
|
||||
if n, e := strconv.Atoi(v); e == nil {
|
||||
pc = n
|
||||
}
|
||||
}
|
||||
item := logic.BomItemReq{
|
||||
ProductCode: productCode,
|
||||
BomName: bomName,
|
||||
MaterialCode: materialCode,
|
||||
MaterialName: trimCell(row, 3),
|
||||
Spec: trimCell(row, 4),
|
||||
Unit: trimCell(row, 5),
|
||||
ManageMode: "2",
|
||||
ProcessCode: &pc,
|
||||
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, "未解析到有效 BOM 行(至少需产品编号与物料编码)")
|
||||
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 {
|
||||
|
||||
@@ -2,6 +2,7 @@ package production
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
@@ -41,6 +42,28 @@ func PlcSendLogsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
|
||||
// ---------- 拧紧 ----------
|
||||
|
||||
// parseTorqueTime 兼容 RFC3339 与纯日期(YYYY-MM-DD)两种查询参数;
|
||||
// endOfDay=true 时把纯日期补到当日 23:59:59,保证「截止日期」含当天。
|
||||
func parseTorqueTime(v string, endOfDay bool) time.Time {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return time.Time{}
|
||||
}
|
||||
var t time.Time
|
||||
if err := t.UnmarshalText([]byte(v)); err == nil {
|
||||
return t
|
||||
}
|
||||
for _, layout := range []string{"2006-01-02T15:04:05", "2006-01-02 15:04:05", "2006-01-02"} {
|
||||
if p, err := time.ParseInLocation(layout, v, time.Local); err == nil {
|
||||
if layout == "2006-01-02" && endOfDay {
|
||||
p = p.Add(24*time.Hour - time.Second)
|
||||
}
|
||||
return p
|
||||
}
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func TorqueReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req logic.TorqueReq
|
||||
@@ -59,14 +82,9 @@ func TorqueReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
func TorqueRecordsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
var from, to time.Time
|
||||
if v := q.Get("from"); v != "" {
|
||||
_ = from.UnmarshalText([]byte(v))
|
||||
}
|
||||
if v := q.Get("to"); v != "" {
|
||||
_ = to.UnmarshalText([]byte(v))
|
||||
}
|
||||
data, err := logic.New(svcCtx).ListTorqueRecords(r.Context(), q.Get("sn"), q.Get("workOrderNo"), from, to)
|
||||
from := parseTorqueTime(q.Get("from"), false)
|
||||
to := parseTorqueTime(q.Get("to"), true)
|
||||
data, err := logic.New(svcCtx).ListTorqueRecords(r.Context(), q.Get("sn"), q.Get("workOrderNo"), q.Get("stationNo"), from, to)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3204, err.Error())
|
||||
return
|
||||
@@ -75,6 +93,39 @@ func TorqueRecordsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// TorqueGroupsHandler GET /torque/groups 拧紧一级汇总(工单号/SN/日期/工位 四条件复合 + 应拧 vs 实拧满足判定)
|
||||
func TorqueGroupsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
from := parseTorqueTime(q.Get("from"), false)
|
||||
to := parseTorqueTime(q.Get("to"), true)
|
||||
data, err := logic.New(svcCtx).ListTorqueGroups(r.Context(), q.Get("sn"), q.Get("workOrderNo"), q.Get("stationNo"),
|
||||
from, to, atoiDefault(q.Get("page"), 1), atoiDefault(q.Get("pageSize"), 20))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3210, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// TorqueStatHandler GET /torque/stat 单工件单工位应拧 vs 实拧统计
|
||||
func TorqueStatHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
if q.Get("sn") == "" {
|
||||
httpx.Fail(w, 3211, "缺少 sn 参数")
|
||||
return
|
||||
}
|
||||
data, err := logic.New(svcCtx).TorqueScrewStat(r.Context(), q.Get("sn"), q.Get("stationNo"))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3211, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 扫码报工 ----------
|
||||
|
||||
func ScanReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
@@ -124,32 +175,6 @@ func ProcessStepsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
|
||||
|
||||
// TorqueManualAddHandler POST /torque/manual-add 拧紧数据补录(设备漏传/手工修正)
|
||||
func TorqueManualAddHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Sn string `json:"sn"`
|
||||
WorkOrderNo string `json:"workOrderNo"`
|
||||
ScrewNo string `json:"screwNo"`
|
||||
StationNo string `json:"stationNo"`
|
||||
Strain float64 `json:"strain"`
|
||||
Angle float64 `json:"angle"`
|
||||
Result string `json:"result"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).AddTorqueRecord(r.Context(), req.Sn, req.WorkOrderNo, req.ScrewNo,
|
||||
req.StationNo, req.Strain, req.Angle, req.Result, req.Reason, operator(r, "系统管理员")); err != nil {
|
||||
httpx.Fail(w, 3205, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "补录成功", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// TorqueAuditHandler POST /torque/audit 拧紧记录审核签字
|
||||
func TorqueAuditHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -94,7 +94,7 @@ func WorkpieceTraceInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc
|
||||
func OrderQueryInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
orderNo := r.URL.Query().Get("orderNo")
|
||||
list, err := logic.New(svcCtx).ListWorkOrders(r.Context(), orderNo, "", "", "")
|
||||
list, err := logic.New(svcCtx).ListWorkOrders(r.Context(), orderNo, "", "", "", "", "", "", "")
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2004, err.Error())
|
||||
return
|
||||
@@ -103,6 +103,69 @@ func OrderQueryInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 工位终端 0/13 虚拟工位内部接口(第五步 5.2/5.4) ----------
|
||||
|
||||
// InProcessInternalHandler 在制品列表(工位终端「在制品默认展示」/上下线面板)。
|
||||
// GET ?stationNo=(<=0 全部;0=上线位, 13=下线位, 其余为物理工位),返回 {list}。
|
||||
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)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2006, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, map[string]any{"list": data})
|
||||
}
|
||||
}
|
||||
|
||||
// OnlineWorkpieceInternalHandler 工件进线建档(工位终端上线位 StationNo=0)。
|
||||
// POST OnlineReq{sn,orderNo,workOrderId,processSeq};operator 从 query 兜底(内部接口无 JWT)。
|
||||
func OnlineWorkpieceInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req logic.OnlineReq
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).OnlineWorkpiece(r.Context(), req, operator(r, r.URL.Query().Get("operator"))); err != nil {
|
||||
httpx.Fail(w, 2007, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "进线登记成功", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// DoneWorkpieceInternalHandler 工件完工入库(工位终端下线位 StationNo=13)。
|
||||
// POST DoneReq{sn,batchItems,serialItems};完工内部自动回流 WMS 成品库存。
|
||||
func DoneWorkpieceInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req logic.DoneReq
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).DoneWorkpiece(r.Context(), req, operator(r, r.URL.Query().Get("operator"))); err != nil {
|
||||
httpx.Fail(w, 2008, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "完工成功", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// BomInternalHandler BOM 物料清单(工位终端上线位「领料确认」按物料清单展示应领料)。
|
||||
// GET ?productCode=&bomName=,返回 []*ent.BomItem。
|
||||
func BomInternalHandler(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, 2009, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 看板内部接口 ----------
|
||||
|
||||
// DashboardSnapshotInternalHandler 看板全量快照。
|
||||
|
||||
@@ -113,7 +113,8 @@ func ListWorkOrdersHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
data, err := logic.New(svcCtx).ListWorkOrders(r.Context(),
|
||||
q.Get("orderNo"), q.Get("status"), q.Get("productCode"), q.Get("productName"))
|
||||
q.Get("orderNo"), q.Get("status"), q.Get("productCode"), q.Get("productName"),
|
||||
q.Get("contractNo"), q.Get("projectNo"), q.Get("dueFrom"), q.Get("dueTo"))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3004, err.Error())
|
||||
return
|
||||
@@ -194,6 +195,22 @@ func DeleteWorkOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// AutoScheduleHandler POST /work-orders/auto-schedule 引导式自动排产(生成多条 PENDING 排产单)
|
||||
func AutoScheduleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req logic.AutoScheduleReq
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).AutoSchedule(r.Context(), req, operator(r, "")); err != nil {
|
||||
httpx.Fail(w, 3011, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "自动排产已生成(待人工启用)", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 日排产 ----------
|
||||
|
||||
func SaveDailyPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
@@ -251,3 +268,17 @@ func SuggestedDockCodesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
httpx.Ok(w, map[string]any{"dockCodes": docks})
|
||||
}
|
||||
}
|
||||
|
||||
// InProcessHandler GET /workpieces/in-process 跨天在制品视图(停在各工位未完工工件)
|
||||
// stationNo 可选:0=上线位, 13=下线位, 其余为物理工位;不传或<=0 返回全部。
|
||||
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)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3001, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, map[string]any{"list": data})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,19 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/files", Handler: internal(FileDownloadInternalHandler(serverCtx)),
|
||||
})
|
||||
// ---------- 工位终端 0/13 上下线 + 在制品(第五步 5.2/5.4)----------
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/workpieces/in-process", Handler: internal(production.InProcessInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/workpiece/online", Handler: internal(production.OnlineWorkpieceInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
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)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/material-requests", Handler: internal(production.ListMaterialRequestsInternalHandler(serverCtx)),
|
||||
})
|
||||
@@ -137,6 +150,7 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{Method: http.MethodPost, Path: "/work-orders/status", Handler: production.SetWorkOrderStatusHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/work-orders/:id", Handler: production.GetWorkOrderHandler(serverCtx)},
|
||||
{Method: http.MethodDelete, Path: "/work-orders/:id", Handler: production.DeleteWorkOrderHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/work-orders/auto-schedule", Handler: production.AutoScheduleHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/daily-plans", Handler: production.SaveDailyPlanHandler(serverCtx)},
|
||||
{Method: http.MethodPut, Path: "/daily-plans", Handler: production.SaveDailyPlanHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/daily-plans", Handler: production.ListDailyPlansHandler(serverCtx)},
|
||||
@@ -147,6 +161,7 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{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)},
|
||||
@@ -156,9 +171,10 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
|
||||
{Method: http.MethodPost, Path: "/torque/report", Handler: production.TorqueReportHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/torque/records", Handler: production.TorqueRecordsHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/torque/groups", Handler: production.TorqueGroupsHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/torque/stat", Handler: production.TorqueStatHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/torque/audit", Handler: production.TorqueAuditHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/torque/audit-log", Handler: production.TorqueAuditLogHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/torque/manual-add", Handler: production.TorqueManualAddHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/torque/audit-log", Handler: production.TorqueAuditLogHandler(serverCtx)},
|
||||
|
||||
{Method: http.MethodPost, Path: "/scan/report", Handler: production.ScanReportHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/scan/records", Handler: production.ScanRecordsHandler(serverCtx)},
|
||||
@@ -168,6 +184,7 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{Method: http.MethodPost, Path: "/workpiece/process/report", Handler: production.ReportWorkpieceProcessHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/workpiece/done", Handler: production.DoneWorkpieceHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/workpieces", Handler: production.ListWorkpiecesHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/workpieces/in-process", Handler: production.InProcessHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/trace", Handler: production.TraceHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/bind-panel", Handler: production.BindPanelHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/binds", Handler: production.BindHandler(serverCtx)},
|
||||
@@ -212,6 +229,10 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{Method: http.MethodGet, Path: "/inspections", Handler: ListInspectionsHandler(serverCtx)},
|
||||
{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)},
|
||||
|
||||
// ---------- 数量不符上报(第三批) ----------
|
||||
|
||||
@@ -217,21 +217,20 @@ func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
}
|
||||
f := excelize.NewFile()
|
||||
type view struct {
|
||||
name string
|
||||
key string
|
||||
name string
|
||||
key string
|
||||
headers []string
|
||||
cols func(m map[string]any) []any
|
||||
cols func(r *logic.WorkloadRow) []any
|
||||
}
|
||||
rowOf := func(v any) map[string]any { m, _ := v.(map[string]any); return m }
|
||||
views := []view{
|
||||
{"按人", "byOperator", []string{"操作人", "完成数", "合格", "不合格"}, func(m map[string]any) []any {
|
||||
return []any{m["operator"], m["doneCount"], m["okCount"], m["ngCount"]}
|
||||
{"按人", "byOperator", []string{"操作人", "完成数", "合格", "不合格", "累计作业时长(秒)", "平均作业时长(秒)", "效率(件/小时)"}, func(r *logic.WorkloadRow) []any {
|
||||
return []any{r.Operator, r.DoneCount, r.OkCount, r.NgCount, r.TotalDurationSec, r.AvgDurationSec, r.Efficiency}
|
||||
}},
|
||||
{"按工位", "byStation", []string{"工位", "完成数", "合格", "不合格"}, func(m map[string]any) []any {
|
||||
return []any{m["stationNo"], m["doneCount"], m["okCount"], m["ngCount"]}
|
||||
{"按工位", "byStation", []string{"工位", "完成数", "合格", "不合格", "累计作业时长(秒)", "平均作业时长(秒)", "效率(件/小时)"}, func(r *logic.WorkloadRow) []any {
|
||||
return []any{r.StationNo, r.DoneCount, r.OkCount, r.NgCount, r.TotalDurationSec, r.AvgDurationSec, r.Efficiency}
|
||||
}},
|
||||
{"明细", "detail", []string{"操作人", "工位", "日期", "工序", "工序名", "完成数", "合格", "不合格"}, func(m map[string]any) []any {
|
||||
return []any{m["operator"], m["stationNo"], m["date"], m["processCode"], m["processName"], m["doneCount"], m["okCount"], m["ngCount"]}
|
||||
{"明细", "detail", []string{"操作人", "工位", "日期", "工序", "工序名", "完成数", "合格", "不合格", "累计作业时长(秒)", "平均作业时长(秒)", "效率(件/小时)"}, func(r *logic.WorkloadRow) []any {
|
||||
return []any{r.Operator, r.StationNo, r.Date, r.ProcessCode, r.ProcessName, r.DoneCount, r.OkCount, r.NgCount, r.TotalDurationSec, r.AvgDurationSec, r.Efficiency}
|
||||
}},
|
||||
}
|
||||
for i, vw := range views {
|
||||
@@ -246,12 +245,12 @@ func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
_ = f.SetCellValue(sheet, cell, h)
|
||||
}
|
||||
vm, _ := data[vw.key].(map[string]any)
|
||||
list, _ := vm["list"].([]any)
|
||||
list, _ := vm["list"].([]*logic.WorkloadRow)
|
||||
for rIdx, it := range list {
|
||||
m := rowOf(it)
|
||||
vals := vw.cols(it)
|
||||
for c := range vw.headers {
|
||||
cell, _ := excelize.CoordinatesToCellName(c+1, rIdx+2)
|
||||
_ = f.SetCellValue(sheet, cell, vw.cols(m)[c])
|
||||
_ = f.SetCellValue(sheet, cell, vals[c])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user