refactor: 重构工艺工序相关命名与数据模型

1.  全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2.  重构工单与工件工艺数据模型:
    - 新增工艺组合表flow_material作为备料/齐套唯一源头
    - 新增工位状态表station_state支持自主停单/恢复接单
    - 移除station_process派工表,改用工单工艺组合作为路线唯一源头
    - 替换processCode为flowId作为工艺关联标识
    - 重构工件当前进度字段为currentStationNo
3.  删除冗余的静态备份资源文件
4.  调整物料选择组件默认启用仅显示激活物料
5.  优化工单创建/编辑逻辑,新增工艺组合校验与保存
This commit is contained in:
SunYF
2026-09-22 11:32:29 +08:00
parent 932700ca65
commit d609ff8a9e
185 changed files with 8623 additions and 4501 deletions
+39 -28
View File
@@ -80,19 +80,19 @@ type cardTorque struct {
}
type cardTmpl struct {
Sn string
OrderNo string
Product string
Quantity int
CreatedAt string
Processes []cardProcess
Materials []string
Operator string
Inspector string
DoneTime string
Result string
NeedTorque bool
TorqueRows []cardTorque
Sn string
OrderNo string
Product string
Quantity int
CreatedAt string
Processes []cardProcess
Materials []string
Operator string
Inspector string
DoneTime string
Result string
NeedTorque bool
TorqueRows []cardTorque
}
// buildCard 汇总流程卡所需全部数据(ent 实体直查),并计算缺失项。
@@ -132,23 +132,27 @@ func buildCard(ctx context.Context, svcCtx *svc.ServiceContext, sn string) (*car
missing = append(missing, "产品数量")
}
// 工时间线(操作人 + 完成时间)
// 工时间线(操作人 + 完成时间)
procs, _ := client.WorkpieceProcess.Query().
Where(workpieceprocess.Sn(sn)).Order(workpieceprocess.ByProcessCode()).All(ctx)
Where(workpieceprocess.Sn(sn)).Order(ent.Asc(workpieceprocess.FieldFlowId), ent.Asc(workpieceprocess.FieldID)).All(ctx)
for _, p := range procs {
doneAt := ""
if p.EndedAt != nil {
doneAt = p.EndedAt.Format("2006-01-02 15:04")
}
flowLabel := p.ProcessName
if flowLabel == "" {
flowLabel = "工艺" + itoaCard(p.FlowId)
}
if p.Operator == "" {
missing = append(missing, fmt.Sprintf("工%s报工记录(缺操作人)", itoaCard(p.ProcessCode)))
missing = append(missing, fmt.Sprintf("工艺「%s报工记录(缺操作人)", flowLabel))
}
if p.EndedAt == nil {
missing = append(missing, fmt.Sprintf("工%s报工记录(缺完成时间)", itoaCard(p.ProcessCode)))
missing = append(missing, fmt.Sprintf("工艺「%s报工记录(缺完成时间)", flowLabel))
}
cp := cardProcess{
ID: p.ID,
Code: fmt.Sprintf("%d", p.ProcessCode),
Code: p.StationNo,
Name: p.ProcessName,
Station: p.StationNo,
Operator: p.Operator,
@@ -162,7 +166,7 @@ func buildCard(ctx context.Context, svcCtx *svc.ServiceContext, sn string) (*car
t.Processes = append(t.Processes, cp)
}
if len(procs) == 0 {
missing = append(missing, "工序报工记录")
missing = append(missing, "报工记录")
}
// 步骤考核数据按工序实绩 ID 归组,回填到对应工序行的"采集参数"
@@ -206,15 +210,22 @@ func buildCard(ctx context.Context, svcCtx *svc.ServiceContext, sn string) (*car
return t, missing, nil
}
// hasTorqueStep 判断该工件加工路径上是否存在"拧紧采集"步骤(经 工位→流程→步骤 链)
// 路径由今日工位派工(station_process)派生
// hasTorqueStep 判断该工件加工路径上是否存在"拧紧采集"步骤(经 工单工艺组合→工位→流程→步骤 链)
// 路线 = 工单工艺组合(唯一路线源头)
func hasTorqueStep(ctx context.Context, client *ent.Client, sn string, wp *ent.Workpiece) bool {
route, err := logic.RouteStations(ctx, client, logic.TodayStr(), "")
if err != nil || len(route) == 0 {
if wp.WorkOrderId <= 0 {
return false
}
wo, err := client.WorkOrder.Get(ctx, wp.WorkOrderId)
if err != nil {
return false
}
items, _ := logic.RouteStationsFromWO(ctx, client, wo)
if len(items) == 0 {
return false
}
stations, err := client.Station.Query().
Where(station.StationNoIn(route...), station.FlowIdGT(0)).All(ctx)
Where(station.StationNoIn(logic.RouteStationsOfWO(items)...), station.FlowIdGT(0)).All(ctx)
if err != nil || len(stations) == 0 {
return false
}
@@ -269,13 +280,13 @@ func renderProcessCard(t *cardTmpl) (string, error) {
<span>判定:<b>{{if eq .Result "NG"}}<span class="ng">NG</span>{{else}}{{.Result}}{{end}}</b></span>
</div>
{{if .Processes}}
<h2>工流转记录</h2>
<h2>工流转记录</h2>
<table>
<thead><tr><th>工</th><th>工名称</th><th>工位</th><th>操作人</th><th>完成时间</th><th>采集参数</th><th>结果</th></tr></thead>
<thead><tr><th>工位号</th><th>工名称</th><th>工位</th><th>操作人</th><th>完成时间</th><th>采集参数</th><th>结果</th></tr></thead>
<tbody>
{{range .Processes}}
<tr>
<td>工序{{.Code}}</td><td>{{.Name}}</td><td>{{.Station}}</td><td>{{.Operator}}</td><td>{{.DoneTime}}</td>
<td>{{.Code}}</td><td>{{.Name}}</td><td>{{.Station}}</td><td>{{.Operator}}</td><td>{{.DoneTime}}</td>
<td>{{if .Steps}}{{range .Steps}}{{.Name}}={{.Value}}({{if .OK}}<span class="ok">OK</span>{{else}}<span class="ng">NG</span>{{end}}) {{end}}{{else}}-{{end}}</td>
<td>{{if eq .Result "NG"}}<span class="ng">NG</span>{{else}}OK{{end}}</td>
</tr>
@@ -283,7 +294,7 @@ func renderProcessCard(t *cardTmpl) (string, error) {
</tbody>
</table>
{{else}}
<p style="color:#b45309;">暂无工序报工记录。</p>
<p style="color:#b45309;">暂无报工记录。</p>
{{end}}
{{if .NeedTorque}}
<h2>拧紧数据{{if not .TorqueRows}}(无数据){{end}}</h2>
@@ -21,8 +21,8 @@ import (
"bj_power_mes/ent/workorder"
"bj_power_mes/ent/workpieceprocess"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/wmsclient"
"bj_power_mes/internal/svc"
"bj_power_mes/internal/wmsclient"
)
// ModelProcessCardHandler GET /api/v1/process-card/model?productTypeId=&productCode=[&format=json]
@@ -31,7 +31,7 @@ import (
// 该型号的完整制造定义,用于工艺交底/审核/客户查阅:
//
// 产品型号 → 工位组合 → 每工位工艺流程与步骤(含采集方式/检测点/拧紧/考核标准/工艺图纸)
// → 物料清单(按 BOM 分组,含装配工位/单台用量/损耗/需求总量)
// → 物料清单(按 BOM 分组,含工艺清单/单台用量/损耗/需求总量)
// → 检验卡项(所有"检测确认"步骤汇总为检验项清单)
// → 各环节时间(工单计划时间 + 该型号历史实绩的首末作业时间)
//
@@ -116,7 +116,7 @@ type modelBomItem struct {
Spec string
Unit string
ManageMode string
StationNo string
FlowNames string // 使用该物料的工艺清单(经 flow_material 映射)
UnitQty string
LossRate string
RequiredQty string
@@ -158,37 +158,40 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
return nil, fmt.Errorf("产品型号不存在")
}
// 2) 产品最近工单(仅取工单号/计划时间用于卡头展示;路线不再存工单
// 2) 产品最近工单(卡头展示 + 工艺组合路线唯一来源
var refWO *ent.WorkOrder
if productCode != "" {
if wo, err := client.WorkOrder.Query().
refWO, _ = client.WorkOrder.Query().
Where(workorder.ProductCode(productCode)).
Order(ent.Desc(workorder.FieldID)).First(ctx); err == nil && wo != nil {
card.OrderNo = wo.WorkOrderNo
if wo.PlanStart != nil {
card.PlanStart = wo.PlanStart.Format("2006-01-02 15:04")
}
if wo.PlanEnd != nil {
card.PlanEnd = wo.PlanEnd.Format("2006-01-02 15:04")
}
Order(ent.Desc(workorder.FieldID)).First(ctx)
}
if refWO != nil {
card.OrderNo = refWO.WorkOrderNo
if refWO.PlanStart != nil {
card.PlanStart = refWO.PlanStart.Format("2006-01-02 15:04")
}
if refWO.PlanEnd != nil {
card.PlanEnd = refWO.PlanEnd.Format("2006-01-02 15:04")
}
}
// 3) 工位组合:由「今日工位派工(station_process)」派生;未派工则展示全产线已配置工艺的工位
seq, _ := logic.RouteStations(ctx, client, logic.TodayStr(), "")
if len(seq) == 0 {
// 3) 工位组合:唯一路线源头 = 最近工单的工艺组合;未配置则展示全产线已配置工艺的工位
var seq []int
if items, _ := logic.RouteStationsFromWO(ctx, client, refWO); len(items) > 0 {
seq = logic.RouteStationsOfWO(items)
card.SeqSource = "取自最近工单的工艺组合(工单 " + refWO.WorkOrderNo + ""
} else {
sts, _ := client.Station.Query().Where(station.FlowIdGT(0)).Order(ent.Asc(station.FieldStationNo)).All(ctx)
for _, st := range sts {
seq = append(seq, st.StationNo)
}
card.SeqSource = "今日未派工,按全产线已配置工位顺序展示"
} else {
card.SeqSource = "取自今日工位派工路线"
card.SeqSource = "未配置工艺组合,按全产线已配置工位顺序展示"
}
sort.Ints(seq)
card.ProcessSeq = joinInts(seq)
card.StationCount = len(seq)
// 3) 工位 + 工艺流程 + 步骤 + 考核标准
// 4) 工位 + 工艺流程 + 步骤 + 考核标准
sts, _ := client.Station.Query().Where(station.StationNoIn(seq...)).All(ctx)
stationByName := map[int]*ent.Station{}
flowIds := []int{}
@@ -227,8 +230,9 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
for _, no := range seq {
st := stationByName[no]
ms := modelStation{StationNo: no, StationName: fmt.Sprintf("工位%d", no), RefTime: refTimes[no]}
ms := modelStation{StationNo: no, StationName: fmt.Sprintf("工位%d", no)}
if st != nil {
ms.RefTime = refTimes[st.FlowId]
if st.Name != "" {
ms.StationName = st.Name
}
@@ -263,8 +267,22 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
card.Stations = append(card.Stations, ms)
}
// 4) 物料清单(按 BOM 名称分组)
// 5) 物料清单(按 BOM 名称分组;工艺清单经 flow_material + process_flow 映射
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)
}
}
}
items, _ := client.BomItem.Query().
Where(bomitem.ProductCode(productCode)).
Order(ent.Asc(bomitem.FieldBomName), ent.Asc(bomitem.FieldMaterialCode)).All(ctx)
@@ -280,9 +298,9 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
gi = len(card.BomGroups) - 1
groupIdx[name] = gi
}
stationNo := "-"
if it.ProcessCode > 0 {
stationNo = fmt.Sprintf("工位%d", it.ProcessCode)
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,
@@ -290,7 +308,7 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
Spec: it.Spec,
Unit: it.Unit,
ManageMode: manageModeCN(it.ManageMode),
StationNo: stationNo,
FlowNames: fnames,
UnitQty: f2s(it.UnitQty),
LossRate: f2s(it.LossRate),
RequiredQty: f2s(it.RequiredQty),
@@ -303,7 +321,7 @@ func buildModelCard(ctx context.Context, svcCtx *svc.ServiceContext, productType
return card, nil
}
// stationRefTimes 该产品型号历史实绩按工聚合的首末作业时间(无实绩则返回空 map)
// stationRefTimes 该产品型号历史实绩按工聚合的首末作业时间(无实绩则返回空 map,键=工艺ID
func stationRefTimes(ctx context.Context, client *ent.Client, productCode string) map[int]string {
out := map[int]string{}
if productCode == "" {
@@ -326,10 +344,10 @@ func stationRefTimes(ctx context.Context, client *ent.Client, productCode string
}
m := map[int]*span{}
for _, p := range procs {
s := m[p.ProcessCode]
s := m[p.FlowId]
if s == nil {
s = &span{}
m[p.ProcessCode] = s
m[p.FlowId] = s
}
if p.StartedAt != nil {
t := p.StartedAt.Unix()
@@ -346,15 +364,15 @@ func stationRefTimes(ctx context.Context, client *ent.Client, productCode string
s.seen = true
}
}
for code, s := range m {
for flowId, s := range m {
if !s.seen {
continue
}
if s.last == 0 {
out[code] = tsText(s.first) + " 起(未完成)"
out[flowId] = tsText(s.first) + " 起(未完成)"
continue
}
out[code] = tsText(s.first) + " → " + tsText(s.last)
out[flowId] = tsText(s.first) + " → " + tsText(s.last)
}
return out
}
@@ -511,7 +529,7 @@ func renderModelCard(c *modelCard) (string, error) {
{{if .Steps}}
<table>
<thead>
<tr><th colspan="6" class="st-name">工位{{.StationNo}} {{.StationName}} · {{if .FlowName}}{{.FlowName}}{{else}}未绑定流程{{end}}</th></tr>
<tr><th colspan="6" class="st-name">工位{{.StationNo}} {{.StationName}} · {{if .FlowName}}{{.FlowName}}{{else}}未绑定工艺{{end}}</th></tr>
<tr><th style="width:64px;">步骤号</th><th style="width:26%;">步骤名称</th><th>采集方式</th><th>检测确认</th><th>拧紧采集</th><th>考核标准</th></tr>
</thead>
<tbody>
@@ -534,13 +552,13 @@ func renderModelCard(c *modelCard) (string, error) {
<table>
<thead>
<tr><th colspan="9" class="st-name">BOM{{.BomName}}</th></tr>
<tr><th>图号</th><th>物料名称</th><th>规格</th><th>单位</th><th>管理方式</th><th>装配工位</th><th>单台用量</th><th>损耗率(%)</th><th>需求总量</th></tr>
<tr><th>图号</th><th>物料名称</th><th>规格</th><th>单位</th><th>管理方式</th><th>工艺清单</th><th>单台用量</th><th>损耗率(%)</th><th>需求总量</th></tr>
</thead>
<tbody>
{{range .Items}}
<tr>
<td>{{.MaterialCode}}</td><td class="left">{{.MaterialName}}</td><td class="left">{{.Spec}}</td>
<td>{{.Unit}}</td><td>{{.ManageMode}}</td><td>{{.StationNo}}</td>
<td>{{.Unit}}</td><td>{{.ManageMode}}</td><td>{{.FlowNames}}</td>
<td>{{.UnitQty}}</td><td>{{.LossRate}}</td><td>{{.RequiredQty}}</td>
</tr>
{{end}}
@@ -13,12 +13,12 @@ func itoa(n int) string { return strconv.Itoa(n) }
// BindReq 绑定提交请求
type BindReq struct {
Sn string `json:"sn"`
OrderNo string `json:"orderNo"`
ProcessCode int `json:"processCode"`
StationNo int `json:"stationNo"`
Operator string `json:"operator"`
Items []logic.BindItemReq `json:"items"`
Sn string `json:"sn"`
OrderNo string `json:"orderNo"`
FlowId int `json:"flowId"` // 报工/绑定工艺(唯一路线源头=工单工艺组合)
StationNo int `json:"stationNo"`
Operator string `json:"operator"`
Items []logic.BindItemReq `json:"items"`
}
func doBind(svcCtx *svc.ServiceContext, r *http.Request) (any, error) {
@@ -26,7 +26,7 @@ func doBind(svcCtx *svc.ServiceContext, r *http.Request) (any, error) {
if err := httpx.ParseJSON(r, &req); err != nil {
return nil, err
}
if req.Sn == "" || req.ProcessCode <= 0 {
if req.Sn == "" || req.FlowId <= 0 {
return nil, nil
}
op := operator(r, req.Operator)
@@ -34,7 +34,7 @@ func doBind(svcCtx *svc.ServiceContext, r *http.Request) (any, error) {
if req.StationNo > 0 {
stationNo = itoa(req.StationNo)
}
return logic.New(svcCtx).BindWorkpiece(r.Context(), req.Sn, req.OrderNo, req.ProcessCode, stationNo, op, req.Items)
return logic.New(svcCtx).BindWorkpiece(r.Context(), req.Sn, req.OrderNo, req.FlowId, stationNo, op, req.Items)
}
// BindInternalHandler POST /api/internal/workpiece/binds —— 工位终端扫码绑料
@@ -46,7 +46,7 @@ func BindInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return
}
if data == nil {
httpx.BadRequest(w, "sn / processCode 必填")
httpx.BadRequest(w, "sn / flowId 必填")
return
}
httpx.Ok(w, data)
@@ -55,16 +55,16 @@ func BindInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func doUnbind(svcCtx *svc.ServiceContext, r *http.Request) error {
var req struct {
Sn string `json:"sn"`
ProcessCode int `json:"processCode"`
Sn string `json:"sn"`
FlowId int `json:"flowId"`
MaterialCode string `json:"materialCode"`
BindValue string `json:"bindValue"`
Operator string `json:"operator"`
BindValue string `json:"bindValue"`
Operator string `json:"operator"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
return err
}
return logic.New(svcCtx).RemoveBind(r.Context(), req.Sn, req.ProcessCode, req.MaterialCode, req.BindValue, operator(r, req.Operator))
return logic.New(svcCtx).RemoveBind(r.Context(), req.Sn, req.FlowId, req.MaterialCode, req.BindValue, operator(r, req.Operator))
}
// UnbindInternalHandler POST /api/internal/workpiece/binds/remove
@@ -80,17 +80,17 @@ func UnbindInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
// ---------- 装机绑定:应绑清单/齐套状态查询 ----------
// bindPanelFromReq 通用查询逻辑:按 sn+processCode 返回绑定面板(应绑物料+已绑情况)
// bindPanelFromReq 通用查询逻辑:按 sn+flowId 返回绑定面板(应绑物料+已绑情况)
func bindPanelFromReq(svcCtx *svc.ServiceContext, r *http.Request) (any, error) {
sn := r.URL.Query().Get("sn")
processCode := atoiDefault(r.URL.Query().Get("processCode"), 0)
if sn == "" || processCode <= 0 {
flowId := atoiDefault(r.URL.Query().Get("flowId"), 0)
if sn == "" || flowId <= 0 {
return nil, nil
}
return logic.New(svcCtx).StationBindPanelData(r.Context(), sn, processCode)
return logic.New(svcCtx).StationBindPanelData(r.Context(), sn, flowId)
}
// BindPanelInternalHandler GET /api/internal/workpiece/bind-panel?sn=&processCode=
// BindPanelInternalHandler GET /api/internal/workpiece/bind-panel?sn=&flowId=
// 工位终端(12 个触控一体机)扫码工件后拉取绑定面板,驱动工人扫码绑料。
func BindPanelInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -100,7 +100,7 @@ func BindPanelInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return
}
if data == nil {
httpx.BadRequest(w, "缺少 sn / processCode 参数")
httpx.BadRequest(w, "缺少 sn / flowId 参数")
return
}
httpx.Ok(w, data)
@@ -14,7 +14,7 @@ import (
// ---------- 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 {
@@ -59,12 +59,6 @@ func ImportBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
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,
@@ -73,7 +67,6 @@ func ImportBomHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
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),
@@ -0,0 +1,42 @@
package production
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// ListFlowMaterialsHandler GET /api/v1/process-flows/:id/materials —— 查某工艺的物料清单(flow_material
func ListFlowMaterialsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
flowId := pathID(r)
data, err := logic.New(svcCtx).ListFlowMaterials(r.Context(), flowId)
if err != nil {
httpx.Fail(w, 2113, err.Error())
return
}
httpx.Ok(w, data)
}
}
// SaveFlowMaterialsHandler POST /api/v1/process-flows/:id/materials —— 整表覆盖保存工艺物料清单
// body: {items: [{materialCode, materialName, spec, unit, manageMode, unitQty, lossRate}]}
func SaveFlowMaterialsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
flowId := pathID(r)
var req struct {
Items []logic.FlowMaterialReq `json:"items"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if err := logic.New(svcCtx).SaveFlowMaterials(r.Context(), flowId, req.Items, operator(r, "")); err != nil {
httpx.Fail(w, 2114, err.Error())
return
}
httpx.OkMessage(w, "工艺物料清单已保存", nil)
}
}
@@ -1,69 +0,0 @@
package production
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// ---------- 工位派工(工位↔工序,替代原工序组合串) ----------
// ListStationProcessHandler GET /station-process 查询某日班次全部工位派工(按工位号升序)
func ListStationProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
data, err := logic.New(svcCtx).ListStationProcess(r.Context(), q.Get("effectDate"), q.Get("shift"))
if err != nil {
httpx.Fail(w, 3301, err.Error())
return
}
httpx.Ok(w, data)
}
}
// SaveStationProcessHandler POST /station-process/save 保存单条工位派工
// 校验:同工位同天同班次唯一;已派工工位沿工位号必须单调不降、不可穿插。processCode=0 表示解除派工。
func SaveStationProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req logic.StationProcessReq
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if req.Operator == "" {
req.Operator = operator(r, "")
}
if err := logic.New(svcCtx).UpsertStationProcess(r.Context(), req); err != nil {
httpx.Fail(w, 3302, err.Error())
return
}
httpx.OkMessage(w, "保存成功", nil)
}
}
// RouteStationProcessHandler GET /station-process/route 派生路线(工位号列表/工序映射/串)
// 供流程卡、PLC 下发、备料单等依赖"今日路线"的模块读取,不再手填组合串。
func RouteStationProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
ctx := r.Context()
stations, err := logic.RouteStations(ctx, svcCtx.EntClient, q.Get("effectDate"), q.Get("shift"))
if err != nil {
httpx.Fail(w, 3303, err.Error())
return
}
procMap, err := logic.RouteStationProcessCodeMap(ctx, svcCtx.EntClient, q.Get("effectDate"), q.Get("shift"))
if err != nil {
httpx.Fail(w, 3303, err.Error())
return
}
str, _ := logic.RouteStationsStr(ctx, svcCtx.EntClient, q.Get("effectDate"), q.Get("shift"))
httpx.Ok(w, map[string]any{
"stations": stations,
"processByStation": procMap,
"routeStr": str,
})
}
}
@@ -0,0 +1,44 @@
package production
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// StationPauseInternalHandler POST /api/internal/station/pause —— 工位停单/恢复(工人自主,只认当天)
// body: {stationNo, paused, reason, operator}
func StationPauseInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
StationNo int `json:"stationNo"`
Paused bool `json:"paused"`
Reason string `json:"reason"`
Operator string `json:"operator"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if err := logic.New(svcCtx).SetStationPause(r.Context(), req.StationNo, req.Paused, req.Reason, operator(r, req.Operator)); err != nil {
httpx.Fail(w, 2115, err.Error())
return
}
httpx.OkMessage(w, "已更新工位停单状态", nil)
}
}
// StationStateInternalHandler GET /api/internal/station/state?date= —— 查询停单状态(date 空取当天)
// 响应: {list: [{id, stationNo, date, paused, reason, operator, createdAt, updatedAt}]}
func StationStateInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).ListStationStates(r.Context(), r.URL.Query().Get("date"))
if err != nil {
httpx.Fail(w, 2116, err.Error())
return
}
httpx.Ok(w, map[string]any{"list": data})
}
}
@@ -23,7 +23,7 @@ func pathID(r *http.Request) int {
func ListProductTypesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, _, err := logic.New(svcCtx).ListProductTypes(r.Context())
data, _, err := logic.New(svcCtx).ListProductTypes(r.Context(), r.URL.Query().Get("isActive"))
if err != nil {
httpx.Fail(w, 3001, err.Error())
return
@@ -232,7 +232,7 @@ func ListDailyPlansHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderNo := r.URL.Query().Get("orderNo")
planDate := r.URL.Query().Get("planDate")
data, err := logic.New(svcCtx).ListDailyPlans(r.Context(), orderNo, planDate)
data, err := logic.New(svcCtx).ListDailyPlansWithCombination(r.Context(), orderNo, planDate)
if err != nil {
httpx.Fail(w, 3009, err.Error())
return
@@ -77,6 +77,13 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
server.AddRoute(rest.Route{
Method: http.MethodGet, Path: "/api/internal/files", Handler: internal(FileDownloadInternalHandler(serverCtx)),
})
// ---------- 工位停单(只认当天;工人自主停/恢复,上位机据此不下发流转指令) ----------
server.AddRoute(rest.Route{
Method: http.MethodPost, Path: "/api/internal/station/pause", Handler: production.StationPauseInternalHandler(serverCtx),
})
server.AddRoute(rest.Route{
Method: http.MethodGet, Path: "/api/internal/station/state", Handler: production.StationStateInternalHandler(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)),
@@ -265,10 +272,9 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
// 产品型号级全景流程卡(D2):按型号聚合 工位组合→工艺流程/步骤→物料清单→检验卡项
{Method: http.MethodGet, Path: "/process-card/model", Handler: ModelProcessCardHandler(serverCtx)},
// ---------- 工位派工(工位↔工序,替代原工序组合串 ----------
{Method: http.MethodGet, Path: "/station-process", Handler: production.ListStationProcessHandler(serverCtx)},
{Method: http.MethodPost, Path: "/station-process/save", Handler: production.SaveStationProcessHandler(serverCtx)},
{Method: http.MethodGet, Path: "/station-process/route", Handler: production.RouteStationProcessHandler(serverCtx)},
// ---------- 工艺物料清单(flow_material,一份工艺定义一份清单 ----------
{Method: http.MethodGet, Path: "/process-flows/:id/materials", Handler: production.ListFlowMaterialsHandler(serverCtx)},
{Method: http.MethodPost, Path: "/process-flows/:id/materials", Handler: production.SaveFlowMaterialsHandler(serverCtx)},
// ---------- 排产支撑视图(§A2 / §D1):可生产套数 + 支撑天数 + 最先告罄短板物料 ----------
{Method: http.MethodGet, Path: "/production/producible-support", Handler: production.ProducibleSupportHandler(serverCtx)},
+2 -2
View File
@@ -229,8 +229,8 @@ func PerformanceExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
{"按工位", "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(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}
{"明细", "detail", []string{"操作人", "工位", "日期", "工艺ID", "工名", "完成数", "合格", "不合格", "累计作业时长(秒)", "平均作业时长(秒)", "效率(件/小时)"}, func(r *logic.WorkloadRow) []any {
return []any{r.Operator, r.StationNo, r.Date, r.FlowId, r.ProcessName, r.DoneCount, r.OkCount, r.NgCount, r.TotalDurationSec, r.AvgDurationSec, r.Efficiency}
}},
}
for i, vw := range views {