WIP call in booking

This commit is contained in:
2026-01-24 22:08:36 +00:00
parent 3a8ea4c98f
commit 2ace6d4d87
6 changed files with 330 additions and 41 deletions
+160 -24
View File
@@ -168,57 +168,193 @@ func AdminEditBookingHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// AdminCreateBookingForUserHandler creates a booking on behalf of a user.
func AdminCreateBookingForUserHandler(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "user_id")
var req CreateBookingRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
type AdminCreateBookingForUserRequest struct {
UserID string `json:"user_id" validate:"required"`
StartTime time.Time `json:"start_time" validate:"required"`
ServiceIDs []string `json:"service_ids" validate:"required,min=1"`
ServiceOverrides []ServiceOverride `json:"service_overrides,omitempty"`
Notes *string `json:"notes,omitempty"` // staff notes
}
func oo(w http.ResponseWriter, r *http.Request) {
// Admin identity (creator)
adminID, ok := r.Context().Value(mw.UserIDKey).(string)
if !ok || adminID == "" {
http.Error(w, "Authentication required", http.StatusUnauthorized)
return
}
adminID, _ := r.Context().Value(mw.UserIDKey).(string)
var req AdminCreateBookingForUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
log.Printf("Failed to decode request: %v", err)
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// Basic validation
if req.UserID == "" {
http.Error(w, "User ID is required", http.StatusBadRequest)
return
}
if req.StartTime.IsZero() {
http.Error(w, "Start time is required", http.StatusBadRequest)
return
}
if len(req.ServiceIDs) == 0 {
http.Error(w, "At least one service is required", http.StatusBadRequest)
return
}
// Validate overrides
for _, override := range req.ServiceOverrides {
if override.ServiceID == "" {
http.Error(w, "Service ID is required for overrides", http.StatusBadRequest)
return
}
if override.OverridePrice != nil && *override.OverridePrice < 0 {
http.Error(w, "Override price cannot be negative", http.StatusBadRequest)
return
}
if override.OverrideDurationMinutes != nil && *override.OverrideDurationMinutes <= 0 {
http.Error(w, "Override duration must be positive", http.StatusBadRequest)
return
}
}
tx, err := db.DB.Begin(r.Context())
if err != nil {
log.Printf("Failed to begin transaction: %v", err)
log.Printf("Failed to start transaction: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
defer tx.Rollback(r.Context())
var bookingID string
err = tx.QueryRow(r.Context(), `
INSERT INTO bookings (id, user_id, start_time, status, created_by)
VALUES (generate_booking_id(), $1, $2, 'pending', $3)
RETURNING id
`, userID, req.StartTime, adminID).Scan(&bookingID)
// Create booking directly as confirmed
bookingQuery := `
INSERT INTO bookings (
user_id,
start_time,
status,
notes,
created_by
)
VALUES ($1, $2, 'confirmed', $3, $4)
RETURNING id, user_id, start_time, status, notes, created_at, updated_at, created_by
`
var booking Booking
booking.User = &UserSummary{}
err = tx.QueryRow(
r.Context(),
bookingQuery,
req.UserID,
req.StartTime,
req.Notes,
adminID,
).Scan(
&booking.ID,
&booking.User.ID,
&booking.StartTime,
&booking.Status,
&booking.Notes,
&booking.CreatedAt,
&booking.UpdatedAt,
&booking.CreatedBy,
)
if err != nil {
log.Printf("Failed to create booking: %v", err)
log.Printf("Failed to create admin booking: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
for _, svcID := range req.ServiceIDs {
if _, err := tx.Exec(r.Context(), `
INSERT INTO booking_services (booking_id, service_id)
VALUES ($1, $2)
`, bookingID, svcID); err != nil {
log.Printf("Failed to insert booking service: %v", err)
// Insert booking services
serviceInsertQuery := `
INSERT INTO booking_services (booking_id, service_id)
VALUES ($1, $2)
`
for _, serviceID := range req.ServiceIDs {
_, err := tx.Exec(r.Context(), serviceInsertQuery, booking.ID, serviceID)
if err != nil {
log.Printf("Failed to insert booking service %s: %v", serviceID, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
}
if err = tx.Commit(r.Context()); err != nil {
log.Printf("Failed to commit transaction: %v", err)
// Apply overrides (optional)
if len(req.ServiceOverrides) > 0 {
// Ensure overrides only reference services in this booking
serviceCheckQuery := `
SELECT COUNT(*) FROM booking_services
WHERE booking_id = $1 AND service_id = ANY($2)
`
overrideServiceIDs := make([]string, len(req.ServiceOverrides))
for i, o := range req.ServiceOverrides {
overrideServiceIDs[i] = o.ServiceID
}
var count int
err = tx.QueryRow(
r.Context(),
serviceCheckQuery,
booking.ID,
overrideServiceIDs,
).Scan(&count)
if err != nil {
log.Printf("Failed to verify service overrides: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if count != len(req.ServiceOverrides) {
http.Error(w, "One or more service overrides do not belong to this booking", http.StatusBadRequest)
return
}
overrideUpdateQuery := `
UPDATE booking_services
SET override_price = $1,
override_duration_minutes = $2
WHERE booking_id = $3 AND service_id = $4
`
for _, override := range req.ServiceOverrides {
_, err := tx.Exec(
r.Context(),
overrideUpdateQuery,
override.OverridePrice,
override.OverrideDurationMinutes,
booking.ID,
override.ServiceID,
)
if err != nil {
log.Printf(
"Failed to apply override (booking %s, service %s): %v",
booking.ID,
override.ServiceID,
err,
)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
}
}
if err := tx.Commit(r.Context()); err != nil {
log.Printf("Failed to commit admin booking creation: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(w).Encode(map[string]string{"id": bookingID}); err != nil {
if err := json.NewEncoder(w).Encode(booking); err != nil {
log.Printf("Failed to encode response: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}