- Add new AdminHandler with methods for dashboard, schedules, users, and bookings - Implement GetDashboard method to retrieve admin dashboard statistics - Add CreateSchedule method with validation and error handling - Implement GetUsers method with pagination support - Add GetBookings method with pagination and filtering capabilities - Implement GetFinancialReports method with date range filtering - Add UpdateSchedule method to modify existing schedule slots - Enhance error handling and response formatting for admin-related operations - Integrate admin service methods for comprehensive administrative tasks
305 lines
9.3 KiB
Go
305 lines
9.3 KiB
Go
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
|
|
}
|