diff --git a/api/alembic.ini b/api/alembic.ini new file mode 100644 index 0000000..54f756d --- /dev/null +++ b/api/alembic.ini @@ -0,0 +1,141 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/api/alembic/README b/api/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/api/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/api/alembic/env.py b/api/alembic/env.py new file mode 100644 index 0000000..bf37d10 --- /dev/null +++ b/api/alembic/env.py @@ -0,0 +1,66 @@ +import asyncio +from logging.config import fileConfig + +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. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + 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 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() + + +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() + + +def run_migrations_online() -> None: + asyncio.run(run_async_migrations()) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/api/alembic/script.py.mako b/api/alembic/script.py.mako new file mode 100644 index 0000000..480b130 --- /dev/null +++ b/api/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/api/alembic/versions/0001_initial_schema.py b/api/alembic/versions/0001_initial_schema.py new file mode 100644 index 0000000..c4561d8 --- /dev/null +++ b/api/alembic/versions/0001_initial_schema.py @@ -0,0 +1,1074 @@ +"""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( + 'form3_phase', + sa.Column('rf_project_report_id', sa.Integer(),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_v3_form3_phase_window', 'form3_phase', + sa.text('closes_at > opens_at'), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'chk_v3_form3_phase_columns', 'form3_phase', + sa.text('cardinality(column_keys) > 0'), + schema='v3', + ) + create_check_constraint_if_not_exists( + 'chk_v3_form3_phase_no_admin', 'form3_phase', + sa.text("role != 'ADMIN'"), + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_form3_phase_active', 'form3_phase', + ['rf_project_report_id', 'role', 'opens_at', 'closes_at'], + schema='v3', + ) + create_index_if_not_exists( + 'ix_v3_form3_phase_columns_gin', 'form3_phase', + ['column_keys'], + schema='v3', + postgresql_using='gin', + ) + 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('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/0004_vsp_delete_with_close_date.py b/api/alembic/versions/0004_vsp_delete_with_close_date.py new file mode 100644 index 0000000..309685a --- /dev/null +++ b/api/alembic/versions/0004_vsp_delete_with_close_date.py @@ -0,0 +1,63 @@ +from alembic import op + + +revision = "0004" +down_revision = "0003" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + """ + 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; + 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, + is_active = false, + 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; + + 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 + ), + null, null, v_old.branch_id + ); + + RETURN p_vsp_id; + END; + $function$; + """ + ) + + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/0005_phase_timestamps.py b/api/alembic/versions/0005_phase_timestamps.py new file mode 100644 index 0000000..2db5672 --- /dev/null +++ b/api/alembic/versions/0005_phase_timestamps.py @@ -0,0 +1,330 @@ +from alembic import op +import sqlalchemy as sa + + +revision = "0005" +down_revision = "0004" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute('ALTER TABLE v3.phase_template ALTER COLUMN opens_at TYPE timestamp USING opens_at::timestamp') + op.execute('ALTER TABLE v3.phase_template ALTER COLUMN closes_at TYPE timestamp USING closes_at::timestamp') + op.execute("ALTER TABLE v3.org_unit ADD COLUMN utc_offset VARCHAR(3) DEFAULT '+03' NOT NULL") + # заводим фазу с нужной таймзоной + op.execute( + """ + 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; + v_utc_offset VARCHAR(3); + v_org_unit INTEGER; + BEGIN + SELECT form_type_code, org_unit_id INTO v_form_type, v_org_unit + FROM v3.budget_form + WHERE id = p_form_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'budget_form id=% не существует', p_form_id; + END IF; + + SELECT utc_offset INTO v_utc_offset + FROM v3.org_unit + WHERE id = v_org_unit; + + 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::text || v_utc_offset)::timestamptz, + (pt.closes_at::text || v_utc_offset)::timestamptz + 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$ + ; + """ + ) + # Создавая фазу добавляем таймзону орг юнита + op.execute( + """ + 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, p_closes_at timestamp) + 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; + v_utc_offset VARCHAR(3); + BEGIN + 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; + + SELECT utc_offset INTO v_utc_offset + FROM v3.org_unit + WHERE id = v_org_unit_id; + + 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, + CASE WHEN p_opens_at IS NOT NULL THEN (p_opens_at::text || v_utc_offset)::timestamptz ELSE NULL END, + CASE WHEN p_closes_at IS NOT NULL THEN (p_closes_at::text || v_utc_offset)::timestamptz ELSE NULL END + + ) + RETURNING * INTO v_phase; + + + 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$ + ; + """ + ) + # Обнволяя фазу добавляем таймзону орг юнита + op.execute( + """ + 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 without time zone DEFAULT NULL::timestamp without time zone, p_closes_at timestamp without time zone DEFAULT NULL::timestamp without 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; + v_utc_offset VARCHAR(3); + v_opens_at timestamptz; + v_closes_at timestamptz; + 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; + + SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year, ou.utc_offset + INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year, v_utc_offset + 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 + )); + + IF p_opens_at is not null THEN + v_opens_at = (p_opens_at::text || v_utc_offset)::timestamptz; + END IF; + IF p_closes_at is not null THEN + v_closes_at = (p_closes_at::text || v_utc_offset)::timestamptz; + END IF; + + UPDATE v3.form_phase + SET role = COALESCE(p_role, role), + column_keys = COALESCE(p_column_keys, column_keys), + opens_at = COALESCE(v_opens_at, opens_at), + closes_at = COALESCE(v_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; + + + 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$ + ; + """ + ) + + # при обновлении таймзоны орг юнита обновляем актуальные фазы, связанные с ними + op.execute( + """ + CREATE OR REPLACE FUNCTION v3.upd_phases_timezones(p_org_unit_id integer, p_old_tz varchar(3), p_new_tz varchar(3)) + RETURNS integer + LANGUAGE plpgsql + AS $function$ + DECLARE + v_result integer; + BEGIN + IF p_old_tz = p_new_tz THEN + RETURN 0; + END IF; + WITH updated AS ( + UPDATE v3.form_phase + SET opens_at = ((opens_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz, + closes_at = ((closes_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz + WHERE budget_form_id IN (SELECT id FROM v3.budget_form WHERE org_unit_id = p_org_unit_id) + AND ( + opens_at >= CURRENT_TIMESTAMP + OR closes_at >= CURRENT_TIMESTAMP + ) + RETURNING * + ) + SELECT COUNT(*) INTO v_result FROM updated; + RETURN v_result; + + END; + $function$ + ; + """ + ) + # op.add_column( + # 'org_unit', + # sa.Column( + # 'utc_offset', + # sa.String(3), + # nullable=False, + # server_default='+03', + # ) + # ) + + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/0006_form3.py b/api/alembic/versions/0006_form3.py new file mode 100644 index 0000000..279a5a6 --- /dev/null +++ b/api/alembic/versions/0006_form3.py @@ -0,0 +1,79 @@ +import os +import re +from typing import List, Optional + +from alembic import op + +revision = "0006" +down_revision = "0005" +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", "0006_form3.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/0007_form_constraints.py b/api/alembic/versions/0007_form_constraints.py new file mode 100644 index 0000000..9b9bbc6 --- /dev/null +++ b/api/alembic/versions/0007_form_constraints.py @@ -0,0 +1,180 @@ +from alembic import op +import sqlalchemy as sa + + +revision = "0007" +down_revision = "0006" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute(""" + 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 INT; v_cap INT; v_oper INT; + v_sections TEXT[] := ARRAY['plan','approved','q1','q2','q3','q4']; + BEGIN + -- Одна budget_form на лист содержит ОБА направления (budget_line.direction). + -- Support/Development различаются параметром p_direction, а не отдельной формой. + WITH ranked AS ( + SELECT bf.id AS fid, ei.sheet AS sh + 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 AND bf.form_type_code = 'FORM_1' + GROUP BY bf.id, ei.sheet + ) + SELECT + MAX(CASE WHEN sh='AHR' THEN fid END), + MAX(CASE WHEN sh='CAP' THEN fid END), + MAX(CASE WHEN sh='OPER' THEN fid END) + INTO v_ahr, v_cap, v_oper + FROM ranked; + + RETURN QUERY + WITH + 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, '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, '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, '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, '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, 'OPER', 'Support', v_sections) + WHERE row_type IN ('ROOT','GROUP','ITEM') + ), + merged AS ( + 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 + 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 + 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$ + ; + """) + for t_name in ( + "reserve", "contract_detail", "ckk", "allocation", "sequestration", "contract_summary", + "collegial_approval", "rent_detail", "utility_detail", "plan", "budget_line_quarter" + ): + op.execute(f""" + delete from v3.{t_name} where line_id in ( + select id from v3.budget_line where budget_form_id not in (select min(fid) from ( + SELECT bf.id AS fid, bf.year as yr, bf.form_type_code as ftc, bf.org_unit_id as oui + FROM v3.budget_form bf + ) ranked + group by yr, ftc, oui)); + """) + op.execute(""" + delete from v3.budget_line where budget_form_id not in (select min(fid) from ( + SELECT bf.id AS fid, bf.year as yr, bf.form_type_code as ftc, bf.org_unit_id as oui + FROM v3.budget_form bf + ) ranked + group by yr, ftc, oui); + """) + op.execute(""" + delete from v3.budget_form where id not in (select min(fid) from ( + SELECT bf.id AS fid, bf.year as yr, bf.form_type_code as ftc, bf.org_unit_id as oui + FROM v3.budget_form bf + ) ranked + group by yr, ftc, oui); + """) + op.execute("ALTER TABLE v3.budget_form ADD CONSTRAINT uq_ftype_org_year UNIQUE (form_type_code, org_unit_id, year)") + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/0008_rent_end_constraint.py b/api/alembic/versions/0008_rent_end_constraint.py new file mode 100644 index 0000000..a7c09dc --- /dev/null +++ b/api/alembic/versions/0008_rent_end_constraint.py @@ -0,0 +1,16 @@ +from alembic import op +import sqlalchemy as sa + + +revision = "0008" +down_revision = "0007" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute("update v3.vsp set rent_end_date = coalesce(closed_at, opened_at) where rent_end_date is not null and opened_at is not null and rent_end_date < opened_at") + op.execute("alter table v3.vsp add CONSTRAINT chk_rent_date CHECK (((rent_end_date IS NULL) OR (opened_at IS NULL) OR (rent_end_date >= opened_at)))") + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/0009_phase3_timestamps.py b/api/alembic/versions/0009_phase3_timestamps.py new file mode 100644 index 0000000..9b503ec --- /dev/null +++ b/api/alembic/versions/0009_phase3_timestamps.py @@ -0,0 +1,785 @@ +from alembic import op +import sqlalchemy as sa + + +revision = "0009" +down_revision = "0008" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + """ + CREATE OR REPLACE FUNCTION v3.copy_template_to_report(p_report_id integer) + RETURNS integer + LANGUAGE plpgsql + AS $function$ + DECLARE + v_report_type VARCHAR; + v_inserted INTEGER; + v_org_unit INTEGER; + v_utc_offset VARCHAR(3); + BEGIN + SELECT report_type, org_unit_id INTO v_report_type, v_org_unit + FROM v3.rf_project_report AS rpr + INNER JOIN v3.project AS p ON p.id = rpr.project_id + WHERE rpr.id = p_report_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id; + END IF; + + SELECT utc_offset INTO v_utc_offset + FROM v3.org_unit + WHERE id = v_org_unit; + + INSERT INTO v3.form3_phase + (rf_project_report_id, phase_code, role, column_keys, opens_at, closes_at) + SELECT p_report_id, pt.phase_code, pt.role, + pt.column_keys, (pt.opens_at::text || v_utc_offset)::timestamptz, + (pt.closes_at::text || v_utc_offset)::timestamptz + FROM v3.phase_template pt + WHERE pt.form_type = 'FORM_3' + AND pt.sheet = v_report_type + ON CONFLICT (rf_project_report_id, phase_code) DO NOTHING; + + GET DIAGNOSTICS v_inserted = ROW_COUNT; + RETURN v_inserted; + END; + $function$ + ; + """ + ) + op.execute( + """ + CREATE OR REPLACE FUNCTION v3.add_form3_phase(p_rf_project_report_id integer, p_phase_code character varying, p_role character varying, p_column_keys text[], p_opens_at timestamp without time zone, p_closes_at timestamp without time zone) + RETURNS v3.form3_phase + LANGUAGE plpgsql + AS $function$ + DECLARE + v_phase v3.form3_phase; + v_org_unit_id INT; + v_org_unit_title VARCHAR; + v_year INT; + v_task_name TEXT; + v_phase_id TEXT; + v_utc_offset VARCHAR(3); + v_report_type VARCHAR; + v_project_id INT; + BEGIN + SELECT p.org_unit_id, ou.title, rpr.year, ou.utc_offset, rpr.report_type, rpr.project_id + INTO v_org_unit_id, v_org_unit_title, v_year, v_utc_offset, v_report_type, v_project_id + FROM v3.rf_project_report rpr + LEFT JOIN v3.project p ON p.id = rpr.project_id + LEFT JOIN v3.org_unit ou ON ou.id = p.org_unit_id + WHERE rpr.id = p_rf_project_report_id; + + INSERT INTO v3.form3_phase + (rf_project_report_id, phase_code, role, column_keys, opens_at, closes_at) + VALUES ( + p_rf_project_report_id, + p_phase_code, + p_role, + p_column_keys, + CASE WHEN p_opens_at IS NOT NULL THEN (p_opens_at::text || v_utc_offset)::timestamptz ELSE NULL END, + CASE WHEN p_closes_at IS NOT NULL THEN (p_closes_at::text || v_utc_offset)::timestamptz ELSE NULL END + + ) + RETURNING * INTO v_phase; + + + v_task_name := trim(concat( + 'FORM_3', + 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', p_rf_project_report_id, p_phase_code); + + PERFORM v3.log_event( + 'ACCESS_WINDOW_CHANGE', 'ACCESS', + jsonb_build_object( + 'entity_type', 'project', + 'entity_id', v_project_id, + 'core_entity_type', 'form3_phase', + 'core_entity_id', v_phase_id, + 'action', 'create', + 'phase_id', v_phase_id, + 'task_name', NULLIF(v_task_name, ''), + 'report_id', p_rf_project_report_id, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + 'year', v_year, + 'project_id', v_project_id, + 'report_type', v_report_type, + '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 + ), + null, null, v_org_unit_id + ); + + RETURN v_phase; + END; + $function$ + ; + """ + ) + op.execute( + """ + CREATE OR REPLACE FUNCTION v3.upd_form3_phase(p_rf_project_report_id integer, p_phase_code character varying, p_role character varying DEFAULT NULL::character varying, p_column_keys text[] DEFAULT NULL::text[], p_opens_at timestamp without time zone DEFAULT NULL::timestamp without time zone, p_closes_at timestamp without time zone DEFAULT NULL::timestamp without time zone) + RETURNS v3.form3_phase + LANGUAGE plpgsql + AS $function$ + DECLARE + v_old v3.form3_phase; + v_new v3.form3_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; + v_utc_offset VARCHAR(3); + v_opens_at timestamptz; + v_closes_at timestamptz; + v_report_type VARCHAR; + v_project_id INT; + BEGIN + SELECT * INTO v_old + FROM v3.form3_phase + WHERE rf_project_report_id = p_rf_project_report_id + AND phase_code = p_phase_code; + + IF NOT FOUND THEN + RAISE EXCEPTION 'form3_phase не найден (rf_project_report_id=%, phase=%)', + p_rf_project_report_id, p_phase_code; + END IF; + + SELECT p.org_unit_id, ou.title, rpr.year, ou.utc_offset, rpr.report_type, rpr.project_id + INTO v_org_unit_id, v_org_unit_title, v_year, v_utc_offset, v_report_type, v_project_id + FROM v3.rf_project_report rpr + LEFT JOIN v3.project p ON p.id = rpr.project_id + LEFT JOIN v3.org_unit ou ON ou.id = p.org_unit_id + WHERE rpr.id = p_rf_project_report_id; + v_task_name := trim(concat( + 'FORM_3', + 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 + )); + + IF p_opens_at is not null THEN + v_opens_at = (p_opens_at::text || v_utc_offset)::timestamptz; + END IF; + IF p_closes_at is not null THEN + v_closes_at = (p_closes_at::text || v_utc_offset)::timestamptz; + END IF; + + UPDATE v3.form3_phase + SET role = COALESCE(p_role, role), + column_keys = COALESCE(p_column_keys, column_keys), + opens_at = COALESCE(v_opens_at, opens_at), + closes_at = COALESCE(v_closes_at, closes_at) + WHERE rf_project_report_id = p_rf_project_report_id + AND phase_code = p_phase_code + RETURNING * INTO v_new; + + + v_phase_id := format('%s:%s', p_rf_project_report_id, 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( + 'entity_type', 'project', + 'entity_id', v_project_id, + 'core_entity_type', 'form3_phase', + 'core_entity_id', v_phase_id, + 'action', 'update', + 'phase_id', v_phase_id, + 'phase_name', p_phase_code, + 'task_name', NULLIF(v_task_name, ''), + 'report_id', p_rf_project_report_id, + 'report_type', v_report_type, + 'project_id', v_project_id, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + 'year', v_year, + 'phase_code', p_phase_code, + 'closes_at_before', v_old.closes_at, + 'closes_at_after', v_new.closes_at + ), + null, null, v_org_unit_id + ); + END IF; + + IF v_has_window_change THEN + PERFORM v3.log_event( + 'ACCESS_WINDOW_CHANGE', 'ACCESS', + jsonb_build_object( + 'entity_type', 'project', + 'entity_id', v_project_id, + 'core_entity_type', 'form3_phase', + 'core_entity_id', v_phase_id, + 'action', 'update', + 'phase_id', v_phase_id, + 'phase_name', p_phase_code, + 'task_id', p_rf_project_report_id, + 'task_name', NULLIF(v_task_name, ''), + 'project_id', v_project_id, + 'report_type', v_report_type, + 'year', v_year, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + 'phase_code', p_phase_code, + 'changes', v_changes + ), + null, null, v_org_unit_id + ); + END IF; + + RETURN v_new; + END; + $function$; + """ + ) + op.execute( + """ + CREATE OR REPLACE FUNCTION v3.del_form3_phase(p_rf_project_report_id integer, p_phase_code character varying) + RETURNS boolean + LANGUAGE plpgsql + AS $function$ + DECLARE + v_old v3.form3_phase; + v_org_unit_id INT; + v_org_unit_title VARCHAR; + v_year INT; + v_task_name TEXT; + v_phase_id TEXT; + v_report_type VARCHAR; + v_project_id INT; + BEGIN + DELETE FROM v3.form3_phase + WHERE rf_project_report_id = p_rf_project_report_id + AND phase_code = p_phase_code + RETURNING * INTO v_old; + + IF NOT FOUND THEN + RETURN FALSE; + END IF; + + SELECT p.org_unit_id, ou.title, rpr.year, rpr.report_type, rpr.project_id + INTO v_org_unit_id, v_org_unit_title, v_year, v_report_type, v_project_id + FROM v3.rf_project_report rpr + LEFT JOIN v3.project p ON p.id = rpr.project_id + LEFT JOIN v3.org_unit ou ON ou.id = p.org_unit_id + WHERE rpr.id = p_rf_project_report_id; + v_task_name := trim(concat( + 'FORM_3', + 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', p_rf_project_report_id, p_phase_code); + + PERFORM v3.log_event( + 'ACCESS_WINDOW_CHANGE', 'ACCESS', + jsonb_build_object( + 'entity_type', 'project', + 'entity_id', v_project_id, + 'core_entity_type', 'form3_phase', + 'core_entity_id', v_phase_id, + 'action', 'delete', + 'phase_id', v_phase_id, + 'phase_name', p_phase_code, + 'task_name', NULLIF(v_task_name, ''), + 'project_id', v_project_id, + 'year', v_year, + 'report_type', v_report_type, + 'org_unit_id', v_org_unit_id, + 'org_unit_name', v_org_unit_title, + '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 + ), + null, null, v_org_unit_id + ); + RETURN TRUE; + END; + $function$ + ; + """ + ) + op.execute( + """ + 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, p_system_code character varying DEFAULT NULL::character varying) + 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, + system_code + ) 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, + p_system_code + ) RETURNING id INTO v_pid; + + -- Первый год: 2 отчёта (LIMIT + CURRENT_EXPENSES) + snapshot фаз + сетка статей. + -- p_emit_audit=false — событие года покрыто PROJECT_CREATE ниже. Дальнейшие + -- годы добавляются отдельно через v3.add_project_year. + SELECT y.limit_report_id, y.current_expenses_report_id + INTO v_lim, v_cur + FROM v3.add_project_year(v_pid, p_year, false) y; + + -- Аудит: 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, + 'system_code', p_system_code, + 'staff_count', p_staff_count, + 'total_area', p_total_area, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur + )) + ); + + RETURN QUERY SELECT v_pid, v_lim, v_cur; + END; + $function$ + ; + """ + ) + op.execute( + """ + CREATE OR REPLACE FUNCTION v3.upd_phases_timezones(p_org_unit_id integer, p_old_tz character varying, p_new_tz character varying) + RETURNS integer + LANGUAGE plpgsql + AS $function$ + DECLARE + v_result integer; + v_result3 integer; + BEGIN + IF p_old_tz = p_new_tz THEN + RETURN 0; + END IF; + WITH updated AS ( + UPDATE v3.form_phase + SET opens_at = ((opens_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz, + closes_at = ((closes_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz + WHERE budget_form_id IN (SELECT id FROM v3.budget_form WHERE org_unit_id = p_org_unit_id) + AND ( + opens_at >= CURRENT_TIMESTAMP + OR closes_at >= CURRENT_TIMESTAMP + ) + RETURNING * + ) + SELECT COUNT(*) INTO v_result FROM updated; + + WITH updated AS ( + UPDATE v3.form3_phase + SET opens_at = ((opens_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz, + closes_at = ((closes_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz + WHERE rf_project_report_id IN ( + SELECT id FROM V3.rf_project_report rpr WHERE project_id IN ( + SELECT id FROM v3.project WHERE org_unit_id = p_org_unit_id + ) + ) + AND ( + opens_at >= CURRENT_TIMESTAMP + OR closes_at >= CURRENT_TIMESTAMP + ) + RETURNING * + ) + SELECT COUNT(*) INTO v_result3 FROM updated; + RETURN v_result + v_result3; + + END; + $function$ + ; + """ + ) + op.execute( + """ + 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 without time zone, p_closes_at timestamp without 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; + v_utc_offset VARCHAR(3); + BEGIN + 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; + + SELECT utc_offset INTO v_utc_offset + FROM v3.org_unit + WHERE id = v_org_unit_id; + + 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, + CASE WHEN p_opens_at IS NOT NULL THEN (p_opens_at::text || v_utc_offset)::timestamptz ELSE NULL END, + CASE WHEN p_closes_at IS NOT NULL THEN (p_closes_at::text || v_utc_offset)::timestamptz ELSE NULL END + + ) + RETURNING * INTO v_phase; + + + 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', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_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$ + ; + """ + ) + op.execute( + """ + 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', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_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$ + ; + """ + ) + op.execute( + """ + 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 without time zone DEFAULT NULL::timestamp without time zone, p_closes_at timestamp without time zone DEFAULT NULL::timestamp without 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; + v_utc_offset VARCHAR(3); + v_opens_at timestamptz; + v_closes_at timestamptz; + 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; + + SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year, ou.utc_offset + INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year, v_utc_offset + 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 + )); + + IF p_opens_at is not null THEN + v_opens_at = (p_opens_at::text || v_utc_offset)::timestamptz; + END IF; + IF p_closes_at is not null THEN + v_closes_at = (p_closes_at::text || v_utc_offset)::timestamptz; + END IF; + + UPDATE v3.form_phase + SET role = COALESCE(p_role, role), + column_keys = COALESCE(p_column_keys, column_keys), + opens_at = COALESCE(v_opens_at, opens_at), + closes_at = COALESCE(v_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; + + + 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( + 'entity_type', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_id, + + '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', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_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$ + ; + """ + ) + + +def downgrade() -> None: + pass diff --git a/api/alembic/versions/0010_form4.py b/api/alembic/versions/0010_form4.py new file mode 100644 index 0000000..3ab795b --- /dev/null +++ b/api/alembic/versions/0010_form4.py @@ -0,0 +1,84 @@ +import os +import re +import time + +from alembic import op +import sqlalchemy as sa + + +revision = "0010" +down_revision = "0009" +branch_labels = None +depends_on = None + + +_DOLLAR_TAG_RE = re.compile(r"\$\w+\$") + + +def _find_dollar_tag(line: str) -> str | None: + 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: str | None = None + + for line in sql.split("\n"): + stripped = line.strip() + if stripped.startswith("--"): + continue + + 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", "0010_form4.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/0011_form3_log.py b/api/alembic/versions/0011_form3_log.py new file mode 100644 index 0000000..175abc9 --- /dev/null +++ b/api/alembic/versions/0011_form3_log.py @@ -0,0 +1,84 @@ +import os +import re +import time + +from alembic import op +import sqlalchemy as sa + + +revision = "0011" +down_revision = "0010" +branch_labels = None +depends_on = None + + +_DOLLAR_TAG_RE = re.compile(r"\$\w+\$") + + +def _find_dollar_tag(line: str) -> str | None: + 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: str | None = None + + for line in sql.split("\n"): + stripped = line.strip() + if stripped.startswith("--"): + continue + + 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", "0011_form3_log.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/0012_del_project.py b/api/alembic/versions/0012_del_project.py new file mode 100644 index 0000000..378f0ad --- /dev/null +++ b/api/alembic/versions/0012_del_project.py @@ -0,0 +1,84 @@ +import os +import re +import time + +from alembic import op +import sqlalchemy as sa + + +revision = "0012" +down_revision = "0011" +branch_labels = None +depends_on = None + + +_DOLLAR_TAG_RE = re.compile(r"\$\w+\$") + + +def _find_dollar_tag(line: str) -> str | None: + 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: str | None = None + + for line in sql.split("\n"): + stripped = line.strip() + if stripped.startswith("--"): + continue + + 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", "0012_del_project.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/0013_form2_limit.py b/api/alembic/versions/0013_form2_limit.py new file mode 100644 index 0000000..a1cec04 --- /dev/null +++ b/api/alembic/versions/0013_form2_limit.py @@ -0,0 +1,84 @@ +import os +import re +import time + +from alembic import op +import sqlalchemy as sa + + +revision = "0013" +down_revision = "0012" +branch_labels = None +depends_on = None + + +_DOLLAR_TAG_RE = re.compile(r"\$\w+\$") + + +def _find_dollar_tag(line: str) -> str | None: + 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: str | None = None + + for line in sql.split("\n"): + stripped = line.strip() + if stripped.startswith("--"): + continue + + 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", "0013_form2_limit.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/0014_direction.py b/api/alembic/versions/0014_direction.py new file mode 100644 index 0000000..c090432 --- /dev/null +++ b/api/alembic/versions/0014_direction.py @@ -0,0 +1,82 @@ +import os +import re + +from alembic import op + + +revision = "0014" +down_revision = "0013" +branch_labels = None +depends_on = None + + +_DOLLAR_TAG_RE = re.compile(r"\$\w+\$") + + +def _find_dollar_tag(line: str) -> str | None: + 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: str | None = None + + for line in sql.split("\n"): + stripped = line.strip() + if stripped.startswith("--"): + continue + + 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", "0014_direction.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/0015_aggregates_fix.py b/api/alembic/versions/0015_aggregates_fix.py new file mode 100644 index 0000000..8b679a1 --- /dev/null +++ b/api/alembic/versions/0015_aggregates_fix.py @@ -0,0 +1,82 @@ +import os +import re + +from alembic import op + + +revision = "0015" +down_revision = "0014" +branch_labels = None +depends_on = None + + +_DOLLAR_TAG_RE = re.compile(r"\$\w+\$") + + +def _find_dollar_tag(line: str) -> str | None: + 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: str | None = None + + for line in sql.split("\n"): + stripped = line.strip() + if stripped.startswith("--"): + continue + + 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", "0015_aggregates_fix.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/__init__.py b/api/alembic/versions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/alembic/versions/sql/0002_functions.sql b/api/alembic/versions/sql/0002_functions.sql new file mode 100644 index 0000000..987eb91 --- /dev/null +++ b/api/alembic/versions/sql/0002_functions.sql @@ -0,0 +1,8178 @@ +-- 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, 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_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, 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_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, + '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_form_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]); + + SELECT bf.id + INTO v_form_id + FROM v3.rf_project_report r + JOIN v3.project p + ON p.id = r.project_id + LEFT JOIN v3.budget_form bf + ON bf.form_type_code = 'FORM_3' + AND bf.year = r.year + AND bf.org_unit_id = p.org_unit_id + WHERE r.id = v_report_id + LIMIT 1; + + -- Каскад (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; + + -- Аудит: ROW_DELETE + PERFORM v3.log_event( + 'ROW_DELETE', 'ROW', + jsonb_build_object( + 'sheet', 'FORM_3', + 'line_id', p_line_id, + 'form_id', v_form_id, + 'report_id', v_report_id, + 'expense_item_id', v_eid + ), + v_form_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, 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_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.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_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 (p_direction is null OR bl.direction is null or bl.direction = p_direction) + 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 AND (p_direction is null OR bl.direction is null or bl.direction = p_direction) + 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 AND (p_direction is null OR bl.direction is null or bl.direction = p_direction) + 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 AND (p_direction is null OR bl.direction is null or bl.direction = p_direction) + 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) AND (p_direction is null OR bl.direction is null or bl.direction = p_direction) + 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 AND (p_direction is null OR bl.direction is null or bl.direction = p_direction) + 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 INT; v_cap INT; v_oper INT; + v_sections TEXT[] := ARRAY['plan','approved','q1','q2','q3','q4']; +BEGIN + -- Одна budget_form на лист содержит ОБА направления (budget_line.direction). + -- Support/Development различаются параметром p_direction, а не отдельной формой. + WITH ranked AS ( + SELECT bf.id AS fid, ei.sheet AS sh + 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' THEN fid END), + MAX(CASE WHEN sh='CAP' THEN fid END), + MAX(CASE WHEN sh='OPER' THEN fid END) + INTO v_ahr, v_cap, v_oper + FROM ranked; + + RETURN QUERY + WITH + 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, '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, '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, '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, '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, 'OPER', 'Support', v_sections) + WHERE row_type IN ('ROOT','GROUP','ITEM') + ), + merged AS ( + 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 + 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 + 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$ +; + + + +CREATE OR REPLACE FUNCTION v3.copy_template_to_report(p_report_id INT) +RETURNS INTEGER +AS $function$ +DECLARE + v_report_type VARCHAR; + v_inserted INTEGER; +BEGIN + SELECT report_type INTO v_report_type + FROM v3.rf_project_report + WHERE id = p_report_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id; + END IF; + + INSERT INTO v3.form3_phase + (rf_project_report_id, phase_code, role, column_keys, opens_at, closes_at) + SELECT p_report_id, pt.phase_code, pt.role, + pt.column_keys, pt.opens_at, pt.closes_at + FROM v3.phase_template pt + WHERE pt.form_type = 'FORM_3' + AND pt.sheet = v_report_type + ON CONFLICT (rf_project_report_id, phase_code) DO NOTHING; + + GET DIAGNOSTICS v_inserted = ROW_COUNT; + RETURN v_inserted; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +CREATE OR REPLACE FUNCTION v3.user_in_report_org(p_user_id INT, p_report_id INT) +RETURNS BOOLEAN +AS $function$ +DECLARE + v_org INT; +BEGIN + SELECT p.org_unit_id + INTO v_org + FROM v3.rf_project_report r + JOIN v3.project p ON p.id = r.project_id + WHERE r.id = p_report_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$ +LANGUAGE plpgsql STABLE; + +CREATE OR REPLACE FUNCTION v3.editable_columns_for3( + p_report_id INT, + p_user_id INT +) +RETURNS TABLE ( + column_key TEXT, + closes_at TIMESTAMPTZ +) +AS $function$ +DECLARE + v_role VARCHAR; +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; + + v_role := v3.user_role_code(p_user_id); + IF v_role IS NULL THEN + RETURN; -- неизвестный/неактивный юзер → пустая маска + END IF; + + -- ADMIN: UNION всех column_keys всех фаз отчёта. + IF v_role = 'ADMIN' THEN + RETURN QUERY + SELECT ck AS column_key, + MAX(fp.closes_at) AS closes_at + FROM v3.form3_phase fp, + LATERAL unnest(fp.column_keys) AS ck + WHERE fp.rf_project_report_id = p_report_id + GROUP BY ck; + RETURN; + END IF; + + -- EXECUTOR_RF без назначения → пустая маска (default deny). + IF v_role = 'EXECUTOR_RF' AND NOT v3.user_in_report_org(p_user_id, p_report_id) THEN + RETURN; + END IF; + + RETURN QUERY + SELECT ck AS column_key, + MAX(fp.closes_at) AS closes_at + FROM v3.form3_phase fp, + LATERAL unnest(fp.column_keys) AS ck + WHERE fp.rf_project_report_id = p_report_id + AND fp.role = v_role + AND now() BETWEEN fp.opens_at AND fp.closes_at + GROUP BY ck; +END; +$function$ +LANGUAGE plpgsql STABLE; + +CREATE OR REPLACE FUNCTION v3.can_edit3( + p_report_id INT, + p_column_key TEXT, + p_user_id INT +) +RETURNS JSONB +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.rf_project_report WHERE id = p_report_id) THEN + RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_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_report_org(p_user_id, p_report_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.form3_phase fp + WHERE fp.rf_project_report_id = p_report_id + 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. Колонка В ПРИНЦИПЕ есть хоть в одной фазе этой роли на этом отчёте? + SELECT EXISTS ( + SELECT 1 + FROM v3.form3_phase fp + WHERE fp.rf_project_report_id = p_report_id + 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.form3_phase fp + WHERE fp.rf_project_report_id = p_report_id + 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.form3_phase fp + WHERE fp.rf_project_report_id = p_report_id + 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$ +LANGUAGE plpgsql STABLE; + +-- ─── 3. Authz-обёртки upd_form3_cell/cells (доп. сигнатуры с p_user_id) ───── + +CREATE OR REPLACE FUNCTION v3.upd_form3_cells( + p_report_id INT, + p_changes JSONB, + p_user_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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_edit3(p_report_id, 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; + + RETURN QUERY + SELECT * FROM v3.upd_form3_cells(p_report_id, p_changes); +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +CREATE OR REPLACE FUNCTION v3.upd_form3_cell( + p_report_id INT, + p_line_id INT, + p_column TEXT, + p_value JSONB, + p_user_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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 + )), + p_user_id + ); +$function$ +LANGUAGE sql VOLATILE; + +-- ─── 4. add_project — раскладка snapshot обоих отчётов ────────────────────── + +CREATE OR REPLACE FUNCTION v3.add_project( + p_name VARCHAR, + p_year INT, + p_org_unit_id INT, + p_level VARCHAR DEFAULT 'project', + p_parent_id INT DEFAULT NULL, + p_project_type VARCHAR DEFAULT NULL, + p_vsp_format VARCHAR DEFAULT NULL, + p_placement_type VARCHAR DEFAULT NULL, + p_object_address VARCHAR DEFAULT NULL, + p_staff_count INT DEFAULT NULL, + p_total_area NUMERIC DEFAULT NULL +) +RETURNS TABLE ( + project_id INT, + limit_report_id INT, + current_expenses_report_id INT +) +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 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; + + 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; + + -- Snapshot RBAC-фаз (иначе default deny — отчёт read-only). + PERFORM v3.copy_template_to_report(v_lim); + PERFORM v3.copy_template_to_report(v_cur); + + PERFORM v3.log_event( + 'PROJECT_CREATE', 'PROJECT_CREATE', + 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 + )) + ); + + RETURN QUERY SELECT v_pid, v_lim, v_cur; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + 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..bef1b34 --- /dev/null +++ b/api/alembic/versions/sql/0003_initial_data.sql @@ -0,0 +1,4278 @@ +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; + + +-- ─── 5. Seed фаз FORM_3 (идемпотентно, только FORM_3) ────────────────────── +-- DELETE скоупится на FORM_3 → фазы FORM_1/2/4 не затрагиваются. +DELETE FROM v3.phase_template WHERE form_type = 'FORM_3'; + +INSERT INTO v3.phase_template + (form_type, sheet, phase_code, role, column_keys, opens_at, closes_at) +SELECT 'FORM_3', sh, 'RF_FILL_2026', 'EXECUTOR_RF', + ARRAY[ + 'q1.adj_by_items','q1.adj_increase','q1.m1','q1.m2','q1.m3', + 'q2.adj_by_items','q2.adj_increase','q2.m1','q2.m2','q2.m3', + 'q3.adj_by_items','q3.adj_increase','q3.m1','q3.m2','q3.m3', + 'q4.adj_by_items','q4.adj_increase','q4.m1','q4.m2','q4.m3','q4.spod' + ], + '2026-01-01 00:00:00+00'::timestamptz, + '2026-12-31 23:59:59+00'::timestamptz +FROM (VALUES ('LIMIT'), ('CURRENT_EXPENSES')) AS s(sh); + +-- ─── 6. Backfill v3.form3_phase для существующих отчётов ──────────────────── +-- ON CONFLICT DO NOTHING внутри copy_template_to_report → повторный прогон +-- ничего не дублирует. +SELECT id, v3.copy_template_to_report(id) AS phases_inserted + FROM v3.rf_project_report + ORDER BY id; \ No newline at end of file diff --git a/api/alembic/versions/sql/0006_form3.sql b/api/alembic/versions/sql/0006_form3.sql new file mode 100644 index 0000000..598097d --- /dev/null +++ b/api/alembic/versions/sql/0006_form3.sql @@ -0,0 +1,2602 @@ +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.upd_form3_cell / v3.upd_form3_cells — точечная правка ячейки FORM_3. +-- Хранение FORM_3 идёт через rf_project_report → rf_project_report_line → +-- rf_project_report_quarter (PK = (rf_project_report_line_id, quarter)). +-- API симметричен upd_form_cell, но идентификатор «строки» — это +-- rf_project_report_line.id (то же `line_id`, что в JSONB read-функции). +-- +-- Поддерживаемые ключи p_column (см. v_form3_report_jsonb.sql): +-- Факт: 'qN.adj_by_items', 'qN.adj_increase', 'qN.m1', 'qN.m2', 'qN.m3' +-- (+ 'q4.spod' — только IV квартал) +-- Базовый план: 'q1.base_plan' … 'q4.base_plan' (все 4 квартала) +-- Скоррект план: 'q2.corrected_plan' … 'q4.corrected_plan' +-- (без q1 — шаблон рисует скорр.план с II квартала) +-- +-- Computed (RO): qN.total_corr, qN.quarter_actual, qN.economy (=«Остаток»), +-- qN.carryover, base_plan.year / totals.* (годовые итоги считаются на лету). +-- +-- Атрибуты rf_project_report (dev_type, vsp_format, object_address, ...) — +-- НЕ через эту функцию (это header отчёта, не ячейка строки). +-- ════════════════════════════════════════════════════════════════════════════ + + +DROP FUNCTION IF EXISTS v3._apply_form3_cell(INT, TEXT, JSONB); + +CREATE FUNCTION v3._apply_form3_cell( + p_line_id INT, -- rf_project_report_line.id + p_column TEXT, + p_value JSONB +) +RETURNS VOID +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'; + WHEN 'base_plan' THEN v_target_col := 'base_plan'; v_target_type := 'NUMERIC'; + WHEN 'corrected_plan' THEN + IF v_q = 1 THEN RAISE EXCEPTION 'corrected_plan is q2-q4 only: %', p_column; END IF; + v_target_col := 'corrected_plan'; 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$ +LANGUAGE plpgsql VOLATILE; + + +-- ─── Public batch ────────────────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cells(INT, JSONB); + +CREATE FUNCTION v3.upd_form3_cells( + p_report_id INT, + p_changes JSONB +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── Public single-cell wrapper ──────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cell(INT, INT, TEXT, JSONB); + +CREATE FUNCTION v3.upd_form3_cell( + p_report_id INT, + p_line_id INT, + p_column TEXT, + p_value JSONB +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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$ +LANGUAGE sql VOLATILE; +-- ════════════════════════════════════════════════════════════════════════════ +-- FORM_3 write API: project header + line ADD/DEL + project ADD-master +-- ════════════════════════════════════════════════════════════════════════════ +-- Дополняет существующий v3.upd_form3_cell (правка ячеек квартальных +-- значений в rf_project_report_quarter) — здесь функции для: +-- 1) v3.upd_project — правка атрибутов карточки проекта +-- 2) v3.add_form3_line — [DEPRECATED в UI] добавить позицию вручную. +-- 3) v3.del_form3_line — [DEPRECATED в UI] удалить позицию. +-- В FORM_3 сетка статей фиксирована (нет макроса добавления строк) — +-- строки засеваются автоматически, юзер пишет прямо в строку статьи. +-- add/del_form3_line оставлены для админ-правок/legacy. +-- 3b) v3.seed_form3_report_lines — засев полного набора листовых статей FORM_3. +-- 3c) v3.add_project_year — динамическое дорастание проекта годом +-- (2 отчёта + snapshot фаз + сетка статей). +-- 4) v3.add_project — мастер: project + первый год (add_project_year). +-- ════════════════════════════════════════════════════════════════════════════ + +-- ─── 1. upd_project ──────────────────────────────────────────────────────── + +DROP FUNCTION IF EXISTS v3.upd_project(INT, TEXT, JSONB); + +CREATE FUNCTION v3.upd_project( + p_project_id INT, + p_column TEXT, + p_value JSONB +) +RETURNS JSONB -- весь header проекта после правки +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 'system_code' THEN v_target_col := 'system_code'; 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, + 'system_code', p.system_code, + '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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 2. add_form3_line ───────────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.add_form3_line(INT, INT); + +CREATE FUNCTION v3.add_form3_line( + p_report_id INT, + p_expense_item_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ +DECLARE + v_new_id INT; +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; + 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; + + -- Аудит: ROW_CREATE + PERFORM v3.log_event( + 'ROW_CREATE', 'ROW_CREATE', + jsonb_build_object( + 'sheet', 'FORM_3', + 'line_id', v_new_id, + 'report_id', p_report_id, + 'expense_item_id', p_expense_item_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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 3. del_form3_line ───────────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.del_form3_line(INT); + +CREATE FUNCTION v3.del_form3_line( + p_line_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ +DECLARE + v_report_id INT; + v_eid 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; + + -- Аудит: ROW_DELETE + PERFORM v3.log_event( + 'ROW_DELETE', 'ROW_DELETE', + jsonb_build_object( + 'sheet', 'FORM_3', + 'line_id', p_line_id, + 'report_id', v_report_id, + 'expense_item_id', v_eid + ) + ); + + -- Возврат: только иерархия по пути удалённой (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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 3b. seed_form3_report_lines (фиксированная сетка статей) ────────────── +-- В FORM_3 строки НЕ добавляются пользователем (в шаблоне нет макроса добавления +-- строк) — сетка статей фиксирована, юзер пишет прямо в строку статьи. Поэтому +-- при создании отчёта засеваем ПОЛНЫЙ набор листовых статей FORM_3 (листовая = +-- FORM_3-статья без FORM_3-детей). Идемпотентно (uq_v3_rf_project_report_line). +-- Возвращает число вставленных строк. +DROP FUNCTION IF EXISTS v3.seed_form3_report_lines(INT); + +CREATE FUNCTION v3.seed_form3_report_lines(p_report_id INT) +RETURNS INT +AS $function$ +DECLARE + v_cnt INT; +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; + + INSERT INTO v3.rf_project_report_line (rf_project_report_id, expense_item_id) + SELECT p_report_id, e.id + FROM v3.expense_item e + JOIN v3.expense_item_form_type eft + ON eft.expense_item_id = e.id AND eft.form_type_code = 'FORM_3' + WHERE NOT EXISTS ( + SELECT 1 FROM v3.expense_item c + JOIN v3.expense_item_form_type eftc + ON eftc.expense_item_id = c.id AND eftc.form_type_code = 'FORM_3' + WHERE c.parent_id = e.id + ) + ON CONFLICT (rf_project_report_id, expense_item_id) DO NOTHING; + + GET DIAGNOSTICS v_cnt = ROW_COUNT; + RETURN v_cnt; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 3c. add_project_year (динамическое дорастание проекта по годам) ─────── +-- Проект может дополняться годами в процессе работы. Заводит пару отчётов +-- (LIMIT + CURRENT_EXPENSES) на (project, year), снапшотит RBAC-фазы и засевает +-- фиксированную сетку статей. Год несёт report_id — column_keys без префикса +-- года, RBAC работает пофазно на каждый год-отчёт (см. seed_phase_template). +DROP FUNCTION IF EXISTS v3.add_project_year(INT, INT, BOOLEAN); + +CREATE FUNCTION v3.add_project_year( + p_project_id INT, + p_year INT, + p_emit_audit BOOLEAN DEFAULT true -- false когда зовётся из add_project (там PROJECT_CREATE) +) +RETURNS TABLE ( + limit_report_id INT, + current_expenses_report_id INT +) +AS $function$ +DECLARE + v_lim INT; + v_cur INT; +BEGIN + IF p_year IS NULL THEN + RAISE EXCEPTION 'p_year обязателен'; + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_project_id) THEN + RAISE EXCEPTION 'project id=% не существует', p_project_id; + END IF; + IF EXISTS (SELECT 1 FROM v3.rf_project_report + WHERE project_id = p_project_id AND year = p_year) THEN + RAISE EXCEPTION 'год % у проекта id=% уже заведён', p_year, p_project_id; + END IF; + + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (p_project_id, p_year, 'LIMIT') RETURNING id INTO v_lim; + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (p_project_id, p_year, 'CURRENT_EXPENSES') RETURNING id INTO v_cur; + + -- Snapshot RBAC-фаз (иначе default deny → отчёт read-only) + PERFORM v3.copy_template_to_report(v_lim); + PERFORM v3.copy_template_to_report(v_cur); + + -- Фиксированная сетка статей на оба трека + PERFORM v3.seed_form3_report_lines(v_lim); + PERFORM v3.seed_form3_report_lines(v_cur); + + IF p_emit_audit THEN + PERFORM v3.log_event( + 'PROJECT_YEAR_ADD', 'PROJECT_YEAR_ADD', + jsonb_build_object( + 'project_id', p_project_id, + 'year', p_year, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur + ) + ); + END IF; + + RETURN QUERY SELECT v_lim, v_cur; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 4. add_project (мастер: project + первый год через add_project_year) ── +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC); +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC); +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC); +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC, VARCHAR); + +CREATE FUNCTION v3.add_project( + p_name VARCHAR, + p_year INT, + p_org_unit_id INT, + p_level VARCHAR DEFAULT 'project', -- 'program' | 'project' + p_parent_id INT DEFAULT NULL, -- обязателен если level='project' и есть программа + p_project_type VARCHAR DEFAULT NULL, + p_vsp_format VARCHAR DEFAULT NULL, + p_placement_type VARCHAR DEFAULT NULL, + p_object_address VARCHAR DEFAULT NULL, + p_staff_count INT DEFAULT NULL, + p_total_area NUMERIC DEFAULT NULL, + p_system_code VARCHAR DEFAULT NULL -- Системный код ВСП +) +RETURNS TABLE ( + project_id INT, + limit_report_id INT, + current_expenses_report_id INT +) +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, + system_code + ) 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, + p_system_code + ) RETURNING id INTO v_pid; + + -- Первый год: 2 отчёта (LIMIT + CURRENT_EXPENSES) + snapshot фаз + сетка статей. + -- p_emit_audit=false — событие года покрыто PROJECT_CREATE ниже. Дальнейшие + -- годы добавляются отдельно через v3.add_project_year. + SELECT y.limit_report_id, y.current_expenses_report_id + INTO v_lim, v_cur + FROM v3.add_project_year(v_pid, p_year, false) y; + + -- Аудит: PROJECT_CREATE — все заполненные поля + id отчётов + PERFORM v3.log_event( + 'PROJECT_CREATE', 'PROJECT_CREATE', + 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, + 'system_code', p_system_code, + 'staff_count', p_staff_count, + 'total_area', p_total_area, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur + )) + ); + + RETURN QUERY SELECT v_pid, v_lim, v_cur; +END; +$function$ +LANGUAGE plpgsql VOLATILE; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form3_report_sections — отображение листа ЛП (LIMIT) или ТР (CURRENT) +-- ════════════════════════════════════════════════════════════════════════════ +-- Возвращает иерархию (ROOT/GROUP/ITEM/SUB_ITEM) + INPUT строки в tree-order +-- для одного rf_project_report (один трек: report_type = LIMIT либо +-- CURRENT_EXPENSES). UI выбирает трек, передавая report_id соответствующей +-- записи rf_project_report. +-- +-- Колоночная раскладка повторяет шаблон Формы 3: +-- header, q1, q2, q3, q4, year_totals. +-- +-- Q1: adj_by_items, adj_increase, total_corr, m1, m2, m3, quarter_actual, economy +-- Q2/Q3: carryover, adj_by_items, adj_increase, total_corr, m1..m3, quarter_actual, economy +-- Q4: carryover, adj_by_items, adj_increase, total_corr, m1..m3, spod, quarter_actual, economy +-- year_totals: total_year_corr, total_year_actual, economy_year +-- +-- p_sections — массив включаемых блоков (NULL = все). Допустимые имена: +-- 'q1', 'q2', 'q3', 'q4', 'year' +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form3_report_sections(INT, TEXT[]); + +CREATE FUNCTION v3.v_form3_report_sections( + p_report_id INT, + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + line_id INT, + col_section_code VARCHAR, + col_item_id VARCHAR, + col_num_group_id VARCHAR, + col_name VARCHAR, + -- Q1 + 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, + -- Q2 + 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, + -- Q3 + 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, + -- Q4 + 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, + -- year totals + col_year_total_corr NUMERIC, + col_year_total_actual NUMERIC, + col_year_economy NUMERIC, + -- план-блоки (v2): Базовый план (q1–q4 + Год) и Скоррект план (q2–q4) + col_q1_base_plan NUMERIC, + col_q2_base_plan NUMERIC, + col_q3_base_plan NUMERIC, + col_q4_base_plan NUMERIC, + col_year_base_plan NUMERIC, + col_q2_corrected_plan NUMERIC, + col_q3_corrected_plan NUMERIC, + col_q4_corrected_plan NUMERIC, + _sort_path INT[] +) +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, + q.base_plan, q.corrected_plan + 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, + q1.base_plan AS q1_bp, q2.base_plan AS q2_bp, + q3.base_plan AS q3_bp, q4.base_plan AS q4_bp, + q2.corrected_plan AS q2_cp, q3.corrected_plan AS q3_cp, q4.corrected_plan AS q4_cp + 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, + SUM(q.base_plan) AS bp, SUM(q.corrected_plan) AS cp + 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, + SUM(a.bp) AS bp + 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, + SUM(a.bp) AS bp, SUM(a.cp) AS cp + 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, + SUM(a.bp) AS bp, SUM(a.cp) AS cp + 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, + SUM(a.bp) AS bp, SUM(a.cp) AS cp + 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, + -- план-блоки + CASE WHEN s_q1 THEN e.q1_bp END, + CASE WHEN s_q2 THEN e.q2_bp END, + CASE WHEN s_q3 THEN e.q3_bp END, + CASE WHEN s_q4 THEN e.q4_bp END, + CASE WHEN s_year THEN COALESCE(e.q1_bp,0)+COALESCE(e.q2_bp,0)+ + COALESCE(e.q3_bp,0)+COALESCE(e.q4_bp,0) END, + CASE WHEN s_q2 THEN e.q2_cp END, + CASE WHEN s_q3 THEN e.q3_cp END, + CASE WHEN s_q4 THEN e.q4_cp 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, + -- план-блоки (rollup) + CASE WHEN s_q1 THEN a1.bp END, + CASE WHEN s_q2 THEN a2.bp END, + CASE WHEN s_q3 THEN a3.bp END, + CASE WHEN s_q4 THEN a4.bp END, + CASE WHEN s_year THEN COALESCE(a1.bp,0)+COALESCE(a2.bp,0)+ + COALESCE(a3.bp,0)+COALESCE(a4.bp,0) END, + CASE WHEN s_q2 THEN a2.cp END, + CASE WHEN s_q3 THEN a3.cp END, + CASE WHEN s_q4 THEN a4.cp 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$ +LANGUAGE plpgsql STABLE; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.upd_form3_cell / v3.upd_form3_cells — обёртки FORM_3 с проверкой роли+окна +-- ════════════════════════════════════════════════════════════════════════════ +-- Параллель upd_form_cell_authz.sql, но для ветки rf_project_report. Каждая +-- ячейка прогоняется через v3.can_edit3(report_id, column, user_id). Если хоть +-- одна запрещена — RAISE EXCEPTION в формате, совместимом с конвенцией v3: +-- • role_not_allowed: +-- • org_not_assigned: +-- • window_closed: (opens ) — окно ещё не наступило +-- • window_closed: (closed ) — окно уже закрыто +-- • window_closed: (no phase) — фаз вообще нет +-- +-- HTTP-слой парсит префикс до `:` и мапит на 403 (см. SPEC.md §5). +-- +-- ⚠ Применять СТРОГО после upd_form3_cell.sql (low-level 2-арг/4-арг). Здесь +-- добавляется p_user_id → это ДРУГИЕ сигнатуры (3-арг/5-арг), которые +-- сосуществуют с low-level и делегируют в них после проверки. HTTP вызывает +-- версии с user_id. «Листа» тут нет — report_type определяется отчётом внутри +-- can_edit3. +-- ════════════════════════════════════════════════════════════════════════════ + +-- ─── upd_form3_cells (массовый, с авторизацией) ──────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cells(INT, JSONB, INT); + +CREATE FUNCTION v3.upd_form3_cells( + p_report_id INT, + p_changes JSONB, + p_user_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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_edit3(p_report_id, 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; + + -- Все ячейки разрешены — делегируем в low-level (2-арг). ROW_UPDATE не + -- логируем (см. logging_events.md): cell-edit'ы слишком частые. + RETURN QUERY + SELECT * FROM v3.upd_form3_cells(p_report_id, p_changes); +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +-- ─── upd_form3_cell (одна ячейка, тонкий wrapper) ────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cell(INT, INT, TEXT, JSONB, INT); + +CREATE FUNCTION v3.upd_form3_cell( + p_report_id INT, + p_line_id INT, + p_column TEXT, + p_value JSONB, + p_user_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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 + )), + p_user_id + ); +$function$ +LANGUAGE sql VOLATILE; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.migrate_form3_v2 — DDL под переработку FORM_3 (листы ЛП/ТР, v2) +-- ════════════════════════════════════════════════════════════════════════════ +-- Источник: «Пример_Форма_3_new(1).xlsm» (листы шаблон_ЛП / шаблон_ТР) + PDF-дифф +-- аналитика. Финансовая часть переразбита с 4 на 10 блоков и переведена на +-- многолетнюю раскладку (год1/год2/год3 рядом, кварталы повторяются на год). +-- +-- Новые блоки внутри одного года: +-- • Базовый план по Лимиту Правления — ручной ввод q1–q4, Год = Σ кв. +-- • Скоррект план по Лимиту Правления — ручной ввод q2–q4 (без q1, без Года) +-- • Фактические затраты — уже покрыты rf_project_report_quarter +-- (adj_by_items / adj_increase / actual_m1..m3 / actual_spod), «Остаток» и +-- квартальные/годовые итоги считаются на лету в read-функции. +-- +-- Многолетность уже поддержана моделью: один project → много rf_project_report +-- по (year × report_type). Здесь добавляется ТОЛЬКО хранение план-блоков и +-- «Системный код ВСП» в шапке проекта. +-- ════════════════════════════════════════════════════════════════════════════ + +-- ───────────── 1. rf_project_report_quarter: план-блоки ───────────────────── +-- base_plan — «Базовый план по Лимиту Правления», поквартально (q1–q4). +-- corrected_plan — «Скоррект план по Лимиту Правления», поквартально (q2–q4). +-- Для q1 значения не существует (шаблон рисует скорр.план с +-- II квартала) — гарантируется CHECK ниже + write-слой/RBAC. +ALTER TABLE v3.rf_project_report_quarter + ADD COLUMN IF NOT EXISTS base_plan NUMERIC, + ADD COLUMN IF NOT EXISTS corrected_plan NUMERIC; + +-- Скоррект план не заводится в I квартале (нет колонки в шаблоне). +ALTER TABLE v3.rf_project_report_quarter + DROP CONSTRAINT IF EXISTS chk_v3_rf_quarter_corr_plan_q1; +ALTER TABLE v3.rf_project_report_quarter + ADD CONSTRAINT chk_v3_rf_quarter_corr_plan_q1 + CHECK (quarter <> 1 OR corrected_plan IS NULL); + +-- ───────────── 2. v3.project: «Системный код ВСП» ─────────────────────────── +-- Новое поле шапки «Информация о проекте ВСП» (в примере — 1808). Выбор из +-- справочника ИНФО / выпадающего списка; хранится как атрибут проекта (вводится +-- один раз, не дублируется между треками/годами — как и остальные атрибуты ВСП). +ALTER TABLE v3.project + ADD COLUMN IF NOT EXISTS system_code VARCHAR; + +-- ───────────── 3. Обновление энум-списков шапки ──────────────────────────── +-- В новом шаблоне списки изменились относительно старого VBA-набора. +-- project_type: новый перечень из 8 значений (шаблон_ЛП F2:F9). +ALTER TABLE v3.project DROP CONSTRAINT IF EXISTS project_project_type_check; +ALTER TABLE v3.project + ADD CONSTRAINT project_project_type_check + CHECK (project_type IS NULL OR project_type IN ( + 'Открытие ВСП','Реновация ВСП','Закрытие ВСП', + 'Реновация РФ','Закрытие РФ', + 'Открытие УРМ','Реновация УРМ','Закрытие УРМ' + )); + +-- vsp_format («Новый формат ВСП») — поле автозаполняется из справочника INFO +-- («Утвержденный формат»), не вводится вручную и его словарь ведётся как +-- reference-data. Жёсткий CHECK снимаем, чтобы новые форматы не ломали вставку. +ALTER TABLE v3.project DROP CONSTRAINT IF EXISTS project_vsp_format_check; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.upd_form3_cell / v3.upd_form3_cells — точечная правка ячейки FORM_3. +-- Хранение FORM_3 идёт через rf_project_report → rf_project_report_line → +-- rf_project_report_quarter (PK = (rf_project_report_line_id, quarter)). +-- API симметричен upd_form_cell, но идентификатор «строки» — это +-- rf_project_report_line.id (то же `line_id`, что в JSONB read-функции). +-- +-- Поддерживаемые ключи p_column (см. v_form3_report_jsonb.sql): +-- Факт: 'qN.adj_by_items', 'qN.adj_increase', 'qN.m1', 'qN.m2', 'qN.m3' +-- (+ 'q4.spod' — только IV квартал) +-- Базовый план: 'q1.base_plan' … 'q4.base_plan' (все 4 квартала) +-- Скоррект план: 'q2.corrected_plan' … 'q4.corrected_plan' +-- (без q1 — шаблон рисует скорр.план с II квартала) +-- +-- Computed (RO): qN.total_corr, qN.quarter_actual, qN.economy (=«Остаток»), +-- qN.carryover, base_plan.year / totals.* (годовые итоги считаются на лету). +-- +-- Атрибуты rf_project_report (dev_type, vsp_format, object_address, ...) — +-- НЕ через эту функцию (это header отчёта, не ячейка строки). +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3._apply_form3_cell(INT, TEXT, JSONB); + +CREATE FUNCTION v3._apply_form3_cell( + p_line_id INT, -- rf_project_report_line.id + p_column TEXT, + p_value JSONB +) +RETURNS VOID +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'; + WHEN 'base_plan' THEN v_target_col := 'base_plan'; v_target_type := 'NUMERIC'; + WHEN 'corrected_plan' THEN + IF v_q = 1 THEN RAISE EXCEPTION 'corrected_plan is q2-q4 only: %', p_column; END IF; + v_target_col := 'corrected_plan'; 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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── Public batch ────────────────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cells(INT, JSONB); + +CREATE FUNCTION v3.upd_form3_cells( + p_report_id INT, + p_changes JSONB +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── Public single-cell wrapper ──────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cell(INT, INT, TEXT, JSONB); + +CREATE FUNCTION v3.upd_form3_cell( + p_report_id INT, + p_line_id INT, + p_column TEXT, + p_value JSONB +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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$ +LANGUAGE sql VOLATILE; +-- ════════════════════════════════════════════════════════════════════════════ +-- FORM_3 write API: project header + line ADD/DEL + project ADD-master +-- ════════════════════════════════════════════════════════════════════════════ +-- Дополняет существующий v3.upd_form3_cell (правка ячеек квартальных +-- значений в rf_project_report_quarter) — здесь функции для: +-- 1) v3.upd_project — правка атрибутов карточки проекта +-- 2) v3.add_form3_line — [DEPRECATED в UI] добавить позицию вручную. +-- 3) v3.del_form3_line — [DEPRECATED в UI] удалить позицию. +-- В FORM_3 сетка статей фиксирована (нет макроса добавления строк) — +-- строки засеваются автоматически, юзер пишет прямо в строку статьи. +-- add/del_form3_line оставлены для админ-правок/legacy. +-- 3b) v3.seed_form3_report_lines — засев полного набора листовых статей FORM_3. +-- 3c) v3.add_project_year — динамическое дорастание проекта годом +-- (2 отчёта + snapshot фаз + сетка статей). +-- 4) v3.add_project — мастер: project + первый год (add_project_year). +-- ════════════════════════════════════════════════════════════════════════════ + +-- ─── 1. upd_project ──────────────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_project(INT, TEXT, JSONB); + +CREATE FUNCTION v3.upd_project( + p_project_id INT, + p_column TEXT, + p_value JSONB +) +RETURNS JSONB -- весь header проекта после правки +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 'system_code' THEN v_target_col := 'system_code'; 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, + 'system_code', p.system_code, + '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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 2. add_form3_line ───────────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.add_form3_line(INT, INT); + +CREATE FUNCTION v3.add_form3_line( + p_report_id INT, + p_expense_item_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ +DECLARE + v_new_id INT; +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; + 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; + + -- Аудит: ROW_CREATE + PERFORM v3.log_event( + 'ROW_CREATE', 'ROW_CREATE', + jsonb_build_object( + 'sheet', 'FORM_3', + 'line_id', v_new_id, + 'report_id', p_report_id, + 'expense_item_id', p_expense_item_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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 3. del_form3_line ───────────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.del_form3_line(INT); + +CREATE FUNCTION v3.del_form3_line( + p_line_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ +DECLARE + v_report_id INT; + v_eid 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; + + -- Аудит: ROW_DELETE + PERFORM v3.log_event( + 'ROW_DELETE', 'ROW_DELETE', + jsonb_build_object( + 'sheet', 'FORM_3', + 'line_id', p_line_id, + 'report_id', v_report_id, + 'expense_item_id', v_eid + ) + ); + + -- Возврат: только иерархия по пути удалённой (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$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 3b. seed_form3_report_lines (фиксированная сетка статей) ────────────── +-- В FORM_3 строки НЕ добавляются пользователем (в шаблоне нет макроса добавления +-- строк) — сетка статей фиксирована, юзер пишет прямо в строку статьи. Поэтому +-- при создании отчёта засеваем ПОЛНЫЙ набор листовых статей FORM_3 (листовая = +-- FORM_3-статья без FORM_3-детей). Идемпотентно (uq_v3_rf_project_report_line). +-- Возвращает число вставленных строк. +DROP FUNCTION IF EXISTS v3.seed_form3_report_lines(INT); + +CREATE FUNCTION v3.seed_form3_report_lines(p_report_id INT) +RETURNS INT +AS $function$ +DECLARE + v_cnt INT; +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; + + INSERT INTO v3.rf_project_report_line (rf_project_report_id, expense_item_id) + SELECT p_report_id, e.id + FROM v3.expense_item e + JOIN v3.expense_item_form_type eft + ON eft.expense_item_id = e.id AND eft.form_type_code = 'FORM_3' + WHERE NOT EXISTS ( + SELECT 1 FROM v3.expense_item c + JOIN v3.expense_item_form_type eftc + ON eftc.expense_item_id = c.id AND eftc.form_type_code = 'FORM_3' + WHERE c.parent_id = e.id + ) + ON CONFLICT (rf_project_report_id, expense_item_id) DO NOTHING; + + GET DIAGNOSTICS v_cnt = ROW_COUNT; + RETURN v_cnt; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 3c. add_project_year (динамическое дорастание проекта по годам) ─────── +-- Проект может дополняться годами в процессе работы. Заводит пару отчётов +-- (LIMIT + CURRENT_EXPENSES) на (project, year), снапшотит RBAC-фазы и засевает +-- фиксированную сетку статей. Год несёт report_id — column_keys без префикса +-- года, RBAC работает пофазно на каждый год-отчёт (см. seed_phase_template). +DROP FUNCTION IF EXISTS v3.add_project_year(INT, INT, BOOLEAN); + +CREATE FUNCTION v3.add_project_year( + p_project_id INT, + p_year INT, + p_emit_audit BOOLEAN DEFAULT true -- false когда зовётся из add_project (там PROJECT_CREATE) +) +RETURNS TABLE ( + limit_report_id INT, + current_expenses_report_id INT +) +AS $function$ +DECLARE + v_lim INT; + v_cur INT; +BEGIN + IF p_year IS NULL THEN + RAISE EXCEPTION 'p_year обязателен'; + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_project_id) THEN + RAISE EXCEPTION 'project id=% не существует', p_project_id; + END IF; + IF EXISTS (SELECT 1 FROM v3.rf_project_report + WHERE project_id = p_project_id AND year = p_year) THEN + RAISE EXCEPTION 'год % у проекта id=% уже заведён', p_year, p_project_id; + END IF; + + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (p_project_id, p_year, 'LIMIT') RETURNING id INTO v_lim; + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (p_project_id, p_year, 'CURRENT_EXPENSES') RETURNING id INTO v_cur; + + -- Snapshot RBAC-фаз (иначе default deny → отчёт read-only) + PERFORM v3.copy_template_to_report(v_lim); + PERFORM v3.copy_template_to_report(v_cur); + + -- Фиксированная сетка статей на оба трека + PERFORM v3.seed_form3_report_lines(v_lim); + PERFORM v3.seed_form3_report_lines(v_cur); + + IF p_emit_audit THEN + PERFORM v3.log_event( + 'PROJECT_YEAR_ADD', 'PROJECT_YEAR_ADD', + jsonb_build_object( + 'project_id', p_project_id, + 'year', p_year, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur + ) + ); + END IF; + + RETURN QUERY SELECT v_lim, v_cur; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +-- ─── 4. add_project (мастер: project + первый год через add_project_year) ── +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC); +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC); +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC); +DROP FUNCTION IF EXISTS v3.add_project(VARCHAR, INT, INT, VARCHAR, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, NUMERIC, VARCHAR); + +CREATE FUNCTION v3.add_project( + p_name VARCHAR, + p_year INT, + p_org_unit_id INT, + p_level VARCHAR DEFAULT 'project', -- 'program' | 'project' + p_parent_id INT DEFAULT NULL, -- обязателен если level='project' и есть программа + p_project_type VARCHAR DEFAULT NULL, + p_vsp_format VARCHAR DEFAULT NULL, + p_placement_type VARCHAR DEFAULT NULL, + p_object_address VARCHAR DEFAULT NULL, + p_staff_count INT DEFAULT NULL, + p_total_area NUMERIC DEFAULT NULL, + p_system_code VARCHAR DEFAULT NULL -- Системный код ВСП +) +RETURNS TABLE ( + project_id INT, + limit_report_id INT, + current_expenses_report_id INT +) +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, + system_code + ) 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, + p_system_code + ) RETURNING id INTO v_pid; + + -- Первый год: 2 отчёта (LIMIT + CURRENT_EXPENSES) + snapshot фаз + сетка статей. + -- p_emit_audit=false — событие года покрыто PROJECT_CREATE ниже. Дальнейшие + -- годы добавляются отдельно через v3.add_project_year. + SELECT y.limit_report_id, y.current_expenses_report_id + INTO v_lim, v_cur + FROM v3.add_project_year(v_pid, p_year, false) y; + + -- Аудит: PROJECT_CREATE — все заполненные поля + id отчётов + PERFORM v3.log_event( + 'PROJECT_CREATE', 'PROJECT_CREATE', + 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, + 'system_code', p_system_code, + 'staff_count', p_staff_count, + 'total_area', p_total_area, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur + )) + ); + + RETURN QUERY SELECT v_pid, v_lim, v_cur; +END; +$function$ +LANGUAGE plpgsql VOLATILE; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form3_report_sections — отображение листа ЛП (LIMIT) или ТР (CURRENT) +-- ════════════════════════════════════════════════════════════════════════════ +-- Возвращает иерархию (ROOT/GROUP/ITEM/SUB_ITEM) + INPUT строки в tree-order +-- для одного rf_project_report (один трек: report_type = LIMIT либо +-- CURRENT_EXPENSES). UI выбирает трек, передавая report_id соответствующей +-- записи rf_project_report. +-- +-- Колоночная раскладка повторяет шаблон Формы 3: +-- header, q1, q2, q3, q4, year_totals. +-- +-- Q1: adj_by_items, adj_increase, total_corr, m1, m2, m3, quarter_actual, economy +-- Q2/Q3: carryover, adj_by_items, adj_increase, total_corr, m1..m3, quarter_actual, economy +-- Q4: carryover, adj_by_items, adj_increase, total_corr, m1..m3, spod, quarter_actual, economy +-- year_totals: total_year_corr, total_year_actual, economy_year +-- +-- p_sections — массив включаемых блоков (NULL = все). Допустимые имена: +-- 'q1', 'q2', 'q3', 'q4', 'year' +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form3_report_sections(INT, TEXT[]); + +CREATE FUNCTION v3.v_form3_report_sections( + p_report_id INT, + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + line_id INT, + col_section_code VARCHAR, + col_item_id VARCHAR, + col_num_group_id VARCHAR, + col_name VARCHAR, + -- Q1 + 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, + -- Q2 + 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, + -- Q3 + 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, + -- Q4 + 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, + -- year totals + col_year_total_corr NUMERIC, + col_year_total_actual NUMERIC, + col_year_economy NUMERIC, + -- план-блоки (v2): Базовый план (q1–q4 + Год) и Скоррект план (q2–q4) + col_q1_base_plan NUMERIC, + col_q2_base_plan NUMERIC, + col_q3_base_plan NUMERIC, + col_q4_base_plan NUMERIC, + col_year_base_plan NUMERIC, + col_q2_corrected_plan NUMERIC, + col_q3_corrected_plan NUMERIC, + col_q4_corrected_plan NUMERIC, + _sort_path INT[] +) +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, + q.base_plan, q.corrected_plan + 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, + q1.base_plan AS q1_bp, q2.base_plan AS q2_bp, + q3.base_plan AS q3_bp, q4.base_plan AS q4_bp, + q2.corrected_plan AS q2_cp, q3.corrected_plan AS q3_cp, q4.corrected_plan AS q4_cp + 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, + SUM(q.base_plan) AS bp, SUM(q.corrected_plan) AS cp + 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, + SUM(a.bp) AS bp + 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, + SUM(a.bp) AS bp, SUM(a.cp) AS cp + 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, + SUM(a.bp) AS bp, SUM(a.cp) AS cp + 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, + SUM(a.bp) AS bp, SUM(a.cp) AS cp + 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, + -- план-блоки + CASE WHEN s_q1 THEN e.q1_bp END, + CASE WHEN s_q2 THEN e.q2_bp END, + CASE WHEN s_q3 THEN e.q3_bp END, + CASE WHEN s_q4 THEN e.q4_bp END, + CASE WHEN s_year THEN COALESCE(e.q1_bp,0)+COALESCE(e.q2_bp,0)+ + COALESCE(e.q3_bp,0)+COALESCE(e.q4_bp,0) END, + CASE WHEN s_q2 THEN e.q2_cp END, + CASE WHEN s_q3 THEN e.q3_cp END, + CASE WHEN s_q4 THEN e.q4_cp 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, + -- план-блоки (rollup) + CASE WHEN s_q1 THEN a1.bp END, + CASE WHEN s_q2 THEN a2.bp END, + CASE WHEN s_q3 THEN a3.bp END, + CASE WHEN s_q4 THEN a4.bp END, + CASE WHEN s_year THEN COALESCE(a1.bp,0)+COALESCE(a2.bp,0)+ + COALESCE(a3.bp,0)+COALESCE(a4.bp,0) END, + CASE WHEN s_q2 THEN a2.cp END, + CASE WHEN s_q3 THEN a3.cp END, + CASE WHEN s_q4 THEN a4.cp 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$ +LANGUAGE plpgsql STABLE; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.upd_form3_cell / v3.upd_form3_cells — обёртки FORM_3 с проверкой роли+окна +-- ════════════════════════════════════════════════════════════════════════════ +-- Параллель upd_form_cell_authz.sql, но для ветки rf_project_report. Каждая +-- ячейка прогоняется через v3.can_edit3(report_id, column, user_id). Если хоть +-- одна запрещена — RAISE EXCEPTION в формате, совместимом с конвенцией v3: +-- • role_not_allowed: +-- • org_not_assigned: +-- • window_closed: (opens ) — окно ещё не наступило +-- • window_closed: (closed ) — окно уже закрыто +-- • window_closed: (no phase) — фаз вообще нет +-- +-- HTTP-слой парсит префикс до `:` и мапит на 403 (см. SPEC.md §5). +-- +-- ⚠ Применять СТРОГО после upd_form3_cell.sql (low-level 2-арг/4-арг). Здесь +-- добавляется p_user_id → это ДРУГИЕ сигнатуры (3-арг/5-арг), которые +-- сосуществуют с low-level и делегируют в них после проверки. HTTP вызывает +-- версии с user_id. «Листа» тут нет — report_type определяется отчётом внутри +-- can_edit3. +-- ════════════════════════════════════════════════════════════════════════════ + +-- ─── upd_form3_cells (массовый, с авторизацией) ──────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cells(INT, JSONB, INT); + +CREATE FUNCTION v3.upd_form3_cells( + p_report_id INT, + p_changes JSONB, + p_user_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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_edit3(p_report_id, 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; + + -- Все ячейки разрешены — делегируем в low-level (2-арг). ROW_UPDATE не + -- логируем (см. logging_events.md): cell-edit'ы слишком частые. + RETURN QUERY + SELECT * FROM v3.upd_form3_cells(p_report_id, p_changes); +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +-- ─── upd_form3_cell (одна ячейка, тонкий wrapper) ────────────────────────── +DROP FUNCTION IF EXISTS v3.upd_form3_cell(INT, INT, TEXT, JSONB, INT); + +CREATE FUNCTION v3.upd_form3_cell( + p_report_id INT, + p_line_id INT, + p_column TEXT, + p_value JSONB, + p_user_id INT +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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 + )), + p_user_id + ); +$function$ +LANGUAGE sql VOLATILE; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.migrate_form3_v2 — DDL под переработку FORM_3 (листы ЛП/ТР, v2) +-- ════════════════════════════════════════════════════════════════════════════ +-- Источник: «Пример_Форма_3_new(1).xlsm» (листы шаблон_ЛП / шаблон_ТР) + PDF-дифф +-- аналитика. Финансовая часть переразбита с 4 на 10 блоков и переведена на +-- многолетнюю раскладку (год1/год2/год3 рядом, кварталы повторяются на год). +-- +-- Новые блоки внутри одного года: +-- • Базовый план по Лимиту Правления — ручной ввод q1–q4, Год = Σ кв. +-- • Скоррект план по Лимиту Правления — ручной ввод q2–q4 (без q1, без Года) +-- • Фактические затраты — уже покрыты rf_project_report_quarter +-- (adj_by_items / adj_increase / actual_m1..m3 / actual_spod), «Остаток» и +-- квартальные/годовые итоги считаются на лету в read-функции. +-- +-- Многолетность уже поддержана моделью: один project → много rf_project_report +-- по (year × report_type). Здесь добавляется ТОЛЬКО хранение план-блоков и +-- «Системный код ВСП» в шапке проекта. +-- ════════════════════════════════════════════════════════════════════════════ + +-- ───────────── 1. rf_project_report_quarter: план-блоки ───────────────────── +-- base_plan — «Базовый план по Лимиту Правления», поквартально (q1–q4). +-- corrected_plan — «Скоррект план по Лимиту Правления», поквартально (q2–q4). +-- Для q1 значения не существует (шаблон рисует скорр.план с +-- II квартала) — гарантируется CHECK ниже + write-слой/RBAC. +ALTER TABLE v3.rf_project_report_quarter + ADD COLUMN IF NOT EXISTS base_plan NUMERIC, + ADD COLUMN IF NOT EXISTS corrected_plan NUMERIC; + +-- Скоррект план не заводится в I квартале (нет колонки в шаблоне). +ALTER TABLE v3.rf_project_report_quarter + DROP CONSTRAINT IF EXISTS chk_v3_rf_quarter_corr_plan_q1; +ALTER TABLE v3.rf_project_report_quarter + ADD CONSTRAINT chk_v3_rf_quarter_corr_plan_q1 + CHECK (quarter <> 1 OR corrected_plan IS NULL); + +-- ───────────── 2. v3.project: «Системный код ВСП» ─────────────────────────── +-- Новое поле шапки «Информация о проекте ВСП» (в примере — 1808). Выбор из +-- справочника ИНФО / выпадающего списка; хранится как атрибут проекта (вводится +-- один раз, не дублируется между треками/годами — как и остальные атрибуты ВСП). +ALTER TABLE v3.project + ADD COLUMN IF NOT EXISTS system_code VARCHAR; + +-- ───────────── 3. Обновление энум-списков шапки ──────────────────────────── +-- В новом шаблоне списки изменились относительно старого VBA-набора. +-- project_type: новый перечень из 8 значений (шаблон_ЛП F2:F9). +ALTER TABLE v3.project DROP CONSTRAINT IF EXISTS project_project_type_check; +ALTER TABLE v3.project + ADD CONSTRAINT project_project_type_check + CHECK (project_type IS NULL OR project_type IN ( + 'Открытие ВСП','Реновация ВСП','Закрытие ВСП', + 'Реновация РФ','Закрытие РФ', + 'Открытие УРМ','Реновация УРМ','Закрытие УРМ' + )); + +-- vsp_format («Новый формат ВСП») — поле автозаполняется из справочника INFO +-- («Утвержденный формат»), не вводится вручную и его словарь ведётся как +-- reference-data. Жёсткий CHECK снимаем, чтобы новые форматы не ломали вставку. +ALTER TABLE v3.project DROP CONSTRAINT IF EXISTS project_vsp_format_check; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form3_report_jsonb — JSONB-обёртка над v_form3_report_sections +-- ════════════════════════════════════════════════════════════════════════════ +-- Один трек FORM_3 (LIMIT либо CURRENT_EXPENSES) как (row_type, depth, +-- sort_order, data jsonb) — общий формат UI с FORM_1/2/4. +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form3_report_jsonb(INT, TEXT[]); + +CREATE FUNCTION v3.v_form3_report_jsonb( + p_report_id INT, + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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( + 'base_plan', s.col_q1_base_plan, + '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( + 'base_plan', s.col_q2_base_plan, + 'corrected_plan', s.col_q2_corrected_plan, + '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( + 'base_plan', s.col_q3_base_plan, + 'corrected_plan', s.col_q3_corrected_plan, + '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( + 'base_plan', s.col_q4_base_plan, + 'corrected_plan', s.col_q4_corrected_plan, + '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( + 'base_plan', s.col_year_base_plan, -- Год Базового плана (Σ q1–q4) + '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$ +LANGUAGE sql STABLE; +-- ════════════════════════════════════════════════════════════════════════════ +-- 09_backfill_existing.sql — донакат на СУЩЕСТВУЮЩИЕ отчёты FORM_3 +-- ════════════════════════════════════════════════════════════════════════════ +-- Нужен ТОЛЬКО если в БД уже есть отчёты rf_project_report, созданные до этого +-- апдейта. На чистой БД / для новых проектов не требуется (add_project и +-- add_project_year уже дают правильные снапшоты и сетку). +-- +-- 1) copy_template_to_report использует ON CONFLICT DO NOTHING → у старых +-- отчётов в form3_phase остаются СТАРЫЕ column_keys (без base_plan/ +-- corrected_plan). Ресинкаем column_keys из обновлённого phase_template. +-- 2) (ОПЦИОНАЛЬНО) досеиваем фиксированную сетку статей для старых отчётов. +-- ⚠ Меняет набор строк старых отчётов (добавит все листовые статьи FORM_3). +-- Раскомментируйте, только если хотите привести старые отчёты к фикс-сетке. +-- ════════════════════════════════════════════════════════════════════════════ + + +-- 1. Ресинк column_keys существующих снапшотов с шаблоном (только добавляет +-- новые ключи план-блоков; окна opens_at/closes_at не трогаем). +UPDATE v3.form3_phase fp + SET column_keys = pt.column_keys + FROM v3.rf_project_report r, + v3.phase_template pt + WHERE fp.rf_project_report_id = r.id + AND pt.form_type = 'FORM_3' + AND pt.sheet = r.report_type + AND pt.phase_code = fp.phase_code + AND pt.role = fp.role + AND fp.column_keys IS DISTINCT FROM pt.column_keys; + +-- Также подхватить фазы, которых у старого отчёта вовсе не было (идемпотентно). +SELECT v3.copy_template_to_report(id) FROM v3.rf_project_report; + +-- 2. ОПЦИОНАЛЬНО: фиксированная сетка статей для старых отчётов. +-- Раскомментируйте при необходимости: +-- SELECT v3.seed_form3_report_lines(id) FROM v3.rf_project_report; + +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form3_project_jsonb — многолетний широкий лист ЛП/ТР одного проекта +-- ════════════════════════════════════════════════════════════════════════════ +-- Новый шаблон FORM_3 (v2) показывает годы проекта рядом (год1|год2|…) — +-- «кварталы повторяются для каждого года проекта». Горизонт переменный: сколько +-- у проекта заведено годовых rf_project_report того же трека, столько year-групп. +-- +-- Модель: каждый (год × трек) — отдельный rf_project_report со своим набором +-- строк (фиксированная сетка статей, одинаковая во всех годах). Здесь мы сшиваем +-- эти годовые отчёты по СТАТЬЕ (expense_item_id — стабильный ключ между годами; +-- line_id у строки в каждом году свой и лежит внутри года — для адресной правки). +-- +-- Возврат — общий формат UI (row_type, depth, sort_order, data), где data: +-- { "header": {...}, -- общая шапка строки (одна на все годы) +-- "years": { "2025": <год-объект>, ... },-- год → data из v_form3_report_jsonb +-- -- (внутри line_id года + q1..q4 + totals) +-- "project_total": } -- «Стоимость проекта за N лет» = +-- -- Σ годовых фактических итогов (totals.total_actual) +-- +-- p_report_type — 'LIMIT' (ЛП) либо 'CURRENT_EXPENSES' (ТР). +-- p_sections — как в v_form3_report_sections ('q1'..'q4','year'), прокидывается +-- в каждый год. +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form3_project_jsonb(INT, VARCHAR, TEXT[]); + +CREATE FUNCTION v3.v_form3_project_jsonb( + p_project_id INT, + p_report_type VARCHAR, + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ + WITH reports AS ( + SELECT r.id AS report_id, r.year + FROM v3.rf_project_report r + WHERE r.project_id = p_project_id + AND r.report_type = p_report_type + ), + -- Разворачиваем каждый год в строки его листа + per_year AS ( + SELECT + v.row_type, + v.depth, + (v.data->'header'->>'expense_item_id')::INT AS eiid, + v.data->'header' AS header, + v.sort_order AS y_sort, + rp.year AS year, + v.data AS ydata + FROM reports rp + CROSS JOIN LATERAL v3.v_form3_report_jsonb(rp.report_id, p_sections) v + ), + -- Сшиваем по идентичности строки (row_type + статья): сетка одинакова во всех годах + agg AS ( + SELECT + py.row_type, + py.depth, + py.eiid, + MIN(py.y_sort) AS sort_order, + (array_agg(py.header ORDER BY py.year))[1] AS header, + jsonb_object_agg(py.year::text, py.ydata ORDER BY py.year) AS years, + SUM((py.ydata->'totals'->>'total_actual')::numeric) AS project_total + FROM per_year py + GROUP BY py.row_type, py.depth, py.eiid + ) + SELECT + a.row_type, + a.depth, + ROW_NUMBER() OVER (ORDER BY a.sort_order) AS sort_order, + jsonb_build_object( + 'header', a.header, + 'years', a.years, + 'project_total', a.project_total + ) AS data + FROM agg a + ORDER BY a.sort_order; +$function$ +LANGUAGE sql STABLE; +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.phase_template — seed (фазы редактирования: роль × лист × окно × колонки) +-- ════════════════════════════════════════════════════════════════════════════ +-- Шаблон редактируется админом. Изменения здесь НЕ влияют на уже созданные +-- формы — у них свой snapshot в v3.form_phase. +-- +-- Этот seed — стартовая разметка, в которой жёстко прошита только семантика +-- sequestration / seq_ssp / seq_dfip (где actor однозначно следует из v3-кода). +-- Остальные колонки (plan, q1.adj_*, contract_*, allocation, reserve, ckk, +-- collegial, header.*, ...) НЕ покрыты — добавлять по мере согласования +-- бизнес-правила «кто правит в какую фазу». +-- +-- Для smoke-теста все фазы открыты широко (весь 2026). В проде окна должны +-- соответствовать реальному регламенту бюджетного цикла. +-- +-- Возвратные фазы (EXECUTOR_RF→DFIP→EXECUTOR_RF) — несколько строк с одной +-- ролью, разные phase_code и окна, без особых случаев. +-- +-- ⚠ role ссылается на v3.role(code): ADMIN / DFIP / EXECUTOR_RF. ADMIN в +-- шаблоне быть не должен (в can_edit делается bypass) — CHECK-констрейнт +-- chk_v3_phase_template_no_admin отрежет. +-- ════════════════════════════════════════════════════════════════════════════ + +TRUNCATE TABLE v3.phase_template; + +-- ─── FORM_1: actor sequestration = DFIP (alias на одно поле в v3.sequestration) +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', + ARRAY['sequestration.q1','sequestration.q2','sequestration.q3','sequestration.q4', + 'sequestration.justification'], + '2026-01-01 00:00:00+00', '2026-12-31 23:59:59+00'), + ('FORM_1', 'CAP', 'DFIP_SEQ_2026', 'DFIP', + ARRAY['sequestration.q1','sequestration.q2','sequestration.q3','sequestration.q4', + 'sequestration.justification'], + '2026-01-01 00:00:00+00', '2026-12-31 23:59:59+00'), + ('FORM_1', 'OPER', 'DFIP_SEQ_2026', 'DFIP', + ARRAY['sequestration.q1','sequestration.q2','sequestration.q3','sequestration.q4', + 'sequestration.justification'], + '2026-01-01 00:00:00+00', '2026-12-31 23:59:59+00'); + +-- ─── FORM_2 / FORM_4: разнесены по actor через scope (seq_ssp / seq_dfip) +INSERT INTO v3.phase_template + (form_type, sheet, phase_code, role, column_keys, opens_at, closes_at) +SELECT ft, sh, ph, rl, ck, + '2026-01-01 00:00:00+00'::timestamptz, + '2026-12-31 23:59:59+00'::timestamptz +FROM (VALUES + ('FORM_2','AHR'), ('FORM_2','CAP'), ('FORM_2','OPER'), + ('FORM_4','AHR'), ('FORM_4','CAP'), ('FORM_4','OPER') +) AS f(ft, sh) +CROSS JOIN (VALUES + ('SSP_SEQ_2026', 'EXECUTOR_RF', ARRAY['seq_ssp.q1','seq_ssp.q2','seq_ssp.q3','seq_ssp.q4', + 'seq_ssp.justification']), + ('DFIP_SEQ_2026', 'DFIP', ARRAY['seq_dfip.q1','seq_dfip.q2','seq_dfip.q3','seq_dfip.q4', + 'seq_dfip.justification']) +) AS p(ph, rl, ck); + +-- ─── FORM_3: sheet = report_type ('LIMIT' / 'CURRENT_EXPENSES') ──────────── +-- Ветка rf_project_report. column_keys — scope.field формата qN. +-- (см. upd_form3_cell.sql). Год несёт report_id (каждый год проекта — свой +-- rf_project_report), поэтому ключи БЕЗ префикса года. Наборы полей: +-- Факт: adj_by_items / adj_increase / m1 / m2 / m3 по кварталу + q4.spod +-- Базовый план: q1..q4.base_plan +-- Скоррект план: q2..q4.corrected_plan (без q1 — шаблон с II квартала) +-- EXECUTOR_RF — основной заполнитель отчётов РФ. Стартовая разметка: одна +-- широкая фаза заполнения на весь 2026 для обоих треков. Реальные окна/роли +-- (в т.ч. проверка DFIP) — уточнять по регламенту. +INSERT INTO v3.phase_template + (form_type, sheet, phase_code, role, column_keys, opens_at, closes_at) +SELECT 'FORM_3', sh, 'RF_FILL_2026', 'EXECUTOR_RF', + ARRAY[ + -- Факт + 'q1.adj_by_items','q1.adj_increase','q1.m1','q1.m2','q1.m3', + 'q2.adj_by_items','q2.adj_increase','q2.m1','q2.m2','q2.m3', + 'q3.adj_by_items','q3.adj_increase','q3.m1','q3.m2','q3.m3', + 'q4.adj_by_items','q4.adj_increase','q4.m1','q4.m2','q4.m3','q4.spod', + -- Базовый план (q1–q4) + 'q1.base_plan','q2.base_plan','q3.base_plan','q4.base_plan', + -- Скоррект план (q2–q4) + 'q2.corrected_plan','q3.corrected_plan','q4.corrected_plan' + ], + '2026-01-01 00:00:00+00'::timestamptz, + '2026-12-31 23:59:59+00'::timestamptz +FROM (VALUES ('LIMIT'), ('CURRENT_EXPENSES')) AS s(sh); + +-- Образец возвратной DFIP-фазы (ГО правит корректировки после РФ): +-- INSERT INTO v3.phase_template +-- (form_type, sheet, phase_code, role, column_keys, opens_at, closes_at) +-- VALUES ('FORM_3', 'CURRENT_EXPENSES', 'DFIP_REVIEW_2026', 'DFIP', +-- ARRAY['q1.adj_by_items','q1.adj_increase','q2.adj_by_items','q2.adj_increase'], +-- '2026-04-01 00:00:00+00', '2026-04-30 23:59:59+00'); + +-- ─── TODO: phase для plan, q1.adj_*, contract, allocation, reserve, etc. ─── +-- Образец на будущее — план EXECUTOR_RF в первое окно цикла: +-- +-- INSERT INTO v3.phase_template +-- (form_type, sheet, phase_code, role, column_keys, opens_at, closes_at) +-- VALUES ( +-- 'FORM_1', 'AHR', 'SSP_PLAN_INITIAL_2026', 'EXECUTOR_RF', +-- ARRAY['plan.q1','plan.q2','plan.q3','plan.q4','plan.comment'], +-- '2026-02-01 00:00:00+00', '2026-02-28 23:59:59+00' +-- ); +-- +-- Полный список scope.field — см. v3/upd_form_cell.sql (комментарий вверху +-- файла + блоки в _apply_form_cell). diff --git a/api/alembic/versions/sql/0010_form4.sql b/api/alembic/versions/sql/0010_form4.sql new file mode 100644 index 0000000..60d7875 --- /dev/null +++ b/api/alembic/versions/sql/0010_form4.sql @@ -0,0 +1,852 @@ +-- ════════════════════════════════════════════════════════════════════════════ +-- migrate_form4_project.sql — выделение FORM_4 программ/проектов в отдельную +-- сущность v3.form4_project (перестают делить таблицу с FORM_3 v3.project). +-- ════════════════════════════════════════════════════════════════════════════ +-- Мотивация: v3.project исторически был FORM_4-таблицей (program/project + +-- parent_id), FORM_3-редизайн навесил сверху свои атрибуты (project_type, +-- vsp_format, address, area, staff, org_unit_id) и строгий chk_v3_project_name. +-- Два разных концепта под одним словом «project» → разреженные колонки, нет +-- защиты FK, констрейнт вынужден ветвиться по «виду» строки. Разводим. +-- +-- Новая таблица v3.form4_project — точный костяк без FORM_3-полей: +-- program (level='program', parent_id=NULL) +-- project (level='project', parent_id=program) +-- +-- Дискриминатор FORM_4 vs FORM_3: FORM_3-строки — те, что ссылаются из +-- v3.rf_project_report (add_project всегда создаёт отчёты для КАЖДОЙ строки, +-- включая программы). Всё остальное в v3.project — FORM_4. +-- +-- Порядок: после init.sql + всех migrate_form3*/migrate_to_identity (нужна +-- финальная форма v3.project с org_unit_id). Один прогон — copy защищён +-- проверкой на пустоту form4_project. +-- ════════════════════════════════════════════════════════════════════════════ + +-- ───── 1. Новая таблица ───────────────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS v3.form4_project ( + id SERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + level VARCHAR CHECK (level IN ('program', 'project')), + section_code VARCHAR, + parent_id INTEGER REFERENCES v3.form4_project(id), + -- org_unit_id INTEGER REFERENCES v3.org_unit(id), + form_id INTEGER REFERENCES v3.budget_form(id), + -- project (level='project') обязан иметь родителя-программу; program — нет. + CONSTRAINT chk_form4_project_parent CHECK ( + (level = 'program' AND parent_id IS NULL) + OR (level = 'project') + ), + CONSTRAINT chk_form4_project_section_code CHECK ( + (level = 'program') = (section_code IS NOT NULL) + ) +); + +CREATE INDEX ix_form4_project_section_code + ON v3.form4_project(section_code) WHERE level = 'program'; +CREATE INDEX IF NOT EXISTS ix_v3_form4_project_parent ON v3.form4_project(parent_id); +CREATE INDEX IF NOT EXISTS ix_v3_form4_project_form ON v3.form4_project(form_id); + +-- ───── 2. Перенос FORM_4-строк из v3.project (с сохранением id) ────────────── +-- FK budget_line.project_id ссылается по id → id сохраняем 1:1. +-- Копируем обе роли (program + project) одним INSERT: self-FK parent_id внутри +-- одного оператора проверяется в конце, порядок строк не важен. +-- DO $migrate$ +-- BEGIN +-- IF NOT EXISTS (SELECT 1 FROM v3.form4_project) THEN +-- INSERT INTO v3.form4_project (id, name, level, parent_id, org_unit_id) +-- SELECT p.id, p.name, p.level, p.parent_id, (p.org_unit_id +-- FROM v3.project p +-- WHERE p.id NOT IN ( +-- SELECT project_id FROM v3.rf_project_report WHERE project_id IS NOT NULL +-- ); + +-- -- Синхронизируем sequence, чтобы новые INSERT не конфликтовали по id. +-- PERFORM setval('v3.form4_project_id_seq', +-- GREATEST((SELECT COALESCE(MAX(id), 0) FROM v3.form4_project), 1)); + +-- RAISE NOTICE 'form4_project: перенесено % строк из v3.project', +-- (SELECT count(*) FROM v3.form4_project); +-- ELSE +-- RAISE NOTICE 'form4_project уже наполнена — перенос пропущен'; +-- END IF; +-- END +-- $migrate$; + +-- ───── 3. Перецепка FK budget_line.project_id на новую таблицу ────────────── +ALTER TABLE v3.budget_line DROP CONSTRAINT IF EXISTS budget_line_project_id_fkey; +ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_project_id_fkey FOREIGN KEY (project_id) REFERENCES v3.form4_project(id); + +-- ───── 4. Удаление перенесённых строк из v3.project ───────────────────────── +-- После перецепки budget_line на v3.project больше не ссылается ничего FORM_4. +-- Остаются только FORM_3-строки (referenced by rf_project_report). +DELETE FROM v3.project p + WHERE p.id NOT IN ( + SELECT project_id FROM v3.rf_project_report WHERE project_id IS NOT NULL + ); + +-- ───── 5. Восстановление chk_v3_project_name в строгой форме ───────────────── +-- FORM_4-строки ушли — v3.project теперь чисто FORM_3, правило коротких имён +-- ВСП применимо ко всей таблице (как до form4_write.sql). NOT VALID сохраняем. +ALTER TABLE v3.project DROP CONSTRAINT IF EXISTS chk_v3_project_name; +ALTER TABLE v3.project + ADD CONSTRAINT chk_v3_project_name + CHECK (name IS NULL OR ( + length(name) <= 30 AND + name !~ E'[+\\-/\\\\=&* ]' + )) NOT VALID; + + +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.add_form4_program / v3.add_form4_project — создание программ и проектов +-- FORM_4 в выделенной таблице v3.form4_project (иерархия program → project). +-- ════════════════════════════════════════════════════════════════════════════ +-- FORM_4 хранит иерархию отдельно от FORM_3 (v3.project) — см. +-- migrate_form4_project.sql. Костяк: +-- program (level='program', parent_id=NULL) → depth=1 (GROUP) в v_form4 +-- project (level='project', parent_id=program) → depth=2 (ITEM) в v_form4 +-- Строки бюджета привязываются к проекту через budget_line.project_id. +-- +-- Никаких FORM_3-атрибутов / rf_project_report / snapshot фаз — это ветка +-- budget_form. p_org_unit_id опционален (read-path FORM_4 по нему не фильтрует, +-- заполняется для аудита/принадлежности). +-- +-- Порядок применения: после migrate_form4_project.sql (нужна таблица +-- v3.form4_project) и audit.sql (нужна v3.log_event). +-- ════════════════════════════════════════════════════════════════════════════ + +-- ───── 1. add_form4_program ───────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.add_form4_program(VARCHAR, INT, VARCHAR, INT); + +CREATE FUNCTION v3.add_form4_program( + p_name VARCHAR, + p_form_id INT, + p_section_code VARCHAR DEFAULT NULL +) +RETURNS TABLE ( + project_id INT, + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) AS $function$ +#variable_conflict use_column +DECLARE + v_pid INT; +BEGIN + IF p_name IS NULL OR length(trim(p_name)) = 0 THEN + RAISE EXCEPTION 'p_name обязателен'; + END IF; + IF p_form_id IS NULL THEN + RAISE EXCEPTION 'p_form_id обязателен'; + END IF; + IF NOT EXISTS ( + SELECT 1 FROM v3.budget_form bf + WHERE bf.form_type_code = 'FORM_4' AND bf.id = p_form_id + ) THEN + RAISE EXCEPTION 'Только FORM_4'; + END IF; + IF p_section_code IS NOT NULL AND NOT EXISTS ( + SELECT 1 FROM v3.expense_item + WHERE section_code = p_section_code AND depth = 0 + ) THEN + RAISE EXCEPTION 'section_code=% не существует в expense_item (depth=0)', p_section_code; + END IF; + + INSERT INTO v3.form4_project (name, level, parent_id, form_id, section_code) + VALUES (p_name, 'program', NULL, p_form_id, p_section_code) + RETURNING id INTO v_pid; + + PERFORM v3.log_event( + 'PROJECT_CREATE', 'PROJECT', + jsonb_strip_nulls(jsonb_build_object( + 'project_id', v_pid, + 'name', p_name, + 'level', 'program', + 'form_id', p_form_id, + 'form_type', 'FORM_4' + )), + p_form_id, NULL, NULL + ); + + RETURN QUERY + SELECT v_pid, v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form4_sheet_jsonb(p_form_id, p_section_code, NULL) v + WHERE (v.row_type = 'GROUP' AND (v.data->'header'->>'program_id')::INT = v_pid) + OR (v.row_type = 'ROOT' AND v.data->'header'->>'section_code' = p_section_code) + ORDER BY v.sort_order; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + +-- ───── 2. add_form4_project ───────────────────────────────────────────────── +DROP FUNCTION IF EXISTS v3.add_form4_project(VARCHAR, INT); + +CREATE FUNCTION v3.add_form4_project( + p_name VARCHAR, + p_program_id INT +) +RETURNS TABLE ( + project_id INT, + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ +#variable_conflict use_column +DECLARE + v_pid INT; + v_section_code VARCHAR; + v_form_id INT; +BEGIN + IF p_name IS NULL OR length(trim(p_name)) = 0 THEN + RAISE EXCEPTION 'p_name обязателен'; + END IF; + IF p_program_id IS NULL THEN + RAISE EXCEPTION 'p_program_id обязателен (проект создаётся под программой)'; + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.form4_project fp + WHERE fp.id = p_program_id AND fp.level = 'program') THEN + RAISE EXCEPTION 'program id=% не существует или не имеет level=program', p_program_id; + END IF; + + SELECT form_id INTO v_form_id FROM v3.form4_project WHERE id = p_program_id; + + INSERT INTO v3.form4_project (name, level, parent_id, form_id) + VALUES (p_name, 'project', p_program_id, v_form_id) + RETURNING id INTO v_pid; + + PERFORM v3.log_event( + 'PROJECT_CREATE', 'PROJECT', + jsonb_strip_nulls(jsonb_build_object( + 'project_id', v_pid, + 'name', p_name, + 'level', 'project', + 'parent_id', p_program_id, + 'form_id', v_form_id, + 'form_type', 'FORM_4' + )), + v_form_id, NULL, NULL + ); + + SELECT fp.section_code INTO v_section_code + FROM v3.form4_project fp + WHERE fp.id = p_program_id AND fp.level = 'program'; + + IF v_section_code IS NOT NULL THEN + RETURN QUERY + SELECT v_pid, v.row_type, v.depth, v.sort_order, v.data + FROM v3.v_form4_sheet_jsonb(v_form_id, v_section_code, NULL) v + WHERE (v.row_type = 'ITEM' AND (v.data->'header'->>'project_id')::INT = v_pid) + OR (v.row_type = 'GROUP' AND (v.data->'header'->>'program_id')::INT = p_program_id) + OR (v.row_type = 'ROOT' AND v.data->'header'->>'section_code' = v_section_code) + ORDER BY v.sort_order; + END IF; +END; +$function$ +LANGUAGE plpgsql VOLATILE; + + +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form4_sheet_jsonb — JSONB-выдача FORM_4 для АХР_Р / КВ_Р / Операц +-- ════════════════════════════════════════════════════════════════════════════ +-- Иерархия (4 уровня): +-- ROOT depth=0 — section_code (1.00.0/3.00.0/4.00.0/5.00.0) +-- PROGRAM depth=1 — v3.form4_project level=program +-- PROJECT depth=2 — v3.form4_project level=project (parent_id=program) +-- INPUT depth=3 — budget_line (R-код) +-- +-- Финансовые блоки (plan, seq_dfip, approved, booking, q1-q4, totals) +-- агрегируются на уровнях ROOT/PROGRAM/PROJECT через v3.jsonb_numeric_sum. +-- Текстовые поля (counterparty, comment, justification…) на агрегатных +-- уровнях NULL'ятся (jsonb_numeric_sum их игнорирует). +-- +-- p_form_id — id формы FORM_4 +-- p_sheet — 'AHR' / 'CAP' / 'OPER' +-- p_sections — массив включаемых блоков (как в v_form4_sheet_lines_jsonb) +-- ════════════════════════════════════════════════════════════════════════════ + +-- ───── 1. Хелпер: рекурсивная сумма численных листьев двух 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 IF EXISTS v3.jsonb_sum(JSONB); +CREATE AGGREGATE v3.jsonb_sum(JSONB) ( + SFUNC = v3.jsonb_numeric_sum, + STYPE = JSONB +); + +-- ───── 2. Основная функция ───────────────────────────────────────────────── +CREATE OR REPLACE FUNCTION v3.v_form4_sheet_jsonb( + p_form_id INT, + p_sheet VARCHAR, + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ + -- ═══ 1. Базовые INPUT-строки с расширенной иерархией ═══════════════════════ + WITH inp_raw AS ( + SELECT j.* + FROM v3.v_form4_sheet_lines_jsonb(p_form_id, p_sheet, p_sections) j + WHERE j.row_type = 'INPUT' + ), + inp AS ( + SELECT + i.*, + bl.project_id AS line_project_id, + + -- --- разрешение проекта --- + -- A. bl.project_id → project (level='project') + -- B. bl.project_id → program (level='program') — тогда проекта нет + CASE WHEN prj.id IS NOT NULL THEN prj.id END AS prj_id, + CASE WHEN prj.id IS NOT NULL THEN prj.name END AS prj_name, + + -- --- разрешение программы --- + -- A. через проект: prog_via_prj + -- B. напрямую: prog_direct + COALESCE(prog_via_prj.id, prog_direct.id) AS prog_id, + COALESCE(prog_via_prj.name, prog_direct.name) AS prog_name, + + -- --- section (depth=0) --- + sec.id AS sec_id, + sec.section_code AS sec_code, + sec.name AS sec_name + + FROM inp_raw i + JOIN v3.budget_line bl ON bl.id = i.line_id + + -- проект (level='project') + LEFT JOIN v3.form4_project prj + ON prj.id = bl.project_id AND prj.level = 'project' + + -- программа через проект + LEFT JOIN v3.form4_project prog_via_prj + ON prog_via_prj.id = prj.parent_id AND prog_via_prj.level = 'program' + + -- программа напрямую (bl.project_id → program) + LEFT JOIN v3.form4_project prog_direct + ON prog_direct.id = bl.project_id AND prog_direct.level = 'program' + + -- раздел depth=0 (через expense_item) + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN LATERAL ( + WITH RECURSIVE up AS ( + SELECT id, parent_id, name, section_code, depth + FROM v3.expense_item WHERE id = ei.id + UNION ALL + SELECT e.id, e.parent_id, e.name, e.section_code, e.depth + FROM v3.expense_item e JOIN up u ON u.parent_id = e.id + ) + SELECT id, name, section_code FROM up WHERE depth = 0 LIMIT 1 + ) sec ON TRUE + ), + + -- ═══ 2. Все программы (включая пустые) ═════════════════════════════════════ + all_programs AS ( + SELECT + fp.id AS prog_id, + fp.name AS prog_name, + fp.section_code, + sec.id AS sec_id, + sec.section_code AS sec_code, + sec.name AS sec_name + FROM v3.form4_project fp + LEFT JOIN LATERAL ( + SELECT id, section_code, name + FROM v3.expense_item + WHERE section_code = fp.section_code AND depth = 0 + LIMIT 1 + ) sec ON TRUE + WHERE fp.level = 'program' + AND (p_sections IS NULL OR fp.section_code = ANY(p_sections)) + AND (fp.section_code = p_sheet) AND fp.form_id = p_form_id + ), + + -- ═══ 3. Все проекты под программами (включая пустые) ═══════════════════════ + all_projects AS ( + SELECT + prj.id AS prj_id, + prj.name AS prj_name, + prj.parent_id AS prog_id, + ap.sec_id, ap.sec_code, ap.sec_name + FROM v3.form4_project prj + JOIN all_programs ap ON ap.prog_id = prj.parent_id + WHERE prj.level = 'project' AND (ap.section_code = p_sheet) + ), + + -- ═══ 4. Агрегаты с финансовыми данными (только из inp) ════════════════════ + + -- PROJECT-агрегаты + prj_agg 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 + ), + + -- PROGRAM-агрегаты + prog_agg 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 + ), + + -- ROOT-агрегаты + root_agg 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 + ), + + -- Нулевой JSONB-шаблон (для пустых программ/проектов) + zero AS ( + SELECT '{}'::jsonb AS z + ), + + -- ═══ 5. UNION всех уровней ═════════════════════════════════════════════════ + unioned AS ( + -- ROOT — только из агрегатов (не имеет смысла пустой ROOT без строк) + SELECT + 'ROOT'::VARCHAR AS row_type, 0 AS depth, NULL::INT AS line_id, + jsonb_build_object('section_code', sec_code, 'name', sec_name, 'expense_item_id', sec_id) AS header, + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, 0, 0, 0]::INT[] AS sort_path + FROM root_agg + + UNION ALL + + -- GROUP — из агрегатов (программы с бюджетными строками) + SELECT + 'GROUP', 1, NULL, + jsonb_build_object('section_code', sec_code, 'name', prog_name, 'program_id', prog_id, 'expense_item_id', sec_id), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, prog_id, 0, 0]::INT[] + FROM prog_agg + + UNION ALL + + -- GROUP — пустые программы (нет ни одной budget_line) + SELECT + 'GROUP', 1, NULL, + jsonb_build_object('section_code', sec_code, 'name', prog_name, 'program_id', prog_id, 'expense_item_id', sec_id), + NULL::jsonb, NULL::jsonb, NULL::jsonb, -- plan, seq_dfip, approved + NULL::jsonb, NULL::jsonb, NULL::jsonb, -- contract_summary, allocation, reserve + NULL::jsonb, NULL::jsonb, -- collegial, ckk + NULL::jsonb, NULL::jsonb, -- contract, booking + NULL::jsonb, NULL::jsonb, NULL::jsonb, NULL::jsonb, NULL::jsonb, -- q1-q4, totals + ARRAY[sec_id, prog_id, 0, 0]::INT[] + FROM all_programs ap + WHERE NOT EXISTS ( + SELECT 1 FROM v3.budget_line bl + WHERE bl.budget_form_id = p_form_id + AND ( + bl.project_id = ap.prog_id -- прямая привязка к программе + OR bl.project_id IN (SELECT id FROM v3.form4_project pr -- через проект + WHERE pr.parent_id = ap.prog_id AND pr.level = 'project') + ) + ) + + UNION ALL + + -- ITEM — из агрегатов (проекты с бюджетными строками) + SELECT + 'ITEM', 2, NULL, + jsonb_build_object('section_code', sec_code, 'name', prj_name, + 'program_id', prog_id, 'project_id', prj_id, 'expense_item_id', sec_id), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, prog_id, prj_id, 0]::INT[] + FROM prj_agg + + UNION ALL + + -- ITEM — пустые проекты (нет budget_line) + SELECT + 'ITEM', 2, NULL, + jsonb_build_object('section_code', sec_code, 'name', prj_name, + 'program_id', prog_id, 'project_id', prj_id, 'expense_item_id', sec_id), + NULL::jsonb, NULL::jsonb, NULL::jsonb, + NULL::jsonb, NULL::jsonb, NULL::jsonb, + NULL::jsonb, NULL::jsonb, + NULL::jsonb, NULL::jsonb, + NULL::jsonb, NULL::jsonb, NULL::jsonb, NULL::jsonb, NULL::jsonb, + ARRAY[sec_id, prog_id, prj_id, 0]::INT[] + FROM all_projects apr + WHERE NOT EXISTS ( + SELECT 1 FROM v3.budget_line bl + WHERE bl.budget_form_id = p_form_id + AND bl.project_id = apr.prj_id + ) + + UNION ALL + + -- INPUT — все строки бюджета + SELECT + 'INPUT', 3, line_id, + header || jsonb_build_object( + 'project_id', prj_id, 'project_name', prj_name, + 'program_id', prog_id, 'program_name', prog_name + ), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, COALESCE(prog_id, 999999), COALESCE(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$ +LANGUAGE sql STABLE; + + + +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.add_budget_line — создать новую INPUT-строку (budget_line) для FORM_1/2/4. +-- +-- Способы передать expense_item: +-- путь A — численный id: p_expense_item_id (берётся из header.expense_item_id) +-- путь B — резолв по коду: p_sheet + p_item_id [+ p_section_code для коллизий] +-- [+ p_direction для FORM_1] +-- +-- Авто-direction: +-- FORM_2 → 'Support' +-- FORM_4 → 'Development' +-- FORM_1/OPER → NULL +-- FORM_1/AHR|CAP → p_direction обязателен (Support|Development) +-- Если p_direction передан и противоречит авто-значению — ошибка. +-- +-- Дубликаты на одном expense_item разрешены (UNIQUE не накладывается). +-- vsp_id опционален — для FORM_2 фронт может не знать ВСП на момент +-- создания, дозаполнит позже через upd_form_cell(.., 'header.vsp_id', ..). +-- +-- FORM_3 не поддерживается — там строки живут в rf_project_report_line, +-- API будет в add_form3_line.sql (отдельная итерация). +-- +-- Возврат: вся иерархия (ROOT/GROUP/ITEM/SUB_ITEM/SECTION/LEAF) + новая +-- INPUT (отфильтрована по новому line_id). Новый id извлекается из +-- (data->>'line_id')::INT первой INPUT-строки. +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.add_budget_line(INT, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, INT, VARCHAR); +DROP FUNCTION IF EXISTS v3.add_budget_line(INT, INT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, VARCHAR, VARCHAR, INT, INT, VARCHAR, VARCHAR, DATE); + +CREATE FUNCTION v3.add_budget_line( + p_form_id INT, + p_expense_item_id INT DEFAULT NULL, + p_sheet VARCHAR DEFAULT NULL, + p_item_id VARCHAR DEFAULT NULL, + p_section_code VARCHAR DEFAULT NULL, + p_direction VARCHAR DEFAULT NULL, + p_name VARCHAR DEFAULT NULL, + p_internal_order VARCHAR DEFAULT NULL, + p_vsp_id INT DEFAULT NULL, + p_project_id INT DEFAULT NULL, + p_justification VARCHAR DEFAULT NULL, + -- сателлитные листы (AHR_RENT/UTILITY/SECURITY) используют дополнительно: + p_contract_number VARCHAR DEFAULT NULL, + p_contract_end_date DATE DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ +DECLARE + v_form_type VARCHAR; + 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 INTO v_form_type 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.form4_project WHERE id = p_project_id) THEN + RAISE EXCEPTION 'form4_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 + ), + p_form_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$ +LANGUAGE plpgsql VOLATILE; diff --git a/api/alembic/versions/sql/0011_form3_log.sql b/api/alembic/versions/sql/0011_form3_log.sql new file mode 100644 index 0000000..6a5a4ab --- /dev/null +++ b/api/alembic/versions/sql/0011_form3_log.sql @@ -0,0 +1,172 @@ +CREATE OR REPLACE FUNCTION v3.add_project_year(p_project_id integer, p_year integer, p_emit_audit boolean DEFAULT true) + RETURNS TABLE(limit_report_id integer, current_expenses_report_id integer) + LANGUAGE plpgsql +AS $function$ +DECLARE + v_lim INT; + v_cur INT; +BEGIN + IF p_year IS NULL THEN + RAISE EXCEPTION 'p_year обязателен'; + END IF; + IF NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_project_id) THEN + RAISE EXCEPTION 'project id=% не существует', p_project_id; + END IF; + IF EXISTS (SELECT 1 FROM v3.rf_project_report + WHERE project_id = p_project_id AND year = p_year) THEN + RAISE EXCEPTION 'год % у проекта id=% уже заведён', p_year, p_project_id; + END IF; + + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (p_project_id, p_year, 'LIMIT') RETURNING id INTO v_lim; + INSERT INTO v3.rf_project_report (project_id, year, report_type) + VALUES (p_project_id, p_year, 'CURRENT_EXPENSES') RETURNING id INTO v_cur; + + -- Snapshot RBAC-фаз (иначе default deny → отчёт read-only) + PERFORM v3.copy_template_to_report(v_lim); + PERFORM v3.copy_template_to_report(v_cur); + + -- Фиксированная сетка статей на оба трека + PERFORM v3.seed_form3_report_lines(v_lim); + PERFORM v3.seed_form3_report_lines(v_cur); + + IF p_emit_audit THEN + PERFORM v3.log_event( + 'PROJECT_YEAR_ADD', 'PROJECT', + jsonb_build_object( + 'project_id', p_project_id, + 'year', p_year, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur + ) + ); + END IF; + + RETURN QUERY SELECT v_lim, v_cur; +END; +$function$ +; + +-- FKs +ALTER TABLE v3.app_user ADD CONSTRAINT app_user_role_fk FOREIGN KEY (role_id) REFERENCES v3."role"(id); +ALTER TABLE v3.audit_log ADD CONSTRAINT audit_log_app_user_fk FOREIGN KEY (user_id) REFERENCES v3.app_user(id); +ALTER TABLE v3.audit_log ADD CONSTRAINT audit_log_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id); +ALTER TABLE v3.audit_log ADD CONSTRAINT audit_log_budget_form_fk FOREIGN KEY (form_id) REFERENCES v3.budget_form(id); +ALTER TABLE v3.booking ADD CONSTRAINT booking_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.budget_form ADD CONSTRAINT budget_form_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id); +ALTER TABLE v3.budget_form ADD CONSTRAINT budget_form_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id); +ALTER TABLE v3.budget_form ADD CONSTRAINT budget_form_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id); +ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id); +ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id); +ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id); +ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id); +ALTER TABLE v3.budget_line ADD CONSTRAINT budget_line_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id); +ALTER TABLE v3.budget_line_quarter ADD CONSTRAINT budget_line_quarter_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.ckk ADD CONSTRAINT ckk_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.collegial_approval ADD CONSTRAINT collegial_approval_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.contract_detail ADD CONSTRAINT contract_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.contract_summary ADD CONSTRAINT contract_summary_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.expense_item ADD CONSTRAINT expense_item_expense_item_fk FOREIGN KEY (parent_id) REFERENCES v3.expense_item(id); +ALTER TABLE v3.expense_item_form_type ADD CONSTRAINT expense_item_form_type_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id); +ALTER TABLE v3.fixed_asset_report ADD CONSTRAINT fixed_asset_report_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id); +ALTER TABLE v3.fixed_asset_report ADD CONSTRAINT fixed_asset_report_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id); +ALTER TABLE v3.form3_phase ADD CONSTRAINT form3_phase_rf_project_report_fk FOREIGN KEY (rf_project_report_id) REFERENCES v3.rf_project_report(id); +ALTER TABLE v3.form_limit ADD CONSTRAINT form_limit_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id); +ALTER TABLE v3.form_phase ADD CONSTRAINT form_phase_budget_form_fk FOREIGN KEY (budget_form_id) REFERENCES v3.budget_form(id); +ALTER TABLE v3.phase_template ADD CONSTRAINT phase_template_form_type_fk FOREIGN KEY (form_type) REFERENCES v3.form_type(code); +ALTER TABLE v3."plan" ADD CONSTRAINT plan_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.project ADD CONSTRAINT project_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id); +ALTER TABLE v3.rent_detail ADD CONSTRAINT rent_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.rent_detail ADD CONSTRAINT rent_detail_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id); +ALTER TABLE v3.reserve ADD CONSTRAINT reserve_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.rf_project_report ADD CONSTRAINT rf_project_report_project_fk FOREIGN KEY (project_id) REFERENCES v3.project(id); +ALTER TABLE v3.rf_project_report ADD CONSTRAINT rf_project_report_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id); +ALTER TABLE v3.rf_project_report ADD CONSTRAINT rf_project_report_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id); +ALTER TABLE v3.rf_project_report_line ADD CONSTRAINT rf_project_report_line_rf_project_report_fk FOREIGN KEY (rf_project_report_id) REFERENCES v3.rf_project_report(id); +ALTER TABLE v3.rf_project_report_line ADD CONSTRAINT rf_project_report_line_expense_item_fk FOREIGN KEY (expense_item_id) REFERENCES v3.expense_item(id); +ALTER TABLE v3.rf_project_report_quarter ADD CONSTRAINT rf_project_report_quarter_rf_project_report_line_fk FOREIGN KEY (rf_project_report_line_id) REFERENCES v3.rf_project_report_line(id); +ALTER TABLE v3.security_detail ADD CONSTRAINT security_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.security_detail ADD CONSTRAINT security_detail_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id); +ALTER TABLE v3.sequestration ADD CONSTRAINT sequestration_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.user_org ADD CONSTRAINT user_org_app_user_fk FOREIGN KEY (user_id) REFERENCES v3.app_user(id); +ALTER TABLE v3.user_org ADD CONSTRAINT user_org_org_unit_fk FOREIGN KEY (org_unit_id) REFERENCES v3.org_unit(id); +ALTER TABLE v3.utility_detail ADD CONSTRAINT utility_detail_budget_line_fk FOREIGN KEY (line_id) REFERENCES v3.budget_line(id); +ALTER TABLE v3.utility_detail ADD CONSTRAINT utility_detail_vsp_fk FOREIGN KEY (vsp_id) REFERENCES v3.vsp(id); +ALTER TABLE v3.vsp ADD CONSTRAINT vsp_org_unit_fk FOREIGN KEY (branch_id) REFERENCES v3.org_unit(id); +ALTER TABLE v3.vsp ADD CONSTRAINT vsp_app_user_fk FOREIGN KEY (created_by) REFERENCES v3.app_user(id); +ALTER TABLE v3.vsp ADD CONSTRAINT vsp_app_user_fk_1 FOREIGN KEY (updated_by) REFERENCES v3.app_user(id); + + +-- DROP FUNCTION v3.add_project(varchar, int4, int4, varchar, int4, varchar, varchar, varchar, varchar, int4, numeric, varchar); + +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, p_system_code character varying DEFAULT NULL::character varying) + 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 ou WHERE id = p_org_unit_id AND ou.is_active ) 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, + system_code + ) 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, + p_system_code + ) RETURNING id INTO v_pid; + + -- Первый год: 2 отчёта (LIMIT + CURRENT_EXPENSES) + snapshot фаз + сетка статей. + -- p_emit_audit=false — событие года покрыто PROJECT_CREATE ниже. Дальнейшие + -- годы добавляются отдельно через v3.add_project_year. + SELECT y.limit_report_id, y.current_expenses_report_id + INTO v_lim, v_cur + FROM v3.add_project_year(v_pid, p_year, false) y; + + -- Аудит: 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, + 'system_code', p_system_code, + 'staff_count', p_staff_count, + 'total_area', p_total_area, + 'limit_report_id', v_lim, + 'current_expenses_report_id', v_cur + )) + ); + + RETURN QUERY SELECT v_pid, v_lim, v_cur; + END; + $function$ +; \ No newline at end of file diff --git a/api/alembic/versions/sql/0012_del_project.sql b/api/alembic/versions/sql/0012_del_project.sql new file mode 100644 index 0000000..55e5668 --- /dev/null +++ b/api/alembic/versions/sql/0012_del_project.sql @@ -0,0 +1,2464 @@ +-- удаление проектов +CREATE OR REPLACE FUNCTION v3.can_delete_project(p_project_id integer) +RETURNS BOOL +LANGUAGE plpgsql +AS $function$ +DECLARE + v_old RECORD; + v_actor_id INTEGER; + v_role VARCHAR; + -- v_phase v3.form3_phase; +BEGIN + v_actor_id := v3.current_user_id(); + v_role := v3.user_role_code(v_actor_id); + + SELECT p.name, p.level, p.org_unit_id, p.created_at + INTO v_old + FROM v3.project p WHERE p.id = p_project_id; + + IF v_role != 'ADMIN' AND NOT EXISTS ( + SELECT 1 FROM v3.user_org uo + WHERE uo.org_unit_id = v_old.org_unit_id AND uo.user_id = v_actor_id + ) THEN + RETURN false; + END IF; + + IF ( + select count(*) from ( + select rpr.id, max(fp.closes_at) from v3.form3_phase fp + inner join v3.rf_project_report rpr + on rpr.id = fp.rf_project_report_id + and rpr.project_id = p_project_id + and fp.opens_at <= current_timestamp + and current_timestamp <= fp.closes_at + and fp.opens_at <= v_old.created_at + and v_old.created_at <= fp.closes_at + group by rpr.id + ) + ) = (select COUNT(*) from v3.rf_project_report rpr where rpr.project_id = p_project_id) THEN + RETURN true; + END IF; + + IF NOT EXISTS ( + SELECT 1 + FROM v3.rf_project_report_line rprl + INNER JOIN v3.rf_project_report rpr ON rpr.id = rprl.rf_project_report_id + WHERE rpr.project_id = p_project_id + ) THEN + RETURN true; + END IF; + + RETURN false; + + + END; +$function$ +; + +CREATE OR REPLACE FUNCTION v3.del_project(p_project_id integer) +RETURNS integer +LANGUAGE plpgsql +AS $function$ +DECLARE + v_old RECORD; + v_actor_id INTEGER; + v_role VARCHAR; +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; + + IF NOT v3.can_delete_project(p_project_id) THEN + RAISE EXCEPTION 'project #% невозможно удалить', p_project_id; + END IF; + + -- Каскад вручную: rf_project_report_quarter / line / report (нет ON DELETE) + DELETE FROM v3.form3_phase f3p WHERE f3p.rf_project_report_id IN ( + SELECT id FROM v3.rf_project_report rpr WHERE project_id = p_project_id + ); + 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$ +; + + + +-- Форма 4 + +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, + r.justification AS rjust, + -- 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, CASE WHEN s_rsv THEN f.rjust END, + -- 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$ +; + +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( + 'total', 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_y1', s.col_cs_future_y1, + 'future_y2', s.col_cs_future_y2, + 'other_ssp', 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( + 'approved', s.col_col_amount, + 'protocol', 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, + 'q1', s.col_ckk_q1, + 'q2', s.col_ckk_q2, + 'q3', s.col_ckk_q3, + 'q4', s.col_ckk_q4, + 'rf_schedule', s.col_ckk_rf_schedule, + 'deadline', s.col_ckk_delivery_deadline, + 'proc_plan', s.col_ckk_procurement_plan, + 'proc_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, + 'q1', s.col_cd_q1, + 'q2', s.col_cd_q2, + 'q3', s.col_cd_q3, + '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$ +; + + +CREATE OR REPLACE FUNCTION v3.v_form4_sheet_jsonb(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb) + LANGUAGE sql + STABLE +AS $function$ + -- ═══ 1. Базовые INPUT-строки с расширенной иерархией ═══════════════════════ + WITH inp_raw AS ( + SELECT j.* + FROM v3.v_form4_sheet_lines_jsonb(p_form_id, p_sheet, p_sections) j + WHERE j.row_type = 'INPUT' + ), + inp AS ( + SELECT + i.*, + bl.project_id AS line_project_id, + + -- --- разрешение проекта --- + -- A. bl.project_id → project (level='project') + -- B. bl.project_id → program (level='program') — тогда проекта нет + CASE WHEN prj.id IS NOT NULL THEN prj.id END AS prj_id, + CASE WHEN prj.id IS NOT NULL THEN prj.name END AS prj_name, + + -- --- разрешение программы --- + -- A. через проект: prog_via_prj + -- B. напрямую: prog_direct + COALESCE(prog_via_prj.id, prog_direct.id) AS prog_id, + COALESCE(prog_via_prj.name, prog_direct.name) AS prog_name, + + -- --- section (depth=0) --- + sec.id AS sec_id, + sec.section_code AS sec_code, + sec.name AS sec_name + + FROM inp_raw i + JOIN v3.budget_line bl ON bl.id = i.line_id + + -- проект (level='project') + LEFT JOIN v3.form4_project prj + ON prj.id = bl.project_id AND prj.level = 'project' + + -- программа через проект + LEFT JOIN v3.form4_project prog_via_prj + ON prog_via_prj.id = prj.parent_id AND prog_via_prj.level = 'program' + + -- программа напрямую (bl.project_id → program) + LEFT JOIN v3.form4_project prog_direct + ON prog_direct.id = bl.project_id AND prog_direct.level = 'program' + + -- раздел depth=0 (через expense_item) + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN LATERAL ( + WITH RECURSIVE up AS ( + SELECT id, parent_id, name, section_code, depth + FROM v3.expense_item WHERE id = ei.id + UNION ALL + SELECT e.id, e.parent_id, e.name, e.section_code, e.depth + FROM v3.expense_item e JOIN up u ON u.parent_id = e.id + ) + SELECT id, name, section_code FROM up WHERE depth = 0 LIMIT 1 + ) sec ON TRUE + ), + tp_agg AS ( + SELECT 1 AS tp_id, + jsonb_build_object( + 'q1', 0, 'q2', 0, + 'q3', 0, 'q4', 0, + 'year', 0, 'comment', null + ) AS tp_plan, + + + + jsonb_build_object( + 'q1', 0, 'q2', 0, + 'q3', 0, 'q4', 0, + 'year', 0, + 'justification', null + ) AS tp_seq_dfip, + + jsonb_build_object( + 'q1', 0, 'q2', 0, + 'q3', 0, 'q4', 0, + 'year', 0 + ) AS tp_approved, + + jsonb_build_object( + 'total', null, + 'reference', null, + 'counterparty', null, + 'deadline', null, + 'comment', null, + 'future_y1', null, + 'future_y2', null, + 'other_ssp', null, + 'centralized_flag', null + ) AS tp_contract_summary, + + jsonb_build_object( + 'contract_ref', null, + 'allocation_purpose', null + ) AS tp_allocation, + + jsonb_build_object( + 'q1', 0, 'q2', 0, + 'q3', 0, 'q4', 0, + 'year', 0, + 'justification', null + ) AS tp_reserve, + + jsonb_build_object( + 'approved', null, + 'protocol', null, + 'note', null + ) AS tp_collegial, + + jsonb_build_object( + 'ceiling', null, + 'q1', null, + 'q2', null, + 'q3', null, + 'q4', null, + 'rf_schedule', null, + 'delivery_deadline', null, + 'proc_plan', null, + 'proc_method', null, + 'comment', null + ) AS tp_ckk, + + jsonb_build_object( + 'act', null, + 'date', null, + 'scheme', null, + 'addenda', null, + 'ceiling', null, + 'comment', null, + 'subject', null, + 'currency', null, + 'deadline', null, + 'vat_rate', null, + 'reference', null, + 'q1', null, + 'q2', null, + 'q3', null, + 'q4', null, + 'rf_schedule', null, + 'counterparty', null, + 'exchange_rate', null, + 'amount_foreign', null + ) AS tp_contract, + + jsonb_build_object( + 'y2026', jsonb_build_object('q1', null, 'q2', null, 'q3', null, 'q4', null), + 'y2027', jsonb_build_object('q1', null, 'q2', null, 'q3', null, 'q4', null) + ) AS tp_booking, + + jsonb_build_object( + 'total', 0, + 'adj_rf', null, + 'pay_ho', null, + 'pay_rf', null, + 'adj_ssp', null, + 'booking', null, + 'pay_act', null, + 'pay_date', null, + 'actual_m1', null, + 'actual_m2', null, + 'actual_m3', null, + 'pay_amount', null, + 'adj_comment', null, + 'adj_current', null, + 'adj_reserve', null, + 'pay_comment', null, + 'transfer_q2', null, + 'transfer_q3', null, + 'transfer_q4', null, + 'transfer_econ', null, + 'actual_quarter', 0, + 'corrected_plan', 0, + 'residual_after_actual', 0, + 'residual_after_booking', 0 + ) AS tp_q1, + + jsonb_build_object( + 'target_change', null, + 'base_correction', null, + 'base_correction_comment', null, + 'revision_inc', null, + 'revision_seq', null, + 'revision_comment', null, + 'new_plan', 0, + 'adj_current', null, + 'adj_ssp', null, + 'adj_rf', null, + 'adj_reserve', null, + 'adj_comment', null, + 'corrected_plan', 0, + 'pay_date', null, + 'pay_amount', null, + 'pay_ho', null, + 'pay_rf', null, + 'pay_comment', null, + 'pay_act', null, + 'booking', null, + 'actual_m1', null, + 'actual_m2', null, + 'actual_m3', null, + 'actual_quarter', 0, + 'residual_after_booking', 0, + 'residual_after_actual', 0, + 'transfer_q3', null, + 'transfer_q4', null, + 'transfer_econ', null, + 'total', 0 + ) AS tp_q2, + + jsonb_build_object( + 'target_change', null, + 'base_correction', null, + 'base_correction_comment', null, + 'revision_inc', null, + 'revision_seq', null, + 'revision_comment', null, + 'new_plan', 0, + 'adj_current', null, + 'adj_ssp', null, + 'adj_rf', null, + 'adj_reserve', null, + 'adj_comment', null, + 'corrected_plan', 0, + 'pay_date', null, + 'pay_amount', null, + 'pay_ho', null, + 'pay_rf', null, + 'pay_comment', null, + 'pay_act', null, + 'booking', null, + 'actual_m1', null, + 'actual_m2', null, + 'actual_m3', null, + 'actual_quarter', 0, + 'residual_after_booking', 0, + 'residual_after_actual', 0, + 'transfer_q4', null, + 'transfer_econ', null, + 'total', 0 + ) AS tp_q3, + + jsonb_build_object( + 'target_change', null, + 'base_correction', null, + 'base_correction_comment', null, + 'revision_inc', null, + 'revision_seq', null, + 'revision_comment', null, + 'new_plan', 0, + 'adj_current', null, + 'adj_ssp', null, + 'adj_rf', null, + 'adj_reserve', null, + 'adj_comment', null, + 'corrected_plan', 0, + 'pay_date', null, + 'pay_amount', null, + 'pay_ho', null, + 'pay_rf', null, + 'pay_comment', null, + 'pay_act', null, + 'booking', null, + 'actual_m1', null, + 'actual_m2', null, + 'actual_m3', null, + 'actual_spod', null, + 'actual_quarter', 0, + 'residual_after_booking', 0, + 'residual_after_actual', 0, + 'transfer_econ', null, + 'total', 0 + ) AS tp_q4, + jsonb_build_object( + 'fact_year', 0 + ) AS tp_totals + + + + + + ), + -- ═══ 2. Все программы (включая пустые) ═════════════════════════════════════ + all_programs AS ( + SELECT + fp.id AS prog_id, + fp.name AS prog_name, + fp.section_code, + sec.id AS sec_id, + sec.section_code AS sec_code, + sec.name AS sec_name + FROM v3.form4_project fp + LEFT JOIN LATERAL ( + SELECT id, section_code, name + FROM v3.expense_item + WHERE section_code = fp.section_code AND depth = 0 + LIMIT 1 + ) sec ON TRUE + WHERE fp.level = 'program' + AND (p_sections IS NULL OR fp.section_code = ANY(p_sections)) + AND (fp.section_code = p_sheet) AND fp.form_id = p_form_id + ), + + -- ═══ 3. Все проекты под программами (включая пустые) ═══════════════════════ + all_projects AS ( + SELECT + prj.id AS prj_id, + prj.name AS prj_name, + prj.parent_id AS prog_id, + ap.sec_id, ap.sec_code, ap.sec_name + FROM v3.form4_project prj + JOIN all_programs ap ON ap.prog_id = prj.parent_id + WHERE prj.level = 'project' AND (ap.section_code = p_sheet) + ), + + -- ═══ 4. Агрегаты с финансовыми данными (только из inp) ════════════════════ + + -- PROJECT-агрегаты + prj_agg 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 + ), + + -- PROGRAM-агрегаты + prog_agg 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 + ), + + -- ROOT-агрегаты + root_agg 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 + ), + + -- Нулевой JSONB-шаблон (для пустых программ/проектов) + zero AS ( + SELECT '{}'::jsonb AS z + ), + + -- ═══ 5. UNION всех уровней ═════════════════════════════════════════════════ + unioned AS ( + -- ROOT — только из агрегатов (не имеет смысла пустой ROOT без строк) + SELECT + 'ROOT'::VARCHAR AS row_type, 0 AS depth, NULL::INT AS line_id, + jsonb_build_object('section_code', sec_code, 'name', sec_name, 'expense_item_id', sec_id) AS header, + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, 0, 0, 0]::INT[] AS sort_path + FROM root_agg + + UNION ALL + + -- GROUP — из агрегатов (программы с бюджетными строками) + SELECT + 'GROUP', 1, NULL, + jsonb_build_object('section_code', sec_code, 'name', prog_name, 'program_id', prog_id, 'expense_item_id', sec_id), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, prog_id, 0, 0]::INT[] + FROM prog_agg + + UNION ALL + + -- GROUP — пустые программы (нет ни одной budget_line) + SELECT + 'GROUP', 1, NULL, + jsonb_build_object('section_code', sec_code, 'name', prog_name, 'program_id', prog_id, 'expense_item_id', sec_id), + tp_plan, tp_seq_dfip, tp_approved, -- plan, seq_dfip, approved + tp_contract_summary, tp_allocation, tp_reserve, -- contract_summary, allocation, reserve + tp_collegial, tp_ckk, -- collegial, ckk + tp_contract, tp_booking, -- contract, booking + tp_q1, tp_q2, tp_q3, tp_q4, tp_totals, -- q1-q4, totals + ARRAY[sec_id, prog_id, 0, 0]::INT[] + FROM all_programs ap + JOIN tp_agg ON tp_agg.tp_id = 1 + WHERE NOT EXISTS ( + SELECT 1 FROM v3.budget_line bl + WHERE bl.budget_form_id = p_form_id + AND ( + bl.project_id = ap.prog_id -- прямая привязка к программе + OR bl.project_id IN (SELECT id FROM v3.form4_project pr -- через проект + WHERE pr.parent_id = ap.prog_id AND pr.level = 'project') + ) + ) + + UNION ALL + + -- ITEM — из агрегатов (проекты с бюджетными строками) + SELECT + 'ITEM', 2, NULL, + jsonb_build_object('section_code', sec_code, 'name', prj_name, + 'program_id', prog_id, 'project_id', prj_id, 'expense_item_id', sec_id), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, prog_id, prj_id, 0]::INT[] + FROM prj_agg + + UNION ALL + + -- ITEM — пустые проекты (нет budget_line) + SELECT + 'ITEM', 2, NULL, + jsonb_build_object('section_code', sec_code, 'name', prj_name, + 'program_id', prog_id, 'project_id', prj_id, 'expense_item_id', sec_id), + tp_plan, tp_seq_dfip, tp_approved, -- plan, seq_dfip, approved + tp_contract_summary, tp_allocation, tp_reserve, -- contract_summary, allocation, reserve + tp_collegial, tp_ckk, -- collegial, ckk + tp_contract, tp_booking, -- contract, booking + tp_q1, tp_q2, tp_q3, tp_q4, tp_totals, -- q1-q4, totals + ARRAY[sec_id, prog_id, prj_id, 0]::INT[] + FROM all_projects apr + JOIN tp_agg ON tp_agg.tp_id = 1 + WHERE NOT EXISTS ( + SELECT 1 FROM v3.budget_line bl + WHERE bl.budget_form_id = p_form_id + AND bl.project_id = apr.prj_id + ) + + UNION ALL + + -- INPUT — все строки бюджета + SELECT + 'INPUT', 3, line_id, + header || jsonb_build_object( + 'project_id', prj_id, 'project_name', prj_name, + 'program_id', prog_id, 'program_name', prog_name + ), + plan_data, seq_dfip_data, approved_data, + contract_summary_data, allocation_data, reserve_data, collegial_data, ckk_data, + contract_data, booking_data, + q1_data, q2_data, q3_data, q4_data, totals_data, + ARRAY[sec_id, COALESCE(prog_id, 999999), COALESCE(CASE WHEN prj_id is null and prog_id is not null THEN 0 ELSE prj_id END, 999999), line_id]::INT[] + FROM inp + ) + + SELECT + u.row_type, u.depth, + ROW_NUMBER() OVER (ORDER BY u.sort_path) AS sort_order, + jsonb_build_object( + 'sort_path', u.sort_path, + 'line_id', u.line_id, + 'header', u.header, + 'plan', u.plan_data, + 'seq_dfip', u.seq_dfip_data, + 'approved', u.approved_data, + 'contract_summary', u.contract_summary_data, + 'allocation', u.allocation_data, + 'reserve', u.reserve_data, + 'collegial', u.collegial_data, + 'ckk', u.ckk_data, + 'contract', u.contract_data, + 'booking', u.booking_data, + 'q1', u.q1_data, + 'q2', u.q2_data, + 'q3', u.q3_data, + 'q4', u.q4_data, + 'totals', u.totals_data + ) AS data + FROM unioned u + ORDER BY u.sort_path; +$function$ +; + +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'), + ('allocation','contract_ref', 'allocation','contract_ref','TEXT','line'), + ('allocation','allocation_purpose', 'allocation','allocation_purpose','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'), + ('contract_summary','reference', 'contract_summary','reference', 'TEXT','line'), + ('contract_summary','centralized_flag', 'contract_summary','centralized_flag', 'TEXT','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','addenda', 'contract_detail','addenda', 'TEXT', 'line'), + ('contract','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'), + ('contract','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'), + ('contract','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'), + ('contract','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'), + ('contract','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'), + ('contract','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'), + ('contract','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','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$ +; + + +-- Удаляем FK у audit_log на случай удаления исконной записи и сохранения в журнале +ALTER TABLE v3.audit_log DROP CONSTRAINT IF EXISTS audit_log_app_user_fk; +ALTER TABLE v3.audit_log DROP CONSTRAINT IF EXISTS audit_log_org_unit_fk; +ALTER TABLE v3.audit_log DROP CONSTRAINT IF EXISTS audit_log_budget_form_fk; +ALTER TABLE v3.project ADD COLUMN created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now(); + + +-- ════════════════════════════════════════════════════════════════════════════ +-- ПАТЧ: v3.v_form_view — добавление p_user_id + META-строки (editable-маска) +-- ════════════════════════════════════════════════════════════════════════════ +-- Идемпотентен. Пересоздаёт функцию (DROP обеих сигнатур + CREATE). +-- Зависимости (должны уже быть на целевой БД): v3.editable_columns_for(INT,INT,VARCHAR), +-- v3.user_role_code(INT), v3.budget_form, а также v_form1/2/4-функции. +-- +-- Применение: +-- psql -U dfip -d dfip -f patch_v_form_view_meta.sql +-- docker exec -i psql -U dfip -d dfip < patch_v_form_view_meta.sql +-- ════════════════════════════════════════════════════════════════════════════ + +-- ─── precheck зависимостей: падаем ДО DROP, если чего-то нет ───────────────── +DO $precheck$ +BEGIN + IF to_regprocedure('v3.editable_columns_for(int,int,varchar)') IS NULL THEN + RAISE EXCEPTION 'Зависимость отсутствует: v3.editable_columns_for(INT,INT,VARCHAR). Накатите sql/v3/permissions.sql'; + END IF; + IF to_regprocedure('v3.user_role_code(int)') IS NULL THEN + RAISE EXCEPTION 'Зависимость отсутствует: v3.user_role_code(INT). Накатите sql/v3/permissions.sql'; + END IF; +END +$precheck$; + +DROP FUNCTION IF EXISTS v3.v_form_view(INT, VARCHAR, TEXT[], VARCHAR); +DROP FUNCTION IF EXISTS v3.v_form_view(INT, VARCHAR, TEXT[], VARCHAR, INT); + +CREATE FUNCTION v3.v_form_view( + p_form_id INT, + p_sheet VARCHAR, + p_sections TEXT[] DEFAULT NULL, + p_direction VARCHAR DEFAULT NULL, + p_user_id INT DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +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; + + -- ═══ META-строка (маска редактируемых колонок) ════════════════════════════ + -- Отдаётся первой (sort_order=0), только если передан p_user_id. НЕ гейт на + -- просмотр — сами строки листа возвращаются ниже независимо от прав. + IF p_user_id IS NOT NULL THEN + RETURN QUERY + SELECT + 'META'::VARCHAR, + -1, + 0::BIGINT, + jsonb_build_object( + 'user_id', p_user_id, + 'role', v3.user_role_code(p_user_id), + 'sheet', p_sheet, + 'editable', COALESCE( + (SELECT jsonb_agg( + jsonb_build_object('column', ec.column_key, + 'closes_at', ec.closes_at) + ORDER BY ec.column_key) + FROM v3.editable_columns_for(p_form_id, p_user_id, p_sheet) ec), + '[]'::JSONB) + ); + 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$ +LANGUAGE plpgsql STABLE; + + +-- ════════════════════════════════════════════════════════════════════════════ +-- ПАТЧ: v3.v_form3_report_jsonb — добавление p_user_id + META-строки (editable) +-- ════════════════════════════════════════════════════════════════════════════ +-- Идемпотентен. Пересоздаёт функцию (DROP 2-арг и 3-арг сигнатур + CREATE). +-- Зависимости (должны быть на целевой БД): v3.editable_columns_for3(INT,INT), +-- v3.user_role_code(INT), v3.rf_project_report, v3.v_form3_report_sections. +-- +-- Применение: +-- psql -U dfip -d dfip -f patch_v_form3_report_jsonb_meta.sql +-- docker exec -i psql -U dfip -d dfip < patch_v_form3_report_jsonb_meta.sql +-- ════════════════════════════════════════════════════════════════════════════ + +DO $precheck$ +BEGIN + IF to_regprocedure('v3.editable_columns_for3(int,int)') IS NULL THEN + RAISE EXCEPTION 'Зависимость отсутствует: v3.editable_columns_for3(INT,INT). Накатите sql/v3/permissions_form3.sql'; + END IF; + IF to_regprocedure('v3.user_role_code(int)') IS NULL THEN + RAISE EXCEPTION 'Зависимость отсутствует: v3.user_role_code(INT). Накатите sql/v3/permissions.sql'; + END IF; +END +$precheck$; + +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form3_report_jsonb — JSONB-обёртка над v_form3_report_sections +-- ════════════════════════════════════════════════════════════════════════════ +-- Один трек FORM_3 (LIMIT либо CURRENT_EXPENSES) как (row_type, depth, +-- sort_order, data jsonb) — общий формат UI с FORM_1/2/4. +-- +-- p_user_id — если задан, ПЕРВОЙ строкой (row_type='META', depth=-1, +-- sort_order=0) идёт маска редактируемых колонок текущей фазы для этого юзера: +-- data = { user_id, role, report_id, report_type, editable:[{column,closes_at}] }. +-- Маска — v3.editable_columns_for3(report, user) (якорь rf_project_report, +-- лист/трек определяется отчётом внутри). Это НЕ гейт на просмотр — строки +-- отчёта отдаются всегда; при отсутствии прав editable=[] → фронт read-only. +-- NULL (по умолчанию) → META-строки нет (обратная совместимость). +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form3_report_jsonb(INT, TEXT[]); +DROP FUNCTION IF EXISTS v3.v_form3_report_jsonb(INT, TEXT[], INT); + +CREATE FUNCTION v3.v_form3_report_jsonb( + p_report_id INT, + p_sections TEXT[] DEFAULT NULL, + p_user_id INT DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ + SELECT u.row_type, u.depth, u.sort_order, u.data + FROM ( + -- ═══ META-строка (маска редактируемых колонок) ══════════════════════════ + -- Только при переданном p_user_id. НЕ гейт на просмотр. + SELECT + 'META'::VARCHAR AS row_type, + -1 AS depth, + 0::BIGINT AS sort_order, + jsonb_build_object( + 'user_id', p_user_id, + 'role', v3.user_role_code(p_user_id), + 'report_id', p_report_id, + 'report_type', (SELECT report_type FROM v3.rf_project_report WHERE id = p_report_id), + 'editable', COALESCE( + (SELECT jsonb_agg( + jsonb_build_object('column', ec.column_key, + 'closes_at', ec.closes_at) + ORDER BY ec.column_key) + FROM v3.editable_columns_for3(p_report_id, p_user_id) ec), + '[]'::JSONB) + ) AS data + WHERE p_user_id IS NOT NULL + + UNION ALL + + 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( + 'base_plan', s.col_q1_base_plan, + '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( + 'base_plan', s.col_q2_base_plan, + 'corrected_plan', s.col_q2_corrected_plan, + '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( + 'base_plan', s.col_q3_base_plan, + 'corrected_plan', s.col_q3_corrected_plan, + '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( + 'base_plan', s.col_q4_base_plan, + 'corrected_plan', s.col_q4_corrected_plan, + '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( + 'base_plan', s.col_year_base_plan, -- Год Базового плана (Σ q1–q4) + '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 + ) u + ORDER BY u.sort_order; +$function$ +LANGUAGE sql STABLE; + diff --git a/api/alembic/versions/sql/0013_form2_limit.sql b/api/alembic/versions/sql/0013_form2_limit.sql new file mode 100644 index 0000000..39e5c6d --- /dev/null +++ b/api/alembic/versions/sql/0013_form2_limit.sql @@ -0,0 +1,1733 @@ +-- ════════════════════════════════════════════════════════════════════════════ +-- МИГРАЦИЯ: FORM_2 — вывод недостающих блоков в DTO листов КВ_П / Операц +-- ════════════════════════════════════════════════════════════════════════════ +-- Функции чтения FORM_2 (AHR/CAP/OPER) раньше НЕ отдавали ряд блоков, хотя +-- данные лежат в БД. Эта миграция переопределяет 3 функции так, что для +-- листов КВ_П/Операц дополнительно отдаются: +-- header.internal_order (budget_line.internal_order) +-- contract_summary.* (v3.contract_summary) +-- allocation.* (v3.allocation) +-- collegial.* (v3.collegial_approval) +-- ckk.* (v3.ckk: ceiling/rf_schedule/deadline/ +-- proc_plan/proc_method/comment) +-- contract.{addenda,q1-q4,rf_schedule,exchange_rate,amount_foreign} +-- (v3.contract_detail — доп. поля) +-- q*.adj_rf (budget_line_quarter.adj_rf) +-- q*.pay_ho / q*.pay_rf (budget_line_quarter.payment_amount_ho/rf) +-- Лист АХР формы 2 остаётся с прежней (более простой) раскладкой — новые блоки +-- для него срезаются в v3.v_form2_view (в шаблоне АХР этих колонок нет). +-- +-- ТРЕБОВАНИЯ К ЦЕЛЕВОЙ БД: схема v3 на актуальной версии (наличие таблиц +-- v3.contract_summary / v3.allocation / v3.collegial_approval / v3.ckk / +-- v3.contract_detail и колонок budget_line.internal_order, +-- budget_line_quarter.adj_rf / payment_amount_ho / payment_amount_rf). +-- +-- ИДЕМПОТЕНТНО: каждая функция пересоздаётся через DROP ... IF EXISTS + CREATE. +-- Порядок важен: sections → jsonb (зовёт sections) → view (зовёт jsonb). +-- ════════════════════════════════════════════════════════════════════════════ + + +-- ═══ 1/3 ═══ v3.v_form2_sheet_sections ═══════════════════════════════════ +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form2_sheet_sections — основное отображение листов АХР / КВ_П / Операц +-- ════════════════════════════════════════════════════════════════════════════ +-- Возвращает иерархию (ROOT/GROUP/ITEM/SUB_ITEM) + INPUT строки в tree-order. +-- Колоночная раскладка повторяет шаблон Формы 2: +-- header, plan, seq_dfip, seq_ssp_go, approved, contract, booking (2026+2027), +-- q1, q2, q3, q4, totals. +-- +-- p_sections — массив включаемых блоков (NULL = все). +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form2_sheet_sections(INT, VARCHAR, TEXT[]); + +CREATE FUNCTION v3.v_form2_sheet_sections( + p_form_id INT, + p_sheet VARCHAR, -- 'AHR' / 'CAP' / 'OPER' + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + line_id INT, + -- header + col_section_code VARCHAR, + col_item_id VARCHAR, + col_num_group_id VARCHAR, + col_name VARCHAR, + col_vsp_id INT, + col_vsp_address VARCHAR, + -- plan + col_plan_q1 NUMERIC, + col_plan_q2 NUMERIC, + col_plan_q3 NUMERIC, + col_plan_q4 NUMERIC, + col_plan_year NUMERIC, + col_plan_comment VARCHAR, + -- seq_dfip + col_seq_dfip_q1 NUMERIC, + col_seq_dfip_q2 NUMERIC, + col_seq_dfip_q3 NUMERIC, + col_seq_dfip_q4 NUMERIC, + col_seq_dfip_year NUMERIC, + col_seq_dfip_just VARCHAR, + -- seq_ssp_go + col_seq_ssp_q1 NUMERIC, + col_seq_ssp_q2 NUMERIC, + col_seq_ssp_q3 NUMERIC, + col_seq_ssp_q4 NUMERIC, + col_seq_ssp_year NUMERIC, + col_seq_ssp_just VARCHAR, + -- approved (= plan + seq_dfip + seq_ssp + reserve) + col_appr_q1 NUMERIC, + col_appr_q2 NUMERIC, + col_appr_q3 NUMERIC, + col_appr_q4 NUMERIC, + col_appr_year NUMERIC, + -- contract + col_cd_counterparty VARCHAR, + col_cd_reference VARCHAR, + col_cd_date DATE, + col_cd_subject VARCHAR, + col_cd_currency VARCHAR, + col_cd_ceiling NUMERIC, + col_cd_vat_rate VARCHAR, + col_cd_deadline VARCHAR, + col_cd_scheme VARCHAR, + col_cd_act VARCHAR, + col_cd_comment VARCHAR, + -- booking 2026 (текущий год) + col_book_q1 NUMERIC, + col_book_q2 NUMERIC, + col_book_q3 NUMERIC, + col_book_q4 NUMERIC, + -- booking 2027 (следующий год — НОВОЕ в Форме 2) + col_book_next_q1 NUMERIC, + col_book_next_q2 NUMERIC, + col_book_next_q3 NUMERIC, + col_book_next_q4 NUMERIC, + -- Q1 (полный набор полей с pay_act, booking, остатки, transfer reasons) + col_q1_adj_current NUMERIC, + col_q1_adj_ssp NUMERIC, + col_q1_adj_reserve NUMERIC, + col_q1_adj_comment VARCHAR, + col_q1_corrected_plan NUMERIC, + col_q1_pay_date DATE, + col_q1_pay_amount NUMERIC, + col_q1_pay_comment VARCHAR, + col_q1_pay_act VARCHAR, + col_q1_booking NUMERIC, + col_q1_actual_m1 NUMERIC, + col_q1_actual_m2 NUMERIC, + col_q1_actual_m3 NUMERIC, + col_q1_actual_quarter NUMERIC, + col_q1_residual_after_booking NUMERIC, + col_q1_residual_after_actual NUMERIC, + col_q1_transfer_q2 NUMERIC, + col_q1_transfer_q2_delay_acts NUMERIC, + col_q1_transfer_q2_delay_procurement NUMERIC, + col_q1_transfer_q2_economy_rf NUMERIC, + col_q1_transfer_next_comment VARCHAR, + col_q1_transfer_q3 NUMERIC, + col_q1_transfer_q4 NUMERIC, + col_q1_transfer_far_comment VARCHAR, + col_q1_transfer_econ NUMERIC, + col_q1_total NUMERIC, + -- Q2 (полный набор) + col_q2_target_change NUMERIC, + col_q2_base_correction NUMERIC, + col_q2_base_correction_comment VARCHAR, + col_q2_revision_inc NUMERIC, + col_q2_revision_seq NUMERIC, + col_q2_revision_comment VARCHAR, + col_q2_new_plan NUMERIC, + col_q2_adj_current NUMERIC, + col_q2_adj_ssp NUMERIC, + col_q2_adj_reserve NUMERIC, + col_q2_adj_comment VARCHAR, + col_q2_corrected_plan NUMERIC, + col_q2_pay_date DATE, + col_q2_pay_amount NUMERIC, + col_q2_pay_comment VARCHAR, + col_q2_pay_act VARCHAR, + col_q2_booking NUMERIC, + col_q2_actual_m1 NUMERIC, + col_q2_actual_m2 NUMERIC, + col_q2_actual_m3 NUMERIC, + col_q2_actual_quarter NUMERIC, + col_q2_residual_after_booking NUMERIC, + col_q2_residual_after_actual NUMERIC, + col_q2_transfer_q3 NUMERIC, + col_q2_transfer_q3_delay_acts NUMERIC, + col_q2_transfer_q3_delay_procurement NUMERIC, + col_q2_transfer_q3_economy_rf NUMERIC, + col_q2_transfer_next_comment VARCHAR, + col_q2_transfer_q4 NUMERIC, + col_q2_transfer_far_comment VARCHAR, + col_q2_transfer_econ NUMERIC, + col_q2_total NUMERIC, + -- Q3 + col_q3_target_change NUMERIC, + col_q3_base_correction NUMERIC, + col_q3_base_correction_comment VARCHAR, + col_q3_revision_inc NUMERIC, + col_q3_revision_seq NUMERIC, + col_q3_revision_comment VARCHAR, + col_q3_new_plan NUMERIC, + col_q3_adj_current NUMERIC, + col_q3_adj_ssp NUMERIC, + col_q3_adj_reserve NUMERIC, + col_q3_adj_comment VARCHAR, + col_q3_corrected_plan NUMERIC, + col_q3_pay_date DATE, + col_q3_pay_amount NUMERIC, + col_q3_pay_comment VARCHAR, + col_q3_pay_act VARCHAR, + col_q3_booking NUMERIC, + col_q3_actual_m1 NUMERIC, + col_q3_actual_m2 NUMERIC, + col_q3_actual_m3 NUMERIC, + col_q3_actual_quarter NUMERIC, + col_q3_residual_after_booking NUMERIC, + col_q3_residual_after_actual NUMERIC, + col_q3_transfer_q4 NUMERIC, + col_q3_transfer_q4_delay_acts NUMERIC, + col_q3_transfer_q4_delay_procurement NUMERIC, + col_q3_transfer_q4_economy_rf NUMERIC, + col_q3_transfer_next_comment VARCHAR, + col_q3_transfer_econ NUMERIC, + col_q3_total NUMERIC, + -- Q4 (+ СПОД, без переноса в next quarter) + col_q4_target_change NUMERIC, + col_q4_base_correction NUMERIC, + col_q4_base_correction_comment VARCHAR, + col_q4_revision_inc NUMERIC, + col_q4_revision_seq NUMERIC, + col_q4_revision_comment VARCHAR, + col_q4_new_plan NUMERIC, + col_q4_adj_current NUMERIC, + col_q4_adj_ssp NUMERIC, + col_q4_adj_reserve NUMERIC, + col_q4_adj_comment VARCHAR, + col_q4_corrected_plan NUMERIC, + col_q4_pay_date DATE, + col_q4_pay_amount NUMERIC, + col_q4_pay_comment VARCHAR, + col_q4_pay_act VARCHAR, + col_q4_booking NUMERIC, + col_q4_actual_m1 NUMERIC, + col_q4_actual_m2 NUMERIC, + col_q4_actual_m3 NUMERIC, + col_q4_actual_spod NUMERIC, + col_q4_actual_quarter NUMERIC, + col_q4_residual_after_booking NUMERIC, + col_q4_residual_after_actual NUMERIC, + col_q4_transfer_econ NUMERIC, + col_q4_total NUMERIC, + -- totals + col_fact_year NUMERIC, + col_pay_year NUMERIC, + col_economy_year NUMERIC, + + -- ─── НОВОЕ: блоки, которых раньше не было в DTO (данные лежали в БД) ─── + col_internal_order VARCHAR, + -- contract_summary (план-блок «Договоры ССП») + col_cs_total NUMERIC, + col_cs_counterparty VARCHAR, + col_cs_deadline VARCHAR, + col_cs_comment VARCHAR, + col_cs_future_y1 NUMERIC, + col_cs_future_y2 NUMERIC, + col_cs_other_ssp NUMERIC, + -- allocation + col_al_contract_ref VARCHAR, + col_al_purpose VARCHAR, + col_al_order VARCHAR, + col_al_property VARCHAR, + -- collegial + col_ca_approved NUMERIC, + col_ca_protocol VARCHAR, + col_ca_note VARCHAR, + -- ckk (полный блок; поквартальные expenses остаются также в booking.y2026) + col_ckk_ceiling NUMERIC, + col_ckk_rf_sched VARCHAR, + col_ckk_deadline VARCHAR, + col_ckk_proc_plan VARCHAR, + col_ckk_proc_meth VARCHAR, + col_ckk_comment VARCHAR, + -- contract_detail: доп. поля (сверх блока contract) + col_cd_addenda VARCHAR, + col_cd_exp_q1 NUMERIC, + col_cd_exp_q2 NUMERIC, + col_cd_exp_q3 NUMERIC, + col_cd_exp_q4 NUMERIC, + col_cd_rf_sched VARCHAR, + col_cd_exchange_rate NUMERIC, + col_cd_amount_foreign NUMERIC, + -- adj_rf / платёж ГО / платёж РФ по кварталам + col_q1_adj_rf NUMERIC, + col_q1_pay_ho NUMERIC, + col_q1_pay_rf NUMERIC, + col_q2_adj_rf NUMERIC, + col_q2_pay_ho NUMERIC, + col_q2_pay_rf NUMERIC, + col_q3_adj_rf NUMERIC, + col_q3_pay_ho NUMERIC, + col_q3_pay_rf NUMERIC, + col_q4_adj_rf NUMERIC, + col_q4_pay_ho NUMERIC, + col_q4_pay_rf NUMERIC, + + _sort_path INT[] +) +AS $function$ +#variable_conflict use_column +DECLARE + s_plan BOOL; s_seq_d BOOL; s_seq_s BOOL; s_appr BOOL; + s_cd BOOL; s_book BOOL; + s_cs BOOL; s_al BOOL; s_ca BOOL; s_ckk BOOL; -- НОВЫЕ блоки + s_q1 BOOL; s_q2 BOOL; s_q3 BOOL; s_q4 BOOL; s_tot BOOL; + s_need_ap BOOL; -- plan/seq/reserve нужны для approved + cp1..cp4 +BEGIN + s_plan := p_sections IS NULL OR 'plan' = ANY(p_sections); + s_seq_d := p_sections IS NULL OR 'seq_dfip' = ANY(p_sections); + s_seq_s := p_sections IS NULL OR 'seq_ssp' = ANY(p_sections); + s_appr := p_sections IS NULL OR 'approved' = ANY(p_sections); + s_cd := p_sections IS NULL OR 'contract' = ANY(p_sections); + s_book := p_sections IS NULL OR 'booking' = ANY(p_sections); + s_cs := p_sections IS NULL OR 'contract_summary' = ANY(p_sections); + s_al := p_sections IS NULL OR 'allocation' = ANY(p_sections); + s_ca := p_sections IS NULL OR 'collegial' = ANY(p_sections); + s_ckk := p_sections IS NULL OR 'ckk' = ANY(p_sections); + s_q1 := p_sections IS NULL OR 'q1' = ANY(p_sections); + s_q2 := p_sections IS NULL OR 'q2' = ANY(p_sections); + s_q3 := p_sections IS NULL OR 'q3' = ANY(p_sections); + s_q4 := p_sections IS NULL OR 'q4' = ANY(p_sections); + s_tot := p_sections IS NULL OR 'totals' = ANY(p_sections); + s_need_ap := s_plan OR s_appr OR s_q1 OR s_q2 OR s_q3 OR s_q4; + + RETURN QUERY + WITH + tw AS ( + SELECT t.id, t.section_code, t.item_id, t.num_group_id, t.name, + t.depth, t.path, t.parent_id, t.parent_item_id, t.desc_ids + FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id + JOIN v3.budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code + WHERE t.sheet = p_sheet + ), + page AS ( + SELECT bl.id AS lid, bl.expense_item_id AS eid, bl.name AS bname, bl.vsp_id, + bl.internal_order AS io + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + ), + -- Conditional joins (gated by section flags) + jp AS (SELECT p.* FROM v3.plan p JOIN page pg ON p.line_id = pg.lid WHERE s_need_ap), + jsd AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='DFIP'), + jsg AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='SSP_GO'), + jr AS (SELECT r.* FROM v3.reserve r JOIN page pg ON r.line_id = pg.lid WHERE s_need_ap), + jcd AS (SELECT c.* FROM v3.contract_detail c JOIN page pg ON c.line_id = pg.lid WHERE s_cd), + jck AS (SELECT c.* FROM v3.ckk c JOIN page pg ON c.line_id = pg.lid WHERE s_book OR s_ckk), + jcs AS (SELECT c.* FROM v3.contract_summary c JOIN page pg ON c.line_id = pg.lid WHERE s_cs), + jal AS (SELECT a.* FROM v3.allocation a JOIN page pg ON a.line_id = pg.lid WHERE s_al), + jca AS (SELECT c.* FROM v3.collegial_approval c JOIN page pg ON c.line_id = pg.lid WHERE s_ca), + jq1 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q1 AND q.quarter=1), + jq2 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q2 AND q.quarter=2), + jq3 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q3 AND q.quarter=3), + jq4 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE (s_q4 OR s_tot) AND q.quarter=4), + + -- INPUT строки: enriched данные + input_base AS ( + SELECT + pg.lid, pg.eid, pg.bname, pg.vsp_id, + v.address AS vsp_addr, + t.parent_item_id AS sc, t.item_id AS ic, t.num_group_id AS ng, t.name AS ename, t.path AS tree_path, + -- plan + COALESCE(p.plan_q1,0) AS pq1, COALESCE(p.plan_q2,0) AS pq2, + COALESCE(p.plan_q3,0) AS pq3, COALESCE(p.plan_q4,0) AS pq4, + p.comment AS pcmt, + -- seq dfip / ssp_go + COALESCE(sd.adj_q1,0) AS dq1, COALESCE(sd.adj_q2,0) AS dq2, + COALESCE(sd.adj_q3,0) AS dq3, COALESCE(sd.adj_q4,0) AS dq4, + sd.justification AS djust, + COALESCE(sg.adj_q1,0) AS gq1, COALESCE(sg.adj_q2,0) AS gq2, + COALESCE(sg.adj_q3,0) AS gq3, COALESCE(sg.adj_q4,0) AS gq4, + sg.justification AS gjust, + -- reserve + COALESCE(r.amount_q1,0) AS rq1, COALESCE(r.amount_q2,0) AS rq2, + COALESCE(r.amount_q3,0) AS rq3, COALESCE(r.amount_q4,0) AS rq4, + -- contract + cd.counterparty AS cd_cp, cd.reference AS cd_ref, cd.contract_date AS cd_dt, + cd.subject AS cd_subj, cd.currency AS cd_cur, cd.ceiling_amount AS cd_ceil, + cd.vat_rate AS cd_vat, cd.deadline AS cd_dl, cd.payment_scheme AS cd_sch, + cd.act AS cd_act, cd.comment AS cd_cmt, + -- ckk (booking) + ck.expenses_q1 AS bk1, ck.expenses_q2 AS bk2, ck.expenses_q3 AS bk3, ck.expenses_q4 AS bk4, + ck.expenses_next_year_q1 AS bn1, ck.expenses_next_year_q2 AS bn2, + ck.expenses_next_year_q3 AS bn3, ck.expenses_next_year_q4 AS bn4, + -- НОВОЕ: internal_order + pg.io AS io, + -- НОВОЕ: contract_summary + cs.total_amount AS cs_total, cs.counterparty AS cs_cp, cs.deadline AS cs_dl, + cs.comment AS cs_cmt, cs.future_payments_y1 AS cs_fy1, cs.future_payments_y2 AS cs_fy2, + cs.other_ssp_amount AS cs_oth, + -- НОВОЕ: allocation + al.contract_ref AS al_cref, al.allocation_purpose AS al_purp, + al.internal_order AS al_io, al.property_object AS al_prop, + -- НОВОЕ: collegial + ca.approved_amount AS ca_amt, ca.protocol_reference AS ca_proto, ca.note AS ca_note, + -- НОВОЕ: ckk (полный, кроме expenses) + ck.ceiling_amount AS ckk_ceil, ck.rf_schedule AS ckk_rf, ck.delivery_deadline AS ckk_dl, + ck.procurement_plan AS ckk_pplan, ck.procurement_method AS ckk_pmeth, ck.comment AS ckk_cmt, + -- НОВОЕ: contract_detail доп. поля + cd.addenda AS cd_add, cd.expenses_q1 AS cd_e1, cd.expenses_q2 AS cd_e2, + cd.expenses_q3 AS cd_e3, cd.expenses_q4 AS cd_e4, cd.rf_schedule AS cd_rf, + cd.exchange_rate AS cd_rate, cd.amount_foreign AS cd_fx, + -- НОВОЕ: adj_rf / платёж ГО / платёж РФ по кварталам + q1.adj_rf AS q1_arf, q1.payment_amount_ho AS q1_pho, q1.payment_amount_rf AS q1_prf, + q2.adj_rf AS q2_arf, q2.payment_amount_ho AS q2_pho, q2.payment_amount_rf AS q2_prf, + q3.adj_rf AS q3_arf, q3.payment_amount_ho AS q3_pho, q3.payment_amount_rf AS q3_prf, + q4.adj_rf AS q4_arf, q4.payment_amount_ho AS q4_pho, q4.payment_amount_rf AS q4_prf, + -- Q1 + q1.adj_current AS q1_ac, q1.adj_ssp AS q1_as, q1.adj_reserve AS q1_arv, q1.adj_comment AS q1_acmt, + q1.payment_date AS q1_pd, q1.payment_amount AS q1_pa, + q1.payment_comment AS q1_pcmt, + q1.payment_act AS q1_pact, q1.booking_amount AS q1_book, + q1.actual_m1 AS q1_m1, q1.actual_m2 AS q1_m2, q1.actual_m3 AS q1_m3, + q1.transfer_to_q2 AS q1_tq2, q1.transfer_to_q3 AS q1_tq3, q1.transfer_to_q4 AS q1_tq4, + q1.transfer_to_economy AS q1_te, + q1.transfer_delay_acts AS q1_tda, q1.transfer_delay_procurement AS q1_tdp, + q1.transfer_economy_rf AS q1_terf, + q1.transfer_next_comment AS q1_tnc, q1.transfer_far_comment AS q1_tfc, + -- Q2 + q2.target_change AS q2_tc, q2.base_plan_correction AS q2_bc, q2.base_plan_correction_comment AS q2_bcc, + q2.plan_revision_increase AS q2_rinc, q2.plan_revision_sequester AS q2_rseq, + q2.plan_revision_comment AS q2_rcmt, + q2.adj_current AS q2_ac, q2.adj_ssp AS q2_as, q2.adj_reserve AS q2_arv, + q2.adj_comment AS q2_acmt, + q2.payment_date AS q2_pd, q2.payment_amount AS q2_pa, + q2.payment_comment AS q2_pcmt, q2.payment_act AS q2_pact, + q2.booking_amount AS q2_book, + q2.actual_m1 AS q2_m1, q2.actual_m2 AS q2_m2, q2.actual_m3 AS q2_m3, + q2.transfer_to_q3 AS q2_tq3, q2.transfer_to_q4 AS q2_tq4, q2.transfer_to_economy AS q2_te, + q2.transfer_delay_acts AS q2_tda, q2.transfer_delay_procurement AS q2_tdp, + q2.transfer_economy_rf AS q2_terf, + q2.transfer_next_comment AS q2_tnc, q2.transfer_far_comment AS q2_tfc, + -- Q3 + q3.target_change AS q3_tc, q3.base_plan_correction AS q3_bc, + q3.base_plan_correction_comment AS q3_bcc, + q3.plan_revision_increase AS q3_rinc, q3.plan_revision_sequester AS q3_rseq, + q3.plan_revision_comment AS q3_rcmt, + q3.adj_current AS q3_ac, q3.adj_ssp AS q3_as, q3.adj_reserve AS q3_arv, + q3.adj_comment AS q3_acmt, + q3.payment_date AS q3_pd, q3.payment_amount AS q3_pa, + q3.payment_comment AS q3_pcmt, q3.payment_act AS q3_pact, + q3.booking_amount AS q3_book, + q3.actual_m1 AS q3_m1, q3.actual_m2 AS q3_m2, q3.actual_m3 AS q3_m3, + q3.transfer_to_q4 AS q3_tq4, q3.transfer_to_economy AS q3_te, + q3.transfer_delay_acts AS q3_tda, q3.transfer_delay_procurement AS q3_tdp, + q3.transfer_economy_rf AS q3_terf, q3.transfer_next_comment AS q3_tnc, + -- Q4 + q4.target_change AS q4_tc, q4.base_plan_correction AS q4_bc, + q4.base_plan_correction_comment AS q4_bcc, + q4.plan_revision_increase AS q4_rinc, q4.plan_revision_sequester AS q4_rseq, + q4.plan_revision_comment AS q4_rcmt, + q4.adj_current AS q4_ac, q4.adj_ssp AS q4_as, q4.adj_reserve AS q4_arv, + q4.adj_comment AS q4_acmt, + q4.payment_date AS q4_pd, q4.payment_amount AS q4_pa, + q4.payment_comment AS q4_pcmt, q4.payment_act AS q4_pact, + q4.booking_amount AS q4_book, + q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, q4.actual_spod AS q4_spod, + q4.transfer_to_economy AS q4_te, + -- approved per quarter + COALESCE(p.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(sg.adj_q1,0)+COALESCE(r.amount_q1,0) AS ap1, + COALESCE(p.plan_q2,0)+COALESCE(sd.adj_q2,0)+COALESCE(sg.adj_q2,0)+COALESCE(r.amount_q2,0) AS ap2, + COALESCE(p.plan_q3,0)+COALESCE(sd.adj_q3,0)+COALESCE(sg.adj_q3,0)+COALESCE(r.amount_q3,0) AS ap3, + COALESCE(p.plan_q4,0)+COALESCE(sd.adj_q4,0)+COALESCE(sg.adj_q4,0)+COALESCE(r.amount_q4,0) AS ap4 + FROM page pg + JOIN tw t ON t.id = pg.eid + LEFT JOIN v3.vsp v ON v.id = pg.vsp_id + LEFT JOIN jp p ON p.line_id = pg.lid + LEFT JOIN jsd sd ON sd.line_id = pg.lid + LEFT JOIN jsg sg ON sg.line_id = pg.lid + LEFT JOIN jr r ON r.line_id = pg.lid + LEFT JOIN jcd cd ON cd.line_id = pg.lid + LEFT JOIN jck ck ON ck.line_id = pg.lid + LEFT JOIN jcs cs ON cs.line_id = pg.lid + LEFT JOIN jal al ON al.line_id = pg.lid + LEFT JOIN jca ca ON ca.line_id = pg.lid + LEFT JOIN jq1 q1 ON q1.line_id = pg.lid + LEFT JOIN jq2 q2 ON q2.line_id = pg.lid + LEFT JOIN jq3 q3 ON q3.line_id = pg.lid + LEFT JOIN jq4 q4 ON q4.line_id = pg.lid + ), + -- Computed: corrected/new plans + enriched AS ( + SELECT b.*, + -- Q1 corrected_plan = approved + adj_* + b.ap1 + COALESCE(b.q1_ac,0)+COALESCE(b.q1_as,0)+COALESCE(b.q1_arv,0) AS cp1, + COALESCE(b.q1_m1,0)+COALESCE(b.q1_m2,0)+COALESCE(b.q1_m3,0) AS aq1, + -- Q2 new_plan = approved + transfer_q1 + revisions(inc/seq) + base_correction + b.ap2 + COALESCE(b.q1_tq2,0) + COALESCE(b.q2_rinc,0)+COALESCE(b.q2_rseq,0) + COALESCE(b.q2_bc,0) AS np2, + COALESCE(b.q2_m1,0)+COALESCE(b.q2_m2,0)+COALESCE(b.q2_m3,0) AS aq2, + b.ap3 + COALESCE(b.q1_tq3,0)+COALESCE(b.q2_tq3,0) + COALESCE(b.q3_rinc,0)+COALESCE(b.q3_rseq,0) + COALESCE(b.q3_bc,0) AS np3, + COALESCE(b.q3_m1,0)+COALESCE(b.q3_m2,0)+COALESCE(b.q3_m3,0) AS aq3, + b.ap4 + COALESCE(b.q1_tq4,0)+COALESCE(b.q2_tq4,0)+COALESCE(b.q3_tq4,0) + COALESCE(b.q4_rinc,0)+COALESCE(b.q4_rseq,0) + COALESCE(b.q4_bc,0) AS np4, + COALESCE(b.q4_m1,0)+COALESCE(b.q4_m2,0)+COALESCE(b.q4_m3,0)+COALESCE(b.q4_spod,0) AS aq4 + FROM input_base b + ), + final_input AS ( + SELECT e.*, + e.np2 + COALESCE(e.q2_ac,0)+COALESCE(e.q2_as,0)+COALESCE(e.q2_arv,0) AS cp2, + e.np3 + COALESCE(e.q3_ac,0)+COALESCE(e.q3_as,0)+COALESCE(e.q3_arv,0) AS cp3, + e.np4 + COALESCE(e.q4_ac,0)+COALESCE(e.q4_as,0)+COALESCE(e.q4_arv,0) AS cp4 + FROM enriched e + ), + + -- Aggregates per expense_item (для иерархии) + agg AS ( + SELECT bl.expense_item_id AS eid, + SUM(p.plan_q1) AS sp1, SUM(p.plan_q2) AS sp2, SUM(p.plan_q3) AS sp3, SUM(p.plan_q4) AS sp4, + SUM(sd.adj_q1) AS sd1, SUM(sd.adj_q2) AS sd2, SUM(sd.adj_q3) AS sd3, SUM(sd.adj_q4) AS sd4, + SUM(sg.adj_q1) AS sg1, SUM(sg.adj_q2) AS sg2, SUM(sg.adj_q3) AS sg3, SUM(sg.adj_q4) AS sg4, + SUM(r.amount_q1) AS sr1, SUM(r.amount_q2) AS sr2, SUM(r.amount_q3) AS sr3, SUM(r.amount_q4) AS sr4, + SUM(ck.expenses_q1) AS bk1, SUM(ck.expenses_q2) AS bk2, SUM(ck.expenses_q3) AS bk3, SUM(ck.expenses_q4) AS bk4, + SUM(ck.expenses_next_year_q1) AS bn1, SUM(ck.expenses_next_year_q2) AS bn2, + SUM(ck.expenses_next_year_q3) AS bn3, SUM(ck.expenses_next_year_q4) AS bn4 + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.plan p ON p.line_id = bl.id AND s_need_ap + LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor='DFIP' AND s_need_ap + LEFT JOIN v3.sequestration sg ON sg.line_id = bl.id AND sg.actor='SSP_GO' AND s_need_ap + LEFT JOIN v3.reserve r ON r.line_id = bl.id AND s_need_ap + LEFT JOIN v3.ckk ck ON ck.line_id = bl.id AND s_book + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (s_need_ap OR s_book) + GROUP BY bl.expense_item_id + ), + -- Per-quarter aggregates from blq + aq_q AS ( + SELECT bl.expense_item_id AS eid, q.quarter, + SUM(q.adj_current) AS ac, SUM(q.adj_ssp) AS as_v, SUM(q.adj_reserve) AS arv, + SUM(q.payment_amount) AS pa, + SUM(q.booking_amount) AS bk, + SUM(q.actual_m1) AS m1, SUM(q.actual_m2) AS m2, SUM(q.actual_m3) AS m3, + SUM(q.actual_spod) AS spod, + SUM(q.transfer_to_q2) AS tq2, SUM(q.transfer_to_q3) AS tq3, SUM(q.transfer_to_q4) AS tq4, + SUM(q.transfer_to_economy) AS te, + SUM(q.transfer_delay_acts) AS tda, + SUM(q.transfer_delay_procurement) AS tdp, + SUM(q.transfer_economy_rf) AS terf, + -- Δ к approved для перехода к new_plan: revision_inc/seq + target_change + base_correction + -- (Excel: DJ10 = SUM(AN, CY, DH, CF, DE:DG)). Должно совпадать с INPUT-формулой np. + SUM(COALESCE(q.plan_revision_increase,0) + + COALESCE(q.plan_revision_sequester,0) + + COALESCE(q.target_change,0) + + COALESCE(q.base_plan_correction,0)) AS rev + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + JOIN v3.budget_line_quarter q ON q.line_id = bl.id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + GROUP BY bl.expense_item_id, q.quarter + ), + -- Tree-rollup + tw_agg AS ( + SELECT tw.id, + SUM(a.sp1) AS sp1, SUM(a.sp2) AS sp2, SUM(a.sp3) AS sp3, SUM(a.sp4) AS sp4, + SUM(a.sd1) AS sd1, SUM(a.sd2) AS sd2, SUM(a.sd3) AS sd3, SUM(a.sd4) AS sd4, + SUM(a.sg1) AS sg1, SUM(a.sg2) AS sg2, SUM(a.sg3) AS sg3, SUM(a.sg4) AS sg4, + SUM(a.sr1) AS sr1, SUM(a.sr2) AS sr2, SUM(a.sr3) AS sr3, SUM(a.sr4) AS sr4, + SUM(a.bk1) AS bk1, SUM(a.bk2) AS bk2, SUM(a.bk3) AS bk3, SUM(a.bk4) AS bk4, + SUM(a.bn1) AS bn1, SUM(a.bn2) AS bn2, SUM(a.bn3) AS bn3, SUM(a.bn4) AS bn4 + FROM tw LEFT JOIN agg a ON a.eid = ANY(tw.desc_ids) + GROUP BY tw.id + ), + tw_aq1 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq2) AS tq2, SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=1 GROUP BY tw.id + ), + tw_aq2 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=2 GROUP BY tw.id + ), + tw_aq3 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=3 GROUP BY tw.id + ), + tw_aq4 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, SUM(b.spod) AS spod, + SUM(b.te) AS te, SUM(b.rev) AS rev + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=4 GROUP BY tw.id + ) + + -- ═══ Часть A: INPUT строки ═════════════════════════════════════════════ + SELECT * FROM ( + SELECT + 'INPUT'::VARCHAR, 3, f.lid::INT, + f.sc, f.ic, f.ng, COALESCE(f.bname, f.ename), f.vsp_id, f.vsp_addr, + -- plan + CASE WHEN s_plan THEN f.pq1 END, CASE WHEN s_plan THEN f.pq2 END, + CASE WHEN s_plan THEN f.pq3 END, CASE WHEN s_plan THEN f.pq4 END, + CASE WHEN s_plan THEN f.pq1+f.pq2+f.pq3+f.pq4 END, CASE WHEN s_plan THEN f.pcmt END, + -- seq dfip + CASE WHEN s_seq_d THEN f.dq1 END, CASE WHEN s_seq_d THEN f.dq2 END, + CASE WHEN s_seq_d THEN f.dq3 END, CASE WHEN s_seq_d THEN f.dq4 END, + CASE WHEN s_seq_d THEN f.dq1+f.dq2+f.dq3+f.dq4 END, CASE WHEN s_seq_d THEN f.djust END, + -- seq ssp_go + CASE WHEN s_seq_s THEN f.gq1 END, CASE WHEN s_seq_s THEN f.gq2 END, + CASE WHEN s_seq_s THEN f.gq3 END, CASE WHEN s_seq_s THEN f.gq4 END, + CASE WHEN s_seq_s THEN f.gq1+f.gq2+f.gq3+f.gq4 END, CASE WHEN s_seq_s THEN f.gjust END, + -- approved + CASE WHEN s_appr THEN f.ap1 END, CASE WHEN s_appr THEN f.ap2 END, + CASE WHEN s_appr THEN f.ap3 END, CASE WHEN s_appr THEN f.ap4 END, + CASE WHEN s_appr THEN f.ap1+f.ap2+f.ap3+f.ap4 END, + -- contract + CASE WHEN s_cd THEN f.cd_cp END, CASE WHEN s_cd THEN f.cd_ref END, + CASE WHEN s_cd THEN f.cd_dt END, CASE WHEN s_cd THEN f.cd_subj END, + CASE WHEN s_cd THEN f.cd_cur END, CASE WHEN s_cd THEN f.cd_ceil END, + CASE WHEN s_cd THEN f.cd_vat END, CASE WHEN s_cd THEN f.cd_dl END, + CASE WHEN s_cd THEN f.cd_sch END, CASE WHEN s_cd THEN f.cd_act END, + CASE WHEN s_cd THEN f.cd_cmt END, + -- booking 2026 + CASE WHEN s_book THEN f.bk1 END, CASE WHEN s_book THEN f.bk2 END, + CASE WHEN s_book THEN f.bk3 END, CASE WHEN s_book THEN f.bk4 END, + -- booking 2027 + CASE WHEN s_book THEN f.bn1 END, CASE WHEN s_book THEN f.bn2 END, + CASE WHEN s_book THEN f.bn3 END, CASE WHEN s_book THEN f.bn4 END, + -- Q1 + CASE WHEN s_q1 THEN f.q1_ac END, CASE WHEN s_q1 THEN f.q1_as END, CASE WHEN s_q1 THEN f.q1_arv END, + CASE WHEN s_q1 THEN f.q1_acmt END, CASE WHEN s_q1 THEN f.cp1 END, + CASE WHEN s_q1 THEN f.q1_pd END, CASE WHEN s_q1 THEN f.q1_pa END, + CASE WHEN s_q1 THEN f.q1_pcmt END, + CASE WHEN s_q1 THEN f.q1_pact END, CASE WHEN s_q1 THEN f.q1_book END, + CASE WHEN s_q1 THEN f.q1_m1 END, CASE WHEN s_q1 THEN f.q1_m2 END, CASE WHEN s_q1 THEN f.q1_m3 END, + CASE WHEN s_q1 THEN f.aq1 END, + CASE WHEN s_q1 THEN f.cp1 - COALESCE(f.q1_book,0) END, -- residual_after_booking + CASE WHEN s_q1 THEN f.cp1 - f.aq1 END, -- residual_after_actual + CASE WHEN s_q1 THEN f.q1_tq2 END, + CASE WHEN s_q1 THEN f.q1_tda END, CASE WHEN s_q1 THEN f.q1_tdp END, CASE WHEN s_q1 THEN f.q1_terf END, + CASE WHEN s_q1 THEN f.q1_tnc END, + CASE WHEN s_q1 THEN f.q1_tq3 END, CASE WHEN s_q1 THEN f.q1_tq4 END, + CASE WHEN s_q1 THEN f.q1_tfc END, + CASE WHEN s_q1 THEN f.q1_te END, + -- total = SUM transfer_q2_reasons + transfer_q3 + transfer_q4 + transfer_econ (Excel CC5) + CASE WHEN s_q1 THEN + COALESCE(f.q1_tda,0)+COALESCE(f.q1_tdp,0)+COALESCE(f.q1_terf,0) + +COALESCE(f.q1_tq3,0)+COALESCE(f.q1_tq4,0)+COALESCE(f.q1_te,0) + END, + -- Q2 + CASE WHEN s_q2 THEN f.q2_tc END, CASE WHEN s_q2 THEN f.q2_bc END, CASE WHEN s_q2 THEN f.q2_bcc END, + CASE WHEN s_q2 THEN f.q2_rinc END, CASE WHEN s_q2 THEN f.q2_rseq END, + CASE WHEN s_q2 THEN f.q2_rcmt END, + CASE WHEN s_q2 THEN f.np2 END, + CASE WHEN s_q2 THEN f.q2_ac END, CASE WHEN s_q2 THEN f.q2_as END, CASE WHEN s_q2 THEN f.q2_arv END, + CASE WHEN s_q2 THEN f.q2_acmt END, + CASE WHEN s_q2 THEN f.cp2 END, + CASE WHEN s_q2 THEN f.q2_pd END, CASE WHEN s_q2 THEN f.q2_pa END, + CASE WHEN s_q2 THEN f.q2_pcmt END, CASE WHEN s_q2 THEN f.q2_pact END, + CASE WHEN s_q2 THEN f.q2_book END, + CASE WHEN s_q2 THEN f.q2_m1 END, CASE WHEN s_q2 THEN f.q2_m2 END, CASE WHEN s_q2 THEN f.q2_m3 END, + CASE WHEN s_q2 THEN f.aq2 END, + CASE WHEN s_q2 THEN f.cp2 - COALESCE(f.q2_book,0) END, + CASE WHEN s_q2 THEN f.cp2 - f.aq2 END, + CASE WHEN s_q2 THEN f.q2_tq3 END, + CASE WHEN s_q2 THEN f.q2_tda END, CASE WHEN s_q2 THEN f.q2_tdp END, CASE WHEN s_q2 THEN f.q2_terf END, + CASE WHEN s_q2 THEN f.q2_tnc END, + CASE WHEN s_q2 THEN f.q2_tq4 END, + CASE WHEN s_q2 THEN f.q2_tfc END, + CASE WHEN s_q2 THEN f.q2_te END, + -- total = SUM transfer_q3_reasons + transfer_q4 + transfer_econ (Excel DN5) + CASE WHEN s_q2 THEN + COALESCE(f.q2_tda,0)+COALESCE(f.q2_tdp,0)+COALESCE(f.q2_terf,0) + +COALESCE(f.q2_tq4,0)+COALESCE(f.q2_te,0) + END, + -- Q3 + CASE WHEN s_q3 THEN f.q3_tc END, CASE WHEN s_q3 THEN f.q3_bc END, + CASE WHEN s_q3 THEN f.q3_bcc END, + CASE WHEN s_q3 THEN f.q3_rinc END, CASE WHEN s_q3 THEN f.q3_rseq END, + CASE WHEN s_q3 THEN f.q3_rcmt END, + CASE WHEN s_q3 THEN f.np3 END, + CASE WHEN s_q3 THEN f.q3_ac END, CASE WHEN s_q3 THEN f.q3_as END, CASE WHEN s_q3 THEN f.q3_arv END, + CASE WHEN s_q3 THEN f.q3_acmt END, + CASE WHEN s_q3 THEN f.cp3 END, + CASE WHEN s_q3 THEN f.q3_pd END, CASE WHEN s_q3 THEN f.q3_pa END, + CASE WHEN s_q3 THEN f.q3_pcmt END, CASE WHEN s_q3 THEN f.q3_pact END, + CASE WHEN s_q3 THEN f.q3_book END, + CASE WHEN s_q3 THEN f.q3_m1 END, CASE WHEN s_q3 THEN f.q3_m2 END, CASE WHEN s_q3 THEN f.q3_m3 END, + CASE WHEN s_q3 THEN f.aq3 END, + CASE WHEN s_q3 THEN f.cp3 - COALESCE(f.q3_book,0) END, + CASE WHEN s_q3 THEN f.cp3 - f.aq3 END, + CASE WHEN s_q3 THEN f.q3_tq4 END, + CASE WHEN s_q3 THEN f.q3_tda END, CASE WHEN s_q3 THEN f.q3_tdp END, CASE WHEN s_q3 THEN f.q3_terf END, + CASE WHEN s_q3 THEN f.q3_tnc END, + CASE WHEN s_q3 THEN f.q3_te END, + -- total = SUM transfer_q4_reasons + transfer_econ (Excel EW5) + CASE WHEN s_q3 THEN + COALESCE(f.q3_tda,0)+COALESCE(f.q3_tdp,0)+COALESCE(f.q3_terf,0) + +COALESCE(f.q3_te,0) + END, + -- Q4 + CASE WHEN s_q4 THEN f.q4_tc END, CASE WHEN s_q4 THEN f.q4_bc END, + CASE WHEN s_q4 THEN f.q4_bcc END, + CASE WHEN s_q4 THEN f.q4_rinc END, CASE WHEN s_q4 THEN f.q4_rseq END, + CASE WHEN s_q4 THEN f.q4_rcmt END, + CASE WHEN s_q4 THEN f.np4 END, + CASE WHEN s_q4 THEN f.q4_ac END, CASE WHEN s_q4 THEN f.q4_as END, CASE WHEN s_q4 THEN f.q4_arv END, + CASE WHEN s_q4 THEN f.q4_acmt END, + CASE WHEN s_q4 THEN f.cp4 END, + CASE WHEN s_q4 THEN f.q4_pd END, CASE WHEN s_q4 THEN f.q4_pa END, + CASE WHEN s_q4 THEN f.q4_pcmt END, CASE WHEN s_q4 THEN f.q4_pact END, + CASE WHEN s_q4 THEN f.q4_book END, + CASE WHEN s_q4 THEN f.q4_m1 END, CASE WHEN s_q4 THEN f.q4_m2 END, CASE WHEN s_q4 THEN f.q4_m3 END, + CASE WHEN s_q4 THEN f.q4_spod END, + CASE WHEN s_q4 THEN f.aq4 END, + CASE WHEN s_q4 THEN f.cp4 - COALESCE(f.q4_book,0) END, + CASE WHEN s_q4 THEN f.cp4 - f.aq4 END, + CASE WHEN s_q4 THEN f.q4_te END, + -- q4.total = q4.transfer_econ (Q4 has no other outgoing transfers) + CASE WHEN s_q4 THEN f.q4_te END, + -- totals + CASE WHEN s_tot THEN f.aq1+f.aq2+f.aq3+f.aq4 END, + CASE WHEN s_tot THEN COALESCE(f.q1_pa,0)+COALESCE(f.q2_pa,0)+COALESCE(f.q3_pa,0)+COALESCE(f.q4_pa,0) END, + -- economy_year = q4.corrected_plan − q4.actual_quarter (Excel GA5 = FY5 = FK5 − FW5) + CASE WHEN s_tot THEN f.cp4 - f.aq4 END, + + -- ─── НОВЫЕ блоки (INPUT) ─── + f.io, + CASE WHEN s_cs THEN f.cs_total END, CASE WHEN s_cs THEN f.cs_cp END, + CASE WHEN s_cs THEN f.cs_dl END, CASE WHEN s_cs THEN f.cs_cmt END, + CASE WHEN s_cs THEN f.cs_fy1 END, CASE WHEN s_cs THEN f.cs_fy2 END, + CASE WHEN s_cs THEN f.cs_oth END, + CASE WHEN s_al THEN f.al_cref END, CASE WHEN s_al THEN f.al_purp END, + CASE WHEN s_al THEN f.al_io END, CASE WHEN s_al THEN f.al_prop END, + CASE WHEN s_ca THEN f.ca_amt END, CASE WHEN s_ca THEN f.ca_proto END, + CASE WHEN s_ca THEN f.ca_note END, + CASE WHEN s_ckk THEN f.ckk_ceil END, CASE WHEN s_ckk THEN f.ckk_rf END, + CASE WHEN s_ckk THEN f.ckk_dl END, CASE WHEN s_ckk THEN f.ckk_pplan END, + CASE WHEN s_ckk THEN f.ckk_pmeth END, CASE WHEN s_ckk THEN f.ckk_cmt END, + CASE WHEN s_cd THEN f.cd_add END, CASE WHEN s_cd THEN f.cd_e1 END, + CASE WHEN s_cd THEN f.cd_e2 END, CASE WHEN s_cd THEN f.cd_e3 END, + CASE WHEN s_cd THEN f.cd_e4 END, CASE WHEN s_cd THEN f.cd_rf END, + CASE WHEN s_cd THEN f.cd_rate END, CASE WHEN s_cd THEN f.cd_fx END, + CASE WHEN s_q1 THEN f.q1_arf END, CASE WHEN s_q1 THEN f.q1_pho END, CASE WHEN s_q1 THEN f.q1_prf END, + CASE WHEN s_q2 THEN f.q2_arf END, CASE WHEN s_q2 THEN f.q2_pho END, CASE WHEN s_q2 THEN f.q2_prf END, + CASE WHEN s_q3 THEN f.q3_arf END, CASE WHEN s_q3 THEN f.q3_pho END, CASE WHEN s_q3 THEN f.q3_prf END, + CASE WHEN s_q4 THEN f.q4_arf END, CASE WHEN s_q4 THEN f.q4_pho END, CASE WHEN s_q4 THEN f.q4_prf END, + + f.tree_path || ARRAY[f.lid::INT] AS _sort + FROM final_input f + + UNION ALL + + -- ═══ Часть B: Иерархия ════════════════════════════════════════════════ + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' END::VARCHAR, + t.depth, NULL::INT, + CASE WHEN t.depth < 3 THEN t.item_id ELSE t.parent_item_id END, + CASE WHEN t.depth = 3 THEN t.item_id ELSE NULL END, + t.num_group_id, t.name, NULL::INT, NULL::VARCHAR, + -- plan + CASE WHEN s_plan THEN ta.sp1 END, CASE WHEN s_plan THEN ta.sp2 END, + CASE WHEN s_plan THEN ta.sp3 END, CASE WHEN s_plan THEN ta.sp4 END, + CASE WHEN s_plan THEN COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0) END, + NULL::VARCHAR, + -- seq dfip + CASE WHEN s_seq_d THEN ta.sd1 END, CASE WHEN s_seq_d THEN ta.sd2 END, + CASE WHEN s_seq_d THEN ta.sd3 END, CASE WHEN s_seq_d THEN ta.sd4 END, + CASE WHEN s_seq_d THEN COALESCE(ta.sd1,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sd4,0) END, + NULL::VARCHAR, + -- seq ssp_go + CASE WHEN s_seq_s THEN ta.sg1 END, CASE WHEN s_seq_s THEN ta.sg2 END, + CASE WHEN s_seq_s THEN ta.sg3 END, CASE WHEN s_seq_s THEN ta.sg4 END, + CASE WHEN s_seq_s THEN COALESCE(ta.sg1,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sg4,0) END, + NULL::VARCHAR, + -- approved + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0) + +COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0) + +COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0) + +COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END, + -- contract — нет агрегации (текстовые) + NULL::VARCHAR, NULL::VARCHAR, NULL::DATE, NULL::VARCHAR, + NULL::VARCHAR, NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + -- booking 2026 / 2027 + CASE WHEN s_book THEN ta.bk1 END, CASE WHEN s_book THEN ta.bk2 END, + CASE WHEN s_book THEN ta.bk3 END, CASE WHEN s_book THEN ta.bk4 END, + CASE WHEN s_book THEN ta.bn1 END, CASE WHEN s_book THEN ta.bn2 END, + CASE WHEN s_book THEN ta.bn3 END, CASE WHEN s_book THEN ta.bn4 END, + -- Q1 hierarchy: новые поля (pay_act, booking, residuals, transfer reasons, + -- transfer comments) на агрегатах NULL'им — их корректный rollup можно + -- добавить позже расширением aq_q. + CASE WHEN s_q1 THEN tb1.ac END, CASE WHEN s_q1 THEN tb1.as_v END, CASE WHEN s_q1 THEN tb1.arv END, + NULL::VARCHAR, + CASE WHEN s_q1 THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0) END, + NULL::DATE, + CASE WHEN s_q1 THEN tb1.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q1 THEN tb1.bk END, -- booking (rollup) + CASE WHEN s_q1 THEN tb1.m1 END, CASE WHEN s_q1 THEN tb1.m2 END, CASE WHEN s_q1 THEN tb1.m3 END, + CASE WHEN s_q1 THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) END, + -- residual_after_booking = q1.cp − Σ booking + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0)) + - COALESCE(tb1.bk,0) + END, + -- residual_after_actual = q1.cp − Σ actual + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0)) + - (COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)) + END, + CASE WHEN s_q1 THEN tb1.tq2 END, + CASE WHEN s_q1 THEN tb1.tda END, CASE WHEN s_q1 THEN tb1.tdp END, CASE WHEN s_q1 THEN tb1.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q1 THEN tb1.tq3 END, CASE WHEN s_q1 THEN tb1.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q1 THEN tb1.te END, + -- total = SUM transfer_q2_reasons + transfer_q3 + transfer_q4 + transfer_econ (Excel CC) + CASE WHEN s_q1 THEN + COALESCE(tb1.tda,0)+COALESCE(tb1.tdp,0)+COALESCE(tb1.terf,0) + +COALESCE(tb1.tq3,0)+COALESCE(tb1.tq4,0)+COALESCE(tb1.te,0) + END, + -- Q2 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, -- revision_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0) + END, + CASE WHEN s_q2 THEN tb2.ac END, CASE WHEN s_q2 THEN tb2.as_v END, CASE WHEN s_q2 THEN tb2.arv END, + NULL::VARCHAR, -- adj_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0) + END, + NULL::DATE, + CASE WHEN s_q2 THEN tb2.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q2 THEN tb2.bk END, -- booking (rollup) + CASE WHEN s_q2 THEN tb2.m1 END, CASE WHEN s_q2 THEN tb2.m2 END, CASE WHEN s_q2 THEN tb2.m3 END, + CASE WHEN s_q2 THEN COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) END, + -- residual_after_booking = q2.cp − Σ booking + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0)) + - COALESCE(tb2.bk,0) + END, + -- residual_after_actual = q2.cp − Σ actual + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0)) + - (COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)) + END, + CASE WHEN s_q2 THEN tb2.tq3 END, + CASE WHEN s_q2 THEN tb2.tda END, CASE WHEN s_q2 THEN tb2.tdp END, CASE WHEN s_q2 THEN tb2.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q2 THEN tb2.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q2 THEN tb2.te END, + -- total = SUM transfer_q3_reasons + transfer_q4 + transfer_econ (Excel DN) + CASE WHEN s_q2 THEN + COALESCE(tb2.tda,0)+COALESCE(tb2.tdp,0)+COALESCE(tb2.terf,0) + +COALESCE(tb2.tq4,0)+COALESCE(tb2.te,0) + END, + -- Q3 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0) + END, + CASE WHEN s_q3 THEN tb3.ac END, CASE WHEN s_q3 THEN tb3.as_v END, CASE WHEN s_q3 THEN tb3.arv END, + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0) + END, + NULL::DATE, + CASE WHEN s_q3 THEN tb3.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q3 THEN tb3.bk END, -- booking (rollup) + CASE WHEN s_q3 THEN tb3.m1 END, CASE WHEN s_q3 THEN tb3.m2 END, CASE WHEN s_q3 THEN tb3.m3 END, + CASE WHEN s_q3 THEN COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) END, + -- residual_after_booking = q3.cp − Σ booking + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0)) + - COALESCE(tb3.bk,0) + END, + -- residual_after_actual = q3.cp − Σ actual + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0)) + - (COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)) + END, + CASE WHEN s_q3 THEN tb3.tq4 END, + CASE WHEN s_q3 THEN tb3.tda END, CASE WHEN s_q3 THEN tb3.tdp END, CASE WHEN s_q3 THEN tb3.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q3 THEN tb3.te END, + -- total = SUM transfer_q4_reasons + transfer_econ (Excel EW) + CASE WHEN s_q3 THEN + COALESCE(tb3.tda,0)+COALESCE(tb3.tdp,0)+COALESCE(tb3.terf,0) + +COALESCE(tb3.te,0) + END, + -- Q4 hierarchy + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0) + END, + CASE WHEN s_q4 THEN tb4.ac END, CASE WHEN s_q4 THEN tb4.as_v END, CASE WHEN s_q4 THEN tb4.arv END, + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0) + END, + NULL::DATE, + CASE WHEN s_q4 THEN tb4.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q4 THEN tb4.bk END, -- booking (rollup) + CASE WHEN s_q4 THEN tb4.m1 END, CASE WHEN s_q4 THEN tb4.m2 END, CASE WHEN s_q4 THEN tb4.m3 END, + CASE WHEN s_q4 THEN tb4.spod END, + CASE WHEN s_q4 THEN COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, + -- residual_after_booking = q4.cp − Σ booking + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - COALESCE(tb4.bk,0) + END, + -- residual_after_actual = q4.cp − Σ actual (incl. spod) + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)) + END, + CASE WHEN s_q4 THEN tb4.te END, + -- q4.total = q4.transfer_econ (Q4 has no other outgoing transfers) + CASE WHEN s_q4 THEN tb4.te END, + -- totals + CASE WHEN s_tot THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) + +COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) + +COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) + +COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, + CASE WHEN s_tot THEN COALESCE(tb1.pa,0)+COALESCE(tb2.pa,0)+COALESCE(tb3.pa,0)+COALESCE(tb4.pa,0) END, + -- economy_year = q4.corrected_plan − q4.actual_quarter (Excel GA = FY = FK − FW) + CASE WHEN s_tot THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) + +COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0) + +COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)) + END, + + -- ─── НОВЫЕ блоки (иерархия) — per-line детали не агрегируются → NULL ─── + NULL::VARCHAR, -- internal_order + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- contract_summary + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- allocation + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, -- collegial + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- ckk + NULL::VARCHAR, NULL::VARCHAR, + NULL::VARCHAR, NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- contract_detail extras + NULL::NUMERIC, NULL::VARCHAR, NULL::NUMERIC, NULL::NUMERIC, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q1 adj_rf/pho/prf + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q2 + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q3 + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q4 + + t.path AS _sort + FROM tw t + LEFT JOIN tw_agg ta ON ta.id = t.id + LEFT JOIN tw_aq1 tb1 ON tb1.id = t.id + LEFT JOIN tw_aq2 tb2 ON tb2.id = t.id + LEFT JOIN tw_aq3 tb3 ON tb3.id = t.id + LEFT JOIN tw_aq4 tb4 ON tb4.id = t.id + ) sub + ORDER BY sub._sort; +END; +$function$ +LANGUAGE plpgsql STABLE; + +-- ═══ 2/3 ═══ v3.v_form2_sheet_jsonb ═══════════════════════════════════════ +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form2_sheet_jsonb — JSONB-чанки (thin wrapper над v3.v_form2_sheet_sections) +-- ════════════════════════════════════════════════════════════════════════════ +-- Вся логика в v3.v_form2_sheet_sections. Здесь только переупаковка +-- типизированных колонок в JSONB-чанки. NULL чанк = секция не запрошена. +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form2_sheet_jsonb(INT,VARCHAR,TEXT[]); + +CREATE FUNCTION v3.v_form2_sheet_jsonb( + p_form_id INT, + p_sheet VARCHAR, + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + line_id INT, + header JSONB, + plan_data JSONB, + seq_dfip_data JSONB, + seq_ssp_data JSONB, + approved_data JSONB, + contract_data JSONB, + booking_data JSONB, + contract_summary JSONB, + allocation_data JSONB, + collegial_data JSONB, + ckk_data JSONB, + q1_data JSONB, + q2_data JSONB, + q3_data JSONB, + q4_data JSONB, + totals_data JSONB, + _sort_path INT[] +) +AS $function$ + WITH flags AS ( + SELECT + (p_sections IS NULL OR 'plan' = ANY(p_sections)) AS s_plan, + (p_sections IS NULL OR 'seq_dfip' = ANY(p_sections)) AS s_seq_d, + (p_sections IS NULL OR 'seq_ssp' = ANY(p_sections)) AS s_seq_s, + (p_sections IS NULL OR 'approved' = ANY(p_sections)) AS s_appr, + (p_sections IS NULL OR 'contract' = ANY(p_sections)) AS s_cd, + (p_sections IS NULL OR 'booking' = ANY(p_sections)) AS s_book, + (p_sections IS NULL OR 'contract_summary' = ANY(p_sections)) AS s_cs, + (p_sections IS NULL OR 'allocation' = ANY(p_sections)) AS s_al, + (p_sections IS NULL OR 'collegial' = ANY(p_sections)) AS s_ca, + (p_sections IS NULL OR 'ckk' = ANY(p_sections)) AS s_ckk, + (p_sections IS NULL OR 'q1' = ANY(p_sections)) AS s_q1, + (p_sections IS NULL OR 'q2' = ANY(p_sections)) AS s_q2, + (p_sections IS NULL OR 'q3' = ANY(p_sections)) AS s_q3, + (p_sections IS NULL OR 'q4' = ANY(p_sections)) AS s_q4, + (p_sections IS NULL OR 'totals' = ANY(p_sections)) AS s_tot + ) + SELECT + s.row_type, s.depth, s.line_id, + + -- header (всегда). expense_item_id из _sort_path (см. v_form1_jsonb). + jsonb_build_object( + 'section_code', s.col_section_code, + 'item_id', s.col_item_id, + 'num_group', s.col_num_group_id, + 'name', s.col_name, + 'vsp_id', s.col_vsp_id, + 'vsp_address', s.col_vsp_address, + 'internal_order', s.col_internal_order, + 'expense_item_id', CASE WHEN s.row_type = 'INPUT' + THEN s._sort_path[array_upper(s._sort_path,1)-1] + ELSE s._sort_path[array_upper(s._sort_path,1)] END + ) AS header, + + CASE WHEN f.s_plan THEN jsonb_build_object( + 'q1', s.col_plan_q1, 'q2', s.col_plan_q2, + 'q3', s.col_plan_q3, 'q4', s.col_plan_q4, + 'year', s.col_plan_year, 'comment', s.col_plan_comment + ) END AS plan_data, + + CASE WHEN f.s_seq_d THEN jsonb_build_object( + 'q1', s.col_seq_dfip_q1, 'q2', s.col_seq_dfip_q2, + 'q3', s.col_seq_dfip_q3, 'q4', s.col_seq_dfip_q4, + 'year', s.col_seq_dfip_year, + 'justification', s.col_seq_dfip_just + ) END AS seq_dfip_data, + + CASE WHEN f.s_seq_s THEN jsonb_build_object( + 'q1', s.col_seq_ssp_q1, 'q2', s.col_seq_ssp_q2, + 'q3', s.col_seq_ssp_q3, 'q4', s.col_seq_ssp_q4, + 'year', s.col_seq_ssp_year, + 'justification', s.col_seq_ssp_just + ) END AS seq_ssp_data, + + CASE WHEN f.s_appr THEN jsonb_build_object( + 'q1', s.col_appr_q1, 'q2', s.col_appr_q2, + 'q3', s.col_appr_q3, 'q4', s.col_appr_q4, + 'year', s.col_appr_year + ) END AS approved_data, + + CASE WHEN f.s_cd THEN jsonb_build_object( + 'counterparty', s.col_cd_counterparty, + 'reference', s.col_cd_reference, + 'addenda', s.col_cd_addenda, + 'date', s.col_cd_date, + 'subject', s.col_cd_subject, + 'currency', s.col_cd_currency, + 'ceiling', s.col_cd_ceiling, + 'q1', s.col_cd_exp_q1, + 'q2', s.col_cd_exp_q2, + 'q3', s.col_cd_exp_q3, + 'q4', s.col_cd_exp_q4, + 'rf_schedule', s.col_cd_rf_sched, + 'vat_rate', s.col_cd_vat_rate, + 'exchange_rate', s.col_cd_exchange_rate, + 'amount_foreign', s.col_cd_amount_foreign, + 'deadline', s.col_cd_deadline, + 'scheme', s.col_cd_scheme, + 'act', s.col_cd_act, + 'comment', s.col_cd_comment + ) END AS contract_data, + + CASE WHEN f.s_book THEN jsonb_build_object( + 'y2026', jsonb_build_object('q1', s.col_book_q1, 'q2', s.col_book_q2, 'q3', s.col_book_q3, 'q4', s.col_book_q4), + 'y2027', jsonb_build_object('q1', s.col_book_next_q1, 'q2', s.col_book_next_q2, 'q3', s.col_book_next_q3, 'q4', s.col_book_next_q4) + ) END AS booking_data, + + CASE WHEN f.s_cs THEN jsonb_build_object( + 'total', s.col_cs_total, + 'counterparty', s.col_cs_counterparty, + 'deadline', s.col_cs_deadline, + 'comment', s.col_cs_comment, + 'future_y1', s.col_cs_future_y1, + 'future_y2', s.col_cs_future_y2, + 'other_ssp', s.col_cs_other_ssp + ) END AS contract_summary, + + CASE WHEN f.s_al THEN jsonb_build_object( + 'contract_ref', s.col_al_contract_ref, + 'purpose', s.col_al_purpose, + 'order', s.col_al_order, + 'property', s.col_al_property + ) END AS allocation_data, + + CASE WHEN f.s_ca THEN jsonb_build_object( + 'approved', s.col_ca_approved, + 'protocol', s.col_ca_protocol, + 'note', s.col_ca_note + ) END AS collegial_data, + + -- ckk: поквартальные expenses уже отдаются в booking.y2026 (не дублируем); + -- здесь только поля, которых нет в других блоках. + CASE WHEN f.s_ckk THEN jsonb_build_object( + 'ceiling', s.col_ckk_ceiling, + 'rf_schedule', s.col_ckk_rf_sched, + 'deadline', s.col_ckk_deadline, + 'proc_plan', s.col_ckk_proc_plan, + 'proc_method', s.col_ckk_proc_meth, + 'comment', s.col_ckk_comment + ) END AS ckk_data, + + CASE WHEN f.s_q1 THEN jsonb_build_object( + 'adj_current', s.col_q1_adj_current, + 'adj_ssp', s.col_q1_adj_ssp, + 'adj_rf', s.col_q1_adj_rf, + 'adj_reserve', s.col_q1_adj_reserve, + 'adj_comment', s.col_q1_adj_comment, + 'corrected_plan', s.col_q1_corrected_plan, + 'pay_date', s.col_q1_pay_date, + 'pay_amount', s.col_q1_pay_amount, + 'pay_ho', s.col_q1_pay_ho, + 'pay_rf', s.col_q1_pay_rf, + 'pay_comment', s.col_q1_pay_comment, + 'pay_act', s.col_q1_pay_act, + 'booking', s.col_q1_booking, + 'actual_m1', s.col_q1_actual_m1, + 'actual_m2', s.col_q1_actual_m2, + 'actual_m3', s.col_q1_actual_m3, + 'actual_quarter', s.col_q1_actual_quarter, + 'residual_after_booking', s.col_q1_residual_after_booking, + 'residual_after_actual', s.col_q1_residual_after_actual, + 'transfer_q2', s.col_q1_transfer_q2, + 'transfer_q2_delay_acts', s.col_q1_transfer_q2_delay_acts, + 'transfer_q2_delay_procurement', s.col_q1_transfer_q2_delay_procurement, + 'transfer_q2_economy_rf', s.col_q1_transfer_q2_economy_rf, + 'transfer_next_comment', s.col_q1_transfer_next_comment, + 'transfer_q3', s.col_q1_transfer_q3, + 'transfer_q4', s.col_q1_transfer_q4, + 'transfer_far_comment', s.col_q1_transfer_far_comment, + 'transfer_econ', s.col_q1_transfer_econ, + 'total', s.col_q1_total + ) END AS q1_data, + + CASE WHEN f.s_q2 THEN jsonb_build_object( + 'target_change', s.col_q2_target_change, + 'base_correction', s.col_q2_base_correction, + 'base_correction_comment', s.col_q2_base_correction_comment, + 'revision_inc', s.col_q2_revision_inc, + 'revision_seq', s.col_q2_revision_seq, + 'revision_comment', s.col_q2_revision_comment, + 'new_plan', s.col_q2_new_plan, + 'adj_current', s.col_q2_adj_current, + 'adj_ssp', s.col_q2_adj_ssp, + 'adj_rf', s.col_q2_adj_rf, + 'adj_reserve', s.col_q2_adj_reserve, + 'adj_comment', s.col_q2_adj_comment, + 'corrected_plan', s.col_q2_corrected_plan, + 'pay_date', s.col_q2_pay_date, + 'pay_amount', s.col_q2_pay_amount, + 'pay_ho', s.col_q2_pay_ho, + 'pay_rf', s.col_q2_pay_rf, + 'pay_comment', s.col_q2_pay_comment, + 'pay_act', s.col_q2_pay_act, + 'booking', s.col_q2_booking, + 'actual_m1', s.col_q2_actual_m1, + 'actual_m2', s.col_q2_actual_m2, + 'actual_m3', s.col_q2_actual_m3, + 'actual_quarter', s.col_q2_actual_quarter, + 'residual_after_booking', s.col_q2_residual_after_booking, + 'residual_after_actual', s.col_q2_residual_after_actual, + 'transfer_q3', s.col_q2_transfer_q3, + 'transfer_q3_delay_acts', s.col_q2_transfer_q3_delay_acts, + 'transfer_q3_delay_procurement', s.col_q2_transfer_q3_delay_procurement, + 'transfer_q3_economy_rf', s.col_q2_transfer_q3_economy_rf, + 'transfer_next_comment', s.col_q2_transfer_next_comment, + 'transfer_q4', s.col_q2_transfer_q4, + 'transfer_far_comment', s.col_q2_transfer_far_comment, + 'transfer_econ', s.col_q2_transfer_econ, + 'total', s.col_q2_total + ) END AS q2_data, + + CASE WHEN f.s_q3 THEN jsonb_build_object( + 'target_change', s.col_q3_target_change, + 'base_correction', s.col_q3_base_correction, + 'base_correction_comment', s.col_q3_base_correction_comment, + 'revision_inc', s.col_q3_revision_inc, + 'revision_seq', s.col_q3_revision_seq, + 'revision_comment', s.col_q3_revision_comment, + 'new_plan', s.col_q3_new_plan, + 'adj_current', s.col_q3_adj_current, + 'adj_ssp', s.col_q3_adj_ssp, + 'adj_rf', s.col_q3_adj_rf, + 'adj_reserve', s.col_q3_adj_reserve, + 'adj_comment', s.col_q3_adj_comment, + 'corrected_plan', s.col_q3_corrected_plan, + 'pay_date', s.col_q3_pay_date, + 'pay_amount', s.col_q3_pay_amount, + 'pay_ho', s.col_q3_pay_ho, + 'pay_rf', s.col_q3_pay_rf, + 'pay_comment', s.col_q3_pay_comment, + 'pay_act', s.col_q3_pay_act, + 'booking', s.col_q3_booking, + 'actual_m1', s.col_q3_actual_m1, + 'actual_m2', s.col_q3_actual_m2, + 'actual_m3', s.col_q3_actual_m3, + 'actual_quarter', s.col_q3_actual_quarter, + 'residual_after_booking', s.col_q3_residual_after_booking, + 'residual_after_actual', s.col_q3_residual_after_actual, + 'transfer_q4', s.col_q3_transfer_q4, + 'transfer_q4_delay_acts', s.col_q3_transfer_q4_delay_acts, + 'transfer_q4_delay_procurement', s.col_q3_transfer_q4_delay_procurement, + 'transfer_q4_economy_rf', s.col_q3_transfer_q4_economy_rf, + 'transfer_next_comment', s.col_q3_transfer_next_comment, + 'transfer_econ', s.col_q3_transfer_econ, + 'total', s.col_q3_total + ) END AS q3_data, + + CASE WHEN f.s_q4 THEN jsonb_build_object( + 'target_change', s.col_q4_target_change, + 'base_correction', s.col_q4_base_correction, + 'base_correction_comment', s.col_q4_base_correction_comment, + 'revision_inc', s.col_q4_revision_inc, + 'revision_seq', s.col_q4_revision_seq, + 'revision_comment', s.col_q4_revision_comment, + 'new_plan', s.col_q4_new_plan, + 'adj_current', s.col_q4_adj_current, + 'adj_ssp', s.col_q4_adj_ssp, + 'adj_rf', s.col_q4_adj_rf, + 'adj_reserve', s.col_q4_adj_reserve, + 'adj_comment', s.col_q4_adj_comment, + 'corrected_plan', s.col_q4_corrected_plan, + 'pay_date', s.col_q4_pay_date, + 'pay_amount', s.col_q4_pay_amount, + 'pay_ho', s.col_q4_pay_ho, + 'pay_rf', s.col_q4_pay_rf, + 'pay_comment', s.col_q4_pay_comment, + 'pay_act', s.col_q4_pay_act, + 'booking', s.col_q4_booking, + 'actual_m1', s.col_q4_actual_m1, + 'actual_m2', s.col_q4_actual_m2, + 'actual_m3', s.col_q4_actual_m3, + 'actual_spod', s.col_q4_actual_spod, + 'actual_quarter', s.col_q4_actual_quarter, + 'residual_after_booking', s.col_q4_residual_after_booking, + 'residual_after_actual', s.col_q4_residual_after_actual, + 'transfer_econ', s.col_q4_transfer_econ, + 'total', s.col_q4_total + ) END AS q4_data, + + CASE WHEN f.s_tot THEN jsonb_build_object( + 'fact_year', s.col_fact_year, + 'pay_year', s.col_pay_year, + 'economy_year', s.col_economy_year + ) END AS totals_data, + + s._sort_path + FROM v3.v_form2_sheet_sections(p_form_id, p_sheet, p_sections) s + CROSS JOIN flags f + ORDER BY s._sort_path; +$function$ +LANGUAGE sql STABLE; + +-- ═══ 3/3 ═══ v3.v_form2_view ══════════════════════════════════════════════ +-- ════════════════════════════════════════════════════════════════════════════ +-- v3.v_form2_view — единая точка входа для всех листов Формы 2 +-- ════════════════════════════════════════════════════════════════════════════ +-- Возвращает унифицированный формат: (row_type, depth, sort_order, data). +-- p_sheet определяет источник: +-- +-- 'AHR' / 'CAP' / 'OPER' → основные листы (через v_form2_sheet_jsonb) +-- 'AHR_LIMIT' → лист АХР_лимит (иерархия SECTION/GROUP/LEAF) +-- 'AHR_RENT' → АХР_аренда (rent_detail × vsp) +-- 'AHR_UTILITY' → АХР_коммунал (utility_detail × vsp) +-- 'AHR_SECURITY' → АХР_охрана (security_detail × vsp) +-- 'OTCH9F' → Отч.9ф (fixed_asset_report, 12 месяцев) +-- 'SMETA' → Смета_2026 (агрегационный, через v_form2_smeta) +-- +-- p_sections — массив включаемых блоков (зависит от листа). NULL = все. +-- ════════════════════════════════════════════════════════════════════════════ + +DROP FUNCTION IF EXISTS v3.v_form2_view(INT, VARCHAR, TEXT[]); + +CREATE FUNCTION v3.v_form2_view( + p_form_id INT, + p_sheet VARCHAR, + p_sections TEXT[] DEFAULT NULL +) +RETURNS TABLE ( + row_type VARCHAR, + depth INT, + sort_order BIGINT, + data JSONB +) +AS $function$ +BEGIN + + -- ═══ Основные листы АХР / КВ_П / Операц ════════════════════════════════ + IF p_sheet IN ('AHR','CAP','OPER') THEN + RETURN QUERY + SELECT + j.row_type, + j.depth, + ROW_NUMBER() OVER (ORDER BY j._sort_path) AS sort_order, + -- Лист АХР (форма 2) имеет более простую раскладку: у него нет колонок под + -- internal_order / contract_summary / allocation / collegial / ckk / + -- contract.{addenda,q1-q4,rf_schedule,exchange_rate,amount_foreign} / + -- q*.{adj_rf,pay_ho,pay_rf}. Функция чтения — единая (надмножество для + -- AHR/CAP/OPER), поэтому здесь для AHR срезаем поля, которых нет в шаблоне, + -- а новые верхнеуровневые блоки добавляем только для CAP/OPER. + (jsonb_build_object( + 'line_id', j.line_id, + 'header', CASE WHEN p_sheet = 'AHR' THEN j.header - 'internal_order' ELSE j.header END, + 'plan', j.plan_data, + 'seq_dfip', j.seq_dfip_data, + 'seq_ssp', j.seq_ssp_data, + 'approved', j.approved_data, + 'contract', CASE WHEN p_sheet = 'AHR' + THEN j.contract_data - 'addenda' - 'q1' - 'q2' - 'q3' - 'q4' + - 'rf_schedule' - 'exchange_rate' - 'amount_foreign' + ELSE j.contract_data END, + 'booking', j.booking_data, + 'q1', CASE WHEN p_sheet = 'AHR' THEN j.q1_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q1_data END, + 'q2', CASE WHEN p_sheet = 'AHR' THEN j.q2_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q2_data END, + 'q3', CASE WHEN p_sheet = 'AHR' THEN j.q3_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q3_data END, + 'q4', CASE WHEN p_sheet = 'AHR' THEN j.q4_data - 'adj_rf' - 'pay_ho' - 'pay_rf' ELSE j.q4_data END, + 'totals', j.totals_data + ) + || CASE WHEN p_sheet <> 'AHR' + THEN jsonb_build_object( + 'contract_summary', j.contract_summary, + 'allocation', j.allocation_data, + 'collegial', j.collegial_data, + 'ckk', j.ckk_data) + ELSE '{}'::jsonb END + ) AS data + FROM v3.v_form2_sheet_jsonb(p_form_id, p_sheet, p_sections) j + ORDER BY j._sort_path; + RETURN; + END IF; + + -- ═══ АХР_лимит ═════════════════════════════════════════════════════════ + -- SECTION/GROUP-строки агрегируют qty/amount по всем LEAF-потомкам + -- (Excel: J6=SUM(J7:J13) и т.д.). Дерево строится через parent_id. + -- + -- Структура (limit_template) — глобальный каталог нормативов, единый на банк. + -- Значения qty/comment — per-form (form_limit ON budget_form_id = p_form_id). + -- amount_qN — computed по формуле: + -- amount_qN = qty_qN * limit_with_vat * period_factor(unit), где + -- period_factor = 1/4 если unit ~ '/год' + -- 3 если unit ~ '/месяц' (3 месяца в квартале) + -- 1 иначе (по умолчанию /квартал) + IF p_sheet = 'AHR_LIMIT' THEN + RETURN QUERY + WITH RECURSIVE tree AS ( + SELECT id AS root_id, id AS desc_id FROM v3.limit_template + UNION + SELECT t.root_id, child.id + FROM tree t + JOIN v3.limit_template child ON child.parent_id = t.desc_id + ), + leaf_amt AS ( + -- per-LEAF computed amount по формуле; qty берётся из form_limit для p_form_id + SELECT lt.id, + fl.qty_q1, fl.qty_q2, fl.qty_q3, fl.qty_q4, + COALESCE(fl.qty_q1,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q1, + COALESCE(fl.qty_q2,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q2, + COALESCE(fl.qty_q3,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q3, + COALESCE(fl.qty_q4,0) * COALESCE(lt.limit_with_vat,0) * + CASE WHEN lt.unit LIKE '%/год' THEN 0.25 + WHEN lt.unit LIKE '%/месяц' THEN 3 + ELSE 1 END AS amount_q4 + FROM v3.limit_template lt + LEFT JOIN v3.form_limit fl + ON fl.template_id = lt.id AND fl.budget_form_id = p_form_id + WHERE lt.row_type = 'LEAF' + ), + agg AS ( + SELECT t.root_id, + SUM(la.qty_q1) AS qty_q1, SUM(la.amount_q1) AS amount_q1, + SUM(la.qty_q2) AS qty_q2, SUM(la.amount_q2) AS amount_q2, + SUM(la.qty_q3) AS qty_q3, SUM(la.amount_q3) AS amount_q3, + SUM(la.qty_q4) AS qty_q4, SUM(la.amount_q4) AS amount_q4 + FROM tree t + JOIN leaf_amt la ON la.id = t.desc_id + GROUP BY t.root_id + ) + SELECT + -- Маппинг физического хранения limit_template → DTO: + -- SECTION → ROOT, GROUP → GROUP, LEAF → INPUT (редактируемая ячейка). + CASE lt.row_type WHEN 'SECTION' THEN 'ROOT' + WHEN 'GROUP' THEN 'GROUP' + WHEN 'LEAF' THEN 'INPUT' END::VARCHAR, + CASE lt.row_type WHEN 'SECTION' THEN 0 WHEN 'GROUP' THEN 1 ELSE 2 END, + lt.sort_order::BIGINT, + jsonb_build_object( + 'id', lt.id, -- LEAF id для адресации в write API (= template_id) + 'section_no', lt.section_no, + 'expense_item_code',lt.expense_item_code, + 'name', lt.name, + 'unit', lt.unit, + 'limit_with_vat', lt.limit_with_vat, + 'limit_without_vat',lt.limit_without_vat, + 'qty_q1', a.qty_q1, 'amount_q1', a.amount_q1, + 'qty_q2', a.qty_q2, 'amount_q2', a.amount_q2, + 'qty_q3', a.qty_q3, 'amount_q3', a.amount_q3, + 'qty_q4', a.qty_q4, 'amount_q4', a.amount_q4, + 'comment', fl.comment + ) + FROM v3.limit_template lt + LEFT JOIN agg a ON a.root_id = lt.id + LEFT JOIN v3.form_limit fl + ON fl.template_id = lt.id AND fl.budget_form_id = p_form_id + ORDER BY lt.sort_order; + RETURN; + END IF; + + -- ═══ АХР_аренда ════════════════════════════════════════════════════════ + IF p_sheet = 'AHR_RENT' THEN + RETURN QUERY + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY rd.id) AS sort_order, + jsonb_build_object( + 'id', rd.id, -- id rent_detail (для write API) + 'line_id', rd.line_id, + 'vsp_id', rd.vsp_id, + 'address', v.address, + 'object_type', v.format, + 'rented_area', v.total_area, + 'contract_number', rd.contract_number, + 'contract_end_date',rd.contract_end_date, + 'plan', jsonb_build_object('q1', rd.plan_q1, 'q2', rd.plan_q2, 'q3', rd.plan_q3, 'q4', rd.plan_q4, + 'year', COALESCE(rd.plan_q1,0)+COALESCE(rd.plan_q2,0)+COALESCE(rd.plan_q3,0)+COALESCE(rd.plan_q4,0)), + 'fact_q1', jsonb_build_object('jan', rd.actual_jan, 'feb', rd.actual_feb, 'mar', rd.actual_mar, + 'total', COALESCE(rd.actual_jan,0)+COALESCE(rd.actual_feb,0)+COALESCE(rd.actual_mar,0)), + 'fact_q2', jsonb_build_object('apr', rd.actual_apr, 'may', rd.actual_may, 'jun', rd.actual_jun, + 'total', COALESCE(rd.actual_apr,0)+COALESCE(rd.actual_may,0)+COALESCE(rd.actual_jun,0)), + 'fact_q3', jsonb_build_object('jul', rd.actual_jul, 'aug', rd.actual_aug, 'sep', rd.actual_sep, + 'total', COALESCE(rd.actual_jul,0)+COALESCE(rd.actual_aug,0)+COALESCE(rd.actual_sep,0)), + 'fact_q4', jsonb_build_object('oct', rd.actual_oct, 'nov', rd.actual_nov, 'dec', rd.actual_dec, + 'total', COALESCE(rd.actual_oct,0)+COALESCE(rd.actual_nov,0)+COALESCE(rd.actual_dec,0)), + 'comment', rd.comment + ) + FROM v3.rent_detail rd + JOIN v3.budget_line bl ON bl.id = rd.line_id + JOIN v3.vsp v ON v.id = rd.vsp_id + WHERE bl.budget_form_id = p_form_id + ORDER BY rd.id; + RETURN; + END IF; + + -- ═══ АХР_коммунал ═══════════════════════════════════════════════════════ + IF p_sheet = 'AHR_UTILITY' THEN + RETURN QUERY + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY ud.id) AS sort_order, + jsonb_build_object( + 'id', ud.id, -- id utility_detail (для write API) + 'line_id', ud.line_id, + 'vsp_id', ud.vsp_id, + 'address', v.address, + 'object_type', v.format, + 'object_area', v.total_area, + 'contract_number', ud.contract_number, + 'contract_end_date',ud.contract_end_date, + 'plan', jsonb_build_object('q1', ud.plan_q1, 'q2', ud.plan_q2, 'q3', ud.plan_q3, 'q4', ud.plan_q4, + 'year', COALESCE(ud.plan_q1,0)+COALESCE(ud.plan_q2,0)+COALESCE(ud.plan_q3,0)+COALESCE(ud.plan_q4,0)), + 'fact_q1', jsonb_build_object('jan', ud.actual_jan, 'feb', ud.actual_feb, 'mar', ud.actual_mar, + 'total', COALESCE(ud.actual_jan,0)+COALESCE(ud.actual_feb,0)+COALESCE(ud.actual_mar,0)), + 'fact_q2', jsonb_build_object('apr', ud.actual_apr, 'may', ud.actual_may, 'jun', ud.actual_jun, + 'total', COALESCE(ud.actual_apr,0)+COALESCE(ud.actual_may,0)+COALESCE(ud.actual_jun,0)), + 'fact_q3', jsonb_build_object('jul', ud.actual_jul, 'aug', ud.actual_aug, 'sep', ud.actual_sep, + 'total', COALESCE(ud.actual_jul,0)+COALESCE(ud.actual_aug,0)+COALESCE(ud.actual_sep,0)), + 'fact_q4', jsonb_build_object('oct', ud.actual_oct, 'nov', ud.actual_nov, 'dec', ud.actual_dec, + 'total', COALESCE(ud.actual_oct,0)+COALESCE(ud.actual_nov,0)+COALESCE(ud.actual_dec,0)), + 'comment', ud.comment + ) + FROM v3.utility_detail ud + JOIN v3.budget_line bl ON bl.id = ud.line_id + JOIN v3.vsp v ON v.id = ud.vsp_id + WHERE bl.budget_form_id = p_form_id + ORDER BY ud.id; + RETURN; + END IF; + + -- ═══ АХР_охрана ═════════════════════════════════════════════════════════ + IF p_sheet = 'AHR_SECURITY' THEN + RETURN QUERY + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY sd.id) AS sort_order, + jsonb_build_object( + 'id', sd.id, -- id security_detail (для write API) + 'line_id', sd.line_id, + 'vsp_id', sd.vsp_id, + 'address', v.address, + 'object_type', v.format, + 'object_area', v.total_area, + 'contract_number', sd.contract_number, + 'contract_end_date',sd.contract_end_date, + 'plan', jsonb_build_object('q1', sd.plan_q1, 'q2', sd.plan_q2, 'q3', sd.plan_q3, 'q4', sd.plan_q4, + 'year', COALESCE(sd.plan_q1,0)+COALESCE(sd.plan_q2,0)+COALESCE(sd.plan_q3,0)+COALESCE(sd.plan_q4,0)), + 'fact_q1', jsonb_build_object('jan', sd.actual_jan, 'feb', sd.actual_feb, 'mar', sd.actual_mar, + 'total', COALESCE(sd.actual_jan,0)+COALESCE(sd.actual_feb,0)+COALESCE(sd.actual_mar,0)), + 'fact_q2', jsonb_build_object('apr', sd.actual_apr, 'may', sd.actual_may, 'jun', sd.actual_jun, + 'total', COALESCE(sd.actual_apr,0)+COALESCE(sd.actual_may,0)+COALESCE(sd.actual_jun,0)), + 'fact_q3', jsonb_build_object('jul', sd.actual_jul, 'aug', sd.actual_aug, 'sep', sd.actual_sep, + 'total', COALESCE(sd.actual_jul,0)+COALESCE(sd.actual_aug,0)+COALESCE(sd.actual_sep,0)), + 'fact_q4', jsonb_build_object('oct', sd.actual_oct, 'nov', sd.actual_nov, 'dec', sd.actual_dec, + 'total', COALESCE(sd.actual_oct,0)+COALESCE(sd.actual_nov,0)+COALESCE(sd.actual_dec,0)), + 'comment', sd.comment + ) + FROM v3.security_detail sd + JOIN v3.budget_line bl ON bl.id = sd.line_id + JOIN v3.vsp v ON v.id = sd.vsp_id + WHERE bl.budget_form_id = p_form_id + ORDER BY sd.id; + RETURN; + END IF; + + -- ═══ Отч.9ф (Fixed Asset Report) ════════════════════════════════════════ + -- Одна строка = (expense_item × equipment_name × month). + -- + -- opening_qty/amt_604/60415 хранятся в БД ТОЛЬКО для month=1 (база года). + -- Для month=2..12 вычисляются на лету: накопительная сумма дельт по + -- предыдущим месяцам от base (Excel: opening_M2 = F1+H1−J1). + -- + -- totals.* — также computed (acquired_total, disposed_total, balance_qty/amt). + IF p_sheet = 'OTCH9F' THEN + RETURN QUERY + WITH base AS ( + SELECT far.id, far.budget_form_id, far.expense_item_id, + far.equipment_name, far.month, + far.acquired_qty_604, far.acquired_amt_604, + far.disposed_qty_604, far.disposed_amt_604, + far.acquired_qty_60415, far.acquired_amt_60415, + far.transferred_qty_60415, far.transferred_amt_60415, + far.go_balance_only_amt, + -- база: opening_qty/amt из month=1 этой группы + FIRST_VALUE(COALESCE(far.opening_qty_604,0)) + OVER w AS base_qty_604, + FIRST_VALUE(COALESCE(far.opening_amt_604,0)) + OVER w AS base_amt_604, + FIRST_VALUE(COALESCE(far.opening_qty_60415,0)) + OVER w AS base_qty_60415, + FIRST_VALUE(COALESCE(far.opening_amt_60415,0)) + OVER w AS base_amt_60415, + -- кумулятивная дельта по предыдущим месяцам в группе + COALESCE(SUM(COALESCE(far.acquired_qty_604,0) - COALESCE(far.disposed_qty_604,0)) + OVER w_prev, 0) AS cum_dq_604, + COALESCE(SUM(COALESCE(far.acquired_amt_604,0) - COALESCE(far.disposed_amt_604,0)) + OVER w_prev, 0) AS cum_da_604, + COALESCE(SUM(COALESCE(far.acquired_qty_60415,0) - COALESCE(far.transferred_qty_60415,0)) + OVER w_prev, 0) AS cum_dq_60415, + COALESCE(SUM(COALESCE(far.acquired_amt_60415,0) - COALESCE(far.transferred_amt_60415,0)) + OVER w_prev, 0) AS cum_da_60415 + FROM v3.fixed_asset_report far + WHERE far.budget_form_id = p_form_id + WINDOW w AS (PARTITION BY far.budget_form_id, far.expense_item_id, far.equipment_name + ORDER BY far.month), + w_prev AS (PARTITION BY far.budget_form_id, far.expense_item_id, far.equipment_name + ORDER BY far.month + ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) + ), + eff AS ( + SELECT b.*, + (b.base_qty_604 + b.cum_dq_604) AS opening_qty_604_eff, + (b.base_amt_604 + b.cum_da_604) AS opening_amt_604_eff, + (b.base_qty_60415 + b.cum_dq_60415) AS opening_qty_60415_eff, + (b.base_amt_60415 + b.cum_da_60415) AS opening_amt_60415_eff + FROM base b + ) + SELECT + 'INPUT'::VARCHAR, + 0, + ROW_NUMBER() OVER (ORDER BY eff.expense_item_id, eff.month) AS sort_order, + jsonb_build_object( + 'id', eff.id, -- fixed_asset_report.id (для write API) + 'expense_item_id', ei.id, + 'item_id', ei.item_id, + 'section_code', ei.section_code, + 'equipment_name', eff.equipment_name, + 'month', eff.month, + 'b_604', jsonb_build_object( + 'opening_qty', eff.opening_qty_604_eff, + 'opening_amt', eff.opening_amt_604_eff, + 'acquired_qty', eff.acquired_qty_604, + 'acquired_amt', eff.acquired_amt_604, + 'disposed_qty', eff.disposed_qty_604, + 'disposed_amt', eff.disposed_amt_604 + ), + 'b_60415', jsonb_build_object( + 'opening_qty', eff.opening_qty_60415_eff, + 'opening_amt', eff.opening_amt_60415_eff, + 'acquired_qty', eff.acquired_qty_60415, + 'acquired_amt', eff.acquired_amt_60415, + 'transferred_qty',eff.transferred_qty_60415, + 'transferred_amt',eff.transferred_amt_60415 + ), + 'totals', jsonb_build_object( + -- Excel R = SUM(I, O, S) − Q + 'acquired_total', COALESCE(eff.acquired_amt_604,0)+COALESCE(eff.acquired_amt_60415,0) + +COALESCE(eff.go_balance_only_amt,0)-COALESCE(eff.transferred_amt_60415,0), + 'go_balance_only',eff.go_balance_only_amt, + -- Excel T = K + 'disposed_total', COALESCE(eff.disposed_amt_604,0), + -- Excel U = (opening+acquired−disposed)_604 + (opening+acquired−transferred)_60415 + 'balance_qty', (eff.opening_qty_604_eff + COALESCE(eff.acquired_qty_604,0) - COALESCE(eff.disposed_qty_604,0)) + + (eff.opening_qty_60415_eff + COALESCE(eff.acquired_qty_60415,0) - COALESCE(eff.transferred_qty_60415,0)), + 'balance_amt', (eff.opening_amt_604_eff + COALESCE(eff.acquired_amt_604,0) - COALESCE(eff.disposed_amt_604,0)) + + (eff.opening_amt_60415_eff + COALESCE(eff.acquired_amt_60415,0) - COALESCE(eff.transferred_amt_60415,0)) + ) + ) + FROM eff + JOIN v3.expense_item ei ON ei.id = eff.expense_item_id + ORDER BY eff.expense_item_id, eff.month; + RETURN; + END IF; + + -- ═══ Смета_2026 ═════════════════════════════════════════════════════════ + IF p_sheet = 'SMETA' THEN + RETURN QUERY + SELECT + sm.row_type, + sm.depth, + ROW_NUMBER() OVER (ORDER BY sm.section_code) AS sort_order, + jsonb_build_object( + 'section_code', sm.section_code, + 'name', sm.name, + 'plan', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.plan_supp_q1, 'q2', sm.plan_supp_q2, 'q3', sm.plan_supp_q3, 'q4', sm.plan_supp_q4, 'year', sm.plan_supp_year), + 'development', jsonb_build_object('q1', sm.plan_dev_q1, 'q2', sm.plan_dev_q2, 'q3', sm.plan_dev_q3, 'q4', sm.plan_dev_q4, 'year', sm.plan_dev_year) + ), + 'approved', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.appr_supp_q1, 'q2', sm.appr_supp_q2, 'q3', sm.appr_supp_q3, 'q4', sm.appr_supp_q4, 'year', sm.appr_supp_year), + 'development', jsonb_build_object('q1', sm.appr_dev_q1, 'q2', sm.appr_dev_q2, 'q3', sm.appr_dev_q3, 'q4', sm.appr_dev_q4, 'year', sm.appr_dev_year) + ), + 'fact', jsonb_build_object( + 'support', jsonb_build_object('q1', sm.fact_supp_q1, 'q2', sm.fact_supp_q2, 'q3', sm.fact_supp_q3, 'q4', sm.fact_supp_q4, 'year', sm.fact_supp_year), + 'development', jsonb_build_object('q1', sm.fact_dev_q1, 'q2', sm.fact_dev_q2, 'q3', sm.fact_dev_q3, 'q4', sm.fact_dev_q4, 'year', sm.fact_dev_year) + ), + -- corrected: только Q2/Q3/Q4 (см. шаблон Смета_2026, блок V-X). Q1 нет + -- (Q1 corrected ≡ approved_q1), year нет (q1+q2+q3+q4 даёт двойной счёт переносов). + 'corrected', jsonb_build_object( + 'support', jsonb_build_object('q2', sm.corr_supp_q2, 'q3', sm.corr_supp_q3, 'q4', sm.corr_supp_q4), + 'development', jsonb_build_object('q2', sm.corr_dev_q2, 'q3', sm.corr_dev_q3, 'q4', sm.corr_dev_q4) + ) + ) + FROM v3.v_form2_smeta(p_form_id) sm + ORDER BY sm.section_code; + RETURN; + END IF; + + RAISE EXCEPTION 'Unknown sheet: %', p_sheet + USING HINT = 'Use AHR / CAP / OPER / AHR_LIMIT / AHR_RENT / AHR_UTILITY / AHR_SECURITY / OTCH9F / SMETA'; + +END; +$function$ +LANGUAGE plpgsql STABLE; + + +-- Данные каталога АХР-лимитов (v3.limit_template) — 26 строк. +-- Импорт в другую БД: таблица v3.limit_template должна уже существовать. +-- Идемпотентно (ON CONFLICT (id) DO UPDATE). id сохраняются — на них +-- ссылаются v3.form_limit.template_id и self-FK parent_id, поэтому порядок по id. +INSERT INTO v3.limit_template + (id, parent_id, sort_order, row_type, section_no, expense_item_code, name, unit, limit_with_vat, limit_without_vat) +VALUES + (1, NULL, 100, 'SECTION', '1', NULL, 'Телефонные, почтовые, телеграфные расходы и др. услуги связи', NULL, NULL, NULL), + (2, 1, 110, 'GROUP', '1.1', 'R061015039', 'Мобильная связь:', NULL, NULL, NULL), + (3, 2, 111, 'LEAF', NULL, 'R061015039', '- Директор', '1 чел/квартал', 2400, 2000), + (4, 2, 112, 'LEAF', NULL, 'R061015039', '- Заместитель директора', '1 чел/квартал', 2400, 2000), + (5, 2, 113, 'LEAF', NULL, 'R061015039', '- Заместитель директора-гл.бухгалтер/гл.бухгалтер', '1 чел/квартал', 1200, 1000), + (6, 2, 114, 'LEAF', NULL, 'R061015039', '- Руководитель отдела/службы безопасности', '1 чел/квартал', 750, 625), + (7, 2, 115, 'LEAF', NULL, 'R061015039', '- SIM-карта для планшета (легкий фронт)', '1 планшет /квартал', 450, NULL), + (8, 2, 116, 'LEAF', NULL, 'R061015039', '- SIM-карта для уполномоченного работника', '1 чел/квартал', 450, NULL), + (9, 2, 117, 'LEAF', NULL, 'R061015039', '- прочие работники (по усмотрению директора филиала)', '1 чел/квартал', 450, 375), + (10, 1, 120, 'LEAF', '1.2', 'R061015038', 'Услуги стационарной связи', '1 чел/квартал', 1500, 1250), + (11, NULL, 200, 'SECTION', '2', NULL, 'Расходы на содержание и обслуживание вычислительной техники, оргтехники и другого банковского оборудования', NULL, NULL, NULL), + (12, 11, 210, 'GROUP', NULL, '204620030', 'Содержание, обслуживание техники', NULL, NULL, NULL), + (13, 12, 211, 'LEAF', NULL, '204620030', '- принтеры', NULL, NULL, 0), + (14, 12, 212, 'LEAF', NULL, '204620030', '- МФУ', NULL, NULL, 0), + (15, 12, 213, 'LEAF', NULL, '204620030', '- копировальные аппараты', NULL, NULL, 0), + (16, 11, 220, 'GROUP', NULL, '204720043', 'Кассовое оборудование', NULL, NULL, NULL), + (17, 16, 221, 'LEAF', NULL, '204720043', '- счетчики/сортировщики банкнот', '1шт/квартал', 4500, 3750), + (18, 16, 222, 'LEAF', NULL, '204720043', '- контрольно-кассовые машины', '1шт/квартал', 4500, 3750), + (19, NULL, 300, 'SECTION', '3', NULL, 'Канцелярские расходы', NULL, NULL, NULL), + (20, 19, 310, 'LEAF', NULL, '401440097', '- Бумага', '1 чел/квартал', 1600, 1333.3333), + (21, 19, 320, 'LEAF', NULL, '401540098', '- Канцелярские принадлежности', NULL, 500, 416.6667), + (22, NULL, 400, 'SECTION', '4', 'R061082031', 'Расходы по добровольному мед.страхованию работников Банка', NULL, NULL, NULL), + (23, 22, 410, 'LEAF', NULL, 'R061082031', '- бизнес РФ', '1 чел/год', 60308, 60308), + (24, 22, 420, 'LEAF', NULL, 'R061082031', '- стандарт РФ', NULL, 27858, 27858), + (25, 22, 430, 'LEAF', NULL, 'R061082031', '- стандарт 1 РФ', NULL, 16987, 16987), + (26, NULL, 500, 'LEAF', '5', 'R061142000', 'Подписка на периодические издания, приобретение спец. литературы', '1 филиал/квартал', 30000, 25000) +ON CONFLICT (id) DO UPDATE SET + parent_id = EXCLUDED.parent_id, + sort_order = EXCLUDED.sort_order, + row_type = EXCLUDED.row_type, + section_no = EXCLUDED.section_no, + expense_item_code = EXCLUDED.expense_item_code, + name = EXCLUDED.name, + unit = EXCLUDED.unit, + limit_with_vat = EXCLUDED.limit_with_vat, + limit_without_vat = EXCLUDED.limit_without_vat; + +-- синхронизируем sequence, чтобы новые INSERT'ы не конфликтовали по id +SELECT setval('v3.limit_template_id_seq', (SELECT max(id) FROM v3.limit_template)); diff --git a/api/alembic/versions/sql/0014_direction.sql b/api/alembic/versions/sql/0014_direction.sql new file mode 100644 index 0000000..939ec66 --- /dev/null +++ b/api/alembic/versions/sql/0014_direction.sql @@ -0,0 +1,1281 @@ +-- ════════════════════════════════════════════════════════════════════════════ +-- МИГРАЦИЯ: Добавление поля direction в phase_template и form_phase +-- ════════════════════════════════════════════════════════════════════════════ + + +-- Добавляем direction +ALTER TABLE v3.phase_template ADD COLUMN direction VARCHAR; +ALTER TABLE v3.form_phase ADD COLUMN direction VARCHAR; + +ALTER TABLE v3.form_phase DROP CONSTRAINT IF EXISTS form_phase_pkey; +CREATE UNIQUE INDEX form_phase_uidx ON v3.form_phase(budget_form_id, sheet, direction, phase_code); + +ALTER TABLE v3.phase_template DROP CONSTRAINT IF EXISTS phase_template_pkey; +CREATE UNIQUE INDEX phase_template_uidx ON v3.phase_template(form_type, sheet, direction, phase_code); + + +-- Добаввляем direction во все нужные функции фаз и шаблонов фаз +DROP FUNCTION IF EXISTS v3.add_form_phase(int4, varchar, varchar, varchar, _text, timestamp without time zone, timestamp without time zone); + +CREATE OR REPLACE FUNCTION v3.add_form_phase( + p_budget_form_id integer, + p_sheet character varying, + p_direction character varying, + p_phase_code character varying, + p_role character varying, + p_column_keys text[], + p_opens_at timestamp without time zone, + p_closes_at timestamp without 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; + v_utc_offset VARCHAR(3); +BEGIN + 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; + + SELECT utc_offset INTO v_utc_offset + FROM v3.org_unit + WHERE id = v_org_unit_id; + + INSERT INTO v3.form_phase( + budget_form_id, sheet, direction, phase_code, role, column_keys, opens_at, closes_at + ) + VALUES ( + p_budget_form_id, + p_sheet, + p_direction, + p_phase_code, + p_role, + p_column_keys, + CASE WHEN p_opens_at IS NOT NULL THEN (p_opens_at::text || v_utc_offset)::timestamptz ELSE NULL END, + CASE WHEN p_closes_at IS NOT NULL THEN (p_closes_at::text || v_utc_offset)::timestamptz ELSE NULL END + + ) + RETURNING * INTO v_phase; + + + 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', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_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, + 'direction', p_direction, + '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 IF EXISTS v3.can_edit(int4, varchar, text, int4); + + +CREATE OR REPLACE FUNCTION v3.can_edit(p_form_id integer, p_sheet character varying, p_direction 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.direction = p_direction OR (fp.direction IS NULL AND p_direction IS NULL)) + 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.direction = p_direction OR (fp.direction IS NULL AND p_direction IS NULL)) + 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.direction = p_direction OR (fp.direction IS NULL AND p_direction IS NULL)) + 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.direction = p_direction OR (fp.direction IS NULL AND p_direction IS NULL)) + 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$ +; + + +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, p_direction, 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$ +; + + +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; + v_utc_offset VARCHAR(3); + v_org_unit INTEGER; + v_year INTEGER; + BEGIN + SELECT form_type_code, org_unit_id, "year" INTO v_form_type, v_org_unit, v_year + FROM v3.budget_form + WHERE id = p_form_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'budget_form id=% не существует', p_form_id; + END IF; + + SELECT utc_offset INTO v_utc_offset + FROM v3.org_unit + WHERE id = v_org_unit; + + INSERT INTO v3.form_phase + (budget_form_id, sheet, direction, phase_code, role, column_keys, opens_at, closes_at) + SELECT p_form_id, pt.sheet, pt.direction, pt.phase_code, pt.role, + pt.column_keys, + ((pt.opens_at + INTERVAL '1 year' * v_year)::text || v_utc_offset)::timestamptz, + ((pt.closes_at + INTERVAL '1 year' * v_year)::text || v_utc_offset)::timestamptz + FROM v3.phase_template pt + WHERE pt.form_type = v_form_type + ON CONFLICT (budget_form_id, sheet, phase_code, direction) DO NOTHING; + + GET DIAGNOSTICS v_inserted = ROW_COUNT; + RETURN v_inserted; + END; + $function$ +; + + +-- DROP FUNCTION v3.copy_template_to_report(int4); + +CREATE OR REPLACE FUNCTION v3.copy_template_to_report(p_report_id integer) + RETURNS integer + LANGUAGE plpgsql +AS $function$ +DECLARE + v_report_type VARCHAR; + v_inserted INTEGER; + v_org_unit INTEGER; + v_utc_offset VARCHAR(3); + v_year INTEGER; +BEGIN + SELECT report_type, org_unit_id, "year" INTO v_report_type, v_org_unit, v_year + FROM v3.rf_project_report AS rpr + INNER JOIN v3.project AS p ON p.id = rpr.project_id + WHERE rpr.id = p_report_id; + + IF NOT FOUND THEN + RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id; + END IF; + + SELECT utc_offset INTO v_utc_offset + FROM v3.org_unit + WHERE id = v_org_unit; + + INSERT INTO v3.form3_phase + (rf_project_report_id, phase_code, role, column_keys, opens_at, closes_at) + SELECT p_report_id, pt.phase_code, pt.role, + pt.column_keys, + ((pt.opens_at + INTERVAL '1 year' * v_year)::text || v_utc_offset)::timestamptz, + ((pt.closes_at + INTERVAL '1 year' * v_year)::text || v_utc_offset)::timestamptz + FROM v3.phase_template pt + WHERE pt.form_type = 'FORM_3' + AND pt.sheet = v_report_type + ON CONFLICT (rf_project_report_id, phase_code) DO NOTHING; + + GET DIAGNOSTICS v_inserted = ROW_COUNT; + RETURN v_inserted; +END; +$function$ +; + +DROP FUNCTION IF EXISTS 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_direction 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 (direction = p_direction OR (direction IS NULL AND p_direction IS NULL)) + 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', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_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, + 'direction', p_direction, + '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 IF EXISTS 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, p_direction 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 + AND (fp.direction = p_direction OR (fp.direction IS NULL AND p_direction IS NULL)) + 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.direction = p_direction OR (fp.direction IS NULL AND p_direction IS NULL)) + AND fp.role = v_role + AND now() BETWEEN fp.opens_at AND fp.closes_at + GROUP BY ck; +END; +$function$ +; + + +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, p_user_id integer DEFAULT NULL::integer) + 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 + 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; + + IF p_user_id IS NOT NULL THEN + RETURN QUERY + SELECT + 'META'::VARCHAR, + -1, + 0::BIGINT, + jsonb_build_object( + 'user_id', p_user_id, + 'role', v3.user_role_code(p_user_id), + 'sheet', p_sheet, + 'editable', COALESCE( + (SELECT jsonb_agg( + jsonb_build_object('column', ec.column_key, + 'closes_at', ec.closes_at) + ORDER BY ec.column_key) + FROM v3.editable_columns_for(p_form_id, p_user_id, p_sheet, p_direction) ec), + '[]'::JSONB) + ); + END IF; + + IF v_form_type = 'FORM_1' THEN + + 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.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; + + 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; + + 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$ +; + + + +DROP FUNCTION IF EXISTS v3.upd_form_phase(int4, varchar, varchar, varchar, _text, timestamp, timestamp); + +CREATE OR REPLACE FUNCTION v3.upd_form_phase(p_budget_form_id integer, p_sheet character varying, p_direction 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 without time zone DEFAULT NULL::timestamp without time zone, p_closes_at timestamp without time zone DEFAULT NULL::timestamp without 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; + v_utc_offset VARCHAR(3); + v_opens_at timestamptz; + v_closes_at timestamptz; +BEGIN + SELECT * INTO v_old + FROM v3.form_phase + WHERE budget_form_id = p_budget_form_id + AND sheet = p_sheet + AND (direction = p_direction OR (direction IS NULL AND p_direction IS NULL)) + 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; + + SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year, ou.utc_offset + INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year, v_utc_offset + 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 + )); + + IF p_opens_at is not null THEN + v_opens_at = (p_opens_at::text || v_utc_offset)::timestamptz; + END IF; + IF p_closes_at is not null THEN + v_closes_at = (p_closes_at::text || v_utc_offset)::timestamptz; + END IF; + + UPDATE v3.form_phase + SET role = COALESCE(p_role, role), + column_keys = COALESCE(p_column_keys, column_keys), + opens_at = COALESCE(v_opens_at, opens_at), + closes_at = COALESCE(v_closes_at, closes_at) + WHERE budget_form_id = p_budget_form_id + AND sheet = p_sheet + AND (direction = p_direction OR (direction IS NULL AND p_direction IS NULL)) + AND phase_code = p_phase_code + RETURNING * INTO v_new; + + + 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( + 'entity_type', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_id, + + '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', 'task', + 'entity_id', p_budget_form_id, + 'core_entity_type', 'form_phase', + 'core_entity_id', v_phase_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$ +; + + +-- Обновляем direction для фаз первой формы + +UPDATE v3.phase_template pt +SET direction = 'Support' +WHERE sheet IN ('AHR', 'CAP') + AND form_type = 'FORM_1'; + +INSERT INTO v3.phase_template + (form_type, sheet, direction, phase_code, role, column_keys, opens_at, closes_at) + SELECT pt.form_type, pt.sheet, 'Development', pt.phase_code, pt.role, + pt.column_keys, pt.opens_at, + pt.closes_at + FROM v3.phase_template pt + WHERE pt.sheet IN ('AHR', 'CAP') + AND pt.form_type = 'FORM_1'; + + + +UPDATE v3.form_phase fp +SET direction = 'Support' +WHERE sheet IN ('AHR', 'CAP') + AND budget_form_id IN ( + SELECT id FROM v3.budget_form WHERE form_type_code = 'FORM_1' + ); + +INSERT INTO v3.form_phase + (budget_form_id, sheet, direction, phase_code, role, column_keys, opens_at, closes_at) + SELECT fp.budget_form_id, fp.sheet, 'Development', fp.phase_code, fp.role, + fp.column_keys, fp.opens_at, fp.closes_at + FROM v3.form_phase fp + WHERE fp.sheet IN ('AHR', 'CAP') + AND budget_form_id IN ( + SELECT id FROM v3.budget_form WHERE form_type_code = 'FORM_1' + ); + + + +-- Добавляем возможность менять justification у budget_line + +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, '.'); + + 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; + + 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; + + 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 + IF v_parts[2] LIKE 'opening_%' AND v_far_month <> 1 THEN + RAISE EXCEPTION 'computed_field: % (opening для month>1 вычисляется из base + накопит. дельты)', p_column; + END IF; + 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; + + IF p_sheet = 'AHR_LIMIT' THEN + IF p_form_id IS NULL THEN + RAISE EXCEPTION 'internal: p_form_id не передан для AHR_LIMIT'; + END IF; + + 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; + + 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; + + 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 = '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 = 'justification' THEN + UPDATE v3.budget_line SET justification = (p_value #>> '{}')::TEXT, 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; + + IF v_scope IN ('approved','totals') THEN + RAISE EXCEPTION 'computed_field: %', p_column; + END IF; + + 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; + + + IF v_scope IN ('q1','q2','q3','q4') THEN + v_q := substring(v_scope FROM 2)::SMALLINT; + ELSE + v_q := NULL; + END IF; + + 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','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','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','order', 'allocation','internal_order','TEXT','line'), + ('allocation','property', 'allocation','property_object','TEXT','line'), + ('allocation','contract_ref', 'allocation','contract_ref','TEXT','line'), + ('allocation','allocation_purpose', 'allocation','allocation_purpose','TEXT','line'), + + ('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'), + ('contract_summary','reference', 'contract_summary','reference', 'TEXT','line'), + ('contract_summary','centralized_flag', 'contract_summary','centralized_flag', 'TEXT','line'), + + ('collegial','approved', 'collegial_approval','approved_amount', 'NUMERIC','line'), + ('collegial','protocol', 'collegial_approval','protocol_reference','TEXT', 'line'), + ('collegial','note', 'collegial_approval','note', 'TEXT', 'line'), + + ('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','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','addenda', 'contract_detail','addenda', 'TEXT', 'line'), + ('contract','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'), + ('contract','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'), + ('contract','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'), + ('contract','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'), + ('contract','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'), + ('contract','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'), + ('contract','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','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','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'), + + ('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'), + ('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'), + ('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 + 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; + + + 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; + 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; + + UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id; +END; +$function$ +; + +UPDATE v3.phase_template +SET closes_at = closes_at - INTERVAL '2026 years', opens_at = opens_at - INTERVAL '2026 years'; diff --git a/api/alembic/versions/sql/0015_aggregates_fix.sql b/api/alembic/versions/sql/0015_aggregates_fix.sql new file mode 100644 index 0000000..6f3382c --- /dev/null +++ b/api/alembic/versions/sql/0015_aggregates_fix.sql @@ -0,0 +1,2907 @@ +-- DROP FUNCTION v3.v_form2_sheet_sections(int4, varchar, _text); + +CREATE OR REPLACE FUNCTION v3.v_form2_sheet_sections(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[]) + RETURNS TABLE(row_type character varying, depth integer, line_id integer, col_section_code character varying, col_item_id character varying, col_num_group_id character varying, col_name character varying, col_vsp_id integer, col_vsp_address character varying, col_plan_q1 numeric, col_plan_q2 numeric, col_plan_q3 numeric, col_plan_q4 numeric, col_plan_year numeric, col_plan_comment character varying, col_seq_dfip_q1 numeric, col_seq_dfip_q2 numeric, col_seq_dfip_q3 numeric, col_seq_dfip_q4 numeric, col_seq_dfip_year numeric, col_seq_dfip_just character varying, col_seq_ssp_q1 numeric, col_seq_ssp_q2 numeric, col_seq_ssp_q3 numeric, col_seq_ssp_q4 numeric, col_seq_ssp_year numeric, col_seq_ssp_just character varying, col_appr_q1 numeric, col_appr_q2 numeric, col_appr_q3 numeric, col_appr_q4 numeric, col_appr_year numeric, col_cd_counterparty character varying, col_cd_reference character varying, col_cd_date date, col_cd_subject character varying, col_cd_currency character varying, col_cd_ceiling numeric, col_cd_vat_rate character varying, col_cd_deadline character varying, col_cd_scheme character varying, col_cd_act character varying, col_cd_comment character varying, col_book_q1 numeric, col_book_q2 numeric, col_book_q3 numeric, col_book_q4 numeric, col_book_next_q1 numeric, col_book_next_q2 numeric, col_book_next_q3 numeric, col_book_next_q4 numeric, col_q1_adj_current numeric, col_q1_adj_ssp numeric, col_q1_adj_reserve numeric, col_q1_adj_comment character varying, col_q1_corrected_plan numeric, col_q1_pay_date date, col_q1_pay_amount numeric, col_q1_pay_comment character varying, col_q1_pay_act character varying, col_q1_booking numeric, col_q1_actual_m1 numeric, col_q1_actual_m2 numeric, col_q1_actual_m3 numeric, col_q1_actual_quarter numeric, col_q1_residual_after_booking numeric, col_q1_residual_after_actual numeric, col_q1_transfer_q2 numeric, col_q1_transfer_q2_delay_acts numeric, col_q1_transfer_q2_delay_procurement numeric, col_q1_transfer_q2_economy_rf numeric, col_q1_transfer_next_comment character varying, col_q1_transfer_q3 numeric, col_q1_transfer_q4 numeric, col_q1_transfer_far_comment character varying, col_q1_transfer_econ numeric, col_q1_total numeric, col_q2_target_change numeric, col_q2_base_correction numeric, col_q2_base_correction_comment character varying, col_q2_revision_inc numeric, col_q2_revision_seq numeric, col_q2_revision_comment character varying, col_q2_new_plan numeric, col_q2_adj_current numeric, col_q2_adj_ssp numeric, col_q2_adj_reserve numeric, col_q2_adj_comment character varying, col_q2_corrected_plan numeric, col_q2_pay_date date, col_q2_pay_amount numeric, col_q2_pay_comment character varying, col_q2_pay_act character varying, col_q2_booking numeric, col_q2_actual_m1 numeric, col_q2_actual_m2 numeric, col_q2_actual_m3 numeric, col_q2_actual_quarter numeric, col_q2_residual_after_booking numeric, col_q2_residual_after_actual numeric, col_q2_transfer_q3 numeric, col_q2_transfer_q3_delay_acts numeric, col_q2_transfer_q3_delay_procurement numeric, col_q2_transfer_q3_economy_rf numeric, col_q2_transfer_next_comment character varying, col_q2_transfer_q4 numeric, col_q2_transfer_far_comment character varying, col_q2_transfer_econ numeric, col_q2_total numeric, col_q3_target_change numeric, col_q3_base_correction numeric, col_q3_base_correction_comment character varying, col_q3_revision_inc numeric, col_q3_revision_seq numeric, col_q3_revision_comment character varying, col_q3_new_plan numeric, col_q3_adj_current numeric, col_q3_adj_ssp numeric, col_q3_adj_reserve numeric, col_q3_adj_comment character varying, col_q3_corrected_plan numeric, col_q3_pay_date date, col_q3_pay_amount numeric, col_q3_pay_comment character varying, col_q3_pay_act character varying, col_q3_booking numeric, col_q3_actual_m1 numeric, col_q3_actual_m2 numeric, col_q3_actual_m3 numeric, col_q3_actual_quarter numeric, col_q3_residual_after_booking numeric, col_q3_residual_after_actual numeric, col_q3_transfer_q4 numeric, col_q3_transfer_q4_delay_acts numeric, col_q3_transfer_q4_delay_procurement numeric, col_q3_transfer_q4_economy_rf numeric, col_q3_transfer_next_comment character varying, col_q3_transfer_econ numeric, col_q3_total numeric, col_q4_target_change numeric, col_q4_base_correction numeric, col_q4_base_correction_comment character varying, col_q4_revision_inc numeric, col_q4_revision_seq numeric, col_q4_revision_comment character varying, col_q4_new_plan numeric, col_q4_adj_current numeric, col_q4_adj_ssp numeric, col_q4_adj_reserve numeric, col_q4_adj_comment character varying, col_q4_corrected_plan numeric, col_q4_pay_date date, col_q4_pay_amount numeric, col_q4_pay_comment character varying, col_q4_pay_act character varying, col_q4_booking numeric, col_q4_actual_m1 numeric, col_q4_actual_m2 numeric, col_q4_actual_m3 numeric, col_q4_actual_spod numeric, col_q4_actual_quarter numeric, col_q4_residual_after_booking numeric, col_q4_residual_after_actual numeric, col_q4_transfer_econ numeric, col_q4_total numeric, col_fact_year numeric, col_pay_year numeric, col_economy_year numeric, col_internal_order character varying, col_cs_total numeric, col_cs_counterparty character varying, col_cs_deadline character varying, col_cs_comment character varying, col_cs_future_y1 numeric, col_cs_future_y2 numeric, col_cs_other_ssp numeric, col_al_contract_ref character varying, col_al_purpose character varying, col_al_order character varying, col_al_property character varying, col_ca_approved numeric, col_ca_protocol character varying, col_ca_note character varying, col_ckk_ceiling numeric, col_ckk_rf_sched character varying, col_ckk_deadline character varying, col_ckk_proc_plan character varying, col_ckk_proc_meth character varying, col_ckk_comment character varying, col_cd_addenda character varying, col_cd_exp_q1 numeric, col_cd_exp_q2 numeric, col_cd_exp_q3 numeric, col_cd_exp_q4 numeric, col_cd_rf_sched character varying, col_cd_exchange_rate numeric, col_cd_amount_foreign numeric, col_q1_adj_rf numeric, col_q1_pay_ho numeric, col_q1_pay_rf numeric, col_q2_adj_rf numeric, col_q2_pay_ho numeric, col_q2_pay_rf numeric, col_q3_adj_rf numeric, col_q3_pay_ho numeric, col_q3_pay_rf numeric, col_q4_adj_rf numeric, col_q4_pay_ho numeric, col_q4_pay_rf numeric, _sort_path integer[]) + LANGUAGE plpgsql + STABLE +AS $function$ +#variable_conflict use_column +DECLARE + s_plan BOOL; s_seq_d BOOL; s_seq_s BOOL; s_appr BOOL; + s_cd BOOL; s_book BOOL; + s_cs BOOL; s_al BOOL; s_ca BOOL; s_ckk BOOL; -- НОВЫЕ блоки + s_q1 BOOL; s_q2 BOOL; s_q3 BOOL; s_q4 BOOL; s_tot BOOL; + s_need_ap BOOL; -- plan/seq/reserve нужны для approved + cp1..cp4 +BEGIN + s_plan := p_sections IS NULL OR 'plan' = ANY(p_sections); + s_seq_d := p_sections IS NULL OR 'seq_dfip' = ANY(p_sections); + s_seq_s := p_sections IS NULL OR 'seq_ssp' = ANY(p_sections); + s_appr := p_sections IS NULL OR 'approved' = ANY(p_sections); + s_cd := p_sections IS NULL OR 'contract' = ANY(p_sections); + s_book := p_sections IS NULL OR 'booking' = ANY(p_sections); + s_cs := p_sections IS NULL OR 'contract_summary' = ANY(p_sections); + s_al := p_sections IS NULL OR 'allocation' = ANY(p_sections); + s_ca := p_sections IS NULL OR 'collegial' = ANY(p_sections); + s_ckk := p_sections IS NULL OR 'ckk' = ANY(p_sections); + s_q1 := p_sections IS NULL OR 'q1' = ANY(p_sections); + s_q2 := p_sections IS NULL OR 'q2' = ANY(p_sections); + s_q3 := p_sections IS NULL OR 'q3' = ANY(p_sections); + s_q4 := p_sections IS NULL OR 'q4' = ANY(p_sections); + s_tot := p_sections IS NULL OR 'totals' = ANY(p_sections); + s_need_ap := s_plan OR s_appr OR s_q1 OR s_q2 OR s_q3 OR s_q4; + + RETURN QUERY + WITH + tw AS ( + SELECT t.id, t.section_code, t.item_id, t.num_group_id, t.name, + t.depth, t.path, t.parent_id, t.parent_item_id, t.desc_ids + FROM v3.mv_expense_item_tree t + JOIN v3.expense_item_form_type eft ON eft.expense_item_id = t.id + JOIN v3.budget_form bf ON bf.id = p_form_id AND bf.form_type_code = eft.form_type_code + WHERE t.sheet = p_sheet + ), + page AS ( + SELECT bl.id AS lid, bl.expense_item_id AS eid, bl.name AS bname, bl.vsp_id, + bl.internal_order AS io + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + ), + jp AS (SELECT p.* FROM v3.plan p JOIN page pg ON p.line_id = pg.lid WHERE s_need_ap), + jsd AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='DFIP'), + jsg AS (SELECT s.* FROM v3.sequestration s JOIN page pg ON s.line_id = pg.lid WHERE s_need_ap AND s.actor='SSP_GO'), + jr AS (SELECT r.* FROM v3.reserve r JOIN page pg ON r.line_id = pg.lid WHERE s_need_ap), + jcd AS (SELECT c.* FROM v3.contract_detail c JOIN page pg ON c.line_id = pg.lid WHERE s_cd), + jck AS (SELECT c.* FROM v3.ckk c JOIN page pg ON c.line_id = pg.lid WHERE s_book OR s_ckk), + jcs AS (SELECT c.* FROM v3.contract_summary c JOIN page pg ON c.line_id = pg.lid WHERE s_cs), + jal AS (SELECT a.* FROM v3.allocation a JOIN page pg ON a.line_id = pg.lid WHERE s_al), + jca AS (SELECT c.* FROM v3.collegial_approval c JOIN page pg ON c.line_id = pg.lid WHERE s_ca), + jq1 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q1 AND q.quarter=1), + jq2 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q2 AND q.quarter=2), + jq3 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE s_q3 AND q.quarter=3), + jq4 AS (SELECT q.* FROM v3.budget_line_quarter q JOIN page pg ON q.line_id = pg.lid WHERE (s_q4 OR s_tot) AND q.quarter=4), + + input_base AS ( + SELECT + pg.lid, pg.eid, pg.bname, pg.vsp_id, + v.address AS vsp_addr, + 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, + COALESCE(p.plan_q1,0) AS pq1, COALESCE(p.plan_q2,0) AS pq2, + COALESCE(p.plan_q3,0) AS pq3, COALESCE(p.plan_q4,0) AS pq4, + p.comment AS pcmt, + COALESCE(sd.adj_q1,0) AS dq1, COALESCE(sd.adj_q2,0) AS dq2, + COALESCE(sd.adj_q3,0) AS dq3, COALESCE(sd.adj_q4,0) AS dq4, + sd.justification AS djust, + COALESCE(sg.adj_q1,0) AS gq1, COALESCE(sg.adj_q2,0) AS gq2, + COALESCE(sg.adj_q3,0) AS gq3, COALESCE(sg.adj_q4,0) AS gq4, + sg.justification AS gjust, + COALESCE(r.amount_q1,0) AS rq1, COALESCE(r.amount_q2,0) AS rq2, + COALESCE(r.amount_q3,0) AS rq3, COALESCE(r.amount_q4,0) AS rq4, + cd.counterparty AS cd_cp, cd.reference AS cd_ref, cd.contract_date AS cd_dt, + cd.subject AS cd_subj, cd.currency AS cd_cur, cd.ceiling_amount AS cd_ceil, + cd.vat_rate AS cd_vat, cd.deadline AS cd_dl, cd.payment_scheme AS cd_sch, + cd.act AS cd_act, cd.comment AS cd_cmt, + ck.expenses_q1 AS bk1, ck.expenses_q2 AS bk2, ck.expenses_q3 AS bk3, ck.expenses_q4 AS bk4, + ck.expenses_next_year_q1 AS bn1, ck.expenses_next_year_q2 AS bn2, + ck.expenses_next_year_q3 AS bn3, ck.expenses_next_year_q4 AS bn4, + pg.io AS io, + cs.total_amount AS cs_total, cs.counterparty AS cs_cp, cs.deadline AS cs_dl, + cs.comment AS cs_cmt, cs.future_payments_y1 AS cs_fy1, cs.future_payments_y2 AS cs_fy2, + cs.other_ssp_amount AS cs_oth, + al.contract_ref AS al_cref, al.allocation_purpose AS al_purp, + al.internal_order AS al_io, al.property_object AS al_prop, + ca.approved_amount AS ca_amt, ca.protocol_reference AS ca_proto, ca.note AS ca_note, + ck.ceiling_amount AS ckk_ceil, ck.rf_schedule AS ckk_rf, ck.delivery_deadline AS ckk_dl, + ck.procurement_plan AS ckk_pplan, ck.procurement_method AS ckk_pmeth, ck.comment AS ckk_cmt, + cd.addenda AS cd_add, cd.expenses_q1 AS cd_e1, cd.expenses_q2 AS cd_e2, + cd.expenses_q3 AS cd_e3, cd.expenses_q4 AS cd_e4, cd.rf_schedule AS cd_rf, + cd.exchange_rate AS cd_rate, cd.amount_foreign AS cd_fx, + q1.adj_rf AS q1_arf, q1.payment_amount_ho AS q1_pho, q1.payment_amount_rf AS q1_prf, + q2.adj_rf AS q2_arf, q2.payment_amount_ho AS q2_pho, q2.payment_amount_rf AS q2_prf, + q3.adj_rf AS q3_arf, q3.payment_amount_ho AS q3_pho, q3.payment_amount_rf AS q3_prf, + q4.adj_rf AS q4_arf, q4.payment_amount_ho AS q4_pho, q4.payment_amount_rf AS q4_prf, + q1.adj_current AS q1_ac, q1.adj_ssp AS q1_as, q1.adj_reserve AS q1_arv, q1.adj_comment AS q1_acmt, + q1.payment_date AS q1_pd, q1.payment_amount AS q1_pa, + q1.payment_comment AS q1_pcmt, + q1.payment_act AS q1_pact, + COALESCE( + q1.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q1 + WHEN ck.expenses_q1 > 0 THEN ck.expenses_q1 + ELSE cd.expenses_q1 + END + + ) AS q1_book, + q1.actual_m1 AS q1_m1, q1.actual_m2 AS q1_m2, q1.actual_m3 AS q1_m3, + q1.transfer_to_q2 AS q1_tq2, q1.transfer_to_q3 AS q1_tq3, q1.transfer_to_q4 AS q1_tq4, + q1.transfer_to_economy AS q1_te, + q1.transfer_delay_acts AS q1_tda, q1.transfer_delay_procurement AS q1_tdp, + q1.transfer_economy_rf AS q1_terf, + q1.transfer_next_comment AS q1_tnc, q1.transfer_far_comment AS q1_tfc, + q2.target_change AS q2_tc, q2.base_plan_correction AS q2_bc, q2.base_plan_correction_comment AS q2_bcc, + q2.plan_revision_increase AS q2_rinc, q2.plan_revision_sequester AS q2_rseq, + q2.plan_revision_comment AS q2_rcmt, + q2.adj_current AS q2_ac, q2.adj_ssp AS q2_as, q2.adj_reserve AS q2_arv, + q2.adj_comment AS q2_acmt, + q2.payment_date AS q2_pd, q2.payment_amount AS q2_pa, + q2.payment_comment AS q2_pcmt, q2.payment_act AS q2_pact, + COALESCE( + q2.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q2 + WHEN ck.expenses_q2 > 0 THEN ck.expenses_q2 + ELSE cd.expenses_q2 + END + + ) AS q2_book, + q2.actual_m1 AS q2_m1, q2.actual_m2 AS q2_m2, q2.actual_m3 AS q2_m3, + q2.transfer_to_q3 AS q2_tq3, q2.transfer_to_q4 AS q2_tq4, q2.transfer_to_economy AS q2_te, + q2.transfer_delay_acts AS q2_tda, q2.transfer_delay_procurement AS q2_tdp, + q2.transfer_economy_rf AS q2_terf, + q2.transfer_next_comment AS q2_tnc, q2.transfer_far_comment AS q2_tfc, + q3.target_change AS q3_tc, q3.base_plan_correction AS q3_bc, + q3.base_plan_correction_comment AS q3_bcc, + q3.plan_revision_increase AS q3_rinc, q3.plan_revision_sequester AS q3_rseq, + q3.plan_revision_comment AS q3_rcmt, + q3.adj_current AS q3_ac, q3.adj_ssp AS q3_as, q3.adj_reserve AS q3_arv, + q3.adj_comment AS q3_acmt, + q3.payment_date AS q3_pd, q3.payment_amount AS q3_pa, + q3.payment_comment AS q3_pcmt, q3.payment_act AS q3_pact, + COALESCE( + q3.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q3 + WHEN ck.expenses_q3 > 0 THEN ck.expenses_q3 + ELSE cd.expenses_q3 + END + + ) AS q3_book, + q3.actual_m1 AS q3_m1, q3.actual_m2 AS q3_m2, q3.actual_m3 AS q3_m3, + q3.transfer_to_q4 AS q3_tq4, q3.transfer_to_economy AS q3_te, + q3.transfer_delay_acts AS q3_tda, q3.transfer_delay_procurement AS q3_tdp, + q3.transfer_economy_rf AS q3_terf, q3.transfer_next_comment AS q3_tnc, + q4.target_change AS q4_tc, q4.base_plan_correction AS q4_bc, + q4.base_plan_correction_comment AS q4_bcc, + q4.plan_revision_increase AS q4_rinc, q4.plan_revision_sequester AS q4_rseq, + q4.plan_revision_comment AS q4_rcmt, + q4.adj_current AS q4_ac, q4.adj_ssp AS q4_as, q4.adj_reserve AS q4_arv, + q4.adj_comment AS q4_acmt, + q4.payment_date AS q4_pd, q4.payment_amount AS q4_pa, + q4.payment_comment AS q4_pcmt, q4.payment_act AS q4_pact, + COALESCE( + q4.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q4 + WHEN ck.expenses_q4 > 0 THEN ck.expenses_q4 + ELSE cd.expenses_q4 + END + + ) AS q4_book, + q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, q4.actual_spod AS q4_spod, + q4.transfer_to_economy AS q4_te, + COALESCE(p.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(sg.adj_q1,0)+COALESCE(r.amount_q1,0) AS ap1, + COALESCE(p.plan_q2,0)+COALESCE(sd.adj_q2,0)+COALESCE(sg.adj_q2,0)+COALESCE(r.amount_q2,0) AS ap2, + COALESCE(p.plan_q3,0)+COALESCE(sd.adj_q3,0)+COALESCE(sg.adj_q3,0)+COALESCE(r.amount_q3,0) AS ap3, + COALESCE(p.plan_q4,0)+COALESCE(sd.adj_q4,0)+COALESCE(sg.adj_q4,0)+COALESCE(r.amount_q4,0) AS ap4 + FROM page pg + JOIN tw t ON t.id = pg.eid + LEFT JOIN v3.vsp v ON v.id = pg.vsp_id + LEFT JOIN jp p ON p.line_id = pg.lid + LEFT JOIN jsd sd ON sd.line_id = pg.lid + LEFT JOIN jsg sg ON sg.line_id = pg.lid + LEFT JOIN jr r ON r.line_id = pg.lid + LEFT JOIN jcd cd ON cd.line_id = pg.lid + LEFT JOIN jck ck ON ck.line_id = pg.lid + LEFT JOIN jcs cs ON cs.line_id = pg.lid + LEFT JOIN jal al ON al.line_id = pg.lid + LEFT JOIN jca ca ON ca.line_id = pg.lid + LEFT JOIN jq1 q1 ON q1.line_id = pg.lid + LEFT JOIN jq2 q2 ON q2.line_id = pg.lid + LEFT JOIN jq3 q3 ON q3.line_id = pg.lid + LEFT JOIN jq4 q4 ON q4.line_id = pg.lid + ), + enriched AS ( + SELECT b.*, + b.ap1 + COALESCE(b.q1_ac,0)+COALESCE(b.q1_as,0)+COALESCE(b.q1_arv,0) AS cp1, + COALESCE(b.q1_m1,0)+COALESCE(b.q1_m2,0)+COALESCE(b.q1_m3,0) AS aq1, + b.ap2 + COALESCE(b.q1_tq2,0) + COALESCE(b.q2_rinc,0)+COALESCE(b.q2_rseq,0) + COALESCE(b.q2_bc,0) AS np2, + COALESCE(b.q2_m1,0)+COALESCE(b.q2_m2,0)+COALESCE(b.q2_m3,0) AS aq2, + b.ap3 + COALESCE(b.q1_tq3,0)+COALESCE(b.q2_tq3,0) + COALESCE(b.q3_rinc,0)+COALESCE(b.q3_rseq,0) + COALESCE(b.q3_bc,0) AS np3, + COALESCE(b.q3_m1,0)+COALESCE(b.q3_m2,0)+COALESCE(b.q3_m3,0) AS aq3, + b.ap4 + COALESCE(b.q1_tq4,0)+COALESCE(b.q2_tq4,0)+COALESCE(b.q3_tq4,0) + COALESCE(b.q4_rinc,0)+COALESCE(b.q4_rseq,0) + COALESCE(b.q4_bc,0) AS np4, + COALESCE(b.q4_m1,0)+COALESCE(b.q4_m2,0)+COALESCE(b.q4_m3,0)+COALESCE(b.q4_spod,0) AS aq4 + FROM input_base b + ), + final_input AS ( + SELECT e.*, + e.np2 + COALESCE(e.q2_ac,0)+COALESCE(e.q2_as,0)+COALESCE(e.q2_arv,0) AS cp2, + e.np3 + COALESCE(e.q3_ac,0)+COALESCE(e.q3_as,0)+COALESCE(e.q3_arv,0) AS cp3, + e.np4 + COALESCE(e.q4_ac,0)+COALESCE(e.q4_as,0)+COALESCE(e.q4_arv,0) AS cp4 + FROM enriched e + ), + + agg AS ( + SELECT bl.expense_item_id AS eid, + SUM(p.plan_q1) AS sp1, SUM(p.plan_q2) AS sp2, SUM(p.plan_q3) AS sp3, SUM(p.plan_q4) AS sp4, + SUM(sd.adj_q1) AS sd1, SUM(sd.adj_q2) AS sd2, SUM(sd.adj_q3) AS sd3, SUM(sd.adj_q4) AS sd4, + SUM(sg.adj_q1) AS sg1, SUM(sg.adj_q2) AS sg2, SUM(sg.adj_q3) AS sg3, SUM(sg.adj_q4) AS sg4, + SUM(r.amount_q1) AS sr1, SUM(r.amount_q2) AS sr2, SUM(r.amount_q3) AS sr3, SUM(r.amount_q4) AS sr4, + SUM(ck.expenses_q1) AS bk1, SUM(ck.expenses_q2) AS bk2, SUM(ck.expenses_q3) AS bk3, SUM(ck.expenses_q4) AS bk4, + SUM(cd.expenses_q1) AS bkc1, SUM(cd.expenses_q2) AS bkc2, SUM(cd.expenses_q3) AS bkc3, SUM(cd.expenses_q4) AS bkc4, + SUM(ck.expenses_next_year_q1) AS bn1, SUM(ck.expenses_next_year_q2) AS bn2, + SUM(ck.expenses_next_year_q3) AS bn3, SUM(ck.expenses_next_year_q4) AS bn4 + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.plan p ON p.line_id = bl.id AND s_need_ap + LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor='DFIP' AND s_need_ap + LEFT JOIN v3.sequestration sg ON sg.line_id = bl.id AND sg.actor='SSP_GO' AND s_need_ap + LEFT JOIN v3.reserve r ON r.line_id = bl.id AND s_need_ap + LEFT JOIN v3.ckk ck ON ck.line_id = bl.id AND s_book + LEFT JOIN v3.contract_detail cd ON cd.line_id = bl.id AND s_cd + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (s_need_ap OR s_book OR s_cd) + GROUP BY bl.expense_item_id + ), + aq_q AS ( + SELECT bl.expense_item_id AS eid, qrtrs.qrtr as quarter, + SUM(q.adj_current) AS ac, SUM(q.adj_ssp) AS as_v, SUM(q.adj_reserve) AS arv, + SUM(q.payment_amount) AS pa, + SUM( + COALESCE( + q.booking_amount, + CASE + WHEN qrtrs.qrtr = 1 + THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q1 + WHEN ck.expenses_q1 > 0 THEN ck.expenses_q1 + ELSE cd.expenses_q1 + END + WHEN qrtrs.qrtr = 2 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q2 + WHEN ck.expenses_q2 > 0 THEN ck.expenses_q2 + ELSE cd.expenses_q2 + END + WHEN qrtrs.qrtr = 3 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q3 + WHEN ck.expenses_q3 > 0 THEN ck.expenses_q3 + ELSE cd.expenses_q3 + END + WHEN qrtrs.qrtr = 4 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q4 + WHEN ck.expenses_q4 > 0 THEN ck.expenses_q4 + ELSE cd.expenses_q4 + END + END + + )) AS bk, + SUM(q.actual_m1) AS m1, SUM(q.actual_m2) AS m2, SUM(q.actual_m3) AS m3, + SUM(q.actual_spod) AS spod, + SUM(q.transfer_to_q2) AS tq2, SUM(q.transfer_to_q3) AS tq3, SUM(q.transfer_to_q4) AS tq4, + SUM(q.transfer_to_economy) AS te, + SUM(q.transfer_delay_acts) AS tda, + SUM(q.transfer_delay_procurement) AS tdp, + SUM(q.transfer_economy_rf) AS terf, + SUM(COALESCE(q.plan_revision_increase,0) + + COALESCE(q.plan_revision_sequester,0) + + COALESCE(q.target_change,0) + + COALESCE(q.base_plan_correction,0)) AS rev + FROM v3.budget_line bl + CROSS JOIN (VALUES (1), (2), (3), (4)) AS qrtrs(qrtr) + + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.budget_line_quarter q ON q.line_id = bl.id AND q.quarter = qrtrs.qrtr + LEFT JOIN v3.ckk ck ON bl.id = ck.line_id + LEFT JOIN v3.contract_detail cd ON bl.id = cd.line_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + GROUP BY bl.expense_item_id, qrtrs.qrtr + ), + tw_agg AS ( + SELECT tw.id, + SUM(a.sp1) AS sp1, SUM(a.sp2) AS sp2, SUM(a.sp3) AS sp3, SUM(a.sp4) AS sp4, + SUM(a.sd1) AS sd1, SUM(a.sd2) AS sd2, SUM(a.sd3) AS sd3, SUM(a.sd4) AS sd4, + SUM(a.sg1) AS sg1, SUM(a.sg2) AS sg2, SUM(a.sg3) AS sg3, SUM(a.sg4) AS sg4, + SUM(a.sr1) AS sr1, SUM(a.sr2) AS sr2, SUM(a.sr3) AS sr3, SUM(a.sr4) AS sr4, + SUM(a.bk1) AS bk1, SUM(a.bk2) AS bk2, SUM(a.bk3) AS bk3, SUM(a.bk4) AS bk4, + SUM(a.bkc1) AS bkc1, SUM(a.bkc2) AS bkc2, SUM(a.bkc3) AS bkc3, SUM(a.bkc4) AS bkc4, + SUM(a.bn1) AS bn1, SUM(a.bn2) AS bn2, SUM(a.bn3) AS bn3, SUM(a.bn4) AS bn4 + FROM tw LEFT JOIN agg a ON a.eid = ANY(tw.desc_ids) + GROUP BY tw.id + ), + tw_aq1 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq2) AS tq2, SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=1 GROUP BY tw.id + ), + tw_aq2 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq3) AS tq3, SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=2 GROUP BY tw.id + ), + tw_aq3 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, + SUM(b.tq4) AS tq4, SUM(b.te) AS te, SUM(b.rev) AS rev, + SUM(b.tda) AS tda, SUM(b.tdp) AS tdp, SUM(b.terf) AS terf + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=3 GROUP BY tw.id + ), + tw_aq4 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.arv) AS arv, + SUM(b.pa) AS pa, SUM(b.bk) AS bk, + SUM(b.m1) AS m1, SUM(b.m2) AS m2, SUM(b.m3) AS m3, SUM(b.spod) AS spod, + SUM(b.te) AS te, SUM(b.rev) AS rev + FROM tw LEFT JOIN aq_q b ON b.eid = ANY(tw.desc_ids) AND b.quarter=4 GROUP BY tw.id + ) + + SELECT * FROM ( + SELECT + 'INPUT'::VARCHAR, 3, f.lid::INT, + f.sc, f.ic, f.ng, COALESCE(f.bname, f.ename), f.vsp_id, f.vsp_addr, + CASE WHEN s_plan THEN f.pq1 END, CASE WHEN s_plan THEN f.pq2 END, + CASE WHEN s_plan THEN f.pq3 END, CASE WHEN s_plan THEN f.pq4 END, + CASE WHEN s_plan THEN f.pq1+f.pq2+f.pq3+f.pq4 END, CASE WHEN s_plan THEN f.pcmt END, + CASE WHEN s_seq_d THEN f.dq1 END, CASE WHEN s_seq_d THEN f.dq2 END, + CASE WHEN s_seq_d THEN f.dq3 END, CASE WHEN s_seq_d THEN f.dq4 END, + CASE WHEN s_seq_d THEN f.dq1+f.dq2+f.dq3+f.dq4 END, CASE WHEN s_seq_d THEN f.djust END, + CASE WHEN s_seq_s THEN f.gq1 END, CASE WHEN s_seq_s THEN f.gq2 END, + CASE WHEN s_seq_s THEN f.gq3 END, CASE WHEN s_seq_s THEN f.gq4 END, + CASE WHEN s_seq_s THEN f.gq1+f.gq2+f.gq3+f.gq4 END, CASE WHEN s_seq_s THEN f.gjust END, + CASE WHEN s_appr THEN f.ap1 END, CASE WHEN s_appr THEN f.ap2 END, + CASE WHEN s_appr THEN f.ap3 END, CASE WHEN s_appr THEN f.ap4 END, + CASE WHEN s_appr THEN f.ap1+f.ap2+f.ap3+f.ap4 END, + CASE WHEN s_cd THEN f.cd_cp END, CASE WHEN s_cd THEN f.cd_ref END, + CASE WHEN s_cd THEN f.cd_dt END, CASE WHEN s_cd THEN f.cd_subj END, + CASE WHEN s_cd THEN f.cd_cur END, CASE WHEN s_cd THEN f.cd_ceil END, + CASE WHEN s_cd THEN f.cd_vat END, CASE WHEN s_cd THEN f.cd_dl END, + CASE WHEN s_cd THEN f.cd_sch END, CASE WHEN s_cd THEN f.cd_act END, + CASE WHEN s_cd THEN f.cd_cmt END, + CASE WHEN s_book THEN f.bk1 END, CASE WHEN s_book THEN f.bk2 END, + CASE WHEN s_book THEN f.bk3 END, CASE WHEN s_book THEN f.bk4 END, + CASE WHEN s_book THEN f.bn1 END, CASE WHEN s_book THEN f.bn2 END, + CASE WHEN s_book THEN f.bn3 END, CASE WHEN s_book THEN f.bn4 END, + CASE WHEN s_q1 THEN f.q1_ac END, CASE WHEN s_q1 THEN f.q1_as END, CASE WHEN s_q1 THEN f.q1_arv END, + CASE WHEN s_q1 THEN f.q1_acmt END, CASE WHEN s_q1 THEN f.cp1 END, + CASE WHEN s_q1 THEN f.q1_pd END, CASE WHEN s_q1 THEN f.q1_pa END, + CASE WHEN s_q1 THEN f.q1_pcmt END, + CASE WHEN s_q1 THEN f.q1_pact END, CASE WHEN s_q1 THEN f.q1_book END, + CASE WHEN s_q1 THEN f.q1_m1 END, CASE WHEN s_q1 THEN f.q1_m2 END, CASE WHEN s_q1 THEN f.q1_m3 END, + CASE WHEN s_q1 THEN f.aq1 END, + CASE WHEN s_q1 THEN f.cp1 - COALESCE(f.q1_book,0) END, -- residual_after_booking + CASE WHEN s_q1 THEN f.cp1 - f.aq1 END, -- residual_after_actual + CASE WHEN s_q1 THEN f.q1_tq2 END, + CASE WHEN s_q1 THEN f.q1_tda END, CASE WHEN s_q1 THEN f.q1_tdp END, CASE WHEN s_q1 THEN f.q1_terf END, + CASE WHEN s_q1 THEN f.q1_tnc END, + CASE WHEN s_q1 THEN f.q1_tq3 END, CASE WHEN s_q1 THEN f.q1_tq4 END, + CASE WHEN s_q1 THEN f.q1_tfc END, + CASE WHEN s_q1 THEN f.q1_te END, + CASE WHEN s_q1 THEN + COALESCE(f.q1_tda,0)+COALESCE(f.q1_tdp,0)+COALESCE(f.q1_terf,0) + +COALESCE(f.q1_tq3,0)+COALESCE(f.q1_tq4,0)+COALESCE(f.q1_te,0) + END, + CASE WHEN s_q2 THEN f.q2_tc END, CASE WHEN s_q2 THEN f.q2_bc END, CASE WHEN s_q2 THEN f.q2_bcc END, + CASE WHEN s_q2 THEN f.q2_rinc END, CASE WHEN s_q2 THEN f.q2_rseq END, + CASE WHEN s_q2 THEN f.q2_rcmt END, + CASE WHEN s_q2 THEN f.np2 END, + CASE WHEN s_q2 THEN f.q2_ac END, CASE WHEN s_q2 THEN f.q2_as END, CASE WHEN s_q2 THEN f.q2_arv END, + CASE WHEN s_q2 THEN f.q2_acmt END, + CASE WHEN s_q2 THEN f.cp2 END, + CASE WHEN s_q2 THEN f.q2_pd END, CASE WHEN s_q2 THEN f.q2_pa END, + CASE WHEN s_q2 THEN f.q2_pcmt END, CASE WHEN s_q2 THEN f.q2_pact END, + CASE WHEN s_q2 THEN f.q2_book END, + CASE WHEN s_q2 THEN f.q2_m1 END, CASE WHEN s_q2 THEN f.q2_m2 END, CASE WHEN s_q2 THEN f.q2_m3 END, + CASE WHEN s_q2 THEN f.aq2 END, + CASE WHEN s_q2 THEN f.cp2 - COALESCE(f.q2_book,0) END, + CASE WHEN s_q2 THEN f.cp2 - f.aq2 END, + CASE WHEN s_q2 THEN f.q2_tq3 END, + CASE WHEN s_q2 THEN f.q2_tda END, CASE WHEN s_q2 THEN f.q2_tdp END, CASE WHEN s_q2 THEN f.q2_terf END, + CASE WHEN s_q2 THEN f.q2_tnc END, + CASE WHEN s_q2 THEN f.q2_tq4 END, + CASE WHEN s_q2 THEN f.q2_tfc END, + CASE WHEN s_q2 THEN f.q2_te END, + CASE WHEN s_q2 THEN + COALESCE(f.q2_tda,0)+COALESCE(f.q2_tdp,0)+COALESCE(f.q2_terf,0) + +COALESCE(f.q2_tq4,0)+COALESCE(f.q2_te,0) + END, + CASE WHEN s_q3 THEN f.q3_tc END, CASE WHEN s_q3 THEN f.q3_bc END, + CASE WHEN s_q3 THEN f.q3_bcc END, + CASE WHEN s_q3 THEN f.q3_rinc END, CASE WHEN s_q3 THEN f.q3_rseq END, + CASE WHEN s_q3 THEN f.q3_rcmt END, + CASE WHEN s_q3 THEN f.np3 END, + CASE WHEN s_q3 THEN f.q3_ac END, CASE WHEN s_q3 THEN f.q3_as END, CASE WHEN s_q3 THEN f.q3_arv END, + CASE WHEN s_q3 THEN f.q3_acmt END, + CASE WHEN s_q3 THEN f.cp3 END, + CASE WHEN s_q3 THEN f.q3_pd END, CASE WHEN s_q3 THEN f.q3_pa END, + CASE WHEN s_q3 THEN f.q3_pcmt END, CASE WHEN s_q3 THEN f.q3_pact END, + CASE WHEN s_q3 THEN f.q3_book END, + CASE WHEN s_q3 THEN f.q3_m1 END, CASE WHEN s_q3 THEN f.q3_m2 END, CASE WHEN s_q3 THEN f.q3_m3 END, + CASE WHEN s_q3 THEN f.aq3 END, + CASE WHEN s_q3 THEN f.cp3 - COALESCE(f.q3_book,0) END, + CASE WHEN s_q3 THEN f.cp3 - f.aq3 END, + CASE WHEN s_q3 THEN f.q3_tq4 END, + CASE WHEN s_q3 THEN f.q3_tda END, CASE WHEN s_q3 THEN f.q3_tdp END, CASE WHEN s_q3 THEN f.q3_terf END, + CASE WHEN s_q3 THEN f.q3_tnc END, + CASE WHEN s_q3 THEN f.q3_te END, + CASE WHEN s_q3 THEN + COALESCE(f.q3_tda,0)+COALESCE(f.q3_tdp,0)+COALESCE(f.q3_terf,0) + +COALESCE(f.q3_te,0) + END, + CASE WHEN s_q4 THEN f.q4_tc END, CASE WHEN s_q4 THEN f.q4_bc END, + CASE WHEN s_q4 THEN f.q4_bcc END, + CASE WHEN s_q4 THEN f.q4_rinc END, CASE WHEN s_q4 THEN f.q4_rseq END, + CASE WHEN s_q4 THEN f.q4_rcmt END, + CASE WHEN s_q4 THEN f.np4 END, + CASE WHEN s_q4 THEN f.q4_ac END, CASE WHEN s_q4 THEN f.q4_as END, CASE WHEN s_q4 THEN f.q4_arv END, + CASE WHEN s_q4 THEN f.q4_acmt END, + CASE WHEN s_q4 THEN f.cp4 END, + CASE WHEN s_q4 THEN f.q4_pd END, CASE WHEN s_q4 THEN f.q4_pa END, + CASE WHEN s_q4 THEN f.q4_pcmt END, CASE WHEN s_q4 THEN f.q4_pact END, + CASE WHEN s_q4 THEN f.q4_book END, + CASE WHEN s_q4 THEN f.q4_m1 END, CASE WHEN s_q4 THEN f.q4_m2 END, CASE WHEN s_q4 THEN f.q4_m3 END, + CASE WHEN s_q4 THEN f.q4_spod END, + CASE WHEN s_q4 THEN f.aq4 END, + CASE WHEN s_q4 THEN f.cp4 - COALESCE(f.q4_book,0) END, + CASE WHEN s_q4 THEN f.cp4 - f.aq4 END, + CASE WHEN s_q4 THEN f.q4_te END, + CASE WHEN s_q4 THEN f.q4_te END, + CASE WHEN s_tot THEN f.aq1+f.aq2+f.aq3+f.aq4 END, + CASE WHEN s_tot THEN COALESCE(f.q1_pa,0)+COALESCE(f.q2_pa,0)+COALESCE(f.q3_pa,0)+COALESCE(f.q4_pa,0) END, + CASE WHEN s_tot THEN f.cp4 - f.aq4 END, + + f.io, + CASE WHEN s_cs THEN f.cs_total END, CASE WHEN s_cs THEN f.cs_cp END, + CASE WHEN s_cs THEN f.cs_dl END, CASE WHEN s_cs THEN f.cs_cmt END, + CASE WHEN s_cs THEN f.cs_fy1 END, CASE WHEN s_cs THEN f.cs_fy2 END, + CASE WHEN s_cs THEN f.cs_oth END, + CASE WHEN s_al THEN f.al_cref END, CASE WHEN s_al THEN f.al_purp END, + CASE WHEN s_al THEN f.al_io END, CASE WHEN s_al THEN f.al_prop END, + CASE WHEN s_ca THEN f.ca_amt END, CASE WHEN s_ca THEN f.ca_proto END, + CASE WHEN s_ca THEN f.ca_note END, + CASE WHEN s_ckk THEN f.ckk_ceil END, CASE WHEN s_ckk THEN f.ckk_rf END, + CASE WHEN s_ckk THEN f.ckk_dl END, CASE WHEN s_ckk THEN f.ckk_pplan END, + CASE WHEN s_ckk THEN f.ckk_pmeth END, CASE WHEN s_ckk THEN f.ckk_cmt END, + CASE WHEN s_cd THEN f.cd_add END, CASE WHEN s_cd THEN f.cd_e1 END, + CASE WHEN s_cd THEN f.cd_e2 END, CASE WHEN s_cd THEN f.cd_e3 END, + CASE WHEN s_cd THEN f.cd_e4 END, CASE WHEN s_cd THEN f.cd_rf END, + CASE WHEN s_cd THEN f.cd_rate END, CASE WHEN s_cd THEN f.cd_fx END, + CASE WHEN s_q1 THEN f.q1_arf END, CASE WHEN s_q1 THEN f.q1_pho END, CASE WHEN s_q1 THEN f.q1_prf END, + CASE WHEN s_q2 THEN f.q2_arf END, CASE WHEN s_q2 THEN f.q2_pho END, CASE WHEN s_q2 THEN f.q2_prf END, + CASE WHEN s_q3 THEN f.q3_arf END, CASE WHEN s_q3 THEN f.q3_pho END, CASE WHEN s_q3 THEN f.q3_prf END, + CASE WHEN s_q4 THEN f.q4_arf END, CASE WHEN s_q4 THEN f.q4_pho END, CASE WHEN s_q4 THEN f.q4_prf END, + + f.tree_path || ARRAY[f.lid::INT] AS _sort + FROM final_input f + + UNION ALL + + SELECT + CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' WHEN 2 THEN 'ITEM' WHEN 3 THEN 'SUB_ITEM' 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, + CASE WHEN s_plan THEN ta.sp1 END, CASE WHEN s_plan THEN ta.sp2 END, + CASE WHEN s_plan THEN ta.sp3 END, CASE WHEN s_plan THEN ta.sp4 END, + CASE WHEN s_plan THEN COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0) END, + NULL::VARCHAR, + CASE WHEN s_seq_d THEN ta.sd1 END, CASE WHEN s_seq_d THEN ta.sd2 END, + CASE WHEN s_seq_d THEN ta.sd3 END, CASE WHEN s_seq_d THEN ta.sd4 END, + CASE WHEN s_seq_d THEN COALESCE(ta.sd1,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sd4,0) END, + NULL::VARCHAR, + CASE WHEN s_seq_s THEN ta.sg1 END, CASE WHEN s_seq_s THEN ta.sg2 END, + CASE WHEN s_seq_s THEN ta.sg3 END, CASE WHEN s_seq_s THEN ta.sg4 END, + CASE WHEN s_seq_s THEN COALESCE(ta.sg1,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sg4,0) END, + NULL::VARCHAR, + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END, + CASE WHEN s_appr THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0) + +COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0) + +COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0) + +COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) END, + NULL::VARCHAR, NULL::VARCHAR, NULL::DATE, NULL::VARCHAR, + NULL::VARCHAR, NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + CASE WHEN s_book THEN ta.bk1 END, CASE WHEN s_book THEN ta.bk2 END, + CASE WHEN s_book THEN ta.bk3 END, CASE WHEN s_book THEN ta.bk4 END, + CASE WHEN s_book THEN ta.bn1 END, CASE WHEN s_book THEN ta.bn2 END, + CASE WHEN s_book THEN ta.bn3 END, CASE WHEN s_book THEN ta.bn4 END, + CASE WHEN s_q1 THEN tb1.ac END, CASE WHEN s_q1 THEN tb1.as_v END, CASE WHEN s_q1 THEN tb1.arv END, + NULL::VARCHAR, + CASE WHEN s_q1 THEN COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0) END, + NULL::DATE, + CASE WHEN s_q1 THEN tb1.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q1 THEN tb1.bk END, -- booking (rollup) + CASE WHEN s_q1 THEN tb1.m1 END, CASE WHEN s_q1 THEN tb1.m2 END, CASE WHEN s_q1 THEN tb1.m3 END, + CASE WHEN s_q1 THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) END, + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0)) + - COALESCE(tb1.bk,0) + END, + CASE WHEN s_q1 THEN + (COALESCE(ta.sp1,0)+COALESCE(ta.sd1,0)+COALESCE(ta.sg1,0)+COALESCE(ta.sr1,0)+COALESCE(tb1.ac,0)+COALESCE(tb1.as_v,0)+COALESCE(tb1.arv,0)) + - (COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0)) + END, + CASE WHEN s_q1 THEN tb1.tq2 END, + CASE WHEN s_q1 THEN tb1.tda END, CASE WHEN s_q1 THEN tb1.tdp END, CASE WHEN s_q1 THEN tb1.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q1 THEN tb1.tq3 END, CASE WHEN s_q1 THEN tb1.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q1 THEN tb1.te END, + CASE WHEN s_q1 THEN + COALESCE(tb1.tda,0)+COALESCE(tb1.tdp,0)+COALESCE(tb1.terf,0) + +COALESCE(tb1.tq3,0)+COALESCE(tb1.tq4,0)+COALESCE(tb1.te,0) + END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, -- revision_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0) + END, + CASE WHEN s_q2 THEN tb2.ac END, CASE WHEN s_q2 THEN tb2.as_v END, CASE WHEN s_q2 THEN tb2.arv END, + NULL::VARCHAR, -- adj_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0) + END, + NULL::DATE, + CASE WHEN s_q2 THEN tb2.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q2 THEN tb2.bk END, -- booking (rollup) + CASE WHEN s_q2 THEN tb2.m1 END, CASE WHEN s_q2 THEN tb2.m2 END, CASE WHEN s_q2 THEN tb2.m3 END, + CASE WHEN s_q2 THEN COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) END, + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0)) + - COALESCE(tb2.bk,0) + END, + CASE WHEN s_q2 THEN + (COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sg2,0)+COALESCE(ta.sr2,0)+COALESCE(tb1.tq2,0)+COALESCE(tb2.rev,0)+COALESCE(tb2.ac,0)+COALESCE(tb2.as_v,0)+COALESCE(tb2.arv,0)) + - (COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0)) + END, + CASE WHEN s_q2 THEN tb2.tq3 END, + CASE WHEN s_q2 THEN tb2.tda END, CASE WHEN s_q2 THEN tb2.tdp END, CASE WHEN s_q2 THEN tb2.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q2 THEN tb2.tq4 END, + NULL::VARCHAR, -- far comment + CASE WHEN s_q2 THEN tb2.te END, + CASE WHEN s_q2 THEN + COALESCE(tb2.tda,0)+COALESCE(tb2.tdp,0)+COALESCE(tb2.terf,0) + +COALESCE(tb2.tq4,0)+COALESCE(tb2.te,0) + END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0) + END, + CASE WHEN s_q3 THEN tb3.ac END, CASE WHEN s_q3 THEN tb3.as_v END, CASE WHEN s_q3 THEN tb3.arv END, + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0) + END, + NULL::DATE, + CASE WHEN s_q3 THEN tb3.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q3 THEN tb3.bk END, -- booking (rollup) + CASE WHEN s_q3 THEN tb3.m1 END, CASE WHEN s_q3 THEN tb3.m2 END, CASE WHEN s_q3 THEN tb3.m3 END, + CASE WHEN s_q3 THEN COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) END, + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0)) + - COALESCE(tb3.bk,0) + END, + CASE WHEN s_q3 THEN + (COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sg3,0)+COALESCE(ta.sr3,0)+COALESCE(tb1.tq3,0)+COALESCE(tb2.tq3,0)+COALESCE(tb3.rev,0)+COALESCE(tb3.ac,0)+COALESCE(tb3.as_v,0)+COALESCE(tb3.arv,0)) + - (COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0)) + END, + CASE WHEN s_q3 THEN tb3.tq4 END, + CASE WHEN s_q3 THEN tb3.tda END, CASE WHEN s_q3 THEN tb3.tdp END, CASE WHEN s_q3 THEN tb3.terf END, + NULL::VARCHAR, -- transfer_next_comment (text, no rollup) + CASE WHEN s_q3 THEN tb3.te END, + CASE WHEN s_q3 THEN + COALESCE(tb3.tda,0)+COALESCE(tb3.tdp,0)+COALESCE(tb3.terf,0) + +COALESCE(tb3.te,0) + END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0) + END, + CASE WHEN s_q4 THEN tb4.ac END, CASE WHEN s_q4 THEN tb4.as_v END, CASE WHEN s_q4 THEN tb4.arv END, + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0) + END, + NULL::DATE, + CASE WHEN s_q4 THEN tb4.pa END, + NULL::VARCHAR, -- pay_comment + NULL::VARCHAR, -- pay_act + CASE WHEN s_q4 THEN tb4.bk END, -- booking (rollup) + CASE WHEN s_q4 THEN tb4.m1 END, CASE WHEN s_q4 THEN tb4.m2 END, CASE WHEN s_q4 THEN tb4.m3 END, + CASE WHEN s_q4 THEN tb4.spod END, + CASE WHEN s_q4 THEN COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - COALESCE(tb4.bk,0) + END, + CASE WHEN s_q4 THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0)+COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0)+COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)) + END, + CASE WHEN s_q4 THEN tb4.te END, + CASE WHEN s_q4 THEN tb4.te END, + CASE WHEN s_tot THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) + +COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) + +COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) + +COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, + CASE WHEN s_tot THEN COALESCE(tb1.pa,0)+COALESCE(tb2.pa,0)+COALESCE(tb3.pa,0)+COALESCE(tb4.pa,0) END, + CASE WHEN s_tot THEN + (COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.sg4,0)+COALESCE(ta.sr4,0) + +COALESCE(tb1.tq4,0)+COALESCE(tb2.tq4,0)+COALESCE(tb3.tq4,0)+COALESCE(tb4.rev,0) + +COALESCE(tb4.ac,0)+COALESCE(tb4.as_v,0)+COALESCE(tb4.arv,0)) + - (COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0)) + END, + + NULL::VARCHAR, -- internal_order + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- contract_summary + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, + NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- allocation + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, -- collegial + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, -- ckk + NULL::VARCHAR, NULL::VARCHAR, + NULL::VARCHAR, CASE WHEN s_cd THEN ta.bkc1 END, CASE WHEN s_cd THEN ta.bkc2 END, + CASE WHEN s_cd THEN ta.bkc3 END, CASE WHEN s_cd THEN ta.bkc4 END, + NULL::VARCHAR, NULL::NUMERIC, NULL::NUMERIC, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q1 adj_rf/pho/prf + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q2 + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q3 + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, -- q4 + + t.path AS _sort + FROM tw t + LEFT JOIN tw_agg ta ON ta.id = t.id + LEFT JOIN tw_aq1 tb1 ON tb1.id = t.id + LEFT JOIN tw_aq2 tb2 ON tb2.id = t.id + LEFT JOIN tw_aq3 tb3 ON tb3.id = t.id + LEFT JOIN tw_aq4 tb4 ON tb4.id = t.id + ) sub + ORDER BY sub._sort; +END; +$function$ +; + + + +-- 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 + ), + 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_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, + COALESCE(p.plan_q1,0) AS pq1, COALESCE(p.plan_q2,0) AS pq2, + COALESCE(p.plan_q3,0) AS pq3, COALESCE(p.plan_q4,0) AS pq4, + p.comment AS pcmt, + COALESCE(sd.adj_q1,0) AS dq1, COALESCE(sd.adj_q2,0) AS dq2, + COALESCE(sd.adj_q3,0) AS dq3, COALESCE(sd.adj_q4,0) AS dq4, + sd.justification AS djust, + COALESCE(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, + 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, + 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, + 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, + al.contract_ref AS al_ref, al.allocation_purpose AS al_purp, + col.approved_amount AS col_amt, col.protocol_reference AS col_pr, col.note AS col_note, + 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, + COALESCE( + q1.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q1 + WHEN ck.expenses_q1 > 0 THEN ck.expenses_q1 + ELSE cd.expenses_q1 + END + + ) AS q1_book, + q1.actual_m1 AS q1_m1, q1.actual_m2 AS q1_m2, q1.actual_m3 AS q1_m3, + q1.transfer_to_q2 AS q1_tq2, q1.transfer_to_q3 AS q1_tq3, q1.transfer_to_q4 AS q1_tq4, + q1.transfer_to_economy AS q1_te, + q1.transfer_delay_acts AS q1_tda, q1.transfer_delay_procurement AS q1_tdp, + q1.transfer_economy_rf AS q1_terf, + q1.transfer_next_comment AS q1_tnc, q1.transfer_far_comment AS q1_tfc, + q2.target_change AS q2_tc, q2.base_plan_correction AS q2_bc, q2.base_plan_correction_comment AS q2_bcc, + q2.plan_revision_increase AS q2_rinc, q2.plan_revision_sequester AS q2_rseq, + q2.plan_revision_comment AS q2_rcmt, + q2.adj_current AS q2_ac, q2.adj_ssp AS q2_as, q2.adj_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, + COALESCE( + q2.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q2 + WHEN ck.expenses_q2 > 0 THEN ck.expenses_q2 + ELSE cd.expenses_q2 + END + + ) AS q2_book, + q2.actual_m1 AS q2_m1, q2.actual_m2 AS q2_m2, q2.actual_m3 AS q2_m3, + q2.transfer_to_q3 AS q2_tq3, q2.transfer_to_q4 AS q2_tq4, q2.transfer_to_economy AS q2_te, + q2.transfer_delay_acts AS q2_tda, q2.transfer_delay_procurement AS q2_tdp, + q2.transfer_economy_rf AS q2_terf, + q2.transfer_next_comment AS q2_tnc, q2.transfer_far_comment AS q2_tfc, + q3.target_change AS q3_tc, q3.base_plan_correction AS q3_bc, + q3.base_plan_correction_comment AS q3_bcc, + q3.plan_revision_increase AS q3_rinc, q3.plan_revision_sequester AS q3_rseq, + q3.plan_revision_comment AS q3_rcmt, + q3.adj_current AS q3_ac, q3.adj_ssp AS q3_as, q3.adj_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, + COALESCE( + q3.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q3 + WHEN ck.expenses_q3 > 0 THEN ck.expenses_q3 + ELSE cd.expenses_q3 + END + + ) AS q3_book, + q3.actual_m1 AS q3_m1, q3.actual_m2 AS q3_m2, q3.actual_m3 AS q3_m3, + q3.transfer_to_q4 AS q3_tq4, q3.transfer_to_economy AS q3_te, + q3.transfer_delay_acts AS q3_tda, q3.transfer_delay_procurement AS q3_tdp, + q3.transfer_economy_rf AS q3_terf, q3.transfer_next_comment AS q3_tnc, + q4.target_change AS q4_tc, q4.base_plan_correction AS q4_bc, + q4.base_plan_correction_comment AS q4_bcc, + q4.plan_revision_increase AS q4_rinc, q4.plan_revision_sequester AS q4_rseq, + q4.plan_revision_comment AS q4_rcmt, + q4.adj_current AS q4_ac, q4.adj_ssp AS q4_as, q4.adj_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, + COALESCE( + q4.booking_amount, + CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q4 + WHEN ck.expenses_q4 > 0 THEN ck.expenses_q4 + ELSE cd.expenses_q4 + END + + ) AS q4_book, + q4.actual_m1 AS q4_m1, q4.actual_m2 AS q4_m2, q4.actual_m3 AS q4_m3, q4.actual_spod AS q4_spod, + q4.transfer_to_economy AS q4_te, + COALESCE(p.plan_q1,0)+COALESCE(sd.adj_q1,0)+COALESCE(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 + ), + enriched AS ( + SELECT b.*, + 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, + 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.*, + 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, + 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, + 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 + ), + + 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(cd.expenses_q1) AS bkc1, SUM(cd.expenses_q2) AS bkc2, SUM(cd.expenses_q3) AS bkc3, SUM(cd.expenses_q4) AS bkc4, + SUM(ck.expenses_next_year_q1) AS bn1, SUM(ck.expenses_next_year_q2) AS bn2, + SUM(ck.expenses_next_year_q3) AS bn3, SUM(ck.expenses_next_year_q4) AS bn4 + FROM v3.budget_line bl + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + LEFT JOIN v3.plan p ON p.line_id = bl.id AND s_need_ap + LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor='DFIP' AND s_need_ap + LEFT JOIN v3.reserve r ON r.line_id = bl.id AND s_need_ap + LEFT JOIN v3.ckk ck ON ck.line_id = bl.id AND s_book + LEFT JOIN v3.contract_detail cd ON cd.line_id = bl.id AND s_cd + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + AND (s_need_ap OR s_book OR s_cd) + GROUP BY bl.expense_item_id + ), + aq_q AS ( + SELECT bl.expense_item_id AS eid, qrtrs.qrtr as quarter, + SUM(q.adj_current) AS ac, SUM(q.adj_ssp) AS as_v, SUM(q.adj_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( + COALESCE( + q.booking_amount, + CASE + WHEN qrtrs.qrtr = 1 + THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q1 + WHEN ck.expenses_q1 > 0 THEN ck.expenses_q1 + ELSE cd.expenses_q1 + END + WHEN qrtrs.qrtr = 2 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q2 + WHEN ck.expenses_q2 > 0 THEN ck.expenses_q2 + ELSE cd.expenses_q2 + END + WHEN qrtrs.qrtr = 3 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q3 + WHEN ck.expenses_q3 > 0 THEN ck.expenses_q3 + ELSE cd.expenses_q3 + END + WHEN qrtrs.qrtr = 4 THEN CASE + WHEN p_sheet = 'AHR' THEN ck.expenses_q4 + WHEN ck.expenses_q4 > 0 THEN ck.expenses_q4 + ELSE cd.expenses_q4 + END + END + + )) AS 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, + SUM(COALESCE(q.plan_revision_increase,0) + + COALESCE(q.plan_revision_sequester,0) + + COALESCE(q.target_change,0) + + COALESCE(q.base_plan_correction,0)) AS rev + FROM v3.budget_line bl + CROSS JOIN (VALUES (1), (2), (3), (4)) AS qrtrs(qrtr) + JOIN v3.expense_item ei ON ei.id = bl.expense_item_id + JOIN v3.budget_line_quarter q ON q.line_id = bl.id AND q.quarter = qrtrs.qrtr + LEFT JOIN v3.ckk ck ON bl.id = ck.line_id + LEFT JOIN v3.contract_detail cd ON bl.id = cd.line_id + WHERE bl.budget_form_id = p_form_id AND ei.sheet = p_sheet + GROUP BY bl.expense_item_id, qrtrs.qrtr + ), + tw_agg AS ( + SELECT tw.id, + SUM(a.sp1) AS sp1, SUM(a.sp2) AS sp2, SUM(a.sp3) AS sp3, SUM(a.sp4) AS sp4, + SUM(a.sd1) AS sd1, SUM(a.sd2) AS sd2, SUM(a.sd3) AS sd3, SUM(a.sd4) AS sd4, + SUM(a.sr1) AS sr1, SUM(a.sr2) AS sr2, SUM(a.sr3) AS sr3, SUM(a.sr4) AS sr4, + SUM(a.bk1) AS bk1, SUM(a.bk2) AS bk2, SUM(a.bk3) AS bk3, SUM(a.bk4) AS bk4, + SUM(a.bkc1) AS bkc1, SUM(a.bkc2) AS bkc2, SUM(a.bkc3) AS bkc3, SUM(a.bkc4) AS bkc4, + SUM(a.bn1) AS bn1, SUM(a.bn2) AS bn2, SUM(a.bn3) AS bn3, SUM(a.bn4) AS bn4 + FROM tw LEFT JOIN agg a ON a.eid = ANY(tw.desc_ids) + GROUP BY tw.id + ), + tw_aq1 AS ( + SELECT tw.id, SUM(b.ac) AS ac, SUM(b.as_v) AS as_v, SUM(b.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 + ) + + 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, + CASE WHEN s_plan THEN f.pq1 END, CASE WHEN s_plan THEN f.pq2 END, + CASE WHEN s_plan THEN f.pq3 END, CASE WHEN s_plan THEN f.pq4 END, + CASE WHEN s_plan THEN f.pq1+f.pq2+f.pq3+f.pq4 END, CASE WHEN s_plan THEN f.pcmt END, + CASE WHEN s_seq_d THEN f.dq1 END, CASE WHEN s_seq_d THEN f.dq2 END, + CASE WHEN s_seq_d THEN f.dq3 END, CASE WHEN s_seq_d THEN f.dq4 END, + CASE WHEN s_seq_d THEN f.dq1+f.dq2+f.dq3+f.dq4 END, CASE WHEN s_seq_d THEN f.djust END, + CASE WHEN s_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_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, + CASE WHEN s_al THEN f.al_ref END, CASE WHEN s_al THEN f.al_purp END, + 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, CASE WHEN s_rsv THEN f.rjust END, + 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, + 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, + 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, + CASE WHEN s_book THEN f.bk1 END, CASE WHEN s_book THEN f.bk2 END, + CASE WHEN s_book THEN f.bk3 END, CASE WHEN s_book THEN f.bk4 END, + CASE WHEN s_book THEN f.bn1 END, CASE WHEN s_book THEN f.bn2 END, + CASE WHEN s_book THEN f.bn3 END, CASE WHEN s_book THEN f.bn4 END, + CASE WHEN s_q1 THEN f.q1_ac END, CASE WHEN s_q1 THEN f.q1_as END, + CASE WHEN s_q1 THEN f.q1_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, + 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, + 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, + 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, + 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, + 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 + + 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, + CASE WHEN s_plan THEN ta.sp1 END, CASE WHEN s_plan THEN ta.sp2 END, + CASE WHEN s_plan THEN ta.sp3 END, CASE WHEN s_plan THEN ta.sp4 END, + CASE WHEN s_plan THEN COALESCE(ta.sp1,0)+COALESCE(ta.sp2,0)+COALESCE(ta.sp3,0)+COALESCE(ta.sp4,0) END, + NULL::VARCHAR, + CASE WHEN s_seq_d THEN ta.sd1 END, CASE WHEN s_seq_d THEN ta.sd2 END, + CASE WHEN s_seq_d THEN ta.sd3 END, CASE WHEN s_seq_d THEN ta.sd4 END, + CASE WHEN s_seq_d THEN COALESCE(ta.sd1,0)+COALESCE(ta.sd2,0)+COALESCE(ta.sd3,0)+COALESCE(ta.sd4,0) END, + NULL::VARCHAR, + CASE WHEN s_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, + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, NULL::VARCHAR, + NULL::NUMERIC, NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, + NULL::VARCHAR, NULL::VARCHAR, + 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, + NULL::NUMERIC, NULL::VARCHAR, NULL::VARCHAR, + 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, + 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, + CASE WHEN s_book THEN ta.bk1 END, CASE WHEN s_book THEN ta.bk2 END, + CASE WHEN s_book THEN ta.bk3 END, CASE WHEN s_book THEN ta.bk4 END, + CASE WHEN s_book THEN ta.bn1 END, CASE WHEN s_book THEN ta.bn2 END, + CASE WHEN s_book THEN ta.bn3 END, CASE WHEN s_book THEN ta.bn4 END, + CASE WHEN s_q1 THEN tb1.ac END, CASE WHEN s_q1 THEN tb1.as_v END, + CASE WHEN s_q1 THEN tb1.arf 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.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, + 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, + 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, + CASE WHEN s_q1 THEN + COALESCE(tb1.tq2,0)+COALESCE(tb1.tq3,0)+COALESCE(tb1.tq4,0)+COALESCE(tb1.te,0) + END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, -- revision_comment + CASE WHEN s_q2 THEN + COALESCE(ta.sp2,0)+COALESCE(ta.sd2,0)+COALESCE(ta.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 + 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, + 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, + 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, + CASE WHEN s_q2 THEN + COALESCE(tb2.tq3,0)+COALESCE(tb2.tq4,0)+COALESCE(tb2.te,0) + END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q3 THEN + COALESCE(ta.sp3,0)+COALESCE(ta.sd3,0)+COALESCE(ta.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, + 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, + 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, + 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, + CASE WHEN s_q3 THEN + COALESCE(tb3.tq4,0)+COALESCE(tb3.te,0) + END, + NULL::NUMERIC, NULL::NUMERIC, NULL::VARCHAR, -- target_change/base_correction/comment + NULL::NUMERIC, NULL::NUMERIC, -- revision_inc/revision_seq + NULL::VARCHAR, + CASE WHEN s_q4 THEN + COALESCE(ta.sp4,0)+COALESCE(ta.sd4,0)+COALESCE(ta.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, + 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, + 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, + 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, + CASE WHEN s_q4 THEN COALESCE(tb4.te,0) END, + CASE WHEN s_tot THEN COALESCE(tb1.m1,0)+COALESCE(tb1.m2,0)+COALESCE(tb1.m3,0) + +COALESCE(tb2.m1,0)+COALESCE(tb2.m2,0)+COALESCE(tb2.m3,0) + +COALESCE(tb3.m1,0)+COALESCE(tb3.m2,0)+COALESCE(tb3.m3,0) + +COALESCE(tb4.m1,0)+COALESCE(tb4.m2,0)+COALESCE(tb4.m3,0)+COALESCE(tb4.spod,0) END, -- 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$ +; + + + +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, p_user_id integer DEFAULT NULL::integer) + 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; + v_user_id INT; +BEGIN + 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; + + v_user_id := p_user_id; + IF p_user_id IS NULL THEN + v_user_id := v3.current_user_id(); + END IF; + RETURN QUERY + SELECT + 'META'::VARCHAR, + -1, + 0::BIGINT, + jsonb_build_object( + 'user_id', v_user_id, + 'role', v3.user_role_code(v_user_id), + 'sheet', p_sheet, + 'editable', COALESCE( + (SELECT jsonb_agg( + jsonb_build_object('column', ec.column_key, + 'closes_at', ec.closes_at) + ORDER BY ec.column_key) + FROM v3.editable_columns_for(p_form_id, v_user_id, p_sheet, p_direction) ec), + '[]'::JSONB) + ); + + IF v_form_type = 'FORM_1' THEN + + 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.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; + + 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; + + 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$ +; + + +-- 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, '.'); + + 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; + + 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; + + 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 + IF v_parts[2] LIKE 'opening_%' AND v_far_month <> 1 THEN + RAISE EXCEPTION 'computed_field: % (opening для month>1 вычисляется из base + накопит. дельты)', p_column; + END IF; + 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; + + IF p_sheet = 'AHR_LIMIT' THEN + IF p_form_id IS NULL THEN + RAISE EXCEPTION 'internal: p_form_id не передан для AHR_LIMIT'; + END IF; + + 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; + + 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; + + 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 = '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 = 'justification' THEN + UPDATE v3.budget_line SET justification = (p_value #>> '{}')::TEXT, 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; + + IF v_scope IN ('approved','totals') THEN + RAISE EXCEPTION 'computed_field: %', p_column; + END IF; + + 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; + + + IF v_scope IN ('q1','q2','q3','q4') THEN + v_q := substring(v_scope FROM 2)::SMALLINT; + ELSE + v_q := NULL; + END IF; + + 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','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','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','order', 'allocation','internal_order','TEXT','line'), + ('allocation','property', 'allocation','property_object','TEXT','line'), + ('allocation','contract_ref', 'allocation','contract_ref','TEXT','line'), + ('allocation','allocation_purpose', 'allocation','allocation_purpose','TEXT','line'), + + ('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'), + ('contract_summary','reference', 'contract_summary','reference', 'TEXT','line'), + ('contract_summary','centralized_flag', 'contract_summary','centralized_flag', 'TEXT','line'), + + ('collegial','approved', 'collegial_approval','approved_amount', 'NUMERIC','line'), + ('collegial','protocol', 'collegial_approval','protocol_reference','TEXT', 'line'), + ('collegial','note', 'collegial_approval','note', 'TEXT', 'line'), + + ('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','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','addenda', 'contract_detail','addenda', 'TEXT', 'line'), + ('contract','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'), + ('contract','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'), + ('contract','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'), + ('contract','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'), + ('contract','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'), + ('contract','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'), + ('contract','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','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','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'), + + ('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'), + ('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'), + ('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 + 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; + + + IF v_key_kind = 'line' THEN + IF v_target_type = 'DATE' THEN + v_sql := format( + 'INSERT INTO v3.%1$I (line_id, %2$I) VALUES ($1, to_date($2->>0, ''DD.MM.YYYY'')) ' + || 'ON CONFLICT (line_id) DO UPDATE SET %2$I = EXCLUDED.%2$I', + v_target_table, v_target_col + ); + ELSE + 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 + ); + END IF; + 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; + + IF v_target_type = 'DATE' THEN + + v_sql := format( + 'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) ' + || 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I', + v_target_table, v_target_col + ); + + ELSE + 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 + ); + END IF; + + + 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; + + IF v_target_type = 'DATE' THEN + v_sql := format( + 'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) ' + || 'ON CONFLICT (line_id, actor) DO UPDATE SET %2$I = EXCLUDED.%2$I', + v_target_table, v_target_col, v_target_type + ); + ELSE + 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 + ); + END IF; + + EXECUTE v_sql USING p_line_id, v_actor, p_value; + + ELSE + RAISE EXCEPTION 'internal: unknown key_kind %', v_key_kind; + END IF; + + UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id; +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, + + 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( + 'total', 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_y1', s.col_cs_future_y1, + 'future_y2', s.col_cs_future_y2, + 'other_ssp', 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( + 'approved', s.col_col_amount, + 'protocol', 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, + 'q1', s.col_ckk_q1, + 'q2', s.col_ckk_q2, + 'q3', s.col_ckk_q3, + 'q4', s.col_ckk_q4, + 'rf_schedule', s.col_ckk_rf_schedule, + 'deadline', s.col_ckk_delivery_deadline, + 'proc_plan', s.col_ckk_procurement_plan, + 'proc_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', TO_CHAR(s.col_cd_date, 'DD.MM.YYYY'), + 'subject', s.col_cd_subject, + 'currency', s.col_cd_currency, + 'ceiling', s.col_cd_ceiling, + 'q1', s.col_cd_q1, + 'q2', s.col_cd_q2, + 'q3', s.col_cd_q3, + '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', TO_CHAR(s.col_q1_pay_date, 'DD.MM.YYYY'), + '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', TO_CHAR(s.col_q2_pay_date, 'DD.MM.YYYY'), + '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', TO_CHAR(s.col_q3_pay_date, 'DD.MM.YYYY'), + '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', TO_CHAR(s.col_q4_pay_date, 'DD.MM.YYYY'), + '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_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', TO_CHAR(s.col_CH_q1_pay_date, 'DD.MM.YYYY'), + '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', TO_CHAR(s.col_DQ_q2_pay_date, 'DD.MM.YYYY'), + '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', TO_CHAR(s.col_EY_q3_pay_date, 'DD.MM.YYYY'), + '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', TO_CHAR(s.col_GF_q4_pay_date, 'DD.MM.YYYY'), + '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_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, contract_summary jsonb, allocation_data jsonb, collegial_data jsonb, ckk_data jsonb, q1_data jsonb, q2_data jsonb, q3_data jsonb, q4_data jsonb, totals_data jsonb, _sort_path 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 'contract_summary' = ANY(p_sections)) AS s_cs, + (p_sections IS NULL OR 'allocation' = ANY(p_sections)) AS s_al, + (p_sections IS NULL OR 'collegial' = ANY(p_sections)) AS s_ca, + (p_sections IS NULL OR 'ckk' = ANY(p_sections)) AS s_ckk, + (p_sections IS NULL OR 'q1' = ANY(p_sections)) AS s_q1, + (p_sections IS NULL OR 'q2' = ANY(p_sections)) AS s_q2, + (p_sections IS NULL OR 'q3' = ANY(p_sections)) AS s_q3, + (p_sections IS NULL OR 'q4' = ANY(p_sections)) AS s_q4, + (p_sections IS NULL OR 'totals' = ANY(p_sections)) AS s_tot + ) + SELECT + s.row_type, s.depth, s.line_id, + + jsonb_build_object( + 'section_code', s.col_section_code, + 'item_id', s.col_item_id, + 'num_group', s.col_num_group_id, + 'name', s.col_name, + 'vsp_id', s.col_vsp_id, + 'vsp_address', s.col_vsp_address, + 'internal_order', s.col_internal_order, + 'expense_item_id', CASE WHEN s.row_type = 'INPUT' + THEN s._sort_path[array_upper(s._sort_path,1)-1] + ELSE s._sort_path[array_upper(s._sort_path,1)] END + ) AS header, + + CASE WHEN f.s_plan THEN jsonb_build_object( + 'q1', s.col_plan_q1, 'q2', s.col_plan_q2, + 'q3', s.col_plan_q3, 'q4', s.col_plan_q4, + 'year', s.col_plan_year, 'comment', s.col_plan_comment + ) END AS plan_data, + + CASE WHEN f.s_seq_d THEN jsonb_build_object( + 'q1', s.col_seq_dfip_q1, 'q2', s.col_seq_dfip_q2, + 'q3', s.col_seq_dfip_q3, 'q4', s.col_seq_dfip_q4, + 'year', s.col_seq_dfip_year, + 'justification', s.col_seq_dfip_just + ) END AS seq_dfip_data, + + CASE WHEN f.s_seq_s THEN jsonb_build_object( + 'q1', s.col_seq_ssp_q1, 'q2', s.col_seq_ssp_q2, + 'q3', s.col_seq_ssp_q3, 'q4', s.col_seq_ssp_q4, + 'year', s.col_seq_ssp_year, + 'justification', s.col_seq_ssp_just + ) END AS seq_ssp_data, + + CASE WHEN f.s_appr THEN jsonb_build_object( + 'q1', s.col_appr_q1, 'q2', s.col_appr_q2, + 'q3', s.col_appr_q3, 'q4', s.col_appr_q4, + 'year', s.col_appr_year + ) END AS approved_data, + + CASE WHEN f.s_cd THEN jsonb_build_object( + 'counterparty', s.col_cd_counterparty, + 'reference', s.col_cd_reference, + 'addenda', s.col_cd_addenda, + 'date', TO_CHAR(s.col_cd_date, 'DD.MM.YYYY'), + 'subject', s.col_cd_subject, + 'currency', s.col_cd_currency, + 'ceiling', s.col_cd_ceiling, + 'q1', s.col_cd_exp_q1, + 'q2', s.col_cd_exp_q2, + 'q3', s.col_cd_exp_q3, + 'q4', s.col_cd_exp_q4, + 'rf_schedule', s.col_cd_rf_sched, + 'vat_rate', s.col_cd_vat_rate, + 'exchange_rate', s.col_cd_exchange_rate, + 'amount_foreign', s.col_cd_amount_foreign, + 'deadline', s.col_cd_deadline, + 'scheme', s.col_cd_scheme, + 'act', s.col_cd_act, + 'comment', s.col_cd_comment + ) END AS contract_data, + + CASE WHEN f.s_book THEN jsonb_build_object( + 'y2026', jsonb_build_object('q1', s.col_book_q1, 'q2', s.col_book_q2, 'q3', s.col_book_q3, 'q4', s.col_book_q4), + 'y2027', jsonb_build_object('q1', s.col_book_next_q1, 'q2', s.col_book_next_q2, 'q3', s.col_book_next_q3, 'q4', s.col_book_next_q4) + ) END AS booking_data, + + CASE WHEN f.s_cs THEN jsonb_build_object( + 'total', s.col_cs_total, + 'counterparty', s.col_cs_counterparty, + 'deadline', s.col_cs_deadline, + 'comment', s.col_cs_comment, + 'future_y1', s.col_cs_future_y1, + 'future_y2', s.col_cs_future_y2, + 'other_ssp', s.col_cs_other_ssp + ) END AS contract_summary, + + CASE WHEN f.s_al THEN jsonb_build_object( + 'contract_ref', s.col_al_contract_ref, + 'purpose', s.col_al_purpose, + 'order', s.col_al_order, + 'property', s.col_al_property + ) END AS allocation_data, + + CASE WHEN f.s_ca THEN jsonb_build_object( + 'approved', s.col_ca_approved, + 'protocol', s.col_ca_protocol, + 'note', s.col_ca_note + ) END AS collegial_data, + + CASE WHEN f.s_ckk THEN jsonb_build_object( + 'ceiling', s.col_ckk_ceiling, + 'rf_schedule', s.col_ckk_rf_sched, + 'deadline', s.col_ckk_deadline, + 'proc_plan', s.col_ckk_proc_plan, + 'proc_method', s.col_ckk_proc_meth, + 'comment', s.col_ckk_comment + ) END AS ckk_data, + + CASE WHEN f.s_q1 THEN jsonb_build_object( + 'adj_current', s.col_q1_adj_current, + 'adj_ssp', s.col_q1_adj_ssp, + 'adj_rf', s.col_q1_adj_rf, + 'adj_reserve', s.col_q1_adj_reserve, + 'adj_comment', s.col_q1_adj_comment, + 'corrected_plan', s.col_q1_corrected_plan, + 'pay_date', TO_CHAR(s.col_q1_pay_date, 'DD.MM.YYYY'), + 'pay_amount', s.col_q1_pay_amount, + 'pay_ho', s.col_q1_pay_ho, + 'pay_rf', s.col_q1_pay_rf, + 'pay_comment', s.col_q1_pay_comment, + 'pay_act', s.col_q1_pay_act, + 'booking', s.col_q1_booking, + 'actual_m1', s.col_q1_actual_m1, + 'actual_m2', s.col_q1_actual_m2, + 'actual_m3', s.col_q1_actual_m3, + 'actual_quarter', s.col_q1_actual_quarter, + 'residual_after_booking', s.col_q1_residual_after_booking, + 'residual_after_actual', s.col_q1_residual_after_actual, + 'transfer_q2', s.col_q1_transfer_q2, + 'transfer_q2_delay_acts', s.col_q1_transfer_q2_delay_acts, + 'transfer_q2_delay_procurement', s.col_q1_transfer_q2_delay_procurement, + 'transfer_q2_economy_rf', s.col_q1_transfer_q2_economy_rf, + 'transfer_next_comment', s.col_q1_transfer_next_comment, + 'transfer_q3', s.col_q1_transfer_q3, + 'transfer_q4', s.col_q1_transfer_q4, + 'transfer_far_comment', s.col_q1_transfer_far_comment, + 'transfer_econ', s.col_q1_transfer_econ, + 'total', s.col_q1_total + ) END AS q1_data, + + CASE WHEN f.s_q2 THEN jsonb_build_object( + 'target_change', s.col_q2_target_change, + 'base_correction', s.col_q2_base_correction, + 'base_correction_comment', s.col_q2_base_correction_comment, + 'revision_inc', s.col_q2_revision_inc, + 'revision_seq', s.col_q2_revision_seq, + 'revision_comment', s.col_q2_revision_comment, + 'new_plan', s.col_q2_new_plan, + 'adj_current', s.col_q2_adj_current, + 'adj_ssp', s.col_q2_adj_ssp, + 'adj_rf', s.col_q2_adj_rf, + 'adj_reserve', s.col_q2_adj_reserve, + 'adj_comment', s.col_q2_adj_comment, + 'corrected_plan', s.col_q2_corrected_plan, + 'pay_date', TO_CHAR(s.col_q2_pay_date, 'DD.MM.YYYY'), + 'pay_amount', s.col_q2_pay_amount, + 'pay_ho', s.col_q2_pay_ho, + 'pay_rf', s.col_q2_pay_rf, + 'pay_comment', s.col_q2_pay_comment, + 'pay_act', s.col_q2_pay_act, + 'booking', s.col_q2_booking, + 'actual_m1', s.col_q2_actual_m1, + 'actual_m2', s.col_q2_actual_m2, + 'actual_m3', s.col_q2_actual_m3, + 'actual_quarter', s.col_q2_actual_quarter, + 'residual_after_booking', s.col_q2_residual_after_booking, + 'residual_after_actual', s.col_q2_residual_after_actual, + 'transfer_q3', s.col_q2_transfer_q3, + 'transfer_q3_delay_acts', s.col_q2_transfer_q3_delay_acts, + 'transfer_q3_delay_procurement', s.col_q2_transfer_q3_delay_procurement, + 'transfer_q3_economy_rf', s.col_q2_transfer_q3_economy_rf, + 'transfer_next_comment', s.col_q2_transfer_next_comment, + 'transfer_q4', s.col_q2_transfer_q4, + 'transfer_far_comment', s.col_q2_transfer_far_comment, + 'transfer_econ', s.col_q2_transfer_econ, + 'total', s.col_q2_total + ) END AS q2_data, + + CASE WHEN f.s_q3 THEN jsonb_build_object( + 'target_change', s.col_q3_target_change, + 'base_correction', s.col_q3_base_correction, + 'base_correction_comment', s.col_q3_base_correction_comment, + 'revision_inc', s.col_q3_revision_inc, + 'revision_seq', s.col_q3_revision_seq, + 'revision_comment', s.col_q3_revision_comment, + 'new_plan', s.col_q3_new_plan, + 'adj_current', s.col_q3_adj_current, + 'adj_ssp', s.col_q3_adj_ssp, + 'adj_rf', s.col_q3_adj_rf, + 'adj_reserve', s.col_q3_adj_reserve, + 'adj_comment', s.col_q3_adj_comment, + 'corrected_plan', s.col_q3_corrected_plan, + 'pay_date', TO_CHAR(s.col_q3_pay_date, 'DD.MM.YYYY'), + 'pay_amount', s.col_q3_pay_amount, + 'pay_ho', s.col_q3_pay_ho, + 'pay_rf', s.col_q3_pay_rf, + 'pay_comment', s.col_q3_pay_comment, + 'pay_act', s.col_q3_pay_act, + 'booking', s.col_q3_booking, + 'actual_m1', s.col_q3_actual_m1, + 'actual_m2', s.col_q3_actual_m2, + 'actual_m3', s.col_q3_actual_m3, + 'actual_quarter', s.col_q3_actual_quarter, + 'residual_after_booking', s.col_q3_residual_after_booking, + 'residual_after_actual', s.col_q3_residual_after_actual, + 'transfer_q4', s.col_q3_transfer_q4, + 'transfer_q4_delay_acts', s.col_q3_transfer_q4_delay_acts, + 'transfer_q4_delay_procurement', s.col_q3_transfer_q4_delay_procurement, + 'transfer_q4_economy_rf', s.col_q3_transfer_q4_economy_rf, + 'transfer_next_comment', s.col_q3_transfer_next_comment, + 'transfer_econ', s.col_q3_transfer_econ, + 'total', s.col_q3_total + ) END AS q3_data, + + CASE WHEN f.s_q4 THEN jsonb_build_object( + 'target_change', s.col_q4_target_change, + 'base_correction', s.col_q4_base_correction, + 'base_correction_comment', s.col_q4_base_correction_comment, + 'revision_inc', s.col_q4_revision_inc, + 'revision_seq', s.col_q4_revision_seq, + 'revision_comment', s.col_q4_revision_comment, + 'new_plan', s.col_q4_new_plan, + 'adj_current', s.col_q4_adj_current, + 'adj_ssp', s.col_q4_adj_ssp, + 'adj_rf', s.col_q4_adj_rf, + 'adj_reserve', s.col_q4_adj_reserve, + 'adj_comment', s.col_q4_adj_comment, + 'corrected_plan', s.col_q4_corrected_plan, + 'pay_date', TO_CHAR(s.col_q4_pay_date, 'DD.MM.YYYY'), + 'pay_amount', s.col_q4_pay_amount, + 'pay_ho', s.col_q4_pay_ho, + 'pay_rf', s.col_q4_pay_rf, + 'pay_comment', s.col_q4_pay_comment, + 'pay_act', s.col_q4_pay_act, + 'booking', s.col_q4_booking, + 'actual_m1', s.col_q4_actual_m1, + 'actual_m2', s.col_q4_actual_m2, + 'actual_m3', s.col_q4_actual_m3, + 'actual_spod', s.col_q4_actual_spod, + 'actual_quarter', s.col_q4_actual_quarter, + 'residual_after_booking', s.col_q4_residual_after_booking, + 'residual_after_actual', s.col_q4_residual_after_actual, + 'transfer_econ', s.col_q4_transfer_econ, + 'total', s.col_q4_total + ) END AS q4_data, + + CASE WHEN f.s_tot THEN jsonb_build_object( + 'fact_year', s.col_fact_year, + 'pay_year', s.col_pay_year, + 'economy_year', s.col_economy_year + ) END AS totals_data, + + s._sort_path + FROM v3.v_form2_sheet_sections(p_form_id, p_sheet, p_sections) s + CROSS JOIN flags f + ORDER BY s._sort_path; +$function$ +; + + + +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) sq1 WHERE sq1.row_type != 'INPUT' + UNION ALL + SELECT * FROM v3.v_form4_sheet_lines_jsonb(p_form_id, 'CAP', p_sections) sq2 WHERE sq2.row_type != 'INPUT' + UNION ALL + SELECT * FROM v3.v_form4_sheet_lines_jsonb(p_form_id, 'OPER', p_sections) sq3 WHERE sq3.row_type != 'INPUT' + ) + SELECT + j.row_type, + j.depth, + ROW_NUMBER() OVER ( + ORDER BY (j.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$ +; \ No newline at end of file diff --git a/api/back_static/swagger-ui-bundle.js b/api/back_static/swagger-ui-bundle.js new file mode 100644 index 0000000..b29716b --- /dev/null +++ b/api/back_static/swagger-ui-bundle.js @@ -0,0 +1,3 @@ +/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ +!function webpackUniversalModuleDefinition(i,s){"object"==typeof exports&&"object"==typeof module?module.exports=s():"function"==typeof define&&define.amd?define([],s):"object"==typeof exports?exports.SwaggerUIBundle=s():i.SwaggerUIBundle=s()}(this,(()=>(()=>{var i={17967:(i,s)=>{"use strict";s.Nm=s.Rq=void 0;var u=/^([^\w]*)(javascript|data|vbscript)/im,m=/&#(\w+)(^\w|;)?/g,v=/&(newline|tab);/gi,_=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,j=/^.+(:|:)/gim,M=[".","/"];s.Rq="about:blank",s.Nm=function sanitizeUrl(i){if(!i)return s.Rq;var $=function decodeHtmlCharacters(i){return i.replace(_,"").replace(m,(function(i,s){return String.fromCharCode(s)}))}(i).replace(v,"").replace(_,"").trim();if(!$)return s.Rq;if(function isRelativeUrlWithoutProtocol(i){return M.indexOf(i[0])>-1}($))return $;var W=$.match(j);if(!W)return $;var X=W[0];return u.test(X)?s.Rq:$}},79742:(i,s)=>{"use strict";s.byteLength=function byteLength(i){var s=getLens(i),u=s[0],m=s[1];return 3*(u+m)/4-m},s.toByteArray=function toByteArray(i){var s,u,_=getLens(i),j=_[0],M=_[1],$=new v(function _byteLength(i,s,u){return 3*(s+u)/4-u}(0,j,M)),W=0,X=M>0?j-4:j;for(u=0;u>16&255,$[W++]=s>>8&255,$[W++]=255&s;2===M&&(s=m[i.charCodeAt(u)]<<2|m[i.charCodeAt(u+1)]>>4,$[W++]=255&s);1===M&&(s=m[i.charCodeAt(u)]<<10|m[i.charCodeAt(u+1)]<<4|m[i.charCodeAt(u+2)]>>2,$[W++]=s>>8&255,$[W++]=255&s);return $},s.fromByteArray=function fromByteArray(i){for(var s,m=i.length,v=m%3,_=[],j=16383,M=0,$=m-v;M<$;M+=j)_.push(encodeChunk(i,M,M+j>$?$:M+j));1===v?(s=i[m-1],_.push(u[s>>2]+u[s<<4&63]+"==")):2===v&&(s=(i[m-2]<<8)+i[m-1],_.push(u[s>>10]+u[s>>4&63]+u[s<<2&63]+"="));return _.join("")};for(var u=[],m=[],v="undefined"!=typeof Uint8Array?Uint8Array:Array,_="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",j=0;j<64;++j)u[j]=_[j],m[_.charCodeAt(j)]=j;function getLens(i){var s=i.length;if(s%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var u=i.indexOf("=");return-1===u&&(u=s),[u,u===s?0:4-u%4]}function encodeChunk(i,s,m){for(var v,_,j=[],M=s;M>18&63]+u[_>>12&63]+u[_>>6&63]+u[63&_]);return j.join("")}m["-".charCodeAt(0)]=62,m["_".charCodeAt(0)]=63},48764:(i,s,u)=>{"use strict";const m=u(79742),v=u(80645),_="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;s.Buffer=Buffer,s.SlowBuffer=function SlowBuffer(i){+i!=i&&(i=0);return Buffer.alloc(+i)},s.INSPECT_MAX_BYTES=50;const j=2147483647;function createBuffer(i){if(i>j)throw new RangeError('The value "'+i+'" is invalid for option "size"');const s=new Uint8Array(i);return Object.setPrototypeOf(s,Buffer.prototype),s}function Buffer(i,s,u){if("number"==typeof i){if("string"==typeof s)throw new TypeError('The "string" argument must be of type string. Received type number');return allocUnsafe(i)}return from(i,s,u)}function from(i,s,u){if("string"==typeof i)return function fromString(i,s){"string"==typeof s&&""!==s||(s="utf8");if(!Buffer.isEncoding(s))throw new TypeError("Unknown encoding: "+s);const u=0|byteLength(i,s);let m=createBuffer(u);const v=m.write(i,s);v!==u&&(m=m.slice(0,v));return m}(i,s);if(ArrayBuffer.isView(i))return function fromArrayView(i){if(isInstance(i,Uint8Array)){const s=new Uint8Array(i);return fromArrayBuffer(s.buffer,s.byteOffset,s.byteLength)}return fromArrayLike(i)}(i);if(null==i)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof i);if(isInstance(i,ArrayBuffer)||i&&isInstance(i.buffer,ArrayBuffer))return fromArrayBuffer(i,s,u);if("undefined"!=typeof SharedArrayBuffer&&(isInstance(i,SharedArrayBuffer)||i&&isInstance(i.buffer,SharedArrayBuffer)))return fromArrayBuffer(i,s,u);if("number"==typeof i)throw new TypeError('The "value" argument must not be of type number. Received type number');const m=i.valueOf&&i.valueOf();if(null!=m&&m!==i)return Buffer.from(m,s,u);const v=function fromObject(i){if(Buffer.isBuffer(i)){const s=0|checked(i.length),u=createBuffer(s);return 0===u.length||i.copy(u,0,0,s),u}if(void 0!==i.length)return"number"!=typeof i.length||numberIsNaN(i.length)?createBuffer(0):fromArrayLike(i);if("Buffer"===i.type&&Array.isArray(i.data))return fromArrayLike(i.data)}(i);if(v)return v;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof i[Symbol.toPrimitive])return Buffer.from(i[Symbol.toPrimitive]("string"),s,u);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof i)}function assertSize(i){if("number"!=typeof i)throw new TypeError('"size" argument must be of type number');if(i<0)throw new RangeError('The value "'+i+'" is invalid for option "size"')}function allocUnsafe(i){return assertSize(i),createBuffer(i<0?0:0|checked(i))}function fromArrayLike(i){const s=i.length<0?0:0|checked(i.length),u=createBuffer(s);for(let m=0;m=j)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+j.toString(16)+" bytes");return 0|i}function byteLength(i,s){if(Buffer.isBuffer(i))return i.length;if(ArrayBuffer.isView(i)||isInstance(i,ArrayBuffer))return i.byteLength;if("string"!=typeof i)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof i);const u=i.length,m=arguments.length>2&&!0===arguments[2];if(!m&&0===u)return 0;let v=!1;for(;;)switch(s){case"ascii":case"latin1":case"binary":return u;case"utf8":case"utf-8":return utf8ToBytes(i).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*u;case"hex":return u>>>1;case"base64":return base64ToBytes(i).length;default:if(v)return m?-1:utf8ToBytes(i).length;s=(""+s).toLowerCase(),v=!0}}function slowToString(i,s,u){let m=!1;if((void 0===s||s<0)&&(s=0),s>this.length)return"";if((void 0===u||u>this.length)&&(u=this.length),u<=0)return"";if((u>>>=0)<=(s>>>=0))return"";for(i||(i="utf8");;)switch(i){case"hex":return hexSlice(this,s,u);case"utf8":case"utf-8":return utf8Slice(this,s,u);case"ascii":return asciiSlice(this,s,u);case"latin1":case"binary":return latin1Slice(this,s,u);case"base64":return base64Slice(this,s,u);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return utf16leSlice(this,s,u);default:if(m)throw new TypeError("Unknown encoding: "+i);i=(i+"").toLowerCase(),m=!0}}function swap(i,s,u){const m=i[s];i[s]=i[u],i[u]=m}function bidirectionalIndexOf(i,s,u,m,v){if(0===i.length)return-1;if("string"==typeof u?(m=u,u=0):u>2147483647?u=2147483647:u<-2147483648&&(u=-2147483648),numberIsNaN(u=+u)&&(u=v?0:i.length-1),u<0&&(u=i.length+u),u>=i.length){if(v)return-1;u=i.length-1}else if(u<0){if(!v)return-1;u=0}if("string"==typeof s&&(s=Buffer.from(s,m)),Buffer.isBuffer(s))return 0===s.length?-1:arrayIndexOf(i,s,u,m,v);if("number"==typeof s)return s&=255,"function"==typeof Uint8Array.prototype.indexOf?v?Uint8Array.prototype.indexOf.call(i,s,u):Uint8Array.prototype.lastIndexOf.call(i,s,u):arrayIndexOf(i,[s],u,m,v);throw new TypeError("val must be string, number or Buffer")}function arrayIndexOf(i,s,u,m,v){let _,j=1,M=i.length,$=s.length;if(void 0!==m&&("ucs2"===(m=String(m).toLowerCase())||"ucs-2"===m||"utf16le"===m||"utf-16le"===m)){if(i.length<2||s.length<2)return-1;j=2,M/=2,$/=2,u/=2}function read(i,s){return 1===j?i[s]:i.readUInt16BE(s*j)}if(v){let m=-1;for(_=u;_M&&(u=M-$),_=u;_>=0;_--){let u=!0;for(let m=0;m<$;m++)if(read(i,_+m)!==read(s,m)){u=!1;break}if(u)return _}return-1}function hexWrite(i,s,u,m){u=Number(u)||0;const v=i.length-u;m?(m=Number(m))>v&&(m=v):m=v;const _=s.length;let j;for(m>_/2&&(m=_/2),j=0;j>8,v=u%256,_.push(v),_.push(m);return _}(s,i.length-u),i,u,m)}function base64Slice(i,s,u){return 0===s&&u===i.length?m.fromByteArray(i):m.fromByteArray(i.slice(s,u))}function utf8Slice(i,s,u){u=Math.min(i.length,u);const m=[];let v=s;for(;v239?4:s>223?3:s>191?2:1;if(v+j<=u){let u,m,M,$;switch(j){case 1:s<128&&(_=s);break;case 2:u=i[v+1],128==(192&u)&&($=(31&s)<<6|63&u,$>127&&(_=$));break;case 3:u=i[v+1],m=i[v+2],128==(192&u)&&128==(192&m)&&($=(15&s)<<12|(63&u)<<6|63&m,$>2047&&($<55296||$>57343)&&(_=$));break;case 4:u=i[v+1],m=i[v+2],M=i[v+3],128==(192&u)&&128==(192&m)&&128==(192&M)&&($=(15&s)<<18|(63&u)<<12|(63&m)<<6|63&M,$>65535&&$<1114112&&(_=$))}}null===_?(_=65533,j=1):_>65535&&(_-=65536,m.push(_>>>10&1023|55296),_=56320|1023&_),m.push(_),v+=j}return function decodeCodePointsArray(i){const s=i.length;if(s<=M)return String.fromCharCode.apply(String,i);let u="",m=0;for(;mm.length?(Buffer.isBuffer(s)||(s=Buffer.from(s)),s.copy(m,v)):Uint8Array.prototype.set.call(m,s,v);else{if(!Buffer.isBuffer(s))throw new TypeError('"list" argument must be an Array of Buffers');s.copy(m,v)}v+=s.length}return m},Buffer.byteLength=byteLength,Buffer.prototype._isBuffer=!0,Buffer.prototype.swap16=function swap16(){const i=this.length;if(i%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let s=0;su&&(i+=" ... "),""},_&&(Buffer.prototype[_]=Buffer.prototype.inspect),Buffer.prototype.compare=function compare(i,s,u,m,v){if(isInstance(i,Uint8Array)&&(i=Buffer.from(i,i.offset,i.byteLength)),!Buffer.isBuffer(i))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof i);if(void 0===s&&(s=0),void 0===u&&(u=i?i.length:0),void 0===m&&(m=0),void 0===v&&(v=this.length),s<0||u>i.length||m<0||v>this.length)throw new RangeError("out of range index");if(m>=v&&s>=u)return 0;if(m>=v)return-1;if(s>=u)return 1;if(this===i)return 0;let _=(v>>>=0)-(m>>>=0),j=(u>>>=0)-(s>>>=0);const M=Math.min(_,j),$=this.slice(m,v),W=i.slice(s,u);for(let i=0;i>>=0,isFinite(u)?(u>>>=0,void 0===m&&(m="utf8")):(m=u,u=void 0)}const v=this.length-s;if((void 0===u||u>v)&&(u=v),i.length>0&&(u<0||s<0)||s>this.length)throw new RangeError("Attempt to write outside buffer bounds");m||(m="utf8");let _=!1;for(;;)switch(m){case"hex":return hexWrite(this,i,s,u);case"utf8":case"utf-8":return utf8Write(this,i,s,u);case"ascii":case"latin1":case"binary":return asciiWrite(this,i,s,u);case"base64":return base64Write(this,i,s,u);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ucs2Write(this,i,s,u);default:if(_)throw new TypeError("Unknown encoding: "+m);m=(""+m).toLowerCase(),_=!0}},Buffer.prototype.toJSON=function toJSON(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const M=4096;function asciiSlice(i,s,u){let m="";u=Math.min(i.length,u);for(let v=s;vm)&&(u=m);let v="";for(let m=s;mu)throw new RangeError("Trying to access beyond buffer length")}function checkInt(i,s,u,m,v,_){if(!Buffer.isBuffer(i))throw new TypeError('"buffer" argument must be a Buffer instance');if(s>v||s<_)throw new RangeError('"value" argument is out of bounds');if(u+m>i.length)throw new RangeError("Index out of range")}function wrtBigUInt64LE(i,s,u,m,v){checkIntBI(s,m,v,i,u,7);let _=Number(s&BigInt(4294967295));i[u++]=_,_>>=8,i[u++]=_,_>>=8,i[u++]=_,_>>=8,i[u++]=_;let j=Number(s>>BigInt(32)&BigInt(4294967295));return i[u++]=j,j>>=8,i[u++]=j,j>>=8,i[u++]=j,j>>=8,i[u++]=j,u}function wrtBigUInt64BE(i,s,u,m,v){checkIntBI(s,m,v,i,u,7);let _=Number(s&BigInt(4294967295));i[u+7]=_,_>>=8,i[u+6]=_,_>>=8,i[u+5]=_,_>>=8,i[u+4]=_;let j=Number(s>>BigInt(32)&BigInt(4294967295));return i[u+3]=j,j>>=8,i[u+2]=j,j>>=8,i[u+1]=j,j>>=8,i[u]=j,u+8}function checkIEEE754(i,s,u,m,v,_){if(u+m>i.length)throw new RangeError("Index out of range");if(u<0)throw new RangeError("Index out of range")}function writeFloat(i,s,u,m,_){return s=+s,u>>>=0,_||checkIEEE754(i,0,u,4),v.write(i,s,u,m,23,4),u+4}function writeDouble(i,s,u,m,_){return s=+s,u>>>=0,_||checkIEEE754(i,0,u,8),v.write(i,s,u,m,52,8),u+8}Buffer.prototype.slice=function slice(i,s){const u=this.length;(i=~~i)<0?(i+=u)<0&&(i=0):i>u&&(i=u),(s=void 0===s?u:~~s)<0?(s+=u)<0&&(s=0):s>u&&(s=u),s>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=this[i],v=1,_=0;for(;++_>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=this[i+--s],v=1;for(;s>0&&(v*=256);)m+=this[i+--s]*v;return m},Buffer.prototype.readUint8=Buffer.prototype.readUInt8=function readUInt8(i,s){return i>>>=0,s||checkOffset(i,1,this.length),this[i]},Buffer.prototype.readUint16LE=Buffer.prototype.readUInt16LE=function readUInt16LE(i,s){return i>>>=0,s||checkOffset(i,2,this.length),this[i]|this[i+1]<<8},Buffer.prototype.readUint16BE=Buffer.prototype.readUInt16BE=function readUInt16BE(i,s){return i>>>=0,s||checkOffset(i,2,this.length),this[i]<<8|this[i+1]},Buffer.prototype.readUint32LE=Buffer.prototype.readUInt32LE=function readUInt32LE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),(this[i]|this[i+1]<<8|this[i+2]<<16)+16777216*this[i+3]},Buffer.prototype.readUint32BE=Buffer.prototype.readUInt32BE=function readUInt32BE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),16777216*this[i]+(this[i+1]<<16|this[i+2]<<8|this[i+3])},Buffer.prototype.readBigUInt64LE=defineBigIntMethod((function readBigUInt64LE(i){validateNumber(i>>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=s+256*this[++i]+65536*this[++i]+this[++i]*2**24,v=this[++i]+256*this[++i]+65536*this[++i]+u*2**24;return BigInt(m)+(BigInt(v)<>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=s*2**24+65536*this[++i]+256*this[++i]+this[++i],v=this[++i]*2**24+65536*this[++i]+256*this[++i]+u;return(BigInt(m)<>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=this[i],v=1,_=0;for(;++_=v&&(m-=Math.pow(2,8*s)),m},Buffer.prototype.readIntBE=function readIntBE(i,s,u){i>>>=0,s>>>=0,u||checkOffset(i,s,this.length);let m=s,v=1,_=this[i+--m];for(;m>0&&(v*=256);)_+=this[i+--m]*v;return v*=128,_>=v&&(_-=Math.pow(2,8*s)),_},Buffer.prototype.readInt8=function readInt8(i,s){return i>>>=0,s||checkOffset(i,1,this.length),128&this[i]?-1*(255-this[i]+1):this[i]},Buffer.prototype.readInt16LE=function readInt16LE(i,s){i>>>=0,s||checkOffset(i,2,this.length);const u=this[i]|this[i+1]<<8;return 32768&u?4294901760|u:u},Buffer.prototype.readInt16BE=function readInt16BE(i,s){i>>>=0,s||checkOffset(i,2,this.length);const u=this[i+1]|this[i]<<8;return 32768&u?4294901760|u:u},Buffer.prototype.readInt32LE=function readInt32LE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),this[i]|this[i+1]<<8|this[i+2]<<16|this[i+3]<<24},Buffer.prototype.readInt32BE=function readInt32BE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),this[i]<<24|this[i+1]<<16|this[i+2]<<8|this[i+3]},Buffer.prototype.readBigInt64LE=defineBigIntMethod((function readBigInt64LE(i){validateNumber(i>>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=this[i+4]+256*this[i+5]+65536*this[i+6]+(u<<24);return(BigInt(m)<>>=0,"offset");const s=this[i],u=this[i+7];void 0!==s&&void 0!==u||boundsError(i,this.length-8);const m=(s<<24)+65536*this[++i]+256*this[++i]+this[++i];return(BigInt(m)<>>=0,s||checkOffset(i,4,this.length),v.read(this,i,!0,23,4)},Buffer.prototype.readFloatBE=function readFloatBE(i,s){return i>>>=0,s||checkOffset(i,4,this.length),v.read(this,i,!1,23,4)},Buffer.prototype.readDoubleLE=function readDoubleLE(i,s){return i>>>=0,s||checkOffset(i,8,this.length),v.read(this,i,!0,52,8)},Buffer.prototype.readDoubleBE=function readDoubleBE(i,s){return i>>>=0,s||checkOffset(i,8,this.length),v.read(this,i,!1,52,8)},Buffer.prototype.writeUintLE=Buffer.prototype.writeUIntLE=function writeUIntLE(i,s,u,m){if(i=+i,s>>>=0,u>>>=0,!m){checkInt(this,i,s,u,Math.pow(2,8*u)-1,0)}let v=1,_=0;for(this[s]=255&i;++_>>=0,u>>>=0,!m){checkInt(this,i,s,u,Math.pow(2,8*u)-1,0)}let v=u-1,_=1;for(this[s+v]=255&i;--v>=0&&(_*=256);)this[s+v]=i/_&255;return s+u},Buffer.prototype.writeUint8=Buffer.prototype.writeUInt8=function writeUInt8(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,1,255,0),this[s]=255&i,s+1},Buffer.prototype.writeUint16LE=Buffer.prototype.writeUInt16LE=function writeUInt16LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,65535,0),this[s]=255&i,this[s+1]=i>>>8,s+2},Buffer.prototype.writeUint16BE=Buffer.prototype.writeUInt16BE=function writeUInt16BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,65535,0),this[s]=i>>>8,this[s+1]=255&i,s+2},Buffer.prototype.writeUint32LE=Buffer.prototype.writeUInt32LE=function writeUInt32LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,4294967295,0),this[s+3]=i>>>24,this[s+2]=i>>>16,this[s+1]=i>>>8,this[s]=255&i,s+4},Buffer.prototype.writeUint32BE=Buffer.prototype.writeUInt32BE=function writeUInt32BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,4294967295,0),this[s]=i>>>24,this[s+1]=i>>>16,this[s+2]=i>>>8,this[s+3]=255&i,s+4},Buffer.prototype.writeBigUInt64LE=defineBigIntMethod((function writeBigUInt64LE(i,s=0){return wrtBigUInt64LE(this,i,s,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeBigUInt64BE=defineBigIntMethod((function writeBigUInt64BE(i,s=0){return wrtBigUInt64BE(this,i,s,BigInt(0),BigInt("0xffffffffffffffff"))})),Buffer.prototype.writeIntLE=function writeIntLE(i,s,u,m){if(i=+i,s>>>=0,!m){const m=Math.pow(2,8*u-1);checkInt(this,i,s,u,m-1,-m)}let v=0,_=1,j=0;for(this[s]=255&i;++v>0)-j&255;return s+u},Buffer.prototype.writeIntBE=function writeIntBE(i,s,u,m){if(i=+i,s>>>=0,!m){const m=Math.pow(2,8*u-1);checkInt(this,i,s,u,m-1,-m)}let v=u-1,_=1,j=0;for(this[s+v]=255&i;--v>=0&&(_*=256);)i<0&&0===j&&0!==this[s+v+1]&&(j=1),this[s+v]=(i/_>>0)-j&255;return s+u},Buffer.prototype.writeInt8=function writeInt8(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,1,127,-128),i<0&&(i=255+i+1),this[s]=255&i,s+1},Buffer.prototype.writeInt16LE=function writeInt16LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,32767,-32768),this[s]=255&i,this[s+1]=i>>>8,s+2},Buffer.prototype.writeInt16BE=function writeInt16BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,2,32767,-32768),this[s]=i>>>8,this[s+1]=255&i,s+2},Buffer.prototype.writeInt32LE=function writeInt32LE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,2147483647,-2147483648),this[s]=255&i,this[s+1]=i>>>8,this[s+2]=i>>>16,this[s+3]=i>>>24,s+4},Buffer.prototype.writeInt32BE=function writeInt32BE(i,s,u){return i=+i,s>>>=0,u||checkInt(this,i,s,4,2147483647,-2147483648),i<0&&(i=4294967295+i+1),this[s]=i>>>24,this[s+1]=i>>>16,this[s+2]=i>>>8,this[s+3]=255&i,s+4},Buffer.prototype.writeBigInt64LE=defineBigIntMethod((function writeBigInt64LE(i,s=0){return wrtBigUInt64LE(this,i,s,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeBigInt64BE=defineBigIntMethod((function writeBigInt64BE(i,s=0){return wrtBigUInt64BE(this,i,s,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),Buffer.prototype.writeFloatLE=function writeFloatLE(i,s,u){return writeFloat(this,i,s,!0,u)},Buffer.prototype.writeFloatBE=function writeFloatBE(i,s,u){return writeFloat(this,i,s,!1,u)},Buffer.prototype.writeDoubleLE=function writeDoubleLE(i,s,u){return writeDouble(this,i,s,!0,u)},Buffer.prototype.writeDoubleBE=function writeDoubleBE(i,s,u){return writeDouble(this,i,s,!1,u)},Buffer.prototype.copy=function copy(i,s,u,m){if(!Buffer.isBuffer(i))throw new TypeError("argument should be a Buffer");if(u||(u=0),m||0===m||(m=this.length),s>=i.length&&(s=i.length),s||(s=0),m>0&&m=this.length)throw new RangeError("Index out of range");if(m<0)throw new RangeError("sourceEnd out of bounds");m>this.length&&(m=this.length),i.length-s>>=0,u=void 0===u?this.length:u>>>0,i||(i=0),"number"==typeof i)for(v=s;v=m+4;u-=3)s=`_${i.slice(u-3,u)}${s}`;return`${i.slice(0,u)}${s}`}function checkIntBI(i,s,u,m,v,_){if(i>u||i3?0===s||s===BigInt(0)?`>= 0${m} and < 2${m} ** ${8*(_+1)}${m}`:`>= -(2${m} ** ${8*(_+1)-1}${m}) and < 2 ** ${8*(_+1)-1}${m}`:`>= ${s}${m} and <= ${u}${m}`,new $.ERR_OUT_OF_RANGE("value",v,i)}!function checkBounds(i,s,u){validateNumber(s,"offset"),void 0!==i[s]&&void 0!==i[s+u]||boundsError(s,i.length-(u+1))}(m,v,_)}function validateNumber(i,s){if("number"!=typeof i)throw new $.ERR_INVALID_ARG_TYPE(s,"number",i)}function boundsError(i,s,u){if(Math.floor(i)!==i)throw validateNumber(i,u),new $.ERR_OUT_OF_RANGE(u||"offset","an integer",i);if(s<0)throw new $.ERR_BUFFER_OUT_OF_BOUNDS;throw new $.ERR_OUT_OF_RANGE(u||"offset",`>= ${u?1:0} and <= ${s}`,i)}E("ERR_BUFFER_OUT_OF_BOUNDS",(function(i){return i?`${i} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),E("ERR_INVALID_ARG_TYPE",(function(i,s){return`The "${i}" argument must be of type number. Received type ${typeof s}`}),TypeError),E("ERR_OUT_OF_RANGE",(function(i,s,u){let m=`The value of "${i}" is out of range.`,v=u;return Number.isInteger(u)&&Math.abs(u)>2**32?v=addNumericalSeparator(String(u)):"bigint"==typeof u&&(v=String(u),(u>BigInt(2)**BigInt(32)||u<-(BigInt(2)**BigInt(32)))&&(v=addNumericalSeparator(v)),v+="n"),m+=` It must be ${s}. Received ${v}`,m}),RangeError);const W=/[^+/0-9A-Za-z-_]/g;function utf8ToBytes(i,s){let u;s=s||1/0;const m=i.length;let v=null;const _=[];for(let j=0;j55295&&u<57344){if(!v){if(u>56319){(s-=3)>-1&&_.push(239,191,189);continue}if(j+1===m){(s-=3)>-1&&_.push(239,191,189);continue}v=u;continue}if(u<56320){(s-=3)>-1&&_.push(239,191,189),v=u;continue}u=65536+(v-55296<<10|u-56320)}else v&&(s-=3)>-1&&_.push(239,191,189);if(v=null,u<128){if((s-=1)<0)break;_.push(u)}else if(u<2048){if((s-=2)<0)break;_.push(u>>6|192,63&u|128)}else if(u<65536){if((s-=3)<0)break;_.push(u>>12|224,u>>6&63|128,63&u|128)}else{if(!(u<1114112))throw new Error("Invalid code point");if((s-=4)<0)break;_.push(u>>18|240,u>>12&63|128,u>>6&63|128,63&u|128)}}return _}function base64ToBytes(i){return m.toByteArray(function base64clean(i){if((i=(i=i.split("=")[0]).trim().replace(W,"")).length<2)return"";for(;i.length%4!=0;)i+="=";return i}(i))}function blitBuffer(i,s,u,m){let v;for(v=0;v=s.length||v>=i.length);++v)s[v+u]=i[v];return v}function isInstance(i,s){return i instanceof s||null!=i&&null!=i.constructor&&null!=i.constructor.name&&i.constructor.name===s.name}function numberIsNaN(i){return i!=i}const X=function(){const i="0123456789abcdef",s=new Array(256);for(let u=0;u<16;++u){const m=16*u;for(let v=0;v<16;++v)s[m+v]=i[u]+i[v]}return s}();function defineBigIntMethod(i){return"undefined"==typeof BigInt?BufferBigIntNotDefined:i}function BufferBigIntNotDefined(){throw new Error("BigInt not supported")}},21924:(i,s,u)=>{"use strict";var m=u(40210),v=u(55559),_=v(m("String.prototype.indexOf"));i.exports=function callBoundIntrinsic(i,s){var u=m(i,!!s);return"function"==typeof u&&_(i,".prototype.")>-1?v(u):u}},55559:(i,s,u)=>{"use strict";var m=u(58612),v=u(40210),_=v("%Function.prototype.apply%"),j=v("%Function.prototype.call%"),M=v("%Reflect.apply%",!0)||m.call(j,_),$=v("%Object.getOwnPropertyDescriptor%",!0),W=v("%Object.defineProperty%",!0),X=v("%Math.max%");if(W)try{W({},"a",{value:1})}catch(i){W=null}i.exports=function callBind(i){var s=M(m,j,arguments);$&&W&&($(s,"length").configurable&&W(s,"length",{value:1+X(0,i.length-(arguments.length-1))}));return s};var Y=function applyBind(){return M(m,_,arguments)};W?W(i.exports,"apply",{value:Y}):i.exports.apply=Y},94184:(i,s)=>{var u;!function(){"use strict";var m={}.hasOwnProperty;function classNames(){for(var i=[],s=0;s{"use strict";s.parse=function parse(i,s){if("string"!=typeof i)throw new TypeError("argument str must be a string");var u={},m=(s||{}).decode||decode,v=0;for(;v{"use strict";var m=u(11742),v={"text/plain":"Text","text/html":"Url",default:"Text"};i.exports=function copy(i,s){var u,_,j,M,$,W,X=!1;s||(s={}),u=s.debug||!1;try{if(j=m(),M=document.createRange(),$=document.getSelection(),(W=document.createElement("span")).textContent=i,W.ariaHidden="true",W.style.all="unset",W.style.position="fixed",W.style.top=0,W.style.clip="rect(0, 0, 0, 0)",W.style.whiteSpace="pre",W.style.webkitUserSelect="text",W.style.MozUserSelect="text",W.style.msUserSelect="text",W.style.userSelect="text",W.addEventListener("copy",(function(m){if(m.stopPropagation(),s.format)if(m.preventDefault(),void 0===m.clipboardData){u&&console.warn("unable to use e.clipboardData"),u&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var _=v[s.format]||v.default;window.clipboardData.setData(_,i)}else m.clipboardData.clearData(),m.clipboardData.setData(s.format,i);s.onCopy&&(m.preventDefault(),s.onCopy(m.clipboardData))})),document.body.appendChild(W),M.selectNodeContents(W),$.addRange(M),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");X=!0}catch(m){u&&console.error("unable to copy using execCommand: ",m),u&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(s.format||"text",i),s.onCopy&&s.onCopy(window.clipboardData),X=!0}catch(m){u&&console.error("unable to copy using clipboardData: ",m),u&&console.error("falling back to prompt"),_=function format(i){var s=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return i.replace(/#{\s*key\s*}/g,s)}("message"in s?s.message:"Copy to clipboard: #{key}, Enter"),window.prompt(_,i)}}finally{$&&("function"==typeof $.removeRange?$.removeRange(M):$.removeAllRanges()),W&&document.body.removeChild(W),j()}return X}},44101:(i,s,u)=>{var m=u(18957);i.exports=m},90093:(i,s,u)=>{var m=u(28196);i.exports=m},65362:(i,s,u)=>{var m=u(63383);i.exports=m},50415:(i,s,u)=>{u(61181),u(47627),u(24415),u(66274),u(77971);var m=u(54058);i.exports=m.AggregateError},27700:(i,s,u)=>{u(73381);var m=u(35703);i.exports=m("Function").bind},16246:(i,s,u)=>{var m=u(7046),v=u(27700),_=Function.prototype;i.exports=function(i){var s=i.bind;return i===_||m(_,i)&&s===_.bind?v:s}},45999:(i,s,u)=>{u(49221);var m=u(54058);i.exports=m.Object.assign},16121:(i,s,u)=>{i.exports=u(38644)},14122:(i,s,u)=>{i.exports=u(89097)},60269:(i,s,u)=>{i.exports=u(76936)},38644:(i,s,u)=>{u(89731);var m=u(44101);i.exports=m},89097:(i,s,u)=>{var m=u(90093);i.exports=m},76936:(i,s,u)=>{var m=u(65362);i.exports=m},24883:(i,s,u)=>{var m=u(57475),v=u(69826),_=TypeError;i.exports=function(i){if(m(i))return i;throw _(v(i)+" is not a function")}},11851:(i,s,u)=>{var m=u(57475),v=String,_=TypeError;i.exports=function(i){if("object"==typeof i||m(i))return i;throw _("Can't set "+v(i)+" as a prototype")}},18479:i=>{i.exports=function(){}},96059:(i,s,u)=>{var m=u(10941),v=String,_=TypeError;i.exports=function(i){if(m(i))return i;throw _(v(i)+" is not an object")}},31692:(i,s,u)=>{var m=u(74529),v=u(59413),_=u(10623),createMethod=function(i){return function(s,u,j){var M,$=m(s),W=_($),X=v(j,W);if(i&&u!=u){for(;W>X;)if((M=$[X++])!=M)return!0}else for(;W>X;X++)if((i||X in $)&&$[X]===u)return i||X||0;return!i&&-1}};i.exports={includes:createMethod(!0),indexOf:createMethod(!1)}},93765:(i,s,u)=>{var m=u(95329);i.exports=m([].slice)},82532:(i,s,u)=>{var m=u(95329),v=m({}.toString),_=m("".slice);i.exports=function(i){return _(v(i),8,-1)}},9697:(i,s,u)=>{var m=u(22885),v=u(57475),_=u(82532),j=u(99813)("toStringTag"),M=Object,$="Arguments"==_(function(){return arguments}());i.exports=m?_:function(i){var s,u,m;return void 0===i?"Undefined":null===i?"Null":"string"==typeof(u=function(i,s){try{return i[s]}catch(i){}}(s=M(i),j))?u:$?_(s):"Object"==(m=_(s))&&v(s.callee)?"Arguments":m}},23489:(i,s,u)=>{var m=u(90953),v=u(31136),_=u(49677),j=u(65988);i.exports=function(i,s,u){for(var M=v(s),$=j.f,W=_.f,X=0;X{var m=u(95981);i.exports=!m((function(){function F(){}return F.prototype.constructor=null,Object.getPrototypeOf(new F)!==F.prototype}))},23538:i=>{i.exports=function(i,s){return{value:i,done:s}}},32029:(i,s,u)=>{var m=u(55746),v=u(65988),_=u(31887);i.exports=m?function(i,s,u){return v.f(i,s,_(1,u))}:function(i,s,u){return i[s]=u,i}},31887:i=>{i.exports=function(i,s){return{enumerable:!(1&i),configurable:!(2&i),writable:!(4&i),value:s}}},95929:(i,s,u)=>{var m=u(32029);i.exports=function(i,s,u,v){return v&&v.enumerable?i[s]=u:m(i,s,u),i}},75609:(i,s,u)=>{var m=u(21899),v=Object.defineProperty;i.exports=function(i,s){try{v(m,i,{value:s,configurable:!0,writable:!0})}catch(u){m[i]=s}return s}},55746:(i,s,u)=>{var m=u(95981);i.exports=!m((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},76616:i=>{var s="object"==typeof document&&document.all,u=void 0===s&&void 0!==s;i.exports={all:s,IS_HTMLDDA:u}},61333:(i,s,u)=>{var m=u(21899),v=u(10941),_=m.document,j=v(_)&&v(_.createElement);i.exports=function(i){return j?_.createElement(i):{}}},63281:i=>{i.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},2861:i=>{i.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},53385:(i,s,u)=>{var m,v,_=u(21899),j=u(2861),M=_.process,$=_.Deno,W=M&&M.versions||$&&$.version,X=W&&W.v8;X&&(v=(m=X.split("."))[0]>0&&m[0]<4?1:+(m[0]+m[1])),!v&&j&&(!(m=j.match(/Edge\/(\d+)/))||m[1]>=74)&&(m=j.match(/Chrome\/(\d+)/))&&(v=+m[1]),i.exports=v},35703:(i,s,u)=>{var m=u(54058);i.exports=function(i){return m[i+"Prototype"]}},56759:i=>{i.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},53995:(i,s,u)=>{var m=u(95329),v=Error,_=m("".replace),j=String(v("zxcasd").stack),M=/\n\s*at [^:]*:[^\n]*/,$=M.test(j);i.exports=function(i,s){if($&&"string"==typeof i&&!v.prepareStackTrace)for(;s--;)i=_(i,M,"");return i}},79585:(i,s,u)=>{var m=u(32029),v=u(53995),_=u(18780),j=Error.captureStackTrace;i.exports=function(i,s,u,M){_&&(j?j(i,s):m(i,"stack",v(u,M)))}},18780:(i,s,u)=>{var m=u(95981),v=u(31887);i.exports=!m((function(){var i=Error("a");return!("stack"in i)||(Object.defineProperty(i,"stack",v(1,7)),7!==i.stack)}))},76887:(i,s,u)=>{"use strict";var m=u(21899),v=u(79730),_=u(97484),j=u(57475),M=u(49677).f,$=u(37252),W=u(54058),X=u(86843),Y=u(32029),Z=u(90953),wrapConstructor=function(i){var Wrapper=function(s,u,m){if(this instanceof Wrapper){switch(arguments.length){case 0:return new i;case 1:return new i(s);case 2:return new i(s,u)}return new i(s,u,m)}return v(i,this,arguments)};return Wrapper.prototype=i.prototype,Wrapper};i.exports=function(i,s){var u,v,ee,ae,ie,le,ce,pe,de,fe=i.target,ye=i.global,be=i.stat,_e=i.proto,we=ye?m:be?m[fe]:(m[fe]||{}).prototype,Se=ye?W:W[fe]||Y(W,fe,{})[fe],xe=Se.prototype;for(ae in s)v=!(u=$(ye?ae:fe+(be?".":"#")+ae,i.forced))&&we&&Z(we,ae),le=Se[ae],v&&(ce=i.dontCallGetSet?(de=M(we,ae))&&de.value:we[ae]),ie=v&&ce?ce:s[ae],v&&typeof le==typeof ie||(pe=i.bind&&v?X(ie,m):i.wrap&&v?wrapConstructor(ie):_e&&j(ie)?_(ie):ie,(i.sham||ie&&ie.sham||le&&le.sham)&&Y(pe,"sham",!0),Y(Se,ae,pe),_e&&(Z(W,ee=fe+"Prototype")||Y(W,ee,{}),Y(W[ee],ae,ie),i.real&&xe&&(u||!xe[ae])&&Y(xe,ae,ie)))}},95981:i=>{i.exports=function(i){try{return!!i()}catch(i){return!0}}},79730:(i,s,u)=>{var m=u(18285),v=Function.prototype,_=v.apply,j=v.call;i.exports="object"==typeof Reflect&&Reflect.apply||(m?j.bind(_):function(){return j.apply(_,arguments)})},86843:(i,s,u)=>{var m=u(97484),v=u(24883),_=u(18285),j=m(m.bind);i.exports=function(i,s){return v(i),void 0===s?i:_?j(i,s):function(){return i.apply(s,arguments)}}},18285:(i,s,u)=>{var m=u(95981);i.exports=!m((function(){var i=function(){}.bind();return"function"!=typeof i||i.hasOwnProperty("prototype")}))},98308:(i,s,u)=>{"use strict";var m=u(95329),v=u(24883),_=u(10941),j=u(90953),M=u(93765),$=u(18285),W=Function,X=m([].concat),Y=m([].join),Z={};i.exports=$?W.bind:function bind(i){var s=v(this),u=s.prototype,m=M(arguments,1),$=function bound(){var u=X(m,M(arguments));return this instanceof $?function(i,s,u){if(!j(Z,s)){for(var m=[],v=0;v{var m=u(18285),v=Function.prototype.call;i.exports=m?v.bind(v):function(){return v.apply(v,arguments)}},79417:(i,s,u)=>{var m=u(55746),v=u(90953),_=Function.prototype,j=m&&Object.getOwnPropertyDescriptor,M=v(_,"name"),$=M&&"something"===function something(){}.name,W=M&&(!m||m&&j(_,"name").configurable);i.exports={EXISTS:M,PROPER:$,CONFIGURABLE:W}},45526:(i,s,u)=>{var m=u(95329),v=u(24883);i.exports=function(i,s,u){try{return m(v(Object.getOwnPropertyDescriptor(i,s)[u]))}catch(i){}}},97484:(i,s,u)=>{var m=u(82532),v=u(95329);i.exports=function(i){if("Function"===m(i))return v(i)}},95329:(i,s,u)=>{var m=u(18285),v=Function.prototype,_=v.call,j=m&&v.bind.bind(_,_);i.exports=m?j:function(i){return function(){return _.apply(i,arguments)}}},626:(i,s,u)=>{var m=u(54058),v=u(21899),_=u(57475),aFunction=function(i){return _(i)?i:void 0};i.exports=function(i,s){return arguments.length<2?aFunction(m[i])||aFunction(v[i]):m[i]&&m[i][s]||v[i]&&v[i][s]}},22902:(i,s,u)=>{var m=u(9697),v=u(14229),_=u(82119),j=u(12077),M=u(99813)("iterator");i.exports=function(i){if(!_(i))return v(i,M)||v(i,"@@iterator")||j[m(i)]}},53476:(i,s,u)=>{var m=u(78834),v=u(24883),_=u(96059),j=u(69826),M=u(22902),$=TypeError;i.exports=function(i,s){var u=arguments.length<2?M(i):s;if(v(u))return _(m(u,i));throw $(j(i)+" is not iterable")}},14229:(i,s,u)=>{var m=u(24883),v=u(82119);i.exports=function(i,s){var u=i[s];return v(u)?void 0:m(u)}},21899:function(i,s,u){var check=function(i){return i&&i.Math==Math&&i};i.exports=check("object"==typeof globalThis&&globalThis)||check("object"==typeof window&&window)||check("object"==typeof self&&self)||check("object"==typeof u.g&&u.g)||function(){return this}()||this||Function("return this")()},90953:(i,s,u)=>{var m=u(95329),v=u(89678),_=m({}.hasOwnProperty);i.exports=Object.hasOwn||function hasOwn(i,s){return _(v(i),s)}},27748:i=>{i.exports={}},15463:(i,s,u)=>{var m=u(626);i.exports=m("document","documentElement")},2840:(i,s,u)=>{var m=u(55746),v=u(95981),_=u(61333);i.exports=!m&&!v((function(){return 7!=Object.defineProperty(_("div"),"a",{get:function(){return 7}}).a}))},37026:(i,s,u)=>{var m=u(95329),v=u(95981),_=u(82532),j=Object,M=m("".split);i.exports=v((function(){return!j("z").propertyIsEnumerable(0)}))?function(i){return"String"==_(i)?M(i,""):j(i)}:j},70926:(i,s,u)=>{var m=u(57475),v=u(10941),_=u(88929);i.exports=function(i,s,u){var j,M;return _&&m(j=s.constructor)&&j!==u&&v(M=j.prototype)&&M!==u.prototype&&_(i,M),i}},53794:(i,s,u)=>{var m=u(10941),v=u(32029);i.exports=function(i,s){m(s)&&"cause"in s&&v(i,"cause",s.cause)}},45402:(i,s,u)=>{var m,v,_,j=u(47093),M=u(21899),$=u(10941),W=u(32029),X=u(90953),Y=u(63030),Z=u(44262),ee=u(27748),ae="Object already initialized",ie=M.TypeError,le=M.WeakMap;if(j||Y.state){var ce=Y.state||(Y.state=new le);ce.get=ce.get,ce.has=ce.has,ce.set=ce.set,m=function(i,s){if(ce.has(i))throw ie(ae);return s.facade=i,ce.set(i,s),s},v=function(i){return ce.get(i)||{}},_=function(i){return ce.has(i)}}else{var pe=Z("state");ee[pe]=!0,m=function(i,s){if(X(i,pe))throw ie(ae);return s.facade=i,W(i,pe,s),s},v=function(i){return X(i,pe)?i[pe]:{}},_=function(i){return X(i,pe)}}i.exports={set:m,get:v,has:_,enforce:function(i){return _(i)?v(i):m(i,{})},getterFor:function(i){return function(s){var u;if(!$(s)||(u=v(s)).type!==i)throw ie("Incompatible receiver, "+i+" required");return u}}}},6782:(i,s,u)=>{var m=u(99813),v=u(12077),_=m("iterator"),j=Array.prototype;i.exports=function(i){return void 0!==i&&(v.Array===i||j[_]===i)}},57475:(i,s,u)=>{var m=u(76616),v=m.all;i.exports=m.IS_HTMLDDA?function(i){return"function"==typeof i||i===v}:function(i){return"function"==typeof i}},37252:(i,s,u)=>{var m=u(95981),v=u(57475),_=/#|\.prototype\./,isForced=function(i,s){var u=M[j(i)];return u==W||u!=$&&(v(s)?m(s):!!s)},j=isForced.normalize=function(i){return String(i).replace(_,".").toLowerCase()},M=isForced.data={},$=isForced.NATIVE="N",W=isForced.POLYFILL="P";i.exports=isForced},82119:i=>{i.exports=function(i){return null==i}},10941:(i,s,u)=>{var m=u(57475),v=u(76616),_=v.all;i.exports=v.IS_HTMLDDA?function(i){return"object"==typeof i?null!==i:m(i)||i===_}:function(i){return"object"==typeof i?null!==i:m(i)}},82529:i=>{i.exports=!0},56664:(i,s,u)=>{var m=u(626),v=u(57475),_=u(7046),j=u(32302),M=Object;i.exports=j?function(i){return"symbol"==typeof i}:function(i){var s=m("Symbol");return v(s)&&_(s.prototype,M(i))}},93091:(i,s,u)=>{var m=u(86843),v=u(78834),_=u(96059),j=u(69826),M=u(6782),$=u(10623),W=u(7046),X=u(53476),Y=u(22902),Z=u(7609),ee=TypeError,Result=function(i,s){this.stopped=i,this.result=s},ae=Result.prototype;i.exports=function(i,s,u){var ie,le,ce,pe,de,fe,ye,be=u&&u.that,_e=!(!u||!u.AS_ENTRIES),we=!(!u||!u.IS_RECORD),Se=!(!u||!u.IS_ITERATOR),xe=!(!u||!u.INTERRUPTED),Pe=m(s,be),stop=function(i){return ie&&Z(ie,"normal",i),new Result(!0,i)},callFn=function(i){return _e?(_(i),xe?Pe(i[0],i[1],stop):Pe(i[0],i[1])):xe?Pe(i,stop):Pe(i)};if(we)ie=i.iterator;else if(Se)ie=i;else{if(!(le=Y(i)))throw ee(j(i)+" is not iterable");if(M(le)){for(ce=0,pe=$(i);pe>ce;ce++)if((de=callFn(i[ce]))&&W(ae,de))return de;return new Result(!1)}ie=X(i,le)}for(fe=we?i.next:ie.next;!(ye=v(fe,ie)).done;){try{de=callFn(ye.value)}catch(i){Z(ie,"throw",i)}if("object"==typeof de&&de&&W(ae,de))return de}return new Result(!1)}},7609:(i,s,u)=>{var m=u(78834),v=u(96059),_=u(14229);i.exports=function(i,s,u){var j,M;v(i);try{if(!(j=_(i,"return"))){if("throw"===s)throw u;return u}j=m(j,i)}catch(i){M=!0,j=i}if("throw"===s)throw u;if(M)throw j;return v(j),u}},53847:(i,s,u)=>{"use strict";var m=u(35143).IteratorPrototype,v=u(29290),_=u(31887),j=u(90904),M=u(12077),returnThis=function(){return this};i.exports=function(i,s,u,$){var W=s+" Iterator";return i.prototype=v(m,{next:_(+!$,u)}),j(i,W,!1,!0),M[W]=returnThis,i}},75105:(i,s,u)=>{"use strict";var m=u(76887),v=u(78834),_=u(82529),j=u(79417),M=u(57475),$=u(53847),W=u(249),X=u(88929),Y=u(90904),Z=u(32029),ee=u(95929),ae=u(99813),ie=u(12077),le=u(35143),ce=j.PROPER,pe=j.CONFIGURABLE,de=le.IteratorPrototype,fe=le.BUGGY_SAFARI_ITERATORS,ye=ae("iterator"),be="keys",_e="values",we="entries",returnThis=function(){return this};i.exports=function(i,s,u,j,ae,le,Se){$(u,s,j);var xe,Pe,Ie,getIterationMethod=function(i){if(i===ae&&Ve)return Ve;if(!fe&&i in qe)return qe[i];switch(i){case be:return function keys(){return new u(this,i)};case _e:return function values(){return new u(this,i)};case we:return function entries(){return new u(this,i)}}return function(){return new u(this)}},Te=s+" Iterator",Re=!1,qe=i.prototype,ze=qe[ye]||qe["@@iterator"]||ae&&qe[ae],Ve=!fe&&ze||getIterationMethod(ae),We="Array"==s&&qe.entries||ze;if(We&&(xe=W(We.call(new i)))!==Object.prototype&&xe.next&&(_||W(xe)===de||(X?X(xe,de):M(xe[ye])||ee(xe,ye,returnThis)),Y(xe,Te,!0,!0),_&&(ie[Te]=returnThis)),ce&&ae==_e&&ze&&ze.name!==_e&&(!_&&pe?Z(qe,"name",_e):(Re=!0,Ve=function values(){return v(ze,this)})),ae)if(Pe={values:getIterationMethod(_e),keys:le?Ve:getIterationMethod(be),entries:getIterationMethod(we)},Se)for(Ie in Pe)(fe||Re||!(Ie in qe))&&ee(qe,Ie,Pe[Ie]);else m({target:s,proto:!0,forced:fe||Re},Pe);return _&&!Se||qe[ye]===Ve||ee(qe,ye,Ve,{name:ae}),ie[s]=Ve,Pe}},35143:(i,s,u)=>{"use strict";var m,v,_,j=u(95981),M=u(57475),$=u(10941),W=u(29290),X=u(249),Y=u(95929),Z=u(99813),ee=u(82529),ae=Z("iterator"),ie=!1;[].keys&&("next"in(_=[].keys())?(v=X(X(_)))!==Object.prototype&&(m=v):ie=!0),!$(m)||j((function(){var i={};return m[ae].call(i)!==i}))?m={}:ee&&(m=W(m)),M(m[ae])||Y(m,ae,(function(){return this})),i.exports={IteratorPrototype:m,BUGGY_SAFARI_ITERATORS:ie}},12077:i=>{i.exports={}},10623:(i,s,u)=>{var m=u(43057);i.exports=function(i){return m(i.length)}},35331:i=>{var s=Math.ceil,u=Math.floor;i.exports=Math.trunc||function trunc(i){var m=+i;return(m>0?u:s)(m)}},14649:(i,s,u)=>{var m=u(85803);i.exports=function(i,s){return void 0===i?arguments.length<2?"":s:m(i)}},24420:(i,s,u)=>{"use strict";var m=u(55746),v=u(95329),_=u(78834),j=u(95981),M=u(14771),$=u(87857),W=u(36760),X=u(89678),Y=u(37026),Z=Object.assign,ee=Object.defineProperty,ae=v([].concat);i.exports=!Z||j((function(){if(m&&1!==Z({b:1},Z(ee({},"a",{enumerable:!0,get:function(){ee(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var i={},s={},u=Symbol(),v="abcdefghijklmnopqrst";return i[u]=7,v.split("").forEach((function(i){s[i]=i})),7!=Z({},i)[u]||M(Z({},s)).join("")!=v}))?function assign(i,s){for(var u=X(i),v=arguments.length,j=1,Z=$.f,ee=W.f;v>j;)for(var ie,le=Y(arguments[j++]),ce=Z?ae(M(le),Z(le)):M(le),pe=ce.length,de=0;pe>de;)ie=ce[de++],m&&!_(ee,le,ie)||(u[ie]=le[ie]);return u}:Z},29290:(i,s,u)=>{var m,v=u(96059),_=u(59938),j=u(56759),M=u(27748),$=u(15463),W=u(61333),X=u(44262),Y="prototype",Z="script",ee=X("IE_PROTO"),EmptyConstructor=function(){},scriptTag=function(i){return"<"+Z+">"+i+""},NullProtoObjectViaActiveX=function(i){i.write(scriptTag("")),i.close();var s=i.parentWindow.Object;return i=null,s},NullProtoObject=function(){try{m=new ActiveXObject("htmlfile")}catch(i){}var i,s,u;NullProtoObject="undefined"!=typeof document?document.domain&&m?NullProtoObjectViaActiveX(m):(s=W("iframe"),u="java"+Z+":",s.style.display="none",$.appendChild(s),s.src=String(u),(i=s.contentWindow.document).open(),i.write(scriptTag("document.F=Object")),i.close(),i.F):NullProtoObjectViaActiveX(m);for(var v=j.length;v--;)delete NullProtoObject[Y][j[v]];return NullProtoObject()};M[ee]=!0,i.exports=Object.create||function create(i,s){var u;return null!==i?(EmptyConstructor[Y]=v(i),u=new EmptyConstructor,EmptyConstructor[Y]=null,u[ee]=i):u=NullProtoObject(),void 0===s?u:_.f(u,s)}},59938:(i,s,u)=>{var m=u(55746),v=u(83937),_=u(65988),j=u(96059),M=u(74529),$=u(14771);s.f=m&&!v?Object.defineProperties:function defineProperties(i,s){j(i);for(var u,m=M(s),v=$(s),W=v.length,X=0;W>X;)_.f(i,u=v[X++],m[u]);return i}},65988:(i,s,u)=>{var m=u(55746),v=u(2840),_=u(83937),j=u(96059),M=u(83894),$=TypeError,W=Object.defineProperty,X=Object.getOwnPropertyDescriptor,Y="enumerable",Z="configurable",ee="writable";s.f=m?_?function defineProperty(i,s,u){if(j(i),s=M(s),j(u),"function"==typeof i&&"prototype"===s&&"value"in u&&ee in u&&!u[ee]){var m=X(i,s);m&&m[ee]&&(i[s]=u.value,u={configurable:Z in u?u[Z]:m[Z],enumerable:Y in u?u[Y]:m[Y],writable:!1})}return W(i,s,u)}:W:function defineProperty(i,s,u){if(j(i),s=M(s),j(u),v)try{return W(i,s,u)}catch(i){}if("get"in u||"set"in u)throw $("Accessors not supported");return"value"in u&&(i[s]=u.value),i}},49677:(i,s,u)=>{var m=u(55746),v=u(78834),_=u(36760),j=u(31887),M=u(74529),$=u(83894),W=u(90953),X=u(2840),Y=Object.getOwnPropertyDescriptor;s.f=m?Y:function getOwnPropertyDescriptor(i,s){if(i=M(i),s=$(s),X)try{return Y(i,s)}catch(i){}if(W(i,s))return j(!v(_.f,i,s),i[s])}},10946:(i,s,u)=>{var m=u(55629),v=u(56759).concat("length","prototype");s.f=Object.getOwnPropertyNames||function getOwnPropertyNames(i){return m(i,v)}},87857:(i,s)=>{s.f=Object.getOwnPropertySymbols},249:(i,s,u)=>{var m=u(90953),v=u(57475),_=u(89678),j=u(44262),M=u(91310),$=j("IE_PROTO"),W=Object,X=W.prototype;i.exports=M?W.getPrototypeOf:function(i){var s=_(i);if(m(s,$))return s[$];var u=s.constructor;return v(u)&&s instanceof u?u.prototype:s instanceof W?X:null}},7046:(i,s,u)=>{var m=u(95329);i.exports=m({}.isPrototypeOf)},55629:(i,s,u)=>{var m=u(95329),v=u(90953),_=u(74529),j=u(31692).indexOf,M=u(27748),$=m([].push);i.exports=function(i,s){var u,m=_(i),W=0,X=[];for(u in m)!v(M,u)&&v(m,u)&&$(X,u);for(;s.length>W;)v(m,u=s[W++])&&(~j(X,u)||$(X,u));return X}},14771:(i,s,u)=>{var m=u(55629),v=u(56759);i.exports=Object.keys||function keys(i){return m(i,v)}},36760:(i,s)=>{"use strict";var u={}.propertyIsEnumerable,m=Object.getOwnPropertyDescriptor,v=m&&!u.call({1:2},1);s.f=v?function propertyIsEnumerable(i){var s=m(this,i);return!!s&&s.enumerable}:u},88929:(i,s,u)=>{var m=u(45526),v=u(96059),_=u(11851);i.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var i,s=!1,u={};try{(i=m(Object.prototype,"__proto__","set"))(u,[]),s=u instanceof Array}catch(i){}return function setPrototypeOf(u,m){return v(u),_(m),s?i(u,m):u.__proto__=m,u}}():void 0)},95623:(i,s,u)=>{"use strict";var m=u(22885),v=u(9697);i.exports=m?{}.toString:function toString(){return"[object "+v(this)+"]"}},39811:(i,s,u)=>{var m=u(78834),v=u(57475),_=u(10941),j=TypeError;i.exports=function(i,s){var u,M;if("string"===s&&v(u=i.toString)&&!_(M=m(u,i)))return M;if(v(u=i.valueOf)&&!_(M=m(u,i)))return M;if("string"!==s&&v(u=i.toString)&&!_(M=m(u,i)))return M;throw j("Can't convert object to primitive value")}},31136:(i,s,u)=>{var m=u(626),v=u(95329),_=u(10946),j=u(87857),M=u(96059),$=v([].concat);i.exports=m("Reflect","ownKeys")||function ownKeys(i){var s=_.f(M(i)),u=j.f;return u?$(s,u(i)):s}},54058:i=>{i.exports={}},9056:(i,s,u)=>{var m=u(65988).f;i.exports=function(i,s,u){u in i||m(i,u,{configurable:!0,get:function(){return s[u]},set:function(i){s[u]=i}})}},48219:(i,s,u)=>{var m=u(82119),v=TypeError;i.exports=function(i){if(m(i))throw v("Can't call method on "+i);return i}},90904:(i,s,u)=>{var m=u(22885),v=u(65988).f,_=u(32029),j=u(90953),M=u(95623),$=u(99813)("toStringTag");i.exports=function(i,s,u,W){if(i){var X=u?i:i.prototype;j(X,$)||v(X,$,{configurable:!0,value:s}),W&&!m&&_(X,"toString",M)}}},44262:(i,s,u)=>{var m=u(68726),v=u(99418),_=m("keys");i.exports=function(i){return _[i]||(_[i]=v(i))}},63030:(i,s,u)=>{var m=u(21899),v=u(75609),_="__core-js_shared__",j=m[_]||v(_,{});i.exports=j},68726:(i,s,u)=>{var m=u(82529),v=u(63030);(i.exports=function(i,s){return v[i]||(v[i]=void 0!==s?s:{})})("versions",[]).push({version:"3.31.1",mode:m?"pure":"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.31.1/LICENSE",source:"https://github.com/zloirock/core-js"})},64620:(i,s,u)=>{var m=u(95329),v=u(62435),_=u(85803),j=u(48219),M=m("".charAt),$=m("".charCodeAt),W=m("".slice),createMethod=function(i){return function(s,u){var m,X,Y=_(j(s)),Z=v(u),ee=Y.length;return Z<0||Z>=ee?i?"":void 0:(m=$(Y,Z))<55296||m>56319||Z+1===ee||(X=$(Y,Z+1))<56320||X>57343?i?M(Y,Z):m:i?W(Y,Z,Z+2):X-56320+(m-55296<<10)+65536}};i.exports={codeAt:createMethod(!1),charAt:createMethod(!0)}},63405:(i,s,u)=>{var m=u(53385),v=u(95981),_=u(21899).String;i.exports=!!Object.getOwnPropertySymbols&&!v((function(){var i=Symbol();return!_(i)||!(Object(i)instanceof Symbol)||!Symbol.sham&&m&&m<41}))},59413:(i,s,u)=>{var m=u(62435),v=Math.max,_=Math.min;i.exports=function(i,s){var u=m(i);return u<0?v(u+s,0):_(u,s)}},74529:(i,s,u)=>{var m=u(37026),v=u(48219);i.exports=function(i){return m(v(i))}},62435:(i,s,u)=>{var m=u(35331);i.exports=function(i){var s=+i;return s!=s||0===s?0:m(s)}},43057:(i,s,u)=>{var m=u(62435),v=Math.min;i.exports=function(i){return i>0?v(m(i),9007199254740991):0}},89678:(i,s,u)=>{var m=u(48219),v=Object;i.exports=function(i){return v(m(i))}},46935:(i,s,u)=>{var m=u(78834),v=u(10941),_=u(56664),j=u(14229),M=u(39811),$=u(99813),W=TypeError,X=$("toPrimitive");i.exports=function(i,s){if(!v(i)||_(i))return i;var u,$=j(i,X);if($){if(void 0===s&&(s="default"),u=m($,i,s),!v(u)||_(u))return u;throw W("Can't convert object to primitive value")}return void 0===s&&(s="number"),M(i,s)}},83894:(i,s,u)=>{var m=u(46935),v=u(56664);i.exports=function(i){var s=m(i,"string");return v(s)?s:s+""}},22885:(i,s,u)=>{var m={};m[u(99813)("toStringTag")]="z",i.exports="[object z]"===String(m)},85803:(i,s,u)=>{var m=u(9697),v=String;i.exports=function(i){if("Symbol"===m(i))throw TypeError("Cannot convert a Symbol value to a string");return v(i)}},69826:i=>{var s=String;i.exports=function(i){try{return s(i)}catch(i){return"Object"}}},99418:(i,s,u)=>{var m=u(95329),v=0,_=Math.random(),j=m(1..toString);i.exports=function(i){return"Symbol("+(void 0===i?"":i)+")_"+j(++v+_,36)}},32302:(i,s,u)=>{var m=u(63405);i.exports=m&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},83937:(i,s,u)=>{var m=u(55746),v=u(95981);i.exports=m&&v((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},47093:(i,s,u)=>{var m=u(21899),v=u(57475),_=m.WeakMap;i.exports=v(_)&&/native code/.test(String(_))},99813:(i,s,u)=>{var m=u(21899),v=u(68726),_=u(90953),j=u(99418),M=u(63405),$=u(32302),W=m.Symbol,X=v("wks"),Y=$?W.for||W:W&&W.withoutSetter||j;i.exports=function(i){return _(X,i)||(X[i]=M&&_(W,i)?W[i]:Y("Symbol."+i)),X[i]}},62864:(i,s,u)=>{"use strict";var m=u(626),v=u(90953),_=u(32029),j=u(7046),M=u(88929),$=u(23489),W=u(9056),X=u(70926),Y=u(14649),Z=u(53794),ee=u(79585),ae=u(55746),ie=u(82529);i.exports=function(i,s,u,le){var ce="stackTraceLimit",pe=le?2:1,de=i.split("."),fe=de[de.length-1],ye=m.apply(null,de);if(ye){var be=ye.prototype;if(!ie&&v(be,"cause")&&delete be.cause,!u)return ye;var _e=m("Error"),we=s((function(i,s){var u=Y(le?s:i,void 0),m=le?new ye(i):new ye;return void 0!==u&&_(m,"message",u),ee(m,we,m.stack,2),this&&j(be,this)&&X(m,this,we),arguments.length>pe&&Z(m,arguments[pe]),m}));if(we.prototype=be,"Error"!==fe?M?M(we,_e):$(we,_e,{name:!0}):ae&&ce in ye&&(W(we,ye,ce),W(we,ye,"prepareStackTrace")),$(we,ye),!ie)try{be.name!==fe&&_(be,"name",fe),be.constructor=we}catch(i){}return we}}},24415:(i,s,u)=>{var m=u(76887),v=u(626),_=u(79730),j=u(95981),M=u(62864),$="AggregateError",W=v($),X=!j((function(){return 1!==W([1]).errors[0]}))&&j((function(){return 7!==W([1],$,{cause:7}).cause}));m({global:!0,constructor:!0,arity:2,forced:X},{AggregateError:M($,(function(i){return function AggregateError(s,u){return _(i,this,arguments)}}),X,!0)})},49812:(i,s,u)=>{"use strict";var m=u(76887),v=u(7046),_=u(249),j=u(88929),M=u(23489),$=u(29290),W=u(32029),X=u(31887),Y=u(53794),Z=u(79585),ee=u(93091),ae=u(14649),ie=u(99813)("toStringTag"),le=Error,ce=[].push,pe=function AggregateError(i,s){var u,m=v(de,this);j?u=j(le(),m?_(this):de):(u=m?this:$(de),W(u,ie,"Error")),void 0!==s&&W(u,"message",ae(s)),Z(u,pe,u.stack,1),arguments.length>2&&Y(u,arguments[2]);var M=[];return ee(i,ce,{that:M}),W(u,"errors",M),u};j?j(pe,le):M(pe,le,{name:!0});var de=pe.prototype=$(le.prototype,{constructor:X(1,pe),message:X(1,""),name:X(1,"AggregateError")});m({global:!0,constructor:!0,arity:2},{AggregateError:pe})},47627:(i,s,u)=>{u(49812)},66274:(i,s,u)=>{"use strict";var m=u(74529),v=u(18479),_=u(12077),j=u(45402),M=u(65988).f,$=u(75105),W=u(23538),X=u(82529),Y=u(55746),Z="Array Iterator",ee=j.set,ae=j.getterFor(Z);i.exports=$(Array,"Array",(function(i,s){ee(this,{type:Z,target:m(i),index:0,kind:s})}),(function(){var i=ae(this),s=i.target,u=i.kind,m=i.index++;return!s||m>=s.length?(i.target=void 0,W(void 0,!0)):W("keys"==u?m:"values"==u?s[m]:[m,s[m]],!1)}),"values");var ie=_.Arguments=_.Array;if(v("keys"),v("values"),v("entries"),!X&&Y&&"values"!==ie.name)try{M(ie,"name",{value:"values"})}catch(i){}},61181:(i,s,u)=>{var m=u(76887),v=u(21899),_=u(79730),j=u(62864),M="WebAssembly",$=v[M],W=7!==Error("e",{cause:7}).cause,exportGlobalErrorCauseWrapper=function(i,s){var u={};u[i]=j(i,s,W),m({global:!0,constructor:!0,arity:1,forced:W},u)},exportWebAssemblyErrorCauseWrapper=function(i,s){if($&&$[i]){var u={};u[i]=j(M+"."+i,s,W),m({target:M,stat:!0,constructor:!0,arity:1,forced:W},u)}};exportGlobalErrorCauseWrapper("Error",(function(i){return function Error(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("EvalError",(function(i){return function EvalError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("RangeError",(function(i){return function RangeError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("ReferenceError",(function(i){return function ReferenceError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("SyntaxError",(function(i){return function SyntaxError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("TypeError",(function(i){return function TypeError(s){return _(i,this,arguments)}})),exportGlobalErrorCauseWrapper("URIError",(function(i){return function URIError(s){return _(i,this,arguments)}})),exportWebAssemblyErrorCauseWrapper("CompileError",(function(i){return function CompileError(s){return _(i,this,arguments)}})),exportWebAssemblyErrorCauseWrapper("LinkError",(function(i){return function LinkError(s){return _(i,this,arguments)}})),exportWebAssemblyErrorCauseWrapper("RuntimeError",(function(i){return function RuntimeError(s){return _(i,this,arguments)}}))},73381:(i,s,u)=>{var m=u(76887),v=u(98308);m({target:"Function",proto:!0,forced:Function.bind!==v},{bind:v})},49221:(i,s,u)=>{var m=u(76887),v=u(24420);m({target:"Object",stat:!0,arity:2,forced:Object.assign!==v},{assign:v})},77971:(i,s,u)=>{"use strict";var m=u(64620).charAt,v=u(85803),_=u(45402),j=u(75105),M=u(23538),$="String Iterator",W=_.set,X=_.getterFor($);j(String,"String",(function(i){W(this,{type:$,string:v(i),index:0})}),(function next(){var i,s=X(this),u=s.string,v=s.index;return v>=u.length?M(void 0,!0):(i=m(u,v),s.index+=i.length,M(i,!1))}))},89731:(i,s,u)=>{u(47627)},7634:(i,s,u)=>{u(66274);var m=u(63281),v=u(21899),_=u(9697),j=u(32029),M=u(12077),$=u(99813)("toStringTag");for(var W in m){var X=v[W],Y=X&&X.prototype;Y&&_(Y)!==$&&j(Y,$,W),M[W]=M.Array}},18957:(i,s,u)=>{u(89731);var m=u(50415);u(7634),i.exports=m},28196:(i,s,u)=>{var m=u(16246);i.exports=m},63383:(i,s,u)=>{var m=u(45999);i.exports=m},8269:function(i,s,u){var m;m=void 0!==u.g?u.g:this,i.exports=function(i){if(i.CSS&&i.CSS.escape)return i.CSS.escape;var cssEscape=function(i){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var s,u=String(i),m=u.length,v=-1,_="",j=u.charCodeAt(0);++v=1&&s<=31||127==s||0==v&&s>=48&&s<=57||1==v&&s>=48&&s<=57&&45==j?"\\"+s.toString(16)+" ":0==v&&1==m&&45==s||!(s>=128||45==s||95==s||s>=48&&s<=57||s>=65&&s<=90||s>=97&&s<=122)?"\\"+u.charAt(v):u.charAt(v):_+="�";return _};return i.CSS||(i.CSS={}),i.CSS.escape=cssEscape,cssEscape}(m)},27698:(i,s,u)=>{"use strict";var m=u(48764).Buffer;function isSpecificValue(i){return i instanceof m||i instanceof Date||i instanceof RegExp}function cloneSpecificValue(i){if(i instanceof m){var s=m.alloc?m.alloc(i.length):new m(i.length);return i.copy(s),s}if(i instanceof Date)return new Date(i.getTime());if(i instanceof RegExp)return new RegExp(i);throw new Error("Unexpected situation")}function deepCloneArray(i){var s=[];return i.forEach((function(i,u){"object"==typeof i&&null!==i?Array.isArray(i)?s[u]=deepCloneArray(i):isSpecificValue(i)?s[u]=cloneSpecificValue(i):s[u]=v({},i):s[u]=i})),s}function safeGetProperty(i,s){return"__proto__"===s?void 0:i[s]}var v=i.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var i,s,u=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(m){"object"!=typeof m||null===m||Array.isArray(m)||Object.keys(m).forEach((function(_){return s=safeGetProperty(u,_),(i=safeGetProperty(m,_))===u?void 0:"object"!=typeof i||null===i?void(u[_]=i):Array.isArray(i)?void(u[_]=deepCloneArray(i)):isSpecificValue(i)?void(u[_]=cloneSpecificValue(i)):"object"!=typeof s||null===s||Array.isArray(s)?void(u[_]=v({},i)):void(u[_]=v(s,i))}))})),u}},9996:i=>{"use strict";var s=function isMergeableObject(i){return function isNonNullObject(i){return!!i&&"object"==typeof i}(i)&&!function isSpecial(i){var s=Object.prototype.toString.call(i);return"[object RegExp]"===s||"[object Date]"===s||function isReactElement(i){return i.$$typeof===u}(i)}(i)};var u="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function cloneUnlessOtherwiseSpecified(i,s){return!1!==s.clone&&s.isMergeableObject(i)?deepmerge(function emptyTarget(i){return Array.isArray(i)?[]:{}}(i),i,s):i}function defaultArrayMerge(i,s,u){return i.concat(s).map((function(i){return cloneUnlessOtherwiseSpecified(i,u)}))}function getKeys(i){return Object.keys(i).concat(function getEnumerableOwnPropertySymbols(i){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(i).filter((function(s){return Object.propertyIsEnumerable.call(i,s)})):[]}(i))}function propertyIsOnObject(i,s){try{return s in i}catch(i){return!1}}function mergeObject(i,s,u){var m={};return u.isMergeableObject(i)&&getKeys(i).forEach((function(s){m[s]=cloneUnlessOtherwiseSpecified(i[s],u)})),getKeys(s).forEach((function(v){(function propertyIsUnsafe(i,s){return propertyIsOnObject(i,s)&&!(Object.hasOwnProperty.call(i,s)&&Object.propertyIsEnumerable.call(i,s))})(i,v)||(propertyIsOnObject(i,v)&&u.isMergeableObject(s[v])?m[v]=function getMergeFunction(i,s){if(!s.customMerge)return deepmerge;var u=s.customMerge(i);return"function"==typeof u?u:deepmerge}(v,u)(i[v],s[v],u):m[v]=cloneUnlessOtherwiseSpecified(s[v],u))})),m}function deepmerge(i,u,m){(m=m||{}).arrayMerge=m.arrayMerge||defaultArrayMerge,m.isMergeableObject=m.isMergeableObject||s,m.cloneUnlessOtherwiseSpecified=cloneUnlessOtherwiseSpecified;var v=Array.isArray(u);return v===Array.isArray(i)?v?m.arrayMerge(i,u,m):mergeObject(i,u,m):cloneUnlessOtherwiseSpecified(u,m)}deepmerge.all=function deepmergeAll(i,s){if(!Array.isArray(i))throw new Error("first argument should be an array");return i.reduce((function(i,u){return deepmerge(i,u,s)}),{})};var m=deepmerge;i.exports=m},27856:function(i){i.exports=function(){"use strict";const{entries:i,setPrototypeOf:s,isFrozen:u,getPrototypeOf:m,getOwnPropertyDescriptor:v}=Object;let{freeze:_,seal:j,create:M}=Object,{apply:$,construct:W}="undefined"!=typeof Reflect&&Reflect;_||(_=function freeze(i){return i}),j||(j=function seal(i){return i}),$||($=function apply(i,s,u){return i.apply(s,u)}),W||(W=function construct(i,s){return new i(...s)});const X=unapply(Array.prototype.forEach),Y=unapply(Array.prototype.pop),Z=unapply(Array.prototype.push),ee=unapply(String.prototype.toLowerCase),ae=unapply(String.prototype.toString),ie=unapply(String.prototype.match),le=unapply(String.prototype.replace),ce=unapply(String.prototype.indexOf),pe=unapply(String.prototype.trim),de=unapply(RegExp.prototype.test),fe=unconstruct(TypeError);function unapply(i){return function(s){for(var u=arguments.length,m=new Array(u>1?u-1:0),v=1;v2&&void 0!==arguments[2]?arguments[2]:ee;s&&s(i,null);let _=m.length;for(;_--;){let s=m[_];if("string"==typeof s){const i=v(s);i!==s&&(u(m)||(m[_]=i),s=i)}i[s]=!0}return i}function clone(s){const u=M(null);for(const[m,_]of i(s))void 0!==v(s,m)&&(u[m]=_);return u}function lookupGetter(i,s){for(;null!==i;){const u=v(i,s);if(u){if(u.get)return unapply(u.get);if("function"==typeof u.value)return unapply(u.value)}i=m(i)}function fallbackValue(i){return console.warn("fallback value for",i),null}return fallbackValue}const ye=_(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),be=_(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),_e=_(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),we=_(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),Se=_(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),xe=_(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),Pe=_(["#text"]),Ie=_(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","xmlns","slot"]),Te=_(["accent-height","accumulate","additive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),Re=_(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),qe=_(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),ze=j(/\{\{[\w\W]*|[\w\W]*\}\}/gm),Ve=j(/<%[\w\W]*|[\w\W]*%>/gm),We=j(/\${[\w\W]*}/gm),He=j(/^data-[\-\w.\u00B7-\uFFFF]/),Xe=j(/^aria-[\-\w]+$/),Ye=j(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Qe=j(/^(?:\w+script|data):/i),et=j(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),tt=j(/^html$/i);var rt=Object.freeze({__proto__:null,MUSTACHE_EXPR:ze,ERB_EXPR:Ve,TMPLIT_EXPR:We,DATA_ATTR:He,ARIA_ATTR:Xe,IS_ALLOWED_URI:Ye,IS_SCRIPT_OR_DATA:Qe,ATTR_WHITESPACE:et,DOCTYPE_NAME:tt});const nt=function getGlobal(){return"undefined"==typeof window?null:window},ot=function _createTrustedTypesPolicy(i,s){if("object"!=typeof i||"function"!=typeof i.createPolicy)return null;let u=null;const m="data-tt-policy-suffix";s&&s.hasAttribute(m)&&(u=s.getAttribute(m));const v="dompurify"+(u?"#"+u:"");try{return i.createPolicy(v,{createHTML:i=>i,createScriptURL:i=>i})}catch(i){return console.warn("TrustedTypes policy "+v+" could not be created."),null}};function createDOMPurify(){let s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:nt();const DOMPurify=i=>createDOMPurify(i);if(DOMPurify.version="3.0.6",DOMPurify.removed=[],!s||!s.document||9!==s.document.nodeType)return DOMPurify.isSupported=!1,DOMPurify;let{document:u}=s;const m=u,v=m.currentScript,{DocumentFragment:j,HTMLTemplateElement:$,Node:W,Element:ze,NodeFilter:Ve,NamedNodeMap:We=s.NamedNodeMap||s.MozNamedAttrMap,HTMLFormElement:He,DOMParser:Xe,trustedTypes:Qe}=s,et=ze.prototype,at=lookupGetter(et,"cloneNode"),it=lookupGetter(et,"nextSibling"),st=lookupGetter(et,"childNodes"),lt=lookupGetter(et,"parentNode");if("function"==typeof $){const i=u.createElement("template");i.content&&i.content.ownerDocument&&(u=i.content.ownerDocument)}let ct,ut="";const{implementation:pt,createNodeIterator:ht,createDocumentFragment:dt,getElementsByTagName:mt}=u,{importNode:gt}=m;let yt={};DOMPurify.isSupported="function"==typeof i&&"function"==typeof lt&&pt&&void 0!==pt.createHTMLDocument;const{MUSTACHE_EXPR:vt,ERB_EXPR:bt,TMPLIT_EXPR:_t,DATA_ATTR:Et,ARIA_ATTR:wt,IS_SCRIPT_OR_DATA:St,ATTR_WHITESPACE:xt}=rt;let{IS_ALLOWED_URI:kt}=rt,Ot=null;const At=addToSet({},[...ye,...be,..._e,...Se,...Pe]);let Ct=null;const jt=addToSet({},[...Ie,...Te,...Re,...qe]);let Pt=Object.seal(M(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),It=null,Nt=null,Tt=!0,Mt=!0,Rt=!1,Bt=!0,Dt=!1,Lt=!1,Ft=!1,qt=!1,$t=!1,zt=!1,Ut=!1,Vt=!0,Wt=!1;const Kt="user-content-";let Ht=!0,Jt=!1,Gt={},Xt=null;const Yt=addToSet({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let Qt=null;const Zt=addToSet({},["audio","video","img","source","image","track"]);let er=null;const tr=addToSet({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),rr="http://www.w3.org/1998/Math/MathML",nr="http://www.w3.org/2000/svg",ar="http://www.w3.org/1999/xhtml";let ir=ar,sr=!1,lr=null;const cr=addToSet({},[rr,nr,ar],ae);let ur=null;const pr=["application/xhtml+xml","text/html"],dr="text/html";let fr=null,mr=null;const gr=u.createElement("form"),yr=function isRegexOrFunction(i){return i instanceof RegExp||i instanceof Function},vr=function _parseConfig(){let i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!mr||mr!==i){if(i&&"object"==typeof i||(i={}),i=clone(i),ur=ur=-1===pr.indexOf(i.PARSER_MEDIA_TYPE)?dr:i.PARSER_MEDIA_TYPE,fr="application/xhtml+xml"===ur?ae:ee,Ot="ALLOWED_TAGS"in i?addToSet({},i.ALLOWED_TAGS,fr):At,Ct="ALLOWED_ATTR"in i?addToSet({},i.ALLOWED_ATTR,fr):jt,lr="ALLOWED_NAMESPACES"in i?addToSet({},i.ALLOWED_NAMESPACES,ae):cr,er="ADD_URI_SAFE_ATTR"in i?addToSet(clone(tr),i.ADD_URI_SAFE_ATTR,fr):tr,Qt="ADD_DATA_URI_TAGS"in i?addToSet(clone(Zt),i.ADD_DATA_URI_TAGS,fr):Zt,Xt="FORBID_CONTENTS"in i?addToSet({},i.FORBID_CONTENTS,fr):Yt,It="FORBID_TAGS"in i?addToSet({},i.FORBID_TAGS,fr):{},Nt="FORBID_ATTR"in i?addToSet({},i.FORBID_ATTR,fr):{},Gt="USE_PROFILES"in i&&i.USE_PROFILES,Tt=!1!==i.ALLOW_ARIA_ATTR,Mt=!1!==i.ALLOW_DATA_ATTR,Rt=i.ALLOW_UNKNOWN_PROTOCOLS||!1,Bt=!1!==i.ALLOW_SELF_CLOSE_IN_ATTR,Dt=i.SAFE_FOR_TEMPLATES||!1,Lt=i.WHOLE_DOCUMENT||!1,$t=i.RETURN_DOM||!1,zt=i.RETURN_DOM_FRAGMENT||!1,Ut=i.RETURN_TRUSTED_TYPE||!1,qt=i.FORCE_BODY||!1,Vt=!1!==i.SANITIZE_DOM,Wt=i.SANITIZE_NAMED_PROPS||!1,Ht=!1!==i.KEEP_CONTENT,Jt=i.IN_PLACE||!1,kt=i.ALLOWED_URI_REGEXP||Ye,ir=i.NAMESPACE||ar,Pt=i.CUSTOM_ELEMENT_HANDLING||{},i.CUSTOM_ELEMENT_HANDLING&&yr(i.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Pt.tagNameCheck=i.CUSTOM_ELEMENT_HANDLING.tagNameCheck),i.CUSTOM_ELEMENT_HANDLING&&yr(i.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Pt.attributeNameCheck=i.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),i.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof i.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Pt.allowCustomizedBuiltInElements=i.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Dt&&(Mt=!1),zt&&($t=!0),Gt&&(Ot=addToSet({},[...Pe]),Ct=[],!0===Gt.html&&(addToSet(Ot,ye),addToSet(Ct,Ie)),!0===Gt.svg&&(addToSet(Ot,be),addToSet(Ct,Te),addToSet(Ct,qe)),!0===Gt.svgFilters&&(addToSet(Ot,_e),addToSet(Ct,Te),addToSet(Ct,qe)),!0===Gt.mathMl&&(addToSet(Ot,Se),addToSet(Ct,Re),addToSet(Ct,qe))),i.ADD_TAGS&&(Ot===At&&(Ot=clone(Ot)),addToSet(Ot,i.ADD_TAGS,fr)),i.ADD_ATTR&&(Ct===jt&&(Ct=clone(Ct)),addToSet(Ct,i.ADD_ATTR,fr)),i.ADD_URI_SAFE_ATTR&&addToSet(er,i.ADD_URI_SAFE_ATTR,fr),i.FORBID_CONTENTS&&(Xt===Yt&&(Xt=clone(Xt)),addToSet(Xt,i.FORBID_CONTENTS,fr)),Ht&&(Ot["#text"]=!0),Lt&&addToSet(Ot,["html","head","body"]),Ot.table&&(addToSet(Ot,["tbody"]),delete It.tbody),i.TRUSTED_TYPES_POLICY){if("function"!=typeof i.TRUSTED_TYPES_POLICY.createHTML)throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof i.TRUSTED_TYPES_POLICY.createScriptURL)throw fe('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ct=i.TRUSTED_TYPES_POLICY,ut=ct.createHTML("")}else void 0===ct&&(ct=ot(Qe,v)),null!==ct&&"string"==typeof ut&&(ut=ct.createHTML(""));_&&_(i),mr=i}},br=addToSet({},["mi","mo","mn","ms","mtext"]),_r=addToSet({},["foreignobject","desc","title","annotation-xml"]),Er=addToSet({},["title","style","font","a","script"]),wr=addToSet({},be);addToSet(wr,_e),addToSet(wr,we);const Sr=addToSet({},Se);addToSet(Sr,xe);const xr=function _checkValidNamespace(i){let s=lt(i);s&&s.tagName||(s={namespaceURI:ir,tagName:"template"});const u=ee(i.tagName),m=ee(s.tagName);return!!lr[i.namespaceURI]&&(i.namespaceURI===nr?s.namespaceURI===ar?"svg"===u:s.namespaceURI===rr?"svg"===u&&("annotation-xml"===m||br[m]):Boolean(wr[u]):i.namespaceURI===rr?s.namespaceURI===ar?"math"===u:s.namespaceURI===nr?"math"===u&&_r[m]:Boolean(Sr[u]):i.namespaceURI===ar?!(s.namespaceURI===nr&&!_r[m])&&!(s.namespaceURI===rr&&!br[m])&&!Sr[u]&&(Er[u]||!wr[u]):!("application/xhtml+xml"!==ur||!lr[i.namespaceURI]))},kr=function _forceRemove(i){Z(DOMPurify.removed,{element:i});try{i.parentNode.removeChild(i)}catch(s){i.remove()}},Or=function _removeAttribute(i,s){try{Z(DOMPurify.removed,{attribute:s.getAttributeNode(i),from:s})}catch(i){Z(DOMPurify.removed,{attribute:null,from:s})}if(s.removeAttribute(i),"is"===i&&!Ct[i])if($t||zt)try{kr(s)}catch(i){}else try{s.setAttribute(i,"")}catch(i){}},Ar=function _initDocument(i){let s=null,m=null;if(qt)i=""+i;else{const s=ie(i,/^[\r\n\t ]+/);m=s&&s[0]}"application/xhtml+xml"===ur&&ir===ar&&(i=''+i+"");const v=ct?ct.createHTML(i):i;if(ir===ar)try{s=(new Xe).parseFromString(v,ur)}catch(i){}if(!s||!s.documentElement){s=pt.createDocument(ir,"template",null);try{s.documentElement.innerHTML=sr?ut:v}catch(i){}}const _=s.body||s.documentElement;return i&&m&&_.insertBefore(u.createTextNode(m),_.childNodes[0]||null),ir===ar?mt.call(s,Lt?"html":"body")[0]:Lt?s.documentElement:_},Cr=function _createNodeIterator(i){return ht.call(i.ownerDocument||i,i,Ve.SHOW_ELEMENT|Ve.SHOW_COMMENT|Ve.SHOW_TEXT,null)},jr=function _isClobbered(i){return i instanceof He&&("string"!=typeof i.nodeName||"string"!=typeof i.textContent||"function"!=typeof i.removeChild||!(i.attributes instanceof We)||"function"!=typeof i.removeAttribute||"function"!=typeof i.setAttribute||"string"!=typeof i.namespaceURI||"function"!=typeof i.insertBefore||"function"!=typeof i.hasChildNodes)},Pr=function _isNode(i){return"function"==typeof W&&i instanceof W},Ir=function _executeHook(i,s,u){yt[i]&&X(yt[i],(i=>{i.call(DOMPurify,s,u,mr)}))},Nr=function _sanitizeElements(i){let s=null;if(Ir("beforeSanitizeElements",i,null),jr(i))return kr(i),!0;const u=fr(i.nodeName);if(Ir("uponSanitizeElement",i,{tagName:u,allowedTags:Ot}),i.hasChildNodes()&&!Pr(i.firstElementChild)&&de(/<[/\w]/g,i.innerHTML)&&de(/<[/\w]/g,i.textContent))return kr(i),!0;if(!Ot[u]||It[u]){if(!It[u]&&Mr(u)){if(Pt.tagNameCheck instanceof RegExp&&de(Pt.tagNameCheck,u))return!1;if(Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(u))return!1}if(Ht&&!Xt[u]){const s=lt(i)||i.parentNode,u=st(i)||i.childNodes;if(u&&s)for(let m=u.length-1;m>=0;--m)s.insertBefore(at(u[m],!0),it(i))}return kr(i),!0}return i instanceof ze&&!xr(i)?(kr(i),!0):"noscript"!==u&&"noembed"!==u&&"noframes"!==u||!de(/<\/no(script|embed|frames)/i,i.innerHTML)?(Dt&&3===i.nodeType&&(s=i.textContent,X([vt,bt,_t],(i=>{s=le(s,i," ")})),i.textContent!==s&&(Z(DOMPurify.removed,{element:i.cloneNode()}),i.textContent=s)),Ir("afterSanitizeElements",i,null),!1):(kr(i),!0)},Tr=function _isValidAttribute(i,s,m){if(Vt&&("id"===s||"name"===s)&&(m in u||m in gr))return!1;if(Mt&&!Nt[s]&&de(Et,s));else if(Tt&&de(wt,s));else if(!Ct[s]||Nt[s]){if(!(Mr(i)&&(Pt.tagNameCheck instanceof RegExp&&de(Pt.tagNameCheck,i)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(i))&&(Pt.attributeNameCheck instanceof RegExp&&de(Pt.attributeNameCheck,s)||Pt.attributeNameCheck instanceof Function&&Pt.attributeNameCheck(s))||"is"===s&&Pt.allowCustomizedBuiltInElements&&(Pt.tagNameCheck instanceof RegExp&&de(Pt.tagNameCheck,m)||Pt.tagNameCheck instanceof Function&&Pt.tagNameCheck(m))))return!1}else if(er[s]);else if(de(kt,le(m,xt,"")));else if("src"!==s&&"xlink:href"!==s&&"href"!==s||"script"===i||0!==ce(m,"data:")||!Qt[i])if(Rt&&!de(St,le(m,xt,"")));else if(m)return!1;return!0},Mr=function _isBasicCustomElement(i){return i.indexOf("-")>0},Rr=function _sanitizeAttributes(i){Ir("beforeSanitizeAttributes",i,null);const{attributes:s}=i;if(!s)return;const u={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Ct};let m=s.length;for(;m--;){const v=s[m],{name:_,namespaceURI:j,value:M}=v,$=fr(_);let W="value"===_?M:pe(M);if(u.attrName=$,u.attrValue=W,u.keepAttr=!0,u.forceKeepAttr=void 0,Ir("uponSanitizeAttribute",i,u),W=u.attrValue,u.forceKeepAttr)continue;if(Or(_,i),!u.keepAttr)continue;if(!Bt&&de(/\/>/i,W)){Or(_,i);continue}Dt&&X([vt,bt,_t],(i=>{W=le(W,i," ")}));const Z=fr(i.nodeName);if(Tr(Z,$,W)){if(!Wt||"id"!==$&&"name"!==$||(Or(_,i),W=Kt+W),ct&&"object"==typeof Qe&&"function"==typeof Qe.getAttributeType)if(j);else switch(Qe.getAttributeType(Z,$)){case"TrustedHTML":W=ct.createHTML(W);break;case"TrustedScriptURL":W=ct.createScriptURL(W)}try{j?i.setAttributeNS(j,_,W):i.setAttribute(_,W),Y(DOMPurify.removed)}catch(i){}}}Ir("afterSanitizeAttributes",i,null)},Br=function _sanitizeShadowDOM(i){let s=null;const u=Cr(i);for(Ir("beforeSanitizeShadowDOM",i,null);s=u.nextNode();)Ir("uponSanitizeShadowNode",s,null),Nr(s)||(s.content instanceof j&&_sanitizeShadowDOM(s.content),Rr(s));Ir("afterSanitizeShadowDOM",i,null)};return DOMPurify.sanitize=function(i){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},u=null,v=null,_=null,M=null;if(sr=!i,sr&&(i="\x3c!--\x3e"),"string"!=typeof i&&!Pr(i)){if("function"!=typeof i.toString)throw fe("toString is not a function");if("string"!=typeof(i=i.toString()))throw fe("dirty is not a string, aborting")}if(!DOMPurify.isSupported)return i;if(Ft||vr(s),DOMPurify.removed=[],"string"==typeof i&&(Jt=!1),Jt){if(i.nodeName){const s=fr(i.nodeName);if(!Ot[s]||It[s])throw fe("root node is forbidden and cannot be sanitized in-place")}}else if(i instanceof W)u=Ar("\x3c!----\x3e"),v=u.ownerDocument.importNode(i,!0),1===v.nodeType&&"BODY"===v.nodeName||"HTML"===v.nodeName?u=v:u.appendChild(v);else{if(!$t&&!Dt&&!Lt&&-1===i.indexOf("<"))return ct&&Ut?ct.createHTML(i):i;if(u=Ar(i),!u)return $t?null:Ut?ut:""}u&&qt&&kr(u.firstChild);const $=Cr(Jt?i:u);for(;_=$.nextNode();)Nr(_)||(_.content instanceof j&&Br(_.content),Rr(_));if(Jt)return i;if($t){if(zt)for(M=dt.call(u.ownerDocument);u.firstChild;)M.appendChild(u.firstChild);else M=u;return(Ct.shadowroot||Ct.shadowrootmode)&&(M=gt.call(m,M,!0)),M}let Y=Lt?u.outerHTML:u.innerHTML;return Lt&&Ot["!doctype"]&&u.ownerDocument&&u.ownerDocument.doctype&&u.ownerDocument.doctype.name&&de(tt,u.ownerDocument.doctype.name)&&(Y="\n"+Y),Dt&&X([vt,bt,_t],(i=>{Y=le(Y,i," ")})),ct&&Ut?ct.createHTML(Y):Y},DOMPurify.setConfig=function(){vr(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),Ft=!0},DOMPurify.clearConfig=function(){mr=null,Ft=!1},DOMPurify.isValidAttribute=function(i,s,u){mr||vr({});const m=fr(i),v=fr(s);return Tr(m,v,u)},DOMPurify.addHook=function(i,s){"function"==typeof s&&(yt[i]=yt[i]||[],Z(yt[i],s))},DOMPurify.removeHook=function(i){if(yt[i])return Y(yt[i])},DOMPurify.removeHooks=function(i){yt[i]&&(yt[i]=[])},DOMPurify.removeAllHooks=function(){yt={}},DOMPurify}return createDOMPurify()}()},69450:i=>{"use strict";class SubRange{constructor(i,s){this.low=i,this.high=s,this.length=1+s-i}overlaps(i){return!(this.highi.high)}touches(i){return!(this.high+1i.high)}add(i){return new SubRange(Math.min(this.low,i.low),Math.max(this.high,i.high))}subtract(i){return i.low<=this.low&&i.high>=this.high?[]:i.low>this.low&&i.highi+s.length),0)}add(i,s){var _add=i=>{for(var s=0;s{for(var s=0;s{for(var s=0;s{for(var u=s.low;u<=s.high;)i.push(u),u++;return i}),[])}subranges(){return this.ranges.map((i=>({low:i.low,high:i.high,length:1+i.high-i.low})))}}i.exports=DRange},17187:i=>{"use strict";var s,u="object"==typeof Reflect?Reflect:null,m=u&&"function"==typeof u.apply?u.apply:function ReflectApply(i,s,u){return Function.prototype.apply.call(i,s,u)};s=u&&"function"==typeof u.ownKeys?u.ownKeys:Object.getOwnPropertySymbols?function ReflectOwnKeys(i){return Object.getOwnPropertyNames(i).concat(Object.getOwnPropertySymbols(i))}:function ReflectOwnKeys(i){return Object.getOwnPropertyNames(i)};var v=Number.isNaN||function NumberIsNaN(i){return i!=i};function EventEmitter(){EventEmitter.init.call(this)}i.exports=EventEmitter,i.exports.once=function once(i,s){return new Promise((function(u,m){function errorListener(u){i.removeListener(s,resolver),m(u)}function resolver(){"function"==typeof i.removeListener&&i.removeListener("error",errorListener),u([].slice.call(arguments))}eventTargetAgnosticAddListener(i,s,resolver,{once:!0}),"error"!==s&&function addErrorHandlerIfEventEmitter(i,s,u){"function"==typeof i.on&&eventTargetAgnosticAddListener(i,"error",s,u)}(i,errorListener,{once:!0})}))},EventEmitter.EventEmitter=EventEmitter,EventEmitter.prototype._events=void 0,EventEmitter.prototype._eventsCount=0,EventEmitter.prototype._maxListeners=void 0;var _=10;function checkListener(i){if("function"!=typeof i)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof i)}function _getMaxListeners(i){return void 0===i._maxListeners?EventEmitter.defaultMaxListeners:i._maxListeners}function _addListener(i,s,u,m){var v,_,j;if(checkListener(u),void 0===(_=i._events)?(_=i._events=Object.create(null),i._eventsCount=0):(void 0!==_.newListener&&(i.emit("newListener",s,u.listener?u.listener:u),_=i._events),j=_[s]),void 0===j)j=_[s]=u,++i._eventsCount;else if("function"==typeof j?j=_[s]=m?[u,j]:[j,u]:m?j.unshift(u):j.push(u),(v=_getMaxListeners(i))>0&&j.length>v&&!j.warned){j.warned=!0;var M=new Error("Possible EventEmitter memory leak detected. "+j.length+" "+String(s)+" listeners added. Use emitter.setMaxListeners() to increase limit");M.name="MaxListenersExceededWarning",M.emitter=i,M.type=s,M.count=j.length,function ProcessEmitWarning(i){console&&console.warn&&console.warn(i)}(M)}return i}function onceWrapper(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function _onceWrap(i,s,u){var m={fired:!1,wrapFn:void 0,target:i,type:s,listener:u},v=onceWrapper.bind(m);return v.listener=u,m.wrapFn=v,v}function _listeners(i,s,u){var m=i._events;if(void 0===m)return[];var v=m[s];return void 0===v?[]:"function"==typeof v?u?[v.listener||v]:[v]:u?function unwrapListeners(i){for(var s=new Array(i.length),u=0;u0&&(j=s[0]),j instanceof Error)throw j;var M=new Error("Unhandled error."+(j?" ("+j.message+")":""));throw M.context=j,M}var $=_[i];if(void 0===$)return!1;if("function"==typeof $)m($,this,s);else{var W=$.length,X=arrayClone($,W);for(u=0;u=0;_--)if(u[_]===s||u[_].listener===s){j=u[_].listener,v=_;break}if(v<0)return this;0===v?u.shift():function spliceOne(i,s){for(;s+1=0;m--)this.removeListener(i,s[m]);return this},EventEmitter.prototype.listeners=function listeners(i){return _listeners(this,i,!0)},EventEmitter.prototype.rawListeners=function rawListeners(i){return _listeners(this,i,!1)},EventEmitter.listenerCount=function(i,s){return"function"==typeof i.listenerCount?i.listenerCount(s):listenerCount.call(i,s)},EventEmitter.prototype.listenerCount=listenerCount,EventEmitter.prototype.eventNames=function eventNames(){return this._eventsCount>0?s(this._events):[]}},21102:(i,s,u)=>{"use strict";var m=u(46291),v=create(Error);function create(i){return FormattedError.displayName=i.displayName||i.name,FormattedError;function FormattedError(s){return s&&(s=m.apply(null,arguments)),new i(s)}}i.exports=v,v.eval=create(EvalError),v.range=create(RangeError),v.reference=create(ReferenceError),v.syntax=create(SyntaxError),v.type=create(TypeError),v.uri=create(URIError),v.create=create},46291:i=>{!function(){var s;function format(i){for(var s,u,m,v,_=1,j=[].slice.call(arguments),M=0,$=i.length,W="",X=!1,Y=!1,nextArg=function(){return j[_++]},slurpNumber=function(){for(var u="";/\d/.test(i[M]);)u+=i[M++],s=i[M];return u.length>0?parseInt(u):null};M<$;++M)if(s=i[M],X)switch(X=!1,"."==s?(Y=!1,s=i[++M]):"0"==s&&"."==i[M+1]?(Y=!0,s=i[M+=2]):Y=!0,v=slurpNumber(),s){case"b":W+=parseInt(nextArg(),10).toString(2);break;case"c":W+="string"==typeof(u=nextArg())||u instanceof String?u:String.fromCharCode(parseInt(u,10));break;case"d":W+=parseInt(nextArg(),10);break;case"f":m=String(parseFloat(nextArg()).toFixed(v||6)),W+=Y?m:m.replace(/^0/,"");break;case"j":W+=JSON.stringify(nextArg());break;case"o":W+="0"+parseInt(nextArg(),10).toString(8);break;case"s":W+=nextArg();break;case"x":W+="0x"+parseInt(nextArg(),10).toString(16);break;case"X":W+="0x"+parseInt(nextArg(),10).toString(16).toUpperCase();break;default:W+=s}else"%"===s?X=!0:W+=s;return W}(s=i.exports=format).format=format,s.vsprintf=function vsprintf(i,s){return format.apply(null,[i].concat(s))},"undefined"!=typeof console&&"function"==typeof console.log&&(s.printf=function printf(){console.log(format.apply(null,arguments))})}()},17648:i=>{"use strict";var s=Array.prototype.slice,u=Object.prototype.toString;i.exports=function bind(i){var m=this;if("function"!=typeof m||"[object Function]"!==u.call(m))throw new TypeError("Function.prototype.bind called on incompatible "+m);for(var v,_=s.call(arguments,1),j=Math.max(0,m.length-_.length),M=[],$=0;${"use strict";var m=u(17648);i.exports=Function.prototype.bind||m},40210:(i,s,u)=>{"use strict";var m,v=SyntaxError,_=Function,j=TypeError,getEvalledConstructor=function(i){try{return _('"use strict"; return ('+i+").constructor;")()}catch(i){}},M=Object.getOwnPropertyDescriptor;if(M)try{M({},"")}catch(i){M=null}var throwTypeError=function(){throw new j},$=M?function(){try{return throwTypeError}catch(i){try{return M(arguments,"callee").get}catch(i){return throwTypeError}}}():throwTypeError,W=u(41405)(),X=u(28185)(),Y=Object.getPrototypeOf||(X?function(i){return i.__proto__}:null),Z={},ee="undefined"!=typeof Uint8Array&&Y?Y(Uint8Array):m,ae={"%AggregateError%":"undefined"==typeof AggregateError?m:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?m:ArrayBuffer,"%ArrayIteratorPrototype%":W&&Y?Y([][Symbol.iterator]()):m,"%AsyncFromSyncIteratorPrototype%":m,"%AsyncFunction%":Z,"%AsyncGenerator%":Z,"%AsyncGeneratorFunction%":Z,"%AsyncIteratorPrototype%":Z,"%Atomics%":"undefined"==typeof Atomics?m:Atomics,"%BigInt%":"undefined"==typeof BigInt?m:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?m:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?m:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?m:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":Error,"%eval%":eval,"%EvalError%":EvalError,"%Float32Array%":"undefined"==typeof Float32Array?m:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?m:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?m:FinalizationRegistry,"%Function%":_,"%GeneratorFunction%":Z,"%Int8Array%":"undefined"==typeof Int8Array?m:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?m:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?m:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":W&&Y?Y(Y([][Symbol.iterator]())):m,"%JSON%":"object"==typeof JSON?JSON:m,"%Map%":"undefined"==typeof Map?m:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&W&&Y?Y((new Map)[Symbol.iterator]()):m,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?m:Promise,"%Proxy%":"undefined"==typeof Proxy?m:Proxy,"%RangeError%":RangeError,"%ReferenceError%":ReferenceError,"%Reflect%":"undefined"==typeof Reflect?m:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?m:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&W&&Y?Y((new Set)[Symbol.iterator]()):m,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?m:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":W&&Y?Y(""[Symbol.iterator]()):m,"%Symbol%":W?Symbol:m,"%SyntaxError%":v,"%ThrowTypeError%":$,"%TypedArray%":ee,"%TypeError%":j,"%Uint8Array%":"undefined"==typeof Uint8Array?m:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?m:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?m:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?m:Uint32Array,"%URIError%":URIError,"%WeakMap%":"undefined"==typeof WeakMap?m:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?m:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?m:WeakSet};if(Y)try{null.error}catch(i){var ie=Y(Y(i));ae["%Error.prototype%"]=ie}var le=function doEval(i){var s;if("%AsyncFunction%"===i)s=getEvalledConstructor("async function () {}");else if("%GeneratorFunction%"===i)s=getEvalledConstructor("function* () {}");else if("%AsyncGeneratorFunction%"===i)s=getEvalledConstructor("async function* () {}");else if("%AsyncGenerator%"===i){var u=doEval("%AsyncGeneratorFunction%");u&&(s=u.prototype)}else if("%AsyncIteratorPrototype%"===i){var m=doEval("%AsyncGenerator%");m&&Y&&(s=Y(m.prototype))}return ae[i]=s,s},ce={"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},pe=u(58612),de=u(17642),fe=pe.call(Function.call,Array.prototype.concat),ye=pe.call(Function.apply,Array.prototype.splice),be=pe.call(Function.call,String.prototype.replace),_e=pe.call(Function.call,String.prototype.slice),we=pe.call(Function.call,RegExp.prototype.exec),Se=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,xe=/\\(\\)?/g,Pe=function getBaseIntrinsic(i,s){var u,m=i;if(de(ce,m)&&(m="%"+(u=ce[m])[0]+"%"),de(ae,m)){var _=ae[m];if(_===Z&&(_=le(m)),void 0===_&&!s)throw new j("intrinsic "+i+" exists, but is not available. Please file an issue!");return{alias:u,name:m,value:_}}throw new v("intrinsic "+i+" does not exist!")};i.exports=function GetIntrinsic(i,s){if("string"!=typeof i||0===i.length)throw new j("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof s)throw new j('"allowMissing" argument must be a boolean');if(null===we(/^%?[^%]*%?$/,i))throw new v("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var u=function stringToPath(i){var s=_e(i,0,1),u=_e(i,-1);if("%"===s&&"%"!==u)throw new v("invalid intrinsic syntax, expected closing `%`");if("%"===u&&"%"!==s)throw new v("invalid intrinsic syntax, expected opening `%`");var m=[];return be(i,Se,(function(i,s,u,v){m[m.length]=u?be(v,xe,"$1"):s||i})),m}(i),m=u.length>0?u[0]:"",_=Pe("%"+m+"%",s),$=_.name,W=_.value,X=!1,Y=_.alias;Y&&(m=Y[0],ye(u,fe([0,1],Y)));for(var Z=1,ee=!0;Z=u.length){var pe=M(W,ie);W=(ee=!!pe)&&"get"in pe&&!("originalValue"in pe.get)?pe.get:W[ie]}else ee=de(W,ie),W=W[ie];ee&&!X&&(ae[$]=W)}}return W}},28185:i=>{"use strict";var s={foo:{}},u=Object;i.exports=function hasProto(){return{__proto__:s}.foo===s.foo&&!({__proto__:null}instanceof u)}},41405:(i,s,u)=>{"use strict";var m="undefined"!=typeof Symbol&&Symbol,v=u(55419);i.exports=function hasNativeSymbols(){return"function"==typeof m&&("function"==typeof Symbol&&("symbol"==typeof m("foo")&&("symbol"==typeof Symbol("bar")&&v())))}},55419:i=>{"use strict";i.exports=function hasSymbols(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var i={},s=Symbol("test"),u=Object(s);if("string"==typeof s)return!1;if("[object Symbol]"!==Object.prototype.toString.call(s))return!1;if("[object Symbol]"!==Object.prototype.toString.call(u))return!1;for(s in i[s]=42,i)return!1;if("function"==typeof Object.keys&&0!==Object.keys(i).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(i).length)return!1;var m=Object.getOwnPropertySymbols(i);if(1!==m.length||m[0]!==s)return!1;if(!Object.prototype.propertyIsEnumerable.call(i,s))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var v=Object.getOwnPropertyDescriptor(i,s);if(42!==v.value||!0!==v.enumerable)return!1}return!0}},17642:(i,s,u)=>{"use strict";var m=u(58612);i.exports=m.call(Function.call,Object.prototype.hasOwnProperty)},47802:i=>{function deepFreeze(i){return i instanceof Map?i.clear=i.delete=i.set=function(){throw new Error("map is read-only")}:i instanceof Set&&(i.add=i.clear=i.delete=function(){throw new Error("set is read-only")}),Object.freeze(i),Object.getOwnPropertyNames(i).forEach((function(s){var u=i[s];"object"!=typeof u||Object.isFrozen(u)||deepFreeze(u)})),i}var s=deepFreeze,u=deepFreeze;s.default=u;class Response{constructor(i){void 0===i.data&&(i.data={}),this.data=i.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function escapeHTML(i){return i.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function inherit(i,...s){const u=Object.create(null);for(const s in i)u[s]=i[s];return s.forEach((function(i){for(const s in i)u[s]=i[s]})),u}const emitsWrappingTags=i=>!!i.kind;class HTMLRenderer{constructor(i,s){this.buffer="",this.classPrefix=s.classPrefix,i.walk(this)}addText(i){this.buffer+=escapeHTML(i)}openNode(i){if(!emitsWrappingTags(i))return;let s=i.kind;i.sublanguage||(s=`${this.classPrefix}${s}`),this.span(s)}closeNode(i){emitsWrappingTags(i)&&(this.buffer+="")}value(){return this.buffer}span(i){this.buffer+=``}}class TokenTree{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(i){this.top.children.push(i)}openNode(i){const s={kind:i,children:[]};this.add(s),this.stack.push(s)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(i){return this.constructor._walk(i,this.rootNode)}static _walk(i,s){return"string"==typeof s?i.addText(s):s.children&&(i.openNode(s),s.children.forEach((s=>this._walk(i,s))),i.closeNode(s)),i}static _collapse(i){"string"!=typeof i&&i.children&&(i.children.every((i=>"string"==typeof i))?i.children=[i.children.join("")]:i.children.forEach((i=>{TokenTree._collapse(i)})))}}class TokenTreeEmitter extends TokenTree{constructor(i){super(),this.options=i}addKeyword(i,s){""!==i&&(this.openNode(s),this.addText(i),this.closeNode())}addText(i){""!==i&&this.add(i)}addSublanguage(i,s){const u=i.root;u.kind=s,u.sublanguage=!0,this.add(u)}toHTML(){return new HTMLRenderer(this,this.options).value()}finalize(){return!0}}function source(i){return i?"string"==typeof i?i:i.source:null}const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const v="[a-zA-Z]\\w*",_="[a-zA-Z_]\\w*",j="\\b\\d+(\\.\\d+)?",M="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",$="\\b(0b[01]+)",W={begin:"\\\\[\\s\\S]",relevance:0},X={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[W]},Y={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[W]},Z={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT=function(i,s,u={}){const m=inherit({className:"comment",begin:i,end:s,contains:[]},u);return m.contains.push(Z),m.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),m},ee=COMMENT("//","$"),ae=COMMENT("/\\*","\\*/"),ie=COMMENT("#","$"),le={className:"number",begin:j,relevance:0},ce={className:"number",begin:M,relevance:0},pe={className:"number",begin:$,relevance:0},de={className:"number",begin:j+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},fe={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[W,{begin:/\[/,end:/\]/,relevance:0,contains:[W]}]}]},ye={className:"title",begin:v,relevance:0},be={className:"title",begin:_,relevance:0},_e={begin:"\\.\\s*"+_,relevance:0};var we=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:v,UNDERSCORE_IDENT_RE:_,NUMBER_RE:j,C_NUMBER_RE:M,BINARY_NUMBER_RE:$,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(i={})=>{const s=/^#![ ]*\//;return i.binary&&(i.begin=function concat(...i){return i.map((i=>source(i))).join("")}(s,/.*\b/,i.binary,/\b.*/)),inherit({className:"meta",begin:s,end:/$/,relevance:0,"on:begin":(i,s)=>{0!==i.index&&s.ignoreMatch()}},i)},BACKSLASH_ESCAPE:W,APOS_STRING_MODE:X,QUOTE_STRING_MODE:Y,PHRASAL_WORDS_MODE:Z,COMMENT,C_LINE_COMMENT_MODE:ee,C_BLOCK_COMMENT_MODE:ae,HASH_COMMENT_MODE:ie,NUMBER_MODE:le,C_NUMBER_MODE:ce,BINARY_NUMBER_MODE:pe,CSS_NUMBER_MODE:de,REGEXP_MODE:fe,TITLE_MODE:ye,UNDERSCORE_TITLE_MODE:be,METHOD_GUARD:_e,END_SAME_AS_BEGIN:function(i){return Object.assign(i,{"on:begin":(i,s)=>{s.data._beginMatch=i[1]},"on:end":(i,s)=>{s.data._beginMatch!==i[1]&&s.ignoreMatch()}})}});function skipIfhasPrecedingDot(i,s){"."===i.input[i.index-1]&&s.ignoreMatch()}function beginKeywords(i,s){s&&i.beginKeywords&&(i.begin="\\b("+i.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",i.__beforeBegin=skipIfhasPrecedingDot,i.keywords=i.keywords||i.beginKeywords,delete i.beginKeywords,void 0===i.relevance&&(i.relevance=0))}function compileIllegal(i,s){Array.isArray(i.illegal)&&(i.illegal=function either(...i){return"("+i.map((i=>source(i))).join("|")+")"}(...i.illegal))}function compileMatch(i,s){if(i.match){if(i.begin||i.end)throw new Error("begin & end are not supported with match");i.begin=i.match,delete i.match}}function compileRelevance(i,s){void 0===i.relevance&&(i.relevance=1)}const Se=["of","and","for","in","not","or","if","then","parent","list","value"],xe="keyword";function compileKeywords(i,s,u=xe){const m={};return"string"==typeof i?compileList(u,i.split(" ")):Array.isArray(i)?compileList(u,i):Object.keys(i).forEach((function(u){Object.assign(m,compileKeywords(i[u],s,u))})),m;function compileList(i,u){s&&(u=u.map((i=>i.toLowerCase()))),u.forEach((function(s){const u=s.split("|");m[u[0]]=[i,scoreForKeyword(u[0],u[1])]}))}}function scoreForKeyword(i,s){return s?Number(s):function commonKeyword(i){return Se.includes(i.toLowerCase())}(i)?0:1}function compileLanguage(i,{plugins:s}){function langRe(s,u){return new RegExp(source(s),"m"+(i.case_insensitive?"i":"")+(u?"g":""))}class MultiRegex{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(i,s){s.position=this.position++,this.matchIndexes[this.matchAt]=s,this.regexes.push([s,i]),this.matchAt+=function countMatchGroups(i){return new RegExp(i.toString()+"|").exec("").length-1}(i)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const i=this.regexes.map((i=>i[1]));this.matcherRe=langRe(function join(i,s="|"){let u=0;return i.map((i=>{u+=1;const s=u;let v=source(i),_="";for(;v.length>0;){const i=m.exec(v);if(!i){_+=v;break}_+=v.substring(0,i.index),v=v.substring(i.index+i[0].length),"\\"===i[0][0]&&i[1]?_+="\\"+String(Number(i[1])+s):(_+=i[0],"("===i[0]&&u++)}return _})).map((i=>`(${i})`)).join(s)}(i),!0),this.lastIndex=0}exec(i){this.matcherRe.lastIndex=this.lastIndex;const s=this.matcherRe.exec(i);if(!s)return null;const u=s.findIndex(((i,s)=>s>0&&void 0!==i)),m=this.matchIndexes[u];return s.splice(0,u),Object.assign(s,m)}}class ResumableMultiRegex{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(i){if(this.multiRegexes[i])return this.multiRegexes[i];const s=new MultiRegex;return this.rules.slice(i).forEach((([i,u])=>s.addRule(i,u))),s.compile(),this.multiRegexes[i]=s,s}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(i,s){this.rules.push([i,s]),"begin"===s.type&&this.count++}exec(i){const s=this.getMatcher(this.regexIndex);s.lastIndex=this.lastIndex;let u=s.exec(i);if(this.resumingScanAtSamePosition())if(u&&u.index===this.lastIndex);else{const s=this.getMatcher(0);s.lastIndex=this.lastIndex+1,u=s.exec(i)}return u&&(this.regexIndex+=u.position+1,this.regexIndex===this.count&&this.considerAll()),u}}if(i.compilerExtensions||(i.compilerExtensions=[]),i.contains&&i.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return i.classNameAliases=inherit(i.classNameAliases||{}),function compileMode(s,u){const m=s;if(s.isCompiled)return m;[compileMatch].forEach((i=>i(s,u))),i.compilerExtensions.forEach((i=>i(s,u))),s.__beforeBegin=null,[beginKeywords,compileIllegal,compileRelevance].forEach((i=>i(s,u))),s.isCompiled=!0;let v=null;if("object"==typeof s.keywords&&(v=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=compileKeywords(s.keywords,i.case_insensitive)),s.lexemes&&v)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return v=v||s.lexemes||/\w+/,m.keywordPatternRe=langRe(v,!0),u&&(s.begin||(s.begin=/\B|\b/),m.beginRe=langRe(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(m.endRe=langRe(s.end)),m.terminatorEnd=source(s.end)||"",s.endsWithParent&&u.terminatorEnd&&(m.terminatorEnd+=(s.end?"|":"")+u.terminatorEnd)),s.illegal&&(m.illegalRe=langRe(s.illegal)),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(i){return function expandOrCloneMode(i){i.variants&&!i.cachedVariants&&(i.cachedVariants=i.variants.map((function(s){return inherit(i,{variants:null},s)})));if(i.cachedVariants)return i.cachedVariants;if(dependencyOnParent(i))return inherit(i,{starts:i.starts?inherit(i.starts):null});if(Object.isFrozen(i))return inherit(i);return i}("self"===i?s:i)}))),s.contains.forEach((function(i){compileMode(i,m)})),s.starts&&compileMode(s.starts,u),m.matcher=function buildModeRegex(i){const s=new ResumableMultiRegex;return i.contains.forEach((i=>s.addRule(i.begin,{rule:i,type:"begin"}))),i.terminatorEnd&&s.addRule(i.terminatorEnd,{type:"end"}),i.illegal&&s.addRule(i.illegal,{type:"illegal"}),s}(m),m}(i)}function dependencyOnParent(i){return!!i&&(i.endsWithParent||dependencyOnParent(i.starts))}function BuildVuePlugin(i){const s={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!i.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,escapeHTML(this.code);let s={};return this.autoDetect?(s=i.highlightAuto(this.code),this.detectedLanguage=s.language):(s=i.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),s.value},autoDetect(){return!this.language||function hasValueOrEmptyAttribute(i){return Boolean(i||""===i)}(this.autodetect)},ignoreIllegals:()=>!0},render(i){return i("pre",{},[i("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:s,VuePlugin:{install(i){i.component("highlightjs",s)}}}}const Pe={"after:highlightElement":({el:i,result:s,text:u})=>{const m=nodeStream(i);if(!m.length)return;const v=document.createElement("div");v.innerHTML=s.value,s.value=function mergeStreams(i,s,u){let m=0,v="";const _=[];function selectStream(){return i.length&&s.length?i[0].offset!==s[0].offset?i[0].offset"}function close(i){v+=""}function render(i){("start"===i.event?open:close)(i.node)}for(;i.length||s.length;){let s=selectStream();if(v+=escapeHTML(u.substring(m,s[0].offset)),m=s[0].offset,s===i){_.reverse().forEach(close);do{render(s.splice(0,1)[0]),s=selectStream()}while(s===i&&s.length&&s[0].offset===m);_.reverse().forEach(open)}else"start"===s[0].event?_.push(s[0].node):_.pop(),render(s.splice(0,1)[0])}return v+escapeHTML(u.substr(m))}(m,nodeStream(v),u)}};function tag(i){return i.nodeName.toLowerCase()}function nodeStream(i){const s=[];return function _nodeStream(i,u){for(let m=i.firstChild;m;m=m.nextSibling)3===m.nodeType?u+=m.nodeValue.length:1===m.nodeType&&(s.push({event:"start",offset:u,node:m}),u=_nodeStream(m,u),tag(m).match(/br|hr|img|input/)||s.push({event:"stop",offset:u,node:m}));return u}(i,0),s}const Ie={},error=i=>{console.error(i)},warn=(i,...s)=>{console.log(`WARN: ${i}`,...s)},deprecated=(i,s)=>{Ie[`${i}/${s}`]||(console.log(`Deprecated as of ${i}. ${s}`),Ie[`${i}/${s}`]=!0)},Te=escapeHTML,Re=inherit,qe=Symbol("nomatch");var ze=function(i){const u=Object.create(null),m=Object.create(null),v=[];let _=!0;const j=/(^(<[^>]+>|\t|)+|\n)/gm,M="Could not find the language '{}', did you forget to load/include a language module?",$={disableAutodetect:!0,name:"Plain text",contains:[]};let W={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:TokenTreeEmitter};function shouldNotHighlight(i){return W.noHighlightRe.test(i)}function highlight(i,s,u,m){let v="",_="";"object"==typeof s?(v=i,u=s.ignoreIllegals,_=s.language,m=void 0):(deprecated("10.7.0","highlight(lang, code, ...args) has been deprecated."),deprecated("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),_=i,v=s);const j={code:v,language:_};fire("before:highlight",j);const M=j.result?j.result:_highlight(j.language,j.code,u,m);return M.code=j.code,fire("after:highlight",M),M}function _highlight(i,s,m,j){function keywordData(i,s){const u=X.case_insensitive?s[0].toLowerCase():s[0];return Object.prototype.hasOwnProperty.call(i.keywords,u)&&i.keywords[u]}function processBuffer(){null!=ee.subLanguage?function processSubLanguage(){if(""===le)return;let i=null;if("string"==typeof ee.subLanguage){if(!u[ee.subLanguage])return void ie.addText(le);i=_highlight(ee.subLanguage,le,!0,ae[ee.subLanguage]),ae[ee.subLanguage]=i.top}else i=highlightAuto(le,ee.subLanguage.length?ee.subLanguage:null);ee.relevance>0&&(ce+=i.relevance),ie.addSublanguage(i.emitter,i.language)}():function processKeywords(){if(!ee.keywords)return void ie.addText(le);let i=0;ee.keywordPatternRe.lastIndex=0;let s=ee.keywordPatternRe.exec(le),u="";for(;s;){u+=le.substring(i,s.index);const m=keywordData(ee,s);if(m){const[i,v]=m;if(ie.addText(u),u="",ce+=v,i.startsWith("_"))u+=s[0];else{const u=X.classNameAliases[i]||i;ie.addKeyword(s[0],u)}}else u+=s[0];i=ee.keywordPatternRe.lastIndex,s=ee.keywordPatternRe.exec(le)}u+=le.substr(i),ie.addText(u)}(),le=""}function startNewMode(i){return i.className&&ie.openNode(X.classNameAliases[i.className]||i.className),ee=Object.create(i,{parent:{value:ee}}),ee}function endOfMode(i,s,u){let m=function startsWith(i,s){const u=i&&i.exec(s);return u&&0===u.index}(i.endRe,u);if(m){if(i["on:end"]){const u=new Response(i);i["on:end"](s,u),u.isMatchIgnored&&(m=!1)}if(m){for(;i.endsParent&&i.parent;)i=i.parent;return i}}if(i.endsWithParent)return endOfMode(i.parent,s,u)}function doIgnore(i){return 0===ee.matcher.regexIndex?(le+=i[0],1):(fe=!0,0)}function doBeginMatch(i){const s=i[0],u=i.rule,m=new Response(u),v=[u.__beforeBegin,u["on:begin"]];for(const u of v)if(u&&(u(i,m),m.isMatchIgnored))return doIgnore(s);return u&&u.endSameAsBegin&&(u.endRe=function escape(i){return new RegExp(i.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}(s)),u.skip?le+=s:(u.excludeBegin&&(le+=s),processBuffer(),u.returnBegin||u.excludeBegin||(le=s)),startNewMode(u),u.returnBegin?0:s.length}function doEndMatch(i){const u=i[0],m=s.substr(i.index),v=endOfMode(ee,i,m);if(!v)return qe;const _=ee;_.skip?le+=u:(_.returnEnd||_.excludeEnd||(le+=u),processBuffer(),_.excludeEnd&&(le=u));do{ee.className&&ie.closeNode(),ee.skip||ee.subLanguage||(ce+=ee.relevance),ee=ee.parent}while(ee!==v.parent);return v.starts&&(v.endSameAsBegin&&(v.starts.endRe=v.endRe),startNewMode(v.starts)),_.returnEnd?0:u.length}let $={};function processLexeme(u,v){const j=v&&v[0];if(le+=u,null==j)return processBuffer(),0;if("begin"===$.type&&"end"===v.type&&$.index===v.index&&""===j){if(le+=s.slice(v.index,v.index+1),!_){const s=new Error("0 width match regex");throw s.languageName=i,s.badRule=$.rule,s}return 1}if($=v,"begin"===v.type)return doBeginMatch(v);if("illegal"===v.type&&!m){const i=new Error('Illegal lexeme "'+j+'" for mode "'+(ee.className||"")+'"');throw i.mode=ee,i}if("end"===v.type){const i=doEndMatch(v);if(i!==qe)return i}if("illegal"===v.type&&""===j)return 1;if(de>1e5&&de>3*v.index){throw new Error("potential infinite loop, way more iterations than matches")}return le+=j,j.length}const X=getLanguage(i);if(!X)throw error(M.replace("{}",i)),new Error('Unknown language: "'+i+'"');const Y=compileLanguage(X,{plugins:v});let Z="",ee=j||Y;const ae={},ie=new W.__emitter(W);!function processContinuations(){const i=[];for(let s=ee;s!==X;s=s.parent)s.className&&i.unshift(s.className);i.forEach((i=>ie.openNode(i)))}();let le="",ce=0,pe=0,de=0,fe=!1;try{for(ee.matcher.considerAll();;){de++,fe?fe=!1:ee.matcher.considerAll(),ee.matcher.lastIndex=pe;const i=ee.matcher.exec(s);if(!i)break;const u=processLexeme(s.substring(pe,i.index),i);pe=i.index+u}return processLexeme(s.substr(pe)),ie.closeAllNodes(),ie.finalize(),Z=ie.toHTML(),{relevance:Math.floor(ce),value:Z,language:i,illegal:!1,emitter:ie,top:ee}}catch(u){if(u.message&&u.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:u.message,context:s.slice(pe-100,pe+100),mode:u.mode},sofar:Z,relevance:0,value:Te(s),emitter:ie};if(_)return{illegal:!1,relevance:0,value:Te(s),emitter:ie,language:i,top:ee,errorRaised:u};throw u}}function highlightAuto(i,s){s=s||W.languages||Object.keys(u);const m=function justTextHighlightResult(i){const s={relevance:0,emitter:new W.__emitter(W),value:Te(i),illegal:!1,top:$};return s.emitter.addText(i),s}(i),v=s.filter(getLanguage).filter(autoDetection).map((s=>_highlight(s,i,!1)));v.unshift(m);const _=v.sort(((i,s)=>{if(i.relevance!==s.relevance)return s.relevance-i.relevance;if(i.language&&s.language){if(getLanguage(i.language).supersetOf===s.language)return 1;if(getLanguage(s.language).supersetOf===i.language)return-1}return 0})),[j,M]=_,X=j;return X.second_best=M,X}const X={"before:highlightElement":({el:i})=>{W.useBR&&(i.innerHTML=i.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:i})=>{W.useBR&&(i.value=i.value.replace(/\n/g,"
"))}},Y=/^(<[^>]+>|\t)+/gm,Z={"after:highlightElement":({result:i})=>{W.tabReplace&&(i.value=i.value.replace(Y,(i=>i.replace(/\t/g,W.tabReplace))))}};function highlightElement(i){let s=null;const u=function blockLanguage(i){let s=i.className+" ";s+=i.parentNode?i.parentNode.className:"";const u=W.languageDetectRe.exec(s);if(u){const s=getLanguage(u[1]);return s||(warn(M.replace("{}",u[1])),warn("Falling back to no-highlight mode for this block.",i)),s?u[1]:"no-highlight"}return s.split(/\s+/).find((i=>shouldNotHighlight(i)||getLanguage(i)))}(i);if(shouldNotHighlight(u))return;fire("before:highlightElement",{el:i,language:u}),s=i;const v=s.textContent,_=u?highlight(v,{language:u,ignoreIllegals:!0}):highlightAuto(v);fire("after:highlightElement",{el:i,result:_,text:v}),i.innerHTML=_.value,function updateClassName(i,s,u){const v=s?m[s]:u;i.classList.add("hljs"),v&&i.classList.add(v)}(i,u,_.language),i.result={language:_.language,re:_.relevance,relavance:_.relevance},_.second_best&&(i.second_best={language:_.second_best.language,re:_.second_best.relevance,relavance:_.second_best.relevance})}const initHighlighting=()=>{if(initHighlighting.called)return;initHighlighting.called=!0,deprecated("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(highlightElement)};let ee=!1;function highlightAll(){if("loading"===document.readyState)return void(ee=!0);document.querySelectorAll("pre code").forEach(highlightElement)}function getLanguage(i){return i=(i||"").toLowerCase(),u[i]||u[m[i]]}function registerAliases(i,{languageName:s}){"string"==typeof i&&(i=[i]),i.forEach((i=>{m[i.toLowerCase()]=s}))}function autoDetection(i){const s=getLanguage(i);return s&&!s.disableAutodetect}function fire(i,s){const u=i;v.forEach((function(i){i[u]&&i[u](s)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function boot(){ee&&highlightAll()}),!1),Object.assign(i,{highlight,highlightAuto,highlightAll,fixMarkup:function deprecateFixMarkup(i){return deprecated("10.2.0","fixMarkup will be removed entirely in v11.0"),deprecated("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),function fixMarkup(i){return W.tabReplace||W.useBR?i.replace(j,(i=>"\n"===i?W.useBR?"
":i:W.tabReplace?i.replace(/\t/g,W.tabReplace):i)):i}(i)},highlightElement,highlightBlock:function deprecateHighlightBlock(i){return deprecated("10.7.0","highlightBlock will be removed entirely in v12.0"),deprecated("10.7.0","Please use highlightElement now."),highlightElement(i)},configure:function configure(i){i.useBR&&(deprecated("10.3.0","'useBR' will be removed entirely in v11.0"),deprecated("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),W=Re(W,i)},initHighlighting,initHighlightingOnLoad:function initHighlightingOnLoad(){deprecated("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),ee=!0},registerLanguage:function registerLanguage(s,m){let v=null;try{v=m(i)}catch(i){if(error("Language definition for '{}' could not be registered.".replace("{}",s)),!_)throw i;error(i),v=$}v.name||(v.name=s),u[s]=v,v.rawDefinition=m.bind(null,i),v.aliases&®isterAliases(v.aliases,{languageName:s})},unregisterLanguage:function unregisterLanguage(i){delete u[i];for(const s of Object.keys(m))m[s]===i&&delete m[s]},listLanguages:function listLanguages(){return Object.keys(u)},getLanguage,registerAliases,requireLanguage:function requireLanguage(i){deprecated("10.4.0","requireLanguage will be removed entirely in v11."),deprecated("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const s=getLanguage(i);if(s)return s;throw new Error("The '{}' language is required, but not loaded.".replace("{}",i))},autoDetection,inherit:Re,addPlugin:function addPlugin(i){!function upgradePluginAPI(i){i["before:highlightBlock"]&&!i["before:highlightElement"]&&(i["before:highlightElement"]=s=>{i["before:highlightBlock"](Object.assign({block:s.el},s))}),i["after:highlightBlock"]&&!i["after:highlightElement"]&&(i["after:highlightElement"]=s=>{i["after:highlightBlock"](Object.assign({block:s.el},s))})}(i),v.push(i)},vuePlugin:BuildVuePlugin(i).VuePlugin}),i.debugMode=function(){_=!1},i.safeMode=function(){_=!0},i.versionString="10.7.3";for(const i in we)"object"==typeof we[i]&&s(we[i]);return Object.assign(i,we),i.addPlugin(X),i.addPlugin(Pe),i.addPlugin(Z),i}({});i.exports=ze},61519:i=>{function concat(...i){return i.map((i=>function source(i){return i?"string"==typeof i?i:i.source:null}(i))).join("")}i.exports=function bash(i){const s={},u={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[s]}]};Object.assign(s,{className:"variable",variants:[{begin:concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},u]});const m={className:"subst",begin:/\$\(/,end:/\)/,contains:[i.BACKSLASH_ESCAPE]},v={begin:/<<-?\s*(?=\w+)/,starts:{contains:[i.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},_={className:"string",begin:/"/,end:/"/,contains:[i.BACKSLASH_ESCAPE,s,m]};m.contains.push(_);const j={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},i.NUMBER_MODE,s]},M=i.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),$={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[i.inherit(i.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[M,i.SHEBANG(),$,j,i.HASH_COMMENT_MODE,v,_,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}},30786:i=>{function concat(...i){return i.map((i=>function source(i){return i?"string"==typeof i?i:i.source:null}(i))).join("")}i.exports=function http(i){const s="HTTP/(2|1\\.[01])",u={className:"attribute",begin:concat("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},m=[u,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+s+" \\d{3})",end:/$/,contains:[{className:"meta",begin:s},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:m}},{begin:"(?=^[A-Z]+ (.*?) "+s+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:s},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:m}},i.inherit(u,{relevance:0})]}}},96344:i=>{const s="[A-Za-z$_][0-9A-Za-z$_]*",u=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],m=["true","false","null","undefined","NaN","Infinity"],v=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function lookahead(i){return concat("(?=",i,")")}function concat(...i){return i.map((i=>function source(i){return i?"string"==typeof i?i:i.source:null}(i))).join("")}i.exports=function javascript(i){const _=s,j="<>",M="",$={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(i,s)=>{const u=i[0].length+i.index,m=i.input[u];"<"!==m?">"===m&&(((i,{after:s})=>{const u="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:i.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:W,contains:ye}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:j,end:M},{begin:$.begin,"on:begin":$.isTrulyOpeningTag,end:$.end}],subLanguage:"xml",contains:[{begin:$.begin,end:$.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:W,contains:["self",i.inherit(i.TITLE_MODE,{begin:_}),be],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:i.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[be,i.inherit(i.TITLE_MODE,{begin:_})]},{variants:[{begin:"\\."+_},{begin:"\\$"+_}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},i.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[i.inherit(i.TITLE_MODE,{begin:_}),"self",be]},{begin:"(get|set)\\s+(?="+_+"\\()",end:/\{/,keywords:"get set",contains:[i.inherit(i.TITLE_MODE,{begin:_}),{begin:/\(\)/},be]},{begin:/\$[(.]/}]}}},82026:i=>{i.exports=function json(i){const s={literal:"true false null"},u=[i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE],m=[i.QUOTE_STRING_MODE,i.C_NUMBER_MODE],v={end:",",endsWithParent:!0,excludeEnd:!0,contains:m,keywords:s},_={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[i.BACKSLASH_ESCAPE],illegal:"\\n"},i.inherit(v,{begin:/:/})].concat(u),illegal:"\\S"},j={begin:"\\[",end:"\\]",contains:[i.inherit(v)],illegal:"\\S"};return m.push(_,j),u.forEach((function(i){m.push(i)})),{name:"JSON",contains:m,keywords:s,illegal:"\\S"}}},66336:i=>{i.exports=function powershell(i){const s={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},u={begin:"`[\\s\\S]",relevance:0},m={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},v={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[u,m,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},_={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},j=i.inherit(i.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),M={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},$={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[i.TITLE_MODE]},W={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[m]}]},X={begin:/using\s/,end:/$/,returnBegin:!0,contains:[v,_,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},Y={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},Z={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(s.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},i.inherit(i.TITLE_MODE,{endsParent:!0})]},ee=[Z,j,u,i.NUMBER_MODE,v,_,M,m,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],ae={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",ee,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return Z.contains.unshift(ae),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:s,contains:ee.concat($,W,X,Y,ae)}}},42157:i=>{function source(i){return i?"string"==typeof i?i:i.source:null}function lookahead(i){return concat("(?=",i,")")}function concat(...i){return i.map((i=>source(i))).join("")}function either(...i){return"("+i.map((i=>source(i))).join("|")+")"}i.exports=function xml(i){const s=concat(/[A-Z_]/,function optional(i){return concat("(",i,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),u={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},m={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},v=i.inherit(m,{begin:/\(/,end:/\)/}),_=i.inherit(i.APOS_STRING_MODE,{className:"meta-string"}),j=i.inherit(i.QUOTE_STRING_MODE,{className:"meta-string"}),M={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[m,j,_,v,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[m,v,j,_]}]}]},i.COMMENT(//,{relevance:10}),{begin://,relevance:10},u,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[M],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[M],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:s,relevance:0,starts:M}]},{className:"tag",begin:concat(/<\//,lookahead(concat(s,/>/))),contains:[{className:"name",begin:s,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},54587:i=>{i.exports=function yaml(i){var s="true false yes no null",u="[\\w#;/?:@&=+$,.~*'()[\\]]+",m={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[i.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},v=i.inherit(m,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),_={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},j={end:",",endsWithParent:!0,excludeEnd:!0,keywords:s,relevance:0},M={begin:/\{/,end:/\}/,contains:[j],illegal:"\\n",relevance:0},$={begin:"\\[",end:"\\]",contains:[j],illegal:"\\n",relevance:0},W=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+u},{className:"type",begin:"!<"+u+">"},{className:"type",begin:"!"+u},{className:"type",begin:"!!"+u},{className:"meta",begin:"&"+i.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+i.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},i.HASH_COMMENT_MODE,{beginKeywords:s,keywords:{literal:s}},_,{className:"number",begin:i.C_NUMBER_RE+"\\b",relevance:0},M,$,m],X=[...W];return X.pop(),X.push(v),j.contains=X,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:W}}},8679:(i,s,u)=>{"use strict";var m=u(59864),v={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},_={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},j={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},M={};function getStatics(i){return m.isMemo(i)?j:M[i.$$typeof]||v}M[m.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},M[m.Memo]=j;var $=Object.defineProperty,W=Object.getOwnPropertyNames,X=Object.getOwnPropertySymbols,Y=Object.getOwnPropertyDescriptor,Z=Object.getPrototypeOf,ee=Object.prototype;i.exports=function hoistNonReactStatics(i,s,u){if("string"!=typeof s){if(ee){var m=Z(s);m&&m!==ee&&hoistNonReactStatics(i,m,u)}var v=W(s);X&&(v=v.concat(X(s)));for(var j=getStatics(i),M=getStatics(s),ae=0;ae{s.read=function(i,s,u,m,v){var _,j,M=8*v-m-1,$=(1<>1,X=-7,Y=u?v-1:0,Z=u?-1:1,ee=i[s+Y];for(Y+=Z,_=ee&(1<<-X)-1,ee>>=-X,X+=M;X>0;_=256*_+i[s+Y],Y+=Z,X-=8);for(j=_&(1<<-X)-1,_>>=-X,X+=m;X>0;j=256*j+i[s+Y],Y+=Z,X-=8);if(0===_)_=1-W;else{if(_===$)return j?NaN:1/0*(ee?-1:1);j+=Math.pow(2,m),_-=W}return(ee?-1:1)*j*Math.pow(2,_-m)},s.write=function(i,s,u,m,v,_){var j,M,$,W=8*_-v-1,X=(1<>1,Z=23===v?Math.pow(2,-24)-Math.pow(2,-77):0,ee=m?0:_-1,ae=m?1:-1,ie=s<0||0===s&&1/s<0?1:0;for(s=Math.abs(s),isNaN(s)||s===1/0?(M=isNaN(s)?1:0,j=X):(j=Math.floor(Math.log(s)/Math.LN2),s*($=Math.pow(2,-j))<1&&(j--,$*=2),(s+=j+Y>=1?Z/$:Z*Math.pow(2,1-Y))*$>=2&&(j++,$/=2),j+Y>=X?(M=0,j=X):j+Y>=1?(M=(s*$-1)*Math.pow(2,v),j+=Y):(M=s*Math.pow(2,Y-1)*Math.pow(2,v),j=0));v>=8;i[u+ee]=255&M,ee+=ae,M/=256,v-=8);for(j=j<0;i[u+ee]=255&j,ee+=ae,j/=256,W-=8);i[u+ee-ae]|=128*ie}},43393:function(i){i.exports=function(){"use strict";var i=Array.prototype.slice;function createClass(i,s){s&&(i.prototype=Object.create(s.prototype)),i.prototype.constructor=i}function Iterable(i){return isIterable(i)?i:Seq(i)}function KeyedIterable(i){return isKeyed(i)?i:KeyedSeq(i)}function IndexedIterable(i){return isIndexed(i)?i:IndexedSeq(i)}function SetIterable(i){return isIterable(i)&&!isAssociative(i)?i:SetSeq(i)}function isIterable(i){return!(!i||!i[s])}function isKeyed(i){return!(!i||!i[u])}function isIndexed(i){return!(!i||!i[m])}function isAssociative(i){return isKeyed(i)||isIndexed(i)}function isOrdered(i){return!(!i||!i[v])}createClass(KeyedIterable,Iterable),createClass(IndexedIterable,Iterable),createClass(SetIterable,Iterable),Iterable.isIterable=isIterable,Iterable.isKeyed=isKeyed,Iterable.isIndexed=isIndexed,Iterable.isAssociative=isAssociative,Iterable.isOrdered=isOrdered,Iterable.Keyed=KeyedIterable,Iterable.Indexed=IndexedIterable,Iterable.Set=SetIterable;var s="@@__IMMUTABLE_ITERABLE__@@",u="@@__IMMUTABLE_KEYED__@@",m="@@__IMMUTABLE_INDEXED__@@",v="@@__IMMUTABLE_ORDERED__@@",_="delete",j=5,M=1<>>0;if(""+u!==s||4294967295===u)return NaN;s=u}return s<0?ensureSize(i)+s:s}function returnTrue(){return!0}function wholeSlice(i,s,u){return(0===i||void 0!==u&&i<=-u)&&(void 0===s||void 0!==u&&s>=u)}function resolveBegin(i,s){return resolveIndex(i,s,0)}function resolveEnd(i,s){return resolveIndex(i,s,s)}function resolveIndex(i,s,u){return void 0===i?u:i<0?Math.max(0,s+i):void 0===s?i:Math.min(s,i)}var Z=0,ee=1,ae=2,ie="function"==typeof Symbol&&Symbol.iterator,le="@@iterator",ce=ie||le;function Iterator(i){this.next=i}function iteratorValue(i,s,u,m){var v=0===i?s:1===i?u:[s,u];return m?m.value=v:m={value:v,done:!1},m}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(i){return!!getIteratorFn(i)}function isIterator(i){return i&&"function"==typeof i.next}function getIterator(i){var s=getIteratorFn(i);return s&&s.call(i)}function getIteratorFn(i){var s=i&&(ie&&i[ie]||i[le]);if("function"==typeof s)return s}function isArrayLike(i){return i&&"number"==typeof i.length}function Seq(i){return null==i?emptySequence():isIterable(i)?i.toSeq():seqFromValue(i)}function KeyedSeq(i){return null==i?emptySequence().toKeyedSeq():isIterable(i)?isKeyed(i)?i.toSeq():i.fromEntrySeq():keyedSeqFromValue(i)}function IndexedSeq(i){return null==i?emptySequence():isIterable(i)?isKeyed(i)?i.entrySeq():i.toIndexedSeq():indexedSeqFromValue(i)}function SetSeq(i){return(null==i?emptySequence():isIterable(i)?isKeyed(i)?i.entrySeq():i:indexedSeqFromValue(i)).toSetSeq()}Iterator.prototype.toString=function(){return"[Iterator]"},Iterator.KEYS=Z,Iterator.VALUES=ee,Iterator.ENTRIES=ae,Iterator.prototype.inspect=Iterator.prototype.toSource=function(){return this.toString()},Iterator.prototype[ce]=function(){return this},createClass(Seq,Iterable),Seq.of=function(){return Seq(arguments)},Seq.prototype.toSeq=function(){return this},Seq.prototype.toString=function(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function(i,s){return seqIterate(this,i,s,!0)},Seq.prototype.__iterator=function(i,s){return seqIterator(this,i,s,!0)},createClass(KeyedSeq,Seq),KeyedSeq.prototype.toKeyedSeq=function(){return this},createClass(IndexedSeq,Seq),IndexedSeq.of=function(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function(){return this},IndexedSeq.prototype.toString=function(){return this.__toString("Seq [","]")},IndexedSeq.prototype.__iterate=function(i,s){return seqIterate(this,i,s,!1)},IndexedSeq.prototype.__iterator=function(i,s){return seqIterator(this,i,s,!1)},createClass(SetSeq,Seq),SetSeq.of=function(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function(){return this},Seq.isSeq=isSeq,Seq.Keyed=KeyedSeq,Seq.Set=SetSeq,Seq.Indexed=IndexedSeq;var pe,de,fe,ye="@@__IMMUTABLE_SEQ__@@";function ArraySeq(i){this._array=i,this.size=i.length}function ObjectSeq(i){var s=Object.keys(i);this._object=i,this._keys=s,this.size=s.length}function IterableSeq(i){this._iterable=i,this.size=i.length||i.size}function IteratorSeq(i){this._iterator=i,this._iteratorCache=[]}function isSeq(i){return!(!i||!i[ye])}function emptySequence(){return pe||(pe=new ArraySeq([]))}function keyedSeqFromValue(i){var s=Array.isArray(i)?new ArraySeq(i).fromEntrySeq():isIterator(i)?new IteratorSeq(i).fromEntrySeq():hasIterator(i)?new IterableSeq(i).fromEntrySeq():"object"==typeof i?new ObjectSeq(i):void 0;if(!s)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+i);return s}function indexedSeqFromValue(i){var s=maybeIndexedSeqFromValue(i);if(!s)throw new TypeError("Expected Array or iterable object of values: "+i);return s}function seqFromValue(i){var s=maybeIndexedSeqFromValue(i)||"object"==typeof i&&new ObjectSeq(i);if(!s)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+i);return s}function maybeIndexedSeqFromValue(i){return isArrayLike(i)?new ArraySeq(i):isIterator(i)?new IteratorSeq(i):hasIterator(i)?new IterableSeq(i):void 0}function seqIterate(i,s,u,m){var v=i._cache;if(v){for(var _=v.length-1,j=0;j<=_;j++){var M=v[u?_-j:j];if(!1===s(M[1],m?M[0]:j,i))return j+1}return j}return i.__iterateUncached(s,u)}function seqIterator(i,s,u,m){var v=i._cache;if(v){var _=v.length-1,j=0;return new Iterator((function(){var i=v[u?_-j:j];return j++>_?iteratorDone():iteratorValue(s,m?i[0]:j-1,i[1])}))}return i.__iteratorUncached(s,u)}function fromJS(i,s){return s?fromJSWith(s,i,"",{"":i}):fromJSDefault(i)}function fromJSWith(i,s,u,m){return Array.isArray(s)?i.call(m,u,IndexedSeq(s).map((function(u,m){return fromJSWith(i,u,m,s)}))):isPlainObj(s)?i.call(m,u,KeyedSeq(s).map((function(u,m){return fromJSWith(i,u,m,s)}))):s}function fromJSDefault(i){return Array.isArray(i)?IndexedSeq(i).map(fromJSDefault).toList():isPlainObj(i)?KeyedSeq(i).map(fromJSDefault).toMap():i}function isPlainObj(i){return i&&(i.constructor===Object||void 0===i.constructor)}function is(i,s){if(i===s||i!=i&&s!=s)return!0;if(!i||!s)return!1;if("function"==typeof i.valueOf&&"function"==typeof s.valueOf){if((i=i.valueOf())===(s=s.valueOf())||i!=i&&s!=s)return!0;if(!i||!s)return!1}return!("function"!=typeof i.equals||"function"!=typeof s.equals||!i.equals(s))}function deepEqual(i,s){if(i===s)return!0;if(!isIterable(s)||void 0!==i.size&&void 0!==s.size&&i.size!==s.size||void 0!==i.__hash&&void 0!==s.__hash&&i.__hash!==s.__hash||isKeyed(i)!==isKeyed(s)||isIndexed(i)!==isIndexed(s)||isOrdered(i)!==isOrdered(s))return!1;if(0===i.size&&0===s.size)return!0;var u=!isAssociative(i);if(isOrdered(i)){var m=i.entries();return s.every((function(i,s){var v=m.next().value;return v&&is(v[1],i)&&(u||is(v[0],s))}))&&m.next().done}var v=!1;if(void 0===i.size)if(void 0===s.size)"function"==typeof i.cacheResult&&i.cacheResult();else{v=!0;var _=i;i=s,s=_}var j=!0,M=s.__iterate((function(s,m){if(u?!i.has(s):v?!is(s,i.get(m,W)):!is(i.get(m,W),s))return j=!1,!1}));return j&&i.size===M}function Repeat(i,s){if(!(this instanceof Repeat))return new Repeat(i,s);if(this._value=i,this.size=void 0===s?1/0:Math.max(0,s),0===this.size){if(de)return de;de=this}}function invariant(i,s){if(!i)throw new Error(s)}function Range(i,s,u){if(!(this instanceof Range))return new Range(i,s,u);if(invariant(0!==u,"Cannot step a Range by 0"),i=i||0,void 0===s&&(s=1/0),u=void 0===u?1:Math.abs(u),sm?iteratorDone():iteratorValue(i,v,u[s?m-v++:v++])}))},createClass(ObjectSeq,KeyedSeq),ObjectSeq.prototype.get=function(i,s){return void 0===s||this.has(i)?this._object[i]:s},ObjectSeq.prototype.has=function(i){return this._object.hasOwnProperty(i)},ObjectSeq.prototype.__iterate=function(i,s){for(var u=this._object,m=this._keys,v=m.length-1,_=0;_<=v;_++){var j=m[s?v-_:_];if(!1===i(u[j],j,this))return _+1}return _},ObjectSeq.prototype.__iterator=function(i,s){var u=this._object,m=this._keys,v=m.length-1,_=0;return new Iterator((function(){var j=m[s?v-_:_];return _++>v?iteratorDone():iteratorValue(i,j,u[j])}))},ObjectSeq.prototype[v]=!0,createClass(IterableSeq,IndexedSeq),IterableSeq.prototype.__iterateUncached=function(i,s){if(s)return this.cacheResult().__iterate(i,s);var u=getIterator(this._iterable),m=0;if(isIterator(u))for(var v;!(v=u.next()).done&&!1!==i(v.value,m++,this););return m},IterableSeq.prototype.__iteratorUncached=function(i,s){if(s)return this.cacheResult().__iterator(i,s);var u=getIterator(this._iterable);if(!isIterator(u))return new Iterator(iteratorDone);var m=0;return new Iterator((function(){var s=u.next();return s.done?s:iteratorValue(i,m++,s.value)}))},createClass(IteratorSeq,IndexedSeq),IteratorSeq.prototype.__iterateUncached=function(i,s){if(s)return this.cacheResult().__iterate(i,s);for(var u,m=this._iterator,v=this._iteratorCache,_=0;_=m.length){var s=u.next();if(s.done)return s;m[v]=s.value}return iteratorValue(i,v,m[v++])}))},createClass(Repeat,IndexedSeq),Repeat.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Repeat.prototype.get=function(i,s){return this.has(i)?this._value:s},Repeat.prototype.includes=function(i){return is(this._value,i)},Repeat.prototype.slice=function(i,s){var u=this.size;return wholeSlice(i,s,u)?this:new Repeat(this._value,resolveEnd(s,u)-resolveBegin(i,u))},Repeat.prototype.reverse=function(){return this},Repeat.prototype.indexOf=function(i){return is(this._value,i)?0:-1},Repeat.prototype.lastIndexOf=function(i){return is(this._value,i)?this.size:-1},Repeat.prototype.__iterate=function(i,s){for(var u=0;u=0&&s=0&&uu?iteratorDone():iteratorValue(i,_++,j)}))},Range.prototype.equals=function(i){return i instanceof Range?this._start===i._start&&this._end===i._end&&this._step===i._step:deepEqual(this,i)},createClass(Collection,Iterable),createClass(KeyedCollection,Collection),createClass(IndexedCollection,Collection),createClass(SetCollection,Collection),Collection.Keyed=KeyedCollection,Collection.Indexed=IndexedCollection,Collection.Set=SetCollection;var be="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(i,s){var u=65535&(i|=0),m=65535&(s|=0);return u*m+((i>>>16)*m+u*(s>>>16)<<16>>>0)|0};function smi(i){return i>>>1&1073741824|3221225471&i}function hash(i){if(!1===i||null==i)return 0;if("function"==typeof i.valueOf&&(!1===(i=i.valueOf())||null==i))return 0;if(!0===i)return 1;var s=typeof i;if("number"===s){if(i!=i||i===1/0)return 0;var u=0|i;for(u!==i&&(u^=4294967295*i);i>4294967295;)u^=i/=4294967295;return smi(u)}if("string"===s)return i.length>Te?cachedHashString(i):hashString(i);if("function"==typeof i.hashCode)return i.hashCode();if("object"===s)return hashJSObj(i);if("function"==typeof i.toString)return hashString(i.toString());throw new Error("Value type "+s+" cannot be hashed.")}function cachedHashString(i){var s=ze[i];return void 0===s&&(s=hashString(i),qe===Re&&(qe=0,ze={}),qe++,ze[i]=s),s}function hashString(i){for(var s=0,u=0;u0)switch(i.nodeType){case 1:return i.uniqueID;case 9:return i.documentElement&&i.documentElement.uniqueID}}var Se,xe="function"==typeof WeakMap;xe&&(Se=new WeakMap);var Pe=0,Ie="__immutablehash__";"function"==typeof Symbol&&(Ie=Symbol(Ie));var Te=16,Re=255,qe=0,ze={};function assertNotInfinite(i){invariant(i!==1/0,"Cannot perform this action with an infinite size.")}function Map(i){return null==i?emptyMap():isMap(i)&&!isOrdered(i)?i:emptyMap().withMutations((function(s){var u=KeyedIterable(i);assertNotInfinite(u.size),u.forEach((function(i,u){return s.set(u,i)}))}))}function isMap(i){return!(!i||!i[We])}createClass(Map,KeyedCollection),Map.of=function(){var s=i.call(arguments,0);return emptyMap().withMutations((function(i){for(var u=0;u=s.length)throw new Error("Missing value for key: "+s[u]);i.set(s[u],s[u+1])}}))},Map.prototype.toString=function(){return this.__toString("Map {","}")},Map.prototype.get=function(i,s){return this._root?this._root.get(0,void 0,i,s):s},Map.prototype.set=function(i,s){return updateMap(this,i,s)},Map.prototype.setIn=function(i,s){return this.updateIn(i,W,(function(){return s}))},Map.prototype.remove=function(i){return updateMap(this,i,W)},Map.prototype.deleteIn=function(i){return this.updateIn(i,(function(){return W}))},Map.prototype.update=function(i,s,u){return 1===arguments.length?i(this):this.updateIn([i],s,u)},Map.prototype.updateIn=function(i,s,u){u||(u=s,s=void 0);var m=updateInDeepMap(this,forceIterator(i),s,u);return m===W?void 0:m},Map.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.merge=function(){return mergeIntoMapWith(this,void 0,arguments)},Map.prototype.mergeWith=function(s){return mergeIntoMapWith(this,s,i.call(arguments,1))},Map.prototype.mergeIn=function(s){var u=i.call(arguments,1);return this.updateIn(s,emptyMap(),(function(i){return"function"==typeof i.merge?i.merge.apply(i,u):u[u.length-1]}))},Map.prototype.mergeDeep=function(){return mergeIntoMapWith(this,deepMerger,arguments)},Map.prototype.mergeDeepWith=function(s){var u=i.call(arguments,1);return mergeIntoMapWith(this,deepMergerWith(s),u)},Map.prototype.mergeDeepIn=function(s){var u=i.call(arguments,1);return this.updateIn(s,emptyMap(),(function(i){return"function"==typeof i.mergeDeep?i.mergeDeep.apply(i,u):u[u.length-1]}))},Map.prototype.sort=function(i){return OrderedMap(sortFactory(this,i))},Map.prototype.sortBy=function(i,s){return OrderedMap(sortFactory(this,s,i))},Map.prototype.withMutations=function(i){var s=this.asMutable();return i(s),s.wasAltered()?s.__ensureOwner(this.__ownerID):this},Map.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)},Map.prototype.asImmutable=function(){return this.__ensureOwner()},Map.prototype.wasAltered=function(){return this.__altered},Map.prototype.__iterator=function(i,s){return new MapIterator(this,i,s)},Map.prototype.__iterate=function(i,s){var u=this,m=0;return this._root&&this._root.iterate((function(s){return m++,i(s[1],s[0],u)}),s),m},Map.prototype.__ensureOwner=function(i){return i===this.__ownerID?this:i?makeMap(this.size,this._root,i,this.__hash):(this.__ownerID=i,this.__altered=!1,this)},Map.isMap=isMap;var Ve,We="@@__IMMUTABLE_MAP__@@",He=Map.prototype;function ArrayMapNode(i,s){this.ownerID=i,this.entries=s}function BitmapIndexedNode(i,s,u){this.ownerID=i,this.bitmap=s,this.nodes=u}function HashArrayMapNode(i,s,u){this.ownerID=i,this.count=s,this.nodes=u}function HashCollisionNode(i,s,u){this.ownerID=i,this.keyHash=s,this.entries=u}function ValueNode(i,s,u){this.ownerID=i,this.keyHash=s,this.entry=u}function MapIterator(i,s,u){this._type=s,this._reverse=u,this._stack=i._root&&mapIteratorFrame(i._root)}function mapIteratorValue(i,s){return iteratorValue(i,s[0],s[1])}function mapIteratorFrame(i,s){return{node:i,index:0,__prev:s}}function makeMap(i,s,u,m){var v=Object.create(He);return v.size=i,v._root=s,v.__ownerID=u,v.__hash=m,v.__altered=!1,v}function emptyMap(){return Ve||(Ve=makeMap(0))}function updateMap(i,s,u){var m,v;if(i._root){var _=MakeRef(X),j=MakeRef(Y);if(m=updateNode(i._root,i.__ownerID,0,void 0,s,u,_,j),!j.value)return i;v=i.size+(_.value?u===W?-1:1:0)}else{if(u===W)return i;v=1,m=new ArrayMapNode(i.__ownerID,[[s,u]])}return i.__ownerID?(i.size=v,i._root=m,i.__hash=void 0,i.__altered=!0,i):m?makeMap(v,m):emptyMap()}function updateNode(i,s,u,m,v,_,j,M){return i?i.update(s,u,m,v,_,j,M):_===W?i:(SetRef(M),SetRef(j),new ValueNode(s,m,[v,_]))}function isLeafNode(i){return i.constructor===ValueNode||i.constructor===HashCollisionNode}function mergeIntoNode(i,s,u,m,v){if(i.keyHash===m)return new HashCollisionNode(s,m,[i.entry,v]);var _,M=(0===u?i.keyHash:i.keyHash>>>u)&$,W=(0===u?m:m>>>u)&$;return new BitmapIndexedNode(s,1<>>=1)j[$]=1&u?s[_++]:void 0;return j[m]=v,new HashArrayMapNode(i,_+1,j)}function mergeIntoMapWith(i,s,u){for(var m=[],v=0;v>1&1431655765))+(i>>2&858993459))+(i>>4)&252645135,i+=i>>8,127&(i+=i>>16)}function setIn(i,s,u,m){var v=m?i:arrCopy(i);return v[s]=u,v}function spliceIn(i,s,u,m){var v=i.length+1;if(m&&s+1===v)return i[s]=u,i;for(var _=new Array(v),j=0,M=0;M=Xe)return createNodes(i,$,m,v);var ee=i&&i===this.ownerID,ae=ee?$:arrCopy($);return Z?M?X===Y-1?ae.pop():ae[X]=ae.pop():ae[X]=[m,v]:ae.push([m,v]),ee?(this.entries=ae,this):new ArrayMapNode(i,ae)}},BitmapIndexedNode.prototype.get=function(i,s,u,m){void 0===s&&(s=hash(u));var v=1<<((0===i?s:s>>>i)&$),_=this.bitmap;return 0==(_&v)?m:this.nodes[popCount(_&v-1)].get(i+j,s,u,m)},BitmapIndexedNode.prototype.update=function(i,s,u,m,v,_,M){void 0===u&&(u=hash(m));var X=(0===s?u:u>>>s)&$,Y=1<=Ye)return expandNodes(i,ie,Z,X,ce);if(ee&&!ce&&2===ie.length&&isLeafNode(ie[1^ae]))return ie[1^ae];if(ee&&ce&&1===ie.length&&isLeafNode(ce))return ce;var pe=i&&i===this.ownerID,de=ee?ce?Z:Z^Y:Z|Y,fe=ee?ce?setIn(ie,ae,ce,pe):spliceOut(ie,ae,pe):spliceIn(ie,ae,ce,pe);return pe?(this.bitmap=de,this.nodes=fe,this):new BitmapIndexedNode(i,de,fe)},HashArrayMapNode.prototype.get=function(i,s,u,m){void 0===s&&(s=hash(u));var v=(0===i?s:s>>>i)&$,_=this.nodes[v];return _?_.get(i+j,s,u,m):m},HashArrayMapNode.prototype.update=function(i,s,u,m,v,_,M){void 0===u&&(u=hash(m));var X=(0===s?u:u>>>s)&$,Y=v===W,Z=this.nodes,ee=Z[X];if(Y&&!ee)return this;var ae=updateNode(ee,i,s+j,u,m,v,_,M);if(ae===ee)return this;var ie=this.count;if(ee){if(!ae&&--ie0&&m=0&&i>>s&$;if(m>=this.array.length)return new VNode([],i);var v,_=0===m;if(s>0){var M=this.array[m];if((v=M&&M.removeBefore(i,s-j,u))===M&&_)return this}if(_&&!v)return this;var W=editableVNode(this,i);if(!_)for(var X=0;X>>s&$;if(v>=this.array.length)return this;if(s>0){var _=this.array[v];if((m=_&&_.removeAfter(i,s-j,u))===_&&v===this.array.length-1)return this}var M=editableVNode(this,i);return M.array.splice(v+1),m&&(M.array[v]=m),M};var rt,nt,ot={};function iterateList(i,s){var u=i._origin,m=i._capacity,v=getTailOffset(m),_=i._tail;return iterateNodeOrLeaf(i._root,i._level,0);function iterateNodeOrLeaf(i,s,u){return 0===s?iterateLeaf(i,u):iterateNode(i,s,u)}function iterateLeaf(i,j){var $=j===v?_&&_.array:i&&i.array,W=j>u?0:u-j,X=m-j;return X>M&&(X=M),function(){if(W===X)return ot;var i=s?--X:W++;return $&&$[i]}}function iterateNode(i,v,_){var $,W=i&&i.array,X=_>u?0:u-_>>v,Y=1+(m-_>>v);return Y>M&&(Y=M),function(){for(;;){if($){var i=$();if(i!==ot)return i;$=null}if(X===Y)return ot;var u=s?--Y:X++;$=iterateNodeOrLeaf(W&&W[u],v-j,_+(u<=i.size||s<0)return i.withMutations((function(i){s<0?setListBounds(i,s).set(0,u):setListBounds(i,0,s+1).set(s,u)}));s+=i._origin;var m=i._tail,v=i._root,_=MakeRef(Y);return s>=getTailOffset(i._capacity)?m=updateVNode(m,i.__ownerID,0,s,u,_):v=updateVNode(v,i.__ownerID,i._level,s,u,_),_.value?i.__ownerID?(i._root=v,i._tail=m,i.__hash=void 0,i.__altered=!0,i):makeList(i._origin,i._capacity,i._level,v,m):i}function updateVNode(i,s,u,m,v,_){var M,W=m>>>u&$,X=i&&W0){var Y=i&&i.array[W],Z=updateVNode(Y,s,u-j,m,v,_);return Z===Y?i:((M=editableVNode(i,s)).array[W]=Z,M)}return X&&i.array[W]===v?i:(SetRef(_),M=editableVNode(i,s),void 0===v&&W===M.array.length-1?M.array.pop():M.array[W]=v,M)}function editableVNode(i,s){return s&&i&&s===i.ownerID?i:new VNode(i?i.array.slice():[],s)}function listNodeFor(i,s){if(s>=getTailOffset(i._capacity))return i._tail;if(s<1<0;)u=u.array[s>>>m&$],m-=j;return u}}function setListBounds(i,s,u){void 0!==s&&(s|=0),void 0!==u&&(u|=0);var m=i.__ownerID||new OwnerID,v=i._origin,_=i._capacity,M=v+s,W=void 0===u?_:u<0?_+u:v+u;if(M===v&&W===_)return i;if(M>=W)return i.clear();for(var X=i._level,Y=i._root,Z=0;M+Z<0;)Y=new VNode(Y&&Y.array.length?[void 0,Y]:[],m),Z+=1<<(X+=j);Z&&(M+=Z,v+=Z,W+=Z,_+=Z);for(var ee=getTailOffset(_),ae=getTailOffset(W);ae>=1<ee?new VNode([],m):ie;if(ie&&ae>ee&&M<_&&ie.array.length){for(var ce=Y=editableVNode(Y,m),pe=X;pe>j;pe-=j){var de=ee>>>pe&$;ce=ce.array[de]=editableVNode(ce.array[de],m)}ce.array[ee>>>j&$]=ie}if(W<_&&(le=le&&le.removeAfter(m,0,W)),M>=ae)M-=ae,W-=ae,X=j,Y=null,le=le&&le.removeBefore(m,0,M);else if(M>v||ae>>X&$;if(fe!==ae>>>X&$)break;fe&&(Z+=(1<v&&(Y=Y.removeBefore(m,X,M-Z)),Y&&aev&&(v=M.size),isIterable(j)||(M=M.map((function(i){return fromJS(i)}))),m.push(M)}return v>i.size&&(i=i.setSize(v)),mergeIntoCollectionWith(i,s,m)}function getTailOffset(i){return i>>j<=M&&j.size>=2*_.size?(m=(v=j.filter((function(i,s){return void 0!==i&&$!==s}))).toKeyedSeq().map((function(i){return i[0]})).flip().toMap(),i.__ownerID&&(m.__ownerID=v.__ownerID=i.__ownerID)):(m=_.remove(s),v=$===j.size-1?j.pop():j.set($,void 0))}else if(X){if(u===j.get($)[1])return i;m=_,v=j.set($,[s,u])}else m=_.set(s,j.size),v=j.set(j.size,[s,u]);return i.__ownerID?(i.size=m.size,i._map=m,i._list=v,i.__hash=void 0,i):makeOrderedMap(m,v)}function ToKeyedSequence(i,s){this._iter=i,this._useKeys=s,this.size=i.size}function ToIndexedSequence(i){this._iter=i,this.size=i.size}function ToSetSequence(i){this._iter=i,this.size=i.size}function FromEntriesSequence(i){this._iter=i,this.size=i.size}function flipFactory(i){var s=makeSequence(i);return s._iter=i,s.size=i.size,s.flip=function(){return i},s.reverse=function(){var s=i.reverse.apply(this);return s.flip=function(){return i.reverse()},s},s.has=function(s){return i.includes(s)},s.includes=function(s){return i.has(s)},s.cacheResult=cacheResultThrough,s.__iterateUncached=function(s,u){var m=this;return i.__iterate((function(i,u){return!1!==s(u,i,m)}),u)},s.__iteratorUncached=function(s,u){if(s===ae){var m=i.__iterator(s,u);return new Iterator((function(){var i=m.next();if(!i.done){var s=i.value[0];i.value[0]=i.value[1],i.value[1]=s}return i}))}return i.__iterator(s===ee?Z:ee,u)},s}function mapFactory(i,s,u){var m=makeSequence(i);return m.size=i.size,m.has=function(s){return i.has(s)},m.get=function(m,v){var _=i.get(m,W);return _===W?v:s.call(u,_,m,i)},m.__iterateUncached=function(m,v){var _=this;return i.__iterate((function(i,v,j){return!1!==m(s.call(u,i,v,j),v,_)}),v)},m.__iteratorUncached=function(m,v){var _=i.__iterator(ae,v);return new Iterator((function(){var v=_.next();if(v.done)return v;var j=v.value,M=j[0];return iteratorValue(m,M,s.call(u,j[1],M,i),v)}))},m}function reverseFactory(i,s){var u=makeSequence(i);return u._iter=i,u.size=i.size,u.reverse=function(){return i},i.flip&&(u.flip=function(){var s=flipFactory(i);return s.reverse=function(){return i.flip()},s}),u.get=function(u,m){return i.get(s?u:-1-u,m)},u.has=function(u){return i.has(s?u:-1-u)},u.includes=function(s){return i.includes(s)},u.cacheResult=cacheResultThrough,u.__iterate=function(s,u){var m=this;return i.__iterate((function(i,u){return s(i,u,m)}),!u)},u.__iterator=function(s,u){return i.__iterator(s,!u)},u}function filterFactory(i,s,u,m){var v=makeSequence(i);return m&&(v.has=function(m){var v=i.get(m,W);return v!==W&&!!s.call(u,v,m,i)},v.get=function(m,v){var _=i.get(m,W);return _!==W&&s.call(u,_,m,i)?_:v}),v.__iterateUncached=function(v,_){var j=this,M=0;return i.__iterate((function(i,_,$){if(s.call(u,i,_,$))return M++,v(i,m?_:M-1,j)}),_),M},v.__iteratorUncached=function(v,_){var j=i.__iterator(ae,_),M=0;return new Iterator((function(){for(;;){var _=j.next();if(_.done)return _;var $=_.value,W=$[0],X=$[1];if(s.call(u,X,W,i))return iteratorValue(v,m?W:M++,X,_)}}))},v}function countByFactory(i,s,u){var m=Map().asMutable();return i.__iterate((function(v,_){m.update(s.call(u,v,_,i),0,(function(i){return i+1}))})),m.asImmutable()}function groupByFactory(i,s,u){var m=isKeyed(i),v=(isOrdered(i)?OrderedMap():Map()).asMutable();i.__iterate((function(_,j){v.update(s.call(u,_,j,i),(function(i){return(i=i||[]).push(m?[j,_]:_),i}))}));var _=iterableClass(i);return v.map((function(s){return reify(i,_(s))}))}function sliceFactory(i,s,u,m){var v=i.size;if(void 0!==s&&(s|=0),void 0!==u&&(u===1/0?u=v:u|=0),wholeSlice(s,u,v))return i;var _=resolveBegin(s,v),j=resolveEnd(u,v);if(_!=_||j!=j)return sliceFactory(i.toSeq().cacheResult(),s,u,m);var M,$=j-_;$==$&&(M=$<0?0:$);var W=makeSequence(i);return W.size=0===M?M:i.size&&M||void 0,!m&&isSeq(i)&&M>=0&&(W.get=function(s,u){return(s=wrapIndex(this,s))>=0&&sM)return iteratorDone();var i=v.next();return m||s===ee?i:iteratorValue(s,$-1,s===Z?void 0:i.value[1],i)}))},W}function takeWhileFactory(i,s,u){var m=makeSequence(i);return m.__iterateUncached=function(m,v){var _=this;if(v)return this.cacheResult().__iterate(m,v);var j=0;return i.__iterate((function(i,v,M){return s.call(u,i,v,M)&&++j&&m(i,v,_)})),j},m.__iteratorUncached=function(m,v){var _=this;if(v)return this.cacheResult().__iterator(m,v);var j=i.__iterator(ae,v),M=!0;return new Iterator((function(){if(!M)return iteratorDone();var i=j.next();if(i.done)return i;var v=i.value,$=v[0],W=v[1];return s.call(u,W,$,_)?m===ae?i:iteratorValue(m,$,W,i):(M=!1,iteratorDone())}))},m}function skipWhileFactory(i,s,u,m){var v=makeSequence(i);return v.__iterateUncached=function(v,_){var j=this;if(_)return this.cacheResult().__iterate(v,_);var M=!0,$=0;return i.__iterate((function(i,_,W){if(!M||!(M=s.call(u,i,_,W)))return $++,v(i,m?_:$-1,j)})),$},v.__iteratorUncached=function(v,_){var j=this;if(_)return this.cacheResult().__iterator(v,_);var M=i.__iterator(ae,_),$=!0,W=0;return new Iterator((function(){var i,_,X;do{if((i=M.next()).done)return m||v===ee?i:iteratorValue(v,W++,v===Z?void 0:i.value[1],i);var Y=i.value;_=Y[0],X=Y[1],$&&($=s.call(u,X,_,j))}while($);return v===ae?i:iteratorValue(v,_,X,i)}))},v}function concatFactory(i,s){var u=isKeyed(i),m=[i].concat(s).map((function(i){return isIterable(i)?u&&(i=KeyedIterable(i)):i=u?keyedSeqFromValue(i):indexedSeqFromValue(Array.isArray(i)?i:[i]),i})).filter((function(i){return 0!==i.size}));if(0===m.length)return i;if(1===m.length){var v=m[0];if(v===i||u&&isKeyed(v)||isIndexed(i)&&isIndexed(v))return v}var _=new ArraySeq(m);return u?_=_.toKeyedSeq():isIndexed(i)||(_=_.toSetSeq()),(_=_.flatten(!0)).size=m.reduce((function(i,s){if(void 0!==i){var u=s.size;if(void 0!==u)return i+u}}),0),_}function flattenFactory(i,s,u){var m=makeSequence(i);return m.__iterateUncached=function(m,v){var _=0,j=!1;function flatDeep(i,M){var $=this;i.__iterate((function(i,v){return(!s||M0}function zipWithFactory(i,s,u){var m=makeSequence(i);return m.size=new ArraySeq(u).map((function(i){return i.size})).min(),m.__iterate=function(i,s){for(var u,m=this.__iterator(ee,s),v=0;!(u=m.next()).done&&!1!==i(u.value,v++,this););return v},m.__iteratorUncached=function(i,m){var v=u.map((function(i){return i=Iterable(i),getIterator(m?i.reverse():i)})),_=0,j=!1;return new Iterator((function(){var u;return j||(u=v.map((function(i){return i.next()})),j=u.some((function(i){return i.done}))),j?iteratorDone():iteratorValue(i,_++,s.apply(null,u.map((function(i){return i.value}))))}))},m}function reify(i,s){return isSeq(i)?s:i.constructor(s)}function validateEntry(i){if(i!==Object(i))throw new TypeError("Expected [K, V] tuple: "+i)}function resolveSize(i){return assertNotInfinite(i.size),ensureSize(i)}function iterableClass(i){return isKeyed(i)?KeyedIterable:isIndexed(i)?IndexedIterable:SetIterable}function makeSequence(i){return Object.create((isKeyed(i)?KeyedSeq:isIndexed(i)?IndexedSeq:SetSeq).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):Seq.prototype.cacheResult.call(this)}function defaultComparator(i,s){return i>s?1:i=0;u--)s={value:arguments[u],next:s};return this.__ownerID?(this.size=i,this._head=s,this.__hash=void 0,this.__altered=!0,this):makeStack(i,s)},Stack.prototype.pushAll=function(i){if(0===(i=IndexedIterable(i)).size)return this;assertNotInfinite(i.size);var s=this.size,u=this._head;return i.reverse().forEach((function(i){s++,u={value:i,next:u}})),this.__ownerID?(this.size=s,this._head=u,this.__hash=void 0,this.__altered=!0,this):makeStack(s,u)},Stack.prototype.pop=function(){return this.slice(1)},Stack.prototype.unshift=function(){return this.push.apply(this,arguments)},Stack.prototype.unshiftAll=function(i){return this.pushAll(i)},Stack.prototype.shift=function(){return this.pop.apply(this,arguments)},Stack.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function(i,s){if(wholeSlice(i,s,this.size))return this;var u=resolveBegin(i,this.size);if(resolveEnd(s,this.size)!==this.size)return IndexedCollection.prototype.slice.call(this,i,s);for(var m=this.size-u,v=this._head;u--;)v=v.next;return this.__ownerID?(this.size=m,this._head=v,this.__hash=void 0,this.__altered=!0,this):makeStack(m,v)},Stack.prototype.__ensureOwner=function(i){return i===this.__ownerID?this:i?makeStack(this.size,this._head,i,this.__hash):(this.__ownerID=i,this.__altered=!1,this)},Stack.prototype.__iterate=function(i,s){if(s)return this.reverse().__iterate(i);for(var u=0,m=this._head;m&&!1!==i(m.value,u++,this);)m=m.next;return u},Stack.prototype.__iterator=function(i,s){if(s)return this.reverse().__iterator(i);var u=0,m=this._head;return new Iterator((function(){if(m){var s=m.value;return m=m.next,iteratorValue(i,u++,s)}return iteratorDone()}))},Stack.isStack=isStack;var pt,ht="@@__IMMUTABLE_STACK__@@",dt=Stack.prototype;function makeStack(i,s,u,m){var v=Object.create(dt);return v.size=i,v._head=s,v.__ownerID=u,v.__hash=m,v.__altered=!1,v}function emptyStack(){return pt||(pt=makeStack(0))}function mixin(i,s){var keyCopier=function(u){i.prototype[u]=s[u]};return Object.keys(s).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(s).forEach(keyCopier),i}dt[ht]=!0,dt.withMutations=He.withMutations,dt.asMutable=He.asMutable,dt.asImmutable=He.asImmutable,dt.wasAltered=He.wasAltered,Iterable.Iterator=Iterator,mixin(Iterable,{toArray:function(){assertNotInfinite(this.size);var i=new Array(this.size||0);return this.valueSeq().__iterate((function(s,u){i[u]=s})),i},toIndexedSeq:function(){return new ToIndexedSequence(this)},toJS:function(){return this.toSeq().map((function(i){return i&&"function"==typeof i.toJS?i.toJS():i})).__toJS()},toJSON:function(){return this.toSeq().map((function(i){return i&&"function"==typeof i.toJSON?i.toJSON():i})).__toJS()},toKeyedSeq:function(){return new ToKeyedSequence(this,!0)},toMap:function(){return Map(this.toKeyedSeq())},toObject:function(){assertNotInfinite(this.size);var i={};return this.__iterate((function(s,u){i[u]=s})),i},toOrderedMap:function(){return OrderedMap(this.toKeyedSeq())},toOrderedSet:function(){return OrderedSet(isKeyed(this)?this.valueSeq():this)},toSet:function(){return Set(isKeyed(this)?this.valueSeq():this)},toSetSeq:function(){return new ToSetSequence(this)},toSeq:function(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Stack(isKeyed(this)?this.valueSeq():this)},toList:function(){return List(isKeyed(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(i,s){return 0===this.size?i+s:i+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+s},concat:function(){return reify(this,concatFactory(this,i.call(arguments,0)))},includes:function(i){return this.some((function(s){return is(s,i)}))},entries:function(){return this.__iterator(ae)},every:function(i,s){assertNotInfinite(this.size);var u=!0;return this.__iterate((function(m,v,_){if(!i.call(s,m,v,_))return u=!1,!1})),u},filter:function(i,s){return reify(this,filterFactory(this,i,s,!0))},find:function(i,s,u){var m=this.findEntry(i,s);return m?m[1]:u},forEach:function(i,s){return assertNotInfinite(this.size),this.__iterate(s?i.bind(s):i)},join:function(i){assertNotInfinite(this.size),i=void 0!==i?""+i:",";var s="",u=!0;return this.__iterate((function(m){u?u=!1:s+=i,s+=null!=m?m.toString():""})),s},keys:function(){return this.__iterator(Z)},map:function(i,s){return reify(this,mapFactory(this,i,s))},reduce:function(i,s,u){var m,v;return assertNotInfinite(this.size),arguments.length<2?v=!0:m=s,this.__iterate((function(s,_,j){v?(v=!1,m=s):m=i.call(u,m,s,_,j)})),m},reduceRight:function(i,s,u){var m=this.toKeyedSeq().reverse();return m.reduce.apply(m,arguments)},reverse:function(){return reify(this,reverseFactory(this,!0))},slice:function(i,s){return reify(this,sliceFactory(this,i,s,!0))},some:function(i,s){return!this.every(not(i),s)},sort:function(i){return reify(this,sortFactory(this,i))},values:function(){return this.__iterator(ee)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(i,s){return ensureSize(i?this.toSeq().filter(i,s):this)},countBy:function(i,s){return countByFactory(this,i,s)},equals:function(i){return deepEqual(this,i)},entrySeq:function(){var i=this;if(i._cache)return new ArraySeq(i._cache);var s=i.toSeq().map(entryMapper).toIndexedSeq();return s.fromEntrySeq=function(){return i.toSeq()},s},filterNot:function(i,s){return this.filter(not(i),s)},findEntry:function(i,s,u){var m=u;return this.__iterate((function(u,v,_){if(i.call(s,u,v,_))return m=[v,u],!1})),m},findKey:function(i,s){var u=this.findEntry(i,s);return u&&u[0]},findLast:function(i,s,u){return this.toKeyedSeq().reverse().find(i,s,u)},findLastEntry:function(i,s,u){return this.toKeyedSeq().reverse().findEntry(i,s,u)},findLastKey:function(i,s){return this.toKeyedSeq().reverse().findKey(i,s)},first:function(){return this.find(returnTrue)},flatMap:function(i,s){return reify(this,flatMapFactory(this,i,s))},flatten:function(i){return reify(this,flattenFactory(this,i,!0))},fromEntrySeq:function(){return new FromEntriesSequence(this)},get:function(i,s){return this.find((function(s,u){return is(u,i)}),void 0,s)},getIn:function(i,s){for(var u,m=this,v=forceIterator(i);!(u=v.next()).done;){var _=u.value;if((m=m&&m.get?m.get(_,W):W)===W)return s}return m},groupBy:function(i,s){return groupByFactory(this,i,s)},has:function(i){return this.get(i,W)!==W},hasIn:function(i){return this.getIn(i,W)!==W},isSubset:function(i){return i="function"==typeof i.includes?i:Iterable(i),this.every((function(s){return i.includes(s)}))},isSuperset:function(i){return(i="function"==typeof i.isSubset?i:Iterable(i)).isSubset(this)},keyOf:function(i){return this.findKey((function(s){return is(s,i)}))},keySeq:function(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(i){return this.toKeyedSeq().reverse().keyOf(i)},max:function(i){return maxFactory(this,i)},maxBy:function(i,s){return maxFactory(this,s,i)},min:function(i){return maxFactory(this,i?neg(i):defaultNegComparator)},minBy:function(i,s){return maxFactory(this,s?neg(s):defaultNegComparator,i)},rest:function(){return this.slice(1)},skip:function(i){return this.slice(Math.max(0,i))},skipLast:function(i){return reify(this,this.toSeq().reverse().skip(i).reverse())},skipWhile:function(i,s){return reify(this,skipWhileFactory(this,i,s,!0))},skipUntil:function(i,s){return this.skipWhile(not(i),s)},sortBy:function(i,s){return reify(this,sortFactory(this,s,i))},take:function(i){return this.slice(0,Math.max(0,i))},takeLast:function(i){return reify(this,this.toSeq().reverse().take(i).reverse())},takeWhile:function(i,s){return reify(this,takeWhileFactory(this,i,s))},takeUntil:function(i,s){return this.takeWhile(not(i),s)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=hashIterable(this))}});var mt=Iterable.prototype;mt[s]=!0,mt[ce]=mt.values,mt.__toJS=mt.toArray,mt.__toStringMapper=quoteString,mt.inspect=mt.toSource=function(){return this.toString()},mt.chain=mt.flatMap,mt.contains=mt.includes,mixin(KeyedIterable,{flip:function(){return reify(this,flipFactory(this))},mapEntries:function(i,s){var u=this,m=0;return reify(this,this.toSeq().map((function(v,_){return i.call(s,[_,v],m++,u)})).fromEntrySeq())},mapKeys:function(i,s){var u=this;return reify(this,this.toSeq().flip().map((function(m,v){return i.call(s,m,v,u)})).flip())}});var gt=KeyedIterable.prototype;function keyMapper(i,s){return s}function entryMapper(i,s){return[s,i]}function not(i){return function(){return!i.apply(this,arguments)}}function neg(i){return function(){return-i.apply(this,arguments)}}function quoteString(i){return"string"==typeof i?JSON.stringify(i):String(i)}function defaultZipper(){return arrCopy(arguments)}function defaultNegComparator(i,s){return is?-1:0}function hashIterable(i){if(i.size===1/0)return 0;var s=isOrdered(i),u=isKeyed(i),m=s?1:0;return murmurHashOfSize(i.__iterate(u?s?function(i,s){m=31*m+hashMerge(hash(i),hash(s))|0}:function(i,s){m=m+hashMerge(hash(i),hash(s))|0}:s?function(i){m=31*m+hash(i)|0}:function(i){m=m+hash(i)|0}),m)}function murmurHashOfSize(i,s){return s=be(s,3432918353),s=be(s<<15|s>>>-15,461845907),s=be(s<<13|s>>>-13,5),s=be((s=(s+3864292196|0)^i)^s>>>16,2246822507),s=smi((s=be(s^s>>>13,3266489909))^s>>>16)}function hashMerge(i,s){return i^s+2654435769+(i<<6)+(i>>2)|0}return gt[u]=!0,gt[ce]=mt.entries,gt.__toJS=mt.toObject,gt.__toStringMapper=function(i,s){return JSON.stringify(s)+": "+quoteString(i)},mixin(IndexedIterable,{toKeyedSeq:function(){return new ToKeyedSequence(this,!1)},filter:function(i,s){return reify(this,filterFactory(this,i,s,!1))},findIndex:function(i,s){var u=this.findEntry(i,s);return u?u[0]:-1},indexOf:function(i){var s=this.keyOf(i);return void 0===s?-1:s},lastIndexOf:function(i){var s=this.lastKeyOf(i);return void 0===s?-1:s},reverse:function(){return reify(this,reverseFactory(this,!1))},slice:function(i,s){return reify(this,sliceFactory(this,i,s,!1))},splice:function(i,s){var u=arguments.length;if(s=Math.max(0|s,0),0===u||2===u&&!s)return this;i=resolveBegin(i,i<0?this.count():this.size);var m=this.slice(0,i);return reify(this,1===u?m:m.concat(arrCopy(arguments,2),this.slice(i+s)))},findLastIndex:function(i,s){var u=this.findLastEntry(i,s);return u?u[0]:-1},first:function(){return this.get(0)},flatten:function(i){return reify(this,flattenFactory(this,i,!1))},get:function(i,s){return(i=wrapIndex(this,i))<0||this.size===1/0||void 0!==this.size&&i>this.size?s:this.find((function(s,u){return u===i}),void 0,s)},has:function(i){return(i=wrapIndex(this,i))>=0&&(void 0!==this.size?this.size===1/0||i{"function"==typeof Object.create?i.exports=function inherits(i,s){s&&(i.super_=s,i.prototype=Object.create(s.prototype,{constructor:{value:i,enumerable:!1,writable:!0,configurable:!0}}))}:i.exports=function inherits(i,s){if(s){i.super_=s;var TempCtor=function(){};TempCtor.prototype=s.prototype,i.prototype=new TempCtor,i.prototype.constructor=i}}},35823:i=>{i.exports=function(i,s,u,m){var v=new Blob(void 0!==m?[m,i]:[i],{type:u||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(v,s);else{var _=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(v):window.webkitURL.createObjectURL(v),j=document.createElement("a");j.style.display="none",j.href=_,j.setAttribute("download",s),void 0===j.download&&j.setAttribute("target","_blank"),document.body.appendChild(j),j.click(),setTimeout((function(){document.body.removeChild(j),window.URL.revokeObjectURL(_)}),200)}}},91296:(i,s,u)=>{var m=NaN,v="[object Symbol]",_=/^\s+|\s+$/g,j=/^[-+]0x[0-9a-f]+$/i,M=/^0b[01]+$/i,$=/^0o[0-7]+$/i,W=parseInt,X="object"==typeof u.g&&u.g&&u.g.Object===Object&&u.g,Y="object"==typeof self&&self&&self.Object===Object&&self,Z=X||Y||Function("return this")(),ee=Object.prototype.toString,ae=Math.max,ie=Math.min,now=function(){return Z.Date.now()};function isObject(i){var s=typeof i;return!!i&&("object"==s||"function"==s)}function toNumber(i){if("number"==typeof i)return i;if(function isSymbol(i){return"symbol"==typeof i||function isObjectLike(i){return!!i&&"object"==typeof i}(i)&&ee.call(i)==v}(i))return m;if(isObject(i)){var s="function"==typeof i.valueOf?i.valueOf():i;i=isObject(s)?s+"":s}if("string"!=typeof i)return 0===i?i:+i;i=i.replace(_,"");var u=M.test(i);return u||$.test(i)?W(i.slice(2),u?2:8):j.test(i)?m:+i}i.exports=function debounce(i,s,u){var m,v,_,j,M,$,W=0,X=!1,Y=!1,Z=!0;if("function"!=typeof i)throw new TypeError("Expected a function");function invokeFunc(s){var u=m,_=v;return m=v=void 0,W=s,j=i.apply(_,u)}function shouldInvoke(i){var u=i-$;return void 0===$||u>=s||u<0||Y&&i-W>=_}function timerExpired(){var i=now();if(shouldInvoke(i))return trailingEdge(i);M=setTimeout(timerExpired,function remainingWait(i){var u=s-(i-$);return Y?ie(u,_-(i-W)):u}(i))}function trailingEdge(i){return M=void 0,Z&&m?invokeFunc(i):(m=v=void 0,j)}function debounced(){var i=now(),u=shouldInvoke(i);if(m=arguments,v=this,$=i,u){if(void 0===M)return function leadingEdge(i){return W=i,M=setTimeout(timerExpired,s),X?invokeFunc(i):j}($);if(Y)return M=setTimeout(timerExpired,s),invokeFunc($)}return void 0===M&&(M=setTimeout(timerExpired,s)),j}return s=toNumber(s)||0,isObject(u)&&(X=!!u.leading,_=(Y="maxWait"in u)?ae(toNumber(u.maxWait)||0,s):_,Z="trailing"in u?!!u.trailing:Z),debounced.cancel=function cancel(){void 0!==M&&clearTimeout(M),W=0,m=$=v=M=void 0},debounced.flush=function flush(){return void 0===M?j:trailingEdge(now())},debounced}},18552:(i,s,u)=>{var m=u(10852)(u(55639),"DataView");i.exports=m},1989:(i,s,u)=>{var m=u(51789),v=u(80401),_=u(57667),j=u(21327),M=u(81866);function Hash(i){var s=-1,u=null==i?0:i.length;for(this.clear();++s{var m=u(3118),v=u(9435);function LazyWrapper(i){this.__wrapped__=i,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}LazyWrapper.prototype=m(v.prototype),LazyWrapper.prototype.constructor=LazyWrapper,i.exports=LazyWrapper},38407:(i,s,u)=>{var m=u(27040),v=u(14125),_=u(82117),j=u(67518),M=u(54705);function ListCache(i){var s=-1,u=null==i?0:i.length;for(this.clear();++s{var m=u(3118),v=u(9435);function LodashWrapper(i,s){this.__wrapped__=i,this.__actions__=[],this.__chain__=!!s,this.__index__=0,this.__values__=void 0}LodashWrapper.prototype=m(v.prototype),LodashWrapper.prototype.constructor=LodashWrapper,i.exports=LodashWrapper},57071:(i,s,u)=>{var m=u(10852)(u(55639),"Map");i.exports=m},83369:(i,s,u)=>{var m=u(24785),v=u(11285),_=u(96e3),j=u(49916),M=u(95265);function MapCache(i){var s=-1,u=null==i?0:i.length;for(this.clear();++s{var m=u(10852)(u(55639),"Promise");i.exports=m},58525:(i,s,u)=>{var m=u(10852)(u(55639),"Set");i.exports=m},88668:(i,s,u)=>{var m=u(83369),v=u(90619),_=u(72385);function SetCache(i){var s=-1,u=null==i?0:i.length;for(this.__data__=new m;++s{var m=u(38407),v=u(37465),_=u(63779),j=u(67599),M=u(44758),$=u(34309);function Stack(i){var s=this.__data__=new m(i);this.size=s.size}Stack.prototype.clear=v,Stack.prototype.delete=_,Stack.prototype.get=j,Stack.prototype.has=M,Stack.prototype.set=$,i.exports=Stack},62705:(i,s,u)=>{var m=u(55639).Symbol;i.exports=m},11149:(i,s,u)=>{var m=u(55639).Uint8Array;i.exports=m},70577:(i,s,u)=>{var m=u(10852)(u(55639),"WeakMap");i.exports=m},96874:i=>{i.exports=function apply(i,s,u){switch(u.length){case 0:return i.call(s);case 1:return i.call(s,u[0]);case 2:return i.call(s,u[0],u[1]);case 3:return i.call(s,u[0],u[1],u[2])}return i.apply(s,u)}},77412:i=>{i.exports=function arrayEach(i,s){for(var u=-1,m=null==i?0:i.length;++u{i.exports=function arrayFilter(i,s){for(var u=-1,m=null==i?0:i.length,v=0,_=[];++u{var m=u(42118);i.exports=function arrayIncludes(i,s){return!!(null==i?0:i.length)&&m(i,s,0)>-1}},14636:(i,s,u)=>{var m=u(22545),v=u(35694),_=u(1469),j=u(44144),M=u(65776),$=u(36719),W=Object.prototype.hasOwnProperty;i.exports=function arrayLikeKeys(i,s){var u=_(i),X=!u&&v(i),Y=!u&&!X&&j(i),Z=!u&&!X&&!Y&&$(i),ee=u||X||Y||Z,ae=ee?m(i.length,String):[],ie=ae.length;for(var le in i)!s&&!W.call(i,le)||ee&&("length"==le||Y&&("offset"==le||"parent"==le)||Z&&("buffer"==le||"byteLength"==le||"byteOffset"==le)||M(le,ie))||ae.push(le);return ae}},29932:i=>{i.exports=function arrayMap(i,s){for(var u=-1,m=null==i?0:i.length,v=Array(m);++u{i.exports=function arrayPush(i,s){for(var u=-1,m=s.length,v=i.length;++u{i.exports=function arrayReduce(i,s,u,m){var v=-1,_=null==i?0:i.length;for(m&&_&&(u=i[++v]);++v<_;)u=s(u,i[v],v,i);return u}},82908:i=>{i.exports=function arraySome(i,s){for(var u=-1,m=null==i?0:i.length;++u{i.exports=function asciiToArray(i){return i.split("")}},49029:i=>{var s=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;i.exports=function asciiWords(i){return i.match(s)||[]}},86556:(i,s,u)=>{var m=u(89465),v=u(77813);i.exports=function assignMergeValue(i,s,u){(void 0!==u&&!v(i[s],u)||void 0===u&&!(s in i))&&m(i,s,u)}},34865:(i,s,u)=>{var m=u(89465),v=u(77813),_=Object.prototype.hasOwnProperty;i.exports=function assignValue(i,s,u){var j=i[s];_.call(i,s)&&v(j,u)&&(void 0!==u||s in i)||m(i,s,u)}},18470:(i,s,u)=>{var m=u(77813);i.exports=function assocIndexOf(i,s){for(var u=i.length;u--;)if(m(i[u][0],s))return u;return-1}},44037:(i,s,u)=>{var m=u(98363),v=u(3674);i.exports=function baseAssign(i,s){return i&&m(s,v(s),i)}},63886:(i,s,u)=>{var m=u(98363),v=u(81704);i.exports=function baseAssignIn(i,s){return i&&m(s,v(s),i)}},89465:(i,s,u)=>{var m=u(38777);i.exports=function baseAssignValue(i,s,u){"__proto__"==s&&m?m(i,s,{configurable:!0,enumerable:!0,value:u,writable:!0}):i[s]=u}},85990:(i,s,u)=>{var m=u(46384),v=u(77412),_=u(34865),j=u(44037),M=u(63886),$=u(64626),W=u(278),X=u(18805),Y=u(1911),Z=u(58234),ee=u(46904),ae=u(64160),ie=u(43824),le=u(29148),ce=u(38517),pe=u(1469),de=u(44144),fe=u(56688),ye=u(13218),be=u(72928),_e=u(3674),we=u(81704),Se="[object Arguments]",xe="[object Function]",Pe="[object Object]",Ie={};Ie[Se]=Ie["[object Array]"]=Ie["[object ArrayBuffer]"]=Ie["[object DataView]"]=Ie["[object Boolean]"]=Ie["[object Date]"]=Ie["[object Float32Array]"]=Ie["[object Float64Array]"]=Ie["[object Int8Array]"]=Ie["[object Int16Array]"]=Ie["[object Int32Array]"]=Ie["[object Map]"]=Ie["[object Number]"]=Ie[Pe]=Ie["[object RegExp]"]=Ie["[object Set]"]=Ie["[object String]"]=Ie["[object Symbol]"]=Ie["[object Uint8Array]"]=Ie["[object Uint8ClampedArray]"]=Ie["[object Uint16Array]"]=Ie["[object Uint32Array]"]=!0,Ie["[object Error]"]=Ie[xe]=Ie["[object WeakMap]"]=!1,i.exports=function baseClone(i,s,u,Te,Re,qe){var ze,Ve=1&s,We=2&s,He=4&s;if(u&&(ze=Re?u(i,Te,Re,qe):u(i)),void 0!==ze)return ze;if(!ye(i))return i;var Xe=pe(i);if(Xe){if(ze=ie(i),!Ve)return W(i,ze)}else{var Ye=ae(i),Qe=Ye==xe||"[object GeneratorFunction]"==Ye;if(de(i))return $(i,Ve);if(Ye==Pe||Ye==Se||Qe&&!Re){if(ze=We||Qe?{}:ce(i),!Ve)return We?Y(i,M(ze,i)):X(i,j(ze,i))}else{if(!Ie[Ye])return Re?i:{};ze=le(i,Ye,Ve)}}qe||(qe=new m);var et=qe.get(i);if(et)return et;qe.set(i,ze),be(i)?i.forEach((function(m){ze.add(baseClone(m,s,u,m,i,qe))})):fe(i)&&i.forEach((function(m,v){ze.set(v,baseClone(m,s,u,v,i,qe))}));var tt=Xe?void 0:(He?We?ee:Z:We?we:_e)(i);return v(tt||i,(function(m,v){tt&&(m=i[v=m]),_(ze,v,baseClone(m,s,u,v,i,qe))})),ze}},3118:(i,s,u)=>{var m=u(13218),v=Object.create,_=function(){function object(){}return function(i){if(!m(i))return{};if(v)return v(i);object.prototype=i;var s=new object;return object.prototype=void 0,s}}();i.exports=_},89881:(i,s,u)=>{var m=u(47816),v=u(99291)(m);i.exports=v},41848:i=>{i.exports=function baseFindIndex(i,s,u,m){for(var v=i.length,_=u+(m?1:-1);m?_--:++_{var m=u(62488),v=u(37285);i.exports=function baseFlatten(i,s,u,_,j){var M=-1,$=i.length;for(u||(u=v),j||(j=[]);++M<$;){var W=i[M];s>0&&u(W)?s>1?baseFlatten(W,s-1,u,_,j):m(j,W):_||(j[j.length]=W)}return j}},28483:(i,s,u)=>{var m=u(25063)();i.exports=m},47816:(i,s,u)=>{var m=u(28483),v=u(3674);i.exports=function baseForOwn(i,s){return i&&m(i,s,v)}},97786:(i,s,u)=>{var m=u(71811),v=u(40327);i.exports=function baseGet(i,s){for(var u=0,_=(s=m(s,i)).length;null!=i&&u<_;)i=i[v(s[u++])];return u&&u==_?i:void 0}},68866:(i,s,u)=>{var m=u(62488),v=u(1469);i.exports=function baseGetAllKeys(i,s,u){var _=s(i);return v(i)?_:m(_,u(i))}},44239:(i,s,u)=>{var m=u(62705),v=u(89607),_=u(2333),j=m?m.toStringTag:void 0;i.exports=function baseGetTag(i){return null==i?void 0===i?"[object Undefined]":"[object Null]":j&&j in Object(i)?v(i):_(i)}},13:i=>{i.exports=function baseHasIn(i,s){return null!=i&&s in Object(i)}},42118:(i,s,u)=>{var m=u(41848),v=u(62722),_=u(42351);i.exports=function baseIndexOf(i,s,u){return s==s?_(i,s,u):m(i,v,u)}},9454:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function baseIsArguments(i){return v(i)&&"[object Arguments]"==m(i)}},90939:(i,s,u)=>{var m=u(2492),v=u(37005);i.exports=function baseIsEqual(i,s,u,_,j){return i===s||(null==i||null==s||!v(i)&&!v(s)?i!=i&&s!=s:m(i,s,u,_,baseIsEqual,j))}},2492:(i,s,u)=>{var m=u(46384),v=u(67114),_=u(18351),j=u(16096),M=u(64160),$=u(1469),W=u(44144),X=u(36719),Y="[object Arguments]",Z="[object Array]",ee="[object Object]",ae=Object.prototype.hasOwnProperty;i.exports=function baseIsEqualDeep(i,s,u,ie,le,ce){var pe=$(i),de=$(s),fe=pe?Z:M(i),ye=de?Z:M(s),be=(fe=fe==Y?ee:fe)==ee,_e=(ye=ye==Y?ee:ye)==ee,we=fe==ye;if(we&&W(i)){if(!W(s))return!1;pe=!0,be=!1}if(we&&!be)return ce||(ce=new m),pe||X(i)?v(i,s,u,ie,le,ce):_(i,s,fe,u,ie,le,ce);if(!(1&u)){var Se=be&&ae.call(i,"__wrapped__"),xe=_e&&ae.call(s,"__wrapped__");if(Se||xe){var Pe=Se?i.value():i,Ie=xe?s.value():s;return ce||(ce=new m),le(Pe,Ie,u,ie,ce)}}return!!we&&(ce||(ce=new m),j(i,s,u,ie,le,ce))}},25588:(i,s,u)=>{var m=u(64160),v=u(37005);i.exports=function baseIsMap(i){return v(i)&&"[object Map]"==m(i)}},2958:(i,s,u)=>{var m=u(46384),v=u(90939);i.exports=function baseIsMatch(i,s,u,_){var j=u.length,M=j,$=!_;if(null==i)return!M;for(i=Object(i);j--;){var W=u[j];if($&&W[2]?W[1]!==i[W[0]]:!(W[0]in i))return!1}for(;++j{i.exports=function baseIsNaN(i){return i!=i}},28458:(i,s,u)=>{var m=u(23560),v=u(15346),_=u(13218),j=u(80346),M=/^\[object .+?Constructor\]$/,$=Function.prototype,W=Object.prototype,X=$.toString,Y=W.hasOwnProperty,Z=RegExp("^"+X.call(Y).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");i.exports=function baseIsNative(i){return!(!_(i)||v(i))&&(m(i)?Z:M).test(j(i))}},29221:(i,s,u)=>{var m=u(64160),v=u(37005);i.exports=function baseIsSet(i){return v(i)&&"[object Set]"==m(i)}},38749:(i,s,u)=>{var m=u(44239),v=u(41780),_=u(37005),j={};j["[object Float32Array]"]=j["[object Float64Array]"]=j["[object Int8Array]"]=j["[object Int16Array]"]=j["[object Int32Array]"]=j["[object Uint8Array]"]=j["[object Uint8ClampedArray]"]=j["[object Uint16Array]"]=j["[object Uint32Array]"]=!0,j["[object Arguments]"]=j["[object Array]"]=j["[object ArrayBuffer]"]=j["[object Boolean]"]=j["[object DataView]"]=j["[object Date]"]=j["[object Error]"]=j["[object Function]"]=j["[object Map]"]=j["[object Number]"]=j["[object Object]"]=j["[object RegExp]"]=j["[object Set]"]=j["[object String]"]=j["[object WeakMap]"]=!1,i.exports=function baseIsTypedArray(i){return _(i)&&v(i.length)&&!!j[m(i)]}},67206:(i,s,u)=>{var m=u(91573),v=u(16432),_=u(6557),j=u(1469),M=u(39601);i.exports=function baseIteratee(i){return"function"==typeof i?i:null==i?_:"object"==typeof i?j(i)?v(i[0],i[1]):m(i):M(i)}},280:(i,s,u)=>{var m=u(25726),v=u(86916),_=Object.prototype.hasOwnProperty;i.exports=function baseKeys(i){if(!m(i))return v(i);var s=[];for(var u in Object(i))_.call(i,u)&&"constructor"!=u&&s.push(u);return s}},10313:(i,s,u)=>{var m=u(13218),v=u(25726),_=u(33498),j=Object.prototype.hasOwnProperty;i.exports=function baseKeysIn(i){if(!m(i))return _(i);var s=v(i),u=[];for(var M in i)("constructor"!=M||!s&&j.call(i,M))&&u.push(M);return u}},9435:i=>{i.exports=function baseLodash(){}},91573:(i,s,u)=>{var m=u(2958),v=u(1499),_=u(42634);i.exports=function baseMatches(i){var s=v(i);return 1==s.length&&s[0][2]?_(s[0][0],s[0][1]):function(u){return u===i||m(u,i,s)}}},16432:(i,s,u)=>{var m=u(90939),v=u(27361),_=u(79095),j=u(15403),M=u(89162),$=u(42634),W=u(40327);i.exports=function baseMatchesProperty(i,s){return j(i)&&M(s)?$(W(i),s):function(u){var j=v(u,i);return void 0===j&&j===s?_(u,i):m(s,j,3)}}},42980:(i,s,u)=>{var m=u(46384),v=u(86556),_=u(28483),j=u(59783),M=u(13218),$=u(81704),W=u(36390);i.exports=function baseMerge(i,s,u,X,Y){i!==s&&_(s,(function(_,$){if(Y||(Y=new m),M(_))j(i,s,$,u,baseMerge,X,Y);else{var Z=X?X(W(i,$),_,$+"",i,s,Y):void 0;void 0===Z&&(Z=_),v(i,$,Z)}}),$)}},59783:(i,s,u)=>{var m=u(86556),v=u(64626),_=u(77133),j=u(278),M=u(38517),$=u(35694),W=u(1469),X=u(29246),Y=u(44144),Z=u(23560),ee=u(13218),ae=u(68630),ie=u(36719),le=u(36390),ce=u(59881);i.exports=function baseMergeDeep(i,s,u,pe,de,fe,ye){var be=le(i,u),_e=le(s,u),we=ye.get(_e);if(we)m(i,u,we);else{var Se=fe?fe(be,_e,u+"",i,s,ye):void 0,xe=void 0===Se;if(xe){var Pe=W(_e),Ie=!Pe&&Y(_e),Te=!Pe&&!Ie&&ie(_e);Se=_e,Pe||Ie||Te?W(be)?Se=be:X(be)?Se=j(be):Ie?(xe=!1,Se=v(_e,!0)):Te?(xe=!1,Se=_(_e,!0)):Se=[]:ae(_e)||$(_e)?(Se=be,$(be)?Se=ce(be):ee(be)&&!Z(be)||(Se=M(_e))):xe=!1}xe&&(ye.set(_e,Se),de(Se,_e,pe,fe,ye),ye.delete(_e)),m(i,u,Se)}}},40371:i=>{i.exports=function baseProperty(i){return function(s){return null==s?void 0:s[i]}}},79152:(i,s,u)=>{var m=u(97786);i.exports=function basePropertyDeep(i){return function(s){return m(s,i)}}},18674:i=>{i.exports=function basePropertyOf(i){return function(s){return null==i?void 0:i[s]}}},10107:i=>{i.exports=function baseReduce(i,s,u,m,v){return v(i,(function(i,v,_){u=m?(m=!1,i):s(u,i,v,_)})),u}},5976:(i,s,u)=>{var m=u(6557),v=u(45357),_=u(30061);i.exports=function baseRest(i,s){return _(v(i,s,m),i+"")}},10611:(i,s,u)=>{var m=u(34865),v=u(71811),_=u(65776),j=u(13218),M=u(40327);i.exports=function baseSet(i,s,u,$){if(!j(i))return i;for(var W=-1,X=(s=v(s,i)).length,Y=X-1,Z=i;null!=Z&&++W{var m=u(6557),v=u(89250),_=v?function(i,s){return v.set(i,s),i}:m;i.exports=_},56560:(i,s,u)=>{var m=u(75703),v=u(38777),_=u(6557),j=v?function(i,s){return v(i,"toString",{configurable:!0,enumerable:!1,value:m(s),writable:!0})}:_;i.exports=j},14259:i=>{i.exports=function baseSlice(i,s,u){var m=-1,v=i.length;s<0&&(s=-s>v?0:v+s),(u=u>v?v:u)<0&&(u+=v),v=s>u?0:u-s>>>0,s>>>=0;for(var _=Array(v);++m{var m=u(89881);i.exports=function baseSome(i,s){var u;return m(i,(function(i,m,v){return!(u=s(i,m,v))})),!!u}},22545:i=>{i.exports=function baseTimes(i,s){for(var u=-1,m=Array(i);++u{var m=u(62705),v=u(29932),_=u(1469),j=u(33448),M=m?m.prototype:void 0,$=M?M.toString:void 0;i.exports=function baseToString(i){if("string"==typeof i)return i;if(_(i))return v(i,baseToString)+"";if(j(i))return $?$.call(i):"";var s=i+"";return"0"==s&&1/i==-Infinity?"-0":s}},27561:(i,s,u)=>{var m=u(67990),v=/^\s+/;i.exports=function baseTrim(i){return i?i.slice(0,m(i)+1).replace(v,""):i}},7518:i=>{i.exports=function baseUnary(i){return function(s){return i(s)}}},57406:(i,s,u)=>{var m=u(71811),v=u(10928),_=u(40292),j=u(40327);i.exports=function baseUnset(i,s){return s=m(s,i),null==(i=_(i,s))||delete i[j(v(s))]}},1757:i=>{i.exports=function baseZipObject(i,s,u){for(var m=-1,v=i.length,_=s.length,j={};++m{i.exports=function cacheHas(i,s){return i.has(s)}},71811:(i,s,u)=>{var m=u(1469),v=u(15403),_=u(55514),j=u(79833);i.exports=function castPath(i,s){return m(i)?i:v(i,s)?[i]:_(j(i))}},40180:(i,s,u)=>{var m=u(14259);i.exports=function castSlice(i,s,u){var v=i.length;return u=void 0===u?v:u,!s&&u>=v?i:m(i,s,u)}},74318:(i,s,u)=>{var m=u(11149);i.exports=function cloneArrayBuffer(i){var s=new i.constructor(i.byteLength);return new m(s).set(new m(i)),s}},64626:(i,s,u)=>{i=u.nmd(i);var m=u(55639),v=s&&!s.nodeType&&s,_=v&&i&&!i.nodeType&&i,j=_&&_.exports===v?m.Buffer:void 0,M=j?j.allocUnsafe:void 0;i.exports=function cloneBuffer(i,s){if(s)return i.slice();var u=i.length,m=M?M(u):new i.constructor(u);return i.copy(m),m}},57157:(i,s,u)=>{var m=u(74318);i.exports=function cloneDataView(i,s){var u=s?m(i.buffer):i.buffer;return new i.constructor(u,i.byteOffset,i.byteLength)}},93147:i=>{var s=/\w*$/;i.exports=function cloneRegExp(i){var u=new i.constructor(i.source,s.exec(i));return u.lastIndex=i.lastIndex,u}},40419:(i,s,u)=>{var m=u(62705),v=m?m.prototype:void 0,_=v?v.valueOf:void 0;i.exports=function cloneSymbol(i){return _?Object(_.call(i)):{}}},77133:(i,s,u)=>{var m=u(74318);i.exports=function cloneTypedArray(i,s){var u=s?m(i.buffer):i.buffer;return new i.constructor(u,i.byteOffset,i.length)}},52157:i=>{var s=Math.max;i.exports=function composeArgs(i,u,m,v){for(var _=-1,j=i.length,M=m.length,$=-1,W=u.length,X=s(j-M,0),Y=Array(W+X),Z=!v;++${var s=Math.max;i.exports=function composeArgsRight(i,u,m,v){for(var _=-1,j=i.length,M=-1,$=m.length,W=-1,X=u.length,Y=s(j-$,0),Z=Array(Y+X),ee=!v;++_{i.exports=function copyArray(i,s){var u=-1,m=i.length;for(s||(s=Array(m));++u{var m=u(34865),v=u(89465);i.exports=function copyObject(i,s,u,_){var j=!u;u||(u={});for(var M=-1,$=s.length;++M<$;){var W=s[M],X=_?_(u[W],i[W],W,u,i):void 0;void 0===X&&(X=i[W]),j?v(u,W,X):m(u,W,X)}return u}},18805:(i,s,u)=>{var m=u(98363),v=u(99551);i.exports=function copySymbols(i,s){return m(i,v(i),s)}},1911:(i,s,u)=>{var m=u(98363),v=u(51442);i.exports=function copySymbolsIn(i,s){return m(i,v(i),s)}},14429:(i,s,u)=>{var m=u(55639)["__core-js_shared__"];i.exports=m},97991:i=>{i.exports=function countHolders(i,s){for(var u=i.length,m=0;u--;)i[u]===s&&++m;return m}},21463:(i,s,u)=>{var m=u(5976),v=u(16612);i.exports=function createAssigner(i){return m((function(s,u){var m=-1,_=u.length,j=_>1?u[_-1]:void 0,M=_>2?u[2]:void 0;for(j=i.length>3&&"function"==typeof j?(_--,j):void 0,M&&v(u[0],u[1],M)&&(j=_<3?void 0:j,_=1),s=Object(s);++m<_;){var $=u[m];$&&i(s,$,m,j)}return s}))}},99291:(i,s,u)=>{var m=u(98612);i.exports=function createBaseEach(i,s){return function(u,v){if(null==u)return u;if(!m(u))return i(u,v);for(var _=u.length,j=s?_:-1,M=Object(u);(s?j--:++j<_)&&!1!==v(M[j],j,M););return u}}},25063:i=>{i.exports=function createBaseFor(i){return function(s,u,m){for(var v=-1,_=Object(s),j=m(s),M=j.length;M--;){var $=j[i?M:++v];if(!1===u(_[$],$,_))break}return s}}},22402:(i,s,u)=>{var m=u(71774),v=u(55639);i.exports=function createBind(i,s,u){var _=1&s,j=m(i);return function wrapper(){return(this&&this!==v&&this instanceof wrapper?j:i).apply(_?u:this,arguments)}}},98805:(i,s,u)=>{var m=u(40180),v=u(62689),_=u(83140),j=u(79833);i.exports=function createCaseFirst(i){return function(s){s=j(s);var u=v(s)?_(s):void 0,M=u?u[0]:s.charAt(0),$=u?m(u,1).join(""):s.slice(1);return M[i]()+$}}},35393:(i,s,u)=>{var m=u(62663),v=u(53816),_=u(58748),j=RegExp("['’]","g");i.exports=function createCompounder(i){return function(s){return m(_(v(s).replace(j,"")),i,"")}}},71774:(i,s,u)=>{var m=u(3118),v=u(13218);i.exports=function createCtor(i){return function(){var s=arguments;switch(s.length){case 0:return new i;case 1:return new i(s[0]);case 2:return new i(s[0],s[1]);case 3:return new i(s[0],s[1],s[2]);case 4:return new i(s[0],s[1],s[2],s[3]);case 5:return new i(s[0],s[1],s[2],s[3],s[4]);case 6:return new i(s[0],s[1],s[2],s[3],s[4],s[5]);case 7:return new i(s[0],s[1],s[2],s[3],s[4],s[5],s[6])}var u=m(i.prototype),_=i.apply(u,s);return v(_)?_:u}}},46347:(i,s,u)=>{var m=u(96874),v=u(71774),_=u(86935),j=u(94487),M=u(20893),$=u(46460),W=u(55639);i.exports=function createCurry(i,s,u){var X=v(i);return function wrapper(){for(var v=arguments.length,Y=Array(v),Z=v,ee=M(wrapper);Z--;)Y[Z]=arguments[Z];var ae=v<3&&Y[0]!==ee&&Y[v-1]!==ee?[]:$(Y,ee);return(v-=ae.length){var m=u(67206),v=u(98612),_=u(3674);i.exports=function createFind(i){return function(s,u,j){var M=Object(s);if(!v(s)){var $=m(u,3);s=_(s),u=function(i){return $(M[i],i,M)}}var W=i(s,u,j);return W>-1?M[$?s[W]:W]:void 0}}},86935:(i,s,u)=>{var m=u(52157),v=u(14054),_=u(97991),j=u(71774),M=u(94487),$=u(20893),W=u(90451),X=u(46460),Y=u(55639);i.exports=function createHybrid(i,s,u,Z,ee,ae,ie,le,ce,pe){var de=128&s,fe=1&s,ye=2&s,be=24&s,_e=512&s,we=ye?void 0:j(i);return function wrapper(){for(var Se=arguments.length,xe=Array(Se),Pe=Se;Pe--;)xe[Pe]=arguments[Pe];if(be)var Ie=$(wrapper),Te=_(xe,Ie);if(Z&&(xe=m(xe,Z,ee,be)),ae&&(xe=v(xe,ae,ie,be)),Se-=Te,be&&Se1&&xe.reverse(),de&&ce{var m=u(96874),v=u(71774),_=u(55639);i.exports=function createPartial(i,s,u,j){var M=1&s,$=v(i);return function wrapper(){for(var s=-1,v=arguments.length,W=-1,X=j.length,Y=Array(X+v),Z=this&&this!==_&&this instanceof wrapper?$:i;++W{var m=u(86528),v=u(258),_=u(69255);i.exports=function createRecurry(i,s,u,j,M,$,W,X,Y,Z){var ee=8&s;s|=ee?32:64,4&(s&=~(ee?64:32))||(s&=-4);var ae=[i,s,M,ee?$:void 0,ee?W:void 0,ee?void 0:$,ee?void 0:W,X,Y,Z],ie=u.apply(void 0,ae);return m(i)&&v(ie,ae),ie.placeholder=j,_(ie,i,s)}},97727:(i,s,u)=>{var m=u(28045),v=u(22402),_=u(46347),j=u(86935),M=u(84375),$=u(66833),W=u(63833),X=u(258),Y=u(69255),Z=u(40554),ee=Math.max;i.exports=function createWrap(i,s,u,ae,ie,le,ce,pe){var de=2&s;if(!de&&"function"!=typeof i)throw new TypeError("Expected a function");var fe=ae?ae.length:0;if(fe||(s&=-97,ae=ie=void 0),ce=void 0===ce?ce:ee(Z(ce),0),pe=void 0===pe?pe:Z(pe),fe-=ie?ie.length:0,64&s){var ye=ae,be=ie;ae=ie=void 0}var _e=de?void 0:$(i),we=[i,s,u,ae,ie,ye,be,le,ce,pe];if(_e&&W(we,_e),i=we[0],s=we[1],u=we[2],ae=we[3],ie=we[4],!(pe=we[9]=void 0===we[9]?de?0:i.length:ee(we[9]-fe,0))&&24&s&&(s&=-25),s&&1!=s)Se=8==s||16==s?_(i,s,pe):32!=s&&33!=s||ie.length?j.apply(void 0,we):M(i,s,u,ae);else var Se=v(i,s,u);return Y((_e?m:X)(Se,we),i,s)}},60696:(i,s,u)=>{var m=u(68630);i.exports=function customOmitClone(i){return m(i)?void 0:i}},69389:(i,s,u)=>{var m=u(18674)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});i.exports=m},38777:(i,s,u)=>{var m=u(10852),v=function(){try{var i=m(Object,"defineProperty");return i({},"",{}),i}catch(i){}}();i.exports=v},67114:(i,s,u)=>{var m=u(88668),v=u(82908),_=u(74757);i.exports=function equalArrays(i,s,u,j,M,$){var W=1&u,X=i.length,Y=s.length;if(X!=Y&&!(W&&Y>X))return!1;var Z=$.get(i),ee=$.get(s);if(Z&&ee)return Z==s&&ee==i;var ae=-1,ie=!0,le=2&u?new m:void 0;for($.set(i,s),$.set(s,i);++ae{var m=u(62705),v=u(11149),_=u(77813),j=u(67114),M=u(68776),$=u(21814),W=m?m.prototype:void 0,X=W?W.valueOf:void 0;i.exports=function equalByTag(i,s,u,m,W,Y,Z){switch(u){case"[object DataView]":if(i.byteLength!=s.byteLength||i.byteOffset!=s.byteOffset)return!1;i=i.buffer,s=s.buffer;case"[object ArrayBuffer]":return!(i.byteLength!=s.byteLength||!Y(new v(i),new v(s)));case"[object Boolean]":case"[object Date]":case"[object Number]":return _(+i,+s);case"[object Error]":return i.name==s.name&&i.message==s.message;case"[object RegExp]":case"[object String]":return i==s+"";case"[object Map]":var ee=M;case"[object Set]":var ae=1&m;if(ee||(ee=$),i.size!=s.size&&!ae)return!1;var ie=Z.get(i);if(ie)return ie==s;m|=2,Z.set(i,s);var le=j(ee(i),ee(s),m,W,Y,Z);return Z.delete(i),le;case"[object Symbol]":if(X)return X.call(i)==X.call(s)}return!1}},16096:(i,s,u)=>{var m=u(58234),v=Object.prototype.hasOwnProperty;i.exports=function equalObjects(i,s,u,_,j,M){var $=1&u,W=m(i),X=W.length;if(X!=m(s).length&&!$)return!1;for(var Y=X;Y--;){var Z=W[Y];if(!($?Z in s:v.call(s,Z)))return!1}var ee=M.get(i),ae=M.get(s);if(ee&&ae)return ee==s&&ae==i;var ie=!0;M.set(i,s),M.set(s,i);for(var le=$;++Y{var m=u(85564),v=u(45357),_=u(30061);i.exports=function flatRest(i){return _(v(i,void 0,m),i+"")}},31957:(i,s,u)=>{var m="object"==typeof u.g&&u.g&&u.g.Object===Object&&u.g;i.exports=m},58234:(i,s,u)=>{var m=u(68866),v=u(99551),_=u(3674);i.exports=function getAllKeys(i){return m(i,_,v)}},46904:(i,s,u)=>{var m=u(68866),v=u(51442),_=u(81704);i.exports=function getAllKeysIn(i){return m(i,_,v)}},66833:(i,s,u)=>{var m=u(89250),v=u(50308),_=m?function(i){return m.get(i)}:v;i.exports=_},97658:(i,s,u)=>{var m=u(52060),v=Object.prototype.hasOwnProperty;i.exports=function getFuncName(i){for(var s=i.name+"",u=m[s],_=v.call(m,s)?u.length:0;_--;){var j=u[_],M=j.func;if(null==M||M==i)return j.name}return s}},20893:i=>{i.exports=function getHolder(i){return i.placeholder}},45050:(i,s,u)=>{var m=u(37019);i.exports=function getMapData(i,s){var u=i.__data__;return m(s)?u["string"==typeof s?"string":"hash"]:u.map}},1499:(i,s,u)=>{var m=u(89162),v=u(3674);i.exports=function getMatchData(i){for(var s=v(i),u=s.length;u--;){var _=s[u],j=i[_];s[u]=[_,j,m(j)]}return s}},10852:(i,s,u)=>{var m=u(28458),v=u(47801);i.exports=function getNative(i,s){var u=v(i,s);return m(u)?u:void 0}},85924:(i,s,u)=>{var m=u(5569)(Object.getPrototypeOf,Object);i.exports=m},89607:(i,s,u)=>{var m=u(62705),v=Object.prototype,_=v.hasOwnProperty,j=v.toString,M=m?m.toStringTag:void 0;i.exports=function getRawTag(i){var s=_.call(i,M),u=i[M];try{i[M]=void 0;var m=!0}catch(i){}var v=j.call(i);return m&&(s?i[M]=u:delete i[M]),v}},99551:(i,s,u)=>{var m=u(34963),v=u(70479),_=Object.prototype.propertyIsEnumerable,j=Object.getOwnPropertySymbols,M=j?function(i){return null==i?[]:(i=Object(i),m(j(i),(function(s){return _.call(i,s)})))}:v;i.exports=M},51442:(i,s,u)=>{var m=u(62488),v=u(85924),_=u(99551),j=u(70479),M=Object.getOwnPropertySymbols?function(i){for(var s=[];i;)m(s,_(i)),i=v(i);return s}:j;i.exports=M},64160:(i,s,u)=>{var m=u(18552),v=u(57071),_=u(53818),j=u(58525),M=u(70577),$=u(44239),W=u(80346),X="[object Map]",Y="[object Promise]",Z="[object Set]",ee="[object WeakMap]",ae="[object DataView]",ie=W(m),le=W(v),ce=W(_),pe=W(j),de=W(M),fe=$;(m&&fe(new m(new ArrayBuffer(1)))!=ae||v&&fe(new v)!=X||_&&fe(_.resolve())!=Y||j&&fe(new j)!=Z||M&&fe(new M)!=ee)&&(fe=function(i){var s=$(i),u="[object Object]"==s?i.constructor:void 0,m=u?W(u):"";if(m)switch(m){case ie:return ae;case le:return X;case ce:return Y;case pe:return Z;case de:return ee}return s}),i.exports=fe},47801:i=>{i.exports=function getValue(i,s){return null==i?void 0:i[s]}},58775:i=>{var s=/\{\n\/\* \[wrapped with (.+)\] \*/,u=/,? & /;i.exports=function getWrapDetails(i){var m=i.match(s);return m?m[1].split(u):[]}},222:(i,s,u)=>{var m=u(71811),v=u(35694),_=u(1469),j=u(65776),M=u(41780),$=u(40327);i.exports=function hasPath(i,s,u){for(var W=-1,X=(s=m(s,i)).length,Y=!1;++W{var s=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");i.exports=function hasUnicode(i){return s.test(i)}},93157:i=>{var s=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;i.exports=function hasUnicodeWord(i){return s.test(i)}},51789:(i,s,u)=>{var m=u(94536);i.exports=function hashClear(){this.__data__=m?m(null):{},this.size=0}},80401:i=>{i.exports=function hashDelete(i){var s=this.has(i)&&delete this.__data__[i];return this.size-=s?1:0,s}},57667:(i,s,u)=>{var m=u(94536),v=Object.prototype.hasOwnProperty;i.exports=function hashGet(i){var s=this.__data__;if(m){var u=s[i];return"__lodash_hash_undefined__"===u?void 0:u}return v.call(s,i)?s[i]:void 0}},21327:(i,s,u)=>{var m=u(94536),v=Object.prototype.hasOwnProperty;i.exports=function hashHas(i){var s=this.__data__;return m?void 0!==s[i]:v.call(s,i)}},81866:(i,s,u)=>{var m=u(94536);i.exports=function hashSet(i,s){var u=this.__data__;return this.size+=this.has(i)?0:1,u[i]=m&&void 0===s?"__lodash_hash_undefined__":s,this}},43824:i=>{var s=Object.prototype.hasOwnProperty;i.exports=function initCloneArray(i){var u=i.length,m=new i.constructor(u);return u&&"string"==typeof i[0]&&s.call(i,"index")&&(m.index=i.index,m.input=i.input),m}},29148:(i,s,u)=>{var m=u(74318),v=u(57157),_=u(93147),j=u(40419),M=u(77133);i.exports=function initCloneByTag(i,s,u){var $=i.constructor;switch(s){case"[object ArrayBuffer]":return m(i);case"[object Boolean]":case"[object Date]":return new $(+i);case"[object DataView]":return v(i,u);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return M(i,u);case"[object Map]":case"[object Set]":return new $;case"[object Number]":case"[object String]":return new $(i);case"[object RegExp]":return _(i);case"[object Symbol]":return j(i)}}},38517:(i,s,u)=>{var m=u(3118),v=u(85924),_=u(25726);i.exports=function initCloneObject(i){return"function"!=typeof i.constructor||_(i)?{}:m(v(i))}},83112:i=>{var s=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;i.exports=function insertWrapDetails(i,u){var m=u.length;if(!m)return i;var v=m-1;return u[v]=(m>1?"& ":"")+u[v],u=u.join(m>2?", ":" "),i.replace(s,"{\n/* [wrapped with "+u+"] */\n")}},37285:(i,s,u)=>{var m=u(62705),v=u(35694),_=u(1469),j=m?m.isConcatSpreadable:void 0;i.exports=function isFlattenable(i){return _(i)||v(i)||!!(j&&i&&i[j])}},65776:i=>{var s=/^(?:0|[1-9]\d*)$/;i.exports=function isIndex(i,u){var m=typeof i;return!!(u=null==u?9007199254740991:u)&&("number"==m||"symbol"!=m&&s.test(i))&&i>-1&&i%1==0&&i{var m=u(77813),v=u(98612),_=u(65776),j=u(13218);i.exports=function isIterateeCall(i,s,u){if(!j(u))return!1;var M=typeof s;return!!("number"==M?v(u)&&_(s,u.length):"string"==M&&s in u)&&m(u[s],i)}},15403:(i,s,u)=>{var m=u(1469),v=u(33448),_=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,j=/^\w*$/;i.exports=function isKey(i,s){if(m(i))return!1;var u=typeof i;return!("number"!=u&&"symbol"!=u&&"boolean"!=u&&null!=i&&!v(i))||(j.test(i)||!_.test(i)||null!=s&&i in Object(s))}},37019:i=>{i.exports=function isKeyable(i){var s=typeof i;return"string"==s||"number"==s||"symbol"==s||"boolean"==s?"__proto__"!==i:null===i}},86528:(i,s,u)=>{var m=u(96425),v=u(66833),_=u(97658),j=u(8111);i.exports=function isLaziable(i){var s=_(i),u=j[s];if("function"!=typeof u||!(s in m.prototype))return!1;if(i===u)return!0;var M=v(u);return!!M&&i===M[0]}},15346:(i,s,u)=>{var m,v=u(14429),_=(m=/[^.]+$/.exec(v&&v.keys&&v.keys.IE_PROTO||""))?"Symbol(src)_1."+m:"";i.exports=function isMasked(i){return!!_&&_ in i}},25726:i=>{var s=Object.prototype;i.exports=function isPrototype(i){var u=i&&i.constructor;return i===("function"==typeof u&&u.prototype||s)}},89162:(i,s,u)=>{var m=u(13218);i.exports=function isStrictComparable(i){return i==i&&!m(i)}},27040:i=>{i.exports=function listCacheClear(){this.__data__=[],this.size=0}},14125:(i,s,u)=>{var m=u(18470),v=Array.prototype.splice;i.exports=function listCacheDelete(i){var s=this.__data__,u=m(s,i);return!(u<0)&&(u==s.length-1?s.pop():v.call(s,u,1),--this.size,!0)}},82117:(i,s,u)=>{var m=u(18470);i.exports=function listCacheGet(i){var s=this.__data__,u=m(s,i);return u<0?void 0:s[u][1]}},67518:(i,s,u)=>{var m=u(18470);i.exports=function listCacheHas(i){return m(this.__data__,i)>-1}},54705:(i,s,u)=>{var m=u(18470);i.exports=function listCacheSet(i,s){var u=this.__data__,v=m(u,i);return v<0?(++this.size,u.push([i,s])):u[v][1]=s,this}},24785:(i,s,u)=>{var m=u(1989),v=u(38407),_=u(57071);i.exports=function mapCacheClear(){this.size=0,this.__data__={hash:new m,map:new(_||v),string:new m}}},11285:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheDelete(i){var s=m(this,i).delete(i);return this.size-=s?1:0,s}},96e3:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheGet(i){return m(this,i).get(i)}},49916:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheHas(i){return m(this,i).has(i)}},95265:(i,s,u)=>{var m=u(45050);i.exports=function mapCacheSet(i,s){var u=m(this,i),v=u.size;return u.set(i,s),this.size+=u.size==v?0:1,this}},68776:i=>{i.exports=function mapToArray(i){var s=-1,u=Array(i.size);return i.forEach((function(i,m){u[++s]=[m,i]})),u}},42634:i=>{i.exports=function matchesStrictComparable(i,s){return function(u){return null!=u&&(u[i]===s&&(void 0!==s||i in Object(u)))}}},24523:(i,s,u)=>{var m=u(88306);i.exports=function memoizeCapped(i){var s=m(i,(function(i){return 500===u.size&&u.clear(),i})),u=s.cache;return s}},63833:(i,s,u)=>{var m=u(52157),v=u(14054),_=u(46460),j="__lodash_placeholder__",M=128,$=Math.min;i.exports=function mergeData(i,s){var u=i[1],W=s[1],X=u|W,Y=X<131,Z=W==M&&8==u||W==M&&256==u&&i[7].length<=s[8]||384==W&&s[7].length<=s[8]&&8==u;if(!Y&&!Z)return i;1&W&&(i[2]=s[2],X|=1&u?0:4);var ee=s[3];if(ee){var ae=i[3];i[3]=ae?m(ae,ee,s[4]):ee,i[4]=ae?_(i[3],j):s[4]}return(ee=s[5])&&(ae=i[5],i[5]=ae?v(ae,ee,s[6]):ee,i[6]=ae?_(i[5],j):s[6]),(ee=s[7])&&(i[7]=ee),W&M&&(i[8]=null==i[8]?s[8]:$(i[8],s[8])),null==i[9]&&(i[9]=s[9]),i[0]=s[0],i[1]=X,i}},89250:(i,s,u)=>{var m=u(70577),v=m&&new m;i.exports=v},94536:(i,s,u)=>{var m=u(10852)(Object,"create");i.exports=m},86916:(i,s,u)=>{var m=u(5569)(Object.keys,Object);i.exports=m},33498:i=>{i.exports=function nativeKeysIn(i){var s=[];if(null!=i)for(var u in Object(i))s.push(u);return s}},31167:(i,s,u)=>{i=u.nmd(i);var m=u(31957),v=s&&!s.nodeType&&s,_=v&&i&&!i.nodeType&&i,j=_&&_.exports===v&&m.process,M=function(){try{var i=_&&_.require&&_.require("util").types;return i||j&&j.binding&&j.binding("util")}catch(i){}}();i.exports=M},2333:i=>{var s=Object.prototype.toString;i.exports=function objectToString(i){return s.call(i)}},5569:i=>{i.exports=function overArg(i,s){return function(u){return i(s(u))}}},45357:(i,s,u)=>{var m=u(96874),v=Math.max;i.exports=function overRest(i,s,u){return s=v(void 0===s?i.length-1:s,0),function(){for(var _=arguments,j=-1,M=v(_.length-s,0),$=Array(M);++j{var m=u(97786),v=u(14259);i.exports=function parent(i,s){return s.length<2?i:m(i,v(s,0,-1))}},52060:i=>{i.exports={}},90451:(i,s,u)=>{var m=u(278),v=u(65776),_=Math.min;i.exports=function reorder(i,s){for(var u=i.length,j=_(s.length,u),M=m(i);j--;){var $=s[j];i[j]=v($,u)?M[$]:void 0}return i}},46460:i=>{var s="__lodash_placeholder__";i.exports=function replaceHolders(i,u){for(var m=-1,v=i.length,_=0,j=[];++m{var m=u(31957),v="object"==typeof self&&self&&self.Object===Object&&self,_=m||v||Function("return this")();i.exports=_},36390:i=>{i.exports=function safeGet(i,s){if(("constructor"!==s||"function"!=typeof i[s])&&"__proto__"!=s)return i[s]}},90619:i=>{i.exports=function setCacheAdd(i){return this.__data__.set(i,"__lodash_hash_undefined__"),this}},72385:i=>{i.exports=function setCacheHas(i){return this.__data__.has(i)}},258:(i,s,u)=>{var m=u(28045),v=u(21275)(m);i.exports=v},21814:i=>{i.exports=function setToArray(i){var s=-1,u=Array(i.size);return i.forEach((function(i){u[++s]=i})),u}},30061:(i,s,u)=>{var m=u(56560),v=u(21275)(m);i.exports=v},69255:(i,s,u)=>{var m=u(58775),v=u(83112),_=u(30061),j=u(87241);i.exports=function setWrapToString(i,s,u){var M=s+"";return _(i,v(M,j(m(M),u)))}},21275:i=>{var s=Date.now;i.exports=function shortOut(i){var u=0,m=0;return function(){var v=s(),_=16-(v-m);if(m=v,_>0){if(++u>=800)return arguments[0]}else u=0;return i.apply(void 0,arguments)}}},37465:(i,s,u)=>{var m=u(38407);i.exports=function stackClear(){this.__data__=new m,this.size=0}},63779:i=>{i.exports=function stackDelete(i){var s=this.__data__,u=s.delete(i);return this.size=s.size,u}},67599:i=>{i.exports=function stackGet(i){return this.__data__.get(i)}},44758:i=>{i.exports=function stackHas(i){return this.__data__.has(i)}},34309:(i,s,u)=>{var m=u(38407),v=u(57071),_=u(83369);i.exports=function stackSet(i,s){var u=this.__data__;if(u instanceof m){var j=u.__data__;if(!v||j.length<199)return j.push([i,s]),this.size=++u.size,this;u=this.__data__=new _(j)}return u.set(i,s),this.size=u.size,this}},42351:i=>{i.exports=function strictIndexOf(i,s,u){for(var m=u-1,v=i.length;++m{var m=u(44286),v=u(62689),_=u(676);i.exports=function stringToArray(i){return v(i)?_(i):m(i)}},55514:(i,s,u)=>{var m=u(24523),v=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,_=/\\(\\)?/g,j=m((function(i){var s=[];return 46===i.charCodeAt(0)&&s.push(""),i.replace(v,(function(i,u,m,v){s.push(m?v.replace(_,"$1"):u||i)})),s}));i.exports=j},40327:(i,s,u)=>{var m=u(33448);i.exports=function toKey(i){if("string"==typeof i||m(i))return i;var s=i+"";return"0"==s&&1/i==-Infinity?"-0":s}},80346:i=>{var s=Function.prototype.toString;i.exports=function toSource(i){if(null!=i){try{return s.call(i)}catch(i){}try{return i+""}catch(i){}}return""}},67990:i=>{var s=/\s/;i.exports=function trimmedEndIndex(i){for(var u=i.length;u--&&s.test(i.charAt(u)););return u}},676:i=>{var s="\\ud800-\\udfff",u="["+s+"]",m="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",v="\\ud83c[\\udffb-\\udfff]",_="[^"+s+"]",j="(?:\\ud83c[\\udde6-\\uddff]){2}",M="[\\ud800-\\udbff][\\udc00-\\udfff]",$="(?:"+m+"|"+v+")"+"?",W="[\\ufe0e\\ufe0f]?",X=W+$+("(?:\\u200d(?:"+[_,j,M].join("|")+")"+W+$+")*"),Y="(?:"+[_+m+"?",m,j,M,u].join("|")+")",Z=RegExp(v+"(?="+v+")|"+Y+X,"g");i.exports=function unicodeToArray(i){return i.match(Z)||[]}},2757:i=>{var s="\\ud800-\\udfff",u="\\u2700-\\u27bf",m="a-z\\xdf-\\xf6\\xf8-\\xff",v="A-Z\\xc0-\\xd6\\xd8-\\xde",_="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",j="["+_+"]",M="\\d+",$="["+u+"]",W="["+m+"]",X="[^"+s+_+M+u+m+v+"]",Y="(?:\\ud83c[\\udde6-\\uddff]){2}",Z="[\\ud800-\\udbff][\\udc00-\\udfff]",ee="["+v+"]",ae="(?:"+W+"|"+X+")",ie="(?:"+ee+"|"+X+")",le="(?:['’](?:d|ll|m|re|s|t|ve))?",ce="(?:['’](?:D|LL|M|RE|S|T|VE))?",pe="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",de="[\\ufe0e\\ufe0f]?",fe=de+pe+("(?:\\u200d(?:"+["[^"+s+"]",Y,Z].join("|")+")"+de+pe+")*"),ye="(?:"+[$,Y,Z].join("|")+")"+fe,be=RegExp([ee+"?"+W+"+"+le+"(?="+[j,ee,"$"].join("|")+")",ie+"+"+ce+"(?="+[j,ee+ae,"$"].join("|")+")",ee+"?"+ae+"+"+le,ee+"+"+ce,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",M,ye].join("|"),"g");i.exports=function unicodeWords(i){return i.match(be)||[]}},87241:(i,s,u)=>{var m=u(77412),v=u(47443),_=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];i.exports=function updateWrapDetails(i,s){return m(_,(function(u){var m="_."+u[0];s&u[1]&&!v(i,m)&&i.push(m)})),i.sort()}},21913:(i,s,u)=>{var m=u(96425),v=u(7548),_=u(278);i.exports=function wrapperClone(i){if(i instanceof m)return i.clone();var s=new v(i.__wrapped__,i.__chain__);return s.__actions__=_(i.__actions__),s.__index__=i.__index__,s.__values__=i.__values__,s}},39514:(i,s,u)=>{var m=u(97727);i.exports=function ary(i,s,u){return s=u?void 0:s,s=i&&null==s?i.length:s,m(i,128,void 0,void 0,void 0,void 0,s)}},68929:(i,s,u)=>{var m=u(48403),v=u(35393)((function(i,s,u){return s=s.toLowerCase(),i+(u?m(s):s)}));i.exports=v},48403:(i,s,u)=>{var m=u(79833),v=u(11700);i.exports=function capitalize(i){return v(m(i).toLowerCase())}},66678:(i,s,u)=>{var m=u(85990);i.exports=function clone(i){return m(i,4)}},75703:i=>{i.exports=function constant(i){return function(){return i}}},40087:(i,s,u)=>{var m=u(97727);function curry(i,s,u){var v=m(i,8,void 0,void 0,void 0,void 0,void 0,s=u?void 0:s);return v.placeholder=curry.placeholder,v}curry.placeholder={},i.exports=curry},23279:(i,s,u)=>{var m=u(13218),v=u(7771),_=u(14841),j=Math.max,M=Math.min;i.exports=function debounce(i,s,u){var $,W,X,Y,Z,ee,ae=0,ie=!1,le=!1,ce=!0;if("function"!=typeof i)throw new TypeError("Expected a function");function invokeFunc(s){var u=$,m=W;return $=W=void 0,ae=s,Y=i.apply(m,u)}function shouldInvoke(i){var u=i-ee;return void 0===ee||u>=s||u<0||le&&i-ae>=X}function timerExpired(){var i=v();if(shouldInvoke(i))return trailingEdge(i);Z=setTimeout(timerExpired,function remainingWait(i){var u=s-(i-ee);return le?M(u,X-(i-ae)):u}(i))}function trailingEdge(i){return Z=void 0,ce&&$?invokeFunc(i):($=W=void 0,Y)}function debounced(){var i=v(),u=shouldInvoke(i);if($=arguments,W=this,ee=i,u){if(void 0===Z)return function leadingEdge(i){return ae=i,Z=setTimeout(timerExpired,s),ie?invokeFunc(i):Y}(ee);if(le)return clearTimeout(Z),Z=setTimeout(timerExpired,s),invokeFunc(ee)}return void 0===Z&&(Z=setTimeout(timerExpired,s)),Y}return s=_(s)||0,m(u)&&(ie=!!u.leading,X=(le="maxWait"in u)?j(_(u.maxWait)||0,s):X,ce="trailing"in u?!!u.trailing:ce),debounced.cancel=function cancel(){void 0!==Z&&clearTimeout(Z),ae=0,$=ee=W=Z=void 0},debounced.flush=function flush(){return void 0===Z?Y:trailingEdge(v())},debounced}},53816:(i,s,u)=>{var m=u(69389),v=u(79833),_=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,j=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");i.exports=function deburr(i){return(i=v(i))&&i.replace(_,m).replace(j,"")}},77813:i=>{i.exports=function eq(i,s){return i===s||i!=i&&s!=s}},13311:(i,s,u)=>{var m=u(67740)(u(30998));i.exports=m},30998:(i,s,u)=>{var m=u(41848),v=u(67206),_=u(40554),j=Math.max;i.exports=function findIndex(i,s,u){var M=null==i?0:i.length;if(!M)return-1;var $=null==u?0:_(u);return $<0&&($=j(M+$,0)),m(i,v(s,3),$)}},85564:(i,s,u)=>{var m=u(21078);i.exports=function flatten(i){return(null==i?0:i.length)?m(i,1):[]}},84599:(i,s,u)=>{var m=u(68836),v=u(69306),_=Array.prototype.push;function baseAry(i,s){return 2==s?function(s,u){return i(s,u)}:function(s){return i(s)}}function cloneArray(i){for(var s=i?i.length:0,u=Array(s);s--;)u[s]=i[s];return u}function wrapImmutable(i,s){return function(){var u=arguments.length;if(u){for(var m=Array(u);u--;)m[u]=arguments[u];var v=m[0]=s.apply(void 0,m);return i.apply(void 0,m),v}}}i.exports=function baseConvert(i,s,u,j){var M="function"==typeof s,$=s===Object(s);if($&&(j=u,u=s,s=void 0),null==u)throw new TypeError;j||(j={});var W={cap:!("cap"in j)||j.cap,curry:!("curry"in j)||j.curry,fixed:!("fixed"in j)||j.fixed,immutable:!("immutable"in j)||j.immutable,rearg:!("rearg"in j)||j.rearg},X=M?u:v,Y="curry"in j&&j.curry,Z="fixed"in j&&j.fixed,ee="rearg"in j&&j.rearg,ae=M?u.runInContext():void 0,ie=M?u:{ary:i.ary,assign:i.assign,clone:i.clone,curry:i.curry,forEach:i.forEach,isArray:i.isArray,isError:i.isError,isFunction:i.isFunction,isWeakMap:i.isWeakMap,iteratee:i.iteratee,keys:i.keys,rearg:i.rearg,toInteger:i.toInteger,toPath:i.toPath},le=ie.ary,ce=ie.assign,pe=ie.clone,de=ie.curry,fe=ie.forEach,ye=ie.isArray,be=ie.isError,_e=ie.isFunction,we=ie.isWeakMap,Se=ie.keys,xe=ie.rearg,Pe=ie.toInteger,Ie=ie.toPath,Te=Se(m.aryMethod),Re={castArray:function(i){return function(){var s=arguments[0];return ye(s)?i(cloneArray(s)):i.apply(void 0,arguments)}},iteratee:function(i){return function(){var s=arguments[1],u=i(arguments[0],s),m=u.length;return W.cap&&"number"==typeof s?(s=s>2?s-2:1,m&&m<=s?u:baseAry(u,s)):u}},mixin:function(i){return function(s){var u=this;if(!_e(u))return i(u,Object(s));var m=[];return fe(Se(s),(function(i){_e(s[i])&&m.push([i,u.prototype[i]])})),i(u,Object(s)),fe(m,(function(i){var s=i[1];_e(s)?u.prototype[i[0]]=s:delete u.prototype[i[0]]})),u}},nthArg:function(i){return function(s){var u=s<0?1:Pe(s)+1;return de(i(s),u)}},rearg:function(i){return function(s,u){var m=u?u.length:0;return de(i(s,u),m)}},runInContext:function(s){return function(u){return baseConvert(i,s(u),j)}}};function castCap(i,s){if(W.cap){var u=m.iterateeRearg[i];if(u)return function iterateeRearg(i,s){return overArg(i,(function(i){var u=s.length;return function baseArity(i,s){return 2==s?function(s,u){return i.apply(void 0,arguments)}:function(s){return i.apply(void 0,arguments)}}(xe(baseAry(i,u),s),u)}))}(s,u);var v=!M&&m.iterateeAry[i];if(v)return function iterateeAry(i,s){return overArg(i,(function(i){return"function"==typeof i?baseAry(i,s):i}))}(s,v)}return s}function castFixed(i,s,u){if(W.fixed&&(Z||!m.skipFixed[i])){var v=m.methodSpread[i],j=v&&v.start;return void 0===j?le(s,u):function flatSpread(i,s){return function(){for(var u=arguments.length,m=u-1,v=Array(u);u--;)v[u]=arguments[u];var j=v[s],M=v.slice(0,s);return j&&_.apply(M,j),s!=m&&_.apply(M,v.slice(s+1)),i.apply(this,M)}}(s,j)}return s}function castRearg(i,s,u){return W.rearg&&u>1&&(ee||!m.skipRearg[i])?xe(s,m.methodRearg[i]||m.aryRearg[u]):s}function cloneByPath(i,s){for(var u=-1,m=(s=Ie(s)).length,v=m-1,_=pe(Object(i)),j=_;null!=j&&++u1?de(s,u):s}(0,v=castCap(_,v),i),!1}})),!v})),v||(v=j),v==s&&(v=Y?de(v,1):function(){return s.apply(this,arguments)}),v.convert=createConverter(_,s),v.placeholder=s.placeholder=u,v}if(!$)return wrap(s,u,X);var qe=u,ze=[];return fe(Te,(function(i){fe(m.aryMethod[i],(function(i){var s=qe[m.remap[i]||i];s&&ze.push([i,wrap(i,s,qe)])}))})),fe(Se(qe),(function(i){var s=qe[i];if("function"==typeof s){for(var u=ze.length;u--;)if(ze[u][0]==i)return;s.convert=createConverter(i,s),ze.push([i,s])}})),fe(ze,(function(i){qe[i[0]]=i[1]})),qe.convert=function convertLib(i){return qe.runInContext.convert(i)(void 0)},qe.placeholder=qe,fe(Se(qe),(function(i){fe(m.realToAlias[i]||[],(function(s){qe[s]=qe[i]}))})),qe}},68836:(i,s)=>{s.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},s.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},s.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},s.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},s.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},s.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},s.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},s.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},s.realToAlias=function(){var i=Object.prototype.hasOwnProperty,u=s.aliasToReal,m={};for(var v in u){var _=u[v];i.call(m,_)?m[_].push(v):m[_]=[v]}return m}(),s.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},s.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},s.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},4269:(i,s,u)=>{i.exports={ary:u(39514),assign:u(44037),clone:u(66678),curry:u(40087),forEach:u(77412),isArray:u(1469),isError:u(64647),isFunction:u(23560),isWeakMap:u(81018),iteratee:u(72594),keys:u(280),rearg:u(4963),toInteger:u(40554),toPath:u(30084)}},72700:(i,s,u)=>{i.exports=u(28252)},92822:(i,s,u)=>{var m=u(84599),v=u(4269);i.exports=function convert(i,s,u){return m(v,i,s,u)}},69306:i=>{i.exports={}},28252:(i,s,u)=>{var m=u(92822)("set",u(36968));m.placeholder=u(69306),i.exports=m},27361:(i,s,u)=>{var m=u(97786);i.exports=function get(i,s,u){var v=null==i?void 0:m(i,s);return void 0===v?u:v}},79095:(i,s,u)=>{var m=u(13),v=u(222);i.exports=function hasIn(i,s){return null!=i&&v(i,s,m)}},6557:i=>{i.exports=function identity(i){return i}},35694:(i,s,u)=>{var m=u(9454),v=u(37005),_=Object.prototype,j=_.hasOwnProperty,M=_.propertyIsEnumerable,$=m(function(){return arguments}())?m:function(i){return v(i)&&j.call(i,"callee")&&!M.call(i,"callee")};i.exports=$},1469:i=>{var s=Array.isArray;i.exports=s},98612:(i,s,u)=>{var m=u(23560),v=u(41780);i.exports=function isArrayLike(i){return null!=i&&v(i.length)&&!m(i)}},29246:(i,s,u)=>{var m=u(98612),v=u(37005);i.exports=function isArrayLikeObject(i){return v(i)&&m(i)}},51584:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function isBoolean(i){return!0===i||!1===i||v(i)&&"[object Boolean]"==m(i)}},44144:(i,s,u)=>{i=u.nmd(i);var m=u(55639),v=u(95062),_=s&&!s.nodeType&&s,j=_&&i&&!i.nodeType&&i,M=j&&j.exports===_?m.Buffer:void 0,$=(M?M.isBuffer:void 0)||v;i.exports=$},41609:(i,s,u)=>{var m=u(280),v=u(64160),_=u(35694),j=u(1469),M=u(98612),$=u(44144),W=u(25726),X=u(36719),Y=Object.prototype.hasOwnProperty;i.exports=function isEmpty(i){if(null==i)return!0;if(M(i)&&(j(i)||"string"==typeof i||"function"==typeof i.splice||$(i)||X(i)||_(i)))return!i.length;var s=v(i);if("[object Map]"==s||"[object Set]"==s)return!i.size;if(W(i))return!m(i).length;for(var u in i)if(Y.call(i,u))return!1;return!0}},18446:(i,s,u)=>{var m=u(90939);i.exports=function isEqual(i,s){return m(i,s)}},64647:(i,s,u)=>{var m=u(44239),v=u(37005),_=u(68630);i.exports=function isError(i){if(!v(i))return!1;var s=m(i);return"[object Error]"==s||"[object DOMException]"==s||"string"==typeof i.message&&"string"==typeof i.name&&!_(i)}},23560:(i,s,u)=>{var m=u(44239),v=u(13218);i.exports=function isFunction(i){if(!v(i))return!1;var s=m(i);return"[object Function]"==s||"[object GeneratorFunction]"==s||"[object AsyncFunction]"==s||"[object Proxy]"==s}},41780:i=>{i.exports=function isLength(i){return"number"==typeof i&&i>-1&&i%1==0&&i<=9007199254740991}},56688:(i,s,u)=>{var m=u(25588),v=u(7518),_=u(31167),j=_&&_.isMap,M=j?v(j):m;i.exports=M},45220:i=>{i.exports=function isNull(i){return null===i}},81763:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function isNumber(i){return"number"==typeof i||v(i)&&"[object Number]"==m(i)}},13218:i=>{i.exports=function isObject(i){var s=typeof i;return null!=i&&("object"==s||"function"==s)}},37005:i=>{i.exports=function isObjectLike(i){return null!=i&&"object"==typeof i}},68630:(i,s,u)=>{var m=u(44239),v=u(85924),_=u(37005),j=Function.prototype,M=Object.prototype,$=j.toString,W=M.hasOwnProperty,X=$.call(Object);i.exports=function isPlainObject(i){if(!_(i)||"[object Object]"!=m(i))return!1;var s=v(i);if(null===s)return!0;var u=W.call(s,"constructor")&&s.constructor;return"function"==typeof u&&u instanceof u&&$.call(u)==X}},72928:(i,s,u)=>{var m=u(29221),v=u(7518),_=u(31167),j=_&&_.isSet,M=j?v(j):m;i.exports=M},47037:(i,s,u)=>{var m=u(44239),v=u(1469),_=u(37005);i.exports=function isString(i){return"string"==typeof i||!v(i)&&_(i)&&"[object String]"==m(i)}},33448:(i,s,u)=>{var m=u(44239),v=u(37005);i.exports=function isSymbol(i){return"symbol"==typeof i||v(i)&&"[object Symbol]"==m(i)}},36719:(i,s,u)=>{var m=u(38749),v=u(7518),_=u(31167),j=_&&_.isTypedArray,M=j?v(j):m;i.exports=M},81018:(i,s,u)=>{var m=u(64160),v=u(37005);i.exports=function isWeakMap(i){return v(i)&&"[object WeakMap]"==m(i)}},72594:(i,s,u)=>{var m=u(85990),v=u(67206);i.exports=function iteratee(i){return v("function"==typeof i?i:m(i,1))}},3674:(i,s,u)=>{var m=u(14636),v=u(280),_=u(98612);i.exports=function keys(i){return _(i)?m(i):v(i)}},81704:(i,s,u)=>{var m=u(14636),v=u(10313),_=u(98612);i.exports=function keysIn(i){return _(i)?m(i,!0):v(i)}},10928:i=>{i.exports=function last(i){var s=null==i?0:i.length;return s?i[s-1]:void 0}},88306:(i,s,u)=>{var m=u(83369);function memoize(i,s){if("function"!=typeof i||null!=s&&"function"!=typeof s)throw new TypeError("Expected a function");var memoized=function(){var u=arguments,m=s?s.apply(this,u):u[0],v=memoized.cache;if(v.has(m))return v.get(m);var _=i.apply(this,u);return memoized.cache=v.set(m,_)||v,_};return memoized.cache=new(memoize.Cache||m),memoized}memoize.Cache=m,i.exports=memoize},82492:(i,s,u)=>{var m=u(42980),v=u(21463)((function(i,s,u){m(i,s,u)}));i.exports=v},94885:i=>{i.exports=function negate(i){if("function"!=typeof i)throw new TypeError("Expected a function");return function(){var s=arguments;switch(s.length){case 0:return!i.call(this);case 1:return!i.call(this,s[0]);case 2:return!i.call(this,s[0],s[1]);case 3:return!i.call(this,s[0],s[1],s[2])}return!i.apply(this,s)}}},50308:i=>{i.exports=function noop(){}},7771:(i,s,u)=>{var m=u(55639);i.exports=function(){return m.Date.now()}},57557:(i,s,u)=>{var m=u(29932),v=u(85990),_=u(57406),j=u(71811),M=u(98363),$=u(60696),W=u(99021),X=u(46904),Y=W((function(i,s){var u={};if(null==i)return u;var W=!1;s=m(s,(function(s){return s=j(s,i),W||(W=s.length>1),s})),M(i,X(i),u),W&&(u=v(u,7,$));for(var Y=s.length;Y--;)_(u,s[Y]);return u}));i.exports=Y},39601:(i,s,u)=>{var m=u(40371),v=u(79152),_=u(15403),j=u(40327);i.exports=function property(i){return _(i)?m(j(i)):v(i)}},4963:(i,s,u)=>{var m=u(97727),v=u(99021),_=v((function(i,s){return m(i,256,void 0,void 0,void 0,s)}));i.exports=_},54061:(i,s,u)=>{var m=u(62663),v=u(89881),_=u(67206),j=u(10107),M=u(1469);i.exports=function reduce(i,s,u){var $=M(i)?m:j,W=arguments.length<3;return $(i,_(s,4),u,W,v)}},36968:(i,s,u)=>{var m=u(10611);i.exports=function set(i,s,u){return null==i?i:m(i,s,u)}},59704:(i,s,u)=>{var m=u(82908),v=u(67206),_=u(5076),j=u(1469),M=u(16612);i.exports=function some(i,s,u){var $=j(i)?m:_;return u&&M(i,s,u)&&(s=void 0),$(i,v(s,3))}},70479:i=>{i.exports=function stubArray(){return[]}},95062:i=>{i.exports=function stubFalse(){return!1}},18601:(i,s,u)=>{var m=u(14841),v=1/0;i.exports=function toFinite(i){return i?(i=m(i))===v||i===-1/0?17976931348623157e292*(i<0?-1:1):i==i?i:0:0===i?i:0}},40554:(i,s,u)=>{var m=u(18601);i.exports=function toInteger(i){var s=m(i),u=s%1;return s==s?u?s-u:s:0}},7334:(i,s,u)=>{var m=u(79833);i.exports=function toLower(i){return m(i).toLowerCase()}},14841:(i,s,u)=>{var m=u(27561),v=u(13218),_=u(33448),j=/^[-+]0x[0-9a-f]+$/i,M=/^0b[01]+$/i,$=/^0o[0-7]+$/i,W=parseInt;i.exports=function toNumber(i){if("number"==typeof i)return i;if(_(i))return NaN;if(v(i)){var s="function"==typeof i.valueOf?i.valueOf():i;i=v(s)?s+"":s}if("string"!=typeof i)return 0===i?i:+i;i=m(i);var u=M.test(i);return u||$.test(i)?W(i.slice(2),u?2:8):j.test(i)?NaN:+i}},30084:(i,s,u)=>{var m=u(29932),v=u(278),_=u(1469),j=u(33448),M=u(55514),$=u(40327),W=u(79833);i.exports=function toPath(i){return _(i)?m(i,$):j(i)?[i]:v(M(W(i)))}},59881:(i,s,u)=>{var m=u(98363),v=u(81704);i.exports=function toPlainObject(i){return m(i,v(i))}},79833:(i,s,u)=>{var m=u(80531);i.exports=function toString(i){return null==i?"":m(i)}},11700:(i,s,u)=>{var m=u(98805)("toUpperCase");i.exports=m},58748:(i,s,u)=>{var m=u(49029),v=u(93157),_=u(79833),j=u(2757);i.exports=function words(i,s,u){return i=_(i),void 0===(s=u?void 0:s)?v(i)?j(i):m(i):i.match(s)||[]}},8111:(i,s,u)=>{var m=u(96425),v=u(7548),_=u(9435),j=u(1469),M=u(37005),$=u(21913),W=Object.prototype.hasOwnProperty;function lodash(i){if(M(i)&&!j(i)&&!(i instanceof m)){if(i instanceof v)return i;if(W.call(i,"__wrapped__"))return $(i)}return new v(i)}lodash.prototype=_.prototype,lodash.prototype.constructor=lodash,i.exports=lodash},7287:(i,s,u)=>{var m=u(34865),v=u(1757);i.exports=function zipObject(i,s){return v(i||[],s||[],m)}},96470:(i,s,u)=>{"use strict";var m=u(47802),v=u(21102);s.highlight=highlight,s.highlightAuto=function highlightAuto(i,s){var u,j,M,$,W=s||{},X=W.subset||m.listLanguages(),Y=W.prefix,Z=X.length,ee=-1;null==Y&&(Y=_);if("string"!=typeof i)throw v("Expected `string` for value, got `%s`",i);j={relevance:0,language:null,value:[]},u={relevance:0,language:null,value:[]};for(;++eej.relevance&&(j=M),M.relevance>u.relevance&&(j=u,u=M));j.language&&(u.secondBest=j);return u},s.registerLanguage=function registerLanguage(i,s){m.registerLanguage(i,s)},s.listLanguages=function listLanguages(){return m.listLanguages()},s.registerAlias=function registerAlias(i,s){var u,v=i;s&&((v={})[i]=s);for(u in v)m.registerAliases(v[u],{languageName:u})},Emitter.prototype.addText=function text(i){var s,u,m=this.stack;if(""===i)return;s=m[m.length-1],(u=s.children[s.children.length-1])&&"text"===u.type?u.value+=i:s.children.push({type:"text",value:i})},Emitter.prototype.addKeyword=function addKeyword(i,s){this.openNode(s),this.addText(i),this.closeNode()},Emitter.prototype.addSublanguage=function addSublanguage(i,s){var u=this.stack,m=u[u.length-1],v=i.rootNode.children,_=s?{type:"element",tagName:"span",properties:{className:[s]},children:v}:v;m.children=m.children.concat(_)},Emitter.prototype.openNode=function open(i){var s=this.stack,u=this.options.classPrefix+i,m=s[s.length-1],v={type:"element",tagName:"span",properties:{className:[u]},children:[]};m.children.push(v),s.push(v)},Emitter.prototype.closeNode=function close(){this.stack.pop()},Emitter.prototype.closeAllNodes=noop,Emitter.prototype.finalize=noop,Emitter.prototype.toHTML=function toHtmlNoop(){return""};var _="hljs-";function highlight(i,s,u){var j,M=m.configure({}),$=(u||{}).prefix;if("string"!=typeof i)throw v("Expected `string` for name, got `%s`",i);if(!m.getLanguage(i))throw v("Unknown language: `%s` is not registered",i);if("string"!=typeof s)throw v("Expected `string` for value, got `%s`",s);if(null==$&&($=_),m.configure({__emitter:Emitter,classPrefix:$}),j=m.highlight(s,{language:i,ignoreIllegals:!0}),m.configure(M||{}),j.errorRaised)throw j.errorRaised;return{relevance:j.relevance,language:j.language,value:j.emitter.rootNode.children}}function Emitter(i){this.options=i,this.rootNode={children:[]},this.stack=[this.rootNode]}function noop(){}},42566:(i,s,u)=>{const m=u(94885);function coerceElementMatchingCallback(i){return"string"==typeof i?s=>s.element===i:i.constructor&&i.extend?s=>s instanceof i:i}class ArraySlice{constructor(i){this.elements=i||[]}toValue(){return this.elements.map((i=>i.toValue()))}map(i,s){return this.elements.map(i,s)}flatMap(i,s){return this.map(i,s).reduce(((i,s)=>i.concat(s)),[])}compactMap(i,s){const u=[];return this.forEach((m=>{const v=i.bind(s)(m);v&&u.push(v)})),u}filter(i,s){return i=coerceElementMatchingCallback(i),new ArraySlice(this.elements.filter(i,s))}reject(i,s){return i=coerceElementMatchingCallback(i),new ArraySlice(this.elements.filter(m(i),s))}find(i,s){return i=coerceElementMatchingCallback(i),this.elements.find(i,s)}forEach(i,s){this.elements.forEach(i,s)}reduce(i,s){return this.elements.reduce(i,s)}includes(i){return this.elements.some((s=>s.equals(i)))}shift(){return this.elements.shift()}unshift(i){this.elements.unshift(this.refract(i))}push(i){return this.elements.push(this.refract(i)),this}add(i){this.push(i)}get(i){return this.elements[i]}getValue(i){const s=this.elements[i];if(s)return s.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(ArraySlice.prototype[Symbol.iterator]=function symbol(){return this.elements[Symbol.iterator]()}),i.exports=ArraySlice},17645:i=>{class KeyValuePair{constructor(i,s){this.key=i,this.value=s}clone(){const i=new KeyValuePair;return this.key&&(i.key=this.key.clone()),this.value&&(i.value=this.value.clone()),i}}i.exports=KeyValuePair},78520:(i,s,u)=>{const m=u(45220),v=u(47037),_=u(81763),j=u(51584),M=u(13218),$=u(28219),W=u(99829);class Namespace{constructor(i){this.elementMap={},this.elementDetection=[],this.Element=W.Element,this.KeyValuePair=W.KeyValuePair,i&&i.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(i){return i.namespace&&i.namespace({base:this}),i.load&&i.load({base:this}),this}useDefault(){return this.register("null",W.NullElement).register("string",W.StringElement).register("number",W.NumberElement).register("boolean",W.BooleanElement).register("array",W.ArrayElement).register("object",W.ObjectElement).register("member",W.MemberElement).register("ref",W.RefElement).register("link",W.LinkElement),this.detect(m,W.NullElement,!1).detect(v,W.StringElement,!1).detect(_,W.NumberElement,!1).detect(j,W.BooleanElement,!1).detect(Array.isArray,W.ArrayElement,!1).detect(M,W.ObjectElement,!1),this}register(i,s){return this._elements=void 0,this.elementMap[i]=s,this}unregister(i){return this._elements=void 0,delete this.elementMap[i],this}detect(i,s,u){return void 0===u||u?this.elementDetection.unshift([i,s]):this.elementDetection.push([i,s]),this}toElement(i){if(i instanceof this.Element)return i;let s;for(let u=0;u{const s=i[0].toUpperCase()+i.substr(1);this._elements[s]=this.elementMap[i]}))),this._elements}get serialiser(){return new $(this)}}$.prototype.Namespace=Namespace,i.exports=Namespace},87526:(i,s,u)=>{const m=u(94885),v=u(42566);class ObjectSlice extends v{map(i,s){return this.elements.map((u=>i.bind(s)(u.value,u.key,u)))}filter(i,s){return new ObjectSlice(this.elements.filter((u=>i.bind(s)(u.value,u.key,u))))}reject(i,s){return this.filter(m(i.bind(s)))}forEach(i,s){return this.elements.forEach(((u,m)=>{i.bind(s)(u.value,u.key,u,m)}))}keys(){return this.map(((i,s)=>s.toValue()))}values(){return this.map((i=>i.toValue()))}}i.exports=ObjectSlice},99829:(i,s,u)=>{const m=u(3079),v=u(96295),_=u(16036),j=u(91090),M=u(18866),$=u(35804),W=u(5946),X=u(76735),Y=u(59964),Z=u(38588),ee=u(42566),ae=u(87526),ie=u(17645);function refract(i){if(i instanceof m)return i;if("string"==typeof i)return new _(i);if("number"==typeof i)return new j(i);if("boolean"==typeof i)return new M(i);if(null===i)return new v;if(Array.isArray(i))return new $(i.map(refract));if("object"==typeof i){return new X(i)}return i}m.prototype.ObjectElement=X,m.prototype.RefElement=Z,m.prototype.MemberElement=W,m.prototype.refract=refract,ee.prototype.refract=refract,i.exports={Element:m,NullElement:v,StringElement:_,NumberElement:j,BooleanElement:M,ArrayElement:$,MemberElement:W,ObjectElement:X,LinkElement:Y,RefElement:Z,refract,ArraySlice:ee,ObjectSlice:ae,KeyValuePair:ie}},59964:(i,s,u)=>{const m=u(3079);i.exports=class LinkElement extends m{constructor(i,s,u){super(i||[],s,u),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(i){this.attributes.set("relation",i)}get href(){return this.attributes.get("href")}set href(i){this.attributes.set("href",i)}}},38588:(i,s,u)=>{const m=u(3079);i.exports=class RefElement extends m{constructor(i,s,u){super(i||[],s,u),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(i){this.attributes.set("path",i)}}},43500:(i,s,u)=>{const m=u(78520),v=u(99829);s.lS=m,u(17645),s.O4=v.ArraySlice,v.ObjectSlice,s.W_=v.Element,s.RP=v.StringElement,s.VL=v.NumberElement,s.hh=v.BooleanElement,s.zr=v.NullElement,s.ON=v.ArrayElement,s.Sb=v.ObjectElement,s.c6=v.MemberElement,s.tK=v.RefElement,s.EA=v.LinkElement,s.Qc=v.refract,u(28219),u(3414)},35804:(i,s,u)=>{const m=u(94885),v=u(3079),_=u(42566);class ArrayElement extends v{constructor(i,s,u){super(i||[],s,u),this.element="array"}primitive(){return"array"}get(i){return this.content[i]}getValue(i){const s=this.get(i);if(s)return s.toValue()}getIndex(i){return this.content[i]}set(i,s){return this.content[i]=this.refract(s),this}remove(i){const s=this.content.splice(i,1);return s.length?s[0]:null}map(i,s){return this.content.map(i,s)}flatMap(i,s){return this.map(i,s).reduce(((i,s)=>i.concat(s)),[])}compactMap(i,s){const u=[];return this.forEach((m=>{const v=i.bind(s)(m);v&&u.push(v)})),u}filter(i,s){return new _(this.content.filter(i,s))}reject(i,s){return this.filter(m(i),s)}reduce(i,s){let u,m;void 0!==s?(u=0,m=this.refract(s)):(u=1,m="object"===this.primitive()?this.first.value:this.first);for(let s=u;s{i.bind(s)(u,this.refract(m))}))}shift(){return this.content.shift()}unshift(i){this.content.unshift(this.refract(i))}push(i){return this.content.push(this.refract(i)),this}add(i){this.push(i)}findElements(i,s){const u=s||{},m=!!u.recursive,v=void 0===u.results?[]:u.results;return this.forEach(((s,u,_)=>{m&&void 0!==s.findElements&&s.findElements(i,{results:v,recursive:m}),i(s,u,_)&&v.push(s)})),v}find(i){return new _(this.findElements(i,{recursive:!0}))}findByElement(i){return this.find((s=>s.element===i))}findByClass(i){return this.find((s=>s.classes.includes(i)))}getById(i){return this.find((s=>s.id.toValue()===i)).first}includes(i){return this.content.some((s=>s.equals(i)))}contains(i){return this.includes(i)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(i){return new this.constructor(this.content.concat(i.content))}"fantasy-land/concat"(i){return this.concat(i)}"fantasy-land/map"(i){return new this.constructor(this.map(i))}"fantasy-land/chain"(i){return this.map((s=>i(s)),this).reduce(((i,s)=>i.concat(s)),this.empty())}"fantasy-land/filter"(i){return new this.constructor(this.content.filter(i))}"fantasy-land/reduce"(i,s){return this.content.reduce(i,s)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),i.exports=ArrayElement},18866:(i,s,u)=>{const m=u(3079);i.exports=class BooleanElement extends m{constructor(i,s,u){super(i,s,u),this.element="boolean"}primitive(){return"boolean"}}},3079:(i,s,u)=>{const m=u(18446),v=u(17645),_=u(42566);class Element{constructor(i,s,u){s&&(this.meta=s),u&&(this.attributes=u),this.content=i}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((i=>{i.parent=this,i.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const i=new this.constructor;return i.element=this.element,this.meta.length&&(i._meta=this.meta.clone()),this.attributes.length&&(i._attributes=this.attributes.clone()),this.content?this.content.clone?i.content=this.content.clone():Array.isArray(this.content)?i.content=this.content.map((i=>i.clone())):i.content=this.content:i.content=this.content,i}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof v?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((i=>i.toValue()),this):this.content}toRef(i){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const s=new this.RefElement(this.id.toValue());return i&&(s.path=i),s}findRecursive(...i){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const s=i.pop();let u=new _;const append=(i,s)=>(i.push(s),i),checkElement=(i,u)=>{u.element===s&&i.push(u);const m=u.findRecursive(s);return m&&m.reduce(append,i),u.content instanceof v&&(u.content.key&&checkElement(i,u.content.key),u.content.value&&checkElement(i,u.content.value)),i};return this.content&&(this.content.element&&checkElement(u,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,u)),i.isEmpty||(u=u.filter((s=>{let u=s.parents.map((i=>i.element));for(const s in i){const m=i[s],v=u.indexOf(m);if(-1===v)return!1;u=u.splice(0,v)}return!0}))),u}set(i){return this.content=i,this}equals(i){return m(this.toValue(),i)}getMetaProperty(i,s){if(!this.meta.hasKey(i)){if(this.isFrozen){const i=this.refract(s);return i.freeze(),i}this.meta.set(i,s)}return this.meta.get(i)}setMetaProperty(i,s){this.meta.set(i,s)}get element(){return this._storedElement||"element"}set element(i){this._storedElement=i}get content(){return this._content}set content(i){if(i instanceof Element)this._content=i;else if(i instanceof _)this.content=i.elements;else if("string"==typeof i||"number"==typeof i||"boolean"==typeof i||"null"===i||null==i)this._content=i;else if(i instanceof v)this._content=i;else if(Array.isArray(i))this._content=i.map(this.refract);else{if("object"!=typeof i)throw new Error("Cannot set content to given value");this._content=Object.keys(i).map((s=>new this.MemberElement(s,i[s])))}}get meta(){if(!this._meta){if(this.isFrozen){const i=new this.ObjectElement;return i.freeze(),i}this._meta=new this.ObjectElement}return this._meta}set meta(i){i instanceof this.ObjectElement?this._meta=i:this.meta.set(i||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const i=new this.ObjectElement;return i.freeze(),i}this._attributes=new this.ObjectElement}return this._attributes}set attributes(i){i instanceof this.ObjectElement?this._attributes=i:this.attributes.set(i||{})}get id(){return this.getMetaProperty("id","")}set id(i){this.setMetaProperty("id",i)}get classes(){return this.getMetaProperty("classes",[])}set classes(i){this.setMetaProperty("classes",i)}get title(){return this.getMetaProperty("title","")}set title(i){this.setMetaProperty("title",i)}get description(){return this.getMetaProperty("description","")}set description(i){this.setMetaProperty("description",i)}get links(){return this.getMetaProperty("links",[])}set links(i){this.setMetaProperty("links",i)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:i}=this;const s=new _;for(;i;)s.push(i),i=i.parent;return s}get children(){if(Array.isArray(this.content))return new _(this.content);if(this.content instanceof v){const i=new _([this.content.key]);return this.content.value&&i.push(this.content.value),i}return this.content instanceof Element?new _([this.content]):new _}get recursiveChildren(){const i=new _;return this.children.forEach((s=>{i.push(s),s.recursiveChildren.forEach((s=>{i.push(s)}))})),i}}i.exports=Element},5946:(i,s,u)=>{const m=u(17645),v=u(3079);i.exports=class MemberElement extends v{constructor(i,s,u,v){super(new m,u,v),this.element="member",this.key=i,this.value=s}get key(){return this.content.key}set key(i){this.content.key=this.refract(i)}get value(){return this.content.value}set value(i){this.content.value=this.refract(i)}}},96295:(i,s,u)=>{const m=u(3079);i.exports=class NullElement extends m{constructor(i,s,u){super(i||null,s,u),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},91090:(i,s,u)=>{const m=u(3079);i.exports=class NumberElement extends m{constructor(i,s,u){super(i,s,u),this.element="number"}primitive(){return"number"}}},76735:(i,s,u)=>{const m=u(94885),v=u(13218),_=u(35804),j=u(5946),M=u(87526);i.exports=class ObjectElement extends _{constructor(i,s,u){super(i||[],s,u),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((i,s)=>(i[s.key.toValue()]=s.value?s.value.toValue():void 0,i)),{})}get(i){const s=this.getMember(i);if(s)return s.value}getMember(i){if(void 0!==i)return this.content.find((s=>s.key.toValue()===i))}remove(i){let s=null;return this.content=this.content.filter((u=>u.key.toValue()!==i||(s=u,!1))),s}getKey(i){const s=this.getMember(i);if(s)return s.key}set(i,s){if(v(i))return Object.keys(i).forEach((s=>{this.set(s,i[s])})),this;const u=i,m=this.getMember(u);return m?m.value=s:this.content.push(new j(u,s)),this}keys(){return this.content.map((i=>i.key.toValue()))}values(){return this.content.map((i=>i.value.toValue()))}hasKey(i){return this.content.some((s=>s.key.equals(i)))}items(){return this.content.map((i=>[i.key.toValue(),i.value.toValue()]))}map(i,s){return this.content.map((u=>i.bind(s)(u.value,u.key,u)))}compactMap(i,s){const u=[];return this.forEach(((m,v,_)=>{const j=i.bind(s)(m,v,_);j&&u.push(j)})),u}filter(i,s){return new M(this.content).filter(i,s)}reject(i,s){return this.filter(m(i),s)}forEach(i,s){return this.content.forEach((u=>i.bind(s)(u.value,u.key,u)))}}},16036:(i,s,u)=>{const m=u(3079);i.exports=class StringElement extends m{constructor(i,s,u){super(i,s,u),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},3414:(i,s,u)=>{const m=u(28219);i.exports=class JSON06Serialiser extends m{serialise(i){if(!(i instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${i}\` is not an Element instance`);let s;i._attributes&&i.attributes.get("variable")&&(s=i.attributes.get("variable"));const u={element:i.element};i._meta&&i._meta.length>0&&(u.meta=this.serialiseObject(i.meta));const m="enum"===i.element||-1!==i.attributes.keys().indexOf("enumerations");if(m){const s=this.enumSerialiseAttributes(i);s&&(u.attributes=s)}else if(i._attributes&&i._attributes.length>0){let{attributes:m}=i;m.get("metadata")&&(m=m.clone(),m.set("meta",m.get("metadata")),m.remove("metadata")),"member"===i.element&&s&&(m=m.clone(),m.remove("variable")),m.length>0&&(u.attributes=this.serialiseObject(m))}if(m)u.content=this.enumSerialiseContent(i,u);else if(this[`${i.element}SerialiseContent`])u.content=this[`${i.element}SerialiseContent`](i,u);else if(void 0!==i.content){let m;s&&i.content.key?(m=i.content.clone(),m.key.attributes.set("variable",s),m=this.serialiseContent(m)):m=this.serialiseContent(i.content),this.shouldSerialiseContent(i,m)&&(u.content=m)}else this.shouldSerialiseContent(i,i.content)&&i instanceof this.namespace.elements.Array&&(u.content=[]);return u}shouldSerialiseContent(i,s){return"parseResult"===i.element||"httpRequest"===i.element||"httpResponse"===i.element||"category"===i.element||"link"===i.element||void 0!==s&&(!Array.isArray(s)||0!==s.length)}refSerialiseContent(i,s){return delete s.attributes,{href:i.toValue(),path:i.path.toValue()}}sourceMapSerialiseContent(i){return i.toValue()}dataStructureSerialiseContent(i){return[this.serialiseContent(i.content)]}enumSerialiseAttributes(i){const s=i.attributes.clone(),u=s.remove("enumerations")||new this.namespace.elements.Array([]),m=s.get("default");let v=s.get("samples")||new this.namespace.elements.Array([]);if(m&&m.content&&(m.content.attributes&&m.content.attributes.remove("typeAttributes"),s.set("default",new this.namespace.elements.Array([m.content]))),v.forEach((i=>{i.content&&i.content.element&&i.content.attributes.remove("typeAttributes")})),i.content&&0!==u.length&&v.unshift(i.content),v=v.map((i=>i instanceof this.namespace.elements.Array?[i]:new this.namespace.elements.Array([i.content]))),v.length&&s.set("samples",v),s.length>0)return this.serialiseObject(s)}enumSerialiseContent(i){if(i._attributes){const s=i.attributes.get("enumerations");if(s&&s.length>0)return s.content.map((i=>{const s=i.clone();return s.attributes.remove("typeAttributes"),this.serialise(s)}))}if(i.content){const s=i.content.clone();return s.attributes.remove("typeAttributes"),[this.serialise(s)]}return[]}deserialise(i){if("string"==typeof i)return new this.namespace.elements.String(i);if("number"==typeof i)return new this.namespace.elements.Number(i);if("boolean"==typeof i)return new this.namespace.elements.Boolean(i);if(null===i)return new this.namespace.elements.Null;if(Array.isArray(i))return new this.namespace.elements.Array(i.map(this.deserialise,this));const s=this.namespace.getElementClass(i.element),u=new s;u.element!==i.element&&(u.element=i.element),i.meta&&this.deserialiseObject(i.meta,u.meta),i.attributes&&this.deserialiseObject(i.attributes,u.attributes);const m=this.deserialiseContent(i.content);if(void 0===m&&null!==u.content||(u.content=m),"enum"===u.element){u.content&&u.attributes.set("enumerations",u.content);let i=u.attributes.get("samples");if(u.attributes.remove("samples"),i){const m=i;i=new this.namespace.elements.Array,m.forEach((m=>{m.forEach((m=>{const v=new s(m);v.element=u.element,i.push(v)}))}));const v=i.shift();u.content=v?v.content:void 0,u.attributes.set("samples",i)}else u.content=void 0;let m=u.attributes.get("default");if(m&&m.length>0){m=m.get(0);const i=new s(m);i.element=u.element,u.attributes.set("default",i)}}else if("dataStructure"===u.element&&Array.isArray(u.content))[u.content]=u.content;else if("category"===u.element){const i=u.attributes.get("meta");i&&(u.attributes.set("metadata",i),u.attributes.remove("meta"))}else"member"===u.element&&u.key&&u.key._attributes&&u.key._attributes.getValue("variable")&&(u.attributes.set("variable",u.key.attributes.get("variable")),u.key.attributes.remove("variable"));return u}serialiseContent(i){if(i instanceof this.namespace.elements.Element)return this.serialise(i);if(i instanceof this.namespace.KeyValuePair){const s={key:this.serialise(i.key)};return i.value&&(s.value=this.serialise(i.value)),s}return i&&i.map?i.map(this.serialise,this):i}deserialiseContent(i){if(i){if(i.element)return this.deserialise(i);if(i.key){const s=new this.namespace.KeyValuePair(this.deserialise(i.key));return i.value&&(s.value=this.deserialise(i.value)),s}if(i.map)return i.map(this.deserialise,this)}return i}shouldRefract(i){return!!(i._attributes&&i.attributes.keys().length||i._meta&&i.meta.keys().length)||"enum"!==i.element&&(i.element!==i.primitive()||"member"===i.element)}convertKeyToRefract(i,s){return this.shouldRefract(s)?this.serialise(s):"enum"===s.element?this.serialiseEnum(s):"array"===s.element?s.map((s=>this.shouldRefract(s)||"default"===i?this.serialise(s):"array"===s.element||"object"===s.element||"enum"===s.element?s.children.map((i=>this.serialise(i))):s.toValue())):"object"===s.element?(s.content||[]).map(this.serialise,this):s.toValue()}serialiseEnum(i){return i.children.map((i=>this.serialise(i)))}serialiseObject(i){const s={};return i.forEach(((i,u)=>{if(i){const m=u.toValue();s[m]=this.convertKeyToRefract(m,i)}})),s}deserialiseObject(i,s){Object.keys(i).forEach((u=>{s.set(u,this.deserialise(i[u]))}))}}},28219:i=>{i.exports=class JSONSerialiser{constructor(i){this.namespace=i||new this.Namespace}serialise(i){if(!(i instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${i}\` is not an Element instance`);const s={element:i.element};i._meta&&i._meta.length>0&&(s.meta=this.serialiseObject(i.meta)),i._attributes&&i._attributes.length>0&&(s.attributes=this.serialiseObject(i.attributes));const u=this.serialiseContent(i.content);return void 0!==u&&(s.content=u),s}deserialise(i){if(!i.element)throw new Error("Given value is not an object containing an element name");const s=new(this.namespace.getElementClass(i.element));s.element!==i.element&&(s.element=i.element),i.meta&&this.deserialiseObject(i.meta,s.meta),i.attributes&&this.deserialiseObject(i.attributes,s.attributes);const u=this.deserialiseContent(i.content);return void 0===u&&null!==s.content||(s.content=u),s}serialiseContent(i){if(i instanceof this.namespace.elements.Element)return this.serialise(i);if(i instanceof this.namespace.KeyValuePair){const s={key:this.serialise(i.key)};return i.value&&(s.value=this.serialise(i.value)),s}if(i&&i.map){if(0===i.length)return;return i.map(this.serialise,this)}return i}deserialiseContent(i){if(i){if(i.element)return this.deserialise(i);if(i.key){const s=new this.namespace.KeyValuePair(this.deserialise(i.key));return i.value&&(s.value=this.deserialise(i.value)),s}if(i.map)return i.map(this.deserialise,this)}return i}serialiseObject(i){const s={};if(i.forEach(((i,u)=>{i&&(s[u.toValue()]=this.serialise(i))})),0!==Object.keys(s).length)return s}deserialiseObject(i,s){Object.keys(i).forEach((u=>{s.set(u,this.deserialise(i[u]))}))}}},27418:i=>{"use strict";var s=Object.getOwnPropertySymbols,u=Object.prototype.hasOwnProperty,m=Object.prototype.propertyIsEnumerable;i.exports=function shouldUseNative(){try{if(!Object.assign)return!1;var i=new String("abc");if(i[5]="de","5"===Object.getOwnPropertyNames(i)[0])return!1;for(var s={},u=0;u<10;u++)s["_"+String.fromCharCode(u)]=u;if("0123456789"!==Object.getOwnPropertyNames(s).map((function(i){return s[i]})).join(""))return!1;var m={};return"abcdefghijklmnopqrst".split("").forEach((function(i){m[i]=i})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},m)).join("")}catch(i){return!1}}()?Object.assign:function(i,v){for(var _,j,M=function toObject(i){if(null==i)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(i)}(i),$=1;${var m="function"==typeof Map&&Map.prototype,v=Object.getOwnPropertyDescriptor&&m?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,_=m&&v&&"function"==typeof v.get?v.get:null,j=m&&Map.prototype.forEach,M="function"==typeof Set&&Set.prototype,$=Object.getOwnPropertyDescriptor&&M?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,W=M&&$&&"function"==typeof $.get?$.get:null,X=M&&Set.prototype.forEach,Y="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,Z="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,ee="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,ae=Boolean.prototype.valueOf,ie=Object.prototype.toString,le=Function.prototype.toString,ce=String.prototype.match,pe=String.prototype.slice,de=String.prototype.replace,fe=String.prototype.toUpperCase,ye=String.prototype.toLowerCase,be=RegExp.prototype.test,_e=Array.prototype.concat,we=Array.prototype.join,Se=Array.prototype.slice,xe=Math.floor,Pe="function"==typeof BigInt?BigInt.prototype.valueOf:null,Ie=Object.getOwnPropertySymbols,Te="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,Re="function"==typeof Symbol&&"object"==typeof Symbol.iterator,qe="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===Re||"symbol")?Symbol.toStringTag:null,ze=Object.prototype.propertyIsEnumerable,Ve=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(i){return i.__proto__}:null);function addNumericSeparator(i,s){if(i===1/0||i===-1/0||i!=i||i&&i>-1e3&&i<1e3||be.call(/e/,s))return s;var u=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof i){var m=i<0?-xe(-i):xe(i);if(m!==i){var v=String(m),_=pe.call(s,v.length+1);return de.call(v,u,"$&_")+"."+de.call(de.call(_,/([0-9]{3})/g,"$&_"),/_$/,"")}}return de.call(s,u,"$&_")}var We=u(24654),He=We.custom,Xe=isSymbol(He)?He:null;function wrapQuotes(i,s,u){var m="double"===(u.quoteStyle||s)?'"':"'";return m+i+m}function quote(i){return de.call(String(i),/"/g,""")}function isArray(i){return!("[object Array]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}function isRegExp(i){return!("[object RegExp]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}function isSymbol(i){if(Re)return i&&"object"==typeof i&&i instanceof Symbol;if("symbol"==typeof i)return!0;if(!i||"object"!=typeof i||!Te)return!1;try{return Te.call(i),!0}catch(i){}return!1}i.exports=function inspect_(i,s,u,m){var v=s||{};if(has(v,"quoteStyle")&&"single"!==v.quoteStyle&&"double"!==v.quoteStyle)throw new TypeError('option "quoteStyle" must be "single" or "double"');if(has(v,"maxStringLength")&&("number"==typeof v.maxStringLength?v.maxStringLength<0&&v.maxStringLength!==1/0:null!==v.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var M=!has(v,"customInspect")||v.customInspect;if("boolean"!=typeof M&&"symbol"!==M)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(has(v,"indent")&&null!==v.indent&&"\t"!==v.indent&&!(parseInt(v.indent,10)===v.indent&&v.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(has(v,"numericSeparator")&&"boolean"!=typeof v.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var $=v.numericSeparator;if(void 0===i)return"undefined";if(null===i)return"null";if("boolean"==typeof i)return i?"true":"false";if("string"==typeof i)return inspectString(i,v);if("number"==typeof i){if(0===i)return 1/0/i>0?"0":"-0";var ie=String(i);return $?addNumericSeparator(i,ie):ie}if("bigint"==typeof i){var fe=String(i)+"n";return $?addNumericSeparator(i,fe):fe}var be=void 0===v.depth?5:v.depth;if(void 0===u&&(u=0),u>=be&&be>0&&"object"==typeof i)return isArray(i)?"[Array]":"[Object]";var xe=function getIndent(i,s){var u;if("\t"===i.indent)u="\t";else{if(!("number"==typeof i.indent&&i.indent>0))return null;u=we.call(Array(i.indent+1)," ")}return{base:u,prev:we.call(Array(s+1),u)}}(v,u);if(void 0===m)m=[];else if(indexOf(m,i)>=0)return"[Circular]";function inspect(i,s,_){if(s&&(m=Se.call(m)).push(s),_){var j={depth:v.depth};return has(v,"quoteStyle")&&(j.quoteStyle=v.quoteStyle),inspect_(i,j,u+1,m)}return inspect_(i,v,u+1,m)}if("function"==typeof i&&!isRegExp(i)){var Ie=function nameOf(i){if(i.name)return i.name;var s=ce.call(le.call(i),/^function\s*([\w$]+)/);if(s)return s[1];return null}(i),He=arrObjKeys(i,inspect);return"[Function"+(Ie?": "+Ie:" (anonymous)")+"]"+(He.length>0?" { "+we.call(He,", ")+" }":"")}if(isSymbol(i)){var Ye=Re?de.call(String(i),/^(Symbol\(.*\))_[^)]*$/,"$1"):Te.call(i);return"object"!=typeof i||Re?Ye:markBoxed(Ye)}if(function isElement(i){if(!i||"object"!=typeof i)return!1;if("undefined"!=typeof HTMLElement&&i instanceof HTMLElement)return!0;return"string"==typeof i.nodeName&&"function"==typeof i.getAttribute}(i)){for(var Qe="<"+ye.call(String(i.nodeName)),et=i.attributes||[],tt=0;tt"}if(isArray(i)){if(0===i.length)return"[]";var rt=arrObjKeys(i,inspect);return xe&&!function singleLineValues(i){for(var s=0;s=0)return!1;return!0}(rt)?"["+indentedJoin(rt,xe)+"]":"[ "+we.call(rt,", ")+" ]"}if(function isError(i){return!("[object Error]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i)){var nt=arrObjKeys(i,inspect);return"cause"in Error.prototype||!("cause"in i)||ze.call(i,"cause")?0===nt.length?"["+String(i)+"]":"{ ["+String(i)+"] "+we.call(nt,", ")+" }":"{ ["+String(i)+"] "+we.call(_e.call("[cause]: "+inspect(i.cause),nt),", ")+" }"}if("object"==typeof i&&M){if(Xe&&"function"==typeof i[Xe]&&We)return We(i,{depth:be-u});if("symbol"!==M&&"function"==typeof i.inspect)return i.inspect()}if(function isMap(i){if(!_||!i||"object"!=typeof i)return!1;try{_.call(i);try{W.call(i)}catch(i){return!0}return i instanceof Map}catch(i){}return!1}(i)){var ot=[];return j&&j.call(i,(function(s,u){ot.push(inspect(u,i,!0)+" => "+inspect(s,i))})),collectionOf("Map",_.call(i),ot,xe)}if(function isSet(i){if(!W||!i||"object"!=typeof i)return!1;try{W.call(i);try{_.call(i)}catch(i){return!0}return i instanceof Set}catch(i){}return!1}(i)){var at=[];return X&&X.call(i,(function(s){at.push(inspect(s,i))})),collectionOf("Set",W.call(i),at,xe)}if(function isWeakMap(i){if(!Y||!i||"object"!=typeof i)return!1;try{Y.call(i,Y);try{Z.call(i,Z)}catch(i){return!0}return i instanceof WeakMap}catch(i){}return!1}(i))return weakCollectionOf("WeakMap");if(function isWeakSet(i){if(!Z||!i||"object"!=typeof i)return!1;try{Z.call(i,Z);try{Y.call(i,Y)}catch(i){return!0}return i instanceof WeakSet}catch(i){}return!1}(i))return weakCollectionOf("WeakSet");if(function isWeakRef(i){if(!ee||!i||"object"!=typeof i)return!1;try{return ee.call(i),!0}catch(i){}return!1}(i))return weakCollectionOf("WeakRef");if(function isNumber(i){return!("[object Number]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i))return markBoxed(inspect(Number(i)));if(function isBigInt(i){if(!i||"object"!=typeof i||!Pe)return!1;try{return Pe.call(i),!0}catch(i){}return!1}(i))return markBoxed(inspect(Pe.call(i)));if(function isBoolean(i){return!("[object Boolean]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i))return markBoxed(ae.call(i));if(function isString(i){return!("[object String]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i))return markBoxed(inspect(String(i)));if(!function isDate(i){return!("[object Date]"!==toStr(i)||qe&&"object"==typeof i&&qe in i)}(i)&&!isRegExp(i)){var it=arrObjKeys(i,inspect),st=Ve?Ve(i)===Object.prototype:i instanceof Object||i.constructor===Object,lt=i instanceof Object?"":"null prototype",ct=!st&&qe&&Object(i)===i&&qe in i?pe.call(toStr(i),8,-1):lt?"Object":"",ut=(st||"function"!=typeof i.constructor?"":i.constructor.name?i.constructor.name+" ":"")+(ct||lt?"["+we.call(_e.call([],ct||[],lt||[]),": ")+"] ":"");return 0===it.length?ut+"{}":xe?ut+"{"+indentedJoin(it,xe)+"}":ut+"{ "+we.call(it,", ")+" }"}return String(i)};var Ye=Object.prototype.hasOwnProperty||function(i){return i in this};function has(i,s){return Ye.call(i,s)}function toStr(i){return ie.call(i)}function indexOf(i,s){if(i.indexOf)return i.indexOf(s);for(var u=0,m=i.length;us.maxStringLength){var u=i.length-s.maxStringLength,m="... "+u+" more character"+(u>1?"s":"");return inspectString(pe.call(i,0,s.maxStringLength),s)+m}return wrapQuotes(de.call(de.call(i,/(['\\])/g,"\\$1"),/[\x00-\x1f]/g,lowbyte),"single",s)}function lowbyte(i){var s=i.charCodeAt(0),u={8:"b",9:"t",10:"n",12:"f",13:"r"}[s];return u?"\\"+u:"\\x"+(s<16?"0":"")+fe.call(s.toString(16))}function markBoxed(i){return"Object("+i+")"}function weakCollectionOf(i){return i+" { ? }"}function collectionOf(i,s,u,m){return i+" ("+s+") {"+(m?indentedJoin(u,m):we.call(u,", "))+"}"}function indentedJoin(i,s){if(0===i.length)return"";var u="\n"+s.prev+s.base;return u+we.call(i,","+u)+"\n"+s.prev}function arrObjKeys(i,s){var u=isArray(i),m=[];if(u){m.length=i.length;for(var v=0;v{var s,u,m=i.exports={};function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}function runTimeout(i){if(s===setTimeout)return setTimeout(i,0);if((s===defaultSetTimout||!s)&&setTimeout)return s=setTimeout,setTimeout(i,0);try{return s(i,0)}catch(u){try{return s.call(null,i,0)}catch(u){return s.call(this,i,0)}}}!function(){try{s="function"==typeof setTimeout?setTimeout:defaultSetTimout}catch(i){s=defaultSetTimout}try{u="function"==typeof clearTimeout?clearTimeout:defaultClearTimeout}catch(i){u=defaultClearTimeout}}();var v,_=[],j=!1,M=-1;function cleanUpNextTick(){j&&v&&(j=!1,v.length?_=v.concat(_):M=-1,_.length&&drainQueue())}function drainQueue(){if(!j){var i=runTimeout(cleanUpNextTick);j=!0;for(var s=_.length;s;){for(v=_,_=[];++M1)for(var u=1;u{"use strict";var m=u(50414);function emptyFunction(){}function emptyFunctionWithReset(){}emptyFunctionWithReset.resetWarningCache=emptyFunction,i.exports=function(){function shim(i,s,u,v,_,j){if(j!==m){var M=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw M.name="Invariant Violation",M}}function getShim(){return shim}shim.isRequired=shim;var i={array:shim,bigint:shim,bool:shim,func:shim,number:shim,object:shim,string:shim,symbol:shim,any:shim,arrayOf:getShim,element:shim,elementType:shim,instanceOf:getShim,node:shim,objectOf:getShim,oneOf:getShim,oneOfType:getShim,shape:getShim,exact:getShim,checkPropTypes:emptyFunctionWithReset,resetWarningCache:emptyFunction};return i.PropTypes=i,i}},45697:(i,s,u)=>{i.exports=u(92703)()},50414:i=>{"use strict";i.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},55798:i=>{"use strict";var s=String.prototype.replace,u=/%20/g,m="RFC1738",v="RFC3986";i.exports={default:v,formatters:{RFC1738:function(i){return s.call(i,u,"+")},RFC3986:function(i){return String(i)}},RFC1738:m,RFC3986:v}},80129:(i,s,u)=>{"use strict";var m=u(58261),v=u(55235),_=u(55798);i.exports={formats:_,parse:v,stringify:m}},55235:(i,s,u)=>{"use strict";var m=u(12769),v=Object.prototype.hasOwnProperty,_=Array.isArray,j={allowDots:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decoder:m.decode,delimiter:"&",depth:5,ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictNullHandling:!1},interpretNumericEntities=function(i){return i.replace(/&#(\d+);/g,(function(i,s){return String.fromCharCode(parseInt(s,10))}))},parseArrayValue=function(i,s){return i&&"string"==typeof i&&s.comma&&i.indexOf(",")>-1?i.split(","):i},M=function parseQueryStringKeys(i,s,u,m){if(i){var _=u.allowDots?i.replace(/\.([^.[]+)/g,"[$1]"):i,j=/(\[[^[\]]*])/g,M=u.depth>0&&/(\[[^[\]]*])/.exec(_),$=M?_.slice(0,M.index):_,W=[];if($){if(!u.plainObjects&&v.call(Object.prototype,$)&&!u.allowPrototypes)return;W.push($)}for(var X=0;u.depth>0&&null!==(M=j.exec(_))&&X=0;--_){var j,M=i[_];if("[]"===M&&u.parseArrays)j=[].concat(v);else{j=u.plainObjects?Object.create(null):{};var $="["===M.charAt(0)&&"]"===M.charAt(M.length-1)?M.slice(1,-1):M,W=parseInt($,10);u.parseArrays||""!==$?!isNaN(W)&&M!==$&&String(W)===$&&W>=0&&u.parseArrays&&W<=u.arrayLimit?(j=[])[W]=v:"__proto__"!==$&&(j[$]=v):j={0:v}}v=j}return v}(W,s,u,m)}};i.exports=function(i,s){var u=function normalizeParseOptions(i){if(!i)return j;if(null!==i.decoder&&void 0!==i.decoder&&"function"!=typeof i.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==i.charset&&"utf-8"!==i.charset&&"iso-8859-1"!==i.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var s=void 0===i.charset?j.charset:i.charset;return{allowDots:void 0===i.allowDots?j.allowDots:!!i.allowDots,allowPrototypes:"boolean"==typeof i.allowPrototypes?i.allowPrototypes:j.allowPrototypes,allowSparse:"boolean"==typeof i.allowSparse?i.allowSparse:j.allowSparse,arrayLimit:"number"==typeof i.arrayLimit?i.arrayLimit:j.arrayLimit,charset:s,charsetSentinel:"boolean"==typeof i.charsetSentinel?i.charsetSentinel:j.charsetSentinel,comma:"boolean"==typeof i.comma?i.comma:j.comma,decoder:"function"==typeof i.decoder?i.decoder:j.decoder,delimiter:"string"==typeof i.delimiter||m.isRegExp(i.delimiter)?i.delimiter:j.delimiter,depth:"number"==typeof i.depth||!1===i.depth?+i.depth:j.depth,ignoreQueryPrefix:!0===i.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof i.interpretNumericEntities?i.interpretNumericEntities:j.interpretNumericEntities,parameterLimit:"number"==typeof i.parameterLimit?i.parameterLimit:j.parameterLimit,parseArrays:!1!==i.parseArrays,plainObjects:"boolean"==typeof i.plainObjects?i.plainObjects:j.plainObjects,strictNullHandling:"boolean"==typeof i.strictNullHandling?i.strictNullHandling:j.strictNullHandling}}(s);if(""===i||null==i)return u.plainObjects?Object.create(null):{};for(var $="string"==typeof i?function parseQueryStringValues(i,s){var u,M={},$=s.ignoreQueryPrefix?i.replace(/^\?/,""):i,W=s.parameterLimit===1/0?void 0:s.parameterLimit,X=$.split(s.delimiter,W),Y=-1,Z=s.charset;if(s.charsetSentinel)for(u=0;u-1&&(ae=_(ae)?[ae]:ae),v.call(M,ee)?M[ee]=m.combine(M[ee],ae):M[ee]=ae}return M}(i,u):i,W=u.plainObjects?Object.create(null):{},X=Object.keys($),Y=0;Y{"use strict";var m=u(37478),v=u(12769),_=u(55798),j=Object.prototype.hasOwnProperty,M={brackets:function brackets(i){return i+"[]"},comma:"comma",indices:function indices(i,s){return i+"["+s+"]"},repeat:function repeat(i){return i}},$=Array.isArray,W=String.prototype.split,X=Array.prototype.push,pushToArray=function(i,s){X.apply(i,$(s)?s:[s])},Y=Date.prototype.toISOString,Z=_.default,ee={addQueryPrefix:!1,allowDots:!1,charset:"utf-8",charsetSentinel:!1,delimiter:"&",encode:!0,encoder:v.encode,encodeValuesOnly:!1,format:Z,formatter:_.formatters[Z],indices:!1,serializeDate:function serializeDate(i){return Y.call(i)},skipNulls:!1,strictNullHandling:!1},ae={},ie=function stringify(i,s,u,_,j,M,X,Y,Z,ie,le,ce,pe,de,fe,ye){for(var be=i,_e=ye,we=0,Se=!1;void 0!==(_e=_e.get(ae))&&!Se;){var xe=_e.get(i);if(we+=1,void 0!==xe){if(xe===we)throw new RangeError("Cyclic object value");Se=!0}void 0===_e.get(ae)&&(we=0)}if("function"==typeof Y?be=Y(s,be):be instanceof Date?be=le(be):"comma"===u&&$(be)&&(be=v.maybeMap(be,(function(i){return i instanceof Date?le(i):i}))),null===be){if(j)return X&&!de?X(s,ee.encoder,fe,"key",ce):s;be=""}if(function isNonNullishPrimitive(i){return"string"==typeof i||"number"==typeof i||"boolean"==typeof i||"symbol"==typeof i||"bigint"==typeof i}(be)||v.isBuffer(be)){if(X){var Pe=de?s:X(s,ee.encoder,fe,"key",ce);if("comma"===u&&de){for(var Ie=W.call(String(be),","),Te="",Re=0;Re0?be.join(",")||null:void 0}];else if($(Y))qe=Y;else{var Ve=Object.keys(be);qe=Z?Ve.sort(Z):Ve}for(var We=_&&$(be)&&1===be.length?s+"[]":s,He=0;He0?fe+de:""}},12769:(i,s,u)=>{"use strict";var m=u(55798),v=Object.prototype.hasOwnProperty,_=Array.isArray,j=function(){for(var i=[],s=0;s<256;++s)i.push("%"+((s<16?"0":"")+s.toString(16)).toUpperCase());return i}(),M=function arrayToObject(i,s){for(var u=s&&s.plainObjects?Object.create(null):{},m=0;m1;){var s=i.pop(),u=s.obj[s.prop];if(_(u)){for(var m=[],v=0;v=48&&X<=57||X>=65&&X<=90||X>=97&&X<=122||_===m.RFC1738&&(40===X||41===X)?$+=M.charAt(W):X<128?$+=j[X]:X<2048?$+=j[192|X>>6]+j[128|63&X]:X<55296||X>=57344?$+=j[224|X>>12]+j[128|X>>6&63]+j[128|63&X]:(W+=1,X=65536+((1023&X)<<10|1023&M.charCodeAt(W)),$+=j[240|X>>18]+j[128|X>>12&63]+j[128|X>>6&63]+j[128|63&X])}return $},isBuffer:function isBuffer(i){return!(!i||"object"!=typeof i)&&!!(i.constructor&&i.constructor.isBuffer&&i.constructor.isBuffer(i))},isRegExp:function isRegExp(i){return"[object RegExp]"===Object.prototype.toString.call(i)},maybeMap:function maybeMap(i,s){if(_(i)){for(var u=[],m=0;m{"use strict";var u=Object.prototype.hasOwnProperty;function decode(i){try{return decodeURIComponent(i.replace(/\+/g," "))}catch(i){return null}}function encode(i){try{return encodeURIComponent(i)}catch(i){return null}}s.stringify=function querystringify(i,s){s=s||"";var m,v,_=[];for(v in"string"!=typeof s&&(s="?"),i)if(u.call(i,v)){if((m=i[v])||null!=m&&!isNaN(m)||(m=""),v=encode(v),m=encode(m),null===v||null===m)continue;_.push(v+"="+m)}return _.length?s+_.join("&"):""},s.parse=function querystring(i){for(var s,u=/([^=?#&]+)=?([^&]*)/g,m={};s=u.exec(i);){var v=decode(s[1]),_=decode(s[2]);null===v||null===_||v in m||(m[v]=_)}return m}},14419:(i,s,u)=>{const m=u(60697),v=u(69450),_=m.types;i.exports=class RandExp{constructor(i,s){if(this._setDefaults(i),i instanceof RegExp)this.ignoreCase=i.ignoreCase,this.multiline=i.multiline,i=i.source;else{if("string"!=typeof i)throw new Error("Expected a regexp or string");this.ignoreCase=s&&-1!==s.indexOf("i"),this.multiline=s&&-1!==s.indexOf("m")}this.tokens=m(i)}_setDefaults(i){this.max=null!=i.max?i.max:null!=RandExp.prototype.max?RandExp.prototype.max:100,this.defaultRange=i.defaultRange?i.defaultRange:this.defaultRange.clone(),i.randInt&&(this.randInt=i.randInt)}gen(){return this._gen(this.tokens,[])}_gen(i,s){var u,m,v,j,M;switch(i.type){case _.ROOT:case _.GROUP:if(i.followedBy||i.notFollowedBy)return"";for(i.remember&&void 0===i.groupNumber&&(i.groupNumber=s.push(null)-1),m="",j=0,M=(u=i.options?this._randSelect(i.options):i.stack).length;j{"use strict";var m=u(34155),v=65536,_=4294967295;var j=u(89509).Buffer,M=u.g.crypto||u.g.msCrypto;M&&M.getRandomValues?i.exports=function randomBytes(i,s){if(i>_)throw new RangeError("requested too many random bytes");var u=j.allocUnsafe(i);if(i>0)if(i>v)for(var $=0;${"use strict";function _typeof(i){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(i){return typeof i}:function(i){return i&&"function"==typeof Symbol&&i.constructor===Symbol&&i!==Symbol.prototype?"symbol":typeof i},_typeof(i)}Object.defineProperty(s,"__esModule",{value:!0}),s.CopyToClipboard=void 0;var m=_interopRequireDefault(u(67294)),v=_interopRequireDefault(u(20640)),_=["text","onCopy","options","children"];function _interopRequireDefault(i){return i&&i.__esModule?i:{default:i}}function ownKeys(i,s){var u=Object.keys(i);if(Object.getOwnPropertySymbols){var m=Object.getOwnPropertySymbols(i);s&&(m=m.filter((function(s){return Object.getOwnPropertyDescriptor(i,s).enumerable}))),u.push.apply(u,m)}return u}function _objectSpread(i){for(var s=1;s=0||(v[u]=i[u]);return v}(i,s);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(i);for(m=0;m<_.length;m++)u=_[m],s.indexOf(u)>=0||Object.prototype.propertyIsEnumerable.call(i,u)&&(v[u]=i[u])}return v}function _defineProperties(i,s){for(var u=0;u{"use strict";var m=u(74300).CopyToClipboard;m.CopyToClipboard=m,i.exports=m},53441:(i,s,u)=>{"use strict";function _typeof(i){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(i){return typeof i}:function(i){return i&&"function"==typeof Symbol&&i.constructor===Symbol&&i!==Symbol.prototype?"symbol":typeof i},_typeof(i)}Object.defineProperty(s,"__esModule",{value:!0}),s.DebounceInput=void 0;var m=_interopRequireDefault(u(67294)),v=_interopRequireDefault(u(91296)),_=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function _interopRequireDefault(i){return i&&i.__esModule?i:{default:i}}function _objectWithoutProperties(i,s){if(null==i)return{};var u,m,v=function _objectWithoutPropertiesLoose(i,s){if(null==i)return{};var u,m,v={},_=Object.keys(i);for(m=0;m<_.length;m++)u=_[m],s.indexOf(u)>=0||(v[u]=i[u]);return v}(i,s);if(Object.getOwnPropertySymbols){var _=Object.getOwnPropertySymbols(i);for(m=0;m<_.length;m++)u=_[m],s.indexOf(u)>=0||Object.prototype.propertyIsEnumerable.call(i,u)&&(v[u]=i[u])}return v}function ownKeys(i,s){var u=Object.keys(i);if(Object.getOwnPropertySymbols){var m=Object.getOwnPropertySymbols(i);s&&(m=m.filter((function(s){return Object.getOwnPropertyDescriptor(i,s).enumerable}))),u.push.apply(u,m)}return u}function _objectSpread(i){for(var s=1;s=m?u.notify(i):s.length>v.length&&u.notify(_objectSpread(_objectSpread({},i),{},{target:_objectSpread(_objectSpread({},i.target),{},{value:""})}))}))})),_defineProperty(_assertThisInitialized(u),"onKeyDown",(function(i){"Enter"===i.key&&u.forceNotify(i);var s=u.props.onKeyDown;s&&(i.persist(),s(i))})),_defineProperty(_assertThisInitialized(u),"onBlur",(function(i){u.forceNotify(i);var s=u.props.onBlur;s&&(i.persist(),s(i))})),_defineProperty(_assertThisInitialized(u),"createNotifier",(function(i){if(i<0)u.notify=function(){return null};else if(0===i)u.notify=u.doNotify;else{var s=(0,v.default)((function(i){u.isDebouncing=!1,u.doNotify(i)}),i);u.notify=function(i){u.isDebouncing=!0,s(i)},u.flush=function(){return s.flush()},u.cancel=function(){u.isDebouncing=!1,s.cancel()}}})),_defineProperty(_assertThisInitialized(u),"doNotify",(function(){u.props.onChange.apply(void 0,arguments)})),_defineProperty(_assertThisInitialized(u),"forceNotify",(function(i){var s=u.props.debounceTimeout;if(u.isDebouncing||!(s>0)){u.cancel&&u.cancel();var m=u.state.value,v=u.props.minLength;m.length>=v?u.doNotify(i):u.doNotify(_objectSpread(_objectSpread({},i),{},{target:_objectSpread(_objectSpread({},i.target),{},{value:m})}))}})),u.isDebouncing=!1,u.state={value:void 0===i.value||null===i.value?"":i.value};var m=u.props.debounceTimeout;return u.createNotifier(m),u}return function _createClass(i,s,u){return s&&_defineProperties(i.prototype,s),u&&_defineProperties(i,u),Object.defineProperty(i,"prototype",{writable:!1}),i}(DebounceInput,[{key:"componentDidUpdate",value:function componentDidUpdate(i){if(!this.isDebouncing){var s=this.props,u=s.value,m=s.debounceTimeout,v=i.debounceTimeout,_=i.value,j=this.state.value;void 0!==u&&_!==u&&j!==u&&this.setState({value:u}),m!==v&&this.createNotifier(m)}}},{key:"componentWillUnmount",value:function componentWillUnmount(){this.flush&&this.flush()}},{key:"render",value:function render(){var i,s,u=this.props,v=u.element,j=(u.onChange,u.value,u.minLength,u.debounceTimeout,u.forceNotifyByEnter),M=u.forceNotifyOnBlur,$=u.onKeyDown,W=u.onBlur,X=u.inputRef,Y=_objectWithoutProperties(u,_),Z=this.state.value;i=j?{onKeyDown:this.onKeyDown}:$?{onKeyDown:$}:{},s=M?{onBlur:this.onBlur}:W?{onBlur:W}:{};var ee=X?{ref:X}:{};return m.default.createElement(v,_objectSpread(_objectSpread(_objectSpread(_objectSpread({},Y),{},{onChange:this.onChange,value:Z},i),s),ee))}}]),DebounceInput}(m.default.PureComponent);s.DebounceInput=j,_defineProperty(j,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},775:(i,s,u)=>{"use strict";var m=u(53441).DebounceInput;m.DebounceInput=m,i.exports=m},64448:(i,s,u)=>{"use strict";var m=u(67294),v=u(27418),_=u(63840);function y(i){for(var s="https://reactjs.org/docs/error-decoder.html?invariant="+i,u=1;u