feat(backend): fetch standard and custom services in bookings list

Populate booking.Services with a UNION query across booking_services and booking_custom_services tables, returning service name, price, duration, and override values.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-15 21:27:29 +01:00
co-authored by Sisyphus
parent 597a527d44
commit 2574baf099
2 changed files with 292 additions and 0 deletions
+83
View File
@@ -456,6 +456,8 @@ func GetAllUserBookingsHandler(w http.ResponseWriter, r *http.Request) {
defer rows.Close()
var bookings []Booking
var bookingIDs []string
for rows.Next() {
var b Booking
var createdBy sql.NullString
@@ -481,7 +483,88 @@ func GetAllUserBookingsHandler(w http.ResponseWriter, r *http.Request) {
b.AmountDue = totalAmount - amountPaid
b.DurationMinutes = durationMinutes
populateDepositFields(&b, depositRequired, preStartAmountPaid)
b.Services = []BookingService{}
bookings = append(bookings, b)
bookingIDs = append(bookingIDs, b.ID)
}
if len(bookingIDs) > 0 {
serviceRows, err := db.DB.Query(r.Context(), `
SELECT
bs.service_id, bs.override_price, bs.override_duration_minutes,
s.name, s.description, s.price, s.duration_minutes,
bs.booking_id
FROM booking_services bs
LEFT JOIN services s ON bs.service_id = s.id
WHERE bs.booking_id = ANY($1)
UNION ALL
SELECT
bcs.custom_service_id, bcs.override_price, bcs.override_duration_minutes,
cs.name, cs.description, cs.price, cs.duration_minutes,
bcs.booking_id
FROM booking_custom_services bcs
LEFT JOIN custom_services cs ON bcs.custom_service_id = cs.id
WHERE bcs.booking_id = ANY($1)
ORDER BY booking_id, name
`, bookingIDs)
if err != nil {
log.Printf("Failed to fetch booking services: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
defer serviceRows.Close()
servicesByBooking := make(map[string][]BookingService)
for serviceRows.Next() {
var s BookingService
var overridePrice sql.NullFloat64
var overrideDuration sql.NullInt32
var name, description sql.NullString
var basePrice sql.NullFloat64
var baseDuration sql.NullInt32
var bookingID string
if err := serviceRows.Scan(
&s.ServiceID,
&overridePrice, &overrideDuration,
&name, &description, &basePrice, &baseDuration,
&bookingID,
); err != nil {
log.Printf("Failed to scan service row: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
s.BookingID = bookingID
if overridePrice.Valid {
s.OverridePrice = &overridePrice.Float64
}
if overrideDuration.Valid {
d := int(overrideDuration.Int32)
s.OverrideDurationMinutes = &d
}
if name.Valid {
s.ServiceName = &name.String
}
if description.Valid {
s.ServiceDescription = &description.String
}
if basePrice.Valid {
s.Price = &basePrice.Float64
}
if baseDuration.Valid {
d := int(baseDuration.Int32)
s.DurationMinutes = &d
}
servicesByBooking[bookingID] = append(servicesByBooking[bookingID], s)
}
for i := range bookings {
if services, exists := servicesByBooking[bookings[i].ID]; exists {
bookings[i].Services = services
}
}
}
w.Header().Set("Content-Type", "application/json")