diff --git a/db/core/changelog-master.xml b/db/core/changelog-master.xml index 5c4f997..d11dbb0 100644 --- a/db/core/changelog-master.xml +++ b/db/core/changelog-master.xml @@ -45,5 +45,6 @@ + diff --git a/db/core/changesets/036-fn-work-program-get-curve.sql b/db/core/changesets/036-fn-work-program-get-curve.sql new file mode 100644 index 0000000..e6fe33c --- /dev/null +++ b/db/core/changesets/036-fn-work-program-get-curve.sql @@ -0,0 +1,92 @@ +--liquibase formatted sql +-- PANELS · core · fn_work_program_get: jsonb_agg no puede anidar SUM (42803) + +--changeset panel:core-036-fn-work-program-get-curve splitStatements:false +CREATE OR REPLACE FUNCTION core.fn_work_program_get(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 jsonb; + v_periods jsonb; + v_budget_total numeric(14,2); + v_scheduled jsonb; + v_cumulative numeric := 0; + v_row jsonb; + v_arr jsonb := '[]'::jsonb; + v_pct_period numeric; + v_pct_cum numeric; +BEGIN + IF v_project_id IS NULL THEN + RETURN core.rpc_err('VALIDATION', 'fn_work_program_get: project_id es obligatorio', + jsonb_build_object('fn', 'fn_work_program_get')); + END IF; + + SELECT to_jsonb(wp) INTO v_program + FROM work_programs wp WHERE wp.project_id = v_project_id; + + IF v_program IS NULL THEN + SELECT COALESCE(SUM(amount), 0) INTO v_budget_total FROM budget_items WHERE project_id = v_project_id; + RETURN core.rpc_ok( + jsonb_build_object( + 'program', NULL, + 'periods', '[]'::jsonb, + 'budget_subtotal', v_budget_total, + 'curve', '[]'::jsonb + ), + format('Proyecto %s sin programa de obra', v_project_id), + jsonb_build_object('fn', 'fn_work_program_get', 'project_id', v_project_id) + ); + END IF; + + 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')::bigint; + + SELECT COALESCE(SUM(amount), 0) INTO v_budget_total FROM budget_items WHERE project_id = v_project_id; + + -- SUM va en la subquery; jsonb_agg afuera. Postgres no permite agregados anidados + -- (sqlstate 42803) y esto petaba el GET justo después de importar por partida. + SELECT COALESCE(jsonb_agg(to_jsonb(x) ORDER BY x.period_index), '[]'::jsonb) + INTO v_scheduled + FROM ( + SELECT + pr.period_index, + pr.label, + round(COALESCE(SUM(pa.amount), 0), 2) AS amount + FROM work_program_periods pr + LEFT JOIN work_program_partida_amounts pa ON pa.period_id = pr.id + WHERE pr.program_id = (v_program->>'id')::bigint + GROUP BY pr.period_index, pr.label + ) x; + + FOR v_row IN SELECT value FROM jsonb_array_elements(v_scheduled) LOOP + v_pct_period := CASE WHEN v_budget_total > 0 + THEN round((v_row.value->>'amount')::numeric / v_budget_total, 6) ELSE 0 END; + v_cumulative := v_cumulative + (v_row.value->>'amount')::numeric; + v_pct_cum := CASE WHEN v_budget_total > 0 THEN round(v_cumulative / v_budget_total, 6) ELSE 0 END; + v_arr := v_arr || jsonb_build_array(jsonb_build_object( + 'period_index', v_row.value->'period_index', + 'label', v_row.value->'label', + 'amount', v_row.value->'amount', + 'pct_period', v_pct_period, + 'pct_cumulative', v_pct_cum + )); + END LOOP; + + RETURN core.rpc_ok( + jsonb_build_object( + 'program', v_program, + 'periods', v_periods, + 'budget_subtotal', v_budget_total, + 'curve', v_arr + ), + format('Programa de obra del proyecto %s', v_project_id), + jsonb_build_object('fn', 'fn_work_program_get', 'project_id', v_project_id) + ); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_work_program_get', SQLSTATE, SQLERRM); +END; +$$;