- );
-};
diff --git a/supabase/log_order_group_change.sql b/supabase/log_order_group_change.sql
new file mode 100644
index 0000000..ea8e29b
--- /dev/null
+++ b/supabase/log_order_group_change.sql
@@ -0,0 +1,123 @@
+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;
\ No newline at end of file
diff --git a/supabase/order_history_rls.sql b/supabase/order_history_rls.sql
new file mode 100644
index 0000000..d37e723
--- /dev/null
+++ b/supabase/order_history_rls.sql
@@ -0,0 +1,24 @@
+-- Drop old policies that reference orders table
+DROP POLICY IF EXISTS "history select by order role" ON order_history;
+DROP POLICY IF EXISTS "history insert workflow" ON order_history;
+
+-- New SELECT policy: users can read history for order_groups they have access to
+CREATE POLICY "order_history_select" ON order_history
+FOR SELECT TO authenticated
+USING (
+ EXISTS (
+ SELECT 1 FROM order_groups og
+ WHERE og.id = order_history.order_group_id
+ AND (
+ current_role_name() = ANY (ARRAY['admin', 'mega_admin', 'production_lead'])
+ OR (current_role_name() = 'manager')
+ OR (current_role_name() = 'logistician')
+ OR (current_role_name() = 'driver' AND og.assigned_driver_id = auth.uid())
+ )
+ )
+);
+
+-- INSERT policy: only trigger (SECURITY DEFINER) inserts, users don't insert directly
+CREATE POLICY "order_history_insert_trigger" ON order_history
+FOR INSERT TO authenticated
+WITH CHECK (true);
\ No newline at end of file