diff --git a/backend/handlers/today/today.go b/backend/handlers/today/today.go index 92637ed..88b6fdf 100644 --- a/backend/handlers/today/today.go +++ b/backend/handlers/today/today.go @@ -236,116 +236,60 @@ func computeAggregateSummary(r *http.Request, rangeStart, rangeEnd time.Time) *D summary := &DailySummary{} // Single combined query replacing 9 separate round-trips - _ = db.Conn.QueryRow(r.Context(), ` + query := ` SELECT - COALESCE((SELECT SUM(p.amount) - FROM bookings b - JOIN payments p ON p.booking_id = b.id - WHERE b.start_time >= $1 AND b.start_time < $2 - AND p.status = 'completed' - AND p.payment_type != 'tip'), 0), - COALESCE((SELECT SUM(p.amount) - FROM bookings b - JOIN payments p ON p.booking_id = b.id - WHERE b.start_time >= $1 AND b.start_time < $2 - AND p.status = 'completed' - AND p.payment_type = 'tip'), 0), - COALESCE((SELECT COALESCE(SUM(sub.amount_due), 0) - FROM ( - SELECT b.id, - COALESCE(s.service_total, 0) + COALESCE(cs.service_total, 0) - COALESCE(p.payment_total, 0) AS amount_due - FROM bookings b - LEFT JOIN ( - SELECT booking_id, SUM(amount) AS payment_total - FROM payments WHERE status = 'completed' - GROUP BY booking_id - ) p ON p.booking_id = b.id - LEFT JOIN ( - SELECT bs.booking_id, SUM(COALESCE(bs.override_price, s.price)) AS service_total - FROM booking_services bs JOIN services s ON bs.service_id = s.id - GROUP BY bs.booking_id - ) s ON s.booking_id = b.id - LEFT JOIN ( - SELECT bcs.booking_id, SUM(COALESCE(bcs.override_price, cs.price)) AS service_total - FROM booking_custom_services bcs JOIN custom_services cs ON bcs.custom_service_id = cs.id - GROUP BY bcs.booking_id - ) cs ON cs.booking_id = b.id - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status NOT IN ('client_cancelled', 'we_cancelled', 'no_show', 'deposit_lapsed') - ) sub - WHERE sub.amount_due > 0), 0), - COALESCE((SELECT SUM(duration_minutes) - FROM ( - SELECT b.id, - COALESCE(SUM(COALESCE(bs.override_duration_minutes, s.duration_minutes)), 0) - + COALESCE(SUM(COALESCE(bcs.override_duration_minutes, cs.duration_minutes)), 0) AS duration_minutes - FROM bookings b - LEFT JOIN booking_services bs ON bs.booking_id = b.id - LEFT JOIN services s ON s.id = bs.service_id - LEFT JOIN booking_custom_services bcs ON bcs.booking_id = b.id - LEFT JOIN custom_services cs ON cs.id = bcs.custom_service_id - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status IN ('completed', 'in_progress') - GROUP BY b.id - ) sub), 0), - COALESCE((SELECT COUNT(DISTINCT b.id) - FROM bookings b - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status NOT IN ('client_cancelled', 'we_cancelled', 'no_show', 'deposit_lapsed')), 0), - COALESCE((SELECT COUNT(*) - FROM ( - SELECT DISTINCT b.user_id - FROM bookings b - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status NOT IN ('client_cancelled', 'we_cancelled', 'no_show') - AND b.user_id IS NOT NULL - ) rc - WHERE NOT EXISTS ( - SELECT 1 FROM bookings b2 - WHERE b2.user_id = rc.user_id AND b2.start_time < $1 - LIMIT 1 - )), 0), - COALESCE((SELECT COUNT(*) - FROM ( - SELECT DISTINCT b.user_id - FROM bookings b - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status NOT IN ('client_cancelled', 'we_cancelled', 'no_show') - AND b.user_id IS NOT NULL - ) rc - WHERE EXISTS ( - SELECT 1 FROM bookings b2 - WHERE b2.user_id = rc.user_id AND b2.start_time < $1 - LIMIT 1 - )), 0), - COALESCE((SELECT COUNT(*) - FROM gift_cards - WHERE created_at >= $1 AND created_at < $2 - AND is_inventory = false), 0), - COALESCE((SELECT u.fn - FROM bookings b - JOIN users u ON u.id = b.user_id - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status = 'completed' + COALESCE(SUM(p.amount) FILTER (WHERE p.status = 'completed' AND p.payment_type != 'tip'), 0) AS total_payments, + COALESCE(SUM(p.amount) FILTER (WHERE p.status = 'completed' AND p.payment_type = 'tip'), 0) AS total_tips, + COALESCE(SUM(sub.amount_due) FILTER (WHERE sub.amount_due > 0), 0) AS amount_due, + COALESCE(SUM(sub.duration_minutes), 0) AS total_duration, + COUNT(DISTINCT b.id) FILTER (WHERE b.status NOT IN ('client_cancelled','we_cancelled','no_show','deposit_lapsed')) AS total_bookings, + COUNT(DISTINCT b.user_id) FILTER ( + WHERE b.status NOT IN ('client_cancelled','we_cancelled','no_show') AND b.user_id IS NOT NULL - ORDER BY b.start_time DESC - LIMIT 1), ''), - COALESCE((SELECT COUNT(b2.id) - FROM bookings b - JOIN users u ON u.id = b.user_id - LEFT JOIN bookings b2 ON b2.user_id = b.user_id AND b2.status NOT IN ('client_cancelled', 'we_cancelled') - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status = 'completed' + AND NOT EXISTS (SELECT 1 FROM bookings b2 WHERE b2.user_id = b.user_id AND b2.start_time < $1) + ) AS new_customers, + COUNT(DISTINCT b.user_id) FILTER ( + WHERE b.status NOT IN ('client_cancelled','we_cancelled','no_show') AND b.user_id IS NOT NULL - GROUP BY u.id, u.fn, b.start_time - ORDER BY b.start_time DESC - LIMIT 1), 0), - COALESCE((SELECT COUNT(DISTINCT b.id) - FROM bookings b - WHERE b.start_time >= $1 AND b.start_time < $2 - AND b.status NOT IN ('client_cancelled', 'we_cancelled', 'no_show') - AND b.user_id IS NULL), 0) - `, rangeStart, rangeEnd).Scan( + AND EXISTS (SELECT 1 FROM bookings b2 WHERE b2.user_id = b.user_id AND b2.start_time < $1) + ) AS returning_customers, + (SELECT COUNT(*) FROM gift_cards WHERE created_at >= $1 AND created_at < $2 AND is_inventory = false) AS gift_cards_sold, + COALESCE((SELECT u.fn FROM bookings b2 + JOIN users u ON u.id = b2.user_id + WHERE b2.start_time >= $1 AND b2.start_time < $2 + AND b2.status = 'completed' AND b2.user_id IS NOT NULL + ORDER BY b2.start_time DESC LIMIT 1), '') AS last_customer_name, + COALESCE((WITH last_customer AS ( + SELECT b2.user_id FROM bookings b2 + WHERE b2.start_time >= $1 AND b2.start_time < $2 + AND b2.status = 'completed' AND b2.user_id IS NOT NULL + ORDER BY b2.start_time DESC LIMIT 1 + ) + SELECT COUNT(b3.id) + FROM last_customer lc + LEFT JOIN bookings b3 ON b3.user_id = lc.user_id + AND b3.status NOT IN ('client_cancelled','we_cancelled')), 0) AS last_customer_visits, + COUNT(DISTINCT b.id) FILTER (WHERE b.status NOT IN ('client_cancelled','we_cancelled','no_show') AND b.user_id IS NULL) AS guest_customers + FROM bookings b + LEFT JOIN payments p ON p.booking_id = b.id AND p.status = 'completed' + LEFT JOIN LATERAL ( + SELECT + COALESCE(SUM(COALESCE(bs.override_price, s.price)), 0) + + COALESCE(SUM(COALESCE(bcs.override_price, cs.price)), 0) + - COALESCE((SELECT SUM(amount) FROM payments WHERE booking_id = b.id AND status = 'completed'), 0) AS amount_due, + COALESCE(SUM(COALESCE(bs.override_duration_minutes, s.duration_minutes)), 0) + + COALESCE(SUM(COALESCE(bcs.override_duration_minutes, cs.duration_minutes)), 0) AS duration_minutes + FROM bookings b_inner + LEFT JOIN booking_services bs ON bs.booking_id = b_inner.id + LEFT JOIN services s ON s.id = bs.service_id + LEFT JOIN booking_custom_services bcs ON bcs.booking_id = b_inner.id + LEFT JOIN custom_services cs ON cs.id = bcs.custom_service_id + WHERE b_inner.id = b.id + GROUP BY b_inner.id + ) sub ON true + WHERE b.start_time >= $1 AND b.start_time < $2 + ` + _ = db.Conn.QueryRow(r.Context(), query, rangeStart, rangeEnd).Scan( &summary.TotalPaymentsToday, &summary.TotalTipsToday, &summary.AmountDueToday, @@ -471,6 +415,7 @@ func isDayOpen(r *http.Request, date time.Time) bool { } dateStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) + // Try exceptional hours first var exceptionalOpen sql.NullBool err := db.Conn.QueryRow(r.Context(), ` SELECT ewh.is_open @@ -487,6 +432,7 @@ func isDayOpen(r *http.Request, date time.Time) bool { return exceptionalOpen.Bool } + // Fall back to default working hours var isOpen bool err = db.Conn.QueryRow(r.Context(), ` SELECT is_open FROM working_hours WHERE weekday = $1