- 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
237 lines
6.9 KiB
Go
237 lines
6.9 KiB
Go
package repositories
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"attune-heart-therapy/internal/models"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// bookingRepository implements the BookingRepository interface
|
|
type bookingRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewBookingRepository creates a new instance of BookingRepository
|
|
func NewBookingRepository(db *gorm.DB) BookingRepository {
|
|
return &bookingRepository{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// Create creates a new booking in the database
|
|
func (r *bookingRepository) Create(booking *models.Booking) error {
|
|
if booking == nil {
|
|
return errors.New("booking cannot be nil")
|
|
}
|
|
|
|
if err := r.db.Create(booking).Error; err != nil {
|
|
return fmt.Errorf("failed to create booking: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetByID retrieves a booking by its ID with user preloaded
|
|
func (r *bookingRepository) GetByID(id uint) (*models.Booking, error) {
|
|
if id == 0 {
|
|
return nil, errors.New("invalid booking ID")
|
|
}
|
|
|
|
var booking models.Booking
|
|
if err := r.db.Preload("User").First(&booking, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("booking with ID %d not found", id)
|
|
}
|
|
return nil, fmt.Errorf("failed to get booking by ID: %w", err)
|
|
}
|
|
|
|
return &booking, nil
|
|
}
|
|
|
|
// GetByUserID retrieves all bookings for a specific user with user preloaded
|
|
func (r *bookingRepository) GetByUserID(userID uint) ([]models.Booking, error) {
|
|
if userID == 0 {
|
|
return nil, errors.New("invalid user ID")
|
|
}
|
|
|
|
var bookings []models.Booking
|
|
if err := r.db.Preload("User").Where("user_id = ?", userID).
|
|
Order("scheduled_at DESC").Find(&bookings).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get bookings for user %d: %w", userID, err)
|
|
}
|
|
|
|
return bookings, nil
|
|
}
|
|
|
|
// Update updates an existing booking in the database
|
|
func (r *bookingRepository) Update(booking *models.Booking) error {
|
|
if booking == nil {
|
|
return errors.New("booking cannot be nil")
|
|
}
|
|
|
|
if booking.ID == 0 {
|
|
return errors.New("booking ID is required for update")
|
|
}
|
|
|
|
// Check if booking exists
|
|
var existingBooking models.Booking
|
|
if err := r.db.First(&existingBooking, booking.ID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fmt.Errorf("booking with ID %d not found", booking.ID)
|
|
}
|
|
return fmt.Errorf("failed to check booking existence: %w", err)
|
|
}
|
|
|
|
// Update the booking
|
|
if err := r.db.Save(booking).Error; err != nil {
|
|
return fmt.Errorf("failed to update booking: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete soft deletes a booking by its ID
|
|
func (r *bookingRepository) Delete(id uint) error {
|
|
if id == 0 {
|
|
return errors.New("invalid booking ID")
|
|
}
|
|
|
|
// Check if booking exists
|
|
var booking models.Booking
|
|
if err := r.db.First(&booking, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fmt.Errorf("booking with ID %d not found", id)
|
|
}
|
|
return fmt.Errorf("failed to check booking existence: %w", err)
|
|
}
|
|
|
|
// Soft delete the booking
|
|
if err := r.db.Delete(&booking).Error; err != nil {
|
|
return fmt.Errorf("failed to delete booking: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetUpcomingBookings retrieves all upcoming bookings for notification scheduling
|
|
func (r *bookingRepository) GetUpcomingBookings() ([]models.Booking, error) {
|
|
var bookings []models.Booking
|
|
|
|
// Get bookings that are scheduled and in the future
|
|
if err := r.db.Preload("User").
|
|
Where("status = ? AND scheduled_at > ?", models.BookingStatusScheduled, time.Now()).
|
|
Order("scheduled_at ASC").
|
|
Find(&bookings).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get upcoming bookings: %w", err)
|
|
}
|
|
|
|
return bookings, nil
|
|
}
|
|
|
|
// GetByPaymentID retrieves a booking by its payment ID with user preloaded
|
|
func (r *bookingRepository) GetByPaymentID(paymentID string) (*models.Booking, error) {
|
|
if paymentID == "" {
|
|
return nil, errors.New("payment ID cannot be empty")
|
|
}
|
|
|
|
var booking models.Booking
|
|
if err := r.db.Preload("User").Where("payment_id = ?", paymentID).First(&booking).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("booking with payment ID %s not found", paymentID)
|
|
}
|
|
return nil, fmt.Errorf("failed to get booking by payment ID: %w", err)
|
|
}
|
|
|
|
return &booking, nil
|
|
}
|
|
|
|
// GetAllBookings retrieves all bookings with pagination
|
|
func (r *bookingRepository) GetAllBookings(limit, offset int) ([]models.Booking, int64, error) {
|
|
var bookings []models.Booking
|
|
var total int64
|
|
|
|
// Get total count
|
|
if err := r.db.Model(&models.Booking{}).Count(&total).Error; err != nil {
|
|
return nil, 0, fmt.Errorf("failed to get bookings count: %w", err)
|
|
}
|
|
|
|
// Get bookings with pagination and user preloaded
|
|
if err := r.db.Preload("User").Limit(limit).Offset(offset).
|
|
Order("created_at DESC").Find(&bookings).Error; err != nil {
|
|
return nil, 0, fmt.Errorf("failed to get bookings: %w", err)
|
|
}
|
|
|
|
// Clear password hashes for security
|
|
for i := range bookings {
|
|
bookings[i].User.PasswordHash = ""
|
|
}
|
|
|
|
return bookings, total, nil
|
|
}
|
|
|
|
// GetBookingStats retrieves booking statistics for admin dashboard
|
|
func (r *bookingRepository) GetBookingStats() (*BookingStats, error) {
|
|
var stats BookingStats
|
|
|
|
// Get total bookings
|
|
if err := r.db.Model(&models.Booking{}).Count(&stats.TotalBookings).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get total bookings count: %w", err)
|
|
}
|
|
|
|
// Get upcoming bookings
|
|
if err := r.db.Model(&models.Booking{}).
|
|
Where("status = ? AND scheduled_at > ?", models.BookingStatusScheduled, time.Now()).
|
|
Count(&stats.UpcomingBookings).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get upcoming bookings count: %w", err)
|
|
}
|
|
|
|
// Get completed bookings
|
|
if err := r.db.Model(&models.Booking{}).
|
|
Where("status = ?", models.BookingStatusCompleted).
|
|
Count(&stats.CompletedBookings).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get completed bookings count: %w", err)
|
|
}
|
|
|
|
// Get cancelled bookings
|
|
if err := r.db.Model(&models.Booking{}).
|
|
Where("status = ?", models.BookingStatusCancelled).
|
|
Count(&stats.CancelledBookings).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get cancelled bookings count: %w", err)
|
|
}
|
|
|
|
return &stats, nil
|
|
}
|
|
|
|
// GetFinancialStats retrieves financial statistics for admin reports
|
|
func (r *bookingRepository) GetFinancialStats(startDate, endDate time.Time) (*FinancialStats, error) {
|
|
var stats FinancialStats
|
|
|
|
// Get total revenue and booking count for the date range
|
|
var result struct {
|
|
TotalRevenue float64 `json:"total_revenue"`
|
|
TotalBookings int64 `json:"total_bookings"`
|
|
}
|
|
|
|
if err := r.db.Model(&models.Booking{}).
|
|
Select("COALESCE(SUM(amount), 0) as total_revenue, COUNT(*) as total_bookings").
|
|
Where("payment_status = ? AND created_at >= ? AND created_at <= ?",
|
|
models.PaymentStatusSucceeded, startDate, endDate).
|
|
Scan(&result).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get financial stats: %w", err)
|
|
}
|
|
|
|
stats.TotalRevenue = result.TotalRevenue
|
|
stats.TotalBookings = result.TotalBookings
|
|
|
|
// Calculate average booking value
|
|
if stats.TotalBookings > 0 {
|
|
stats.AverageBooking = stats.TotalRevenue / float64(stats.TotalBookings)
|
|
}
|
|
|
|
return &stats, nil
|
|
}
|