-- ════════════════════════════════════════════════════════════════════════════
-- МИГРАЦИЯ: FORM_2 — вывод недостающих блоков в DTO листов КВ_П / Операц
-- ════════════════════════════════════════════════════════════════════════════
-- Функции чтения FORM_2 (AHR/CAP/OPER) раньше НЕ отдавали ряд блоков, хотя
-- данные лежат в БД. Эта миграция переопределяет 3 функции так, что для
-- листов КВ_П/Операц дополнительно отдаются:
--   header.internal_order            (budget_line.internal_order)
--   contract_summary.*               (v3.contract_summary)
--   allocation.*                     (v3.allocation)
--   collegial.*                      (v3.collegial_approval)
--   ckk.*                            (v3.ckk: ceiling/rf_schedule/deadline/
--                                     proc_plan/proc_method/comment)
--   contract.{addenda,q1-q4,rf_schedule,exchange_rate,amount_foreign}
--                                     (v3.contract_detail — доп. поля)
--   q*.adj_rf                        (budget_line_quarter.adj_rf)
--   q*.pay_ho / q*.pay_rf            (budget_line_quarter.payment_amount_ho/rf)
-- Лист АХР формы 2 остаётся с прежней (более простой) раскладкой — новые блоки
-- для него срезаются в v3.v_form2_view (в шаблоне АХР этих колонок нет).
--
-- ТРЕБОВАНИЯ К ЦЕЛЕВОЙ БД: схема v3 на актуальной версии (наличие таблиц
--   v3.contract_summary / v3.allocation / v3.collegial_approval / v3.ckk /
--   v3.contract_detail и колонок budget_line.internal_order,
--   budget_line_quarter.adj_rf / payment_amount_ho / payment_amount_rf).
--
-- ИДЕМПОТЕНТНО: каждая функция пересоздаётся через DROP ... IF EXISTS + CREATE.
-- Порядок важен: sections → jsonb (зовёт sections) → view (зовёт jsonb).
-- ════════════════════════════════════════════════════════════════════════════


-- ═══ 1/3 ═══ v3.v_form2_sheet_sections ═══════════════════════════════════
-- ════════════════════════════════════════════════════════════════════════════
-- v3.v_form2_sheet_sections — основное отображение листов АХР / КВ_П / Операц
-- ════════════════════════════════════════════════════════════════════════════
-- Возвращает иерархию (ROOT/GROUP/ITEM/SUB_ITEM) + INPUT строки в tree-order.
-- Колоночная раскладка повторяет шаблон Формы 2:
--   header, plan, seq_dfip, seq_ssp_go, approved, contract, booking (2026+2027),
--   q1, q2, q3, q4, totals.
--
-- p_sections — массив включаемых блоков (NULL = все).
-- ════════════════════════════════════════════════════════════════════════════

DROP FUNCTION IF EXISTS v3.v_form2_sheet_sections(INT, VARCHAR, TEXT[]);

CREATE FUNCTION v3.v_form2_sheet_sections(
    p_form_id   INT,
    p_sheet     VARCHAR,           -- 'AHR' / 'CAP' / 'OPER'
    p_sections  TEXT[] DEFAULT NULL
)
RETURNS TABLE (
    row_type             VARCHAR,
    depth                INT,
    line_id              INT,
    -- header
    col_section_code     VARCHAR,
    col_item_id          VARCHAR,
    col_num_group_id     VARCHAR,
    col_name             VARCHAR,
    col_vsp_id           INT,
    col_vsp_address      VARCHAR,
    -- plan
    col_plan_q1          NUMERIC,
    col_plan_q2          NUMERIC,
    col_plan_q3          NUMERIC,
    col_plan_q4          NUMERIC,
    col_plan_year        NUMERIC,
    col_plan_comment     VARCHAR,
    -- seq_dfip
    col_seq_dfip_q1      NUMERIC,
    col_seq_dfip_q2      NUMERIC,
    col_seq_dfip_q3      NUMERIC,
    col_seq_dfip_q4      NUMERIC,
    col_seq_dfip_year    NUMERIC,
    col_seq_dfip_just    VARCHAR,
    -- seq_ssp_go
    col_seq_ssp_q1       NUMERIC,
    col_seq_ssp_q2       NUMERIC,
    col_seq_ssp_q3       NUMERIC,
    col_seq_ssp_q4       NUMERIC,
    col_seq_ssp_year     NUMERIC,
    col_seq_ssp_just     VARCHAR,
    -- approved (= plan + seq_dfip + seq_ssp + reserve)
    col_appr_q1          NUMERIC,
    col_appr_q2          NUMERIC,
    col_appr_q3          NUMERIC,
    col_appr_q4          NUMERIC,
    col_appr_year        NUMERIC,
    -- contract
    col_cd_counterparty  VARCHAR,
    col_cd_reference     VARCHAR,
    col_cd_date          DATE,
    col_cd_subject       VARCHAR,
    col_cd_currency      VARCHAR,
    col_cd_ceiling       NUMERIC,
    col_cd_vat_rate      VARCHAR,
    col_cd_deadline      VARCHAR,
    col_cd_scheme        VARCHAR,
    col_cd_act           VARCHAR,
    col_cd_comment       VARCHAR,
    -- booking 2026 (текущий год)
    col_book_q1          NUMERIC,
    col_book_q2          NUMERIC,
    col_book_q3          NUMERIC,
    col_book_q4          NUMERIC,
    -- booking 2027 (следующий год — НОВОЕ в Форме 2)
    col_book_next_q1     NUMERIC,
    col_book_next_q2     NUMERIC,
    col_book_next_q3     NUMERIC,
    col_book_next_q4     NUMERIC,
    -- Q1 (полный набор полей с pay_act, booking, остатки, transfer reasons)
    col_q1_adj_current   NUMERIC,
    col_q1_adj_ssp       NUMERIC,
    col_q1_adj_reserve   NUMERIC,
    col_q1_adj_comment   VARCHAR,
    col_q1_corrected_plan NUMERIC,
    col_q1_pay_date      DATE,
    col_q1_pay_amount    NUMERIC,
    col_q1_pay_comment   VARCHAR,
    col_q1_pay_act       VARCHAR,
    col_q1_booking       NUMERIC,
    col_q1_actual_m1     NUMERIC,
    col_q1_actual_m2     NUMERIC,
    col_q1_actual_m3     NUMERIC,
    col_q1_actual_quarter NUMERIC,
    col_q1_residual_after_booking NUMERIC,
    col_q1_residual_after_actual  NUMERIC,
    col_q1_transfer_q2   NUMERIC,
    col_q1_transfer_q2_delay_acts        NUMERIC,
    col_q1_transfer_q2_delay_procurement NUMERIC,
    col_q1_transfer_q2_economy_rf        NUMERIC,
    col_q1_transfer_next_comment VARCHAR,
    col_q1_transfer_q3   NUMERIC,
    col_q1_transfer_q4   NUMERIC,
    col_q1_transfer_far_comment VARCHAR,
    col_q1_transfer_econ NUMERIC,
    col_q1_total         NUMERIC,
    -- Q2 (полный набор)
    col_q2_target_change   NUMERIC,
    col_q2_base_correction NUMERIC,
    col_q2_base_correction_comment VARCHAR,
    col_q2_revision_inc    NUMERIC,
    col_q2_revision_seq    NUMERIC,
    col_q2_revision_comment VARCHAR,
    col_q2_new_plan        NUMERIC,
    col_q2_adj_current     NUMERIC,
    col_q2_adj_ssp         NUMERIC,
    col_q2_adj_reserve     NUMERIC,
    col_q2_adj_comment     VARCHAR,
    col_q2_corrected_plan  NUMERIC,
    col_q2_pay_date        DATE,
    col_q2_pay_amount      NUMERIC,
    col_q2_pay_comment     VARCHAR,
    col_q2_pay_act         VARCHAR,
    col_q2_booking         NUMERIC,
    col_q2_actual_m1       NUMERIC,
    col_q2_actual_m2       NUMERIC,
    col_q2_actual_m3       NUMERIC,
    col_q2_actual_quarter  NUMERIC,
    col_q2_residual_after_booking NUMERIC,
    col_q2_residual_after_actual  NUMERIC,
    col_q2_transfer_q3     NUMERIC,
    col_q2_transfer_q3_delay_acts        NUMERIC,
    col_q2_transfer_q3_delay_procurement NUMERIC,
    col_q2_transfer_q3_economy_rf        NUMERIC,
    col_q2_transfer_next_comment VARCHAR,
    col_q2_transfer_q4     NUMERIC,
    col_q2_transfer_far_comment VARCHAR,
    col_q2_transfer_econ   NUMERIC,
    col_q2_total           NUMERIC,
    -- Q3
    col_q3_target_change   NUMERIC,
    col_q3_base_correction NUMERIC,
    col_q3_base_correction_comment VARCHAR,
    col_q3_revision_inc    NUMERIC,
    col_q3_revision_seq    NUMERIC,
    col_q3_revision_comment VARCHAR,
    col_q3_new_plan        NUMERIC,
    col_q3_adj_current     NUMERIC,
    col_q3_adj_ssp         NUMERIC,
    col_q3_adj_reserve     NUMERIC,
    col_q3_adj_comment     VARCHAR,
    col_q3_corrected_plan  NUMERIC,
    col_q3_pay_date        DATE,
    col_q3_pay_amount      NUMERIC,
    col_q3_pay_comment     VARCHAR,
    col_q3_pay_act         VARCHAR,
    col_q3_booking         NUMERIC,
    col_q3_actual_m1       NUMERIC,
    col_q3_actual_m2       NUMERIC,
    col_q3_actual_m3       NUMERIC,
    col_q3_actual_quarter  NUMERIC,
    col_q3_residual_after_booking NUMERIC,
    col_q3_residual_after_actual  NUMERIC,
    col_q3_transfer_q4     NUMERIC,
    col_q3_transfer_q4_delay_acts        NUMERIC,
    col_q3_transfer_q4_delay_procurement NUMERIC,
    col_q3_transfer_q4_economy_rf        NUMERIC,
    col_q3_transfer_next_comment VARCHAR,
    col_q3_transfer_econ   NUMERIC,
    col_q3_total           NUMERIC,
    -- Q4 (+ СПОД, без переноса в next quarter)
    col_q4_target_change   NUMERIC,
    col_q4_base_correction NUMERIC,
    col_q4_base_correction_comment VARCHAR,
    col_q4_revision_inc    NUMERIC,
    col_q4_revision_seq    NUMERIC,
    col_q4_revision_comment VARCHAR,
    col_q4_new_plan        NUMERIC,
    col_q4_adj_current     NUMERIC,
    col_q4_adj_ssp         NUMERIC,
    col_q4_adj_reserve     NUMERIC,
    col_q4_adj_comment     VARCHAR,
    col_q4_corrected_plan  NUMERIC,
    col_q4_pay_date        DATE,
    col_q4_pay_amount      NUMERIC,
    col_q4_pay_comment     VARCHAR,
    col_q4_pay_act         VARCHAR,
    col_q4_booking         NUMERIC,
    col_q4_actual_m1       NUMERIC,
    col_q4_actual_m2       NUMERIC,
    col_q4_actual_m3       NUMERIC,
    col_q4_actual_spod     NUMERIC,
    col_q4_actual_quarter  NUMERIC,
    col_q4_residual_after_booking NUMERIC,
    col_q4_residual_after_actual  NUMERIC,
    col_q4_transfer_econ   NUMERIC,
    col_q4_total           NUMERIC,
    -- totals
    col_fact_year          NUMERIC,
    col_pay_year           NUMERIC,
    col_economy_year       NUMERIC,

    -- ─── НОВОЕ: блоки, которых раньше не было в DTO (данные лежали в БД) ───
    col_internal_order     VARCHAR,
    -- contract_summary (план-блок «Договоры ССП»)
    col_cs_total           NUMERIC,
    col_cs_counterparty    VARCHAR,
    col_cs_deadline        VARCHAR,
    col_cs_comment         VARCHAR,
    col_cs_future_y1       NUMERIC,
    col_cs_future_y2       NUMERIC,
    col_cs_other_ssp       NUMERIC,
    -- allocation
    col_al_contract_ref    VARCHAR,
    col_al_purpose         VARCHAR,
    col_al_order           VARCHAR,
    col_al_property        VARCHAR,
    -- collegial
    col_ca_approved        NUMERIC,
    col_ca_protocol        VARCHAR,
    col_ca_note            VARCHAR,
    -- ckk (полный блок; поквартальные expenses остаются также в booking.y2026)
    col_ckk_ceiling        NUMERIC,
    col_ckk_rf_sched       VARCHAR,
    col_ckk_deadline       VARCHAR,
    col_ckk_proc_plan      VARCHAR,
    col_ckk_proc_meth      VARCHAR,
    col_ckk_comment        VARCHAR,
    -- contract_detail: доп. поля (сверх блока contract)
    col_cd_addenda         VARCHAR,
    col_cd_exp_q1          NUMERIC,
    col_cd_exp_q2          NUMERIC,
    col_cd_exp_q3          NUMERIC,
    col_cd_exp_q4          NUMERIC,
    col_cd_rf_sched        VARCHAR,
    col_cd_exchange_rate   NUMERIC,
    col_cd_amount_foreign  NUMERIC,
    -- adj_rf / платёж ГО / платёж РФ по кварталам
    col_q1_adj_rf          NUMERIC,
    col_q1_pay_ho          NUMERIC,
    col_q1_pay_rf          NUMERIC,
    col_q2_adj_rf          NUMERIC,
    col_q2_pay_ho          NUMERIC,
    col_q2_pay_rf          NUMERIC,
    col_q3_adj_rf          NUMERIC,
    col_q3_pay_ho          NUMERIC,
    col_q3_pay_rf          NUMERIC,
    col_q4_adj_rf          NUMERIC,
    col_q4_pay_ho          NUMERIC,
    col_q4_pay_rf          NUMERIC,

    _sort_path           INT[]
)
AS $function$
#variable_conflict use_column
DECLARE
  s_plan BOOL; s_seq_d BOOL; s_seq_s BOOL; s_appr BOOL;
  s_cd BOOL; s_book BOOL;
  s_cs BOOL; s_al BOOL; s_ca BOOL; s_ckk BOOL;  -- НОВЫЕ блоки
  s_q1 BOOL; s_q2 BOOL; s_q3 BOOL; s_q4 BOOL; s_tot BOOL;
  s_need_ap BOOL;  -- plan/seq/reserve нужны для approved + cp1..cp4
