Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
111 lines
4.8 KiB
Go
111 lines
4.8 KiB
Go
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
|
|
} |