|
|
@@ -3,6 +3,7 @@ package repository
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
|
"time"
|
|
|
|
|
|
@@ -11,6 +12,8 @@ import (
|
|
|
"gogs.fxtmmsk.ru/foxtime/photoplaces/backend/internal/models"
|
|
|
)
|
|
|
|
|
|
+var ErrPlaceNotFound = errors.New("place not found")
|
|
|
+
|
|
|
type BookingRepo struct {
|
|
|
pool *pgxpool.Pool
|
|
|
}
|
|
|
@@ -20,12 +23,43 @@ func NewBookingRepo(pool *pgxpool.Pool) *BookingRepo {
|
|
|
}
|
|
|
|
|
|
func (r *BookingRepo) Create(ctx context.Context, b *models.Booking) error {
|
|
|
- return r.pool.QueryRow(ctx,
|
|
|
+ tx, err := r.pool.Begin(ctx)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("begin tx: %w", err)
|
|
|
+ }
|
|
|
+ defer tx.Rollback(ctx)
|
|
|
+
|
|
|
+ var hourlyRate *int
|
|
|
+ err = tx.QueryRow(ctx,
|
|
|
+ `SELECT hourly_rate, currency FROM places WHERE id = $1 AND deleted_at IS NULL FOR UPDATE`,
|
|
|
+ b.PlaceID).Scan(&hourlyRate, &b.Currency)
|
|
|
+ if err != nil {
|
|
|
+ if errors.Is(err, pgx.ErrNoRows) {
|
|
|
+ return ErrPlaceNotFound
|
|
|
+ }
|
|
|
+ return fmt.Errorf("lock place: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if hourlyRate != nil {
|
|
|
+ hours := int(b.EndTime.Sub(b.StartTime).Hours())
|
|
|
+ if hours < 1 {
|
|
|
+ hours = 1
|
|
|
+ }
|
|
|
+ total := *hourlyRate * hours
|
|
|
+ b.TotalPrice = &total
|
|
|
+ }
|
|
|
+
|
|
|
+ err = tx.QueryRow(ctx,
|
|
|
`INSERT INTO bookings (place_id, user_id, start_time, end_time, status, total_price, currency, comment)
|
|
|
VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7)
|
|
|
RETURNING id, created_at, updated_at`,
|
|
|
b.PlaceID, b.UserID, b.StartTime, b.EndTime, b.TotalPrice, b.Currency, b.Comment,
|
|
|
).Scan(&b.ID, &b.CreatedAt, &b.UpdatedAt)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("insert booking: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return tx.Commit(ctx)
|
|
|
}
|
|
|
|
|
|
func (r *BookingRepo) IsTimeSlotAvailable(ctx context.Context, placeID string, start, end time.Time) (bool, error) {
|
|
|
@@ -104,31 +138,4 @@ func scanBookings(rows pgx.Rows) ([]*models.Booking, error) {
|
|
|
return bookings, nil
|
|
|
}
|
|
|
|
|
|
-func (r *BookingRepo) CancelOverlapping(ctx context.Context, placeID string, start, end time.Time) error {
|
|
|
- _, err := r.pool.Exec(ctx,
|
|
|
- `UPDATE bookings SET status='cancelled', updated_at=now()
|
|
|
- WHERE place_id=$1 AND status='pending'
|
|
|
- AND tsrange(start_time, end_time) && tsrange($2, $3)`,
|
|
|
- placeID, start, end)
|
|
|
- return err
|
|
|
-}
|
|
|
|
|
|
-func (r *BookingRepo) CalculateTotalPrice(ctx context.Context, placeID string, start, end time.Time) (*int, error) {
|
|
|
- var hourlyRate *int
|
|
|
- err := r.pool.QueryRow(ctx,
|
|
|
- `SELECT hourly_rate FROM places WHERE id=$1 AND deleted_at IS NULL AND type='studio'`,
|
|
|
- placeID).Scan(&hourlyRate)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("get studio rate: %w", err)
|
|
|
- }
|
|
|
- if hourlyRate == nil {
|
|
|
- return nil, nil
|
|
|
- }
|
|
|
-
|
|
|
- hours := int(end.Sub(start).Hours())
|
|
|
- if hours < 1 {
|
|
|
- hours = 1
|
|
|
- }
|
|
|
- total := *hourlyRate * hours
|
|
|
- return &total, nil
|
|
|
-}
|