BEGIN
  s_plan  := p_sections IS NULL OR 'plan'             = ANY(p_sections);
  s_seq_d := p_sections IS NULL OR 'seq_dfip'         = ANY(p_sections);
  s_seq_s := p_sections IS NULL OR 'seq_ssp'          = ANY(p_sections);
  s_appr  := p_sections IS NULL OR 'approved'         = ANY(p_sections);
  s_cd    := p_sections IS NULL OR 'contract'         = ANY(p_sections);
  s_book  := p_sections IS NULL OR 'booking'          = ANY(p_sections);
  s_cs    := p_sections IS NULL OR 'contract_summary' = ANY(p_sections);
  s_al    := p_sections IS NULL OR 'allocation'       = ANY(p_sections);
  s_ca    := p_sections IS NULL OR 'collegial'        = ANY(p_sections);
  s_ckk   := p_sections IS NULL OR 'ckk'              = ANY(p_sections);
  s_q1    := p_sections IS NULL OR 'q1'               = ANY(p_sections);
  s_q2    := p_sections IS NULL OR 'q2'               = ANY(p_sections);
  s_q3    := p_sections IS NULL OR 'q3'               = ANY(p_sections);
  s_q4    := p_sections IS NULL OR 'q4'               = ANY(p_sections);
  s_tot   := p_sections IS NULL OR 'totals'           = ANY(p_sections);
  s_need_ap := s_plan OR s_appr OR s_q1 OR s_q2 OR s_q3 OR s_q4;

  RETURN QUERY
  WITH
    tw AS (
      SELECT t.id, t.section_code, t.item_id, t.num_group_id, t.name,
             t.depth, t.path, t.parent_id, t.parent_item_id, t.desc_ids
      FROM v3.mv_expense_item_tree t
      JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id
      JOIN v3.budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code
      WHERE t.sheet = p_sheet
    ),
    page AS (
      SELECT bl.id AS lid, bl.expense_item_id AS eid, bl.name AS bname, bl.vsp_id,
             bl.internal_order AS io
      FROM v3.budget_line bl
      JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
      WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet
    ),
    -- Conditional joins (gated by section flags)
    jp   AS (SELECT p.* FROM v3.plan p JOIN page pg ON p.line_id = pg.lid WHERE s_need_ap),
    jsd  AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='DFIP'),
    jsg  AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='SSP_GO'),
    jr   AS (SELECT r.* FROM v3.reserve r JOIN page pg ON r.line_id = pg.lid WHERE s_need_ap),
    jcd  AS (SELECT c.* FROM v3.contract_detail c JOIN page pg ON c.line_id = pg.lid WHERE s_cd),
    jck  AS (SELECT c.* FROM v3.ckk c JOIN page pg ON c.line_id = pg.lid WHERE s_book OR s_ckk),
    jcs  AS (SELECT c.* FROM v3.contract_summary c JOIN page pg ON c.line_id = pg.lid WHERE s_cs),
    jal  AS (SELECT a.* FROM v3.allocation a JOIN page pg ON a.line_id = pg.lid WHERE s_al),
    jca  AS (SELECT c.* FROM v3.collegial_approval c JOIN page pg ON c.line_id = pg.lid WHERE s_ca),
    jq1  AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q1 AND q.quarter=1),
    jq2  AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q2 AND q.quarter=2),
    jq3  AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q3 AND q.quarter=3),
    jq4  AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE (s_q4 OR s_tot) AND q.quarter=4),

    -- INPUT строки: enriched данные
    input_base AS (
      SELECT
        pg.lid, pg.eid, pg.bname, pg.vsp_id,
        v.address AS vsp_addr,
        t.parent_item_id AS sc, t.item_id AS ic, t.num_group_id AS ng, t.name AS ename, t.path AS tree_path,
        -- plan
        COALESCE(p.plan_q1,0) AS pq1, COALESCE(p.plan_q2,0) AS pq2,
        COALESCE(p.plan_q3,0) AS pq3, COALESCE(p.plan_q4,0) AS pq4,
        p.comment AS pcmt,
        -- seq dfip / ssp_go
        COALESCE(sd.adj_q1,0) AS dq1, COALESCE(sd.adj_q2,0) AS dq2,
        COALESCE(sd.adj_q3,0) AS dq3, COALESCE(sd.adj_q4,0) AS dq4,
        sd.justification AS djust,
        COALESCE(sg.adj_q1,0) AS gq1, COALESCE(sg.adj_q2,0) AS gq2,
        COALESCE(sg.adj_q3,0) AS gq3, COALESCE(sg.adj_q4,0) AS gq4,
        sg.justification AS gjust,
        -- reserve
        COALESCE(r.amount_q1,0) AS rq1, COALESCE(r.amount_q2,0) AS rq2,
        COALESCE(r.amount_q3,0) AS rq3, COALESCE(r.amount_q4,0) AS rq4,
        -- contract
        cd.counterparty AS cd_cp, cd.reference AS cd_ref, cd.contract_date AS cd_dt,
        cd.subject AS cd_subj, cd.currency AS cd_cur, cd.ceiling_amount AS cd_ceil,
        cd.vat_rate AS cd_vat, cd.deadline AS cd_dl, cd.payment_scheme AS cd_sch,
        cd.act AS cd_act, cd.comment AS cd_cmt,
        -- ckk (booking)
        ck.expenses_q1 AS bk1, ck.expenses_q2 AS bk2, ck.expenses_q3 AS bk3, ck.expenses_q4 AS bk4,
        ck.expenses_next_year_q1 AS bn1, ck.expenses_next_year_q2 AS bn2,
        ck.expenses_next_year_q3 AS bn3, ck.expenses_next_year_q4 AS bn4,
        -- НОВОЕ: internal_order
        pg.io AS io,
        -- НОВОЕ: contract_summary
        cs.total_amount AS cs_total, cs.counterparty AS cs_cp, cs.deadline AS cs_dl,
        cs.comment AS cs_cmt, cs.future_payments_y1 AS cs_fy1, cs.future_payments_y2 AS cs_fy2,
        cs.other_ssp_amount AS cs_oth,
        -- НОВОЕ: allocation
        al.contract_ref AS al_cref, al.allocation_purpose AS al_purp,
        al.internal_order AS al_io, al.property_object AS al_prop,
        -- НОВОЕ: collegial
        ca.approved_amount AS ca_amt, ca.protocol_reference AS ca_proto, ca.note AS ca_note,
        -- НОВОЕ: ckk (полный, кроме expenses)
        ck.ceiling_amount AS ckk_ceil, ck.rf_schedule AS ckk_rf, ck.delivery_deadline AS ckk_dl,
        ck.procurement_plan AS ckk_pplan, ck.procurement_method AS ckk_pmeth, ck.comment AS ckk_cmt,
        -- НОВОЕ: contract_detail доп. поля
        cd.addenda AS cd_add, cd.expenses_q1 AS cd_e1, cd.expenses_q2 AS cd_e2,
        cd.expenses_q3 AS cd_e3, cd.expenses_q4 AS cd_e4, cd.rf_schedule AS cd_rf,
        cd.exchange_rate AS cd_rate, cd.amount_foreign AS cd_fx,
        -- НОВОЕ: adj_rf / платёж ГО / платёж РФ по кварталам
        q1.adj_rf AS q1_arf, q1.payment_amount_ho AS q1_pho, q1.payment_amount_rf AS q1_prf,
        q2.adj_rf AS q2_arf, q2.payment_amount_ho AS q2_pho, q2.payment_amount_rf AS q2_prf,
        q3.adj_rf AS q3_arf, q3.payment_amount_ho AS q3_pho, q3.payment_amount_rf AS q3_prf,
        q4.adj_rf AS q4_arf, q4.payment_amount_ho AS q4_pho, q4.payment_amount_rf AS q4_prf,
        -- Q1
        q1.adj_current AS q1_ac, q1.adj_ssp AS q1_as, q1.adj_reserve AS q1_arv, q1.adj_comment AS q1_acmt,
        q1.payment_date AS q1_pd, q1.payment_amount AS q1_pa,
        q1.payment_comment AS q1_pcmt,
        q1.payment_act AS q1_pact, q1.booking_amount AS q1_book,
        q1.actual_m1 AS q1_m1, q1.actual_m2 AS q1_m2, q1.actual_m3 AS q1_m3,
        q1.transfer_to_q2 AS q1_tq2, q1.transfer_to_q3 AS q1_tq3, q1.transfer_to_q4 AS q1_tq4,
        q1.transfer_to_economy AS q1_te,
        q1.transfer_delay_acts AS q1_tda, q1.transfer_delay_procurement AS q1_tdp,
        q1.transfer_economy_rf AS q1_terf,
        q1.transfer_next_comment AS q1_tnc, q1.transfer_far_comment AS q1_tfc,
        -- Q2
        q2.target_change AS q2_tc, q2.base_plan_correction AS q2_bc, q2.base_plan_correction_comment AS q2_bcc,
        q2.plan_revision_increase AS q2_rinc, q2.plan_revision_sequester AS q2_rseq,
        q2.plan_revision_comment AS q2_rcmt,
        q2.adj_current AS q2_ac, q2.adj_ssp AS q2_as, q2.adj_reserve AS q2_arv,
        q2.adj_comment AS q2_acmt,
        q2.payment_date AS q2_pd, q2.payment_amount AS q2_pa,
        q2.payment_comment AS q2_pcmt, q2.payment_act AS q2_pact,
        q2.booking_amount AS q2_book,
        q2.actual_m1 AS q2_m1, q2.actual_m2 AS q2_m2, q2.actual_m3 AS q2_m3,
        q2.transfer_to_q3 AS q2_tq3, q2.transfer_to_q4 AS q2_tq4, q2.transfer_to_economy AS q2_te,
        q2.transfer_delay_acts AS q2_tda, q2.transfer_delay_procurement AS q2_tdp,
        q2.transfer_economy_rf AS q2_terf,
        q2.transfer_next_comment AS q2_tnc, q2.transfer_far_comment AS q2_tfc,
        -- Q3
        q3.target_change AS q3_tc, q3.base_plan_correction AS q3_bc,
        q3.base_plan_correction_comment AS q3_bcc,
        q3.plan_revision_increase AS q3_rinc, q3.plan_revision_sequester AS q3_rseq,
        q3.plan_revision_comment AS q3_rcmt,
        q3.adj_current AS q3_ac, q3.adj_ssp AS q3_as, q3.adj_reserve AS q3_arv,
        q3.adj_comment AS q3_acmt,
        q3.payment_date AS q3_pd, q3.payment_amount AS q3_pa,
        q3.payment_comment AS q3_pcmt, q3.payment_act AS q3_pact,
        q3.booking_amount AS q3_book,
        q3.actual_m1 AS q3_m1, q3.actual_m2 AS q3_m2, q3.actual_m3 AS q3_m3,
        q3.transfer_to_q4 AS q3_tq4, q3.transfer_to_economy AS q3_te,
        q3.transfer_delay_acts AS q3_tda, q3.transfer_delay_procurement AS q3_tdp,
        q3.transfer_economy_rf AS q3_terf, q3.transfer_next_comment AS q3_tnc,
        -- Q4
        q4.target_change AS q4_tc, q4.base_plan_correction AS q4_bc,
        q4.base_plan_correction_comment AS q4_bcc,
        q4.plan_revision_increase AS q4_rinc, q4.plan_revision_sequester AS q4_rseq,
        q4.plan_revision_comment AS q4_rcmt,
        q4.adj_current AS q4_ac, q4.adj_ssp AS q4_as, q4.adj_reserve AS q4_arv,
        q4.adj_comment AS q4_acmt,
        q4.payment_date AS q4_pd, q4.payment_amount AS q4_pa,
        q4.payment_comment AS q4_pcmt, q4.payment_act AS q4_pact,
        q4.booking_amount AS q4_book,
        q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, q4.actual_spod AS q4_spod,
        q4.transfer_to_economy AS q4_te,
        -- approved per quarter
        COALESCE(p.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(sg.adj_q1,0)+COALESCE(r.amount_q1,0) AS ap1,
        COALESCE(p.plan_q2,0)+COALESCE(sd.adj_q2,0)+COALESCE(sg.adj_q2,0)+COALESCE(r.amount_q2,0) AS ap2,
        COALESCE(p.plan_q3,0)+COALESCE(sd.adj_q3,0)+COALESCE(sg.adj_q3,0)+COALESCE(r.amount_q3,0) AS ap3,
        COALESCE(p.plan_q4,0)+COALESCE(sd.adj_q4,0)+COALESCE(sg.adj_q4,0)+COALESCE(r.amount_q4,0) AS ap4
      FROM page pg
      JOIN tw t ON t.id = pg.eid
      LEFT JOIN v3.vsp v ON v.id = pg.vsp_id
      LEFT JOIN jp p   ON p.line_id  = pg.lid
      LEFT JOIN jsd sd ON sd.line_id = pg.lid
      LEFT JOIN jsg sg ON sg.line_id = pg.lid
      LEFT JOIN jr r   ON r.line_id  = pg.lid
      LEFT JOIN jcd cd ON cd.line_id = pg.lid
      LEFT JOIN jck ck ON ck.line_id = pg.lid
      LEFT JOIN jcs cs ON cs.line_id = pg.lid
      LEFT JOIN jal al ON al.line_id = pg.lid
      LEFT JOIN jca ca ON ca.line_id = pg.lid
      LEFT JOIN jq1 q1 ON q1.line_id = pg.lid
      LEFT JOIN jq2 q2 ON q2.line_id = pg.lid
      LEFT JOIN jq3 q3 ON q3.line_id = pg.lid
      LEFT JOIN jq4 q4 ON q4.line_id = pg.lid
    ),
    -- Computed: corrected/new plans
    enriched AS (
      SELECT b.*,
        -- Q1 corrected_plan = approved + adj_*
        b.ap1 + COALESCE(b.q1_ac,0)+COALESCE(b.q1_as,0)+COALESCE(b.q1_arv,0) AS cp1,
        COALESCE(b.q1_m1,0)+COALESCE(b.q1_m2,0)+COALESCE(b.q1_m3,0) AS aq1,
        -- Q2 new_plan = approved + transfer_q1 + revisions(inc/seq) + base_correction
        b.ap2 + COALESCE(b.q1_tq2,0) + COALESCE(b.q2_rinc,0)+COALESCE(b.q2_rseq,0) + COALESCE(b.q2_bc,0) AS np2,
        COALESCE(b.q2_m1,0)+COALESCE(b.q2_m2,0)+COALESCE(b.q2_m3,0) AS aq2,
        b.ap3 + COALESCE(b.q1_tq3,0)+COALESCE(b.q2_tq3,0) + COALESCE(b.q3_rinc,0)+COALESCE(b.q3_rseq,0) + COALESCE(b.q3_bc,0) AS np3,
        COALESCE(b.q3_m1,0)+COALESCE(b.q3_m2,0)+COALESCE(b.q3_m3,0) AS aq3,
        b.ap4 + COALESCE(b.q1_tq4,0)+COALESCE(b.q2_tq4,0)+COALESCE(b.q3_tq4,0) + COALESCE(b.q4_rinc,0)+COALESCE(b.q4_rseq,0) + COALESCE(b.q4_bc,0) AS np4,
        COALESCE(b.q4_m1,0)+COALESCE(b.q4_m2,0)+COALESCE(b.q4_m3,0)+COALESCE(b.q4_spod,0) AS aq4
      FROM input_base b
    ),
    final_input AS (
      SELECT e.*,
        e.np2 + COALESCE(e.q2_ac,0)+COALESCE(e.q2_as,0)+COALESCE(e.q2_arv,0) AS cp2,
        e.np3 + COALESCE(e.q3_ac,0)+COALESCE(e.q3_as,0)+COALESCE(e.q3_arv,0) AS cp3,
        e.np4 + COALESCE(e.q4_ac,0)+COALESCE(e.q4_as,0)+COALESCE(e.q4_arv,0) AS cp4
      FROM enriched e
    ),

    -- Aggregates per expense_item (для иерархии)
    agg AS (
      SELECT bl.expense_item_id AS eid,
        SUM(p.plan_q1) AS sp1, SUM(p.plan_q2) AS sp2, SUM(p.plan_q3) AS sp3, SUM(p.plan_q4) AS sp4,
        SUM(sd.adj_q1) AS sd1, SUM(sd.adj_q2) AS sd2, SUM(sd.adj_q3) AS sd3, SUM(sd.adj_q4) AS sd4,
        SUM(sg.adj_q1) AS sg1, SUM(sg.adj_q2) AS sg2, SUM(sg.adj_q3) AS sg3, SUM(sg.adj_q4) AS sg4,
        SUM(r.amount_q1) AS sr1, SUM(r.amount_q2) AS sr2, SUM(r.amount_q3) AS sr3, SUM(r.amount_q4) AS sr4,
        SUM(ck.expenses_q1) AS bk1, SUM(ck.expenses_q2) AS bk2, SUM(ck.expenses_q3) AS bk3, SUM(ck.expenses_q4) AS bk4,
        SUM(ck.expenses_next_year_q1) AS bn1, SUM(ck.expenses_next_year_q2) AS bn2,
        SUM(ck.expenses_next_year_q3) AS bn3, SUM(ck.expenses_next_year_q4) AS bn4
      FROM v3.budget_line bl
      JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
      LEFT JOIN v3.plan p ON p.line_id = bl.id AND s_need_ap
      LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor='DFIP' AND s_need_ap
      LEFT JOIN v3.sequestration sg ON sg.line_id = bl.id AND sg.actor='SSP_GO' AND s_need_ap
      LEFT JOIN v3.reserve r ON r.line_id = bl.id AND s_need_ap
      LEFT JOIN v3.ckk ck ON ck.line_id = bl.id AND s_book
      WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet
        AND (s_need_ap OR s_book)
      GROUP BY bl.expense_item_id
    ),
    -- Per-quarter aggregates from blq
    aq_q AS (
      SELECT bl.expense_item_id AS eid, q.quarter,
        SUM(q.adj_current) AS ac, SUM(q.adj_ssp) AS as_v, SUM(q.adj_reserve) AS arv,
        SUM(q.payment_amount) AS pa,
        SUM(q.booking_amount) AS bk,
        SUM(q.actual_m1) AS m1, SUM(q.actual_m2) AS m2, SUM(q.actual_m3) AS m3,
        SUM(q.actual_spod) AS spod,
        SUM(q.transfer_to_q2) AS tq2, SUM(q.transfer_to_q3) AS tq3, SUM(q.transfer_to_q4) AS tq4,
        SUM(q.transfer_to_economy) AS te,
        SUM(q.transfer_delay_acts) AS tda,
        SUM(q.transfer_delay_procurement) AS tdp,
        SUM(q.transfer_economy_rf) AS terf,
        -- Δ к approved для перехода к new_plan: revision_inc/seq + target_change + base_correction
        -- (Excel: DJ10 = SUM(AN, CY, DH, CF, DE:DG)). Должно совпадать с INPUT-формулой np.
        SUM(COALESCE(q.plan_revision_increase,0)
          + COALESCE(q.plan_revision_sequester,0)
          + COALESCE(q.target_change,0)
          + COALESCE(q.base_plan_correction,0)) AS rev
      FROM v3.budget_line bl
      JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
      JOIN v3.budget_line_quarter q ON q.line_id = bl.id
      WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet
      GROUP BY bl.expense_item_id, q.quarter
    ),
    -- Tree-rollup
    tw_agg AS (
      SELECT tw.id,
        SUM(a.sp1) AS sp1, SUM(a.sp2) AS sp2, SUM(a.sp3) AS sp3, SUM(a.sp4) AS sp4,
        SUM(a.sd1) AS sd1, SUM(a.sd2) AS sd2, SUM(a.sd3) AS sd3, SUM(a.sd4) AS sd4,
        SUM(a.sg1) AS sg1, SUM(a.sg2) AS sg2, SUM(a.sg3) AS sg3, SUM(a.sg4) AS sg4,
        SUM(a.sr1) AS sr1, SUM(a.sr2) AS sr2, SUM(a.sr3) AS sr3, SUM(a.sr4) AS sr4,
        SUM(a.bk1) AS bk1, SUM(a.bk2) AS bk2, SUM(a.bk3) AS bk3, SUM(a.bk4) AS bk4,
        SUM(a.bn1) AS bn1, SUM(a.bn2) AS bn2, SUM(a.bn3) AS bn3, SUM(a.bn4) AS bn4
      FROM tw LEFT JOIN agg a ON a.eid = ANY(tw.desc_ids)
      GROUP BY tw.id
    ),
    tw_aq1 AS (
      SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv,
        SUM(b.pa) AS pa, SUM(b.bk) AS bk,
        SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3,
        SUM(b.tq2) AS tq2, SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te,
        SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf
      FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=1 GROUP BY tw.id
    ),
    tw_aq2 AS (
      SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv,
        SUM(b.pa) AS pa, SUM(b.bk) AS bk,
        SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3,
        SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev,
        SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf
      FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=2 GROUP BY tw.id
    ),
    tw_aq3 AS (
      SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv,
        SUM(b.pa) AS pa, SUM(b.bk) AS bk,
        SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3,
        SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev,
        SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf
      FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=3 GROUP BY tw.id
    ),
    tw_aq4 AS (
      SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv,
        SUM(b.pa) AS pa, SUM(b.bk) AS bk,
        SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, SUM(b.spod) AS spod,
        SUM(b.te) AS te, SUM(b.rev) AS rev
      FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=4 GROUP BY tw.id
    )

  -- ═══ Часть A: INPUT строки ═════════════════════════════════════════════
  SELECT * FROM (
    SELECT
      'INPUT'::VARCHAR, 3, f.lid::INT,
      f.sc, f.ic, f.ng, COALESCE(f.bname, f.ename), f.vsp_id, f.vsp_addr,
      -- plan
      CASE WHEN s_plan THEN f.pq1 END, CASE WHEN s_plan THEN f.pq2 END,
      CASE WHEN s_plan THEN f.pq3 END, CASE WHEN s_plan THEN f.pq4 END,
      CASE WHEN s_plan THEN f.pq1+f.pq2+f.pq3+f.pq4 END, CASE WHEN s_plan THEN f.pcmt END,
      -- seq dfip
      CASE WHEN s_seq_d THEN f.dq1 END, CASE WHEN s_seq_d THEN f.dq2 END,
      CASE WHEN s_seq_d THEN f.dq3 END, CASE WHEN s_seq_d THEN f.dq4 END,
      CASE WHEN s_seq_d THEN f.dq1+f.dq2+f.dq3+f.dq4 END, CASE WHEN s_seq_d THEN f.djust END,
      -- seq ssp_go
      CASE WHEN s_seq_s THEN f.gq1 END, CASE WHEN s_seq_s THEN f.gq2 END,
      CASE WHEN s_seq_s THEN f.gq3 END, CASE WHEN s_seq_s THEN f.gq4 END,
      CASE WHEN s_seq_s THEN f.gq1+f.gq2+f.gq3+f.gq4 END, CASE WHEN s_seq_s THEN f.gjust END,
      -- approved
      CASE WHEN s_appr THEN f.ap1 END, CASE WHEN s_appr THEN f.ap2 END,
      CASE WHEN s_appr THEN f.ap3 END, CASE WHEN s_appr THEN f.ap4 END,
      CASE WHEN s_appr THEN f.ap1+f.ap2+f.ap3+f.ap4 END,
      -- contract
      CASE WHEN s_cd THEN f.cd_cp END, CASE WHEN s_cd THEN f.cd_ref END,
      CASE WHEN s_cd THEN f.cd_dt END, CASE WHEN s_cd THEN f.cd_subj END,
      CASE WHEN s_cd THEN f.cd_cur END, CASE WHEN s_cd THEN f.cd_ceil END,
      CASE WHEN s_cd THEN f.cd_vat END, CASE WHEN s_cd THEN f.cd_dl END,
      CASE WHEN s_cd THEN f.cd_sch END, CASE WHEN s_cd THEN f.cd_act END,
      CASE WHEN s_cd THEN f.cd_cmt END,
      -- booking 2026
      CASE WHEN s_book THEN f.bk1 END, CASE WHEN s_book THEN f.bk2 END,
      CASE WHEN s_book THEN f.bk3 END, CASE WHEN s_book THEN f.bk4 END,
      -- booking 2027
      CASE WHEN s_book THEN f.bn1 END, CASE WHEN s_book THEN f.bn2 END,
      CASE WHEN s_book THEN f.bn3 END, CASE WHEN s_book THEN f.bn4 END,
      -- Q1
      CASE WHEN s_q1 THEN f.q1_ac END, CASE WHEN s_q1 THEN f.q1_as END, CASE WHEN s_q1 THEN f.q1_arv END,
      CASE WHEN s_q1 THEN f.q1_acmt END, CASE WHEN s_q1 THEN f.cp1 END,
      CASE WHEN s_q1 THEN f.q1_pd END, CASE WHEN s_q1 THEN f.q1_pa END,
      CASE WHEN s_q1 THEN f.q1_pcmt END,
      CASE WHEN s_q1 THEN f.q1_pact END, CASE WHEN s_q1 THEN f.q1_book END,
      CASE WHEN s_q1 THEN f.q1_m1 END, CASE WHEN s_q1 THEN f.q1_m2 END, CASE WHEN s_q1 THEN f.q1_m3 END,
      CASE WHEN s_q1 THEN f.aq1 END,
      CASE WHEN s_q1 THEN f.cp1 - COALESCE(f.q1_book,0) END,    -- residual_after_booking
      CASE WHEN s_q1 THEN f.cp1 - f.aq1 END,                    -- residual_after_actual
      CASE WHEN s_q1 THEN f.q1_tq2 END,
      CASE WHEN s_q1 THEN f.q1_tda END, CASE WHEN s_q1 THEN f.q1_tdp END, CASE WHEN s_q1 THEN f.q1_terf END,
      CASE WHEN s_q1 THEN f.q1_tnc END,
      CASE WHEN s_q1 THEN f.q1_tq3 END, CASE WHEN s_q1 THEN f.q1_tq4 END,
      CASE WHEN s_q1 THEN f.q1_tfc END,
      CASE WHEN s_q1 THEN f.q1_te END,
      -- total = SUM transfer_q2_reasons + transfer_q3 + transfer_q4 + transfer_econ (Excel CC5)
      CASE WHEN s_q1 THEN
        COALESCE(f.q1_tda,0)+COALESCE(f.q1_tdp,0)+COALESCE(f.q1_terf,0)
        +COALESCE(f.q1_tq3,0)+COALESCE(f.q1_tq4,0)+COALESCE(f.q1_te,0)
      END,
      -- Q2
      CASE WHEN s_q2 THEN f.q2_tc END, CASE WHEN s_q2 THEN f.q2_bc END, CASE WHEN s_q2 THEN f.q2_bcc END,
      CASE WHEN s_q2 THEN f.q2_rinc END, CASE WHEN s_q2 THEN f.q2_rseq END,
      CASE WHEN s_q2 THEN f.q2_rcmt END,
      CASE WHEN s_q2 THEN f.np2 END,
      CASE WHEN s_q2 THEN f.q2_ac END, CASE WHEN s_q2 THEN f.q2_as END, CASE WHEN s_q2 THEN f.q2_arv END,
      CASE WHEN s_q2 THEN f.q2_acmt END,
      CASE WHEN s_q2 THEN f.cp2 END,
      CASE WHEN s_q2 THEN f.q2_pd END, CASE WHEN s_q2 THEN f.q2_pa END,
      CASE WHEN s_q2 THEN f.q2_pcmt END, CASE WHEN s_q2 THEN f.q2_pact END,
      CASE WHEN s_q2 THEN f.q2_book END,
      CASE WHEN s_q2 THEN f.q2_m1 END, CASE WHEN s_q2 THEN f.q2_m2 END, CASE WHEN s_q2 THEN f.q2_m3 END,
      CASE WHEN s_q2 THEN f.aq2 END,
      CASE WHEN s_q2 THEN f.cp2 - COALESCE(f.q2_book,0) END,
      CASE WHEN s_q2 THEN f.cp2 - f.aq2 END,
      CASE WHEN s_q2 THEN f.q2_tq3 END,
      CASE WHEN s_q2 THEN f.q2_tda END, CASE WHEN s_q2 THEN f.q2_tdp END, CASE WHEN s_q2 THEN f.q2_terf END,
      CASE WHEN s_q2 THEN f.q2_tnc END,
      CASE WHEN s_q2 THEN f.q2_tq4 END,
      CASE WHEN s_q2 THEN f.q2_tfc END,
      CASE WHEN s_q2 THEN f.q2_te END,
      -- total = SUM transfer_q3_reasons + transfer_q4 + transfer_econ (Excel DN5)
      CASE WHEN s_q2 THEN
        COALESCE(f.q2_tda,0)+COALESCE(f.q2_tdp,0)+COALESCE(f.q2_terf,0)
        +COALESCE(f.q2_tq4,0)+COALESCE(f.q2_te,0)
      END,
      -- Q3
      CASE WHEN s_q3 THEN f.q3_tc END, CASE WHEN s_q3 THEN f.q3_bc END,
      CASE WHEN s_q3 THEN f.q3_bcc END,
      CASE WHEN s_q3 THEN f.q3_rinc END, CASE WHEN s_q3 THEN f.q3_rseq END,
      CASE WHEN s_q3 THEN f.q3_rcmt END,
      CASE WHEN s_q3 THEN f.np3 END,
      CASE WHEN s_q3 THEN f.q3_ac END, CASE WHEN s_q3 THEN f.q3_as END, CASE WHEN s_q3 THEN f.q3_arv END,
      CASE WHEN s_q3 THEN f.q3_acmt END,
      CASE WHEN s_q3 THEN f.cp3 END,
      CASE WHEN s_q3 THEN f.q3_pd END, CASE WHEN s_q3 THEN f.q3_pa END,
      CASE WHEN s_q3 THEN f.q3_pcmt END, CASE WHEN s_q3 THEN f.q3_pact END,
      CASE WHEN s_q3 THEN f.q3_book END,
      CASE WHEN s_q3 THEN f.q3_m1 END, CASE WHEN s_q3 THEN f.q3_m2 END, CASE WHEN s_q3 THEN f.q3_m3 END,
      CASE WHEN s_q3 THEN f.aq3 END,
      CASE WHEN s_q3 THEN f.cp3 - COALESCE(f.q3_book,0) END,
      CASE WHEN s_q3 THEN f.cp3 - f.aq3 END,
      CASE WHEN s_q3 THEN f.q3_tq4 END,
      CASE WHEN s_q3 THEN f.q3_tda END, CASE WHEN s_q3 THEN f.q3_tdp END, CASE WHEN s_q3 THEN f.q3_terf END,
      CASE WHEN s_q3 THEN f.q3_tnc END,
      CASE WHEN s_q3 THEN f.q3_te END,
      -- total = SUM transfer_q4_reasons + transfer_econ (Excel EW5)
      CASE WHEN s_q3 THEN
        COALESCE(f.q3_tda,0)+COALESCE(f.q3_tdp,0)+COALESCE(f.q3_terf,0)
        +COALESCE(f.q3_te,0)
      END,
      -- Q4
      CASE WHEN s_q4 THEN f.q4_tc END, CASE WHEN s_q4 THEN f.q4_bc END,
      CASE WHEN s_q4 THEN f.q4_bcc END,
      CASE WHEN s_q4 THEN f.q4_rinc END, CASE WHEN s_q4 THEN f.q4_rseq END,
      CASE WHEN s_q4 THEN f.q4_rcmt END,
      CASE WHEN s_q4 THEN f.np4 END,
      CASE WHEN s_q4 THEN f.q4_ac END, CASE WHEN s_q4 THEN f.q4_as END, CASE WHEN s_q4 THEN f.q4_arv END,
      CASE WHEN s_q4 THEN f.q4_acmt END,
      CASE WHEN s_q4 THEN f.cp4 END,
      CASE WHEN s_q4 THEN f.q4_pd END, CASE WHEN s_q4 THEN f.q4_pa END,
      CASE WHEN s_q4 THEN f.q4_pcmt END, CASE WHEN s_q4 THEN f.q4_pact END,
      CASE WHEN s_q4 THEN f.q4_book END,
      CASE WHEN s_q4 THEN f.q4_m1 END, CASE WHEN s_q4 THEN f.q4_m2 END, CASE WHEN s_q4 THEN f.q4_m3 END,
      CASE WHEN s_q4 THEN f.q4_spod END,
      CASE WHEN s_q4 THEN f.aq4 END,
      CASE WHEN s_q4 THEN f.cp4 - COALESCE(f.q4_book,0) END,
      CASE WHEN s_q4 THEN f.cp4 - f.aq4 END,
      CASE WHEN s_q4 THEN f.q4_te END,
      -- q4.total = q4.transfer_econ (Q4 has no other outgoing transfers)
      CASE WHEN s_q4 THEN f.q4_te END,
      -- totals
      CASE WHEN s_tot THEN f.aq1+f.aq2+f.aq3+f.aq4 END,
      CASE WHEN s_tot THEN COALESCE(f.q1_pa,0)+COALESCE(f.q2_pa,0)+COALESCE(f.q3_pa,0)+COALESCE(f.q4_pa,0) END,
      -- economy_year = q4.corrected_plan − q4.actual_quarter (Excel GA5 = FY5 = FK5 − FW5)
      CASE WHEN s_tot THEN f.cp4 - f.aq4 END,

      -- ─── НОВЫЕ блоки (INPUT) ───
      f.io,
      CASE WHEN s_cs THEN f.cs_total END, CASE WHEN s_cs THEN f.cs_cp END,
      CASE WHEN s_cs THEN f.cs_dl END, CASE WHEN s_cs THEN f.cs_cmt END,
      CASE WHEN s_cs THEN f.cs_fy1 END, CASE WHEN s_cs THEN f.cs_fy2 END,
      CASE WHEN s_cs THEN f.cs_oth END,
      CASE WHEN s_al THEN f.al_cref END, CASE WHEN s_al THEN f.al_purp END,
      CASE WHEN s_al THEN f.al_io END, CASE WHEN s_al THEN f.al_prop END,
      CASE WHEN s_ca THEN f.ca_amt END, CASE WHEN s_ca THEN f.ca_proto END,
      CASE WHEN s_ca THEN f.ca_note END,
      CASE WHEN s_ckk THEN f.ckk_ceil END, CASE WHEN s_ckk THEN f.ckk_rf END,
      CASE WHEN s_ckk THEN f.ckk_dl END, CASE WHEN s_ckk THEN f.ckk_pplan END,
      CASE WHEN s_ckk THEN f.ckk_pmeth END, CASE WHEN s_ckk THEN f.ckk_cmt END,
      CASE WHEN s_cd THEN f.cd_add END, CASE WHEN s_cd THEN f.cd_e1 END,
      CASE WHEN s_cd THEN f.cd_e2 END, CASE WHEN s_cd THEN f.cd_e3 END,
      CASE WHEN s_cd THEN f.cd_e4 END, CASE WHEN s_cd THEN f.cd_rf END,
      CASE WHEN s_cd THEN f.cd_rate END, CASE WHEN s_cd THEN f.cd_fx END,
      CASE WHEN s_q1 THEN f.q1_arf END, CASE WHEN s_q1 THEN f.q1_pho END, CASE WHEN s_q1 THEN f.q1_prf END,
      CASE WHEN s_q2 THEN f.q2_arf END, CASE WHEN s_q2 THEN f.q2_pho END, CASE WHEN s_q2 THEN f.q2_prf END,
      CASE WHEN s_q3 THEN f.q3_arf END, CASE WHEN s_q3 THEN f.q3_pho END, CASE WHEN s_q3 THEN f.q3_prf END,
      CASE WHEN s_q4 THEN f.q4_arf END, CASE WHEN s_q4 THEN f.q4_pho END, CASE WHEN s_q4 THEN f.q4_prf END,

      f.tree_path || ARRAY[f.lid::INT] AS _sort
    FROM final_input f

    UNION ALL

    -- ═══ Часть B: Иерархия ════════════════════════════════════════════════
    SELECT
      CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' END::VARCHAR,
      t.depth, NULL::INT,
      CASE WHEN t.depth < 3 THEN t.item_id ELSE t.parent_item_id END,
      CASE WHEN t.depth = 3 THEN t.item_id ELSE NULL END,
      t.num_group_id, t.name, NULL::INT, NULL::VARCHAR,
      -- plan
      CASE WHEN s_plan THEN ta.sp1 END, CASE WHEN s_plan THEN ta.sp2 END,
      CASE WHEN s_plan THEN ta.sp3 END, CASE WHEN s_plan THEN ta.sp4 END,
      CASE WHEN s_plan THEN COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0) END,
      NULL::VARCHAR,
      -- seq dfip
      CASE WHEN s_seq_d THEN ta.sd1 END, CASE WHEN s_seq_d THEN ta.sd2 END,
      CASE WHEN s_seq_d THEN ta.sd3 END, CASE WHEN s_seq_d THEN ta.sd4 END,
      CASE WHEN s_seq_d THEN COALESCE(ta.sd1,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sd4,0) END,
      NULL::VARCHAR,
      -- seq ssp_go
      CASE WHEN s_seq_s THEN ta.sg1 END, CASE WHEN s_seq_s THEN ta.sg2 END,
      CASE WHEN s_seq_s THEN ta.sg3 END, CASE WHEN s_seq_s THEN ta.sg4 END,
      CASE WHEN s_seq_s THEN COALESCE(ta.sg1,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sg4,0) END,
      NULL::VARCHAR,
      -- approved
      CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0) END,
      CASE WHEN s_appr THEN COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0) END,
      CASE WHEN s_appr THEN COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0) END,
      CASE WHEN s_appr THEN COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END,
      CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)
                          +COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)
                          +COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)
                          +COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END,
      -- contract — нет агрегации (текстовые)
      NULL::VARCHAR, NULL::VARCHAR, NULL::DATE, NULL::VARCHAR,
      NULL::VARCHAR, NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR,
      NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR,
      -- booking 2026 / 2027
      CASE WHEN s_book THEN ta.bk1 END, CASE WHEN s_book THEN ta.bk2 END,
      CASE WHEN s_book THEN ta.bk3 END, CASE WHEN s_book THEN ta.bk4 END,
      CASE WHEN s_book THEN ta.bn1 END, CASE WHEN s_book THEN ta.bn2 END,
      CASE WHEN s_book THEN ta.bn3 END, CASE WHEN s_book THEN ta.bn4 END,
      -- Q1 hierarchy: новые поля (pay_act, booking, residuals, transfer reasons,
      -- transfer comments) на агрегатах NULL'им — их корректный rollup можно
      -- добавить позже расширением aq_q.
      CASE WHEN s_q1 THEN tb1.ac END, CASE WHEN s_q1 THEN tb1.as_v END, CASE WHEN s_q1 THEN tb1.arv END,
      NULL::VARCHAR,
      CASE WHEN s_q1 THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0) END,
      NULL::DATE,
      CASE WHEN s_q1 THEN tb1.pa END,
      NULL::VARCHAR,                                            -- pay_comment
      NULL::VARCHAR,                                            -- pay_act
      CASE WHEN s_q1 THEN tb1.bk END,                           -- booking (rollup)
      CASE WHEN s_q1 THEN tb1.m1 END, CASE WHEN s_q1 THEN tb1.m2 END, CASE WHEN s_q1 THEN tb1.m3 END,
      CASE WHEN s_q1 THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) END,
      -- residual_after_booking = q1.cp − Σ booking
      CASE WHEN s_q1 THEN
        (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0))
        - COALESCE(tb1.bk,0)
      END,
      -- residual_after_actual = q1.cp − Σ actual
      CASE WHEN s_q1 THEN
        (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0))
        - (COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0))
      END,
      CASE WHEN s_q1 THEN tb1.tq2 END,
      CASE WHEN s_q1 THEN tb1.tda END, CASE WHEN s_q1 THEN tb1.tdp END, CASE WHEN s_q1 THEN tb1.terf END,
      NULL::VARCHAR,                                            -- transfer_next_comment (text, no rollup)
      CASE WHEN s_q1 THEN tb1.tq3 END, CASE WHEN s_q1 THEN tb1.tq4 END,
      NULL::VARCHAR,                                             -- far comment
      CASE WHEN s_q1 THEN tb1.te END,
      -- total = SUM transfer_q2_reasons + transfer_q3 + transfer_q4 + transfer_econ (Excel CC)
      CASE WHEN s_q1 THEN
        COALESCE(tb1.tda,0)+COALESCE(tb1.tdp,0)+COALESCE(tb1.terf,0)
        +COALESCE(tb1.tq3,0)+COALESCE(tb1.tq4,0)+COALESCE(tb1.te,0)
      END,
      -- Q2 hierarchy
      NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR,    -- target_change/base_correction
      NULL::NUMERIC, NULL::NUMERIC,                   -- revision_inc/revision_seq
      NULL::VARCHAR,                                  -- revision_comment
      CASE WHEN s_q2 THEN
        COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)
      END,
      CASE WHEN s_q2 THEN tb2.ac END, CASE WHEN s_q2 THEN tb2.as_v END, CASE WHEN s_q2 THEN tb2.arv END,
      NULL::VARCHAR,                                  -- adj_comment
      CASE WHEN s_q2 THEN
        COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0)
      END,
      NULL::DATE,
      CASE WHEN s_q2 THEN tb2.pa END,
      NULL::VARCHAR,                                  -- pay_comment
      NULL::VARCHAR,                                  -- pay_act
      CASE WHEN s_q2 THEN tb2.bk END,                 -- booking (rollup)
      CASE WHEN s_q2 THEN tb2.m1 END, CASE WHEN s_q2 THEN tb2.m2 END, CASE WHEN s_q2 THEN tb2.m3 END,
      CASE WHEN s_q2 THEN COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) END,
      -- residual_after_booking = q2.cp − Σ booking
      CASE WHEN s_q2 THEN
        (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0))
        - COALESCE(tb2.bk,0)
      END,
      -- residual_after_actual = q2.cp − Σ actual
      CASE WHEN s_q2 THEN
        (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0))
        - (COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0))
      END,
      CASE WHEN s_q2 THEN tb2.tq3 END,
      CASE WHEN s_q2 THEN tb2.tda END, CASE WHEN s_q2 THEN tb2.tdp END, CASE WHEN s_q2 THEN tb2.terf END,
      NULL::VARCHAR,                                  -- transfer_next_comment (text, no rollup)
      CASE WHEN s_q2 THEN tb2.tq4 END,
      NULL::VARCHAR,                                  -- far comment
      CASE WHEN s_q2 THEN tb2.te END,
      -- total = SUM transfer_q3_reasons + transfer_q4 + transfer_econ (Excel DN)
      CASE WHEN s_q2 THEN
        COALESCE(tb2.tda,0)+COALESCE(tb2.tdp,0)+COALESCE(tb2.terf,0)
        +COALESCE(tb2.tq4,0)+COALESCE(tb2.te,0)
      END,
      -- Q3 hierarchy
      NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR,    -- target_change/base_correction/comment
      NULL::NUMERIC, NULL::NUMERIC,                   -- revision_inc/revision_seq
      NULL::VARCHAR,
      CASE WHEN s_q3 THEN
        COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)
      END,
      CASE WHEN s_q3 THEN tb3.ac END, CASE WHEN s_q3 THEN tb3.as_v END, CASE WHEN s_q3 THEN tb3.arv END,
      NULL::VARCHAR,
      CASE WHEN s_q3 THEN
        COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0)
      END,
      NULL::DATE,
      CASE WHEN s_q3 THEN tb3.pa END,
      NULL::VARCHAR,                                  -- pay_comment
      NULL::VARCHAR,                                  -- pay_act
      CASE WHEN s_q3 THEN tb3.bk END,                 -- booking (rollup)
      CASE WHEN s_q3 THEN tb3.m1 END, CASE WHEN s_q3 THEN tb3.m2 END, CASE WHEN s_q3 THEN tb3.m3 END,
      CASE WHEN s_q3 THEN COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) END,
      -- residual_after_booking = q3.cp − Σ booking
      CASE WHEN s_q3 THEN
        (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0))
        - COALESCE(tb3.bk,0)
      END,
      -- residual_after_actual = q3.cp − Σ actual
      CASE WHEN s_q3 THEN
        (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0))
        - (COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0))
      END,
      CASE WHEN s_q3 THEN tb3.tq4 END,
      CASE WHEN s_q3 THEN tb3.tda END, CASE WHEN s_q3 THEN tb3.tdp END, CASE WHEN s_q3 THEN tb3.terf END,
      NULL::VARCHAR,                                  -- transfer_next_comment (text, no rollup)
      CASE WHEN s_q3 THEN tb3.te END,
      -- total = SUM transfer_q4_reasons + transfer_econ (Excel EW)
      CASE WHEN s_q3 THEN
        COALESCE(tb3.tda,0)+COALESCE(tb3.tdp,0)+COALESCE(tb3.terf,0)
        +COALESCE(tb3.te,0)
      END,
      -- Q4 hierarchy
      NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR,    -- target_change/base_correction/comment
      NULL::NUMERIC, NULL::NUMERIC,                   -- revision_inc/revision_seq
      NULL::VARCHAR,
      CASE WHEN s_q4 THEN
        COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)
      END,
      CASE WHEN s_q4 THEN tb4.ac END, CASE WHEN s_q4 THEN tb4.as_v END, CASE WHEN s_q4 THEN tb4.arv END,
      NULL::VARCHAR,
      CASE WHEN s_q4 THEN
        COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)
      END,
      NULL::DATE,
      CASE WHEN s_q4 THEN tb4.pa END,
      NULL::VARCHAR,                                  -- pay_comment
      NULL::VARCHAR,                                  -- pay_act
      CASE WHEN s_q4 THEN tb4.bk END,                 -- booking (rollup)
      CASE WHEN s_q4 THEN tb4.m1 END, CASE WHEN s_q4 THEN tb4.m2 END, CASE WHEN s_q4 THEN tb4.m3 END,
      CASE WHEN s_q4 THEN tb4.spod END,
      CASE WHEN s_q4 THEN COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END,
      -- residual_after_booking = q4.cp − Σ booking
      CASE WHEN s_q4 THEN
        (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0))
        - COALESCE(tb4.bk,0)
      END,
      -- residual_after_actual = q4.cp − Σ actual (incl. spod)
      CASE WHEN s_q4 THEN
        (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0))
        - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0))
      END,
      CASE WHEN s_q4 THEN tb4.te END,
      -- q4.total = q4.transfer_econ (Q4 has no other outgoing transfers)
      CASE WHEN s_q4 THEN tb4.te END,
      -- totals
      CASE WHEN s_tot THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)
                          +COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)
                          +COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)
                          +COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END,
      CASE WHEN s_tot THEN COALESCE(tb1.pa,0)+COALESCE(tb2.pa,0)+COALESCE(tb3.pa,0)+COALESCE(tb4.pa,0) END,
      -- economy_year = q4.corrected_plan − q4.actual_quarter (Excel GA = FY = FK − FW)
      CASE WHEN s_tot THEN
        (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)
         +COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)
         +COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0))
        - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0))
      END,

      -- ─── НОВЫЕ блоки (иерархия) — per-line детали не агрегируются → NULL ───
      NULL::VARCHAR,                                             -- internal_order
      NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- contract_summary
      NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC,
      NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- allocation
      NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR,                -- collegial
      NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- ckk
      NULL::VARCHAR, NULL::VARCHAR,
      NULL::VARCHAR, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- contract_detail extras
      NULL::NUMERIC, NULL::VARCHAR, NULL::NUMERIC, NULL::NUMERIC,
      NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC,                -- q1 adj_rf/pho/prf
      NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC,                -- q2
      NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC,                -- q3
      NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC,                -- q4

      t.path AS _sort
    FROM tw t
    LEFT JOIN tw_agg ta ON ta.id = t.id
    LEFT JOIN tw_aq1 tb1 ON tb1.id = t.id
    LEFT JOIN tw_aq2 tb2 ON tb2.id = t.id
    LEFT JOIN tw_aq3 tb3 ON tb3.id = t.id
    LEFT JOIN tw_aq4 tb4 ON tb4.id = t.id
  ) sub
  ORDER BY sub._sort;
