feat(mes): 添加定时同步可生产数量到WMS及BOM项相关标准字段
- 在main.go中添加定时任务每10分钟同步可生产数量到WMS系统 - 为BomItem实体添加relatedStandard字段及相关CRUD方法 - 为InspectionRecord实体添加reportNo、materialCode、materialName等字段 - 更新ent schema确保新字段的验证和默认值设置 - 添加必要的数据库迁移和字段映射逻辑
This commit is contained in:
@@ -3,9 +3,13 @@ package logic
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/processstep"
|
||||
"bj_power_mes/ent/station"
|
||||
"bj_power_mes/ent/stepcriterion"
|
||||
"bj_power_mes/ent/torqueauditlog"
|
||||
@@ -89,8 +93,8 @@ func (s *Service) evaluateTorqueCriterion(ctx context.Context, sn, stationNo str
|
||||
}
|
||||
}
|
||||
|
||||
// ListTorqueRecords 查询拧紧数据
|
||||
func (s *Service) ListTorqueRecords(ctx context.Context, sn, workOrderNo string, from, to time.Time) ([]*ent.TorqueRecord, error) {
|
||||
// ListTorqueRecords 查询拧紧数据(四条件复合取交集:工单号/SN/日期区间/工位)
|
||||
func (s *Service) ListTorqueRecords(ctx context.Context, sn, workOrderNo, stationNo string, from, to time.Time) ([]*ent.TorqueRecord, error) {
|
||||
q := s.ctx.EntClient.TorqueRecord.Query()
|
||||
if sn != "" {
|
||||
// 大小写不敏感(2026-09-08 全项目搜索规范)
|
||||
@@ -99,35 +103,154 @@ func (s *Service) ListTorqueRecords(ctx context.Context, sn, workOrderNo string,
|
||||
if workOrderNo != "" {
|
||||
q = q.Where(torquerecord.WorkOrderNoEqualFold(workOrderNo))
|
||||
}
|
||||
if stationNo != "" {
|
||||
q = q.Where(torquerecord.StationNoEqualFold(stationNo))
|
||||
}
|
||||
if !from.IsZero() {
|
||||
q = q.Where(torquerecord.TimeGTE(from))
|
||||
}
|
||||
if !to.IsZero() {
|
||||
q = q.Where(torquerecord.TimeLTE(to))
|
||||
}
|
||||
return q.Order(ent.Desc(torquerecord.FieldTime)).Limit(500).All(ctx)
|
||||
}
|
||||
|
||||
// AddTorqueRecord 拧紧数据补录(管理端"拧紧查询-拧紧补录"tab 用,定位为设备漏传/手工修正)
|
||||
func (s *Service) AddTorqueRecord(ctx context.Context, sn, workOrderNo, screwNo, stationNo string, strain, angle float64, result, reason, operator string) error {
|
||||
if sn == "" || workOrderNo == "" {
|
||||
return errors.New("工单号与 SN 必填")
|
||||
// requiredScrews 按工位号取该工位绑定工艺流程中 isTorque=true 的步骤数 = 应拧数量。
|
||||
// 工位未绑定流程或流程无扭矩步骤时返回 0(表示无工艺基准,前端标注为「未配置」)。
|
||||
func (s *Service) requiredScrews(ctx context.Context, stationNo string) int {
|
||||
no, err := strconv.Atoi(strings.TrimSpace(stationNo))
|
||||
if err != nil || no <= 0 {
|
||||
return 0
|
||||
}
|
||||
if reason == "" {
|
||||
return errors.New("补录原因必填(如:设备漏传/手工修正)")
|
||||
st, err := s.ctx.EntClient.Station.Query().Where(station.StationNo(no)).First(ctx)
|
||||
if err != nil || st.FlowId <= 0 {
|
||||
return 0
|
||||
}
|
||||
if result == "" {
|
||||
result = "OK"
|
||||
}
|
||||
now := time.Now()
|
||||
rec, err := s.ctx.EntClient.TorqueRecord.Create().
|
||||
SetSn(sn).SetWorkOrderNo(workOrderNo).SetScrewNo(screwNo).SetStationNo(stationNo).
|
||||
SetStrain(strain).SetTorque(strain).SetAngle(angle).
|
||||
SetResult(result).SetOperator(operator + "(补录)").SetTime(now).
|
||||
Save(ctx)
|
||||
cnt, _ := s.ctx.EntClient.ProcessStep.Query().
|
||||
Where(processstep.FlowId(st.FlowId), processstep.IsTorque(true)).Count(ctx)
|
||||
return cnt
|
||||
}
|
||||
|
||||
// TorqueGroupVO 拧紧一级汇总行(按 工单号+SN+工位 分组)
|
||||
type TorqueGroupVO struct {
|
||||
WorkOrderNo string `json:"workOrderNo"`
|
||||
Sn string `json:"sn"`
|
||||
StationNo string `json:"stationNo"`
|
||||
Required int `json:"required"` // 应拧数量(工艺流程 isTorque 步骤数)
|
||||
Actual int `json:"actual"` // 实际记录数
|
||||
OkCount int `json:"okCount"` // 合格颗数
|
||||
NgCount int `json:"ngCount"` // 不合格颗数
|
||||
Satisfied bool `json:"satisfied"` // 是否满足:应拧>0 且 实拧>=应拧 且 无不合格
|
||||
Audited bool `json:"audited"` // 是否已全部审核签字
|
||||
Operator string `json:"operator"` // 最近操作人
|
||||
LastTime time.Time `json:"lastTime"` // 最近采集时间
|
||||
}
|
||||
|
||||
// ListTorqueGroups 拧紧一级汇总列表:按 工单号+SN+工位 分组,附「应拧 vs 实拧」满足判定,后端真分页。
|
||||
func (s *Service) ListTorqueGroups(ctx context.Context, sn, workOrderNo, stationNo string, from, to time.Time, page, pageSize int) (map[string]any, error) {
|
||||
recs, err := s.ListTorqueRecords(ctx, sn, workOrderNo, stationNo, from, to)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
_, _ = s.ctx.EntClient.TorqueAuditLog.Create().
|
||||
SetTorqueRecordId(rec.ID).SetAction("补录").SetOperator(operator).
|
||||
SetRemark(reason).SetNewVal(result).Save(ctx)
|
||||
s.ctx.EventLog.Write(ctx, "torque.manual.add", workOrderNo, operator, "torque_record", sn,
|
||||
"拧紧数据补录 "+workOrderNo+" "+sn, map[string]any{"reason": reason, "result": result, "screwNo": screwNo, "stationNo": stationNo})
|
||||
return nil
|
||||
type gkey struct{ wo, sn, st string }
|
||||
agg := map[gkey]*TorqueGroupVO{}
|
||||
reqCache := map[string]int{}
|
||||
for _, r := range recs {
|
||||
k := gkey{r.WorkOrderNo, r.Sn, r.StationNo}
|
||||
g, ok := agg[k]
|
||||
if !ok {
|
||||
g = &TorqueGroupVO{WorkOrderNo: r.WorkOrderNo, Sn: r.Sn, StationNo: r.StationNo}
|
||||
agg[k] = g
|
||||
}
|
||||
g.Actual++
|
||||
if r.Result == "OK" {
|
||||
g.OkCount++
|
||||
} else {
|
||||
g.NgCount++
|
||||
}
|
||||
if r.Time.After(g.LastTime) {
|
||||
g.LastTime = r.Time
|
||||
g.Operator = r.Operator
|
||||
}
|
||||
}
|
||||
list := make([]*TorqueGroupVO, 0, len(agg))
|
||||
for _, g := range agg {
|
||||
req, ok := reqCache[g.StationNo]
|
||||
if !ok {
|
||||
req = s.requiredScrews(ctx, g.StationNo)
|
||||
reqCache[g.StationNo] = req
|
||||
}
|
||||
g.Required = req
|
||||
g.Satisfied = req > 0 && g.Actual >= req && g.NgCount == 0
|
||||
list = append(list, g)
|
||||
}
|
||||
// Audited 需基于分组内全部记录:任一条未审核即整组未审核
|
||||
auditOK := map[gkey]bool{}
|
||||
for _, r := range recs {
|
||||
k := gkey{r.WorkOrderNo, r.Sn, r.StationNo}
|
||||
if _, seen := auditOK[k]; !seen {
|
||||
auditOK[k] = true
|
||||
}
|
||||
if r.AuditBy == "" {
|
||||
auditOK[k] = false
|
||||
}
|
||||
}
|
||||
for _, g := range list {
|
||||
g.Audited = auditOK[gkey{g.WorkOrderNo, g.Sn, g.StationNo}]
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool { return list[i].LastTime.After(list[j].LastTime) })
|
||||
|
||||
total := len(list)
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
end := start + pageSize
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
return map[string]any{"total": total, "list": list[start:end], "page": page, "pageSize": pageSize}, nil
|
||||
}
|
||||
|
||||
// TorqueScrewStat 单工件单工位「应拧 vs 实拧」统计(二级明细抽屉顶部展示)
|
||||
func (s *Service) TorqueScrewStat(ctx context.Context, sn, stationNo string) (*TorqueGroupVO, error) {
|
||||
if strings.TrimSpace(sn) == "" {
|
||||
return nil, errors.New("sn 不能为空")
|
||||
}
|
||||
q := s.ctx.EntClient.TorqueRecord.Query().Where(torquerecord.SnEqualFold(sn))
|
||||
if stationNo != "" {
|
||||
q = q.Where(torquerecord.StationNoEqualFold(stationNo))
|
||||
}
|
||||
recs, _ := q.All(ctx)
|
||||
vo := &TorqueGroupVO{Sn: sn, StationNo: stationNo, Required: s.requiredScrews(ctx, stationNo)}
|
||||
allAudited := len(recs) > 0
|
||||
for _, r := range recs {
|
||||
vo.Actual++
|
||||
if r.WorkOrderNo != "" && vo.WorkOrderNo == "" {
|
||||
vo.WorkOrderNo = r.WorkOrderNo
|
||||
}
|
||||
if r.Result == "OK" {
|
||||
vo.OkCount++
|
||||
} else {
|
||||
vo.NgCount++
|
||||
}
|
||||
if r.AuditBy == "" {
|
||||
allAudited = false
|
||||
}
|
||||
if r.Time.After(vo.LastTime) {
|
||||
vo.LastTime = r.Time
|
||||
vo.Operator = r.Operator
|
||||
}
|
||||
}
|
||||
vo.Satisfied = vo.Required > 0 && vo.Actual >= vo.Required && vo.NgCount == 0
|
||||
vo.Audited = allAudited
|
||||
return vo, nil
|
||||
}
|
||||
|
||||
// AuditTorqueRecord 拧紧记录审核签字:标记审核人/时间并写留痕
|
||||
@@ -166,14 +289,15 @@ func (s *Service) hasWorkpieceProcess(ctx context.Context, sn string, processCod
|
||||
|
||||
// ProcessStepTemplate 下发给前端渲染的参数采集模板
|
||||
type ProcessStepTemplate struct {
|
||||
ID int `json:"id"`
|
||||
Seq int `json:"seq"`
|
||||
Name string `json:"name"`
|
||||
CollectType string `json:"collectType"` // AUTO(拧紧枪自动)/MANUAL(手填)/NONE
|
||||
IsTorque bool `json:"isTorque"`
|
||||
NeedCheck bool `json:"needCheck"`
|
||||
Remark string `json:"remark"`
|
||||
Criteria []CriterionVO `json:"criteria"`
|
||||
ID int `json:"id"`
|
||||
Seq int `json:"seq"`
|
||||
Name string `json:"name"`
|
||||
CollectType string `json:"collectType"` // AUTO(拧紧枪自动)/MANUAL(手填)/NONE
|
||||
IsTorque bool `json:"isTorque"`
|
||||
NeedCheck bool `json:"needCheck"`
|
||||
Remark string `json:"remark"`
|
||||
Attachment []map[string]string `json:"attachment"` // 本步骤图纸/附件 [{name,url}],按工序分发到工位终端
|
||||
Criteria []CriterionVO `json:"criteria"`
|
||||
}
|
||||
|
||||
// CriterionVO 考核标准视图
|
||||
@@ -208,12 +332,13 @@ func (s *Service) ListStationSteps(ctx context.Context, stationNo int) ([]*Proce
|
||||
|
||||
// StepItemReq 一次保存的一个步骤(含其考核标准)
|
||||
type StepItemReq struct {
|
||||
ID int `json:"id"`
|
||||
Seq int `json:"seq"`
|
||||
Name string `json:"name"`
|
||||
CollectType string `json:"collectType"`
|
||||
IsTorque bool `json:"isTorque"`
|
||||
NeedCheck bool `json:"needCheck"`
|
||||
Remark string `json:"remark"`
|
||||
Criteria []CriterionVO `json:"criteria"`
|
||||
ID int `json:"id"`
|
||||
Seq int `json:"seq"`
|
||||
Name string `json:"name"`
|
||||
CollectType string `json:"collectType"`
|
||||
IsTorque bool `json:"isTorque"`
|
||||
NeedCheck bool `json:"needCheck"`
|
||||
Remark string `json:"remark"`
|
||||
Attachment []map[string]string `json:"attachment"` // 本步骤图纸/附件 [{name,url}]
|
||||
Criteria []CriterionVO `json:"criteria"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user