261 lines
8.9 KiB
Go
261 lines
8.9 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-kratos/kratos/v3/errors"
|
|
)
|
|
|
|
var (
|
|
ErrAlertRecordInput = errors.BadRequest("SW_ALERT_RECORD_INPUT", "预警记录参数不正确")
|
|
ErrAlertRecordNotFound = errors.NotFound("SW_ALERT_RECORD_NOT_FOUND", "预警记录不存在")
|
|
ErrAlertRecordScope = errors.Forbidden("SW_ALERT_RECORD_SCOPE", "无权限查看该预警记录")
|
|
ErrAlertAuditLevel = errors.Forbidden("SW_ALERT_AUDIT_LEVEL", "仅1级商务可审核预警")
|
|
ErrAlertChannelsUnavailable = errors.BadRequest("SW_ALERT_CHANNELS_UNAVAILABLE", "渠道数据尚未接入,暂时无法核对预警可见范围")
|
|
ErrAlertRecordChanged = errors.Conflict("SW_ALERT_RECORD_CHANGED", "预警状态已变化,请刷新后重试")
|
|
ErrAlertNoteEmpty = errors.BadRequest("SW_ALERT_NOTE_EMPTY", "备注内容不能为空")
|
|
)
|
|
|
|
type AlertRecord struct {
|
|
OwnerScope *UserScope
|
|
// 检测写入策略,仅供任务与仓储使用,不向 HTTP 返回。
|
|
GapDays int
|
|
IndependentRule bool
|
|
TotalPlayers *int64
|
|
TotalPay, WarningColor *string
|
|
Lost *bool
|
|
ID, RuleID, TargetPromoteID uint64
|
|
PlatformCode, PlatformName, RuleName, RuleType string
|
|
MonitorMetric *int
|
|
TargetPromoteName string
|
|
BusinessID uint
|
|
BusinessName, AlertContent string
|
|
Status int
|
|
CreatedAt, UpdatedAt time.Time
|
|
}
|
|
|
|
type AlertLog struct {
|
|
ID, AlertID uint64
|
|
OperatorID uint
|
|
OperatorName string
|
|
LogType int
|
|
ActionDetail, NoteContent string
|
|
// 写入兼容旧文件 ID 与 URL,读取由仓储批量解析成附件 URL。
|
|
AttachmentIDs, Attachments []string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
type AlertRecordScope struct {
|
|
Owner LevelOneScope
|
|
Viewer UserScope
|
|
Selected *UserScope
|
|
}
|
|
|
|
type AlertRecordFilter struct {
|
|
AlertContent string
|
|
Lost *bool
|
|
PlatformCodes []string
|
|
TargetPromoteID *uint64
|
|
PlatformBusierID *uint64
|
|
BusinessID *uint
|
|
TargetPromoteName, RuleName, RuleType, BusinessName string
|
|
MonitorMetric, Status *int
|
|
}
|
|
|
|
type AlertRecordPage struct {
|
|
Rows []*AlertRecord
|
|
Total int64
|
|
StatusCount map[int]int64
|
|
Available bool
|
|
}
|
|
|
|
type AlertRecordDetail struct {
|
|
Record *AlertRecord
|
|
Logs []*AlertLog
|
|
CanAudit bool
|
|
}
|
|
|
|
type AlertRecordRepo interface {
|
|
Platforms(context.Context, AlertRecordScope) ([]string, error)
|
|
Ignore(context.Context, AlertRecordScope, []uint64, *AlertLog) error
|
|
List(context.Context, AlertRecordScope, AlertRecordFilter, ...ListOption) (*AlertRecordPage, error)
|
|
Detail(context.Context, AlertRecordScope, uint64) (*AlertRecordDetail, error)
|
|
// 在同一事务和行锁内校验当前记录、执行业务回调、写入状态与日志。
|
|
WithRecord(context.Context, AlertRecordScope, uint64, func(*AlertRecord) ([]*AlertLog, error)) error
|
|
}
|
|
|
|
func (uc *AlertRecordUsecase) Platforms(ctx context.Context) ([]string, error) {
|
|
scope, _, err := uc.scope(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return uc.repo.Platforms(ctx, scope)
|
|
}
|
|
|
|
type AlertAction string
|
|
|
|
const (
|
|
AlertUnprocessed = 1
|
|
AlertProcessing = 2
|
|
AlertPendingAudit = 3
|
|
AlertAuditRejected = 4
|
|
AlertProcessed = 5
|
|
AlertIgnored = 6
|
|
)
|
|
|
|
const (
|
|
AlertStart AlertAction = "start"
|
|
AlertSubmit AlertAction = "submit"
|
|
AlertApprove AlertAction = "pass"
|
|
AlertReject AlertAction = "reject"
|
|
AlertReprocess AlertAction = "reprocess"
|
|
)
|
|
|
|
type AlertRecordUsecase struct {
|
|
repo AlertRecordRepo
|
|
accounts *AccountUsecase
|
|
}
|
|
|
|
func NewAlertRecordUsecase(repo AlertRecordRepo, accounts *AccountUsecase) *AlertRecordUsecase {
|
|
return &AlertRecordUsecase{repo: repo, accounts: accounts}
|
|
}
|
|
|
|
func alertUserScope(a *Account) UserScope {
|
|
if a == nil {
|
|
return UserScope{Empty: true}
|
|
}
|
|
return UserScope{SelfID: a.ID, IncludeSelf: true, OnlySelf: a.Level >= 3, DescendantPath: descendantPath(a)}
|
|
}
|
|
|
|
func (uc *AlertRecordUsecase) scope(ctx context.Context, selected *uint) (AlertRecordScope, *Account, error) {
|
|
actor, account, err := uc.accounts.actor(ctx)
|
|
if err != nil {
|
|
return AlertRecordScope{}, nil, err
|
|
}
|
|
scope := AlertRecordScope{Owner: LevelOneOwner(actor, account), Viewer: alertUserScope(account)}
|
|
if actor.AuthorityID == 888 {
|
|
scope.Viewer = UserScope{All: true}
|
|
}
|
|
if selected != nil {
|
|
target, err := uc.accounts.repo.Find(ctx, *selected)
|
|
if err != nil && !errors.IsNotFound(err) {
|
|
return scope, nil, err
|
|
}
|
|
// 指定商务只增加渠道交集条件,不能替换查看者范围或预警归属一级商务。
|
|
s := alertUserScope(target)
|
|
scope.Selected = &s
|
|
}
|
|
return scope, account, nil
|
|
}
|
|
|
|
func (uc *AlertRecordUsecase) List(ctx context.Context, filter AlertRecordFilter, options ...ListOption) (*AlertRecordPage, error) {
|
|
scope, _, err := uc.scope(ctx, filter.BusinessID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return uc.repo.List(ctx, scope, filter, options...)
|
|
}
|
|
|
|
func (uc *AlertRecordUsecase) Detail(ctx context.Context, id uint64) (*AlertRecordDetail, error) {
|
|
scope, account, err := uc.scope(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
detail, err := uc.repo.Detail(ctx, scope, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// 审核只看商务等级,与规则管理的超管例外不同。
|
|
detail.CanAudit = account.Level == 1
|
|
return detail, nil
|
|
}
|
|
|
|
func alertTransition(status int, action AlertAction) (int, error) {
|
|
var from, to int
|
|
var message string
|
|
switch action {
|
|
case AlertStart:
|
|
from, to, message = AlertUnprocessed, AlertProcessing, "仅未处理状态的预警可开始处理"
|
|
case AlertSubmit:
|
|
from, to, message = AlertProcessing, AlertPendingAudit, "仅处理中状态的预警可提交审核"
|
|
case AlertApprove:
|
|
from, to, message = AlertPendingAudit, AlertProcessed, "仅待审核状态的预警可审核通过"
|
|
case AlertReject:
|
|
from, to, message = AlertPendingAudit, AlertAuditRejected, "仅待审核状态的预警可审核驳回"
|
|
case AlertReprocess:
|
|
from, to, message = AlertAuditRejected, AlertProcessing, "仅审核驳回状态的预警可重新处理"
|
|
default:
|
|
return status, ErrAlertRecordInput
|
|
}
|
|
if status != from {
|
|
return status, errors.BadRequest("SW_ALERT_RECORD_STATUS", message)
|
|
}
|
|
return to, nil
|
|
}
|
|
|
|
func (uc *AlertRecordUsecase) Process(ctx context.Context, id uint64, action AlertAction, note string) error {
|
|
scope, account, err := uc.scope(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (action == AlertApprove || action == AlertReject) && account.Level != 1 {
|
|
return ErrAlertAuditLevel
|
|
}
|
|
return uc.repo.WithRecord(ctx, scope, id, func(record *AlertRecord) ([]*AlertLog, error) {
|
|
status, err := alertTransition(record.Status, action)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
record.Status = status
|
|
logs := []*AlertLog{{AlertID: id, OperatorID: account.ID, OperatorName: account.Username, LogType: 1, ActionDetail: string(action)}}
|
|
if strings.TrimSpace(note) != "" {
|
|
logs = append(logs, &AlertLog{AlertID: id, OperatorID: account.ID, OperatorName: account.Username, LogType: 2, NoteContent: note})
|
|
}
|
|
return logs, nil
|
|
})
|
|
}
|
|
|
|
func (uc *AlertRecordUsecase) AddNote(ctx context.Context, id uint64, note string, attachments []string) error {
|
|
if strings.TrimSpace(note) == "" {
|
|
return ErrAlertNoteEmpty
|
|
}
|
|
scope, account, err := uc.scope(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return uc.repo.WithRecord(ctx, scope, id, func(record *AlertRecord) ([]*AlertLog, error) {
|
|
if record.Status == AlertProcessed || record.Status == AlertIgnored {
|
|
return nil, ErrAlertRecordChanged
|
|
}
|
|
return []*AlertLog{{AlertID: id, OperatorID: account.ID, OperatorName: account.Username, LogType: 2, NoteContent: note, AttachmentIDs: attachments}}, nil
|
|
})
|
|
}
|
|
|
|
func AlertStatusLabel(status int) string {
|
|
return map[int]string{1: "未处理", 2: "处理中", 3: "待审核", 4: "审核驳回", 5: "已处理", 6: "已忽略"}[status]
|
|
}
|
|
|
|
func (uc *AlertRecordUsecase) Ignore(ctx context.Context, ids []uint64) error {
|
|
actor, account, err := uc.accounts.actor(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if actor.AuthorityID != 888 && account.Level != 1 {
|
|
return ErrAlertGovernanceLevel
|
|
}
|
|
if len(ids) == 0 || len(ids) > 500 {
|
|
return ErrAlertRecordInput
|
|
}
|
|
scope, _, err := uc.scope(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return uc.repo.Ignore(ctx, scope, ids, &AlertLog{OperatorID: account.ID, OperatorName: account.Username, LogType: 1, ActionDetail: "ignore"})
|
|
}
|
|
|
|
func AlertLogTypeLabel(logType int) string {
|
|
return map[int]string{1: "状态变更操作", 2: "添加独立备注"}[logType]
|
|
}
|