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 <alberto.martinez@mrdev.mx>
This commit is contained in:
Cursor Agent 2026-09-08 19:49:26 +00:00
parent b0a1a3147c
commit b7bd167e08
No known key found for this signature in database
2 changed files with 124 additions and 0 deletions

View file

@ -38,5 +38,6 @@
<include file="changesets/029-schema-work-program.sql" relativeToChangelogFile="true"/> <include file="changesets/029-schema-work-program.sql" relativeToChangelogFile="true"/>
<include file="changesets/030-rpc-work-program.sql" relativeToChangelogFile="true"/> <include file="changesets/030-rpc-work-program.sql" relativeToChangelogFile="true"/>
<include file="changesets/031-project-doc-types-technical.sql" relativeToChangelogFile="true"/> <include file="changesets/031-project-doc-types-technical.sql" relativeToChangelogFile="true"/>
<include file="changesets/032-fix-work-program-jsonb-object-length.sql" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>

View file

@ -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;
$$;