-- Verifica las 4 lecturas que carga Programa de obra (get, concepts, partidas, vs-cost). -- Esquema mínimo con las columnas reales. No es Liquibase. DROP SCHEMA IF EXISTS core CASCADE; CREATE SCHEMA core; SET search_path = core; CREATE FUNCTION rpc_generic_messages() RETURNS text[] LANGUAGE sql IMMUTABLE AS $$ SELECT ARRAY['OK']::text[]; $$; CREATE FUNCTION rpc_assert_message(p_message text) RETURNS void LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN IF p_message IS NULL OR btrim(p_message) = '' THEN RAISE EXCEPTION 'empty'; END IF; END; $$; CREATE FUNCTION rpc_ok(p_data jsonb, p_message text, p_ctx jsonb DEFAULT '{}'::jsonb) RETURNS jsonb LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN PERFORM rpc_assert_message(p_message); RETURN jsonb_build_object('ok', true, 'code', 'OK', 'message', p_message, 'data', p_data); END; $$; CREATE FUNCTION rpc_err(p_code text, p_message text, p_ctx jsonb DEFAULT '{}'::jsonb, p_errors jsonb DEFAULT NULL) RETURNS jsonb LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN PERFORM rpc_assert_message(p_message); RETURN jsonb_build_object('ok', false, 'code', p_code, 'message', p_message, 'data', NULL); END; $$; CREATE FUNCTION rpc_from_exception(p_fn text, p_sqlstate text, p_message text, p_detail text DEFAULT NULL) RETURNS jsonb LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN RETURN rpc_err('INTERNAL', format('Error inesperado en %s (sqlstate=%s): %s', p_fn, p_sqlstate, p_message), jsonb_build_object('fn', p_fn, 'sqlstate', p_sqlstate)); END; $$; CREATE TABLE companies (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text); CREATE TABLE projects ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text, status text DEFAULT 'activo', tenant_id int, start_date date, end_date date ); CREATE TABLE budget_chapters ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id BIGINT REFERENCES projects(id), code text, name text, sort_order int DEFAULT 0 ); CREATE TABLE budget_items ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id BIGINT REFERENCES projects(id), chapter_id BIGINT REFERENCES budget_chapters(id), code text, description text, unit text DEFAULT '', amount numeric(14,2) DEFAULT 0, sort_order int DEFAULT 0 ); CREATE TABLE work_programs ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id BIGINT UNIQUE REFERENCES projects(id), start_date date, end_date date, duration_days int, source text, status text ); CREATE TABLE work_program_periods ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, program_id BIGINT REFERENCES work_programs(id), period_index int, label text, start_date date, end_date date ); CREATE TABLE work_program_partida_amounts ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, program_id BIGINT REFERENCES work_programs(id), budget_chapter_id BIGINT REFERENCES budget_chapters(id), period_id BIGINT REFERENCES work_program_periods(id), amount numeric(14,2) ); CREATE TABLE work_program_concept_slots ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, program_id BIGINT, budget_item_id BIGINT, period_id BIGINT, start_pct numeric, end_pct numeric ); CREATE TABLE work_program_progress ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, program_id BIGINT, budget_item_id BIGINT, period_id BIGINT, pct_complete numeric ); CREATE TABLE expense_entries ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id BIGINT, expense_date date, subtotal numeric(14,2), status text, counts_toward_budget boolean, budget_item_id BIGINT ); CREATE TABLE warehouses ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tenant_id int, kind text, project_id BIGINT ); CREATE TABLE warehouse_materials ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tenant_id int, code text, name text ); CREATE TABLE warehouse_movements ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tenant_id int, warehouse_id BIGINT REFERENCES warehouses(id), material_id BIGINT, movement_type text, movement_date date, quantity numeric, total_cost numeric(14,2), budget_item_id BIGINT ); CREATE FUNCTION _cost_executed_amount(p_budget_item_id bigint) RETURNS numeric LANGUAGE sql STABLE SET search_path = core AS $$ SELECT round(COALESCE( (SELECT SUM(e.subtotal) FROM expense_entries e WHERE e.budget_item_id = p_budget_item_id AND e.status = 'confirmed' AND e.counts_toward_budget = true), 0 ) + COALESCE( (SELECT SUM(m.total_cost) FROM warehouse_movements m WHERE m.budget_item_id = p_budget_item_id AND m.movement_type = 'exit'), 0 ), 2); $$;