diff --git a/backend/handlers/bookings/bookings.go b/backend/handlers/bookings/bookings.go index 610d81a..4b5ea35 100644 --- a/backend/handlers/bookings/bookings.go +++ b/backend/handlers/bookings/bookings.go @@ -2698,8 +2698,9 @@ func ProgressBookingHandler(w http.ResponseWriter, r *http.Request) { // Don't award a stamp if this booking already used a loyalty redemption // (take or receive, never both). var loyaltyAppliedOnThisBooking bool -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'loyalty')`, bookingID).Scan(&loyaltyAppliedOnThisBooking) + if err := tx.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'loyalty')`, bookingID).Scan(&loyaltyAppliedOnThisBooking); err != nil { + log.Printf("Failed to check loyalty applied on booking %s: %v", bookingID, err) + } var newStampCount int if bookingTotal > 0 && !loyaltyAppliedOnThisBooking { @@ -2735,8 +2736,9 @@ func ProgressBookingHandler(w http.ResponseWriter, r *http.Request) { // Skip time-based campaign if already applied at payment time var timeBasedApplied bool -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'campaign' AND campaign_type = 'time_based')`, bookingID).Scan(&timeBasedApplied) + if err := tx.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'campaign' AND campaign_type = 'time_based')`, bookingID).Scan(&timeBasedApplied); err != nil { + log.Printf("Failed to check time-based campaign applied on booking %s: %v", bookingID, err) + } if bookingTotal > 0 && !timeBasedApplied { var campaignID string var campaignPercent float64 @@ -2778,13 +2780,14 @@ func ProgressBookingHandler(w http.ResponseWriter, r *http.Request) { var milestoneCampaignID string var milestonePercent float64 -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), ` + if err := tx.QueryRow(r.Context(), ` SELECT id, discount_percent FROM discount_campaigns WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'per_user_booking_count' AND milestone_value = $1 AND NOT EXISTS (SELECT 1 FROM booking_discounts WHERE user_id = $2 AND source_id = discount_campaigns.id) - `, userBookingCount, booking.User.ID).Scan(&milestoneCampaignID, &milestonePercent) + `, userBookingCount, booking.User.ID).Scan(&milestoneCampaignID, &milestonePercent); err != nil { + log.Printf("Failed to query per-user milestone campaign for booking %s: %v", bookingID, err) + } if milestoneCampaignID != "" { discountAmount := roundTo2(bookingTotal * milestonePercent / 100) @@ -2808,31 +2811,34 @@ func ProgressBookingHandler(w http.ResponseWriter, r *http.Request) { } var globalMilestoneApplied bool -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'campaign' AND campaign_type = 'milestone' AND milestone_type = 'global_booking_count')`, bookingID).Scan(&globalMilestoneApplied) + if err := tx.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'campaign' AND campaign_type = 'milestone' AND milestone_type = 'global_booking_count')`, bookingID).Scan(&globalMilestoneApplied); err != nil { + log.Printf("Failed to check global milestone applied on booking %s: %v", bookingID, err) + } if !globalMilestoneApplied { var globalCount int //nolint:errcheck // zero value is acceptable fallback on scan failure _ = tx.QueryRow(r.Context(), `SELECT COUNT(*) FROM bookings WHERE status = 'completed'`).Scan(&globalCount) - var hasInPersonPayment bool -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), ` - SELECT EXISTS(SELECT 1 FROM payments WHERE booking_id = $1 AND payment_method = 'in_person_card')`, bookingID).Scan(&hasInPersonPayment) + var hasInPersonPayment bool + if err := tx.QueryRow(r.Context(), ` + SELECT EXISTS(SELECT 1 FROM payments WHERE booking_id = $1 AND payment_method = 'in_person_card')`, bookingID).Scan(&hasInPersonPayment); err != nil { + log.Printf("Failed to check in-person payment on booking %s: %v", bookingID, err) + } - if hasInPersonPayment { - var globalCampaignID string - var globalPercent float64 -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), ` - SELECT id, discount_percent FROM discount_campaigns - WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'global_booking_count' - AND milestone_value <= $1 - AND (max_redemptions IS NULL OR times_redeemed < max_redemptions) - ORDER BY milestone_value DESC LIMIT 1 - `, globalCount).Scan(&globalCampaignID, &globalPercent) + if hasInPersonPayment { + var globalCampaignID string + var globalPercent float64 + if err := tx.QueryRow(r.Context(), ` + SELECT id, discount_percent FROM discount_campaigns + WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'global_booking_count' + AND milestone_value <= $1 + AND (max_redemptions IS NULL OR times_redeemed < max_redemptions) + ORDER BY milestone_value DESC LIMIT 1 + `, globalCount).Scan(&globalCampaignID, &globalPercent); err != nil { + log.Printf("Failed to query global milestone campaign for booking %s: %v", bookingID, err) + } - if globalCampaignID != "" { + if globalCampaignID != "" { discountAmount := roundTo2(bookingTotal * globalPercent / 100) if _, err := tx.Exec(r.Context(), ` INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, campaign_type, milestone_type, discount_percent, original_total, discount_amount) diff --git a/backend/handlers/bookings/manage.go b/backend/handlers/bookings/manage.go index d9f9c14..0cb6ae2 100644 --- a/backend/handlers/bookings/manage.go +++ b/backend/handlers/bookings/manage.go @@ -283,14 +283,11 @@ func AdminCreateBookingForUserHandler(w http.ResponseWriter, r *http.Request) { } } - // Get deposit info - var depositRequired bool + // Get deposit info — deposit_required already fetched above in the main booking query. var preStartPaid float64 -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = db.Conn.QueryRow(r.Context(), `SELECT deposit_required FROM bookings WHERE id = $1`, existingID).Scan(&depositRequired) -//nolint:errcheck // zero value is acceptable fallback on scan failure +//nolint:errcheck // zero value is acceptable fallback on scan failure (aggregate with COALESCE) _ = db.Conn.QueryRow(r.Context(), `SELECT COALESCE(SUM(amount), 0) FROM payments WHERE booking_id = $1 AND payment_type IN ('deposit', 'full') AND status = 'completed'`, existingID).Scan(&preStartPaid) - populateDepositFields(&existingBooking, depositRequired, preStartPaid) + populateDepositFields(&existingBooking, existingBooking.DepositRequired, preStartPaid) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -1211,13 +1208,19 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { // Query payment and timing info (used for validation AND auto-approval later) var hasPayments bool hoursUntilCurrent := currentStartTime.Sub(clock.Now()).Hours() -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = db.Conn.QueryRow(r.Context(), "SELECT EXISTS(SELECT 1 FROM payments WHERE booking_id = $1 AND status = 'completed')", bookingID).Scan(&hasPayments) + if err := db.Conn.QueryRow(r.Context(), "SELECT EXISTS(SELECT 1 FROM payments WHERE booking_id = $1 AND status = 'completed')", bookingID).Scan(&hasPayments); err != nil { + log.Printf("Failed to check payments for booking %s: %v", bookingID, err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } // Check if booking has discounts (affects auto-approval decisions) var hasDiscounts bool -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = db.Conn.QueryRow(r.Context(), "SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1)", bookingID).Scan(&hasDiscounts) + if err := db.Conn.QueryRow(r.Context(), "SELECT EXISTS(SELECT 1 FROM booking_discounts WHERE booking_id = $1)", bookingID).Scan(&hasDiscounts); err != nil { + log.Printf("Failed to check discounts for booking %s: %v", bookingID, err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } if req.NewStartTime != nil && !req.NewStartTime.Equal(currentStartTime) { if hasPayments && hoursUntilCurrent < 72 { @@ -1447,8 +1450,7 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { var durationMinutes int if len(req.NewServices) > 0 { -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), ` + if err := tx.QueryRow(r.Context(), ` SELECT COALESCE(SUM(dur), 60) FROM ( SELECT s.duration_minutes AS dur FROM services s @@ -1458,12 +1460,17 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { FROM custom_services cs WHERE cs.id = ANY($1) ) sub - `, req.NewServices).Scan(&durationMinutes) + `, req.NewServices).Scan(&durationMinutes); err != nil { + log.Printf("Failed to calculate duration for edit request %s: %v, using default", bookingID, err) + durationMinutes = 60 + } } else { -//nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), ` + if err := tx.QueryRow(r.Context(), ` SELECT total_duration_minutes FROM bookings WHERE id = $1 - `, bookingID).Scan(&durationMinutes) + `, bookingID).Scan(&durationMinutes); err != nil { + log.Printf("Failed to get booking duration for edit request %s: %v, using default", bookingID, err) + durationMinutes = 60 + } } _, err = tx.Exec(r.Context(), ` diff --git a/backend/handlers/payments/handlers.go b/backend/handlers/payments/handlers.go index 1cee950..b304f11 100644 --- a/backend/handlers/payments/handlers.go +++ b/backend/handlers/payments/handlers.go @@ -141,10 +141,11 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri } var bookingTotal float64 - //nolint:errcheck // zero value is acceptable fallback on scan failure - _ = db.Conn.QueryRow(ctx, ` + if err := db.Conn.QueryRow(ctx, ` SELECT total_amount FROM bookings WHERE id = $1 - `, bookingID).Scan(&bookingTotal) + `, bookingID).Scan(&bookingTotal); err != nil { + log.Printf("Failed to query booking total for discount preview %s: %v", bookingID, err) + } if bookingTotal <= 0 { return resp @@ -185,13 +186,14 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri var milestoneCampaignID string var milestonePercent float64 var milestoneName string - //nolint:errcheck // zero value is acceptable fallback on scan failure - _ = db.Conn.QueryRow(ctx, ` + if err := db.Conn.QueryRow(ctx, ` SELECT id, discount_percent, name FROM discount_campaigns WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'per_user_booking_count' AND milestone_value = $1 AND NOT EXISTS (SELECT 1 FROM booking_discounts WHERE user_id = $2 AND source_id = discount_campaigns.id) - `, userBookingCount, userID).Scan(&milestoneCampaignID, &milestonePercent, &milestoneName) + `, userBookingCount, userID).Scan(&milestoneCampaignID, &milestonePercent, &milestoneName); err != nil { + log.Printf("Failed to query milestone campaign for discount preview (user %s, count %d): %v", userID, userBookingCount, err) + } if milestoneCampaignID != "" { var exists int @@ -1021,8 +1023,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) { // Promote deposit to confirmed if total paid meets the 20% threshold. // Check is inside the transaction so it sees the just-inserted payments. var depositMet bool - //nolint:errcheck // zero value is acceptable fallback on scan failure - _ = tx.QueryRow(r.Context(), ` + if err := tx.QueryRow(r.Context(), ` WITH booking_total AS ( SELECT total_amount * 100 AS total_cents FROM bookings WHERE id = $1 ), @@ -1033,7 +1034,9 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) { ) SELECT pt.paid_cents >= ROUND(bt.total_cents * 0.2) FROM booking_total bt, paid_total pt - `, bookingID).Scan(&depositMet) + `, bookingID).Scan(&depositMet); err != nil { + log.Printf("Failed to check deposit threshold for booking %s: %v", bookingID, err) + } if depositMet { if _, err := tx.Exec(r.Context(), ` @@ -1139,13 +1142,14 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI var milestoneCampaignID string var milestonePercent float64 - //nolint:errcheck // zero value is acceptable fallback on scan failure - _ = q.QueryRow(ctx, ` + if err := q.QueryRow(ctx, ` SELECT id, discount_percent FROM discount_campaigns WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'per_user_booking_count' AND milestone_value = $1 AND NOT EXISTS (SELECT 1 FROM booking_discounts WHERE user_id = $2 AND source_id = discount_campaigns.id) - `, userBookingCount, userID).Scan(&milestoneCampaignID, &milestonePercent) + `, userBookingCount, userID).Scan(&milestoneCampaignID, &milestonePercent); err != nil { + log.Printf("Failed to query per-user milestone campaign: %v", err) + } if milestoneCampaignID != "" { var exists int @@ -1251,15 +1255,16 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI var globalCampaignID string var globalPercent float64 - //nolint:errcheck // zero value is acceptable fallback on scan failure - _ = q.QueryRow(ctx, ` + if err := q.QueryRow(ctx, ` SELECT id, discount_percent FROM discount_campaigns WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'global_booking_count' AND milestone_value <= $1 AND (max_redemptions IS NULL OR times_redeemed < max_redemptions) AND NOT EXISTS (SELECT 1 FROM booking_discounts WHERE source_id = discount_campaigns.id AND booking_id = $2) ORDER BY milestone_value DESC LIMIT 1 - `, globalCount, bookingID).Scan(&globalCampaignID, &globalPercent) + `, globalCount, bookingID).Scan(&globalCampaignID, &globalPercent); err != nil { + log.Printf("Failed to query global milestone campaign: %v", err) + } if globalCampaignID != "" { var exists int