feat: 新增预警与附件模块,优化工位派工功能

1. 新增预警规则、预警消息、业务附件数据库表与CRUD逻辑
2. 为拧紧记录添加审核人、审核时间字段及审核留痕功能
3. 优化产线点位类型与编号描述,更新工位组合下发菜单名称为工艺路线派工
4. 新增上传文件获取原文件名与大小的工具方法
5. 在系统管理菜单新增预警中心与附件中心入口
This commit is contained in:
SunYF
2026-09-14 16:31:08 +08:00
parent 0ab21b1234
commit 5c3b747a20
399 changed files with 147674 additions and 2213 deletions
+106
View File
@@ -0,0 +1,106 @@
package logic
import (
"context"
"errors"
"bj_power_mes/ent"
"bj_power_mes/ent/alert"
"bj_power_mes/ent/alertrule"
)
// CreateAlertRule 新增预警规则
func (s *Service) CreateAlertRule(ctx context.Context, name, typ string, threshold float64, receiver string, enabled bool, createdBy string) error {
if name == "" || typ == "" {
return errors.New("规则名称与类型必填")
}
_, err := s.ctx.EntClient.AlertRule.Create().
SetName(name).SetType(typ).SetThreshold(threshold).
SetReceiver(receiver).SetEnabled(enabled).SetCreatedBy(createdBy).Save(ctx)
return err
}
// UpdateAlertRule 修改预警规则
func (s *Service) UpdateAlertRule(ctx context.Context, id int, name, typ string, threshold float64, receiver string, enabled bool) error {
if id <= 0 {
return errors.New("规则ID无效")
}
if name == "" || typ == "" {
return errors.New("规则名称与类型必填")
}
_, err := s.ctx.EntClient.AlertRule.UpdateOneID(id).
SetName(name).SetType(typ).SetThreshold(threshold).
SetReceiver(receiver).SetEnabled(enabled).Save(ctx)
return err
}
// DeleteAlertRule 删除预警规则
func (s *Service) DeleteAlertRule(ctx context.Context, id int) error {
if id <= 0 {
return errors.New("规则ID无效")
}
return s.ctx.EntClient.AlertRule.DeleteOneID(id).Exec(ctx)
}
// ListAlertRules 查询全部预警规则
func (s *Service) ListAlertRules(ctx context.Context) ([]*ent.AlertRule, error) {
return s.ctx.EntClient.AlertRule.Query().
Order(ent.Desc(alertrule.FieldCreatedAt), ent.Desc(alertrule.FieldID)).All(ctx)
}
// ListAlerts 查询预警消息(status 空=全部)
func (s *Service) ListAlerts(ctx context.Context, status string) ([]*ent.Alert, error) {
q := s.ctx.EntClient.Alert.Query()
if status != "" {
q = q.Where(alert.Status(status))
}
return q.Order(ent.Desc(alert.FieldCreatedAt), ent.Desc(alert.FieldID)).All(ctx)
}
// UnreadAlertCount 未读预警数
func (s *Service) UnreadAlertCount(ctx context.Context) (int, error) {
return s.ctx.EntClient.Alert.Query().Where(alert.Status("UNREAD")).Count(ctx)
}
// MarkAlertRead 标记预警已读
func (s *Service) MarkAlertRead(ctx context.Context, id int) error {
if id <= 0 {
return errors.New("预警ID无效")
}
_, err := s.ctx.EntClient.Alert.UpdateOneID(id).SetStatus("READ").Save(ctx)
return err
}
// alertDirection 阈值比较方向:inventory_low 为 <=,其余为 >=
func alertDirection(typ string) int {
if typ == "inventory_low" {
return -1
}
return 1
}
// Evaluate 预警规则评估:遍历启用且类型匹配的规则,命中阈值则写 alert 消息。
// 由业务事件触发(如拧紧不合格),也可手动调用。
func (s *Service) Evaluate(ctx context.Context, typ string, value float64, title, content, refType, refId string) {
rules, err := s.ctx.EntClient.AlertRule.Query().
Where(alertrule.Type(typ), alertrule.Enabled(true)).All(ctx)
if err != nil || len(rules) == 0 {
return
}
dir := alertDirection(typ)
for _, r := range rules {
hit := (dir == 1 && value >= r.Threshold) || (dir == -1 && value <= r.Threshold)
if !hit {
continue
}
_ = s.createAlert(ctx, r.ID, typ, title, content, refType, refId, r.Receiver)
}
}
func (s *Service) createAlert(ctx context.Context, ruleId int, typ, title, content, refType, refId, receiver string) error {
_, err := s.ctx.EntClient.Alert.Create().
SetRuleId(ruleId).SetType(typ).SetTitle(title).
SetContent(content).SetRefType(refType).SetRefId(refId).
SetReceiver(receiver).SetStatus("UNREAD").Save(ctx)
return err
}
+39
View File
@@ -0,0 +1,39 @@
package logic
import (
"context"
"bj_power_mes/ent"
"bj_power_mes/ent/attachment"
)
// AddAttachment 新增附件记录
func (s *Service) AddAttachment(ctx context.Context, bizType, bizId, fileName, filePath string, fileSize int, uploader string) (*ent.Attachment, error) {
return s.ctx.EntClient.Attachment.Create().
SetBizType(bizType).SetBizId(bizId).SetFileName(fileName).
SetFilePath(filePath).SetFileSize(fileSize).SetUploader(uploader).Save(ctx)
}
// ListAttachments 按业务类型/ID 查询附件
func (s *Service) ListAttachments(ctx context.Context, bizType, bizId string) ([]*ent.Attachment, error) {
q := s.ctx.EntClient.Attachment.Query()
if bizType != "" {
q = q.Where(attachment.BizType(bizType))
}
if bizId != "" {
q = q.Where(attachment.BizId(bizId))
}
return q.Order(ent.Desc(attachment.FieldCreatedAt), ent.Desc(attachment.FieldID)).All(ctx)
}
// DeleteAttachment 删除附件记录并返回记录(供删除物理文件)
func (s *Service) DeleteAttachment(ctx context.Context, id int) (*ent.Attachment, error) {
att, err := s.ctx.EntClient.Attachment.Get(ctx, id)
if err != nil {
return nil, err
}
if err := s.ctx.EntClient.Attachment.DeleteOneID(id).Exec(ctx); err != nil {
return nil, err
}
return att, nil
}
+39 -1
View File
@@ -8,6 +8,7 @@ import (
"bj_power_mes/ent"
"bj_power_mes/ent/station"
"bj_power_mes/ent/stepcriterion"
"bj_power_mes/ent/torqueauditlog"
"bj_power_mes/ent/torquerecord"
"bj_power_mes/ent/workpieceprocess"
)
@@ -63,6 +64,7 @@ func (s *Service) ReportTorque(ctx context.Context, req TorqueReq, operator stri
func (s *Service) evaluateTorqueCriterion(ctx context.Context, sn, stationNo string, strain, angle float64, operator string) {
q := s.ctx.EntClient.StepCriterion.Query().Where(stepcriterion.Name("扭矩"))
crits, _ := q.All(ctx)
ng := false
for _, c := range crits {
if c.Logic != "RANGE" {
continue
@@ -78,6 +80,12 @@ func (s *Service) evaluateTorqueCriterion(ctx context.Context, sn, stationNo str
SetSn(sn).SetStepId(c.StepId).SetCriterionId(c.ID).
SetCriterionName(c.Name).SetCriterionLogic(c.Logic).
SetValue(strain).SetIsOK(ok).SetOperator(operator).Save(ctx)
if !ok {
ng = true
}
}
if ng {
s.Evaluate(ctx, "torque_ng", 1, "拧紧扭矩不合格", "SN="+sn+" 工位"+stationNo, "torque_record", sn)
}
}
@@ -106,7 +114,7 @@ func (s *Service) AddTorqueRecord(ctx context.Context, sn, workOrderNo, screwNo,
result = "OK"
}
now := time.Now()
_, err := s.ctx.EntClient.TorqueRecord.Create().
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).
@@ -114,11 +122,41 @@ func (s *Service) AddTorqueRecord(ctx context.Context, sn, workOrderNo, screwNo,
if err != nil {
return 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
}
// AuditTorqueRecord 拧紧记录审核签字:标记审核人/时间并写留痕
func (s *Service) AuditTorqueRecord(ctx context.Context, recordId int, remark, operator string) error {
rec, err := s.ctx.EntClient.TorqueRecord.Get(ctx, recordId)
if err != nil {
return errors.New("拧紧记录不存在")
}
if rec.AuditBy != "" {
return errors.New("该记录已审核,不可重复审核")
}
_, err = rec.Update().SetAuditBy(operator).SetAuditAt(time.Now()).Save(ctx)
if err != nil {
return err
}
_, _ = s.ctx.EntClient.TorqueAuditLog.Create().
SetTorqueRecordId(rec.ID).SetAction("审核").SetOperator(operator).
SetRemark(remark).SetOldVal(rec.AuditBy).SetNewVal(operator).Save(ctx)
return nil
}
// ListTorqueAuditLog 查询某拧紧记录的修改留痕
func (s *Service) ListTorqueAuditLog(ctx context.Context, recordId int) ([]*ent.TorqueAuditLog, error) {
return s.ctx.EntClient.TorqueAuditLog.Query().
Where(torqueauditlog.TorqueRecordId(recordId)).
Order(ent.Desc(torqueauditlog.FieldCreatedAt)).All(ctx)
}
// 计算一块工件某工序的扭矩步骤是否已报
func (s *Service) hasWorkpieceProcess(ctx context.Context, sn string, processCode int) bool {
cnt, _ := s.ctx.EntClient.WorkpieceProcess.Query().