diff --git a/api/alembic/env.py b/api/alembic/env.py index 36112a3..bf37d10 100644 --- a/api/alembic/env.py +++ b/api/alembic/env.py @@ -1,9 +1,12 @@ +import asyncio from logging.config import fileConfig -from sqlalchemy import engine_from_config -from sqlalchemy import pool - from alembic import context +from sqlalchemy import pool +from sqlalchemy.ext.asyncio import create_async_engine + +from src.core.config import settings +from src.db.base import Base # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -14,62 +17,47 @@ config = context.config if config.config_file_name is not None: fileConfig(config.config_file_name) -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = None - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. +target_metadata = Base.metadata def run_migrations_offline() -> None: - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") + url = settings.DATABASE_URL context.configure( url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, + include_schemas=True, + version_table_schema="v3", ) - with context.begin_transaction(): context.run_migrations() -def run_migrations_online() -> None: - """Run migrations in 'online' mode. +def do_run_migrations(connection): + context.configure( + connection=connection, + target_metadata=target_metadata, + include_schemas=True, + version_table_schema="v3", + ) + with context.begin_transaction(): + context.execute(f"CREATE SCHEMA IF NOT EXISTS v3") + context.run_migrations() - In this scenario we need to create an Engine - and associate a connection with the context. - """ - connectable = engine_from_config( - config.get_section(config.config_ini_section, {}), - prefix="sqlalchemy.", +async def run_async_migrations() -> None: + connectable = create_async_engine( + settings.DATABASE_URL, poolclass=pool.NullPool, ) + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + await connectable.dispose() - with connectable.connect() as connection: - context.configure( - connection=connection, target_metadata=target_metadata - ) - with context.begin_transaction(): - context.run_migrations() +def run_migrations_online() -> None: + asyncio.run(run_async_migrations()) if context.is_offline_mode(): diff --git a/api/alembic/versions/0001_initial_schema.py b/api/alembic/versions/0001_initial_schema.py new file mode 100644 index 0000000..7872039 --- /dev/null +++ b/api/alembic/versions/0001_initial_schema.py @@ -0,0 +1,1038 @@ +"""initial schema — tables and sequences + +Revision ID: 0001 +Revises: +Create Date: 2026-06-22 +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision = "0001" +down_revision = None +branch_labels = None +depends_on = None + +def create_table_if_not_exists(table_name, *columns, **kwargs): + conn = op.get_bind() + inspector = sa.inspect(conn) + + if table_name not in inspector.get_table_names(schema="v3"): + op.create_table(table_name, *columns, **kwargs) + return True + return False + + +def create_index_if_not_exists(index_name, table_name, columns, **kwargs): + conn = op.get_bind() + inspector = sa.inspect(conn) + existing_indexes = inspector.get_indexes(table_name, schema="v3") + + if not any(idx['name'] == index_name for idx in existing_indexes): + op.create_index(index_name, table_name, columns, **kwargs) + return True + return False + + +def create_check_constraint_if_not_exists( + constraint_name, + table_name, + condition, + schema=None, +): + conn = op.get_bind() + inspector = sa.inspect(conn) + + existing_constraints = inspector.get_check_constraints( + table_name, + schema=schema, + ) + + constraint_exists = any( + c['name'] == constraint_name + for c in existing_constraints + ) + + if not constraint_exists: + op.create_check_constraint( + constraint_name, + table_name, + condition, + schema=schema, + ) + return True + else: + return False + + +def create_unique_constraint_if_not_exists( + constraint_name, + table_name, + columns, + schema=None, +): + conn = op.get_bind() + inspector = sa.inspect(conn) + + existing_constraints = inspector.get_unique_constraints( + table_name, + schema=schema, + ) + + constraint_exists = any( + c['name'] == constraint_name + for c in existing_constraints + ) + + if not constraint_exists: + op.create_unique_constraint( + constraint_name, + table_name, + columns, + schema=schema, + ) + return True + else: + return False + + +def upgrade() -> None: + op.execute("CREATE SCHEMA IF NOT EXISTS v3") + create_table_if_not_exists( + 'allocation', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('internal_order', sa.String()), + sa.Column('property_object', sa.String()), + sa.Column('contract_ref', sa.String()), + sa.Column('allocation_purpose', sa.String()), + schema='v3', + ) + + create_table_if_not_exists( + 'app_user', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('email', sa.String(), + nullable=False), + sa.Column('username', sa.String(), + nullable=False), + sa.Column('hashed_password', sa.String(), + nullable=False), + sa.Column('full_name', sa.String()), + sa.Column('role_id', sa.Integer(), + nullable=False), + sa.Column('is_active', sa.Boolean(), + nullable=False), + sa.Column('created_at', sa.DateTime(timezone=True), + nullable=False, + server_default=sa.func.now()), + sa.Column('updated_at', sa.DateTime(timezone=True)), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v4_app_user_active', 'app_user', ['is_active'], + schema='v3', + postgresql_where='is_active', + ) + create_index_if_not_exists( + 'ix_v4_app_user_role', 'app_user', ['role_id'], + schema='v3', + ) + create_table_if_not_exists( + 'audit_log', + sa.Column('id', sa.BigInteger(), + primary_key=True, + autoincrement=True), + sa.Column('user_id', sa.Integer()), + sa.Column('org_unit_id', sa.Integer()), + sa.Column('task_id', sa.Integer()), + sa.Column('form_id', sa.Integer()), + sa.Column('event_dt', sa.DateTime(timezone=True), + nullable=False, + server_default=sa.func.now()), + sa.Column('event', sa.String(), + nullable=False), + sa.Column('event_type', sa.String(), + nullable=False), + sa.Column('event_data', sa.JSON()), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_audit_log_form', 'audit_log', ['form_id'], + schema='v3', + postgresql_where='form_id IS NOT NULL', + ) + create_index_if_not_exists( + 'ix_v3_audit_log_event', 'audit_log', ['event'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v4_audit_log_dt', 'audit_log', ['event_dt'], + postgresql_ops={'event_dt': 'DESC'}, + schema='v3', + ) + create_index_if_not_exists( + 'ix_v4_audit_log_event', 'audit_log', ['event'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v4_audit_log_form', 'audit_log', ['form_id'], + schema='v3', + postgresql_where='form_id IS NOT NULL', + ) + create_index_if_not_exists( + 'ix_v3_audit_log_user', 'audit_log', ['user_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v4_audit_log_user', 'audit_log', ['user_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_audit_log_dt', 'audit_log', ['event_dt'], + postgresql_ops={'event_dt': 'DESC'}, + schema='v3', + ) + create_table_if_not_exists( + 'booking', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('line_id', sa.Integer(), + nullable=False), + sa.Column('quarter', sa.SmallInteger(), + nullable=False), + sa.Column('source', sa.String(), + nullable=False), + sa.Column('booked_amount_ckk', sa.Numeric()), + sa.Column('booked_amount_contract', sa.Numeric()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'ck_v3_booking_quarter', 'booking', + sa.text('quarter BETWEEN 1 AND 4'), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'ck_v3_booking_source', 'booking', + sa.text("source IN ('CKK','CONTRACT')"), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_booking_line', 'booking', ['line_id'], + schema='v3', + ) + create_table_if_not_exists( + 'budget_form', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('form_type_code', sa.String(), + nullable=False), + sa.Column('year', sa.Integer()), + sa.Column('created_by', sa.Integer()), + sa.Column('created_at', sa.DateTime(), + server_default=sa.func.now() + ), + sa.Column('updated_by', sa.Integer()), + sa.Column('updated_at', sa.DateTime(), + server_default=sa.func.now(), + onupdate=sa.func.now(), + ), + sa.Column('org_unit_id', sa.Integer()), + schema='v3', + ) + + create_table_if_not_exists( + 'budget_line', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('budget_form_id', sa.Integer(), + nullable=False), + sa.Column('expense_item_id', sa.Integer()), + sa.Column('name', sa.String()), + sa.Column('internal_order', sa.String()), + sa.Column('vsp_id', sa.Integer()), + sa.Column('project_id', sa.Integer()), + sa.Column('justification', sa.String()), + sa.Column('created_by', sa.Integer()), + sa.Column('created_at', sa.DateTime()), + sa.Column('updated_by', sa.Integer()), + sa.Column('updated_at', sa.DateTime()), + sa.Column('direction', sa.String()), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_budget_line_vsp', 'budget_line', ['vsp_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_budget_line_form_item', 'budget_line', ['budget_form_id', 'expense_item_id'], + schema='v3', + ) + create_table_if_not_exists( + 'budget_line_quarter', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('quarter', sa.SmallInteger(), + primary_key=True), + sa.Column('adj_current', sa.Numeric()), + sa.Column('adj_ssp', sa.Numeric()), + sa.Column('adj_rf', sa.Numeric()), + sa.Column('adj_reserve', sa.Numeric()), + sa.Column('adj_comment', sa.String()), + sa.Column('target_change', sa.Numeric()), + sa.Column('base_plan_correction', sa.Numeric()), + sa.Column('base_plan_correction_comment', sa.String()), + sa.Column('payment_date', sa.Date()), + sa.Column('payment_amount', sa.Numeric()), + sa.Column('payment_amount_ho', sa.Numeric()), + sa.Column('payment_amount_rf', sa.Numeric()), + sa.Column('payment_comment', sa.String()), + sa.Column('payment_act', sa.String()), + sa.Column('actual_m1', sa.Numeric()), + sa.Column('actual_m2', sa.Numeric()), + sa.Column('actual_m3', sa.Numeric()), + sa.Column('actual_spod', sa.Numeric()), + sa.Column('transfer_to_q2', sa.Numeric()), + sa.Column('transfer_to_q3', sa.Numeric()), + sa.Column('transfer_to_q4', sa.Numeric()), + sa.Column('transfer_to_economy', sa.Numeric()), + sa.Column('transfer_delay_acts', sa.Numeric()), + sa.Column('transfer_delay_procurement', sa.Numeric()), + sa.Column('transfer_economy_rf', sa.Numeric()), + sa.Column('transfer_next_comment', sa.String()), + sa.Column('transfer_far_comment', sa.String()), + sa.Column('booking_amount', sa.Numeric()), + sa.Column('plan_revision_eco_change', sa.Numeric()), + sa.Column('plan_revision_item_adj', sa.Numeric()), + sa.Column('plan_revision_increase', sa.Numeric()), + sa.Column('plan_revision_sequester', sa.Numeric()), + sa.Column('plan_revision_comment', sa.String()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'chk_budget_line_quarter_quarter', 'budget_line_quarter', + sa.text('quarter >= 1 AND quarter <= 4'), + schema='v3', + ) + create_table_if_not_exists( + 'ckk', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('ceiling_amount', sa.Numeric()), + sa.Column('expenses_q1', sa.Numeric()), + sa.Column('expenses_q2', sa.Numeric()), + sa.Column('expenses_q3', sa.Numeric()), + sa.Column('expenses_q4', sa.Numeric()), + sa.Column('expenses_next_year_q1', sa.Numeric()), + sa.Column('expenses_next_year_q2', sa.Numeric()), + sa.Column('expenses_next_year_q3', sa.Numeric()), + sa.Column('expenses_next_year_q4', sa.Numeric()), + sa.Column('rf_schedule', sa.String()), + sa.Column('delivery_deadline', sa.String()), + sa.Column('procurement_plan', sa.String()), + sa.Column('procurement_method', sa.String()), + sa.Column('comment', sa.String()), + schema='v3', + ) + + create_table_if_not_exists( + 'collegial_approval', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('approved_amount', sa.Numeric()), + sa.Column('protocol_reference', sa.String()), + sa.Column('note', sa.String()), + schema='v3', + ) + + create_table_if_not_exists( + 'contract_detail', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('counterparty', sa.String()), + sa.Column('reference', sa.String()), + sa.Column('addenda', sa.String()), + sa.Column('subject', sa.String()), + sa.Column('currency', sa.String()), + sa.Column('ceiling_amount', sa.Numeric()), + sa.Column('expenses_q1', sa.Numeric()), + sa.Column('expenses_q2', sa.Numeric()), + sa.Column('expenses_q3', sa.Numeric()), + sa.Column('expenses_q4', sa.Numeric()), + sa.Column('rf_schedule', sa.String()), + sa.Column('vat_rate', sa.String()), + sa.Column('exchange_rate', sa.Numeric()), + sa.Column('amount_foreign', sa.Numeric()), + sa.Column('deadline', sa.String()), + sa.Column('payment_scheme', sa.String()), + sa.Column('act', sa.String()), + sa.Column('comment', sa.String()), + sa.Column('contract_date', sa.Date()), + schema='v3', + ) + + create_table_if_not_exists( + 'contract_summary', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('total_amount', sa.Numeric()), + sa.Column('counterparty', sa.String()), + sa.Column('reference', sa.String()), + sa.Column('deadline', sa.String()), + sa.Column('comment', sa.String()), + sa.Column('future_payments_y1', sa.Numeric()), + sa.Column('future_payments_y2', sa.Numeric()), + sa.Column('other_ssp_amount', sa.Numeric()), + sa.Column('centralized_flag', sa.String()), + schema='v3', + ) + + create_table_if_not_exists( + 'expense_item', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('section_code', sa.String()), + sa.Column('item_id', sa.String()), + sa.Column('num_group_id', sa.String()), + sa.Column('name', sa.String()), + sa.Column('sheet', sa.String()), + sa.Column('direction', sa.String()), + sa.Column('parent_id', sa.Integer()), + sa.Column('depth', sa.Integer()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'expense_item_sheet_check', 'expense_item', + sa.text("sheet IN ('AHR', 'CAP', 'OPER')"), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'expense_item_direction_check', 'expense_item', + sa.text("direction IN ('Support', 'Development')"), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_expense_item_parent', 'expense_item', ['parent_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_expense_item_sheet', 'expense_item', ['sheet'], + schema='v3', + ) + create_table_if_not_exists( + 'form_limit', + sa.Column('budget_form_id', sa.Integer(), + primary_key=True), + sa.Column('template_id', sa.Integer(), + primary_key=True), + sa.Column('qty_q1', sa.Integer()), + sa.Column('qty_q2', sa.Integer()), + sa.Column('qty_q3', sa.Integer()), + sa.Column('qty_q4', sa.Integer()), + sa.Column('comment', sa.String()), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_form_limit_form', 'form_limit', ['budget_form_id'], + schema='v3', + ) + create_table_if_not_exists( + 'form_phase', + sa.Column('budget_form_id', sa.Integer(), + primary_key=True), + sa.Column('sheet', sa.String(), + primary_key=True), + sa.Column('phase_code', sa.String(), + primary_key=True), + sa.Column('role', sa.String(), + nullable=False), + sa.Column('column_keys', postgresql.ARRAY(sa.Text()), + nullable=False), + sa.Column('opens_at', sa.DateTime(timezone=True), + nullable=False), + sa.Column('closes_at', sa.DateTime(timezone=True), + nullable=False), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'chk_v4_form_phase_window', 'form_phase', + sa.text('closes_at > opens_at'), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'chk_v4_form_phase_columns', 'form_phase', + sa.text('cardinality(column_keys) > 0'), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'chk_v4_form_phase_no_admin', 'form_phase', + sa.text("role != 'ADMIN'"), + schema='v3', + ) + create_table_if_not_exists( + 'form_type', + sa.Column('code', sa.String(), + primary_key=True), + sa.Column('name', sa.String()), + sa.Column('scope', sa.String()), + sa.Column('storage_entity', sa.String()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'ck_form_type_scope', 'form_type', + sa.text("scope IN ('GO','PD','RF')"), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'ck_form_type_storage_entity', 'form_type', + sa.text("storage_entity IN ('BUDGET_FORM','RF_PROJECT_REPORT')"), + schema='v3', + ) + create_table_if_not_exists( + 'limit_template', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('parent_id', sa.Integer()), + sa.Column('sort_order', sa.Integer(), + nullable=False), + sa.Column('row_type', sa.String(), + nullable=False), + sa.Column('section_no', sa.String()), + sa.Column('expense_item_code', sa.String()), + sa.Column('name', sa.String(), + nullable=False), + sa.Column('unit', sa.String()), + sa.Column('limit_with_vat', sa.Numeric()), + sa.Column('limit_without_vat', sa.Numeric()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'ck_v3_limit_template_row_type', 'limit_template', + sa.text("row_type IN ('SECTION','GROUP','LEAF')"), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_limit_template_parent', 'limit_template', ['parent_id'], + schema='v3', + ) + create_table_if_not_exists( + 'org_unit', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('title', sa.String(), + nullable=False), + sa.Column('is_active', sa.Boolean(), + nullable=False), + sa.Column('is_ssp', sa.Boolean(), + nullable=False), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v4_org_unit_active', 'org_unit', ['is_active'], + schema='v3', + postgresql_where='is_active', ) + create_table_if_not_exists( + 'phase_template', + sa.Column('form_type', sa.String(), + primary_key=True), + sa.Column('sheet', sa.String(), + primary_key=True), + sa.Column('phase_code', sa.String(), + primary_key=True), + sa.Column('role', sa.String(), + nullable=False), + sa.Column('column_keys', postgresql.ARRAY(sa.Text()), + nullable=False), + sa.Column('opens_at', sa.DateTime(timezone=True), + nullable=False), + sa.Column('closes_at', sa.DateTime(timezone=True), + nullable=False), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'chk_v4_phase_template_columns', 'phase_template', + sa.text('cardinality(column_keys) > 0'), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'chk_v4_phase_template_no_admin', 'phase_template', + sa.text("role != 'ADMIN'"), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'chk_v4_phase_template_window', 'phase_template', + sa.text('closes_at > opens_at'), + schema='v3', + ) + create_table_if_not_exists( + 'plan', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('plan_q1', sa.Numeric()), + sa.Column('plan_q2', sa.Numeric()), + sa.Column('plan_q3', sa.Numeric()), + sa.Column('plan_q4', sa.Numeric()), + sa.Column('comment', sa.String()), + schema='v3', + ) + + create_table_if_not_exists( + 'project', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('name', sa.String()), + sa.Column('level', sa.String()), + sa.Column('parent_id', sa.Integer()), + sa.Column('project_type', sa.String()), + sa.Column('vsp_format', sa.String()), + sa.Column('placement_type', sa.String()), + sa.Column('object_address', sa.String()), + sa.Column('staff_count', sa.Integer()), + sa.Column('total_area', sa.Numeric()), + sa.Column('org_unit_id', sa.Integer()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'project_project_type_check', 'project', + sa.text("project_type IS NULL OR project_type IN ('current', 'development')"), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'project_level_check', 'project', + sa.text("level IN ('program', 'project')"), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'project_vsp_format_check', 'project', + sa.text("vsp_format IS NULL OR vsp_format IN ('office', 'standalone', 'atm', 'other')"), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'project_placement_type_check', 'project', + sa.text("placement_type IS NULL OR placement_type IN ('own', 'rent', 'other')"), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'chk_v3_project_name', 'project', + sa.text("name IS NOT NULL AND name <> ''"), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_project_ssp', 'project', ['org_unit_id'], + schema='v3', + ) + create_table_if_not_exists( + 'rent_detail', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('line_id', sa.Integer(), + nullable=False), + sa.Column('vsp_id', sa.Integer(), + nullable=False), + sa.Column('contract_number', sa.String()), + sa.Column('contract_end_date', sa.Date()), + sa.Column('plan_q1', sa.Numeric()), + sa.Column('plan_q2', sa.Numeric()), + sa.Column('plan_q3', sa.Numeric()), + sa.Column('plan_q4', sa.Numeric()), + sa.Column('actual_jan', sa.Numeric()), + sa.Column('actual_feb', sa.Numeric()), + sa.Column('actual_mar', sa.Numeric()), + sa.Column('actual_apr', sa.Numeric()), + sa.Column('actual_may', sa.Numeric()), + sa.Column('actual_jun', sa.Numeric()), + sa.Column('actual_jul', sa.Numeric()), + sa.Column('actual_aug', sa.Numeric()), + sa.Column('actual_sep', sa.Numeric()), + sa.Column('actual_oct', sa.Numeric()), + sa.Column('actual_nov', sa.Numeric()), + sa.Column('actual_dec', sa.Numeric()), + sa.Column('comment', sa.String()), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_rent_detail_line', 'rent_detail', ['line_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_rent_detail_vsp', 'rent_detail', ['vsp_id'], + schema='v3', + ) + create_table_if_not_exists( + 'reserve', + sa.Column('line_id', sa.Integer(), + primary_key=True), + sa.Column('amount_q1', sa.Numeric()), + sa.Column('amount_q2', sa.Numeric()), + sa.Column('amount_q3', sa.Numeric()), + sa.Column('amount_q4', sa.Numeric()), + sa.Column('justification', sa.String()), + schema='v3', + ) + + create_table_if_not_exists( + 'rf_project_report', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('project_id', sa.Integer(), + nullable=False), + sa.Column('year', sa.Integer(), + nullable=False), + sa.Column('report_type', sa.String(), + nullable=False), + sa.Column('created_by', sa.Integer()), + sa.Column('created_at', sa.DateTime(), + server_default=sa.func.now()), + sa.Column('updated_by', sa.Integer()), + sa.Column('updated_at', sa.DateTime(), + server_default=sa.func.now()), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_rf_project_report_proj_year', 'rf_project_report', ['project_id', 'year'], + schema='v3', + ) + create_unique_constraint_if_not_exists( + 'uq_v3_rf_project_report', 'rf_project_report', + ['project_id', 'year', 'report_type'], + schema='v3', + ) + create_table_if_not_exists( + 'rf_project_report_line', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('rf_project_report_id', sa.Integer(), + nullable=False), + sa.Column('expense_item_id', sa.Integer(), + nullable=False), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_rf_project_report_line_rep', 'rf_project_report_line', ['rf_project_report_id'], + schema='v3', + ) + create_unique_constraint_if_not_exists( + 'uq_v3_rf_project_report_line', 'rf_project_report_line', + ['rf_project_report_id', 'expense_item_id'], + schema='v3', + ) + create_table_if_not_exists( + 'rf_project_report_quarter', + sa.Column('rf_project_report_line_id', sa.Integer(), + primary_key=True), + sa.Column('quarter', sa.SmallInteger(), + primary_key=True), + sa.Column('adj_by_items', sa.Numeric()), + sa.Column('adj_increase', sa.Numeric()), + sa.Column('actual_m1', sa.Numeric()), + sa.Column('actual_m2', sa.Numeric()), + sa.Column('actual_m3', sa.Numeric()), + sa.Column('actual_spod', sa.Numeric()), + schema='v3', + ) + + create_table_if_not_exists( + 'role', + sa.Column('id', sa.Integer(), + primary_key=True, autoincrement=False), + sa.Column('code', sa.String(), + nullable=False), + schema='v3', + ) + + create_unique_constraint_if_not_exists( + 'uq_role_code', 'role', + ['code'], + schema='v3', + ) + create_table_if_not_exists( + 'security_detail', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('line_id', sa.Integer(), + nullable=False), + sa.Column('vsp_id', sa.Integer(), + nullable=False), + sa.Column('contract_number', sa.String()), + sa.Column('contract_end_date', sa.Date()), + sa.Column('plan_q1', sa.Numeric()), + sa.Column('plan_q2', sa.Numeric()), + sa.Column('plan_q3', sa.Numeric()), + sa.Column('plan_q4', sa.Numeric()), + sa.Column('actual_jan', sa.Numeric()), + sa.Column('actual_feb', sa.Numeric()), + sa.Column('actual_mar', sa.Numeric()), + sa.Column('actual_apr', sa.Numeric()), + sa.Column('actual_may', sa.Numeric()), + sa.Column('actual_jun', sa.Numeric()), + sa.Column('actual_jul', sa.Numeric()), + sa.Column('actual_aug', sa.Numeric()), + sa.Column('actual_sep', sa.Numeric()), + sa.Column('actual_oct', sa.Numeric()), + sa.Column('actual_nov', sa.Numeric()), + sa.Column('actual_dec', sa.Numeric()), + sa.Column('comment', sa.String()), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_security_detail_vsp', 'security_detail', ['vsp_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_security_detail_line', 'security_detail', ['line_id'], + schema='v3', + ) + create_table_if_not_exists( + 'sequestration', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('line_id', sa.Integer(), + nullable=False), + sa.Column('actor', sa.String(), + nullable=False), + sa.Column('adj_q1', sa.Numeric()), + sa.Column('adj_q2', sa.Numeric()), + sa.Column('adj_q3', sa.Numeric()), + sa.Column('adj_q4', sa.Numeric()), + sa.Column('justification', sa.String()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'ck_v3_sequestration_actor', 'sequestration', + sa.text("actor IN ('DFIP','SSP_GO')"), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_sequestration_line_actor', 'sequestration', ['line_id', 'actor'], + schema='v3', + ) + create_unique_constraint_if_not_exists( + 'uq_v3_sequestration_line_actor', 'sequestration', + ['line_id', 'actor'], + schema='v3', + ) + create_table_if_not_exists( + 'user_org', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('user_id', sa.Integer(), + nullable=False), + sa.Column('org_unit_id', sa.Integer(), + nullable=False), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v4_user_org_user', 'user_org', ['user_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v4_user_org_org', 'user_org', ['org_unit_id'], + schema='v3', + ) + create_unique_constraint_if_not_exists( + None, 'user_org', + ['user_id', 'org_unit_id'], + schema='v3', + ) + create_table_if_not_exists( + 'utility_detail', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('line_id', sa.Integer(), + nullable=False), + sa.Column('vsp_id', sa.Integer(), + nullable=False), + sa.Column('contract_number', sa.String()), + sa.Column('contract_end_date', sa.Date()), + sa.Column('plan_q1', sa.Numeric()), + sa.Column('plan_q2', sa.Numeric()), + sa.Column('plan_q3', sa.Numeric()), + sa.Column('plan_q4', sa.Numeric()), + sa.Column('actual_jan', sa.Numeric()), + sa.Column('actual_feb', sa.Numeric()), + sa.Column('actual_mar', sa.Numeric()), + sa.Column('actual_apr', sa.Numeric()), + sa.Column('actual_may', sa.Numeric()), + sa.Column('actual_jun', sa.Numeric()), + sa.Column('actual_jul', sa.Numeric()), + sa.Column('actual_aug', sa.Numeric()), + sa.Column('actual_sep', sa.Numeric()), + sa.Column('actual_oct', sa.Numeric()), + sa.Column('actual_nov', sa.Numeric()), + sa.Column('actual_dec', sa.Numeric()), + sa.Column('comment', sa.String()), + schema='v3', + ) + + create_index_if_not_exists( + 'ix_v3_utility_detail_line', 'utility_detail', ['line_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_utility_detail_vsp', 'utility_detail', ['vsp_id'], + schema='v3', + ) + create_table_if_not_exists( + 'vsp', + sa.Column('id', sa.Integer(), + primary_key=True), + sa.Column('branch_id', sa.Integer(), + nullable=False), + sa.Column('reg_number', sa.String()), + sa.Column('address', sa.String()), + sa.Column('format', sa.String()), + sa.Column('opened_at', sa.Date()), + sa.Column('placement_type', sa.String()), + sa.Column('staff_count', sa.Integer()), + sa.Column('total_area', sa.Numeric()), + sa.Column('closed_at', sa.Date()), + sa.Column('is_active', sa.Boolean(), + nullable=False), + sa.Column('is_deleted', sa.Boolean(), + nullable=False), + sa.Column('system_code', sa.String(100)), + sa.Column('created_at', sa.DateTime(timezone=True), + nullable=False, + server_default=sa.func.now()), + sa.Column('updated_at', sa.DateTime(timezone=True)), + sa.Column('created_by', sa.Integer()), + sa.Column('updated_by', sa.Integer()), + sa.Column('vsp_type', sa.String(100)), + sa.Column('notes', sa.Text()), + sa.Column('location_form', sa.String()), + sa.Column('numbers', sa.Integer()), + sa.Column('rent_contract_num', sa.String()), + sa.Column('rent_end_date', sa.Date()), + schema='v3', + ) + + create_check_constraint_if_not_exists( + 'chk_closed_after_opened', 'vsp', + sa.text('closed_at IS NULL OR opened_at IS NULL OR closed_at >= opened_at'), + schema='v3', + ) + create_unique_constraint_if_not_exists( + 'vsp_reg_number_unique', 'vsp', + ['reg_number'], + schema='v3', + ) + + create_table_if_not_exists( + 'fixed_asset_report', + sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True), + sa.Column('budget_form_id', sa.Integer(), nullable=False), + sa.Column('expense_item_id', sa.Integer()), + sa.Column('equipment_name', sa.String()), + sa.Column('month', sa.SmallInteger(), nullable=False), + sa.Column('opening_qty_604', sa.Integer()), + sa.Column('opening_amt_604', sa.Numeric()), + sa.Column('acquired_qty_604', sa.Integer()), + sa.Column('acquired_amt_604', sa.Numeric()), + sa.Column('disposed_qty_604', sa.Integer()), + sa.Column('disposed_amt_604', sa.Numeric()), + sa.Column('opening_qty_60415', sa.Integer()), + sa.Column('opening_amt_60415', sa.Numeric()), + sa.Column('acquired_qty_60415', sa.Integer()), + sa.Column('acquired_amt_60415', sa.Numeric()), + sa.Column('transferred_qty_60415', sa.Integer()), + sa.Column('transferred_amt_60415', sa.Numeric()), + sa.Column('go_balance_only_amt', sa.Numeric()), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'fixed_asset_report_month_check', 'fixed_asset_report', + sa.text('month >= 1 AND month <= 12'), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_fixed_asset_report_form', 'fixed_asset_report', ['budget_form_id'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_fixed_asset_report_item', 'fixed_asset_report', ['expense_item_id'], + schema='v3', + ) + + create_table_if_not_exists( + 'expense_item_form_type', + sa.Column('expense_item_id', sa.Integer(), primary_key=True), + sa.Column('form_type_code', sa.String(), primary_key=True), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_eift_item', 'expense_item_form_type', ['expense_item_id'], + schema='v3', + ) + op.execute( + """ + CREATE MATERIALIZED VIEW IF NOT EXISTS v3.mv_expense_item_tree + TABLESPACE pg_default + AS WITH RECURSIVE tree AS ( + SELECT ei.id, + ei.sheet, + ei.section_code, + ei.item_id, + ei.num_group_id, + ei.name, + ei.parent_id, + ei.depth, + ARRAY[ei.id] AS path, + NULL::character varying AS parent_item_id + FROM v3.expense_item ei + WHERE ei.parent_id IS NULL + UNION ALL + SELECT ei.id, + ei.sheet, + ei.section_code, + ei.item_id, + ei.num_group_id, + ei.name, + ei.parent_id, + ei.depth, + t_1.path || ei.id, + t_1.item_id AS parent_item_id + FROM v3.expense_item ei + JOIN tree t_1 ON ei.parent_id = t_1.id + ) + SELECT id, + sheet, + section_code, + item_id, + num_group_id, + name, + parent_id, + parent_item_id, + depth, + path, + ARRAY( SELECT c.id + FROM tree c + WHERE t.id = ANY (c.path)) AS desc_ids + FROM tree t + WITH DATA; + """ + ) + op.execute("CREATE INDEX IF NOT EXISTS ix_v3_mv_expense_item_tree_sheet ON v3.mv_expense_item_tree USING btree (sheet);") + op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ux_v2_mv_expense_item_tree_id ON v3.mv_expense_item_tree USING btree (id);") + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/0002_functions.py b/api/alembic/versions/0002_functions.py new file mode 100644 index 0000000..a71e997 --- /dev/null +++ b/api/alembic/versions/0002_functions.py @@ -0,0 +1,79 @@ +import os +import re +from typing import List, Optional + +from alembic import op + +revision = "0002" +down_revision = "0001" +branch_labels = None +depends_on = None + +_DOLLAR_TAG_RE = re.compile(r"\$\w+\$") + + +def _find_dollar_tag(line: str) -> Optional[str]: + m = _DOLLAR_TAG_RE.search(line.strip()) + return m.group(0) if m else None + + +def _split_statements(sql: str) -> List[str]: + statements: List[str] = [] + current: List[str] = [] + in_dollar = False + dollar_tag: Optional[str] = None + + for line in sql.split("\n"): + stripped = line.strip() + + if not in_dollar: + tag = _find_dollar_tag(stripped) + if tag and tag.endswith("$") and tag.startswith("$"): + dollar_tag = tag + in_dollar = True + current.append(line) + continue + + if in_dollar and dollar_tag and stripped.startswith(dollar_tag): + after = stripped[len(dollar_tag):].strip() + if after == ";" or after == "": + in_dollar = False + dollar_tag = None + if after == ";": + current.append(line) + statements.append("\n".join(current)) + current = [] + continue + + if not in_dollar and stripped.rstrip().endswith(";"): + current.append(line) + statements.append("\n".join(current)) + current = [] + continue + + current.append(line) + + remaining = "\n".join(current).strip() + if remaining: + statements.append(remaining) + + return statements + + +def upgrade() -> None: + ddl_path = os.path.join(os.path.dirname(__file__), "sql", "0002_functions.sql") + with open(ddl_path) as f: + content = f.read() + + statements = _split_statements(content) + for stmt in statements: + stripped = stmt.strip().rstrip(";").strip() + if not stripped: + continue + if all(l.strip().startswith("--") or not l.strip() for l in stripped.split("\n")): + continue + op.execute(stripped) + + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/0003_initial_data.py b/api/alembic/versions/0003_initial_data.py new file mode 100644 index 0000000..18453f5 --- /dev/null +++ b/api/alembic/versions/0003_initial_data.py @@ -0,0 +1,30 @@ +import os + +from alembic import op +import sqlalchemy as sa + +revision = "0003" +down_revision = "0002" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + conn = op.get_bind() + has_data = conn.execute(sa.text("SELECT 1 FROM v3.role LIMIT 1")).scalar() + + if has_data: + return + sql_path = os.path.join(os.path.dirname(__file__), "sql", "0003_initial_data.sql") + with open(sql_path) as f: + content = f.read() + + for stmt in content.split(";\n"): + stripped = stmt.strip() + if not stripped: + continue + op.execute(stripped) + + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/sql/0002_functions.sql b/api/alembic/versions/sql/0002_functions.sql new file mode 100644 index 0000000..c8c2011 --- /dev/null +++ b/api/alembic/versions/sql/0002_functions.sql @@ -0,0 +1,7793 @@ +-- DROP FUNCTION v3._apply_form3_cell(int4, text, jsonb); + +CREATE OR REPLACE FUNCTION v3._apply_form3_cell(p_line_id integer, p_column text, p_value jsonb) + RETURNS void + LANGUAGE plpgsql +AS $function$ +DECLARE + v_parts TEXT[]; + v_scope TEXT; + v_field TEXT; + v_q SMALLINT; + v_target_col TEXT; + v_target_type TEXT; + v_sql TEXT; +BEGIN + v_parts := string_to_array(p_column, '.'); + IF array_length(v_parts, 1) < 2 THEN + RAISE EXCEPTION 'bad_column_format: %, expected scope.field', p_column; + END IF; + v_scope := v_parts[1]; + v_field := v_parts[2]; + + IF v_scope = 'totals' OR v_field IN ('total_corr','quarter_actual','economy','carryover') THEN + RAISE EXCEPTION 'computed_field: %', p_column; + END IF; + + IF v_scope NOT IN ('q1','q2','q3','q4') THEN + RAISE EXCEPTION 'unknown_scope: % (FORM_3 supports only q1..q4)', v_scope; + END IF; + + v_q := substring(v_scope FROM 2)::SMALLINT; + + -- Маппинг JSONB-ключ → колонка rf_project_report_quarter + CASE v_field + WHEN 'adj_by_items' THEN v_target_col := 'adj_by_items'; v_target_type := 'NUMERIC'; + WHEN 'adj_increase' THEN v_target_col := 'adj_increase'; v_target_type := 'NUMERIC'; + WHEN 'm1' THEN v_target_col := 'actual_m1'; v_target_type := 'NUMERIC'; + WHEN 'm2' THEN v_target_col := 'actual_m2'; v_target_type := 'NUMERIC'; + WHEN 'm3' THEN v_target_col := 'actual_m3'; v_target_type := 'NUMERIC'; + WHEN 'spod' THEN + IF v_q <> 4 THEN RAISE EXCEPTION 'spod is q4-only: %', p_column; END IF; + v_target_col := 'actual_spod'; v_target_type := 'NUMERIC'; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END CASE; + + v_sql := format( + 'INSERT INTO v3.rf_project_report_quarter (rf_project_report_line_id, quarter, %1$I) ' + || 'VALUES ($1, $2, ($3 #>> ''{}'')::%2$s) ' + || 'ON CONFLICT (rf_project_report_line_id, quarter) DO UPDATE SET %1$I = EXCLUDED.%1$I', + v_target_col, v_target_type + ); + EXECUTE v_sql USING p_line_id, v_q, p_value; +END; +$function$ +; + +-- DROP FUNCTION v3._apply_form_cell(int4, text, jsonb, varchar, int4); + +CREATE OR REPLACE FUNCTION v3._apply_form_cell(p_line_id integer, p_column text, p_value jsonb, p_sheet character varying DEFAULT NULL::character varying, p_form_id integer DEFAULT NULL::integer) + RETURNS void + LANGUAGE plpgsql +AS $function$ +DECLARE + v_parts TEXT[]; + v_scope TEXT; + v_field TEXT; + v_q SMALLINT; + v_actor TEXT; + v_target_table TEXT; + v_target_col TEXT; + v_target_type TEXT; + v_key_kind TEXT; + v_str TEXT; + v_sql TEXT; + v_sat_table TEXT; + v_cnt INT; +BEGIN + v_parts := string_to_array(p_column, '.'); + + -- ═══ OTCH9F (fixed_asset_report) ══════════════════════════════════════ + -- p_line_id = fixed_asset_report.id. + -- Editable: b_604.{acquired,disposed}_*, b_60415.{acquired,transferred}_*, + -- totals.go_balance_only; + -- b_604.opening_*, b_60415.opening_* — ТОЛЬКО для month=1 (для + -- месяцев 2..12 это computed накопительно из месяца 1). + -- RO: equipment_name (нормативная номенклатура), month/expense_item_id/ + -- item_id/section_code (ключ строки), totals.{acquired_total, + -- disposed_total, balance_qty, balance_amt} (computed). + IF p_sheet = 'OTCH9F' THEN + DECLARE v_far_month SMALLINT; + BEGIN + SELECT month INTO v_far_month FROM v3.fixed_asset_report WHERE id = p_line_id; + IF v_far_month IS NULL THEN + RAISE EXCEPTION 'fixed_asset_report.id=% не существует', p_line_id; + END IF; + + -- Одноуровневые ключи — только нормативные/ключевые → RO + IF array_length(v_parts,1) = 1 THEN + IF v_parts[1] = 'equipment_name' THEN + RAISE EXCEPTION 'normative_field: equipment_name (нормативная номенклатура, правится отдельным API)'; + ELSIF v_parts[1] IN ('month','expense_item_id','item_id','section_code','id') THEN + RAISE EXCEPTION 'key_field: % (часть ключа строки, RO)', v_parts[1]; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + END IF; + + IF array_length(v_parts,1) <> 2 THEN + RAISE EXCEPTION 'bad_column_format: %', p_column; + END IF; + + -- Двухуровневые: totals.x / b_604.x / b_60415.x + IF v_parts[1] = 'totals' THEN + IF v_parts[2] = 'go_balance_only' THEN + v_target_col := 'go_balance_only_amt'; v_target_type := 'NUMERIC'; + ELSIF v_parts[2] IN ('acquired_total','disposed_total','balance_qty','balance_amt') THEN + RAISE EXCEPTION 'computed_field: totals.%', v_parts[2]; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + ELSIF v_parts[1] IN ('b_604','b_60415') THEN + -- opening_* для месяцев 2..12 — computed + IF v_parts[2] LIKE 'opening_%' AND v_far_month <> 1 THEN + RAISE EXCEPTION 'computed_field: % (opening для month>1 вычисляется из base + накопит. дельты)', p_column; + END IF; + -- Маппинг b_604/60415 → колонки таблицы + IF v_parts[1] = 'b_604' THEN + v_target_col := CASE v_parts[2] + WHEN 'opening_qty' THEN 'opening_qty_604' + WHEN 'opening_amt' THEN 'opening_amt_604' + WHEN 'acquired_qty' THEN 'acquired_qty_604' + WHEN 'acquired_amt' THEN 'acquired_amt_604' + WHEN 'disposed_qty' THEN 'disposed_qty_604' + WHEN 'disposed_amt' THEN 'disposed_amt_604' + END; + IF v_target_col IS NULL THEN + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + v_target_type := CASE WHEN v_parts[2] LIKE '%_qty' THEN 'INTEGER' ELSE 'NUMERIC' END; + ELSE -- b_60415 + v_target_col := CASE v_parts[2] + WHEN 'opening_qty' THEN 'opening_qty_60415' + WHEN 'opening_amt' THEN 'opening_amt_60415' + WHEN 'acquired_qty' THEN 'acquired_qty_60415' + WHEN 'acquired_amt' THEN 'acquired_amt_60415' + WHEN 'transferred_qty' THEN 'transferred_qty_60415' + WHEN 'transferred_amt' THEN 'transferred_amt_60415' + END; + IF v_target_col IS NULL THEN + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + v_target_type := CASE WHEN v_parts[2] LIKE '%_qty' THEN 'INTEGER' ELSE 'NUMERIC' END; + END IF; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + + EXECUTE format( + 'UPDATE v3.fixed_asset_report SET %I = ($2 #>> ''{}'')::%s WHERE id = $1', + v_target_col, v_target_type + ) USING p_line_id, p_value; + RETURN; + END; + END IF; + + -- ═══ AHR_LIMIT (limit_template + form_limit) ═════════════════════════ + -- p_line_id здесь = limit_template.id LEAF (глобальный каталог нормативов). + -- Editable: qty_q1..q4 (INTEGER), comment (TEXT) — пишутся в v3.form_limit + -- (UPSERT по (budget_form_id, template_id)). + -- amount_q1..q4 — computed по формуле в read (qty × limit × period_factor). + -- name/unit/limit_*/section_no/expense_item_code — нормативный справочник + -- (правится отдельным админ-API, не через write UI). + IF p_sheet = 'AHR_LIMIT' THEN + IF p_form_id IS NULL THEN + RAISE EXCEPTION 'internal: p_form_id не передан для AHR_LIMIT'; + END IF; + + -- Sanity: строка должна быть LEAF + IF NOT EXISTS (SELECT 1 FROM v3.limit_template lt + WHERE lt.id = p_line_id AND lt.row_type = 'LEAF') THEN + RAISE EXCEPTION 'limit_template.id=% не существует или не LEAF (SECTION/GROUP не редактируется)', p_line_id; + END IF; + + IF array_length(v_parts,1) <> 1 THEN + RAISE EXCEPTION 'bad_column_format: %, expected single key', p_column; + END IF; + + CASE v_parts[1] + WHEN 'qty_q1','qty_q2','qty_q3','qty_q4' THEN + v_target_col := v_parts[1]; v_target_type := 'INTEGER'; + WHEN 'comment' THEN + v_target_col := 'comment'; v_target_type := 'TEXT'; + WHEN 'amount_q1','amount_q2','amount_q3','amount_q4' THEN + RAISE EXCEPTION 'computed_field: % (вычисляется из qty × limit × period_factor)', p_column; + WHEN 'name','unit','section_no','expense_item_code', + 'limit_with_vat','limit_without_vat' THEN + RAISE EXCEPTION 'normative_field: % (нормативный справочник, правится админом отдельно)', p_column; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END CASE; + + EXECUTE format( + 'INSERT INTO v3.form_limit (budget_form_id, template_id, %1$I) ' + || 'VALUES ($1, $2, ($3 #>> ''{}'')::%2$s) ' + || 'ON CONFLICT (budget_form_id, template_id) DO UPDATE SET %1$I = EXCLUDED.%1$I', + v_target_col, v_target_type + ) USING p_form_id, p_line_id, p_value; + RETURN; + END IF; + + -- ═══ Сателлиты (AHR_RENT / AHR_UTILITY / AHR_SECURITY) ═══════════════ + -- Здесь p_line_id трактуется как id строки сателлита (rent/utility/ + -- security_detail.id) — НЕ budget_line.id. UPDATE по этому id; INSERT/ + -- DELETE — через add_budget_line / del_budget_line с тем же sheet. + IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN + v_sat_table := CASE p_sheet + WHEN 'AHR_RENT' THEN 'rent_detail' + WHEN 'AHR_UTILITY' THEN 'utility_detail' + WHEN 'AHR_SECURITY' THEN 'security_detail' + END; + + -- Парсим (ключи бывают одно- и двухуровневые) + IF array_length(v_parts,1) = 1 THEN + CASE v_parts[1] + WHEN 'contract_number' THEN v_target_col := 'contract_number'; v_target_type := 'TEXT'; + WHEN 'contract_end_date' THEN v_target_col := 'contract_end_date'; v_target_type := 'DATE'; + WHEN 'comment' THEN v_target_col := 'comment'; v_target_type := 'TEXT'; + WHEN 'address','object_type','rented_area','object_area' THEN + RAISE EXCEPTION 'computed_field: % (атрибут v3.vsp, правится отдельно)', p_column; + ELSE RAISE EXCEPTION 'unknown_column: %', p_column; + END CASE; + ELSIF array_length(v_parts,1) = 2 AND v_parts[1] = 'plan' THEN + IF v_parts[2] = 'year' THEN RAISE EXCEPTION 'computed_field: %', p_column; END IF; + IF v_parts[2] NOT IN ('q1','q2','q3','q4') THEN RAISE EXCEPTION 'unknown_column: %', p_column; END IF; + v_target_col := 'plan_' || v_parts[2]; + v_target_type := 'NUMERIC'; + ELSIF array_length(v_parts,1) = 2 AND v_parts[1] LIKE 'fact_q%' THEN + IF v_parts[2] = 'total' THEN RAISE EXCEPTION 'computed_field: %', p_column; END IF; + -- белый список месяцев по соответствующему кварталу + IF (v_parts[1]='fact_q1' AND v_parts[2] NOT IN ('jan','feb','mar')) OR + (v_parts[1]='fact_q2' AND v_parts[2] NOT IN ('apr','may','jun')) OR + (v_parts[1]='fact_q3' AND v_parts[2] NOT IN ('jul','aug','sep')) OR + (v_parts[1]='fact_q4' AND v_parts[2] NOT IN ('oct','nov','dec')) THEN + RAISE EXCEPTION 'unknown_column: % (месяц вне квартала)', p_column; + END IF; + v_target_col := 'actual_' || v_parts[2]; + v_target_type := 'NUMERIC'; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + + EXECUTE format( + 'UPDATE v3.%I SET %I = ($2 #>> ''{}'')::%s WHERE id = $1', + v_sat_table, v_target_col, v_target_type + ) USING p_line_id, p_value; + + GET DIAGNOSTICS v_cnt = ROW_COUNT; + IF v_cnt = 0 THEN + RAISE EXCEPTION '%.id=% не существует', v_sat_table, p_line_id; + END IF; + RETURN; + END IF; + + -- ═══ Основная сетка (FORM_1/2/4 AHR/CAP/OPER) ════════════════════════ + IF array_length(v_parts, 1) < 2 THEN + RAISE EXCEPTION 'bad_column_format: %, expected scope.field', p_column; + END IF; + v_scope := v_parts[1]; + v_field := v_parts[2]; + + -- ── header: editable budget_line columns ────────────────────────────── + IF v_scope = 'header' THEN + IF v_field = 'name' THEN + UPDATE v3.budget_line SET name = (p_value #>> '{}'), updated_at = now() WHERE id = p_line_id; + ELSIF v_field = 'internal_order' THEN + UPDATE v3.budget_line SET internal_order = (p_value #>> '{}'), updated_at = now() WHERE id = p_line_id; + ELSIF v_field = 'vsp_id' THEN + UPDATE v3.budget_line SET vsp_id = (p_value #>> '{}')::INT, updated_at = now() WHERE id = p_line_id; + ELSIF v_field IN ('year','section','item_id','num_group','vsp_address') THEN + RAISE EXCEPTION 'computed_field: %', p_column; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + RETURN; + END IF; + + -- ── computed scopes (RO) ────────────────────────────────────────────── + IF v_scope IN ('approved','totals') THEN + RAISE EXCEPTION 'computed_field: %', p_column; + END IF; + + -- ── booking.y{2026|2027}.qN → v3.ckk (см. read FORM_2/4 jsonb) ──────── + -- В read-обёртках booking синтезируется из ckk.expenses_qN (= y2026, + -- текущий год) и ckk.expenses_next_year_qN (= y2027, следующий). + -- Имена y2026/y2027 захардкожены в read; повторяем то же в write. + IF v_scope = 'booking' THEN + IF array_length(v_parts, 1) <> 3 THEN + RAISE EXCEPTION 'bad_column_format: %, expected booking.y{2026|2027}.qN', p_column; + END IF; + IF v_parts[2] NOT IN ('y2026','y2027') THEN + RAISE EXCEPTION 'bad_booking_year: % (only y2026/y2027)', v_parts[2]; + END IF; + IF v_parts[3] NOT IN ('q1','q2','q3','q4') THEN + RAISE EXCEPTION 'bad_booking_quarter: %', v_parts[3]; + END IF; + v_target_col := CASE v_parts[2] + WHEN 'y2026' THEN 'expenses_' || v_parts[3] + WHEN 'y2027' THEN 'expenses_next_year_' || v_parts[3] + END; + v_sql := format( + 'INSERT INTO v3.ckk (line_id, %1$I) VALUES ($1, ($2 #>> ''{}'')::NUMERIC) ' + || 'ON CONFLICT (line_id) DO UPDATE SET %1$I = EXCLUDED.%1$I', + v_target_col + ); + EXECUTE v_sql USING p_line_id, p_value; + UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id; + RETURN; + END IF; + + -- ── Маппинг (scope, field) → (table, col, type, key_kind, actor) ───── + -- key_kind: 'line' — PK/UQ = (line_id) + -- 'line_quarter'— PK = (line_id, quarter), quarter из v_q (берётся из scope qN) + -- 'line_actor' — UQ = (line_id, actor), actor из v_actor + -- + -- VALUES — это и есть white-list редактируемых полей. Поля, отсутствующие + -- здесь (computed: corrected_plan, new_plan, actual_quarter, rem_*, economy, + -- total, booking; FORM_2 q1.adj_rf — нет такого поля у FORM_2; и т.п.) → + -- падают с unknown_column ниже. + + -- Quarter scope qN → ставим v_q и нормализуем scope в 'q' для маппинга + IF v_scope IN ('q1','q2','q3','q4') THEN + v_q := substring(v_scope FROM 2)::SMALLINT; + ELSE + v_q := NULL; + END IF; + + -- Actor для sequestration scopes + v_actor := CASE v_scope + WHEN 'sequestration' THEN 'DFIP' + WHEN 'seq_dfip' THEN 'DFIP' + WHEN 'seq_ssp' THEN 'SSP_GO' + ELSE NULL END; + + SELECT m.target_table, m.target_col, m.target_type, m.key_kind + INTO v_target_table, v_target_col, v_target_type, v_key_kind + FROM (VALUES + -- ─── plan ────────────────────────────────────────────────────────── + ('plan','q1', 'plan','plan_q1','NUMERIC','line'), + ('plan','q2', 'plan','plan_q2','NUMERIC','line'), + ('plan','q3', 'plan','plan_q3','NUMERIC','line'), + ('plan','q4', 'plan','plan_q4','NUMERIC','line'), + ('plan','comment', 'plan','comment','TEXT', 'line'), + + -- ─── reserve ─────────────────────────────────────────────────────── + ('reserve','q1', 'reserve','amount_q1','NUMERIC','line'), + ('reserve','q2', 'reserve','amount_q2','NUMERIC','line'), + ('reserve','q3', 'reserve','amount_q3','NUMERIC','line'), + ('reserve','q4', 'reserve','amount_q4','NUMERIC','line'), + ('reserve','justification','reserve','justification','TEXT','line'), + + -- ─── allocation (FORM_1/4) ───────────────────────────────────────── + ('allocation','order', 'allocation','internal_order','TEXT','line'), + ('allocation','property', 'allocation','property_object','TEXT','line'), + + -- ─── contract_summary (FORM_1/4) ─────────────────────────────────── + ('contract_summary','total', 'contract_summary','total_amount', 'NUMERIC','line'), + ('contract_summary','counterparty', 'contract_summary','counterparty', 'TEXT', 'line'), + ('contract_summary','deadline', 'contract_summary','deadline', 'TEXT', 'line'), + ('contract_summary','comment', 'contract_summary','comment', 'TEXT', 'line'), + ('contract_summary','future_y1', 'contract_summary','future_payments_y1', 'NUMERIC','line'), + ('contract_summary','future_y2', 'contract_summary','future_payments_y2', 'NUMERIC','line'), + ('contract_summary','other_ssp', 'contract_summary','other_ssp_amount', 'NUMERIC','line'), + + -- ─── collegial_approval (FORM_1/4) ───────────────────────────────── + ('collegial','approved', 'collegial_approval','approved_amount', 'NUMERIC','line'), + ('collegial','protocol', 'collegial_approval','protocol_reference','TEXT', 'line'), + ('collegial','note', 'collegial_approval','note', 'TEXT', 'line'), + + -- ─── ckk (FORM_1/4) ──────────────────────────────────────────────── + ('ckk','ceiling', 'ckk','ceiling_amount', 'NUMERIC','line'), + ('ckk','q1', 'ckk','expenses_q1', 'NUMERIC','line'), + ('ckk','q2', 'ckk','expenses_q2', 'NUMERIC','line'), + ('ckk','q3', 'ckk','expenses_q3', 'NUMERIC','line'), + ('ckk','q4', 'ckk','expenses_q4', 'NUMERIC','line'), + ('ckk','next_q1', 'ckk','expenses_next_year_q1', 'NUMERIC','line'), + ('ckk','next_q2', 'ckk','expenses_next_year_q2', 'NUMERIC','line'), + ('ckk','next_q3', 'ckk','expenses_next_year_q3', 'NUMERIC','line'), + ('ckk','next_q4', 'ckk','expenses_next_year_q4', 'NUMERIC','line'), + ('ckk','rf_schedule', 'ckk','rf_schedule', 'TEXT', 'line'), + ('ckk','deadline', 'ckk','delivery_deadline', 'TEXT', 'line'), + ('ckk','proc_plan', 'ckk','procurement_plan', 'TEXT', 'line'), + ('ckk','proc_method', 'ckk','procurement_method','TEXT', 'line'), + ('ckk','comment', 'ckk','comment', 'TEXT', 'line'), + + -- ─── contract_detail (FORM_1) и его alias contract (FORM_2/4) ───── + ('contract_detail','counterparty', 'contract_detail','counterparty', 'TEXT', 'line'), + ('contract_detail','reference', 'contract_detail','reference', 'TEXT', 'line'), + ('contract_detail','addenda', 'contract_detail','addenda', 'TEXT', 'line'), + ('contract_detail','subject', 'contract_detail','subject', 'TEXT', 'line'), + ('contract_detail','currency', 'contract_detail','currency', 'TEXT', 'line'), + ('contract_detail','ceiling', 'contract_detail','ceiling_amount','NUMERIC','line'), + ('contract_detail','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'), + ('contract_detail','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'), + ('contract_detail','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'), + ('contract_detail','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'), + ('contract_detail','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'), + ('contract_detail','vat_rate', 'contract_detail','vat_rate', 'TEXT', 'line'), + ('contract_detail','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','line'), + ('contract_detail','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'), + ('contract_detail','deadline', 'contract_detail','deadline', 'TEXT', 'line'), + ('contract_detail','payment_scheme', 'contract_detail','payment_scheme','TEXT', 'line'), + ('contract_detail','act', 'contract_detail','act', 'TEXT', 'line'), + ('contract_detail','comment', 'contract_detail','comment', 'TEXT', 'line'), + ('contract_detail','contract_date', 'contract_detail','contract_date', 'DATE', 'line'), + + ('contract','counterparty', 'contract_detail','counterparty', 'TEXT', 'line'), + ('contract','reference', 'contract_detail','reference', 'TEXT', 'line'), + ('contract','date', 'contract_detail','contract_date', 'DATE', 'line'), + ('contract','subject', 'contract_detail','subject', 'TEXT', 'line'), + ('contract','currency', 'contract_detail','currency', 'TEXT', 'line'), + ('contract','ceiling', 'contract_detail','ceiling_amount','NUMERIC','line'), + ('contract','vat_rate', 'contract_detail','vat_rate', 'TEXT', 'line'), + ('contract','deadline', 'contract_detail','deadline', 'TEXT', 'line'), + ('contract','scheme', 'contract_detail','payment_scheme','TEXT', 'line'), + ('contract','act', 'contract_detail','act', 'TEXT', 'line'), + ('contract','comment', 'contract_detail','comment', 'TEXT', 'line'), + + -- ─── sequestration (line_id, actor) — три алиаса scope ──────────── + ('sequestration','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'), + ('sequestration','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'), + ('sequestration','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'), + ('sequestration','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'), + ('sequestration','justification','sequestration','justification','TEXT', 'line_actor'), + + ('seq_dfip','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'), + ('seq_dfip','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'), + ('seq_dfip','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'), + ('seq_dfip','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'), + ('seq_dfip','justification','sequestration','justification','TEXT', 'line_actor'), + + ('seq_ssp','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'), + ('seq_ssp','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'), + ('seq_ssp','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'), + ('seq_ssp','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'), + ('seq_ssp','justification','sequestration','justification','TEXT', 'line_actor'), + + -- ─── budget_line_quarter — все 4 квартала через нормализованный scope='q' ─ + -- (v_scope qN, v_q заполнен; маппинг ниже использует scope='q') + ('q','adj_current', 'budget_line_quarter','adj_current', 'NUMERIC','line_quarter'), + ('q','adj_ssp', 'budget_line_quarter','adj_ssp', 'NUMERIC','line_quarter'), + ('q','adj_rf', 'budget_line_quarter','adj_rf', 'NUMERIC','line_quarter'), + ('q','adj_reserve', 'budget_line_quarter','adj_reserve', 'NUMERIC','line_quarter'), + ('q','adj_comment', 'budget_line_quarter','adj_comment', 'TEXT', 'line_quarter'), + ('q','target_change', 'budget_line_quarter','target_change', 'NUMERIC','line_quarter'), + ('q','base_correction', 'budget_line_quarter','base_plan_correction', 'NUMERIC','line_quarter'), + ('q','base_correction_comment', 'budget_line_quarter','base_plan_correction_comment','TEXT', 'line_quarter'), + ('q','pay_date', 'budget_line_quarter','payment_date', 'DATE', 'line_quarter'), + ('q','pay_amount', 'budget_line_quarter','payment_amount', 'NUMERIC','line_quarter'), + ('q','pay_ho', 'budget_line_quarter','payment_amount_ho', 'NUMERIC','line_quarter'), + ('q','pay_rf', 'budget_line_quarter','payment_amount_rf', 'NUMERIC','line_quarter'), + ('q','pay_comment', 'budget_line_quarter','payment_comment', 'TEXT', 'line_quarter'), + ('q','pay_act', 'budget_line_quarter','payment_act', 'TEXT', 'line_quarter'), + ('q','actual_m1', 'budget_line_quarter','actual_m1', 'NUMERIC','line_quarter'), + ('q','actual_m2', 'budget_line_quarter','actual_m2', 'NUMERIC','line_quarter'), + ('q','actual_m3', 'budget_line_quarter','actual_m3', 'NUMERIC','line_quarter'), + ('q','actual_spod', 'budget_line_quarter','actual_spod', 'NUMERIC','line_quarter'), + ('q','transfer_q2', 'budget_line_quarter','transfer_to_q2', 'NUMERIC','line_quarter'), + ('q','transfer_q3', 'budget_line_quarter','transfer_to_q3', 'NUMERIC','line_quarter'), + ('q','transfer_q4', 'budget_line_quarter','transfer_to_q4', 'NUMERIC','line_quarter'), + ('q','transfer_econ', 'budget_line_quarter','transfer_to_economy', 'NUMERIC','line_quarter'), + -- разбивка переноса в следующий квартал (transfer_q{N+1}_*) — единые поля, + -- индекс N+1 в имени ключа для UI смыслово важен, в БД одно и то же поле: + ('q','transfer_q2_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'), + ('q','transfer_q2_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'), + ('q','transfer_q2_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'), + ('q','transfer_q3_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'), + ('q','transfer_q3_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'), + ('q','transfer_q3_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'), + ('q','transfer_q4_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'), + ('q','transfer_q4_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'), + ('q','transfer_q4_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'), + ('q','transfer_next_comment', 'budget_line_quarter','transfer_next_comment', 'TEXT', 'line_quarter'), + ('q','transfer_far_comment', 'budget_line_quarter','transfer_far_comment', 'TEXT', 'line_quarter'), + -- plan_revision_* — две группы алиасов: FORM_1 (rev_*) и FORM_2/4 (revision_*). + ('q','rev_eco', 'budget_line_quarter','plan_revision_eco_change','NUMERIC','line_quarter'), + ('q','rev_item', 'budget_line_quarter','plan_revision_item_adj', 'NUMERIC','line_quarter'), + ('q','rev_inc', 'budget_line_quarter','plan_revision_increase', 'NUMERIC','line_quarter'), + ('q','rev_seq', 'budget_line_quarter','plan_revision_sequester', 'NUMERIC','line_quarter'), + ('q','rev_comment', 'budget_line_quarter','plan_revision_comment', 'TEXT', 'line_quarter'), + ('q','revision_inc', 'budget_line_quarter','plan_revision_increase', 'NUMERIC','line_quarter'), + ('q','revision_seq', 'budget_line_quarter','plan_revision_sequester', 'NUMERIC','line_quarter'), + ('q','revision_comment', 'budget_line_quarter','plan_revision_comment', 'TEXT', 'line_quarter'), + ('q','booking_amount', 'budget_line_quarter','booking_amount', 'NUMERIC','line_quarter') + ) AS m(scope, field, target_table, target_col, target_type, key_kind) + WHERE m.scope = (CASE WHEN v_q IS NOT NULL THEN 'q' ELSE v_scope END) + AND m.field = v_field; + + IF v_target_table IS NULL THEN + -- Распознаваемые computed-ключи отдельно — для понятной ошибки: + IF v_field IN ('corrected_plan','new_plan','actual_quarter','booking', + 'rem_booking','rem_actual','residual_after_booking', + 'residual_after_actual','economy','total','year') THEN + RAISE EXCEPTION 'computed_field: %', p_column; + END IF; + RAISE EXCEPTION 'unknown_column: %', p_column; + END IF; + + -- Извлекаем скаляр из JSONB — '#>> {}' возвращает NULL для jsonb null + -- Cast прицельный (NUMERIC/TEXT/DATE/INT) делается в dynamic SQL ниже. + + -- ── Dynamic UPSERT ──────────────────────────────────────────────────── + IF v_key_kind = 'line' THEN + v_sql := format( + 'INSERT INTO v3.%1$I (line_id, %2$I) VALUES ($1, ($2 #>> ''{}'')::%3$s) ' + || 'ON CONFLICT (line_id) DO UPDATE SET %2$I = EXCLUDED.%2$I', + v_target_table, v_target_col, v_target_type + ); + EXECUTE v_sql USING p_line_id, p_value; + + ELSIF v_key_kind = 'line_quarter' THEN + IF v_q IS NULL THEN RAISE EXCEPTION 'internal: quarter not set for %', p_column; END IF; + v_sql := format( + 'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, ($3 #>> ''{}'')::%3$s) ' + || 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I', + v_target_table, v_target_col, v_target_type + ); + EXECUTE v_sql USING p_line_id, v_q, p_value; + + ELSIF v_key_kind = 'line_actor' THEN + IF v_actor IS NULL THEN RAISE EXCEPTION 'internal: actor not set for %', p_column; END IF; + -- Для quarter-полей sequestration: scope.field=qN→adj_qN, justification без quarter + v_sql := format( + 'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, ($3 #>> ''{}'')::%3$s) ' + || 'ON CONFLICT (line_id, actor) DO UPDATE SET %2$I = EXCLUDED.%2$I', + v_target_table, v_target_col, v_target_type + ); + EXECUTE v_sql USING p_line_id, v_actor, p_value; + + ELSE + RAISE EXCEPTION 'internal: unknown key_kind %', v_key_kind; + END IF; + + -- Помечаем budget_line как изменённую (для updated_at iteration) + UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id; +END; +$function$ +; + +-- DROP FUNCTION v3._diff_val(anyelement); + +CREATE OR REPLACE FUNCTION v3._diff_val(p_val anyelement) + RETURNS jsonb + LANGUAGE sql + IMMUTABLE +AS $function$ + SELECT CASE WHEN p_val IS NULL THEN 'null'::jsonb + ELSE to_jsonb(p_val::TEXT) END; +$function$ +; + +-- DROP FUNCTION v3._expense_item_path_set(_int4); + +CREATE OR REPLACE FUNCTION v3._expense_item_path_set(p_eids integer[]) + RETURNS integer[] + LANGUAGE sql + STABLE +AS $function$ + WITH RECURSIVE up AS ( + SELECT id, parent_id FROM v3.expense_item WHERE id = ANY(p_eids) + UNION + SELECT p.id, p.parent_id + FROM v3.expense_item p + JOIN up u ON p.id = u.parent_id + ) + SELECT array_agg(DISTINCT id) FROM up; +$function$ +; + +-- DROP FUNCTION v3._jsonb_diff(jsonb, jsonb); + +CREATE OR REPLACE FUNCTION v3._jsonb_diff(a jsonb, b jsonb) + RETURNS jsonb + LANGUAGE plpgsql + IMMUTABLE +AS $function$ +DECLARE + result JSONB := '{}'::jsonb; + k TEXT; + va JSONB; + vb JSONB; + sub JSONB; +BEGIN + IF a IS NULL THEN + RETURN COALESCE(b, '{}'::jsonb); + END IF; + IF b IS NULL THEN + RETURN '{}'::jsonb; + END IF; + + -- Ключи `b`: если отличаются от `a` — добавляем + FOR k IN SELECT jsonb_object_keys(b) LOOP + va := a -> k; + vb := b -> k; + IF jsonb_typeof(va) = 'object' AND jsonb_typeof(vb) = 'object' THEN + sub := v3._jsonb_diff(va, vb); + IF sub <> '{}'::jsonb THEN + result := result || jsonb_build_object(k, sub); + END IF; + ELSIF va IS DISTINCT FROM vb THEN + result := result || jsonb_build_object(k, vb); + END IF; + END LOOP; + + -- Ключи которые БЫЛИ в `a` но УДАЛЕНЫ в `b` — представляем как null + FOR k IN SELECT jsonb_object_keys(a) LOOP + IF NOT (b ? k) THEN + result := result || jsonb_build_object(k, NULL); + END IF; + END LOOP; + + RETURN result; +END; +$function$ +; + +-- DROP FUNCTION v3._limit_template_path_set(_int4); + +CREATE OR REPLACE FUNCTION v3._limit_template_path_set(p_ids integer[]) + RETURNS integer[] + LANGUAGE sql + STABLE +AS $function$ + WITH RECURSIVE up AS ( + SELECT id, parent_id FROM v3.limit_template WHERE id = ANY(p_ids) + UNION + SELECT p.id, p.parent_id + FROM v3.limit_template p + JOIN up u ON p.id = u.parent_id + ) + SELECT array_agg(DISTINCT id) FROM up; +$function$ +; + +-- DROP FUNCTION v3.add_budget_form(varchar, int4, int4, int4); + +CREATE OR REPLACE FUNCTION v3.add_budget_form(p_form_type character varying, p_year integer, p_org_unit_id integer DEFAULT NULL::integer, p_user_id integer DEFAULT NULL::integer) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_form_id INTEGER; + v_ssp_list JSONB := '[]'::jsonb; +BEGIN + IF p_user_id IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM v3.app_user WHERE id = p_user_id) THEN + RAISE EXCEPTION 'app_user id=% не существует', p_user_id; + END IF; + + IF p_org_unit_id IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM v3.org_unit WHERE id = p_org_unit_id) THEN + RAISE EXCEPTION 'org_unit id=% не существует', p_org_unit_id; + END IF; + + INSERT INTO v3.budget_form + (form_type_code, year, org_unit_id, created_by) + VALUES + (p_form_type, p_year, p_org_unit_id, p_user_id) + RETURNING id INTO v_form_id; + + PERFORM v3.copy_template_to_form(v_form_id); + + IF p_org_unit_id IS NOT NULL THEN + SELECT jsonb_build_array( + jsonb_build_object( + 'id', ou.id, + 'title', ou.title + ) + ) + INTO v_ssp_list + FROM v3.org_unit ou + WHERE ou.id = p_org_unit_id; + END IF; + + PERFORM v3.log_event( + 'CREATE_TASK', 'TASK', + jsonb_build_object( + 'form_type', p_form_type, + 'year', p_year, + 'org_unit_id', p_org_unit_id, + 'ssp_list', COALESCE(v_ssp_list, '[]'::jsonb), + 'entity_id', v_form_id + ), + v_form_id, null, p_org_unit_id + ); + + RETURN v_form_id; +END; +$function$ +; + +-- DROP FUNCTION v3.add_budget_line(int4, int4, varchar, varchar, varchar, varchar, varchar, varchar, int4, int4, varchar, varchar, date); + +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) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_form_type VARCHAR; + v_org_id INT; + v_eid INT; + v_sheet VARCHAR; + v_auto_dir VARCHAR; + v_final_dir VARCHAR; + v_new_id INT; + v_cnt INT; + v_sat_table TEXT; + v_sat_eid INT; + v_parent_line INT; +BEGIN + -- 1. form_id → form_type + SELECT form_type_code, org_unit_id INTO v_form_type, v_org_id FROM v3.budget_form WHERE id = p_form_id; + IF v_form_type IS NULL THEN + RAISE EXCEPTION 'budget_form id=% не существует', p_form_id; + END IF; + IF v_form_type = 'FORM_3' THEN + RAISE EXCEPTION 'FORM_3 не использует budget_line — см. add_form3_line (TBD)'; + END IF; + + -- ═══ Сателлитные листы (AHR_RENT/UTILITY/SECURITY) ═══════════════════ + -- Фиксированный expense_item_id на лист — UI не передаёт R-код. + -- p_vsp_id обязателен. Опц.: p_contract_number, p_contract_end_date. + -- Внутри: находим/создаём родительскую budget_line с фиксированным ei, + -- затем INSERT в *_detail. Возврат — одна новая LEAF. + IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN + v_sat_table := CASE p_sheet + WHEN 'AHR_RENT' THEN 'rent_detail' + WHEN 'AHR_UTILITY' THEN 'utility_detail' + WHEN 'AHR_SECURITY' THEN 'security_detail' + END; + v_sat_eid := CASE p_sheet + WHEN 'AHR_RENT' THEN 293 -- R061031001 "Аренда_ОН" + WHEN 'AHR_UTILITY' THEN 24 -- 1.05.1. "Коммунальные услуги" + WHEN 'AHR_SECURITY' THEN 779 -- R061062001 "Пультовая охр.ОН" + END; + + IF p_vsp_id IS NULL THEN + RAISE EXCEPTION 'p_vsp_id обязателен для сателлитного листа %', p_sheet; + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.vsp WHERE id = p_vsp_id) THEN + RAISE EXCEPTION 'vsp id=% не существует', p_vsp_id; + END IF; + + -- Находим/создаём родительскую budget_line под фиксированным ei + SELECT id INTO v_parent_line + FROM v3.budget_line + WHERE budget_form_id = p_form_id AND expense_item_id = v_sat_eid + LIMIT 1; + IF v_parent_line IS NULL THEN + INSERT INTO v3.budget_line (budget_form_id, expense_item_id, direction) + VALUES ( + p_form_id, v_sat_eid, + CASE v_form_type WHEN 'FORM_2' THEN 'Support' WHEN 'FORM_4' THEN 'Development' ELSE NULL END + ) RETURNING id INTO v_parent_line; + END IF; + + -- INSERT в сателлит + EXECUTE format( + 'INSERT INTO v3.%I (line_id, vsp_id, contract_number, contract_end_date) ' + || 'VALUES ($1, $2, $3, $4) RETURNING id', + v_sat_table + ) USING v_parent_line, p_vsp_id, p_contract_number, p_contract_end_date + INTO v_new_id; + + -- Возврат — одна новая INPUT-строка (отфильтрована по новому id сателлита) + RETURN QUERY + SELECT v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form_view(p_form_id, p_sheet, NULL, NULL) v + WHERE v.row_type = 'INPUT' AND (v.data->>'id')::INT = v_new_id; + RETURN; + END IF; + + -- 2. Резолв expense_item_id + IF p_expense_item_id IS NOT NULL THEN + SELECT ei.id, ei.sheet INTO v_eid, v_sheet + FROM v3.expense_item ei WHERE ei.id = p_expense_item_id; + IF v_eid IS NULL THEN + RAISE EXCEPTION 'expense_item id=% не существует', p_expense_item_id; + END IF; + IF NOT EXISTS ( + SELECT 1 FROM v3.expense_item_form_type + WHERE expense_item_id = v_eid AND form_type_code = v_form_type + ) THEN + RAISE EXCEPTION 'expense_item id=% не привязан к form_type %', v_eid, v_form_type; + END IF; + ELSE + IF p_sheet IS NULL OR p_item_id IS NULL THEN + RAISE EXCEPTION 'нужен либо p_expense_item_id, либо (p_sheet + p_item_id)'; + END IF; + + SELECT count(*) INTO v_cnt + FROM v3.expense_item ei + JOIN v3.expense_item_form_type eift ON eift.expense_item_id = ei.id + WHERE ei.item_id = p_item_id + AND ei.sheet = p_sheet + AND eift.form_type_code = v_form_type + AND (p_section_code IS NULL OR ei.section_code = p_section_code) + AND (p_direction IS NULL OR ei.direction IS NULL OR ei.direction = p_direction); + + IF v_cnt = 0 THEN + RAISE EXCEPTION 'expense_item не найден: sheet=%, item_id=%, section=%, direction=%, form=%', + p_sheet, p_item_id, p_section_code, p_direction, v_form_type; + END IF; + IF v_cnt > 1 THEN + RAISE EXCEPTION 'expense_item не уникален (% совпадений): sheet=%, item_id=%, section=%, direction=%. Уточните p_section_code или передайте p_expense_item_id напрямую', + v_cnt, p_sheet, p_item_id, p_section_code, p_direction; + END IF; + + SELECT ei.id, ei.sheet INTO v_eid, v_sheet + FROM v3.expense_item ei + JOIN v3.expense_item_form_type eift ON eift.expense_item_id = ei.id + WHERE ei.item_id = p_item_id + AND ei.sheet = p_sheet + AND eift.form_type_code = v_form_type + AND (p_section_code IS NULL OR ei.section_code = p_section_code) + AND (p_direction IS NULL OR ei.direction IS NULL OR ei.direction = p_direction); + END IF; + + -- 3. Авто-direction (для FORM_1/AHR|CAP — обязателен у юзера) + v_auto_dir := CASE + WHEN v_form_type = 'FORM_2' THEN 'Support' + WHEN v_form_type = 'FORM_4' THEN 'Development' + WHEN v_form_type = 'FORM_1' AND v_sheet = 'OPER' THEN NULL + ELSE NULL + END; + + IF v_form_type = 'FORM_1' AND v_sheet IN ('AHR','CAP') AND p_direction IS NULL THEN + RAISE EXCEPTION 'p_direction обязателен для FORM_1 / sheet=% (Support|Development)', v_sheet; + END IF; + + IF v_form_type IN ('FORM_2','FORM_4') AND p_direction IS NOT NULL AND p_direction <> v_auto_dir THEN + RAISE EXCEPTION 'p_direction=% несовместим с form_type=% (фиксирован=%)', + p_direction, v_form_type, v_auto_dir; + END IF; + + IF v_form_type = 'FORM_1' AND v_sheet = 'OPER' AND p_direction IS NOT NULL THEN + RAISE EXCEPTION 'p_direction должен быть NULL для FORM_1 / sheet=OPER (получено: %)', p_direction; + END IF; + + v_final_dir := COALESCE(p_direction, v_auto_dir); + + -- 4. Sanity на vsp/project + IF p_vsp_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM v3.vsp WHERE id = p_vsp_id) THEN + RAISE EXCEPTION 'vsp id=% не существует', p_vsp_id; + END IF; + IF p_project_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_project_id) THEN + RAISE EXCEPTION 'project id=% не существует', p_project_id; + END IF; + + -- 5. INSERT + INSERT INTO v3.budget_line ( + budget_form_id, expense_item_id, name, internal_order, + vsp_id, project_id, direction, justification + ) VALUES ( + p_form_id, v_eid, p_name, p_internal_order, + p_vsp_id, p_project_id, v_final_dir, p_justification + ) RETURNING id INTO v_new_id; + + -- 5а. Аудит: ROW_CREATE + PERFORM v3.log_event( + 'ROW_CREATE', 'ROW', + jsonb_build_object( + 'sheet', v_sheet, + 'line_id', v_new_id, + 'expense_item_id', v_eid, + 'direction', v_final_dir, + 'name', p_name, + 'vsp_id', p_vsp_id, + 'project_id', p_project_id, + 'entity_id', v_new_id, + 'form_id', p_form_id + ), + p_form_id, null, v_org_id + ); + + -- 6. Возврат: только иерархия по пути нового ei + новая INPUT + DECLARE v_ei_path INT[]; + BEGIN + v_ei_path := v3._expense_item_path_set(ARRAY[v_eid]); + RETURN QUERY + SELECT v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form_view(p_form_id, v_sheet, NULL, v_final_dir) v + WHERE (v.row_type IN ('ROOT','GROUP','ITEM','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 = v_new_id) + ORDER BY v.sort_order; + END; +END; +$function$ +; + +-- DROP FUNCTION v3.add_form3_line(int4, int4); + +CREATE OR REPLACE FUNCTION v3.add_form3_line(p_report_id integer, p_expense_item_id integer) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_new_id INT; + v_project_id INT; + v_org_id INT; +BEGIN + SELECT project_id INTO v_project_id FROM v3.rf_project_report WHERE id = p_report_id; + IF v_project_id IS NULL THEN + RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id; + + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.expense_item WHERE id = p_expense_item_id) THEN + RAISE EXCEPTION 'expense_item id=% не существует', p_expense_item_id; + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.expense_item_form_type + WHERE expense_item_id = p_expense_item_id AND form_type_code = 'FORM_3') THEN + RAISE EXCEPTION 'expense_item id=% не привязан к form_type FORM_3', p_expense_item_id; + END IF; + + INSERT INTO v3.rf_project_report_line (rf_project_report_id, expense_item_id) + VALUES (p_report_id, p_expense_item_id) + RETURNING id INTO v_new_id; + + SELECT org_unit_id INTO v_org_id FROM v3.project WHERE id = v_project_id; + + + + -- Аудит: ROW_CREATE + PERFORM v3.log_event( + 'ROW_CREATE', 'ROW', + jsonb_build_object( + 'sheet', 'FORM_3', + 'line_id', v_new_id, + 'report_id', p_report_id, + 'expense_item_id', p_expense_item_id, + 'entity_id', v_new_id + ), null, null, v_org_id + ); + + -- Возврат: только иерархия по пути нового ei + новая INPUT + DECLARE v_ei_path INT[]; + BEGIN + v_ei_path := v3._expense_item_path_set(ARRAY[p_expense_item_id]); + RETURN QUERY + SELECT v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form3_report_jsonb(p_report_id, NULL) v + WHERE (v.row_type IN ('ROOT','GROUP','ITEM','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 = v_new_id) + ORDER BY v.sort_order; + END; +END; +$function$ +; + +-- DROP FUNCTION v3.add_form_phase(int4, varchar, varchar, varchar, _text, timestamptz, timestamptz); + +CREATE OR REPLACE FUNCTION v3.add_form_phase(p_budget_form_id integer, p_sheet character varying, p_phase_code character varying, p_role character varying, p_column_keys text[], p_opens_at timestamp with time zone, p_closes_at timestamp with time zone) + RETURNS v3.form_phase + LANGUAGE plpgsql +AS $function$ +DECLARE + v_phase v3.form_phase; + v_org_unit_id INT; + v_org_unit_title VARCHAR; + v_form_type_code VARCHAR; + v_year INT; + v_task_name TEXT; + v_phase_id TEXT; +BEGIN + INSERT INTO v3.form_phase( + budget_form_id, sheet, phase_code, role, column_keys, opens_at, closes_at + ) + VALUES ( + p_budget_form_id, p_sheet, p_phase_code, p_role, p_column_keys, p_opens_at, p_closes_at + ) + RETURNING * INTO v_phase; + + SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year + INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year + FROM v3.budget_form bf + LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id + WHERE bf.id = p_budget_form_id; + v_task_name := trim(concat( + COALESCE(v_form_type_code, ''), + CASE WHEN v_year IS NOT NULL THEN ' ' || v_year::TEXT ELSE '' END, + CASE WHEN v_org_unit_title IS NOT NULL THEN ' [' || v_org_unit_title || ']' ELSE '' END + )); + v_phase_id := format('%s:%s:%s', p_budget_form_id, p_sheet, p_phase_code); + + PERFORM v3.log_event( + 'ACCESS_WINDOW_CHANGE', 'ACCESS', + jsonb_build_object( + 'entity_type', 'form_phase', + 'entity_id', p_budget_form_id, + 'action', 'create', + 'phase_id', v_phase_id, + 'phase_name', p_phase_code, + 'task_id', p_budget_form_id, + 'form_id', p_budget_form_id, + 'budget_form_id', p_budget_form_id, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + 'sheet', p_sheet, + 'phase_code', p_phase_code, + 'role', v_phase.role, + 'column_keys', v_phase.column_keys, + 'opens_at', v_phase.opens_at, + 'closes_at', v_phase.closes_at + ), + p_budget_form_id, null, v_org_unit_id + ); + + RETURN v_phase; +END; +$function$ +; + +-- DROP FUNCTION v3.add_project(varchar, int4, int4, varchar, int4, varchar, varchar, varchar, varchar, int4, numeric); + +CREATE OR REPLACE FUNCTION v3.add_project(p_name character varying, p_year integer, p_org_unit_id integer, p_level character varying DEFAULT 'project'::character varying, p_parent_id integer DEFAULT NULL::integer, p_project_type character varying DEFAULT NULL::character varying, p_vsp_format character varying DEFAULT NULL::character varying, p_placement_type character varying DEFAULT NULL::character varying, p_object_address character varying DEFAULT NULL::character varying, p_staff_count integer DEFAULT NULL::integer, p_total_area numeric DEFAULT NULL::numeric) + RETURNS TABLE(project_id integer, limit_report_id integer, current_expenses_report_id integer) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_pid INT; + v_lim INT; + v_cur INT; +BEGIN + IF p_name IS NULL OR length(trim(p_name)) = 0 THEN + RAISE EXCEPTION 'p_name обязателен'; + END IF; + IF p_year IS NULL THEN + RAISE EXCEPTION 'p_year обязателен'; + END IF; + IF p_org_unit_id IS NULL THEN + RAISE EXCEPTION 'p_org_unit_id обязателен'; + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.org_unit WHERE id = p_org_unit_id) THEN + RAISE EXCEPTION 'org_unit id=% не существует', p_org_unit_id; + END IF; + IF p_parent_id IS NOT NULL AND + NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_parent_id AND level = 'program') THEN + RAISE EXCEPTION 'parent project id=% не существует или не имеет level=program', p_parent_id; + END IF; + + -- INSERT project (CHECK-ы name/project_type/vsp_format/placement_type + -- из migrate_form3_v1.sql применятся автоматически). + INSERT INTO v3.project ( + name, level, parent_id, org_unit_id, + project_type, vsp_format, placement_type, object_address, staff_count, total_area + ) VALUES ( + p_name, p_level, p_parent_id, p_org_unit_id, + p_project_type, p_vsp_format, p_placement_type, p_object_address, p_staff_count, p_total_area + ) RETURNING id INTO v_pid; + + -- Создаём 2 отчёта (LIMIT + CURRENT_EXPENSES) для проекта × год + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (v_pid, p_year, 'LIMIT') RETURNING id INTO v_lim; + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (v_pid, p_year, 'CURRENT_EXPENSES') RETURNING id INTO v_cur; + + -- Аудит: PROJECT_CREATE — все заполненные поля + id отчётов + PERFORM v3.log_event( + 'PROJECT_CREATE', 'PROJECT', + jsonb_strip_nulls(jsonb_build_object( + 'project_id', v_pid, + 'name', p_name, + 'year', p_year, + 'level', p_level, + 'parent_id', p_parent_id, + 'org_unit_id', p_org_unit_id, + 'project_type', p_project_type, + 'vsp_format', p_vsp_format, + 'placement_type', p_placement_type, + 'object_address', p_object_address, + 'staff_count', p_staff_count, + 'total_area', p_total_area, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur, + 'entity_id', v_pid + )), null, null, p_org_unit_id + ); + + RETURN QUERY SELECT v_pid, v_lim, v_cur; +END; +$function$ +; + +-- DROP FUNCTION v3.add_user(varchar, varchar, varchar, varchar, int4); + +CREATE OR REPLACE FUNCTION v3.add_user(p_email character varying, p_username character varying, p_hashed_password character varying, p_full_name character varying, p_role_id integer) + RETURNS v3.app_user + LANGUAGE plpgsql +AS $function$ +DECLARE + v_user v3.app_user; +BEGIN + INSERT INTO v3.app_user(email, username, hashed_password, full_name, role_id, is_active) + VALUES (p_email, p_username, p_hashed_password, p_full_name, p_role_id, TRUE) + RETURNING * INTO v_user; + + PERFORM v3.log_event( + 'USER_CREATE', 'USER', + jsonb_strip_nulls(jsonb_build_object( + 'user_id', v_user.id, + 'email', v_user.email, + 'username', v_user.username, + 'full_name', v_user.full_name, + 'role_id', v_user.role_id, + 'entity_id', v_user.id + )) + ); + + RETURN v_user; +END; +$function$ +; + +-- DROP FUNCTION v3.add_vsp(int4, varchar, varchar, varchar, date, varchar, int4, numeric, date, bool, bool, varchar, int4, varchar, text, varchar, int4, varchar, date); + +CREATE OR REPLACE FUNCTION v3.add_vsp(p_branch_id integer, p_reg_number character varying DEFAULT NULL::character varying, p_address character varying DEFAULT NULL::character varying, p_format character varying DEFAULT NULL::character varying, p_opened_at date DEFAULT NULL::date, p_placement_type character varying DEFAULT NULL::character varying, p_staff_count integer DEFAULT NULL::integer, p_total_area numeric DEFAULT NULL::numeric, p_closed_at date DEFAULT NULL::date, p_is_active boolean DEFAULT true, p_is_deleted boolean DEFAULT false, p_system_code character varying DEFAULT NULL::character varying, p_created_by integer DEFAULT NULL::integer, p_vsp_type character varying DEFAULT NULL::character varying, p_notes text DEFAULT NULL::text, p_location_form character varying DEFAULT NULL::character varying, p_numbers integer DEFAULT NULL::integer, p_rent_contract_num character varying DEFAULT NULL::character varying, p_rent_end_date date DEFAULT NULL::date) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_id INT; + v_branch_name VARCHAR; + v_actor_id INT; + v_actor_email VARCHAR; + v_actor_full_name VARCHAR; +BEGIN + SELECT ou.title INTO v_branch_name + FROM v3.org_unit ou + WHERE ou.id = p_branch_id; + + v_actor_id := COALESCE(p_created_by, v3.current_user_id()); + + IF v_actor_id IS NOT NULL THEN + SELECT u.email, u.full_name + INTO v_actor_email, v_actor_full_name + FROM v3.app_user u + WHERE u.id = v_actor_id; + END IF; + + INSERT INTO v3.vsp + (branch_id, reg_number, address, format, opened_at, + placement_type, staff_count, total_area, closed_at, + is_active, is_deleted, system_code, created_by, + vsp_type, notes, location_form, numbers, + rent_contract_num, rent_end_date) + VALUES + (p_branch_id, p_reg_number, p_address, p_format, p_opened_at, + p_placement_type, p_staff_count, p_total_area, p_closed_at, + p_is_active, p_is_deleted, p_system_code, p_created_by, + p_vsp_type, p_notes, p_location_form, p_numbers, + p_rent_contract_num, p_rent_end_date) + RETURNING id INTO v_id; + + PERFORM v3.log_event( + 'VSP_CREATE', 'VSP', + jsonb_build_object( + 'vsp_id', v_id, + 'entity_id', v_id, + 'branch_id', p_branch_id, + 'branch_name', v_branch_name, + 'reg_number', p_reg_number, + 'address', p_address, + 'format', p_format, + 'opened_at', v3._diff_val(p_opened_at), + 'placement_type', p_placement_type, + 'staff_count', p_staff_count, + 'total_area', v3._diff_val(p_total_area), + 'closed_at', v3._diff_val(p_closed_at), + 'is_active', p_is_active, + 'is_deleted', p_is_deleted, + 'system_code', p_system_code, + 'created_by', p_created_by, + 'vsp_type', p_vsp_type, + 'notes', p_notes, + 'location_form', p_location_form, + 'numbers', p_numbers, + 'rent_contract_num', p_rent_contract_num, + 'rent_end_date', v3._diff_val(p_rent_end_date), + 'user_email', v_actor_email, + 'user_full_name', v_actor_full_name + ), null, null, p_branch_id + ); + + RETURN v_id; +END; +$function$ +; + +-- DROP FUNCTION v3.can_edit(int4, varchar, text, int4); + +CREATE OR REPLACE FUNCTION v3.can_edit(p_form_id integer, p_sheet character varying, p_column_key text, p_user_id integer) + RETURNS jsonb + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + v_role VARCHAR; + v_active_close TIMESTAMPTZ; + v_role_has_col BOOLEAN; + v_next_open TIMESTAMPTZ; + v_last_close TIMESTAMPTZ; +BEGIN + 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; + + v_role := v3.user_role_code(p_user_id); + IF v_role IS NULL THEN + RETURN jsonb_build_object( + 'ok', false, + 'code', 'role_not_allowed', + 'detail', 'unknown or inactive user' + ); + END IF; + + -- ADMIN — bypass всех проверок. + IF v_role = 'ADMIN' THEN + RETURN jsonb_build_object('ok', true, 'admin', true); + END IF; + + -- EXECUTOR_RF: фильтр по user_org формы. + IF v_role = 'EXECUTOR_RF' AND NOT v3.user_in_form_org(p_user_id, p_form_id) THEN + RETURN jsonb_build_object( + 'ok', false, + 'code', 'org_not_assigned', + 'detail', p_column_key + ); + END IF; + + -- 1. Активная сейчас фаза, в которой эта колонка перечислена для этой роли? + SELECT MAX(fp.closes_at) + INTO v_active_close + FROM v3.form_phase fp + WHERE fp.budget_form_id = p_form_id + AND fp.sheet = p_sheet + AND fp.role = v_role + AND p_column_key = ANY(fp.column_keys) + AND now() BETWEEN fp.opens_at AND fp.closes_at; + + IF v_active_close IS NOT NULL THEN + RETURN jsonb_build_object( + 'ok', true, + 'closes_at', v_active_close + ); + END IF; + + -- 2. Колонка В ПРИНЦИПЕ есть хоть в одной фазе этой роли (на этой форме/листе)? + -- Если нет — это вопрос whitelist, отдаём role_not_allowed. + SELECT EXISTS ( + SELECT 1 + FROM v3.form_phase fp + WHERE fp.budget_form_id = p_form_id + AND fp.sheet = p_sheet + AND fp.role = v_role + AND p_column_key = ANY(fp.column_keys) + ) INTO v_role_has_col; + + IF NOT v_role_has_col THEN + RETURN jsonb_build_object( + 'ok', false, + 'code', 'role_not_allowed', + 'detail', p_column_key + ); + END IF; + + -- 3. Колонка доступна, но окно не активно. Подсказать ближайшее. + SELECT MIN(fp.opens_at) + INTO v_next_open + FROM v3.form_phase fp + WHERE fp.budget_form_id = p_form_id + AND fp.sheet = p_sheet + AND fp.role = v_role + AND p_column_key = ANY(fp.column_keys) + AND fp.opens_at > now(); + + IF v_next_open IS NOT NULL THEN + RETURN jsonb_build_object( + 'ok', false, + 'code', 'window_closed', + 'detail', p_column_key, + 'opens_at', v_next_open + ); + END IF; + + SELECT MAX(fp.closes_at) + INTO v_last_close + FROM v3.form_phase fp + WHERE fp.budget_form_id = p_form_id + AND fp.sheet = p_sheet + AND fp.role = v_role + AND p_column_key = ANY(fp.column_keys) + AND fp.closes_at <= now(); + + RETURN jsonb_build_object( + 'ok', false, + 'code', 'window_closed', + 'detail', p_column_key, + 'closes_at', v_last_close + ); +END; +$function$ +; + +-- DROP FUNCTION v3.copy_template_to_form(int4); + +CREATE OR REPLACE FUNCTION v3.copy_template_to_form(p_form_id integer) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_form_type VARCHAR; + v_inserted INTEGER; +BEGIN + SELECT form_type_code INTO v_form_type + FROM v3.budget_form + WHERE id = p_form_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'budget_form id=% не существует', p_form_id; + END IF; + + INSERT INTO v3.form_phase + (budget_form_id, sheet, phase_code, role, column_keys, opens_at, closes_at) + SELECT p_form_id, pt.sheet, pt.phase_code, pt.role, + pt.column_keys, pt.opens_at, pt.closes_at + FROM v3.phase_template pt + WHERE pt.form_type = v_form_type + ON CONFLICT (budget_form_id, sheet, phase_code) DO NOTHING; + + GET DIAGNOSTICS v_inserted = ROW_COUNT; + RETURN v_inserted; +END; +$function$ +; + +-- DROP FUNCTION v3.current_user_id(); + +CREATE OR REPLACE FUNCTION v3.current_user_id() + RETURNS integer + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + v_raw TEXT; + v_id INTEGER; +BEGIN + v_raw := current_setting('app.user_id', true); + IF v_raw IS NULL OR v_raw = '' THEN + RETURN NULL; + END IF; + BEGIN + v_id := v_raw::INTEGER; + EXCEPTION WHEN OTHERS THEN + RETURN NULL; + END; + RETURN v_id; +END; +$function$ +; + +-- DROP FUNCTION v3.deactivate_vsp(int4); + +CREATE OR REPLACE FUNCTION v3.deactivate_vsp(p_vsp_id integer) + RETURNS date + LANGUAGE plpgsql +AS $function$ +DECLARE + v_closed DATE; + v_old_closed DATE; + v_branch_id INT; + v_branch_name VARCHAR; + v_actor_id INT; + v_actor_email VARCHAR; + v_actor_full_name VARCHAR; +BEGIN + SELECT v.branch_id, v.closed_at, ou.title + INTO v_branch_id, v_old_closed, v_branch_name + FROM v3.vsp v + LEFT JOIN v3.org_unit ou ON ou.id = v.branch_id + WHERE v.id = p_vsp_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'vsp #% не существует', p_vsp_id; + END IF; + IF v_old_closed IS NOT NULL THEN + RAISE EXCEPTION 'vsp #% уже закрыт', p_vsp_id; + END IF; + + UPDATE v3.vsp SET closed_at = CURRENT_DATE + WHERE id = p_vsp_id + RETURNING closed_at INTO v_closed; + + v_actor_id := v3.current_user_id(); + IF v_actor_id IS NOT NULL THEN + SELECT u.email, u.full_name + INTO v_actor_email, v_actor_full_name + FROM v3.app_user u + WHERE u.id = v_actor_id; + END IF; + + PERFORM v3.log_event( + 'VSP_UPDATE', 'VSP', + jsonb_build_object( + 'vsp_id', p_vsp_id, + 'branch_id', v_branch_id, + 'branch_name', v_branch_name, + 'changes', jsonb_build_object( + 'closed_at', jsonb_build_object( + 'before', v3._diff_val(v_old_closed), + 'after', v3._diff_val(v_closed) + ) + ), + 'closed_at', v_closed::TEXT, + 'user_id', v_actor_id, + 'user_email', v_actor_email, + 'user_full_name', v_actor_full_name + ), null, null, v_branch_id); + + RETURN v_closed; +END; +$function$ +; + +-- DROP FUNCTION v3.del_budget_form(int4); + +CREATE OR REPLACE FUNCTION v3.del_budget_form(p_form_id integer) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old RECORD; +BEGIN + SELECT bf.form_type_code, bf.year, bf.org_unit_id + INTO v_old + FROM v3.budget_form bf WHERE bf.id = p_form_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'budget_form #% не существует', p_form_id; + END IF; + + DELETE FROM v3.budget_form WHERE id = p_form_id; + + PERFORM v3.log_event( + 'DELETE_TASK', 'TASK', + jsonb_build_object( + 'form_id', p_form_id, + 'form_type', v_old.form_type_code, + 'year', v_old.year, + 'org_unit_id', v_old.org_unit_id), + p_form_id, null, v_old.org_unit_id); + + RETURN p_form_id; +END; +$function$ +; + +-- DROP FUNCTION v3.del_budget_line(int4, varchar, varchar); + +CREATE OR REPLACE FUNCTION v3.del_budget_line(p_id integer, p_direction character varying DEFAULT NULL::character varying, p_sheet character varying DEFAULT NULL::character varying) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_form_id INT; + v_org_id INT; + v_sheet VARCHAR; + v_sat_table TEXT; + v_eid INT; + v_ei_path INT[]; +BEGIN + -- ═══ Сателлитные листы — DELETE из *_detail по id ═════════════════════ + IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN + v_sat_table := CASE p_sheet + WHEN 'AHR_RENT' THEN 'rent_detail' + WHEN 'AHR_UTILITY' THEN 'utility_detail' + WHEN 'AHR_SECURITY' THEN 'security_detail' + END; + + -- Извлекаем form_id для возврата (через JOIN на budget_line) + EXECUTE format( + 'SELECT bl.budget_form_id FROM v3.%I s + JOIN v3.budget_line bl ON bl.id = s.line_id + WHERE s.id = $1', v_sat_table + ) USING p_id INTO v_form_id; + + IF v_form_id IS NULL THEN + RAISE EXCEPTION '%.id=% не существует', v_sat_table, p_id; + END IF; + + EXECUTE format('DELETE FROM v3.%I WHERE id = $1', v_sat_table) USING p_id; + + -- Возврат пустой — UI сам уберёт строку по id + RETURN; + END IF; + + -- ═══ Основная сетка ═══════════════════════════════════════════════════ + -- Sanity + извлечение form_id/sheet/expense_item_id (нужен для path + -- ДО DELETE — после удаления budget_line его уже не получить). + SELECT bl.budget_form_id, ei.sheet, bl.expense_item_id + INTO v_form_id, v_sheet, v_eid + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + WHERE bl.id = p_id; + + IF v_form_id IS NULL THEN + RAISE EXCEPTION 'budget_line id=% не существует', p_id; + END IF; + + v_ei_path := v3._expense_item_path_set(ARRAY[v_eid]); + + -- Каскадное удаление дочерних записей + DELETE FROM v3.plan WHERE line_id = p_id; + DELETE FROM v3.sequestration WHERE line_id = p_id; + DELETE FROM v3.reserve WHERE line_id = p_id; + DELETE FROM v3.allocation WHERE line_id = p_id; + DELETE FROM v3.contract_summary WHERE line_id = p_id; + DELETE FROM v3.collegial_approval WHERE line_id = p_id; + DELETE FROM v3.ckk WHERE line_id = p_id; + DELETE FROM v3.contract_detail WHERE line_id = p_id; + DELETE FROM v3.booking WHERE line_id = p_id; + DELETE FROM v3.budget_line_quarter WHERE line_id = p_id; + DELETE FROM v3.rent_detail WHERE line_id = p_id; + DELETE FROM v3.utility_detail WHERE line_id = p_id; + DELETE FROM v3.security_detail WHERE line_id = p_id; + -- AHR_LIMIT с v3 не имеет привязки к budget_line — каталог limit_template + -- глобальный, per-form qty в form_limit удаляется через ON DELETE CASCADE + -- от budget_form. + + -- Удаляем сам budget_line + DELETE FROM v3.budget_line WHERE id = p_id; + + SELECT org_unit_id INTO v_org_id FROM v3.budget_form WHERE id = v_form_id; + + -- Аудит: ROW_DELETE + PERFORM v3.log_event( + 'ROW_DELETE', 'ROW', + jsonb_build_object( + 'sheet', v_sheet, + 'line_id', p_id, + 'entity_id', p_id, + 'expense_item_id', v_eid, + 'direction', p_direction + ), + v_form_id, NULL, v_org_id + ); + + -- Возврат: иерархия только по пути удалённой строки (INPUT уже нет) + RETURN QUERY + SELECT v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form_view(v_form_id, v_sheet, NULL, p_direction) v + WHERE v.row_type IN ('ROOT','GROUP','ITEM','SUB_ITEM') + AND (v.data->'header'->>'expense_item_id')::INT = ANY(v_ei_path) + ORDER BY v.sort_order; +END; +$function$ +; + +-- DROP FUNCTION v3.del_form3_line(int4); + +CREATE OR REPLACE FUNCTION v3.del_form3_line(p_line_id integer) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_report_id INT; + v_eid INT; + v_org_id INT; + v_ei_path INT[]; +BEGIN + -- Извлекаем report_id + ei ДО DELETE (нужен для path) + SELECT rfprl.rf_project_report_id, rfprl.expense_item_id + INTO v_report_id, v_eid + FROM v3.rf_project_report_line rfprl + WHERE rfprl.id = p_line_id; + IF v_report_id IS NULL THEN + RAISE EXCEPTION 'rf_project_report_line id=% не существует', p_line_id; + END IF; + + v_ei_path := v3._expense_item_path_set(ARRAY[v_eid]); + + -- Каскад (FK без ON DELETE CASCADE) + DELETE FROM v3.rf_project_report_quarter WHERE rf_project_report_line_id = p_line_id; + DELETE FROM v3.rf_project_report_line WHERE id = p_line_id; + + SELECT org_unit_id INTO v_org_id FROM v3.project WHERE id = v_report_id; + -- Аудит: ROW_DELETE + PERFORM v3.log_event( + 'ROW_DELETE', 'ROW', + jsonb_build_object( + 'sheet', 'FORM_3', + 'line_id', p_line_id, + 'entity_id', p_line_id, + 'report_id', v_report_id, + 'expense_item_id', v_eid + ), null, null, v_org_id + ); + + -- Возврат: только иерархия по пути удалённой (INPUT уже нет) + RETURN QUERY + SELECT v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form3_report_jsonb(v_report_id, NULL) v + WHERE v.row_type IN ('ROOT','GROUP','ITEM','SUB_ITEM') + AND (v.data->'header'->>'expense_item_id')::INT = ANY(v_ei_path) + ORDER BY v.sort_order; +END; +$function$ +; + +-- DROP FUNCTION v3.del_form_phase(int4, varchar, varchar); + +CREATE OR REPLACE FUNCTION v3.del_form_phase(p_budget_form_id integer, p_sheet character varying, p_phase_code character varying) + RETURNS boolean + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old v3.form_phase; + v_org_unit_id INT; + v_org_unit_title VARCHAR; + v_form_type_code VARCHAR; + v_year INT; + v_task_name TEXT; + v_phase_id TEXT; +BEGIN + DELETE FROM v3.form_phase + WHERE budget_form_id = p_budget_form_id + AND sheet = p_sheet + AND phase_code = p_phase_code + RETURNING * INTO v_old; + + IF NOT FOUND THEN + RETURN FALSE; + END IF; + + SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year + INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year + FROM v3.budget_form bf + LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id + WHERE bf.id = p_budget_form_id; + v_task_name := trim(concat( + COALESCE(v_form_type_code, ''), + CASE WHEN v_year IS NOT NULL THEN ' ' || v_year::TEXT ELSE '' END, + CASE WHEN v_org_unit_title IS NOT NULL THEN ' [' || v_org_unit_title || ']' ELSE '' END + )); + v_phase_id := format('%s:%s:%s', p_budget_form_id, p_sheet, p_phase_code); + + PERFORM v3.log_event( + 'ACCESS_WINDOW_CHANGE', 'ACCESS', + jsonb_build_object( + 'entity_type', 'form_phase', + 'entity_id', p_budget_form_id, + 'action', 'delete', + 'phase_id', v_phase_id, + 'phase_name', p_phase_code, + 'task_id', p_budget_form_id, + 'task_name', NULLIF(v_task_name, ''), + 'form_id', p_budget_form_id, + 'budget_form_id', p_budget_form_id, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + 'sheet', p_sheet, + 'phase_code', p_phase_code, + 'role', v_old.role, + 'column_keys', v_old.column_keys, + 'opens_at', v_old.opens_at, + 'closes_at', v_old.closes_at + ), + p_budget_form_id, null, v_org_unit_id + ); + RETURN TRUE; +END; +$function$ +; + +-- DROP FUNCTION v3.del_project(int4); + +CREATE OR REPLACE FUNCTION v3.del_project(p_project_id integer) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old RECORD; +BEGIN + SELECT p.name, p.level, p.org_unit_id + INTO v_old + FROM v3.project p WHERE p.id = p_project_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'project #% не существует', p_project_id; + END IF; + + -- Каскад вручную: rf_project_report_quarter / line / report (нет ON DELETE) + DELETE FROM v3.rf_project_report_quarter + WHERE rf_project_report_line_id IN ( + SELECT l.id FROM v3.rf_project_report_line l + JOIN v3.rf_project_report r ON r.id = l.rf_project_report_id + WHERE r.project_id = p_project_id); + DELETE FROM v3.rf_project_report_line + WHERE rf_project_report_id IN ( + SELECT id FROM v3.rf_project_report WHERE project_id = p_project_id); + DELETE FROM v3.rf_project_report WHERE project_id = p_project_id; + DELETE FROM v3.project WHERE id = p_project_id; + + PERFORM v3.log_event( + 'PROJECT_DELETE', 'PROJECT', + jsonb_build_object( + 'project_id', p_project_id, + 'name', v_old.name, + 'level', v_old.level, + 'org_unit_id', v_old.org_unit_id), null, null, v_old.org_unit_id); + + RETURN p_project_id; +END; +$function$ +; + +-- DROP FUNCTION v3.del_vsp(int4, int4); + +CREATE OR REPLACE FUNCTION v3.del_vsp(p_vsp_id integer, p_deleted_by integer) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old RECORD; + v_usr RECORD; + v_closed DATE; +BEGIN + SELECT vsp.id, vsp.branch_id + INTO v_old + FROM v3.vsp vsp WHERE vsp.id = p_vsp_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'vsp #% не существует', p_vsp_id; + END IF; + SELECT usr.id + INTO v_usr + FROM v3.app_user usr WHERE usr.id = p_deleted_by; + IF NOT FOUND THEN + RAISE EXCEPTION 'user #% не существует', p_deleted_by; + END IF; + + + UPDATE v3.vsp SET is_deleted = true, updated_by = p_deleted_by WHERE id = p_vsp_id; + UPDATE v3.vsp SET closed_at = CURRENT_DATE + WHERE id = p_vsp_id AND closed_at IS NULL + RETURNING closed_at INTO v_closed; + + IF v_closed IS NULL THEN + IF EXISTS (SELECT 1 FROM v3.vsp WHERE id = p_vsp_id) THEN + RAISE EXCEPTION 'vsp #% уже закрыт', p_vsp_id; + ELSE + RAISE EXCEPTION 'vsp #% не существует', p_vsp_id; + END IF; + END IF; + + PERFORM v3.log_event( + 'DELETE_VSP', 'VSP', + jsonb_build_object( + 'vsp_id', p_vsp_id, + 'entity_id', p_vsp_id, + 'branch_id', v_old.branch_id, + 'updated_by', p_deleted_by +), +--vsp_id, +null, null, v_old.branch_id); + + RETURN p_vsp_id; +END; +$function$ +; + +-- DROP FUNCTION v3.editable_columns_for(int4, int4, varchar); + +CREATE OR REPLACE FUNCTION v3.editable_columns_for(p_form_id integer, p_user_id integer, p_sheet character varying) + RETURNS TABLE(column_key text, closes_at timestamp with time zone) + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + v_role VARCHAR; +BEGIN + 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; + + v_role := v3.user_role_code(p_user_id); + IF v_role IS NULL THEN + RETURN; -- неизвестный/неактивный юзер → пустая маска + END IF; + + -- ADMIN: вернуть UNION всех column_keys всех phase'ов на форме/листе. + IF v_role = 'ADMIN' THEN + RETURN QUERY + SELECT ck AS column_key, + MAX(fp.closes_at) AS closes_at + FROM v3.form_phase fp, + LATERAL unnest(fp.column_keys) AS ck + WHERE fp.budget_form_id = p_form_id + AND fp.sheet = p_sheet + GROUP BY ck; + RETURN; + END IF; + + -- EXECUTOR_RF без назначения → пустая маска (default deny). + IF v_role = 'EXECUTOR_RF' AND NOT v3.user_in_form_org(p_user_id, p_form_id) THEN + RETURN; + END IF; + + RETURN QUERY + SELECT ck AS column_key, + MAX(fp.closes_at) AS closes_at + FROM v3.form_phase fp, + LATERAL unnest(fp.column_keys) AS ck + WHERE fp.budget_form_id = p_form_id + AND fp.sheet = p_sheet + AND fp.role = v_role + AND now() BETWEEN fp.opens_at AND fp.closes_at + GROUP BY ck; +END; +$function$ +; + +-- DROP FUNCTION v3.grant_user_org_access(int4, int4); + +CREATE OR REPLACE FUNCTION v3.grant_user_org_access(p_user_id integer, p_org_unit_id integer) + RETURNS boolean + LANGUAGE plpgsql +AS $function$ +DECLARE + v_inserted INT; + v_user_email VARCHAR; + v_user_full_name VARCHAR; + v_org_name VARCHAR; +BEGIN + SELECT email, full_name + INTO v_user_email, v_user_full_name + FROM v3.app_user + WHERE id = p_user_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'app_user #% не существует', p_user_id; + END IF; + + SELECT title INTO v_org_name FROM v3.org_unit WHERE id = p_org_unit_id; + IF v_org_name IS NULL THEN + RAISE EXCEPTION 'org_unit #% не существует', p_org_unit_id; + END IF; + + INSERT INTO v3.user_org(user_id, org_unit_id) + VALUES (p_user_id, p_org_unit_id) + ON CONFLICT (user_id, org_unit_id) DO NOTHING; + + GET DIAGNOSTICS v_inserted = ROW_COUNT; + + IF v_inserted > 0 THEN + PERFORM v3.log_event( + 'USER_ACCESS_GRANTED', 'USER', + jsonb_build_object( + 'user_id', p_user_id, + 'entity_id', p_user_id, + 'email', v_user_email, + 'full_name', v_user_full_name, + 'org_unit_id', p_org_unit_id, + 'org_unit_name', v_org_name + ), null, null, p_org_unit_id + ); + RETURN TRUE; + END IF; + + RETURN FALSE; +END; +$function$ +; + +-- DROP FUNCTION v3.jsonb_numeric_sum(jsonb, jsonb); + +CREATE OR REPLACE FUNCTION v3.jsonb_numeric_sum(a jsonb, b jsonb) + RETURNS jsonb + LANGUAGE sql + IMMUTABLE +AS $function$ + SELECT CASE + WHEN a IS NULL THEN b + WHEN b IS NULL THEN a + WHEN jsonb_typeof(a) = 'number' AND jsonb_typeof(b) = 'number' THEN + to_jsonb((a)::text::numeric + (b)::text::numeric) + WHEN jsonb_typeof(a) = 'object' AND jsonb_typeof(b) = 'object' THEN ( + SELECT jsonb_object_agg(k, + v3.jsonb_numeric_sum(a -> k, b -> k) + ) + FROM ( + SELECT k FROM jsonb_object_keys(a) k + UNION + SELECT k FROM jsonb_object_keys(b) k + ) keys + ) + -- одна сторона число/объект, другая нет → берём числовую/объектную + WHEN jsonb_typeof(a) IN ('number','object') THEN a + WHEN jsonb_typeof(b) IN ('number','object') THEN b + ELSE NULL + END; +$function$ +; + +-- DROP AGGREGATE v3.jsonb_sum(jsonb); + +CREATE OR REPLACE AGGREGATE v3.jsonb_sum(pg_catalog.jsonb) ( + SFUNC = v3.jsonb_numeric_sum, + STYPE = jsonb +); + +-- DROP FUNCTION v3.log_event(text, text, jsonb, int4, int4, int4); + +CREATE OR REPLACE FUNCTION v3.log_event(p_event text, p_event_type text, p_data jsonb, p_form_id integer DEFAULT NULL::integer, p_task_id integer DEFAULT NULL::integer, p_org_unit_id integer DEFAULT NULL::integer) + RETURNS bigint + LANGUAGE plpgsql +AS $function$ +DECLARE + v_user_id INTEGER; + v_audit_id BIGINT; +BEGIN + v_user_id := v3.current_user_id(); + + -- Невалидный user_id → пишем NULL (не падаем). + IF v_user_id IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM v3.app_user WHERE id = v_user_id) THEN + v_user_id := NULL; + END IF; + + INSERT INTO v3.audit_log + (user_id, org_unit_id, form_id, task_id, event, event_type, event_data) + VALUES + (v_user_id, p_org_unit_id, p_form_id, p_task_id, + p_event, p_event_type, COALESCE(p_data, '{}'::jsonb)) + RETURNING id INTO v_audit_id; + + RETURN v_audit_id; +END; +$function$ +; + +-- DROP FUNCTION v3.revoke_many_user_org_access(int4, _int4); + +CREATE OR REPLACE FUNCTION v3.revoke_many_user_org_access(p_user_id integer, p_org_unit_ids integer[]) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_org_id INT; + v_count INT := 0; +BEGIN + IF p_org_unit_ids IS NULL OR cardinality(p_org_unit_ids) = 0 THEN + RETURN 0; + END IF; + + FOREACH v_org_id IN ARRAY p_org_unit_ids + LOOP + IF v3.revoke_user_org_access(p_user_id, v_org_id) THEN + v_count := v_count + 1; + END IF; + END LOOP; + + RETURN v_count; +END; +$function$ +; + +-- DROP FUNCTION v3.revoke_user_org_access(int4, int4); + +CREATE OR REPLACE FUNCTION v3.revoke_user_org_access(p_user_id integer, p_org_unit_id integer) + RETURNS boolean + LANGUAGE plpgsql +AS $function$ +DECLARE + v_deleted INT; + v_user_email VARCHAR; + v_user_full_name VARCHAR; + v_org_name VARCHAR; +BEGIN + SELECT email, full_name + INTO v_user_email, v_user_full_name + FROM v3.app_user + WHERE id = p_user_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'app_user #% не существует', p_user_id; + END IF; + + SELECT title INTO v_org_name FROM v3.org_unit WHERE id = p_org_unit_id; + IF v_org_name IS NULL THEN + RAISE EXCEPTION 'org_unit #% не существует', p_org_unit_id; + END IF; + + DELETE FROM v3.user_org + WHERE user_id = p_user_id + AND org_unit_id = p_org_unit_id; + + GET DIAGNOSTICS v_deleted = ROW_COUNT; + + IF v_deleted > 0 THEN + PERFORM v3.log_event( + 'USER_ACCESS_REVOKED', 'USER', + jsonb_build_object( + 'user_id', p_user_id, + 'entity_id', p_user_id, + 'email', v_user_email, + 'full_name', v_user_full_name, + 'org_unit_id', p_org_unit_id, + 'org_unit_name', v_org_name + ), null, null, p_org_unit_id + ); + RETURN TRUE; + END IF; + + RETURN FALSE; +END; +$function$ +; + +-- DROP FUNCTION v3.upd_budget_form(int4, int4, int4); + +CREATE OR REPLACE FUNCTION v3.upd_budget_form(p_form_id integer, p_year integer DEFAULT NULL::integer, p_org_unit_id integer DEFAULT NULL::integer) + RETURNS TABLE(id integer, form_type_code character varying, year integer, org_unit_id integer) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old RECORD; + v_changes JSONB := '{}'::jsonb; +BEGIN + SELECT bf.year, bf.org_unit_id + INTO v_old + FROM v3.budget_form bf WHERE bf.id = p_form_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'budget_form #% не существует', p_form_id; + END IF; + + IF p_year IS NOT NULL AND p_year IS DISTINCT FROM v_old.year THEN + UPDATE v3.budget_form bf SET year = p_year WHERE bf.id = p_form_id; + v_changes := v_changes || jsonb_build_object( + 'year', jsonb_build_object( + 'before', v3._diff_val(v_old.year), + 'after', v3._diff_val(p_year))); + END IF; + + IF p_org_unit_id IS NOT NULL AND p_org_unit_id IS DISTINCT FROM v_old.org_unit_id THEN + UPDATE v3.budget_form bf SET org_unit_id = p_org_unit_id WHERE bf.id = p_form_id; + v_changes := v_changes || jsonb_build_object( + 'org_unit_id', jsonb_build_object( + 'before', v3._diff_val(v_old.org_unit_id), + 'after', v3._diff_val(p_org_unit_id))); + END IF; + + IF v_changes <> '{}'::jsonb THEN + UPDATE v3.budget_form bf SET updated_at = now() WHERE bf.id = p_form_id; + PERFORM v3.log_event( + 'UPDATE_TASK', 'UPDATE_TASK', + jsonb_build_object('form_id', p_form_id, 'changes', v_changes), + p_form_id, null, COALESCE(p_org_unit_id, v_old.org_unit_id)); + END IF; + + RETURN QUERY + SELECT bf.id, bf.form_type_code, bf.year, bf.org_unit_id + FROM v3.budget_form bf WHERE bf.id = p_form_id; +END; +$function$ +; + +-- DROP FUNCTION v3.upd_form3_cells(int4, jsonb); + +CREATE OR REPLACE FUNCTION v3.upd_form3_cells(p_report_id integer, p_changes jsonb) + 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[]; +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.rf_project_report WHERE id = p_report_id) THEN + RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_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 NOT EXISTS ( + SELECT 1 FROM v3.rf_project_report_line + WHERE id = v_line_id AND rf_project_report_id = p_report_id + ) THEN + RAISE EXCEPTION 'rf_project_report_line id=% не принадлежит report id=%', + v_line_id, p_report_id; + END IF; + + PERFORM v3._apply_form3_cell(v_line_id, v_column, v_value); + + IF NOT (v_line_id = ANY(v_changed_lines)) THEN + v_changed_lines := v_changed_lines || v_line_id; + END IF; + END LOOP; + + -- Возврат: иерархия по пути изменённых rfprl + изменённые INPUT по line_id + DECLARE + v_eid_set INT[]; + v_ei_path INT[]; + BEGIN + SELECT array_agg(DISTINCT rfprl.expense_item_id) + INTO v_eid_set + FROM v3.rf_project_report_line rfprl + WHERE rfprl.id = ANY(v_changed_lines); + v_ei_path := v3._expense_item_path_set(v_eid_set); + + RETURN QUERY + SELECT v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form3_report_jsonb(p_report_id, NULL) v + WHERE (v.row_type IN ('ROOT','GROUP','ITEM','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)) + ORDER BY v.sort_order; + END; +END; +$function$ +; + +-- DROP FUNCTION v3.upd_form3_cell(int4, int4, text, jsonb); + +CREATE OR REPLACE FUNCTION v3.upd_form3_cell(p_report_id integer, p_line_id integer, p_column text, p_value jsonb) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE sql +AS $function$ + SELECT * FROM v3.upd_form3_cells( + p_report_id, + jsonb_build_array(jsonb_build_object( + 'line_id', p_line_id, + 'column', p_column, + 'value', p_value + )) + ); +$function$ +; + +-- DROP FUNCTION v3.upd_form_cells(int4, varchar, jsonb, varchar, _text); + +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; -- map sort_order(text) → data BEFORE + 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; + + -- Проверка: form_id существует + 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; + + -- Применяем все изменения в одной транзакции (вся функция = одна tx) + 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'; -- именно ->, чтобы NULL остался jsonb null + + 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; + + -- Sanity: для основной сетки — line_id принадлежит budget_form; + -- для сателлитов — id принадлежит соответствующей таблице сателлита + -- и через её line_id — данной budget_form; + -- для AHR_LIMIT — id принадлежит limit_template (глобальный каталог, + -- проверим что строка LEAF; per-form значения летят в form_limit). + 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; + + -- Сначала ТОЛЬКО регистрируем v_changed_lines, не применяем (для snapshot + -- before нужно знать все затронутые до изменения). + IF NOT (v_line_id = ANY(v_changed_lines)) THEN + v_changed_lines := v_changed_lines || v_line_id; + END IF; + END LOOP; + + -- ── Вычисляем path-set для каждого типа листа (ОДИН раз, для before и after) + 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; + + -- ── Snapshot BEFORE (тот же фильтр что и для возврата) ──────────────── + 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') + 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; + + -- ── Возврат: AFTER vs BEFORE diff. Только строки с непустым diff. + -- Identifier-ключи (line_id / header.expense_item_id / id) гарантированно + -- добавлены в diff, чтобы фронт мог match с своим state. + 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 + -- Основная сетка FORM_1/2/4 + 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') + 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 + -- иерархия: добавить header.expense_item_id если diff его не содержит + 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$ +; + + +-- DROP FUNCTION v3.upd_form_cells(int4, varchar, jsonb, int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.upd_form_cells(p_form_id integer, p_sheet character varying, p_changes jsonb, p_user_id integer, 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_column TEXT; + v_decision JSONB; + v_msg TEXT; +BEGIN + IF p_user_id IS NULL THEN + RAISE EXCEPTION 'role_not_allowed: '; + END IF; + + IF NOT EXISTS (SELECT 1 FROM v3.app_user WHERE id = p_user_id AND is_active) THEN + RAISE EXCEPTION 'role_not_allowed: unknown or inactive user %', p_user_id; + END IF; + + IF p_changes IS NULL OR jsonb_typeof(p_changes) <> 'array' THEN + RAISE EXCEPTION 'p_changes must be a JSONB array'; + END IF; + + -- Проверяем КАЖДУЮ ячейку до начала записи. Падаем на первой запрещённой — + -- транзакция целиком откатится, частичной записи не будет. + FOR v_change IN SELECT * FROM jsonb_array_elements(p_changes) + LOOP + v_column := v_change->>'column'; + IF v_column IS NULL THEN + RAISE EXCEPTION 'change must have column: %', v_change; + END IF; + + v_decision := v3.can_edit(p_form_id, p_sheet, v_column, p_user_id); + + IF NOT (v_decision->>'ok')::boolean THEN + IF v_decision->>'code' = 'role_not_allowed' THEN + RAISE EXCEPTION 'role_not_allowed: %', v_column; + + ELSIF v_decision->>'code' = 'org_not_assigned' THEN + RAISE EXCEPTION 'org_not_assigned: %', v_column; + + ELSIF v_decision->>'code' = 'window_closed' THEN + IF v_decision ? 'opens_at' THEN + v_msg := format('window_closed: %s (opens %s)', + v_column, v_decision->>'opens_at'); + ELSIF v_decision->>'closes_at' IS NOT NULL THEN + v_msg := format('window_closed: %s (closed %s)', + v_column, v_decision->>'closes_at'); + ELSE + v_msg := format('window_closed: %s (no phase)', v_column); + END IF; + RAISE EXCEPTION '%', v_msg; + + ELSE + RAISE EXCEPTION '%: %', v_decision->>'code', v_column; + END IF; + END IF; + END LOOP; + + -- Все ячейки разрешены — делегируем в v3. + -- ROW_UPDATE намеренно НЕ логируется (см. logging_events.md): cell-edit'ы + -- слишком частые. В audit_log пишем только структурные события + -- (ROW_CREATE / ROW_DELETE) + бизнес-операции (CREATE_TASK и т.д.). + RETURN QUERY + SELECT * FROM v3.upd_form_cells( + p_form_id, p_sheet, p_changes, p_direction, p_sections + ); +END; +$function$ +; + +-- DROP FUNCTION v3.upd_form_cell(int4, varchar, int4, text, jsonb, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.upd_form_cell(p_form_id integer, p_sheet character varying, p_budget_line_id integer, p_column text, p_value 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 sql +AS $function$ + SELECT * FROM v3.upd_form_cells( + p_form_id, + p_sheet, + jsonb_build_array(jsonb_build_object( + 'line_id', p_budget_line_id, + 'column', p_column, + 'value', p_value + )), + p_direction, + p_sections + ); +$function$ +; + +-- DROP FUNCTION v3.upd_form_cell(int4, varchar, int4, text, jsonb, int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.upd_form_cell(p_form_id integer, p_sheet character varying, p_budget_line_id integer, p_column text, p_value jsonb, p_user_id integer, 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 sql +AS $function$ + SELECT * FROM v3.upd_form_cells( + p_form_id, + p_sheet, + jsonb_build_array(jsonb_build_object( + 'line_id', p_budget_line_id, + 'column', p_column, + 'value', p_value + )), + p_user_id, + p_direction, + p_sections + ); +$function$ +; + +-- DROP FUNCTION v3.upd_form_phase(int4, varchar, varchar, varchar, _text, timestamptz, timestamptz); + +CREATE OR REPLACE FUNCTION v3.upd_form_phase(p_budget_form_id integer, p_sheet character varying, p_phase_code character varying, p_role character varying DEFAULT NULL::character varying, p_column_keys text[] DEFAULT NULL::text[], p_opens_at timestamp with time zone DEFAULT NULL::timestamp with time zone, p_closes_at timestamp with time zone DEFAULT NULL::timestamp with time zone) + RETURNS v3.form_phase + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old v3.form_phase; + v_new v3.form_phase; + v_is_extend BOOLEAN := FALSE; + v_has_non_extend_change BOOLEAN := FALSE; + v_has_window_change BOOLEAN := FALSE; + v_changes JSONB := '{}'::jsonb; + v_org_unit_id INT; + v_org_unit_title VARCHAR; + v_form_type_code VARCHAR; + v_year INT; + v_task_name TEXT; + v_phase_id TEXT; +BEGIN + SELECT * INTO v_old + FROM v3.form_phase + WHERE budget_form_id = p_budget_form_id + AND sheet = p_sheet + AND phase_code = p_phase_code; + + IF NOT FOUND THEN + RAISE EXCEPTION 'form_phase не найден (form_id=%, sheet=%, phase=%)', + p_budget_form_id, p_sheet, p_phase_code; + END IF; + + UPDATE v3.form_phase + SET role = COALESCE(p_role, role), + column_keys = COALESCE(p_column_keys, column_keys), + opens_at = COALESCE(p_opens_at, opens_at), + closes_at = COALESCE(p_closes_at, closes_at) + WHERE budget_form_id = p_budget_form_id + AND sheet = p_sheet + AND phase_code = p_phase_code + RETURNING * INTO v_new; + + SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year + INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year + FROM v3.budget_form bf + LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id + WHERE bf.id = p_budget_form_id; + v_task_name := trim(concat( + COALESCE(v_form_type_code, ''), + CASE WHEN v_year IS NOT NULL THEN ' ' || v_year::TEXT ELSE '' END, + CASE WHEN v_org_unit_title IS NOT NULL THEN ' [' || v_org_unit_title || ']' ELSE '' END + )); + v_phase_id := format('%s:%s:%s', p_budget_form_id, p_sheet, p_phase_code); + + v_is_extend := v_new.closes_at > v_old.closes_at; + v_has_non_extend_change := v_old.role IS DISTINCT FROM v_new.role + OR v_old.column_keys IS DISTINCT FROM v_new.column_keys + OR v_old.opens_at IS DISTINCT FROM v_new.opens_at; + v_has_window_change := v_has_non_extend_change + OR (v_old.closes_at IS DISTINCT FROM v_new.closes_at AND NOT v_is_extend); + + IF v_old.role IS DISTINCT FROM v_new.role THEN + v_changes := v_changes || jsonb_build_object( + 'role', jsonb_build_object('before', v_old.role, 'after', v_new.role) + ); + END IF; + IF v_old.column_keys IS DISTINCT FROM v_new.column_keys THEN + v_changes := v_changes || jsonb_build_object( + 'column_keys', jsonb_build_object('before', v_old.column_keys, 'after', v_new.column_keys) + ); + END IF; + IF v_old.opens_at IS DISTINCT FROM v_new.opens_at THEN + v_changes := v_changes || jsonb_build_object( + 'opens_at', jsonb_build_object( + 'before', v_old.opens_at, + 'after', v_new.opens_at + ) + ); + END IF; + IF v_old.closes_at IS DISTINCT FROM v_new.closes_at THEN + v_changes := v_changes || jsonb_build_object( + 'closes_at', jsonb_build_object( + 'before', v_old.closes_at, + 'after', v_new.closes_at + ) + ); + END IF; + + IF v_is_extend THEN + PERFORM v3.log_event( + 'ACCESS_EXTEND', 'ACCESS', + jsonb_build_object( + 'phase_id', v_phase_id, + 'phase_name', p_phase_code, + 'task_id', p_budget_form_id, + 'task_name', NULLIF(v_task_name, ''), + 'form_id', p_budget_form_id, + 'budget_form_id', p_budget_form_id, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + 'sheet', p_sheet, + 'phase_code', p_phase_code, + 'closes_at_before', v_old.closes_at, + 'closes_at_after', v_new.closes_at + ), + p_budget_form_id + ); + END IF; + + IF v_has_window_change THEN + PERFORM v3.log_event( + 'ACCESS_WINDOW_CHANGE', 'ACCESS', + jsonb_build_object( + 'entity_type', 'form_phase', + 'entity_id', p_budget_form_id, + 'action', 'update', + 'phase_id', v_phase_id, + 'phase_name', p_phase_code, + 'task_id', p_budget_form_id, + 'task_name', NULLIF(v_task_name, ''), + 'form_id', p_budget_form_id, + 'budget_form_id', p_budget_form_id, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + 'sheet', p_sheet, + 'phase_code', p_phase_code, + 'changes', v_changes + ), + p_budget_form_id, null, v_org_unit_id + ); + END IF; + + RETURN v_new; +END; +$function$ +; + +-- DROP FUNCTION v3.upd_project(int4, text, jsonb); + +CREATE OR REPLACE FUNCTION v3.upd_project(p_project_id integer, p_column text, p_value jsonb) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$ +DECLARE + v_target_col TEXT; + v_target_type TEXT; + v_sql TEXT; + v_out JSONB; +BEGIN + IF NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_project_id) THEN + RAISE EXCEPTION 'project id=% не существует', p_project_id; + END IF; + + -- Editable атрибуты (CHECK-ы из migrate_form3_v1.sql применятся при UPDATE). + -- Не editable: level (структура), parent_id (иерархия). + CASE p_column + WHEN 'name' THEN v_target_col := 'name'; v_target_type := 'TEXT'; + WHEN 'project_type' THEN v_target_col := 'project_type'; v_target_type := 'TEXT'; + WHEN 'vsp_format' THEN v_target_col := 'vsp_format'; v_target_type := 'TEXT'; + WHEN 'placement_type' THEN v_target_col := 'placement_type'; v_target_type := 'TEXT'; + WHEN 'object_address' THEN v_target_col := 'object_address'; v_target_type := 'TEXT'; + WHEN 'staff_count' THEN v_target_col := 'staff_count'; v_target_type := 'INTEGER'; + WHEN 'total_area' THEN v_target_col := 'total_area'; v_target_type := 'NUMERIC'; + WHEN 'org_unit_id' THEN v_target_col := 'org_unit_id'; v_target_type := 'INTEGER'; + WHEN 'level','parent_id' THEN + RAISE EXCEPTION 'structural_field: % (правится отдельно через ADD project / структурный API)', p_column; + WHEN 'id' THEN + RAISE EXCEPTION 'key_field: id (RO)'; + ELSE + RAISE EXCEPTION 'unknown_column: %', p_column; + END CASE; + + -- FK санити (если применимо) + IF v_target_col = 'org_unit_id' AND p_value <> 'null'::jsonb THEN + IF NOT EXISTS (SELECT 1 FROM v3.org_unit WHERE id = (p_value #>> '{}')::INT) THEN + RAISE EXCEPTION 'org_unit id=% не существует', (p_value #>> '{}'); + END IF; + END IF; + + v_sql := format( + 'UPDATE v3.project SET %1$I = ($2 #>> ''{}'')::%2$s WHERE id = $1', + v_target_col, v_target_type + ); + EXECUTE v_sql USING p_project_id, p_value; + + -- Возврат — весь header проекта + SELECT jsonb_build_object( + 'id', p.id, + 'name', p.name, + 'level', p.level, + 'parent_id', p.parent_id, + 'org_unit_id', p.org_unit_id, + 'project_type', p.project_type, + 'vsp_format', p.vsp_format, + 'placement_type', p.placement_type, + 'object_address', p.object_address, + 'staff_count', p.staff_count, + 'total_area', p.total_area + ) + INTO v_out + FROM v3.project p WHERE p.id = p_project_id; + RETURN v_out; +END; +$function$ +; + +-- DROP FUNCTION v3.upd_user(int4, varchar, varchar, varchar, varchar, int4, bool); + +CREATE OR REPLACE FUNCTION v3.upd_user(p_user_id integer, p_email character varying DEFAULT NULL::character varying, p_username character varying DEFAULT NULL::character varying, p_hashed_password character varying DEFAULT NULL::character varying, p_full_name character varying DEFAULT NULL::character varying, p_role_id integer DEFAULT NULL::integer, p_is_active boolean DEFAULT NULL::boolean) + RETURNS v3.app_user + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old_role_id INT; + v_new v3.app_user; +BEGIN + SELECT role_id INTO v_old_role_id + FROM v3.app_user + WHERE id = p_user_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'app_user #% не существует', p_user_id; + END IF; + + UPDATE v3.app_user + SET email = COALESCE(p_email, email), + username = COALESCE(p_username, username), + hashed_password = COALESCE(p_hashed_password, hashed_password), + full_name = COALESCE(p_full_name, full_name), + role_id = COALESCE(p_role_id, role_id), + is_active = COALESCE(p_is_active, is_active), + updated_at = now() + WHERE id = p_user_id + RETURNING * INTO v_new; + + IF v_old_role_id IS DISTINCT FROM v_new.role_id THEN + PERFORM v3.log_event( + 'USER_ROLE_CHANGE', 'USER', + jsonb_build_object( + 'user_id', p_user_id, + 'entity_id', p_user_id, + 'email', v_new.email, + 'full_name', v_new.full_name, + 'role_id_before', v_old_role_id, + 'role_id_after', v_new.role_id + ) + ); + END IF; + + RETURN v_new; +END; +$function$ +; + +-- DROP FUNCTION v3.upd_vsp(int4, int4, varchar, varchar, varchar, date, varchar, int4, numeric, date, bool, bool, varchar, int4, varchar, text, varchar, int4, varchar, date); + +CREATE OR REPLACE FUNCTION v3.upd_vsp(p_vsp_id integer, p_branch_id integer DEFAULT NULL::integer, p_reg_number character varying DEFAULT NULL::character varying, p_address character varying DEFAULT NULL::character varying, p_format character varying DEFAULT NULL::character varying, p_opened_at date DEFAULT NULL::date, p_placement_type character varying DEFAULT NULL::character varying, p_staff_count integer DEFAULT NULL::integer, p_total_area numeric DEFAULT NULL::numeric, p_closed_at date DEFAULT NULL::date, p_is_active boolean DEFAULT NULL::boolean, p_is_deleted boolean DEFAULT NULL::boolean, p_system_code character varying DEFAULT NULL::character varying, p_updated_by integer DEFAULT NULL::integer, p_vsp_type character varying DEFAULT NULL::character varying, p_notes text DEFAULT NULL::text, p_location_form character varying DEFAULT NULL::character varying, p_numbers integer DEFAULT NULL::integer, p_rent_contract_num character varying DEFAULT NULL::character varying, p_rent_end_date date DEFAULT NULL::date) + RETURNS void + LANGUAGE plpgsql +AS $function$ +DECLARE + v_old RECORD; + v_changes JSONB := '{}'::jsonb; + + v_branch_name_before VARCHAR; + v_branch_name_after VARCHAR; + + v_actor_id INT; + v_actor_email VARCHAR; + v_actor_full_name VARCHAR; +BEGIN + SELECT v.branch_id, v.reg_number, v.address, v.format, v.opened_at, + v.placement_type, v.staff_count, v.total_area, v.closed_at, + v.is_active, v.is_deleted, v.system_code, + v.vsp_type, v.notes, v.location_form, v.numbers, + v.rent_contract_num, v.rent_end_date + INTO v_old + FROM v3.vsp v + WHERE v.id = p_vsp_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'vsp #% не существует', p_vsp_id; + END IF; + + SELECT ou.title + INTO v_branch_name_before + FROM v3.org_unit ou + WHERE ou.id = v_old.branch_id; + + v_branch_name_after := v_branch_name_before; + + v_actor_id := COALESCE(p_updated_by, v3.current_user_id()); + IF v_actor_id IS NOT NULL THEN + SELECT u.email, u.full_name + INTO v_actor_email, v_actor_full_name + FROM v3.app_user u + WHERE u.id = v_actor_id; + END IF; + + IF p_branch_id IS NOT NULL AND p_branch_id IS DISTINCT FROM v_old.branch_id THEN + SELECT ou.title INTO v_branch_name_after + FROM v3.org_unit ou + WHERE ou.id = p_branch_id; + + UPDATE v3.vsp SET branch_id = p_branch_id WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object( + 'branch_id', jsonb_build_object( + 'before', v3._diff_val(v_old.branch_id), + 'after', v3._diff_val(p_branch_id) + ), + 'branch_name', jsonb_build_object( + 'before', v3._diff_val(v_branch_name_before), + 'after', v3._diff_val(v_branch_name_after) + ) + ); + END IF; + + IF p_reg_number IS NOT NULL AND p_reg_number IS DISTINCT FROM v_old.reg_number THEN + UPDATE v3.vsp SET reg_number = p_reg_number WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('reg_number', + jsonb_build_object('before', v3._diff_val(v_old.reg_number), + 'after', v3._diff_val(p_reg_number))); + END IF; + + IF p_address IS NOT NULL AND p_address IS DISTINCT FROM v_old.address THEN + UPDATE v3.vsp SET address = p_address WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('address', + jsonb_build_object('before', v3._diff_val(v_old.address), + 'after', v3._diff_val(p_address))); + END IF; + + IF p_format IS NOT NULL AND p_format IS DISTINCT FROM v_old.format THEN + UPDATE v3.vsp SET format = p_format WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('format', + jsonb_build_object('before', v3._diff_val(v_old.format), + 'after', v3._diff_val(p_format))); + END IF; + + IF p_opened_at IS NOT NULL AND p_opened_at IS DISTINCT FROM v_old.opened_at THEN + UPDATE v3.vsp SET opened_at = p_opened_at WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('opened_at', + jsonb_build_object('before', v3._diff_val(v_old.opened_at), + 'after', v3._diff_val(p_opened_at))); + END IF; + + IF p_placement_type IS NOT NULL AND p_placement_type IS DISTINCT FROM v_old.placement_type THEN + UPDATE v3.vsp SET placement_type = p_placement_type WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('placement_type', + jsonb_build_object('before', v3._diff_val(v_old.placement_type), + 'after', v3._diff_val(p_placement_type))); + END IF; + + IF p_staff_count IS NOT NULL AND p_staff_count IS DISTINCT FROM v_old.staff_count THEN + UPDATE v3.vsp SET staff_count = p_staff_count WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('staff_count', + jsonb_build_object('before', v3._diff_val(v_old.staff_count), + 'after', v3._diff_val(p_staff_count))); + END IF; + + IF p_total_area IS NOT NULL AND p_total_area IS DISTINCT FROM v_old.total_area THEN + UPDATE v3.vsp SET total_area = p_total_area WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('total_area', + jsonb_build_object('before', v3._diff_val(v_old.total_area), + 'after', v3._diff_val(p_total_area))); + END IF; + + IF p_closed_at IS NOT NULL AND p_closed_at IS DISTINCT FROM v_old.closed_at THEN + UPDATE v3.vsp SET closed_at = p_closed_at WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('closed_at', + jsonb_build_object('before', v3._diff_val(v_old.closed_at), + 'after', v3._diff_val(p_closed_at))); + END IF; + + IF p_is_active IS NOT NULL AND p_is_active IS DISTINCT FROM v_old.is_active THEN + UPDATE v3.vsp SET is_active = p_is_active WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('is_active', + jsonb_build_object('before', v3._diff_val(v_old.is_active), + 'after', v3._diff_val(p_is_active))); + END IF; + + IF p_is_deleted IS NOT NULL AND p_is_deleted IS DISTINCT FROM v_old.is_deleted THEN + UPDATE v3.vsp SET is_deleted = p_is_deleted WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('is_deleted', + jsonb_build_object('before', v3._diff_val(v_old.is_deleted), + 'after', v3._diff_val(p_is_deleted))); + END IF; + + IF p_system_code IS NOT NULL AND p_system_code IS DISTINCT FROM v_old.system_code THEN + UPDATE v3.vsp SET system_code = p_system_code WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('system_code', + jsonb_build_object('before', v3._diff_val(v_old.system_code), + 'after', v3._diff_val(p_system_code))); + END IF; + + IF p_vsp_type IS NOT NULL AND p_vsp_type IS DISTINCT FROM v_old.vsp_type THEN + UPDATE v3.vsp SET vsp_type = p_vsp_type WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('vsp_type', + jsonb_build_object('before', v3._diff_val(v_old.vsp_type), + 'after', v3._diff_val(p_vsp_type))); + END IF; + + IF p_notes IS NOT NULL AND p_notes IS DISTINCT FROM v_old.notes THEN + UPDATE v3.vsp SET notes = p_notes WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('notes', + jsonb_build_object('before', v3._diff_val(v_old.notes), + 'after', v3._diff_val(p_notes))); + END IF; + + IF p_location_form IS NOT NULL AND p_location_form IS DISTINCT FROM v_old.location_form THEN + UPDATE v3.vsp SET location_form = p_location_form WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('location_form', + jsonb_build_object('before', v3._diff_val(v_old.location_form), + 'after', v3._diff_val(p_location_form))); + END IF; + + IF p_numbers IS NOT NULL AND p_numbers IS DISTINCT FROM v_old.numbers THEN + UPDATE v3.vsp SET numbers = p_numbers WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('numbers', + jsonb_build_object('before', v3._diff_val(v_old.numbers), + 'after', v3._diff_val(p_numbers))); + END IF; + + IF p_rent_contract_num IS NOT NULL AND p_rent_contract_num IS DISTINCT FROM v_old.rent_contract_num THEN + UPDATE v3.vsp SET rent_contract_num = p_rent_contract_num WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('rent_contract_num', + jsonb_build_object('before', v3._diff_val(v_old.rent_contract_num), + 'after', v3._diff_val(p_rent_contract_num))); + END IF; + + IF p_rent_end_date IS NOT NULL AND p_rent_end_date IS DISTINCT FROM v_old.rent_end_date THEN + UPDATE v3.vsp SET rent_end_date = p_rent_end_date WHERE id = p_vsp_id; + v_changes := v_changes || jsonb_build_object('rent_end_date', + jsonb_build_object('before', v3._diff_val(v_old.rent_end_date), + 'after', v3._diff_val(p_rent_end_date))); + END IF; + + IF v_changes <> '{}'::jsonb THEN + UPDATE v3.vsp + SET updated_at = now(), + updated_by = p_updated_by + WHERE id = p_vsp_id; + + PERFORM v3.log_event( + 'VSP_UPDATE', 'VSP', + jsonb_build_object( + 'vsp_id', p_vsp_id, + 'entity_id', p_vsp_id, + 'branch_id', COALESCE(p_branch_id, v_old.branch_id), + 'branch_name', COALESCE(v_branch_name_after, v_branch_name_before), + 'changes', v_changes, + 'user_email', v_actor_email, + 'user_full_name', v_actor_full_name + ), null, null, COALESCE(p_branch_id, v_old.branch_id) + ); + END IF; +END; +$function$ +; + +-- DROP FUNCTION v3.user_in_form_org(int4, int4); + +CREATE OR REPLACE FUNCTION v3.user_in_form_org(p_user_id integer, p_form_id integer) + RETURNS boolean + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + v_org INT; +BEGIN + SELECT org_unit_id INTO v_org + FROM v3.budget_form + WHERE id = p_form_id; + + IF v_org IS NULL THEN + RETURN TRUE; -- форма не привязана к org_unit — пропускаем фильтр + END IF; + + RETURN EXISTS ( + SELECT 1 + FROM v3.user_org + WHERE user_id = p_user_id + AND org_unit_id = v_org + ); +END; +$function$ +; + +-- DROP FUNCTION v3.user_role_code(int4); + +CREATE OR REPLACE FUNCTION v3.user_role_code(p_user_id integer) + RETURNS character varying + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + v_code VARCHAR; +BEGIN + SELECT r.code + INTO v_code + FROM v3.app_user u + JOIN v3.role r ON r.id = u.role_id + WHERE u.id = p_user_id + AND u.is_active; + RETURN v_code; -- NULL если юзер не найден или неактивен +END; +$function$ +; + +-- DROP FUNCTION v3.v_form1_sheet_sections(int4, varchar, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form1_sheet_sections(p_form_id integer, p_sheet character varying, p_direction character varying DEFAULT NULL::character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, line_id integer, col_b_year integer, col_c_section character varying, col_d_item_id character varying, col_e_num_group character varying, col_f_name character varying, col_g_internal_order character varying, col_h_plan_q1 numeric, col_i_plan_q2 numeric, col_j_plan_q3 numeric, col_k_plan_q4 numeric, col_l_plan_year numeric, col_m_plan_comment character varying, col_n_cs_total numeric, col_o_cs_counterparty character varying, col_p_cs_deadline character varying, col_q_cs_comment character varying, col_r_cs_future_y1 numeric, col_s_cs_future_y2 numeric, col_t_cs_other_ssp numeric, col_v_al_order character varying, col_w_al_property character varying, col_y_seq_q1 numeric, col_z_seq_q2 numeric, col_aa_seq_q3 numeric, col_ab_seq_q4 numeric, col_ac_seq_year numeric, col_ad_seq_justification character varying, col_af_res_q1 numeric, col_ag_res_q2 numeric, col_ah_res_q3 numeric, col_ai_res_q4 numeric, col_aj_res_year numeric, col_ak_res_justification character varying, col_am_approved_q1 numeric, col_an_approved_q2 numeric, col_ao_approved_q3 numeric, col_ap_approved_q4 numeric, col_aq_approved_year numeric, col_as_ca_approved numeric, col_at_ca_protocol character varying, col_au_ca_note character varying, col_aw_ckk_ceiling numeric, col_ax_ckk_exp_q1 numeric, col_ay_ckk_exp_q2 numeric, col_az_ckk_exp_q3 numeric, col_ba_ckk_exp_q4 numeric, col_bb_ckk_rf_sched character varying, col_bc_ckk_deadline character varying, col_bd_ckk_proc_plan character varying, col_be_ckk_proc_meth character varying, col_bf_ckk_comment character varying, col_bh_cd_counterparty character varying, col_bi_cd_reference character varying, col_bj_cd_addenda character varying, col_bk_cd_subject character varying, col_bl_cd_currency character varying, col_bm_cd_ceiling numeric, col_bn_cd_exp_q1 numeric, col_bo_cd_exp_q2 numeric, col_bp_cd_exp_q3 numeric, col_bq_cd_exp_q4 numeric, col_br_cd_rf_sched character varying, col_bs_cd_vat character varying, col_bt_cd_rate numeric, col_bu_cd_amount_fx numeric, col_bv_cd_deadline character varying, col_bw_cd_scheme character varying, col_bx_cd_act character varying, col_by_cd_comment character varying, col_ca_q1_adj_current numeric, col_cb_q1_adj_ssp numeric, col_cc_q1_adj_rf numeric, col_cd_q1_adj_reserve numeric, col_ce_q1_adj_comment character varying, col_cf_q1_corrected_plan numeric, col_ch_q1_pay_date date, col_ci_q1_pay_amount numeric, col_cj_q1_pay_ho numeric, col_ck_q1_pay_rf numeric, col_cl_q1_pay_comment character varying, col_cm_q1_pay_act character varying, col_co_q1_booking numeric, col_cp_q1_actual_m1 numeric, col_cq_q1_actual_m2 numeric, col_cr_q1_actual_m3 numeric, col_cs_q1_actual_quarter numeric, col_ct_q1_rem_booking numeric, col_cu_q1_rem_actual numeric, col_cw_q1_trans_q2 numeric, col_cx_q1_trans_q3 numeric, col_cy_q1_trans_q4 numeric, col_cz_q1_economy numeric, col_da_q1_total numeric, col_dc_q2_rev_eco numeric, col_dd_q2_rev_item numeric, col_de_q2_rev_inc numeric, col_df_q2_rev_seq numeric, col_dg_q2_rev_comment character varying, col_dh_q2_new_plan numeric, col_dj_q2_adj_current numeric, col_dk_q2_adj_ssp numeric, col_dl_q2_adj_rf numeric, col_dm_q2_adj_reserve numeric, col_dn_q2_adj_comment character varying, col_do_q2_corrected_plan numeric, col_dq_q2_pay_date date, col_dr_q2_pay_amount numeric, col_ds_q2_pay_ho numeric, col_dt_q2_pay_rf numeric, col_du_q2_pay_comment character varying, col_dv_q2_pay_act character varying, col_dx_q2_booking numeric, col_dy_q2_actual_m1 numeric, col_dz_q2_actual_m2 numeric, col_ea_q2_actual_m3 numeric, col_eb_q2_actual_quarter numeric, col_ec_q2_rem_booking numeric, col_ed_q2_rem_actual numeric, col_ef_q2_trans_q3 numeric, col_eg_q2_trans_q4 numeric, col_eh_q2_economy numeric, col_ei_q2_total numeric, col_ek_q3_rev_eco numeric, col_el_q3_rev_item numeric, col_em_q3_rev_inc numeric, col_en_q3_rev_seq numeric, col_eo_q3_rev_comment character varying, col_ep_q3_new_plan numeric, col_er_q3_adj_current numeric, col_es_q3_adj_ssp numeric, col_et_q3_adj_rf numeric, col_eu_q3_adj_reserve numeric, col_ev_q3_adj_comment character varying, col_ew_q3_corrected_plan numeric, col_ey_q3_pay_date date, col_ez_q3_pay_amount numeric, col_fa_q3_pay_ho numeric, col_fb_q3_pay_rf numeric, col_fc_q3_pay_comment character varying, col_fd_q3_pay_act character varying, col_ff_q3_booking numeric, col_fg_q3_actual_m1 numeric, col_fh_q3_actual_m2 numeric, col_fi_q3_actual_m3 numeric, col_fj_q3_actual_quarter numeric, col_fk_q3_rem_booking numeric, col_fl_q3_rem_actual numeric, col_fn_q3_trans_q4 numeric, col_fo_q3_economy numeric, col_fp_q3_total numeric, col_fr_q4_rev_eco numeric, col_fs_q4_rev_item numeric, col_ft_q4_rev_inc numeric, col_fu_q4_rev_seq numeric, col_fv_q4_rev_comment character varying, col_fw_q4_new_plan numeric, col_fy_q4_adj_current numeric, col_fz_q4_adj_ssp numeric, col_ga_q4_adj_rf numeric, col_gb_q4_adj_reserve numeric, col_gc_q4_adj_comment character varying, col_gd_q4_corrected_plan numeric, col_gf_q4_pay_date date, col_gg_q4_pay_amount numeric, col_gh_q4_pay_ho numeric, col_gi_q4_pay_rf numeric, col_gj_q4_pay_comment character varying, col_gk_q4_pay_act character varying, col_gm_q4_booking numeric, col_gn_q4_actual_m1 numeric, col_go_q4_actual_m2 numeric, col_gp_q4_actual_m3 numeric, col_gq_q4_actual_quarter numeric, col_gr_q4_actual_spod numeric, col_gs_q4_rem_booking numeric, col_gt_q4_rem_actual numeric, col_gv_q4_economy numeric, col_gw_q4_total numeric, col_gz_fact_year numeric, col_hb_pay_year numeric, _sort_path integer[]) + LANGUAGE plpgsql + STABLE + SET search_path TO 'v3', 'pg_catalog' +AS $function$ +#variable_conflict use_column +DECLARE + v_year INT; + s_plan BOOL; s_cs BOOL; s_al BOOL; s_seq BOOL; s_res BOOL; + s_appr BOOL; s_ca BOOL; s_ckk BOOL; s_cd BOOL; + s_q1 BOOL; s_q2 BOOL; s_q3 BOOL; s_q4 BOOL; s_tot BOOL; + s_any_q BOOL; + s_need_ap BOOL; -- plan/seq/res нужны: approved И/ИЛИ cp1..cp4 + s_need_bk BOOL; -- ckk/cd нужны: booking bk1..bk4 (в Q-секциях) +BEGIN + SELECT bf.year INTO v_year FROM budget_form bf WHERE bf.id = p_form_id; + + s_plan := p_sections IS NULL OR 'plan' = 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_seq := p_sections IS NULL OR 'sequestration' = ANY(p_sections); + s_res := p_sections IS NULL OR 'reserve' = ANY(p_sections); + s_appr := p_sections IS NULL OR 'approved' = 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_cd := p_sections IS NULL OR 'contract_detail' = 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_any_q := s_q1 OR s_q2 OR s_q3 OR s_q4 OR s_tot; + s_need_ap := s_plan OR s_appr OR s_q1 OR s_q2 OR s_q3 OR s_q4; + s_need_bk := s_q1 OR s_q2 OR s_q3 OR s_q4; + + RETURN QUERY + WITH + -- ═══ 1. Дерево expense_item из materialized view ══════════════════════ + -- JOIN с expense_item_form_type фильтрует узлы по form_type текущей формы + tw AS ( + SELECT t.id, t.section_code, t.item_id, t.num_group_id, t.name, + t.depth, t.path, t.parent_id, t.parent_item_id, t.desc_ids + FROM mv_expense_item_tree t + JOIN expense_item_form_type eft ON eft.expense_item_id = t.id + JOIN budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code + WHERE t.sheet = p_sheet + ), + + -- ═══ 2. Все INPUT строки формы ══════════════════════════════════════════ + -- p_direction: фильтр по budget_line.direction. NULL = без фильтра. + -- bl.direction IS NULL обходит фильтр (для листов без direction, напр. Операц). + page AS ( + SELECT bl.id AS lid, bl.expense_item_id AS eid, bl.internal_order AS io, + bl.name AS bname + FROM budget_line bl + JOIN expense_item ei ON ei.id = bl.expense_item_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (p_direction IS NULL OR bl.direction IS NULL OR bl.direction = p_direction) + ), + + -- ═══ 3. Satellite JOINs (условные) ════════════════════════════════════ + jp AS (SELECT p.* FROM plan p JOIN page pg ON p.line_id = pg.lid WHERE s_plan), + jcs AS (SELECT cs.* FROM contract_summary cs JOIN page pg ON cs.line_id = pg.lid WHERE s_cs), + jal AS (SELECT al.* FROM allocation al JOIN page pg ON al.line_id = pg.lid WHERE s_al), + jsd AS (SELECT sd.* FROM sequestration sd JOIN page pg ON sd.line_id = pg.lid WHERE s_seq AND sd.actor='DFIP'), + jr AS (SELECT r.* FROM reserve r JOIN page pg ON r.line_id = pg.lid WHERE s_res), + jca AS (SELECT ca.* FROM collegial_approval ca JOIN page pg ON ca.line_id = pg.lid WHERE s_ca), + jck AS (SELECT ck.* FROM ckk ck JOIN page pg ON ck.line_id = pg.lid WHERE s_ckk), + jcd AS (SELECT cd.* FROM contract_detail cd JOIN page pg ON cd.line_id = pg.lid WHERE s_cd), + -- Direct quarter joins (no pivot — WHERE quarter=N) + jq1 AS (SELECT blq.* FROM budget_line_quarter blq JOIN page pg ON blq.line_id = pg.lid WHERE s_q1 AND blq.quarter = 1), + jq2 AS (SELECT blq.* FROM budget_line_quarter blq JOIN page pg ON blq.line_id = pg.lid WHERE s_q2 AND blq.quarter = 2), + jq3 AS (SELECT blq.* FROM budget_line_quarter blq JOIN page pg ON blq.line_id = pg.lid WHERE s_q3 AND blq.quarter = 3), + jq4 AS (SELECT blq.* FROM budget_line_quarter blq JOIN page pg ON blq.line_id = pg.lid WHERE (s_q4 OR s_tot) AND blq.quarter = 4), + -- For totals: need all 4 quarters to sum actual across year — but only when s_tot and not all q loaded + jtot AS ( + SELECT blq.line_id AS tlid, + SUM(CASE WHEN blq.quarter=1 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0) END) AS tot_aq1, + SUM(CASE WHEN blq.quarter=2 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0) END) AS tot_aq2, + SUM(CASE WHEN blq.quarter=3 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0) END) AS tot_aq3, + SUM(CASE WHEN blq.quarter=4 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0)+COALESCE(blq.actual_spod,0) END) AS tot_aq4, + SUM(CASE WHEN blq.quarter=1 THEN blq.payment_amount END) AS tot_pa1, + SUM(CASE WHEN blq.quarter=2 THEN blq.payment_amount END) AS tot_pa2, + SUM(CASE WHEN blq.quarter=3 THEN blq.payment_amount END) AS tot_pa3, + SUM(CASE WHEN blq.quarter=4 THEN blq.payment_amount END) AS tot_pa4 + FROM budget_line_quarter blq JOIN page pg ON blq.line_id = pg.lid + WHERE s_tot + GROUP BY blq.line_id + ), + + -- ═══ 4. INPUT строки: enriched с computed ═════════════════════════════ + -- Промежуточные вычисления для computed-полей + input_base AS ( + SELECT + pg.lid, pg.eid, pg.io, pg.bname, + -- section_code = parent's item_id (hierarchy code like 1.01.1.) + t.parent_item_id AS sc, + -- item_id = R-code for depth=3, else hierarchy code + t.item_id AS ic, + t.num_group_id AS ng, t.name AS ename, + -- raw satellite values (for computed) + 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 sq1, COALESCE(sd.adj_q2,0) AS sq2, + COALESCE(sd.adj_q3,0) AS sq3, COALESCE(sd.adj_q4,0) AS sq4, + sd.justification AS sjust, + 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, + r.justification AS rjust, + cs.*, al.internal_order AS al_io, al.property_object AS al_po, + ca.approved_amount AS ca_amt, ca.protocol_reference AS ca_prot, ca.note AS ca_note, + ck.ceiling_amount AS ckk_ceil, + COALESCE(ck.expenses_q1,0) AS ckk1, COALESCE(ck.expenses_q2,0) AS ckk2, + COALESCE(ck.expenses_q3,0) AS ckk3, COALESCE(ck.expenses_q4,0) AS ckk4, + ck.rf_schedule AS ckk_rf, ck.delivery_deadline AS ckk_dl, + ck.procurement_plan AS ckk_pp, ck.procurement_method AS ckk_pm, ck.comment AS ckk_cmt, + cd.counterparty AS cd_cp, cd.reference AS cd_ref, cd.addenda AS cd_add, + cd.subject AS cd_subj, cd.currency AS cd_cur, cd.ceiling_amount AS cd_ceil, + COALESCE(cd.expenses_q1,0) AS cdq1, COALESCE(cd.expenses_q2,0) AS cdq2, + COALESCE(cd.expenses_q3,0) AS cdq3, COALESCE(cd.expenses_q4,0) AS cdq4, + cd.rf_schedule AS cd_rf, cd.vat_rate AS cd_vat, cd.exchange_rate AS cd_rate, + cd.amount_foreign AS cd_fx, cd.deadline AS cd_dl, cd.payment_scheme AS cd_sch, + cd.act AS cd_act, cd.comment AS cd_cmt2, + -- Q1 direct + q1.adj_current AS q1_ac, q1.adj_ssp AS q1_as, q1.adj_rf AS q1_ar, 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_amount_ho AS q1_pho, q1.payment_amount_rf AS q1_prf, + q1.payment_comment AS q1_pcmt, q1.payment_act AS q1_pact, + 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, + -- Q2 direct + q2.plan_revision_eco_change AS q2_reco, q2.plan_revision_item_adj AS q2_ritm, + 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_rf AS q2_ar, 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_amount_ho AS q2_pho, q2.payment_amount_rf AS q2_prf, + q2.payment_comment AS q2_pcmt, q2.payment_act AS q2_pact, + 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, + -- Q3 direct + q3.plan_revision_eco_change AS q3_reco, q3.plan_revision_item_adj AS q3_ritm, + 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_rf AS q3_ar, 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_amount_ho AS q3_pho, q3.payment_amount_rf AS q3_prf, + q3.payment_comment AS q3_pcmt, q3.payment_act AS q3_pact, + 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, + -- Q4 direct + q4.plan_revision_eco_change AS q4_reco, q4.plan_revision_item_adj AS q4_ritm, + 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_rf AS q4_ar, 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_amount_ho AS q4_pho, q4.payment_amount_rf AS q4_prf, + q4.payment_comment AS q4_pcmt, q4.payment_act AS q4_pact, + q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, + q4.actual_spod AS q4_spod, + -- totals + tot.tot_aq1, tot.tot_aq2, tot.tot_aq3, tot.tot_aq4, + tot.tot_pa1, tot.tot_pa2, tot.tot_pa3, tot.tot_pa4, + -- booking per quarter + CASE WHEN COALESCE(cd.expenses_q1,0)>0 THEN cd.expenses_q1 ELSE ck.expenses_q1 END AS bk1, + CASE WHEN COALESCE(cd.expenses_q2,0)>0 THEN cd.expenses_q2 ELSE ck.expenses_q2 END AS bk2, + CASE WHEN COALESCE(cd.expenses_q3,0)>0 THEN cd.expenses_q3 ELSE ck.expenses_q3 END AS bk3, + CASE WHEN COALESCE(cd.expenses_q4,0)>0 THEN cd.expenses_q4 ELSE ck.expenses_q4 END AS bk4, + -- approved per quarter + COALESCE(p.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(r.amount_q1,0) AS ap1, + COALESCE(p.plan_q2,0)+COALESCE(sd.adj_q2,0)+COALESCE(r.amount_q2,0) AS ap2, + COALESCE(p.plan_q3,0)+COALESCE(sd.adj_q3,0)+COALESCE(r.amount_q3,0) AS ap3, + COALESCE(p.plan_q4,0)+COALESCE(sd.adj_q4,0)+COALESCE(r.amount_q4,0) AS ap4, + -- tree path for ordering + t.path AS tree_path + FROM page pg + JOIN tw t ON t.id = pg.eid + LEFT JOIN jp p ON p.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 jsd sd ON sd.line_id = pg.lid + LEFT JOIN jr r ON r.line_id = pg.lid + LEFT JOIN jca ca ON ca.line_id = pg.lid + LEFT JOIN jck ck ON ck.line_id = pg.lid + LEFT JOIN jcd cd ON cd.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 + LEFT JOIN jtot tot ON tot.tlid = pg.lid + ), + -- Q1-Q4 computed fields + enriched AS ( + SELECT b.*, + -- Q1 corrected_plan + b.ap1 + COALESCE(b.q1_ac,0)+COALESCE(b.q1_as,0)+COALESCE(b.q1_ar,0)+COALESCE(b.q1_arv,0) AS cp1, + -- Q1 actual_quarter + COALESCE(b.q1_m1,0)+COALESCE(b.q1_m2,0)+COALESCE(b.q1_m3,0) AS aq1, + -- Q2 new_plan + b.ap2 + COALESCE(b.q1_tq2,0) + COALESCE(b.q2_reco,0)+COALESCE(b.q2_ritm,0)+COALESCE(b.q2_rinc,0)+COALESCE(b.q2_rseq,0) AS np2, + -- Q2 actual_quarter + COALESCE(b.q2_m1,0)+COALESCE(b.q2_m2,0)+COALESCE(b.q2_m3,0) AS aq2, + -- Q3 new_plan + b.ap3 + COALESCE(b.q1_tq3,0)+COALESCE(b.q2_tq3,0) + COALESCE(b.q3_reco,0)+COALESCE(b.q3_ritm,0)+COALESCE(b.q3_rinc,0)+COALESCE(b.q3_rseq,0) AS np3, + -- Q3 actual_quarter + COALESCE(b.q3_m1,0)+COALESCE(b.q3_m2,0)+COALESCE(b.q3_m3,0) AS aq3, + -- Q4 new_plan + b.ap4 + COALESCE(b.q1_tq4,0)+COALESCE(b.q2_tq4,0)+COALESCE(b.q3_tq4,0) + COALESCE(b.q4_reco,0)+COALESCE(b.q4_ritm,0)+COALESCE(b.q4_rinc,0)+COALESCE(b.q4_rseq,0) AS np4, + -- Q4 actual_quarter (без СПОД) + COALESCE(b.q4_m1,0)+COALESCE(b.q4_m2,0)+COALESCE(b.q4_m3,0) AS aq4_no_spod, + -- Q4 actual with СПОД + 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 computed: corrected_plan Qn = new_plan + adj, rem/economy/total + final_input AS ( + SELECT e.*, + -- Q2 corrected_plan + e.np2 + COALESCE(e.q2_ac,0)+COALESCE(e.q2_as,0)+COALESCE(e.q2_ar,0)+COALESCE(e.q2_arv,0) AS cp2, + -- Q3 corrected_plan + e.np3 + COALESCE(e.q3_ac,0)+COALESCE(e.q3_as,0)+COALESCE(e.q3_ar,0)+COALESCE(e.q3_arv,0) AS cp3, + -- Q4 corrected_plan + e.np4 + COALESCE(e.q4_ac,0)+COALESCE(e.q4_as,0)+COALESCE(e.q4_ar,0)+COALESCE(e.q4_arv,0) AS cp4 + FROM enriched e + ), + + -- ═══ 5. Агрегаты для иерархии ═════════════════════════════════════════ + 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 ss1, SUM(sd.adj_q2) AS ss2, SUM(sd.adj_q3) AS ss3, SUM(sd.adj_q4) AS ss4, + 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(ca.approved_amount) AS sca, + SUM(ck.ceiling_amount) AS sckc, + SUM(ck.expenses_q1) AS sck1, SUM(ck.expenses_q2) AS sck2, SUM(ck.expenses_q3) AS sck3, SUM(ck.expenses_q4) AS sck4, + SUM(cd.ceiling_amount) AS scdc, + SUM(cd.expenses_q1) AS scd1, SUM(cd.expenses_q2) AS scd2, SUM(cd.expenses_q3) AS scd3, SUM(cd.expenses_q4) AS scd4, + SUM(cs.total_amount) AS scst, SUM(cs.future_payments_y1) AS sfy1, SUM(cs.future_payments_y2) AS sfy2, + SUM(cs.other_ssp_amount) AS sossp + FROM budget_line bl + JOIN expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN plan p ON p.line_id = bl.id AND s_need_ap + LEFT JOIN contract_summary cs ON cs.line_id = bl.id AND s_cs + LEFT JOIN sequestration sd ON sd.line_id = bl.id AND sd.actor = 'DFIP' AND s_need_ap + LEFT JOIN reserve r ON r.line_id = bl.id AND s_need_ap + LEFT JOIN collegial_approval ca ON ca.line_id = bl.id AND s_ca + LEFT JOIN ckk ck ON ck.line_id = bl.id AND (s_ckk OR s_need_bk) + LEFT JOIN contract_detail cd ON cd.line_id = bl.id AND (s_cd OR s_need_bk) + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (s_need_ap OR s_cs OR s_ca OR s_ckk OR s_cd OR s_need_bk) + GROUP BY bl.expense_item_id + ), + -- Per-quarter hierarchy aggregates (no pivot — direct WHERE quarter=N) + aq1 AS ( + SELECT bl.expense_item_id AS eid, + SUM(blq.adj_current) AS ac, SUM(blq.adj_ssp) AS as2, SUM(blq.adj_rf) AS ar, SUM(blq.adj_reserve) AS arv, + SUM(blq.payment_amount) AS pa, SUM(blq.payment_amount_ho) AS pho, SUM(blq.payment_amount_rf) AS prf, + SUM(blq.actual_m1) AS m1, SUM(blq.actual_m2) AS m2, SUM(blq.actual_m3) AS m3, + SUM(blq.transfer_to_q2) AS tq2, SUM(blq.transfer_to_q3) AS tq3, SUM(blq.transfer_to_q4) AS tq4 + FROM budget_line bl JOIN expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN budget_line_quarter blq ON blq.line_id = bl.id AND blq.quarter = 1 + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet AND s_q1 + GROUP BY bl.expense_item_id + ), + aq2 AS ( + SELECT bl.expense_item_id AS eid, + SUM(blq.plan_revision_eco_change) AS reco, SUM(blq.plan_revision_item_adj) AS ritm, + SUM(blq.plan_revision_increase) AS rinc, SUM(blq.plan_revision_sequester) AS rseq, + SUM(blq.adj_current) AS ac, SUM(blq.adj_ssp) AS as2, SUM(blq.adj_rf) AS ar, SUM(blq.adj_reserve) AS arv, + SUM(blq.payment_amount) AS pa, SUM(blq.payment_amount_ho) AS pho, SUM(blq.payment_amount_rf) AS prf, + SUM(blq.actual_m1) AS m1, SUM(blq.actual_m2) AS m2, SUM(blq.actual_m3) AS m3, + SUM(blq.transfer_to_q3) AS tq3, SUM(blq.transfer_to_q4) AS tq4 + FROM budget_line bl JOIN expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN budget_line_quarter blq ON blq.line_id = bl.id AND blq.quarter = 2 + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet AND s_q2 + GROUP BY bl.expense_item_id + ), + aq3 AS ( + SELECT bl.expense_item_id AS eid, + SUM(blq.plan_revision_eco_change) AS reco, SUM(blq.plan_revision_item_adj) AS ritm, + SUM(blq.plan_revision_increase) AS rinc, SUM(blq.plan_revision_sequester) AS rseq, + SUM(blq.adj_current) AS ac, SUM(blq.adj_ssp) AS as2, SUM(blq.adj_rf) AS ar, SUM(blq.adj_reserve) AS arv, + SUM(blq.payment_amount) AS pa, SUM(blq.payment_amount_ho) AS pho, SUM(blq.payment_amount_rf) AS prf, + SUM(blq.actual_m1) AS m1, SUM(blq.actual_m2) AS m2, SUM(blq.actual_m3) AS m3, + SUM(blq.transfer_to_q4) AS tq4 + FROM budget_line bl JOIN expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN budget_line_quarter blq ON blq.line_id = bl.id AND blq.quarter = 3 + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet AND s_q3 + GROUP BY bl.expense_item_id + ), + aq4 AS ( + SELECT bl.expense_item_id AS eid, + SUM(blq.plan_revision_eco_change) AS reco, SUM(blq.plan_revision_item_adj) AS ritm, + SUM(blq.plan_revision_increase) AS rinc, SUM(blq.plan_revision_sequester) AS rseq, + SUM(blq.adj_current) AS ac, SUM(blq.adj_ssp) AS as2, SUM(blq.adj_rf) AS ar, SUM(blq.adj_reserve) AS arv, + SUM(blq.payment_amount) AS pa, SUM(blq.payment_amount_ho) AS pho, SUM(blq.payment_amount_rf) AS prf, + SUM(blq.actual_m1) AS m1, SUM(blq.actual_m2) AS m2, SUM(blq.actual_m3) AS m3, + SUM(blq.actual_spod) AS spod + FROM budget_line bl JOIN expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN budget_line_quarter blq ON blq.line_id = bl.id AND blq.quarter = 4 + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet AND (s_q4 OR s_tot) + GROUP BY bl.expense_item_id + ), + atot AS ( + SELECT bl.expense_item_id AS eid, + SUM(CASE WHEN blq.quarter=1 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0) END) AS fq1, + SUM(CASE WHEN blq.quarter=2 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0) END) AS fq2, + SUM(CASE WHEN blq.quarter=3 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0) END) AS fq3, + SUM(CASE WHEN blq.quarter=4 THEN COALESCE(blq.actual_m1,0)+COALESCE(blq.actual_m2,0)+COALESCE(blq.actual_m3,0)+COALESCE(blq.actual_spod,0) END) AS fq4, + SUM(blq.payment_amount) AS pay + FROM budget_line bl JOIN expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN budget_line_quarter blq ON blq.line_id = bl.id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet AND s_tot + GROUP BY bl.expense_item_id + ), + -- ═══ Pre-computed per-node aggregates (one pass over tw × agg) ═════════ + 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.ss1) AS ss1, SUM(a.ss2) AS ss2, SUM(a.ss3) AS ss3, SUM(a.ss4) AS ss4, + SUM(a.sr1) AS sr1, SUM(a.sr2) AS sr2, SUM(a.sr3) AS sr3, SUM(a.sr4) AS sr4, + SUM(a.sca) AS sca, + SUM(a.sckc) AS sckc, SUM(a.sck1) AS sck1, SUM(a.sck2) AS sck2, SUM(a.sck3) AS sck3, SUM(a.sck4) AS sck4, + SUM(a.scdc) AS scdc, SUM(a.scd1) AS scd1, SUM(a.scd2) AS scd2, SUM(a.scd3) AS scd3, SUM(a.scd4) AS scd4, + SUM(a.scst) AS scst, SUM(a.sfy1) AS sfy1, SUM(a.sfy2) AS sfy2, SUM(a.sossp) AS sossp, + SUM(CASE WHEN COALESCE(a.scd1,0)>0 THEN a.scd1 ELSE a.sck1 END) AS bk1, + SUM(CASE WHEN COALESCE(a.scd2,0)>0 THEN a.scd2 ELSE a.sck2 END) AS bk2, + SUM(CASE WHEN COALESCE(a.scd3,0)>0 THEN a.scd3 ELSE a.sck3 END) AS bk3, + SUM(CASE WHEN COALESCE(a.scd4,0)>0 THEN a.scd4 ELSE a.sck4 END) AS bk4 + 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.as2) AS as2, SUM(b.ar) AS ar, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.pho) AS pho, SUM(b.prf) AS prf, + 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 + FROM tw LEFT JOIN aq1 b ON b.eid = ANY(tw.desc_ids) GROUP BY tw.id + ), + tw_aq2 AS ( + SELECT tw.id, SUM(b.reco) AS reco, SUM(b.ritm) AS ritm, SUM(b.rinc) AS rinc, SUM(b.rseq) AS rseq, + SUM(b.ac) AS ac, SUM(b.as2) AS as2, SUM(b.ar) AS ar, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.pho) AS pho, SUM(b.prf) AS prf, + 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 + FROM tw LEFT JOIN aq2 b ON b.eid = ANY(tw.desc_ids) GROUP BY tw.id + ), + tw_aq3 AS ( + SELECT tw.id, SUM(b.reco) AS reco, SUM(b.ritm) AS ritm, SUM(b.rinc) AS rinc, SUM(b.rseq) AS rseq, + SUM(b.ac) AS ac, SUM(b.as2) AS as2, SUM(b.ar) AS ar, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.pho) AS pho, SUM(b.prf) AS prf, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq4) AS tq4 + FROM tw LEFT JOIN aq3 b ON b.eid = ANY(tw.desc_ids) GROUP BY tw.id + ), + tw_aq4 AS ( + SELECT tw.id, SUM(b.reco) AS reco, SUM(b.ritm) AS ritm, SUM(b.rinc) AS rinc, SUM(b.rseq) AS rseq, + SUM(b.ac) AS ac, SUM(b.as2) AS as2, SUM(b.ar) AS ar, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.pho) AS pho, SUM(b.prf) AS prf, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.spod) AS spod + FROM tw LEFT JOIN aq4 b ON b.eid = ANY(tw.desc_ids) GROUP BY tw.id + ), + tw_atot AS ( + SELECT tw.id, SUM(b.fq1) AS fq1, SUM(b.fq2) AS fq2, SUM(b.fq3) AS fq3, SUM(b.fq4) AS fq4, + SUM(b.pay) AS pay + FROM tw LEFT JOIN atot b ON b.eid = ANY(tw.desc_ids) GROUP BY tw.id + ) + + -- ═══ ЧАСТЬ A: INPUT строки ════════════════════════════════════════════ + SELECT * FROM ( + SELECT + 'INPUT'::VARCHAR, 3, f.lid, + v_year, f.sc, f.ic, f.ng, COALESCE(f.bname, f.ename), f.io, + 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_cs THEN f.total_amount END, CASE WHEN s_cs THEN f.counterparty END, + CASE WHEN s_cs THEN f.deadline END, CASE WHEN s_cs THEN f.comment END, + CASE WHEN s_cs THEN f.future_payments_y1 END, CASE WHEN s_cs THEN f.future_payments_y2 END, + CASE WHEN s_cs THEN f.other_ssp_amount END, + CASE WHEN s_al THEN f.al_io END, CASE WHEN s_al THEN f.al_po END, + CASE WHEN s_seq THEN f.sq1 END, CASE WHEN s_seq THEN f.sq2 END, + CASE WHEN s_seq THEN f.sq3 END, CASE WHEN s_seq THEN f.sq4 END, + CASE WHEN s_seq THEN f.sq1+f.sq2+f.sq3+f.sq4 END, CASE WHEN s_seq THEN f.sjust END, + CASE WHEN s_res THEN f.rq1 END, CASE WHEN s_res THEN f.rq2 END, + CASE WHEN s_res THEN f.rq3 END, CASE WHEN s_res THEN f.rq4 END, + CASE WHEN s_res THEN f.rq1+f.rq2+f.rq3+f.rq4 END, CASE WHEN s_res THEN f.rjust 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_ca THEN f.ca_amt END, CASE WHEN s_ca THEN f.ca_prot 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.ckk1 END, CASE WHEN s_ckk THEN f.ckk2 END, + CASE WHEN s_ckk THEN f.ckk3 END, CASE WHEN s_ckk THEN f.ckk4 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_pp END, CASE WHEN s_ckk THEN f.ckk_pm END, CASE WHEN s_ckk THEN f.ckk_cmt 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_add 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.cdq1 END, CASE WHEN s_cd THEN f.cdq2 END, + CASE WHEN s_cd THEN f.cdq3 END, CASE WHEN s_cd THEN f.cdq4 END, + CASE WHEN s_cd THEN f.cd_rf END, CASE WHEN s_cd THEN f.cd_vat END, + CASE WHEN s_cd THEN f.cd_rate END, CASE WHEN s_cd THEN f.cd_fx 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_cmt2 END, + -- Q1 + CASE WHEN s_q1 THEN f.q1_ac END, CASE WHEN s_q1 THEN f.q1_as END, + CASE WHEN s_q1 THEN f.q1_ar 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_pho END, CASE WHEN s_q1 THEN f.q1_prf END, + CASE WHEN s_q1 THEN f.q1_pcmt END, CASE WHEN s_q1 THEN f.q1_pact END, + CASE WHEN s_q1 THEN COALESCE(f.bk1,0) 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.bk1,0) END, + CASE WHEN s_q1 THEN f.cp1 - f.aq1 END, + CASE WHEN s_q1 THEN f.q1_tq2 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.cp1 - f.aq1 - COALESCE(f.q1_tq2,0) - COALESCE(f.q1_tq3,0) - COALESCE(f.q1_tq4,0) END, + CASE WHEN s_q1 THEN f.cp1 - f.aq1 END, + -- Q2 + CASE WHEN s_q2 THEN f.q2_reco END, CASE WHEN s_q2 THEN f.q2_ritm 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_ar 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_pho END, CASE WHEN s_q2 THEN f.q2_prf END, + CASE WHEN s_q2 THEN f.q2_pcmt END, CASE WHEN s_q2 THEN f.q2_pact END, + CASE WHEN s_q2 THEN COALESCE(f.bk2,0) 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.bk2,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_tq4 END, + CASE WHEN s_q2 THEN f.cp2 - f.aq2 - COALESCE(f.q2_tq3,0) - COALESCE(f.q2_tq4,0) END, + CASE WHEN s_q2 THEN f.cp2 - f.aq2 END, + -- Q3 + CASE WHEN s_q3 THEN f.q3_reco END, CASE WHEN s_q3 THEN f.q3_ritm 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_ar 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_pho END, CASE WHEN s_q3 THEN f.q3_prf END, + CASE WHEN s_q3 THEN f.q3_pcmt END, CASE WHEN s_q3 THEN f.q3_pact END, + CASE WHEN s_q3 THEN COALESCE(f.bk3,0) 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.bk3,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.cp3 - f.aq3 - COALESCE(f.q3_tq4,0) END, + CASE WHEN s_q3 THEN f.cp3 - f.aq3 END, + -- Q4 + CASE WHEN s_q4 THEN f.q4_reco END, CASE WHEN s_q4 THEN f.q4_ritm 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_ar 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_pho END, CASE WHEN s_q4 THEN f.q4_prf END, + CASE WHEN s_q4 THEN f.q4_pcmt END, CASE WHEN s_q4 THEN f.q4_pact END, + CASE WHEN s_q4 THEN COALESCE(f.bk4,0) 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.aq4_no_spod END, + CASE WHEN s_q4 THEN f.q4_spod END, + CASE WHEN s_q4 THEN f.cp4 - COALESCE(f.bk4,0) END, + CASE WHEN s_q4 THEN f.cp4 - f.aq4 END, + CASE WHEN s_q4 THEN f.cp4 - f.aq4 END, + CASE WHEN s_q4 THEN f.cp4 - f.aq4 END, + -- totals + CASE WHEN s_tot THEN COALESCE(f.tot_aq1,0)+COALESCE(f.tot_aq2,0)+COALESCE(f.tot_aq3,0)+COALESCE(f.tot_aq4,0) END, + CASE WHEN s_tot THEN COALESCE(f.tot_pa1,0)+COALESCE(f.tot_pa2,0)+COALESCE(f.tot_pa3,0)+COALESCE(f.tot_pa4,0) END, + f.tree_path || ARRAY[f.lid] AS _sort + FROM final_input f + + UNION ALL + + -- ═══ ЧАСТЬ B: Строки иерархии ═════════════════════════════════════════ + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' END::VARCHAR, + t.depth, NULL::INT, + v_year, + -- col_C = hierarchy code for depth 0-2, parent's code for depth 3 (SUB_ITEM) + CASE WHEN t.depth < 3 THEN t.item_id ELSE t.parent_item_id END, + -- col_D = NULL for hierarchy, R-code for SUB_ITEM + CASE WHEN t.depth = 3 THEN t.item_id ELSE NULL::VARCHAR END, + t.num_group_id, t.name, NULL::VARCHAR, + -- plan + CASE WHEN s_plan THEN ta.sp1 END, + CASE WHEN s_plan THEN ta.sp2 END, + CASE WHEN s_plan THEN ta.sp3 END, + CASE WHEN s_plan THEN ta.sp4 END, + CASE WHEN s_plan THEN (COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0)) END, + NULL::VARCHAR, + -- contract_summary + CASE WHEN s_cs THEN ta.scst END, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + CASE WHEN s_cs THEN ta.sfy1 END, + CASE WHEN s_cs THEN ta.sfy2 END, + CASE WHEN s_cs THEN ta.sossp END, + NULL::VARCHAR, NULL::VARCHAR, + -- sequestration + CASE WHEN s_seq THEN ta.ss1 END, + CASE WHEN s_seq THEN ta.ss2 END, + CASE WHEN s_seq THEN ta.ss3 END, + CASE WHEN s_seq THEN ta.ss4 END, + CASE WHEN s_seq THEN (COALESCE(ta.ss1,0)+COALESCE(ta.ss2,0)+COALESCE(ta.ss3,0)+COALESCE(ta.ss4,0)) END, + NULL::VARCHAR, + -- reserve + CASE WHEN s_res THEN ta.sr1 END, + CASE WHEN s_res THEN ta.sr2 END, + CASE WHEN s_res THEN ta.sr3 END, + CASE WHEN s_res THEN ta.sr4 END, + CASE WHEN s_res THEN (COALESCE(ta.sr1,0)+COALESCE(ta.sr2,0)+COALESCE(ta.sr3,0)+COALESCE(ta.sr4,0)) END, + NULL::VARCHAR, + -- approved + CASE WHEN s_appr THEN (COALESCE(ta.sp1,0)+COALESCE(ta.ss1,0)+COALESCE(ta.sr1,0)) END, + CASE WHEN s_appr THEN (COALESCE(ta.sp2,0)+COALESCE(ta.ss2,0)+COALESCE(ta.sr2,0)) END, + CASE WHEN s_appr THEN (COALESCE(ta.sp3,0)+COALESCE(ta.ss3,0)+COALESCE(ta.sr3,0)) END, + CASE WHEN s_appr THEN (COALESCE(ta.sp4,0)+COALESCE(ta.ss4,0)+COALESCE(ta.sr4,0)) END, + CASE WHEN s_appr THEN (COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0) + +COALESCE(ta.ss1,0)+COALESCE(ta.ss2,0)+COALESCE(ta.ss3,0)+COALESCE(ta.ss4,0) + +COALESCE(ta.sr1,0)+COALESCE(ta.sr2,0)+COALESCE(ta.sr3,0)+COALESCE(ta.sr4,0)) END, + -- collegial + CASE WHEN s_ca THEN ta.sca END, + NULL::VARCHAR, NULL::VARCHAR, + -- ckk + CASE WHEN s_ckk THEN ta.sckc END, + CASE WHEN s_ckk THEN ta.sck1 END, + CASE WHEN s_ckk THEN ta.sck2 END, + CASE WHEN s_ckk THEN ta.sck3 END, + CASE WHEN s_ckk THEN ta.sck4 END, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + -- contract_detail + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + CASE WHEN s_cd THEN ta.scdc END, + CASE WHEN s_cd THEN ta.scd1 END, + CASE WHEN s_cd THEN ta.scd2 END, + CASE WHEN s_cd THEN ta.scd3 END, + CASE WHEN s_cd THEN ta.scd4 END, + NULL::VARCHAR, NULL::VARCHAR, NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + -- Q1 hierarchy (from aq1) + CASE WHEN s_q1 THEN tb1.ac END, + CASE WHEN s_q1 THEN tb1.as2 END, + CASE WHEN s_q1 THEN tb1.ar END, + CASE WHEN s_q1 THEN tb1.arv END, + NULL::VARCHAR, + -- corrected_plan Q1 = approved_q1 + SUM(adj_*) + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.ss1,0)+COALESCE(ta.sr1,0)) + + COALESCE((COALESCE(tb1.ac,0)+COALESCE(tb1.as2,0)+COALESCE(tb1.ar,0)+COALESCE(tb1.arv,0)), 0) + END, + NULL::DATE, + CASE WHEN s_q1 THEN tb1.pa END, + CASE WHEN s_q1 THEN tb1.pho END, + CASE WHEN s_q1 THEN tb1.prf END, + NULL::VARCHAR, NULL::VARCHAR, + -- booking Q1 = SUM(CASE cd>0 THEN cd ELSE ckk) + CASE WHEN s_q1 THEN ta.bk1 END, + 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, + -- rem_booking Q1 = corrected_plan - booking (ckk or cd) + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.ss1,0)+COALESCE(ta.sr1,0)) + + COALESCE((COALESCE(tb1.ac,0)+COALESCE(tb1.as2,0)+COALESCE(tb1.ar,0)+COALESCE(tb1.arv,0)),0) + - COALESCE(ta.bk1,0) + END, + -- rem_actual Q1 = corrected_plan - actual_quarter + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.ss1,0)+COALESCE(ta.sr1,0)) + + COALESCE((COALESCE(tb1.ac,0)+COALESCE(tb1.as2,0)+COALESCE(tb1.ar,0)+COALESCE(tb1.arv,0)),0) + - COALESCE((COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)),0) + END, + CASE WHEN s_q1 THEN tb1.tq2 END, + CASE WHEN s_q1 THEN tb1.tq3 END, + CASE WHEN s_q1 THEN tb1.tq4 END, + -- economy Q1 = rem_actual - transfers + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.ss1,0)+COALESCE(ta.sr1,0)) + + COALESCE((COALESCE(tb1.ac,0)+COALESCE(tb1.as2,0)+COALESCE(tb1.ar,0)+COALESCE(tb1.arv,0)),0) + - COALESCE((COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)),0) + - COALESCE((COALESCE(tb1.tq2,0)+COALESCE(tb1.tq3,0)+COALESCE(tb1.tq4,0)),0) + END, + -- total Q1 = rem_actual + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.ss1,0)+COALESCE(ta.sr1,0)) + + COALESCE((COALESCE(tb1.ac,0)+COALESCE(tb1.as2,0)+COALESCE(tb1.ar,0)+COALESCE(tb1.arv,0)),0) + - COALESCE((COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)),0) + END, + -- Q2 hierarchy (from aq2) + CASE WHEN s_q2 THEN tb2.reco END, + CASE WHEN s_q2 THEN tb2.ritm END, + CASE WHEN s_q2 THEN tb2.rinc END, + CASE WHEN s_q2 THEN tb2.rseq END, + NULL::VARCHAR, + -- new_plan Q2 = approved_q2 + transfer_q1→q2 + revisions + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.ss2,0)+COALESCE(ta.sr2,0)) + + COALESCE(tb1.tq2, 0) + + COALESCE((COALESCE(tb2.reco,0)+COALESCE(tb2.ritm,0)+COALESCE(tb2.rinc,0)+COALESCE(tb2.rseq,0)), 0) + END, + CASE WHEN s_q2 THEN tb2.ac END, + CASE WHEN s_q2 THEN tb2.as2 END, + CASE WHEN s_q2 THEN tb2.ar END, + CASE WHEN s_q2 THEN tb2.arv END, + NULL::VARCHAR, + -- corrected_plan Q2 = new_plan_q2 + adj_* + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.ss2,0)+COALESCE(ta.sr2,0)) + + COALESCE(tb1.tq2, 0) + + COALESCE((COALESCE(tb2.reco,0)+COALESCE(tb2.ritm,0)+COALESCE(tb2.rinc,0)+COALESCE(tb2.rseq,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as2,0)+COALESCE(tb2.ar,0)+COALESCE(tb2.arv,0)), 0) + END, + NULL::DATE, + CASE WHEN s_q2 THEN tb2.pa END, + CASE WHEN s_q2 THEN tb2.pho END, + CASE WHEN s_q2 THEN tb2.prf END, + NULL::VARCHAR, NULL::VARCHAR, + CASE WHEN s_q2 THEN ta.bk2 END, + 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, + -- rem_booking Q2 + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.ss2,0)+COALESCE(ta.sr2,0)) + + COALESCE(tb1.tq2,0) + + COALESCE((COALESCE(tb2.reco,0)+COALESCE(tb2.ritm,0)+COALESCE(tb2.rinc,0)+COALESCE(tb2.rseq,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as2,0)+COALESCE(tb2.ar,0)+COALESCE(tb2.arv,0)),0) + - COALESCE(ta.bk2,0) + END, + -- rem_actual Q2 + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.ss2,0)+COALESCE(ta.sr2,0)) + + COALESCE(tb1.tq2,0) + + COALESCE((COALESCE(tb2.reco,0)+COALESCE(tb2.ritm,0)+COALESCE(tb2.rinc,0)+COALESCE(tb2.rseq,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as2,0)+COALESCE(tb2.ar,0)+COALESCE(tb2.arv,0)),0) + - COALESCE((COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)),0) + END, + CASE WHEN s_q2 THEN tb2.tq3 END, + CASE WHEN s_q2 THEN tb2.tq4 END, + -- economy Q2 + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.ss2,0)+COALESCE(ta.sr2,0)) + + COALESCE(tb1.tq2,0) + + COALESCE((COALESCE(tb2.reco,0)+COALESCE(tb2.ritm,0)+COALESCE(tb2.rinc,0)+COALESCE(tb2.rseq,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as2,0)+COALESCE(tb2.ar,0)+COALESCE(tb2.arv,0)),0) + - COALESCE((COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)),0) + - COALESCE((COALESCE(tb2.tq3,0)+COALESCE(tb2.tq4,0)),0) + END, + -- total Q2 = rem_actual + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.ss2,0)+COALESCE(ta.sr2,0)) + + COALESCE(tb1.tq2,0) + + COALESCE((COALESCE(tb2.reco,0)+COALESCE(tb2.ritm,0)+COALESCE(tb2.rinc,0)+COALESCE(tb2.rseq,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as2,0)+COALESCE(tb2.ar,0)+COALESCE(tb2.arv,0)),0) + - COALESCE((COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)),0) + END, + -- Q3 hierarchy (from aq3) + CASE WHEN s_q3 THEN tb3.reco END, + CASE WHEN s_q3 THEN tb3.ritm END, + CASE WHEN s_q3 THEN tb3.rinc END, + CASE WHEN s_q3 THEN tb3.rseq END, + NULL::VARCHAR, + -- new_plan Q3 = approved_q3 + transfer_q1→q3 + transfer_q2→q3 + revisions + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.ss3,0)+COALESCE(ta.sr3,0)) + + COALESCE(tb1.tq3, 0) + + COALESCE(tb2.tq3, 0) + + COALESCE((COALESCE(tb3.reco,0)+COALESCE(tb3.ritm,0)+COALESCE(tb3.rinc,0)+COALESCE(tb3.rseq,0)), 0) + END, + CASE WHEN s_q3 THEN tb3.ac END, + CASE WHEN s_q3 THEN tb3.as2 END, + CASE WHEN s_q3 THEN tb3.ar END, + CASE WHEN s_q3 THEN tb3.arv END, + NULL::VARCHAR, + -- corrected_plan Q3 = new_plan + adj_* + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.ss3,0)+COALESCE(ta.sr3,0)) + + COALESCE(tb1.tq3, 0) + + COALESCE(tb2.tq3, 0) + + COALESCE((COALESCE(tb3.reco,0)+COALESCE(tb3.ritm,0)+COALESCE(tb3.rinc,0)+COALESCE(tb3.rseq,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as2,0)+COALESCE(tb3.ar,0)+COALESCE(tb3.arv,0)), 0) + END, + NULL::DATE, + CASE WHEN s_q3 THEN tb3.pa END, + CASE WHEN s_q3 THEN tb3.pho END, + CASE WHEN s_q3 THEN tb3.prf END, + NULL::VARCHAR, NULL::VARCHAR, + CASE WHEN s_q3 THEN ta.bk3 END, + 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, + -- rem_booking Q3 + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.ss3,0)+COALESCE(ta.sr3,0)) + + COALESCE(tb1.tq3,0) + + COALESCE(tb2.tq3,0) + + COALESCE((COALESCE(tb3.reco,0)+COALESCE(tb3.ritm,0)+COALESCE(tb3.rinc,0)+COALESCE(tb3.rseq,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as2,0)+COALESCE(tb3.ar,0)+COALESCE(tb3.arv,0)),0) + - COALESCE(ta.bk3,0) + END, + -- rem_actual Q3 + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.ss3,0)+COALESCE(ta.sr3,0)) + + COALESCE(tb1.tq3,0) + + COALESCE(tb2.tq3,0) + + COALESCE((COALESCE(tb3.reco,0)+COALESCE(tb3.ritm,0)+COALESCE(tb3.rinc,0)+COALESCE(tb3.rseq,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as2,0)+COALESCE(tb3.ar,0)+COALESCE(tb3.arv,0)),0) + - COALESCE((COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)),0) + END, + CASE WHEN s_q3 THEN tb3.tq4 END, + -- economy Q3 + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.ss3,0)+COALESCE(ta.sr3,0)) + + COALESCE(tb1.tq3,0) + + COALESCE(tb2.tq3,0) + + COALESCE((COALESCE(tb3.reco,0)+COALESCE(tb3.ritm,0)+COALESCE(tb3.rinc,0)+COALESCE(tb3.rseq,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as2,0)+COALESCE(tb3.ar,0)+COALESCE(tb3.arv,0)),0) + - COALESCE((COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)),0) + - COALESCE(COALESCE(tb3.tq4,0),0) + END, + -- total Q3 = rem_actual + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.ss3,0)+COALESCE(ta.sr3,0)) + + COALESCE(tb1.tq3,0) + + COALESCE(tb2.tq3,0) + + COALESCE((COALESCE(tb3.reco,0)+COALESCE(tb3.ritm,0)+COALESCE(tb3.rinc,0)+COALESCE(tb3.rseq,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as2,0)+COALESCE(tb3.ar,0)+COALESCE(tb3.arv,0)),0) + - COALESCE((COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)),0) + END, + -- Q4 hierarchy (from aq4) + CASE WHEN s_q4 THEN tb4.reco END, + CASE WHEN s_q4 THEN tb4.ritm END, + CASE WHEN s_q4 THEN tb4.rinc END, + CASE WHEN s_q4 THEN tb4.rseq END, + NULL::VARCHAR, + -- new_plan Q4 = approved_q4 + transfers_q1→q4 + q2→q4 + q3→q4 + revisions + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.ss4,0)+COALESCE(ta.sr4,0)) + + COALESCE(tb1.tq4, 0) + + COALESCE(tb2.tq4, 0) + + COALESCE(tb3.tq4, 0) + + COALESCE((COALESCE(tb4.reco,0)+COALESCE(tb4.ritm,0)+COALESCE(tb4.rinc,0)+COALESCE(tb4.rseq,0)), 0) + END, + CASE WHEN s_q4 THEN tb4.ac END, + CASE WHEN s_q4 THEN tb4.as2 END, + CASE WHEN s_q4 THEN tb4.ar END, + CASE WHEN s_q4 THEN tb4.arv END, + NULL::VARCHAR, + -- corrected_plan Q4 = new_plan + adj_* + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.ss4,0)+COALESCE(ta.sr4,0)) + + COALESCE(tb1.tq4, 0) + + COALESCE(tb2.tq4, 0) + + COALESCE(tb3.tq4, 0) + + COALESCE((COALESCE(tb4.reco,0)+COALESCE(tb4.ritm,0)+COALESCE(tb4.rinc,0)+COALESCE(tb4.rseq,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as2,0)+COALESCE(tb4.ar,0)+COALESCE(tb4.arv,0)), 0) + END, + NULL::DATE, + CASE WHEN s_q4 THEN tb4.pa END, + CASE WHEN s_q4 THEN tb4.pho END, + CASE WHEN s_q4 THEN tb4.prf END, + NULL::VARCHAR, NULL::VARCHAR, + CASE WHEN s_q4 THEN ta.bk4 END, + 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 (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)) END, + CASE WHEN s_q4 THEN tb4.spod END, + -- rem_booking Q4 + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.ss4,0)+COALESCE(ta.sr4,0)) + + COALESCE(tb1.tq4,0) + + COALESCE(tb2.tq4,0) + + COALESCE(tb3.tq4,0) + + COALESCE((COALESCE(tb4.reco,0)+COALESCE(tb4.ritm,0)+COALESCE(tb4.rinc,0)+COALESCE(tb4.rseq,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as2,0)+COALESCE(tb4.ar,0)+COALESCE(tb4.arv,0)),0) + - COALESCE(ta.bk4,0) + END, + -- rem_actual Q4 (with spod) + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.ss4,0)+COALESCE(ta.sr4,0)) + + COALESCE(tb1.tq4,0) + + COALESCE(tb2.tq4,0) + + COALESCE(tb3.tq4,0) + + COALESCE((COALESCE(tb4.reco,0)+COALESCE(tb4.ritm,0)+COALESCE(tb4.rinc,0)+COALESCE(tb4.rseq,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as2,0)+COALESCE(tb4.ar,0)+COALESCE(tb4.arv,0)),0) + - COALESCE((COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)),0) + END, + -- economy Q4 = rem_actual (no transfers from Q4) + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.ss4,0)+COALESCE(ta.sr4,0)) + + COALESCE(tb1.tq4,0) + + COALESCE(tb2.tq4,0) + + COALESCE(tb3.tq4,0) + + COALESCE((COALESCE(tb4.reco,0)+COALESCE(tb4.ritm,0)+COALESCE(tb4.rinc,0)+COALESCE(tb4.rseq,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as2,0)+COALESCE(tb4.ar,0)+COALESCE(tb4.arv,0)),0) + - COALESCE((COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)),0) + END, + -- total Q4 = rem_actual + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.ss4,0)+COALESCE(ta.sr4,0)) + + COALESCE(tb1.tq4,0) + + COALESCE(tb2.tq4,0) + + COALESCE(tb3.tq4,0) + + COALESCE((COALESCE(tb4.reco,0)+COALESCE(tb4.ritm,0)+COALESCE(tb4.rinc,0)+COALESCE(tb4.rseq,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as2,0)+COALESCE(tb4.ar,0)+COALESCE(tb4.arv,0)),0) + - COALESCE((COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)),0) + END, + -- totals hierarchy (from atot) + CASE WHEN s_tot THEN (COALESCE(tt.fq1,0)+COALESCE(tt.fq2,0)+COALESCE(tt.fq3,0)+COALESCE(tt.fq4,0)) END, + CASE WHEN s_tot THEN tt.pay END, + 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 + LEFT JOIN tw_atot tt ON tt.id = t.id + ) sub + ORDER BY sub._sort; + +END; +$function$ +; + +-- DROP FUNCTION v3.v_form1_sheet_jsonb(int4, varchar, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form1_sheet_jsonb(p_form_id integer, p_sheet character varying, p_direction character varying DEFAULT NULL::character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, line_id integer, header jsonb, plan_data jsonb, contract_summary jsonb, allocation_data jsonb, sequestration_data jsonb, reserve_data jsonb, approved_data jsonb, collegial_data jsonb, ckk_data jsonb, contract_detail jsonb, q1_data jsonb, q2_data jsonb, q3_data jsonb, q4_data jsonb, totals_data jsonb, _sort_path integer[]) + LANGUAGE sql + STABLE + SET search_path TO 'v3', 'pg_catalog' +AS $function$ + WITH flags AS ( + SELECT + (p_sections IS NULL OR 'plan' = ANY(p_sections)) AS s_plan, + (p_sections IS NULL OR 'contract_summary' = ANY(p_sections)) AS s_cs, + (p_sections IS NULL OR 'allocation' = ANY(p_sections)) AS s_al, + (p_sections IS NULL OR 'sequestration' = ANY(p_sections)) AS s_seq, + (p_sections IS NULL OR 'reserve' = ANY(p_sections)) AS s_res, + (p_sections IS NULL OR 'approved' = ANY(p_sections)) AS s_appr, + (p_sections IS NULL OR 'collegial' = ANY(p_sections)) AS s_ca, + (p_sections IS NULL OR 'ckk' = ANY(p_sections)) AS s_ckk, + (p_sections IS NULL OR 'contract_detail' = ANY(p_sections)) AS s_cd, + (p_sections IS NULL OR 'q1' = ANY(p_sections)) AS s_q1, + (p_sections IS NULL OR 'q2' = ANY(p_sections)) AS s_q2, + (p_sections IS NULL OR 'q3' = ANY(p_sections)) AS s_q3, + (p_sections IS NULL OR 'q4' = ANY(p_sections)) AS s_q4, + (p_sections IS NULL OR 'totals' = ANY(p_sections)) AS s_tot + ) + SELECT + s.row_type, s.depth, s.line_id, + + -- header (всегда). expense_item_id извлекается из _sort_path: + -- для INPUT путь = {...ei_id, line_id} → предпоследний элемент + -- для иерархии путь = {...ei_id} → последний элемент + jsonb_build_object( + 'year', s.col_B_year, + 'section', s.col_C_section, + 'item_id', s.col_D_item_id, + 'num_group', s.col_E_num_group, + 'name', s.col_F_name, + 'internal_order', s.col_G_internal_order, + 'expense_item_id', CASE WHEN s.row_type = 'INPUT' + THEN s._sort_path[array_upper(s._sort_path,1)-1] + ELSE s._sort_path[array_upper(s._sort_path,1)] END + ) AS header, + + CASE WHEN f.s_plan THEN jsonb_build_object( + 'q1', s.col_H_plan_q1, + 'q2', s.col_I_plan_q2, + 'q3', s.col_J_plan_q3, + 'q4', s.col_K_plan_q4, + 'year', s.col_L_plan_year, + 'comment', s.col_M_plan_comment + ) END AS plan_data, + + CASE WHEN f.s_cs THEN jsonb_build_object( + 'total', s.col_N_cs_total, + 'counterparty', s.col_O_cs_counterparty, + 'deadline', s.col_P_cs_deadline, + 'comment', s.col_Q_cs_comment, + 'future_y1', s.col_R_cs_future_y1, + 'future_y2', s.col_S_cs_future_y2, + 'other_ssp', s.col_T_cs_other_ssp + ) END AS contract_summary, + + CASE WHEN f.s_al THEN jsonb_build_object( + 'order', s.col_V_al_order, + 'property', s.col_W_al_property + ) END AS allocation_data, + + CASE WHEN f.s_seq THEN jsonb_build_object( + 'q1', s.col_Y_seq_q1, + 'q2', s.col_Z_seq_q2, + 'q3', s.col_AA_seq_q3, + 'q4', s.col_AB_seq_q4, + 'year', s.col_AC_seq_year, + 'justification', s.col_AD_seq_justification + ) END AS sequestration_data, + + CASE WHEN f.s_res THEN jsonb_build_object( + 'q1', s.col_AF_res_q1, + 'q2', s.col_AG_res_q2, + 'q3', s.col_AH_res_q3, + 'q4', s.col_AI_res_q4, + 'year', s.col_AJ_res_year, + 'justification', s.col_AK_res_justification + ) END AS reserve_data, + + CASE WHEN f.s_appr THEN jsonb_build_object( + 'q1', s.col_AM_approved_q1, + 'q2', s.col_AN_approved_q2, + 'q3', s.col_AO_approved_q3, + 'q4', s.col_AP_approved_q4, + 'year', s.col_AQ_approved_year + ) END AS approved_data, + + CASE WHEN f.s_ca THEN jsonb_build_object( + 'approved', s.col_AS_ca_approved, + 'protocol', s.col_AT_ca_protocol, + 'note', s.col_AU_ca_note + ) END AS collegial_data, + + CASE WHEN f.s_ckk THEN jsonb_build_object( + 'ceiling', s.col_AW_ckk_ceiling, + 'q1', s.col_AX_ckk_exp_q1, + 'q2', s.col_AY_ckk_exp_q2, + 'q3', s.col_AZ_ckk_exp_q3, + 'q4', s.col_BA_ckk_exp_q4, + 'rf_schedule', s.col_BB_ckk_rf_sched, + 'deadline', s.col_BC_ckk_deadline, + 'proc_plan', s.col_BD_ckk_proc_plan, + 'proc_method', s.col_BE_ckk_proc_meth, + 'comment', s.col_BF_ckk_comment + ) END AS ckk_data, + + CASE WHEN f.s_cd THEN jsonb_build_object( + 'counterparty', s.col_BH_cd_counterparty, + 'reference', s.col_BI_cd_reference, + 'addenda', s.col_BJ_cd_addenda, + 'subject', s.col_BK_cd_subject, + 'currency', s.col_BL_cd_currency, + 'ceiling', s.col_BM_cd_ceiling, + 'q1', s.col_BN_cd_exp_q1, + 'q2', s.col_BO_cd_exp_q2, + 'q3', s.col_BP_cd_exp_q3, + 'q4', s.col_BQ_cd_exp_q4, + 'rf_schedule', s.col_BR_cd_rf_sched, + 'vat_rate', s.col_BS_cd_vat, + 'exchange_rate', s.col_BT_cd_rate, + 'amount_foreign', s.col_BU_cd_amount_fx, + 'deadline', s.col_BV_cd_deadline, + 'payment_scheme', s.col_BW_cd_scheme, + 'act', s.col_BX_cd_act, + 'comment', s.col_BY_cd_comment + ) END AS contract_detail, + + CASE WHEN f.s_q1 THEN jsonb_build_object( + 'adj_current', s.col_CA_q1_adj_current, + 'adj_ssp', s.col_CB_q1_adj_ssp, + 'adj_rf', s.col_CC_q1_adj_rf, + 'adj_reserve', s.col_CD_q1_adj_reserve, + 'adj_comment', s.col_CE_q1_adj_comment, + 'corrected_plan', s.col_CF_q1_corrected_plan, + 'pay_date', s.col_CH_q1_pay_date, + 'pay_amount', s.col_CI_q1_pay_amount, + 'pay_ho', s.col_CJ_q1_pay_ho, + 'pay_rf', s.col_CK_q1_pay_rf, + 'pay_comment', s.col_CL_q1_pay_comment, + 'pay_act', s.col_CM_q1_pay_act, + 'booking', s.col_CO_q1_booking, + 'actual_m1', s.col_CP_q1_actual_m1, + 'actual_m2', s.col_CQ_q1_actual_m2, + 'actual_m3', s.col_CR_q1_actual_m3, + 'actual_quarter', s.col_CS_q1_actual_quarter, + 'rem_booking', s.col_CT_q1_rem_booking, + 'rem_actual', s.col_CU_q1_rem_actual, + 'transfer_q2', s.col_CW_q1_trans_q2, + 'transfer_q3', s.col_CX_q1_trans_q3, + 'transfer_q4', s.col_CY_q1_trans_q4, + 'economy', s.col_CZ_q1_economy, + 'total', s.col_DA_q1_total + ) END AS q1_data, + + CASE WHEN f.s_q2 THEN jsonb_build_object( + 'rev_eco', s.col_DC_q2_rev_eco, + 'rev_item', s.col_DD_q2_rev_item, + 'rev_inc', s.col_DE_q2_rev_inc, + 'rev_seq', s.col_DF_q2_rev_seq, + 'rev_comment', s.col_DG_q2_rev_comment, + 'new_plan', s.col_DH_q2_new_plan, + 'adj_current', s.col_DJ_q2_adj_current, + 'adj_ssp', s.col_DK_q2_adj_ssp, + 'adj_rf', s.col_DL_q2_adj_rf, + 'adj_reserve', s.col_DM_q2_adj_reserve, + 'adj_comment', s.col_DN_q2_adj_comment, + 'corrected_plan', s.col_DO_q2_corrected_plan, + 'pay_date', s.col_DQ_q2_pay_date, + 'pay_amount', s.col_DR_q2_pay_amount, + 'pay_ho', s.col_DS_q2_pay_ho, + 'pay_rf', s.col_DT_q2_pay_rf, + 'pay_comment', s.col_DU_q2_pay_comment, + 'pay_act', s.col_DV_q2_pay_act, + 'booking', s.col_DX_q2_booking, + 'actual_m1', s.col_DY_q2_actual_m1, + 'actual_m2', s.col_DZ_q2_actual_m2, + 'actual_m3', s.col_EA_q2_actual_m3, + 'actual_quarter', s.col_EB_q2_actual_quarter, + 'rem_booking', s.col_EC_q2_rem_booking, + 'rem_actual', s.col_ED_q2_rem_actual, + 'transfer_q3', s.col_EF_q2_trans_q3, + 'transfer_q4', s.col_EG_q2_trans_q4, + 'economy', s.col_EH_q2_economy, + 'total', s.col_EI_q2_total + ) END AS q2_data, + + CASE WHEN f.s_q3 THEN jsonb_build_object( + 'rev_eco', s.col_EK_q3_rev_eco, + 'rev_item', s.col_EL_q3_rev_item, + 'rev_inc', s.col_EM_q3_rev_inc, + 'rev_seq', s.col_EN_q3_rev_seq, + 'rev_comment', s.col_EO_q3_rev_comment, + 'new_plan', s.col_EP_q3_new_plan, + 'adj_current', s.col_ER_q3_adj_current, + 'adj_ssp', s.col_ES_q3_adj_ssp, + 'adj_rf', s.col_ET_q3_adj_rf, + 'adj_reserve', s.col_EU_q3_adj_reserve, + 'adj_comment', s.col_EV_q3_adj_comment, + 'corrected_plan', s.col_EW_q3_corrected_plan, + 'pay_date', s.col_EY_q3_pay_date, + 'pay_amount', s.col_EZ_q3_pay_amount, + 'pay_ho', s.col_FA_q3_pay_ho, + 'pay_rf', s.col_FB_q3_pay_rf, + 'pay_comment', s.col_FC_q3_pay_comment, + 'pay_act', s.col_FD_q3_pay_act, + 'booking', s.col_FF_q3_booking, + 'actual_m1', s.col_FG_q3_actual_m1, + 'actual_m2', s.col_FH_q3_actual_m2, + 'actual_m3', s.col_FI_q3_actual_m3, + 'actual_quarter', s.col_FJ_q3_actual_quarter, + 'rem_booking', s.col_FK_q3_rem_booking, + 'rem_actual', s.col_FL_q3_rem_actual, + 'transfer_q4', s.col_FN_q3_trans_q4, + 'economy', s.col_FO_q3_economy, + 'total', s.col_FP_q3_total + ) END AS q3_data, + + CASE WHEN f.s_q4 THEN jsonb_build_object( + 'rev_eco', s.col_FR_q4_rev_eco, + 'rev_item', s.col_FS_q4_rev_item, + 'rev_inc', s.col_FT_q4_rev_inc, + 'rev_seq', s.col_FU_q4_rev_seq, + 'rev_comment', s.col_FV_q4_rev_comment, + 'new_plan', s.col_FW_q4_new_plan, + 'adj_current', s.col_FY_q4_adj_current, + 'adj_ssp', s.col_FZ_q4_adj_ssp, + 'adj_rf', s.col_GA_q4_adj_rf, + 'adj_reserve', s.col_GB_q4_adj_reserve, + 'adj_comment', s.col_GC_q4_adj_comment, + 'corrected_plan', s.col_GD_q4_corrected_plan, + 'pay_date', s.col_GF_q4_pay_date, + 'pay_amount', s.col_GG_q4_pay_amount, + 'pay_ho', s.col_GH_q4_pay_ho, + 'pay_rf', s.col_GI_q4_pay_rf, + 'pay_comment', s.col_GJ_q4_pay_comment, + 'pay_act', s.col_GK_q4_pay_act, + 'booking', s.col_GM_q4_booking, + 'actual_m1', s.col_GN_q4_actual_m1, + 'actual_m2', s.col_GO_q4_actual_m2, + 'actual_m3', s.col_GP_q4_actual_m3, + 'actual_quarter', s.col_GQ_q4_actual_quarter, + 'actual_spod', s.col_GR_q4_actual_spod, + 'rem_booking', s.col_GS_q4_rem_booking, + 'rem_actual', s.col_GT_q4_rem_actual, + 'economy', s.col_GV_q4_economy, + 'total', s.col_GW_q4_total + ) END AS q4_data, + + CASE WHEN f.s_tot THEN jsonb_build_object( + 'fact_year', s.col_GZ_fact_year, + 'pay_year', s.col_HB_pay_year + ) END AS totals_data, + + s._sort_path + FROM v_form1_sheet_sections(p_form_id, p_sheet, p_direction, p_sections) s + CROSS JOIN flags f + ORDER BY s._sort_path; +$function$ +; + +-- DROP FUNCTION v3.v_form1_smeta(int4, int4); + +CREATE OR REPLACE FUNCTION v3.v_form1_smeta(p_year integer, p_org_unit_id integer) + RETURNS TABLE(row_type character varying, depth integer, section_code character varying, name character varying, supp_plan_q1 numeric, supp_plan_q2 numeric, supp_plan_q3 numeric, supp_plan_q4 numeric, supp_plan_year numeric, dev_plan_q1 numeric, dev_plan_q2 numeric, dev_plan_q3 numeric, dev_plan_q4 numeric, dev_plan_year numeric, total_plan_year numeric, supp_appr_q1 numeric, supp_appr_q2 numeric, supp_appr_q3 numeric, supp_appr_q4 numeric, supp_appr_year numeric, dev_appr_q1 numeric, dev_appr_q2 numeric, dev_appr_q3 numeric, dev_appr_q4 numeric, dev_appr_year numeric, total_appr_year numeric, supp_act_q1 numeric, supp_act_q2 numeric, supp_act_q3 numeric, supp_act_q4 numeric, supp_act_year numeric, dev_act_q1 numeric, dev_act_q2 numeric, dev_act_q3 numeric, dev_act_q4 numeric, dev_act_year numeric, total_act_year numeric, supp_corr_q2 numeric, supp_corr_q3 numeric, supp_corr_q4 numeric, dev_corr_q2 numeric, dev_corr_q3 numeric, dev_corr_q4 numeric) + LANGUAGE plpgsql + STABLE + SET search_path TO 'v3', 'pg_catalog' +AS $function$ +#variable_conflict use_column +DECLARE + v_ahr_s INT; v_ahr_d INT; + v_cap_s INT; v_cap_d INT; + v_oper_s INT; v_oper_d INT; + v_sections TEXT[] := ARRAY['plan','approved','q1','q2','q3','q4']; +BEGIN + -- Resolve form_ids: within each (year, ssp, sheet), lower bf.id = Support, higher = Development + WITH ranked AS ( + SELECT bf.id AS fid, ei.sheet AS sh, + ROW_NUMBER() OVER (PARTITION BY ei.sheet ORDER BY bf.id) AS rn + FROM budget_form bf + JOIN budget_line bl ON bl.budget_form_id = bf.id + JOIN expense_item ei ON ei.id = bl.expense_item_id + WHERE bf.year = p_year AND bf.org_unit_id = p_org_unit_id + GROUP BY bf.id, ei.sheet + ) + SELECT + MAX(CASE WHEN sh='AHR' AND rn=1 THEN fid END), + MAX(CASE WHEN sh='AHR' AND rn=2 THEN fid END), + MAX(CASE WHEN sh='CAP' AND rn=1 THEN fid END), + MAX(CASE WHEN sh='CAP' AND rn=2 THEN fid END), + MAX(CASE WHEN sh='OPER' AND rn=1 THEN fid END), + MAX(CASE WHEN sh='OPER' AND rn=2 THEN fid END) + INTO v_ahr_s, v_ahr_d, v_cap_s, v_cap_d, v_oper_s, v_oper_d + FROM ranked; + + RETURN QUERY + WITH + -- 5 источников: row_type, depth, section_code, name + 22 метрик из sections + ahr_s AS ( + SELECT col_C_section AS sc, col_F_name AS nm, depth, row_type, + col_H_plan_q1 AS pq1, col_I_plan_q2 AS pq2, col_J_plan_q3 AS pq3, col_K_plan_q4 AS pq4, col_L_plan_year AS pyr, + col_AM_approved_q1 AS aq1, col_AN_approved_q2 AS aq2, col_AO_approved_q3 AS aq3, col_AP_approved_q4 AS aq4, col_AQ_approved_year AS ayr, + col_CS_q1_actual_quarter AS fq1, col_EB_q2_actual_quarter AS fq2, col_FJ_q3_actual_quarter AS fq3, + COALESCE(col_GQ_q4_actual_quarter,0) + COALESCE(col_GR_q4_actual_spod,0) AS fq4, + col_DH_q2_new_plan AS np2, col_EP_q3_new_plan AS np3, col_FW_q4_new_plan AS np4 + FROM v_form1_sheet_sections(v_ahr_s, 'AHR', 'Support', v_sections) + WHERE row_type IN ('ROOT','GROUP','ITEM') + ), + ahr_d AS ( + SELECT col_C_section AS sc, col_F_name AS nm, depth, row_type, + col_H_plan_q1 AS pq1, col_I_plan_q2 AS pq2, col_J_plan_q3 AS pq3, col_K_plan_q4 AS pq4, col_L_plan_year AS pyr, + col_AM_approved_q1 AS aq1, col_AN_approved_q2 AS aq2, col_AO_approved_q3 AS aq3, col_AP_approved_q4 AS aq4, col_AQ_approved_year AS ayr, + col_CS_q1_actual_quarter AS fq1, col_EB_q2_actual_quarter AS fq2, col_FJ_q3_actual_quarter AS fq3, + COALESCE(col_GQ_q4_actual_quarter,0) + COALESCE(col_GR_q4_actual_spod,0) AS fq4, + col_DH_q2_new_plan AS np2, col_EP_q3_new_plan AS np3, col_FW_q4_new_plan AS np4 + FROM v_form1_sheet_sections(v_ahr_d, 'AHR', 'Development', v_sections) + WHERE row_type IN ('ROOT','GROUP','ITEM') + ), + cap_s AS ( + SELECT col_C_section AS sc, col_F_name AS nm, depth, row_type, + col_H_plan_q1 AS pq1, col_I_plan_q2 AS pq2, col_J_plan_q3 AS pq3, col_K_plan_q4 AS pq4, col_L_plan_year AS pyr, + col_AM_approved_q1 AS aq1, col_AN_approved_q2 AS aq2, col_AO_approved_q3 AS aq3, col_AP_approved_q4 AS aq4, col_AQ_approved_year AS ayr, + col_CS_q1_actual_quarter AS fq1, col_EB_q2_actual_quarter AS fq2, col_FJ_q3_actual_quarter AS fq3, + COALESCE(col_GQ_q4_actual_quarter,0) + COALESCE(col_GR_q4_actual_spod,0) AS fq4, + col_DH_q2_new_plan AS np2, col_EP_q3_new_plan AS np3, col_FW_q4_new_plan AS np4 + FROM v_form1_sheet_sections(v_cap_s, 'CAP', 'Support', v_sections) + WHERE row_type IN ('ROOT','GROUP','ITEM') + ), + cap_d AS ( + SELECT col_C_section AS sc, col_F_name AS nm, depth, row_type, + col_H_plan_q1 AS pq1, col_I_plan_q2 AS pq2, col_J_plan_q3 AS pq3, col_K_plan_q4 AS pq4, col_L_plan_year AS pyr, + col_AM_approved_q1 AS aq1, col_AN_approved_q2 AS aq2, col_AO_approved_q3 AS aq3, col_AP_approved_q4 AS aq4, col_AQ_approved_year AS ayr, + col_CS_q1_actual_quarter AS fq1, col_EB_q2_actual_quarter AS fq2, col_FJ_q3_actual_quarter AS fq3, + COALESCE(col_GQ_q4_actual_quarter,0) + COALESCE(col_GR_q4_actual_spod,0) AS fq4, + col_DH_q2_new_plan AS np2, col_EP_q3_new_plan AS np3, col_FW_q4_new_plan AS np4 + FROM v_form1_sheet_sections(v_cap_d, 'CAP', 'Development', v_sections) + WHERE row_type IN ('ROOT','GROUP','ITEM') + ), + oper_s AS ( + SELECT col_C_section AS sc, col_F_name AS nm, depth, row_type, + col_H_plan_q1 AS pq1, col_I_plan_q2 AS pq2, col_J_plan_q3 AS pq3, col_K_plan_q4 AS pq4, col_L_plan_year AS pyr, + col_AM_approved_q1 AS aq1, col_AN_approved_q2 AS aq2, col_AO_approved_q3 AS aq3, col_AP_approved_q4 AS aq4, col_AQ_approved_year AS ayr, + col_CS_q1_actual_quarter AS fq1, col_EB_q2_actual_quarter AS fq2, col_FJ_q3_actual_quarter AS fq3, + COALESCE(col_GQ_q4_actual_quarter,0) + COALESCE(col_GR_q4_actual_spod,0) AS fq4, + col_DH_q2_new_plan AS np2, col_EP_q3_new_plan AS np3, col_FW_q4_new_plan AS np4 + FROM v_form1_sheet_sections(v_oper_s, 'OPER', 'Support', v_sections) + WHERE row_type IN ('ROOT','GROUP','ITEM') + ), + -- Пивот по section_code + merged AS ( + -- AHR блок + SELECT COALESCE(s.row_type, d.row_type) AS row_type, + COALESCE(s.depth, d.depth) AS depth, + COALESCE(s.sc, d.sc) AS sc, + COALESCE(s.nm, d.nm) AS nm, + s.pq1, s.pq2, s.pq3, s.pq4, s.pyr, + d.pq1 AS d_pq1, d.pq2 AS d_pq2, d.pq3 AS d_pq3, d.pq4 AS d_pq4, d.pyr AS d_pyr, + s.aq1, s.aq2, s.aq3, s.aq4, s.ayr, + d.aq1 AS d_aq1, d.aq2 AS d_aq2, d.aq3 AS d_aq3, d.aq4 AS d_aq4, d.ayr AS d_ayr, + s.fq1, s.fq2, s.fq3, s.fq4, + d.fq1 AS d_fq1, d.fq2 AS d_fq2, d.fq3 AS d_fq3, d.fq4 AS d_fq4, + s.np2, s.np3, s.np4, + d.np2 AS d_np2, d.np3 AS d_np3, d.np4 AS d_np4 + FROM ahr_s s FULL OUTER JOIN ahr_d d ON s.sc = d.sc + UNION ALL + -- CAP блок + SELECT COALESCE(s.row_type, d.row_type), COALESCE(s.depth, d.depth), + COALESCE(s.sc, d.sc), COALESCE(s.nm, d.nm), + s.pq1, s.pq2, s.pq3, s.pq4, s.pyr, + d.pq1, d.pq2, d.pq3, d.pq4, d.pyr, + s.aq1, s.aq2, s.aq3, s.aq4, s.ayr, + d.aq1, d.aq2, d.aq3, d.aq4, d.ayr, + s.fq1, s.fq2, s.fq3, s.fq4, + d.fq1, d.fq2, d.fq3, d.fq4, + s.np2, s.np3, s.np4, + d.np2, d.np3, d.np4 + FROM cap_s s FULL OUTER JOIN cap_d d ON s.sc = d.sc + UNION ALL + -- OPER блок (только Support) + SELECT s.row_type, s.depth, s.sc, s.nm, + s.pq1, s.pq2, s.pq3, s.pq4, s.pyr, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, + s.aq1, s.aq2, s.aq3, s.aq4, s.ayr, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, + s.fq1, s.fq2, s.fq3, s.fq4, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, + s.np2, s.np3, s.np4, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC + FROM oper_s s + ) + SELECT + m.row_type, m.depth, m.sc, m.nm, + m.pq1, m.pq2, m.pq3, m.pq4, m.pyr, + m.d_pq1, m.d_pq2, m.d_pq3, m.d_pq4, m.d_pyr, + COALESCE(m.pyr,0) + COALESCE(m.d_pyr,0) AS total_plan_year, + m.aq1, m.aq2, m.aq3, m.aq4, m.ayr, + m.d_aq1, m.d_aq2, m.d_aq3, m.d_aq4, m.d_ayr, + COALESCE(m.ayr,0) + COALESCE(m.d_ayr,0) AS total_appr_year, + m.fq1, m.fq2, m.fq3, m.fq4, COALESCE(m.fq1,0)+COALESCE(m.fq2,0)+COALESCE(m.fq3,0)+COALESCE(m.fq4,0), + m.d_fq1, m.d_fq2, m.d_fq3, m.d_fq4, COALESCE(m.d_fq1,0)+COALESCE(m.d_fq2,0)+COALESCE(m.d_fq3,0)+COALESCE(m.d_fq4,0), + COALESCE(m.fq1,0)+COALESCE(m.fq2,0)+COALESCE(m.fq3,0)+COALESCE(m.fq4,0) + + COALESCE(m.d_fq1,0)+COALESCE(m.d_fq2,0)+COALESCE(m.d_fq3,0)+COALESCE(m.d_fq4,0), + m.np2, m.np3, m.np4, + m.d_np2, m.d_np3, m.d_np4 + FROM merged m + ORDER BY m.sc; +END; +$function$ +; + +-- 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, _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_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_q1 := p_sections IS NULL OR 'q1' = ANY(p_sections); + s_q2 := p_sections IS NULL OR 'q2' = ANY(p_sections); + s_q3 := p_sections IS NULL OR 'q3' = ANY(p_sections); + s_q4 := p_sections IS NULL OR 'q4' = ANY(p_sections); + s_tot := p_sections IS NULL OR 'totals' = ANY(p_sections); + s_need_ap := s_plan OR s_appr OR s_q1 OR s_q2 OR s_q3 OR s_q4; + + RETURN QUERY + WITH + tw AS ( + SELECT t.id, t.section_code, t.item_id, t.num_group_id, t.name, + t.depth, t.path, t.parent_id, t.parent_item_id, t.desc_ids + FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id + JOIN v3.budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code + WHERE t.sheet = p_sheet + ), + page AS ( + SELECT bl.id AS lid, bl.expense_item_id AS eid, bl.name AS bname, bl.vsp_id + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + ), + -- Conditional joins (gated by section flags) + jp AS (SELECT p.* FROM v3.plan p JOIN page pg ON p.line_id = pg.lid WHERE s_need_ap), + jsd AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='DFIP'), + jsg AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='SSP_GO'), + jr AS (SELECT r.* FROM v3.reserve r JOIN page pg ON r.line_id = pg.lid WHERE s_need_ap), + jcd AS (SELECT c.* FROM v3.contract_detail c JOIN page pg ON c.line_id = pg.lid WHERE s_cd), + jck AS (SELECT c.* FROM v3.ckk c JOIN page pg ON c.line_id = pg.lid WHERE s_book), + jq1 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q1 AND q.quarter=1), + jq2 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q2 AND q.quarter=2), + jq3 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q3 AND q.quarter=3), + jq4 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE (s_q4 OR s_tot) AND q.quarter=4), + + -- INPUT строки: enriched данные + input_base AS ( + SELECT + pg.lid, pg.eid, pg.bname, pg.vsp_id, + v.address AS vsp_addr, + t.parent_item_id AS sc, t.item_id AS ic, t.num_group_id AS ng, t.name AS ename, t.path AS tree_path, + -- plan + COALESCE(p.plan_q1,0) AS pq1, COALESCE(p.plan_q2,0) AS pq2, + COALESCE(p.plan_q3,0) AS pq3, COALESCE(p.plan_q4,0) AS pq4, + p.comment AS pcmt, + -- seq dfip / ssp_go + COALESCE(sd.adj_q1,0) AS dq1, COALESCE(sd.adj_q2,0) AS dq2, + COALESCE(sd.adj_q3,0) AS dq3, COALESCE(sd.adj_q4,0) AS dq4, + sd.justification AS djust, + COALESCE(sg.adj_q1,0) AS gq1, COALESCE(sg.adj_q2,0) AS gq2, + COALESCE(sg.adj_q3,0) AS gq3, COALESCE(sg.adj_q4,0) AS gq4, + sg.justification AS gjust, + -- reserve + COALESCE(r.amount_q1,0) AS rq1, COALESCE(r.amount_q2,0) AS rq2, + COALESCE(r.amount_q3,0) AS rq3, COALESCE(r.amount_q4,0) AS rq4, + -- contract + cd.counterparty AS cd_cp, cd.reference AS cd_ref, cd.contract_date AS cd_dt, + cd.subject AS cd_subj, cd.currency AS cd_cur, cd.ceiling_amount AS cd_ceil, + cd.vat_rate AS cd_vat, cd.deadline AS cd_dl, cd.payment_scheme AS cd_sch, + cd.act AS cd_act, cd.comment AS cd_cmt, + -- ckk (booking) + ck.expenses_q1 AS bk1, ck.expenses_q2 AS bk2, ck.expenses_q3 AS bk3, ck.expenses_q4 AS bk4, + ck.expenses_next_year_q1 AS bn1, ck.expenses_next_year_q2 AS bn2, + ck.expenses_next_year_q3 AS bn3, ck.expenses_next_year_q4 AS bn4, + -- Q1 + q1.adj_current AS q1_ac, q1.adj_ssp AS q1_as, q1.adj_reserve AS q1_arv, q1.adj_comment AS q1_acmt, + q1.payment_date AS q1_pd, q1.payment_amount AS q1_pa, + q1.payment_comment AS q1_pcmt, + q1.payment_act AS q1_pact, q1.booking_amount AS q1_book, + q1.actual_m1 AS q1_m1, q1.actual_m2 AS q1_m2, q1.actual_m3 AS q1_m3, + q1.transfer_to_q2 AS q1_tq2, q1.transfer_to_q3 AS q1_tq3, q1.transfer_to_q4 AS q1_tq4, + q1.transfer_to_economy AS q1_te, + q1.transfer_delay_acts AS q1_tda, q1.transfer_delay_procurement AS q1_tdp, + q1.transfer_economy_rf AS q1_terf, + q1.transfer_next_comment AS q1_tnc, q1.transfer_far_comment AS q1_tfc, + -- Q2 + q2.target_change AS q2_tc, q2.base_plan_correction AS q2_bc, q2.base_plan_correction_comment AS q2_bcc, + q2.plan_revision_increase AS q2_rinc, q2.plan_revision_sequester AS q2_rseq, + q2.plan_revision_comment AS q2_rcmt, + q2.adj_current AS q2_ac, q2.adj_ssp AS q2_as, q2.adj_reserve AS q2_arv, + q2.adj_comment AS q2_acmt, + q2.payment_date AS q2_pd, q2.payment_amount AS q2_pa, + q2.payment_comment AS q2_pcmt, q2.payment_act AS q2_pact, + q2.booking_amount AS q2_book, + q2.actual_m1 AS q2_m1, q2.actual_m2 AS q2_m2, q2.actual_m3 AS q2_m3, + q2.transfer_to_q3 AS q2_tq3, q2.transfer_to_q4 AS q2_tq4, q2.transfer_to_economy AS q2_te, + q2.transfer_delay_acts AS q2_tda, q2.transfer_delay_procurement AS q2_tdp, + q2.transfer_economy_rf AS q2_terf, + q2.transfer_next_comment AS q2_tnc, q2.transfer_far_comment AS q2_tfc, + -- Q3 + q3.target_change AS q3_tc, q3.base_plan_correction AS q3_bc, + q3.base_plan_correction_comment AS q3_bcc, + q3.plan_revision_increase AS q3_rinc, q3.plan_revision_sequester AS q3_rseq, + q3.plan_revision_comment AS q3_rcmt, + q3.adj_current AS q3_ac, q3.adj_ssp AS q3_as, q3.adj_reserve AS q3_arv, + q3.adj_comment AS q3_acmt, + q3.payment_date AS q3_pd, q3.payment_amount AS q3_pa, + q3.payment_comment AS q3_pcmt, q3.payment_act AS q3_pact, + q3.booking_amount AS q3_book, + q3.actual_m1 AS q3_m1, q3.actual_m2 AS q3_m2, q3.actual_m3 AS q3_m3, + q3.transfer_to_q4 AS q3_tq4, q3.transfer_to_economy AS q3_te, + q3.transfer_delay_acts AS q3_tda, q3.transfer_delay_procurement AS q3_tdp, + q3.transfer_economy_rf AS q3_terf, q3.transfer_next_comment AS q3_tnc, + -- Q4 + q4.target_change AS q4_tc, q4.base_plan_correction AS q4_bc, + q4.base_plan_correction_comment AS q4_bcc, + q4.plan_revision_increase AS q4_rinc, q4.plan_revision_sequester AS q4_rseq, + q4.plan_revision_comment AS q4_rcmt, + q4.adj_current AS q4_ac, q4.adj_ssp AS q4_as, q4.adj_reserve AS q4_arv, + q4.adj_comment AS q4_acmt, + q4.payment_date AS q4_pd, q4.payment_amount AS q4_pa, + q4.payment_comment AS q4_pcmt, q4.payment_act AS q4_pact, + q4.booking_amount AS q4_book, + q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, q4.actual_spod AS q4_spod, + q4.transfer_to_economy AS q4_te, + -- approved per quarter + COALESCE(p.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(sg.adj_q1,0)+COALESCE(r.amount_q1,0) AS ap1, + COALESCE(p.plan_q2,0)+COALESCE(sd.adj_q2,0)+COALESCE(sg.adj_q2,0)+COALESCE(r.amount_q2,0) AS ap2, + COALESCE(p.plan_q3,0)+COALESCE(sd.adj_q3,0)+COALESCE(sg.adj_q3,0)+COALESCE(r.amount_q3,0) AS ap3, + COALESCE(p.plan_q4,0)+COALESCE(sd.adj_q4,0)+COALESCE(sg.adj_q4,0)+COALESCE(r.amount_q4,0) AS ap4 + FROM page pg + JOIN tw t ON t.id = pg.eid + LEFT JOIN v3.vsp v ON v.id = pg.vsp_id + LEFT JOIN jp p ON p.line_id = pg.lid + LEFT JOIN jsd sd ON sd.line_id = pg.lid + LEFT JOIN jsg sg ON sg.line_id = pg.lid + LEFT JOIN jr r ON r.line_id = pg.lid + LEFT JOIN jcd cd ON cd.line_id = pg.lid + LEFT JOIN jck ck ON ck.line_id = pg.lid + LEFT JOIN jq1 q1 ON q1.line_id = pg.lid + LEFT JOIN jq2 q2 ON q2.line_id = pg.lid + LEFT JOIN jq3 q3 ON q3.line_id = pg.lid + LEFT JOIN jq4 q4 ON q4.line_id = pg.lid + ), + -- Computed: corrected/new plans + enriched AS ( + SELECT b.*, + -- Q1 corrected_plan = approved + adj_* + b.ap1 + COALESCE(b.q1_ac,0)+COALESCE(b.q1_as,0)+COALESCE(b.q1_arv,0) AS cp1, + COALESCE(b.q1_m1,0)+COALESCE(b.q1_m2,0)+COALESCE(b.q1_m3,0) AS aq1, + -- Q2 new_plan = approved + transfer_q1 + revisions(inc/seq) + base_correction + b.ap2 + COALESCE(b.q1_tq2,0) + COALESCE(b.q2_rinc,0)+COALESCE(b.q2_rseq,0) + COALESCE(b.q2_bc,0) AS np2, + COALESCE(b.q2_m1,0)+COALESCE(b.q2_m2,0)+COALESCE(b.q2_m3,0) AS aq2, + b.ap3 + COALESCE(b.q1_tq3,0)+COALESCE(b.q2_tq3,0) + COALESCE(b.q3_rinc,0)+COALESCE(b.q3_rseq,0) + COALESCE(b.q3_bc,0) AS np3, + COALESCE(b.q3_m1,0)+COALESCE(b.q3_m2,0)+COALESCE(b.q3_m3,0) AS aq3, + b.ap4 + COALESCE(b.q1_tq4,0)+COALESCE(b.q2_tq4,0)+COALESCE(b.q3_tq4,0) + COALESCE(b.q4_rinc,0)+COALESCE(b.q4_rseq,0) + COALESCE(b.q4_bc,0) AS np4, + COALESCE(b.q4_m1,0)+COALESCE(b.q4_m2,0)+COALESCE(b.q4_m3,0)+COALESCE(b.q4_spod,0) AS aq4 + FROM input_base b + ), + final_input AS ( + SELECT e.*, + e.np2 + COALESCE(e.q2_ac,0)+COALESCE(e.q2_as,0)+COALESCE(e.q2_arv,0) AS cp2, + e.np3 + COALESCE(e.q3_ac,0)+COALESCE(e.q3_as,0)+COALESCE(e.q3_arv,0) AS cp3, + e.np4 + COALESCE(e.q4_ac,0)+COALESCE(e.q4_as,0)+COALESCE(e.q4_arv,0) AS cp4 + FROM enriched e + ), + + -- Aggregates per expense_item (для иерархии) + agg AS ( + SELECT bl.expense_item_id AS eid, + SUM(p.plan_q1) AS sp1, SUM(p.plan_q2) AS sp2, SUM(p.plan_q3) AS sp3, SUM(p.plan_q4) AS sp4, + SUM(sd.adj_q1) AS sd1, SUM(sd.adj_q2) AS sd2, SUM(sd.adj_q3) AS sd3, SUM(sd.adj_q4) AS sd4, + SUM(sg.adj_q1) AS sg1, SUM(sg.adj_q2) AS sg2, SUM(sg.adj_q3) AS sg3, SUM(sg.adj_q4) AS sg4, + SUM(r.amount_q1) AS sr1, SUM(r.amount_q2) AS sr2, SUM(r.amount_q3) AS sr3, SUM(r.amount_q4) AS sr4, + SUM(ck.expenses_q1) AS bk1, SUM(ck.expenses_q2) AS bk2, SUM(ck.expenses_q3) AS bk3, SUM(ck.expenses_q4) AS bk4, + SUM(ck.expenses_next_year_q1) AS bn1, SUM(ck.expenses_next_year_q2) AS bn2, + SUM(ck.expenses_next_year_q3) AS bn3, SUM(ck.expenses_next_year_q4) AS bn4 + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.plan p ON p.line_id = bl.id AND s_need_ap + LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor='DFIP' AND s_need_ap + LEFT JOIN v3.sequestration sg ON sg.line_id = bl.id AND sg.actor='SSP_GO' AND s_need_ap + LEFT JOIN v3.reserve r ON r.line_id = bl.id AND s_need_ap + LEFT JOIN v3.ckk ck ON ck.line_id = bl.id AND s_book + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (s_need_ap OR s_book) + GROUP BY bl.expense_item_id + ), + -- Per-quarter aggregates from blq + aq_q AS ( + SELECT bl.expense_item_id AS eid, q.quarter, + SUM(q.adj_current) AS ac, SUM(q.adj_ssp) AS as_v, SUM(q.adj_reserve) AS arv, + SUM(q.payment_amount) AS pa, + SUM(q.booking_amount) AS bk, + SUM(q.actual_m1) AS m1, SUM(q.actual_m2) AS m2, SUM(q.actual_m3) AS m3, + SUM(q.actual_spod) AS spod, + SUM(q.transfer_to_q2) AS tq2, SUM(q.transfer_to_q3) AS tq3, SUM(q.transfer_to_q4) AS tq4, + SUM(q.transfer_to_economy) AS te, + SUM(q.transfer_delay_acts) AS tda, + SUM(q.transfer_delay_procurement) AS tdp, + SUM(q.transfer_economy_rf) AS terf, + -- Δ к approved для перехода к new_plan: revision_inc/seq + target_change + base_correction + -- (Excel: DJ10 = SUM(AN, CY, DH, CF, DE:DG)). Должно совпадать с INPUT-формулой np. + SUM(COALESCE(q.plan_revision_increase,0) + + COALESCE(q.plan_revision_sequester,0) + + COALESCE(q.target_change,0) + + COALESCE(q.base_plan_correction,0)) AS rev + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + JOIN v3.budget_line_quarter q ON q.line_id = bl.id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + GROUP BY bl.expense_item_id, q.quarter + ), + -- Tree-rollup + tw_agg AS ( + SELECT tw.id, + SUM(a.sp1) AS sp1, SUM(a.sp2) AS sp2, SUM(a.sp3) AS sp3, SUM(a.sp4) AS sp4, + SUM(a.sd1) AS sd1, SUM(a.sd2) AS sd2, SUM(a.sd3) AS sd3, SUM(a.sd4) AS sd4, + SUM(a.sg1) AS sg1, SUM(a.sg2) AS sg2, SUM(a.sg3) AS sg3, SUM(a.sg4) AS sg4, + SUM(a.sr1) AS sr1, SUM(a.sr2) AS sr2, SUM(a.sr3) AS sr3, SUM(a.sr4) AS sr4, + SUM(a.bk1) AS bk1, SUM(a.bk2) AS bk2, SUM(a.bk3) AS bk3, SUM(a.bk4) AS bk4, + SUM(a.bn1) AS bn1, SUM(a.bn2) AS bn2, SUM(a.bn3) AS bn3, SUM(a.bn4) AS bn4 + FROM tw LEFT JOIN agg a ON a.eid = ANY(tw.desc_ids) + GROUP BY tw.id + ), + tw_aq1 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq2) AS tq2, SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=1 GROUP BY tw.id + ), + tw_aq2 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=2 GROUP BY tw.id + ), + tw_aq3 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=3 GROUP BY tw.id + ), + tw_aq4 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, SUM(b.spod) AS spod, + SUM(b.te) AS te, SUM(b.rev) AS rev + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=4 GROUP BY tw.id + ) + + -- ═══ Часть A: INPUT строки ═════════════════════════════════════════════ + SELECT * FROM ( + SELECT + 'INPUT'::VARCHAR, 3, f.lid::INT, + f.sc, f.ic, f.ng, COALESCE(f.bname, f.ename), f.vsp_id, f.vsp_addr, + -- plan + CASE WHEN s_plan THEN f.pq1 END, CASE WHEN s_plan THEN f.pq2 END, + CASE WHEN s_plan THEN f.pq3 END, CASE WHEN s_plan THEN f.pq4 END, + CASE WHEN s_plan THEN f.pq1+f.pq2+f.pq3+f.pq4 END, CASE WHEN s_plan THEN f.pcmt END, + -- seq dfip + CASE WHEN s_seq_d THEN f.dq1 END, CASE WHEN s_seq_d THEN f.dq2 END, + CASE WHEN s_seq_d THEN f.dq3 END, CASE WHEN s_seq_d THEN f.dq4 END, + CASE WHEN s_seq_d THEN f.dq1+f.dq2+f.dq3+f.dq4 END, CASE WHEN s_seq_d THEN f.djust END, + -- seq ssp_go + CASE WHEN s_seq_s THEN f.gq1 END, CASE WHEN s_seq_s THEN f.gq2 END, + CASE WHEN s_seq_s THEN f.gq3 END, CASE WHEN s_seq_s THEN f.gq4 END, + CASE WHEN s_seq_s THEN f.gq1+f.gq2+f.gq3+f.gq4 END, CASE WHEN s_seq_s THEN f.gjust END, + -- approved + CASE WHEN s_appr THEN f.ap1 END, CASE WHEN s_appr THEN f.ap2 END, + CASE WHEN s_appr THEN f.ap3 END, CASE WHEN s_appr THEN f.ap4 END, + CASE WHEN s_appr THEN f.ap1+f.ap2+f.ap3+f.ap4 END, + -- contract + CASE WHEN s_cd THEN f.cd_cp END, CASE WHEN s_cd THEN f.cd_ref END, + CASE WHEN s_cd THEN f.cd_dt END, CASE WHEN s_cd THEN f.cd_subj END, + CASE WHEN s_cd THEN f.cd_cur END, CASE WHEN s_cd THEN f.cd_ceil END, + CASE WHEN s_cd THEN f.cd_vat END, CASE WHEN s_cd THEN f.cd_dl END, + CASE WHEN s_cd THEN f.cd_sch END, CASE WHEN s_cd THEN f.cd_act END, + CASE WHEN s_cd THEN f.cd_cmt END, + -- booking 2026 + CASE WHEN s_book THEN f.bk1 END, CASE WHEN s_book THEN f.bk2 END, + CASE WHEN s_book THEN f.bk3 END, CASE WHEN s_book THEN f.bk4 END, + -- booking 2027 + CASE WHEN s_book THEN f.bn1 END, CASE WHEN s_book THEN f.bn2 END, + CASE WHEN s_book THEN f.bn3 END, CASE WHEN s_book THEN f.bn4 END, + -- Q1 + CASE WHEN s_q1 THEN f.q1_ac END, CASE WHEN s_q1 THEN f.q1_as END, CASE WHEN s_q1 THEN f.q1_arv END, + CASE WHEN s_q1 THEN f.q1_acmt END, CASE WHEN s_q1 THEN f.cp1 END, + CASE WHEN s_q1 THEN f.q1_pd END, CASE WHEN s_q1 THEN f.q1_pa END, + CASE WHEN s_q1 THEN f.q1_pcmt END, + CASE WHEN s_q1 THEN f.q1_pact END, CASE WHEN s_q1 THEN f.q1_book END, + CASE WHEN s_q1 THEN f.q1_m1 END, CASE WHEN s_q1 THEN f.q1_m2 END, CASE WHEN s_q1 THEN f.q1_m3 END, + CASE WHEN s_q1 THEN f.aq1 END, + CASE WHEN s_q1 THEN f.cp1 - COALESCE(f.q1_book,0) END, -- residual_after_booking + CASE WHEN s_q1 THEN f.cp1 - f.aq1 END, -- residual_after_actual + CASE WHEN s_q1 THEN f.q1_tq2 END, + CASE WHEN s_q1 THEN f.q1_tda END, CASE WHEN s_q1 THEN f.q1_tdp END, CASE WHEN s_q1 THEN f.q1_terf END, + CASE WHEN s_q1 THEN f.q1_tnc END, + CASE WHEN s_q1 THEN f.q1_tq3 END, CASE WHEN s_q1 THEN f.q1_tq4 END, + CASE WHEN s_q1 THEN f.q1_tfc END, + CASE WHEN s_q1 THEN f.q1_te END, + -- total = SUM transfer_q2_reasons + transfer_q3 + transfer_q4 + transfer_econ (Excel CC5) + CASE WHEN s_q1 THEN + COALESCE(f.q1_tda,0)+COALESCE(f.q1_tdp,0)+COALESCE(f.q1_terf,0) + +COALESCE(f.q1_tq3,0)+COALESCE(f.q1_tq4,0)+COALESCE(f.q1_te,0) + END, + -- Q2 + CASE WHEN s_q2 THEN f.q2_tc END, CASE WHEN s_q2 THEN f.q2_bc END, CASE WHEN s_q2 THEN f.q2_bcc END, + CASE WHEN s_q2 THEN f.q2_rinc END, CASE WHEN s_q2 THEN f.q2_rseq END, + CASE WHEN s_q2 THEN f.q2_rcmt END, + CASE WHEN s_q2 THEN f.np2 END, + CASE WHEN s_q2 THEN f.q2_ac END, CASE WHEN s_q2 THEN f.q2_as END, CASE WHEN s_q2 THEN f.q2_arv END, + CASE WHEN s_q2 THEN f.q2_acmt END, + CASE WHEN s_q2 THEN f.cp2 END, + CASE WHEN s_q2 THEN f.q2_pd END, CASE WHEN s_q2 THEN f.q2_pa END, + CASE WHEN s_q2 THEN f.q2_pcmt END, CASE WHEN s_q2 THEN f.q2_pact END, + CASE WHEN s_q2 THEN f.q2_book END, + CASE WHEN s_q2 THEN f.q2_m1 END, CASE WHEN s_q2 THEN f.q2_m2 END, CASE WHEN s_q2 THEN f.q2_m3 END, + CASE WHEN s_q2 THEN f.aq2 END, + CASE WHEN s_q2 THEN f.cp2 - COALESCE(f.q2_book,0) END, + CASE WHEN s_q2 THEN f.cp2 - f.aq2 END, + CASE WHEN s_q2 THEN f.q2_tq3 END, + CASE WHEN s_q2 THEN f.q2_tda END, CASE WHEN s_q2 THEN f.q2_tdp END, CASE WHEN s_q2 THEN f.q2_terf END, + CASE WHEN s_q2 THEN f.q2_tnc END, + CASE WHEN s_q2 THEN f.q2_tq4 END, + CASE WHEN s_q2 THEN f.q2_tfc END, + CASE WHEN s_q2 THEN f.q2_te END, + -- total = SUM transfer_q3_reasons + transfer_q4 + transfer_econ (Excel DN5) + CASE WHEN s_q2 THEN + COALESCE(f.q2_tda,0)+COALESCE(f.q2_tdp,0)+COALESCE(f.q2_terf,0) + +COALESCE(f.q2_tq4,0)+COALESCE(f.q2_te,0) + END, + -- Q3 + CASE WHEN s_q3 THEN f.q3_tc END, CASE WHEN s_q3 THEN f.q3_bc END, + CASE WHEN s_q3 THEN f.q3_bcc END, + CASE WHEN s_q3 THEN f.q3_rinc END, CASE WHEN s_q3 THEN f.q3_rseq END, + CASE WHEN s_q3 THEN f.q3_rcmt END, + CASE WHEN s_q3 THEN f.np3 END, + CASE WHEN s_q3 THEN f.q3_ac END, CASE WHEN s_q3 THEN f.q3_as END, CASE WHEN s_q3 THEN f.q3_arv END, + CASE WHEN s_q3 THEN f.q3_acmt END, + CASE WHEN s_q3 THEN f.cp3 END, + CASE WHEN s_q3 THEN f.q3_pd END, CASE WHEN s_q3 THEN f.q3_pa END, + CASE WHEN s_q3 THEN f.q3_pcmt END, CASE WHEN s_q3 THEN f.q3_pact END, + CASE WHEN s_q3 THEN f.q3_book END, + CASE WHEN s_q3 THEN f.q3_m1 END, CASE WHEN s_q3 THEN f.q3_m2 END, CASE WHEN s_q3 THEN f.q3_m3 END, + CASE WHEN s_q3 THEN f.aq3 END, + CASE WHEN s_q3 THEN f.cp3 - COALESCE(f.q3_book,0) END, + CASE WHEN s_q3 THEN f.cp3 - f.aq3 END, + CASE WHEN s_q3 THEN f.q3_tq4 END, + CASE WHEN s_q3 THEN f.q3_tda END, CASE WHEN s_q3 THEN f.q3_tdp END, CASE WHEN s_q3 THEN f.q3_terf END, + CASE WHEN s_q3 THEN f.q3_tnc END, + CASE WHEN s_q3 THEN f.q3_te END, + -- total = SUM transfer_q4_reasons + transfer_econ (Excel EW5) + CASE WHEN s_q3 THEN + COALESCE(f.q3_tda,0)+COALESCE(f.q3_tdp,0)+COALESCE(f.q3_terf,0) + +COALESCE(f.q3_te,0) + END, + -- Q4 + CASE WHEN s_q4 THEN f.q4_tc END, CASE WHEN s_q4 THEN f.q4_bc END, + CASE WHEN s_q4 THEN f.q4_bcc END, + CASE WHEN s_q4 THEN f.q4_rinc END, CASE WHEN s_q4 THEN f.q4_rseq END, + CASE WHEN s_q4 THEN f.q4_rcmt END, + CASE WHEN s_q4 THEN f.np4 END, + CASE WHEN s_q4 THEN f.q4_ac END, CASE WHEN s_q4 THEN f.q4_as END, CASE WHEN s_q4 THEN f.q4_arv END, + CASE WHEN s_q4 THEN f.q4_acmt END, + CASE WHEN s_q4 THEN f.cp4 END, + CASE WHEN s_q4 THEN f.q4_pd END, CASE WHEN s_q4 THEN f.q4_pa END, + CASE WHEN s_q4 THEN f.q4_pcmt END, CASE WHEN s_q4 THEN f.q4_pact END, + CASE WHEN s_q4 THEN f.q4_book END, + CASE WHEN s_q4 THEN f.q4_m1 END, CASE WHEN s_q4 THEN f.q4_m2 END, CASE WHEN s_q4 THEN f.q4_m3 END, + CASE WHEN s_q4 THEN f.q4_spod END, + CASE WHEN s_q4 THEN f.aq4 END, + CASE WHEN s_q4 THEN f.cp4 - COALESCE(f.q4_book,0) END, + CASE WHEN s_q4 THEN f.cp4 - f.aq4 END, + CASE WHEN s_q4 THEN f.q4_te END, + -- q4.total = q4.transfer_econ (Q4 has no other outgoing transfers) + CASE WHEN s_q4 THEN f.q4_te END, + -- totals + CASE WHEN s_tot THEN f.aq1+f.aq2+f.aq3+f.aq4 END, + CASE WHEN s_tot THEN COALESCE(f.q1_pa,0)+COALESCE(f.q2_pa,0)+COALESCE(f.q3_pa,0)+COALESCE(f.q4_pa,0) END, + -- economy_year = q4.corrected_plan − q4.actual_quarter (Excel GA5 = FY5 = FK5 − FW5) + CASE WHEN s_tot THEN f.cp4 - f.aq4 END, + + f.tree_path || ARRAY[f.lid::INT] AS _sort + FROM final_input f + + UNION ALL + + -- ═══ Часть B: Иерархия ════════════════════════════════════════════════ + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' END::VARCHAR, + t.depth, NULL::INT, + CASE WHEN t.depth < 3 THEN t.item_id ELSE t.parent_item_id END, + CASE WHEN t.depth = 3 THEN t.item_id ELSE NULL END, + t.num_group_id, t.name, NULL::INT, NULL::VARCHAR, + -- plan + CASE WHEN s_plan THEN ta.sp1 END, CASE WHEN s_plan THEN ta.sp2 END, + CASE WHEN s_plan THEN ta.sp3 END, CASE WHEN s_plan THEN ta.sp4 END, + CASE WHEN s_plan THEN COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0) END, + NULL::VARCHAR, + -- seq dfip + CASE WHEN s_seq_d THEN ta.sd1 END, CASE WHEN s_seq_d THEN ta.sd2 END, + CASE WHEN s_seq_d THEN ta.sd3 END, CASE WHEN s_seq_d THEN ta.sd4 END, + CASE WHEN s_seq_d THEN COALESCE(ta.sd1,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sd4,0) END, + NULL::VARCHAR, + -- seq ssp_go + CASE WHEN s_seq_s THEN ta.sg1 END, CASE WHEN s_seq_s THEN ta.sg2 END, + CASE WHEN s_seq_s THEN ta.sg3 END, CASE WHEN s_seq_s THEN ta.sg4 END, + CASE WHEN s_seq_s THEN COALESCE(ta.sg1,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sg4,0) END, + NULL::VARCHAR, + -- approved + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0) + +COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0) + +COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0) + +COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END, + -- contract — нет агрегации (текстовые) + NULL::VARCHAR, NULL::VARCHAR, NULL::DATE, NULL::VARCHAR, + NULL::VARCHAR, NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + -- booking 2026 / 2027 + CASE WHEN s_book THEN ta.bk1 END, CASE WHEN s_book THEN ta.bk2 END, + CASE WHEN s_book THEN ta.bk3 END, CASE WHEN s_book THEN ta.bk4 END, + CASE WHEN s_book THEN ta.bn1 END, CASE WHEN s_book THEN ta.bn2 END, + CASE WHEN s_book THEN ta.bn3 END, CASE WHEN s_book THEN ta.bn4 END, + -- Q1 hierarchy: новые поля (pay_act, booking, residuals, transfer reasons, + -- transfer comments) на агрегатах NULL'им — их корректный rollup можно + -- добавить позже расширением aq_q. + CASE WHEN s_q1 THEN tb1.ac END, CASE WHEN s_q1 THEN tb1.as_v END, CASE WHEN s_q1 THEN tb1.arv END, + NULL::VARCHAR, + CASE WHEN s_q1 THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0) END, + NULL::DATE, + CASE WHEN s_q1 THEN tb1.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q1 THEN tb1.bk END, -- booking (rollup) + CASE WHEN s_q1 THEN tb1.m1 END, CASE WHEN s_q1 THEN tb1.m2 END, CASE WHEN s_q1 THEN tb1.m3 END, + CASE WHEN s_q1 THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) END, + -- residual_after_booking = q1.cp − Σ booking + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0)) + - COALESCE(tb1.bk,0) + END, + -- residual_after_actual = q1.cp − Σ actual + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0)) + - (COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)) + END, + CASE WHEN s_q1 THEN tb1.tq2 END, + CASE WHEN s_q1 THEN tb1.tda END, CASE WHEN s_q1 THEN tb1.tdp END, CASE WHEN s_q1 THEN tb1.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q1 THEN tb1.tq3 END, CASE WHEN s_q1 THEN tb1.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q1 THEN tb1.te END, + -- total = SUM transfer_q2_reasons + transfer_q3 + transfer_q4 + transfer_econ (Excel CC) + CASE WHEN s_q1 THEN + COALESCE(tb1.tda,0)+COALESCE(tb1.tdp,0)+COALESCE(tb1.terf,0) + +COALESCE(tb1.tq3,0)+COALESCE(tb1.tq4,0)+COALESCE(tb1.te,0) + END, + -- Q2 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, -- revision_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0) + END, + CASE WHEN s_q2 THEN tb2.ac END, CASE WHEN s_q2 THEN tb2.as_v END, CASE WHEN s_q2 THEN tb2.arv END, + NULL::VARCHAR, -- adj_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0) + END, + NULL::DATE, + CASE WHEN s_q2 THEN tb2.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q2 THEN tb2.bk END, -- booking (rollup) + CASE WHEN s_q2 THEN tb2.m1 END, CASE WHEN s_q2 THEN tb2.m2 END, CASE WHEN s_q2 THEN tb2.m3 END, + CASE WHEN s_q2 THEN COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) END, + -- residual_after_booking = q2.cp − Σ booking + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0)) + - COALESCE(tb2.bk,0) + END, + -- residual_after_actual = q2.cp − Σ actual + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0)) + - (COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)) + END, + CASE WHEN s_q2 THEN tb2.tq3 END, + CASE WHEN s_q2 THEN tb2.tda END, CASE WHEN s_q2 THEN tb2.tdp END, CASE WHEN s_q2 THEN tb2.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q2 THEN tb2.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q2 THEN tb2.te END, + -- total = SUM transfer_q3_reasons + transfer_q4 + transfer_econ (Excel DN) + CASE WHEN s_q2 THEN + COALESCE(tb2.tda,0)+COALESCE(tb2.tdp,0)+COALESCE(tb2.terf,0) + +COALESCE(tb2.tq4,0)+COALESCE(tb2.te,0) + END, + -- Q3 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0) + END, + CASE WHEN s_q3 THEN tb3.ac END, CASE WHEN s_q3 THEN tb3.as_v END, CASE WHEN s_q3 THEN tb3.arv END, + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0) + END, + NULL::DATE, + CASE WHEN s_q3 THEN tb3.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q3 THEN tb3.bk END, -- booking (rollup) + CASE WHEN s_q3 THEN tb3.m1 END, CASE WHEN s_q3 THEN tb3.m2 END, CASE WHEN s_q3 THEN tb3.m3 END, + CASE WHEN s_q3 THEN COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) END, + -- residual_after_booking = q3.cp − Σ booking + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0)) + - COALESCE(tb3.bk,0) + END, + -- residual_after_actual = q3.cp − Σ actual + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0)) + - (COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)) + END, + CASE WHEN s_q3 THEN tb3.tq4 END, + CASE WHEN s_q3 THEN tb3.tda END, CASE WHEN s_q3 THEN tb3.tdp END, CASE WHEN s_q3 THEN tb3.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q3 THEN tb3.te END, + -- total = SUM transfer_q4_reasons + transfer_econ (Excel EW) + CASE WHEN s_q3 THEN + COALESCE(tb3.tda,0)+COALESCE(tb3.tdp,0)+COALESCE(tb3.terf,0) + +COALESCE(tb3.te,0) + END, + -- Q4 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0) + END, + CASE WHEN s_q4 THEN tb4.ac END, CASE WHEN s_q4 THEN tb4.as_v END, CASE WHEN s_q4 THEN tb4.arv END, + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0) + END, + NULL::DATE, + CASE WHEN s_q4 THEN tb4.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q4 THEN tb4.bk END, -- booking (rollup) + CASE WHEN s_q4 THEN tb4.m1 END, CASE WHEN s_q4 THEN tb4.m2 END, CASE WHEN s_q4 THEN tb4.m3 END, + CASE WHEN s_q4 THEN tb4.spod END, + CASE WHEN s_q4 THEN COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, + -- residual_after_booking = q4.cp − Σ booking + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - COALESCE(tb4.bk,0) + END, + -- residual_after_actual = q4.cp − Σ actual (incl. spod) + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)) + END, + CASE WHEN s_q4 THEN tb4.te END, + -- q4.total = q4.transfer_econ (Q4 has no other outgoing transfers) + CASE WHEN s_q4 THEN tb4.te END, + -- totals + CASE WHEN s_tot THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) + +COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) + +COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) + +COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, + CASE WHEN s_tot THEN COALESCE(tb1.pa,0)+COALESCE(tb2.pa,0)+COALESCE(tb3.pa,0)+COALESCE(tb4.pa,0) END, + -- economy_year = q4.corrected_plan − q4.actual_quarter (Excel GA = FY = FK − FW) + CASE WHEN s_tot THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) + +COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0) + +COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)) + END, + + 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$ +; + +-- DROP FUNCTION v3.v_form2_sheet_jsonb(int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form2_sheet_jsonb(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, header jsonb, plan_data jsonb, seq_dfip_data jsonb, seq_ssp_data jsonb, approved_data jsonb, contract_data jsonb, booking_data jsonb, q1_data jsonb, q2_data jsonb, q3_data jsonb, q4_data jsonb, totals_data jsonb, _sort_path integer[]) + LANGUAGE sql + STABLE +AS $function$ + WITH flags AS ( + SELECT + (p_sections IS NULL OR 'plan' = ANY(p_sections)) AS s_plan, + (p_sections IS NULL OR 'seq_dfip' = ANY(p_sections)) AS s_seq_d, + (p_sections IS NULL OR 'seq_ssp' = ANY(p_sections)) AS s_seq_s, + (p_sections IS NULL OR 'approved' = ANY(p_sections)) AS s_appr, + (p_sections IS NULL OR 'contract' = ANY(p_sections)) AS s_cd, + (p_sections IS NULL OR 'booking' = ANY(p_sections)) AS s_book, + (p_sections IS NULL OR 'q1' = ANY(p_sections)) AS s_q1, + (p_sections IS NULL OR 'q2' = ANY(p_sections)) AS s_q2, + (p_sections IS NULL OR 'q3' = ANY(p_sections)) AS s_q3, + (p_sections IS NULL OR 'q4' = ANY(p_sections)) AS s_q4, + (p_sections IS NULL OR 'totals' = ANY(p_sections)) AS s_tot + ) + SELECT + s.row_type, s.depth, s.line_id, + + -- header (всегда). expense_item_id из _sort_path (см. v_form1_jsonb). + jsonb_build_object( + 'section_code', s.col_section_code, + 'item_id', s.col_item_id, + 'num_group', s.col_num_group_id, + 'name', s.col_name, + 'vsp_id', s.col_vsp_id, + 'vsp_address', s.col_vsp_address, + 'expense_item_id', CASE WHEN s.row_type = 'INPUT' + THEN s._sort_path[array_upper(s._sort_path,1)-1] + ELSE s._sort_path[array_upper(s._sort_path,1)] END + ) AS header, + + CASE WHEN f.s_plan THEN jsonb_build_object( + 'q1', s.col_plan_q1, 'q2', s.col_plan_q2, + 'q3', s.col_plan_q3, 'q4', s.col_plan_q4, + 'year', s.col_plan_year, 'comment', s.col_plan_comment + ) END AS plan_data, + + CASE WHEN f.s_seq_d THEN jsonb_build_object( + 'q1', s.col_seq_dfip_q1, 'q2', s.col_seq_dfip_q2, + 'q3', s.col_seq_dfip_q3, 'q4', s.col_seq_dfip_q4, + 'year', s.col_seq_dfip_year, + 'justification', s.col_seq_dfip_just + ) END AS seq_dfip_data, + + CASE WHEN f.s_seq_s THEN jsonb_build_object( + 'q1', s.col_seq_ssp_q1, 'q2', s.col_seq_ssp_q2, + 'q3', s.col_seq_ssp_q3, 'q4', s.col_seq_ssp_q4, + 'year', s.col_seq_ssp_year, + 'justification', s.col_seq_ssp_just + ) END AS seq_ssp_data, + + CASE WHEN f.s_appr THEN jsonb_build_object( + 'q1', s.col_appr_q1, 'q2', s.col_appr_q2, + 'q3', s.col_appr_q3, 'q4', s.col_appr_q4, + 'year', s.col_appr_year + ) END AS approved_data, + + CASE WHEN f.s_cd THEN jsonb_build_object( + 'counterparty', s.col_cd_counterparty, + 'reference', s.col_cd_reference, + 'date', s.col_cd_date, + 'subject', s.col_cd_subject, + 'currency', s.col_cd_currency, + 'ceiling', s.col_cd_ceiling, + 'vat_rate', s.col_cd_vat_rate, + 'deadline', s.col_cd_deadline, + 'scheme', s.col_cd_scheme, + 'act', s.col_cd_act, + 'comment', s.col_cd_comment + ) END AS contract_data, + + CASE WHEN f.s_book THEN jsonb_build_object( + 'y2026', jsonb_build_object('q1', s.col_book_q1, 'q2', s.col_book_q2, 'q3', s.col_book_q3, 'q4', s.col_book_q4), + 'y2027', jsonb_build_object('q1', s.col_book_next_q1, 'q2', s.col_book_next_q2, 'q3', s.col_book_next_q3, 'q4', s.col_book_next_q4) + ) END AS booking_data, + + CASE WHEN f.s_q1 THEN jsonb_build_object( + 'adj_current', s.col_q1_adj_current, + 'adj_ssp', s.col_q1_adj_ssp, + 'adj_reserve', s.col_q1_adj_reserve, + 'adj_comment', s.col_q1_adj_comment, + 'corrected_plan', s.col_q1_corrected_plan, + 'pay_date', s.col_q1_pay_date, + 'pay_amount', s.col_q1_pay_amount, + 'pay_comment', s.col_q1_pay_comment, + 'pay_act', s.col_q1_pay_act, + 'booking', s.col_q1_booking, + 'actual_m1', s.col_q1_actual_m1, + 'actual_m2', s.col_q1_actual_m2, + 'actual_m3', s.col_q1_actual_m3, + 'actual_quarter', s.col_q1_actual_quarter, + 'residual_after_booking', s.col_q1_residual_after_booking, + 'residual_after_actual', s.col_q1_residual_after_actual, + 'transfer_q2', s.col_q1_transfer_q2, + 'transfer_q2_delay_acts', s.col_q1_transfer_q2_delay_acts, + 'transfer_q2_delay_procurement', s.col_q1_transfer_q2_delay_procurement, + 'transfer_q2_economy_rf', s.col_q1_transfer_q2_economy_rf, + 'transfer_next_comment', s.col_q1_transfer_next_comment, + 'transfer_q3', s.col_q1_transfer_q3, + 'transfer_q4', s.col_q1_transfer_q4, + 'transfer_far_comment', s.col_q1_transfer_far_comment, + 'transfer_econ', s.col_q1_transfer_econ, + 'total', s.col_q1_total + ) END AS q1_data, + + CASE WHEN f.s_q2 THEN jsonb_build_object( + 'target_change', s.col_q2_target_change, + 'base_correction', s.col_q2_base_correction, + 'base_correction_comment', s.col_q2_base_correction_comment, + 'revision_inc', s.col_q2_revision_inc, + 'revision_seq', s.col_q2_revision_seq, + 'revision_comment', s.col_q2_revision_comment, + 'new_plan', s.col_q2_new_plan, + 'adj_current', s.col_q2_adj_current, + 'adj_ssp', s.col_q2_adj_ssp, + 'adj_reserve', s.col_q2_adj_reserve, + 'adj_comment', s.col_q2_adj_comment, + 'corrected_plan', s.col_q2_corrected_plan, + 'pay_date', s.col_q2_pay_date, + 'pay_amount', s.col_q2_pay_amount, + 'pay_comment', s.col_q2_pay_comment, + 'pay_act', s.col_q2_pay_act, + 'booking', s.col_q2_booking, + 'actual_m1', s.col_q2_actual_m1, + 'actual_m2', s.col_q2_actual_m2, + 'actual_m3', s.col_q2_actual_m3, + 'actual_quarter', s.col_q2_actual_quarter, + 'residual_after_booking', s.col_q2_residual_after_booking, + 'residual_after_actual', s.col_q2_residual_after_actual, + 'transfer_q3', s.col_q2_transfer_q3, + 'transfer_q3_delay_acts', s.col_q2_transfer_q3_delay_acts, + 'transfer_q3_delay_procurement', s.col_q2_transfer_q3_delay_procurement, + 'transfer_q3_economy_rf', s.col_q2_transfer_q3_economy_rf, + 'transfer_next_comment', s.col_q2_transfer_next_comment, + 'transfer_q4', s.col_q2_transfer_q4, + 'transfer_far_comment', s.col_q2_transfer_far_comment, + 'transfer_econ', s.col_q2_transfer_econ, + 'total', s.col_q2_total + ) END AS q2_data, + + CASE WHEN f.s_q3 THEN jsonb_build_object( + 'target_change', s.col_q3_target_change, + 'base_correction', s.col_q3_base_correction, + 'base_correction_comment', s.col_q3_base_correction_comment, + 'revision_inc', s.col_q3_revision_inc, + 'revision_seq', s.col_q3_revision_seq, + 'revision_comment', s.col_q3_revision_comment, + 'new_plan', s.col_q3_new_plan, + 'adj_current', s.col_q3_adj_current, + 'adj_ssp', s.col_q3_adj_ssp, + 'adj_reserve', s.col_q3_adj_reserve, + 'adj_comment', s.col_q3_adj_comment, + 'corrected_plan', s.col_q3_corrected_plan, + 'pay_date', s.col_q3_pay_date, + 'pay_amount', s.col_q3_pay_amount, + 'pay_comment', s.col_q3_pay_comment, + 'pay_act', s.col_q3_pay_act, + 'booking', s.col_q3_booking, + 'actual_m1', s.col_q3_actual_m1, + 'actual_m2', s.col_q3_actual_m2, + 'actual_m3', s.col_q3_actual_m3, + 'actual_quarter', s.col_q3_actual_quarter, + 'residual_after_booking', s.col_q3_residual_after_booking, + 'residual_after_actual', s.col_q3_residual_after_actual, + 'transfer_q4', s.col_q3_transfer_q4, + 'transfer_q4_delay_acts', s.col_q3_transfer_q4_delay_acts, + 'transfer_q4_delay_procurement', s.col_q3_transfer_q4_delay_procurement, + 'transfer_q4_economy_rf', s.col_q3_transfer_q4_economy_rf, + 'transfer_next_comment', s.col_q3_transfer_next_comment, + 'transfer_econ', s.col_q3_transfer_econ, + 'total', s.col_q3_total + ) END AS q3_data, + + CASE WHEN f.s_q4 THEN jsonb_build_object( + 'target_change', s.col_q4_target_change, + 'base_correction', s.col_q4_base_correction, + 'base_correction_comment', s.col_q4_base_correction_comment, + 'revision_inc', s.col_q4_revision_inc, + 'revision_seq', s.col_q4_revision_seq, + 'revision_comment', s.col_q4_revision_comment, + 'new_plan', s.col_q4_new_plan, + 'adj_current', s.col_q4_adj_current, + 'adj_ssp', s.col_q4_adj_ssp, + 'adj_reserve', s.col_q4_adj_reserve, + 'adj_comment', s.col_q4_adj_comment, + 'corrected_plan', s.col_q4_corrected_plan, + 'pay_date', s.col_q4_pay_date, + 'pay_amount', s.col_q4_pay_amount, + 'pay_comment', s.col_q4_pay_comment, + 'pay_act', s.col_q4_pay_act, + 'booking', s.col_q4_booking, + 'actual_m1', s.col_q4_actual_m1, + 'actual_m2', s.col_q4_actual_m2, + 'actual_m3', s.col_q4_actual_m3, + 'actual_spod', s.col_q4_actual_spod, + 'actual_quarter', s.col_q4_actual_quarter, + 'residual_after_booking', s.col_q4_residual_after_booking, + 'residual_after_actual', s.col_q4_residual_after_actual, + 'transfer_econ', s.col_q4_transfer_econ, + 'total', s.col_q4_total + ) END AS q4_data, + + CASE WHEN f.s_tot THEN jsonb_build_object( + 'fact_year', s.col_fact_year, + 'pay_year', s.col_pay_year, + 'economy_year', s.col_economy_year + ) END AS totals_data, + + s._sort_path + FROM v3.v_form2_sheet_sections(p_form_id, p_sheet, p_sections) s + CROSS JOIN flags f + ORDER BY s._sort_path; +$function$ +; + +-- DROP FUNCTION v3.v_form2_smeta(int4); + +CREATE OR REPLACE FUNCTION v3.v_form2_smeta(p_form_id integer) + RETURNS TABLE(row_type character varying, depth integer, section_code character varying, name character varying, plan_supp_q1 numeric, plan_supp_q2 numeric, plan_supp_q3 numeric, plan_supp_q4 numeric, plan_supp_year numeric, plan_dev_q1 numeric, plan_dev_q2 numeric, plan_dev_q3 numeric, plan_dev_q4 numeric, plan_dev_year numeric, appr_supp_q1 numeric, appr_supp_q2 numeric, appr_supp_q3 numeric, appr_supp_q4 numeric, appr_supp_year numeric, appr_dev_q1 numeric, appr_dev_q2 numeric, appr_dev_q3 numeric, appr_dev_q4 numeric, appr_dev_year numeric, fact_supp_q1 numeric, fact_supp_q2 numeric, fact_supp_q3 numeric, fact_supp_q4 numeric, fact_supp_year numeric, fact_dev_q1 numeric, fact_dev_q2 numeric, fact_dev_q3 numeric, fact_dev_q4 numeric, fact_dev_year numeric, corr_supp_q2 numeric, corr_supp_q3 numeric, corr_supp_q4 numeric, corr_dev_q2 numeric, corr_dev_q3 numeric, corr_dev_q4 numeric) + LANGUAGE plpgsql + STABLE +AS $function$ +#variable_conflict use_column +BEGIN + RETURN QUERY + WITH + -- Per-line per-direction агрегаты квартальных данных + q_per_line AS ( + SELECT line_id, + SUM(CASE WHEN quarter=1 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0) END) AS fq1, + SUM(CASE WHEN quarter=2 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0) END) AS fq2, + SUM(CASE WHEN quarter=3 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0) END) AS fq3, + SUM(CASE WHEN quarter=4 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0)+COALESCE(actual_spod,0) END) AS fq4, + SUM(CASE WHEN quarter=1 THEN transfer_to_q2 END) AS t1q2, + SUM(CASE WHEN quarter=1 THEN transfer_to_q3 END) AS t1q3, + SUM(CASE WHEN quarter=1 THEN transfer_to_q4 END) AS t1q4, + SUM(CASE WHEN quarter=2 THEN transfer_to_q3 END) AS t2q3, + SUM(CASE WHEN quarter=2 THEN transfer_to_q4 END) AS t2q4, + SUM(CASE WHEN quarter=3 THEN transfer_to_q4 END) AS t3q4, + SUM(CASE WHEN quarter=1 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj1, + SUM(CASE WHEN quarter=2 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj2, + SUM(CASE WHEN quarter=3 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj3, + SUM(CASE WHEN quarter=4 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj4, + SUM(CASE WHEN quarter=2 THEN COALESCE(plan_revision_eco_change,0)+COALESCE(plan_revision_item_adj,0)+COALESCE(plan_revision_increase,0)+COALESCE(plan_revision_sequester,0) END) AS rev2, + SUM(CASE WHEN quarter=3 THEN COALESCE(plan_revision_eco_change,0)+COALESCE(plan_revision_item_adj,0)+COALESCE(plan_revision_increase,0)+COALESCE(plan_revision_sequester,0) END) AS rev3, + SUM(CASE WHEN quarter=4 THEN COALESCE(plan_revision_eco_change,0)+COALESCE(plan_revision_item_adj,0)+COALESCE(plan_revision_increase,0)+COALESCE(plan_revision_sequester,0) END) AS rev4 + FROM v3.budget_line_quarter GROUP BY line_id + ), + seq_dfip AS ( + SELECT line_id, SUM(adj_q1) AS s1, SUM(adj_q2) AS s2, SUM(adj_q3) AS s3, SUM(adj_q4) AS s4 + FROM v3.sequestration WHERE actor='DFIP' GROUP BY line_id + ), + seq_ssp AS ( + SELECT line_id, SUM(adj_q1) AS g1, SUM(adj_q2) AS g2, SUM(adj_q3) AS g3, SUM(adj_q4) AS g4 + FROM v3.sequestration WHERE actor='SSP_GO' GROUP BY line_id + ), + -- Per-line aggregate: учёт direction из expense_item + line_agg AS ( + SELECT + bl.expense_item_id AS eid, ei.direction AS dir, + SUM(p.plan_q1) AS pq1, SUM(p.plan_q2) AS pq2, SUM(p.plan_q3) AS pq3, SUM(p.plan_q4) AS pq4, + SUM(sd.s1) AS sd1, SUM(sd.s2) AS sd2, SUM(sd.s3) AS sd3, SUM(sd.s4) AS sd4, + SUM(sg.g1) AS sg1, SUM(sg.g2) AS sg2, SUM(sg.g3) AS sg3, SUM(sg.g4) AS sg4, + SUM(r.amount_q1) AS rq1, SUM(r.amount_q2) AS rq2, SUM(r.amount_q3) AS rq3, SUM(r.amount_q4) AS rq4, + SUM(qp.fq1) AS fq1, SUM(qp.fq2) AS fq2, SUM(qp.fq3) AS fq3, SUM(qp.fq4) AS fq4, + SUM(qp.t1q2) AS t1q2, SUM(qp.t1q3) AS t1q3, SUM(qp.t1q4) AS t1q4, + SUM(qp.t2q3) AS t2q3, SUM(qp.t2q4) AS t2q4, SUM(qp.t3q4) AS t3q4, + SUM(qp.adj1) AS adj1, SUM(qp.adj2) AS adj2, SUM(qp.adj3) AS adj3, SUM(qp.adj4) AS adj4, + SUM(qp.rev2) AS rev2, SUM(qp.rev3) AS rev3, SUM(qp.rev4) AS rev4 + 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 + LEFT JOIN seq_dfip sd ON sd.line_id = bl.id + LEFT JOIN seq_ssp sg ON sg.line_id = bl.id + LEFT JOIN v3.reserve r ON r.line_id = bl.id + LEFT JOIN q_per_line qp ON qp.line_id = bl.id + WHERE bl.budget_form_id = p_form_id + GROUP BY bl.expense_item_id, ei.direction + ), + -- Tree rollup с расщеплением по direction. Используем FILTER (WHERE) + tw_rollup AS ( + SELECT + t.id, t.depth, t.item_id AS section_code, t.name, + -- Поддержка + SUM(a.pq1) FILTER (WHERE a.dir='Support') AS s_pq1, + SUM(a.pq2) FILTER (WHERE a.dir='Support') AS s_pq2, + SUM(a.pq3) FILTER (WHERE a.dir='Support') AS s_pq3, + SUM(a.pq4) FILTER (WHERE a.dir='Support') AS s_pq4, + SUM(a.sd1) FILTER (WHERE a.dir='Support') AS s_sd1, + SUM(a.sd2) FILTER (WHERE a.dir='Support') AS s_sd2, + SUM(a.sd3) FILTER (WHERE a.dir='Support') AS s_sd3, + SUM(a.sd4) FILTER (WHERE a.dir='Support') AS s_sd4, + SUM(a.sg1) FILTER (WHERE a.dir='Support') AS s_sg1, + SUM(a.sg2) FILTER (WHERE a.dir='Support') AS s_sg2, + SUM(a.sg3) FILTER (WHERE a.dir='Support') AS s_sg3, + SUM(a.sg4) FILTER (WHERE a.dir='Support') AS s_sg4, + SUM(a.rq1) FILTER (WHERE a.dir='Support') AS s_rq1, + SUM(a.rq2) FILTER (WHERE a.dir='Support') AS s_rq2, + SUM(a.rq3) FILTER (WHERE a.dir='Support') AS s_rq3, + SUM(a.rq4) FILTER (WHERE a.dir='Support') AS s_rq4, + SUM(a.fq1) FILTER (WHERE a.dir='Support') AS s_fq1, + SUM(a.fq2) FILTER (WHERE a.dir='Support') AS s_fq2, + SUM(a.fq3) FILTER (WHERE a.dir='Support') AS s_fq3, + SUM(a.fq4) FILTER (WHERE a.dir='Support') AS s_fq4, + SUM(a.t1q2) FILTER (WHERE a.dir='Support') AS s_t1q2, + SUM(a.t1q3) FILTER (WHERE a.dir='Support') AS s_t1q3, + SUM(a.t1q4) FILTER (WHERE a.dir='Support') AS s_t1q4, + SUM(a.t2q3) FILTER (WHERE a.dir='Support') AS s_t2q3, + SUM(a.t2q4) FILTER (WHERE a.dir='Support') AS s_t2q4, + SUM(a.t3q4) FILTER (WHERE a.dir='Support') AS s_t3q4, + SUM(a.adj1) FILTER (WHERE a.dir='Support') AS s_adj1, + SUM(a.adj2) FILTER (WHERE a.dir='Support') AS s_adj2, + SUM(a.adj3) FILTER (WHERE a.dir='Support') AS s_adj3, + SUM(a.adj4) FILTER (WHERE a.dir='Support') AS s_adj4, + SUM(a.rev2) FILTER (WHERE a.dir='Support') AS s_rev2, + SUM(a.rev3) FILTER (WHERE a.dir='Support') AS s_rev3, + SUM(a.rev4) FILTER (WHERE a.dir='Support') AS s_rev4, + -- Развитие + SUM(a.pq1) FILTER (WHERE a.dir='Development') AS d_pq1, + SUM(a.pq2) FILTER (WHERE a.dir='Development') AS d_pq2, + SUM(a.pq3) FILTER (WHERE a.dir='Development') AS d_pq3, + SUM(a.pq4) FILTER (WHERE a.dir='Development') AS d_pq4, + SUM(a.sd1) FILTER (WHERE a.dir='Development') AS d_sd1, + SUM(a.sd2) FILTER (WHERE a.dir='Development') AS d_sd2, + SUM(a.sd3) FILTER (WHERE a.dir='Development') AS d_sd3, + SUM(a.sd4) FILTER (WHERE a.dir='Development') AS d_sd4, + SUM(a.sg1) FILTER (WHERE a.dir='Development') AS d_sg1, + SUM(a.sg2) FILTER (WHERE a.dir='Development') AS d_sg2, + SUM(a.sg3) FILTER (WHERE a.dir='Development') AS d_sg3, + SUM(a.sg4) FILTER (WHERE a.dir='Development') AS d_sg4, + SUM(a.rq1) FILTER (WHERE a.dir='Development') AS d_rq1, + SUM(a.rq2) FILTER (WHERE a.dir='Development') AS d_rq2, + SUM(a.rq3) FILTER (WHERE a.dir='Development') AS d_rq3, + SUM(a.rq4) FILTER (WHERE a.dir='Development') AS d_rq4, + SUM(a.fq1) FILTER (WHERE a.dir='Development') AS d_fq1, + SUM(a.fq2) FILTER (WHERE a.dir='Development') AS d_fq2, + SUM(a.fq3) FILTER (WHERE a.dir='Development') AS d_fq3, + SUM(a.fq4) FILTER (WHERE a.dir='Development') AS d_fq4, + SUM(a.t1q2) FILTER (WHERE a.dir='Development') AS d_t1q2, + SUM(a.t1q3) FILTER (WHERE a.dir='Development') AS d_t1q3, + SUM(a.t1q4) FILTER (WHERE a.dir='Development') AS d_t1q4, + SUM(a.t2q3) FILTER (WHERE a.dir='Development') AS d_t2q3, + SUM(a.t2q4) FILTER (WHERE a.dir='Development') AS d_t2q4, + SUM(a.t3q4) FILTER (WHERE a.dir='Development') AS d_t3q4, + SUM(a.adj1) FILTER (WHERE a.dir='Development') AS d_adj1, + SUM(a.adj2) FILTER (WHERE a.dir='Development') AS d_adj2, + SUM(a.adj3) FILTER (WHERE a.dir='Development') AS d_adj3, + SUM(a.adj4) FILTER (WHERE a.dir='Development') AS d_adj4, + SUM(a.rev2) FILTER (WHERE a.dir='Development') AS d_rev2, + SUM(a.rev3) FILTER (WHERE a.dir='Development') AS d_rev3, + SUM(a.rev4) FILTER (WHERE a.dir='Development') AS d_rev4 + FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id + JOIN v3.budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code + LEFT JOIN line_agg a ON a.eid = ANY(t.desc_ids) + WHERE t.depth <= 2 + GROUP BY t.id, t.depth, t.item_id, t.name + ) + SELECT + CASE r.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' END::VARCHAR, + r.depth, + r.section_code::VARCHAR, + r.name::VARCHAR, + -- Plan supp / dev + r.s_pq1, r.s_pq2, r.s_pq3, r.s_pq4, + COALESCE(r.s_pq1,0)+COALESCE(r.s_pq2,0)+COALESCE(r.s_pq3,0)+COALESCE(r.s_pq4,0), + r.d_pq1, r.d_pq2, r.d_pq3, r.d_pq4, + COALESCE(r.d_pq1,0)+COALESCE(r.d_pq2,0)+COALESCE(r.d_pq3,0)+COALESCE(r.d_pq4,0), + -- Approved supp = plan + seq_dfip + seq_ssp + reserve + COALESCE(r.s_pq1,0)+COALESCE(r.s_sd1,0)+COALESCE(r.s_sg1,0)+COALESCE(r.s_rq1,0), + COALESCE(r.s_pq2,0)+COALESCE(r.s_sd2,0)+COALESCE(r.s_sg2,0)+COALESCE(r.s_rq2,0), + COALESCE(r.s_pq3,0)+COALESCE(r.s_sd3,0)+COALESCE(r.s_sg3,0)+COALESCE(r.s_rq3,0), + COALESCE(r.s_pq4,0)+COALESCE(r.s_sd4,0)+COALESCE(r.s_sg4,0)+COALESCE(r.s_rq4,0), + COALESCE(r.s_pq1,0)+COALESCE(r.s_sd1,0)+COALESCE(r.s_sg1,0)+COALESCE(r.s_rq1,0) + +COALESCE(r.s_pq2,0)+COALESCE(r.s_sd2,0)+COALESCE(r.s_sg2,0)+COALESCE(r.s_rq2,0) + +COALESCE(r.s_pq3,0)+COALESCE(r.s_sd3,0)+COALESCE(r.s_sg3,0)+COALESCE(r.s_rq3,0) + +COALESCE(r.s_pq4,0)+COALESCE(r.s_sd4,0)+COALESCE(r.s_sg4,0)+COALESCE(r.s_rq4,0), + -- Approved dev + COALESCE(r.d_pq1,0)+COALESCE(r.d_sd1,0)+COALESCE(r.d_sg1,0)+COALESCE(r.d_rq1,0), + COALESCE(r.d_pq2,0)+COALESCE(r.d_sd2,0)+COALESCE(r.d_sg2,0)+COALESCE(r.d_rq2,0), + COALESCE(r.d_pq3,0)+COALESCE(r.d_sd3,0)+COALESCE(r.d_sg3,0)+COALESCE(r.d_rq3,0), + COALESCE(r.d_pq4,0)+COALESCE(r.d_sd4,0)+COALESCE(r.d_sg4,0)+COALESCE(r.d_rq4,0), + COALESCE(r.d_pq1,0)+COALESCE(r.d_sd1,0)+COALESCE(r.d_sg1,0)+COALESCE(r.d_rq1,0) + +COALESCE(r.d_pq2,0)+COALESCE(r.d_sd2,0)+COALESCE(r.d_sg2,0)+COALESCE(r.d_rq2,0) + +COALESCE(r.d_pq3,0)+COALESCE(r.d_sd3,0)+COALESCE(r.d_sg3,0)+COALESCE(r.d_rq3,0) + +COALESCE(r.d_pq4,0)+COALESCE(r.d_sd4,0)+COALESCE(r.d_sg4,0)+COALESCE(r.d_rq4,0), + -- Fact supp / dev + r.s_fq1, r.s_fq2, r.s_fq3, r.s_fq4, + COALESCE(r.s_fq1,0)+COALESCE(r.s_fq2,0)+COALESCE(r.s_fq3,0)+COALESCE(r.s_fq4,0), + r.d_fq1, r.d_fq2, r.d_fq3, r.d_fq4, + COALESCE(r.d_fq1,0)+COALESCE(r.d_fq2,0)+COALESCE(r.d_fq3,0)+COALESCE(r.d_fq4,0), + -- Corrected supp Q2..Q4 = approved_qN + transfers_into_qN + revisions + adj + COALESCE(r.s_pq2,0)+COALESCE(r.s_sd2,0)+COALESCE(r.s_sg2,0)+COALESCE(r.s_rq2,0) + +COALESCE(r.s_t1q2,0)+COALESCE(r.s_rev2,0)+COALESCE(r.s_adj2,0), + COALESCE(r.s_pq3,0)+COALESCE(r.s_sd3,0)+COALESCE(r.s_sg3,0)+COALESCE(r.s_rq3,0) + +COALESCE(r.s_t1q3,0)+COALESCE(r.s_t2q3,0)+COALESCE(r.s_rev3,0)+COALESCE(r.s_adj3,0), + COALESCE(r.s_pq4,0)+COALESCE(r.s_sd4,0)+COALESCE(r.s_sg4,0)+COALESCE(r.s_rq4,0) + +COALESCE(r.s_t1q4,0)+COALESCE(r.s_t2q4,0)+COALESCE(r.s_t3q4,0)+COALESCE(r.s_rev4,0)+COALESCE(r.s_adj4,0), + -- Corrected dev Q2..Q4 + COALESCE(r.d_pq2,0)+COALESCE(r.d_sd2,0)+COALESCE(r.d_sg2,0)+COALESCE(r.d_rq2,0) + +COALESCE(r.d_t1q2,0)+COALESCE(r.d_rev2,0)+COALESCE(r.d_adj2,0), + COALESCE(r.d_pq3,0)+COALESCE(r.d_sd3,0)+COALESCE(r.d_sg3,0)+COALESCE(r.d_rq3,0) + +COALESCE(r.d_t1q3,0)+COALESCE(r.d_t2q3,0)+COALESCE(r.d_rev3,0)+COALESCE(r.d_adj3,0), + COALESCE(r.d_pq4,0)+COALESCE(r.d_sd4,0)+COALESCE(r.d_sg4,0)+COALESCE(r.d_rq4,0) + +COALESCE(r.d_t1q4,0)+COALESCE(r.d_t2q4,0)+COALESCE(r.d_t3q4,0)+COALESCE(r.d_rev4,0)+COALESCE(r.d_adj4,0) + FROM tw_rollup r + ORDER BY r.section_code; +END; +$function$ +; + +-- DROP FUNCTION v3.v_form2_view(int4, varchar, _text); + +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 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; + + -- ═══ АХР_лимит ═════════════════════════════════════════════════════════ + -- 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$ +; + +-- DROP FUNCTION v3.v_form3_report_sections(int4, _text); + +CREATE OR REPLACE FUNCTION v3.v_form3_report_sections(p_report_id integer, 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_q1_adj_by_items numeric, col_q1_adj_increase numeric, col_q1_total_corr numeric, col_q1_m1 numeric, col_q1_m2 numeric, col_q1_m3 numeric, col_q1_quarter_actual numeric, col_q1_economy numeric, col_q2_carryover numeric, col_q2_adj_by_items numeric, col_q2_adj_increase numeric, col_q2_total_corr numeric, col_q2_m1 numeric, col_q2_m2 numeric, col_q2_m3 numeric, col_q2_quarter_actual numeric, col_q2_economy numeric, col_q3_carryover numeric, col_q3_adj_by_items numeric, col_q3_adj_increase numeric, col_q3_total_corr numeric, col_q3_m1 numeric, col_q3_m2 numeric, col_q3_m3 numeric, col_q3_quarter_actual numeric, col_q3_economy numeric, col_q4_carryover numeric, col_q4_adj_by_items numeric, col_q4_adj_increase numeric, col_q4_total_corr numeric, col_q4_m1 numeric, col_q4_m2 numeric, col_q4_m3 numeric, col_q4_spod numeric, col_q4_quarter_actual numeric, col_q4_economy numeric, col_year_total_corr numeric, col_year_total_actual numeric, col_year_economy numeric, _sort_path integer[]) + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + s_q1 BOOLEAN := p_sections IS NULL OR 'q1' = ANY(p_sections); + s_q2 BOOLEAN := p_sections IS NULL OR 'q2' = ANY(p_sections); + s_q3 BOOLEAN := p_sections IS NULL OR 'q3' = ANY(p_sections); + s_q4 BOOLEAN := p_sections IS NULL OR 'q4' = ANY(p_sections); + s_year BOOLEAN := p_sections IS NULL OR 'year' = ANY(p_sections); +BEGIN + IF NOT EXISTS (SELECT 1 FROM v3.rf_project_report WHERE id = p_report_id) THEN + RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id; + END IF; + + RETURN QUERY + WITH + -- Дерево expense_item, ограниченное FORM_3 (через junction) + tw AS ( + SELECT t.* FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft + ON eft.expense_item_id = t.id AND eft.form_type_code = 'FORM_3' + ), + -- INPUT строки: одна на v3.rf_project_report_line + page AS ( + SELECT l.id AS lid, l.expense_item_id AS eid, + t.parent_item_id AS sc, -- section-код родителя в дереве (1.01.1.) + ei.item_id AS ic, + ei.num_group_id AS ng, ei.name AS ename, + t.path AS tree_path + FROM v3.rf_project_report_line l + JOIN v3.expense_item ei ON ei.id = l.expense_item_id + JOIN tw t ON t.id = l.expense_item_id + WHERE l.rf_project_report_id = p_report_id + ), + -- Поквартальные данные + jq AS ( + SELECT q.rf_project_report_line_id AS lid, q.quarter, + q.adj_by_items, q.adj_increase, + q.actual_m1, q.actual_m2, q.actual_m3, q.actual_spod + FROM v3.rf_project_report_quarter q + JOIN v3.rf_project_report_line l ON l.id = q.rf_project_report_line_id + WHERE l.rf_project_report_id = p_report_id + ), + -- INPUT base: соединяем page + 4 квартала + input_base AS ( + SELECT + pg.lid, pg.eid, pg.sc, pg.ic, pg.ng, pg.ename, pg.tree_path, + q1.adj_by_items AS q1_abi, q1.adj_increase AS q1_ai, + q1.actual_m1 AS q1_m1, q1.actual_m2 AS q1_m2, q1.actual_m3 AS q1_m3, + q2.adj_by_items AS q2_abi, q2.adj_increase AS q2_ai, + q2.actual_m1 AS q2_m1, q2.actual_m2 AS q2_m2, q2.actual_m3 AS q2_m3, + q3.adj_by_items AS q3_abi, q3.adj_increase AS q3_ai, + q3.actual_m1 AS q3_m1, q3.actual_m2 AS q3_m2, q3.actual_m3 AS q3_m3, + q4.adj_by_items AS q4_abi, q4.adj_increase AS q4_ai, + q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, + q4.actual_spod AS q4_spod + FROM page pg + LEFT JOIN jq q1 ON q1.lid = pg.lid AND q1.quarter = 1 + LEFT JOIN jq q2 ON q2.lid = pg.lid AND q2.quarter = 2 + LEFT JOIN jq q3 ON q3.lid = pg.lid AND q3.quarter = 3 + LEFT JOIN jq q4 ON q4.lid = pg.lid AND q4.quarter = 4 + ), + -- Computed totals per row + enriched AS ( + SELECT b.*, + COALESCE(b.q1_abi,0) + COALESCE(b.q1_ai,0) AS q1_tc, + COALESCE(b.q1_m1,0) + COALESCE(b.q1_m2,0) + COALESCE(b.q1_m3,0) AS q1_qa, + COALESCE(b.q2_abi,0) + COALESCE(b.q2_ai,0) AS q2_tc, + COALESCE(b.q2_m1,0) + COALESCE(b.q2_m2,0) + COALESCE(b.q2_m3,0) AS q2_qa, + COALESCE(b.q3_abi,0) + COALESCE(b.q3_ai,0) AS q3_tc, + COALESCE(b.q3_m1,0) + COALESCE(b.q3_m2,0) + COALESCE(b.q3_m3,0) AS q3_qa, + COALESCE(b.q4_abi,0) + COALESCE(b.q4_ai,0) AS q4_tc, + COALESCE(b.q4_m1,0) + COALESCE(b.q4_m2,0) + COALESCE(b.q4_m3,0) + COALESCE(b.q4_spod,0) AS q4_qa + FROM input_base b + ), + -- Per-line aggregates for tree rollup + agg_q AS ( + SELECT l.expense_item_id AS eid, q.quarter, + SUM(q.adj_by_items) AS abi, SUM(q.adj_increase) AS ai, + SUM(q.actual_m1) AS m1, SUM(q.actual_m2) AS m2, SUM(q.actual_m3) AS m3, + SUM(q.actual_spod) AS spod + FROM v3.rf_project_report_line l + JOIN v3.rf_project_report_quarter q ON q.rf_project_report_line_id = l.id + WHERE l.rf_project_report_id = p_report_id + GROUP BY l.expense_item_id, q.quarter + ), + tw_aq1 AS ( + SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3 + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter = 1 + GROUP BY tw.id + ), + tw_aq2 AS ( + SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3 + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter = 2 + GROUP BY tw.id + ), + tw_aq3 AS ( + SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3 + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter = 3 + GROUP BY tw.id + ), + tw_aq4 AS ( + SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3, + SUM(a.spod) AS spod + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter = 4 + GROUP BY tw.id + ) + + -- ═══ Часть A: INPUT строки ════════════════════════════════════════════════ + SELECT * FROM ( + SELECT + 'INPUT'::VARCHAR AS row_type, 3 AS depth, e.lid::INT AS line_id, + e.sc, e.ic, e.ng, e.ename, + -- Q1 + CASE WHEN s_q1 THEN e.q1_abi END, + CASE WHEN s_q1 THEN e.q1_ai END, + CASE WHEN s_q1 THEN e.q1_tc END, + CASE WHEN s_q1 THEN e.q1_m1 END, + CASE WHEN s_q1 THEN e.q1_m2 END, + CASE WHEN s_q1 THEN e.q1_m3 END, + CASE WHEN s_q1 THEN e.q1_qa END, + CASE WHEN s_q1 THEN ROUND(e.q1_tc - e.q1_qa, 1) END, + -- Q2 (carryover пока NULL — нет столбца в _quarter; задел на будущее) + CASE WHEN s_q2 THEN NULL::NUMERIC END, + CASE WHEN s_q2 THEN e.q2_abi END, + CASE WHEN s_q2 THEN e.q2_ai END, + CASE WHEN s_q2 THEN e.q2_tc END, + CASE WHEN s_q2 THEN e.q2_m1 END, + CASE WHEN s_q2 THEN e.q2_m2 END, + CASE WHEN s_q2 THEN e.q2_m3 END, + CASE WHEN s_q2 THEN e.q2_qa END, + CASE WHEN s_q2 THEN ROUND(e.q2_tc - e.q2_qa, 1) END, + -- Q3 + CASE WHEN s_q3 THEN NULL::NUMERIC END, + CASE WHEN s_q3 THEN e.q3_abi END, + CASE WHEN s_q3 THEN e.q3_ai END, + CASE WHEN s_q3 THEN e.q3_tc END, + CASE WHEN s_q3 THEN e.q3_m1 END, + CASE WHEN s_q3 THEN e.q3_m2 END, + CASE WHEN s_q3 THEN e.q3_m3 END, + CASE WHEN s_q3 THEN e.q3_qa END, + CASE WHEN s_q3 THEN ROUND(e.q3_tc - e.q3_qa, 1) END, + -- Q4 + CASE WHEN s_q4 THEN NULL::NUMERIC END, + CASE WHEN s_q4 THEN e.q4_abi END, + CASE WHEN s_q4 THEN e.q4_ai END, + CASE WHEN s_q4 THEN e.q4_tc END, + CASE WHEN s_q4 THEN e.q4_m1 END, + CASE WHEN s_q4 THEN e.q4_m2 END, + CASE WHEN s_q4 THEN e.q4_m3 END, + CASE WHEN s_q4 THEN e.q4_spod END, + CASE WHEN s_q4 THEN e.q4_qa END, + CASE WHEN s_q4 THEN ROUND(e.q4_tc - e.q4_qa, 1) END, + -- year + CASE WHEN s_year THEN e.q1_tc + e.q2_tc + e.q3_tc + e.q4_tc END, + CASE WHEN s_year THEN e.q1_qa + e.q2_qa + e.q3_qa + e.q4_qa END, + CASE WHEN s_year THEN ROUND((e.q1_tc + e.q2_tc + e.q3_tc + e.q4_tc) - + (e.q1_qa + e.q2_qa + e.q3_qa + e.q4_qa), 1) END, + e.tree_path || ARRAY[e.lid::INT] AS _sort_path + FROM enriched e + + UNION ALL + + -- ═══ Часть B: Иерархия (ROOT / GROUP / ITEM / SUB_ITEM) ═════════════════ + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' + WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' END::VARCHAR, + t.depth, NULL::INT, + CASE WHEN t.depth < 3 THEN t.item_id ELSE t.parent_item_id END, + CASE WHEN t.depth = 3 THEN t.item_id ELSE NULL END, + t.num_group_id, t.name, + -- Q1 + CASE WHEN s_q1 THEN a1.abi END, + CASE WHEN s_q1 THEN a1.ai END, + CASE WHEN s_q1 THEN COALESCE(a1.abi,0)+COALESCE(a1.ai,0) END, + CASE WHEN s_q1 THEN a1.m1 END, + CASE WHEN s_q1 THEN a1.m2 END, + CASE WHEN s_q1 THEN a1.m3 END, + CASE WHEN s_q1 THEN COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0) END, + CASE WHEN s_q1 THEN ROUND((COALESCE(a1.abi,0)+COALESCE(a1.ai,0)) - + (COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0)), 1) END, + -- Q2 + CASE WHEN s_q2 THEN NULL::NUMERIC END, + CASE WHEN s_q2 THEN a2.abi END, + CASE WHEN s_q2 THEN a2.ai END, + CASE WHEN s_q2 THEN COALESCE(a2.abi,0)+COALESCE(a2.ai,0) END, + CASE WHEN s_q2 THEN a2.m1 END, + CASE WHEN s_q2 THEN a2.m2 END, + CASE WHEN s_q2 THEN a2.m3 END, + CASE WHEN s_q2 THEN COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0) END, + CASE WHEN s_q2 THEN ROUND((COALESCE(a2.abi,0)+COALESCE(a2.ai,0)) - + (COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0)), 1) END, + -- Q3 + CASE WHEN s_q3 THEN NULL::NUMERIC END, + CASE WHEN s_q3 THEN a3.abi END, + CASE WHEN s_q3 THEN a3.ai END, + CASE WHEN s_q3 THEN COALESCE(a3.abi,0)+COALESCE(a3.ai,0) END, + CASE WHEN s_q3 THEN a3.m1 END, + CASE WHEN s_q3 THEN a3.m2 END, + CASE WHEN s_q3 THEN a3.m3 END, + CASE WHEN s_q3 THEN COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0) END, + CASE WHEN s_q3 THEN ROUND((COALESCE(a3.abi,0)+COALESCE(a3.ai,0)) - + (COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0)), 1) END, + -- Q4 + CASE WHEN s_q4 THEN NULL::NUMERIC END, + CASE WHEN s_q4 THEN a4.abi END, + CASE WHEN s_q4 THEN a4.ai END, + CASE WHEN s_q4 THEN COALESCE(a4.abi,0)+COALESCE(a4.ai,0) END, + CASE WHEN s_q4 THEN a4.m1 END, + CASE WHEN s_q4 THEN a4.m2 END, + CASE WHEN s_q4 THEN a4.m3 END, + CASE WHEN s_q4 THEN a4.spod END, + CASE WHEN s_q4 THEN COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0) END, + CASE WHEN s_q4 THEN ROUND((COALESCE(a4.abi,0)+COALESCE(a4.ai,0)) - + (COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0)), 1) END, + -- year + CASE WHEN s_year THEN COALESCE(a1.abi,0)+COALESCE(a1.ai,0)+ + COALESCE(a2.abi,0)+COALESCE(a2.ai,0)+ + COALESCE(a3.abi,0)+COALESCE(a3.ai,0)+ + COALESCE(a4.abi,0)+COALESCE(a4.ai,0) END, + CASE WHEN s_year THEN COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0)+ + COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0)+ + COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0)+ + COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0) END, + CASE WHEN s_year THEN + ROUND( + (COALESCE(a1.abi,0)+COALESCE(a1.ai,0)+COALESCE(a2.abi,0)+COALESCE(a2.ai,0)+ + COALESCE(a3.abi,0)+COALESCE(a3.ai,0)+COALESCE(a4.abi,0)+COALESCE(a4.ai,0)) + - + (COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0)+ + COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0)+ + COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0)+ + COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0)), + 1) + END, + t.path + FROM tw t + LEFT JOIN tw_aq1 a1 ON a1.id = t.id + LEFT JOIN tw_aq2 a2 ON a2.id = t.id + LEFT JOIN tw_aq3 a3 ON a3.id = t.id + LEFT JOIN tw_aq4 a4 ON a4.id = t.id + ) sub + ORDER BY sub._sort_path; + +END; +$function$ +; + +-- DROP FUNCTION v3.v_form3_report_jsonb(int4, _text); + +CREATE OR REPLACE FUNCTION v3.v_form3_report_jsonb(p_report_id integer, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE sql + STABLE +AS $function$ + SELECT + s.row_type, + s.depth, + ROW_NUMBER() OVER (ORDER BY s._sort_path) AS sort_order, + jsonb_build_object( + 'line_id', s.line_id, + 'header', jsonb_build_object( + 'section_code', s.col_section_code, + 'item_id', s.col_item_id, + 'num_group_id', s.col_num_group_id, + 'name', s.col_name, + -- expense_item_id из _sort_path: для INPUT путь = {...ei_id, line_id}, + -- для иерархии = {...ei_id} → последний/предпоследний элемент. + 'expense_item_id', CASE WHEN s.row_type = 'INPUT' + THEN s._sort_path[array_upper(s._sort_path,1)-1] + ELSE s._sort_path[array_upper(s._sort_path,1)] END + ), + 'q1', jsonb_build_object( + 'adj_by_items', s.col_q1_adj_by_items, + 'adj_increase', s.col_q1_adj_increase, + 'total_corr', s.col_q1_total_corr, + 'm1', s.col_q1_m1, + 'm2', s.col_q1_m2, + 'm3', s.col_q1_m3, + 'quarter_actual', s.col_q1_quarter_actual, + 'economy', s.col_q1_economy + ), + 'q2', jsonb_build_object( + 'carryover', s.col_q2_carryover, + 'adj_by_items', s.col_q2_adj_by_items, + 'adj_increase', s.col_q2_adj_increase, + 'total_corr', s.col_q2_total_corr, + 'm1', s.col_q2_m1, + 'm2', s.col_q2_m2, + 'm3', s.col_q2_m3, + 'quarter_actual', s.col_q2_quarter_actual, + 'economy', s.col_q2_economy + ), + 'q3', jsonb_build_object( + 'carryover', s.col_q3_carryover, + 'adj_by_items', s.col_q3_adj_by_items, + 'adj_increase', s.col_q3_adj_increase, + 'total_corr', s.col_q3_total_corr, + 'm1', s.col_q3_m1, + 'm2', s.col_q3_m2, + 'm3', s.col_q3_m3, + 'quarter_actual', s.col_q3_quarter_actual, + 'economy', s.col_q3_economy + ), + 'q4', jsonb_build_object( + 'carryover', s.col_q4_carryover, + 'adj_by_items', s.col_q4_adj_by_items, + 'adj_increase', s.col_q4_adj_increase, + 'total_corr', s.col_q4_total_corr, + 'm1', s.col_q4_m1, + 'm2', s.col_q4_m2, + 'm3', s.col_q4_m3, + 'spod', s.col_q4_spod, + 'quarter_actual', s.col_q4_quarter_actual, + 'economy', s.col_q4_economy + ), + 'totals', jsonb_build_object( + 'total_corr', s.col_year_total_corr, + 'total_actual', s.col_year_total_actual, + 'economy', s.col_year_economy + ) + ) AS data + FROM v3.v_form3_report_sections(p_report_id, p_sections) s + ORDER BY s._sort_path; +$function$ +; + +-- DROP FUNCTION v3.v_form3_rf_rollup_sections(int4, int4, _text); + +CREATE OR REPLACE FUNCTION v3.v_form3_rf_rollup_sections(p_branch_id integer, p_year integer, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, col_section_code character varying, col_item_id character varying, col_num_group_id character varying, col_name character varying, col_q1_adj_by_items numeric, col_q1_adj_increase numeric, col_q1_total_corr numeric, col_q1_m1 numeric, col_q1_m2 numeric, col_q1_m3 numeric, col_q1_quarter_actual numeric, col_q1_economy numeric, col_q2_adj_by_items numeric, col_q2_adj_increase numeric, col_q2_total_corr numeric, col_q2_m1 numeric, col_q2_m2 numeric, col_q2_m3 numeric, col_q2_quarter_actual numeric, col_q2_economy numeric, col_q3_adj_by_items numeric, col_q3_adj_increase numeric, col_q3_total_corr numeric, col_q3_m1 numeric, col_q3_m2 numeric, col_q3_m3 numeric, col_q3_quarter_actual numeric, col_q3_economy numeric, col_q4_adj_by_items numeric, col_q4_adj_increase numeric, col_q4_total_corr numeric, col_q4_m1 numeric, col_q4_m2 numeric, col_q4_m3 numeric, col_q4_spod numeric, col_q4_quarter_actual numeric, col_q4_economy numeric, col_year_total_corr numeric, col_year_total_actual numeric, col_year_economy numeric, _sort_path integer[]) + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + s_q1 BOOLEAN := p_sections IS NULL OR 'q1' = ANY(p_sections); + s_q2 BOOLEAN := p_sections IS NULL OR 'q2' = ANY(p_sections); + s_q3 BOOLEAN := p_sections IS NULL OR 'q3' = ANY(p_sections); + s_q4 BOOLEAN := p_sections IS NULL OR 'q4' = ANY(p_sections); + s_year BOOLEAN := p_sections IS NULL OR 'year' = ANY(p_sections); +BEGIN + RETURN QUERY + WITH + tw AS ( + SELECT t.* FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft + ON eft.expense_item_id = t.id AND eft.form_type_code = 'FORM_3' + ), + -- Агрегаты per (expense_item_id, quarter) — sum по обоим трекам всех проектов РФ × год + agg_q AS ( + SELECT l.expense_item_id AS eid, q.quarter, + SUM(q.adj_by_items) AS abi, SUM(q.adj_increase) AS ai, + SUM(q.actual_m1) AS m1, SUM(q.actual_m2) AS m2, SUM(q.actual_m3) AS m3, + SUM(q.actual_spod) AS spod + FROM v3.rf_project_report r + JOIN v3.project p ON p.id = r.project_id + JOIN v3.rf_project_report_line l ON l.rf_project_report_id = r.id + JOIN v3.rf_project_report_quarter q ON q.rf_project_report_line_id = l.id + WHERE p.org_unit_id = p_branch_id AND r.year = p_year + GROUP BY l.expense_item_id, q.quarter + ), + tw_aq1 AS (SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3 + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter=1 + GROUP BY tw.id), + tw_aq2 AS (SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3 + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter=2 + GROUP BY tw.id), + tw_aq3 AS (SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3 + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter=3 + GROUP BY tw.id), + tw_aq4 AS (SELECT tw.id, SUM(a.abi) AS abi, SUM(a.ai) AS ai, + SUM(a.m1) AS m1, SUM(a.m2) AS m2, SUM(a.m3) AS m3, + SUM(a.spod) AS spod + FROM tw LEFT JOIN agg_q a ON a.eid = ANY(tw.desc_ids) AND a.quarter=4 + GROUP BY tw.id) + + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' + WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' END::VARCHAR, + t.depth, + CASE WHEN t.depth < 3 THEN t.item_id ELSE t.parent_item_id END, + CASE WHEN t.depth = 3 THEN t.item_id ELSE NULL END, + t.num_group_id, t.name, + -- Q1 + CASE WHEN s_q1 THEN a1.abi END, + CASE WHEN s_q1 THEN a1.ai END, + CASE WHEN s_q1 THEN COALESCE(a1.abi,0)+COALESCE(a1.ai,0) END, + CASE WHEN s_q1 THEN a1.m1 END, + CASE WHEN s_q1 THEN a1.m2 END, + CASE WHEN s_q1 THEN a1.m3 END, + CASE WHEN s_q1 THEN COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0) END, + CASE WHEN s_q1 THEN ROUND((COALESCE(a1.abi,0)+COALESCE(a1.ai,0)) + -(COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0)), 1) END, + -- Q2 + CASE WHEN s_q2 THEN a2.abi END, + CASE WHEN s_q2 THEN a2.ai END, + CASE WHEN s_q2 THEN COALESCE(a2.abi,0)+COALESCE(a2.ai,0) END, + CASE WHEN s_q2 THEN a2.m1 END, + CASE WHEN s_q2 THEN a2.m2 END, + CASE WHEN s_q2 THEN a2.m3 END, + CASE WHEN s_q2 THEN COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0) END, + CASE WHEN s_q2 THEN ROUND((COALESCE(a2.abi,0)+COALESCE(a2.ai,0)) + -(COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0)), 1) END, + -- Q3 + CASE WHEN s_q3 THEN a3.abi END, + CASE WHEN s_q3 THEN a3.ai END, + CASE WHEN s_q3 THEN COALESCE(a3.abi,0)+COALESCE(a3.ai,0) END, + CASE WHEN s_q3 THEN a3.m1 END, + CASE WHEN s_q3 THEN a3.m2 END, + CASE WHEN s_q3 THEN a3.m3 END, + CASE WHEN s_q3 THEN COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0) END, + CASE WHEN s_q3 THEN ROUND((COALESCE(a3.abi,0)+COALESCE(a3.ai,0)) + -(COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0)), 1) END, + -- Q4 + CASE WHEN s_q4 THEN a4.abi END, + CASE WHEN s_q4 THEN a4.ai END, + CASE WHEN s_q4 THEN COALESCE(a4.abi,0)+COALESCE(a4.ai,0) END, + CASE WHEN s_q4 THEN a4.m1 END, + CASE WHEN s_q4 THEN a4.m2 END, + CASE WHEN s_q4 THEN a4.m3 END, + CASE WHEN s_q4 THEN a4.spod END, + CASE WHEN s_q4 THEN COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0) END, + CASE WHEN s_q4 THEN ROUND((COALESCE(a4.abi,0)+COALESCE(a4.ai,0)) + -(COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0)), 1) END, + -- year + CASE WHEN s_year THEN COALESCE(a1.abi,0)+COALESCE(a1.ai,0)+COALESCE(a2.abi,0)+COALESCE(a2.ai,0)+ + COALESCE(a3.abi,0)+COALESCE(a3.ai,0)+COALESCE(a4.abi,0)+COALESCE(a4.ai,0) END, + CASE WHEN s_year THEN COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0)+ + COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0)+ + COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0)+ + COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0) END, + CASE WHEN s_year THEN + ROUND( + (COALESCE(a1.abi,0)+COALESCE(a1.ai,0)+COALESCE(a2.abi,0)+COALESCE(a2.ai,0)+ + COALESCE(a3.abi,0)+COALESCE(a3.ai,0)+COALESCE(a4.abi,0)+COALESCE(a4.ai,0)) + -(COALESCE(a1.m1,0)+COALESCE(a1.m2,0)+COALESCE(a1.m3,0)+ + COALESCE(a2.m1,0)+COALESCE(a2.m2,0)+COALESCE(a2.m3,0)+ + COALESCE(a3.m1,0)+COALESCE(a3.m2,0)+COALESCE(a3.m3,0)+ + COALESCE(a4.m1,0)+COALESCE(a4.m2,0)+COALESCE(a4.m3,0)+COALESCE(a4.spod,0)), + 1) + END, + t.path + FROM tw t + LEFT JOIN tw_aq1 a1 ON a1.id = t.id + LEFT JOIN tw_aq2 a2 ON a2.id = t.id + LEFT JOIN tw_aq3 a3 ON a3.id = t.id + LEFT JOIN tw_aq4 a4 ON a4.id = t.id + ORDER BY t.path; +END; +$function$ +; + +-- DROP FUNCTION v3.v_form3_rf_rollup_jsonb(int4, int4, _text); + +CREATE OR REPLACE FUNCTION v3.v_form3_rf_rollup_jsonb(p_branch_id integer, p_year integer, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE sql + STABLE +AS $function$ + SELECT + s.row_type, + s.depth, + ROW_NUMBER() OVER (ORDER BY s._sort_path) AS sort_order, + jsonb_build_object( + 'header', jsonb_build_object( + 'section_code', s.col_section_code, + 'item_id', s.col_item_id, + 'num_group_id', s.col_num_group_id, + 'name', s.col_name + ), + 'q1', jsonb_build_object( + 'adj_by_items', s.col_q1_adj_by_items, + 'adj_increase', s.col_q1_adj_increase, + 'total_corr', s.col_q1_total_corr, + 'm1', s.col_q1_m1, + 'm2', s.col_q1_m2, + 'm3', s.col_q1_m3, + 'quarter_actual', s.col_q1_quarter_actual, + 'economy', s.col_q1_economy + ), + 'q2', jsonb_build_object( + 'adj_by_items', s.col_q2_adj_by_items, + 'adj_increase', s.col_q2_adj_increase, + 'total_corr', s.col_q2_total_corr, + 'm1', s.col_q2_m1, + 'm2', s.col_q2_m2, + 'm3', s.col_q2_m3, + 'quarter_actual', s.col_q2_quarter_actual, + 'economy', s.col_q2_economy + ), + 'q3', jsonb_build_object( + 'adj_by_items', s.col_q3_adj_by_items, + 'adj_increase', s.col_q3_adj_increase, + 'total_corr', s.col_q3_total_corr, + 'm1', s.col_q3_m1, + 'm2', s.col_q3_m2, + 'm3', s.col_q3_m3, + 'quarter_actual', s.col_q3_quarter_actual, + 'economy', s.col_q3_economy + ), + 'q4', jsonb_build_object( + 'adj_by_items', s.col_q4_adj_by_items, + 'adj_increase', s.col_q4_adj_increase, + 'total_corr', s.col_q4_total_corr, + 'm1', s.col_q4_m1, + 'm2', s.col_q4_m2, + 'm3', s.col_q4_m3, + 'spod', s.col_q4_spod, + 'quarter_actual', s.col_q4_quarter_actual, + 'economy', s.col_q4_economy + ), + 'totals', jsonb_build_object( + 'total_corr', s.col_year_total_corr, + 'total_actual', s.col_year_total_actual, + 'economy', s.col_year_economy + ) + ) AS data + FROM v3.v_form3_rf_rollup_sections(p_branch_id, p_year, p_sections) s + ORDER BY s._sort_path; +$function$ +; + +-- DROP FUNCTION v3.v_form3_smeta(int4, int4); + +CREATE OR REPLACE FUNCTION v3.v_form3_smeta(p_branch_id integer, p_year integer) + RETURNS TABLE(row_type character varying, section_code character varying, name character varying, total_corr numeric, total_actual numeric, economy numeric) + LANGUAGE sql + STABLE +AS $function$ + -- Top-level sections (ROOT) + общая итоговая + SELECT row_type, col_section_code, col_name, + col_year_total_corr, col_year_total_actual, col_year_economy + FROM v3.v_form3_rf_rollup_sections(p_branch_id, p_year, ARRAY['year']) + WHERE depth = 0 + + UNION ALL + + -- Итог: «Всего расходов по смете (п.1+п.3)» + SELECT 'TOTAL'::VARCHAR, NULL::VARCHAR, + 'Всего расходов по смете (п.1+п.3)'::VARCHAR, + SUM(col_year_total_corr), + SUM(col_year_total_actual), + ROUND(SUM(col_year_total_corr) - SUM(col_year_total_actual), 1) + FROM v3.v_form3_rf_rollup_sections(p_branch_id, p_year, ARRAY['year']) + WHERE depth = 0; +$function$ +; + +-- DROP FUNCTION v3.v_form4_sheet_sections(int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form4_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_justification character varying, col_internal_order 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_appr_q1 numeric, col_appr_q2 numeric, col_appr_q3 numeric, col_appr_q4 numeric, col_appr_year numeric, col_cs_amount numeric, col_cs_reference character varying, 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_cs_centralized_flag character varying, col_al_contract_ref character varying, col_al_purpose character varying, col_rsv_q1 numeric, col_rsv_q2 numeric, col_rsv_q3 numeric, col_rsv_q4 numeric, col_rsv_year numeric, col_rsv_just character varying, col_col_amount numeric, col_col_protocol character varying, col_col_note character varying, col_ckk_ceiling numeric, col_ckk_q1 numeric, col_ckk_q2 numeric, col_ckk_q3 numeric, col_ckk_q4 numeric, col_ckk_rf_schedule character varying, col_ckk_delivery_deadline character varying, col_ckk_procurement_plan character varying, col_ckk_procurement_method character varying, col_ckk_comment character varying, col_cd_counterparty character varying, col_cd_reference character varying, col_cd_addenda character varying, col_cd_date date, col_cd_subject character varying, col_cd_currency character varying, col_cd_ceiling numeric, col_cd_q1 numeric, col_cd_q2 numeric, col_cd_q3 numeric, col_cd_q4 numeric, col_cd_rf_schedule character varying, col_cd_vat_rate character varying, col_cd_exchange_rate numeric, col_cd_amount_foreign numeric, 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_rf 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_ho numeric, col_q1_pay_rf 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_rf 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_ho numeric, col_q2_pay_rf 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_rf 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_ho numeric, col_q3_pay_rf 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_rf 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_ho numeric, col_q4_pay_rf 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, _sort_path integer[]) + LANGUAGE plpgsql + STABLE +AS $function$ +#variable_conflict use_column +DECLARE + s_plan BOOL; s_seq_d BOOL; s_appr BOOL; + s_cd BOOL; s_book BOOL; + s_q1 BOOL; s_q2 BOOL; s_q3 BOOL; s_q4 BOOL; s_tot BOOL; + s_cs BOOL; s_al BOOL; s_rsv BOOL; s_col BOOL; s_ckk 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_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_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_cs := p_sections IS NULL OR 'contract_summary' = ANY(p_sections); + s_al := p_sections IS NULL OR 'allocation' = ANY(p_sections); + s_rsv := p_sections IS NULL OR 'reserve' = ANY(p_sections); + s_col := p_sections IS NULL OR 'collegial' = ANY(p_sections); + s_ckk := p_sections IS NULL OR 'ckk' = ANY(p_sections); + s_need_ap := s_plan OR s_appr OR s_q1 OR s_q2 OR s_q3 OR s_q4; + + RETURN QUERY + WITH + tw AS ( + SELECT t.id, t.section_code, t.item_id, t.num_group_id, t.name, + t.depth, t.path, t.parent_id, t.parent_item_id, t.desc_ids + FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id + JOIN v3.budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code + WHERE t.sheet = p_sheet + ), + page AS ( + SELECT bl.id AS lid, bl.expense_item_id AS eid, bl.name AS bname, bl.vsp_id + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + ), + -- Conditional joins (gated by section flags) + jp AS (SELECT p.* FROM v3.plan p JOIN page pg ON p.line_id = pg.lid WHERE s_need_ap), + jsd AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='DFIP'), + 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), + jcol AS (SELECT c.* FROM v3.collegial_approval c JOIN page pg ON c.line_id = pg.lid WHERE s_col), + jq1 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q1 AND q.quarter=1), + jq2 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q2 AND q.quarter=2), + jq3 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q3 AND q.quarter=3), + jq4 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE (s_q4 OR s_tot) AND q.quarter=4), + + -- INPUT строки: enriched данные + input_base AS ( + SELECT + pg.lid, pg.eid, pg.bname, pg.vsp_id, + v.address AS vsp_addr, + bl_just.justification AS just, bl_just.internal_order AS io, + t.parent_item_id AS sc, t.item_id AS ic, t.num_group_id AS ng, t.name AS ename, t.path AS tree_path, + -- plan + COALESCE(p.plan_q1,0) AS pq1, COALESCE(p.plan_q2,0) AS pq2, + COALESCE(p.plan_q3,0) AS pq3, COALESCE(p.plan_q4,0) AS pq4, + p.comment AS pcmt, + -- seq dfip / ssp_go + COALESCE(sd.adj_q1,0) AS dq1, COALESCE(sd.adj_q2,0) AS dq2, + COALESCE(sd.adj_q3,0) AS dq3, COALESCE(sd.adj_q4,0) AS dq4, + sd.justification AS djust, + -- reserve + COALESCE(r.amount_q1,0) AS rq1, COALESCE(r.amount_q2,0) AS rq2, + COALESCE(r.amount_q3,0) AS rq3, COALESCE(r.amount_q4,0) AS rq4, + -- contract (расширено: q1-4, addenda, exchange_rate, amount_foreign) + cd.counterparty AS cd_cp, cd.reference AS cd_ref, cd.addenda AS cd_add, + cd.contract_date AS cd_dt, cd.subject AS cd_subj, cd.currency AS cd_cur, + cd.ceiling_amount AS cd_ceil, + cd.expenses_q1 AS cd_q1, cd.expenses_q2 AS cd_q2, cd.expenses_q3 AS cd_q3, cd.expenses_q4 AS cd_q4, + cd.rf_schedule AS cd_rfsch, + cd.vat_rate AS cd_vat, cd.exchange_rate AS cd_xr, cd.amount_foreign AS cd_af, + cd.deadline AS cd_dl, cd.payment_scheme AS cd_sch, + cd.act AS cd_act, cd.comment AS cd_cmt, + -- ckk (booking из expenses_q* + полный блок: ceiling, rf_schedule, …) + 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, + ck.ceiling_amount AS ck_ceil, ck.rf_schedule AS ck_rfsch, + ck.delivery_deadline AS ck_dd, ck.procurement_plan AS ck_pp, + ck.procurement_method AS ck_pm, ck.comment AS ck_cmt, + -- contract_summary (Действующий договор) + cs.total_amount AS cs_amt, cs.reference AS cs_ref, cs.counterparty AS cs_cp, + cs.deadline AS cs_dl, cs.comment AS cs_cmt, + cs.future_payments_y1 AS cs_y1, cs.future_payments_y2 AS cs_y2, + cs.other_ssp_amount AS cs_oss, cs.centralized_flag AS cs_cflag, + -- allocation + al.contract_ref AS al_ref, al.allocation_purpose AS al_purp, + -- collegial + col.approved_amount AS col_amt, col.protocol_reference AS col_pr, col.note AS col_note, + -- Q1 + q1.adj_current AS q1_ac, q1.adj_ssp AS q1_as, q1.adj_rf AS q1_arf, 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_amount_ho AS q1_pho, q1.payment_amount_rf AS q1_prf, q1.payment_comment AS q1_pcmt, + q1.payment_act AS q1_pact, q1.booking_amount AS q1_book, + q1.actual_m1 AS q1_m1, q1.actual_m2 AS q1_m2, q1.actual_m3 AS q1_m3, + q1.transfer_to_q2 AS q1_tq2, q1.transfer_to_q3 AS q1_tq3, q1.transfer_to_q4 AS q1_tq4, + q1.transfer_to_economy AS q1_te, + q1.transfer_delay_acts AS q1_tda, q1.transfer_delay_procurement AS q1_tdp, + q1.transfer_economy_rf AS q1_terf, + q1.transfer_next_comment AS q1_tnc, q1.transfer_far_comment AS q1_tfc, + -- Q2 + q2.target_change AS q2_tc, q2.base_plan_correction AS q2_bc, q2.base_plan_correction_comment AS q2_bcc, + q2.plan_revision_increase AS q2_rinc, q2.plan_revision_sequester AS q2_rseq, + q2.plan_revision_comment AS q2_rcmt, + q2.adj_current AS q2_ac, q2.adj_ssp AS q2_as, q2.adj_rf AS q2_arf, 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_amount_ho AS q2_pho, q2.payment_amount_rf AS q2_prf, + q2.payment_comment AS q2_pcmt, q2.payment_act AS q2_pact, + q2.booking_amount AS q2_book, + q2.actual_m1 AS q2_m1, q2.actual_m2 AS q2_m2, q2.actual_m3 AS q2_m3, + q2.transfer_to_q3 AS q2_tq3, q2.transfer_to_q4 AS q2_tq4, q2.transfer_to_economy AS q2_te, + q2.transfer_delay_acts AS q2_tda, q2.transfer_delay_procurement AS q2_tdp, + q2.transfer_economy_rf AS q2_terf, + q2.transfer_next_comment AS q2_tnc, q2.transfer_far_comment AS q2_tfc, + -- Q3 + q3.target_change AS q3_tc, q3.base_plan_correction AS q3_bc, + q3.base_plan_correction_comment AS q3_bcc, + q3.plan_revision_increase AS q3_rinc, q3.plan_revision_sequester AS q3_rseq, + q3.plan_revision_comment AS q3_rcmt, + q3.adj_current AS q3_ac, q3.adj_ssp AS q3_as, q3.adj_rf AS q3_arf, 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_amount_ho AS q3_pho, q3.payment_amount_rf AS q3_prf, + q3.payment_comment AS q3_pcmt, q3.payment_act AS q3_pact, + q3.booking_amount AS q3_book, + q3.actual_m1 AS q3_m1, q3.actual_m2 AS q3_m2, q3.actual_m3 AS q3_m3, + q3.transfer_to_q4 AS q3_tq4, q3.transfer_to_economy AS q3_te, + q3.transfer_delay_acts AS q3_tda, q3.transfer_delay_procurement AS q3_tdp, + q3.transfer_economy_rf AS q3_terf, q3.transfer_next_comment AS q3_tnc, + -- Q4 + q4.target_change AS q4_tc, q4.base_plan_correction AS q4_bc, + q4.base_plan_correction_comment AS q4_bcc, + q4.plan_revision_increase AS q4_rinc, q4.plan_revision_sequester AS q4_rseq, + q4.plan_revision_comment AS q4_rcmt, + q4.adj_current AS q4_ac, q4.adj_ssp AS q4_as, q4.adj_rf AS q4_arf, 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_amount_ho AS q4_pho, q4.payment_amount_rf AS q4_prf, + q4.payment_comment AS q4_pcmt, q4.payment_act AS q4_pact, + q4.booking_amount AS q4_book, + q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, q4.actual_spod AS q4_spod, + q4.transfer_to_economy AS q4_te, + -- approved per quarter + COALESCE(p.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(r.amount_q1,0) AS ap1, + COALESCE(p.plan_q2,0)+COALESCE(sd.adj_q2,0)+COALESCE(r.amount_q2,0) AS ap2, + COALESCE(p.plan_q3,0)+COALESCE(sd.adj_q3,0)+COALESCE(r.amount_q3,0) AS ap3, + COALESCE(p.plan_q4,0)+COALESCE(sd.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 v3.budget_line bl_just ON bl_just.id = pg.lid + LEFT JOIN jp p ON p.line_id = pg.lid + LEFT JOIN jsd sd ON sd.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 jcol col ON col.line_id = pg.lid + LEFT JOIN jq1 q1 ON q1.line_id = pg.lid + LEFT JOIN jq2 q2 ON q2.line_id = pg.lid + LEFT JOIN jq3 q3 ON q3.line_id = pg.lid + LEFT JOIN jq4 q4 ON q4.line_id = pg.lid + ), + -- Computed: corrected/new plans + -- Excel: CH10 (cp1) = AM10 + SUM(CB:CE) − CF10 + -- DJ10 (np2) = SUM(AN10, CY10, DH10, CF10, DE10:DG10) + -- В Excel CY и CF — две отдельные колонки (CY = «Закрытие 1-го квартала / Перенос + -- во 2 кв», CF = плановый «Перенос во 2 кв» внутри корректировок 1 кв). В DB + -- хранится одно поле q1.transfer_to_q2 — оно работает «за двоих»: вычитается + -- из cp1 (как CF) и прибавляется к np2 (как CY). Допущение: CY = CF. + enriched AS ( + SELECT b.*, + -- Q1 corrected_plan = approved + adj_* − transfer_to_q2 + b.ap1 + COALESCE(b.q1_ac,0)+COALESCE(b.q1_as,0)+COALESCE(b.q1_arf,0)+COALESCE(b.q1_arv,0) + - COALESCE(b.q1_tq2,0) AS cp1, + COALESCE(b.q1_m1,0)+COALESCE(b.q1_m2,0)+COALESCE(b.q1_m3,0) AS aq1, + -- Q2 new_plan = approved + transfer_q1 + revision_inc/seq + base_correction + target_change + b.ap2 + COALESCE(b.q1_tq2,0) + COALESCE(b.q2_rinc,0)+COALESCE(b.q2_rseq,0) + COALESCE(b.q2_bc,0)+COALESCE(b.q2_tc,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)+COALESCE(b.q3_tc,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)+COALESCE(b.q4_tc,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.*, + -- Q2 cp = new_plan + adj_* − transfer_to_q3 + e.np2 + COALESCE(e.q2_ac,0)+COALESCE(e.q2_as,0)+COALESCE(e.q2_arf,0)+COALESCE(e.q2_arv,0) + - COALESCE(e.q2_tq3,0) AS cp2, + -- Q3 cp = new_plan + adj_* − transfer_to_q4 + e.np3 + COALESCE(e.q3_ac,0)+COALESCE(e.q3_as,0)+COALESCE(e.q3_arf,0)+COALESCE(e.q3_arv,0) + - COALESCE(e.q3_tq4,0) AS cp3, + -- Q4 cp = new_plan + adj_* (нет дальнейшего переноса) + e.np4 + COALESCE(e.q4_ac,0)+COALESCE(e.q4_as,0)+COALESCE(e.q4_arf,0)+COALESCE(e.q4_arv,0) AS cp4 + FROM enriched e + ), + + -- Aggregates per expense_item (для иерархии) + agg AS ( + SELECT bl.expense_item_id AS eid, + SUM(p.plan_q1) AS sp1, SUM(p.plan_q2) AS sp2, SUM(p.plan_q3) AS sp3, SUM(p.plan_q4) AS sp4, + SUM(sd.adj_q1) AS sd1, SUM(sd.adj_q2) AS sd2, SUM(sd.adj_q3) AS sd3, SUM(sd.adj_q4) AS sd4, + SUM(r.amount_q1) AS sr1, SUM(r.amount_q2) AS sr2, SUM(r.amount_q3) AS sr3, SUM(r.amount_q4) AS sr4, + SUM(ck.expenses_q1) AS bk1, SUM(ck.expenses_q2) AS bk2, SUM(ck.expenses_q3) AS bk3, SUM(ck.expenses_q4) AS bk4, + SUM(ck.expenses_next_year_q1) AS bn1, SUM(ck.expenses_next_year_q2) AS bn2, + SUM(ck.expenses_next_year_q3) AS bn3, SUM(ck.expenses_next_year_q4) AS bn4 + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.plan p ON p.line_id = bl.id AND s_need_ap + LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor='DFIP' AND s_need_ap + LEFT JOIN v3.reserve r ON r.line_id = bl.id AND s_need_ap + LEFT JOIN v3.ckk ck ON ck.line_id = bl.id AND s_book + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (s_need_ap OR s_book) + GROUP BY bl.expense_item_id + ), + -- Per-quarter aggregates from blq + aq_q AS ( + SELECT bl.expense_item_id AS eid, q.quarter, + SUM(q.adj_current) AS ac, SUM(q.adj_ssp) AS as_v, SUM(q.adj_rf) AS arf, SUM(q.adj_reserve) AS arv, + SUM(q.payment_amount) AS pa, + SUM(q.payment_amount_ho) AS pho, SUM(q.payment_amount_rf) AS prf, + SUM(q.booking_amount) AS book, + 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, + -- Δ к approved для перехода к new_plan: revision_inc/seq + target_change + base_correction + -- (Excel: DJ10 = SUM(AN, CY, DH, CF, DE:DG)). Должно совпадать с INPUT-формулой np. + SUM(COALESCE(q.plan_revision_increase,0) + + COALESCE(q.plan_revision_sequester,0) + + COALESCE(q.target_change,0) + + COALESCE(q.base_plan_correction,0)) AS rev + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + JOIN v3.budget_line_quarter q ON q.line_id = bl.id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + GROUP BY bl.expense_item_id, q.quarter + ), + -- Tree-rollup + tw_agg AS ( + SELECT tw.id, + SUM(a.sp1) AS sp1, SUM(a.sp2) AS sp2, SUM(a.sp3) AS sp3, SUM(a.sp4) AS sp4, + SUM(a.sd1) AS sd1, SUM(a.sd2) AS sd2, SUM(a.sd3) AS sd3, SUM(a.sd4) AS sd4, + SUM(a.sr1) AS sr1, SUM(a.sr2) AS sr2, SUM(a.sr3) AS sr3, SUM(a.sr4) AS sr4, + SUM(a.bk1) AS bk1, SUM(a.bk2) AS bk2, SUM(a.bk3) AS bk3, SUM(a.bk4) AS bk4, + SUM(a.bn1) AS bn1, SUM(a.bn2) AS bn2, SUM(a.bn3) AS bn3, SUM(a.bn4) AS bn4 + FROM tw LEFT JOIN agg a ON a.eid = ANY(tw.desc_ids) + GROUP BY tw.id + ), + tw_aq1 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arf) AS arf, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.book) AS book, + 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 + 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.arf) AS arf, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.book) AS book, + 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 + 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.arf) AS arf, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.book) AS book, + 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 + 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.arf) AS arf, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.book) AS book, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, SUM(b.spod) AS spod, + SUM(b.te) AS te, SUM(b.rev) AS rev + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=4 GROUP BY tw.id + ) + + -- ═══ Часть A: INPUT строки ═════════════════════════════════════════════ + SELECT * FROM ( + SELECT + 'INPUT'::VARCHAR, 3, f.lid::INT, + f.sc, f.ic, f.ng, COALESCE(f.bname, f.ename), + f.just, f.io, f.vsp_id, f.vsp_addr, + -- plan + CASE WHEN s_plan THEN f.pq1 END, CASE WHEN s_plan THEN f.pq2 END, + CASE WHEN s_plan THEN f.pq3 END, CASE WHEN s_plan THEN f.pq4 END, + CASE WHEN s_plan THEN f.pq1+f.pq2+f.pq3+f.pq4 END, CASE WHEN s_plan THEN f.pcmt END, + -- seq dfip + CASE WHEN s_seq_d THEN f.dq1 END, CASE WHEN s_seq_d THEN f.dq2 END, + CASE WHEN s_seq_d THEN f.dq3 END, CASE WHEN s_seq_d THEN f.dq4 END, + CASE WHEN s_seq_d THEN f.dq1+f.dq2+f.dq3+f.dq4 END, CASE WHEN s_seq_d THEN f.djust END, + -- approved + CASE WHEN s_appr THEN f.ap1 END, CASE WHEN s_appr THEN f.ap2 END, + CASE WHEN s_appr THEN f.ap3 END, CASE WHEN s_appr THEN f.ap4 END, + CASE WHEN s_appr THEN f.ap1+f.ap2+f.ap3+f.ap4 END, + -- contract_summary (Действующий договор) + CASE WHEN s_cs THEN f.cs_amt END, CASE WHEN s_cs THEN f.cs_ref 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_y1 END, CASE WHEN s_cs THEN f.cs_y2 END, + CASE WHEN s_cs THEN f.cs_oss END, CASE WHEN s_cs THEN f.cs_cflag END, + -- allocation + CASE WHEN s_al THEN f.al_ref END, CASE WHEN s_al THEN f.al_purp END, + -- reserve (отдельный блок) + CASE WHEN s_rsv THEN f.rq1 END, CASE WHEN s_rsv THEN f.rq2 END, + CASE WHEN s_rsv THEN f.rq3 END, CASE WHEN s_rsv THEN f.rq4 END, + CASE WHEN s_rsv THEN f.rq1+f.rq2+f.rq3+f.rq4 END, NULL::VARCHAR, + -- collegial + CASE WHEN s_col THEN f.col_amt END, CASE WHEN s_col THEN f.col_pr END, + CASE WHEN s_col THEN f.col_note END, + -- ckk полный + CASE WHEN s_ckk THEN f.ck_ceil END, + CASE WHEN s_ckk THEN f.bk1 END, CASE WHEN s_ckk THEN f.bk2 END, + CASE WHEN s_ckk THEN f.bk3 END, CASE WHEN s_ckk THEN f.bk4 END, + CASE WHEN s_ckk THEN f.ck_rfsch END, CASE WHEN s_ckk THEN f.ck_dd END, + CASE WHEN s_ckk THEN f.ck_pp END, CASE WHEN s_ckk THEN f.ck_pm END, + CASE WHEN s_ckk THEN f.ck_cmt END, + -- contract (расширено) + CASE WHEN s_cd THEN f.cd_cp END, CASE WHEN s_cd THEN f.cd_ref END, + CASE WHEN s_cd THEN f.cd_add 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_q1 END, CASE WHEN s_cd THEN f.cd_q2 END, + CASE WHEN s_cd THEN f.cd_q3 END, CASE WHEN s_cd THEN f.cd_q4 END, + CASE WHEN s_cd THEN f.cd_rfsch END, + CASE WHEN s_cd THEN f.cd_vat END, + CASE WHEN s_cd THEN f.cd_xr END, CASE WHEN s_cd THEN f.cd_af END, + CASE WHEN s_cd THEN f.cd_dl END, + CASE WHEN s_cd THEN f.cd_sch END, CASE WHEN s_cd THEN f.cd_act END, + CASE WHEN s_cd THEN f.cd_cmt END, + -- booking 2026 + CASE WHEN s_book THEN f.bk1 END, CASE WHEN s_book THEN f.bk2 END, + CASE WHEN s_book THEN f.bk3 END, CASE WHEN s_book THEN f.bk4 END, + -- booking 2027 + CASE WHEN s_book THEN f.bn1 END, CASE WHEN s_book THEN f.bn2 END, + CASE WHEN s_book THEN f.bn3 END, CASE WHEN s_book THEN f.bn4 END, + -- Q1 + CASE WHEN s_q1 THEN f.q1_ac END, CASE WHEN s_q1 THEN f.q1_as END, + CASE WHEN s_q1 THEN f.q1_arf 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_pho END, CASE WHEN s_q1 THEN f.q1_prf END, CASE WHEN s_q1 THEN f.q1_pcmt END, + CASE WHEN s_q1 THEN f.q1_pact END, CASE WHEN s_q1 THEN f.q1_book END, + CASE WHEN s_q1 THEN f.q1_m1 END, CASE WHEN s_q1 THEN f.q1_m2 END, CASE WHEN s_q1 THEN f.q1_m3 END, + CASE WHEN s_q1 THEN f.aq1 END, + CASE WHEN s_q1 THEN f.cp1 - COALESCE(f.q1_book,0) END, -- residual_after_booking + CASE WHEN s_q1 THEN f.cp1 - f.aq1 END, -- residual_after_actual + CASE WHEN s_q1 THEN f.q1_tq2 END, + CASE WHEN s_q1 THEN f.q1_tda END, CASE WHEN s_q1 THEN f.q1_tdp END, CASE WHEN s_q1 THEN f.q1_terf END, + CASE WHEN s_q1 THEN f.q1_tnc END, + CASE WHEN s_q1 THEN f.q1_tq3 END, CASE WHEN s_q1 THEN f.q1_tq4 END, + CASE WHEN s_q1 THEN f.q1_tfc END, + CASE WHEN s_q1 THEN f.q1_te END, + -- total = "Закрытие квартала" = сумма transfer-колонок + CASE WHEN s_q1 THEN COALESCE(f.q1_tq2,0)+COALESCE(f.q1_tq3,0)+COALESCE(f.q1_tq4,0)+COALESCE(f.q1_te,0) END, + -- Q2 + CASE WHEN s_q2 THEN f.q2_tc END, CASE WHEN s_q2 THEN f.q2_bc END, CASE WHEN s_q2 THEN f.q2_bcc END, + CASE WHEN s_q2 THEN f.q2_rinc END, CASE WHEN s_q2 THEN f.q2_rseq END, + CASE WHEN s_q2 THEN f.q2_rcmt END, + CASE WHEN s_q2 THEN f.np2 END, + CASE WHEN s_q2 THEN f.q2_ac END, CASE WHEN s_q2 THEN f.q2_as END, + CASE WHEN s_q2 THEN f.q2_arf 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_pho END, CASE WHEN s_q2 THEN f.q2_prf 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_tq3,0)+COALESCE(f.q2_tq4,0)+COALESCE(f.q2_te,0) END, + -- Q3 + CASE WHEN s_q3 THEN f.q3_tc END, CASE WHEN s_q3 THEN f.q3_bc END, + CASE WHEN s_q3 THEN f.q3_bcc END, + CASE WHEN s_q3 THEN f.q3_rinc END, CASE WHEN s_q3 THEN f.q3_rseq END, + CASE WHEN s_q3 THEN f.q3_rcmt END, + CASE WHEN s_q3 THEN f.np3 END, + CASE WHEN s_q3 THEN f.q3_ac END, CASE WHEN s_q3 THEN f.q3_as END, + CASE WHEN s_q3 THEN f.q3_arf 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_pho END, CASE WHEN s_q3 THEN f.q3_prf 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_tq4,0)+COALESCE(f.q3_te,0) END, + -- Q4 + CASE WHEN s_q4 THEN f.q4_tc END, CASE WHEN s_q4 THEN f.q4_bc END, + CASE WHEN s_q4 THEN f.q4_bcc END, + CASE WHEN s_q4 THEN f.q4_rinc END, CASE WHEN s_q4 THEN f.q4_rseq END, + CASE WHEN s_q4 THEN f.q4_rcmt END, + CASE WHEN s_q4 THEN f.np4 END, + CASE WHEN s_q4 THEN f.q4_ac END, CASE WHEN s_q4 THEN f.q4_as END, + CASE WHEN s_q4 THEN f.q4_arf 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_pho END, CASE WHEN s_q4 THEN f.q4_prf 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 COALESCE(f.q4_te,0) END, + -- totals + CASE WHEN s_tot THEN f.aq1+f.aq2+f.aq3+f.aq4 END, -- fact_year + + f.tree_path || ARRAY[f.lid::INT] AS _sort + FROM final_input f + + UNION ALL + + -- ═══ Часть B: Иерархия ════════════════════════════════════════════════ + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' END::VARCHAR, + t.depth, NULL::INT, + CASE WHEN t.depth < 3 THEN t.item_id ELSE t.parent_item_id END, + CASE WHEN t.depth = 3 THEN t.item_id ELSE NULL END, + t.num_group_id, t.name, + NULL::VARCHAR, NULL::VARCHAR, -- justification, internal_order + NULL::INT, NULL::VARCHAR, + -- plan + CASE WHEN s_plan THEN ta.sp1 END, CASE WHEN s_plan THEN ta.sp2 END, + CASE WHEN s_plan THEN ta.sp3 END, CASE WHEN s_plan THEN ta.sp4 END, + CASE WHEN s_plan THEN COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0) END, + NULL::VARCHAR, + -- seq dfip + CASE WHEN s_seq_d THEN ta.sd1 END, CASE WHEN s_seq_d THEN ta.sd2 END, + CASE WHEN s_seq_d THEN ta.sd3 END, CASE WHEN s_seq_d THEN ta.sd4 END, + CASE WHEN s_seq_d THEN COALESCE(ta.sd1,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sd4,0) END, + NULL::VARCHAR, + -- approved + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sr1,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sr2,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sr3,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sr4,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sr1,0) + +COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sr2,0) + +COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sr3,0) + +COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sr4,0) END, + -- contract_summary (NULL на иерархии — текстовые) + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, + -- allocation + NULL::VARCHAR, NULL::VARCHAR, + -- reserve (q1-4 + year агрегаты, justification NULL) + CASE WHEN s_rsv THEN ta.sr1 END, CASE WHEN s_rsv THEN ta.sr2 END, + CASE WHEN s_rsv THEN ta.sr3 END, CASE WHEN s_rsv THEN ta.sr4 END, + CASE WHEN s_rsv THEN COALESCE(ta.sr1,0)+COALESCE(ta.sr2,0)+COALESCE(ta.sr3,0)+COALESCE(ta.sr4,0) END, + NULL::VARCHAR, + -- collegial + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, + -- ckk полный (q1-4 агрегаты есть в ta.bk*, остальное NULL) + NULL::NUMERIC, + CASE WHEN s_ckk THEN ta.bk1 END, CASE WHEN s_ckk THEN ta.bk2 END, + CASE WHEN s_ckk THEN ta.bk3 END, CASE WHEN s_ckk THEN ta.bk4 END, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + -- contract (расширено) — все NULL на иерархии + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + NULL::DATE, NULL::VARCHAR, + NULL::VARCHAR, NULL::NUMERIC, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, + NULL::VARCHAR, + NULL::VARCHAR, NULL::NUMERIC, NULL::NUMERIC, + NULL::VARCHAR, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + -- booking 2026 / 2027 + CASE WHEN s_book THEN ta.bk1 END, CASE WHEN s_book THEN ta.bk2 END, + CASE WHEN s_book THEN ta.bk3 END, CASE WHEN s_book THEN ta.bk4 END, + CASE WHEN s_book THEN ta.bn1 END, CASE WHEN s_book THEN ta.bn2 END, + CASE WHEN s_book THEN ta.bn3 END, CASE WHEN s_book THEN ta.bn4 END, + -- Q1 hierarchy + CASE WHEN s_q1 THEN tb1.ac END, CASE WHEN s_q1 THEN tb1.as_v END, + CASE WHEN s_q1 THEN tb1.arf END, + CASE WHEN s_q1 THEN tb1.arv END, + NULL::VARCHAR, + -- corrected_plan = approved + adj_* − transfer_to_q2 + CASE WHEN s_q1 THEN + COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sr1,0) + +COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arf,0)+COALESCE(tb1.arv,0) + -COALESCE(tb1.tq2,0) + END, + NULL::DATE, + CASE WHEN s_q1 THEN tb1.pa END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, + NULL::VARCHAR, -- pay_act + CASE WHEN s_q1 THEN tb1.book END, -- booking агрегат + CASE WHEN s_q1 THEN tb1.m1 END, CASE WHEN s_q1 THEN tb1.m2 END, CASE WHEN s_q1 THEN tb1.m3 END, + CASE WHEN s_q1 THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) END, + -- residual_after_booking = cp − booking + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sr1,0) + +COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arf,0)+COALESCE(tb1.arv,0) + -COALESCE(tb1.tq2,0)) + - COALESCE(tb1.book,0) + END, + -- residual_after_actual = cp − actual + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sr1,0) + +COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arf,0)+COALESCE(tb1.arv,0) + -COALESCE(tb1.tq2,0)) + - (COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)) + END, + CASE WHEN s_q1 THEN tb1.tq2 END, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- tq2 reasons + next comment + CASE WHEN s_q1 THEN tb1.tq3 END, CASE WHEN s_q1 THEN tb1.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q1 THEN tb1.te END, + -- total = "Закрытие квартала" = сумма transfer-колонок + CASE WHEN s_q1 THEN + COALESCE(tb1.tq2,0)+COALESCE(tb1.tq3,0)+COALESCE(tb1.tq4,0)+COALESCE(tb1.te,0) + END, + -- Q2 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, -- revision_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.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.arf END, + CASE WHEN s_q2 THEN tb2.arv END, + NULL::VARCHAR, -- adj_comment + -- corrected_plan = new_plan + adj_* − transfer_to_q3 + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sr2,0) + +COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0) + +COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arf,0)+COALESCE(tb2.arv,0) + -COALESCE(tb2.tq3,0) + END, + NULL::DATE, + CASE WHEN s_q2 THEN tb2.pa END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- pay_ho/pay_rf/pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q2 THEN tb2.book END, + CASE WHEN s_q2 THEN tb2.m1 END, CASE WHEN s_q2 THEN tb2.m2 END, CASE WHEN s_q2 THEN tb2.m3 END, + CASE WHEN s_q2 THEN COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) END, + -- residual_after_booking = cp − booking + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sr2,0) + +COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0) + +COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arf,0)+COALESCE(tb2.arv,0) + -COALESCE(tb2.tq3,0)) + - COALESCE(tb2.book,0) + END, + -- residual_after_actual = cp − actual + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sr2,0) + +COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0) + +COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arf,0)+COALESCE(tb2.arv,0) + -COALESCE(tb2.tq3,0)) + - (COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)) + END, + CASE WHEN s_q2 THEN tb2.tq3 END, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- tq3 reasons + next comment + CASE WHEN s_q2 THEN tb2.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q2 THEN tb2.te END, + -- total = сумма transfer-колонок q2 + CASE WHEN s_q2 THEN + COALESCE(tb2.tq3,0)+COALESCE(tb2.tq4,0)+COALESCE(tb2.te,0) + END, + -- Q3 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.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.arf END, + CASE WHEN s_q3 THEN tb3.arv END, + NULL::VARCHAR, + -- corrected_plan = new_plan + adj_* − transfer_to_q4 + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,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.arf,0)+COALESCE(tb3.arv,0) + -COALESCE(tb3.tq4,0) + END, + NULL::DATE, + CASE WHEN s_q3 THEN tb3.pa END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, + NULL::VARCHAR, + CASE WHEN s_q3 THEN tb3.book END, + CASE WHEN s_q3 THEN tb3.m1 END, CASE WHEN s_q3 THEN tb3.m2 END, CASE WHEN s_q3 THEN tb3.m3 END, + CASE WHEN s_q3 THEN COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) END, + -- residual_after_booking = cp − booking + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,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.arf,0)+COALESCE(tb3.arv,0) + -COALESCE(tb3.tq4,0)) + - COALESCE(tb3.book,0) + END, + -- residual_after_actual = cp − actual + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,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.arf,0)+COALESCE(tb3.arv,0) + -COALESCE(tb3.tq4,0)) + - (COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)) + END, + CASE WHEN s_q3 THEN tb3.tq4 END, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, + CASE WHEN s_q3 THEN tb3.te END, + -- total = сумма transfer-колонок q3 + CASE WHEN s_q3 THEN + COALESCE(tb3.tq4,0)+COALESCE(tb3.te,0) + END, + -- Q4 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.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.arf END, + CASE WHEN s_q4 THEN tb4.arv END, + NULL::VARCHAR, + -- corrected_plan = new_plan + adj_* (нет дальнейшего переноса) + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,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.arf,0)+COALESCE(tb4.arv,0) + END, + NULL::DATE, + CASE WHEN s_q4 THEN tb4.pa END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, + NULL::VARCHAR, + CASE WHEN s_q4 THEN tb4.book END, + CASE WHEN s_q4 THEN tb4.m1 END, CASE WHEN s_q4 THEN tb4.m2 END, CASE WHEN s_q4 THEN tb4.m3 END, + CASE WHEN s_q4 THEN tb4.spod END, + CASE WHEN s_q4 THEN COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, + -- residual_after_booking = cp − booking + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,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.arf,0)+COALESCE(tb4.arv,0)) + - COALESCE(tb4.book,0) + END, + -- residual_after_actual = cp − actual + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,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.arf,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, + -- total = "Закрытие" q4 = transfer_to_economy + CASE WHEN s_q4 THEN COALESCE(tb4.te,0) END, + -- totals + CASE WHEN s_tot THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) + +COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) + +COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) + +COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, -- fact_year + + 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$ +; + +-- DROP FUNCTION v3.v_form4_sheet_lines_jsonb(int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form4_sheet_lines_jsonb(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, header jsonb, plan_data jsonb, seq_dfip_data jsonb, approved_data jsonb, contract_summary_data jsonb, allocation_data jsonb, reserve_data jsonb, collegial_data jsonb, ckk_data jsonb, contract_data jsonb, booking_data jsonb, q1_data jsonb, q2_data jsonb, q3_data jsonb, q4_data jsonb, totals_data jsonb, _sort_path integer[]) + LANGUAGE sql + STABLE +AS $function$ + WITH flags AS ( + SELECT + (p_sections IS NULL OR 'plan' = ANY(p_sections)) AS s_plan, + (p_sections IS NULL OR 'seq_dfip' = ANY(p_sections)) AS s_seq_d, + (p_sections IS NULL OR 'approved' = ANY(p_sections)) AS s_appr, + (p_sections IS NULL OR 'contract_summary' = ANY(p_sections)) AS s_cs, + (p_sections IS NULL OR 'allocation' = ANY(p_sections)) AS s_al, + (p_sections IS NULL OR 'reserve' = ANY(p_sections)) AS s_rsv, + (p_sections IS NULL OR 'collegial' = ANY(p_sections)) AS s_col, + (p_sections IS NULL OR 'ckk' = ANY(p_sections)) AS s_ckk, + (p_sections IS NULL OR 'contract' = ANY(p_sections)) AS s_cd, + (p_sections IS NULL OR 'booking' = ANY(p_sections)) AS s_book, + (p_sections IS NULL OR 'q1' = ANY(p_sections)) AS s_q1, + (p_sections IS NULL OR 'q2' = ANY(p_sections)) AS s_q2, + (p_sections IS NULL OR 'q3' = ANY(p_sections)) AS s_q3, + (p_sections IS NULL OR 'q4' = ANY(p_sections)) AS s_q4, + (p_sections IS NULL OR 'totals' = ANY(p_sections)) AS s_tot + ) + SELECT + s.row_type, s.depth, s.line_id, + + -- header (всегда). expense_item_id из _sort_path (см. v_form1_jsonb). + jsonb_build_object( + 'section_code', s.col_section_code, + 'item_id', s.col_item_id, + 'num_group', s.col_num_group_id, + 'name', s.col_name, + 'justification', s.col_justification, + 'internal_order', s.col_internal_order, + 'vsp_id', s.col_vsp_id, + 'vsp_address', s.col_vsp_address, + 'expense_item_id', CASE WHEN s.row_type = 'INPUT' + THEN s._sort_path[array_upper(s._sort_path,1)-1] + ELSE s._sort_path[array_upper(s._sort_path,1)] END + ) AS header, + + CASE WHEN f.s_plan THEN jsonb_build_object( + 'q1', s.col_plan_q1, 'q2', s.col_plan_q2, + 'q3', s.col_plan_q3, 'q4', s.col_plan_q4, + 'year', s.col_plan_year, 'comment', s.col_plan_comment + ) END AS plan_data, + + CASE WHEN f.s_seq_d THEN jsonb_build_object( + 'q1', s.col_seq_dfip_q1, 'q2', s.col_seq_dfip_q2, + 'q3', s.col_seq_dfip_q3, 'q4', s.col_seq_dfip_q4, + 'year', s.col_seq_dfip_year, + 'justification', s.col_seq_dfip_just + ) END AS seq_dfip_data, + + CASE WHEN f.s_appr THEN jsonb_build_object( + 'q1', s.col_appr_q1, 'q2', s.col_appr_q2, + 'q3', s.col_appr_q3, 'q4', s.col_appr_q4, + 'year', s.col_appr_year + ) END AS approved_data, + + CASE WHEN f.s_cs THEN jsonb_build_object( + 'amount', s.col_cs_amount, + 'reference', s.col_cs_reference, + 'counterparty', s.col_cs_counterparty, + 'deadline', s.col_cs_deadline, + 'comment', s.col_cs_comment, + 'future_payments_y1', s.col_cs_future_y1, + 'future_payments_y2', s.col_cs_future_y2, + 'other_ssp_amount', s.col_cs_other_ssp, + 'centralized_flag', s.col_cs_centralized_flag + ) END AS contract_summary_data, + + CASE WHEN f.s_al THEN jsonb_build_object( + 'contract_ref', s.col_al_contract_ref, + 'allocation_purpose', s.col_al_purpose + ) END AS allocation_data, + + CASE WHEN f.s_rsv THEN jsonb_build_object( + 'q1', s.col_rsv_q1, 'q2', s.col_rsv_q2, + 'q3', s.col_rsv_q3, 'q4', s.col_rsv_q4, + 'year', s.col_rsv_year, + 'justification', s.col_rsv_just + ) END AS reserve_data, + + CASE WHEN f.s_col THEN jsonb_build_object( + 'amount', s.col_col_amount, + 'protocol_reference', s.col_col_protocol, + 'note', s.col_col_note + ) END AS collegial_data, + + CASE WHEN f.s_ckk THEN jsonb_build_object( + 'ceiling', s.col_ckk_ceiling, + 'expenses_q1', s.col_ckk_q1, + 'expenses_q2', s.col_ckk_q2, + 'expenses_q3', s.col_ckk_q3, + 'expenses_q4', s.col_ckk_q4, + 'rf_schedule', s.col_ckk_rf_schedule, + 'delivery_deadline', s.col_ckk_delivery_deadline, + 'procurement_plan', s.col_ckk_procurement_plan, + 'procurement_method', s.col_ckk_procurement_method, + 'comment', s.col_ckk_comment + ) END AS ckk_data, + + CASE WHEN f.s_cd THEN jsonb_build_object( + 'counterparty', s.col_cd_counterparty, + 'reference', s.col_cd_reference, + 'addenda', s.col_cd_addenda, + 'date', s.col_cd_date, + 'subject', s.col_cd_subject, + 'currency', s.col_cd_currency, + 'ceiling', s.col_cd_ceiling, + 'expenses_q1', s.col_cd_q1, + 'expenses_q2', s.col_cd_q2, + 'expenses_q3', s.col_cd_q3, + 'expenses_q4', s.col_cd_q4, + 'rf_schedule', s.col_cd_rf_schedule, + 'vat_rate', s.col_cd_vat_rate, + 'exchange_rate', s.col_cd_exchange_rate, + 'amount_foreign', s.col_cd_amount_foreign, + 'deadline', s.col_cd_deadline, + 'scheme', s.col_cd_scheme, + 'act', s.col_cd_act, + 'comment', s.col_cd_comment + ) END AS contract_data, + + CASE WHEN f.s_book THEN jsonb_build_object( + 'y2026', jsonb_build_object('q1', s.col_book_q1, 'q2', s.col_book_q2, 'q3', s.col_book_q3, 'q4', s.col_book_q4), + 'y2027', jsonb_build_object('q1', s.col_book_next_q1, 'q2', s.col_book_next_q2, 'q3', s.col_book_next_q3, 'q4', s.col_book_next_q4) + ) END AS booking_data, + + CASE WHEN f.s_q1 THEN jsonb_build_object( + 'adj_current', s.col_q1_adj_current, + 'adj_ssp', s.col_q1_adj_ssp, + 'adj_rf', s.col_q1_adj_rf, + 'adj_reserve', s.col_q1_adj_reserve, + 'adj_comment', s.col_q1_adj_comment, + 'corrected_plan', s.col_q1_corrected_plan, + 'pay_date', s.col_q1_pay_date, + 'pay_amount', s.col_q1_pay_amount, + 'pay_ho', s.col_q1_pay_ho, + 'pay_rf', s.col_q1_pay_rf, + 'pay_comment', s.col_q1_pay_comment, + 'pay_act', s.col_q1_pay_act, + 'booking', s.col_q1_booking, + 'actual_m1', s.col_q1_actual_m1, + 'actual_m2', s.col_q1_actual_m2, + 'actual_m3', s.col_q1_actual_m3, + 'actual_quarter', s.col_q1_actual_quarter, + 'residual_after_booking', s.col_q1_residual_after_booking, + 'residual_after_actual', s.col_q1_residual_after_actual, + 'transfer_q2', s.col_q1_transfer_q2, + 'transfer_q3', s.col_q1_transfer_q3, + 'transfer_q4', s.col_q1_transfer_q4, + 'transfer_econ', s.col_q1_transfer_econ, + 'total', s.col_q1_total + ) END AS q1_data, + + CASE WHEN f.s_q2 THEN jsonb_build_object( + 'target_change', s.col_q2_target_change, + 'base_correction', s.col_q2_base_correction, + 'base_correction_comment', s.col_q2_base_correction_comment, + 'revision_inc', s.col_q2_revision_inc, + 'revision_seq', s.col_q2_revision_seq, + 'revision_comment', s.col_q2_revision_comment, + 'new_plan', s.col_q2_new_plan, + 'adj_current', s.col_q2_adj_current, + 'adj_ssp', s.col_q2_adj_ssp, + 'adj_rf', s.col_q2_adj_rf, + 'adj_reserve', s.col_q2_adj_reserve, + 'adj_comment', s.col_q2_adj_comment, + 'corrected_plan', s.col_q2_corrected_plan, + 'pay_date', s.col_q2_pay_date, + 'pay_amount', s.col_q2_pay_amount, + 'pay_ho', s.col_q2_pay_ho, + 'pay_rf', s.col_q2_pay_rf, + 'pay_comment', s.col_q2_pay_comment, + 'pay_act', s.col_q2_pay_act, + 'booking', s.col_q2_booking, + 'actual_m1', s.col_q2_actual_m1, + 'actual_m2', s.col_q2_actual_m2, + 'actual_m3', s.col_q2_actual_m3, + 'actual_quarter', s.col_q2_actual_quarter, + 'residual_after_booking', s.col_q2_residual_after_booking, + 'residual_after_actual', s.col_q2_residual_after_actual, + 'transfer_q3', s.col_q2_transfer_q3, + 'transfer_q4', s.col_q2_transfer_q4, + 'transfer_econ', s.col_q2_transfer_econ, + 'total', s.col_q2_total + ) END AS q2_data, + + CASE WHEN f.s_q3 THEN jsonb_build_object( + 'target_change', s.col_q3_target_change, + 'base_correction', s.col_q3_base_correction, + 'base_correction_comment', s.col_q3_base_correction_comment, + 'revision_inc', s.col_q3_revision_inc, + 'revision_seq', s.col_q3_revision_seq, + 'revision_comment', s.col_q3_revision_comment, + 'new_plan', s.col_q3_new_plan, + 'adj_current', s.col_q3_adj_current, + 'adj_ssp', s.col_q3_adj_ssp, + 'adj_rf', s.col_q3_adj_rf, + 'adj_reserve', s.col_q3_adj_reserve, + 'adj_comment', s.col_q3_adj_comment, + 'corrected_plan', s.col_q3_corrected_plan, + 'pay_date', s.col_q3_pay_date, + 'pay_amount', s.col_q3_pay_amount, + 'pay_ho', s.col_q3_pay_ho, + 'pay_rf', s.col_q3_pay_rf, + 'pay_comment', s.col_q3_pay_comment, + 'pay_act', s.col_q3_pay_act, + 'booking', s.col_q3_booking, + 'actual_m1', s.col_q3_actual_m1, + 'actual_m2', s.col_q3_actual_m2, + 'actual_m3', s.col_q3_actual_m3, + 'actual_quarter', s.col_q3_actual_quarter, + 'residual_after_booking', s.col_q3_residual_after_booking, + 'residual_after_actual', s.col_q3_residual_after_actual, + 'transfer_q4', s.col_q3_transfer_q4, + 'transfer_econ', s.col_q3_transfer_econ, + 'total', s.col_q3_total + ) END AS q3_data, + + CASE WHEN f.s_q4 THEN jsonb_build_object( + 'target_change', s.col_q4_target_change, + 'base_correction', s.col_q4_base_correction, + 'base_correction_comment', s.col_q4_base_correction_comment, + 'revision_inc', s.col_q4_revision_inc, + 'revision_seq', s.col_q4_revision_seq, + 'revision_comment', s.col_q4_revision_comment, + 'new_plan', s.col_q4_new_plan, + 'adj_current', s.col_q4_adj_current, + 'adj_ssp', s.col_q4_adj_ssp, + 'adj_rf', s.col_q4_adj_rf, + 'adj_reserve', s.col_q4_adj_reserve, + 'adj_comment', s.col_q4_adj_comment, + 'corrected_plan', s.col_q4_corrected_plan, + 'pay_date', s.col_q4_pay_date, + 'pay_amount', s.col_q4_pay_amount, + 'pay_ho', s.col_q4_pay_ho, + 'pay_rf', s.col_q4_pay_rf, + 'pay_comment', s.col_q4_pay_comment, + 'pay_act', s.col_q4_pay_act, + 'booking', s.col_q4_booking, + 'actual_m1', s.col_q4_actual_m1, + 'actual_m2', s.col_q4_actual_m2, + 'actual_m3', s.col_q4_actual_m3, + 'actual_spod', s.col_q4_actual_spod, + 'actual_quarter', s.col_q4_actual_quarter, + 'residual_after_booking', s.col_q4_residual_after_booking, + 'residual_after_actual', s.col_q4_residual_after_actual, + 'transfer_econ', s.col_q4_transfer_econ, + 'total', s.col_q4_total + ) END AS q4_data, + + CASE WHEN f.s_tot THEN jsonb_build_object( + 'fact_year', s.col_fact_year + ) END AS totals_data, + + s._sort_path + FROM v3.v_form4_sheet_sections(p_form_id, p_sheet, p_sections) s + CROSS JOIN flags f + ORDER BY s._sort_path; +$function$ +; + +-- DROP FUNCTION v3.v_form4_sheet_jsonb(int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form4_sheet_jsonb(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE sql + STABLE +AS $function$ + -- 1. Базовые INPUT-чанки + project / section расширение + WITH inp_raw AS ( + SELECT j.* + FROM v3.v_form4_sheet_lines_jsonb(p_form_id, p_sheet, p_sections) j + WHERE j.row_type = 'INPUT' + ), + inp AS ( + SELECT + i.*, + bl.project_id AS line_project_id, + pp.id AS prj_id, pp.name AS prj_name, + pg.id AS prog_id, pg.name AS prog_name, + ei.id AS ei_id, ei.section_code, + -- ROOT: ищем depth=0 предка через recursive CTE inline + sec.id AS sec_id, sec.section_code AS sec_code, sec.name AS sec_name + FROM inp_raw i + JOIN v3.budget_line bl ON bl.id = i.line_id + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.project pp ON pp.id = bl.project_id AND pp.level = 'project' + LEFT JOIN v3.project pg ON pg.id = pp.parent_id AND pg.level = 'program' + LEFT JOIN LATERAL ( + WITH RECURSIVE up AS ( + SELECT id, parent_id, name, section_code, depth FROM v3.expense_item WHERE id = ei.id + UNION ALL + SELECT e.id, e.parent_id, e.name, e.section_code, e.depth + FROM v3.expense_item e JOIN up u ON u.parent_id = e.id + ) + SELECT id, name, section_code FROM up WHERE depth = 0 LIMIT 1 + ) sec ON TRUE + ), + -- 2. PROJECT агрегаты + prj AS ( + SELECT + sec_id, MAX(sec_code) AS sec_code, MAX(sec_name) AS sec_name, + prog_id, MAX(prog_name) AS prog_name, + prj_id, MAX(prj_name) AS prj_name, + v3.jsonb_sum(plan_data) AS plan_data, + v3.jsonb_sum(seq_dfip_data) AS seq_dfip_data, + v3.jsonb_sum(approved_data) AS approved_data, + v3.jsonb_sum(contract_summary_data) AS contract_summary_data, + v3.jsonb_sum(allocation_data) AS allocation_data, + v3.jsonb_sum(reserve_data) AS reserve_data, + v3.jsonb_sum(collegial_data) AS collegial_data, + v3.jsonb_sum(ckk_data) AS ckk_data, + v3.jsonb_sum(contract_data) AS contract_data, + v3.jsonb_sum(booking_data) AS booking_data, + v3.jsonb_sum(q1_data) AS q1_data, + v3.jsonb_sum(q2_data) AS q2_data, + v3.jsonb_sum(q3_data) AS q3_data, + v3.jsonb_sum(q4_data) AS q4_data, + v3.jsonb_sum(totals_data) AS totals_data + FROM inp WHERE prj_id IS NOT NULL + GROUP BY sec_id, prog_id, prj_id + ), + -- 3. PROGRAM агрегаты + prog AS ( + SELECT + sec_id, MAX(sec_code) AS sec_code, MAX(sec_name) AS sec_name, + prog_id, MAX(prog_name) AS prog_name, + v3.jsonb_sum(plan_data) AS plan_data, + v3.jsonb_sum(seq_dfip_data) AS seq_dfip_data, + v3.jsonb_sum(approved_data) AS approved_data, + v3.jsonb_sum(contract_summary_data) AS contract_summary_data, + v3.jsonb_sum(allocation_data) AS allocation_data, + v3.jsonb_sum(reserve_data) AS reserve_data, + v3.jsonb_sum(collegial_data) AS collegial_data, + v3.jsonb_sum(ckk_data) AS ckk_data, + v3.jsonb_sum(contract_data) AS contract_data, + v3.jsonb_sum(booking_data) AS booking_data, + v3.jsonb_sum(q1_data) AS q1_data, + v3.jsonb_sum(q2_data) AS q2_data, + v3.jsonb_sum(q3_data) AS q3_data, + v3.jsonb_sum(q4_data) AS q4_data, + v3.jsonb_sum(totals_data) AS totals_data + FROM inp WHERE prog_id IS NOT NULL + GROUP BY sec_id, prog_id + ), + -- 4. ROOT (section) агрегаты по всем строкам секции (с проектом и без) + root AS ( + SELECT + sec_id, MAX(sec_code) AS sec_code, MAX(sec_name) AS sec_name, + v3.jsonb_sum(plan_data) AS plan_data, + v3.jsonb_sum(seq_dfip_data) AS seq_dfip_data, + v3.jsonb_sum(approved_data) AS approved_data, + v3.jsonb_sum(contract_summary_data) AS contract_summary_data, + v3.jsonb_sum(allocation_data) AS allocation_data, + v3.jsonb_sum(reserve_data) AS reserve_data, + v3.jsonb_sum(collegial_data) AS collegial_data, + v3.jsonb_sum(ckk_data) AS ckk_data, + v3.jsonb_sum(contract_data) AS contract_data, + v3.jsonb_sum(booking_data) AS booking_data, + v3.jsonb_sum(q1_data) AS q1_data, + v3.jsonb_sum(q2_data) AS q2_data, + v3.jsonb_sum(q3_data) AS q3_data, + v3.jsonb_sum(q4_data) AS q4_data, + v3.jsonb_sum(totals_data) AS totals_data + FROM inp + GROUP BY sec_id + ), + -- 5. Все 4 уровня в едином формате (sort_path для упорядочения) + unioned AS ( + SELECT + 'ROOT'::VARCHAR AS row_type, 0 AS depth, NULL::INT AS line_id, + jsonb_build_object('section_code', sec_code, 'name', sec_name) AS header, + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, 0, 0, 0]::INT[] AS sort_path + FROM root + UNION ALL + SELECT + 'GROUP', 1, NULL, + jsonb_build_object('section_code', sec_code, 'name', prog_name, + 'program_id', prog_id), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, prog_id, 0, 0]::INT[] + FROM prog + UNION ALL + SELECT + 'ITEM', 2, NULL, + jsonb_build_object('section_code', sec_code, 'name', prj_name, + 'program_id', prog_id, 'project_id', prj_id), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, prog_id, prj_id, 0]::INT[] + FROM prj + UNION ALL + SELECT + 'INPUT', 3, line_id, + header || jsonb_build_object( + 'project_id', prj_id, 'project_name', prj_name, + 'program_id', prog_id, 'program_name', prog_name + ), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, COALESCE(prog_id, 999999), COALESCE(prj_id, 999999), line_id]::INT[] + FROM inp + ) + SELECT + u.row_type, u.depth, + ROW_NUMBER() OVER (ORDER BY u.sort_path) AS sort_order, + jsonb_build_object( + 'line_id', u.line_id, + 'header', u.header, + 'plan', u.plan_data, + 'seq_dfip', u.seq_dfip_data, + 'approved', u.approved_data, + 'contract_summary', u.contract_summary_data, + 'allocation', u.allocation_data, + 'reserve', u.reserve_data, + 'collegial', u.collegial_data, + 'ckk', u.ckk_data, + 'contract', u.contract_data, + 'booking', u.booking_data, + 'q1', u.q1_data, + 'q2', u.q2_data, + 'q3', u.q3_data, + 'q4', u.q4_data, + 'totals', u.totals_data + ) AS data + FROM unioned u + ORDER BY u.sort_path; +$function$ +; + +-- DROP FUNCTION v3.v_form4_smeta(int4); + +CREATE OR REPLACE FUNCTION v3.v_form4_smeta(p_form_id integer) + RETURNS TABLE(row_type character varying, depth integer, section_code character varying, name character varying, plan_supp_q1 numeric, plan_supp_q2 numeric, plan_supp_q3 numeric, plan_supp_q4 numeric, plan_supp_year numeric, plan_dev_q1 numeric, plan_dev_q2 numeric, plan_dev_q3 numeric, plan_dev_q4 numeric, plan_dev_year numeric, appr_supp_q1 numeric, appr_supp_q2 numeric, appr_supp_q3 numeric, appr_supp_q4 numeric, appr_supp_year numeric, appr_dev_q1 numeric, appr_dev_q2 numeric, appr_dev_q3 numeric, appr_dev_q4 numeric, appr_dev_year numeric, fact_supp_q1 numeric, fact_supp_q2 numeric, fact_supp_q3 numeric, fact_supp_q4 numeric, fact_supp_year numeric, fact_dev_q1 numeric, fact_dev_q2 numeric, fact_dev_q3 numeric, fact_dev_q4 numeric, fact_dev_year numeric, corr_supp_q1 numeric, corr_supp_q2 numeric, corr_supp_q3 numeric, corr_supp_q4 numeric, corr_supp_year numeric, corr_dev_q1 numeric, corr_dev_q2 numeric, corr_dev_q3 numeric, corr_dev_q4 numeric, corr_dev_year numeric) + LANGUAGE plpgsql + STABLE +AS $function$ +#variable_conflict use_column +BEGIN + RETURN QUERY + WITH + -- Per-line per-direction агрегаты квартальных данных + q_per_line AS ( + SELECT line_id, + SUM(CASE WHEN quarter=1 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0) END) AS fq1, + SUM(CASE WHEN quarter=2 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0) END) AS fq2, + SUM(CASE WHEN quarter=3 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0) END) AS fq3, + SUM(CASE WHEN quarter=4 THEN COALESCE(actual_m1,0)+COALESCE(actual_m2,0)+COALESCE(actual_m3,0)+COALESCE(actual_spod,0) END) AS fq4, + SUM(CASE WHEN quarter=1 THEN transfer_to_q2 END) AS t1q2, + SUM(CASE WHEN quarter=1 THEN transfer_to_q3 END) AS t1q3, + SUM(CASE WHEN quarter=1 THEN transfer_to_q4 END) AS t1q4, + SUM(CASE WHEN quarter=2 THEN transfer_to_q3 END) AS t2q3, + SUM(CASE WHEN quarter=2 THEN transfer_to_q4 END) AS t2q4, + SUM(CASE WHEN quarter=3 THEN transfer_to_q4 END) AS t3q4, + SUM(CASE WHEN quarter=1 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj1, + SUM(CASE WHEN quarter=2 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj2, + SUM(CASE WHEN quarter=3 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj3, + SUM(CASE WHEN quarter=4 THEN COALESCE(adj_current,0)+COALESCE(adj_ssp,0)+COALESCE(adj_reserve,0) END) AS adj4, + -- Δ approved → new_plan: revision_inc/seq + target_change + base_correction + -- (согласовано с v_form4_sheet_sections / Excel DJ10 = SUM(AN, CY, DH, CF, DE:DG)) + SUM(CASE WHEN quarter=2 THEN COALESCE(plan_revision_increase,0)+COALESCE(plan_revision_sequester,0)+COALESCE(target_change,0)+COALESCE(base_plan_correction,0) END) AS rev2, + SUM(CASE WHEN quarter=3 THEN COALESCE(plan_revision_increase,0)+COALESCE(plan_revision_sequester,0)+COALESCE(target_change,0)+COALESCE(base_plan_correction,0) END) AS rev3, + SUM(CASE WHEN quarter=4 THEN COALESCE(plan_revision_increase,0)+COALESCE(plan_revision_sequester,0)+COALESCE(target_change,0)+COALESCE(base_plan_correction,0) END) AS rev4 + FROM v3.budget_line_quarter GROUP BY line_id + ), + seq_dfip AS ( + SELECT line_id, SUM(adj_q1) AS s1, SUM(adj_q2) AS s2, SUM(adj_q3) AS s3, SUM(adj_q4) AS s4 + FROM v3.sequestration WHERE actor='DFIP' GROUP BY line_id + ), + seq_ssp AS ( + SELECT line_id, SUM(adj_q1) AS g1, SUM(adj_q2) AS g2, SUM(adj_q3) AS g3, SUM(adj_q4) AS g4 + FROM v3.sequestration WHERE actor='SSP_GO' GROUP BY line_id + ), + -- Per-line aggregate. + -- FORM_4 = "Расходы на развитие" (АХР_Р!F5='ПРОЕКТНАЯ ДЕЯТЕЛЬНОСТЬ' в Excel), + -- поэтому все строки идут в колонку Development независимо от + -- expense_item.direction (этот справочник общий с FORM_2 и для FORM_2 + -- проставлен 'Support'). Колонка Support по форме 4 всегда = 0. + line_agg AS ( + SELECT + bl.expense_item_id AS eid, 'Development'::VARCHAR AS dir, + SUM(p.plan_q1) AS pq1, SUM(p.plan_q2) AS pq2, SUM(p.plan_q3) AS pq3, SUM(p.plan_q4) AS pq4, + SUM(sd.s1) AS sd1, SUM(sd.s2) AS sd2, SUM(sd.s3) AS sd3, SUM(sd.s4) AS sd4, + SUM(sg.g1) AS sg1, SUM(sg.g2) AS sg2, SUM(sg.g3) AS sg3, SUM(sg.g4) AS sg4, + SUM(r.amount_q1) AS rq1, SUM(r.amount_q2) AS rq2, SUM(r.amount_q3) AS rq3, SUM(r.amount_q4) AS rq4, + SUM(qp.fq1) AS fq1, SUM(qp.fq2) AS fq2, SUM(qp.fq3) AS fq3, SUM(qp.fq4) AS fq4, + SUM(qp.t1q2) AS t1q2, SUM(qp.t1q3) AS t1q3, SUM(qp.t1q4) AS t1q4, + SUM(qp.t2q3) AS t2q3, SUM(qp.t2q4) AS t2q4, SUM(qp.t3q4) AS t3q4, + SUM(qp.adj1) AS adj1, SUM(qp.adj2) AS adj2, SUM(qp.adj3) AS adj3, SUM(qp.adj4) AS adj4, + SUM(qp.rev2) AS rev2, SUM(qp.rev3) AS rev3, SUM(qp.rev4) AS rev4 + FROM v3.budget_line bl + LEFT JOIN v3.plan p ON p.line_id = bl.id + LEFT JOIN seq_dfip sd ON sd.line_id = bl.id + LEFT JOIN seq_ssp sg ON sg.line_id = bl.id + LEFT JOIN v3.reserve r ON r.line_id = bl.id + LEFT JOIN q_per_line qp ON qp.line_id = bl.id + WHERE bl.budget_form_id = p_form_id + GROUP BY bl.expense_item_id + ), + -- Tree rollup с расщеплением по direction. Используем FILTER (WHERE) + tw_rollup AS ( + SELECT + t.id, t.depth, t.item_id AS section_code, t.name, + -- Поддержка + SUM(a.pq1) FILTER (WHERE a.dir='Support') AS s_pq1, + SUM(a.pq2) FILTER (WHERE a.dir='Support') AS s_pq2, + SUM(a.pq3) FILTER (WHERE a.dir='Support') AS s_pq3, + SUM(a.pq4) FILTER (WHERE a.dir='Support') AS s_pq4, + SUM(a.sd1) FILTER (WHERE a.dir='Support') AS s_sd1, + SUM(a.sd2) FILTER (WHERE a.dir='Support') AS s_sd2, + SUM(a.sd3) FILTER (WHERE a.dir='Support') AS s_sd3, + SUM(a.sd4) FILTER (WHERE a.dir='Support') AS s_sd4, + SUM(a.sg1) FILTER (WHERE a.dir='Support') AS s_sg1, + SUM(a.sg2) FILTER (WHERE a.dir='Support') AS s_sg2, + SUM(a.sg3) FILTER (WHERE a.dir='Support') AS s_sg3, + SUM(a.sg4) FILTER (WHERE a.dir='Support') AS s_sg4, + SUM(a.rq1) FILTER (WHERE a.dir='Support') AS s_rq1, + SUM(a.rq2) FILTER (WHERE a.dir='Support') AS s_rq2, + SUM(a.rq3) FILTER (WHERE a.dir='Support') AS s_rq3, + SUM(a.rq4) FILTER (WHERE a.dir='Support') AS s_rq4, + SUM(a.fq1) FILTER (WHERE a.dir='Support') AS s_fq1, + SUM(a.fq2) FILTER (WHERE a.dir='Support') AS s_fq2, + SUM(a.fq3) FILTER (WHERE a.dir='Support') AS s_fq3, + SUM(a.fq4) FILTER (WHERE a.dir='Support') AS s_fq4, + SUM(a.t1q2) FILTER (WHERE a.dir='Support') AS s_t1q2, + SUM(a.t1q3) FILTER (WHERE a.dir='Support') AS s_t1q3, + SUM(a.t1q4) FILTER (WHERE a.dir='Support') AS s_t1q4, + SUM(a.t2q3) FILTER (WHERE a.dir='Support') AS s_t2q3, + SUM(a.t2q4) FILTER (WHERE a.dir='Support') AS s_t2q4, + SUM(a.t3q4) FILTER (WHERE a.dir='Support') AS s_t3q4, + SUM(a.adj1) FILTER (WHERE a.dir='Support') AS s_adj1, + SUM(a.adj2) FILTER (WHERE a.dir='Support') AS s_adj2, + SUM(a.adj3) FILTER (WHERE a.dir='Support') AS s_adj3, + SUM(a.adj4) FILTER (WHERE a.dir='Support') AS s_adj4, + SUM(a.rev2) FILTER (WHERE a.dir='Support') AS s_rev2, + SUM(a.rev3) FILTER (WHERE a.dir='Support') AS s_rev3, + SUM(a.rev4) FILTER (WHERE a.dir='Support') AS s_rev4, + -- Развитие + SUM(a.pq1) FILTER (WHERE a.dir='Development') AS d_pq1, + SUM(a.pq2) FILTER (WHERE a.dir='Development') AS d_pq2, + SUM(a.pq3) FILTER (WHERE a.dir='Development') AS d_pq3, + SUM(a.pq4) FILTER (WHERE a.dir='Development') AS d_pq4, + SUM(a.sd1) FILTER (WHERE a.dir='Development') AS d_sd1, + SUM(a.sd2) FILTER (WHERE a.dir='Development') AS d_sd2, + SUM(a.sd3) FILTER (WHERE a.dir='Development') AS d_sd3, + SUM(a.sd4) FILTER (WHERE a.dir='Development') AS d_sd4, + SUM(a.sg1) FILTER (WHERE a.dir='Development') AS d_sg1, + SUM(a.sg2) FILTER (WHERE a.dir='Development') AS d_sg2, + SUM(a.sg3) FILTER (WHERE a.dir='Development') AS d_sg3, + SUM(a.sg4) FILTER (WHERE a.dir='Development') AS d_sg4, + SUM(a.rq1) FILTER (WHERE a.dir='Development') AS d_rq1, + SUM(a.rq2) FILTER (WHERE a.dir='Development') AS d_rq2, + SUM(a.rq3) FILTER (WHERE a.dir='Development') AS d_rq3, + SUM(a.rq4) FILTER (WHERE a.dir='Development') AS d_rq4, + SUM(a.fq1) FILTER (WHERE a.dir='Development') AS d_fq1, + SUM(a.fq2) FILTER (WHERE a.dir='Development') AS d_fq2, + SUM(a.fq3) FILTER (WHERE a.dir='Development') AS d_fq3, + SUM(a.fq4) FILTER (WHERE a.dir='Development') AS d_fq4, + SUM(a.t1q2) FILTER (WHERE a.dir='Development') AS d_t1q2, + SUM(a.t1q3) FILTER (WHERE a.dir='Development') AS d_t1q3, + SUM(a.t1q4) FILTER (WHERE a.dir='Development') AS d_t1q4, + SUM(a.t2q3) FILTER (WHERE a.dir='Development') AS d_t2q3, + SUM(a.t2q4) FILTER (WHERE a.dir='Development') AS d_t2q4, + SUM(a.t3q4) FILTER (WHERE a.dir='Development') AS d_t3q4, + SUM(a.adj1) FILTER (WHERE a.dir='Development') AS d_adj1, + SUM(a.adj2) FILTER (WHERE a.dir='Development') AS d_adj2, + SUM(a.adj3) FILTER (WHERE a.dir='Development') AS d_adj3, + SUM(a.adj4) FILTER (WHERE a.dir='Development') AS d_adj4, + SUM(a.rev2) FILTER (WHERE a.dir='Development') AS d_rev2, + SUM(a.rev3) FILTER (WHERE a.dir='Development') AS d_rev3, + SUM(a.rev4) FILTER (WHERE a.dir='Development') AS d_rev4 + FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id + JOIN v3.budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code + LEFT JOIN line_agg a ON a.eid = ANY(t.desc_ids) + WHERE t.depth <= 2 + GROUP BY t.id, t.depth, t.item_id, t.name + ) + SELECT + CASE r.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' END::VARCHAR, + r.depth, + r.section_code::VARCHAR, + r.name::VARCHAR, + -- Plan supp / dev + r.s_pq1, r.s_pq2, r.s_pq3, r.s_pq4, + COALESCE(r.s_pq1,0)+COALESCE(r.s_pq2,0)+COALESCE(r.s_pq3,0)+COALESCE(r.s_pq4,0), + r.d_pq1, r.d_pq2, r.d_pq3, r.d_pq4, + COALESCE(r.d_pq1,0)+COALESCE(r.d_pq2,0)+COALESCE(r.d_pq3,0)+COALESCE(r.d_pq4,0), + -- Approved supp = plan + seq_dfip + seq_ssp + reserve + COALESCE(r.s_pq1,0)+COALESCE(r.s_sd1,0)+COALESCE(r.s_sg1,0)+COALESCE(r.s_rq1,0), + COALESCE(r.s_pq2,0)+COALESCE(r.s_sd2,0)+COALESCE(r.s_sg2,0)+COALESCE(r.s_rq2,0), + COALESCE(r.s_pq3,0)+COALESCE(r.s_sd3,0)+COALESCE(r.s_sg3,0)+COALESCE(r.s_rq3,0), + COALESCE(r.s_pq4,0)+COALESCE(r.s_sd4,0)+COALESCE(r.s_sg4,0)+COALESCE(r.s_rq4,0), + COALESCE(r.s_pq1,0)+COALESCE(r.s_sd1,0)+COALESCE(r.s_sg1,0)+COALESCE(r.s_rq1,0) + +COALESCE(r.s_pq2,0)+COALESCE(r.s_sd2,0)+COALESCE(r.s_sg2,0)+COALESCE(r.s_rq2,0) + +COALESCE(r.s_pq3,0)+COALESCE(r.s_sd3,0)+COALESCE(r.s_sg3,0)+COALESCE(r.s_rq3,0) + +COALESCE(r.s_pq4,0)+COALESCE(r.s_sd4,0)+COALESCE(r.s_sg4,0)+COALESCE(r.s_rq4,0), + -- Approved dev + COALESCE(r.d_pq1,0)+COALESCE(r.d_sd1,0)+COALESCE(r.d_sg1,0)+COALESCE(r.d_rq1,0), + COALESCE(r.d_pq2,0)+COALESCE(r.d_sd2,0)+COALESCE(r.d_sg2,0)+COALESCE(r.d_rq2,0), + COALESCE(r.d_pq3,0)+COALESCE(r.d_sd3,0)+COALESCE(r.d_sg3,0)+COALESCE(r.d_rq3,0), + COALESCE(r.d_pq4,0)+COALESCE(r.d_sd4,0)+COALESCE(r.d_sg4,0)+COALESCE(r.d_rq4,0), + COALESCE(r.d_pq1,0)+COALESCE(r.d_sd1,0)+COALESCE(r.d_sg1,0)+COALESCE(r.d_rq1,0) + +COALESCE(r.d_pq2,0)+COALESCE(r.d_sd2,0)+COALESCE(r.d_sg2,0)+COALESCE(r.d_rq2,0) + +COALESCE(r.d_pq3,0)+COALESCE(r.d_sd3,0)+COALESCE(r.d_sg3,0)+COALESCE(r.d_rq3,0) + +COALESCE(r.d_pq4,0)+COALESCE(r.d_sd4,0)+COALESCE(r.d_sg4,0)+COALESCE(r.d_rq4,0), + -- Fact supp / dev + r.s_fq1, r.s_fq2, r.s_fq3, r.s_fq4, + COALESCE(r.s_fq1,0)+COALESCE(r.s_fq2,0)+COALESCE(r.s_fq3,0)+COALESCE(r.s_fq4,0), + r.d_fq1, r.d_fq2, r.d_fq3, r.d_fq4, + COALESCE(r.d_fq1,0)+COALESCE(r.d_fq2,0)+COALESCE(r.d_fq3,0)+COALESCE(r.d_fq4,0), + -- Corrected supp = approved + transfers + revisions + adj + COALESCE(r.s_pq1,0)+COALESCE(r.s_sd1,0)+COALESCE(r.s_sg1,0)+COALESCE(r.s_rq1,0) + +COALESCE(r.s_adj1,0), + COALESCE(r.s_pq2,0)+COALESCE(r.s_sd2,0)+COALESCE(r.s_sg2,0)+COALESCE(r.s_rq2,0) + +COALESCE(r.s_t1q2,0)+COALESCE(r.s_rev2,0)+COALESCE(r.s_adj2,0), + COALESCE(r.s_pq3,0)+COALESCE(r.s_sd3,0)+COALESCE(r.s_sg3,0)+COALESCE(r.s_rq3,0) + +COALESCE(r.s_t1q3,0)+COALESCE(r.s_t2q3,0)+COALESCE(r.s_rev3,0)+COALESCE(r.s_adj3,0), + COALESCE(r.s_pq4,0)+COALESCE(r.s_sd4,0)+COALESCE(r.s_sg4,0)+COALESCE(r.s_rq4,0) + +COALESCE(r.s_t1q4,0)+COALESCE(r.s_t2q4,0)+COALESCE(r.s_t3q4,0)+COALESCE(r.s_rev4,0)+COALESCE(r.s_adj4,0), + COALESCE(r.s_pq1,0)+COALESCE(r.s_pq2,0)+COALESCE(r.s_pq3,0)+COALESCE(r.s_pq4,0) + +COALESCE(r.s_sd1,0)+COALESCE(r.s_sd2,0)+COALESCE(r.s_sd3,0)+COALESCE(r.s_sd4,0) + +COALESCE(r.s_sg1,0)+COALESCE(r.s_sg2,0)+COALESCE(r.s_sg3,0)+COALESCE(r.s_sg4,0) + +COALESCE(r.s_rq1,0)+COALESCE(r.s_rq2,0)+COALESCE(r.s_rq3,0)+COALESCE(r.s_rq4,0) + +COALESCE(r.s_adj1,0)+COALESCE(r.s_adj2,0)+COALESCE(r.s_adj3,0)+COALESCE(r.s_adj4,0) + +COALESCE(r.s_rev2,0)+COALESCE(r.s_rev3,0)+COALESCE(r.s_rev4,0), + -- Corrected dev + COALESCE(r.d_pq1,0)+COALESCE(r.d_sd1,0)+COALESCE(r.d_sg1,0)+COALESCE(r.d_rq1,0) + +COALESCE(r.d_adj1,0), + COALESCE(r.d_pq2,0)+COALESCE(r.d_sd2,0)+COALESCE(r.d_sg2,0)+COALESCE(r.d_rq2,0) + +COALESCE(r.d_t1q2,0)+COALESCE(r.d_rev2,0)+COALESCE(r.d_adj2,0), + COALESCE(r.d_pq3,0)+COALESCE(r.d_sd3,0)+COALESCE(r.d_sg3,0)+COALESCE(r.d_rq3,0) + +COALESCE(r.d_t1q3,0)+COALESCE(r.d_t2q3,0)+COALESCE(r.d_rev3,0)+COALESCE(r.d_adj3,0), + COALESCE(r.d_pq4,0)+COALESCE(r.d_sd4,0)+COALESCE(r.d_sg4,0)+COALESCE(r.d_rq4,0) + +COALESCE(r.d_t1q4,0)+COALESCE(r.d_t2q4,0)+COALESCE(r.d_t3q4,0)+COALESCE(r.d_rev4,0)+COALESCE(r.d_adj4,0), + COALESCE(r.d_pq1,0)+COALESCE(r.d_pq2,0)+COALESCE(r.d_pq3,0)+COALESCE(r.d_pq4,0) + +COALESCE(r.d_sd1,0)+COALESCE(r.d_sd2,0)+COALESCE(r.d_sd3,0)+COALESCE(r.d_sd4,0) + +COALESCE(r.d_sg1,0)+COALESCE(r.d_sg2,0)+COALESCE(r.d_sg3,0)+COALESCE(r.d_sg4,0) + +COALESCE(r.d_rq1,0)+COALESCE(r.d_rq2,0)+COALESCE(r.d_rq3,0)+COALESCE(r.d_rq4,0) + +COALESCE(r.d_adj1,0)+COALESCE(r.d_adj2,0)+COALESCE(r.d_adj3,0)+COALESCE(r.d_adj4,0) + +COALESCE(r.d_rev2,0)+COALESCE(r.d_rev3,0)+COALESCE(r.d_rev4,0) + FROM tw_rollup r + ORDER BY r.section_code; +END; +$function$ +; + +-- 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 + + -- ═══ AHR / CAP / OPER — с проектной иерархией ════════════════════════════ + 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; + + -- ═══ Структура_Р ═══════════════════════════════════════════════════════ + -- AHR + CAP + OPER в одной выдаче, отсортировано по (section_code, + -- num_group, _sort_path). БЕЗ проектной иерархии. + 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) + UNION ALL + SELECT * FROM v3.v_form4_sheet_lines_jsonb(p_form_id, 'CAP', p_sections) + UNION ALL + SELECT * FROM v3.v_form4_sheet_lines_jsonb(p_form_id, 'OPER', p_sections) + ) + SELECT + j.row_type, + j.depth, + ROW_NUMBER() OVER ( + ORDER BY (j.header->>'section_code'), + (j.header->>'num_group') NULLS FIRST, + 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.header->>'section_code'), + (j.header->>'num_group') NULLS FIRST, + j._sort_path; + 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', 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 v3.v_form_view(int4, varchar, _text, varchar); + +CREATE OR REPLACE FUNCTION v3.v_form_view(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[], p_direction character varying DEFAULT NULL::character varying) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE plpgsql + STABLE +AS $function$ +DECLARE + v_form_type VARCHAR; + v_year INT; + v_org_unit_id INT; +BEGIN + -- Определяем form_type автоматически + SELECT form_type_code, year, org_unit_id + INTO v_form_type, v_year, v_org_unit_id + FROM v3.budget_form + WHERE id = p_form_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'budget_form id=% не существует в v3.budget_form', p_form_id; + END IF; + + -- ═══ FORM_1 ═══════════════════════════════════════════════════════════════ + IF v_form_type = 'FORM_1' THEN + + IF p_sheet = 'SMETA' THEN + -- v3.v_form1_smeta параметризуется year+org_unit_id + 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.supp_plan_q1, 'q2', sm.supp_plan_q2, + 'q3', sm.supp_plan_q3, 'q4', sm.supp_plan_q4, + 'year', sm.supp_plan_year), + 'development', jsonb_build_object('q1', sm.dev_plan_q1, 'q2', sm.dev_plan_q2, + 'q3', sm.dev_plan_q3, 'q4', sm.dev_plan_q4, + 'year', sm.dev_plan_year), + 'total_year', sm.total_plan_year), + 'approved', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.supp_appr_q1, 'q2', sm.supp_appr_q2, + 'q3', sm.supp_appr_q3, 'q4', sm.supp_appr_q4, + 'year', sm.supp_appr_year), + 'development', jsonb_build_object('q1', sm.dev_appr_q1, 'q2', sm.dev_appr_q2, + 'q3', sm.dev_appr_q3, 'q4', sm.dev_appr_q4, + 'year', sm.dev_appr_year), + 'total_year', sm.total_appr_year), + 'fact', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.supp_act_q1, 'q2', sm.supp_act_q2, + 'q3', sm.supp_act_q3, 'q4', sm.supp_act_q4, + 'year', sm.supp_act_year), + 'development', jsonb_build_object('q1', sm.dev_act_q1, 'q2', sm.dev_act_q2, + 'q3', sm.dev_act_q3, 'q4', sm.dev_act_q4, + 'year', sm.dev_act_year), + 'total_year', sm.total_act_year), + 'corrected', jsonb_build_object( + 'support', jsonb_build_object('q2', sm.supp_corr_q2, 'q3', sm.supp_corr_q3, 'q4', sm.supp_corr_q4), + 'development', jsonb_build_object('q2', sm.dev_corr_q2, 'q3', sm.dev_corr_q3, 'q4', sm.dev_corr_q4)) + ) + FROM v3.v_form1_smeta(v_year, v_org_unit_id) sm + ORDER BY sm.section_code; + RETURN; + END IF; + + 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, + 'contract_summary', j.contract_summary, + 'allocation', j.allocation_data, + 'sequestration', j.sequestration_data, + 'reserve', j.reserve_data, + 'approved', j.approved_data, + 'collegial', j.collegial_data, + 'ckk', j.ckk_data, + 'contract_detail', j.contract_detail, + 'q1', j.q1_data, + 'q2', j.q2_data, + 'q3', j.q3_data, + 'q4', j.q4_data, + 'totals', j.totals_data + ) + FROM v3.v_form1_sheet_jsonb(p_form_id, p_sheet, p_direction, p_sections) j + ORDER BY j._sort_path; + RETURN; + END IF; + + RAISE EXCEPTION 'Unknown FORM_1 sheet: %', p_sheet + USING HINT = 'Use AHR / CAP / OPER / SMETA'; + END IF; + + -- ═══ FORM_2 ═══════════════════════════════════════════════════════════════ + IF v_form_type = 'FORM_2' THEN + RETURN QUERY SELECT * FROM v3.v_form2_view(p_form_id, p_sheet, p_sections); + RETURN; + END IF; + + -- ═══ FORM_4 ═══════════════════════════════════════════════════════════════ + IF v_form_type = 'FORM_4' THEN + RETURN QUERY SELECT * FROM v3.v_form4_view(p_form_id, p_sheet, p_sections); + RETURN; + END IF; + + RAISE EXCEPTION 'Unsupported form_type=% for form_id=%', v_form_type, p_form_id; + +END; +$function$ +; \ No newline at end of file diff --git a/api/alembic/versions/sql/0003_initial_data.sql b/api/alembic/versions/sql/0003_initial_data.sql new file mode 100644 index 0000000..6c98e5e --- /dev/null +++ b/api/alembic/versions/sql/0003_initial_data.sql @@ -0,0 +1,4253 @@ +INSERT INTO v3."role" (id,code) VALUES + (1,'ADMIN'), + (2,'DFIP'), + (3,'EXECUTOR_RF'); + + +INSERT INTO v3.form_type (code,"name","scope",storage_entity) VALUES + ('FORM_1','Смета ГО','GO','BUDGET_FORM'), + ('FORM_2','ПД','PD','BUDGET_FORM'), + ('FORM_3','Отчёт РФ развитие','RF','RF_PROJECT_REPORT'), + ('FORM_4','Смета РФ поддержка','RF','BUDGET_FORM'); + +INSERT INTO v3.phase_template (form_type,sheet,phase_code,"role",column_keys,opens_at,closes_at) VALUES + ('FORM_1','AHR','DFIP_SEQ_2026','DFIP','{sequestration.q1,sequestration.q2,sequestration.q3,sequestration.q4,sequestration.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_1','CAP','DFIP_SEQ_2026','DFIP','{sequestration.q1,sequestration.q2,sequestration.q3,sequestration.q4,sequestration.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_1','OPER','DFIP_SEQ_2026','DFIP','{sequestration.q1,sequestration.q2,sequestration.q3,sequestration.q4,sequestration.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_2','AHR','SSP_SEQ_2026','EXECUTOR_RF','{seq_ssp.q1,seq_ssp.q2,seq_ssp.q3,seq_ssp.q4,seq_ssp.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_2','AHR','DFIP_SEQ_2026','DFIP','{seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_2','CAP','SSP_SEQ_2026','EXECUTOR_RF','{seq_ssp.q1,seq_ssp.q2,seq_ssp.q3,seq_ssp.q4,seq_ssp.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_2','CAP','DFIP_SEQ_2026','DFIP','{seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_2','OPER','SSP_SEQ_2026','EXECUTOR_RF','{seq_ssp.q1,seq_ssp.q2,seq_ssp.q3,seq_ssp.q4,seq_ssp.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_2','OPER','DFIP_SEQ_2026','DFIP','{seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_4','AHR','SSP_SEQ_2026','EXECUTOR_RF','{seq_ssp.q1,seq_ssp.q2,seq_ssp.q3,seq_ssp.q4,seq_ssp.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'); +INSERT INTO v3.phase_template (form_type,sheet,phase_code,"role",column_keys,opens_at,closes_at) VALUES + ('FORM_4','AHR','DFIP_SEQ_2026','DFIP','{seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_4','CAP','SSP_SEQ_2026','EXECUTOR_RF','{seq_ssp.q1,seq_ssp.q2,seq_ssp.q3,seq_ssp.q4,seq_ssp.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_4','CAP','DFIP_SEQ_2026','DFIP','{seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_4','OPER','SSP_SEQ_2026','EXECUTOR_RF','{seq_ssp.q1,seq_ssp.q2,seq_ssp.q3,seq_ssp.q4,seq_ssp.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_4','OPER','DFIP_SEQ_2026','DFIP','{seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.justification}','2026-01-01 03:00:00+03','2027-01-01 02:59:59+03'), + ('FORM_1','AHR','rf_test','EXECUTOR_RF','{plan.q1}','2026-05-05 03:00:00+03','2026-05-11 23:59:59+03'), + ('FORM_1','CAP','тест дФиП','DFIP','{sequestration.q1,sequestration.q2,sequestration.q3,sequestration.q4,sequestration.justification}','2026-05-01 13:16:00+03','2026-05-31 13:16:00+03'), + ('FORM_2','AHR','Тест второй формы ','EXECUTOR_RF','{seq_ssp.q1,seq_ssp.q2,seq_ssp.q3,seq_ssp.q4,seq_ssp.justification}','2026-05-01 13:41:00+03','2026-05-31 13:41:00+03'), + ('FORM_4','CAP','Тест формы 4','DFIP','{seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.justification}','2026-05-08 13:47:00+03','2026-06-30 13:47:00+03'); + + +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (1,'AHR','1.00.0.',NULL,'Административно-хозяйственные расходы','AHR','Support',NULL,0), + (2,'AHR','1.01.0.',NULL,'Расходы на информационные технологии','AHR','Support',1,1), + (3,'AHR','1.01.1.',NULL,'Права использования ПО','AHR','Support',2,2), + (4,'AHR','1.01.2.',NULL,'Внедрение и доработка ПО','AHR','Support',2,2), + (5,'AHR','1.01.3.',NULL,'Права доступа к информационным ресурсам, сервисам и информационные услуги','AHR','Support',2,2), + (6,'AHR','1.01.4.',NULL,'Сопровождение, техническая поддержка программного обеспечения','AHR','Support',2,2), + (7,'AHR','1.01.5.',NULL,'Расходы на телекоммуникационные услуги','AHR','Support',2,2), + (8,'AHR','1.01.6.',NULL,'Техническое обслуживание и эксплуатация оборудования','AHR','Support',2,2), + (9,'AHR','1.01.7.',NULL,'Ремонт оборудования','AHR','Support',2,2), + (10,'AHR','1.01.8.',NULL,'Расходы на приобретение материальных ценностей','AHR','Support',2,2), + (11,'AHR','1.02.0.',NULL,'Реклама','AHR','Support',1,1); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (12,'AHR','1.03.0.',NULL,'Аренда и прочие аналогичные расходы','AHR','Support',1,1), + (13,'AHR','1.03.1.',NULL,'Аренда','AHR','Support',12,2), + (14,'AHR','1.03.6.',NULL,'Доходы по аренде','AHR','Support',12,2), + (15,'AHR','1.04.0.',NULL,'Содержание, ремонт служебных автомобилей и аутсорсинг транспортных услуг','AHR','Support',1,1), + (16,'AHR','1.04.1.',NULL,'ГСМ','AHR','Support',15,2), + (17,'AHR','1.04.2.',NULL,'Аутсорсинг транспортных услуг','AHR','Support',15,2), + (18,'AHR','1.04.3.',NULL,'Техническое обслуживание: транспорт','AHR','Support',15,2), + (19,'AHR','1.04.4.',NULL,'Другие услуги, связанные с эксплуатацией транспорта','AHR','Support',15,2), + (20,'AHR','1.04.5.',NULL,'Дополнительные расходы по персонал (транспорт)','AHR','Support',15,2), + (21,'AHR','1.04.6.',NULL,'Ремонт: АТС','AHR','Support',15,2); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (22,'AHR','1.04.7.',NULL,'Материальные ценности, связанные с эксплуатацией транспорта','AHR','Support',15,2), + (23,'AHR','1.05.0.',NULL,'Расходы на содержание, ремонт зданий, сооружений и прочих объектов недвижимости','AHR','Support',1,1), + (24,'AHR','1.05.1.',NULL,'Коммунальные услуги','AHR','Support',23,2), + (25,'AHR','1.05.2.',NULL,'Клининг, включая санитарную обработку и прочие аналогичные услуги','AHR','Support',23,2), + (26,'AHR','1.05.3.',NULL,'Техническое обслуживание и эксплуатация объектов недвижимости и их оснащения','AHR','Support',23,2), + (27,'AHR','1.05.4.',NULL,'Благоустройство и озеленение','AHR','Support',23,2), + (28,'AHR','1.05.5.',NULL,'Другие услуги, связанные с эксплуатацией объектов недвижимости','AHR','Support',23,2), + (29,'AHR','1.05.6.',NULL,'Ремонт объектов недвижимости и оснащения','AHR','Support',23,2), + (30,'AHR','1.05.7.',NULL,'Материальные ценности, связанные с эксплуатацией объектов недвижимости','AHR','Support',23,2), + (31,'AHR','1.06.0.',NULL,'Охрана','AHR','Support',1,1); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (32,'AHR','1.06.1.',NULL,'Физическая охр.ОН','AHR','Support',31,2), + (33,'AHR','1.06.2.',NULL,'Пультовая охр.ОН','AHR','Support',31,2), + (34,'AHR','1.06.3.',NULL,'Охр.инкассат перевоз','AHR','Support',31,2), + (35,'AHR','1.06.4.',NULL,'Охр.имущ. п/транс-ке','AHR','Support',31,2), + (36,'AHR','1.06.5.',NULL,'Пультовая охр.УС','AHR','Support',31,2), + (37,'AHR','1.06.6.',NULL,'ТО/эксп_обор.сис охр','AHR','Support',31,2), + (38,'AHR','1.06.7.',NULL,'Ремонт:обор.сис охр','AHR','Support',31,2), + (39,'AHR','1.06.8.',NULL,'Материальные ценности, связанные с эксплуатацией систем охраны объектов недвижимости','AHR','Support',31,2), + (40,'AHR','1.07.0.',NULL,'Канцелярские расходы и малоценное офисное оборудование','AHR','Support',1,1), + (41,'AHR','1.07.1.',NULL,'Бумага','AHR','Support',40,2); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (42,'AHR','1.07.2.',NULL,'Канцтовары','AHR','Support',40,2), + (43,'AHR','1.07.3.',NULL,'Архивный короб','AHR','Support',40,2), + (44,'AHR','1.07.4.',NULL,'Материалы для касс','AHR','Support',40,2), + (45,'AHR','1.07.5.',NULL,'Мебель','AHR','Support',40,2), + (46,'AHR','1.07.6.',NULL,'Малоценное офисное оборудование','AHR','Support',40,2), + (47,'AHR','1.07.7.',NULL,'Другие офисные материалы','AHR','Support',40,2), + (48,'AHR','1.08.0.',NULL,'Страхование','AHR','Support',1,1), + (49,'AHR','1.08.1.',NULL,'Страхование имущества','AHR','Support',48,2), + (50,'AHR','1.08.2.',NULL,'Страхование персонала','AHR','Support',48,2), + (51,'AHR','1.08.3.',NULL,'Страхование гражданской ответственности','AHR','Support',48,2); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (52,'AHR','1.09.0.',NULL,'Аудиторские, консультационные, рейтинговые услуги','AHR','Support',1,1), + (53,'AHR','1.09.1.',NULL,'Аудиторские услуги','AHR','Support',52,2), + (54,'AHR','1.09.2.',NULL,'Консультационные услуги','AHR','Support',52,2), + (55,'AHR','1.09.3.',NULL,'Рейтинговые услуги','AHR','Support',52,2), + (56,'AHR','1.09.4.',NULL,'Информационно-аналитические и маркетинговые услуги','AHR','Support',52,2), + (57,'AHR','1.09.5.',NULL,'Оценка ОН','AHR','Support',52,2), + (58,'AHR','1.09.6.',NULL,'Оценка пр активов','AHR','Support',52,2), + (59,'AHR','1.09.7.',NULL,'Исслед и разработки','AHR','Support',52,2), + (60,'AHR','1.10.0.',NULL,'Командировочные расходы','AHR','Support',1,1), + (61,'AHR','1.11.0.',NULL,'Благотворительность','AHR','Support',1,1); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (62,'AHR','1.12.0.',NULL,'Спонсорская деятельность','AHR','Support',1,1), + (63,'AHR','1.13.0.',NULL,'Обучение персонала','AHR','Support',1,1), + (64,'AHR','1.13.1.',NULL,'Корпоративное обучение','AHR','Support',63,2), + (65,'AHR','1.13.2.',NULL,'Внешнее обучение','AHR','Support',63,2), + (66,'AHR','1.14.0.',NULL,'Прочие административно-хозяйственные расходы','AHR','Support',1,1), + (67,'AHR','1.14.1.',NULL,'Взносы в орг/ассоц','AHR','Support',66,2), + (68,'AHR','1.14.2.',NULL,'Подписка на специализированные издания','AHR','Support',66,2), + (69,'AHR','1.14.3.',NULL,'Судебные и арбитражные издержки и расходы за нотариальное оформление документов','AHR','Support',66,2), + (70,'AHR','1.14.4.',NULL,'Перевозка валютных ценностей_АХР','AHR','Support',66,2), + (71,'AHR','1.14.5.',NULL,'Перевозка материальных ценностей (транспортные услуги)','AHR','Support',66,2); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (72,'AHR','1.14.6.',NULL,'СИЗ для персонала','AHR','Support',66,2), + (73,'AHR','1.14.7.',NULL,'Представительские расходы','AHR','Support',66,2), + (74,'AHR','1.14.8.',NULL,'Другие административно-хозяйственные расходы','AHR','Support',66,2), + (75,'AHR','1.15.0.',NULL,'Расходы, связанные с непрофильными и профильными нецелевыми активами','AHR','Support',1,1), + (76,'AHR','1.15.1.',NULL,'Комм усл и сод_НВНОД','AHR','Support',75,2), + (77,'AHR','1.15.2.',NULL,'Ремонт_НВНОД','AHR','Support',75,2), + (78,'AHR','1.15.3.',NULL,'Охрана_НВНОД','AHR','Support',75,2), + (79,'AHR','1.15.4.',NULL,'Орг торгов_НВНОД','AHR','Support',75,2), + (80,'AHR','1.15.5.',NULL,'Оценка объект_НВНОД','AHR','Support',75,2), + (81,'AHR','1.15.6.',NULL,'Другие расходы, связанные с НВНОД','AHR','Support',75,2); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (82,'AHR','1.17.0.',NULL,'Расходы по выбытию (реализации) имущества','AHR','Support',1,1), + (83,'AHR','1.17.1.',NULL,'Расходы по выбытию основных средств','AHR','Support',82,2), + (84,'AHR','1.17.2.',NULL,'Расходы по выбытию нематериальных активов','AHR','Support',82,2), + (85,'AHR','1.18.0.',NULL,'Расходы по переоценке стоимости имущества','AHR','Support',1,1), + (86,'AHR','1.18.1.',NULL,'Расходы по переоценке основных средств','AHR','Support',85,2), + (87,'AHR','1.18.2.',NULL,'Расходы по переоценке нематериальных активов','AHR','Support',85,2), + (88,'AHR','R061011021',NULL,'Права использования: бизнес ПО','AHR','Support',3,3), + (89,'AHR','R061011022',NULL,'Права использования: пользовательское ПО','AHR','Support',3,3), + (90,'AHR','R061011023',NULL,'Права использования: инфраструктурное ПО','AHR','Support',3,3), + (91,'AHR','R061011024',NULL,'Права использования: ПО информационной безопасности','AHR','Support',3,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (92,'AHR','R061012021',NULL,'Внедрение и доработка внешними контрагентами: бизнес ПО','AHR','Support',4,3), + (93,'AHR','R061012022',NULL,'Внедрение и доработка внешними контрагентами: пользовательское ПО','AHR','Support',4,3), + (94,'AHR','R061012023',NULL,'Внедрение и доработка внешними контрагентами: инфраструктурное ПО','AHR','Support',4,3), + (95,'AHR','R061012024',NULL,'Внедрение и доработка внешними контрагентами: ПО информационной безопасности','AHR','Support',4,3), + (96,'AHR','R061012121',NULL,'Внедрение и доработка внутренними контрагентами: бизнес ПО','AHR','Support',4,3), + (97,'AHR','R061012122',NULL,'Внедрение и доработка внутренними контрагентами: пользовательское ПО','AHR','Support',4,3), + (98,'AHR','R061012123',NULL,'Внедрение и доработка внутренними контрагентами: инфраструктурное ПО','AHR','Support',4,3), + (99,'AHR','R061012124',NULL,'Внедрение и доработка внутренними контрагентами: ПО информационной безопасности','AHR','Support',4,3), + (100,'AHR','R061013021',NULL,'Права доступа и информационные услуги: бизнес ПО','AHR','Support',5,3), + (101,'AHR','R061013022',NULL,'Права доступа и информационные услуги: пользовательское ПО','AHR','Support',5,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (102,'AHR','R061013023',NULL,'Права доступа и информационные услуги: инфраструктурное ПО','AHR','Support',5,3), + (103,'AHR','R061013024',NULL,'Права доступа и информационные услуги: ПО информационной безопасности','AHR','Support',5,3), + (104,'AHR','R061014021',NULL,'Сопровождение, техническая поддержка внешними контрагентами: бизнес ПО','AHR','Support',6,3), + (105,'AHR','R061014022',NULL,'Сопровождение, техническая поддержка внешними контрагентами: пользовательское ПО','AHR','Support',6,3), + (106,'AHR','R061014023',NULL,'Сопровождение, техническая поддержка внешними контрагентами: инфраструктурное ПО','AHR','Support',6,3), + (107,'AHR','R061014024',NULL,'Сопровождение, техническая поддержка внешними контрагентами: ПО информационной безопасности','AHR','Support',6,3), + (108,'AHR','R061014121',NULL,'Сопровождение, техническая поддержка внутренними контрагентами: бизнес ПО','AHR','Support',6,3), + (782,'AHR','R061066009',NULL,'ТО/эксп_обор.сис охр','AHR',NULL,37,3), + (783,'AHR','R061067009',NULL,'Ремонт:обор.сис охр','AHR',NULL,38,3), + (109,'AHR','R061014122',NULL,'Сопровождение, техническая поддержка внутренними контрагентами: пользовательское ПО','AHR','Support',6,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (110,'AHR','R061014123',NULL,'Сопровождение, техническая поддержка внутренними контрагентами: инфраструктурное ПО','AHR','Support',6,3), + (111,'AHR','R061014124',NULL,'Сопровождение, техническая поддержка внутренними контрагентами: ПО информационной безопасности','AHR','Support',6,3), + (112,'AHR','R061015031',NULL,'Каналы связи','AHR','Support',7,3), + (113,'AHR','R061015031','202520001','Каналы связи_осн','AHR','Support',7,3), + (114,'AHR','R061015031','202520002','Каналы связи_резерв','AHR','Support',7,3), + (115,'AHR','R061015031','202520003','Обслуживание ВОЛС','AHR','Support',7,3), + (116,'AHR','R061015031','202520004','Канал связи_плат сис','AHR','Support',7,3), + (117,'AHR','R061015032','202620005','Интернет каналы','AHR','Support',7,3), + (118,'AHR','R061015033','202720006','И-трафик д/клиентов','AHR','Support',7,3), + (119,'AHR','R061015034',NULL,'Интернет трафик для оборудования','AHR','Support',7,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (120,'AHR','R061015034','202820007','И-трафик д/обор_УС','AHR','Support',7,3), + (121,'AHR','R061015034','202820008','И-трафик д/обор_ИТ','AHR','Support',7,3), + (122,'AHR','R061015035',NULL,'Торгово-аналитические терминалы','AHR','Support',7,3), + (123,'AHR','R061015035','202920009','Терминалы Блумберг','AHR','Support',7,3), + (124,'AHR','R061015035','202920010','Терминалы Рейтерс','AHR','Support',7,3), + (125,'AHR','R061015035','202920011','Терминалы прочие','AHR','Support',7,3), + (126,'AHR','R061015036','203020012','SWIFT','AHR','Support',7,3), + (127,'AHR','R061015037','203120013','Дилинг','AHR','Support',7,3), + (128,'AHR','R061015038','203220019','Стационарная связь','AHR','Support',7,3), + (129,'AHR','R061015039','203320020','Мобильная связь','AHR','Support',7,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (130,'AHR','R061015040','203420021','Беспл кор номер 7787','AHR','Support',7,3), + (131,'AHR','R061015041','203520022','Беспл вызов 8-800','AHR','Support',7,3), + (132,'AHR','R061015042','203620023','Голосовая карусель','AHR','Support',7,3), + (133,'AHR','R061015043','203720024','Смс-инф (транзакц)','AHR','Support',7,3), + (134,'AHR','R061015044','203820025','Cмс-расс_перс предл','AHR','Support',7,3), + (135,'AHR','R061015045','203920014','Услуги спецсвязи','AHR','Support',7,3), + (136,'AHR','R061015046','204020015','Курьерские услуги','AHR','Support',7,3), + (137,'AHR','R061015047','204120016','Дост карт/пин-конв','AHR','Support',7,3), + (138,'AHR','R061015048','204220017','Почтовые расходы','AHR','Support',7,3), + (139,'AHR','R061015049',NULL,'Другие услуги связи','AHR','Support',7,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (140,'AHR','R061015049','204320018','Др телеком услуги','AHR','Support',7,3), + (141,'AHR','R061015049','204320026','Другие услуги связи','AHR','Support',7,3), + (142,'AHR','R061015050','204420027','Телеграфные расходы','AHR','Support',7,3), + (143,'AHR','R061015051','204520028','Спец телефон связь','AHR','Support',7,3), + (144,'AHR','R061016002',NULL,'Техническое обслуживание и эксплуатация: ИТ-оборудование','AHR','Support',8,3), + (145,'AHR','R061016002','204620029','ТО/экспл выч техн','AHR','Support',8,3), + (146,'AHR','R061016002','204620030','ТО/экспл оргтехника','AHR','Support',8,3), + (147,'AHR','R061016002','204620031','ТО/экспл тел-м обор','AHR','Support',8,3), + (148,'AHR','R061016002','204620032','ТО/экспл СХД','AHR','Support',8,3), + (149,'AHR','R061016002','204620033','ТО/экспл сет обор','AHR','Support',8,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (150,'AHR','R061016002','204620034','ТО/экспл межсет экр','AHR','Support',8,3), + (151,'AHR','R061016002','204620035','ТО/экспл телефонии','AHR','Support',8,3), + (152,'AHR','R061016002','204620036','ТО/экспл ВКС','AHR','Support',8,3), + (153,'AHR','R061016002','204620037','ТО/экспл инж систем','AHR','Support',8,3), + (154,'AHR','R061016002','204620038','ТО/экспл конт-центр','AHR','Support',8,3), + (155,'AHR','R061016002','204620039','ТО/экспл серв обор','AHR','Support',8,3), + (156,'AHR','R061016002','204620040','ТО/экспл ПАК','AHR','Support',8,3), + (157,'AHR','R061016004',NULL,'Техническое обслуживание и эксплуатация: кассовое оборудование','AHR','Support',8,3), + (158,'AHR','R061016004','204720043','ТО/экспл касс обор','AHR','Support',8,3), + (159,'AHR','R061016004','204720044','ТО/экспл cист технТВ','AHR','Support',8,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (160,'AHR','R061016004','204720045','ТО/экспл депоз стоек','AHR','Support',8,3), + (161,'AHR','R061016004','204720046','Поверка весов','AHR','Support',8,3), + (162,'AHR','R061016003','204820041','ТО/экспл УС','AHR','Support',8,3), + (163,'AHR','R061016013','204920042','ТО/экспл POS-терм','AHR','Support',8,3), + (164,'AHR','R061016006','205020047','ТО/экспл обор ИБ','AHR','Support',8,3), + (165,'AHR','R061016010','205120048','ТО/экспл пр техн об','AHR','Support',8,3), + (166,'AHR','R061017002',NULL,'Ремонт: ИТ-оборудование','AHR','Support',9,3), + (167,'AHR','R061017002','205220049','Ремонт выч техн','AHR','Support',9,3), + (168,'AHR','R061017002','205220050','Ремонт оргтехника','AHR','Support',9,3), + (169,'AHR','R061017002','205220051','Ремонт тел-м обор','AHR','Support',9,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (170,'AHR','R061017002','205220052','Ремонт СХД','AHR','Support',9,3), + (171,'AHR','R061017002','205220053','Ремонт сет обор','AHR','Support',9,3), + (172,'AHR','R061017002','205220054','Ремонт серв обор','AHR','Support',9,3), + (173,'AHR','R061017002','205220055','Ремонт ПАК','AHR','Support',9,3), + (174,'AHR','R061017004',NULL,'Ремонт: кассовое оборудование','AHR','Support',9,3), + (175,'AHR','R061017004','205320058','Ремонт касс обор','AHR','Support',9,3), + (176,'AHR','R061017004','205320059','Ремонт cист технТВ','AHR','Support',9,3), + (177,'AHR','R061017004','205320060','Ремонт депоз стоек','AHR','Support',9,3), + (178,'AHR','R061017004','205320061','Ремонт сейф обор','AHR','Support',9,3), + (179,'AHR','R061017003','205420056','Ремонт УС','AHR','Support',9,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (180,'AHR','R061017013','205520057','Ремонт POS-терм','AHR','Support',9,3), + (181,'AHR','R061017006','205620062','Ремонт оборуд ИБ','AHR','Support',9,3), + (182,'AHR','R061017010','205720063','Ремонт прочее технол оборуд','AHR','Support',9,3), + (183,'AHR','R061018002',NULL,'Материальные ценности, связанные с ИТ-оборудованием','AHR','Support',10,3), + (184,'AHR','R061018002','400140037','Вебкамеры МЦ','AHR','Support',10,3), + (185,'AHR','R061018002','400140039','Видеокамеры МЦ','AHR','Support',10,3), + (186,'AHR','R061018002','400040200','ВОЛС МЦ','AHR','Support',10,3), + (187,'AHR','R061018002','400040033','Другая вычислит тех. м/ц','AHR','Support',10,3), + (188,'AHR','R061018002','400140051','ИБП МЦ','AHR','Support',10,3), + (189,'AHR','R061018002','400040064','Инфрастру. системыМЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (190,'AHR','R061018002','400040022','Кабельн системы МЦ','AHR','Support',10,3), + (191,'AHR','R061018002','400140043','Картриджи МЦ','AHR','Support',10,3), + (192,'AHR','R061018002','400140036','Клавиатура МЦ','AHR','Support',10,3), + (193,'AHR','R061018002','400040020','Коммутаторы МЦ','AHR','Support',10,3), + (194,'AHR','R061018002','400140035','Компьютерная мышь МЦ','AHR','Support',10,3), + (195,'AHR','R061018002','400040014','Копиры МЦ','AHR','Support',10,3), + (196,'AHR','R061018002','400040021','Маршрутизаторы МЦ','AHR','Support',10,3), + (197,'AHR','R061018002','400140038','Микрофон МЦ','AHR','Support',10,3), + (198,'AHR','R061018002','400040015','Мобильн принтер МЦ','AHR','Support',10,3), + (199,'AHR','R061018002','400040030','Модемы МЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (200,'AHR','R061018002','400040003','Моноблоки МЦ','AHR','Support',10,3), + (201,'AHR','R061018002','400040012','МФУ МЦ','AHR','Support',10,3), + (202,'AHR','R061018002','400140042','Носит дл хран инф. МЦ','AHR','Support',10,3), + (203,'AHR','R061018002','400040001','Ноутбуки МЦ','AHR','Support',10,3), + (204,'AHR','R061018002','400040019','Оборудов. безопас МЦ','AHR','Support',10,3), + (205,'AHR','R061018002','400040008','Офисн мониторы МЦ','AHR','Support',10,3), + (206,'AHR','R061018002','400040002','Офисн сист блоки МЦ','AHR','Support',10,3), + (207,'AHR','R061018002','400040168','Плазм. и ЖК панелиМЦ','AHR','Support',10,3), + (208,'AHR','R061018002','400040006','Планшеты МЦ','AHR','Support',10,3), + (209,'AHR','R061018002','400040005','Портативн комп МЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (210,'AHR','R061018002','400040011','Принтеры МЦ','AHR','Support',10,3), + (211,'AHR','R061018002','400040016','Принтеры спец МЦ','AHR','Support',10,3), + (212,'AHR','R061018002','400040018','Програм-аппарат к.МЦ','AHR','Support',10,3), + (213,'AHR','R061018002','400040007','Проч комп оборудМЦ','AHR','Support',10,3), + (214,'AHR','R061018002','400040017','Проч оргтехникаМЦ','AHR','Support',10,3), + (215,'AHR','R061018002','400040034','Проч сетевое оборМЦ','AHR','Support',10,3), + (216,'AHR','R061018002','400040010','Прочие мониторы МЦ','AHR','Support',10,3), + (217,'AHR','R061018002','400240049','РасхМ д/видеоустр МЦ','AHR','Support',10,3), + (218,'AHR','R061018002','400240045','РасхМ д/комп техн МЦ','AHR','Support',10,3), + (219,'AHR','R061018002','400240044','РасхМ д/оргтехникиМЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (220,'AHR','R061018002','400240050','РасхМ д/серв. оборМЦ','AHR','Support',10,3), + (221,'AHR','R061018002','400240048','РасхМ д/сетев оборМЦ','AHR','Support',10,3), + (222,'AHR','R061018002','400240047','РасхМ д/СХД МЦ','AHR','Support',10,3), + (223,'AHR','R061018002','400240046','РасхМ д/телеком обМЦ','AHR','Support',10,3), + (224,'AHR','R061018002','400040028','Сист биллинг связиМЦ','AHR','Support',10,3), + (225,'AHR','R061018002','400040013','Сканеры МЦ','AHR','Support',10,3), + (226,'AHR','R061018002','400040167','Телевизоры МЦ','AHR','Support',10,3), + (227,'AHR','R061018002','400040026','Телеграфные апп МЦ','AHR','Support',10,3), + (228,'AHR','R061018002','400040024','Телефонн апп МЦ','AHR','Support',10,3), + (229,'AHR','R061018002','400040023','Телефонн оборуд МЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (230,'AHR','R061018002','400040032','Телефонн станции МЦ','AHR','Support',10,3), + (231,'AHR','R061018002','400040025','Телефоны мобильн МЦ','AHR','Support',10,3), + (232,'AHR','R061018002','400040009','Технол мониторы МЦ','AHR','Support',10,3), + (233,'AHR','R061018002','400040004','Технол сист блок МЦ','AHR','Support',10,3), + (234,'AHR','R061018002','400040029','Точки доступа МЦ','AHR','Support',10,3), + (235,'AHR','R061018002','400040027','Факс аппараты МЦ','AHR','Support',10,3), + (236,'AHR','R061018004',NULL,'Материальные ценности, связанные с кассовым оборудованием','AHR','Support',10,3), + (237,'AHR','R061018004','400440066','Апп-т_опр под-тиМЦ','AHR','Support',10,3), + (238,'AHR','R061018004','400340069','Безвакуум упаковщ МЦ','AHR','Support',10,3), + (239,'AHR','R061018004','400440055','Вак упак б-нот_1пМЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (240,'AHR','R061018004','400440056','Вак упак б-нот_2пМЦ','AHR','Support',10,3), + (241,'AHR','R061018004','400440057','Весы_мон/мер слиткМЦ','AHR','Support',10,3), + (242,'AHR','R061018004','400440058','Весы_мон/ст слиткМЦ','AHR','Support',10,3), + (243,'AHR','R061018004','400440063','Витрины_монеты_2клМЦ','AHR','Support',10,3), + (244,'AHR','R061018004','400440074','Внут контейн д/стоМЦ','AHR','Support',10,3), + (245,'AHR','R061018004','400440059','Деп колонк_сейф ячМЦ','AHR','Support',10,3), + (246,'AHR','R061018004','400440068','Др касс оборудМЦ','AHR','Support',10,3), + (247,'AHR','R061018004','400440067','ПневмопочтаМЦ','AHR','Support',10,3), + (248,'AHR','R061018004','400440070','Просм дет-р_кассаМЦ','AHR','Support',10,3), + (249,'AHR','R061018004','400440071','Просм дет-р_фронтМЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (250,'AHR','R061018004','400440073','РасхМ д/касс обор МЦ','AHR','Support',10,3), + (251,'AHR','R061018004','400440061','Сейфы_2 кл уст-тиМЦ','AHR','Support',10,3), + (252,'AHR','R061018004','400440062','Сейфы_3 кл уст-тиМЦ','AHR','Support',10,3), + (253,'AHR','R061018004','400440060','Сист к-ля мон/слитМЦ','AHR','Support',10,3), + (254,'AHR','R061018004','400440065','Система технол ТВ МЦ','AHR','Support',10,3), + (255,'AHR','R061018004','400440053','Сч-сорт банкнотМЦ','AHR','Support',10,3), + (256,'AHR','R061018004','400440054','Сч-сорт монетМЦ','AHR','Support',10,3), + (257,'AHR','R061018004','400440072','Цифр штанг-циркулиМЦ','AHR','Support',10,3), + (258,'AHR','R061018013','400540052','POS-терм оборуд МЦ','AHR','Support',10,3), + (259,'AHR','R061018003','400640148','Материалы для УС','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (260,'AHR','R061018006',NULL,'Материальные ценности, связанные с оборудованием систем информационной безопасности','AHR','Support',10,3), + (261,'AHR','R061018006','400840149','Аккорд МЦ','AHR','Support',10,3), + (262,'AHR','R061018006','400840150','Секрет-диск МЦ','AHR','Support',10,3), + (263,'AHR','R061018006','400740151','Рутокен МЦ','AHR','Support',10,3), + (264,'AHR','R061018006','400740152','Етокен МЦ','AHR','Support',10,3), + (265,'AHR','R061018006','400740153','Ключи безоп МЦ','AHR','Support',10,3), + (266,'AHR','R061018006','400740154','ЭЦП МЦ','AHR','Support',10,3), + (267,'AHR','R061018006','400840155','Защитн экраны МЦ','AHR','Support',10,3), + (268,'AHR','R061018006','400840156','Проч матер для ИБ МЦ','AHR','Support',10,3), + (269,'AHR','R061018010',NULL,'Материальные ценности, связанные с прочим технологическим оборудованием','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (270,'AHR','R061018010','400940079','РасхМ д/эмбоссеровМЦ','AHR','Support',10,3), + (271,'AHR','R061018010','400940080','РасхМ д/обор персоМЦ','AHR','Support',10,3), + (272,'AHR','R061018010','400940081','РасхМ_пр технол обМЦ','AHR','Support',10,3), + (273,'AHR','R061020031','205820064','Рекл_ТВ: фед каналы','AHR','Support',10,3), + (274,'AHR','R061020032','205920065','Рекл_ТВ: рег каналы','AHR','Support',10,3), + (275,'AHR','R061020033','206020066','Рекл_Интернет','AHR','Support',10,3), + (276,'AHR','R061020034','206120067','Рекл на радио','AHR','Support',10,3), + (277,'AHR','R061020035','206220068','Рекл в прессе','AHR','Support',10,3), + (278,'AHR','R061020036','206320069','Наружная реклама','AHR','Support',10,3), + (279,'AHR','R061020037',NULL,'Рекламная полиграфическая продукция','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (280,'AHR','R061020037','403740190','Н/год полиграф проМЦ','AHR','Support',10,3), + (281,'AHR','R061020037','403740191','Рекл полиграф продМЦ','AHR','Support',10,3), + (282,'AHR','R061020037','403740192','Полиграф прод д/соМЦ','AHR','Support',10,3), + (283,'AHR','R061020037','403740193','Полигр прод д/АПП МЦ','AHR','Support',10,3), + (284,'AHR','R061020037','403740194','Др полиграф прод МЦ','AHR','Support',10,3), + (285,'AHR','R061020038','206520070','Орг выстав деят-ти','AHR','Support',10,3), + (286,'AHR','R061020039',NULL,'Рекламная сувенирная продукция','AHR','Support',10,3), + (287,'AHR','R061020039','403240109','Новог сувенир продМЦ','AHR','Support',10,3), + (288,'AHR','R061020039','403240110','Рекл сувенир прод МЦ','AHR','Support',10,3), + (289,'AHR','R061020039','403240111','Сувенир прод д/сотМЦ','AHR','Support',10,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (290,'AHR','R061020039','403240112','Суверн прод д/АПП МЦ','AHR','Support',10,3), + (291,'AHR','R061020040','403640189','Др рекл-инф осн подр','AHR','Support',10,3), + (292,'AHR','R061020041','206820071','Др расх на рекламу','AHR','Support',10,3), + (293,'AHR','R061031001','206920072','Аренда_ОН','AHR','Support',13,3), + (294,'AHR','R061031012','207020073','Аренда_земля','AHR','Support',13,3), + (295,'AHR','R061031005','207120074','Аренда_АТС','AHR','Support',13,3), + (296,'AHR','R061031010',NULL,'Аренда: прочее технологическое оборудование','AHR','Support',13,3), + (297,'AHR','R061031010','207220075','Аренда_пр.техн.обор','AHR','Support',13,3), + (298,'AHR','R061031010','207220076','Аренда_ЦОД','AHR','Support',13,3), + (299,'AHR','R061031011',NULL,'Аренда: прочее имущество','AHR','Support',13,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (300,'AHR','R061031011','207320077','Аренда_оборуд ИТ','AHR','Support',13,3), + (301,'AHR','R061031011','207320078','Аренда_др об/пр имущ','AHR','Support',13,3), + (302,'AHR','R011031001',NULL,'Доходы от аренды/субаренды объектов недвижимости','AHR','Support',14,3), + (303,'AHR','R011031005',NULL,'Доход от аренды/субаренды транспорта','AHR','Support',14,3), + (304,'AHR','R011031005','500120001','Аренда дох_АТС','AHR','Support',14,3), + (305,'AHR','R011031005','500320003','Доход от субаренды транспорта','AHR','Support',14,3), + (306,'AHR','R011031011',NULL,'Доход от аренды/субаренды прочего имущества','AHR','Support',14,3), + (307,'AHR','R011031011','500220002','Аренда дох_др. обор','AHR','Support',14,3), + (308,'AHR','R011031011','500420004','Доход от субаренды прочего имущества','AHR','Support',14,3), + (309,'AHR','R061042005','209020082','Аутсорс трансп услуг','AHR','Support',17,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (310,'AHR','R061042005','209020083','Услуги такси','AHR','Support',17,3), + (311,'AHR','R061043005','209220085','ТО:Регламент ТО АТС','AHR','Support',18,3), + (312,'AHR','R061043005','209220086','ТО:Проч ТО АТС','AHR','Support',18,3), + (313,'AHR','R061044005','209320087','Услуги стоянки','AHR','Support',19,3), + (314,'AHR','R061044005','209320088','Услуги мойки','AHR','Support',19,3), + (387,'AHR','R061076000','402040187','ВывескиМЦ','AHR','Support',46,3), + (315,'AHR','R061044005','209320089','Услуги по сопр/слеж','AHR','Support',19,3), + (316,'AHR','R061044005','209320090','Госпошл/пр расх АТС','AHR','Support',19,3), + (317,'AHR','R061044005','209320091','Платные дороги','AHR','Support',19,3), + (318,'AHR','R061045005','209120084','ТО:Предрейс ТО АТС','AHR','Support',19,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (319,'AHR','R061045031',NULL,'Компенсация работникам за использование личного транспорта в служебных целях','AHR','Support',20,3), + (320,'AHR','R061045032',NULL,'Такси водителям','AHR','Support',20,3), + (321,'AHR','R061045033','209620093','Др расх на водителей','AHR','Support',20,3), + (322,'AHR','R061047005','401240090','Шины МЦ','AHR','Support',22,3), + (323,'AHR','R061047005','401240091','Диски МЦ','AHR','Support',22,3), + (324,'AHR','R061047005','401240092','Незамерз жид-ть МЦ','AHR','Support',22,3), + (325,'AHR','R061047005','401240199','Др матер АТС МЦ','AHR','Support',22,3), + (326,'AHR','R061051001','209820094','Комм расх_теплоснабж','AHR','Support',24,3), + (327,'AHR','R061051001','209820095','Комм расх_водоснабж','AHR','Support',24,3), + (328,'AHR','R061051001','209820096','Комм расх_электр','AHR','Support',24,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (329,'AHR','R061051001','209820097','Комм расх_сточн воды','AHR','Support',24,3), + (330,'AHR','R061051001','209820098','Возмещ комм расх','AHR','Support',24,3), + (331,'AHR','R061052001','209920099','Клининг','AHR','Support',25,3), + (332,'AHR','R061052001','209920100','Санитарная обработка','AHR','Support',25,3), + (333,'AHR','R061052001','209920101','Дезинфекция','AHR','Support',25,3), + (334,'AHR','R061052001','209920102','Дезинсекция','AHR','Support',25,3), + (335,'AHR','R061053001','210020103','ТО/эксп_ОН','AHR','Support',26,3), + (336,'AHR','R061053008','210120104','ТО/эксп_инж.сист.ОН','AHR','Support',26,3), + (337,'AHR','R061053007','210320106','ТО/эксп_пр.обор.ОН','AHR','Support',26,3), + (338,'AHR','R061054001','210420107','Благ-во и озеленение','AHR','Support',27,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (339,'AHR','R061055001','210520108','Вывоз ТБО','AHR','Support',28,3), + (340,'AHR','R061055001','210520109','Возмещ пр расходов','AHR','Support',28,3), + (341,'AHR','R061055001','210520110','Госпошл/пр расх ОН','AHR','Support',28,3), + (342,'AHR','R061056001','210620111','Ремонт: ОН','AHR','Support',29,3), + (343,'AHR','R061056007','210720114','Ремонт: пр.обор.ОН','AHR','Support',29,3), + (344,'AHR','R061056008','210820112','Ремонт: инж.сист.ОН','AHR','Support',29,3), + (345,'AHR','R061057031','403140094','Сан-гиг и хоз принад','AHR','Support',30,3), + (346,'AHR','R061057001','401340096','Топливо для недв','AHR','Support',30,3), + (347,'AHR','R061063000','211320117','Услуги физической охраны инкассаторских перевозок в рамках текущей деятельности (ЧОП/ЧОО) СИ ОД ГО _ Есть 2200','AHR','Support',34,3), + (348,'AHR','R061068009','401040082','СОПиТС МЦ','AHR','Support',39,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (349,'AHR','R061068009','401040083','СОТ МЦ','AHR','Support',39,3), + (350,'AHR','R061068009','401040084','СКУД МЦ','AHR','Support',39,3), + (351,'AHR','R061068009','401040085','СУД АТС МЦ','AHR','Support',39,3), + (352,'AHR','R061068009','401040086','СБиОДТС МЦ','AHR','Support',39,3), + (353,'AHR','R061068009','401040087','ЛВС инж-тех ср охрМЦ','AHR','Support',39,3), + (354,'AHR','R061068009','401040088','Прочие сист охрОН МЦ','AHR','Support',39,3), + (355,'AHR','R061068009','401040157','Системы охр_прочиеМЦ','AHR','Support',39,3), + (356,'AHR','R061074000','401740077','Бандероли','AHR','Support',44,3), + (357,'AHR','R061074000','401740078','Др. матер для касс','AHR','Support',44,3), + (358,'AHR','R061074000','401840075','Баулы','AHR','Support',44,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (359,'AHR','R061074000','401840076','Сумки для денег','AHR','Support',44,3), + (360,'AHR','R061075000','401940138','Банк/опер стойкиМЦ','AHR','Support',45,3), + (361,'AHR','R061075000','401940125','Бенч-системыМЦ','AHR','Support',45,3), + (362,'AHR','R061075000','401940132','ГардеробыМЦ','AHR','Support',45,3), + (363,'AHR','R061075000','401940126','ДиваныМЦ','AHR','Support',45,3), + (364,'AHR','R061075000','401940140','Другие виды мебелиМЦ','AHR','Support',45,3), + (365,'AHR','R061075000','401940144','КартотекиМЦ','AHR','Support',45,3), + (366,'AHR','R061075000','401940127','КреслаМЦ','AHR','Support',45,3), + (367,'AHR','R061075000','401940139','Кухонная мебельМЦ','AHR','Support',45,3), + (368,'AHR','R061075000','401940136','Приставки д/столовМЦ','AHR','Support',45,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (369,'AHR','R061075000','401940141','Сейфы_0 кл уст-тиМЦ','AHR','Support',45,3), + (370,'AHR','R061075000','401940142','Сейфы_1 кл уст-тиМЦ','AHR','Support',45,3), + (371,'AHR','R061075000','401940146','СтеллажиМЦ','AHR','Support',45,3), + (372,'AHR','R061075000','401940135','Столы журнальныеМЦ','AHR','Support',45,3), + (373,'AHR','R061075000','401940130','Столы переговорнМЦ','AHR','Support',45,3), + (374,'AHR','R061075000','401940124','Столы рабочиеМЦ','AHR','Support',45,3), + (375,'AHR','R061075000','401940128','Стулья офисныеМЦ','AHR','Support',45,3), + (376,'AHR','R061075000','401940129','Стулья переговорнМЦ','AHR','Support',45,3), + (377,'AHR','R061075000','401940131','ТумбыМЦ','AHR','Support',45,3), + (378,'AHR','R061075000','401940145','Шкафы архивныеМЦ','AHR','Support',45,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (379,'AHR','R061075000','401940133','Шкафы для докумМЦ','AHR','Support',45,3), + (380,'AHR','R061075000','401940134','Шкафы для одеждыМЦ','AHR','Support',45,3), + (381,'AHR','R061075000','401940143','Шкафы металлМЦ','AHR','Support',45,3), + (382,'AHR','R061076000','402040196','Бронежиле д/инкассМЦ','AHR','Support',46,3), + (383,'AHR','R061076000','402040160','БрошюровщикиМЦ','AHR','Support',46,3), + (384,'AHR','R061076000','402040161','Бумагосвер машиныМЦ','AHR','Support',46,3), + (385,'AHR','R061076000','402040177','ВентиляторыМЦ','AHR','Support',46,3), + (386,'AHR','R061076000','402040173','ВидеокамерыМЦ','AHR','Support',46,3), + (388,'AHR','R061076000','402040163','ДиктофоныМЦ','AHR','Support',46,3), + (389,'AHR','R061076000','402040178','Др быт техникаМЦ','AHR','Support',46,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (390,'AHR','R061076000','402040106','Др оборуд/ матер МЦ','AHR','Support',46,3), + (391,'AHR','R061076000','402040166','Др офисная технМЦ','AHR','Support',46,3), + (392,'AHR','R061076000','402040186','Другие матер МЦ','AHR','Support',46,3), + (393,'AHR','R061076000','402040183','ЗеркалаМЦ','AHR','Support',46,3), + (394,'AHR','R061076000','402040184','Ковровое покрытиеМЦ','AHR','Support',46,3), + (395,'AHR','R061076000','402040179','Комплекты шторМЦ','AHR','Support',46,3), + (396,'AHR','R061076000','402040171','КондиционерыМЦ','AHR','Support',46,3), + (397,'AHR','R061076000','402040176','КофемашиныМЦ','AHR','Support',46,3), + (398,'AHR','R061076000','402040162','ЛаминаторыМЦ','AHR','Support',46,3), + (399,'AHR','R061076000','402040174','ЛампыМЦ','AHR','Support',46,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (400,'AHR','R061076000','402040181','НасосыМЦ','AHR','Support',46,3), + (401,'AHR','R061076000','402040159','Переплетн системыМЦ','AHR','Support',46,3), + (402,'AHR','R061076000','402040104','Пред Инте и диз-наМЦ','AHR','Support',46,3), + (403,'AHR','R061076000','402040102','Приборы МЦ','AHR','Support',46,3), + (404,'AHR','R061076000','402040103','Произв. искусства МЦ','AHR','Support',46,3), + (405,'AHR','R061076000','402040175','ПылесосыМЦ','AHR','Support',46,3), + (406,'AHR','R061076000','402040169','Ресиверы/РекодерыМЦ','AHR','Support',46,3), + (407,'AHR','R061076000','402040101','Спец. одеж и обувьМЦ','AHR','Support',46,3), + (408,'AHR','R061076000','402040164','СтеплерыМЦ','AHR','Support',46,3), + (409,'AHR','R061076000','402040188','Табло валютМЦ','AHR','Support',46,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (410,'AHR','R061076000','402040165','Уничтожители бумагМЦ','AHR','Support',46,3), + (411,'AHR','R061076000','402040195','Форма д/инкасс рабМЦ','AHR','Support',46,3), + (412,'AHR','R061076000','402040172','ФотоаппаратыМЦ','AHR','Support',46,3), + (413,'AHR','R061076000','402040158','Франкиров машиныМЦ','AHR','Support',46,3), + (414,'AHR','R061076000','402040100','Хозинвентарь МЦ','AHR','Support',46,3), + (415,'AHR','R061076000','402040170','ХолодильникиМЦ','AHR','Support',46,3), + (416,'AHR','R061076000','402040185','ЧасыМЦ','AHR','Support',46,3), + (417,'AHR','R061076000','402040182','Элект инструментМЦ','AHR','Support',46,3), + (418,'AHR','R061076000','402040180','Эндоскопы технМЦ','AHR','Support',46,3), + (419,'AHR','R061077000','402240117','Бланки','AHR','Support',47,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (420,'AHR','R061077000','402240122','Вода питьевая бутил','AHR','Support',47,3), + (421,'AHR','R061077000','402140123','З/ч -ремонт др имущ','AHR','Support',47,3), + (422,'AHR','R061077000','402240115','Марки','AHR','Support',47,3), + (423,'AHR','R061077000','402240116','Открытки','AHR','Support',47,3), + (424,'AHR','R061077000','402240107','Типог расх д/виз-ок','AHR','Support',47,3), + (425,'AHR','R061077000','402240121','Цветы','AHR','Support',47,3), + (426,'AHR','R061077000','402340108','Штампы','AHR','Support',47,3), + (427,'AHR','R061081005','211620120','Страх АТС_КАСКО','AHR','Support',49,3), + (428,'AHR','R061081001','211720121','Страх ОН','AHR','Support',49,3), + (429,'AHR','R061081003','211820122','Страх УС и нал-ти','AHR','Support',49,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (430,'AHR','R061081031','211920123','Страх нал-ти_касса','AHR','Support',49,3), + (431,'AHR','R061081032','212020124','Страх нал-ти_инкасс','AHR','Support',49,3), + (432,'AHR','R061081011',NULL,'Страхование: прочее имущество','AHR','Support',49,3), + (433,'AHR','R061081011','212120125','Страхование витрин','AHR','Support',49,3), + (434,'AHR','R061081011','212120126','Страх пр.имущ.','AHR','Support',49,3), + (435,'AHR','R061082031','212220127','Добр мед страх перс','AHR','Support',50,3), + (436,'AHR','R061082032','212320128','Страх перс_несч сл','AHR','Support',50,3), + (437,'AHR','R061082033','212420129','Др страх перс','AHR','Support',50,3), + (438,'AHR','R061083005','212520130','Страх отв АТС_ОСАГО','AHR','Support',51,3), + (439,'AHR','R061083000','212620131','Др страх гр отв-ти','AHR','Support',51,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (440,'AHR','R061092000','212820133','Консульт услуги','AHR','Support',54,3), + (441,'AHR','R061092000','212820137','Инф усл тех развитие','AHR','Support',54,3), + (442,'AHR','R061092000','212820138','Конс усл_цифр реш','AHR','Support',54,3), + (443,'AHR','R061092000','212820139','Аудит систем ИБ','AHR','Support',54,3), + (444,'AHR','R061094000','213020135','Информ услуги','AHR','Support',56,3), + (445,'AHR','R061094000','213020136','Маркетинг услуги','AHR','Support',56,3), + (446,'AHR','R061100000','213420143','Агент возн_ком расх','AHR','Support',59,3), + (447,'AHR','R061110031','213520144','Благ-ть_образование','AHR','Support',59,3), + (448,'AHR','R061110032','213620145','Благ-ть_соц под/НКО','AHR','Support',59,3), + (449,'AHR','R061110033','213720146','Благ-ть_дух-нр восп','AHR','Support',59,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (450,'AHR','R061110034','213820147','Благ-ть_спорт/физ-ра','AHR','Support',59,3), + (451,'AHR','R061110035','213920148','Благ-ть_ООС/защ жив','AHR','Support',59,3), + (452,'AHR','R061110036','214020149','Благ-ть_резерв','AHR','Support',59,3), + (453,'AHR','R061120031','214120150','Спонс-во_фед мер-я','AHR','Support',59,3), + (454,'AHR','R061120032','214220151','Спонс-во_рег проекты','AHR','Support',59,3), + (455,'AHR','R061120033','214320152','Спонс-во_отрасл СМИ','AHR','Support',59,3), + (456,'AHR','R061120034','214420153','Спонс-во_резерв','AHR','Support',59,3), + (457,'AHR','R061131031','214520154','Корп обуч_программы','AHR','Support',64,3), + (458,'AHR','R061131032','214620155','Корп обуч_сем-совещ','AHR','Support',64,3), + (459,'AHR','R061132031','214720156','Сем/курсы/повыш квал','AHR','Support',65,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (460,'AHR','R061132032','214820157','Конференции/форумы','AHR','Support',65,3), + (461,'AHR','R061141000','214920158','Участие в Ассоциации НДО','AHR','Support',67,3), + (462,'AHR','R061142000','215020159','Подписка_издания','AHR','Support',68,3), + (463,'AHR','R061142031','402440118','Книги МЦ','AHR','Support',68,3), + (464,'AHR','R061142031','402440119','Брошюры МЦ','AHR','Support',68,3), + (465,'AHR','R061142031','402440120','Справочные материаМЦ','AHR','Support',68,3), + (466,'AHR','R061143031','215120160','Госпошлина','AHR','Support',69,3), + (467,'AHR','R061143032','215220161','Нотар оформл докум','AHR','Support',69,3), + (468,'AHR','R061143033','215320162','Судебные издержки','AHR','Support',69,3), + (469,'AHR','R061144000','215420163','Пер-ка валют цен_АХР','AHR','Support',70,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (470,'AHR','R061145000','215520164','Пер-ка МЦ_транс усл','AHR','Support',71,3), + (471,'AHR','R061147000','215620165','Представит расходы','AHR','Support',73,3), + (472,'AHR','R061147031','403340113','Представительские МЦ','AHR','Support',73,3), + (473,'AHR','R061148000','215720166','Аттестация раб мест','AHR','Support',74,3), + (474,'AHR','R061148000','215720167','ПрПольз_интел соб-ть','AHR','Support',74,3), + (475,'AHR','R061148000','215720168','Пересертиф сейф обор','AHR','Support',74,3), + (476,'AHR','R061148000','215720169','Другие АХР','AHR','Support',74,3), + (477,'AHR','R061156019','403540147','Материалы к НВНОД МЦ','AHR','Support',81,3), + (478,'AHR','R061159019','216220174','Др расходы_НВНОД','AHR','Support',81,3), + (479,'AHR','R061171001',NULL,'Расходы по выбытию: объекты недвижимости','AHR','Support',83,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (480,'AHR','R061171012',NULL,'Расходы по выбытию: земля','AHR','Support',83,3), + (481,'AHR','R061171005',NULL,'Расходы по выбытию: транспорт','AHR','Support',83,3), + (482,'AHR','R061171002',NULL,'Расходы по выбытию: ИТ-оборудование','AHR','Support',83,3), + (483,'AHR','R061171004',NULL,'Расходы по выбытию: кассовое оборудование','AHR','Support',83,3), + (484,'AHR','R061171003',NULL,'Расходы по выбытию: устройства самообслуживания','AHR','Support',83,3), + (485,'AHR','R061171006',NULL,'Расходы по выбытию: оборудование информационной безопасности','AHR','Support',83,3), + (486,'AHR','R061171010',NULL,'Расходы по выбытию: прочее технологическое оборудование','AHR','Support',83,3), + (487,'AHR','R061171007',NULL,'Расходы по выбытию: прочее оборудование объектов недвижимости','AHR','Support',83,3), + (488,'AHR','R061171008',NULL,'Расходы по выбытию: инженерные системы объектов недвижимости','AHR','Support',83,3), + (489,'AHR','R061171009',NULL,'Расходы по выбытию: оборудование систем охраны объектов недвижимости','AHR','Support',83,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (490,'AHR','R061171011',NULL,'Расходы по выбытию: прочее имущество','AHR','Support',83,3), + (491,'AHR','R061171019',NULL,'Расходы по выбытию: НВНОД','AHR','Support',83,3), + (492,'AHR','R061172021',NULL,'Расходы по выбытию: бизнес ПО','AHR','Support',84,3), + (493,'AHR','R061172022',NULL,'Расходы по выбытию: пользовательское ПО','AHR','Support',84,3), + (494,'AHR','R061172023',NULL,'Расходы по выбытию: инфраструктурное ПО','AHR','Support',84,3), + (495,'AHR','R061172024',NULL,'Расходы по выбытию: ПО информационной безопасности','AHR','Support',84,3), + (496,'AHR','R061172025',NULL,'Расходы по выбытию: прочие НМА','AHR','Support',84,3), + (497,'AHR','R061191001',NULL,'Расходы по переоценке: объекты недвижимости','AHR','Support',86,3), + (498,'AHR','R061191005',NULL,'Расходы по переоценке: транспорт','AHR','Support',86,3), + (499,'AHR','R061191011',NULL,'Расходы по переоценке: прочее имущество','AHR','Support',86,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (500,'AHR','R061191019',NULL,'Расходы по переоценке: НВНОД','AHR','Support',86,3), + (501,'AHR','R061192025',NULL,'Расходы по переоценке: прочие НМА','AHR','Support',87,3), + (502,'CAP','3.00.0.',NULL,'Затраты капитального характера (капитальные вложения)','CAP','Support',NULL,0), + (503,'CAP','3.01.0.',NULL,'Капитальные вложения в объекты недвижимости','CAP','Support',502,1), + (504,'CAP','3.01.1.',NULL,'Приобретение: объекты недвижимости','CAP','Support',503,2), + (505,'CAP','3.01.2.',NULL,'Реконструкция, модернизация: объекты недвижимости','CAP','Support',503,2), + (506,'CAP','3.01.3.',NULL,'Разработка проектной документации: объекты недвижимости','CAP','Support',503,2), + (507,'CAP','3.01.4.',NULL,'Строительство: объекты недвижимости','CAP','Support',503,2), + (508,'CAP','3.02.0.',NULL,'Капитальные вложения в прочее оборудование объектов недвижимости','CAP','Support',502,1), + (509,'CAP','3.03.0.',NULL,'Капитальные вложения в оборудование инженерных систем объектов недвижимости','CAP','Support',502,1); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (510,'CAP','3.04.0.',NULL,'Капитальные вложения в оборудование систем охраны объектов недвижимости','CAP','Support',502,1), + (511,'CAP','3.05.0.',NULL,'Капитальные вложения в автотранспорт','CAP','Support',502,1), + (512,'CAP','3.06.0.',NULL,'Капитальные вложения в ИТ-оборудование','CAP','Support',502,1), + (513,'CAP','3.07.0.',NULL,'Капитальные вложения в кассовое оборудование','CAP','Support',502,1), + (514,'CAP','3.08.0.',NULL,'Капитальные вложения в устройства самообслуживания','CAP','Support',502,1), + (515,'CAP','3.08.1.',NULL,'Приобретение: устройства самообслуживания','CAP','Support',514,2), + (516,'CAP','3.08.2.',NULL,'Перевозка_УС','CAP','Support',514,2), + (517,'CAP','3.09.0.',NULL,'Капитальные вложения в оборудование информационной безопасности','CAP','Support',502,1), + (518,'CAP','3.10.0.',NULL,'Капитальные вложения в прочее технологическое оборудование','CAP','Support',502,1), + (519,'CAP','3.11.0.',NULL,'Капитальные вложения в прочее имущество','CAP','Support',502,1); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (520,'CAP','3.12.0.',NULL,'Капитальные вложения в землю','CAP','Support',502,1), + (521,'CAP','3.13.0.',NULL,'Вложения в бизнес-ПО','CAP','Support',502,1), + (522,'CAP','3.14.0.',NULL,'Вложения в пользовательское ПО','CAP','Support',502,1), + (523,'CAP','3.15.0.',NULL,'Вложения в инфраструктурное ПО','CAP','Support',502,1), + (524,'CAP','3.16.0.',NULL,'Вложения в ПО информационной безопасности','CAP','Support',502,1), + (525,'CAP','3.17.0.',NULL,'Вложения в прочие НМА','CAP','Support',502,1), + (526,'CAP','R063011001','300030059','Здан/пом_ГО/ССП','CAP','Support',504,3), + (527,'CAP','R063011001','300030060','Здан/пом_ЦО РФ','CAP','Support',504,3), + (528,'CAP','R063011001','300030061','Здан/пом_ВСП РФ','CAP','Support',504,3), + (529,'CAP','R063011001','300030062','Здан/пом_размещ ЦДА','CAP','Support',504,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (530,'CAP','R063011001','300030063','Гаражи','CAP','Support',504,3), + (531,'CAP','R063011001','300030064','Парковочные места','CAP','Support',504,3), + (532,'CAP','R063011001','300030065','Склады','CAP','Support',504,3), + (533,'CAP','R063011001','300030067','Непроизв здан/сооруж','CAP','Support',504,3), + (534,'CAP','R063011001','300030066','Др. здан/сооруж','CAP','Support',504,3), + (535,'CAP','R063012001','220030001','Рек/мод_ОН','CAP','Support',505,3), + (536,'CAP','R063013001','220130002','Разр ПроектДок_ОН','CAP','Support',506,3), + (537,'CAP','R063014001','220230003','Строительство_ОН','CAP','Support',507,3), + (538,'CAP','R063021007','300630091','Банк/опер/рес стойки','CAP','Support',507,3), + (539,'CAP','R063021007','300630080','Бенч-системы','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (540,'CAP','R063021007','300630137','Брошюровщики','CAP','Support',507,3), + (541,'CAP','R063021007','300630138','Бумагосверли. машины','CAP','Support',507,3), + (542,'CAP','R063021007','300630154','Вентиляторы','CAP','Support',507,3), + (543,'CAP','R063021007','300630150','Видеокамеры','CAP','Support',507,3), + (544,'CAP','R063021007','300630087','Гардеробы','CAP','Support',507,3), + (545,'CAP','R063021007','300630081','Диваны','CAP','Support',507,3), + (546,'CAP','R063021007','300630140','Диктофоны','CAP','Support',507,3), + (547,'CAP','R063021007','300630155','Др быт техника','CAP','Support',507,3), + (548,'CAP','R063021007','300630143','Др офис техн','CAP','Support',507,3), + (549,'CAP','R063021007','300630093','Другие виды мебели','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (550,'CAP','R063021007','300630097','Картотеки','CAP','Support',507,3), + (551,'CAP','R063021007','300630148','Кондиционеры','CAP','Support',507,3), + (552,'CAP','R063021007','300630153','Кофемашины','CAP','Support',507,3), + (553,'CAP','R063021007','300630082','Кресла','CAP','Support',507,3), + (554,'CAP','R063021007','300630092','Кухонная мебель','CAP','Support',507,3), + (555,'CAP','R063021007','300630139','Ламинаторы','CAP','Support',507,3), + (556,'CAP','R063021007','300630151','Лампы','CAP','Support',507,3), + (557,'CAP','R063021007','300630136','Переплетные системы','CAP','Support',507,3), + (558,'CAP','R063021007','300630090','Приставки для столов','CAP','Support',507,3), + (559,'CAP','R063021007','300630152','Пылесосы','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (560,'CAP','R063021007','300630146','Ресиверы/Рекодеры','CAP','Support',507,3), + (561,'CAP','R063021007','300630094','Сейфы (0 кл уст-ти)','CAP','Support',507,3), + (562,'CAP','R063021007','300630095','Сейфы (I кл уст-ти)','CAP','Support',507,3), + (563,'CAP','R063021007','300630099','Стеллажи','CAP','Support',507,3), + (564,'CAP','R063021007','300630141','Степлеры','CAP','Support',507,3), + (565,'CAP','R063021007','300630089','Столы журнальные','CAP','Support',507,3), + (566,'CAP','R063021007','300630085','Столы переговорные','CAP','Support',507,3), + (567,'CAP','R063021007','300630079','Столы рабочие','CAP','Support',507,3), + (568,'CAP','R063021007','300630083','Стулья офисные','CAP','Support',507,3), + (569,'CAP','R063021007','300630084','Стулья переговорн','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (570,'CAP','R063021007','300630086','Тумбы','CAP','Support',507,3), + (571,'CAP','R063021007','300630142','Уничтожители бумаг','CAP','Support',507,3), + (572,'CAP','R063021007','300630149','Фотоаппараты','CAP','Support',507,3), + (573,'CAP','R063021007','300630135','Франкироваль. машины','CAP','Support',507,3), + (574,'CAP','R063021007','300630147','Холодильники','CAP','Support',507,3), + (575,'CAP','R063021007','300630098','Шкафы архивные','CAP','Support',507,3), + (576,'CAP','R063021007','300630088','Шкафы для документов','CAP','Support',507,3), + (577,'CAP','R063021007','300630096','Шкафы металлические','CAP','Support',507,3), + (578,'CAP','R063031008','300730167','Инженерн сист ОН','CAP','Support',507,3), + (579,'CAP','R063041009','300830133','ЛВС инж-тех ср охран','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (580,'CAP','R063041009','300830134','Проч обор сист охр','CAP','Support',507,3), + (581,'CAP','R063041009','300830132','СБиОДТС','CAP','Support',507,3), + (582,'CAP','R063041009','300830130','СКУД','CAP','Support',507,3), + (583,'CAP','R063041009','300830128','СОПиТС','CAP','Support',507,3), + (584,'CAP','R063041009','300830129','СОТ','CAP','Support',507,3), + (585,'CAP','R063041009','300830131','СУД АТС','CAP','Support',507,3), + (586,'CAP','R063051005','300430072','АТС грузо-пассажирск','CAP','Support',507,3), + (587,'CAP','R063051005','300430070','АТС легковые','CAP','Support',507,3), + (588,'CAP','R063051005','300430073','АТС спец назначения','CAP','Support',507,3), + (589,'CAP','R063051005','300430071','АТС грузовые','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (590,'CAP','R063051005','300430074','Прицепы','CAP','Support',507,3), + (591,'CAP','R063051005','300430075','Доп Оборуд. Для АТС','CAP','Support',507,3), + (592,'CAP','R063061002','300130076','Автосекретари','CAP','Support',507,3), + (593,'CAP','R063061002','300130169','ВОЛС','CAP','Support',507,3), + (594,'CAP','R063061002','300130058','Дисковые массивы','CAP','Support',507,3), + (595,'CAP','R063061002','300130056','Другая вычислит тех.','CAP','Support',507,3), + (596,'CAP','R063061002','300130034','Инфраструкт системы','CAP','Support',507,3), + (597,'CAP','R063061002','300130051','Источники бесп питан','CAP','Support',507,3), + (598,'CAP','R063061002','300130028','Кабельные системы','CAP','Support',507,3), + (599,'CAP','R063061002','300130026','Коммутаторы','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (600,'CAP','R063061002','300130024','Комплектущ для серв оборуд','CAP','Support',507,3), + (601,'CAP','R063061002','300130014','Копиры','CAP','Support',507,3), + (602,'CAP','R063061002','300130045','Ленточные библиотеки','CAP','Support',507,3), + (603,'CAP','R063061002','300130027','Маршрутизаторы','CAP','Support',507,3), + (604,'CAP','R063061002','300130037','Модемы','CAP','Support',507,3), + (605,'CAP','R063061002','300130003','Моноблоки','CAP','Support',507,3), + (606,'CAP','R063061002','300130012','МФУ','CAP','Support',507,3), + (607,'CAP','R063061002','300130001','Ноутбуки','CAP','Support',507,3), + (608,'CAP','R063061002','300130047','Оборуд линии связи','CAP','Support',507,3), + (609,'CAP','R063061002','300130018','Оборудование безопасн.','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (610,'CAP','R063061002','300130008','Офисн мониторы','CAP','Support',507,3), + (611,'CAP','R063061002','300130002','Офисн сист блоки','CAP','Support',507,3), + (612,'CAP','R063061002','300130145','Плазмен. и ЖК панели','CAP','Support',507,3), + (613,'CAP','R063061002','300130006','Планшеты','CAP','Support',507,3), + (614,'CAP','R063061002','300130005','Портативн комп','CAP','Support',507,3), + (615,'CAP','R063061002','300130011','Принтеры','CAP','Support',507,3), + (616,'CAP','R063061002','300130016','Принтеры спец_ИТ','CAP','Support',507,3), + (617,'CAP','R063061002','300130053','Програм-аппарат комп','CAP','Support',507,3), + (618,'CAP','R063061002','300130057','Процессоры','CAP','Support',507,3), + (619,'CAP','R063061002','300130017','Прочая оргтехника','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (620,'CAP','R063061002','300130039','Прочее сетев оборуд','CAP','Support',507,3), + (621,'CAP','R063061002','300130048','Прочее телеком оборуд','CAP','Support',507,3), + (622,'CAP','R063061002','300130007','Прочие компьютеры','CAP','Support',507,3), + (623,'CAP','R063061002','300130010','Прочие мониторы','CAP','Support',507,3), + (624,'CAP','R063061002','300130055','Радиометки','CAP','Support',507,3), + (625,'CAP','R063061002','300130077','Радиостанции','CAP','Support',507,3), + (626,'CAP','R063061002','300130019','Сервер приложений','CAP','Support',507,3), + (627,'CAP','R063061002','300130022','Сервер специальный','CAP','Support',507,3), + (628,'CAP','R063061002','300130021','Сервер СУБД','CAP','Support',507,3), + (629,'CAP','R063061002','300130020','Сервер тест среды','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (630,'CAP','R063061002','300130023','Серверные шасси','CAP','Support',507,3), + (631,'CAP','R063061002','300130046','Сетевое комп оборуд','CAP','Support',507,3), + (632,'CAP','R063061002','300130035','Сист биллинга связи','CAP','Support',507,3), + (633,'CAP','R063061002','300130049','Системы голос почты','CAP','Support',507,3), + (634,'CAP','R063061002','300130013','Сканеры','CAP','Support',507,3), + (635,'CAP','R063061002','300130040','СХД тип 1','CAP','Support',507,3), + (636,'CAP','R063061002','300130041','СХД тип 2','CAP','Support',507,3), + (637,'CAP','R063061002','300130042','СХД тип 3','CAP','Support',507,3), + (638,'CAP','R063061002','300130043','СХД тип 4','CAP','Support',507,3), + (639,'CAP','R063061002','300130044','СХД другая','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (640,'CAP','R063061002','300130144','Телевизоры','CAP','Support',507,3), + (641,'CAP','R063061002','300130033','Телеграфные аппараты','CAP','Support',507,3), + (642,'CAP','R063061002','300130030','Телефонное оборуд','CAP','Support',507,3), + (643,'CAP','R063061002','300130031','Телефонные аппараты','CAP','Support',507,3), + (644,'CAP','R063061002','300130038','Телефонные станции','CAP','Support',507,3), + (645,'CAP','R063061002','300130032','Телефоны мобильные','CAP','Support',507,3), + (646,'CAP','R063061002','300130009','Технол мониторы','CAP','Support',507,3), + (647,'CAP','R063061002','300130004','Технол сист блоки','CAP','Support',507,3), + (648,'CAP','R063061002','300130036','Точки доступа','CAP','Support',507,3), + (649,'CAP','R063061002','300130029','Факс аппараты','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (650,'CAP','R063061002','300130052','Шифровальное оборуд','CAP','Support',507,3), + (651,'CAP','R063061002','300130025','Шкафы серв (телеком)','CAP','Support',507,3), + (652,'CAP','R063071004','300330113','Апп-т_опр под-ти','CAP','Support',507,3), + (653,'CAP','R063071004','300330103','Вак упак б-нот_1п','CAP','Support',507,3), + (654,'CAP','R063071004','300330104','Вак упак б-нот_2п','CAP','Support',507,3), + (655,'CAP','R063071004','300330105','Весы_мон/мер слитк','CAP','Support',507,3), + (656,'CAP','R063071004','300330106','Весы_мон/ст слитк','CAP','Support',507,3), + (657,'CAP','R063071004','300330111','Витрины_монеты_2кл','CAP','Support',507,3), + (658,'CAP','R063071004','300330107','Деп колонк_сейф яч','CAP','Support',507,3), + (659,'CAP','R063071004','300330117','Дозиметры','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (660,'CAP','R063071004','300330115','Др касс оборуд','CAP','Support',507,3), + (661,'CAP','R063071004','300330116','Конт-кассов машины','CAP','Support',507,3), + (662,'CAP','R063071004','300330114','Пневмопочта','CAP','Support',507,3), + (663,'CAP','R063071004','300330109','Сейфы_2 кл уст-ти','CAP','Support',507,3), + (664,'CAP','R063071004','300330110','Сейфы_3 кл уст-ти','CAP','Support',507,3), + (665,'CAP','R063071004','300330108','Сист к-ля мон/слитк','CAP','Support',507,3), + (666,'CAP','R063071004','300330112','Система технол ТВ','CAP','Support',507,3), + (667,'CAP','R063071004','300330101','Сч-сорт банкнот','CAP','Support',507,3), + (668,'CAP','R063071004','300330102','Сч-сорт монет','CAP','Support',507,3), + (669,'CAP','R063071004','300330054','Электронная очередь','CAP','Support',507,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (670,'CAP','R063081003','300230118','Банкоматы','CAP','Support',515,3), + (671,'CAP','R063081003','300230119','ИПТ','CAP','Support',515,3), + (672,'CAP','R063081003','300230120','ИПК','CAP','Support',515,3), + (673,'CAP','R063081003','300230121','АДМ','CAP','Support',515,3), + (674,'CAP','R063081003','300230122','Прочие УС','CAP','Support',515,3), + (675,'CAP','R063091006','301030078','Системы ИБ','CAP','Support',516,3), + (676,'CAP','R063101010','300930124','Оборуд персонализац','CAP','Support',516,3), + (677,'CAP','R063101010','300930126','Принтеры спец','CAP','Support',516,3), + (678,'CAP','R063101010','300930127','Проч техн обор_проч','CAP','Support',516,3), + (679,'CAP','R063101010','300930123','Счетчики карт','CAP','Support',516,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (680,'CAP','R063101010','300930125','Эмбоссеры','CAP','Support',516,3), + (681,'CAP','R063111011','301030166','Вывески','CAP','Support',516,3), + (682,'CAP','R063111011','301030161','Др хоз инвент','CAP','Support',516,3), + (683,'CAP','R063111011','301030165','Другие','CAP','Support',516,3), + (684,'CAP','R063111011','301030160','Зеркала','CAP','Support',516,3), + (685,'CAP','R063111011','301030162','Ковровое покрытие','CAP','Support',516,3), + (686,'CAP','R063111011','301030156','Комплекты штор','CAP','Support',516,3), + (687,'CAP','R063111011','301030158','Насосы','CAP','Support',516,3), + (688,'CAP','R063111011','301030163','Произвед. искусства','CAP','Support',516,3), + (689,'CAP','R063111011','301030168','Табло валют','CAP','Support',516,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (690,'CAP','R063111011','301030164','Часы','CAP','Support',516,3), + (691,'CAP','R063111011','301030159','Электр инструменты','CAP','Support',516,3), + (692,'CAP','R063111011','301030157','Эндоскопы техн','CAP','Support',516,3), + (693,'CAP','R063121012','301230068','Земельные участки','CAP','Support',516,3), + (694,'CAP','R063121012','301230069','Сервитут','CAP','Support',516,3), + (695,'CAP','R063131021',NULL,'Вложения в разработку внешними контрагентами: бизнес ПО','CAP','Support',516,3), + (696,'CAP','R063132121',NULL,'Вложения в разработку внутренними контрагентами: бизнес ПО','CAP','Support',516,3), + (697,'CAP','R063133021',NULL,'Приобретение лицензий: бизнес ПО','CAP','Support',516,3), + (698,'CAP','R063141022',NULL,'Вложения в разработку внешними контрагентами: пользовательское ПО','CAP','Support',516,3), + (699,'CAP','R063142122',NULL,'Вложения в разработку внутренними контрагентами: пользовательское ПО','CAP','Support',516,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (700,'CAP','R063143022',NULL,'Приобретение лицензий: пользовательское ПО','CAP','Support',516,3), + (701,'CAP','R063151023',NULL,'Вложения в разработку внешними контрагентами: инфраструктурное ПО','CAP','Support',516,3), + (702,'CAP','R063152123',NULL,'Вложения в разработку внутренними контрагентами: инфраструктурное ПО','CAP','Support',516,3), + (703,'CAP','R063153023',NULL,'Приобретение лицензий: инфраструктурное ПО','CAP','Support',516,3), + (704,'CAP','R063161024',NULL,'Вложения в разработку внешними контрагентами: ПО информационной безопасности','CAP','Support',516,3), + (705,'CAP','R063162124',NULL,'Вложения в разработку внутренними: ПО информационной безопасности','CAP','Support',516,3), + (706,'CAP','R063163024',NULL,'Приобретение лицензий: ПО информационной безопасности','CAP','Support',516,3), + (707,'CAP','R063171025',NULL,'Приобретение: прочие НМА','CAP','Support',516,3), + (708,'OPER','4.00.0.',NULL,'Прочие операционные расходы','OPER','Support',NULL,0), + (709,'OPER','4.01.0.',NULL,'Консультационные и информационные услуги, связанные с осуществлением банковских операций и сделок','OPER','Support',708,1); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (710,'OPER','4.02.0.',NULL,'Юридические услуги','OPER','Support',708,1), + (711,'OPER','4.03.0.',NULL,'Расходы на работу с проблемной задолженностью','OPER','Support',708,1), + (712,'OPER','4.04.0.',NULL,'Другие операционные расходы','OPER','Support',708,1), + (713,'OPER','4.04.1.',NULL,'Расходы на приобретение футляров, капсул для монет из драгоценных металлов','OPER','Support',712,2), + (714,'OPER','4.04.2.',NULL,'Бланк ден/чек книжМЦ','OPER','Support',712,2), + (715,'OPER','4.04.3.',NULL,'Бланки сбер книжекМЦ','OPER','Support',712,2), + (716,'OPER','4.04.4.',NULL,'Изготовление банковских карт_ТМЦ','OPER','Support',712,2), + (717,'OPER','4.04.5.',NULL,'Изгот банк карт','OPER','Support',712,2), + (718,'OPER','4.04.6.',NULL,'Пер-ка валют цен_ПрО','OPER','Support',712,2), + (719,'OPER','4.04.7.',NULL,'Госпошл_рег ипотеки','OPER','Support',712,2); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (720,'OPER','4.04.8.',NULL,'Услуги п/размещ УС','OPER','Support',712,2), + (721,'OPER','4.04.9.',NULL,'Нотариальные и пр. аналогичные расходы по бизнесу','OPER','Support',712,2), + (722,'OPER','4.04.10.',NULL,'Штрафы за нарушения условий хозяйственных договоров','OPER','Support',712,2), + (723,'OPER','4.04.11.',NULL,'Расходы по списанию недостач материальных ценностей','OPER','Support',712,2), + (724,'OPER','4.04.12.',NULL,'Расходы по списанию дебиторской задолженности','OPER','Support',712,2), + (725,'OPER','4.04.13.',NULL,'Расходы от передачи активов в доверительное управление','OPER','Support',712,2), + (726,'OPER','4.04.14.',NULL,'Другие операционные расходы','OPER','Support',712,2), + (727,'OPER','4.04.15.',NULL,'Другие операционные доходы','OPER','Support',712,2), + (728,'OPER','R064010031','221720180','Услуги БКИ и проч','OPER','Support',709,3), + (729,'OPER','R064010032','221820181','Услуги Росреестра','OPER','Support',709,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (730,'OPER','R064010033','221920182','Усл БиллЦентр, Город','OPER','Support',709,3), + (731,'OPER','R064010034','222020183','Др конс/инф усл_ПрОп','OPER','Support',709,3), + (732,'OPER','R064020000','222120184','Юридические услуги','OPER','Support',710,3), + (733,'OPER','R064030000','222220185','Расх_раб/проб задолж','OPER','Support',711,3), + (734,'OPER','R064040031','402910002','Капсулы_мон/слиткМЦ','OPER','Support',713,3), + (735,'OPER','R064040031','402910001','Футляры_мон/слиткМЦ','OPER','Support',713,3), + (736,'OPER','R064040032','402640197','Централизованная закупка бланков денежных чековых книжек для нужд региональных филиалов Банка','OPER','Support',714,3), + (737,'OPER','R064040034','403010003','Пластиковые карты МЦ','OPER','Support',716,3), + (738,'OPER','R064040034','403010004','Бланки ПИН-конвертМЦ','OPER','Support',716,3), + (739,'OPER','R064040034','403010005','Упак мат для картМЦ','OPER','Support',716,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (740,'OPER','R064040037','222720189','Др опер расходы','OPER','Support',726,3), + (741,'OPER','R064040037','222720191','Доверит упр активами','OPER','Support',726,3), + (742,'OPER','R064040037','222720192','Оценка акций','OPER','Support',726,3), + (743,'OPER','R064040037','222720194','Обязательная сертификация сейфов и хранилищ ценностей','OPER','Support',726,3), + (744,'OPER','R064040037','222720193','Таможенные сборы','OPER','Support',726,3), + (745,'OPER','R014040037',NULL,'Услуги Репозитария, предусмотренные Соглашением, заключенным с НКО ЗАО НРД 1347/РП от 22.03.2013г.','OPER','Support',727,3), + (746,'AHR','1.16.0.',NULL,'Амортизация по основным средствам и нематериальным активам','AHR','Support',1,1), + (747,'AHR','1.16.1.',NULL,'Амортизация основных средств','AHR','Support',746,2), + (748,'AHR','R061161008',NULL,'Амортизация: инженерные системы объектов недвижимости','AHR','Support',747,3), + (749,'AHR','R061161011',NULL,'Амортизация: прочее имущество','AHR','Support',747,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (750,'AHR','R061161006',NULL,'Амортизация: оборудование информационной безопасности','AHR','Support',747,3), + (751,'AHR','R061161009',NULL,'Амортизация: оборудование систем охраны объектов недвижимости','AHR','Support',747,3), + (752,'AHR','R061161001',NULL,'Амортизация: объекты недвижимости','AHR','Support',747,3), + (753,'AHR','R061161004',NULL,'Амортизация: кассовое оборудование','AHR','Support',747,3), + (754,'AHR','R061161002',NULL,'Амортизация: ИТ-оборудование','AHR','Support',747,3), + (755,'AHR','R061161019',NULL,'Амортизация: НВНОД','AHR','Support',747,3), + (756,'AHR','R061161010',NULL,'Амортизация: прочее технологическое оборудование','AHR','Support',747,3), + (757,'AHR','R061161003',NULL,'Амортизация: устройства самообслуживания','AHR','Support',747,3), + (758,'AHR','R061161005',NULL,'Амортизация: транспорт','AHR','Support',747,3), + (759,'AHR','R061161007',NULL,'Амортизация прочего оборудования объектов недвижимости','AHR','Support',747,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (760,'AHR','1.16.2.',NULL,'Амортизация нематериальных активов','AHR','Support',746,2), + (761,'AHR','R061162025',NULL,'Амортизация: прочие НМА','AHR','Support',760,3), + (762,'AHR','R061162022',NULL,'Амортизация: пользовательское ПО','AHR','Support',760,3), + (763,'AHR','R061162023',NULL,'Амортизация: инфраструктурное ПО','AHR','Support',760,3), + (764,'AHR','R061162021',NULL,'Амортизация: бизнес ПО','AHR','Support',760,3), + (765,'AHR','R061162024',NULL,'Амортизация: ПО информационной безопасности','AHR','Support',760,3), + (766,'AHR','5.00.0.',NULL,'Налоги и сборы, относимые на расходы в соответствии с законодательством РФ','AHR','Support',NULL,0), + (767,'AHR','R065050005',NULL,'Транспортный налог','AHR','Support',766,1), + (768,'AHR','R065090000',NULL,'Налог на дивиденды, полученные за участие в ассоциированном членстве в СХПК','AHR','Support',766,1), + (769,'AHR','R065100000',NULL,'Другие налоги','AHR','Support',766,1); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (770,'AHR','R065070000',NULL,'Плата за невыполнение условий квотирования рабочих мест','AHR','Support',766,1), + (771,'AHR','R065030012',NULL,'Земельный налог','AHR','Support',766,1), + (772,'AHR','R065010000',NULL,'НДС уплаченный по материальным запасам, оказанным услугам и ценностям','AHR','Support',766,1), + (773,'AHR','R065020001',NULL,'Налог на имущество','AHR','Support',766,1), + (774,'AHR','R065040000',NULL,'Плата за загрязнение окружающей среды','AHR','Support',766,1), + (775,'AHR','R061012025',NULL,'Внедрение и доработка внешними контрагентами: ПО информационной безопасности','AHR',NULL,4,3), + (776,'AHR','R061041005',NULL,'ГСМ','AHR',NULL,16,3), + (777,'AHR','R061046005',NULL,'Ремонт: АТС','AHR',NULL,21,3), + (778,'AHR','R061061001',NULL,'Физическая охр.ОН','AHR',NULL,32,3), + (779,'AHR','R061062001',NULL,'Пультовая охр.ОН','AHR',NULL,33,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (780,'AHR','R061064000',NULL,'Охр.имущ. п/транс-ке','AHR',NULL,35,3), + (781,'AHR','R061065003',NULL,'Пультовая охр.УС','AHR',NULL,36,3), + (784,'AHR','R061071000',NULL,'Бумага','AHR',NULL,41,3), + (785,'AHR','R061072000',NULL,'Канцтовары','AHR',NULL,42,3), + (786,'AHR','R061073000',NULL,'Архивный короб','AHR',NULL,43,3), + (787,'AHR','R061091000',NULL,'Аудиторские услуги','AHR',NULL,53,3), + (788,'AHR','R061093000',NULL,'Рейтинговые услуги','AHR',NULL,55,3), + (789,'AHR','R061095001',NULL,'Оценка ОН','AHR',NULL,57,3), + (790,'AHR','R061096000',NULL,'Оценка пр активов','AHR',NULL,58,3), + (791,'AHR','R061097000',NULL,'Исслед и разработки','AHR',NULL,59,3); +INSERT INTO v3.expense_item (id,section_code,item_id,num_group_id,"name",sheet,direction,parent_id,"depth") VALUES + (792,'AHR','R061146000',NULL,'СИЗ для персонала','AHR',NULL,71,3), + (793,'AHR','R061151019',NULL,'Комм усл и сод_НВНОД','AHR',NULL,76,3), + (794,'AHR','R061152019',NULL,'Ремонт_НВНОД','AHR',NULL,77,3), + (795,'AHR','R061153019',NULL,'Охрана_НВНОД','AHR',NULL,78,3), + (796,'AHR','R061154019',NULL,'Орг торгов_НВНОД','AHR',NULL,79,3), + (797,'AHR','R061155019',NULL,'Оценка объект_НВНОД','AHR',NULL,80,3), + (798,'CAP','R063085003',NULL,'Перевозка_УС','CAP',NULL,516,3); +SELECT setval('v3.expense_item_id_seq', 798); + + +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (1,'FORM_1'), + (2,'FORM_1'), + (3,'FORM_1'), + (4,'FORM_1'), + (5,'FORM_1'), + (6,'FORM_1'), + (7,'FORM_1'), + (8,'FORM_1'), + (9,'FORM_1'), + (10,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (11,'FORM_1'), + (12,'FORM_1'), + (13,'FORM_1'), + (14,'FORM_1'), + (15,'FORM_1'), + (16,'FORM_1'), + (17,'FORM_1'), + (18,'FORM_1'), + (19,'FORM_1'), + (20,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (21,'FORM_1'), + (22,'FORM_1'), + (23,'FORM_1'), + (24,'FORM_1'), + (25,'FORM_1'), + (26,'FORM_1'), + (27,'FORM_1'), + (28,'FORM_1'), + (29,'FORM_1'), + (30,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (31,'FORM_1'), + (32,'FORM_1'), + (33,'FORM_1'), + (34,'FORM_1'), + (35,'FORM_1'), + (36,'FORM_1'), + (37,'FORM_1'), + (38,'FORM_1'), + (39,'FORM_1'), + (40,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (41,'FORM_1'), + (42,'FORM_1'), + (43,'FORM_1'), + (44,'FORM_1'), + (45,'FORM_1'), + (46,'FORM_1'), + (47,'FORM_1'), + (48,'FORM_1'), + (49,'FORM_1'), + (50,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (51,'FORM_1'), + (52,'FORM_1'), + (53,'FORM_1'), + (54,'FORM_1'), + (55,'FORM_1'), + (56,'FORM_1'), + (57,'FORM_1'), + (58,'FORM_1'), + (59,'FORM_1'), + (60,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (61,'FORM_1'), + (62,'FORM_1'), + (63,'FORM_1'), + (64,'FORM_1'), + (65,'FORM_1'), + (66,'FORM_1'), + (67,'FORM_1'), + (68,'FORM_1'), + (69,'FORM_1'), + (70,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (71,'FORM_1'), + (72,'FORM_1'), + (73,'FORM_1'), + (74,'FORM_1'), + (75,'FORM_1'), + (76,'FORM_1'), + (77,'FORM_1'), + (78,'FORM_1'), + (79,'FORM_1'), + (80,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (81,'FORM_1'), + (82,'FORM_1'), + (83,'FORM_1'), + (84,'FORM_1'), + (85,'FORM_1'), + (86,'FORM_1'), + (87,'FORM_1'), + (88,'FORM_1'), + (89,'FORM_1'), + (90,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (91,'FORM_1'), + (92,'FORM_1'), + (93,'FORM_1'), + (94,'FORM_1'), + (95,'FORM_1'), + (96,'FORM_1'), + (97,'FORM_1'), + (98,'FORM_1'), + (99,'FORM_1'), + (100,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (101,'FORM_1'), + (102,'FORM_1'), + (103,'FORM_1'), + (104,'FORM_1'), + (105,'FORM_1'), + (106,'FORM_1'), + (107,'FORM_1'), + (108,'FORM_1'), + (109,'FORM_1'), + (110,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (111,'FORM_1'), + (112,'FORM_1'), + (113,'FORM_1'), + (114,'FORM_1'), + (115,'FORM_1'), + (116,'FORM_1'), + (117,'FORM_1'), + (118,'FORM_1'), + (119,'FORM_1'), + (120,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (121,'FORM_1'), + (122,'FORM_1'), + (123,'FORM_1'), + (124,'FORM_1'), + (125,'FORM_1'), + (126,'FORM_1'), + (127,'FORM_1'), + (128,'FORM_1'), + (129,'FORM_1'), + (130,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (131,'FORM_1'), + (132,'FORM_1'), + (133,'FORM_1'), + (134,'FORM_1'), + (135,'FORM_1'), + (136,'FORM_1'), + (137,'FORM_1'), + (138,'FORM_1'), + (139,'FORM_1'), + (140,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (141,'FORM_1'), + (142,'FORM_1'), + (143,'FORM_1'), + (144,'FORM_1'), + (145,'FORM_1'), + (146,'FORM_1'), + (147,'FORM_1'), + (148,'FORM_1'), + (149,'FORM_1'), + (150,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (151,'FORM_1'), + (152,'FORM_1'), + (153,'FORM_1'), + (154,'FORM_1'), + (155,'FORM_1'), + (156,'FORM_1'), + (157,'FORM_1'), + (158,'FORM_1'), + (159,'FORM_1'), + (160,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (161,'FORM_1'), + (162,'FORM_1'), + (163,'FORM_1'), + (164,'FORM_1'), + (165,'FORM_1'), + (166,'FORM_1'), + (167,'FORM_1'), + (168,'FORM_1'), + (169,'FORM_1'), + (170,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (171,'FORM_1'), + (172,'FORM_1'), + (173,'FORM_1'), + (174,'FORM_1'), + (175,'FORM_1'), + (176,'FORM_1'), + (177,'FORM_1'), + (178,'FORM_1'), + (179,'FORM_1'), + (180,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (181,'FORM_1'), + (182,'FORM_1'), + (183,'FORM_1'), + (184,'FORM_1'), + (185,'FORM_1'), + (186,'FORM_1'), + (187,'FORM_1'), + (188,'FORM_1'), + (189,'FORM_1'), + (190,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (191,'FORM_1'), + (192,'FORM_1'), + (193,'FORM_1'), + (194,'FORM_1'), + (195,'FORM_1'), + (196,'FORM_1'), + (197,'FORM_1'), + (198,'FORM_1'), + (199,'FORM_1'), + (200,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (201,'FORM_1'), + (202,'FORM_1'), + (203,'FORM_1'), + (204,'FORM_1'), + (205,'FORM_1'), + (206,'FORM_1'), + (207,'FORM_1'), + (208,'FORM_1'), + (209,'FORM_1'), + (210,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (211,'FORM_1'), + (212,'FORM_1'), + (213,'FORM_1'), + (214,'FORM_1'), + (215,'FORM_1'), + (216,'FORM_1'), + (217,'FORM_1'), + (218,'FORM_1'), + (219,'FORM_1'), + (220,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (221,'FORM_1'), + (222,'FORM_1'), + (223,'FORM_1'), + (224,'FORM_1'), + (225,'FORM_1'), + (226,'FORM_1'), + (227,'FORM_1'), + (228,'FORM_1'), + (229,'FORM_1'), + (230,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (231,'FORM_1'), + (232,'FORM_1'), + (233,'FORM_1'), + (234,'FORM_1'), + (235,'FORM_1'), + (236,'FORM_1'), + (237,'FORM_1'), + (238,'FORM_1'), + (239,'FORM_1'), + (240,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (241,'FORM_1'), + (242,'FORM_1'), + (243,'FORM_1'), + (244,'FORM_1'), + (245,'FORM_1'), + (246,'FORM_1'), + (247,'FORM_1'), + (248,'FORM_1'), + (249,'FORM_1'), + (250,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (251,'FORM_1'), + (252,'FORM_1'), + (253,'FORM_1'), + (254,'FORM_1'), + (255,'FORM_1'), + (256,'FORM_1'), + (257,'FORM_1'), + (258,'FORM_1'), + (259,'FORM_1'), + (260,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (261,'FORM_1'), + (262,'FORM_1'), + (263,'FORM_1'), + (264,'FORM_1'), + (265,'FORM_1'), + (266,'FORM_1'), + (267,'FORM_1'), + (268,'FORM_1'), + (269,'FORM_1'), + (270,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (271,'FORM_1'), + (272,'FORM_1'), + (273,'FORM_1'), + (274,'FORM_1'), + (275,'FORM_1'), + (276,'FORM_1'), + (277,'FORM_1'), + (278,'FORM_1'), + (279,'FORM_1'), + (280,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (281,'FORM_1'), + (282,'FORM_1'), + (283,'FORM_1'), + (284,'FORM_1'), + (285,'FORM_1'), + (286,'FORM_1'), + (287,'FORM_1'), + (288,'FORM_1'), + (289,'FORM_1'), + (290,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (291,'FORM_1'), + (292,'FORM_1'), + (293,'FORM_1'), + (294,'FORM_1'), + (295,'FORM_1'), + (296,'FORM_1'), + (297,'FORM_1'), + (298,'FORM_1'), + (299,'FORM_1'), + (300,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (301,'FORM_1'), + (302,'FORM_1'), + (303,'FORM_1'), + (304,'FORM_1'), + (305,'FORM_1'), + (306,'FORM_1'), + (307,'FORM_1'), + (308,'FORM_1'), + (309,'FORM_1'), + (310,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (311,'FORM_1'), + (312,'FORM_1'), + (313,'FORM_1'), + (314,'FORM_1'), + (387,'FORM_1'), + (315,'FORM_1'), + (316,'FORM_1'), + (317,'FORM_1'), + (318,'FORM_1'), + (319,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (320,'FORM_1'), + (321,'FORM_1'), + (322,'FORM_1'), + (323,'FORM_1'), + (324,'FORM_1'), + (325,'FORM_1'), + (326,'FORM_1'), + (327,'FORM_1'), + (328,'FORM_1'), + (329,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (330,'FORM_1'), + (331,'FORM_1'), + (332,'FORM_1'), + (333,'FORM_1'), + (334,'FORM_1'), + (335,'FORM_1'), + (336,'FORM_1'), + (337,'FORM_1'), + (338,'FORM_1'), + (339,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (340,'FORM_1'), + (341,'FORM_1'), + (342,'FORM_1'), + (343,'FORM_1'), + (344,'FORM_1'), + (345,'FORM_1'), + (346,'FORM_1'), + (347,'FORM_1'), + (348,'FORM_1'), + (349,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (350,'FORM_1'), + (351,'FORM_1'), + (352,'FORM_1'), + (353,'FORM_1'), + (354,'FORM_1'), + (355,'FORM_1'), + (356,'FORM_1'), + (357,'FORM_1'), + (358,'FORM_1'), + (359,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (360,'FORM_1'), + (361,'FORM_1'), + (362,'FORM_1'), + (363,'FORM_1'), + (364,'FORM_1'), + (365,'FORM_1'), + (366,'FORM_1'), + (367,'FORM_1'), + (368,'FORM_1'), + (369,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (370,'FORM_1'), + (371,'FORM_1'), + (372,'FORM_1'), + (373,'FORM_1'), + (374,'FORM_1'), + (375,'FORM_1'), + (376,'FORM_1'), + (377,'FORM_1'), + (378,'FORM_1'), + (379,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (380,'FORM_1'), + (381,'FORM_1'), + (382,'FORM_1'), + (383,'FORM_1'), + (384,'FORM_1'), + (385,'FORM_1'), + (386,'FORM_1'), + (388,'FORM_1'), + (389,'FORM_1'), + (390,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (391,'FORM_1'), + (392,'FORM_1'), + (393,'FORM_1'), + (394,'FORM_1'), + (395,'FORM_1'), + (396,'FORM_1'), + (397,'FORM_1'), + (398,'FORM_1'), + (399,'FORM_1'), + (400,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (401,'FORM_1'), + (402,'FORM_1'), + (403,'FORM_1'), + (404,'FORM_1'), + (405,'FORM_1'), + (406,'FORM_1'), + (407,'FORM_1'), + (408,'FORM_1'), + (409,'FORM_1'), + (410,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (411,'FORM_1'), + (412,'FORM_1'), + (413,'FORM_1'), + (414,'FORM_1'), + (415,'FORM_1'), + (416,'FORM_1'), + (417,'FORM_1'), + (418,'FORM_1'), + (419,'FORM_1'), + (420,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (421,'FORM_1'), + (422,'FORM_1'), + (423,'FORM_1'), + (424,'FORM_1'), + (425,'FORM_1'), + (426,'FORM_1'), + (427,'FORM_1'), + (428,'FORM_1'), + (429,'FORM_1'), + (430,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (431,'FORM_1'), + (432,'FORM_1'), + (433,'FORM_1'), + (434,'FORM_1'), + (435,'FORM_1'), + (436,'FORM_1'), + (437,'FORM_1'), + (438,'FORM_1'), + (439,'FORM_1'), + (440,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (441,'FORM_1'), + (442,'FORM_1'), + (443,'FORM_1'), + (444,'FORM_1'), + (445,'FORM_1'), + (446,'FORM_1'), + (447,'FORM_1'), + (448,'FORM_1'), + (449,'FORM_1'), + (450,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (451,'FORM_1'), + (452,'FORM_1'), + (453,'FORM_1'), + (454,'FORM_1'), + (455,'FORM_1'), + (456,'FORM_1'), + (457,'FORM_1'), + (458,'FORM_1'), + (459,'FORM_1'), + (460,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (461,'FORM_1'), + (462,'FORM_1'), + (463,'FORM_1'), + (464,'FORM_1'), + (465,'FORM_1'), + (466,'FORM_1'), + (467,'FORM_1'), + (468,'FORM_1'), + (469,'FORM_1'), + (470,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (471,'FORM_1'), + (472,'FORM_1'), + (473,'FORM_1'), + (474,'FORM_1'), + (475,'FORM_1'), + (476,'FORM_1'), + (477,'FORM_1'), + (478,'FORM_1'), + (479,'FORM_1'), + (480,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (481,'FORM_1'), + (482,'FORM_1'), + (483,'FORM_1'), + (484,'FORM_1'), + (485,'FORM_1'), + (486,'FORM_1'), + (487,'FORM_1'), + (488,'FORM_1'), + (489,'FORM_1'), + (490,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (491,'FORM_1'), + (492,'FORM_1'), + (493,'FORM_1'), + (494,'FORM_1'), + (495,'FORM_1'), + (496,'FORM_1'), + (497,'FORM_1'), + (498,'FORM_1'), + (499,'FORM_1'), + (500,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (501,'FORM_1'), + (502,'FORM_1'), + (503,'FORM_1'), + (504,'FORM_1'), + (505,'FORM_1'), + (506,'FORM_1'), + (507,'FORM_1'), + (508,'FORM_1'), + (509,'FORM_1'), + (510,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (511,'FORM_1'), + (512,'FORM_1'), + (513,'FORM_1'), + (514,'FORM_1'), + (515,'FORM_1'), + (516,'FORM_1'), + (517,'FORM_1'), + (518,'FORM_1'), + (519,'FORM_1'), + (520,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (521,'FORM_1'), + (522,'FORM_1'), + (523,'FORM_1'), + (524,'FORM_1'), + (525,'FORM_1'), + (526,'FORM_1'), + (527,'FORM_1'), + (528,'FORM_1'), + (529,'FORM_1'), + (530,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (531,'FORM_1'), + (532,'FORM_1'), + (533,'FORM_1'), + (534,'FORM_1'), + (535,'FORM_1'), + (536,'FORM_1'), + (537,'FORM_1'), + (538,'FORM_1'), + (539,'FORM_1'), + (540,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (541,'FORM_1'), + (542,'FORM_1'), + (543,'FORM_1'), + (544,'FORM_1'), + (545,'FORM_1'), + (546,'FORM_1'), + (547,'FORM_1'), + (548,'FORM_1'), + (549,'FORM_1'), + (550,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (551,'FORM_1'), + (552,'FORM_1'), + (553,'FORM_1'), + (554,'FORM_1'), + (555,'FORM_1'), + (556,'FORM_1'), + (557,'FORM_1'), + (558,'FORM_1'), + (559,'FORM_1'), + (560,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (561,'FORM_1'), + (562,'FORM_1'), + (563,'FORM_1'), + (564,'FORM_1'), + (565,'FORM_1'), + (566,'FORM_1'), + (567,'FORM_1'), + (568,'FORM_1'), + (569,'FORM_1'), + (570,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (571,'FORM_1'), + (572,'FORM_1'), + (573,'FORM_1'), + (574,'FORM_1'), + (575,'FORM_1'), + (576,'FORM_1'), + (577,'FORM_1'), + (578,'FORM_1'), + (579,'FORM_1'), + (580,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (581,'FORM_1'), + (582,'FORM_1'), + (583,'FORM_1'), + (584,'FORM_1'), + (585,'FORM_1'), + (586,'FORM_1'), + (587,'FORM_1'), + (588,'FORM_1'), + (589,'FORM_1'), + (590,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (591,'FORM_1'), + (592,'FORM_1'), + (593,'FORM_1'), + (594,'FORM_1'), + (595,'FORM_1'), + (596,'FORM_1'), + (597,'FORM_1'), + (598,'FORM_1'), + (599,'FORM_1'), + (600,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (601,'FORM_1'), + (602,'FORM_1'), + (603,'FORM_1'), + (604,'FORM_1'), + (605,'FORM_1'), + (606,'FORM_1'), + (607,'FORM_1'), + (608,'FORM_1'), + (609,'FORM_1'), + (610,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (611,'FORM_1'), + (612,'FORM_1'), + (613,'FORM_1'), + (614,'FORM_1'), + (615,'FORM_1'), + (616,'FORM_1'), + (617,'FORM_1'), + (618,'FORM_1'), + (619,'FORM_1'), + (620,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (621,'FORM_1'), + (622,'FORM_1'), + (623,'FORM_1'), + (624,'FORM_1'), + (625,'FORM_1'), + (626,'FORM_1'), + (627,'FORM_1'), + (628,'FORM_1'), + (629,'FORM_1'), + (630,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (631,'FORM_1'), + (632,'FORM_1'), + (633,'FORM_1'), + (634,'FORM_1'), + (635,'FORM_1'), + (636,'FORM_1'), + (637,'FORM_1'), + (638,'FORM_1'), + (639,'FORM_1'), + (640,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (641,'FORM_1'), + (642,'FORM_1'), + (643,'FORM_1'), + (644,'FORM_1'), + (645,'FORM_1'), + (646,'FORM_1'), + (647,'FORM_1'), + (648,'FORM_1'), + (649,'FORM_1'), + (650,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (651,'FORM_1'), + (652,'FORM_1'), + (653,'FORM_1'), + (654,'FORM_1'), + (655,'FORM_1'), + (656,'FORM_1'), + (657,'FORM_1'), + (658,'FORM_1'), + (659,'FORM_1'), + (660,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (661,'FORM_1'), + (662,'FORM_1'), + (663,'FORM_1'), + (664,'FORM_1'), + (665,'FORM_1'), + (666,'FORM_1'), + (667,'FORM_1'), + (668,'FORM_1'), + (669,'FORM_1'), + (670,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (671,'FORM_1'), + (672,'FORM_1'), + (673,'FORM_1'), + (674,'FORM_1'), + (675,'FORM_1'), + (676,'FORM_1'), + (677,'FORM_1'), + (678,'FORM_1'), + (679,'FORM_1'), + (680,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (681,'FORM_1'), + (682,'FORM_1'), + (683,'FORM_1'), + (684,'FORM_1'), + (685,'FORM_1'), + (686,'FORM_1'), + (687,'FORM_1'), + (688,'FORM_1'), + (689,'FORM_1'), + (690,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (691,'FORM_1'), + (692,'FORM_1'), + (693,'FORM_1'), + (694,'FORM_1'), + (695,'FORM_1'), + (696,'FORM_1'), + (697,'FORM_1'), + (698,'FORM_1'), + (699,'FORM_1'), + (700,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (701,'FORM_1'), + (702,'FORM_1'), + (703,'FORM_1'), + (704,'FORM_1'), + (705,'FORM_1'), + (706,'FORM_1'), + (707,'FORM_1'), + (708,'FORM_1'), + (709,'FORM_1'), + (710,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (711,'FORM_1'), + (712,'FORM_1'), + (713,'FORM_1'), + (714,'FORM_1'), + (715,'FORM_1'), + (716,'FORM_1'), + (717,'FORM_1'), + (718,'FORM_1'), + (719,'FORM_1'), + (720,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (721,'FORM_1'), + (722,'FORM_1'), + (723,'FORM_1'), + (724,'FORM_1'), + (725,'FORM_1'), + (726,'FORM_1'), + (727,'FORM_1'), + (728,'FORM_1'), + (729,'FORM_1'), + (730,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (731,'FORM_1'), + (732,'FORM_1'), + (733,'FORM_1'), + (734,'FORM_1'), + (735,'FORM_1'), + (736,'FORM_1'), + (737,'FORM_1'), + (738,'FORM_1'), + (739,'FORM_1'), + (740,'FORM_1'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (741,'FORM_1'), + (742,'FORM_1'), + (743,'FORM_1'), + (744,'FORM_1'), + (745,'FORM_1'), + (1,'FORM_2'), + (2,'FORM_2'), + (3,'FORM_2'), + (4,'FORM_2'), + (5,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (6,'FORM_2'), + (7,'FORM_2'), + (8,'FORM_2'), + (9,'FORM_2'), + (10,'FORM_2'), + (11,'FORM_2'), + (12,'FORM_2'), + (13,'FORM_2'), + (14,'FORM_2'), + (15,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (16,'FORM_2'), + (17,'FORM_2'), + (18,'FORM_2'), + (19,'FORM_2'), + (20,'FORM_2'), + (21,'FORM_2'), + (22,'FORM_2'), + (23,'FORM_2'), + (24,'FORM_2'), + (25,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (26,'FORM_2'), + (27,'FORM_2'), + (28,'FORM_2'), + (29,'FORM_2'), + (30,'FORM_2'), + (31,'FORM_2'), + (32,'FORM_2'), + (33,'FORM_2'), + (34,'FORM_2'), + (35,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (36,'FORM_2'), + (37,'FORM_2'), + (38,'FORM_2'), + (39,'FORM_2'), + (40,'FORM_2'), + (41,'FORM_2'), + (42,'FORM_2'), + (43,'FORM_2'), + (44,'FORM_2'), + (45,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (46,'FORM_2'), + (47,'FORM_2'), + (48,'FORM_2'), + (49,'FORM_2'), + (50,'FORM_2'), + (51,'FORM_2'), + (52,'FORM_2'), + (53,'FORM_2'), + (54,'FORM_2'), + (55,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (56,'FORM_2'), + (57,'FORM_2'), + (58,'FORM_2'), + (59,'FORM_2'), + (60,'FORM_2'), + (61,'FORM_2'), + (62,'FORM_2'), + (63,'FORM_2'), + (64,'FORM_2'), + (65,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (66,'FORM_2'), + (67,'FORM_2'), + (68,'FORM_2'), + (69,'FORM_2'), + (70,'FORM_2'), + (71,'FORM_2'), + (72,'FORM_2'), + (73,'FORM_2'), + (75,'FORM_2'), + (76,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (77,'FORM_2'), + (78,'FORM_2'), + (79,'FORM_2'), + (80,'FORM_2'), + (81,'FORM_2'), + (82,'FORM_2'), + (83,'FORM_2'), + (84,'FORM_2'), + (85,'FORM_2'), + (86,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (87,'FORM_2'), + (88,'FORM_2'), + (89,'FORM_2'), + (90,'FORM_2'), + (91,'FORM_2'), + (92,'FORM_2'), + (93,'FORM_2'), + (94,'FORM_2'), + (95,'FORM_2'), + (96,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (97,'FORM_2'), + (98,'FORM_2'), + (99,'FORM_2'), + (100,'FORM_2'), + (101,'FORM_2'), + (102,'FORM_2'), + (103,'FORM_2'), + (104,'FORM_2'), + (105,'FORM_2'), + (106,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (107,'FORM_2'), + (108,'FORM_2'), + (109,'FORM_2'), + (110,'FORM_2'), + (111,'FORM_2'), + (112,'FORM_2'), + (113,'FORM_2'), + (114,'FORM_2'), + (115,'FORM_2'), + (116,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (117,'FORM_2'), + (118,'FORM_2'), + (119,'FORM_2'), + (120,'FORM_2'), + (121,'FORM_2'), + (122,'FORM_2'), + (123,'FORM_2'), + (124,'FORM_2'), + (125,'FORM_2'), + (126,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (127,'FORM_2'), + (128,'FORM_2'), + (129,'FORM_2'), + (130,'FORM_2'), + (131,'FORM_2'), + (132,'FORM_2'), + (133,'FORM_2'), + (134,'FORM_2'), + (135,'FORM_2'), + (136,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (137,'FORM_2'), + (138,'FORM_2'), + (139,'FORM_2'), + (140,'FORM_2'), + (141,'FORM_2'), + (142,'FORM_2'), + (143,'FORM_2'), + (144,'FORM_2'), + (145,'FORM_2'), + (146,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (147,'FORM_2'), + (148,'FORM_2'), + (149,'FORM_2'), + (150,'FORM_2'), + (151,'FORM_2'), + (152,'FORM_2'), + (153,'FORM_2'), + (154,'FORM_2'), + (155,'FORM_2'), + (156,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (157,'FORM_2'), + (158,'FORM_2'), + (159,'FORM_2'), + (160,'FORM_2'), + (161,'FORM_2'), + (162,'FORM_2'), + (163,'FORM_2'), + (164,'FORM_2'), + (165,'FORM_2'), + (166,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (167,'FORM_2'), + (168,'FORM_2'), + (169,'FORM_2'), + (170,'FORM_2'), + (171,'FORM_2'), + (172,'FORM_2'), + (173,'FORM_2'), + (174,'FORM_2'), + (175,'FORM_2'), + (176,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (177,'FORM_2'), + (178,'FORM_2'), + (179,'FORM_2'), + (180,'FORM_2'), + (181,'FORM_2'), + (182,'FORM_2'), + (183,'FORM_2'), + (184,'FORM_2'), + (185,'FORM_2'), + (186,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (187,'FORM_2'), + (188,'FORM_2'), + (189,'FORM_2'), + (190,'FORM_2'), + (191,'FORM_2'), + (192,'FORM_2'), + (193,'FORM_2'), + (194,'FORM_2'), + (195,'FORM_2'), + (196,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (197,'FORM_2'), + (198,'FORM_2'), + (199,'FORM_2'), + (200,'FORM_2'), + (201,'FORM_2'), + (202,'FORM_2'), + (203,'FORM_2'), + (204,'FORM_2'), + (205,'FORM_2'), + (206,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (207,'FORM_2'), + (208,'FORM_2'), + (209,'FORM_2'), + (210,'FORM_2'), + (211,'FORM_2'), + (212,'FORM_2'), + (213,'FORM_2'), + (214,'FORM_2'), + (215,'FORM_2'), + (216,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (217,'FORM_2'), + (218,'FORM_2'), + (219,'FORM_2'), + (220,'FORM_2'), + (221,'FORM_2'), + (222,'FORM_2'), + (223,'FORM_2'), + (224,'FORM_2'), + (225,'FORM_2'), + (226,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (227,'FORM_2'), + (228,'FORM_2'), + (229,'FORM_2'), + (230,'FORM_2'), + (231,'FORM_2'), + (232,'FORM_2'), + (233,'FORM_2'), + (234,'FORM_2'), + (235,'FORM_2'), + (236,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (237,'FORM_2'), + (238,'FORM_2'), + (239,'FORM_2'), + (240,'FORM_2'), + (241,'FORM_2'), + (242,'FORM_2'), + (243,'FORM_2'), + (244,'FORM_2'), + (245,'FORM_2'), + (246,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (247,'FORM_2'), + (248,'FORM_2'), + (249,'FORM_2'), + (250,'FORM_2'), + (251,'FORM_2'), + (252,'FORM_2'), + (253,'FORM_2'), + (254,'FORM_2'), + (255,'FORM_2'), + (256,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (257,'FORM_2'), + (258,'FORM_2'), + (259,'FORM_2'), + (260,'FORM_2'), + (261,'FORM_2'), + (262,'FORM_2'), + (263,'FORM_2'), + (264,'FORM_2'), + (265,'FORM_2'), + (266,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (267,'FORM_2'), + (268,'FORM_2'), + (269,'FORM_2'), + (270,'FORM_2'), + (271,'FORM_2'), + (272,'FORM_2'), + (273,'FORM_2'), + (274,'FORM_2'), + (275,'FORM_2'), + (276,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (277,'FORM_2'), + (278,'FORM_2'), + (279,'FORM_2'), + (280,'FORM_2'), + (281,'FORM_2'), + (282,'FORM_2'), + (283,'FORM_2'), + (284,'FORM_2'), + (285,'FORM_2'), + (286,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (287,'FORM_2'), + (288,'FORM_2'), + (289,'FORM_2'), + (290,'FORM_2'), + (291,'FORM_2'), + (292,'FORM_2'), + (293,'FORM_2'), + (294,'FORM_2'), + (295,'FORM_2'), + (296,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (297,'FORM_2'), + (298,'FORM_2'), + (299,'FORM_2'), + (300,'FORM_2'), + (301,'FORM_2'), + (302,'FORM_2'), + (303,'FORM_2'), + (304,'FORM_2'), + (305,'FORM_2'), + (306,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (307,'FORM_2'), + (308,'FORM_2'), + (309,'FORM_2'), + (310,'FORM_2'), + (311,'FORM_2'), + (312,'FORM_2'), + (313,'FORM_2'), + (314,'FORM_2'), + (387,'FORM_2'), + (315,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (316,'FORM_2'), + (317,'FORM_2'), + (318,'FORM_2'), + (319,'FORM_2'), + (320,'FORM_2'), + (321,'FORM_2'), + (322,'FORM_2'), + (323,'FORM_2'), + (324,'FORM_2'), + (325,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (331,'FORM_2'), + (332,'FORM_2'), + (333,'FORM_2'), + (334,'FORM_2'), + (335,'FORM_2'), + (336,'FORM_2'), + (337,'FORM_2'), + (338,'FORM_2'), + (339,'FORM_2'), + (340,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (341,'FORM_2'), + (342,'FORM_2'), + (343,'FORM_2'), + (344,'FORM_2'), + (345,'FORM_2'), + (346,'FORM_2'), + (347,'FORM_2'), + (348,'FORM_2'), + (349,'FORM_2'), + (350,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (351,'FORM_2'), + (352,'FORM_2'), + (353,'FORM_2'), + (354,'FORM_2'), + (355,'FORM_2'), + (356,'FORM_2'), + (357,'FORM_2'), + (358,'FORM_2'), + (359,'FORM_2'), + (360,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (361,'FORM_2'), + (362,'FORM_2'), + (363,'FORM_2'), + (364,'FORM_2'), + (365,'FORM_2'), + (366,'FORM_2'), + (367,'FORM_2'), + (368,'FORM_2'), + (369,'FORM_2'), + (370,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (371,'FORM_2'), + (372,'FORM_2'), + (373,'FORM_2'), + (374,'FORM_2'), + (375,'FORM_2'), + (376,'FORM_2'), + (377,'FORM_2'), + (378,'FORM_2'), + (379,'FORM_2'), + (380,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (381,'FORM_2'), + (382,'FORM_2'), + (383,'FORM_2'), + (384,'FORM_2'), + (385,'FORM_2'), + (386,'FORM_2'), + (388,'FORM_2'), + (389,'FORM_2'), + (390,'FORM_2'), + (391,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (392,'FORM_2'), + (393,'FORM_2'), + (394,'FORM_2'), + (395,'FORM_2'), + (396,'FORM_2'), + (397,'FORM_2'), + (398,'FORM_2'), + (399,'FORM_2'), + (400,'FORM_2'), + (401,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (402,'FORM_2'), + (403,'FORM_2'), + (404,'FORM_2'), + (405,'FORM_2'), + (406,'FORM_2'), + (407,'FORM_2'), + (408,'FORM_2'), + (409,'FORM_2'), + (410,'FORM_2'), + (411,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (412,'FORM_2'), + (413,'FORM_2'), + (414,'FORM_2'), + (415,'FORM_2'), + (416,'FORM_2'), + (417,'FORM_2'), + (418,'FORM_2'), + (419,'FORM_2'), + (420,'FORM_2'), + (421,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (422,'FORM_2'), + (423,'FORM_2'), + (424,'FORM_2'), + (425,'FORM_2'), + (426,'FORM_2'), + (427,'FORM_2'), + (428,'FORM_2'), + (429,'FORM_2'), + (430,'FORM_2'), + (431,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (432,'FORM_2'), + (433,'FORM_2'), + (434,'FORM_2'), + (435,'FORM_2'), + (436,'FORM_2'), + (437,'FORM_2'), + (438,'FORM_2'), + (439,'FORM_2'), + (440,'FORM_2'), + (441,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (442,'FORM_2'), + (443,'FORM_2'), + (444,'FORM_2'), + (445,'FORM_2'), + (446,'FORM_2'), + (447,'FORM_2'), + (448,'FORM_2'), + (449,'FORM_2'), + (450,'FORM_2'), + (451,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (452,'FORM_2'), + (453,'FORM_2'), + (454,'FORM_2'), + (455,'FORM_2'), + (456,'FORM_2'), + (457,'FORM_2'), + (458,'FORM_2'), + (459,'FORM_2'), + (460,'FORM_2'), + (461,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (462,'FORM_2'), + (463,'FORM_2'), + (464,'FORM_2'), + (465,'FORM_2'), + (466,'FORM_2'), + (467,'FORM_2'), + (468,'FORM_2'), + (470,'FORM_2'), + (471,'FORM_2'), + (472,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (473,'FORM_2'), + (474,'FORM_2'), + (475,'FORM_2'), + (476,'FORM_2'), + (477,'FORM_2'), + (478,'FORM_2'), + (479,'FORM_2'), + (480,'FORM_2'), + (481,'FORM_2'), + (482,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (483,'FORM_2'), + (484,'FORM_2'), + (485,'FORM_2'), + (486,'FORM_2'), + (487,'FORM_2'), + (488,'FORM_2'), + (489,'FORM_2'), + (490,'FORM_2'), + (491,'FORM_2'), + (492,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (493,'FORM_2'), + (494,'FORM_2'), + (495,'FORM_2'), + (496,'FORM_2'), + (497,'FORM_2'), + (498,'FORM_2'), + (499,'FORM_2'), + (500,'FORM_2'), + (501,'FORM_2'), + (502,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (503,'FORM_2'), + (504,'FORM_2'), + (505,'FORM_2'), + (506,'FORM_2'), + (507,'FORM_2'), + (508,'FORM_2'), + (509,'FORM_2'), + (510,'FORM_2'), + (511,'FORM_2'), + (512,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (513,'FORM_2'), + (514,'FORM_2'), + (515,'FORM_2'), + (516,'FORM_2'), + (517,'FORM_2'), + (518,'FORM_2'), + (519,'FORM_2'), + (520,'FORM_2'), + (521,'FORM_2'), + (522,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (523,'FORM_2'), + (524,'FORM_2'), + (525,'FORM_2'), + (526,'FORM_2'), + (527,'FORM_2'), + (528,'FORM_2'), + (529,'FORM_2'), + (530,'FORM_2'), + (531,'FORM_2'), + (532,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (533,'FORM_2'), + (534,'FORM_2'), + (535,'FORM_2'), + (536,'FORM_2'), + (537,'FORM_2'), + (538,'FORM_2'), + (539,'FORM_2'), + (540,'FORM_2'), + (541,'FORM_2'), + (542,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (543,'FORM_2'), + (544,'FORM_2'), + (545,'FORM_2'), + (546,'FORM_2'), + (547,'FORM_2'), + (548,'FORM_2'), + (549,'FORM_2'), + (550,'FORM_2'), + (551,'FORM_2'), + (552,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (553,'FORM_2'), + (554,'FORM_2'), + (555,'FORM_2'), + (556,'FORM_2'), + (557,'FORM_2'), + (558,'FORM_2'), + (559,'FORM_2'), + (560,'FORM_2'), + (561,'FORM_2'), + (562,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (563,'FORM_2'), + (564,'FORM_2'), + (565,'FORM_2'), + (566,'FORM_2'), + (567,'FORM_2'), + (568,'FORM_2'), + (569,'FORM_2'), + (570,'FORM_2'), + (571,'FORM_2'), + (572,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (573,'FORM_2'), + (574,'FORM_2'), + (575,'FORM_2'), + (576,'FORM_2'), + (577,'FORM_2'), + (578,'FORM_2'), + (579,'FORM_2'), + (580,'FORM_2'), + (581,'FORM_2'), + (582,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (583,'FORM_2'), + (584,'FORM_2'), + (585,'FORM_2'), + (586,'FORM_2'), + (587,'FORM_2'), + (588,'FORM_2'), + (589,'FORM_2'), + (590,'FORM_2'), + (591,'FORM_2'), + (592,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (593,'FORM_2'), + (594,'FORM_2'), + (595,'FORM_2'), + (596,'FORM_2'), + (597,'FORM_2'), + (598,'FORM_2'), + (599,'FORM_2'), + (600,'FORM_2'), + (601,'FORM_2'), + (602,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (603,'FORM_2'), + (604,'FORM_2'), + (605,'FORM_2'), + (606,'FORM_2'), + (607,'FORM_2'), + (608,'FORM_2'), + (609,'FORM_2'), + (610,'FORM_2'), + (611,'FORM_2'), + (612,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (613,'FORM_2'), + (614,'FORM_2'), + (615,'FORM_2'), + (616,'FORM_2'), + (617,'FORM_2'), + (618,'FORM_2'), + (619,'FORM_2'), + (620,'FORM_2'), + (621,'FORM_2'), + (622,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (623,'FORM_2'), + (624,'FORM_2'), + (625,'FORM_2'), + (626,'FORM_2'), + (627,'FORM_2'), + (628,'FORM_2'), + (629,'FORM_2'), + (630,'FORM_2'), + (631,'FORM_2'), + (632,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (633,'FORM_2'), + (634,'FORM_2'), + (635,'FORM_2'), + (636,'FORM_2'), + (637,'FORM_2'), + (638,'FORM_2'), + (639,'FORM_2'), + (640,'FORM_2'), + (641,'FORM_2'), + (642,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (643,'FORM_2'), + (644,'FORM_2'), + (645,'FORM_2'), + (646,'FORM_2'), + (647,'FORM_2'), + (648,'FORM_2'), + (649,'FORM_2'), + (650,'FORM_2'), + (651,'FORM_2'), + (652,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (653,'FORM_2'), + (654,'FORM_2'), + (655,'FORM_2'), + (656,'FORM_2'), + (657,'FORM_2'), + (658,'FORM_2'), + (659,'FORM_2'), + (660,'FORM_2'), + (661,'FORM_2'), + (662,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (663,'FORM_2'), + (664,'FORM_2'), + (665,'FORM_2'), + (666,'FORM_2'), + (667,'FORM_2'), + (668,'FORM_2'), + (669,'FORM_2'), + (670,'FORM_2'), + (671,'FORM_2'), + (672,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (673,'FORM_2'), + (674,'FORM_2'), + (675,'FORM_2'), + (676,'FORM_2'), + (677,'FORM_2'), + (678,'FORM_2'), + (679,'FORM_2'), + (680,'FORM_2'), + (681,'FORM_2'), + (682,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (683,'FORM_2'), + (684,'FORM_2'), + (685,'FORM_2'), + (686,'FORM_2'), + (687,'FORM_2'), + (688,'FORM_2'), + (689,'FORM_2'), + (690,'FORM_2'), + (691,'FORM_2'), + (692,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (693,'FORM_2'), + (694,'FORM_2'), + (695,'FORM_2'), + (696,'FORM_2'), + (697,'FORM_2'), + (698,'FORM_2'), + (699,'FORM_2'), + (700,'FORM_2'), + (701,'FORM_2'), + (702,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (703,'FORM_2'), + (704,'FORM_2'), + (705,'FORM_2'), + (706,'FORM_2'), + (707,'FORM_2'), + (708,'FORM_2'), + (709,'FORM_2'), + (710,'FORM_2'), + (711,'FORM_2'), + (712,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (713,'FORM_2'), + (714,'FORM_2'), + (715,'FORM_2'), + (716,'FORM_2'), + (717,'FORM_2'), + (718,'FORM_2'), + (719,'FORM_2'), + (720,'FORM_2'), + (721,'FORM_2'), + (722,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (723,'FORM_2'), + (724,'FORM_2'), + (725,'FORM_2'), + (726,'FORM_2'), + (728,'FORM_2'), + (729,'FORM_2'), + (730,'FORM_2'), + (731,'FORM_2'), + (732,'FORM_2'), + (733,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (734,'FORM_2'), + (735,'FORM_2'), + (736,'FORM_2'), + (737,'FORM_2'), + (738,'FORM_2'), + (739,'FORM_2'), + (740,'FORM_2'), + (741,'FORM_2'), + (742,'FORM_2'), + (743,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (744,'FORM_2'), + (746,'FORM_2'), + (747,'FORM_2'), + (748,'FORM_2'), + (749,'FORM_2'), + (750,'FORM_2'), + (751,'FORM_2'), + (752,'FORM_2'), + (753,'FORM_2'), + (754,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (755,'FORM_2'), + (756,'FORM_2'), + (757,'FORM_2'), + (758,'FORM_2'), + (759,'FORM_2'), + (760,'FORM_2'), + (761,'FORM_2'), + (762,'FORM_2'), + (763,'FORM_2'), + (764,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (765,'FORM_2'), + (766,'FORM_2'), + (767,'FORM_2'), + (768,'FORM_2'), + (769,'FORM_2'), + (770,'FORM_2'), + (771,'FORM_2'), + (772,'FORM_2'), + (773,'FORM_2'), + (774,'FORM_2'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (1,'FORM_4'), + (2,'FORM_4'), + (3,'FORM_4'), + (4,'FORM_4'), + (5,'FORM_4'), + (6,'FORM_4'), + (7,'FORM_4'), + (8,'FORM_4'), + (9,'FORM_4'), + (10,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (11,'FORM_4'), + (12,'FORM_4'), + (13,'FORM_4'), + (14,'FORM_4'), + (15,'FORM_4'), + (16,'FORM_4'), + (17,'FORM_4'), + (18,'FORM_4'), + (19,'FORM_4'), + (20,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (21,'FORM_4'), + (22,'FORM_4'), + (23,'FORM_4'), + (24,'FORM_4'), + (25,'FORM_4'), + (26,'FORM_4'), + (27,'FORM_4'), + (28,'FORM_4'), + (29,'FORM_4'), + (30,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (31,'FORM_4'), + (32,'FORM_4'), + (33,'FORM_4'), + (34,'FORM_4'), + (35,'FORM_4'), + (36,'FORM_4'), + (37,'FORM_4'), + (38,'FORM_4'), + (39,'FORM_4'), + (40,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (41,'FORM_4'), + (42,'FORM_4'), + (43,'FORM_4'), + (44,'FORM_4'), + (45,'FORM_4'), + (46,'FORM_4'), + (47,'FORM_4'), + (48,'FORM_4'), + (49,'FORM_4'), + (50,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (51,'FORM_4'), + (52,'FORM_4'), + (53,'FORM_4'), + (54,'FORM_4'), + (55,'FORM_4'), + (56,'FORM_4'), + (57,'FORM_4'), + (58,'FORM_4'), + (59,'FORM_4'), + (60,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (61,'FORM_4'), + (62,'FORM_4'), + (63,'FORM_4'), + (64,'FORM_4'), + (65,'FORM_4'), + (66,'FORM_4'), + (67,'FORM_4'), + (68,'FORM_4'), + (69,'FORM_4'), + (70,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (71,'FORM_4'), + (72,'FORM_4'), + (73,'FORM_4'), + (74,'FORM_4'), + (75,'FORM_4'), + (76,'FORM_4'), + (77,'FORM_4'), + (78,'FORM_4'), + (79,'FORM_4'), + (80,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (81,'FORM_4'), + (82,'FORM_4'), + (83,'FORM_4'), + (84,'FORM_4'), + (85,'FORM_4'), + (86,'FORM_4'), + (87,'FORM_4'), + (88,'FORM_4'), + (89,'FORM_4'), + (90,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (91,'FORM_4'), + (92,'FORM_4'), + (93,'FORM_4'), + (94,'FORM_4'), + (95,'FORM_4'), + (96,'FORM_4'), + (97,'FORM_4'), + (98,'FORM_4'), + (99,'FORM_4'), + (100,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (101,'FORM_4'), + (102,'FORM_4'), + (103,'FORM_4'), + (104,'FORM_4'), + (105,'FORM_4'), + (106,'FORM_4'), + (107,'FORM_4'), + (108,'FORM_4'), + (109,'FORM_4'), + (110,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (111,'FORM_4'), + (112,'FORM_4'), + (113,'FORM_4'), + (114,'FORM_4'), + (115,'FORM_4'), + (116,'FORM_4'), + (117,'FORM_4'), + (118,'FORM_4'), + (119,'FORM_4'), + (120,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (121,'FORM_4'), + (122,'FORM_4'), + (123,'FORM_4'), + (124,'FORM_4'), + (125,'FORM_4'), + (126,'FORM_4'), + (127,'FORM_4'), + (128,'FORM_4'), + (129,'FORM_4'), + (130,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (131,'FORM_4'), + (132,'FORM_4'), + (133,'FORM_4'), + (134,'FORM_4'), + (135,'FORM_4'), + (136,'FORM_4'), + (137,'FORM_4'), + (138,'FORM_4'), + (139,'FORM_4'), + (140,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (141,'FORM_4'), + (142,'FORM_4'), + (143,'FORM_4'), + (144,'FORM_4'), + (145,'FORM_4'), + (146,'FORM_4'), + (147,'FORM_4'), + (148,'FORM_4'), + (149,'FORM_4'), + (150,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (151,'FORM_4'), + (152,'FORM_4'), + (153,'FORM_4'), + (154,'FORM_4'), + (155,'FORM_4'), + (156,'FORM_4'), + (157,'FORM_4'), + (158,'FORM_4'), + (159,'FORM_4'), + (160,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (161,'FORM_4'), + (162,'FORM_4'), + (163,'FORM_4'), + (164,'FORM_4'), + (165,'FORM_4'), + (166,'FORM_4'), + (167,'FORM_4'), + (168,'FORM_4'), + (169,'FORM_4'), + (170,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (171,'FORM_4'), + (172,'FORM_4'), + (173,'FORM_4'), + (174,'FORM_4'), + (175,'FORM_4'), + (176,'FORM_4'), + (177,'FORM_4'), + (178,'FORM_4'), + (179,'FORM_4'), + (180,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (181,'FORM_4'), + (182,'FORM_4'), + (183,'FORM_4'), + (184,'FORM_4'), + (185,'FORM_4'), + (186,'FORM_4'), + (187,'FORM_4'), + (188,'FORM_4'), + (189,'FORM_4'), + (190,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (191,'FORM_4'), + (192,'FORM_4'), + (193,'FORM_4'), + (194,'FORM_4'), + (195,'FORM_4'), + (196,'FORM_4'), + (197,'FORM_4'), + (198,'FORM_4'), + (199,'FORM_4'), + (200,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (201,'FORM_4'), + (202,'FORM_4'), + (203,'FORM_4'), + (204,'FORM_4'), + (205,'FORM_4'), + (206,'FORM_4'), + (207,'FORM_4'), + (208,'FORM_4'), + (209,'FORM_4'), + (210,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (211,'FORM_4'), + (212,'FORM_4'), + (213,'FORM_4'), + (214,'FORM_4'), + (215,'FORM_4'), + (216,'FORM_4'), + (217,'FORM_4'), + (218,'FORM_4'), + (219,'FORM_4'), + (220,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (221,'FORM_4'), + (222,'FORM_4'), + (223,'FORM_4'), + (224,'FORM_4'), + (225,'FORM_4'), + (226,'FORM_4'), + (227,'FORM_4'), + (228,'FORM_4'), + (229,'FORM_4'), + (230,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (231,'FORM_4'), + (232,'FORM_4'), + (233,'FORM_4'), + (234,'FORM_4'), + (235,'FORM_4'), + (236,'FORM_4'), + (237,'FORM_4'), + (238,'FORM_4'), + (239,'FORM_4'), + (240,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (241,'FORM_4'), + (242,'FORM_4'), + (243,'FORM_4'), + (244,'FORM_4'), + (245,'FORM_4'), + (246,'FORM_4'), + (247,'FORM_4'), + (248,'FORM_4'), + (249,'FORM_4'), + (250,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (251,'FORM_4'), + (252,'FORM_4'), + (253,'FORM_4'), + (254,'FORM_4'), + (255,'FORM_4'), + (256,'FORM_4'), + (257,'FORM_4'), + (258,'FORM_4'), + (259,'FORM_4'), + (260,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (261,'FORM_4'), + (262,'FORM_4'), + (263,'FORM_4'), + (264,'FORM_4'), + (265,'FORM_4'), + (266,'FORM_4'), + (267,'FORM_4'), + (268,'FORM_4'), + (269,'FORM_4'), + (270,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (271,'FORM_4'), + (272,'FORM_4'), + (273,'FORM_4'), + (274,'FORM_4'), + (275,'FORM_4'), + (276,'FORM_4'), + (277,'FORM_4'), + (278,'FORM_4'), + (279,'FORM_4'), + (280,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (281,'FORM_4'), + (282,'FORM_4'), + (283,'FORM_4'), + (284,'FORM_4'), + (285,'FORM_4'), + (286,'FORM_4'), + (287,'FORM_4'), + (288,'FORM_4'), + (289,'FORM_4'), + (290,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (291,'FORM_4'), + (292,'FORM_4'), + (293,'FORM_4'), + (294,'FORM_4'), + (295,'FORM_4'), + (296,'FORM_4'), + (297,'FORM_4'), + (298,'FORM_4'), + (299,'FORM_4'), + (300,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (301,'FORM_4'), + (302,'FORM_4'), + (303,'FORM_4'), + (304,'FORM_4'), + (305,'FORM_4'), + (306,'FORM_4'), + (307,'FORM_4'), + (308,'FORM_4'), + (309,'FORM_4'), + (310,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (311,'FORM_4'), + (312,'FORM_4'), + (313,'FORM_4'), + (314,'FORM_4'), + (387,'FORM_4'), + (315,'FORM_4'), + (316,'FORM_4'), + (317,'FORM_4'), + (318,'FORM_4'), + (319,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (320,'FORM_4'), + (321,'FORM_4'), + (322,'FORM_4'), + (323,'FORM_4'), + (324,'FORM_4'), + (325,'FORM_4'), + (326,'FORM_4'), + (327,'FORM_4'), + (328,'FORM_4'), + (329,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (330,'FORM_4'), + (331,'FORM_4'), + (332,'FORM_4'), + (333,'FORM_4'), + (334,'FORM_4'), + (335,'FORM_4'), + (336,'FORM_4'), + (337,'FORM_4'), + (338,'FORM_4'), + (339,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (340,'FORM_4'), + (341,'FORM_4'), + (342,'FORM_4'), + (343,'FORM_4'), + (344,'FORM_4'), + (345,'FORM_4'), + (346,'FORM_4'), + (347,'FORM_4'), + (348,'FORM_4'), + (349,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (350,'FORM_4'), + (351,'FORM_4'), + (352,'FORM_4'), + (353,'FORM_4'), + (354,'FORM_4'), + (355,'FORM_4'), + (356,'FORM_4'), + (357,'FORM_4'), + (358,'FORM_4'), + (359,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (360,'FORM_4'), + (361,'FORM_4'), + (362,'FORM_4'), + (363,'FORM_4'), + (364,'FORM_4'), + (365,'FORM_4'), + (366,'FORM_4'), + (367,'FORM_4'), + (368,'FORM_4'), + (369,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (370,'FORM_4'), + (371,'FORM_4'), + (372,'FORM_4'), + (373,'FORM_4'), + (374,'FORM_4'), + (375,'FORM_4'), + (376,'FORM_4'), + (377,'FORM_4'), + (378,'FORM_4'), + (379,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (380,'FORM_4'), + (381,'FORM_4'), + (382,'FORM_4'), + (383,'FORM_4'), + (384,'FORM_4'), + (385,'FORM_4'), + (386,'FORM_4'), + (388,'FORM_4'), + (389,'FORM_4'), + (390,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (391,'FORM_4'), + (392,'FORM_4'), + (393,'FORM_4'), + (394,'FORM_4'), + (395,'FORM_4'), + (396,'FORM_4'), + (397,'FORM_4'), + (398,'FORM_4'), + (399,'FORM_4'), + (400,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (401,'FORM_4'), + (402,'FORM_4'), + (403,'FORM_4'), + (404,'FORM_4'), + (405,'FORM_4'), + (406,'FORM_4'), + (407,'FORM_4'), + (408,'FORM_4'), + (409,'FORM_4'), + (410,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (411,'FORM_4'), + (412,'FORM_4'), + (413,'FORM_4'), + (414,'FORM_4'), + (415,'FORM_4'), + (416,'FORM_4'), + (417,'FORM_4'), + (418,'FORM_4'), + (419,'FORM_4'), + (420,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (421,'FORM_4'), + (422,'FORM_4'), + (423,'FORM_4'), + (424,'FORM_4'), + (425,'FORM_4'), + (426,'FORM_4'), + (427,'FORM_4'), + (428,'FORM_4'), + (429,'FORM_4'), + (430,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (431,'FORM_4'), + (432,'FORM_4'), + (433,'FORM_4'), + (434,'FORM_4'), + (435,'FORM_4'), + (436,'FORM_4'), + (437,'FORM_4'), + (438,'FORM_4'), + (439,'FORM_4'), + (440,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (441,'FORM_4'), + (442,'FORM_4'), + (443,'FORM_4'), + (444,'FORM_4'), + (445,'FORM_4'), + (446,'FORM_4'), + (447,'FORM_4'), + (448,'FORM_4'), + (449,'FORM_4'), + (450,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (451,'FORM_4'), + (452,'FORM_4'), + (453,'FORM_4'), + (454,'FORM_4'), + (455,'FORM_4'), + (456,'FORM_4'), + (457,'FORM_4'), + (458,'FORM_4'), + (459,'FORM_4'), + (460,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (461,'FORM_4'), + (462,'FORM_4'), + (463,'FORM_4'), + (464,'FORM_4'), + (465,'FORM_4'), + (466,'FORM_4'), + (467,'FORM_4'), + (468,'FORM_4'), + (469,'FORM_4'), + (470,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (471,'FORM_4'), + (472,'FORM_4'), + (473,'FORM_4'), + (474,'FORM_4'), + (475,'FORM_4'), + (476,'FORM_4'), + (477,'FORM_4'), + (478,'FORM_4'), + (479,'FORM_4'), + (480,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (481,'FORM_4'), + (482,'FORM_4'), + (483,'FORM_4'), + (484,'FORM_4'), + (485,'FORM_4'), + (486,'FORM_4'), + (487,'FORM_4'), + (488,'FORM_4'), + (489,'FORM_4'), + (490,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (491,'FORM_4'), + (492,'FORM_4'), + (493,'FORM_4'), + (494,'FORM_4'), + (495,'FORM_4'), + (496,'FORM_4'), + (497,'FORM_4'), + (498,'FORM_4'), + (499,'FORM_4'), + (500,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (501,'FORM_4'), + (502,'FORM_4'), + (503,'FORM_4'), + (504,'FORM_4'), + (505,'FORM_4'), + (506,'FORM_4'), + (507,'FORM_4'), + (508,'FORM_4'), + (509,'FORM_4'), + (510,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (511,'FORM_4'), + (512,'FORM_4'), + (513,'FORM_4'), + (514,'FORM_4'), + (515,'FORM_4'), + (516,'FORM_4'), + (517,'FORM_4'), + (518,'FORM_4'), + (519,'FORM_4'), + (520,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (521,'FORM_4'), + (522,'FORM_4'), + (523,'FORM_4'), + (524,'FORM_4'), + (525,'FORM_4'), + (526,'FORM_4'), + (527,'FORM_4'), + (528,'FORM_4'), + (529,'FORM_4'), + (530,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (531,'FORM_4'), + (532,'FORM_4'), + (533,'FORM_4'), + (534,'FORM_4'), + (535,'FORM_4'), + (536,'FORM_4'), + (537,'FORM_4'), + (538,'FORM_4'), + (539,'FORM_4'), + (540,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (541,'FORM_4'), + (542,'FORM_4'), + (543,'FORM_4'), + (544,'FORM_4'), + (545,'FORM_4'), + (546,'FORM_4'), + (547,'FORM_4'), + (548,'FORM_4'), + (549,'FORM_4'), + (550,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (551,'FORM_4'), + (552,'FORM_4'), + (553,'FORM_4'), + (554,'FORM_4'), + (555,'FORM_4'), + (556,'FORM_4'), + (557,'FORM_4'), + (558,'FORM_4'), + (559,'FORM_4'), + (560,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (561,'FORM_4'), + (562,'FORM_4'), + (563,'FORM_4'), + (564,'FORM_4'), + (565,'FORM_4'), + (566,'FORM_4'), + (567,'FORM_4'), + (568,'FORM_4'), + (569,'FORM_4'), + (570,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (571,'FORM_4'), + (572,'FORM_4'), + (573,'FORM_4'), + (574,'FORM_4'), + (575,'FORM_4'), + (576,'FORM_4'), + (577,'FORM_4'), + (578,'FORM_4'), + (579,'FORM_4'), + (580,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (581,'FORM_4'), + (582,'FORM_4'), + (583,'FORM_4'), + (584,'FORM_4'), + (585,'FORM_4'), + (586,'FORM_4'), + (587,'FORM_4'), + (588,'FORM_4'), + (589,'FORM_4'), + (590,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (591,'FORM_4'), + (592,'FORM_4'), + (593,'FORM_4'), + (594,'FORM_4'), + (595,'FORM_4'), + (596,'FORM_4'), + (597,'FORM_4'), + (598,'FORM_4'), + (599,'FORM_4'), + (600,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (601,'FORM_4'), + (602,'FORM_4'), + (603,'FORM_4'), + (604,'FORM_4'), + (605,'FORM_4'), + (606,'FORM_4'), + (607,'FORM_4'), + (608,'FORM_4'), + (609,'FORM_4'), + (610,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (611,'FORM_4'), + (612,'FORM_4'), + (613,'FORM_4'), + (614,'FORM_4'), + (615,'FORM_4'), + (616,'FORM_4'), + (617,'FORM_4'), + (618,'FORM_4'), + (619,'FORM_4'), + (620,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (621,'FORM_4'), + (622,'FORM_4'), + (623,'FORM_4'), + (624,'FORM_4'), + (625,'FORM_4'), + (626,'FORM_4'), + (627,'FORM_4'), + (628,'FORM_4'), + (629,'FORM_4'), + (630,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (631,'FORM_4'), + (632,'FORM_4'), + (633,'FORM_4'), + (634,'FORM_4'), + (635,'FORM_4'), + (636,'FORM_4'), + (637,'FORM_4'), + (638,'FORM_4'), + (639,'FORM_4'), + (640,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (641,'FORM_4'), + (642,'FORM_4'), + (643,'FORM_4'), + (644,'FORM_4'), + (645,'FORM_4'), + (646,'FORM_4'), + (647,'FORM_4'), + (648,'FORM_4'), + (649,'FORM_4'), + (650,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (651,'FORM_4'), + (652,'FORM_4'), + (653,'FORM_4'), + (654,'FORM_4'), + (655,'FORM_4'), + (656,'FORM_4'), + (657,'FORM_4'), + (658,'FORM_4'), + (659,'FORM_4'), + (660,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (661,'FORM_4'), + (662,'FORM_4'), + (663,'FORM_4'), + (664,'FORM_4'), + (665,'FORM_4'), + (666,'FORM_4'), + (667,'FORM_4'), + (668,'FORM_4'), + (669,'FORM_4'), + (670,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (671,'FORM_4'), + (672,'FORM_4'), + (673,'FORM_4'), + (674,'FORM_4'), + (675,'FORM_4'), + (676,'FORM_4'), + (677,'FORM_4'), + (678,'FORM_4'), + (679,'FORM_4'), + (680,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (681,'FORM_4'), + (682,'FORM_4'), + (683,'FORM_4'), + (684,'FORM_4'), + (685,'FORM_4'), + (686,'FORM_4'), + (687,'FORM_4'), + (688,'FORM_4'), + (689,'FORM_4'), + (690,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (691,'FORM_4'), + (692,'FORM_4'), + (693,'FORM_4'), + (694,'FORM_4'), + (695,'FORM_4'), + (696,'FORM_4'), + (697,'FORM_4'), + (698,'FORM_4'), + (699,'FORM_4'), + (700,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (701,'FORM_4'), + (702,'FORM_4'), + (703,'FORM_4'), + (704,'FORM_4'), + (705,'FORM_4'), + (706,'FORM_4'), + (707,'FORM_4'), + (708,'FORM_4'), + (709,'FORM_4'), + (710,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (711,'FORM_4'), + (712,'FORM_4'), + (713,'FORM_4'), + (714,'FORM_4'), + (715,'FORM_4'), + (716,'FORM_4'), + (717,'FORM_4'), + (718,'FORM_4'), + (719,'FORM_4'), + (720,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (721,'FORM_4'), + (722,'FORM_4'), + (723,'FORM_4'), + (724,'FORM_4'), + (725,'FORM_4'), + (726,'FORM_4'), + (727,'FORM_4'), + (728,'FORM_4'), + (729,'FORM_4'), + (730,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (731,'FORM_4'), + (732,'FORM_4'), + (733,'FORM_4'), + (734,'FORM_4'), + (735,'FORM_4'), + (736,'FORM_4'), + (737,'FORM_4'), + (738,'FORM_4'), + (739,'FORM_4'), + (740,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (741,'FORM_4'), + (742,'FORM_4'), + (743,'FORM_4'), + (744,'FORM_4'), + (745,'FORM_4'), + (746,'FORM_4'), + (747,'FORM_4'), + (748,'FORM_4'), + (749,'FORM_4'), + (750,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (751,'FORM_4'), + (752,'FORM_4'), + (753,'FORM_4'), + (754,'FORM_4'), + (755,'FORM_4'), + (756,'FORM_4'), + (757,'FORM_4'), + (758,'FORM_4'), + (759,'FORM_4'), + (760,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (761,'FORM_4'), + (762,'FORM_4'), + (763,'FORM_4'), + (764,'FORM_4'), + (765,'FORM_4'), + (766,'FORM_4'), + (767,'FORM_4'), + (768,'FORM_4'), + (769,'FORM_4'), + (770,'FORM_4'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (771,'FORM_4'), + (772,'FORM_4'), + (773,'FORM_4'), + (774,'FORM_4'), + (1,'FORM_3'), + (2,'FORM_3'), + (3,'FORM_3'), + (4,'FORM_3'), + (5,'FORM_3'), + (6,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (7,'FORM_3'), + (8,'FORM_3'), + (9,'FORM_3'), + (10,'FORM_3'), + (11,'FORM_3'), + (12,'FORM_3'), + (13,'FORM_3'), + (14,'FORM_3'), + (15,'FORM_3'), + (16,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (17,'FORM_3'), + (18,'FORM_3'), + (19,'FORM_3'), + (20,'FORM_3'), + (21,'FORM_3'), + (22,'FORM_3'), + (23,'FORM_3'), + (24,'FORM_3'), + (25,'FORM_3'), + (26,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (27,'FORM_3'), + (28,'FORM_3'), + (29,'FORM_3'), + (30,'FORM_3'), + (31,'FORM_3'), + (32,'FORM_3'), + (33,'FORM_3'), + (34,'FORM_3'), + (35,'FORM_3'), + (36,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (37,'FORM_3'), + (38,'FORM_3'), + (39,'FORM_3'), + (40,'FORM_3'), + (41,'FORM_3'), + (42,'FORM_3'), + (43,'FORM_3'), + (44,'FORM_3'), + (45,'FORM_3'), + (46,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (47,'FORM_3'), + (48,'FORM_3'), + (49,'FORM_3'), + (50,'FORM_3'), + (51,'FORM_3'), + (52,'FORM_3'), + (53,'FORM_3'), + (54,'FORM_3'), + (55,'FORM_3'), + (56,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (57,'FORM_3'), + (58,'FORM_3'), + (59,'FORM_3'), + (60,'FORM_3'), + (61,'FORM_3'), + (62,'FORM_3'), + (63,'FORM_3'), + (64,'FORM_3'), + (65,'FORM_3'), + (66,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (67,'FORM_3'), + (68,'FORM_3'), + (69,'FORM_3'), + (70,'FORM_3'), + (71,'FORM_3'), + (72,'FORM_3'), + (73,'FORM_3'), + (75,'FORM_3'), + (76,'FORM_3'), + (77,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (78,'FORM_3'), + (79,'FORM_3'), + (80,'FORM_3'), + (81,'FORM_3'), + (82,'FORM_3'), + (83,'FORM_3'), + (84,'FORM_3'), + (85,'FORM_3'), + (86,'FORM_3'), + (87,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (88,'FORM_3'), + (89,'FORM_3'), + (90,'FORM_3'), + (91,'FORM_3'), + (92,'FORM_3'), + (93,'FORM_3'), + (94,'FORM_3'), + (96,'FORM_3'), + (97,'FORM_3'), + (98,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (99,'FORM_3'), + (100,'FORM_3'), + (101,'FORM_3'), + (102,'FORM_3'), + (103,'FORM_3'), + (104,'FORM_3'), + (105,'FORM_3'), + (106,'FORM_3'), + (107,'FORM_3'), + (108,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (109,'FORM_3'), + (110,'FORM_3'), + (111,'FORM_3'), + (112,'FORM_3'), + (113,'FORM_3'), + (114,'FORM_3'), + (115,'FORM_3'), + (116,'FORM_3'), + (117,'FORM_3'), + (118,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (119,'FORM_3'), + (120,'FORM_3'), + (121,'FORM_3'), + (122,'FORM_3'), + (123,'FORM_3'), + (124,'FORM_3'), + (125,'FORM_3'), + (126,'FORM_3'), + (127,'FORM_3'), + (128,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (129,'FORM_3'), + (130,'FORM_3'), + (131,'FORM_3'), + (132,'FORM_3'), + (133,'FORM_3'), + (134,'FORM_3'), + (135,'FORM_3'), + (136,'FORM_3'), + (137,'FORM_3'), + (138,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (139,'FORM_3'), + (140,'FORM_3'), + (141,'FORM_3'), + (142,'FORM_3'), + (143,'FORM_3'), + (144,'FORM_3'), + (145,'FORM_3'), + (146,'FORM_3'), + (147,'FORM_3'), + (148,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (149,'FORM_3'), + (150,'FORM_3'), + (151,'FORM_3'), + (152,'FORM_3'), + (153,'FORM_3'), + (154,'FORM_3'), + (155,'FORM_3'), + (156,'FORM_3'), + (157,'FORM_3'), + (158,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (159,'FORM_3'), + (160,'FORM_3'), + (161,'FORM_3'), + (162,'FORM_3'), + (163,'FORM_3'), + (164,'FORM_3'), + (165,'FORM_3'), + (166,'FORM_3'), + (167,'FORM_3'), + (168,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (169,'FORM_3'), + (170,'FORM_3'), + (171,'FORM_3'), + (172,'FORM_3'), + (173,'FORM_3'), + (174,'FORM_3'), + (175,'FORM_3'), + (176,'FORM_3'), + (177,'FORM_3'), + (178,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (179,'FORM_3'), + (180,'FORM_3'), + (181,'FORM_3'), + (182,'FORM_3'), + (183,'FORM_3'), + (184,'FORM_3'), + (185,'FORM_3'), + (186,'FORM_3'), + (187,'FORM_3'), + (188,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (189,'FORM_3'), + (190,'FORM_3'), + (191,'FORM_3'), + (192,'FORM_3'), + (193,'FORM_3'), + (194,'FORM_3'), + (195,'FORM_3'), + (196,'FORM_3'), + (197,'FORM_3'), + (198,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (199,'FORM_3'), + (200,'FORM_3'), + (201,'FORM_3'), + (202,'FORM_3'), + (203,'FORM_3'), + (204,'FORM_3'), + (205,'FORM_3'), + (206,'FORM_3'), + (207,'FORM_3'), + (208,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (209,'FORM_3'), + (210,'FORM_3'), + (211,'FORM_3'), + (212,'FORM_3'), + (213,'FORM_3'), + (214,'FORM_3'), + (215,'FORM_3'), + (216,'FORM_3'), + (217,'FORM_3'), + (218,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (219,'FORM_3'), + (220,'FORM_3'), + (221,'FORM_3'), + (222,'FORM_3'), + (223,'FORM_3'), + (224,'FORM_3'), + (225,'FORM_3'), + (226,'FORM_3'), + (227,'FORM_3'), + (228,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (229,'FORM_3'), + (230,'FORM_3'), + (231,'FORM_3'), + (232,'FORM_3'), + (233,'FORM_3'), + (234,'FORM_3'), + (235,'FORM_3'), + (236,'FORM_3'), + (237,'FORM_3'), + (238,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (239,'FORM_3'), + (240,'FORM_3'), + (241,'FORM_3'), + (242,'FORM_3'), + (243,'FORM_3'), + (244,'FORM_3'), + (245,'FORM_3'), + (246,'FORM_3'), + (247,'FORM_3'), + (248,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (249,'FORM_3'), + (250,'FORM_3'), + (251,'FORM_3'), + (252,'FORM_3'), + (253,'FORM_3'), + (254,'FORM_3'), + (255,'FORM_3'), + (256,'FORM_3'), + (257,'FORM_3'), + (258,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (259,'FORM_3'), + (260,'FORM_3'), + (261,'FORM_3'), + (262,'FORM_3'), + (263,'FORM_3'), + (264,'FORM_3'), + (265,'FORM_3'), + (266,'FORM_3'), + (267,'FORM_3'), + (268,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (269,'FORM_3'), + (270,'FORM_3'), + (271,'FORM_3'), + (272,'FORM_3'), + (273,'FORM_3'), + (274,'FORM_3'), + (275,'FORM_3'), + (276,'FORM_3'), + (277,'FORM_3'), + (278,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (279,'FORM_3'), + (280,'FORM_3'), + (281,'FORM_3'), + (282,'FORM_3'), + (283,'FORM_3'), + (284,'FORM_3'), + (285,'FORM_3'), + (286,'FORM_3'), + (287,'FORM_3'), + (288,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (289,'FORM_3'), + (290,'FORM_3'), + (291,'FORM_3'), + (292,'FORM_3'), + (293,'FORM_3'), + (294,'FORM_3'), + (295,'FORM_3'), + (296,'FORM_3'), + (297,'FORM_3'), + (298,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (299,'FORM_3'), + (300,'FORM_3'), + (301,'FORM_3'), + (302,'FORM_3'), + (303,'FORM_3'), + (304,'FORM_3'), + (305,'FORM_3'), + (306,'FORM_3'), + (307,'FORM_3'), + (308,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (309,'FORM_3'), + (310,'FORM_3'), + (311,'FORM_3'), + (312,'FORM_3'), + (313,'FORM_3'), + (314,'FORM_3'), + (387,'FORM_3'), + (315,'FORM_3'), + (316,'FORM_3'), + (317,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (318,'FORM_3'), + (319,'FORM_3'), + (320,'FORM_3'), + (321,'FORM_3'), + (322,'FORM_3'), + (323,'FORM_3'), + (324,'FORM_3'), + (325,'FORM_3'), + (326,'FORM_3'), + (327,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (328,'FORM_3'), + (329,'FORM_3'), + (330,'FORM_3'), + (331,'FORM_3'), + (332,'FORM_3'), + (333,'FORM_3'), + (334,'FORM_3'), + (335,'FORM_3'), + (336,'FORM_3'), + (337,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (338,'FORM_3'), + (339,'FORM_3'), + (340,'FORM_3'), + (341,'FORM_3'), + (342,'FORM_3'), + (343,'FORM_3'), + (344,'FORM_3'), + (345,'FORM_3'), + (346,'FORM_3'), + (347,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (348,'FORM_3'), + (349,'FORM_3'), + (350,'FORM_3'), + (351,'FORM_3'), + (352,'FORM_3'), + (353,'FORM_3'), + (354,'FORM_3'), + (355,'FORM_3'), + (356,'FORM_3'), + (357,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (358,'FORM_3'), + (359,'FORM_3'), + (360,'FORM_3'), + (361,'FORM_3'), + (362,'FORM_3'), + (363,'FORM_3'), + (364,'FORM_3'), + (365,'FORM_3'), + (366,'FORM_3'), + (367,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (368,'FORM_3'), + (369,'FORM_3'), + (370,'FORM_3'), + (371,'FORM_3'), + (372,'FORM_3'), + (373,'FORM_3'), + (374,'FORM_3'), + (375,'FORM_3'), + (376,'FORM_3'), + (377,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (378,'FORM_3'), + (379,'FORM_3'), + (380,'FORM_3'), + (381,'FORM_3'), + (382,'FORM_3'), + (383,'FORM_3'), + (384,'FORM_3'), + (385,'FORM_3'), + (386,'FORM_3'), + (388,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (389,'FORM_3'), + (390,'FORM_3'), + (391,'FORM_3'), + (392,'FORM_3'), + (393,'FORM_3'), + (394,'FORM_3'), + (395,'FORM_3'), + (396,'FORM_3'), + (397,'FORM_3'), + (398,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (399,'FORM_3'), + (400,'FORM_3'), + (401,'FORM_3'), + (402,'FORM_3'), + (403,'FORM_3'), + (404,'FORM_3'), + (405,'FORM_3'), + (406,'FORM_3'), + (407,'FORM_3'), + (408,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (409,'FORM_3'), + (410,'FORM_3'), + (411,'FORM_3'), + (412,'FORM_3'), + (413,'FORM_3'), + (414,'FORM_3'), + (415,'FORM_3'), + (416,'FORM_3'), + (417,'FORM_3'), + (418,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (419,'FORM_3'), + (420,'FORM_3'), + (421,'FORM_3'), + (422,'FORM_3'), + (423,'FORM_3'), + (424,'FORM_3'), + (425,'FORM_3'), + (426,'FORM_3'), + (427,'FORM_3'), + (428,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (429,'FORM_3'), + (430,'FORM_3'), + (431,'FORM_3'), + (432,'FORM_3'), + (433,'FORM_3'), + (434,'FORM_3'), + (435,'FORM_3'), + (436,'FORM_3'), + (437,'FORM_3'), + (438,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (439,'FORM_3'), + (440,'FORM_3'), + (441,'FORM_3'), + (442,'FORM_3'), + (443,'FORM_3'), + (444,'FORM_3'), + (445,'FORM_3'), + (446,'FORM_3'), + (447,'FORM_3'), + (448,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (449,'FORM_3'), + (450,'FORM_3'), + (451,'FORM_3'), + (452,'FORM_3'), + (453,'FORM_3'), + (454,'FORM_3'), + (455,'FORM_3'), + (456,'FORM_3'), + (457,'FORM_3'), + (458,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (459,'FORM_3'), + (460,'FORM_3'), + (461,'FORM_3'), + (462,'FORM_3'), + (463,'FORM_3'), + (464,'FORM_3'), + (465,'FORM_3'), + (466,'FORM_3'), + (467,'FORM_3'), + (468,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (470,'FORM_3'), + (471,'FORM_3'), + (472,'FORM_3'), + (473,'FORM_3'), + (474,'FORM_3'), + (475,'FORM_3'), + (476,'FORM_3'), + (477,'FORM_3'), + (478,'FORM_3'), + (479,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (480,'FORM_3'), + (481,'FORM_3'), + (482,'FORM_3'), + (483,'FORM_3'), + (484,'FORM_3'), + (485,'FORM_3'), + (486,'FORM_3'), + (487,'FORM_3'), + (488,'FORM_3'), + (489,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (490,'FORM_3'), + (491,'FORM_3'), + (492,'FORM_3'), + (493,'FORM_3'), + (494,'FORM_3'), + (496,'FORM_3'), + (497,'FORM_3'), + (498,'FORM_3'), + (499,'FORM_3'), + (500,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (501,'FORM_3'), + (502,'FORM_3'), + (503,'FORM_3'), + (504,'FORM_3'), + (505,'FORM_3'), + (506,'FORM_3'), + (507,'FORM_3'), + (508,'FORM_3'), + (509,'FORM_3'), + (510,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (511,'FORM_3'), + (512,'FORM_3'), + (513,'FORM_3'), + (514,'FORM_3'), + (515,'FORM_3'), + (516,'FORM_3'), + (517,'FORM_3'), + (518,'FORM_3'), + (519,'FORM_3'), + (520,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (521,'FORM_3'), + (522,'FORM_3'), + (523,'FORM_3'), + (524,'FORM_3'), + (525,'FORM_3'), + (526,'FORM_3'), + (527,'FORM_3'), + (528,'FORM_3'), + (529,'FORM_3'), + (530,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (531,'FORM_3'), + (532,'FORM_3'), + (533,'FORM_3'), + (534,'FORM_3'), + (535,'FORM_3'), + (536,'FORM_3'), + (537,'FORM_3'), + (538,'FORM_3'), + (539,'FORM_3'), + (540,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (541,'FORM_3'), + (542,'FORM_3'), + (543,'FORM_3'), + (544,'FORM_3'), + (545,'FORM_3'), + (546,'FORM_3'), + (547,'FORM_3'), + (548,'FORM_3'), + (549,'FORM_3'), + (550,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (551,'FORM_3'), + (552,'FORM_3'), + (553,'FORM_3'), + (554,'FORM_3'), + (555,'FORM_3'), + (556,'FORM_3'), + (557,'FORM_3'), + (558,'FORM_3'), + (559,'FORM_3'), + (560,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (561,'FORM_3'), + (562,'FORM_3'), + (563,'FORM_3'), + (564,'FORM_3'), + (565,'FORM_3'), + (566,'FORM_3'), + (567,'FORM_3'), + (568,'FORM_3'), + (569,'FORM_3'), + (570,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (571,'FORM_3'), + (572,'FORM_3'), + (573,'FORM_3'), + (574,'FORM_3'), + (575,'FORM_3'), + (576,'FORM_3'), + (577,'FORM_3'), + (578,'FORM_3'), + (579,'FORM_3'), + (580,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (581,'FORM_3'), + (582,'FORM_3'), + (583,'FORM_3'), + (584,'FORM_3'), + (585,'FORM_3'), + (586,'FORM_3'), + (587,'FORM_3'), + (588,'FORM_3'), + (589,'FORM_3'), + (590,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (591,'FORM_3'), + (592,'FORM_3'), + (593,'FORM_3'), + (594,'FORM_3'), + (595,'FORM_3'), + (596,'FORM_3'), + (597,'FORM_3'), + (598,'FORM_3'), + (599,'FORM_3'), + (600,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (601,'FORM_3'), + (602,'FORM_3'), + (603,'FORM_3'), + (604,'FORM_3'), + (605,'FORM_3'), + (606,'FORM_3'), + (607,'FORM_3'), + (608,'FORM_3'), + (609,'FORM_3'), + (610,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (611,'FORM_3'), + (612,'FORM_3'), + (613,'FORM_3'), + (614,'FORM_3'), + (615,'FORM_3'), + (616,'FORM_3'), + (617,'FORM_3'), + (618,'FORM_3'), + (619,'FORM_3'), + (620,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (621,'FORM_3'), + (622,'FORM_3'), + (623,'FORM_3'), + (624,'FORM_3'), + (625,'FORM_3'), + (626,'FORM_3'), + (627,'FORM_3'), + (628,'FORM_3'), + (629,'FORM_3'), + (630,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (631,'FORM_3'), + (632,'FORM_3'), + (633,'FORM_3'), + (634,'FORM_3'), + (635,'FORM_3'), + (636,'FORM_3'), + (637,'FORM_3'), + (638,'FORM_3'), + (639,'FORM_3'), + (640,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (641,'FORM_3'), + (642,'FORM_3'), + (643,'FORM_3'), + (644,'FORM_3'), + (645,'FORM_3'), + (646,'FORM_3'), + (647,'FORM_3'), + (648,'FORM_3'), + (649,'FORM_3'), + (650,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (651,'FORM_3'), + (652,'FORM_3'), + (653,'FORM_3'), + (654,'FORM_3'), + (655,'FORM_3'), + (656,'FORM_3'), + (657,'FORM_3'), + (658,'FORM_3'), + (659,'FORM_3'), + (660,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (661,'FORM_3'), + (662,'FORM_3'), + (663,'FORM_3'), + (664,'FORM_3'), + (665,'FORM_3'), + (666,'FORM_3'), + (667,'FORM_3'), + (668,'FORM_3'), + (669,'FORM_3'), + (670,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (671,'FORM_3'), + (672,'FORM_3'), + (673,'FORM_3'), + (674,'FORM_3'), + (675,'FORM_3'), + (676,'FORM_3'), + (677,'FORM_3'), + (678,'FORM_3'), + (679,'FORM_3'), + (680,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (681,'FORM_3'), + (682,'FORM_3'), + (683,'FORM_3'), + (684,'FORM_3'), + (685,'FORM_3'), + (686,'FORM_3'), + (687,'FORM_3'), + (688,'FORM_3'), + (689,'FORM_3'), + (690,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (691,'FORM_3'), + (692,'FORM_3'), + (693,'FORM_3'), + (694,'FORM_3'), + (695,'FORM_3'), + (696,'FORM_3'), + (697,'FORM_3'), + (698,'FORM_3'), + (699,'FORM_3'), + (700,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (701,'FORM_3'), + (702,'FORM_3'), + (703,'FORM_3'), + (704,'FORM_3'), + (705,'FORM_3'), + (706,'FORM_3'), + (707,'FORM_3'), + (746,'FORM_3'), + (747,'FORM_3'), + (748,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (749,'FORM_3'), + (750,'FORM_3'), + (751,'FORM_3'), + (752,'FORM_3'), + (753,'FORM_3'), + (754,'FORM_3'), + (755,'FORM_3'), + (756,'FORM_3'), + (757,'FORM_3'), + (758,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (759,'FORM_3'), + (760,'FORM_3'), + (761,'FORM_3'), + (762,'FORM_3'), + (763,'FORM_3'), + (764,'FORM_3'), + (765,'FORM_3'), + (775,'FORM_3'), + (776,'FORM_3'), + (777,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (778,'FORM_3'), + (779,'FORM_3'), + (780,'FORM_3'), + (781,'FORM_3'), + (782,'FORM_3'), + (783,'FORM_3'), + (784,'FORM_3'), + (785,'FORM_3'), + (786,'FORM_3'), + (787,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (788,'FORM_3'), + (789,'FORM_3'), + (790,'FORM_3'), + (791,'FORM_3'), + (792,'FORM_3'), + (793,'FORM_3'), + (794,'FORM_3'), + (795,'FORM_3'), + (796,'FORM_3'), + (797,'FORM_3'); +INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES + (798,'FORM_3'); + +REFRESH MATERIALIZED VIEW CONCURRENTLY v3.mv_expense_item_tree; \ No newline at end of file diff --git a/api/src/api/v1/forms.py b/api/src/api/v1/forms.py index 467a6b6..a14880b 100644 --- a/api/src/api/v1/forms.py +++ b/api/src/api/v1/forms.py @@ -190,7 +190,7 @@ async def update_cell( budget_line_service = BudgetLineService(db) budget_line = await budget_line_service.get(budget_line_id=cell_body.line_id, user=current_user) - if budget_line.budget_form_id != form_id: + if not budget_line or budget_line.budget_form_id != form_id: raise HTTPException(404, f"Строка формы не найдена") diff --git a/api/src/db/models/budget_form.py b/api/src/db/models/budget_form.py index f9dee74..7ad9a46 100644 --- a/api/src/db/models/budget_form.py +++ b/api/src/db/models/budget_form.py @@ -26,7 +26,11 @@ class BudgetForm(Base): updated_by: Mapped[int | None] = mapped_column( Integer, ForeignKey("v3.app_user.id", name="fk_v3_budget_form_updated_by") ) - updated_at: Mapped[datetime | None] = mapped_column(DateTime) + updated_at: Mapped[datetime | None] = mapped_column( + DateTime, + default=func.now(), + onupdate=func.now(), + ) org_unit_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("v3.org_unit.id", name="fk_v3_budget_form_org_unit") ) diff --git a/api/src/repository/budget_form_repository.py b/api/src/repository/budget_form_repository.py index 40ea5c5..dce6dc1 100644 --- a/api/src/repository/budget_form_repository.py +++ b/api/src/repository/budget_form_repository.py @@ -47,11 +47,12 @@ class BudgetFormRepository: if result: return result[0][0], [res[1] for res in result] else: + count_query = select(func.count(BudgetForm.id)) if query.whereclause is not None: - count_query = select(func.count(BudgetForm.id)).where( + count_query = count_query.where( query.whereclause ) - return (await self.db.execute(count_query)).scalar(), [] + return (await self.db.execute(count_query)).scalar(), [] else: return (await self.db.execute(query)).scalars().all() diff --git a/api/src/repository/org_unit_repository.py b/api/src/repository/org_unit_repository.py index 590acaf..fccc9b9 100644 --- a/api/src/repository/org_unit_repository.py +++ b/api/src/repository/org_unit_repository.py @@ -57,11 +57,12 @@ class OrgUnitRepository: if result: return result[0][0], [res[1] for res in result] else: + count_query = select(func.count(OrgUnit.id)) if query.whereclause is not None: - count_query = select(func.count(OrgUnit.id)).where( + count_query = count_query.where( query.whereclause ) - return (await self.db.execute(count_query)).scalar(), [] + return (await self.db.execute(count_query)).scalar(), [] else: return (await self.db.execute(query)).scalars().unique().all() diff --git a/api/src/repository/user_repository.py b/api/src/repository/user_repository.py index e841ada..c956744 100644 --- a/api/src/repository/user_repository.py +++ b/api/src/repository/user_repository.py @@ -99,11 +99,12 @@ class UserRepository: if result: return result[0][0], [res[1] for res in result] else: + count_query = select(func.count(AppUser.id)) if query.whereclause is not None: - count_query = select(func.count(AppUser.id)).where( + count_query = count_query.where( query.whereclause ) - return (await self.db.execute(count_query)).scalar(), [] + return (await self.db.execute(count_query)).scalar(), [] else: return (await self.db.execute(query)).scalars().unique().all() diff --git a/api/src/repository/vsp_repository.py b/api/src/repository/vsp_repository.py index 7fef37a..5740b9c 100644 --- a/api/src/repository/vsp_repository.py +++ b/api/src/repository/vsp_repository.py @@ -104,11 +104,12 @@ class VSPRepository: if result: return result[0][0], [res[1] for res in result] else: + count_query = select(func.count(Vsp.id)) if query.whereclause is not None: - count_query = select(func.count(Vsp.id)).where( + count_query = count_query.where( query.whereclause ) - return (await self.db.execute(count_query)).scalar(), [] + return (await self.db.execute(count_query)).scalar(), [] else: return (await self.db.execute(query)).scalars().all() return (await self.db.execute(query.order_by(Vsp.id))).scalars().all() diff --git a/api/tests/conftest.py b/api/tests/conftest.py new file mode 100644 index 0000000..017b7e6 --- /dev/null +++ b/api/tests/conftest.py @@ -0,0 +1,76 @@ +import os + +from argon2 import PasswordHasher +import pytest +import asyncio +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy import text +from alembic.config import Config +from alembic import command + +from src.core.config import settings + +hasher = PasswordHasher() + +ADMIN_PASSWORD = hasher.hash(os.getenv("OPENBAO__TEST_ADMIN_PASSWORD", "admin123")) +ISP_PASSWORD = hasher.hash(os.getenv("OPENBAO__TEST_EXECUTOR_PASSWORD", "admin123")) + + +def pytest_addoption(parser): + parser.addoption( + "--init_db", + action="store_true", + default=False, + help="Initialize database with migrations and seed data" + ) + + +@pytest.fixture(scope="session") +def init_db_flag(request): + return request.config.getoption("--init_db") + + +@pytest.fixture(scope="session", autouse=True) +def setup_database(init_db_flag): + if not init_db_flag: + return + + asyncio.run(run_migrations_async()) + asyncio.run( + run_seed_script_async( + admin_password=ADMIN_PASSWORD, + isp_password=ISP_PASSWORD, + ) + ) + + +async def run_migrations_async(): + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, run_migrations_sync) + + +def run_migrations_sync(): + alembic_cfg = Config("alembic.ini") + command.upgrade(alembic_cfg, "head") + + +async def run_seed_script_async(admin_password, isp_password): + sql_script_path = os.path.join(os.path.dirname(__file__), "sqls", "fixture.sql") + + with open(sql_script_path, "r", encoding="utf-8") as f: + sql_script = f.read() + + engine = create_async_engine(settings.DATABASE_URL, echo=False) + + try: + async with engine.connect() as conn: + for statement in sql_script.split(";"): + statement = statement.strip().format( + admin_password=admin_password, + isp_password=isp_password, + ) + if statement: + await conn.execute(text(statement)) + await conn.commit() + finally: + await engine.dispose() diff --git a/api/tests/sqls/fixture.sql b/api/tests/sqls/fixture.sql new file mode 100644 index 0000000..6cb94e9 --- /dev/null +++ b/api/tests/sqls/fixture.sql @@ -0,0 +1,30 @@ +INSERT INTO v3.app_user (id,email,username,hashed_password,full_name,role_id,is_active,created_at,updated_at) VALUES + (1,'admin@rshb.ru','admin','{admin_password}','admin',1,true,'2026-05-20 11:09:16.987331+03',NULL), + (2,'ispolnitel@rshb.ru','isp1','{isp_password}','Роль Исполнитель Первый',2,true,'2026-05-20 11:00:51.811967+03','2026-06-17 11:51:01.123054+03'); +SELECT setval('v3.app_user_id_seq', 2); + +INSERT INTO v3.org_unit (id,title,is_active,is_ssp) VALUES + (1,'Test_SSP',true,true), + (2,'Test РФ',true,false); +SELECT setval('v3.org_unit_id_seq', 2); + +INSERT INTO v3.user_org (id,user_id,org_unit_id) VALUES + (1,1,2), + (2,1,1), + (3,2,2); +SELECT setval('v3.user_org_id_seq', 3); + +INSERT INTO v3.budget_form (id,form_type_code,"year",created_by,created_at,updated_by,updated_at,org_unit_id) VALUES + (1,'FORM_1',2027,NULL,'2026-05-06 16:57:38.066371',NULL,'2026-06-11 14:35:53.783017',1); +SELECT setval('v3.budget_form_id_seq', 1); + +INSERT INTO v3.budget_line (id,budget_form_id,expense_item_id,"name",internal_order,vsp_id,project_id,justification,created_by,created_at,updated_by,updated_at,direction) VALUES + (1,1,88,'1234',NULL,NULL,NULL,NULL,NULL,'2026-05-06 16:58:05.793083',NULL,'2026-06-22 17:41:54.255313','Support'); +SELECT setval('v3.budget_line_id_seq', 1); + +INSERT INTO v3.form_phase (budget_form_id,sheet,phase_code,"role",column_keys,opens_at,closes_at) VALUES + (1,'AHR','test','DFIP','{{plan.q1}}','2026-05-05 03:00:00+03','2026-06-06 03:00:00+03'); + +INSERT INTO v3.vsp (id,branch_id,reg_number,address,format,opened_at,placement_type,staff_count,total_area,closed_at,is_active,updated_at,is_deleted,created_at,created_by,system_code,updated_by,vsp_type,notes,location_form,numbers,rent_contract_num,rent_end_date) VALUES + (1,2,'Тестовый всп 1','Тестовая 12','укукк','2026-05-07','ывс',2026,230,'2026-06-16',false,'2026-06-18 15:46:38.611903',false,'2026-06-04 14:36:59.737727',1,'1233',91,'','','встроенное помещение',NULL,'',NULL); +SELECT setval('v3.vsp_id_seq', 1);