100 lines
3.1 KiB
Go
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)
|
|
}
|