diff --git a/api/alembic/versions/0030_expense_items_fixes.py b/api/alembic/versions/0030_expense_items_fixes.py new file mode 100644 index 0000000..1053f29 --- /dev/null +++ b/api/alembic/versions/0030_expense_items_fixes.py @@ -0,0 +1,15 @@ +from sql_migration import run_sql_file_migration + + +revision = "0030" +down_revision = "0029" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + run_sql_file_migration("0030_expense_items_fixes.sql") + + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/sql/0030_expense_items_fixes.sql b/api/alembic/versions/sql/0030_expense_items_fixes.sql new file mode 100644 index 0000000..f47972e --- /dev/null +++ b/api/alembic/versions/sql/0030_expense_items_fixes.sql @@ -0,0 +1,691 @@ +-- Исправления по файлу "План_исправления_таблиц_статей_расходов (2).docx". +-- В миграции намеренно не удаляются спорные строки листа Смета ПД и спорные +-- FORM_4-привязки из разделов 12.2-12.3 ТЗ. + +CREATE OR REPLACE FUNCTION v3.expense_item_sort_path(p_path integer[]) + RETURNS integer[] + LANGUAGE sql + STABLE +AS $function$ + WITH sibling_order AS ( + SELECT + ei.id, + row_number() OVER ( + PARTITION BY ei.parent_id + ORDER BY + CASE + WHEN ei.item_id ~ '^\d+(\.\d+)*\.?$' + THEN regexp_split_to_array(trim(trailing '.' from ei.item_id), '\.')::integer[] + END NULLS LAST, + CASE + WHEN ei.item_id !~ '^\d+(\.\d+)*\.?$' + THEN ei.item_id + END NULLS LAST, + ei.num_group_id NULLS FIRST, + ei.id + )::integer AS sort_key + FROM v3.expense_item ei + ), + path_item AS ( + SELECT item_id, ordinality + FROM unnest(p_path) WITH ORDINALITY AS u(item_id, ordinality) + ) + SELECT coalesce( + array_agg(coalesce(so.sort_key, pi.item_id) ORDER BY pi.ordinality), + ARRAY[]::integer[] + ) + FROM path_item pi + LEFT JOIN sibling_order so ON so.id = pi.item_id; +$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 + STABLE +AS $function$ +BEGIN + + -- ═══ Основные листы АХР / КВ_П / Операц ════════════════════════════════ + IF p_sheet IN ('AHR','CAP','OPER') THEN + RETURN QUERY + SELECT + j.row_type, + j.depth, + ROW_NUMBER() OVER (ORDER BY v3.expense_item_sort_path(j._sort_path), 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 v3.expense_item_sort_path(j._sort_path), j._sort_path; + RETURN; + END IF; + + -- ═══ АХР_лимит ═════════════════════════════════════════════════════════ + -- SECTION/GROUP-строки агрегируют qty/amount по всем LEAF-потомкам + -- (Excel: J6=SUM(J7:J13) и т.д.). Дерево строится через parent_id. + -- + -- Структура (limit_template) — глобальный каталог нормативов, единый на банк. + -- Значения qty/comment — per-form (form_limit ON budget_form_id = p_form_id). + -- amount_qN — computed по формуле: + -- amount_qN = qty_qN * limit_with_vat * period_factor(unit), где + -- period_factor = 1/4 если unit ~ '/год' + -- 3 если unit ~ '/месяц' (3 месяца в квартале) + -- 1 иначе (по умолчанию /квартал) + IF p_sheet = 'AHR_LIMIT' THEN + RETURN QUERY + WITH RECURSIVE tree AS ( + SELECT id AS root_id, id AS desc_id FROM v3.limit_template + UNION + SELECT t.root_id, child.id + FROM tree t + JOIN v3.limit_template child ON child.parent_id = t.desc_id + ), + leaf_amt AS ( + -- per-LEAF computed amount по формуле; qty берётся из form_limit для p_form_id + SELECT lt.id, + fl.qty_q1, fl.qty_q2, fl.qty_q3, fl.qty_q4, + COALESCE(fl.qty_q1,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q1, + COALESCE(fl.qty_q2,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q2, + COALESCE(fl.qty_q3,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q3, + COALESCE(fl.qty_q4,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q4 + FROM v3.limit_template lt + LEFT JOIN v3.form_limit fl + ON fl.template_id = lt.id AND fl.budget_form_id = p_form_id + WHERE lt.row_type = 'LEAF' + ), + agg AS ( + SELECT t.root_id, + SUM(la.qty_q1) AS qty_q1, SUM(la.amount_q1) AS amount_q1, + SUM(la.qty_q2) AS qty_q2, SUM(la.amount_q2) AS amount_q2, + SUM(la.qty_q3) AS qty_q3, SUM(la.amount_q3) AS amount_q3, + SUM(la.qty_q4) AS qty_q4, SUM(la.amount_q4) AS amount_q4 + FROM tree t + JOIN leaf_amt la ON la.id = t.desc_id + GROUP BY t.root_id + ) + SELECT + -- Маппинг физического хранения limit_template → DTO: + -- SECTION → ROOT, GROUP → GROUP, LEAF → INPUT (редактируемая ячейка). + CASE lt.row_type WHEN 'SECTION' THEN 'ROOT' + WHEN 'GROUP' THEN 'GROUP' + WHEN 'LEAF' THEN 'INPUT' END::VARCHAR, + CASE lt.row_type WHEN 'SECTION' THEN 0 WHEN 'GROUP' THEN 1 ELSE 2 END, + lt.sort_order::BIGINT, + jsonb_build_object( + 'id', lt.id, -- LEAF id для адресации в write API (= template_id) + 'section_no', lt.section_no, + 'expense_item_code',lt.expense_item_code, + 'name', lt.name, + 'unit', lt.unit, + 'limit_with_vat', lt.limit_with_vat, + 'limit_without_vat',lt.limit_without_vat, + 'qty_q1', a.qty_q1, 'amount_q1', a.amount_q1, + 'qty_q2', a.qty_q2, 'amount_q2', a.amount_q2, + 'qty_q3', a.qty_q3, 'amount_q3', a.amount_q3, + 'qty_q4', a.qty_q4, 'amount_q4', a.amount_q4, + 'comment', fl.comment + ) + FROM v3.limit_template lt + LEFT JOIN agg a ON a.root_id = lt.id + LEFT JOIN v3.form_limit fl + ON fl.template_id = lt.id AND fl.budget_form_id = p_form_id + ORDER BY lt.sort_order; + RETURN; + END IF; + + -- ═══ АХР_аренда ════════════════════════════════════════════════════════ + IF p_sheet = 'AHR_RENT' THEN + RETURN QUERY + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY rd.id) AS sort_order, + jsonb_build_object( + 'id', rd.id, -- id rent_detail (для write API) + 'line_id', rd.line_id, + 'vsp_id', rd.vsp_id, + 'address', v.address, + 'object_type', v.format, + 'rented_area', v.total_area, + 'contract_number', rd.contract_number, + 'contract_end_date',rd.contract_end_date, + 'plan', jsonb_build_object('q1', rd.plan_q1, 'q2', rd.plan_q2, 'q3', rd.plan_q3, 'q4', rd.plan_q4, + 'year', COALESCE(rd.plan_q1,0)+COALESCE(rd.plan_q2,0)+COALESCE(rd.plan_q3,0)+COALESCE(rd.plan_q4,0)), + 'fact_q1', jsonb_build_object('jan', rd.actual_jan, 'feb', rd.actual_feb, 'mar', rd.actual_mar, + 'total', COALESCE(rd.actual_jan,0)+COALESCE(rd.actual_feb,0)+COALESCE(rd.actual_mar,0)), + 'fact_q2', jsonb_build_object('apr', rd.actual_apr, 'may', rd.actual_may, 'jun', rd.actual_jun, + 'total', COALESCE(rd.actual_apr,0)+COALESCE(rd.actual_may,0)+COALESCE(rd.actual_jun,0)), + 'fact_q3', jsonb_build_object('jul', rd.actual_jul, 'aug', rd.actual_aug, 'sep', rd.actual_sep, + 'total', COALESCE(rd.actual_jul,0)+COALESCE(rd.actual_aug,0)+COALESCE(rd.actual_sep,0)), + 'fact_q4', jsonb_build_object('oct', rd.actual_oct, 'nov', rd.actual_nov, 'dec', rd.actual_dec, + 'total', COALESCE(rd.actual_oct,0)+COALESCE(rd.actual_nov,0)+COALESCE(rd.actual_dec,0)), + 'comment', rd.comment + ) + FROM v3.rent_detail rd + JOIN v3.budget_line bl ON bl.id = rd.line_id + JOIN v3.vsp v ON v.id = rd.vsp_id + WHERE bl.budget_form_id = p_form_id + ORDER BY rd.id; + RETURN; + END IF; + + -- ═══ АХР_коммунал ═══════════════════════════════════════════════════════ + IF p_sheet = 'AHR_UTILITY' THEN + RETURN QUERY + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY ud.id) AS sort_order, + jsonb_build_object( + 'id', ud.id, -- id utility_detail (для write API) + 'line_id', ud.line_id, + 'vsp_id', ud.vsp_id, + 'address', v.address, + 'object_type', v.format, + 'object_area', v.total_area, + 'contract_number', ud.contract_number, + 'contract_end_date',ud.contract_end_date, + 'plan', jsonb_build_object('q1', ud.plan_q1, 'q2', ud.plan_q2, 'q3', ud.plan_q3, 'q4', ud.plan_q4, + 'year', COALESCE(ud.plan_q1,0)+COALESCE(ud.plan_q2,0)+COALESCE(ud.plan_q3,0)+COALESCE(ud.plan_q4,0)), + 'fact_q1', jsonb_build_object('jan', ud.actual_jan, 'feb', ud.actual_feb, 'mar', ud.actual_mar, + 'total', COALESCE(ud.actual_jan,0)+COALESCE(ud.actual_feb,0)+COALESCE(ud.actual_mar,0)), + 'fact_q2', jsonb_build_object('apr', ud.actual_apr, 'may', ud.actual_may, 'jun', ud.actual_jun, + 'total', COALESCE(ud.actual_apr,0)+COALESCE(ud.actual_may,0)+COALESCE(ud.actual_jun,0)), + 'fact_q3', jsonb_build_object('jul', ud.actual_jul, 'aug', ud.actual_aug, 'sep', ud.actual_sep, + 'total', COALESCE(ud.actual_jul,0)+COALESCE(ud.actual_aug,0)+COALESCE(ud.actual_sep,0)), + 'fact_q4', jsonb_build_object('oct', ud.actual_oct, 'nov', ud.actual_nov, 'dec', ud.actual_dec, + 'total', COALESCE(ud.actual_oct,0)+COALESCE(ud.actual_nov,0)+COALESCE(ud.actual_dec,0)), + 'comment', ud.comment + ) + FROM v3.utility_detail ud + JOIN v3.budget_line bl ON bl.id = ud.line_id + JOIN v3.vsp v ON v.id = ud.vsp_id + WHERE bl.budget_form_id = p_form_id + ORDER BY ud.id; + RETURN; + END IF; + + -- ═══ АХР_охрана ═════════════════════════════════════════════════════════ + IF p_sheet = 'AHR_SECURITY' THEN + RETURN QUERY + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY sd.id) AS sort_order, + jsonb_build_object( + 'id', sd.id, -- id security_detail (для write API) + 'line_id', sd.line_id, + 'vsp_id', sd.vsp_id, + 'address', v.address, + 'object_type', v.format, + 'object_area', v.total_area, + 'contract_number', sd.contract_number, + 'contract_end_date',sd.contract_end_date, + 'plan', jsonb_build_object('q1', sd.plan_q1, 'q2', sd.plan_q2, 'q3', sd.plan_q3, 'q4', sd.plan_q4, + 'year', COALESCE(sd.plan_q1,0)+COALESCE(sd.plan_q2,0)+COALESCE(sd.plan_q3,0)+COALESCE(sd.plan_q4,0)), + 'fact_q1', jsonb_build_object('jan', sd.actual_jan, 'feb', sd.actual_feb, 'mar', sd.actual_mar, + 'total', COALESCE(sd.actual_jan,0)+COALESCE(sd.actual_feb,0)+COALESCE(sd.actual_mar,0)), + 'fact_q2', jsonb_build_object('apr', sd.actual_apr, 'may', sd.actual_may, 'jun', sd.actual_jun, + 'total', COALESCE(sd.actual_apr,0)+COALESCE(sd.actual_may,0)+COALESCE(sd.actual_jun,0)), + 'fact_q3', jsonb_build_object('jul', sd.actual_jul, 'aug', sd.actual_aug, 'sep', sd.actual_sep, + 'total', COALESCE(sd.actual_jul,0)+COALESCE(sd.actual_aug,0)+COALESCE(sd.actual_sep,0)), + 'fact_q4', jsonb_build_object('oct', sd.actual_oct, 'nov', sd.actual_nov, 'dec', sd.actual_dec, + 'total', COALESCE(sd.actual_oct,0)+COALESCE(sd.actual_nov,0)+COALESCE(sd.actual_dec,0)), + 'comment', sd.comment + ) + FROM v3.security_detail sd + JOIN v3.budget_line bl ON bl.id = sd.line_id + JOIN v3.vsp v ON v.id = sd.vsp_id + WHERE bl.budget_form_id = p_form_id + ORDER BY sd.id; + RETURN; + END IF; + + -- ═══ Отч.9ф (Fixed Asset Report) ════════════════════════════════════════ + -- Одна строка = (expense_item × equipment_name × month). + -- + -- opening_qty/amt_604/60415 хранятся в БД ТОЛЬКО для month=1 (база года). + -- Для month=2..12 вычисляются на лету: накопительная сумма дельт по + -- предыдущим месяцам от base (Excel: opening_M2 = F1+H1−J1). + -- + -- totals.* — также computed (acquired_total, disposed_total, balance_qty/amt). + IF p_sheet = 'OTCH9F' THEN + RETURN QUERY + WITH base AS ( + SELECT far.id, far.budget_form_id, far.expense_item_id, + far.equipment_name, far.month, + far.acquired_qty_604, far.acquired_amt_604, + far.disposed_qty_604, far.disposed_amt_604, + far.acquired_qty_60415, far.acquired_amt_60415, + far.transferred_qty_60415, far.transferred_amt_60415, + far.go_balance_only_amt, + -- база: opening_qty/amt из month=1 этой группы + FIRST_VALUE(COALESCE(far.opening_qty_604,0)) + OVER w AS base_qty_604, + FIRST_VALUE(COALESCE(far.opening_amt_604,0)) + OVER w AS base_amt_604, + FIRST_VALUE(COALESCE(far.opening_qty_60415,0)) + OVER w AS base_qty_60415, + FIRST_VALUE(COALESCE(far.opening_amt_60415,0)) + OVER w AS base_amt_60415, + -- кумулятивная дельта по предыдущим месяцам в группе + COALESCE(SUM(COALESCE(far.acquired_qty_604,0) - COALESCE(far.disposed_qty_604,0)) + OVER w_prev, 0) AS cum_dq_604, + COALESCE(SUM(COALESCE(far.acquired_amt_604,0) - COALESCE(far.disposed_amt_604,0)) + OVER w_prev, 0) AS cum_da_604, + COALESCE(SUM(COALESCE(far.acquired_qty_60415,0) - COALESCE(far.transferred_qty_60415,0)) + OVER w_prev, 0) AS cum_dq_60415, + COALESCE(SUM(COALESCE(far.acquired_amt_60415,0) - COALESCE(far.transferred_amt_60415,0)) + OVER w_prev, 0) AS cum_da_60415 + FROM v3.fixed_asset_report far + WHERE far.budget_form_id = p_form_id + WINDOW w AS (PARTITION BY far.budget_form_id, far.expense_item_id, far.equipment_name + ORDER BY far.month), + w_prev AS (PARTITION BY far.budget_form_id, far.expense_item_id, far.equipment_name + ORDER BY far.month + ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) + ), + eff AS ( + SELECT b.*, + (b.base_qty_604 + b.cum_dq_604) AS opening_qty_604_eff, + (b.base_amt_604 + b.cum_da_604) AS opening_amt_604_eff, + (b.base_qty_60415 + b.cum_dq_60415) AS opening_qty_60415_eff, + (b.base_amt_60415 + b.cum_da_60415) AS opening_amt_60415_eff + FROM base b + ) + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY eff.expense_item_id, eff.month) AS sort_order, + jsonb_build_object( + 'id', eff.id, -- fixed_asset_report.id (для write API) + 'expense_item_id', ei.id, + 'item_id', ei.item_id, + 'section_code', ei.section_code, + 'equipment_name', eff.equipment_name, + 'month', eff.month, + 'b_604', jsonb_build_object( + 'opening_qty', eff.opening_qty_604_eff, + 'opening_amt', eff.opening_amt_604_eff, + 'acquired_qty', eff.acquired_qty_604, + 'acquired_amt', eff.acquired_amt_604, + 'disposed_qty', eff.disposed_qty_604, + 'disposed_amt', eff.disposed_amt_604 + ), + 'b_60415', jsonb_build_object( + 'opening_qty', eff.opening_qty_60415_eff, + 'opening_amt', eff.opening_amt_60415_eff, + 'acquired_qty', eff.acquired_qty_60415, + 'acquired_amt', eff.acquired_amt_60415, + 'transferred_qty',eff.transferred_qty_60415, + 'transferred_amt',eff.transferred_amt_60415 + ), + 'totals', jsonb_build_object( + -- Excel R = SUM(I, O, S) − Q + 'acquired_total', COALESCE(eff.acquired_amt_604,0)+COALESCE(eff.acquired_amt_60415,0) + +COALESCE(eff.go_balance_only_amt,0)-COALESCE(eff.transferred_amt_60415,0), + 'go_balance_only',eff.go_balance_only_amt, + -- Excel T = K + 'disposed_total', COALESCE(eff.disposed_amt_604,0), + -- Excel U = (opening+acquired−disposed)_604 + (opening+acquired−transferred)_60415 + 'balance_qty', (eff.opening_qty_604_eff + COALESCE(eff.acquired_qty_604,0) - COALESCE(eff.disposed_qty_604,0)) + + (eff.opening_qty_60415_eff + COALESCE(eff.acquired_qty_60415,0) - COALESCE(eff.transferred_qty_60415,0)), + 'balance_amt', (eff.opening_amt_604_eff + COALESCE(eff.acquired_amt_604,0) - COALESCE(eff.disposed_amt_604,0)) + + (eff.opening_amt_60415_eff + COALESCE(eff.acquired_amt_60415,0) - COALESCE(eff.transferred_amt_60415,0)) + ) + ) + FROM eff + JOIN v3.expense_item ei ON ei.id = eff.expense_item_id + ORDER BY eff.expense_item_id, eff.month; + RETURN; + END IF; + + -- ═══ Смета_2026 ═════════════════════════════════════════════════════════ + IF p_sheet = 'SMETA' THEN + RETURN QUERY + SELECT + sm.row_type, + sm.depth, + ROW_NUMBER() OVER (ORDER BY sm.section_code) AS sort_order, + jsonb_build_object( + 'section_code', sm.section_code, + 'name', sm.name, + 'plan', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.plan_supp_q1, 'q2', sm.plan_supp_q2, 'q3', sm.plan_supp_q3, 'q4', sm.plan_supp_q4, 'year', sm.plan_supp_year), + 'development', jsonb_build_object('q1', sm.plan_dev_q1, 'q2', sm.plan_dev_q2, 'q3', sm.plan_dev_q3, 'q4', sm.plan_dev_q4, 'year', sm.plan_dev_year) + ), + 'approved', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.appr_supp_q1, 'q2', sm.appr_supp_q2, 'q3', sm.appr_supp_q3, 'q4', sm.appr_supp_q4, 'year', sm.appr_supp_year), + 'development', jsonb_build_object('q1', sm.appr_dev_q1, 'q2', sm.appr_dev_q2, 'q3', sm.appr_dev_q3, 'q4', sm.appr_dev_q4, 'year', sm.appr_dev_year) + ), + 'fact', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.fact_supp_q1, 'q2', sm.fact_supp_q2, 'q3', sm.fact_supp_q3, 'q4', sm.fact_supp_q4, 'year', sm.fact_supp_year), + 'development', jsonb_build_object('q1', sm.fact_dev_q1, 'q2', sm.fact_dev_q2, 'q3', sm.fact_dev_q3, 'q4', sm.fact_dev_q4, 'year', sm.fact_dev_year) + ), + -- corrected: только Q2/Q3/Q4 (см. шаблон Смета_2026, блок V-X). Q1 нет + -- (Q1 corrected ≡ approved_q1), year нет (q1+q2+q3+q4 даёт двойной счёт переносов). + 'corrected', jsonb_build_object( + 'support', jsonb_build_object('q2', sm.corr_supp_q2, 'q3', sm.corr_supp_q3, 'q4', sm.corr_supp_q4), + 'development', jsonb_build_object('q2', sm.corr_dev_q2, 'q3', sm.corr_dev_q3, 'q4', sm.corr_dev_q4) + ) + ) + FROM v3.v_form2_smeta(p_form_id) sm + ORDER BY sm.section_code; + RETURN; + END IF; + + RAISE EXCEPTION 'Unknown sheet: %', p_sheet + USING HINT = 'Use AHR / CAP / OPER / AHR_LIMIT / AHR_RENT / AHR_UTILITY / AHR_SECURITY / OTCH9F / SMETA'; + +END; +$function$ +; + +-- Общие исправления справочника. + + +update v3.expense_item +set name = 'Взносы в орг/ассоц' +where item_id = 'R061141000' + and num_group_id = '214920158'; + +update v3.expense_item +set name = 'Бланк ден/чек книжМЦ' +where item_id = 'R064040032' + and num_group_id = '402640197'; + +update v3.expense_item +set name = 'Другие операционные доходы' +where item_id = 'R014040037' + and num_group_id is null; + + +with renamed(id, name) as ( + values + (32, 'Физическая охрана объектов недвижимости'), + (33, 'Пультовая охрана объектов недвижимости'), + (34, 'Охрана инкассаторских перевозок'), + (35, 'Охрана имущества при его транспортировке'), + (36, 'Пультовая охрана устройств самообслуживания'), + (37, 'Техническое обслуживание и эксплуатация: оборудование систем охраны объектов недвижимости'), + (38, 'Ремонт: оборудование систем охраны объектов недвижимости'), + (57, 'Оценка объектов недвижимости'), + (58, 'Оценка прочих активов'), + (59, 'Расходы на исследования и разработки') +) +update v3.expense_item ei +set name = renamed.name +from renamed +where ei.id = renamed.id or ei.parent_id = renamed.id; + +update v3.expense_item +set depth = 3 +where parent_id = 766 + and item_id in ( + 'R065010000', 'R065020001', 'R065030012', 'R065040000', + '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, + depth = 3 +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), + (801, 'OPER', 'R064040044', '222420186', 'Изгот банк карт', 'OPER', 'Support', 717, 3), + (802, 'OPER', 'R064040036', '222620188', 'Госпошл_рег ипотеки', 'OPER', 'Support', 719, 3), + (803, 'OPER', 'R064040038', '223320190', 'Услуги п/размещ УС', 'OPER', 'Support', 720, 3), + (804, 'OPER', 'R064040039', null, 'Нотариальные и пр. аналогичные расходы по бизнесу', 'OPER', 'Support', 721, 3), + (805, 'OPER', 'R064040040', null, 'Штрафы за нарушения условий хозяйственных договоров', 'OPER', 'Support', 722, 3), + (806, 'OPER', 'R064040041', null, 'Расходы по списанию недостач материальных ценностей', 'OPER', 'Support', 723, 3), + (807, 'OPER', 'R064040042', null, 'Расходы по списанию дебиторской задолженности', 'OPER', 'Support', 724, 3), + (808, 'OPER', 'R064040043', null, 'Расходы от передачи активов в доверительное управление', 'OPER', 'Support', 725, 3) +on conflict (id) do update +set section_code = excluded.section_code, + item_id = excluded.item_id, + num_group_id = excluded.num_group_id, + name = excluded.name, + sheet = excluded.sheet, + direction = excluded.direction, + 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'), + (802, 'FORM_1'), (802, 'FORM_2'), (802, 'FORM_4'), + (803, 'FORM_1'), (803, 'FORM_2'), (803, 'FORM_4'), + (804, 'FORM_1'), (804, 'FORM_2'), (804, 'FORM_4'), + (805, 'FORM_1'), (805, 'FORM_2'), (805, 'FORM_4'), + (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) +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 +where item_id in ( + 'R065010000', 'R065020001', 'R065030012', 'R065040000', + 'R065050005', 'R065070000', 'R065090000', 'R065100000' +) +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 ( + expense_item_id = 70 + or expense_item_id in (746, 747, 760, 766) + or expense_item_id between 748 and 759 + or expense_item_id between 761 and 765 + 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 ( + expense_item_id = 70 + or expense_item_id in (746, 747, 760) + or expense_item_id between 748 and 759 + 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 + and ( + l.expense_item_id = 70 + or l.expense_item_id in (746, 747, 760) + or l.expense_item_id between 748 and 759 + 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) + or expense_item_id between 748 and 759 + or expense_item_id between 761 and 765; + +with target_items(expense_item_id) as ( + values (74), (495) +) +insert into v3.rf_project_report_line (rf_project_report_id, expense_item_id) +select r.id, ti.expense_item_id +from v3.rf_project_report r +cross join target_items ti +where not exists ( + select 1 + from v3.rf_project_report_line l + where l.rf_project_report_id = r.id + and l.expense_item_id = ti.expense_item_id +); + + +-- 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) + 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 v3.expense_item_sort_path(j._sort_path), + 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 v3.expense_item_sort_path(j._sort_path), + 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$ +; + +select setval('v3.expense_item_id_seq', greatest(coalesce((select max(id) from v3.expense_item), 1), 808)); +select setval('v3.rf_project_report_line_id_seq', greatest(coalesce((select max(id) from v3.rf_project_report_line), 1), 1)); +refresh materialized view v3.mv_expense_item_tree;