From b7bd167e0836853a99a769c16b6f5509102d0793 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 8 Sep 2026 19:49:26 +0000 Subject: [PATCH] fix: importar programa de obra sin jsonb_object_length Postgres no tiene jsonb_object_length; fn_work_program_replace tronaba al confirmar el Excel. El conteo de semanas ahora usa un contador en el loop de periodos. Co-authored-by: alberto.martinez --- db/core/changelog-master.xml | 1 + ...2-fix-work-program-jsonb-object-length.sql | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 db/core/changesets/032-fix-work-program-jsonb-object-length.sql diff --git a/db/core/changelog-master.xml b/db/core/changelog-master.xml index ecb60c8..fa34d98 100644 --- a/db/core/changelog-master.xml +++ b/db/core/changelog-master.xml @@ -38,5 +38,6 @@ + diff --git a/db/core/changesets/032-fix-work-program-jsonb-object-length.sql b/db/core/changesets/032-fix-work-program-jsonb-object-length.sql new file mode 100644 index 0000000..b2cb279 --- /dev/null +++ b/db/core/changesets/032-fix-work-program-jsonb-object-length.sql @@ -0,0 +1,123 @@ +--liquibase formatted sql +-- PANELS · core · fn_work_program_replace: jsonb_object_length no existe en PostgreSQL + +--changeset panel:core-032a-fn-work-program-replace-period-count splitStatements:false +CREATE OR REPLACE FUNCTION core.fn_work_program_replace(payload jsonb) +RETURNS jsonb +LANGUAGE plpgsql +SECURITY INVOKER +SET search_path = core +AS $$ +DECLARE + v_project_id bigint := NULLIF(payload->>'project_id', '')::bigint; + v_source text := COALESCE(nullif(btrim(payload->>'source'), ''), 'import'); + v_status text := COALESCE(nullif(btrim(payload->>'status'), ''), 'active'); + v_start date; + v_end date; + v_program_id bigint; + v_period jsonb; + v_period_id bigint; + v_period_map jsonb := '{}'::jsonb; + v_period_count integer := 0; + v_slot jsonb; + v_partida jsonb; + v_chapter jsonb; + v_ch jsonb; + v_slot_count integer := 0; + v_partida_count integer := 0; + v_err jsonb; +BEGIN + IF v_project_id IS NULL THEN + RETURN core.rpc_err('VALIDATION', 'fn_work_program_replace: project_id es obligatorio', + jsonb_build_object('fn', 'fn_work_program_replace')); + END IF; + + v_err := core._wp_assert_project_ready(v_project_id); + IF v_err IS NOT NULL THEN RETURN v_err; END IF; + + SELECT start_date, end_date INTO v_start, v_end FROM projects WHERE id = v_project_id; + + DELETE FROM work_programs WHERE project_id = v_project_id; + + INSERT INTO work_programs (project_id, start_date, end_date, duration_days, source, status) + VALUES (v_project_id, v_start, v_end, (v_end - v_start + 1), v_source, v_status) + RETURNING id INTO v_program_id; + + FOR v_period IN SELECT * FROM jsonb_array_elements(core._wp_generate_periods(v_start, v_end)) LOOP + INSERT INTO work_program_periods (program_id, period_index, label, start_date, end_date) + VALUES ( + v_program_id, + (v_period->>'period_index')::integer, + v_period->>'label', + (v_period->>'start_date')::date, + (v_period->>'end_date')::date + ) + RETURNING id INTO v_period_id; + v_period_map := v_period_map || jsonb_build_object(v_period->>'period_index', v_period_id); + v_period_count := v_period_count + 1; + END LOOP; + + FOR v_slot IN SELECT * FROM jsonb_array_elements(COALESCE(payload->'concept_slots', '[]'::jsonb)) LOOP + v_period_id := (v_period_map->>(v_slot->>'period_index'))::bigint; + IF v_period_id IS NULL THEN CONTINUE; END IF; + IF NOT EXISTS ( + SELECT 1 FROM budget_items bi + WHERE bi.id = (v_slot->>'budget_item_id')::bigint AND bi.project_id = v_project_id + ) THEN CONTINUE; END IF; + IF core._wp_slot_weight((v_slot->>'start_pct')::numeric, (v_slot->>'end_pct')::numeric) <= 0 THEN CONTINUE; END IF; + INSERT INTO work_program_concept_slots (program_id, budget_item_id, period_id, start_pct, end_pct) + VALUES ( + v_program_id, + (v_slot->>'budget_item_id')::bigint, + v_period_id, + COALESCE((v_slot->>'start_pct')::numeric, 0), + COALESCE((v_slot->>'end_pct')::numeric, 0) + ); + v_slot_count := v_slot_count + 1; + END LOOP; + + FOR v_ch IN SELECT * FROM jsonb_array_elements(COALESCE(payload->'chapters_to_create', '[]'::jsonb)) LOOP + INSERT INTO budget_chapters (project_id, parent_id, code, name, wbs, sort_order) + VALUES ( + v_project_id, + NULLIF(v_ch->>'parent_id', '')::bigint, + upper(COALESCE(nullif(btrim(v_ch->>'code'), ''), '')), + COALESCE(nullif(btrim(v_ch->>'name'), ''), 'Partida'), + COALESCE(nullif(btrim(v_ch->>'wbs'), ''), ''), + COALESCE((v_ch->>'sort_order')::integer, 0) + ); + END LOOP; + + FOR v_partida IN SELECT * FROM jsonb_array_elements(COALESCE(payload->'partida_amounts', '[]'::jsonb)) LOOP + v_period_id := (v_period_map->>(v_partida->>'period_index'))::bigint; + IF v_period_id IS NULL THEN CONTINUE; END IF; + IF NOT EXISTS ( + SELECT 1 FROM budget_chapters bc + WHERE bc.id = (v_partida->>'budget_chapter_id')::bigint AND bc.project_id = v_project_id + ) THEN CONTINUE; END IF; + IF COALESCE((v_partida->>'amount')::numeric, 0) = 0 THEN CONTINUE; END IF; + INSERT INTO work_program_partida_amounts (program_id, budget_chapter_id, period_id, amount) + VALUES ( + v_program_id, + (v_partida->>'budget_chapter_id')::bigint, + v_period_id, + round(COALESCE((v_partida->>'amount')::numeric, 0), 2) + ); + v_partida_count := v_partida_count + 1; + END LOOP; + + RETURN core.rpc_ok( + jsonb_build_object( + 'program_id', v_program_id, + 'concept_slots', v_slot_count, + 'partida_amounts', v_partida_count, + 'period_count', v_period_count + ), + format('Programa de obra del proyecto %s reemplazado (%s slots, %s erogaciones)', + v_project_id, v_slot_count, v_partida_count), + jsonb_build_object('fn', 'fn_work_program_replace', 'project_id', v_project_id) + ); +EXCEPTION WHEN OTHERS THEN + RETURN core.rpc_from_exception('fn_work_program_replace', SQLSTATE, SQLERRM); +END; +$$;