package service import ( "context" "time" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" applogger "github.com/gochat/gochat/pkg/logger" ) // SummaryReportService implements business logic for summary reports. // Reference: Chatwoot SummaryReport controller — read-only collection GET endpoints for // agent, team, inbox, and label summary metrics. type SummaryReportService struct { rollupRepo *repository.ReportingEventsRollupRepo } // NewSummaryReportService creates a new SummaryReportService. func NewSummaryReportService(rollupRepo *repository.ReportingEventsRollupRepo) *SummaryReportService { return &SummaryReportService{rollupRepo: rollupRepo} } // GetAgentSummary returns agent-level aggregated metrics for a date range. func (s *SummaryReportService) GetAgentSummary(ctx context.Context, accountID uint, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { return s.getDimensionSummary(ctx, accountID, model.DimensionAgent, since, until) } // GetTeamSummary returns team-level aggregated metrics for a date range. func (s *SummaryReportService) GetTeamSummary(ctx context.Context, accountID uint, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { return s.getDimensionSummary(ctx, accountID, model.DimensionTeam, since, until) } // GetInboxSummary returns inbox-level aggregated metrics for a date range. func (s *SummaryReportService) GetInboxSummary(ctx context.Context, accountID uint, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { return s.getDimensionSummary(ctx, accountID, model.DimensionInbox, since, until) } // GetLabelSummary returns label-level aggregated metrics for a date range. func (s *SummaryReportService) GetLabelSummary(ctx context.Context, accountID uint, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { return s.getDimensionSummary(ctx, accountID, model.DimensionLabel, since, until) } // GetAccountSummary returns account-level aggregated metrics for a date range. func (s *SummaryReportService) GetAccountSummary(ctx context.Context, accountID uint, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { return s.getDimensionSummary(ctx, accountID, model.DimensionAccount, since, until) } // GetConversationSummary returns conversation-level aggregated metrics for a date range. func (s *SummaryReportService) GetConversationSummary(ctx context.Context, accountID uint, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { return s.getDimensionSummary(ctx, accountID, model.DimensionConversation, since, until) } // GetChannelSummary returns channel-level aggregated metrics for a date range. // Reference: Chatwoot summary_reports#channel — enforces max 6-month date range func (s *SummaryReportService) GetChannelSummary(ctx context.Context, accountID uint, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { return s.getDimensionSummary(ctx, accountID, model.DimensionInbox, since, until) } // --- Response DTOs for summary reports --- // SummaryReportDimensionMetrics holds metrics grouped by a dimension entity. type SummaryReportDimensionMetrics struct { DimensionID uint `json:"id"` DimensionType string `json:"dimension_type"` Metrics []SummaryReportMetric `json:"metrics"` } // SummaryReportMetric holds a single aggregated metric within a dimension group. type SummaryReportMetric struct { Metric string `json:"metric"` Count int64 `json:"count"` AverageValue float64 `json:"average_value"` AverageBusinessHours float64 `json:"average_business_hours,omitempty"` } // getDimensionSummary queries rollups for a dimension type and groups results by dimension_id. func (s *SummaryReportService) getDimensionSummary(ctx context.Context, accountID uint, dimType model.DimensionType, since, until time.Time) ([]SummaryReportDimensionMetrics, error) { rollups, err := s.rollupRepo.AggregateSummary(ctx, accountID, dimType, since, until) if err != nil { applogger.L().Errorf("SummaryReportService.getDimensionSummary(%s) query: %v", dimType, err) return nil, err } // Group by dimension_id grouped := map[uint]*SummaryReportDimensionMetrics{} for _, r := range rollups { dm, ok := grouped[r.DimensionID] if !ok { dm = &SummaryReportDimensionMetrics{ DimensionID: r.DimensionID, DimensionType: string(r.DimensionType), } grouped[r.DimensionID] = dm } dm.Metrics = append(dm.Metrics, SummaryReportMetric{ Metric: string(r.Metric), Count: r.Count, AverageValue: r.SumValue / float64(r.Count), AverageBusinessHours: r.SumValueBusinessHours / float64(r.Count), }) } // Convert map to sorted slice result := make([]SummaryReportDimensionMetrics, 0, len(grouped)) for _, dm := range grouped { result = append(result, *dm) } return result, nil }