WIP call in booking
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -128,12 +128,13 @@ func main() {
|
||||
|
||||
r.Route("/admin/bookings", func(r chi.Router) {
|
||||
r.Get("/", bookings.GetAllAdminBookingsHandler)
|
||||
r.Post("/", bookings.AdminCreateBookingForUserHandler)
|
||||
r.Get("/search", bookings.SearchAdminBookingsHandler)
|
||||
r.Get("/user/{user_id}", bookings.GetAllBookingsByUserHandler)
|
||||
r.Get("/{id}", bookings.GetAdminBookingHandler)
|
||||
r.Put("/{id}/progress", bookings.ProgressBookingHandler)
|
||||
r.Post("/{id}/confirm", bookings.ConfirmBookingHandler)
|
||||
r.Post("/{id}/cancel", bookings.ConfirmBookingHandler)
|
||||
r.Post("/{id}/cancel", bookings.ConfirmBookingHandler) // todo
|
||||
})
|
||||
|
||||
r.Route("/admin/users", func(r chi.Router) {
|
||||
|
||||
Reference in New Issue
Block a user