-- Rename table experiment_phases to experiment_books -- This migration renames the table and updates all dependent objects (views, functions, triggers, indexes, RLS). -- ============================================= -- 1. RENAME TABLE -- ============================================= ALTER TABLE public.experiment_phases RENAME TO experiment_books; -- ============================================= -- 2. RENAME TRIGGER -- ============================================= DROP TRIGGER IF EXISTS set_updated_at_experiment_phases ON public.experiment_books; CREATE TRIGGER set_updated_at_experiment_books BEFORE UPDATE ON public.experiment_books FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); -- ============================================= -- 3. RENAME CONSTRAINT -- ============================================= ALTER TABLE public.experiment_books RENAME CONSTRAINT ck_experiment_phases_machine_required_when_cracking TO ck_experiment_books_machine_required_when_cracking; -- ============================================= -- 4. RENAME INDEXES -- ============================================= ALTER INDEX IF EXISTS public.idx_experiment_phases_name RENAME TO idx_experiment_books_name; ALTER INDEX IF EXISTS public.idx_experiment_phases_cracking_machine_type_id RENAME TO idx_experiment_books_cracking_machine_type_id; -- ============================================= -- 5. RLS POLICIES (drop old, create new with updated names) -- ============================================= DROP POLICY IF EXISTS "Experiment phases are viewable by authenticated users" ON public.experiment_books; DROP POLICY IF EXISTS "Experiment phases are insertable by authenticated users" ON public.experiment_books; DROP POLICY IF EXISTS "Experiment phases are updatable by authenticated users" ON public.experiment_books; DROP POLICY IF EXISTS "Experiment phases are deletable by authenticated users" ON public.experiment_books; CREATE POLICY "Experiment books are viewable by authenticated users" ON public.experiment_books FOR SELECT USING (auth.role() = 'authenticated'); CREATE POLICY "Experiment books are insertable by authenticated users" ON public.experiment_books FOR INSERT WITH CHECK (auth.role() = 'authenticated'); CREATE POLICY "Experiment books are updatable by authenticated users" ON public.experiment_books FOR UPDATE USING (auth.role() = 'authenticated'); CREATE POLICY "Experiment books are deletable by authenticated users" ON public.experiment_books FOR DELETE USING (auth.role() = 'authenticated'); -- ============================================= -- 6. UPDATE FUNCTION: create_phase_executions_for_repetition (references experiment_phases) -- ============================================= CREATE OR REPLACE FUNCTION create_phase_executions_for_repetition() RETURNS TRIGGER AS $$ DECLARE exp_phase_config RECORD; phase_type_list TEXT[] := ARRAY[]::TEXT[]; phase_name TEXT; BEGIN SELECT ep.has_soaking, ep.has_airdrying, ep.has_cracking, ep.has_shelling, ep.cracking_machine_type_id INTO exp_phase_config FROM public.experiments e JOIN public.experiment_books ep ON e.phase_id = ep.id WHERE e.id = NEW.experiment_id; IF exp_phase_config.has_soaking THEN phase_type_list := array_append(phase_type_list, 'soaking'); END IF; IF exp_phase_config.has_airdrying THEN phase_type_list := array_append(phase_type_list, 'airdrying'); END IF; IF exp_phase_config.has_cracking THEN phase_type_list := array_append(phase_type_list, 'cracking'); END IF; IF exp_phase_config.has_shelling THEN phase_type_list := array_append(phase_type_list, 'shelling'); END IF; FOREACH phase_name IN ARRAY phase_type_list LOOP INSERT INTO public.experiment_phase_executions ( repetition_id, phase_type, scheduled_start_time, status, created_by, soaking_duration_minutes, duration_minutes, machine_type_id ) VALUES ( NEW.id, phase_name, NOW(), 'pending', NEW.created_by, NULL, NULL, CASE WHEN phase_name = 'cracking' THEN exp_phase_config.cracking_machine_type_id ELSE NULL END ); END LOOP; RETURN NEW; END; $$ LANGUAGE plpgsql; -- ============================================= -- 7. UPDATE FUNCTION: create_sample_experiment_phases (INSERT into experiment_books) -- ============================================= CREATE OR REPLACE FUNCTION public.create_sample_experiment_phases() RETURNS VOID AS $$ DECLARE jc_cracker_id UUID; meyer_cracker_id UUID; BEGIN SELECT id INTO jc_cracker_id FROM public.machine_types WHERE name = 'JC Cracker'; SELECT id INTO meyer_cracker_id FROM public.machine_types WHERE name = 'Meyer Cracker'; INSERT INTO public.experiment_books (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by) VALUES ('Full Process - JC Cracker', 'Complete pecan processing with JC Cracker', true, true, true, true, jc_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)), ('Full Process - Meyer Cracker', 'Complete pecan processing with Meyer Cracker', true, true, true, true, meyer_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)), ('Cracking Only - JC Cracker', 'JC Cracker cracking process only', false, false, true, false, jc_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)), ('Cracking Only - Meyer Cracker', 'Meyer Cracker cracking process only', false, false, true, false, meyer_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)) ON CONFLICT (name) DO NOTHING; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- ============================================= -- 8. UPDATE VIEWS (from 00014 - experiments_with_phases, repetitions_with_phases, experiments_with_all_reps_and_phases, get_experiment_with_reps_and_phases) -- ============================================= CREATE OR REPLACE VIEW public.experiments_with_phases AS SELECT e.id, e.experiment_number, e.reps_required, e.weight_per_repetition_lbs, e.results_status, e.completion_status, e.phase_id, e.created_at, e.updated_at, e.created_by, ep.name as phase_name, ep.description as phase_description, ep.has_soaking, ep.has_airdrying, ep.has_cracking, ep.has_shelling, er.id as first_repetition_id, er.repetition_number as first_repetition_number, soaking_e.id as soaking_id, soaking_e.scheduled_start_time as soaking_scheduled_start, soaking_e.actual_start_time as soaking_actual_start, soaking_e.soaking_duration_minutes, soaking_e.scheduled_end_time as soaking_scheduled_end, soaking_e.actual_end_time as soaking_actual_end, airdrying_e.id as airdrying_id, airdrying_e.scheduled_start_time as airdrying_scheduled_start, airdrying_e.actual_start_time as airdrying_actual_start, airdrying_e.duration_minutes as airdrying_duration, airdrying_e.scheduled_end_time as airdrying_scheduled_end, airdrying_e.actual_end_time as airdrying_actual_end, cracking_e.id as cracking_id, cracking_e.scheduled_start_time as cracking_scheduled_start, cracking_e.actual_start_time as cracking_actual_start, cracking_e.actual_end_time as cracking_actual_end, mt.name as machine_type_name, shelling_e.id as shelling_id, shelling_e.scheduled_start_time as shelling_scheduled_start, shelling_e.actual_start_time as shelling_actual_start, shelling_e.actual_end_time as shelling_actual_end FROM public.experiments e LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id LEFT JOIN LATERAL ( SELECT id, repetition_number FROM public.experiment_repetitions WHERE experiment_id = e.id ORDER BY repetition_number LIMIT 1 ) er ON true LEFT JOIN public.experiment_phase_executions soaking_e ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking' LEFT JOIN public.experiment_phase_executions airdrying_e ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying' LEFT JOIN public.experiment_phase_executions cracking_e ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking' LEFT JOIN public.experiment_phase_executions shelling_e ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling' LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id; CREATE OR REPLACE VIEW public.repetitions_with_phases AS SELECT er.id, er.experiment_id, er.repetition_number, er.status, er.created_at, er.updated_at, er.created_by, e.experiment_number, e.phase_id, e.weight_per_repetition_lbs, ep.name as phase_name, ep.has_soaking, ep.has_airdrying, ep.has_cracking, ep.has_shelling, soaking_e.scheduled_start_time as soaking_scheduled_start, soaking_e.actual_start_time as soaking_actual_start, soaking_e.soaking_duration_minutes, soaking_e.scheduled_end_time as soaking_scheduled_end, soaking_e.actual_end_time as soaking_actual_end, airdrying_e.scheduled_start_time as airdrying_scheduled_start, airdrying_e.actual_start_time as airdrying_actual_start, airdrying_e.duration_minutes as airdrying_duration, airdrying_e.scheduled_end_time as airdrying_scheduled_end, airdrying_e.actual_end_time as airdrying_actual_end, cracking_e.scheduled_start_time as cracking_scheduled_start, cracking_e.actual_start_time as cracking_actual_start, cracking_e.actual_end_time as cracking_actual_end, mt.name as machine_type_name, shelling_e.scheduled_start_time as shelling_scheduled_start, shelling_e.actual_start_time as shelling_actual_start, shelling_e.actual_end_time as shelling_actual_end FROM public.experiment_repetitions er JOIN public.experiments e ON er.experiment_id = e.id LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id LEFT JOIN public.experiment_phase_executions soaking_e ON er.id = soaking_e.repetition_id AND soaking_e.phase_type = 'soaking' LEFT JOIN public.experiment_phase_executions airdrying_e ON er.id = airdrying_e.repetition_id AND airdrying_e.phase_type = 'airdrying' LEFT JOIN public.experiment_phase_executions cracking_e ON er.id = cracking_e.repetition_id AND cracking_e.phase_type = 'cracking' LEFT JOIN public.experiment_phase_executions shelling_e ON er.id = shelling_e.repetition_id AND shelling_e.phase_type = 'shelling' LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id; -- experiments_with_all_reps_and_phases CREATE OR REPLACE VIEW public.experiments_with_all_reps_and_phases AS SELECT e.id as experiment_id, e.experiment_number, e.reps_required, e.weight_per_repetition_lbs, e.results_status, e.completion_status, e.phase_id, e.created_at as experiment_created_at, e.updated_at as experiment_updated_at, e.created_by as experiment_created_by, ep.name as phase_name, ep.description as phase_description, ep.has_soaking, ep.has_airdrying, ep.has_cracking, ep.has_shelling, ep.cracking_machine_type_id as phase_cracking_machine_type_id, er.id as repetition_id, er.repetition_number, er.status as repetition_status, er.scheduled_date, er.created_at as repetition_created_at, er.updated_at as repetition_updated_at, er.created_by as repetition_created_by, soaking_e.id as soaking_execution_id, soaking_e.scheduled_start_time as soaking_scheduled_start, soaking_e.actual_start_time as soaking_actual_start, soaking_e.soaking_duration_minutes, soaking_e.scheduled_end_time as soaking_scheduled_end, soaking_e.actual_end_time as soaking_actual_end, soaking_e.status as soaking_status, airdrying_e.id as airdrying_execution_id, airdrying_e.scheduled_start_time as airdrying_scheduled_start, airdrying_e.actual_start_time as airdrying_actual_start, airdrying_e.duration_minutes as airdrying_duration_minutes, airdrying_e.scheduled_end_time as airdrying_scheduled_end, airdrying_e.actual_end_time as airdrying_actual_end, airdrying_e.status as airdrying_status, cracking_e.id as cracking_execution_id, cracking_e.scheduled_start_time as cracking_scheduled_start, cracking_e.actual_start_time as cracking_actual_start, cracking_e.scheduled_end_time as cracking_scheduled_end, cracking_e.actual_end_time as cracking_actual_end, cracking_e.machine_type_id as cracking_machine_type_id, cracking_e.status as cracking_status, mt.name as machine_type_name, shelling_e.id as shelling_execution_id, shelling_e.scheduled_start_time as shelling_scheduled_start, shelling_e.actual_start_time as shelling_actual_start, shelling_e.scheduled_end_time as shelling_scheduled_end, shelling_e.actual_end_time as shelling_actual_end, shelling_e.status as shelling_status FROM public.experiments e LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id LEFT JOIN public.experiment_repetitions er ON er.experiment_id = e.id LEFT JOIN public.experiment_phase_executions soaking_e ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking' LEFT JOIN public.experiment_phase_executions airdrying_e ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying' LEFT JOIN public.experiment_phase_executions cracking_e ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking' LEFT JOIN public.experiment_phase_executions shelling_e ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling' LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id ORDER BY e.experiment_number, er.repetition_number; -- get_experiment_with_reps_and_phases function CREATE OR REPLACE FUNCTION public.get_experiment_with_reps_and_phases(p_experiment_id UUID) RETURNS TABLE ( experiment_id UUID, experiment_number INTEGER, phase_name TEXT, repetitions JSONB ) AS $$ BEGIN RETURN QUERY SELECT e.id, e.experiment_number, ep.name, COALESCE( jsonb_agg( jsonb_build_object( 'repetition_id', er.id, 'repetition_number', er.repetition_number, 'status', er.status, 'scheduled_date', er.scheduled_date, 'soaking', jsonb_build_object( 'scheduled_start', soaking_e.scheduled_start_time, 'actual_start', soaking_e.actual_start_time, 'duration_minutes', soaking_e.soaking_duration_minutes, 'scheduled_end', soaking_e.scheduled_end_time, 'actual_end', soaking_e.actual_end_time, 'status', soaking_e.status ), 'airdrying', jsonb_build_object( 'scheduled_start', airdrying_e.scheduled_start_time, 'actual_start', airdrying_e.actual_start_time, 'duration_minutes', airdrying_e.duration_minutes, 'scheduled_end', airdrying_e.scheduled_end_time, 'actual_end', airdrying_e.actual_end_time, 'status', airdrying_e.status ), 'cracking', jsonb_build_object( 'scheduled_start', cracking_e.scheduled_start_time, 'actual_start', cracking_e.actual_start_time, 'scheduled_end', cracking_e.scheduled_end_time, 'actual_end', cracking_e.actual_end_time, 'machine_type_id', cracking_e.machine_type_id, 'machine_type_name', mt.name, 'status', cracking_e.status ), 'shelling', jsonb_build_object( 'scheduled_start', shelling_e.scheduled_start_time, 'actual_start', shelling_e.actual_start_time, 'scheduled_end', shelling_e.scheduled_end_time, 'actual_end', shelling_e.actual_end_time, 'status', shelling_e.status ) ) ORDER BY er.repetition_number ), '[]'::jsonb ) as repetitions FROM public.experiments e LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id LEFT JOIN public.experiment_repetitions er ON er.experiment_id = e.id LEFT JOIN public.experiment_phase_executions soaking_e ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking' LEFT JOIN public.experiment_phase_executions airdrying_e ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying' LEFT JOIN public.experiment_phase_executions cracking_e ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking' LEFT JOIN public.experiment_phase_executions shelling_e ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling' LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id WHERE e.id = p_experiment_id GROUP BY e.id, e.experiment_number, ep.name; END; $$ LANGUAGE plpgsql SECURITY DEFINER; GRANT SELECT ON public.experiments_with_all_reps_and_phases TO authenticated; GRANT EXECUTE ON FUNCTION public.get_experiment_with_reps_and_phases(UUID) TO authenticated;