Files
2026-05-12 18:49:52 +08:00

100 lines
3.1 KiB
Go

package discovery
import (
"context"
"errors"
"strings"
"time"
"supply-intelligence/internal/domain"
)
var (
ErrInvalidCandidateInput = errors.New("invalid candidate input")
ErrPlatformNotSupported = errors.New("platform not supported in registry")
)
type CandidateRepository interface {
GetDiscoveryCandidateByIDContext(ctx context.Context, candidateID string) (domain.DiscoveryCandidate, bool)
FindDiscoveryCandidateContext(ctx context.Context, accountID int64, platform, model string) (domain.DiscoveryCandidate, bool)
UpsertDiscoveryCandidateContext(ctx context.Context, candidate domain.DiscoveryCandidate) domain.DiscoveryCandidate
ListDiscoveryCandidatesContext(ctx context.Context, status domain.DiscoveryCandidateStatus) []domain.DiscoveryCandidate
}
type Service struct {
repo CandidateRepository
now func() time.Time
}
type RecordCandidateInput struct {
CandidateID string
AccountID int64
Platform string
Model string
Source string
ReasonCode string
DiscoveredAt time.Time
}
type RecordCandidateOutput struct {
Candidate domain.DiscoveryCandidate `json:"candidate"`
Created bool `json:"created"`
}
func NewService(repo CandidateRepository) *Service {
return &Service{
repo: repo,
now: func() time.Time {
return time.Now().UTC()
},
}
}
func (s *Service) RecordCandidate(ctx context.Context, input RecordCandidateInput) (RecordCandidateOutput, error) {
if s == nil || s.repo == nil {
return RecordCandidateOutput{}, ErrInvalidCandidateInput
}
candidateID := strings.TrimSpace(input.CandidateID)
platform := strings.TrimSpace(input.Platform)
model := strings.TrimSpace(input.Model)
source := strings.TrimSpace(input.Source)
reasonCode := strings.TrimSpace(input.ReasonCode)
if candidateID == "" || input.AccountID <= 0 || platform == "" || model == "" || source == "" {
return RecordCandidateOutput{}, ErrInvalidCandidateInput
}
if existing, ok := s.repo.GetDiscoveryCandidateByIDContext(ctx, candidateID); ok {
return RecordCandidateOutput{Candidate: existing, Created: false}, nil
}
at := input.DiscoveredAt.UTC()
if at.IsZero() {
at = s.now()
}
if existing, ok := s.repo.FindDiscoveryCandidateContext(ctx, input.AccountID, platform, model); ok {
existing.Source = source
existing.ReasonCode = reasonCode
existing.UpdatedAt = at
existing.Version++
return RecordCandidateOutput{Candidate: s.repo.UpsertDiscoveryCandidateContext(ctx, existing), Created: false}, nil
}
candidate := domain.DiscoveryCandidate{
CandidateID: candidateID,
AccountID: input.AccountID,
Platform: platform,
Model: model,
Source: source,
Status: domain.DiscoveryCandidateStatusDiscovered,
ReasonCode: reasonCode,
DiscoveredAt: at,
UpdatedAt: at,
Version: 1,
}
return RecordCandidateOutput{Candidate: s.repo.UpsertDiscoveryCandidateContext(ctx, candidate), Created: true}, nil
}
func (s *Service) ListCandidates(ctx context.Context, status domain.DiscoveryCandidateStatus) []domain.DiscoveryCandidate {
if s == nil || s.repo == nil {
return nil
}
return s.repo.ListDiscoveryCandidatesContext(ctx, status)
}