END;
$function$ 
LANGUAGE plpgsql STABLE;

-- ═══ 2/3 ═══ v3.v_form2_sheet_jsonb ═══════════════════════════════════════
-- ════════════════════════════════════════════════════════════════════════════
-- v3.v_form2_sheet_jsonb — JSONB-чанки (thin wrapper над v3.v_form2_sheet_sections)
-- ════════════════════════════════════════════════════════════════════════════
-- Вся логика в v3.v_form2_sheet_sections. Здесь только переупаковка
-- типизированных колонок в JSONB-чанки. NULL чанк = секция не запрошена.
-- ════════════════════════════════════════════════════════════════════════════

DROP FUNCTION IF EXISTS v3.v_form2_sheet_jsonb(INT,VARCHAR,TEXT[]);

CREATE FUNCTION v3.v_form2_sheet_jsonb(
    p_form_id   INT,
    p_sheet     VARCHAR,
    p_sections  TEXT[] DEFAULT NULL
)
RETURNS TABLE (
    row_type           VARCHAR,
    depth              INT,
    line_id            INT,
    header             JSONB,
    plan_data          JSONB,
    seq_dfip_data      JSONB,
    seq_ssp_data       JSONB,
    approved_data      JSONB,
    contract_data      JSONB,
    booking_data       JSONB,
    contract_summary   JSONB,
    allocation_data    JSONB,
    collegial_data     JSONB,
    ckk_data           JSONB,
    q1_data            JSONB,
    q2_data            JSONB,
    q3_data            JSONB,
    q4_data            JSONB,
    totals_data        JSONB,
    _sort_path         INT[]
)
AS $function$
  WITH flags AS (
    SELECT
      (p_sections IS NULL OR 'plan'     = ANY(p_sections)) AS s_plan,
      (p_sections IS NULL OR 'seq_dfip' = ANY(p_sections)) AS s_seq_d,
      (p_sections IS NULL OR 'seq_ssp'  = ANY(p_sections)) AS s_seq_s,
      (p_sections IS NULL OR 'approved' = ANY(p_sections)) AS s_appr,
      (p_sections IS NULL OR 'contract' = ANY(p_sections)) AS s_cd,
      (p_sections IS NULL OR 'booking'  = ANY(p_sections)) AS s_book,
      (p_sections IS NULL OR 'contract_summary' = ANY(p_sections)) AS s_cs,
      (p_sections IS NULL OR 'allocation'       = ANY(p_sections)) AS s_al,
      (p_sections IS NULL OR 'collegial'        = ANY(p_sections)) AS s_ca,
      (p_sections IS NULL OR 'ckk'              = ANY(p_sections)) AS s_ckk,
      (p_sections IS NULL OR 'q1'       = ANY(p_sections)) AS s_q1,
      (p_sections IS NULL OR 'q2'       = ANY(p_sections)) AS s_q2,
      (p_sections IS NULL OR 'q3'       = ANY(p_sections)) AS s_q3,
      (p_sections IS NULL OR 'q4'       = ANY(p_sections)) AS s_q4,
      (p_sections IS NULL OR 'totals'   = ANY(p_sections)) AS s_tot
  )
  SELECT
    s.row_type, s.depth, s.line_id,

    -- header (всегда). expense_item_id из _sort_path (см. v_form1_jsonb).
    jsonb_build_object(
      'section_code',    s.col_section_code,
      'item_id',         s.col_item_id,
      'num_group',       s.col_num_group_id,
      'name',            s.col_name,
      'vsp_id',          s.col_vsp_id,
      'vsp_address',     s.col_vsp_address,
      'internal_order',  s.col_internal_order,
      'expense_item_id', CASE WHEN s.row_type = 'INPUT'
                           THEN s._sort_path[array_upper(s._sort_path,1)-1]
                           ELSE s._sort_path[array_upper(s._sort_path,1)] END
    ) AS header,

    CASE WHEN f.s_plan THEN jsonb_build_object(
      'q1',      s.col_plan_q1, 'q2', s.col_plan_q2,
      'q3',      s.col_plan_q3, 'q4', s.col_plan_q4,
      'year',    s.col_plan_year, 'comment', s.col_plan_comment
    ) END AS plan_data,

    CASE WHEN f.s_seq_d THEN jsonb_build_object(
      'q1',     s.col_seq_dfip_q1, 'q2', s.col_seq_dfip_q2,
      'q3',     s.col_seq_dfip_q3, 'q4', s.col_seq_dfip_q4,
      'year',   s.col_seq_dfip_year,
      'justification', s.col_seq_dfip_just
    ) END AS seq_dfip_data,

    CASE WHEN f.s_seq_s THEN jsonb_build_object(
      'q1',     s.col_seq_ssp_q1, 'q2', s.col_seq_ssp_q2,
      'q3',     s.col_seq_ssp_q3, 'q4', s.col_seq_ssp_q4,
      'year',   s.col_seq_ssp_year,
      'justification', s.col_seq_ssp_just
    ) END AS seq_ssp_data,

    CASE WHEN f.s_appr THEN jsonb_build_object(
      'q1',   s.col_appr_q1, 'q2', s.col_appr_q2,
      'q3',   s.col_appr_q3, 'q4', s.col_appr_q4,
      'year', s.col_appr_year
    ) END AS approved_data,

    CASE WHEN f.s_cd THEN jsonb_build_object(
      'counterparty',   s.col_cd_counterparty,
      'reference',      s.col_cd_reference,
      'addenda',        s.col_cd_addenda,
      'date',           s.col_cd_date,
      'subject',        s.col_cd_subject,
      'currency',       s.col_cd_currency,
      'ceiling',        s.col_cd_ceiling,
      'q1',             s.col_cd_exp_q1,
      'q2',             s.col_cd_exp_q2,
      'q3',             s.col_cd_exp_q3,
      'q4',             s.col_cd_exp_q4,
      'rf_schedule',    s.col_cd_rf_sched,
      'vat_rate',       s.col_cd_vat_rate,
      'exchange_rate',  s.col_cd_exchange_rate,
      'amount_foreign', s.col_cd_amount_foreign,
      'deadline',       s.col_cd_deadline,
      'scheme',         s.col_cd_scheme,
      'act',            s.col_cd_act,
      'comment',        s.col_cd_comment
    ) END AS contract_data,

    CASE WHEN f.s_book THEN jsonb_build_object(
      'y2026', jsonb_build_object('q1', s.col_book_q1, 'q2', s.col_book_q2, 'q3', s.col_book_q3, 'q4', s.col_book_q4),
      'y2027', jsonb_build_object('q1', s.col_book_next_q1, 'q2', s.col_book_next_q2, 'q3', s.col_book_next_q3, 'q4', s.col_book_next_q4)
    ) END AS booking_data,

    CASE WHEN f.s_cs THEN jsonb_build_object(
      'total',        s.col_cs_total,
      'counterparty', s.col_cs_counterparty,
      'deadline',     s.col_cs_deadline,
      'comment',      s.col_cs_comment,
      'future_y1',    s.col_cs_future_y1,
      'future_y2',    s.col_cs_future_y2,
      'other_ssp',    s.col_cs_other_ssp
    ) END AS contract_summary,

    CASE WHEN f.s_al THEN jsonb_build_object(
      'contract_ref', s.col_al_contract_ref,
      'purpose',      s.col_al_purpose,
      'order',        s.col_al_order,
      'property',     s.col_al_property
    ) END AS allocation_data,

    CASE WHEN f.s_ca THEN jsonb_build_object(
      'approved', s.col_ca_approved,
      'protocol', s.col_ca_protocol,
      'note',     s.col_ca_note
    ) END AS collegial_data,

    -- ckk: поквартальные expenses уже отдаются в booking.y2026 (не дублируем);
    -- здесь только поля, которых нет в других блоках.
    CASE WHEN f.s_ckk THEN jsonb_build_object(
      'ceiling',     s.col_ckk_ceiling,
      'rf_schedule', s.col_ckk_rf_sched,
      'deadline',    s.col_ckk_deadline,
      'proc_plan',   s.col_ckk_proc_plan,
      'proc_method', s.col_ckk_proc_meth,
      'comment',     s.col_ckk_comment
    ) END AS ckk_data,

    CASE WHEN f.s_q1 THEN jsonb_build_object(
      'adj_current',    s.col_q1_adj_current,
      'adj_ssp',        s.col_q1_adj_ssp,
      'adj_rf',         s.col_q1_adj_rf,
      'adj_reserve',    s.col_q1_adj_reserve,
      'adj_comment',    s.col_q1_adj_comment,
      'corrected_plan', s.col_q1_corrected_plan,
      'pay_date',       s.col_q1_pay_date,
      'pay_amount',     s.col_q1_pay_amount,
      'pay_ho',         s.col_q1_pay_ho,
      'pay_rf',         s.col_q1_pay_rf,
      'pay_comment',    s.col_q1_pay_comment,
      'pay_act',        s.col_q1_pay_act,
      'booking',        s.col_q1_booking,
      'actual_m1',      s.col_q1_actual_m1,
      'actual_m2',      s.col_q1_actual_m2,
      'actual_m3',      s.col_q1_actual_m3,
      'actual_quarter', s.col_q1_actual_quarter,
      'residual_after_booking', s.col_q1_residual_after_booking,
      'residual_after_actual',  s.col_q1_residual_after_actual,
      'transfer_q2',    s.col_q1_transfer_q2,
      'transfer_q2_delay_acts',        s.col_q1_transfer_q2_delay_acts,
      'transfer_q2_delay_procurement', s.col_q1_transfer_q2_delay_procurement,
      'transfer_q2_economy_rf',        s.col_q1_transfer_q2_economy_rf,
      'transfer_next_comment',         s.col_q1_transfer_next_comment,
      'transfer_q3',    s.col_q1_transfer_q3,
      'transfer_q4',    s.col_q1_transfer_q4,
      'transfer_far_comment',          s.col_q1_transfer_far_comment,
      'transfer_econ',  s.col_q1_transfer_econ,
      'total',          s.col_q1_total
    ) END AS q1_data,

    CASE WHEN f.s_q2 THEN jsonb_build_object(
      'target_change',           s.col_q2_target_change,
      'base_correction',         s.col_q2_base_correction,
      'base_correction_comment', s.col_q2_base_correction_comment,
      'revision_inc',            s.col_q2_revision_inc,
      'revision_seq',            s.col_q2_revision_seq,
      'revision_comment',        s.col_q2_revision_comment,
      'new_plan',                s.col_q2_new_plan,
      'adj_current',             s.col_q2_adj_current,
      'adj_ssp',                 s.col_q2_adj_ssp,
      'adj_rf',                  s.col_q2_adj_rf,
      'adj_reserve',             s.col_q2_adj_reserve,
      'adj_comment',             s.col_q2_adj_comment,
      'corrected_plan',          s.col_q2_corrected_plan,
      'pay_date',                s.col_q2_pay_date,
      'pay_amount',              s.col_q2_pay_amount,
      'pay_ho',                  s.col_q2_pay_ho,
      'pay_rf',                  s.col_q2_pay_rf,
      'pay_comment',             s.col_q2_pay_comment,
      'pay_act',                 s.col_q2_pay_act,
      'booking',                 s.col_q2_booking,
      'actual_m1',               s.col_q2_actual_m1,
      'actual_m2',               s.col_q2_actual_m2,
      'actual_m3',               s.col_q2_actual_m3,
      'actual_quarter',          s.col_q2_actual_quarter,
      'residual_after_booking',  s.col_q2_residual_after_booking,
      'residual_after_actual',   s.col_q2_residual_after_actual,
      'transfer_q3',             s.col_q2_transfer_q3,
      'transfer_q3_delay_acts',        s.col_q2_transfer_q3_delay_acts,
      'transfer_q3_delay_procurement', s.col_q2_transfer_q3_delay_procurement,
      'transfer_q3_economy_rf',        s.col_q2_transfer_q3_economy_rf,
      'transfer_next_comment',         s.col_q2_transfer_next_comment,
      'transfer_q4',             s.col_q2_transfer_q4,
      'transfer_far_comment',          s.col_q2_transfer_far_comment,
      'transfer_econ',           s.col_q2_transfer_econ,
      'total',                   s.col_q2_total
    ) END AS q2_data,

    CASE WHEN f.s_q3 THEN jsonb_build_object(
      'target_change',           s.col_q3_target_change,
      'base_correction',         s.col_q3_base_correction,
      'base_correction_comment', s.col_q3_base_correction_comment,
      'revision_inc',            s.col_q3_revision_inc,
      'revision_seq',            s.col_q3_revision_seq,
      'revision_comment',        s.col_q3_revision_comment,
      'new_plan',                s.col_q3_new_plan,
      'adj_current',             s.col_q3_adj_current,
      'adj_ssp',                 s.col_q3_adj_ssp,
      'adj_rf',                  s.col_q3_adj_rf,
      'adj_reserve',             s.col_q3_adj_reserve,
      'adj_comment',             s.col_q3_adj_comment,
      'corrected_plan',          s.col_q3_corrected_plan,
      'pay_date',                s.col_q3_pay_date,
      'pay_amount',              s.col_q3_pay_amount,
      'pay_ho',                  s.col_q3_pay_ho,
      'pay_rf',                  s.col_q3_pay_rf,
      'pay_comment',             s.col_q3_pay_comment,
      'pay_act',                 s.col_q3_pay_act,
      'booking',                 s.col_q3_booking,
      'actual_m1',               s.col_q3_actual_m1,
      'actual_m2',               s.col_q3_actual_m2,
      'actual_m3',               s.col_q3_actual_m3,
      'actual_quarter',          s.col_q3_actual_quarter,
      'residual_after_booking',  s.col_q3_residual_after_booking,
      'residual_after_actual',   s.col_q3_residual_after_actual,
      'transfer_q4',             s.col_q3_transfer_q4,
      'transfer_q4_delay_acts',        s.col_q3_transfer_q4_delay_acts,
      'transfer_q4_delay_procurement', s.col_q3_transfer_q4_delay_procurement,
      'transfer_q4_economy_rf',        s.col_q3_transfer_q4_economy_rf,
      'transfer_next_comment',         s.col_q3_transfer_next_comment,
      'transfer_econ',           s.col_q3_transfer_econ,
      'total',                   s.col_q3_total
    ) END AS q3_data,

    CASE WHEN f.s_q4 THEN jsonb_build_object(
      'target_change',           s.col_q4_target_change,
      'base_correction',         s.col_q4_base_correction,
      'base_correction_comment', s.col_q4_base_correction_comment,
      'revision_inc',            s.col_q4_revision_inc,
      'revision_seq',            s.col_q4_revision_seq,
      'revision_comment',        s.col_q4_revision_comment,
      'new_plan',                s.col_q4_new_plan,
      'adj_current',             s.col_q4_adj_current,
      'adj_ssp',                 s.col_q4_adj_ssp,
      'adj_rf',                  s.col_q4_adj_rf,
      'adj_reserve',             s.col_q4_adj_reserve,
      'adj_comment',             s.col_q4_adj_comment,
      'corrected_plan',          s.col_q4_corrected_plan,
      'pay_date',                s.col_q4_pay_date,
      'pay_amount',              s.col_q4_pay_amount,
      'pay_ho',                  s.col_q4_pay_ho,
      'pay_rf',                  s.col_q4_pay_rf,
      'pay_comment',             s.col_q4_pay_comment,
      'pay_act',                 s.col_q4_pay_act,
      'booking',                 s.col_q4_booking,
      'actual_m1',               s.col_q4_actual_m1,
      'actual_m2',               s.col_q4_actual_m2,
      'actual_m3',               s.col_q4_actual_m3,
      'actual_spod',             s.col_q4_actual_spod,
      'actual_quarter',          s.col_q4_actual_quarter,
      'residual_after_booking',  s.col_q4_residual_after_booking,
      'residual_after_actual',   s.col_q4_residual_after_actual,
      'transfer_econ',           s.col_q4_transfer_econ,
      'total',                   s.col_q4_total
    ) END AS q4_data,

    CASE WHEN f.s_tot THEN jsonb_build_object(
      'fact_year',    s.col_fact_year,
      'pay_year',     s.col_pay_year,
      'economy_year', s.col_economy_year
    ) END AS totals_data,

    s._sort_path
  FROM v3.v_form2_sheet_sections(p_form_id, p_sheet, p_sections) s
  CROSS JOIN flags f
  ORDER BY s._sort_path;
