Files
bj_power/bj_power_mes/internal/logic/processflow.go
T

426 lines
14 KiB
Go
Raw Normal View History

package logic
import (
"context"
"errors"
"fmt"
"sort"
"time"
"bj_power_mes/ent"
"bj_power_mes/ent/processflow"
"bj_power_mes/ent/processstep"
"bj_power_mes/ent/station"
"bj_power_mes/ent/stepcriterion"
"bj_power_mes/ent/workorder"
"bj_power_mes/ent/workpieceprocess"
)
// ---------- 工艺流程(块3 ----------
type FlowReq struct {
Id int `json:"id"`
Name string `json:"name"`
StationNo int `json:"stationNo"`
PdfFile string `json:"pdfFile"`
Status string `json:"status"`
Remark string `json:"remark"`
Steps []StepItemReq `json:"steps"`
}
// FlowVO 工艺流程视图(含工序步骤 + 绑定该流程的工位号)
type FlowVO struct {
Id int `json:"id"`
Name string `json:"name"`
StationNo int `json:"stationNo"`
PdfFile string `json:"pdfFile"`
Status string `json:"status"`
Remark string `json:"remark"`
Stations []int `json:"stations"`
Steps []*ProcessStepTemplate `json:"steps"`
CreatedAt time.Time `json:"createdAt"`
}
// ListFlows 查询工艺流程(可按工位号过滤)
func (s *Service) ListFlows(ctx context.Context, stationNo int) ([]*FlowVO, error) {
q := s.ctx.EntClient.ProcessFlow.Query().
Order(ent.Asc(processflow.FieldProcessCode), ent.Asc(processflow.FieldID))
if stationNo > 0 {
q = q.Where(processflow.ProcessCode(stationNo))
}
flows, err := q.All(ctx)
if err != nil {
return nil, err
}
stations, _ := s.ctx.EntClient.Station.Query().All(ctx)
out := make([]*FlowVO, 0, len(flows))
for _, f := range flows {
vo := &FlowVO{
Id: f.ID, Name: f.Name, StationNo: f.ProcessCode,
PdfFile: f.PdfFile, Status: f.Status, Remark: f.Remark,
Stations: []int{}, Steps: s.flowSteps(ctx, f.ID, f.ProcessCode), CreatedAt: f.CreatedAt,
}
for _, st := range stations {
if st.FlowId == f.ID {
vo.Stations = append(vo.Stations, st.StationNo)
}
}
out = append(out, vo)
}
return out, nil
}
func (s *Service) SaveFlow(ctx context.Context, req FlowReq, operator string) error {
if req.Name == "" {
return errors.New("流程名称必填")
}
if req.StationNo < 1 || req.StationNo > 12 {
return errors.New("请选择工位号(1~12,固定12个工位)")
}
status := req.Status
if status == "" {
status = "ACTIVE"
}
oldStationNo := 0
if req.Id > 0 {
if old, err := s.ctx.EntClient.ProcessFlow.Get(ctx, req.Id); err == nil {
oldStationNo = old.ProcessCode
}
}
if status == "ACTIVE" {
q := s.ctx.EntClient.ProcessFlow.Query().
Where(processflow.ProcessCode(req.StationNo), processflow.Status("ACTIVE"))
if req.Id > 0 {
q = q.Where(processflow.IDNEQ(req.Id))
}
if exist, err := q.Exist(ctx); err == nil && exist {
return fmt.Errorf("工位 %d 已存在启用的工艺流程,请先停用旧流程", req.StationNo)
}
}
var flowID int
if req.Id > 0 {
upd := s.ctx.EntClient.ProcessFlow.UpdateOneID(req.Id).
SetName(req.Name).SetProcessCode(req.StationNo).SetStatus(status).SetRemark(req.Remark)
if req.PdfFile != "" {
upd.SetPdfFile(req.PdfFile)
}
if _, err := upd.Save(ctx); err != nil {
return err
}
flowID = req.Id
} else {
f, err := s.ctx.EntClient.ProcessFlow.Create().
SetName(req.Name).SetProcessCode(req.StationNo).SetPdfFile(req.PdfFile).
SetStatus(status).SetRemark(req.Remark).Save(ctx)
if err != nil {
return err
}
flowID = f.ID
}
// 工位号变更:解绑旧工位
if oldStationNo > 0 && oldStationNo != req.StationNo {
_, _ = s.ctx.EntClient.Station.Update().
Where(station.StationNo(oldStationNo), station.FlowId(flowID)).
SetFlowId(0).Save(ctx)
}
// 绑定同步:工位N 绑定该启用流程
if status == "ACTIVE" {
s.syncStationFlow(ctx, req.StationNo, flowID)
} else {
_, _ = s.ctx.EntClient.Station.Update().
Where(station.StationNo(req.StationNo), station.FlowId(flowID)).
SetFlowId(0).Save(ctx)
}
// 覆盖式重建该流程的工序步骤及考核标准
_, _ = s.ctx.EntClient.ProcessStep.Delete().Where(processstep.FlowId(flowID)).Exec(ctx)
for _, st := range req.Steps {
if st.Name == "" {
continue
}
rec, err := s.ctx.EntClient.ProcessStep.Create().
SetFlowId(flowID).SetProcessCode(req.StationNo).SetSeq(st.Seq).SetName(st.Name).
SetCollectType(st.CollectType).SetIsTorque(st.IsTorque).SetRemark(st.Remark).Save(ctx)
if err != nil {
return err
}
_, _ = s.ctx.EntClient.StepCriterion.Delete().Where(stepcriterion.StepId(rec.ID)).Exec(ctx)
for _, c := range st.Criteria {
if c.Name == "" || c.Logic == "" || c.Logic == "NONE" {
continue
}
b := s.ctx.EntClient.StepCriterion.Create().
SetStepId(rec.ID).SetName(c.Name).SetUnit(c.Unit).SetLogic(c.Logic).SetTarget(c.Target)
if c.Min != nil {
b = b.SetMin(*c.Min)
}
if c.Max != nil {
b = b.SetMax(*c.Max)
}
if err := b.Exec(ctx); err != nil {
return err
}
}
}
s.ctx.EventLog.Write(ctx, "process.flow.save", "", operator, "process_flow", "", "维护工艺流程",
map[string]any{"flowId": flowID, "stationNo": req.StationNo, "steps": len(req.Steps)})
return nil
}
// syncStationFlow 同步工位绑定:工位 stationNo 绑定到 flowID(无工位记录时自动创建)
func (s *Service) syncStationFlow(ctx context.Context, stationNo, flowID int) {
st, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(stationNo)).First(ctx)
if err != nil {
_, _ = s.ctx.EntClient.Station.Create().
SetStationNo(stationNo).SetName(fmt.Sprintf("工位%d", stationNo)).
SetFlowId(flowID).Save(ctx)
return
}
_, _ = s.ctx.EntClient.Station.UpdateOneID(st.ID).SetFlowId(flowID).Save(ctx)
}
func (s *Service) DeleteFlow(ctx context.Context, id int, operator string) error {
// 有工位绑定的流程不允许删除
cnt, err := s.ctx.EntClient.Station.Query().Where(station.FlowId(id)).Count(ctx)
if err == nil && cnt > 0 {
return errors.New("该流程已被工位绑定,请先解绑工位")
}
_, _ = s.ctx.EntClient.ProcessStep.Delete().Where(processstep.FlowId(id)).Exec(ctx)
s.ctx.EventLog.Write(ctx, "process.flow.delete", "", operator, "process_flow", "", "删除工艺流程", map[string]any{"flowId": id})
return s.ctx.EntClient.ProcessFlow.DeleteOneID(id).Exec(ctx)
}
// flowSteps 查询某流程的工序步骤(含考核标准)
func (s *Service) flowSteps(ctx context.Context, flowId, processCode int) []*ProcessStepTemplate {
steps, _ := s.ctx.EntClient.ProcessStep.Query().
Where(processstep.FlowId(flowId)).Order(ent.Asc(processstep.FieldSeq)).All(ctx)
out := make([]*ProcessStepTemplate, 0, len(steps))
for _, st := range steps {
t := &ProcessStepTemplate{
ID: st.ID, ProcessCode: st.ProcessCode, Seq: st.Seq,
Name: st.Name, CollectType: st.CollectType, IsTorque: st.IsTorque,
Remark: st.Remark, Criteria: []CriterionVO{},
}
crits, _ := s.ctx.EntClient.StepCriterion.Query().
Where(stepcriterion.StepId(st.ID)).All(ctx)
for _, c := range crits {
t.Criteria = append(t.Criteria, CriterionVO{
ID: c.ID, Name: c.Name, Unit: c.Unit, Logic: c.Logic,
Target: c.Target, Min: c.Min, Max: c.Max,
})
}
out = append(out, t)
}
return out
}
// ---------- 工位主数据(块3 ----------
type StationReq struct {
Id int `json:"id"`
StationNo int `json:"stationNo"`
Name string `json:"name"`
FlowId int `json:"flowId"`
}
type StationVO struct {
Id int `json:"id"`
StationNo int `json:"stationNo"`
Name string `json:"name"`
FlowId int `json:"flowId"`
FlowName string `json:"flowName"`
ProcessCode int `json:"processCode"`
Status string `json:"status"`
}
func (s *Service) ListStations(ctx context.Context) ([]*StationVO, error) {
stas, err := s.ctx.EntClient.Station.Query().Order(ent.Asc(station.FieldStationNo)).All(ctx)
if err != nil {
return nil, err
}
flows, _ := s.ctx.EntClient.ProcessFlow.Query().All(ctx)
flowMap := map[int]*ent.ProcessFlow{}
for _, f := range flows {
flowMap[f.ID] = f
}
out := make([]*StationVO, 0, len(stas))
for _, st := range stas {
vo := &StationVO{Id: st.ID, StationNo: st.StationNo, Name: st.Name, FlowId: st.FlowId, Status: st.Status}
if f, ok := flowMap[st.FlowId]; ok {
vo.FlowName = f.Name
vo.ProcessCode = f.ProcessCode
}
out = append(out, vo)
}
return out, nil
}
func (s *Service) SaveStation(ctx context.Context, req StationReq, operator string) error {
if req.StationNo <= 0 || req.StationNo > 12 {
return errors.New("工位号必须在 1~12")
}
st, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(req.StationNo)).First(ctx)
if err != nil {
if _, err := s.ctx.EntClient.Station.Create().
SetStationNo(req.StationNo).SetName(req.Name).Save(ctx); err != nil {
return err
}
} else {
// 仅改名称:flowId 不传时保留原绑定
upd := s.ctx.EntClient.Station.UpdateOneID(st.ID).SetName(req.Name)
if req.FlowId > 0 {
flow, fErr := s.ctx.EntClient.ProcessFlow.Get(ctx, req.FlowId)
if fErr != nil {
return errors.New("所选工艺流程不存在")
}
if flow.Status != "ACTIVE" {
return errors.New("该工艺流程已停用,请先启用后再绑定工位")
}
upd.SetFlowId(req.FlowId)
}
if _, err := upd.Save(ctx); err != nil {
return err
}
}
s.ctx.EventLog.Write(ctx, "station.save", "", operator, "station", "", "维护工位",
map[string]any{"stationNo": req.StationNo})
return nil
}
// SetFlowStatus 工艺流程启用/停用。
// 停用:自动解绑对应工位;启用:校验该工位无其他启用流程后自动绑定。
func (s *Service) SetFlowStatus(ctx context.Context, id int, status, operator string) error {
if id <= 0 {
return errors.New("缺少 id")
}
if status != "ACTIVE" && status != "INACTIVE" {
return errors.New("非法状态")
}
flow, err := s.ctx.EntClient.ProcessFlow.Get(ctx, id)
if err != nil {
return errors.New("工艺流程不存在")
}
if status == "INACTIVE" {
// 自动解绑该流程对应的工位
_, _ = s.ctx.EntClient.Station.Update().
Where(station.StationNo(flow.ProcessCode), station.FlowId(flow.ID)).
SetFlowId(0).Save(ctx)
} else {
q := s.ctx.EntClient.ProcessFlow.Query().
Where(processflow.ProcessCode(flow.ProcessCode), processflow.Status("ACTIVE"), processflow.IDNEQ(flow.ID))
if exist, err := q.Exist(ctx); err == nil && exist {
return fmt.Errorf("工位 %d 已存在其他启用的工艺流程,请先停用旧流程", flow.ProcessCode)
}
s.syncStationFlow(ctx, flow.ProcessCode, flow.ID)
}
if err := s.ctx.EntClient.ProcessFlow.UpdateOneID(id).SetStatus(status).Exec(ctx); err != nil {
return err
}
action := "停用工艺流程"
if status == "ACTIVE" {
action = "启用工艺流程"
}
s.ctx.EventLog.Write(ctx, "process.flow.status", "", operator, "process_flow", "", action, map[string]any{"flowId": id, "status": status, "name": flow.Name, "stationNo": flow.ProcessCode})
return nil
}
// ---------- 工位任务(块4/5:工位终端拉取) ----------
func (s *Service) StationTask(ctx context.Context, stationNo int) (map[string]any, error) {
st, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(stationNo)).First(ctx)
if err != nil {
return nil, errors.New("工位不存在或未配置")
}
var flow *ent.ProcessFlow
if st.FlowId > 0 {
flow, _ = s.ctx.EntClient.ProcessFlow.Get(ctx, st.FlowId)
}
steps := []*ProcessStepTemplate{}
if flow != nil {
steps = s.flowSteps(ctx, flow.ID, flow.ProcessCode)
}
orderNos := []string{}
wos, _ := s.ctx.EntClient.WorkOrder.Query().
Where(workorder.StatusIn("CREATED", "RELEASED", "IN_PROGRESS")).
Order(ent.Asc(workorder.FieldID)).Limit(10).All(ctx)
for _, w := range wos {
orderNos = append(orderNos, w.WorkOrderNo)
}
return map[string]any{
"stationNo": st.StationNo,
"stationName": st.Name,
"flow": flow,
"steps": steps,
"orderNos": orderNos,
}, nil
}
// ---------- 工作量/绩效报表(块6 ----------
type WorkloadRow struct {
Operator string `json:"operator"`
StationNo string `json:"stationNo"`
Date string `json:"date"`
ProcessCode int `json:"processCode"`
ProcessName string `json:"processName"`
DoneCount int `json:"doneCount"`
OkCount int `json:"okCount"`
NgCount int `json:"ngCount"`
}
func (s *Service) Workload(ctx context.Context, operator, stationNo, from, to string) (map[string]any, error) {
q := s.ctx.EntClient.WorkpieceProcess.Query()
if operator != "" {
q = q.Where(workpieceprocess.Operator(operator))
}
if stationNo != "" {
q = q.Where(workpieceprocess.StationNo(stationNo))
}
if from != "" {
if f, err := time.Parse("2006-01-02", from); err == nil {
q = q.Where(workpieceprocess.CreatedAtGTE(f))
}
}
if to != "" {
if t, err := time.Parse("2006-01-02", to); err == nil {
q = q.Where(workpieceprocess.CreatedAtLT(t.Add(24 * time.Hour)))
}
}
rows, err := q.All(ctx)
if err != nil {
return nil, err
}
type key struct{ op, station, date string; code int }
agg := map[key]*WorkloadRow{}
for _, r := range rows {
k := key{r.Operator, r.StationNo, r.CreatedAt.Format("2006-01-02"), r.ProcessCode}
row, ok := agg[k]
if !ok {
row = &WorkloadRow{Operator: r.Operator, StationNo: r.StationNo, Date: k.date, ProcessCode: r.ProcessCode, ProcessName: r.ProcessName}
agg[k] = row
}
row.DoneCount++
if r.Result == "OK" {
row.OkCount++
} else {
row.NgCount++
}
}
list := make([]*WorkloadRow, 0, len(agg))
for _, v := range agg {
list = append(list, v)
}
sort.Slice(list, func(i, j int) bool {
if list[i].Operator != list[j].Operator {
return list[i].Operator < list[j].Operator
}
if list[i].StationNo != list[j].StationNo {
return list[i].StationNo < list[j].StationNo
}
if list[i].Date != list[j].Date {
return list[i].Date < list[j].Date
}
return list[i].ProcessCode < list[j].ProcessCode
})
return map[string]any{"rows": list, "detail": rows}, nil
}