-- Add an atomic usage-record deletion path. The operation restores the -- entitlement period and reflows stored balance snapshots for later rows. CREATE FUNCTION condon.delete_usage_record( p_usage_record_id uuid ) RETURNS void LANGUAGE plpgsql VOLATILE SECURITY INVOKER SET search_path = pg_catalog AS $function$ DECLARE v_usage condon.usage_records%ROWTYPE; v_period condon.entitlement_periods%ROWTYPE; v_initial_balance integer; v_now timestamptz := pg_catalog.clock_timestamp(); BEGIN IF p_usage_record_id IS NULL THEN RAISE EXCEPTION USING ERRCODE = '22023', MESSAGE = 'CONDON_USAGE_RECORD_ID_REQUIRED'; END IF; SELECT * INTO v_usage FROM condon.usage_records WHERE id = p_usage_record_id FOR UPDATE; IF NOT FOUND THEN RAISE EXCEPTION USING ERRCODE = 'P0002', MESSAGE = 'CONDON_USAGE_RECORD_NOT_FOUND'; END IF; SELECT * INTO v_period FROM condon.entitlement_periods WHERE id = v_usage.entitlement_period_id FOR UPDATE; IF NOT FOUND THEN RAISE EXCEPTION USING ERRCODE = 'P0002', MESSAGE = 'CONDON_ENTITLEMENT_PERIOD_NOT_FOUND'; END IF; DELETE FROM condon.entitlement_ledger WHERE usage_record_id = v_usage.id; DELETE FROM condon.usage_records WHERE id = v_usage.id; v_initial_balance := v_period.annual_grant + v_period.carry_forward; WITH ordered AS ( SELECT ur.id, ( v_initial_balance - COALESCE( SUM(ur.use_nights) OVER ( ORDER BY CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END, ur.source_sheet NULLS LAST, ur.source_sequence NULLS LAST, ur.source_row NULLS LAST, ur.created_at ASC, ur.id ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING ), 0 ) )::integer AS balance_before, ( v_initial_balance - SUM(ur.use_nights) OVER ( ORDER BY CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END, ur.source_sheet NULLS LAST, ur.source_sequence NULLS LAST, ur.source_row NULLS LAST, ur.created_at ASC, ur.id ASC ) )::integer AS balance_after FROM condon.usage_records AS ur WHERE ur.entitlement_period_id = v_period.id ) UPDATE condon.usage_records AS ur SET balance_before = ordered.balance_before, balance_after = ordered.balance_after FROM ordered WHERE ur.id = ordered.id; WITH ordered AS ( SELECT ur.id, ( v_initial_balance - COALESCE( SUM(ur.use_nights) OVER ( ORDER BY CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END, ur.source_sheet NULLS LAST, ur.source_sequence NULLS LAST, ur.source_row NULLS LAST, ur.created_at ASC, ur.id ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING ), 0 ) )::integer AS balance_before, ( v_initial_balance - SUM(ur.use_nights) OVER ( ORDER BY CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END, ur.source_sheet NULLS LAST, ur.source_sequence NULLS LAST, ur.source_row NULLS LAST, ur.created_at ASC, ur.id ASC ) )::integer AS balance_after FROM condon.usage_records AS ur WHERE ur.entitlement_period_id = v_period.id ) UPDATE condon.entitlement_ledger AS ledger SET balance_before = ordered.balance_before, balance_after = ordered.balance_after FROM ordered WHERE ledger.usage_record_id = ordered.id AND ledger.entry_type = 'usage'; UPDATE condon.entitlement_periods SET current_balance = ( v_initial_balance - COALESCE( ( SELECT SUM(ur.use_nights) FROM condon.usage_records AS ur WHERE ur.entitlement_period_id = v_period.id ), 0 ) )::integer, row_version = row_version + 1, updated_at = v_now WHERE id = v_period.id; DELETE FROM condon.bookings AS booking WHERE booking.confirmation_no = v_usage.confirmation_no AND NOT EXISTS ( SELECT 1 FROM condon.usage_records AS ur WHERE ur.confirmation_no = booking.confirmation_no ); END; $function$; REVOKE ALL ON FUNCTION condon.delete_usage_record(uuid) FROM PUBLIC;