backend-service/internal/services/admin_service.go

305 lines
9.3 KiB
Go
Raw Normal View History

package services
import (
"fmt"
"log"
"time"
"attune-heart-therapy/internal/models"
"attune-heart-therapy/internal/repositories"
)
// adminService implements the AdminService interface
type adminService struct {
userRepo repositories.UserRepository
bookingRepo repositories.BookingRepository
scheduleRepo repositories.ScheduleRepository
}
// NewAdminService creates a new instance of AdminService
func NewAdminService(
userRepo repositories.UserRepository,
bookingRepo repositories.BookingRepository,
scheduleRepo repositories.ScheduleRepository,
) AdminService {
return &adminService{
userRepo: userRepo,
bookingRepo: bookingRepo,
scheduleRepo: scheduleRepo,
}
}
// GetDashboardStats retrieves dashboard statistics for admin overview
func (s *adminService) GetDashboardStats() (*DashboardStats, error) {
var stats DashboardStats
// Get user statistics
totalUsers, err := s.userRepo.GetActiveUsersCount()
if err != nil {
log.Printf("Failed to get total users count: %v", err)
return nil, fmt.Errorf("failed to get user statistics: %w", err)
}
stats.TotalUsers = totalUsers
stats.ActiveUsers = totalUsers // For now, all users are considered active
// Get booking statistics
bookingStats, err := s.bookingRepo.GetBookingStats()
if err != nil {
log.Printf("Failed to get booking statistics: %v", err)
return nil, fmt.Errorf("failed to get booking statistics: %w", err)
}
stats.TotalBookings = bookingStats.TotalBookings
stats.UpcomingBookings = bookingStats.UpcomingBookings
stats.CompletedBookings = bookingStats.CompletedBookings
stats.CancelledBookings = bookingStats.CancelledBookings
// Get financial statistics (all time)
allTimeStart := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
allTimeEnd := time.Now()
financialStats, err := s.bookingRepo.GetFinancialStats(allTimeStart, allTimeEnd)
if err != nil {
log.Printf("Failed to get financial statistics: %v", err)
return nil, fmt.Errorf("failed to get financial statistics: %w", err)
}
stats.TotalRevenue = financialStats.TotalRevenue
// Get monthly revenue (current month)
now := time.Now()
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
monthEnd := monthStart.AddDate(0, 1, 0)
monthlyStats, err := s.bookingRepo.GetFinancialStats(monthStart, monthEnd)
if err != nil {
log.Printf("Failed to get monthly financial statistics: %v", err)
return nil, fmt.Errorf("failed to get monthly financial statistics: %w", err)
}
stats.MonthlyRevenue = monthlyStats.TotalRevenue
return &stats, nil
}
// GetFinancialReports generates financial reports for a given date range
func (s *adminService) GetFinancialReports(startDate, endDate time.Time) (*FinancialReport, error) {
// Validate date range
if endDate.Before(startDate) {
return nil, fmt.Errorf("end date cannot be before start date")
}
// Get overall financial statistics
financialStats, err := s.bookingRepo.GetFinancialStats(startDate, endDate)
if err != nil {
log.Printf("Failed to get financial statistics for date range %v to %v: %v", startDate, endDate, err)
return nil, fmt.Errorf("failed to get financial statistics: %w", err)
}
report := &FinancialReport{
StartDate: startDate,
EndDate: endDate,
TotalRevenue: financialStats.TotalRevenue,
TotalBookings: financialStats.TotalBookings,
AverageBooking: financialStats.AverageBooking,
}
// Generate daily breakdown
dailyBreakdown, err := s.generateDailyBreakdown(startDate, endDate)
if err != nil {
log.Printf("Failed to generate daily breakdown: %v", err)
return nil, fmt.Errorf("failed to generate daily breakdown: %w", err)
}
report.DailyBreakdown = dailyBreakdown
// Generate monthly breakdown
monthlyBreakdown, err := s.generateMonthlyBreakdown(startDate, endDate)
if err != nil {
log.Printf("Failed to generate monthly breakdown: %v", err)
return nil, fmt.Errorf("failed to generate monthly breakdown: %w", err)
}
report.MonthlyBreakdown = monthlyBreakdown
return report, nil
}
// CreateSchedule creates a new schedule slot
func (s *adminService) CreateSchedule(req CreateScheduleRequest) (*models.Schedule, error) {
// Validate the request
if req.EndTime.Before(req.StartTime) || req.EndTime.Equal(req.StartTime) {
return nil, fmt.Errorf("end time must be after start time")
}
if req.StartTime.Before(time.Now()) {
return nil, fmt.Errorf("cannot create schedule slots in the past")
}
if req.MaxBookings < 1 {
return nil, fmt.Errorf("max bookings must be at least 1")
}
// Create the schedule
schedule := &models.Schedule{
StartTime: req.StartTime,
EndTime: req.EndTime,
MaxBookings: req.MaxBookings,
IsAvailable: true,
BookedCount: 0,
}
if err := s.scheduleRepo.Create(schedule); err != nil {
log.Printf("Failed to create schedule: %v", err)
return nil, fmt.Errorf("failed to create schedule: %w", err)
}
log.Printf("Successfully created schedule slot from %v to %v with max bookings %d",
schedule.StartTime, schedule.EndTime, schedule.MaxBookings)
return schedule, nil
}
// UpdateSchedule updates an existing schedule slot
func (s *adminService) UpdateSchedule(scheduleID uint, req UpdateScheduleRequest) (*models.Schedule, error) {
// Get the existing schedule
schedule, err := s.scheduleRepo.GetByID(scheduleID)
if err != nil {
log.Printf("Failed to get schedule %d: %v", scheduleID, err)
return nil, fmt.Errorf("schedule not found: %w", err)
}
// Update fields if provided
if req.StartTime != nil {
schedule.StartTime = *req.StartTime
}
if req.EndTime != nil {
schedule.EndTime = *req.EndTime
}
if req.MaxBookings != nil {
if *req.MaxBookings < 1 {
return nil, fmt.Errorf("max bookings must be at least 1")
}
if *req.MaxBookings < schedule.BookedCount {
return nil, fmt.Errorf("max bookings cannot be less than current booked count (%d)", schedule.BookedCount)
}
schedule.MaxBookings = *req.MaxBookings
}
if req.IsAvailable != nil {
schedule.IsAvailable = *req.IsAvailable
}
// Validate time range if updated
if !schedule.EndTime.After(schedule.StartTime) {
return nil, fmt.Errorf("end time must be after start time")
}
// Update the schedule
if err := s.scheduleRepo.Update(schedule); err != nil {
log.Printf("Failed to update schedule %d: %v", scheduleID, err)
return nil, fmt.Errorf("failed to update schedule: %w", err)
}
log.Printf("Successfully updated schedule %d", scheduleID)
return schedule, nil
}
// GetAllUsers retrieves all users with pagination
func (s *adminService) GetAllUsers(limit, offset int) ([]models.User, int64, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 100 {
limit = 100 // Maximum limit
}
if offset < 0 {
offset = 0
}
users, total, err := s.userRepo.GetAllUsers(limit, offset)
if err != nil {
log.Printf("Failed to get users with limit %d, offset %d: %v", limit, offset, err)
return nil, 0, fmt.Errorf("failed to get users: %w", err)
}
return users, total, nil
}
// GetAllBookings retrieves all bookings with pagination
func (s *adminService) GetAllBookings(limit, offset int) ([]models.Booking, int64, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 100 {
limit = 100 // Maximum limit
}
if offset < 0 {
offset = 0
}
bookings, total, err := s.bookingRepo.GetAllBookings(limit, offset)
if err != nil {
log.Printf("Failed to get bookings with limit %d, offset %d: %v", limit, offset, err)
return nil, 0, fmt.Errorf("failed to get bookings: %w", err)
}
return bookings, total, nil
}
// generateDailyBreakdown generates daily financial breakdown for the given date range
func (s *adminService) generateDailyBreakdown(startDate, endDate time.Time) ([]DailyFinancialSummary, error) {
var breakdown []DailyFinancialSummary
// Iterate through each day in the range
for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) {
dayStart := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
dayEnd := dayStart.AddDate(0, 0, 1)
stats, err := s.bookingRepo.GetFinancialStats(dayStart, dayEnd)
if err != nil {
return nil, fmt.Errorf("failed to get daily stats for %v: %w", d, err)
}
breakdown = append(breakdown, DailyFinancialSummary{
Date: dayStart,
Revenue: stats.TotalRevenue,
Bookings: stats.TotalBookings,
})
}
return breakdown, nil
}
// generateMonthlyBreakdown generates monthly financial breakdown for the given date range
func (s *adminService) generateMonthlyBreakdown(startDate, endDate time.Time) ([]MonthlyFinancialSummary, error) {
var breakdown []MonthlyFinancialSummary
// Start from the beginning of the start month
current := time.Date(startDate.Year(), startDate.Month(), 1, 0, 0, 0, 0, startDate.Location())
endMonth := time.Date(endDate.Year(), endDate.Month(), 1, 0, 0, 0, 0, endDate.Location())
for !current.After(endMonth) {
monthStart := current
monthEnd := monthStart.AddDate(0, 1, 0)
// Adjust for the actual date range
if monthStart.Before(startDate) {
monthStart = startDate
}
if monthEnd.After(endDate) {
monthEnd = endDate
}
stats, err := s.bookingRepo.GetFinancialStats(monthStart, monthEnd)
if err != nil {
return nil, fmt.Errorf("failed to get monthly stats for %v: %w", current, err)
}
breakdown = append(breakdown, MonthlyFinancialSummary{
Month: current.Format("2006-01"),
Revenue: stats.TotalRevenue,
Bookings: stats.TotalBookings,
})
current = current.AddDate(0, 1, 0)
}
return breakdown, nil
}