diff --git a/init-scripts/init-script.sql b/init-scripts/init-script.sql index 9c7edad..7173282 100644 --- a/init-scripts/init-script.sql +++ b/init-scripts/init-script.sql @@ -228,6 +228,27 @@ CREATE TABLE services ( CREATE INDEX idx_services_name ON services(name); +-- ======================================= +-- CUSTOM SERVICES TABLE (One-off / special request services) +-- ======================================= + +CREATE TABLE custom_services ( + id CHAR(12) PRIMARY KEY DEFAULT generate_short_id('custom_services'), + name VARCHAR(100) NOT NULL, + description TEXT, + price NUMERIC(10,2) NOT NULL, + duration_minutes INT NOT NULL, + minimum_age_required INT NOT NULL DEFAULT 0, + notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by CHAR(12) REFERENCES users(id), + usage_count INT NOT NULL DEFAULT 0, + last_used_at TIMESTAMPTZ +); + +CREATE INDEX idx_custom_services_name ON custom_services(name); +CREATE INDEX idx_custom_services_usage ON custom_services(usage_count DESC); + -- ======================================= -- BOOKINGS TABLE -- ======================================= @@ -262,6 +283,18 @@ CREATE TABLE booking_services ( PRIMARY KEY (booking_id, service_id) ); +-- ======================================= +-- BOOKING CUSTOM SERVICES JUNCTION TABLE +-- ======================================= + +CREATE TABLE booking_custom_services ( + booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE, + custom_service_id CHAR(12) NOT NULL REFERENCES custom_services(id) ON DELETE RESTRICT, + override_price NUMERIC(10,2), + override_duration_minutes INT, + PRIMARY KEY (booking_id, custom_service_id) +); + -- ======================================= -- BOOKING EDIT REQUESTS TABLE -- ======================================= @@ -760,24 +793,42 @@ BEGIN 'created_at', b.created_at, 'updated_at', b.updated_at, 'total_price', ( - SELECT COALESCE(SUM(COALESCE(bsvc2.override_price, s2.price)), 0) - FROM booking_services bsvc2 - JOIN services s2 ON bsvc2.service_id = s2.id - WHERE bsvc2.booking_id = b.id + SELECT COALESCE(SUM(price_val), 0) FROM ( + SELECT COALESCE(bsvc2.override_price, s2.price) AS price_val + FROM booking_services bsvc2 + JOIN services s2 ON bsvc2.service_id = s2.id + WHERE bsvc2.booking_id = b.id + UNION ALL + SELECT COALESCE(bcs2.override_price, cs2.price) + FROM booking_custom_services bcs2 + JOIN custom_services cs2 ON bcs2.custom_service_id = cs2.id + WHERE bcs2.booking_id = b.id + ) price_sub ), 'services', ( - SELECT COALESCE(json_agg( - json_build_object( + SELECT COALESCE(json_agg(service_obj), '[]'::json) FROM ( + SELECT json_build_object( 'service_id', s.id, 'name', s.name, 'description', s.description, 'price', COALESCE(bsvc.override_price, s.price), 'duration_minutes', COALESCE(bsvc.override_duration_minutes, s.duration_minutes) + ) AS service_obj + FROM booking_services bsvc + JOIN services s ON bsvc.service_id = s.id + WHERE bsvc.booking_id = b.id + UNION ALL + SELECT json_build_object( + 'service_id', cs.id, + 'name', cs.name, + 'description', cs.description, + 'price', COALESCE(bcs.override_price, cs.price), + 'duration_minutes', COALESCE(bcs.override_duration_minutes, cs.duration_minutes) ) - ), '[]'::json) - FROM booking_services bsvc - JOIN services s ON bsvc.service_id = s.id - WHERE bsvc.booking_id = b.id + FROM booking_custom_services bcs + JOIN custom_services cs ON bcs.custom_service_id = cs.id + WHERE bcs.booking_id = b.id + ) services_sub ) ) ORDER BY b.start_time DESC), '[]'::json) @@ -900,10 +951,17 @@ BEGIN 'status', lr.status, 'booking_date', b.start_time, 'booking_services', ( - SELECT COALESCE(string_agg(s.name, ', '), '') - FROM booking_services bsvc - JOIN services s ON bsvc.service_id = s.id - WHERE bsvc.booking_id = lr.applied_to_booking_id + SELECT COALESCE(string_agg(name, ', '), '') FROM ( + SELECT s.name + FROM booking_services bsvc + JOIN services s ON bsvc.service_id = s.id + WHERE bsvc.booking_id = lr.applied_to_booking_id + UNION ALL + SELECT cs.name + FROM booking_custom_services bcs + JOIN custom_services cs ON bcs.custom_service_id = cs.id + WHERE bcs.booking_id = lr.applied_to_booking_id + ) svc_names ), 'redeemed_at', lr.redeemed_at, 'applied_at', lr.applied_at, @@ -1080,7 +1138,7 @@ BEGIN 'Walk-in Customer' ) AS customer_name, CASE WHEN u.n_first_name = 'Deleted' THEN NULL ELSE u.email END AS customer_email, - string_agg(s.name, ', ' ORDER BY s.name) AS service_description, + string_agg(COALESCE(s.name, cs.name), ', ' ORDER BY COALESCE(s.name, cs.name)) AS service_description, p.payment_method::text AS payment_method, p.amount AS gross_amount, CASE @@ -1104,6 +1162,8 @@ BEGIN LEFT JOIN users u ON b.user_id = u.id LEFT JOIN booking_services bsvc ON b.id = bsvc.booking_id LEFT JOIN services s ON bsvc.service_id = s.id + LEFT JOIN booking_custom_services bcs ON b.id = bcs.booking_id + LEFT JOIN custom_services cs ON bcs.custom_service_id = cs.id WHERE p.status = 'completed' AND p.created_at::date BETWEEN start_date AND end_date AND p.payment_type IN ('full', 'partial', 'balance') @@ -1476,26 +1536,43 @@ BEGIN b.status::text AS booking_status, COALESCE(( - SELECT json_agg( - json_build_object( + SELECT json_agg(service_obj) FROM ( + SELECT json_build_object( 'service_id', s.id, 'name', s.name, 'description', s.description, 'price', COALESCE(bsvc.override_price, s.price), 'duration_minutes', COALESCE(bsvc.override_duration_minutes, s.duration_minutes) + ) AS service_obj + FROM booking_services bsvc + JOIN services s ON bsvc.service_id = s.id + WHERE bsvc.booking_id = b.id + UNION ALL + SELECT json_build_object( + 'service_id', cs.id, + 'name', cs.name, + 'description', cs.description, + 'price', COALESCE(bcs.override_price, cs.price), + 'duration_minutes', COALESCE(bcs.override_duration_minutes, cs.duration_minutes) ) - ORDER BY s.name - ) - FROM booking_services bsvc - JOIN services s ON bsvc.service_id = s.id - WHERE bsvc.booking_id = b.id + FROM booking_custom_services bcs + JOIN custom_services cs ON bcs.custom_service_id = cs.id + WHERE bcs.booking_id = b.id + ) svc_sub ), '[]'::json) AS services, COALESCE(( - SELECT SUM(COALESCE(bsvc.override_duration_minutes, s.duration_minutes)) - FROM booking_services bsvc - JOIN services s ON bsvc.service_id = s.id - WHERE bsvc.booking_id = b.id + SELECT SUM(dur) FROM ( + SELECT COALESCE(bsvc.override_duration_minutes, s.duration_minutes) AS dur + FROM booking_services bsvc + JOIN services s ON bsvc.service_id = s.id + WHERE bsvc.booking_id = b.id + UNION ALL + SELECT COALESCE(bcs.override_duration_minutes, cs.duration_minutes) + FROM booking_custom_services bcs + JOIN custom_services cs ON bcs.custom_service_id = cs.id + WHERE bcs.booking_id = b.id + ) dur_sub ), 0)::INT AS total_duration_minutes, p.amount AS gross_amount,