$function$ 
LANGUAGE sql STABLE;

-- ═══ 3/3 ═══ v3.v_form2_view ══════════════════════════════════════════════
-- ════════════════════════════════════════════════════════════════════════════
-- v3.v_form2_view — единая точка входа для всех листов Формы 2
-- ════════════════════════════════════════════════════════════════════════════
-- Возвращает унифицированный формат: (row_type, depth, sort_order, data).
-- p_sheet определяет источник:
--
--   'AHR' / 'CAP' / 'OPER'   → основные листы (через v_form2_sheet_jsonb)
--   'AHR_LIMIT'              → лист АХР_лимит (иерархия SECTION/GROUP/LEAF)
--   'AHR_RENT'               → АХР_аренда   (rent_detail × vsp)
--   'AHR_UTILITY'            → АХР_коммунал (utility_detail × vsp)
--   'AHR_SECURITY'           → АХР_охрана   (security_detail × vsp)
--   'OTCH9F'                 → Отч.9ф       (fixed_asset_report, 12 месяцев)
--   'SMETA'                  → Смета_2026   (агрегационный, через v_form2_smeta)
--
-- p_sections — массив включаемых блоков (зависит от листа). NULL = все.
-- ════════════════════════════════════════════════════════════════════════════

DROP FUNCTION IF EXISTS v3.v_form2_view(INT, VARCHAR, TEXT[]);

