--liquibase formatted sql -- PANELS · core · fn_work_program_vs_cost: warehouse_movements no tiene project_id --changeset panel:core-038-fn-work-program-vs-cost splitStatements:false CREATE OR REPLACE FUNCTION core.fn_work_program_vs_cost(payload jsonb) RETURNS jsonb LANGUAGE plpgsql SECURITY INVOKER SET search_path = core AS $$ DECLARE v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint; v_program_id bigint; v_periods jsonb; v_by_period jsonb; v_by_chapter jsonb; v_budget_total numeric(14,2); BEGIN IF v_project_id IS NULL THEN RETURN core.rpc_err('VALIDATION', 'fn_work_program_vs_cost: project_id es obligatorio', jsonb_build_object('fn', 'fn_work_program_vs_cost')); END IF; SELECT id INTO v_program_id FROM work_programs WHERE project_id = v_project_id; IF v_program_id IS NULL THEN RETURN core.rpc_ok(jsonb_build_object('periods', '[]'::jsonb, 'by_period', '[]'::jsonb, 'by_chapter', '[]'::jsonb), format('Proyecto %s sin programa', v_project_id), jsonb_build_object('fn', 'fn_work_program_vs_cost', 'project_id', v_project_id)); END IF; SELECT COALESCE(SUM(amount), 0) INTO v_budget_total FROM budget_items WHERE project_id = v_project_id; SELECT COALESCE(jsonb_agg(to_jsonb(p) ORDER BY p.period_index), '[]'::jsonb) INTO v_periods FROM work_program_periods p WHERE p.program_id = v_program_id; SELECT COALESCE(jsonb_agg(to_jsonb(x) ORDER BY x.period_index), '[]'::jsonb) INTO v_by_period FROM ( SELECT pr.period_index, pr.label, pr.start_date, pr.end_date, round(COALESCE(SUM(pa.amount), 0), 2) AS scheduled_amount, round(COALESCE(( SELECT SUM(e.subtotal) FROM expense_entries e WHERE e.project_id = v_project_id AND e.status = 'confirmed' AND e.counts_toward_budget = true AND e.expense_date BETWEEN pr.start_date AND pr.end_date ), 0) + COALESCE(( SELECT SUM(m.total_cost) FROM warehouse_movements m JOIN warehouses w ON w.id = m.warehouse_id WHERE w.project_id = v_project_id AND m.movement_type = 'exit' AND m.movement_date BETWEEN pr.start_date AND pr.end_date ), 0), 2) AS executed_amount FROM work_program_periods pr LEFT JOIN work_program_partida_amounts pa ON pa.period_id = pr.id AND pa.program_id = v_program_id WHERE pr.program_id = v_program_id GROUP BY pr.id, pr.period_index, pr.label, pr.start_date, pr.end_date ) x; SELECT COALESCE(jsonb_agg(to_jsonb(y) ORDER BY y.chapter_code), '[]'::jsonb) INTO v_by_chapter FROM ( SELECT bc.id AS chapter_id, bc.code AS chapter_code, bc.name AS chapter_name, round(COALESCE(( SELECT SUM(pa.amount) FROM work_program_partida_amounts pa WHERE pa.budget_chapter_id = bc.id AND pa.program_id = v_program_id ), 0), 2) AS scheduled_amount, round(COALESCE(( SELECT SUM(core._cost_executed_amount(bi.id)) FROM budget_items bi WHERE bi.chapter_id = bc.id ), 0), 2) AS executed_amount, round(COALESCE(( SELECT SUM(bi.amount) FROM budget_items bi WHERE bi.chapter_id = bc.id ), 0), 2) AS budget_amount FROM budget_chapters bc WHERE bc.project_id = v_project_id ) y; RETURN core.rpc_ok( jsonb_build_object( 'periods', v_periods, 'budget_subtotal', v_budget_total, 'by_period', v_by_period, 'by_chapter', v_by_chapter ), format('Programado vs ejecutado del proyecto %s', v_project_id), jsonb_build_object('fn', 'fn_work_program_vs_cost', 'project_id', v_project_id) ); EXCEPTION WHEN OTHERS THEN RETURN core.rpc_from_exception('fn_work_program_vs_cost', SQLSTATE, SQLERRM); END; $$;