Files
Veyra/internal/service/candidate.go
T
Blizzard 92329ddce2
Checks / verify (push) Failing after 9m52s
feat: 完成模拟交易闭环、App 与 A 股行情接入
统一单入口 Go 工程与前端依赖目录,增加后台供应商配置、候选审核、计划预算、成交冲正、持仓复盘和站内通知。接入策略版本、Python 回测、可配置 A 股公开行情及 zap 文件轮转日志,补充迁移、契约与验证。
2026-10-08 22:58:52 +08:00

103 lines
3.7 KiB
Go

package service
import (
"context"
"time"
"git.sundynix.cn/Blizzard/Veyra/internal/apperror"
"git.sundynix.cn/Blizzard/Veyra/internal/model"
"github.com/google/uuid"
)
type CandidateRepository interface {
List(context.Context, bool, string, int) ([]model.CandidateRevision, error)
Detail(context.Context, string, bool) (model.CandidateDetail, error)
Create(context.Context, model.CandidateDraftInput, string, string, string, int64) (model.CandidateRevision, error)
Transition(context.Context, string, string, model.CandidateActionInput, string, string) (model.CandidateRevision, error)
}
type CandidateService struct {
repo CandidateRepository
development bool
}
func NewCandidateService(repo CandidateRepository, development bool) *CandidateService {
return &CandidateService{repo: repo, development: development}
}
func (s *CandidateService) authorize(actor model.User, admin bool) error {
if actor.Status != "ACTIVE" || (admin && actor.Role != "ADMIN") || (!admin && actor.Role != "USER") {
return apperror.ErrForbidden
}
if s == nil || s.repo == nil {
return apperror.ErrUnavailable
}
// Manual simulation publishing is a development capability, not production authorization.
if !s.development {
return apperror.ErrUnavailable
}
return nil
}
func (s *CandidateService) List(ctx context.Context, actor model.User, admin bool, status string, limit int) ([]model.CandidateRevision, error) {
if err := s.authorize(actor, admin); err != nil {
return nil, err
}
if limit < 1 || limit > 100 {
return nil, apperror.ErrInvalidInput
}
switch status {
case "", "DRAFT", "PENDING_REVIEW", "PUBLISHED", "RETURNED", "WITHDRAWN":
default:
return nil, apperror.ErrInvalidInput
}
if !admin && status != "" {
return nil, apperror.ErrInvalidInput
}
return s.repo.List(ctx, admin, status, limit)
}
func (s *CandidateService) Detail(ctx context.Context, actor model.User, id string, admin bool) (model.CandidateDetail, error) {
if err := s.authorize(actor, admin); err != nil {
return model.CandidateDetail{}, err
}
if _, err := uuid.Parse(id); err != nil {
return model.CandidateDetail{}, apperror.ErrInvalidInput
}
return s.repo.Detail(ctx, id, admin)
}
func (s *CandidateService) Create(ctx context.Context, actor model.User, source, requestID string, input model.CandidateDraftInput, expected int64) (model.CandidateRevision, error) {
if err := s.authorize(actor, true); err != nil {
return model.CandidateRevision{}, err
}
if source != "" {
if _, err := uuid.Parse(source); err != nil {
return model.CandidateRevision{}, apperror.ErrInvalidInput
}
if expected < 1 {
return model.CandidateRevision{}, apperror.ErrInvalidInput
}
}
if _, err := uuid.Parse(requestID); err != nil {
return model.CandidateRevision{}, apperror.ErrInvalidInput
}
if err := model.ValidateDraft(input, time.Now().UTC()); err != nil {
return model.CandidateRevision{}, err
}
return s.repo.Create(ctx, input, actor.ID, requestID, source, expected)
}
func (s *CandidateService) Transition(ctx context.Context, actor model.User, id, action, requestID string, input model.CandidateActionInput) (model.CandidateRevision, error) {
if err := s.authorize(actor, true); err != nil {
return model.CandidateRevision{}, err
}
if _, err := uuid.Parse(id); err != nil {
return model.CandidateRevision{}, apperror.ErrInvalidInput
}
if _, err := uuid.Parse(requestID); err != nil {
return model.CandidateRevision{}, apperror.ErrInvalidInput
}
if input.ExpectedVersion < 1 {
return model.CandidateRevision{}, apperror.ErrInvalidInput
}
if err := model.ValidateReason(input.Reason); err != nil {
return model.CandidateRevision{}, err
}
return s.repo.Transition(ctx, id, action, input, actor.ID, requestID)
}