perf(form4): v_form4_sheet_jsonb без рекурсивного агрегата jsonb_sum (GROUPING SETS)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
55ba4e7e96
commit
b2a022953d
15
api/alembic/versions/0050_form4_sheet_sum.py
Normal file
15
api/alembic/versions/0050_form4_sheet_sum.py
Normal file
@ -0,0 +1,15 @@
|
||||
from sql_migration import run_sql_file_migration
|
||||
|
||||
|
||||
revision = "0050"
|
||||
down_revision = "0049"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
run_sql_file_migration("0050_form4_sheet_sum.sql")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
602
api/alembic/versions/sql/0050_form4_sheet_sum.sql
Normal file
602
api/alembic/versions/sql/0050_form4_sheet_sum.sql
Normal file
@ -0,0 +1,602 @@
|
||||
-- AURORA-1846: FORM_4, замена рекурсивного агрегата v3.jsonb_sum в
|
||||
-- v3.v_form4_sheet_jsonb на разворот листьев jsonb + GROUPING SETS.
|
||||
-- Тело — копия sql/0012_del_project.sql:1091-1617, изменён только блок
|
||||
-- «4. Агрегаты». v3.jsonb_sum / v3.jsonb_numeric_sum не удаляются (эталон в тестах).
|
||||
|
||||
CREATE OR REPLACE FUNCTION v3.v_form4_sheet_jsonb(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[])
|
||||
RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
AS $function$
|
||||
-- ═══ 1. Базовые INPUT-строки с расширенной иерархией ═══════════════════════
|
||||
WITH inp_raw AS (
|
||||
SELECT j.*
|
||||
FROM v3.v_form4_sheet_lines_jsonb(p_form_id, p_sheet, p_sections) j
|
||||
WHERE j.row_type = 'INPUT'
|
||||
),
|
||||
inp AS (
|
||||
SELECT
|
||||
i.*,
|
||||
bl.project_id AS line_project_id,
|
||||
|
||||
-- --- разрешение проекта ---
|
||||
-- A. bl.project_id → project (level='project')
|
||||
-- B. bl.project_id → program (level='program') — тогда проекта нет
|
||||
CASE WHEN prj.id IS NOT NULL THEN prj.id END AS prj_id,
|
||||
CASE WHEN prj.id IS NOT NULL THEN prj.name END AS prj_name,
|
||||
|
||||
-- --- разрешение программы ---
|
||||
-- A. через проект: prog_via_prj
|
||||
-- B. напрямую: prog_direct
|
||||
COALESCE(prog_via_prj.id, prog_direct.id) AS prog_id,
|
||||
COALESCE(prog_via_prj.name, prog_direct.name) AS prog_name,
|
||||
|
||||
-- --- section (depth=0) ---
|
||||
sec.id AS sec_id,
|
||||
sec.section_code AS sec_code,
|
||||
sec.name AS sec_name
|
||||
|
||||
FROM inp_raw i
|
||||
JOIN v3.budget_line bl ON bl.id = i.line_id
|
||||
|
||||
-- проект (level='project')
|
||||
LEFT JOIN v3.form4_project prj
|
||||
ON prj.id = bl.project_id AND prj.level = 'project'
|
||||
|
||||
-- программа через проект
|
||||
LEFT JOIN v3.form4_project prog_via_prj
|
||||
ON prog_via_prj.id = prj.parent_id AND prog_via_prj.level = 'program'
|
||||
|
||||
-- программа напрямую (bl.project_id → program)
|
||||
LEFT JOIN v3.form4_project prog_direct
|
||||
ON prog_direct.id = bl.project_id AND prog_direct.level = 'program'
|
||||
|
||||
-- раздел depth=0 (через expense_item)
|
||||
JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
|
||||
LEFT JOIN LATERAL (
|
||||
WITH RECURSIVE up AS (
|
||||
SELECT id, parent_id, name, section_code, depth
|
||||
FROM v3.expense_item WHERE id = ei.id
|
||||
UNION ALL
|
||||
SELECT e.id, e.parent_id, e.name, e.section_code, e.depth
|
||||
FROM v3.expense_item e JOIN up u ON u.parent_id = e.id
|
||||
)
|
||||
SELECT id, name, section_code FROM up WHERE depth = 0 LIMIT 1
|
||||
) sec ON TRUE
|
||||
),
|
||||
tp_agg AS (
|
||||
SELECT 1 AS tp_id,
|
||||
jsonb_build_object(
|
||||
'q1', 0, 'q2', 0,
|
||||
'q3', 0, 'q4', 0,
|
||||
'year', 0, 'comment', null
|
||||
) AS tp_plan,
|
||||
|
||||
|
||||
|
||||
jsonb_build_object(
|
||||
'q1', 0, 'q2', 0,
|
||||
'q3', 0, 'q4', 0,
|
||||
'year', 0,
|
||||
'justification', null
|
||||
) AS tp_seq_dfip,
|
||||
|
||||
jsonb_build_object(
|
||||
'q1', 0, 'q2', 0,
|
||||
'q3', 0, 'q4', 0,
|
||||
'year', 0
|
||||
) AS tp_approved,
|
||||
|
||||
jsonb_build_object(
|
||||
'total', null,
|
||||
'reference', null,
|
||||
'counterparty', null,
|
||||
'deadline', null,
|
||||
'comment', null,
|
||||
'future_y1', null,
|
||||
'future_y2', null,
|
||||
'other_ssp', null,
|
||||
'centralized_flag', null
|
||||
) AS tp_contract_summary,
|
||||
|
||||
jsonb_build_object(
|
||||
'contract_ref', null,
|
||||
'allocation_purpose', null
|
||||
) AS tp_allocation,
|
||||
|
||||
jsonb_build_object(
|
||||
'q1', 0, 'q2', 0,
|
||||
'q3', 0, 'q4', 0,
|
||||
'year', 0,
|
||||
'justification', null
|
||||
) AS tp_reserve,
|
||||
|
||||
jsonb_build_object(
|
||||
'approved', null,
|
||||
'protocol', null,
|
||||
'note', null
|
||||
) AS tp_collegial,
|
||||
|
||||
jsonb_build_object(
|
||||
'ceiling', null,
|
||||
'q1', null,
|
||||
'q2', null,
|
||||
'q3', null,
|
||||
'q4', null,
|
||||
'rf_schedule', null,
|
||||
'delivery_deadline', null,
|
||||
'proc_plan', null,
|
||||
'proc_method', null,
|
||||
'comment', null
|
||||
) AS tp_ckk,
|
||||
|
||||
jsonb_build_object(
|
||||
'act', null,
|
||||
'date', null,
|
||||
'scheme', null,
|
||||
'addenda', null,
|
||||
'ceiling', null,
|
||||
'comment', null,
|
||||
'subject', null,
|
||||
'currency', null,
|
||||
'deadline', null,
|
||||
'vat_rate', null,
|
||||
'reference', null,
|
||||
'q1', null,
|
||||
'q2', null,
|
||||
'q3', null,
|
||||
'q4', null,
|
||||
'rf_schedule', null,
|
||||
'counterparty', null,
|
||||
'exchange_rate', null,
|
||||
'amount_foreign', null
|
||||
) AS tp_contract,
|
||||
|
||||
jsonb_build_object(
|
||||
'y2026', jsonb_build_object('q1', null, 'q2', null, 'q3', null, 'q4', null),
|
||||
'y2027', jsonb_build_object('q1', null, 'q2', null, 'q3', null, 'q4', null)
|
||||
) AS tp_booking,
|
||||
|
||||
jsonb_build_object(
|
||||
'total', 0,
|
||||
'adj_rf', null,
|
||||
'pay_ho', null,
|
||||
'pay_rf', null,
|
||||
'adj_ssp', null,
|
||||
'booking', null,
|
||||
'pay_act', null,
|
||||
'pay_date', null,
|
||||
'actual_m1', null,
|
||||
'actual_m2', null,
|
||||
'actual_m3', null,
|
||||
'pay_amount', null,
|
||||
'adj_comment', null,
|
||||
'adj_current', null,
|
||||
'adj_reserve', null,
|
||||
'pay_comment', null,
|
||||
'transfer_q2', null,
|
||||
'transfer_q3', null,
|
||||
'transfer_q4', null,
|
||||
'transfer_econ', null,
|
||||
'actual_quarter', 0,
|
||||
'corrected_plan', 0,
|
||||
'residual_after_actual', 0,
|
||||
'residual_after_booking', 0
|
||||
) AS tp_q1,
|
||||
|
||||
jsonb_build_object(
|
||||
'target_change', null,
|
||||
'base_correction', null,
|
||||
'base_correction_comment', null,
|
||||
'revision_inc', null,
|
||||
'revision_seq', null,
|
||||
'revision_comment', null,
|
||||
'new_plan', 0,
|
||||
'adj_current', null,
|
||||
'adj_ssp', null,
|
||||
'adj_rf', null,
|
||||
'adj_reserve', null,
|
||||
'adj_comment', null,
|
||||
'corrected_plan', 0,
|
||||
'pay_date', null,
|
||||
'pay_amount', null,
|
||||
'pay_ho', null,
|
||||
'pay_rf', null,
|
||||
'pay_comment', null,
|
||||
'pay_act', null,
|
||||
'booking', null,
|
||||
'actual_m1', null,
|
||||
'actual_m2', null,
|
||||
'actual_m3', null,
|
||||
'actual_quarter', 0,
|
||||
'residual_after_booking', 0,
|
||||
'residual_after_actual', 0,
|
||||
'transfer_q3', null,
|
||||
'transfer_q4', null,
|
||||
'transfer_econ', null,
|
||||
'total', 0
|
||||
) AS tp_q2,
|
||||
|
||||
jsonb_build_object(
|
||||
'target_change', null,
|
||||
'base_correction', null,
|
||||
'base_correction_comment', null,
|
||||
'revision_inc', null,
|
||||
'revision_seq', null,
|
||||
'revision_comment', null,
|
||||
'new_plan', 0,
|
||||
'adj_current', null,
|
||||
'adj_ssp', null,
|
||||
'adj_rf', null,
|
||||
'adj_reserve', null,
|
||||
'adj_comment', null,
|
||||
'corrected_plan', 0,
|
||||
'pay_date', null,
|
||||
'pay_amount', null,
|
||||
'pay_ho', null,
|
||||
'pay_rf', null,
|
||||
'pay_comment', null,
|
||||
'pay_act', null,
|
||||
'booking', null,
|
||||
'actual_m1', null,
|
||||
'actual_m2', null,
|
||||
'actual_m3', null,
|
||||
'actual_quarter', 0,
|
||||
'residual_after_booking', 0,
|
||||
'residual_after_actual', 0,
|
||||
'transfer_q4', null,
|
||||
'transfer_econ', null,
|
||||
'total', 0
|
||||
) AS tp_q3,
|
||||
|
||||
jsonb_build_object(
|
||||
'target_change', null,
|
||||
'base_correction', null,
|
||||
'base_correction_comment', null,
|
||||
'revision_inc', null,
|
||||
'revision_seq', null,
|
||||
'revision_comment', null,
|
||||
'new_plan', 0,
|
||||
'adj_current', null,
|
||||
'adj_ssp', null,
|
||||
'adj_rf', null,
|
||||
'adj_reserve', null,
|
||||
'adj_comment', null,
|
||||
'corrected_plan', 0,
|
||||
'pay_date', null,
|
||||
'pay_amount', null,
|
||||
'pay_ho', null,
|
||||
'pay_rf', null,
|
||||
'pay_comment', null,
|
||||
'pay_act', null,
|
||||
'booking', null,
|
||||
'actual_m1', null,
|
||||
'actual_m2', null,
|
||||
'actual_m3', null,
|
||||
'actual_spod', null,
|
||||
'actual_quarter', 0,
|
||||
'residual_after_booking', 0,
|
||||
'residual_after_actual', 0,
|
||||
'transfer_econ', null,
|
||||
'total', 0
|
||||
) AS tp_q4,
|
||||
jsonb_build_object(
|
||||
'fact_year', 0
|
||||
) AS tp_totals
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
),
|
||||
-- ═══ 2. Все программы (включая пустые) ═════════════════════════════════════
|
||||
all_programs AS (
|
||||
SELECT
|
||||
fp.id AS prog_id,
|
||||
fp.name AS prog_name,
|
||||
fp.section_code,
|
||||
sec.id AS sec_id,
|
||||
sec.section_code AS sec_code,
|
||||
sec.name AS sec_name
|
||||
FROM v3.form4_project fp
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT id, section_code, name
|
||||
FROM v3.expense_item
|
||||
WHERE section_code = fp.section_code AND depth = 0
|
||||
LIMIT 1
|
||||
) sec ON TRUE
|
||||
WHERE fp.level = 'program'
|
||||
AND (p_sections IS NULL OR fp.section_code = ANY(p_sections))
|
||||
AND (fp.section_code = p_sheet) AND fp.form_id = p_form_id
|
||||
),
|
||||
|
||||
-- ═══ 3. Все проекты под программами (включая пустые) ═══════════════════════
|
||||
all_projects AS (
|
||||
SELECT
|
||||
prj.id AS prj_id,
|
||||
prj.name AS prj_name,
|
||||
prj.parent_id AS prog_id,
|
||||
ap.sec_id, ap.sec_code, ap.sec_name
|
||||
FROM v3.form4_project prj
|
||||
JOIN all_programs ap ON ap.prog_id = prj.parent_id
|
||||
WHERE prj.level = 'project' AND (ap.section_code = p_sheet)
|
||||
),
|
||||
|
||||
-- ═══ 4. Агрегаты с финансовыми данными (только из inp) ════════════════════
|
||||
-- Замена рекурсивного агрегата v3.jsonb_sum (AURORA-1846): листья jsonb
|
||||
-- разворачиваются в строки, суммируются GROUPING SETS по трём уровням
|
||||
-- (раздел / программа / проект) и собираются обратно в jsonb.
|
||||
-- Правила те же, что у v3.jsonb_numeric_sum: числа складываются, нечисловые
|
||||
-- игнорируются, если по пути есть число; текст/null сохраняются, если путь
|
||||
-- встретился в группе один раз; иначе JSON null; NULL-секция даёт NULL.
|
||||
|
||||
-- Имена групп (без jsonb)
|
||||
prj_names AS (
|
||||
SELECT
|
||||
sec_id, MAX(sec_code) AS sec_code, MAX(sec_name) AS sec_name,
|
||||
prog_id, MAX(prog_name) AS prog_name,
|
||||
prj_id, MAX(prj_name) AS prj_name
|
||||
FROM inp WHERE prj_id IS NOT NULL
|
||||
GROUP BY sec_id, prog_id, prj_id
|
||||
),
|
||||
prog_names AS (
|
||||
SELECT
|
||||
sec_id, MAX(sec_code) AS sec_code, MAX(sec_name) AS sec_name,
|
||||
prog_id, MAX(prog_name) AS prog_name
|
||||
FROM inp WHERE prog_id IS NOT NULL
|
||||
GROUP BY sec_id, prog_id
|
||||
),
|
||||
root_names AS (
|
||||
SELECT
|
||||
sec_id, MAX(sec_code) AS sec_code, MAX(sec_name) AS sec_name
|
||||
FROM inp
|
||||
GROUP BY sec_id
|
||||
),
|
||||
|
||||
-- Листья jsonb: (колонка, ключ 1-го уровня, ключ 2-го уровня или NULL, значение)
|
||||
leaves AS (
|
||||
SELECT
|
||||
i.sec_id, i.prog_id, i.prj_id,
|
||||
c.col, e1.key AS k1, e2.key AS k2,
|
||||
COALESCE(e2.value, e1.value) AS val
|
||||
FROM inp i
|
||||
CROSS JOIN LATERAL (VALUES
|
||||
('plan', i.plan_data),
|
||||
('seq_dfip', i.seq_dfip_data),
|
||||
('approved', i.approved_data),
|
||||
('contract_summary', i.contract_summary_data),
|
||||
('allocation', i.allocation_data),
|
||||
('reserve', i.reserve_data),
|
||||
('collegial', i.collegial_data),
|
||||
('ckk', i.ckk_data),
|
||||
('contract', i.contract_data),
|
||||
('booking', i.booking_data),
|
||||
('q1', i.q1_data),
|
||||
('q2', i.q2_data),
|
||||
('q3', i.q3_data),
|
||||
('q4', i.q4_data),
|
||||
('totals', i.totals_data)
|
||||
) AS c(col, v)
|
||||
CROSS JOIN LATERAL jsonb_each(
|
||||
CASE WHEN jsonb_typeof(c.v) = 'object' THEN c.v END
|
||||
) AS e1
|
||||
LEFT JOIN LATERAL jsonb_each(
|
||||
CASE WHEN jsonb_typeof(e1.value) = 'object' THEN e1.value END
|
||||
) AS e2 ON TRUE
|
||||
),
|
||||
|
||||
-- Суммы листьев сразу по трём уровням (lvl: 3 раздел, 1 программа, 0 проект)
|
||||
leaf_agg AS (
|
||||
SELECT
|
||||
GROUPING(prog_id, prj_id) AS lvl,
|
||||
sec_id, prog_id, prj_id, col, k1, k2,
|
||||
COUNT(*) AS n,
|
||||
COUNT(*) FILTER (WHERE jsonb_typeof(val) = 'number') AS n_num,
|
||||
SUM((val #>> '{}')::numeric) FILTER (WHERE jsonb_typeof(val) = 'number') AS s,
|
||||
(array_agg(val))[1] AS one
|
||||
FROM leaves
|
||||
GROUP BY GROUPING SETS (
|
||||
(sec_id, col, k1, k2),
|
||||
(sec_id, prog_id, col, k1, k2),
|
||||
(sec_id, prog_id, prj_id, col, k1, k2)
|
||||
)
|
||||
),
|
||||
leaf_val AS (
|
||||
SELECT
|
||||
lvl, sec_id, prog_id, prj_id, col, k1, k2,
|
||||
CASE
|
||||
WHEN n_num > 0 THEN to_jsonb(s)
|
||||
WHEN n = 1 THEN one
|
||||
ELSE 'null'::jsonb
|
||||
END AS v
|
||||
FROM leaf_agg
|
||||
WHERE NOT (lvl = 1 AND prog_id IS NULL)
|
||||
AND NOT (lvl = 0 AND prj_id IS NULL)
|
||||
),
|
||||
-- Сборка второго уровня вложенности (booking.y2026.q1 → объект по ключу k1)
|
||||
k1_val AS (
|
||||
SELECT
|
||||
lvl, sec_id, prog_id, prj_id, col, k1,
|
||||
CASE
|
||||
WHEN bool_or(k2 IS NOT NULL) THEN jsonb_object_agg(k2, v) FILTER (WHERE k2 IS NOT NULL)
|
||||
ELSE (array_agg(v))[1]
|
||||
END AS v
|
||||
FROM leaf_val
|
||||
GROUP BY lvl, sec_id, prog_id, prj_id, col, k1
|
||||
),
|
||||
-- Сборка по колонкам: одна строка на группу
|
||||
lvl_data AS (
|
||||
SELECT
|
||||
lvl, sec_id, prog_id, prj_id,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'plan') AS plan_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'seq_dfip') AS seq_dfip_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'approved') AS approved_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'contract_summary') AS contract_summary_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'allocation') AS allocation_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'reserve') AS reserve_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'collegial') AS collegial_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'ckk') AS ckk_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'contract') AS contract_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'booking') AS booking_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'q1') AS q1_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'q2') AS q2_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'q3') AS q3_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'q4') AS q4_data,
|
||||
jsonb_object_agg(k1, v) FILTER (WHERE col = 'totals') AS totals_data
|
||||
FROM k1_val
|
||||
GROUP BY lvl, sec_id, prog_id, prj_id
|
||||
),
|
||||
|
||||
prj_agg AS (
|
||||
SELECT n.*, d.plan_data, d.seq_dfip_data, d.approved_data, d.contract_summary_data, d.allocation_data, d.reserve_data, d.collegial_data, d.ckk_data, d.contract_data, d.booking_data, d.q1_data, d.q2_data, d.q3_data, d.q4_data, d.totals_data
|
||||
FROM prj_names n
|
||||
LEFT JOIN lvl_data d
|
||||
ON d.lvl = 0 AND d.sec_id = n.sec_id AND d.prog_id = n.prog_id AND d.prj_id = n.prj_id
|
||||
),
|
||||
prog_agg AS (
|
||||
SELECT n.*, d.plan_data, d.seq_dfip_data, d.approved_data, d.contract_summary_data, d.allocation_data, d.reserve_data, d.collegial_data, d.ckk_data, d.contract_data, d.booking_data, d.q1_data, d.q2_data, d.q3_data, d.q4_data, d.totals_data
|
||||
FROM prog_names n
|
||||
LEFT JOIN lvl_data d
|
||||
ON d.lvl = 1 AND d.sec_id = n.sec_id AND d.prog_id = n.prog_id
|
||||
),
|
||||
root_agg AS (
|
||||
SELECT n.*, d.plan_data, d.seq_dfip_data, d.approved_data, d.contract_summary_data, d.allocation_data, d.reserve_data, d.collegial_data, d.ckk_data, d.contract_data, d.booking_data, d.q1_data, d.q2_data, d.q3_data, d.q4_data, d.totals_data
|
||||
FROM root_names n
|
||||
LEFT JOIN lvl_data d
|
||||
ON d.lvl = 3 AND d.sec_id = n.sec_id
|
||||
),
|
||||
|
||||
-- Нулевой JSONB-шаблон (для пустых программ/проектов)
|
||||
zero AS (
|
||||
SELECT '{}'::jsonb AS z
|
||||
),
|
||||
|
||||
-- ═══ 5. UNION всех уровней ═════════════════════════════════════════════════
|
||||
unioned AS (
|
||||
-- ROOT — только из агрегатов (не имеет смысла пустой ROOT без строк)
|
||||
SELECT
|
||||
'ROOT'::VARCHAR AS row_type, 0 AS depth, NULL::INT AS line_id,
|
||||
jsonb_build_object('section_code', sec_code, 'name', sec_name, 'expense_item_id', sec_id) AS header,
|
||||
plan_data, seq_dfip_data, approved_data,
|
||||
contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data,
|
||||
contract_data, booking_data,
|
||||
q1_data, q2_data, q3_data, q4_data, totals_data,
|
||||
ARRAY[sec_id, 0, 0, 0]::INT[] AS sort_path
|
||||
FROM root_agg
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- GROUP — из агрегатов (программы с бюджетными строками)
|
||||
SELECT
|
||||
'GROUP', 1, NULL,
|
||||
jsonb_build_object('section_code', sec_code, 'name', prog_name, 'program_id', prog_id, 'expense_item_id', sec_id),
|
||||
plan_data, seq_dfip_data, approved_data,
|
||||
contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data,
|
||||
contract_data, booking_data,
|
||||
q1_data, q2_data, q3_data, q4_data, totals_data,
|
||||
ARRAY[sec_id, prog_id, 0, 0]::INT[]
|
||||
FROM prog_agg
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- GROUP — пустые программы (нет ни одной budget_line)
|
||||
SELECT
|
||||
'GROUP', 1, NULL,
|
||||
jsonb_build_object('section_code', sec_code, 'name', prog_name, 'program_id', prog_id, 'expense_item_id', sec_id),
|
||||
tp_plan, tp_seq_dfip, tp_approved, -- plan, seq_dfip, approved
|
||||
tp_contract_summary, tp_allocation, tp_reserve, -- contract_summary, allocation, reserve
|
||||
tp_collegial, tp_ckk, -- collegial, ckk
|
||||
tp_contract, tp_booking, -- contract, booking
|
||||
tp_q1, tp_q2, tp_q3, tp_q4, tp_totals, -- q1-q4, totals
|
||||
ARRAY[sec_id, prog_id, 0, 0]::INT[]
|
||||
FROM all_programs ap
|
||||
JOIN tp_agg ON tp_agg.tp_id = 1
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM v3.budget_line bl
|
||||
WHERE bl.budget_form_id = p_form_id
|
||||
AND (
|
||||
bl.project_id = ap.prog_id -- прямая привязка к программе
|
||||
OR bl.project_id IN (SELECT id FROM v3.form4_project pr -- через проект
|
||||
WHERE pr.parent_id = ap.prog_id AND pr.level = 'project')
|
||||
)
|
||||
)
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- ITEM — из агрегатов (проекты с бюджетными строками)
|
||||
SELECT
|
||||
'ITEM', 2, NULL,
|
||||
jsonb_build_object('section_code', sec_code, 'name', prj_name,
|
||||
'program_id', prog_id, 'project_id', prj_id, 'expense_item_id', sec_id),
|
||||
plan_data, seq_dfip_data, approved_data,
|
||||
contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data,
|
||||
contract_data, booking_data,
|
||||
q1_data, q2_data, q3_data, q4_data, totals_data,
|
||||
ARRAY[sec_id, prog_id, prj_id, 0]::INT[]
|
||||
FROM prj_agg
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- ITEM — пустые проекты (нет budget_line)
|
||||
SELECT
|
||||
'ITEM', 2, NULL,
|
||||
jsonb_build_object('section_code', sec_code, 'name', prj_name,
|
||||
'program_id', prog_id, 'project_id', prj_id, 'expense_item_id', sec_id),
|
||||
tp_plan, tp_seq_dfip, tp_approved, -- plan, seq_dfip, approved
|
||||
tp_contract_summary, tp_allocation, tp_reserve, -- contract_summary, allocation, reserve
|
||||
tp_collegial, tp_ckk, -- collegial, ckk
|
||||
tp_contract, tp_booking, -- contract, booking
|
||||
tp_q1, tp_q2, tp_q3, tp_q4, tp_totals, -- q1-q4, totals
|
||||
ARRAY[sec_id, prog_id, prj_id, 0]::INT[]
|
||||
FROM all_projects apr
|
||||
JOIN tp_agg ON tp_agg.tp_id = 1
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM v3.budget_line bl
|
||||
WHERE bl.budget_form_id = p_form_id
|
||||
AND bl.project_id = apr.prj_id
|
||||
)
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- INPUT — все строки бюджета
|
||||
SELECT
|
||||
'INPUT', 3, line_id,
|
||||
header || jsonb_build_object(
|
||||
'project_id', prj_id, 'project_name', prj_name,
|
||||
'program_id', prog_id, 'program_name', prog_name
|
||||
),
|
||||
plan_data, seq_dfip_data, approved_data,
|
||||
contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data,
|
||||
contract_data, booking_data,
|
||||
q1_data, q2_data, q3_data, q4_data, totals_data,
|
||||
ARRAY[sec_id, COALESCE(prog_id, 999999), COALESCE(CASE WHEN prj_id is null and prog_id is not null THEN 0 ELSE prj_id END, 999999), line_id]::INT[]
|
||||
FROM inp
|
||||
)
|
||||
|
||||
SELECT
|
||||
u.row_type, u.depth,
|
||||
ROW_NUMBER() OVER (ORDER BY u.sort_path) AS sort_order,
|
||||
jsonb_build_object(
|
||||
'sort_path', u.sort_path,
|
||||
'line_id', u.line_id,
|
||||
'header', u.header,
|
||||
'plan', u.plan_data,
|
||||
'seq_dfip', u.seq_dfip_data,
|
||||
'approved', u.approved_data,
|
||||
'contract_summary', u.contract_summary_data,
|
||||
'allocation', u.allocation_data,
|
||||
'reserve', u.reserve_data,
|
||||
'collegial', u.collegial_data,
|
||||
'ckk', u.ckk_data,
|
||||
'contract', u.contract_data,
|
||||
'booking', u.booking_data,
|
||||
'q1', u.q1_data,
|
||||
'q2', u.q2_data,
|
||||
'q3', u.q3_data,
|
||||
'q4', u.q4_data,
|
||||
'totals', u.totals_data
|
||||
) AS data
|
||||
FROM unioned u
|
||||
ORDER BY u.sort_path;
|
||||
$function$
|
||||
;
|
||||
Loading…
x
Reference in New Issue
Block a user