package service import ( "context" "strings" "time" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "gorm.io/gorm" ) func reportingServiceDB(eventRepo *repository.ReportingEventRepo, rollupRepo *repository.ReportingEventsRollupRepo) *gorm.DB { if rollupRepo != nil && rollupRepo.DB() != nil { return rollupRepo.DB() } if eventRepo != nil { return eventRepo.DB() } return nil } func accountReportingLocation(ctx context.Context, db *gorm.DB, accountID uint) (*time.Location, bool, error) { if db == nil { return nil, false, nil } var account model.Account if err := db.WithContext(ctx).First(&account, accountID).Error; err != nil { return nil, false, err } tz := strings.TrimSpace(account.ReportingTimezone) if tz == "" { return nil, false, nil } loc, err := time.LoadLocation(tz) if err != nil { return nil, false, nil } return loc, true, nil } func utcBoundariesForReportingDate(date time.Time, loc *time.Location) (time.Time, time.Time, time.Time) { localDate := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, loc) startUTC := localDate.UTC() endUTC := localDate.AddDate(0, 0, 1).UTC() rollupDate := time.Date(localDate.Year(), localDate.Month(), localDate.Day(), 0, 0, 0, 0, time.UTC) return startUTC, endUTC, rollupDate } func rollupDateForReportingTime(value time.Time, loc *time.Location) time.Time { localTime := value.In(loc) return time.Date(localTime.Year(), localTime.Month(), localTime.Day(), 0, 0, 0, 0, time.UTC) } func dimensionIDForEvent(accountID uint, event model.ReportingEvent, dim DimensionSpec) (uint, bool) { switch dim.GroupColumn { case "": return accountID, true case "user_id": if event.UserID == nil { return 0, false } return *event.UserID, true case "inbox_id": if event.InboxID == nil { return 0, false } return *event.InboxID, true default: return 0, false } } func isDistinctCountEvent(name string) bool { for _, eventName := range DistinctCountEvents { if name == eventName { return true } } return false } func aggregateReportingEvents(accountID uint, events []model.ReportingEvent) []RollupAggregate { type groupKey struct { dimType model.DimensionType dimID uint metric model.RollupMetric } groups := map[groupKey]*RollupAggregate{} seenDistinct := map[groupKey]map[uint]struct{}{} for _, event := range events { for _, dim := range BackfillDimensions { dimID, ok := dimensionIDForEvent(accountID, event, dim) if !ok { continue } key := groupKey{dimType: dim.Type, dimID: dimID, metric: model.RollupMetric(event.Name)} agg, ok := groups[key] if !ok { agg = &RollupAggregate{DimensionType: dim.Type, DimensionID: dimID, Metric: model.RollupMetric(event.Name)} groups[key] = agg } if isDistinctCountEvent(event.Name) { if event.ConversationID == nil { continue } if seenDistinct[key] == nil { seenDistinct[key] = map[uint]struct{}{} } if _, exists := seenDistinct[key][*event.ConversationID]; exists { continue } seenDistinct[key][*event.ConversationID] = struct{}{} agg.Count++ continue } agg.Count++ agg.SumValue += event.Value agg.SumBizHours += event.ValueInBusinessHours } } aggregates := make([]RollupAggregate, 0, len(groups)) for _, agg := range groups { if agg.Count == 0 { continue } aggregates = append(aggregates, *agg) } return aggregates } func rollupRowsFromAggregates(accountID uint, date time.Time, aggregates []RollupAggregate) []model.ReportingEventsRollup { var rollupRows []model.ReportingEventsRollup for _, agg := range aggregates { rollupMetrics := ExpandEventToRollupMetrics(agg.Metric, agg.Count, agg.SumValue, agg.SumBizHours) for rm, data := range rollupMetrics { rollupRows = append(rollupRows, model.ReportingEventsRollup{ AccountID: accountID, Date: date, DimensionType: agg.DimensionType, DimensionID: agg.DimensionID, Metric: rm, Count: data.Count, SumValue: data.SumValue, SumValueBusinessHours: data.SumBizHours, }) } } return rollupRows }