CREATE OR REPLACE FUNCTION public.log_order_group_change() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_user_id uuid; v_changes jsonb := '{}'::jsonb; v_action text; v_source text; BEGIN -- Determine who made the change v_user_id := auth.uid(); v_source := CASE WHEN v_user_id IS NOT NULL THEN 'user' ELSE 'system' END; -- Track only meaningful field changes IF NEW.notification_status IS DISTINCT FROM OLD.notification_status THEN v_changes := v_changes || jsonb_build_object( 'notification_status', jsonb_build_object('old', OLD.notification_status, 'new', NEW.notification_status) ); END IF; IF NEW.delivery_status IS DISTINCT FROM OLD.delivery_status THEN v_changes := v_changes || jsonb_build_object( 'delivery_status', jsonb_build_object('old', OLD.delivery_status, 'new', NEW.delivery_status) ); END IF; IF NEW.status IS DISTINCT FROM OLD.status THEN v_changes := v_changes || jsonb_build_object( 'status', jsonb_build_object('old', OLD.status, 'new', NEW.status) ); END IF; IF NEW.assigned_driver_id IS DISTINCT FROM OLD.assigned_driver_id THEN v_changes := v_changes || jsonb_build_object( 'assigned_driver_id', jsonb_build_object('old', OLD.assigned_driver_id, 'new', NEW.assigned_driver_id) ); END IF; IF NEW.delivery_link IS DISTINCT FROM OLD.delivery_link THEN v_changes := v_changes || jsonb_build_object( 'delivery_link', jsonb_build_object('old', OLD.delivery_link, 'new', NEW.delivery_link) ); END IF; IF NEW.first_sms_sent_at IS DISTINCT FROM OLD.first_sms_sent_at THEN v_changes := v_changes || jsonb_build_object( 'first_sms_sent_at', jsonb_build_object('old', OLD.first_sms_sent_at, 'new', NEW.first_sms_sent_at) ); END IF; IF NEW.second_sms_sent_at IS DISTINCT FROM OLD.second_sms_sent_at THEN v_changes := v_changes || jsonb_build_object( 'second_sms_sent_at', jsonb_build_object('old', OLD.second_sms_sent_at, 'new', NEW.second_sms_sent_at) ); END IF; IF NEW.last_sms_error IS DISTINCT FROM OLD.last_sms_error THEN v_changes := v_changes || jsonb_build_object( 'last_sms_error', jsonb_build_object('old', OLD.last_sms_error, 'new', NEW.last_sms_error) ); END IF; -- Only insert if something meaningful changed IF v_changes = '{}'::jsonb THEN RETURN NEW; END IF; -- Determine action label v_action := CASE WHEN NEW.delivery_status IS DISTINCT FROM OLD.delivery_status THEN 'delivery_status_change' WHEN NEW.notification_status IS DISTINCT FROM OLD.notification_status THEN 'notification_status_change' WHEN NEW.assigned_driver_id IS DISTINCT FROM OLD.assigned_driver_id THEN 'driver_assignment_change' WHEN NEW.delivery_link IS DISTINCT FROM OLD.delivery_link THEN 'delivery_link_change' ELSE 'field_change' END; INSERT INTO order_history (order_group_id, action, old_status, new_status, user_id, metadata) VALUES ( NEW.id, v_action, OLD.delivery_status, NEW.delivery_status, v_user_id, jsonb_build_object('changes', v_changes, 'source', v_source, 'group_key', NEW.group_key) ); RETURN NEW; END; $$; -- Drop old trigger if exists DROP TRIGGER IF EXISTS log_order_group_change_trigger ON public.order_groups; -- Create trigger AFTER UPDATE (only when fields actually change) CREATE TRIGGER log_order_group_change_trigger AFTER UPDATE ON public.order_groups FOR EACH ROW WHEN ( NEW.notification_status IS DISTINCT FROM OLD.notification_status OR NEW.delivery_status IS DISTINCT FROM OLD.delivery_status OR NEW.status IS DISTINCT FROM OLD.status OR NEW.assigned_driver_id IS DISTINCT FROM OLD.assigned_driver_id OR NEW.delivery_link IS DISTINCT FROM OLD.delivery_link OR NEW.first_sms_sent_at IS DISTINCT FROM OLD.first_sms_sent_at OR NEW.second_sms_sent_at IS DISTINCT FROM OLD.second_sms_sent_at OR NEW.last_sms_error IS DISTINCT FROM OLD.last_sms_error ) EXECUTE FUNCTION public.log_order_group_change(); -- Grant access GRANT SELECT ON order_history TO authenticated; GRANT INSERT ON order_history TO authenticated;