353 lines
12 KiB
Go
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,
|
|
})
|
|
}
|