package sys import ( "context" "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/contexts" "hotgo/internal/library/hgorm/handler" "hotgo/internal/library/hgorm/hook" "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/grand" ) type sSysApproval struct{} func NewSysApproval() *sSysApproval { return &sSysApproval{} } func init() { service.RegisterSysApproval(NewSysApproval()) } func (s *sSysApproval) Model(ctx context.Context, option ...*handler.Option) *gdb.Model { if len(option) == 0 { option = append(option, &handler.Option{FilterTenant: true}) } return handler.Model(dao.ApprovalRequest.Ctx(ctx), option...) } func (s *sSysApproval) List(ctx context.Context, in *sysin.ApprovalListInp) (list []*sysin.ApprovalListModel, totalCount int, err error) { mod := s.Model(ctx).Fields(sysin.ApprovalListModel{}) columns := dao.ApprovalRequest.Columns() if in.ApprovalNo != "" { mod = mod.WhereLike(columns.ApprovalNo, "%"+in.ApprovalNo+"%") } if in.ApprovalType != "" { mod = mod.Where(columns.ApprovalType, in.ApprovalType) } if in.TargetType != "" { mod = mod.Where(columns.TargetType, in.TargetType) } if in.TargetId > 0 { mod = mod.Where(columns.TargetId, in.TargetId) } if in.ApprovalStatus > 0 { mod = mod.Where(columns.ApprovalStatus, in.ApprovalStatus) } if in.RiskLevel > 0 { mod = mod.Where(columns.RiskLevel, in.RiskLevel) } if in.Applicant != "" { ids, queryErr := service.AdminMember().GetIdsByKeyword(ctx, in.Applicant) if queryErr != nil { return nil, 0, queryErr } mod = mod.WhereIn(columns.ApplicantId, ids) } if len(in.CreatedAt) == 2 { mod = mod.WhereBetween(columns.CreatedAt, in.CreatedAt[0], in.CreatedAt[1]) } mod = mod.Page(in.Page, in.PerPage).OrderDesc(columns.Id) if err = mod.ScanAndCount(&list, &totalCount, false); err != nil { return nil, 0, gerror.Wrap(err, "获取审批列表失败") } for _, item := range list { item.AllowedActions = s.allowedActions(ctx, item.ApplicantId, item.ApprovalStatus) } return } func (s *sSysApproval) View(ctx context.Context, in *sysin.ApprovalViewInp) (res *sysin.ApprovalViewModel, err error) { if err = s.Model(ctx).WherePri(in.Id).Scan(&res); err != nil { return nil, gerror.Wrap(err, "获取审批详情失败") } if res == nil || res.Id == 0 { return nil, gerror.New("审批记录不存在或无权查看") } if err = dao.ApprovalAction.Ctx(ctx). Where(dao.ApprovalAction.Columns().ApprovalRequestId, in.Id). OrderAsc(dao.ApprovalAction.Columns().StepNo). OrderAsc(dao.ApprovalAction.Columns().Id). Scan(&res.Actions); err != nil { return nil, gerror.Wrap(err, "获取审批记录失败") } if res.Actions == nil { res.Actions = []*sysin.ApprovalActionModel{} } res.AllowedActions = s.allowedActions(ctx, res.ApplicantId, res.ApprovalStatus) return } func (s *sSysApproval) allowedActions(ctx context.Context, applicantId uint64, status int) []string { userId := uint64(contexts.GetUserId(ctx)) actions := make([]string, 0, 2) if userId == applicantId { if status == consts.ApprovalStatusDraft { actions = append(actions, "submit") } if status == consts.ApprovalStatusDraft || status == consts.ApprovalStatusPending || status == consts.ApprovalStatusReviewing { actions = append(actions, "cancel") } return actions } if status == consts.ApprovalStatusPending || status == consts.ApprovalStatusReviewing { actions = append(actions, "approve", "reject") } return actions } func (s *sSysApproval) Create(ctx context.Context, in *sysin.ApprovalCreateInp) (res *sysin.ApprovalCreateModel, err error) { userId := contexts.GetUserId(ctx) if userId <= 0 { return nil, gerror.New("未获取到当前操作人") } approvalNo := "AP" + gtime.Now().Format("YmdHis") + grand.S(6) data := &sysin.ApprovalRequestInsertFields{ ApprovalNo: approvalNo, ApprovalType: in.ApprovalType, TargetType: in.TargetType, TargetId: in.TargetId, Action: in.Action, Title: in.Title, PayloadJson: in.PayloadJson, DiffJson: in.DiffJson, RiskLevel: in.RiskLevel, RequiredApprovals: in.RequiredApprovals, ApprovalStatus: consts.ApprovalStatusDraft, ApplicantId: uint64(userId), ExpireAt: in.ExpireAt, Version: 0, Remark: in.Remark, CreatedBy: uint64(userId), } result, err := s.Model(ctx, &handler.Option{}). Fields(sysin.ApprovalRequestInsertFields{}). Hook(hook.SaveTenant). Data(data).Insert() if err != nil { return nil, gerror.Wrap(err, "创建审批草稿失败") } id, err := result.LastInsertId() if err != nil { return nil, gerror.Wrap(err, "获取审批ID失败") } return &sysin.ApprovalCreateModel{Id: uint64(id), ApprovalNo: approvalNo}, nil } func (s *sSysApproval) Submit(ctx context.Context, in *sysin.ApprovalSubmitInp) error { return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { request, err := s.lockRequest(ctx, in.Id) if err != nil { return err } if request.ApplicantId != uint64(contexts.GetUserId(ctx)) { return gerror.New("只能提交本人创建的审批") } if request.ApprovalStatus != consts.ApprovalStatusDraft { return gerror.New("仅草稿状态可以提交") } if request.Version != in.Version { return gerror.New("审批数据已更新,请刷新后重试") } now := gtime.Now() if request.ExpireAt != nil && request.ExpireAt.Before(now) { return gerror.New("审批已超过有效期,无法提交") } if err = s.updateStatus(ctx, request, consts.ApprovalStatusPending, now, false); err != nil { return err } return s.appendAction(ctx, request, 1, consts.ApprovalActionSubmit, "", "提交审批", 0) }) } func (s *sSysApproval) Approve(ctx context.Context, in *sysin.ApprovalDecisionInp) error { return s.decide(ctx, in, true) } func (s *sSysApproval) Reject(ctx context.Context, in *sysin.ApprovalDecisionInp) error { if in.Comment == "" { return gerror.New("驳回时必须填写审批意见") } return s.decide(ctx, in, false) } func (s *sSysApproval) Cancel(ctx context.Context, in *sysin.ApprovalCancelInp) error { return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { request, err := s.lockRequest(ctx, in.Id) if err != nil { return err } if request.ApplicantId != uint64(contexts.GetUserId(ctx)) { return gerror.New("只能取消本人发起的审批") } if request.Version != in.Version { return gerror.New("审批数据已更新,请刷新后重试") } if request.ApprovalStatus != consts.ApprovalStatusDraft && request.ApprovalStatus != consts.ApprovalStatusPending && request.ApprovalStatus != consts.ApprovalStatusReviewing { return gerror.New("当前审批状态不允许取消") } stepNo, err := s.nextStep(ctx, request.Id) if err != nil { return err } if err = s.updateStatus(ctx, request, consts.ApprovalStatusCancelled, gtime.Now(), true); err != nil { return err } return s.appendAction(ctx, request, stepNo, consts.ApprovalActionCancel, "", in.Comment, 0) }) } func (s *sSysApproval) decide(ctx context.Context, in *sysin.ApprovalDecisionInp, approved bool) error { return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { request, err := s.lockRequest(ctx, in.Id) if err != nil { return err } actorId := uint64(contexts.GetUserId(ctx)) if actorId == 0 { return gerror.New("未获取到当前操作人") } if request.ApplicantId == actorId { return gerror.New("申请人不能审批自己的申请") } if request.Version != in.Version { return gerror.New("审批数据已更新,请刷新后重试") } if request.ApprovalStatus != consts.ApprovalStatusPending && request.ApprovalStatus != consts.ApprovalStatusReviewing { return gerror.New("当前审批状态不允许处理") } if request.ExpireAt != nil && request.ExpireAt.Before(gtime.Now()) { return gerror.New("审批已超过有效期") } count, err := dao.ApprovalAction.Ctx(ctx). Where(dao.ApprovalAction.Columns().ApprovalRequestId, request.Id). Where(dao.ApprovalAction.Columns().ActorId, actorId). WhereIn(dao.ApprovalAction.Columns().Action, []int{consts.ApprovalActionApprove, consts.ApprovalActionReject}).Count() if err != nil { return gerror.Wrap(err, "检查重复审批失败") } if count > 0 { return gerror.New("请勿重复审批") } stepNo, err := s.nextStep(ctx, request.Id) if err != nil { return err } action := consts.ApprovalActionReject status := consts.ApprovalStatusRejected decided := true if approved { action = consts.ApprovalActionApprove approvedCount, countErr := dao.ApprovalAction.Ctx(ctx). Where(dao.ApprovalAction.Columns().ApprovalRequestId, request.Id). Where(dao.ApprovalAction.Columns().Action, consts.ApprovalActionApprove).Count() if countErr != nil { return gerror.Wrap(countErr, "统计审批进度失败") } if uint(approvedCount+1) >= request.RequiredApprovals { status = consts.ApprovalStatusApproved } else { status = consts.ApprovalStatusReviewing decided = false } } if err = s.updateStatus(ctx, request, status, gtime.Now(), decided); err != nil { return err } return s.appendAction(ctx, request, stepNo, action, in.ReasonCode, in.Comment, in.MfaVerified) }) } func (s *sSysApproval) lockRequest(ctx context.Context, id uint64) (*entity.ApprovalRequest, error) { var request *entity.ApprovalRequest err := s.Model(ctx).WherePri(id).LockUpdate().Scan(&request) if err != nil { return nil, gerror.Wrap(err, "读取审批数据失败") } if request == nil || request.Id == 0 { return nil, gerror.New("审批记录不存在或无权操作") } return request, nil } func (s *sSysApproval) updateStatus(ctx context.Context, request *entity.ApprovalRequest, status int, now *gtime.Time, decided bool) error { data := g.Map{ dao.ApprovalRequest.Columns().ApprovalStatus: status, dao.ApprovalRequest.Columns().UpdatedBy: contexts.GetUserId(ctx), dao.ApprovalRequest.Columns().Version: request.Version + 1, } if status == consts.ApprovalStatusPending { data[dao.ApprovalRequest.Columns().SubmittedAt] = now } if decided { data[dao.ApprovalRequest.Columns().DecidedAt] = now } result, err := s.Model(ctx). WherePri(request.Id). Where(dao.ApprovalRequest.Columns().Version, request.Version). Data(data).Update() if err != nil { return gerror.Wrap(err, "更新审批状态失败") } affected, err := result.RowsAffected() if err != nil { return gerror.Wrap(err, "确认审批更新结果失败") } if affected != 1 { return gerror.New("审批数据已更新,请刷新后重试") } return nil } func (s *sSysApproval) nextStep(ctx context.Context, requestId uint64) (uint, error) { count, err := dao.ApprovalAction.Ctx(ctx). Where(dao.ApprovalAction.Columns().ApprovalRequestId, requestId).Count() if err != nil { return 0, gerror.Wrap(err, "获取审批步骤失败") } return uint(count + 1), nil } func (s *sSysApproval) appendAction(ctx context.Context, request *entity.ApprovalRequest, stepNo uint, action int, reasonCode, comment string, mfaVerified int) error { snapshot := gjson.New(request) _, err := dao.ApprovalAction.Ctx(ctx). Hook(hook.SaveTenant). Data(g.Map{ dao.ApprovalAction.Columns().ApprovalRequestId: request.Id, dao.ApprovalAction.Columns().StepNo: stepNo, dao.ApprovalAction.Columns().ActorId: contexts.GetUserId(ctx), dao.ApprovalAction.Columns().Action: action, dao.ApprovalAction.Columns().ReasonCode: reasonCode, dao.ApprovalAction.Columns().Comment: comment, dao.ApprovalAction.Columns().SnapshotJson: snapshot, dao.ApprovalAction.Columns().MfaVerified: mfaVerified, }).Insert() if err != nil { return gerror.Wrap(err, "记录审批动作失败") } return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{ Module: "approval", Action: "approval.action", TargetType: "approval_request", TargetId: request.Id, Before: request, After: g.Map{"action": action, "stepNo": stepNo}, ChangeJson: gjson.New(g.Map{"action": action, "stepNo": stepNo, "reasonCode": reasonCode, "comment": comment}), Reason: comment, ApprovalNo: request.ApprovalNo, }) }