From 99ef647c5cf3060714e9bbd8df417ca5e7ed645f Mon Sep 17 00:00:00 2001 From: tsygankoviva Date: Tue, 22 Sep 2026 07:42:38 +0300 Subject: [PATCH] =?UTF-8?q?sort-fix:=20=D0=B2=D0=BE=D1=81=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D1=81=D0=BE=D1=80=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D1=83,=20=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9=20expense=5Fitem=20?= =?UTF-8?q?=D0=B2=20=D0=BB=D0=B8=D1=81=D1=82=D0=B0=D1=85=20=D0=90=D0=A5?= =?UTF-8?q?=D0=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/alembic/versions/0033_form2_old_sort.py | 15 + .../versions/sql/0029_expense_items_fixes.sql | 30 +- .../versions/sql/0030_expense_items_fixes.sql | 124 +- .../sql/0031_form2_ahr_satellite_sync.sql | 40 +- .../0032_empty_name_on_budget_line_create.sql | 12 +- .../versions/sql/0033_form2_old_sort.sql | 1721 +++++++++++++++++ 6 files changed, 1839 insertions(+), 103 deletions(-) create mode 100644 api/alembic/versions/0033_form2_old_sort.py create mode 100644 api/alembic/versions/sql/0033_form2_old_sort.sql diff --git a/api/alembic/versions/0033_form2_old_sort.py b/api/alembic/versions/0033_form2_old_sort.py new file mode 100644 index 0000000..29bf1b5 --- /dev/null +++ b/api/alembic/versions/0033_form2_old_sort.py @@ -0,0 +1,15 @@ +from sql_migration import run_sql_file_migration + + +revision = "0033" +down_revision = "0032" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + run_sql_file_migration("0033_form2_old_sort.sql") + + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/sql/0029_expense_items_fixes.sql b/api/alembic/versions/sql/0029_expense_items_fixes.sql index 861e546..8c685d1 100644 --- a/api/alembic/versions/sql/0029_expense_items_fixes.sql +++ b/api/alembic/versions/sql/0029_expense_items_fixes.sql @@ -1,8 +1,8 @@ --- Исправления справочника статей расходов по файлу --- "План_исправления_таблиц_статей_расходов.docx". --- Правки, связанные с ГСМ, намеренно не включены. --- Общие переименования. + + + + update v3.expense_item set name = 'Охр.инкассат перевоз' where item_id = 'R061063000' @@ -23,7 +23,7 @@ set name = 'Другие операционные доходы' where item_id = 'R014040037' and num_group_id is null; --- Перенос двух статей НВНОД в раздел 1.15.6. + update v3.expense_item set parent_id = 81, depth = 3 @@ -32,7 +32,7 @@ where (item_id, num_group_id) in ( ('R061159019', '216220174') ); --- Раздел 1.14: коды по шаблону, включая добавление 1.14.7 для проектов. + update v3.expense_item set item_id = '1.14.4' where id = 71; update v3.expense_item set item_id = '1.14.5' where id = 72; update v3.expense_item set item_id = '1.14.6' where id = 73; @@ -42,14 +42,14 @@ insert into v3.expense_item_form_type (expense_item_id, form_type_code) values (74, 'FORM_1'), (74, 'FORM_2') on conflict (expense_item_id, form_type_code) do nothing; --- Исправление ошибочного ID статьи по ПО информационной безопасности. + update v3.expense_item set item_id = 'R061012024', direction = coalesce(direction, 'Support') where item_id = 'R061012025' and name = 'Внедрение и доработка внешними контрагентами: ПО информационной безопасности'; --- Статья "Расходы по выбытию: ПО информационной безопасности" для раздела 1.17.2. + update v3.expense_item set direction = coalesce(direction, 'Support'), parent_id = 84, @@ -65,8 +65,8 @@ where item_id = 'R061172024' and name = 'Расходы по выбытию: ПО информационной безопасности' on conflict (expense_item_id, form_type_code) do nothing; --- Девять статей Операц уже есть в справочнике. Настраиваем только доступность --- для Сметы ССП, Сметы РФ и Проектной деятельности. + + with source(expense_item_id, form_type_code) as ( values (736, 'FORM_4'), (745, 'FORM_2'), (745, 'FORM_4'), @@ -85,7 +85,7 @@ select expense_item_id, form_type_code from source on conflict (expense_item_id, form_type_code) do nothing; --- Налоговые статьи раздела 5.00.0 для Сметы РФ. + insert into v3.expense_item_form_type (expense_item_id, form_type_code) select id, 'FORM_2' from v3.expense_item @@ -95,7 +95,7 @@ where item_id in ( ) on conflict (expense_item_id, form_type_code) do nothing; --- Проектная деятельность: добавить недостающие статьи АХР в FORM_4, кроме ГСМ. + with form4_ahr(expense_item_id) as ( values (777), (778), (779), (347), (780), (781), (782), (783), @@ -108,8 +108,8 @@ select expense_item_id, 'FORM_4' from form4_ahr on conflict (expense_item_id, form_type_code) do nothing; --- Проектная деятельность и проектные листы: исключить амортизацию и налоги --- из FORM_4, не удаляя записи из справочника и не затрагивая FORM_2. + + delete from v3.expense_item_form_type where form_type_code = 'FORM_4' and ( @@ -119,7 +119,7 @@ where form_type_code = 'FORM_4' or expense_item_id between 767 and 774 ); --- Проектные таблицы: исключить дубль перевозки валютных ценностей из формы. + delete from v3.expense_item_form_type where form_type_code = 'FORM_4' and expense_item_id = 70; diff --git a/api/alembic/versions/sql/0030_expense_items_fixes.sql b/api/alembic/versions/sql/0030_expense_items_fixes.sql index f47972e..5052f77 100644 --- a/api/alembic/versions/sql/0030_expense_items_fixes.sql +++ b/api/alembic/versions/sql/0030_expense_items_fixes.sql @@ -1,6 +1,6 @@ --- Исправления по файлу "План_исправления_таблиц_статей_расходов (2).docx". --- В миграции намеренно не удаляются спорные строки листа Смета ПД и спорные --- FORM_4-привязки из разделов 12.2-12.3 ТЗ. + + + CREATE OR REPLACE FUNCTION v3.expense_item_sort_path(p_path integer[]) RETURNS integer[] @@ -39,7 +39,7 @@ AS $function$ $function$ ; --- FORM_2 uses natural expense-item ordering for tree rows. + CREATE OR REPLACE FUNCTION v3.v_form2_view(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 plpgsql @@ -47,7 +47,7 @@ CREATE OR REPLACE FUNCTION v3.v_form2_view(p_form_id integer, p_sheet character AS $function$ BEGIN - -- ═══ Основные листы АХР / КВ_П / Операц ════════════════════════════════ + IF p_sheet IN ('AHR','CAP','OPER') THEN RETURN QUERY SELECT @@ -74,17 +74,17 @@ BEGIN 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 ( @@ -95,7 +95,7 @@ BEGIN 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) * @@ -130,15 +130,15 @@ BEGIN 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) + 'id', lt.id, 'section_no', lt.section_no, 'expense_item_code',lt.expense_item_code, 'name', lt.name, @@ -159,7 +159,7 @@ BEGIN RETURN; END IF; - -- ═══ АХР_аренда ════════════════════════════════════════════════════════ + IF p_sheet = 'AHR_RENT' THEN RETURN QUERY SELECT @@ -167,7 +167,7 @@ BEGIN 0, ROW_NUMBER() OVER (ORDER BY rd.id) AS sort_order, jsonb_build_object( - 'id', rd.id, -- id rent_detail (для write API) + 'id', rd.id, 'line_id', rd.line_id, 'vsp_id', rd.vsp_id, 'address', v.address, @@ -195,7 +195,7 @@ BEGIN RETURN; END IF; - -- ═══ АХР_коммунал ═══════════════════════════════════════════════════════ + IF p_sheet = 'AHR_UTILITY' THEN RETURN QUERY SELECT @@ -203,7 +203,7 @@ BEGIN 0, ROW_NUMBER() OVER (ORDER BY ud.id) AS sort_order, jsonb_build_object( - 'id', ud.id, -- id utility_detail (для write API) + 'id', ud.id, 'line_id', ud.line_id, 'vsp_id', ud.vsp_id, 'address', v.address, @@ -231,7 +231,7 @@ BEGIN RETURN; END IF; - -- ═══ АХР_охрана ═════════════════════════════════════════════════════════ + IF p_sheet = 'AHR_SECURITY' THEN RETURN QUERY SELECT @@ -239,7 +239,7 @@ BEGIN 0, ROW_NUMBER() OVER (ORDER BY sd.id) AS sort_order, jsonb_build_object( - 'id', sd.id, -- id security_detail (для write API) + 'id', sd.id, 'line_id', sd.line_id, 'vsp_id', sd.vsp_id, 'address', v.address, @@ -267,14 +267,14 @@ BEGIN 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 ( @@ -285,7 +285,7 @@ BEGIN 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)) @@ -294,7 +294,7 @@ BEGIN 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)) @@ -324,7 +324,7 @@ BEGIN 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) + 'id', eff.id, 'expense_item_id', ei.id, 'item_id', ei.item_id, 'section_code', ei.section_code, @@ -347,13 +347,13 @@ BEGIN '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)) @@ -366,7 +366,7 @@ BEGIN RETURN; END IF; - -- ═══ Смета_2026 ═════════════════════════════════════════════════════════ + IF p_sheet = 'SMETA' THEN RETURN QUERY SELECT @@ -388,8 +388,8 @@ BEGIN '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) @@ -407,7 +407,7 @@ END; $function$ ; --- Общие исправления справочника. + update v3.expense_item @@ -452,21 +452,21 @@ where parent_id = 766 'R065050005', 'R065070000', 'R065090000', 'R065100000' ); --- Раздел 1.14 по шаблону ССП. Лишний раздел 1.14.4 отключается ниже --- от проектных форм/отчётов, но не удаляется из справочника. + + update v3.expense_item set item_id = '1.14.4' where id = 71; update v3.expense_item set item_id = '1.14.5' where id = 72; update v3.expense_item set item_id = '1.14.6' where id = 73; update v3.expense_item set item_id = '1.14.7' where id = 74; --- Исправление ошибочного ID статьи внешних контрагентов. + update v3.expense_item set item_id = 'R061012024', direction = coalesce(direction, 'Support') where item_id = 'R061012025' and name = 'Внедрение и доработка внешними контрагентами: ПО информационной безопасности'; --- Статья выбытия ПО информационной безопасности. + update v3.expense_item set direction = coalesce(direction, 'Support'), parent_id = 84, @@ -474,7 +474,7 @@ set direction = coalesce(direction, 'Support'), where item_id = 'R061172024' and name = 'Расходы по выбытию: ПО информационной безопасности'; --- Девять статей Операц. Если записи уже есть, приводим названия к ТЗ. + insert into v3.expense_item (id, section_code, item_id, num_group_id, name, sheet, direction, parent_id, depth) values (800, 'OPER', 'R064040033', '402740198', 'Бланки сбер книжекМЦ', 'OPER', 'Support', 715, 3), @@ -496,13 +496,13 @@ set section_code = excluded.section_code, parent_id = excluded.parent_id, depth = excluded.depth; --- Доступность форм по ТЗ. + with source(expense_item_id, form_type_code) as ( values - -- FORM_1/FORM_2: раздел 1.14.7 и статья выбытия ПО + (74, 'FORM_1'), (74, 'FORM_2'), (495, 'FORM_1'), (495, 'FORM_2'), - -- FORM_1/FORM_2/FORM_4: девять Операц и две существующие Операц + (736, 'FORM_4'), (745, 'FORM_2'), (745, 'FORM_4'), (800, 'FORM_1'), (800, 'FORM_2'), (800, 'FORM_4'), (801, 'FORM_1'), (801, 'FORM_2'), (801, 'FORM_4'), @@ -513,7 +513,7 @@ with source(expense_item_id, form_type_code) as ( (806, 'FORM_1'), (806, 'FORM_2'), (806, 'FORM_4'), (807, 'FORM_1'), (807, 'FORM_2'), (807, 'FORM_4'), (808, 'FORM_1'), (808, 'FORM_2'), (808, 'FORM_4'), - -- FORM_4: восстановить ГСМ в Структуре ПД без создания дубля + (776, 'FORM_4') ) insert into v3.expense_item_form_type (expense_item_id, form_type_code) @@ -521,7 +521,7 @@ select expense_item_id, form_type_code from source on conflict (expense_item_id, form_type_code) do nothing; --- Налоговые статьи раздела 5.00.0 для Сметы РФ. + insert into v3.expense_item_form_type (expense_item_id, form_type_code) select id, 'FORM_2' from v3.expense_item @@ -531,8 +531,8 @@ where item_id in ( ) on conflict (expense_item_id, form_type_code) do nothing; --- Проектная деятельность: убрать амортизацию и налоги из FORM_4, --- не удаляя записи из справочника. + + delete from v3.expense_item_form_type where form_type_code = 'FORM_4' and ( @@ -543,14 +543,14 @@ where form_type_code = 'FORM_4' or expense_item_id between 767 and 774 ); --- Пункт 8. Исправление отчётов Проекты: Текущие расходы и Лимиты. --- 8.2 добавить заголовок 1.14.7 "Другие административно-хозяйственные расходы"; + + insert into v3.expense_item_form_type (expense_item_id, form_type_code) values (74, 'FORM_3'), (495, 'FORM_3') on conflict (expense_item_id, form_type_code) do nothing; --- 8.1 убрать лишний раздел 1.14.4 "Перевозка валютных ценностей_АХР"; + delete from v3.expense_item_form_type where form_type_code = 'FORM_3' and ( @@ -560,7 +560,7 @@ where form_type_code = 'FORM_3' or expense_item_id between 761 and 765 ); --- 8.1 убрать лишний раздел 1.14.4 "Перевозка валютных ценностей_АХР"; + delete from v3.rf_project_report_quarter q using v3.rf_project_report_line l where q.rf_project_report_line_id = l.id @@ -571,7 +571,7 @@ where q.rf_project_report_line_id = l.id or l.expense_item_id between 761 and 765 ); --- 8.1 убрать лишний раздел 1.14.4 "Перевозка валютных ценностей_АХР"; + delete from v3.rf_project_report_line where expense_item_id = 70 or expense_item_id in (746, 747, 760) @@ -593,7 +593,7 @@ where not exists ( ); --- DROP FUNCTION v3.v_form4_view(int4, varchar, _text); + CREATE OR REPLACE FUNCTION v3.v_form4_view(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) diff --git a/api/alembic/versions/sql/0031_form2_ahr_satellite_sync.sql b/api/alembic/versions/sql/0031_form2_ahr_satellite_sync.sql index 92a289d..c9af8aa 100644 --- a/api/alembic/versions/sql/0031_form2_ahr_satellite_sync.sql +++ b/api/alembic/versions/sql/0031_form2_ahr_satellite_sync.sql @@ -1,9 +1,9 @@ --- Синхронизация сателлитных листов FORM_2 в основной лист АХР. --- --- Источник данных остается в сателлитах: --- AHR_LIMIT, AHR_RENT, AHR_UTILITY, AHR_SECURITY. --- Строки budget_line в основном листе АХР создаются/обновляются автоматически --- и защищены от прямого редактирования через пользовательскую write-функцию. + + + + + + CREATE TABLE IF NOT EXISTS v3.form2_ahr_satellite_line_source ( source_sheet character varying NOT NULL, @@ -327,10 +327,10 @@ BEGIN RETURN; END IF; - -- AHR_LIMIT: каждая редактируемая LEAF-строка из справочника лимитов - -- становится отдельной budget_line в АХР. Строки одной статьи расходов - -- различаются по budget_line.name, например "Директор", - -- "Заместитель директора" и т.д. + + + + FOR v_rec IN WITH limit_amounts AS ( SELECT @@ -374,7 +374,7 @@ BEGIN ); END LOOP; - -- AHR_RENT -> одна строка АХР на каждую строку rent_detail. + FOR v_line_id IN SELECT m.target_line_id FROM v3.form2_ahr_satellite_line_source m @@ -424,7 +424,7 @@ BEGIN AND m.source_id = rd.id; END LOOP; - -- AHR_UTILITY -> одна строка АХР на каждую строку utility_detail. + FOR v_line_id IN SELECT m.target_line_id FROM v3.form2_ahr_satellite_line_source m @@ -474,7 +474,7 @@ BEGIN AND m.source_id = ud.id; END LOOP; - -- AHR_SECURITY -> одна строка АХР на каждую строку security_detail. + FOR v_line_id IN SELECT m.target_line_id FROM v3.form2_ahr_satellite_line_source m @@ -1042,9 +1042,9 @@ BEGIN WHEN 'AHR_SECURITY' THEN 'security_detail' END; v_sat_eid := CASE p_sheet - WHEN 'AHR_RENT' THEN 293 -- Аренда_ОН - WHEN 'AHR_UTILITY' THEN 799 -- Коммунальные услуги - WHEN 'AHR_SECURITY' THEN 31 -- Охрана + WHEN 'AHR_RENT' THEN 293 + WHEN 'AHR_UTILITY' THEN 799 + WHEN 'AHR_SECURITY' THEN 31 END; IF p_vsp_id IS NULL THEN @@ -1289,9 +1289,9 @@ BEGIN END; $function$; --- В существующих данных могут быть строки budget_line, вставленные с явными id, --- при этом serial sequence может отставать от MAX(id). Выравниваем sequence --- перед backfill, который создает сгенерированные строки АХР. + + + DO $do$ DECLARE v_sequence_name text; @@ -1308,7 +1308,7 @@ BEGIN END; $do$; --- Повторно синхронизируем существующие формы FORM_2 во время миграции. + DO $do$ DECLARE v_form_id integer; diff --git a/api/alembic/versions/sql/0032_empty_name_on_budget_line_create.sql b/api/alembic/versions/sql/0032_empty_name_on_budget_line_create.sql index 70c2fa3..a276d1d 100644 --- a/api/alembic/versions/sql/0032_empty_name_on_budget_line_create.sql +++ b/api/alembic/versions/sql/0032_empty_name_on_budget_line_create.sql @@ -1,9 +1,9 @@ --- Новые пользовательские строки создаются с пустым наименованием. --- Заполнение header.name остается через обычную проверку этапов/ролей: --- v3.can_edit(..., 'header.name', ...). --- Сателлитные листы AHR_RENT/AHR_UTILITY/AHR_SECURITY оставлены в прежней логике. --- Используем пустую строку, а не NULL: представления формы показывают --- справочное имя статьи через COALESCE(budget_line.name, expense_item.name). + + + + + + CREATE OR REPLACE FUNCTION v3.add_budget_line(p_form_id integer, p_expense_item_id integer DEFAULT NULL::integer, p_sheet character varying DEFAULT NULL::character varying, p_item_id character varying DEFAULT NULL::character varying, p_section_code character varying DEFAULT NULL::character varying, p_direction character varying DEFAULT NULL::character varying, p_name character varying DEFAULT NULL::character varying, p_internal_order character varying DEFAULT NULL::character varying, p_vsp_id integer DEFAULT NULL::integer, p_project_id integer DEFAULT NULL::integer, p_justification character varying DEFAULT NULL::character varying, p_contract_number character varying DEFAULT NULL::character varying, p_contract_end_date date DEFAULT NULL::date) RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) diff --git a/api/alembic/versions/sql/0033_form2_old_sort.sql b/api/alembic/versions/sql/0033_form2_old_sort.sql new file mode 100644 index 0000000..ca9c255 --- /dev/null +++ b/api/alembic/versions/sql/0033_form2_old_sort.sql @@ -0,0 +1,1721 @@ +CREATE OR REPLACE 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, + jsonb_build_object( + 'line_id', j.line_id, + 'header', j.header, + 'plan', j.plan_data, + 'seq_dfip', j.seq_dfip_data, + 'seq_ssp', j.seq_ssp_data, + 'approved', j.approved_data, + 'contract', j.contract_data, + 'booking', j.booking_data, + 'q1', j.q1_data, + 'q2', j.q2_data, + 'q3', j.q3_data, + 'q4', j.q4_data, + 'totals', j.totals_data + ) AS data + FROM v3.v_form2_sheet_jsonb(p_form_id, p_sheet, p_sections) j + ORDER BY j._sort_path; + RETURN; + END IF; + + + + + + + + + + + + + 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 ( + + 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 + + + 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, + '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, + '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, + '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, + '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; + + + + + + + + + + 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, + + 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, + '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( + + '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, + + 'disposed_total', COALESCE(eff.disposed_amt_604,0), + + '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; + + + 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', 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; + +CREATE OR REPLACE FUNCTION v3.v_form4_view(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 plpgsql + STABLE +AS $function$ +BEGIN + + IF p_sheet IN ('AHR','CAP','OPER') THEN + RETURN QUERY SELECT * FROM v3.v_form4_sheet_jsonb(p_form_id, p_sheet, p_sections); + RETURN; + END IF; + + IF p_sheet = 'STRUCTURE' THEN + RETURN QUERY + WITH all_sheets AS ( + SELECT * FROM v3.v_form4_sheet_lines_jsonb(p_form_id, 'AHR', p_sections) sq1 WHERE sq1.row_type != 'INPUT' + UNION ALL + SELECT * FROM v3.v_form4_sheet_lines_jsonb(p_form_id, 'CAP', p_sections) sq2 WHERE sq2.row_type != 'INPUT' + UNION ALL + SELECT * FROM v3.v_form4_sheet_lines_jsonb(p_form_id, 'OPER', p_sections) sq3 WHERE sq3.row_type != 'INPUT' + ) + SELECT + j.row_type, + j.depth, + ROW_NUMBER() OVER ( + ORDER BY j._sort_path + ) AS sort_order, + jsonb_build_object( + 'line_id', j.line_id, + 'header', j.header, + 'plan', j.plan_data, + 'seq_dfip', j.seq_dfip_data, + 'approved', j.approved_data, + 'contract_summary', j.contract_summary_data, + 'allocation', j.allocation_data, + 'reserve', j.reserve_data, + 'collegial', j.collegial_data, + 'ckk', j.ckk_data, + 'contract', j.contract_data, + 'booking', j.booking_data, + 'q1', j.q1_data, + 'q2', j.q2_data, + 'q3', j.q3_data, + 'q4', j.q4_data, + 'totals', j.totals_data + ) + FROM all_sheets j + ORDER BY j._sort_path; + RETURN; + END IF; + + 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', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.corr_supp_q1, 'q2', sm.corr_supp_q2, 'q3', sm.corr_supp_q3, 'q4', sm.corr_supp_q4, 'year', sm.corr_supp_year), + 'development', jsonb_build_object('q1', sm.corr_dev_q1, 'q2', sm.corr_dev_q2, 'q3', sm.corr_dev_q3, 'q4', sm.corr_dev_q4, 'year', sm.corr_dev_year) + ) + ) + FROM v3.v_form4_smeta(p_form_id) sm + ORDER BY sm.section_code; + RETURN; + END IF; + + RAISE EXCEPTION 'Unknown sheet for FORM_4: %', p_sheet + USING HINT = 'Use AHR / CAP / OPER / SMETA / STRUCTURE'; + +END; +$function$ +; + +DROP FUNCTION IF EXISTS v3.expense_item_sort_path(integer[]); + + +-- DROP FUNCTION v3.v_form2_sheet_sections(int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form2_sheet_sections(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, line_id integer, col_section_code character varying, col_item_id character varying, col_num_group_id character varying, col_name character varying, col_vsp_id integer, col_vsp_address character varying, col_plan_q1 numeric, col_plan_q2 numeric, col_plan_q3 numeric, col_plan_q4 numeric, col_plan_year numeric, col_plan_comment character varying, 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 character varying, 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 character varying, col_appr_q1 numeric, col_appr_q2 numeric, col_appr_q3 numeric, col_appr_q4 numeric, col_appr_year numeric, col_cd_counterparty character varying, col_cd_reference character varying, col_cd_date date, col_cd_subject character varying, col_cd_currency character varying, col_cd_ceiling numeric, col_cd_vat_rate character varying, col_cd_deadline character varying, col_cd_scheme character varying, col_cd_act character varying, col_cd_comment character varying, col_book_q1 numeric, col_book_q2 numeric, col_book_q3 numeric, col_book_q4 numeric, col_book_next_q1 numeric, col_book_next_q2 numeric, col_book_next_q3 numeric, col_book_next_q4 numeric, col_q1_adj_current numeric, col_q1_adj_ssp numeric, col_q1_adj_reserve numeric, col_q1_adj_comment character varying, col_q1_corrected_plan numeric, col_q1_pay_date date, col_q1_pay_amount numeric, col_q1_pay_comment character varying, col_q1_pay_act character varying, 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 character varying, col_q1_transfer_q3 numeric, col_q1_transfer_q4 numeric, col_q1_transfer_far_comment character varying, col_q1_transfer_econ numeric, col_q1_total numeric, col_q2_target_change numeric, col_q2_base_correction numeric, col_q2_base_correction_comment character varying, col_q2_revision_inc numeric, col_q2_revision_seq numeric, col_q2_revision_comment character varying, col_q2_new_plan numeric, col_q2_adj_current numeric, col_q2_adj_ssp numeric, col_q2_adj_reserve numeric, col_q2_adj_comment character varying, col_q2_corrected_plan numeric, col_q2_pay_date date, col_q2_pay_amount numeric, col_q2_pay_comment character varying, col_q2_pay_act character varying, 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 character varying, col_q2_transfer_q4 numeric, col_q2_transfer_far_comment character varying, col_q2_transfer_econ numeric, col_q2_total numeric, col_q3_target_change numeric, col_q3_base_correction numeric, col_q3_base_correction_comment character varying, col_q3_revision_inc numeric, col_q3_revision_seq numeric, col_q3_revision_comment character varying, col_q3_new_plan numeric, col_q3_adj_current numeric, col_q3_adj_ssp numeric, col_q3_adj_reserve numeric, col_q3_adj_comment character varying, col_q3_corrected_plan numeric, col_q3_pay_date date, col_q3_pay_amount numeric, col_q3_pay_comment character varying, col_q3_pay_act character varying, 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 character varying, col_q3_transfer_econ numeric, col_q3_total numeric, col_q4_target_change numeric, col_q4_base_correction numeric, col_q4_base_correction_comment character varying, col_q4_revision_inc numeric, col_q4_revision_seq numeric, col_q4_revision_comment character varying, col_q4_new_plan numeric, col_q4_adj_current numeric, col_q4_adj_ssp numeric, col_q4_adj_reserve numeric, col_q4_adj_comment character varying, col_q4_corrected_plan numeric, col_q4_pay_date date, col_q4_pay_amount numeric, col_q4_pay_comment character varying, col_q4_pay_act character varying, 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, col_fact_year numeric, col_pay_year numeric, col_economy_year numeric, col_internal_order character varying, col_cs_total numeric, col_cs_counterparty character varying, col_cs_deadline character varying, col_cs_comment character varying, col_cs_future_y1 numeric, col_cs_future_y2 numeric, col_cs_other_ssp numeric, col_al_contract_ref character varying, col_al_purpose character varying, col_al_order character varying, col_al_property character varying, col_ca_approved numeric, col_ca_protocol character varying, col_ca_note character varying, col_ckk_ceiling numeric, col_ckk_rf_sched character varying, col_ckk_deadline character varying, col_ckk_proc_plan character varying, col_ckk_proc_meth character varying, col_ckk_comment character varying, col_cd_addenda character varying, col_cd_exp_q1 numeric, col_cd_exp_q2 numeric, col_cd_exp_q3 numeric, col_cd_exp_q4 numeric, col_cd_rf_sched character varying, col_cd_exchange_rate numeric, col_cd_amount_foreign numeric, 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 integer[]) + LANGUAGE plpgsql + STABLE +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, + p.parent_item_id AS grandparent_item_id + FROM v3.mv_expense_item_tree t + LEFT JOIN v3.mv_expense_item_tree p ON p.id = t.parent_id + 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 + ), + 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_base AS ( + SELECT + pg.lid, pg.eid, pg.bname, pg.vsp_id, + v.address AS vsp_addr, + CASE + WHEN t.depth < 3 THEN t.item_id + WHEN t.depth = 3 THEN t.parent_item_id + WHEN t.depth = 4 THEN t.grandparent_item_id + END AS sc, + t.item_id AS ic, t.num_group_id AS ng, t.name AS ename, t.path AS tree_path, + 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, + 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, + 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, + 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, + 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, + pg.io AS io, + 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, + al.contract_ref AS al_cref, al.allocation_purpose AS al_purp, + al.internal_order AS al_io, al.property_object AS al_prop, + ca.approved_amount AS ca_amt, ca.protocol_reference AS ca_proto, ca.note AS ca_note, + 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, + 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, + 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.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, + COALESCE( + q1.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q1 + WHEN ck.expenses_q1 > 0 THEN ck.expenses_q1 + ELSE cd.expenses_q1 + END + + ) 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.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, + COALESCE( + q2.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q2 + WHEN ck.expenses_q2 > 0 THEN ck.expenses_q2 + ELSE cd.expenses_q2 + END + + ) 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.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, + COALESCE( + q3.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q3 + WHEN ck.expenses_q3 > 0 THEN ck.expenses_q3 + ELSE cd.expenses_q3 + END + + ) 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.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, + COALESCE( + q4.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q4 + WHEN ck.expenses_q4 > 0 THEN ck.expenses_q4 + ELSE cd.expenses_q4 + END + + ) 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, + 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 + ), + enriched AS ( + SELECT b.*, + 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, + 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 + ), + + 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(cd.expenses_q1) AS bkc1, SUM(cd.expenses_q2) AS bkc2, SUM(cd.expenses_q3) AS bkc3, SUM(cd.expenses_q4) AS bkc4, + 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 + LEFT JOIN v3.contract_detail cd ON cd.line_id = bl.id AND s_cd + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (s_need_ap OR s_book OR s_cd) + GROUP BY bl.expense_item_id + ), + aq_q AS ( + SELECT bl.expense_item_id AS eid, qrtrs.qrtr as 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( + COALESCE( + q.booking_amount, + CASE + WHEN qrtrs.qrtr = 1 + THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q1 + WHEN ck.expenses_q1 > 0 THEN ck.expenses_q1 + ELSE cd.expenses_q1 + END + WHEN qrtrs.qrtr = 2 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q2 + WHEN ck.expenses_q2 > 0 THEN ck.expenses_q2 + ELSE cd.expenses_q2 + END + WHEN qrtrs.qrtr = 3 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q3 + WHEN ck.expenses_q3 > 0 THEN ck.expenses_q3 + ELSE cd.expenses_q3 + END + WHEN qrtrs.qrtr = 4 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q4 + WHEN ck.expenses_q4 > 0 THEN ck.expenses_q4 + ELSE cd.expenses_q4 + END + END + + )) 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, + 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 + CROSS JOIN (VALUES (1), (2), (3), (4)) AS qrtrs(qrtr) + + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.budget_line_quarter q ON q.line_id = bl.id AND q.quarter = qrtrs.qrtr + LEFT JOIN v3.ckk ck ON bl.id = ck.line_id + LEFT JOIN v3.contract_detail cd ON bl.id = cd.line_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + GROUP BY bl.expense_item_id, qrtrs.qrtr + ), + 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.bkc1) AS bkc1, SUM(a.bkc2) AS bkc2, SUM(a.bkc3) AS bkc3, SUM(a.bkc4) AS bkc4, + 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 + ) + + SELECT * FROM ( + SELECT + 'INPUT'::VARCHAR, array_length(f.tree_path, 1)::INT, f.lid::INT, + f.sc, f.ic, f.ng, COALESCE(f.bname, f.ename), f.vsp_id, f.vsp_addr, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + CASE WHEN s_q4 THEN f.q4_te END, + 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, + CASE WHEN s_tot THEN f.cp4 - f.aq4 END, + + 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 + + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' WHEN 4 THEN 'SUB_SUB_ITEM' END::VARCHAR, + t.depth, NULL::INT, + CASE + WHEN t.depth < 3 THEN t.item_id + WHEN t.depth = 3 THEN t.parent_item_id + WHEN t.depth = 4 THEN t.grandparent_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, + 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, + 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, + 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, + 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, + NULL::VARCHAR, NULL::VARCHAR, NULL::DATE, NULL::VARCHAR, + NULL::VARCHAR, NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + CASE WHEN s_q3 THEN + COALESCE(tb3.tda,0)+COALESCE(tb3.tdp,0)+COALESCE(tb3.terf,0) + +COALESCE(tb3.te,0) + END, + 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, + 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, + 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, + CASE WHEN s_q4 THEN tb4.te END, + 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, + 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, + + 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, CASE WHEN s_cd THEN ta.bkc1 END, CASE WHEN s_cd THEN ta.bkc2 END, + CASE WHEN s_cd THEN ta.bkc3 END, CASE WHEN s_cd THEN ta.bkc4 END, + 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$ +; + +CREATE OR REPLACE FUNCTION v3.upd_form_cells(p_form_id integer, p_sheet character varying, p_changes jsonb, p_direction character varying DEFAULT NULL::character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_change JSONB; + v_line_id INT; + v_column TEXT; + v_value JSONB; + v_changed_lines INT[] := ARRAY[]::INT[]; + v_before JSONB := '{}'::jsonb; + v_lc_path INT[]; + v_eid_set INT[]; + v_ei_path INT[]; +BEGIN + IF p_changes IS NULL OR jsonb_typeof(p_changes) <> 'array' THEN + RAISE EXCEPTION 'p_changes must be a JSONB array'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM v3.budget_form WHERE id = p_form_id) THEN + RAISE EXCEPTION 'budget_form id=% не существует', p_form_id; + END IF; + + FOR v_change IN SELECT * FROM jsonb_array_elements(p_changes) + LOOP + v_line_id := (v_change->>'line_id')::INT; + v_column := v_change->>'column'; + v_value := v_change->'value'; + + IF v_line_id IS NULL OR v_column IS NULL THEN + RAISE EXCEPTION 'change must have line_id and column: %', v_change; + END IF; + + IF p_sheet = 'AHR_LIMIT' THEN + IF NOT EXISTS (SELECT 1 FROM v3.limit_template lt + WHERE lt.id = v_line_id AND lt.row_type = 'LEAF') THEN + RAISE EXCEPTION 'limit_template.id=% не существует или не LEAF', v_line_id; + END IF; + ELSIF p_sheet = 'OTCH9F' THEN + IF NOT EXISTS (SELECT 1 FROM v3.fixed_asset_report + WHERE id = v_line_id AND budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'fixed_asset_report.id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSIF p_sheet = 'AHR_RENT' THEN + IF NOT EXISTS (SELECT 1 FROM v3.rent_detail rd + JOIN v3.budget_line bl ON bl.id = rd.line_id + WHERE rd.id = v_line_id AND bl.budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'rent_detail id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSIF p_sheet = 'AHR_UTILITY' THEN + IF NOT EXISTS (SELECT 1 FROM v3.utility_detail ud + JOIN v3.budget_line bl ON bl.id = ud.line_id + WHERE ud.id = v_line_id AND bl.budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'utility_detail id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSIF p_sheet = 'AHR_SECURITY' THEN + IF NOT EXISTS (SELECT 1 FROM v3.security_detail sd + JOIN v3.budget_line bl ON bl.id = sd.line_id + WHERE sd.id = v_line_id AND bl.budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'security_detail id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSE + IF NOT EXISTS (SELECT 1 FROM v3.budget_line + WHERE id = v_line_id AND budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'budget_line id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + END IF; + + IF NOT (v_line_id = ANY(v_changed_lines)) THEN + v_changed_lines := v_changed_lines || v_line_id; + END IF; + END LOOP; + + IF p_sheet = 'AHR_LIMIT' THEN + v_lc_path := v3._limit_template_path_set(v_changed_lines); + ELSIF p_sheet NOT IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY','OTCH9F') THEN + SELECT array_agg(DISTINCT bl.expense_item_id) + INTO v_eid_set + FROM v3.budget_line bl + WHERE bl.id = ANY(v_changed_lines); + v_ei_path := v3._expense_item_path_set(v_eid_set); + END IF; + + IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE v.row_type = 'INPUT' AND (v.data->>'id')::INT = ANY(v_changed_lines); + ELSIF p_sheet = 'AHR_LIMIT' THEN + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.data->>'id')::INT = ANY(v_lc_path); + ELSIF p_sheet = 'OTCH9F' THEN + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE EXISTS ( + SELECT 1 FROM v3.fixed_asset_report far + WHERE far.id = ANY(v_changed_lines) + AND far.expense_item_id = (v.data->>'expense_item_id')::INT + AND far.equipment_name IS NOT DISTINCT FROM (v.data->>'equipment_name')); + ELSE + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.row_type IN ('ROOT','GROUP','ITEM','SUB_ITEM','SUB_SUB_ITEM') + AND (v.data->'header'->>'expense_item_id')::INT = ANY(v_ei_path)) + OR (v.row_type = 'INPUT' + AND (v.data->>'line_id')::INT = ANY(v_changed_lines)); + END IF; + + FOR v_change IN SELECT * FROM jsonb_array_elements(p_changes) + LOOP + PERFORM v3._apply_form_cell( + (v_change->>'line_id')::INT, + v_change->>'column', + v_change->'value', + p_sheet, + p_form_id); + END LOOP; + + IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE v.row_type = 'INPUT' AND (v.data->>'id')::INT = ANY(v_changed_lines) + ) + SELECT a.row_type, a.depth, a.sort_order, + a.diff || jsonb_build_object('id', (a.data->>'id')::INT) + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + + ELSIF p_sheet = 'AHR_LIMIT' THEN + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.data->>'id')::INT = ANY(v_lc_path) + ) + SELECT a.row_type, a.depth, a.sort_order, + a.diff || jsonb_build_object('id', (a.data->>'id')::INT) + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + + ELSIF p_sheet = 'OTCH9F' THEN + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE EXISTS ( + SELECT 1 FROM v3.fixed_asset_report far + WHERE far.id = ANY(v_changed_lines) + AND far.expense_item_id = (v.data->>'expense_item_id')::INT + AND far.equipment_name IS NOT DISTINCT FROM (v.data->>'equipment_name')) + ) + SELECT a.row_type, a.depth, a.sort_order, + a.diff || jsonb_build_object('id', (a.data->>'id')::INT) + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + + ELSE + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.row_type IN ('ROOT','GROUP','ITEM','SUB_ITEM','SUB_SUB_ITEM') + AND (v.data->'header'->>'expense_item_id')::INT = ANY(v_ei_path)) + OR (v.row_type = 'INPUT' + AND (v.data->>'line_id')::INT = ANY(v_changed_lines)) + ) + SELECT a.row_type, a.depth, a.sort_order, + CASE + WHEN a.row_type = 'INPUT' THEN + a.diff || jsonb_build_object('line_id', (a.data->>'line_id')::INT) + ELSE + a.diff || jsonb_build_object('header', + COALESCE(a.diff->'header', '{}'::jsonb) + || jsonb_build_object('expense_item_id', + (a.data->'header'->>'expense_item_id')::INT)) + END + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + END IF; +END; +$function$ +; + +CREATE OR REPLACE FUNCTION v3.upd_form_cells(p_form_id integer, p_sheet character varying, p_changes jsonb, p_direction character varying DEFAULT NULL::character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_change JSONB; + v_line_id INT; + v_column TEXT; + v_value JSONB; + v_changed_lines INT[] := ARRAY[]::INT[]; + v_before JSONB := '{}'::jsonb; + v_lc_path INT[]; + v_eid_set INT[]; + v_ei_path INT[]; +BEGIN + IF p_changes IS NULL OR jsonb_typeof(p_changes) <> 'array' THEN + RAISE EXCEPTION 'p_changes must be a JSONB array'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM v3.budget_form WHERE id = p_form_id) THEN + RAISE EXCEPTION 'budget_form id=% не существует', p_form_id; + END IF; + + FOR v_change IN SELECT * FROM jsonb_array_elements(p_changes) + LOOP + v_line_id := (v_change->>'line_id')::INT; + v_column := v_change->>'column'; + v_value := v_change->'value'; + + IF v_line_id IS NULL OR v_column IS NULL THEN + RAISE EXCEPTION 'change must have line_id and column: %', v_change; + END IF; + + IF p_sheet = 'AHR_LIMIT' THEN + IF NOT EXISTS (SELECT 1 FROM v3.limit_template lt + WHERE lt.id = v_line_id AND lt.row_type = 'LEAF') THEN + RAISE EXCEPTION 'limit_template.id=% не существует или не LEAF', v_line_id; + END IF; + ELSIF p_sheet = 'OTCH9F' THEN + IF NOT EXISTS (SELECT 1 FROM v3.fixed_asset_report + WHERE id = v_line_id AND budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'fixed_asset_report.id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSIF p_sheet = 'AHR_RENT' THEN + IF NOT EXISTS (SELECT 1 FROM v3.rent_detail rd + JOIN v3.budget_line bl ON bl.id = rd.line_id + WHERE rd.id = v_line_id AND bl.budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'rent_detail id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSIF p_sheet = 'AHR_UTILITY' THEN + IF NOT EXISTS (SELECT 1 FROM v3.utility_detail ud + JOIN v3.budget_line bl ON bl.id = ud.line_id + WHERE ud.id = v_line_id AND bl.budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'utility_detail id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSIF p_sheet = 'AHR_SECURITY' THEN + IF NOT EXISTS (SELECT 1 FROM v3.security_detail sd + JOIN v3.budget_line bl ON bl.id = sd.line_id + WHERE sd.id = v_line_id AND bl.budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'security_detail id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + ELSE + IF NOT EXISTS (SELECT 1 FROM v3.budget_line + WHERE id = v_line_id AND budget_form_id = p_form_id) THEN + RAISE EXCEPTION 'budget_line id=% не принадлежит budget_form id=%', v_line_id, p_form_id; + END IF; + END IF; + + IF NOT (v_line_id = ANY(v_changed_lines)) THEN + v_changed_lines := v_changed_lines || v_line_id; + END IF; + END LOOP; + + IF p_sheet = 'AHR_LIMIT' THEN + v_lc_path := v3._limit_template_path_set(v_changed_lines); + ELSIF p_sheet NOT IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY','OTCH9F') THEN + SELECT array_agg(DISTINCT bl.expense_item_id) + INTO v_eid_set + FROM v3.budget_line bl + WHERE bl.id = ANY(v_changed_lines); + v_ei_path := v3._expense_item_path_set(v_eid_set); + END IF; + + IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE v.row_type = 'INPUT' AND (v.data->>'id')::INT = ANY(v_changed_lines); + ELSIF p_sheet = 'AHR_LIMIT' THEN + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.data->>'id')::INT = ANY(v_lc_path); + ELSIF p_sheet = 'OTCH9F' THEN + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE EXISTS ( + SELECT 1 FROM v3.fixed_asset_report far + WHERE far.id = ANY(v_changed_lines) + AND far.expense_item_id = (v.data->>'expense_item_id')::INT + AND far.equipment_name IS NOT DISTINCT FROM (v.data->>'equipment_name')); + ELSE + SELECT COALESCE(jsonb_object_agg(v.sort_order::text, v.data), '{}'::jsonb) + INTO v_before + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.row_type IN ('ROOT','GROUP','ITEM','SUB_ITEM','SUB_SUB_ITEM') + AND (v.data->'header'->>'expense_item_id')::INT = ANY(v_ei_path)) + OR (v.row_type = 'INPUT' + AND (v.data->>'line_id')::INT = ANY(v_changed_lines)); + END IF; + + FOR v_change IN SELECT * FROM jsonb_array_elements(p_changes) + LOOP + PERFORM v3._apply_form_cell( + (v_change->>'line_id')::INT, + v_change->>'column', + v_change->'value', + p_sheet, + p_form_id); + END LOOP; + + IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE v.row_type = 'INPUT' AND (v.data->>'id')::INT = ANY(v_changed_lines) + ) + SELECT a.row_type, a.depth, a.sort_order, + a.diff || jsonb_build_object('id', (a.data->>'id')::INT) + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + + ELSIF p_sheet = 'AHR_LIMIT' THEN + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.data->>'id')::INT = ANY(v_lc_path) + ) + SELECT a.row_type, a.depth, a.sort_order, + a.diff || jsonb_build_object('id', (a.data->>'id')::INT) + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + + ELSIF p_sheet = 'OTCH9F' THEN + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE EXISTS ( + SELECT 1 FROM v3.fixed_asset_report far + WHERE far.id = ANY(v_changed_lines) + AND far.expense_item_id = (v.data->>'expense_item_id')::INT + AND far.equipment_name IS NOT DISTINCT FROM (v.data->>'equipment_name')) + ) + SELECT a.row_type, a.depth, a.sort_order, + a.diff || jsonb_build_object('id', (a.data->>'id')::INT) + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + + ELSE + RETURN QUERY + WITH after_rows AS ( + SELECT v.row_type, v.depth, v.sort_order, v.data, + v3._jsonb_diff(v_before->(v.sort_order::text), v.data) AS diff + FROM v3.v_form_view(p_form_id, p_sheet, p_sections, p_direction) v + WHERE (v.row_type IN ('ROOT','GROUP','ITEM','SUB_ITEM','SUB_SUB_ITEM') + AND (v.data->'header'->>'expense_item_id')::INT = ANY(v_ei_path)) + OR (v.row_type = 'INPUT' + AND (v.data->>'line_id')::INT = ANY(v_changed_lines)) + ) + SELECT a.row_type, a.depth, a.sort_order, + CASE + WHEN a.row_type = 'INPUT' THEN + a.diff || jsonb_build_object('line_id', (a.data->>'line_id')::INT) + ELSE + a.diff || jsonb_build_object('header', + COALESCE(a.diff->'header', '{}'::jsonb) + || jsonb_build_object('expense_item_id', + (a.data->'header'->>'expense_item_id')::INT)) + END + FROM after_rows a + WHERE a.diff <> '{}'::jsonb + ORDER BY a.sort_order; + END IF; +END; +$function$ +; + + + +WITH target_groups(item_id, num_group_id) AS ( + VALUES + ('R061015031', '202520001'), + ('R061015031', '202520002'), + ('R061015031', '202520003'), + ('R061015031', '202520004'), + ('R061015034', '202820007'), + ('R061015034', '202820008'), + ('R061015035', '202920009'), + ('R061015035', '202920010'), + ('R061015035', '202920011'), + ('R061015049', '204320018'), + ('R061015049', '204320026'), + ('R061016002', '204620029'), + ('R061016002', '204620030'), + ('R061016002', '204620031'), + ('R061016002', '204620032'), + ('R061016002', '204620033'), + ('R061016002', '204620034'), + ('R061016002', '204620035'), + ('R061016002', '204620036'), + ('R061016002', '204620037'), + ('R061016002', '204620038'), + ('R061016002', '204620039'), + ('R061016002', '204620040'), + ('R061016004', '204720043'), + ('R061016004', '204720044'), + ('R061016004', '204720045'), + ('R061016004', '204720046'), + ('R061017002', '205220049'), + ('R061017002', '205220050'), + ('R061017002', '205220051'), + ('R061017002', '205220052'), + ('R061017002', '205220053'), + ('R061017002', '205220054'), + ('R061017002', '205220055'), + ('R061017004', '205320058'), + ('R061017004', '205320059'), + ('R061017004', '205320060'), + ('R061017004', '205320061'), + ('R061018002', '400140037'), + ('R061018002', '400140039'), + ('R061018002', '400040200'), + ('R061018002', '400040033'), + ('R061018002', '400140051'), + ('R061018002', '400040064'), + ('R061018002', '400040022'), + ('R061018002', '400140043'), + ('R061018002', '400140036'), + ('R061018002', '400040020'), + ('R061018002', '400140035'), + ('R061018002', '400040014'), + ('R061018002', '400040021'), + ('R061018002', '400140038'), + ('R061018002', '400040015'), + ('R061018002', '400040030'), + ('R061018002', '400040003'), + ('R061018002', '400040012'), + ('R061018002', '400140042'), + ('R061018002', '400040001'), + ('R061018002', '400040019'), + ('R061018002', '400040008'), + ('R061018002', '400040002'), + ('R061018002', '400040168'), + ('R061018002', '400040006'), + ('R061018002', '400040005'), + ('R061018002', '400040011'), + ('R061018002', '400040016'), + ('R061018002', '400040018'), + ('R061018002', '400040007'), + ('R061018002', '400040017'), + ('R061018002', '400040034'), + ('R061018002', '400040010'), + ('R061018002', '400240049'), + ('R061018002', '400240045'), + ('R061018002', '400240044'), + ('R061018002', '400240050'), + ('R061018002', '400240048'), + ('R061018002', '400240047'), + ('R061018002', '400240046'), + ('R061018002', '400040028'), + ('R061018002', '400040013'), + ('R061018002', '400040167'), + ('R061018002', '400040026'), + ('R061018002', '400040024'), + ('R061018002', '400040023'), + ('R061018002', '400040032'), + ('R061018002', '400040025'), + ('R061018002', '400040009'), + ('R061018002', '400040004'), + ('R061018002', '400040029'), + ('R061018002', '400040027'), + ('R061018004', '400440066'), + ('R061018004', '400340069'), + ('R061018004', '400440055'), + ('R061018004', '400440056'), + ('R061018004', '400440057'), + ('R061018004', '400440058'), + ('R061018004', '400440063'), + ('R061018004', '400440074'), + ('R061018004', '400440059'), + ('R061018004', '400440068'), + ('R061018004', '400440067'), + ('R061018004', '400440070'), + ('R061018004', '400440071'), + ('R061018004', '400440073'), + ('R061018004', '400440061'), + ('R061018004', '400440062'), + ('R061018004', '400440060'), + ('R061018004', '400440065'), + ('R061018004', '400440053'), + ('R061018004', '400440054'), + ('R061018004', '400440072'), + ('R061018006', '400840149'), + ('R061018006', '400840150'), + ('R061018006', '400740151'), + ('R061018006', '400740152'), + ('R061018006', '400740153'), + ('R061018006', '400740154'), + ('R061018006', '400840155'), + ('R061018006', '400840156'), + ('R061018010', '400940079'), + ('R061018010', '400940080'), + ('R061018010', '400940081'), + ('R061020037', '403740190'), + ('R061020037', '403740191'), + ('R061020037', '403740192'), + ('R061020037', '403740193'), + ('R061020037', '403740194'), + ('R061020039', '403240109'), + ('R061020039', '403240110'), + ('R061020039', '403240111'), + ('R061020039', '403240112'), + ('R061031010', '207220075'), + ('R061031010', '207220076'), + ('R061031011', '207320077'), + ('R061031011', '207320078'), + ('R011031005', '500120001'), + ('R011031005', '500320003'), + ('R011031011', '500220002'), + ('R011031011', '500420004'), + ('R061081011', '212120125'), + ('R061081011', '212120126') +), +parents AS ( + SELECT p.id, p.item_id, p.depth + FROM v3.expense_item p + WHERE p.sheet = 'AHR' + AND p.item_id IN (SELECT DISTINCT item_id FROM target_groups) + AND NULLIF(p.num_group_id, '') IS NULL +), +updated_children AS ( + UPDATE v3.expense_item child + SET parent_id = p.id, + depth = p.depth + 1 + FROM target_groups tg + JOIN parents p ON p.item_id = tg.item_id + WHERE child.sheet = 'AHR' + AND child.item_id = tg.item_id + AND child.num_group_id = tg.num_group_id + RETURNING child.id, child.item_id, child.num_group_id, child.name, child.parent_id, child.depth +) +SELECT count(*) AS updated_children_count +FROM updated_children; + +REFRESH MATERIALIZED VIEW v3.mv_expense_item_tree;