Files
olons_admin/server/internal/logic/sys/approval.go
T
2026-10-10 20:56:17 +08:00

353 lines
12 KiB
Go

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,
})
}