CREATE FUNCTION v3.v_form2_view(
    p_form_id   INT,
    p_sheet     VARCHAR,
    p_sections  TEXT[] DEFAULT NULL
)
RETURNS TABLE (
    row_type    VARCHAR,
    depth       INT,
    sort_order  BIGINT,
    data        JSONB
)
AS $function$
BEGIN

  -- ═══ Основные листы АХР / КВ_П / Операц ════════════════════════════════
  IF p_sheet IN ('AHR','CAP','OPER') THEN
    RETURN QUERY
    SELECT
      j.row_type,
      j.depth,
      ROW_NUMBER() OVER (ORDER BY j._sort_path) AS sort_order,
      -- Лист АХР (форма 2) имеет более простую раскладку: у него нет колонок под
      -- internal_order / contract_summary / allocation / collegial / ckk /
      -- contract.{addenda,q1-q4,rf_schedule,exchange_rate,amount_foreign} /
      -- q*.{adj_rf,pay_ho,pay_rf}. Функция чтения — единая (надмножество для
      -- AHR/CAP/OPER), поэтому здесь для AHR срезаем поля, которых нет в шаблоне,
      -- а новые верхнеуровневые блоки добавляем только для CAP/OPER.
      (jsonb_build_object(
        'line_id',          j.line_id,
        'header',           CASE WHEN p_sheet = 'AHR' THEN j.header - 'internal_order' ELSE j.header END,
        'plan',             j.plan_data,
        'seq_dfip',         j.seq_dfip_data,
        'seq_ssp',          j.seq_ssp_data,
        'approved',         j.approved_data,
        'contract',         CASE WHEN p_sheet = 'AHR'
                              THEN j.contract_data - 'addenda' - 'q1' - 'q2' - 'q3' - 'q4'
                                                   - 'rf_schedule' - 'exchange_rate' - 'amount_foreign'
                              ELSE j.contract_data END,
        'booking',          j.booking_data,
        'q1',               CASE WHEN p_sheet = 'AHR' THEN j.q1_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q1_data END,
        'q2',               CASE WHEN p_sheet = 'AHR' THEN j.q2_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q2_data END,
        'q3',               CASE WHEN p_sheet = 'AHR' THEN j.q3_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q3_data END,
        'q4',               CASE WHEN p_sheet = 'AHR' THEN j.q4_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q4_data END,
        'totals',           j.totals_data
      )
      || CASE WHEN p_sheet <> 'AHR'
              THEN jsonb_build_object(
                     'contract_summary', j.contract_summary,
                     'allocation',       j.allocation_data,
                     'collegial',        j.collegial_data,
                     'ckk',              j.ckk_data)
              ELSE '{}'::jsonb END
      ) AS data
    FROM v3.v_form2_sheet_jsonb(p_form_id, p_sheet, p_sections) j
    ORDER BY j._sort_path;
    RETURN;
  END IF;

  -- ═══ АХР_лимит ═════════════════════════════════════════════════════════
  -- SECTION/GROUP-строки агрегируют qty/amount по всем LEAF-потомкам
  -- (Excel: J6=SUM(J7:J13) и т.д.). Дерево строится через parent_id.
  --
  -- Структура (limit_template) — глобальный каталог нормативов, единый на банк.
  -- Значения qty/comment — per-form (form_limit ON budget_form_id = p_form_id).
  -- amount_qN — computed по формуле:
  --   amount_qN = qty_qN * limit_with_vat * period_factor(unit), где
  --   period_factor = 1/4   если unit ~ '/год'
  --                   3     если unit ~ '/месяц' (3 месяца в квартале)
  --                   1     иначе (по умолчанию /квартал)
  IF p_sheet = 'AHR_LIMIT' THEN
    RETURN QUERY
    WITH RECURSIVE tree AS (
      SELECT id AS root_id, id AS desc_id FROM v3.limit_template
      UNION
      SELECT t.root_id, child.id
      FROM tree t
      JOIN v3.limit_template child ON child.parent_id = t.desc_id
    ),
    leaf_amt AS (
      -- per-LEAF computed amount по формуле; qty берётся из form_limit для p_form_id
      SELECT lt.id,
             fl.qty_q1, fl.qty_q2, fl.qty_q3, fl.qty_q4,
             COALESCE(fl.qty_q1,0) * COALESCE(lt.limit_with_vat,0) *
               CASE WHEN lt.unit LIKE '%/год'   THEN 0.25
                    WHEN lt.unit LIKE '%/месяц' THEN 3
                    ELSE 1 END AS amount_q1,
             COALESCE(fl.qty_q2,0) * COALESCE(lt.limit_with_vat,0) *
               CASE WHEN lt.unit LIKE '%/год'   THEN 0.25
                    WHEN lt.unit LIKE '%/месяц' THEN 3
                    ELSE 1 END AS amount_q2,
             COALESCE(fl.qty_q3,0) * COALESCE(lt.limit_with_vat,0) *
               CASE WHEN lt.unit LIKE '%/год'   THEN 0.25
                    WHEN lt.unit LIKE '%/месяц' THEN 3
                    ELSE 1 END AS amount_q3,
             COALESCE(fl.qty_q4,0) * COALESCE(lt.limit_with_vat,0) *
               CASE WHEN lt.unit LIKE '%/год'   THEN 0.25
                    WHEN lt.unit LIKE '%/месяц' THEN 3
                    ELSE 1 END AS amount_q4
      FROM v3.limit_template lt
      LEFT JOIN v3.form_limit fl
        ON fl.template_id = lt.id AND fl.budget_form_id = p_form_id
      WHERE lt.row_type = 'LEAF'
    ),
    agg AS (
      SELECT t.root_id,
        SUM(la.qty_q1)    AS qty_q1,    SUM(la.amount_q1) AS amount_q1,
        SUM(la.qty_q2)    AS qty_q2,    SUM(la.amount_q2) AS amount_q2,
        SUM(la.qty_q3)    AS qty_q3,    SUM(la.amount_q3) AS amount_q3,
        SUM(la.qty_q4)    AS qty_q4,    SUM(la.amount_q4) AS amount_q4
      FROM tree t
      JOIN leaf_amt la ON la.id = t.desc_id
      GROUP BY t.root_id
    )
    SELECT
      -- Маппинг физического хранения limit_template → DTO:
      --   SECTION → ROOT, GROUP → GROUP, LEAF → INPUT (редактируемая ячейка).
      CASE lt.row_type WHEN 'SECTION' THEN 'ROOT'
                       WHEN 'GROUP'   THEN 'GROUP'
                       WHEN 'LEAF'    THEN 'INPUT' END::VARCHAR,
      CASE lt.row_type WHEN 'SECTION' THEN 0 WHEN 'GROUP' THEN 1 ELSE 2 END,
      lt.sort_order::BIGINT,
      jsonb_build_object(
        'id',               lt.id,             -- LEAF id для адресации в write API (= template_id)
        'section_no',       lt.section_no,
        'expense_item_code',lt.expense_item_code,
        'name',             lt.name,
        'unit',             lt.unit,
        'limit_with_vat',   lt.limit_with_vat,
        'limit_without_vat',lt.limit_without_vat,
        'qty_q1',           a.qty_q1,    'amount_q1', a.amount_q1,
        'qty_q2',           a.qty_q2,    'amount_q2', a.amount_q2,
        'qty_q3',           a.qty_q3,    'amount_q3', a.amount_q3,
        'qty_q4',           a.qty_q4,    'amount_q4', a.amount_q4,
        'comment',          fl.comment
      )
    FROM v3.limit_template lt
    LEFT JOIN agg a ON a.root_id = lt.id
    LEFT JOIN v3.form_limit fl
      ON fl.template_id = lt.id AND fl.budget_form_id = p_form_id
    ORDER BY lt.sort_order;
    RETURN;
  END IF;

  -- ═══ АХР_аренда ════════════════════════════════════════════════════════
  IF p_sheet = 'AHR_RENT' THEN
    RETURN QUERY
    SELECT
      'INPUT'::VARCHAR,
      0,
      ROW_NUMBER() OVER (ORDER BY rd.id) AS sort_order,
      jsonb_build_object(
        'id',               rd.id,         -- id rent_detail (для write API)
        'line_id',          rd.line_id,
        'vsp_id',           rd.vsp_id,
        'address',          v.address,
        'object_type',      v.format,
        'rented_area',      v.total_area,
        'contract_number',  rd.contract_number,
        'contract_end_date',rd.contract_end_date,
        'plan',             jsonb_build_object('q1', rd.plan_q1, 'q2', rd.plan_q2, 'q3', rd.plan_q3, 'q4', rd.plan_q4,
                                               'year', COALESCE(rd.plan_q1,0)+COALESCE(rd.plan_q2,0)+COALESCE(rd.plan_q3,0)+COALESCE(rd.plan_q4,0)),
        'fact_q1',          jsonb_build_object('jan', rd.actual_jan, 'feb', rd.actual_feb, 'mar', rd.actual_mar,
                                               'total', COALESCE(rd.actual_jan,0)+COALESCE(rd.actual_feb,0)+COALESCE(rd.actual_mar,0)),
        'fact_q2',          jsonb_build_object('apr', rd.actual_apr, 'may', rd.actual_may, 'jun', rd.actual_jun,
                                               'total', COALESCE(rd.actual_apr,0)+COALESCE(rd.actual_may,0)+COALESCE(rd.actual_jun,0)),
        'fact_q3',          jsonb_build_object('jul', rd.actual_jul, 'aug', rd.actual_aug, 'sep', rd.actual_sep,
                                               'total', COALESCE(rd.actual_jul,0)+COALESCE(rd.actual_aug,0)+COALESCE(rd.actual_sep,0)),
        'fact_q4',          jsonb_build_object('oct', rd.actual_oct, 'nov', rd.actual_nov, 'dec', rd.actual_dec,
                                               'total', COALESCE(rd.actual_oct,0)+COALESCE(rd.actual_nov,0)+COALESCE(rd.actual_dec,0)),
        'comment',          rd.comment
      )
    FROM v3.rent_detail rd
    JOIN v3.budget_line bl ON bl.id = rd.line_id
    JOIN v3.vsp v          ON v.id  = rd.vsp_id
    WHERE bl.budget_form_id = p_form_id
    ORDER BY rd.id;
    RETURN;
  END IF;

  -- ═══ АХР_коммунал ═══════════════════════════════════════════════════════
  IF p_sheet = 'AHR_UTILITY' THEN
    RETURN QUERY
    SELECT
      'INPUT'::VARCHAR,
      0,
      ROW_NUMBER() OVER (ORDER BY ud.id) AS sort_order,
      jsonb_build_object(
        'id',               ud.id,         -- id utility_detail (для write API)
        'line_id',          ud.line_id,
        'vsp_id',           ud.vsp_id,
        'address',          v.address,
        'object_type',      v.format,
        'object_area',      v.total_area,
        'contract_number',  ud.contract_number,
        'contract_end_date',ud.contract_end_date,
        'plan',             jsonb_build_object('q1', ud.plan_q1, 'q2', ud.plan_q2, 'q3', ud.plan_q3, 'q4', ud.plan_q4,
                                               'year', COALESCE(ud.plan_q1,0)+COALESCE(ud.plan_q2,0)+COALESCE(ud.plan_q3,0)+COALESCE(ud.plan_q4,0)),
        'fact_q1',          jsonb_build_object('jan', ud.actual_jan, 'feb', ud.actual_feb, 'mar', ud.actual_mar,
                                               'total', COALESCE(ud.actual_jan,0)+COALESCE(ud.actual_feb,0)+COALESCE(ud.actual_mar,0)),
        'fact_q2',          jsonb_build_object('apr', ud.actual_apr, 'may', ud.actual_may, 'jun', ud.actual_jun,
                                               'total', COALESCE(ud.actual_apr,0)+COALESCE(ud.actual_may,0)+COALESCE(ud.actual_jun,0)),
        'fact_q3',          jsonb_build_object('jul', ud.actual_jul, 'aug', ud.actual_aug, 'sep', ud.actual_sep,
                                               'total', COALESCE(ud.actual_jul,0)+COALESCE(ud.actual_aug,0)+COALESCE(ud.actual_sep,0)),
        'fact_q4',          jsonb_build_object('oct', ud.actual_oct, 'nov', ud.actual_nov, 'dec', ud.actual_dec,
                                               'total', COALESCE(ud.actual_oct,0)+COALESCE(ud.actual_nov,0)+COALESCE(ud.actual_dec,0)),
        'comment',          ud.comment
      )
    FROM v3.utility_detail ud
    JOIN v3.budget_line bl ON bl.id = ud.line_id
    JOIN v3.vsp v          ON v.id  = ud.vsp_id
    WHERE bl.budget_form_id = p_form_id
    ORDER BY ud.id;
    RETURN;
  END IF;

  -- ═══ АХР_охрана ═════════════════════════════════════════════════════════
  IF p_sheet = 'AHR_SECURITY' THEN
    RETURN QUERY
    SELECT
      'INPUT'::VARCHAR,
      0,
      ROW_NUMBER() OVER (ORDER BY sd.id) AS sort_order,
      jsonb_build_object(
        'id',               sd.id,         -- id security_detail (для write API)
        'line_id',          sd.line_id,
        'vsp_id',           sd.vsp_id,
        'address',          v.address,
        'object_type',      v.format,
        'object_area',      v.total_area,
        'contract_number',  sd.contract_number,
        'contract_end_date',sd.contract_end_date,
        'plan',             jsonb_build_object('q1', sd.plan_q1, 'q2', sd.plan_q2, 'q3', sd.plan_q3, 'q4', sd.plan_q4,
                                               'year', COALESCE(sd.plan_q1,0)+COALESCE(sd.plan_q2,0)+COALESCE(sd.plan_q3,0)+COALESCE(sd.plan_q4,0)),
        'fact_q1',          jsonb_build_object('jan', sd.actual_jan, 'feb', sd.actual_feb, 'mar', sd.actual_mar,
                                               'total', COALESCE(sd.actual_jan,0)+COALESCE(sd.actual_feb,0)+COALESCE(sd.actual_mar,0)),
        'fact_q2',          jsonb_build_object('apr', sd.actual_apr, 'may', sd.actual_may, 'jun', sd.actual_jun,
                                               'total', COALESCE(sd.actual_apr,0)+COALESCE(sd.actual_may,0)+COALESCE(sd.actual_jun,0)),
        'fact_q3',          jsonb_build_object('jul', sd.actual_jul, 'aug', sd.actual_aug, 'sep', sd.actual_sep,
                                               'total', COALESCE(sd.actual_jul,0)+COALESCE(sd.actual_aug,0)+COALESCE(sd.actual_sep,0)),
        'fact_q4',          jsonb_build_object('oct', sd.actual_oct, 'nov', sd.actual_nov, 'dec', sd.actual_dec,
                                               'total', COALESCE(sd.actual_oct,0)+COALESCE(sd.actual_nov,0)+COALESCE(sd.actual_dec,0)),
        'comment',          sd.comment
      )
    FROM v3.security_detail sd
    JOIN v3.budget_line bl ON bl.id = sd.line_id
    JOIN v3.vsp v          ON v.id  = sd.vsp_id
    WHERE bl.budget_form_id = p_form_id
    ORDER BY sd.id;
    RETURN;
  END IF;

  -- ═══ Отч.9ф (Fixed Asset Report) ════════════════════════════════════════
  -- Одна строка = (expense_item × equipment_name × month).
  --
  -- opening_qty/amt_604/60415 хранятся в БД ТОЛЬКО для month=1 (база года).
  -- Для month=2..12 вычисляются на лету: накопительная сумма дельт по
  -- предыдущим месяцам от base (Excel: opening_M2 = F1+H1−J1).
  --
  -- totals.* — также computed (acquired_total, disposed_total, balance_qty/amt).
  IF p_sheet = 'OTCH9F' THEN
    RETURN QUERY
    WITH base AS (
      SELECT far.id, far.budget_form_id, far.expense_item_id,
             far.equipment_name, far.month,
             far.acquired_qty_604,    far.acquired_amt_604,
             far.disposed_qty_604,    far.disposed_amt_604,
             far.acquired_qty_60415,  far.acquired_amt_60415,
             far.transferred_qty_60415, far.transferred_amt_60415,
             far.go_balance_only_amt,
             -- база: opening_qty/amt из month=1 этой группы
             FIRST_VALUE(COALESCE(far.opening_qty_604,0))
               OVER w AS base_qty_604,
             FIRST_VALUE(COALESCE(far.opening_amt_604,0))
               OVER w AS base_amt_604,
             FIRST_VALUE(COALESCE(far.opening_qty_60415,0))
               OVER w AS base_qty_60415,
             FIRST_VALUE(COALESCE(far.opening_amt_60415,0))
               OVER w AS base_amt_60415,
             -- кумулятивная дельта по предыдущим месяцам в группе
             COALESCE(SUM(COALESCE(far.acquired_qty_604,0) - COALESCE(far.disposed_qty_604,0))
               OVER w_prev, 0) AS cum_dq_604,
             COALESCE(SUM(COALESCE(far.acquired_amt_604,0) - COALESCE(far.disposed_amt_604,0))
               OVER w_prev, 0) AS cum_da_604,
             COALESCE(SUM(COALESCE(far.acquired_qty_60415,0) - COALESCE(far.transferred_qty_60415,0))
               OVER w_prev, 0) AS cum_dq_60415,
             COALESCE(SUM(COALESCE(far.acquired_amt_60415,0) - COALESCE(far.transferred_amt_60415,0))
               OVER w_prev, 0) AS cum_da_60415
      FROM v3.fixed_asset_report far
      WHERE far.budget_form_id = p_form_id
      WINDOW w AS (PARTITION BY far.budget_form_id, far.expense_item_id, far.equipment_name
                   ORDER BY far.month),
             w_prev AS (PARTITION BY far.budget_form_id, far.expense_item_id, far.equipment_name
                        ORDER BY far.month
                        ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
    ),
    eff AS (
      SELECT b.*,
             (b.base_qty_604 + b.cum_dq_604)     AS opening_qty_604_eff,
             (b.base_amt_604 + b.cum_da_604)     AS opening_amt_604_eff,
             (b.base_qty_60415 + b.cum_dq_60415) AS opening_qty_60415_eff,
             (b.base_amt_60415 + b.cum_da_60415) AS opening_amt_60415_eff
      FROM base b
    )
    SELECT
      'INPUT'::VARCHAR,
      0,
      ROW_NUMBER() OVER (ORDER BY eff.expense_item_id, eff.month) AS sort_order,
      jsonb_build_object(
        'id',               eff.id,        -- fixed_asset_report.id (для write API)
        'expense_item_id',  ei.id,
        'item_id',          ei.item_id,
        'section_code',     ei.section_code,
        'equipment_name',   eff.equipment_name,
        'month',            eff.month,
        'b_604', jsonb_build_object(
          'opening_qty',    eff.opening_qty_604_eff,
          'opening_amt',    eff.opening_amt_604_eff,
          'acquired_qty',   eff.acquired_qty_604,
          'acquired_amt',   eff.acquired_amt_604,
          'disposed_qty',   eff.disposed_qty_604,
          'disposed_amt',   eff.disposed_amt_604
        ),
        'b_60415', jsonb_build_object(
          'opening_qty',    eff.opening_qty_60415_eff,
          'opening_amt',    eff.opening_amt_60415_eff,
          'acquired_qty',   eff.acquired_qty_60415,
          'acquired_amt',   eff.acquired_amt_60415,
          'transferred_qty',eff.transferred_qty_60415,
          'transferred_amt',eff.transferred_amt_60415
        ),
        'totals', jsonb_build_object(
          -- Excel R = SUM(I, O, S) − Q
          'acquired_total', COALESCE(eff.acquired_amt_604,0)+COALESCE(eff.acquired_amt_60415,0)
                            +COALESCE(eff.go_balance_only_amt,0)-COALESCE(eff.transferred_amt_60415,0),
          'go_balance_only',eff.go_balance_only_amt,
          -- Excel T = K
          'disposed_total', COALESCE(eff.disposed_amt_604,0),
          -- Excel U = (opening+acquired−disposed)_604 + (opening+acquired−transferred)_60415
          'balance_qty',    (eff.opening_qty_604_eff   + COALESCE(eff.acquired_qty_604,0)   - COALESCE(eff.disposed_qty_604,0))
                          + (eff.opening_qty_60415_eff + COALESCE(eff.acquired_qty_60415,0) - COALESCE(eff.transferred_qty_60415,0)),
          'balance_amt',    (eff.opening_amt_604_eff   + COALESCE(eff.acquired_amt_604,0)   - COALESCE(eff.disposed_amt_604,0))
                          + (eff.opening_amt_60415_eff + COALESCE(eff.acquired_amt_60415,0) - COALESCE(eff.transferred_amt_60415,0))
        )
      )
    FROM eff
    JOIN v3.expense_item ei ON ei.id = eff.expense_item_id
    ORDER BY eff.expense_item_id, eff.month;
    RETURN;
  END IF;

  -- ═══ Смета_2026 ═════════════════════════════════════════════════════════
  IF p_sheet = 'SMETA' THEN
    RETURN QUERY
    SELECT
      sm.row_type,
      sm.depth,
      ROW_NUMBER() OVER (ORDER BY sm.section_code) AS sort_order,
      jsonb_build_object(
        'section_code', sm.section_code,
        'name',         sm.name,
        'plan', jsonb_build_object(
          'support',     jsonb_build_object('q1', sm.plan_supp_q1, 'q2', sm.plan_supp_q2, 'q3', sm.plan_supp_q3, 'q4', sm.plan_supp_q4, 'year', sm.plan_supp_year),
          'development', jsonb_build_object('q1', sm.plan_dev_q1,  'q2', sm.plan_dev_q2,  'q3', sm.plan_dev_q3,  'q4', sm.plan_dev_q4,  'year', sm.plan_dev_year)
        ),
        'approved', jsonb_build_object(
          'support',     jsonb_build_object('q1', sm.appr_supp_q1, 'q2', sm.appr_supp_q2, 'q3', sm.appr_supp_q3, 'q4', sm.appr_supp_q4, 'year', sm.appr_supp_year),
          'development', jsonb_build_object('q1', sm.appr_dev_q1,  'q2', sm.appr_dev_q2,  'q3', sm.appr_dev_q3,  'q4', sm.appr_dev_q4,  'year', sm.appr_dev_year)
        ),
        'fact', jsonb_build_object(
          'support',     jsonb_build_object('q1', sm.fact_supp_q1, 'q2', sm.fact_supp_q2, 'q3', sm.fact_supp_q3, 'q4', sm.fact_supp_q4, 'year', sm.fact_supp_year),
          'development', jsonb_build_object('q1', sm.fact_dev_q1,  'q2', sm.fact_dev_q2,  'q3', sm.fact_dev_q3,  'q4', sm.fact_dev_q4,  'year', sm.fact_dev_year)
        ),
        -- corrected: только Q2/Q3/Q4 (см. шаблон Смета_2026, блок V-X). Q1 нет
        -- (Q1 corrected ≡ approved_q1), year нет (q1+q2+q3+q4 даёт двойной счёт переносов).
        'corrected', jsonb_build_object(
          'support',     jsonb_build_object('q2', sm.corr_supp_q2, 'q3', sm.corr_supp_q3, 'q4', sm.corr_supp_q4),
          'development', jsonb_build_object('q2', sm.corr_dev_q2,  'q3', sm.corr_dev_q3,  'q4', sm.corr_dev_q4)
        )
      )
    FROM v3.v_form2_smeta(p_form_id) sm
    ORDER BY sm.section_code;
    RETURN;
  END IF;

  RAISE EXCEPTION 'Unknown sheet: %', p_sheet
    USING HINT = 'Use AHR / CAP / OPER / AHR_LIMIT / AHR_RENT / AHR_UTILITY / AHR_SECURITY / OTCH9F / SMETA';

END;
$function$ 
LANGUAGE plpgsql STABLE;


-- Данные каталога АХР-лимитов (v3.limit_template) — 26 строк.
-- Импорт в другую БД: таблица v3.limit_template должна уже существовать.
-- Идемпотентно (ON CONFLICT (id) DO UPDATE). id сохраняются — на них
-- ссылаются v3.form_limit.template_id и self-FK parent_id, поэтому порядок по id.
INSERT INTO v3.limit_template
  (id, parent_id, sort_order, row_type, section_no, expense_item_code, name, unit, limit_with_vat, limit_without_vat)
VALUES
  (1, NULL, 100, 'SECTION', '1', NULL, 'Телефонные, почтовые, телеграфные расходы и др. услуги связи', NULL, NULL, NULL),
  (2, 1, 110, 'GROUP', '1.1', 'R061015039', 'Мобильная связь:', NULL, NULL, NULL),
  (3, 2, 111, 'LEAF', NULL, 'R061015039', '- Директор', '1 чел/квартал', 2400, 2000),
  (4, 2, 112, 'LEAF', NULL, 'R061015039', '- Заместитель директора', '1 чел/квартал', 2400, 2000),
  (5, 2, 113, 'LEAF', NULL, 'R061015039', '- Заместитель директора-гл.бухгалтер/гл.бухгалтер', '1 чел/квартал', 1200, 1000),
  (6, 2, 114, 'LEAF', NULL, 'R061015039', '- Руководитель отдела/службы безопасности', '1 чел/квартал', 750, 625),
  (7, 2, 115, 'LEAF', NULL, 'R061015039', '- SIM-карта для планшета (легкий фронт)', '1 планшет /квартал', 450, NULL),
  (8, 2, 116, 'LEAF', NULL, 'R061015039', '- SIM-карта для уполномоченного работника', '1 чел/квартал', 450, NULL),
  (9, 2, 117, 'LEAF', NULL, 'R061015039', '- прочие работники (по усмотрению директора филиала)', '1 чел/квартал', 450, 375),
  (10, 1, 120, 'LEAF', '1.2', 'R061015038', 'Услуги стационарной связи', '1 чел/квартал', 1500, 1250),
  (11, NULL, 200, 'SECTION', '2', NULL, 'Расходы на содержание и обслуживание вычислительной техники, оргтехники и другого банковского оборудования', NULL, NULL, NULL),
  (12, 11, 210, 'GROUP', NULL, '204620030', 'Содержание, обслуживание техники', NULL, NULL, NULL),
  (13, 12, 211, 'LEAF', NULL, '204620030', '- принтеры', NULL, NULL, 0),
  (14, 12, 212, 'LEAF', NULL, '204620030', '- МФУ', NULL, NULL, 0),
  (15, 12, 213, 'LEAF', NULL, '204620030', '- копировальные аппараты', NULL, NULL, 0),
  (16, 11, 220, 'GROUP', NULL, '204720043', 'Кассовое оборудование', NULL, NULL, NULL),
  (17, 16, 221, 'LEAF', NULL, '204720043', '- счетчики/сортировщики банкнот', '1шт/квартал', 4500, 3750),
  (18, 16, 222, 'LEAF', NULL, '204720043', '- контрольно-кассовые машины', '1шт/квартал', 4500, 3750),
  (19, NULL, 300, 'SECTION', '3', NULL, 'Канцелярские расходы', NULL, NULL, NULL),
  (20, 19, 310, 'LEAF', NULL, '401440097', '- Бумага', '1 чел/квартал', 1600, 1333.3333),
  (21, 19, 320, 'LEAF', NULL, '401540098', '- Канцелярские принадлежности', NULL, 500, 416.6667),
  (22, NULL, 400, 'SECTION', '4', 'R061082031', 'Расходы по добровольному мед.страхованию работников Банка', NULL, NULL, NULL),
  (23, 22, 410, 'LEAF', NULL, 'R061082031', '- бизнес РФ', '1 чел/год', 60308, 60308),
  (24, 22, 420, 'LEAF', NULL, 'R061082031', '- стандарт РФ', NULL, 27858, 27858),
  (25, 22, 430, 'LEAF', NULL, 'R061082031', '- стандарт 1 РФ', NULL, 16987, 16987),
  (26, NULL, 500, 'LEAF', '5', 'R061142000', 'Подписка на периодические издания, приобретение спец. литературы', '1 филиал/квартал', 30000, 25000)
ON CONFLICT (id) DO UPDATE SET
  parent_id = EXCLUDED.parent_id,
  sort_order = EXCLUDED.sort_order,
  row_type = EXCLUDED.row_type,
  section_no = EXCLUDED.section_no,
  expense_item_code = EXCLUDED.expense_item_code,
  name = EXCLUDED.name,
  unit = EXCLUDED.unit,
  limit_with_vat = EXCLUDED.limit_with_vat,
  limit_without_vat = EXCLUDED.limit_without_vat;

-- синхронизируем sequence, чтобы новые INSERT'ы не конфликтовали по id
SELECT setval('v3.limit_template_id_seq', (SELECT max(id) FROM v3.limit_template));
