svod: свод 2
This commit is contained in:
parent
6f6f35c8b1
commit
4708acbd20
61
api/alembic/versions/0022_summary2_contract_rows.py
Normal file
61
api/alembic/versions/0022_summary2_contract_rows.py
Normal file
@ -0,0 +1,61 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
revision = "0022"
|
||||
down_revision = "0021"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
_DOLLAR_TAG_RE = re.compile(r"\$\w+\$")
|
||||
|
||||
|
||||
def _split_statements(sql: str) -> list[str]:
|
||||
statements: list[str] = []
|
||||
current: list[str] = []
|
||||
dollar_tag: str | None = None
|
||||
|
||||
for line in sql.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("--"):
|
||||
continue
|
||||
|
||||
if dollar_tag is None:
|
||||
match = _DOLLAR_TAG_RE.search(stripped)
|
||||
if match:
|
||||
dollar_tag = match.group(0)
|
||||
elif stripped.startswith(dollar_tag):
|
||||
dollar_tag = None
|
||||
|
||||
current.append(line)
|
||||
if dollar_tag is None and stripped.endswith(";"):
|
||||
statements.append("\n".join(current))
|
||||
current = []
|
||||
|
||||
if remaining := "\n".join(current).strip():
|
||||
statements.append(remaining)
|
||||
return statements
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
ddl_path = os.path.join(
|
||||
os.path.dirname(__file__), "sql", "0022_summary2_contract_rows.sql"
|
||||
)
|
||||
with open(ddl_path) as ddl_file:
|
||||
statements = _split_statements(ddl_file.read())
|
||||
for statement in statements:
|
||||
op.execute(statement.rstrip(";").strip())
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[], integer, integer, varchar, varchar)")
|
||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[], integer, integer, varchar, varchar)")
|
||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[], integer, integer)")
|
||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[], integer, integer)")
|
||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[])")
|
||||
op.execute("DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[])")
|
||||
op.execute("DROP INDEX IF EXISTS v3.ix_v3_budget_line_svod2")
|
||||
op.execute("DROP INDEX IF EXISTS v3.ix_v3_budget_form_svod2")
|
||||
@ -1238,50 +1238,10 @@ FROM rolled AS r;
|
||||
|
||||
DROP FUNCTION IF EXISTS v3.v_main_format(integer);
|
||||
|
||||
CREATE OR REPLACE FUNCTION v3.v_main_format(p_year integer, p_org_unit_ids integer[] DEFAULT NULL)
|
||||
RETURNS TABLE(
|
||||
section_code character varying,
|
||||
name character varying,
|
||||
plan_support_q1 numeric,
|
||||
plan_support_q2 numeric,
|
||||
plan_support_q3 numeric,
|
||||
plan_support_q4 numeric,
|
||||
plan_support_year numeric,
|
||||
plan_development_q1 numeric,
|
||||
plan_development_q2 numeric,
|
||||
plan_development_q3 numeric,
|
||||
plan_development_q4 numeric,
|
||||
plan_development_year numeric,
|
||||
plan_total_year numeric,
|
||||
approved_support_q1 numeric,
|
||||
approved_support_q2 numeric,
|
||||
approved_support_q3 numeric,
|
||||
approved_support_q4 numeric,
|
||||
approved_support_year numeric,
|
||||
approved_development_q1 numeric,
|
||||
approved_development_q2 numeric,
|
||||
approved_development_q3 numeric,
|
||||
approved_development_q4 numeric,
|
||||
approved_development_year numeric,
|
||||
approved_total_year numeric,
|
||||
fact_support_q1 numeric,
|
||||
fact_support_q2 numeric,
|
||||
fact_support_q3 numeric,
|
||||
fact_support_q4 numeric,
|
||||
fact_support_year numeric,
|
||||
fact_development_q1 numeric,
|
||||
fact_development_q2 numeric,
|
||||
fact_development_q3 numeric,
|
||||
fact_development_q4 numeric,
|
||||
fact_development_year numeric,
|
||||
fact_total_year numeric,
|
||||
corrected_support_q2 numeric,
|
||||
corrected_support_q3 numeric,
|
||||
corrected_support_q4 numeric,
|
||||
corrected_development_q2 numeric,
|
||||
corrected_development_q3 numeric,
|
||||
corrected_development_q4 numeric
|
||||
)
|
||||
-- DROP FUNCTION v3.v_main_format(int4, _int4);
|
||||
|
||||
CREATE OR REPLACE FUNCTION v3.v_main_format(p_year integer, p_org_unit_ids integer[] DEFAULT NULL::integer[])
|
||||
RETURNS TABLE(section_code character varying, name character varying, plan_support_q1 numeric, plan_support_q2 numeric, plan_support_q3 numeric, plan_support_q4 numeric, plan_support_year numeric, plan_development_q1 numeric, plan_development_q2 numeric, plan_development_q3 numeric, plan_development_q4 numeric, plan_development_year numeric, plan_total_year numeric, approved_support_q1 numeric, approved_support_q2 numeric, approved_support_q3 numeric, approved_support_q4 numeric, approved_support_year numeric, approved_development_q1 numeric, approved_development_q2 numeric, approved_development_q3 numeric, approved_development_q4 numeric, approved_development_year numeric, approved_total_year numeric, fact_support_q1 numeric, fact_support_q2 numeric, fact_support_q3 numeric, fact_support_q4 numeric, fact_support_year numeric, fact_development_q1 numeric, fact_development_q2 numeric, fact_development_q3 numeric, fact_development_q4 numeric, fact_development_year numeric, fact_total_year numeric, corrected_support_q2 numeric, corrected_support_q3 numeric, corrected_support_q4 numeric, corrected_development_q2 numeric, corrected_development_q3 numeric, corrected_development_q4 numeric)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SET search_path TO 'v3', 'pg_catalog'
|
||||
@ -1350,18 +1310,22 @@ summed AS (
|
||||
SELECT
|
||||
s.section_code, s.name,
|
||||
s.plan_support_q1, s.plan_support_q2, s.plan_support_q3, s.plan_support_q4, s.plan_support_year,
|
||||
s.approved_support_q1, s.approved_support_q2, s.approved_support_q3, s.approved_support_q4, s.approved_support_year,
|
||||
s.plan_development_q1, s.plan_development_q2, s.plan_development_q3, s.plan_development_q4, s.plan_development_year,
|
||||
s.plan_support_year + s.plan_development_year AS plan_total_year,
|
||||
s.fact_support_q1, s.fact_support_q2, s.fact_support_q3, s.fact_support_q4, s.fact_support_year,
|
||||
s.corrected_support_q2, s.corrected_support_q3, s.corrected_support_q4, s.plan_development_q1, s.plan_development_q2,
|
||||
s.approved_support_q1, s.approved_support_q2, s.approved_support_q3, s.approved_support_q4, s.approved_support_year,
|
||||
s.approved_development_q1, s.approved_development_q2, s.approved_development_q3, s.approved_development_q4, s.approved_development_year,
|
||||
s.approved_support_year + s.approved_development_year AS approved_total_year,
|
||||
s.plan_development_q3, s.plan_development_q4, s.plan_development_year, s.approved_development_q1, s.approved_development_q2,
|
||||
s.approved_development_q3, s.approved_development_q4, s.approved_development_year, s.fact_development_q1, s.fact_development_q2,
|
||||
|
||||
s.fact_support_q1, s.fact_support_q2, s.fact_support_q3, s.fact_support_q4, s.fact_support_year,
|
||||
s.fact_development_q1, s.fact_development_q2, s.fact_development_q3, s.fact_development_q4, s.fact_development_year,
|
||||
s.fact_support_year + s.fact_development_year AS fact_total_year,
|
||||
s.fact_development_q3, s.fact_development_q4, s.fact_development_year,
|
||||
|
||||
s.corrected_support_q2, s.corrected_support_q3, s.corrected_support_q4,
|
||||
s.corrected_development_q2, s.corrected_development_q3, s.corrected_development_q4
|
||||
FROM summed AS s;
|
||||
$function$;
|
||||
$function$
|
||||
;
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- main_format — VIEW
|
||||
|
||||
460
api/alembic/versions/sql/0022_summary2_contract_rows.sql
Normal file
460
api/alembic/versions/sql/0022_summary2_contract_rows.sql
Normal file
@ -0,0 +1,460 @@
|
||||
-- «Свод 2»: детальные строки без агрегации по статьям.
|
||||
-- ГО: FORM_1 + FORM_4; РФ: FORM_2 + FORM_3.
|
||||
-- Пагинация выполняется по узкому набору ключей до тяжёлых JOIN и сборки JSON.
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_v3_budget_form_svod2
|
||||
ON v3.budget_form (year, form_type_code, org_unit_id, id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_v3_budget_line_svod2
|
||||
ON v3.budget_line (budget_form_id, id) INCLUDE (expense_item_id);
|
||||
|
||||
DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[]);
|
||||
DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[], integer, integer);
|
||||
DROP FUNCTION IF EXISTS v3.v_svod2_go(integer, integer[], integer, integer, varchar, varchar);
|
||||
|
||||
CREATE OR REPLACE FUNCTION v3.v_svod2_go(
|
||||
p_year integer,
|
||||
p_org_unit_ids integer[] DEFAULT NULL,
|
||||
p_offset integer DEFAULT NULL,
|
||||
p_limit integer DEFAULT NULL,
|
||||
p_sort_by varchar DEFAULT 'org_name',
|
||||
p_sort_direction varchar DEFAULT 'asc'
|
||||
)
|
||||
RETURNS TABLE(
|
||||
source_form varchar, source_id integer, source_org_unit_id integer,
|
||||
org_name varchar, smeta_type varchar,
|
||||
source_line_id integer, sort_order bigint, data jsonb
|
||||
)
|
||||
LANGUAGE sql STABLE SET search_path TO 'v3', 'pg_catalog'
|
||||
AS $function$
|
||||
WITH empty_sections AS MATERIALIZED (
|
||||
SELECT
|
||||
(to_jsonb(jsonb_populate_record(NULL::v3.plan, '{}'::jsonb)) - 'line_id')
|
||||
|| jsonb_build_object('plan_year', NULL) AS plan,
|
||||
(to_jsonb(jsonb_populate_record(NULL::v3.sequestration, '{}'::jsonb))
|
||||
- 'id' - 'line_id' - 'actor') || jsonb_build_object('adj_year', NULL) AS sequestration,
|
||||
(to_jsonb(jsonb_populate_record(NULL::v3.reserve, '{}'::jsonb)) - 'line_id')
|
||||
|| jsonb_build_object('amount_year', NULL) AS reserve,
|
||||
jsonb_build_object(
|
||||
'approved_q1', NULL, 'approved_q2', NULL,
|
||||
'approved_q3', NULL, 'approved_q4', NULL,
|
||||
'approved_year', NULL
|
||||
) AS approved,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.contract_summary, '{}'::jsonb)) - 'line_id' AS contract_summary,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.allocation, '{}'::jsonb)) - 'line_id' AS allocation,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.collegial_approval, '{}'::jsonb)) - 'line_id' AS collegial,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.ckk, '{}'::jsonb)) - 'line_id' AS ckk,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.contract_detail, '{}'::jsonb)) - 'line_id' AS contract_detail,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.booking, '{}'::jsonb)) - 'id' - 'line_id' AS booking,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.budget_line_quarter, '{}'::jsonb))
|
||||
- 'line_id' - 'quarter' AS quarter
|
||||
), eligible_lines AS (
|
||||
SELECT
|
||||
bl.id AS line_id,
|
||||
bf.form_type_code,
|
||||
bf.id AS form_id,
|
||||
bf.org_unit_id,
|
||||
ou.title AS org_name,
|
||||
ei.section_code,
|
||||
ei.item_id,
|
||||
CASE ei.sheet WHEN 'AHR' THEN 'АХР' WHEN 'CAP' THEN 'КВП'
|
||||
WHEN 'OPER' THEN 'Операц' END::varchar AS smeta_type
|
||||
FROM v3.budget_form bf
|
||||
JOIN v3.budget_line bl ON bl.budget_form_id = bf.id
|
||||
JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
|
||||
JOIN v3.contract_detail cd ON cd.line_id = bl.id
|
||||
LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id
|
||||
WHERE bf.year = p_year
|
||||
AND bf.form_type_code IN ('FORM_1', 'FORM_4')
|
||||
AND ei.sheet IN ('AHR', 'CAP', 'OPER')
|
||||
AND (p_org_unit_ids IS NULL OR cardinality(p_org_unit_ids) = 0
|
||||
OR bf.org_unit_id = ANY(p_org_unit_ids))
|
||||
), numbered_lines AS (
|
||||
SELECT el.*,
|
||||
row_number() OVER (
|
||||
ORDER BY
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'asc'
|
||||
THEN el.org_name END ASC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'desc'
|
||||
THEN el.org_name END DESC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'asc'
|
||||
THEN el.item_id END ASC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'desc'
|
||||
THEN el.item_id END DESC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'section_code' AND lower(p_sort_direction) = 'asc'
|
||||
THEN el.section_code END ASC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'section_code' AND lower(p_sort_direction) = 'desc'
|
||||
THEN el.section_code END DESC NULLS LAST,
|
||||
el.form_type_code, el.form_id, el.line_id
|
||||
) AS sort_order
|
||||
FROM eligible_lines el
|
||||
), page AS MATERIALIZED (
|
||||
SELECT * FROM numbered_lines
|
||||
ORDER BY sort_order
|
||||
LIMIT p_limit OFFSET COALESCE(p_offset, 0)
|
||||
), booking_data AS (
|
||||
SELECT b.line_id,
|
||||
jsonb_agg(to_jsonb(b) - 'id' - 'line_id' ORDER BY b.quarter, b.source) AS booking
|
||||
FROM page pg
|
||||
JOIN v3.booking b ON b.line_id = pg.line_id
|
||||
GROUP BY b.line_id
|
||||
)
|
||||
SELECT
|
||||
pg.form_type_code,
|
||||
pg.form_id,
|
||||
pg.org_unit_id,
|
||||
pg.org_name,
|
||||
pg.smeta_type,
|
||||
pg.line_id,
|
||||
pg.sort_order,
|
||||
jsonb_build_object(
|
||||
'line_id', bl.id,
|
||||
'header', jsonb_build_object(
|
||||
'section_code', ei.section_code, 'item_id', ei.item_id,
|
||||
'num_group', ei.num_group_id, 'name', COALESCE(bl.name, ei.name),
|
||||
'expense_item_id', ei.id, 'justification', bl.justification,
|
||||
'internal_order', bl.internal_order, 'vsp_id', bl.vsp_id,
|
||||
'project_id', bl.project_id, 'direction', bl.direction,
|
||||
'smeta_direction', CASE bl.direction
|
||||
WHEN 'Support' THEN 'Поддержка'
|
||||
WHEN 'Development' THEN 'Развитие'
|
||||
ELSE NULL
|
||||
END,
|
||||
'source_form', pg.form_type_code, 'source_id', pg.form_id,
|
||||
'source_org_unit_id', pg.org_unit_id, 'org_name', pg.org_name,
|
||||
'smeta_type', pg.smeta_type),
|
||||
'plan', COALESCE(
|
||||
(to_jsonb(pl) - 'line_id') || jsonb_build_object(
|
||||
'plan_year', COALESCE(pl.plan_q1, 0) + COALESCE(pl.plan_q2, 0)
|
||||
+ COALESCE(pl.plan_q3, 0) + COALESCE(pl.plan_q4, 0)),
|
||||
e.plan || jsonb_build_object('plan_year', NULL)),
|
||||
'sequestration', jsonb_build_object(
|
||||
'DFIP', COALESCE(
|
||||
(to_jsonb(sd) - 'id' - 'line_id' - 'actor') || jsonb_build_object(
|
||||
'adj_year', COALESCE(sd.adj_q1, 0) + COALESCE(sd.adj_q2, 0)
|
||||
+ COALESCE(sd.adj_q3, 0) + COALESCE(sd.adj_q4, 0)),
|
||||
e.sequestration || jsonb_build_object('adj_year', NULL)),
|
||||
'SSP_GO', COALESCE(
|
||||
(to_jsonb(ss) - 'id' - 'line_id' - 'actor') || jsonb_build_object(
|
||||
'adj_year', COALESCE(ss.adj_q1, 0) + COALESCE(ss.adj_q2, 0)
|
||||
+ COALESCE(ss.adj_q3, 0) + COALESCE(ss.adj_q4, 0)),
|
||||
e.sequestration || jsonb_build_object('adj_year', NULL))),
|
||||
'reserve', COALESCE(
|
||||
(to_jsonb(rv) - 'line_id') || jsonb_build_object(
|
||||
'amount_year', COALESCE(rv.amount_q1, 0) + COALESCE(rv.amount_q2, 0)
|
||||
+ COALESCE(rv.amount_q3, 0) + COALESCE(rv.amount_q4, 0)),
|
||||
e.reserve || jsonb_build_object('amount_year', NULL)),
|
||||
'approved', CASE WHEN pl.line_id IS NULL AND sd.id IS NULL
|
||||
AND rv.line_id IS NULL
|
||||
THEN e.approved
|
||||
ELSE jsonb_build_object(
|
||||
'approved_q1', COALESCE(pl.plan_q1, 0) + COALESCE(sd.adj_q1, 0)
|
||||
+ COALESCE(rv.amount_q1, 0),
|
||||
'approved_q2', COALESCE(pl.plan_q2, 0) + COALESCE(sd.adj_q2, 0)
|
||||
+ COALESCE(rv.amount_q2, 0),
|
||||
'approved_q3', COALESCE(pl.plan_q3, 0) + COALESCE(sd.adj_q3, 0)
|
||||
+ COALESCE(rv.amount_q3, 0),
|
||||
'approved_q4', COALESCE(pl.plan_q4, 0) + COALESCE(sd.adj_q4, 0)
|
||||
+ COALESCE(rv.amount_q4, 0),
|
||||
'approved_year', COALESCE(pl.plan_q1, 0) + COALESCE(pl.plan_q2, 0)
|
||||
+ COALESCE(pl.plan_q3, 0) + COALESCE(pl.plan_q4, 0)
|
||||
+ COALESCE(sd.adj_q1, 0) + COALESCE(sd.adj_q2, 0)
|
||||
+ COALESCE(sd.adj_q3, 0) + COALESCE(sd.adj_q4, 0)
|
||||
+ COALESCE(rv.amount_q1, 0) + COALESCE(rv.amount_q2, 0)
|
||||
+ COALESCE(rv.amount_q3, 0) + COALESCE(rv.amount_q4, 0)) END,
|
||||
'contract_summary', COALESCE(to_jsonb(cs) - 'line_id', e.contract_summary),
|
||||
'allocation', COALESCE(to_jsonb(al) - 'line_id', e.allocation),
|
||||
'collegial', COALESCE(to_jsonb(ca) - 'line_id', e.collegial),
|
||||
'ckk', COALESCE(to_jsonb(ck) - 'line_id', e.ckk),
|
||||
'contract_detail', COALESCE(to_jsonb(cd) - 'line_id', e.contract_detail),
|
||||
'booking', COALESCE(bk.booking, jsonb_build_array(e.booking)),
|
||||
'q1', COALESCE(to_jsonb(q1) - 'line_id' - 'quarter', e.quarter),
|
||||
'q2', COALESCE(to_jsonb(q2) - 'line_id' - 'quarter', e.quarter),
|
||||
'q3', COALESCE(to_jsonb(q3) - 'line_id' - 'quarter', e.quarter),
|
||||
'q4', COALESCE(to_jsonb(q4) - 'line_id' - 'quarter', e.quarter)
|
||||
) AS data
|
||||
FROM page pg
|
||||
JOIN v3.budget_line bl ON bl.id = pg.line_id
|
||||
JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
|
||||
JOIN v3.contract_detail cd ON cd.line_id = bl.id
|
||||
CROSS JOIN empty_sections e
|
||||
LEFT JOIN v3.plan pl ON pl.line_id = bl.id
|
||||
LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor = 'DFIP'
|
||||
LEFT JOIN v3.sequestration ss ON ss.line_id = bl.id AND ss.actor = 'SSP_GO'
|
||||
LEFT JOIN v3.reserve rv ON rv.line_id = bl.id
|
||||
LEFT JOIN v3.contract_summary cs ON cs.line_id = bl.id
|
||||
LEFT JOIN v3.allocation al ON al.line_id = bl.id
|
||||
LEFT JOIN v3.collegial_approval ca ON ca.line_id = bl.id
|
||||
LEFT JOIN v3.ckk ck ON ck.line_id = bl.id
|
||||
LEFT JOIN booking_data bk ON bk.line_id = bl.id
|
||||
LEFT JOIN v3.budget_line_quarter q1 ON q1.line_id = bl.id AND q1.quarter = 1
|
||||
LEFT JOIN v3.budget_line_quarter q2 ON q2.line_id = bl.id AND q2.quarter = 2
|
||||
LEFT JOIN v3.budget_line_quarter q3 ON q3.line_id = bl.id AND q3.quarter = 3
|
||||
LEFT JOIN v3.budget_line_quarter q4 ON q4.line_id = bl.id AND q4.quarter = 4
|
||||
ORDER BY pg.sort_order;
|
||||
$function$;
|
||||
|
||||
COMMENT ON FUNCTION v3.v_svod2_go(integer, integer[], integer, integer, varchar, varchar) IS
|
||||
'Свод 2 / ГО: договорные строки FORM_1 и FORM_4; пагинация до сборки JSON';
|
||||
|
||||
DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[]);
|
||||
DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[], integer, integer);
|
||||
DROP FUNCTION IF EXISTS v3.v_svod2_rf(integer, integer[], integer, integer, varchar, varchar);
|
||||
|
||||
CREATE OR REPLACE FUNCTION v3.v_svod2_rf(
|
||||
p_year integer,
|
||||
p_org_unit_ids integer[] DEFAULT NULL,
|
||||
p_offset integer DEFAULT NULL,
|
||||
p_limit integer DEFAULT NULL,
|
||||
p_sort_by varchar DEFAULT 'org_name',
|
||||
p_sort_direction varchar DEFAULT 'asc'
|
||||
)
|
||||
RETURNS TABLE(
|
||||
source_form varchar, source_id integer, source_org_unit_id integer,
|
||||
org_name varchar, smeta_type varchar,
|
||||
source_line_id integer, sort_order bigint, data jsonb
|
||||
)
|
||||
LANGUAGE sql STABLE SET search_path TO 'v3', 'pg_catalog'
|
||||
AS $function$
|
||||
WITH empty_sections AS MATERIALIZED (
|
||||
SELECT
|
||||
(to_jsonb(jsonb_populate_record(NULL::v3.plan, '{}'::jsonb)) - 'line_id')
|
||||
|| jsonb_build_object('plan_year', NULL) AS plan,
|
||||
(to_jsonb(jsonb_populate_record(NULL::v3.sequestration, '{}'::jsonb))
|
||||
- 'id' - 'line_id' - 'actor') || jsonb_build_object('adj_year', NULL) AS sequestration,
|
||||
(to_jsonb(jsonb_populate_record(NULL::v3.reserve, '{}'::jsonb)) - 'line_id')
|
||||
|| jsonb_build_object('amount_year', NULL) AS reserve,
|
||||
jsonb_build_object(
|
||||
'approved_q1', NULL, 'approved_q2', NULL,
|
||||
'approved_q3', NULL, 'approved_q4', NULL,
|
||||
'approved_year', NULL
|
||||
) AS approved,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.contract_summary, '{}'::jsonb)) - 'line_id' AS contract_summary,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.allocation, '{}'::jsonb)) - 'line_id' AS allocation,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.collegial_approval, '{}'::jsonb)) - 'line_id' AS collegial,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.ckk, '{}'::jsonb)) - 'line_id' AS ckk,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.contract_detail, '{}'::jsonb)) - 'line_id' AS contract_detail,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.booking, '{}'::jsonb)) - 'id' - 'line_id' AS booking,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.budget_line_quarter, '{}'::jsonb))
|
||||
- 'line_id' - 'quarter' AS budget_quarter,
|
||||
to_jsonb(jsonb_populate_record(NULL::v3.rf_project_report_quarter, '{}'::jsonb))
|
||||
- 'rf_project_report_line_id' - 'quarter' AS report_quarter
|
||||
), eligible_lines AS (
|
||||
SELECT
|
||||
'FORM_2'::varchar AS form_type_code,
|
||||
bl.id AS line_id,
|
||||
bf.id AS source_id,
|
||||
bf.org_unit_id,
|
||||
ou.title AS org_name,
|
||||
ei.section_code,
|
||||
ei.item_id,
|
||||
CASE ei.sheet WHEN 'AHR' THEN 'АХР' WHEN 'CAP' THEN 'КВП'
|
||||
WHEN 'OPER' THEN 'Операц' END::varchar AS smeta_type
|
||||
FROM v3.budget_form bf
|
||||
JOIN v3.budget_line bl ON bl.budget_form_id = bf.id
|
||||
JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
|
||||
JOIN v3.contract_detail cd ON cd.line_id = bl.id
|
||||
LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id
|
||||
WHERE bf.year = p_year AND bf.form_type_code = 'FORM_2'
|
||||
AND ei.sheet IN ('AHR', 'CAP', 'OPER')
|
||||
AND (p_org_unit_ids IS NULL OR cardinality(p_org_unit_ids) = 0
|
||||
OR bf.org_unit_id = ANY(p_org_unit_ids))
|
||||
UNION ALL
|
||||
SELECT
|
||||
'FORM_3'::varchar,
|
||||
rrl.id,
|
||||
rr.id,
|
||||
p.org_unit_id,
|
||||
ou.title,
|
||||
ei.section_code,
|
||||
ei.item_id,
|
||||
CASE ei.sheet WHEN 'AHR' THEN 'АХР' WHEN 'CAP' THEN 'КВП'
|
||||
WHEN 'OPER' THEN 'Операц' END::varchar
|
||||
FROM v3.rf_project_report rr
|
||||
JOIN v3.rf_project_year rpy ON rpy.id = rr.rf_project_year_id
|
||||
JOIN v3.project p ON p.id = rr.project_id
|
||||
JOIN v3.rf_project_report_line rrl ON rrl.rf_project_report_id = rr.id
|
||||
JOIN v3.expense_item ei ON ei.id = rrl.expense_item_id
|
||||
LEFT JOIN v3.org_unit ou ON ou.id = p.org_unit_id
|
||||
WHERE rpy.year = p_year AND ei.sheet IN ('AHR', 'CAP', 'OPER')
|
||||
AND (p_org_unit_ids IS NULL OR cardinality(p_org_unit_ids) = 0
|
||||
OR p.org_unit_id = ANY(p_org_unit_ids))
|
||||
), numbered_lines AS (
|
||||
SELECT el.*,
|
||||
row_number() OVER (
|
||||
ORDER BY
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'asc'
|
||||
THEN el.org_name END ASC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'desc'
|
||||
THEN el.org_name END DESC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'asc'
|
||||
THEN el.item_id END ASC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'org_name' AND lower(p_sort_direction) = 'desc'
|
||||
THEN el.item_id END DESC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'section_code' AND lower(p_sort_direction) = 'asc'
|
||||
THEN el.section_code END ASC NULLS LAST,
|
||||
CASE WHEN p_sort_by = 'section_code' AND lower(p_sort_direction) = 'desc'
|
||||
THEN el.section_code END DESC NULLS LAST,
|
||||
el.form_type_code, el.source_id, el.line_id
|
||||
) AS sort_order
|
||||
FROM eligible_lines el
|
||||
), page AS MATERIALIZED (
|
||||
SELECT * FROM numbered_lines
|
||||
ORDER BY sort_order
|
||||
LIMIT p_limit OFFSET COALESCE(p_offset, 0)
|
||||
), booking_data AS (
|
||||
SELECT b.line_id,
|
||||
jsonb_agg(to_jsonb(b) - 'id' - 'line_id' ORDER BY b.quarter, b.source) AS booking
|
||||
FROM page pg
|
||||
JOIN v3.booking b ON b.line_id = pg.line_id
|
||||
WHERE pg.form_type_code = 'FORM_2'
|
||||
GROUP BY b.line_id
|
||||
), form2_data AS (
|
||||
SELECT
|
||||
pg.form_type_code AS source_form, pg.source_id,
|
||||
pg.org_unit_id AS source_org_unit_id, pg.org_name, pg.smeta_type,
|
||||
pg.line_id AS source_line_id, pg.sort_order,
|
||||
jsonb_build_object(
|
||||
'line_id', bl.id,
|
||||
'header', jsonb_build_object(
|
||||
'section_code', ei.section_code, 'item_id', ei.item_id,
|
||||
'num_group', ei.num_group_id, 'name', COALESCE(bl.name, ei.name),
|
||||
'expense_item_id', ei.id, 'justification', bl.justification,
|
||||
'internal_order', bl.internal_order, 'vsp_id', bl.vsp_id,
|
||||
'project_id', bl.project_id, 'direction', bl.direction,
|
||||
'smeta_direction', CASE bl.direction
|
||||
WHEN 'Support' THEN 'Поддержка'
|
||||
WHEN 'Development' THEN 'Развитие'
|
||||
ELSE NULL
|
||||
END,
|
||||
'source_form', pg.form_type_code, 'source_id', pg.source_id,
|
||||
'source_org_unit_id', pg.org_unit_id, 'org_name', pg.org_name,
|
||||
'smeta_type', pg.smeta_type),
|
||||
'plan', COALESCE(
|
||||
(to_jsonb(pl) - 'line_id') || jsonb_build_object(
|
||||
'plan_year', COALESCE(pl.plan_q1, 0) + COALESCE(pl.plan_q2, 0)
|
||||
+ COALESCE(pl.plan_q3, 0) + COALESCE(pl.plan_q4, 0)),
|
||||
e.plan || jsonb_build_object('plan_year', NULL)),
|
||||
'sequestration', jsonb_build_object(
|
||||
'DFIP', COALESCE(
|
||||
(to_jsonb(sd) - 'id' - 'line_id' - 'actor') || jsonb_build_object(
|
||||
'adj_year', COALESCE(sd.adj_q1, 0) + COALESCE(sd.adj_q2, 0)
|
||||
+ COALESCE(sd.adj_q3, 0) + COALESCE(sd.adj_q4, 0)),
|
||||
e.sequestration || jsonb_build_object('adj_year', NULL)),
|
||||
'SSP_GO', COALESCE(
|
||||
(to_jsonb(ss) - 'id' - 'line_id' - 'actor') || jsonb_build_object(
|
||||
'adj_year', COALESCE(ss.adj_q1, 0) + COALESCE(ss.adj_q2, 0)
|
||||
+ COALESCE(ss.adj_q3, 0) + COALESCE(ss.adj_q4, 0)),
|
||||
e.sequestration || jsonb_build_object('adj_year', NULL))),
|
||||
'reserve', COALESCE(
|
||||
(to_jsonb(rv) - 'line_id') || jsonb_build_object(
|
||||
'amount_year', COALESCE(rv.amount_q1, 0) + COALESCE(rv.amount_q2, 0)
|
||||
+ COALESCE(rv.amount_q3, 0) + COALESCE(rv.amount_q4, 0)),
|
||||
e.reserve || jsonb_build_object('amount_year', NULL)),
|
||||
'approved', CASE WHEN pl.line_id IS NULL AND sd.id IS NULL
|
||||
AND ss.id IS NULL AND rv.line_id IS NULL
|
||||
THEN e.approved
|
||||
ELSE jsonb_build_object(
|
||||
'approved_q1', COALESCE(pl.plan_q1, 0) + COALESCE(sd.adj_q1, 0)
|
||||
+ COALESCE(ss.adj_q1, 0) + COALESCE(rv.amount_q1, 0),
|
||||
'approved_q2', COALESCE(pl.plan_q2, 0) + COALESCE(sd.adj_q2, 0)
|
||||
+ COALESCE(ss.adj_q2, 0) + COALESCE(rv.amount_q2, 0),
|
||||
'approved_q3', COALESCE(pl.plan_q3, 0) + COALESCE(sd.adj_q3, 0)
|
||||
+ COALESCE(ss.adj_q3, 0) + COALESCE(rv.amount_q3, 0),
|
||||
'approved_q4', COALESCE(pl.plan_q4, 0) + COALESCE(sd.adj_q4, 0)
|
||||
+ COALESCE(ss.adj_q4, 0) + COALESCE(rv.amount_q4, 0),
|
||||
'approved_year', COALESCE(pl.plan_q1, 0) + COALESCE(pl.plan_q2, 0)
|
||||
+ COALESCE(pl.plan_q3, 0) + COALESCE(pl.plan_q4, 0)
|
||||
+ COALESCE(sd.adj_q1, 0) + COALESCE(sd.adj_q2, 0)
|
||||
+ COALESCE(sd.adj_q3, 0) + COALESCE(sd.adj_q4, 0)
|
||||
+ COALESCE(ss.adj_q1, 0) + COALESCE(ss.adj_q2, 0)
|
||||
+ COALESCE(ss.adj_q3, 0) + COALESCE(ss.adj_q4, 0)
|
||||
+ COALESCE(rv.amount_q1, 0) + COALESCE(rv.amount_q2, 0)
|
||||
+ COALESCE(rv.amount_q3, 0) + COALESCE(rv.amount_q4, 0)) END,
|
||||
'contract_summary', COALESCE(to_jsonb(cs) - 'line_id', e.contract_summary),
|
||||
'allocation', COALESCE(to_jsonb(al) - 'line_id', e.allocation),
|
||||
'collegial', COALESCE(to_jsonb(ca) - 'line_id', e.collegial),
|
||||
'ckk', COALESCE(to_jsonb(ck) - 'line_id', e.ckk),
|
||||
'contract_detail', COALESCE(to_jsonb(cd) - 'line_id', e.contract_detail),
|
||||
'booking', COALESCE(bk.booking, jsonb_build_array(e.booking)),
|
||||
'q1', COALESCE(to_jsonb(q1) - 'line_id' - 'quarter', e.budget_quarter),
|
||||
'q2', COALESCE(to_jsonb(q2) - 'line_id' - 'quarter', e.budget_quarter),
|
||||
'q3', COALESCE(to_jsonb(q3) - 'line_id' - 'quarter', e.budget_quarter),
|
||||
'q4', COALESCE(to_jsonb(q4) - 'line_id' - 'quarter', e.budget_quarter)
|
||||
) AS data
|
||||
FROM page pg
|
||||
JOIN v3.budget_line bl ON bl.id = pg.line_id
|
||||
JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
|
||||
JOIN v3.contract_detail cd ON cd.line_id = bl.id
|
||||
CROSS JOIN empty_sections e
|
||||
LEFT JOIN v3.plan pl ON pl.line_id = bl.id
|
||||
LEFT JOIN v3.sequestration sd ON sd.line_id = bl.id AND sd.actor = 'DFIP'
|
||||
LEFT JOIN v3.sequestration ss ON ss.line_id = bl.id AND ss.actor = 'SSP_GO'
|
||||
LEFT JOIN v3.reserve rv ON rv.line_id = bl.id
|
||||
LEFT JOIN v3.contract_summary cs ON cs.line_id = bl.id
|
||||
LEFT JOIN v3.allocation al ON al.line_id = bl.id
|
||||
LEFT JOIN v3.collegial_approval ca ON ca.line_id = bl.id
|
||||
LEFT JOIN v3.ckk ck ON ck.line_id = bl.id
|
||||
LEFT JOIN booking_data bk ON bk.line_id = bl.id
|
||||
LEFT JOIN v3.budget_line_quarter q1 ON q1.line_id = bl.id AND q1.quarter = 1
|
||||
LEFT JOIN v3.budget_line_quarter q2 ON q2.line_id = bl.id AND q2.quarter = 2
|
||||
LEFT JOIN v3.budget_line_quarter q3 ON q3.line_id = bl.id AND q3.quarter = 3
|
||||
LEFT JOIN v3.budget_line_quarter q4 ON q4.line_id = bl.id AND q4.quarter = 4
|
||||
WHERE pg.form_type_code = 'FORM_2'
|
||||
), form3_data AS (
|
||||
SELECT
|
||||
pg.form_type_code AS source_form, pg.source_id,
|
||||
pg.org_unit_id AS source_org_unit_id, pg.org_name, pg.smeta_type,
|
||||
pg.line_id AS source_line_id, pg.sort_order,
|
||||
jsonb_build_object(
|
||||
'line_id', rrl.id,
|
||||
'header', jsonb_build_object(
|
||||
'section_code', ei.section_code, 'item_id', ei.item_id,
|
||||
'num_group', ei.num_group_id, 'name', ei.name,
|
||||
'expense_item_id', ei.id, 'project_id', rr.project_id,
|
||||
'report_type', rr.report_type,
|
||||
'smeta_direction', NULL,
|
||||
'source_form', pg.form_type_code, 'source_id', pg.source_id,
|
||||
'source_org_unit_id', pg.org_unit_id, 'org_name', pg.org_name,
|
||||
'smeta_type', pg.smeta_type),
|
||||
'plan', e.plan,
|
||||
'sequestration', jsonb_build_object(
|
||||
'DFIP', e.sequestration, 'SSP_GO', e.sequestration),
|
||||
'reserve', e.reserve, 'contract_summary', e.contract_summary,
|
||||
'approved', e.approved,
|
||||
'allocation', e.allocation, 'collegial', e.collegial,
|
||||
'ckk', e.ckk, 'contract_detail', e.contract_detail,
|
||||
'booking', jsonb_build_array(e.booking),
|
||||
'q1', COALESCE(to_jsonb(q1) - 'rf_project_report_line_id' - 'quarter', e.report_quarter),
|
||||
'q2', COALESCE(to_jsonb(q2) - 'rf_project_report_line_id' - 'quarter', e.report_quarter),
|
||||
'q3', COALESCE(to_jsonb(q3) - 'rf_project_report_line_id' - 'quarter', e.report_quarter),
|
||||
'q4', COALESCE(to_jsonb(q4) - 'rf_project_report_line_id' - 'quarter', e.report_quarter)
|
||||
) AS data
|
||||
FROM page pg
|
||||
JOIN v3.rf_project_report_line rrl ON rrl.id = pg.line_id
|
||||
JOIN v3.rf_project_report rr ON rr.id = pg.source_id
|
||||
AND rr.id = rrl.rf_project_report_id
|
||||
JOIN v3.expense_item ei ON ei.id = rrl.expense_item_id
|
||||
CROSS JOIN empty_sections e
|
||||
LEFT JOIN v3.rf_project_report_quarter q1
|
||||
ON q1.rf_project_report_line_id = rrl.id AND q1.quarter = 1
|
||||
LEFT JOIN v3.rf_project_report_quarter q2
|
||||
ON q2.rf_project_report_line_id = rrl.id AND q2.quarter = 2
|
||||
LEFT JOIN v3.rf_project_report_quarter q3
|
||||
ON q3.rf_project_report_line_id = rrl.id AND q3.quarter = 3
|
||||
LEFT JOIN v3.rf_project_report_quarter q4
|
||||
ON q4.rf_project_report_line_id = rrl.id AND q4.quarter = 4
|
||||
WHERE pg.form_type_code = 'FORM_3'
|
||||
), result_rows AS (
|
||||
SELECT * FROM form2_data
|
||||
UNION ALL
|
||||
SELECT * FROM form3_data
|
||||
)
|
||||
SELECT rr.source_form, rr.source_id, rr.source_org_unit_id, rr.org_name, rr.smeta_type,
|
||||
rr.source_line_id, rr.sort_order, rr.data
|
||||
FROM result_rows rr
|
||||
ORDER BY rr.sort_order;
|
||||
$function$;
|
||||
|
||||
COMMENT ON FUNCTION v3.v_svod2_rf(integer, integer[], integer, integer, varchar, varchar) IS
|
||||
'Свод 2 / РФ: строки FORM_2 и FORM_3; пагинация до сборки JSON';
|
||||
@ -1,11 +1,11 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src.api.v1.deps import get_current_active_user_with_set_db
|
||||
from src.api.v1.deps import require_admin
|
||||
from src.db.models.app_user import AppUser
|
||||
from src.db.session import get_db
|
||||
from src.domain.schemas import BaseListResponse, SheetResponse
|
||||
@ -14,7 +14,7 @@ from src.services.summary_service import SummaryService
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
router = APIRouter(prefix="/svod/1", tags=["svod"])
|
||||
router = APIRouter(prefix="/svod", tags=["svod"])
|
||||
|
||||
VALID_SUMMARY_SHEETS = {
|
||||
"FORM_1",
|
||||
@ -23,6 +23,7 @@ VALID_SUMMARY_SHEETS = {
|
||||
"FORM_4",
|
||||
"MAIN",
|
||||
}
|
||||
VALID_SUMMARY2_SHEETS = {"GO", "RF"}
|
||||
|
||||
|
||||
def _rows_to_sheet_response(result: list[tuple]) -> list[SheetResponse]:
|
||||
@ -46,14 +47,14 @@ def _parse_org_ids(org_ids: Optional[str]) -> Optional[list[int]]:
|
||||
return parsed or None
|
||||
|
||||
|
||||
@router.get("/{sheet}/{year}")
|
||||
@router.get("/1/{sheet}/{year}")
|
||||
async def get_summary(
|
||||
sheet: str,
|
||||
year: int,
|
||||
response: Response,
|
||||
org_ids: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: AppUser = Depends(get_current_active_user_with_set_db),
|
||||
current_user: AppUser = Depends(require_admin),
|
||||
) -> BaseListResponse[SheetResponse]:
|
||||
if sheet not in VALID_SUMMARY_SHEETS:
|
||||
raise HTTPException(
|
||||
@ -86,3 +87,58 @@ async def get_summary(
|
||||
count=len(rows),
|
||||
result=_rows_to_sheet_response(rows),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/2/{sheet}/{year}")
|
||||
async def get_summary2(
|
||||
sheet: str,
|
||||
year: int,
|
||||
response: Response,
|
||||
org_ids: Optional[str] = None,
|
||||
offset: Optional[int] = Query(default=None, ge=0),
|
||||
limit: Optional[int] = Query(default=None, ge=1),
|
||||
sort_by: Literal["org_name", "section_code"] = "org_name",
|
||||
sort_direction: Literal["asc", "desc"] = "asc",
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: AppUser = Depends(require_admin),
|
||||
) -> BaseListResponse[SheetResponse]:
|
||||
sheet = sheet.upper()
|
||||
if sheet not in VALID_SUMMARY2_SHEETS:
|
||||
raise HTTPException(
|
||||
404,
|
||||
f"Лист свода {sheet} должен быть одним из: {', '.join(sorted(VALID_SUMMARY2_SHEETS))}",
|
||||
)
|
||||
|
||||
t0 = time.perf_counter()
|
||||
summary_service = SummaryService(db)
|
||||
rows = await summary_service.get_svod2_rows(
|
||||
year=year,
|
||||
sheet=sheet,
|
||||
org_ids=_parse_org_ids(org_ids),
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
sort_by=sort_by,
|
||||
sort_direction=sort_direction,
|
||||
)
|
||||
db_ms = (time.perf_counter() - t0) * 1000
|
||||
response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
|
||||
|
||||
extra = {
|
||||
"type": "db_time",
|
||||
"time_ms": f"{db_ms:.2f}",
|
||||
"handler": "get_summary2",
|
||||
"sheet": sheet,
|
||||
"year": year,
|
||||
"org_ids": org_ids,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"sort_by": sort_by,
|
||||
"sort_direction": sort_direction,
|
||||
"user": current_user.id,
|
||||
}
|
||||
logger.info(f"db_time: {extra}", extra=extra)
|
||||
|
||||
return BaseListResponse(
|
||||
count=len(rows),
|
||||
result=_rows_to_sheet_response(rows),
|
||||
)
|
||||
|
||||
@ -28,3 +28,49 @@ class SummaryRepository:
|
||||
{"year": year, "sheet": sheet, "org_ids": org_ids},
|
||||
)
|
||||
).all()
|
||||
|
||||
async def get_svod2_rows(
|
||||
self,
|
||||
year: int,
|
||||
sheet: str,
|
||||
org_ids: list[int] | None = None,
|
||||
offset: int | None = None,
|
||||
limit: int | None = None,
|
||||
sort_by: str = "org_name",
|
||||
sort_direction: str = "asc",
|
||||
) -> list[tuple]:
|
||||
function_name = {
|
||||
"GO": "v3.v_svod2_go",
|
||||
"RF": "v3.v_svod2_rf",
|
||||
}[sheet]
|
||||
query = text(
|
||||
f"""
|
||||
SELECT
|
||||
'INPUT'::VARCHAR AS row_type,
|
||||
3 AS depth,
|
||||
sort_order,
|
||||
data
|
||||
FROM {function_name}(
|
||||
CAST(:year AS INT),
|
||||
CAST(:org_ids AS INT[]),
|
||||
CAST(:offset AS INT),
|
||||
CAST(:limit AS INT),
|
||||
CAST(:sort_by AS VARCHAR),
|
||||
CAST(:sort_direction AS VARCHAR)
|
||||
)
|
||||
ORDER BY sort_order
|
||||
"""
|
||||
)
|
||||
return (
|
||||
await self.db.execute(
|
||||
query,
|
||||
{
|
||||
"year": year,
|
||||
"org_ids": org_ids,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"sort_by": sort_by,
|
||||
"sort_direction": sort_direction,
|
||||
},
|
||||
)
|
||||
).all()
|
||||
|
||||
@ -19,3 +19,23 @@ class SummaryService:
|
||||
sheet=sheet,
|
||||
org_ids=org_ids,
|
||||
)
|
||||
|
||||
async def get_svod2_rows(
|
||||
self,
|
||||
year: int,
|
||||
sheet: str,
|
||||
org_ids: list[int] | None = None,
|
||||
offset: int | None = None,
|
||||
limit: int | None = None,
|
||||
sort_by: str = "org_name",
|
||||
sort_direction: str = "asc",
|
||||
) -> list[tuple]:
|
||||
return await self.summary_repo.get_svod2_rows(
|
||||
year=year,
|
||||
sheet=sheet,
|
||||
org_ids=org_ids,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
sort_by=sort_by,
|
||||
sort_direction=sort_direction,
|
||||
)
|
||||
|
||||
@ -38,3 +38,104 @@ def test_summary_sheet_unknown(client, admin_tokens, auth_headers):
|
||||
headers=auth_headers(admin_tokens),
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sheet", ["GO", "RF"])
|
||||
def test_summary2_sheets_smoke(client, admin_tokens, auth_headers, sheet):
|
||||
response = client.get(
|
||||
f"/api/v1/svod/2/{sheet}/2026",
|
||||
headers=auth_headers(admin_tokens),
|
||||
)
|
||||
assert response.status_code == 200, f"sheet={sheet}: {response.text}"
|
||||
payload = response.json()
|
||||
assert "result" in payload
|
||||
assert "count" in payload
|
||||
assert isinstance(payload["result"], list)
|
||||
if payload["result"]:
|
||||
row = payload["result"][0]
|
||||
assert row["row_type"] == "INPUT"
|
||||
assert row["depth"] == 3
|
||||
assert "sort_order" in row
|
||||
assert "data" in row
|
||||
data = row["data"]
|
||||
header = data["header"]
|
||||
assert {
|
||||
"org_name",
|
||||
"source_id",
|
||||
"smeta_type",
|
||||
"source_form",
|
||||
"source_org_unit_id",
|
||||
"smeta_direction",
|
||||
} <= header.keys()
|
||||
assert "source_sheet" not in data
|
||||
assert "source_sheet" not in header
|
||||
assert {
|
||||
"plan",
|
||||
"sequestration",
|
||||
"reserve",
|
||||
"contract_summary",
|
||||
"allocation",
|
||||
"collegial",
|
||||
"ckk",
|
||||
"contract_detail",
|
||||
"booking",
|
||||
"q1",
|
||||
"q2",
|
||||
"q3",
|
||||
"q4",
|
||||
} <= data.keys()
|
||||
|
||||
|
||||
def test_summary2_sheet_with_org_ids(client, admin_tokens, auth_headers):
|
||||
response = client.get(
|
||||
"/api/v1/svod/2/GO/2026?org_ids=1,2,3",
|
||||
headers=auth_headers(admin_tokens),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_summary2_pagination(client, admin_tokens, auth_headers):
|
||||
response = client.get(
|
||||
"/api/v1/svod/2/GO/2026?offset=0&limit=2",
|
||||
headers=auth_headers(admin_tokens),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["count"] <= 2
|
||||
assert len(payload["result"]) <= 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sort_by", ["org_name", "section_code"])
|
||||
@pytest.mark.parametrize("sort_direction", ["asc", "desc"])
|
||||
def test_summary2_sorting(
|
||||
client,
|
||||
admin_tokens,
|
||||
auth_headers,
|
||||
sort_by,
|
||||
sort_direction,
|
||||
):
|
||||
response = client.get(
|
||||
f"/api/v1/svod/2/GO/2026?limit=2&sort_by={sort_by}&sort_direction={sort_direction}",
|
||||
headers=auth_headers(admin_tokens),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"query",
|
||||
["offset=-1", "limit=0", "sort_by=bad", "sort_direction=bad"],
|
||||
)
|
||||
def test_summary2_invalid_pagination(client, admin_tokens, auth_headers, query):
|
||||
response = client.get(
|
||||
f"/api/v1/svod/2/GO/2026?{query}",
|
||||
headers=auth_headers(admin_tokens),
|
||||
)
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
def test_summary2_sheet_unknown(client, admin_tokens, auth_headers):
|
||||
response = client.get(
|
||||
"/api/v1/svod/2/unknown/2026",
|
||||
headers=auth_headers(admin_tokens),
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user