bookings.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // Package repository
  2. package repository
  3. import (
  4. "context"
  5. "fmt"
  6. "time"
  7. "github.com/jackc/pgx/v5"
  8. "github.com/jackc/pgx/v5/pgxpool"
  9. "gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/models"
  10. )
  11. type BookingRepo struct {
  12. pool *pgxpool.Pool
  13. }
  14. func NewBookingRepo(pool *pgxpool.Pool) *BookingRepo {
  15. return &BookingRepo{pool: pool}
  16. }
  17. func (r *BookingRepo) Create(ctx context.Context, b *models.Booking) error {
  18. return r.pool.QueryRow(ctx,
  19. `INSERT INTO bookings (place_id, user_id, start_time, end_time, status, total_price, currency, comment)
  20. VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7)
  21. RETURNING id, created_at, updated_at`,
  22. b.PlaceID, b.UserID, b.StartTime, b.EndTime, b.TotalPrice, b.Currency, b.Comment,
  23. ).Scan(&b.ID, &b.CreatedAt, &b.UpdatedAt)
  24. }
  25. func (r *BookingRepo) IsTimeSlotAvailable(ctx context.Context, placeID string, start, end time.Time) (bool, error) {
  26. var count int
  27. err := r.pool.QueryRow(ctx,
  28. `SELECT COUNT(*) FROM bookings
  29. WHERE place_id = $1 AND status != 'cancelled'
  30. AND tsrange(start_time, end_time) && tsrange($2, $3)`,
  31. placeID, start, end).Scan(&count)
  32. if err != nil {
  33. return false, err
  34. }
  35. return count == 0, nil
  36. }
  37. func (r *BookingRepo) GetByID(ctx context.Context, id string) (*models.Booking, error) {
  38. row := r.pool.QueryRow(ctx,
  39. `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment,
  40. created_at, updated_at
  41. FROM bookings WHERE id = $1`, id)
  42. var b models.Booking
  43. err := row.Scan(&b.ID, &b.PlaceID, &b.UserID, &b.StartTime, &b.EndTime,
  44. &b.Status, &b.TotalPrice, &b.Currency, &b.Comment, &b.CreatedAt, &b.UpdatedAt)
  45. if err != nil {
  46. if err == pgx.ErrNoRows {
  47. return nil, nil
  48. }
  49. return nil, err
  50. }
  51. return &b, nil
  52. }
  53. func (r *BookingRepo) ListByUser(ctx context.Context, userID string) ([]*models.Booking, error) {
  54. rows, err := r.pool.Query(ctx,
  55. `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment,
  56. created_at, updated_at
  57. FROM bookings WHERE user_id = $1 ORDER BY start_time DESC`, userID)
  58. if err != nil {
  59. return nil, err
  60. }
  61. defer rows.Close()
  62. return scanBookings(rows)
  63. }
  64. func (r *BookingRepo) ListByPlace(ctx context.Context, placeID string) ([]*models.Booking, error) {
  65. rows, err := r.pool.Query(ctx,
  66. `SELECT id, place_id, user_id, start_time, end_time, status, total_price, currency, comment,
  67. created_at, updated_at
  68. FROM bookings WHERE place_id = $1 ORDER BY start_time DESC`, placeID)
  69. if err != nil {
  70. return nil, err
  71. }
  72. defer rows.Close()
  73. return scanBookings(rows)
  74. }
  75. func (r *BookingRepo) UpdateStatus(ctx context.Context, id, status string) error {
  76. _, err := r.pool.Exec(ctx,
  77. `UPDATE bookings SET status=$1, updated_at=now() WHERE id=$2`, status, id)
  78. return err
  79. }
  80. func scanBookings(rows pgx.Rows) ([]*models.Booking, error) {
  81. var bookings []*models.Booking
  82. for rows.Next() {
  83. var b models.Booking
  84. if err := rows.Scan(&b.ID, &b.PlaceID, &b.UserID, &b.StartTime, &b.EndTime,
  85. &b.Status, &b.TotalPrice, &b.Currency, &b.Comment, &b.CreatedAt, &b.UpdatedAt); err != nil {
  86. return nil, err
  87. }
  88. bookings = append(bookings, &b)
  89. }
  90. return bookings, nil
  91. }
  92. func (r *BookingRepo) CancelOverlapping(ctx context.Context, placeID string, start, end time.Time) error {
  93. _, err := r.pool.Exec(ctx,
  94. `UPDATE bookings SET status='cancelled', updated_at=now()
  95. WHERE place_id=$1 AND status='pending'
  96. AND tsrange(start_time, end_time) && tsrange($2, $3)`,
  97. placeID, start, end)
  98. return err
  99. }
  100. func (r *BookingRepo) CalculateTotalPrice(ctx context.Context, placeID string, start, end time.Time) (*int, error) {
  101. var hourlyRate *int
  102. err := r.pool.QueryRow(ctx,
  103. `SELECT hourly_rate FROM places WHERE id=$1 AND deleted_at IS NULL AND type='studio'`,
  104. placeID).Scan(&hourlyRate)
  105. if err != nil {
  106. return nil, fmt.Errorf("get studio rate: %w", err)
  107. }
  108. if hourlyRate == nil {
  109. return nil, nil
  110. }
  111. hours := int(end.Sub(start).Hours())
  112. if hours < 1 {
  113. hours = 1
  114. }
  115. total := *hourlyRate * hours
  116. return &total, nil
  117. }