Compare commits
No commits in common. "056d00b2b4a71a6375b3f90f442bd3503bf6a431" and "d5728c8e081422073579595287e8ad97bbdbc2b7" have entirely different histories.
056d00b2b4
...
d5728c8e08
@ -1,15 +0,0 @@
|
|||||||
from sql_migration import run_sql_file_migration
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0037"
|
|
||||||
down_revision = "0036"
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
run_sql_file_migration("0037_form2_ahr_limit_depth_fix.sql")
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
from sql_migration import run_sql_file_migration
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0038"
|
|
||||||
down_revision = "0037"
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
run_sql_file_migration("0038_form3_expense_item_links.sql")
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
from sql_migration import run_sql_file_migration
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0039"
|
|
||||||
down_revision = "0038"
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
run_sql_file_migration("0039_budget_form_delete_workflow.sql")
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
from sql_migration import run_sql_file_migration
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0040"
|
|
||||||
down_revision = "0039"
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
run_sql_file_migration("0040_ahr_limit_delete_guard.sql")
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
from sql_migration import run_sql_file_migration
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0041"
|
|
||||||
down_revision = "0040"
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
run_sql_file_migration("0041_svod2_rf_form2_only.sql")
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
-- After 0033 some AHR expense items with num_group_id became depth=4.
|
|
||||||
-- AHR_LIMIT synchronization still required depth=3, so limit rows such as
|
|
||||||
-- "принтеры", "МФУ", and "копировальные аппараты" stopped matching
|
|
||||||
-- expense_item.num_group_id = '204620030'.
|
|
||||||
--
|
|
||||||
-- The same three limit rows also use a special quarterly amount formula:
|
|
||||||
-- qty * 0.57 * 10900 / 4. Other limit rows keep the previous formula.
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3._form2_ahr_limit_amount(
|
|
||||||
p_name character varying,
|
|
||||||
p_unit character varying,
|
|
||||||
p_limit_with_vat numeric,
|
|
||||||
p_qty numeric
|
|
||||||
)
|
|
||||||
RETURNS numeric
|
|
||||||
LANGUAGE sql
|
|
||||||
IMMUTABLE
|
|
||||||
AS $function$
|
|
||||||
SELECT COALESCE(p_qty, 0) *
|
|
||||||
CASE
|
|
||||||
WHEN regexp_replace(
|
|
||||||
COALESCE(p_name, ''),
|
|
||||||
'^[[:space:]]*-[[:space:]]*',
|
|
||||||
''
|
|
||||||
) IN ('принтеры', 'МФУ', 'копировальные аппараты')
|
|
||||||
THEN 0.57 * 10900 / 4
|
|
||||||
ELSE COALESCE(p_limit_with_vat, 0) *
|
|
||||||
CASE
|
|
||||||
WHEN p_unit LIKE '%/год' THEN 0.25
|
|
||||||
WHEN p_unit LIKE '%/месяц' THEN 3
|
|
||||||
ELSE 1
|
|
||||||
END
|
|
||||||
END;
|
|
||||||
$function$;
|
|
||||||
|
|
||||||
DO $do$
|
|
||||||
DECLARE
|
|
||||||
v_signature regprocedure;
|
|
||||||
v_sql text;
|
|
||||||
v_next_sql text;
|
|
||||||
BEGIN
|
|
||||||
FOREACH v_signature IN ARRAY ARRAY[
|
|
||||||
'v3.v_form2_view(integer,character varying,text[])'::regprocedure,
|
|
||||||
'v3.sync_form2_ahr_satellite_lines(integer)'::regprocedure,
|
|
||||||
'v3.sync_form2_ahr_satellite_line(integer,character varying,integer)'::regprocedure
|
|
||||||
]
|
|
||||||
LOOP
|
|
||||||
v_sql := pg_get_functiondef(v_signature);
|
|
||||||
|
|
||||||
v_next_sql := regexp_replace(
|
|
||||||
v_sql,
|
|
||||||
$re$COALESCE\(fl\.qty_q([1-4]),[[:space:]]*0\)[[:space:]]*\*[[:space:]]*COALESCE\(lt\.limit_with_vat,[[:space:]]*0\)[[:space:]]*\*[[:space:]]*\(?[[:space:]]*CASE[[:space:]]+WHEN[[:space:]]+lt\.unit[[:space:]]+LIKE[[:space:]]+'%/год'[[:space:]]+THEN[[:space:]]+0\.25[[:space:]]+WHEN[[:space:]]+lt\.unit[[:space:]]+LIKE[[:space:]]+'%/месяц'[[:space:]]+THEN[[:space:]]+3[[:space:]]+ELSE[[:space:]]+1[[:space:]]+END[[:space:]]*\)?$re$,
|
|
||||||
$replace$v3._form2_ahr_limit_amount(lt.name, lt.unit, lt.limit_with_vat, fl.qty_q\1)$replace$,
|
|
||||||
'g'
|
|
||||||
);
|
|
||||||
|
|
||||||
IF v_next_sql = v_sql THEN
|
|
||||||
RAISE EXCEPTION 'Expected AHR limit amount expression was not found in %', v_signature;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
EXECUTE v_next_sql;
|
|
||||||
END LOOP;
|
|
||||||
END;
|
|
||||||
$do$;
|
|
||||||
|
|
||||||
DO $do$
|
|
||||||
DECLARE
|
|
||||||
v_signature regprocedure;
|
|
||||||
v_sql text;
|
|
||||||
v_next_sql text;
|
|
||||||
BEGIN
|
|
||||||
FOREACH v_signature IN ARRAY ARRAY[
|
|
||||||
'v3.sync_form2_ahr_satellite_lines(integer)'::regprocedure,
|
|
||||||
'v3.sync_form2_ahr_satellite_line(integer,character varying,integer)'::regprocedure,
|
|
||||||
'v3.get_form2_ahr_satellite_delta(integer,character varying,integer)'::regprocedure,
|
|
||||||
'v3.is_form2_ahr_satellite_line(integer,integer)'::regprocedure
|
|
||||||
]
|
|
||||||
LOOP
|
|
||||||
v_sql := pg_get_functiondef(v_signature);
|
|
||||||
|
|
||||||
v_next_sql := regexp_replace(
|
|
||||||
v_sql,
|
|
||||||
'AND\s+ei\.depth\s*=\s*3\s+AND\s*\(\s*ei\.item_id\s*=\s*lt\.expense_item_code\s+OR\s+ei\.num_group_id\s*=\s*lt\.expense_item_code\s*\)',
|
|
||||||
E'AND (\n (ei.depth = 3 AND ei.item_id = lt.expense_item_code)\n OR ei.num_group_id = lt.expense_item_code\n )',
|
|
||||||
'g'
|
|
||||||
);
|
|
||||||
|
|
||||||
IF v_next_sql = v_sql THEN
|
|
||||||
IF v_sql ~ 'ei\.depth[[:space:]]*=[[:space:]]*3[[:space:]]+AND[[:space:]]+ei\.item_id[[:space:]]*=[[:space:]]*lt\.expense_item_code[[:space:]]*\)[[:space:]]+OR[[:space:]]+ei\.num_group_id[[:space:]]*=[[:space:]]*lt\.expense_item_code' THEN
|
|
||||||
CONTINUE;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RAISE EXCEPTION 'Expected depth=3 limit-template matcher was not found in %', v_signature;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
EXECUTE v_next_sql;
|
|
||||||
END LOOP;
|
|
||||||
END;
|
|
||||||
$do$;
|
|
||||||
|
|
||||||
DO $do$
|
|
||||||
DECLARE
|
|
||||||
v_form_id integer;
|
|
||||||
BEGIN
|
|
||||||
FOR v_form_id IN
|
|
||||||
SELECT id
|
|
||||||
FROM v3.budget_form
|
|
||||||
WHERE form_type_code = 'FORM_2'
|
|
||||||
LOOP
|
|
||||||
PERFORM v3.sync_form2_ahr_satellite_lines(v_form_id);
|
|
||||||
END LOOP;
|
|
||||||
END;
|
|
||||||
$do$;
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
insert into v3.expense_item_form_type (expense_item_id, form_type_code)
|
|
||||||
values
|
|
||||||
(84, 'FORM_3'),
|
|
||||||
(85, 'FORM_3'),
|
|
||||||
(86, 'FORM_3'),
|
|
||||||
(87, 'FORM_3'),
|
|
||||||
(88, 'FORM_3'),
|
|
||||||
(89, 'FORM_3'),
|
|
||||||
(90, 'FORM_3'),
|
|
||||||
(91, 'FORM_3'),
|
|
||||||
(92, 'FORM_3'),
|
|
||||||
(93, 'FORM_3'),
|
|
||||||
(94, 'FORM_3'),
|
|
||||||
(95, 'FORM_3'),
|
|
||||||
(97, 'FORM_3'),
|
|
||||||
(98, 'FORM_3'),
|
|
||||||
(99, 'FORM_3'),
|
|
||||||
(100, 'FORM_3'),
|
|
||||||
(101, 'FORM_3'),
|
|
||||||
(83, 'FORM_3'),
|
|
||||||
(96, 'FORM_3')
|
|
||||||
on conflict (expense_item_id, form_type_code) do nothing;
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
ALTER TABLE v3.budget_form
|
|
||||||
ADD COLUMN IF NOT EXISTS to_delete boolean NOT NULL DEFAULT false,
|
|
||||||
ADD COLUMN IF NOT EXISTS deleted_by integer NULL REFERENCES v3.app_user(id),
|
|
||||||
ADD COLUMN IF NOT EXISTS deleted_at timestamp with time zone NULL;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS ix_v3_budget_form_to_delete
|
|
||||||
ON v3.budget_form (to_delete);
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.hard_delete_budget_form(p_form_id integer)
|
|
||||||
RETURNS void
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
AS $function$
|
|
||||||
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;
|
|
||||||
|
|
||||||
DELETE FROM v3.booking
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.sequestration
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.budget_line_quarter
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.rent_detail
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.utility_detail
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.security_detail
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.contract_detail
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.contract_summary
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.plan
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.reserve
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.allocation
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.ckk
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.collegial_approval
|
|
||||||
WHERE line_id IN (SELECT id FROM v3.budget_line WHERE budget_form_id = p_form_id);
|
|
||||||
|
|
||||||
DELETE FROM v3.fixed_asset_report
|
|
||||||
WHERE budget_form_id = p_form_id;
|
|
||||||
|
|
||||||
DELETE FROM v3.form_limit
|
|
||||||
WHERE budget_form_id = p_form_id;
|
|
||||||
|
|
||||||
DELETE FROM v3.form_phase
|
|
||||||
WHERE budget_form_id = p_form_id;
|
|
||||||
|
|
||||||
DELETE FROM v3.budget_line
|
|
||||||
WHERE budget_form_id = p_form_id;
|
|
||||||
|
|
||||||
DELETE FROM v3.form4_project
|
|
||||||
WHERE form_id = p_form_id;
|
|
||||||
|
|
||||||
DELETE FROM v3.budget_form
|
|
||||||
WHERE id = p_form_id;
|
|
||||||
END;
|
|
||||||
$function$;
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
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_line_id INT;
|
|
||||||
v_ei_path INT[];
|
|
||||||
BEGIN
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
SELECT m.target_line_id
|
|
||||||
INTO v_line_id
|
|
||||||
FROM v3.form2_ahr_satellite_line_source m
|
|
||||||
WHERE m.source_sheet = p_sheet
|
|
||||||
AND m.source_id = p_id;
|
|
||||||
|
|
||||||
EXECUTE format('DELETE FROM v3.%I WHERE id = $1', v_sat_table) USING p_id;
|
|
||||||
|
|
||||||
IF v_line_id IS NOT NULL THEN
|
|
||||||
SELECT bl.expense_item_id
|
|
||||||
INTO v_eid
|
|
||||||
FROM v3.budget_line bl
|
|
||||||
WHERE bl.id = v_line_id;
|
|
||||||
|
|
||||||
DELETE FROM v3.plan WHERE line_id = v_line_id;
|
|
||||||
DELETE FROM v3.budget_line_quarter WHERE line_id = v_line_id;
|
|
||||||
DELETE FROM v3.budget_line WHERE id = v_line_id;
|
|
||||||
|
|
||||||
SELECT org_unit_id INTO v_org_id FROM v3.budget_form WHERE id = v_form_id;
|
|
||||||
|
|
||||||
PERFORM v3.log_event(
|
|
||||||
'ROW_DELETE', 'ROW',
|
|
||||||
jsonb_build_object(
|
|
||||||
'sheet', 'AHR',
|
|
||||||
'line_id', v_line_id,
|
|
||||||
'entity_id', v_line_id,
|
|
||||||
'expense_item_id', v_eid,
|
|
||||||
'direction', 'Support',
|
|
||||||
'source_sheet', p_sheet,
|
|
||||||
'source_id', p_id
|
|
||||||
),
|
|
||||||
v_form_id, NULL, v_org_id
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
RETURN;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
IF v_sheet = 'AHR' AND (
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM v3.form2_ahr_satellite_line_source m
|
|
||||||
WHERE m.target_line_id = p_id
|
|
||||||
)
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM v3.budget_line bl
|
|
||||||
JOIN v3.expense_item ei ON ei.id = bl.expense_item_id
|
|
||||||
JOIN v3.limit_template lt
|
|
||||||
ON lt.row_type = 'LEAF'
|
|
||||||
AND (
|
|
||||||
(ei.depth = 3 AND ei.item_id = lt.expense_item_code)
|
|
||||||
OR ei.num_group_id = lt.expense_item_code
|
|
||||||
)
|
|
||||||
WHERE bl.id = p_id
|
|
||||||
AND bl.budget_form_id = v_form_id
|
|
||||||
AND ei.sheet = 'AHR'
|
|
||||||
AND ei.direction = 'Support'
|
|
||||||
)
|
|
||||||
) THEN
|
|
||||||
RAISE EXCEPTION 'Строку АХР, созданную из листа-сателлита или АХР Лимиты, нельзя удалить напрямую'
|
|
||||||
USING ERRCODE = '23503',
|
|
||||||
HINT = 'Удалите строку на исходном листе АХР Аренда / АХР Коммунальные услуги / АХР Безопасность либо измените строку на листе АХР Лимиты';
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
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$;
|
|
||||||
|
|
||||||
@ -1,299 +0,0 @@
|
|||||||
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
|
|
||||||
), 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))
|
|
||||||
), 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
|
|
||||||
GROUP BY b.line_id
|
|
||||||
)
|
|
||||||
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
|
|
||||||
ORDER BY pg.sort_order;
|
|
||||||
$function$;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.v_3form_rf_r(p_year integer, p_org_unit_ids integer[] DEFAULT NULL)
|
|
||||||
RETURNS TABLE(
|
|
||||||
section_code character varying,
|
|
||||||
name character varying,
|
|
||||||
plan_development_q1 numeric,
|
|
||||||
plan_development_q2 numeric,
|
|
||||||
plan_development_q3 numeric,
|
|
||||||
plan_development_q4 numeric,
|
|
||||||
plan_development_year numeric,
|
|
||||||
approved_development_q1 numeric,
|
|
||||||
approved_development_q2 numeric,
|
|
||||||
approved_development_q3 numeric,
|
|
||||||
approved_development_q4 numeric,
|
|
||||||
approved_development_year numeric,
|
|
||||||
fact_development_q1 numeric,
|
|
||||||
fact_development_q2 numeric,
|
|
||||||
fact_development_q3 numeric,
|
|
||||||
fact_development_q4 numeric,
|
|
||||||
fact_development_year numeric,
|
|
||||||
corrected_development_q2 numeric,
|
|
||||||
corrected_development_q3 numeric,
|
|
||||||
corrected_development_q4 numeric
|
|
||||||
)
|
|
||||||
LANGUAGE sql
|
|
||||||
STABLE
|
|
||||||
SET search_path TO 'v3', 'pg_catalog'
|
|
||||||
AS $function$
|
|
||||||
WITH
|
|
||||||
report_lines AS (
|
|
||||||
SELECT
|
|
||||||
l.expense_item_id AS eid,
|
|
||||||
q.quarter,
|
|
||||||
q.base_plan,
|
|
||||||
q.actual_m1,
|
|
||||||
q.actual_m2,
|
|
||||||
q.actual_m3,
|
|
||||||
q.actual_spod,
|
|
||||||
q.corrected_plan
|
|
||||||
FROM v3.rf_project_report AS r
|
|
||||||
JOIN v3.rf_project_year AS rpry ON rpry.id = r.rf_project_year_id
|
|
||||||
JOIN v3.project AS p ON p.id = r.project_id
|
|
||||||
JOIN v3.rf_project_report_line AS l ON l.rf_project_report_id = r.id
|
|
||||||
JOIN v3.rf_project_report_quarter AS q ON q.rf_project_report_line_id = l.id
|
|
||||||
WHERE rpry.year = p_year
|
|
||||||
AND (p_org_unit_ids IS NULL OR cardinality(p_org_unit_ids) = 0 OR p.org_unit_id = ANY(p_org_unit_ids))
|
|
||||||
),
|
|
||||||
agg AS (
|
|
||||||
SELECT
|
|
||||||
eid,
|
|
||||||
SUM(CASE WHEN quarter = 1 THEN base_plan END) AS bp1,
|
|
||||||
SUM(CASE WHEN quarter = 2 THEN base_plan END) AS bp2,
|
|
||||||
SUM(CASE WHEN quarter = 3 THEN base_plan END) AS bp3,
|
|
||||||
SUM(CASE WHEN quarter = 4 THEN base_plan END) AS bp4,
|
|
||||||
SUM(CASE WHEN quarter = 1 THEN actual_m1 END) AS m1_1,
|
|
||||||
SUM(CASE WHEN quarter = 2 THEN actual_m1 END) AS m1_2,
|
|
||||||
SUM(CASE WHEN quarter = 3 THEN actual_m1 END) AS m1_3,
|
|
||||||
SUM(CASE WHEN quarter = 4 THEN actual_m1 END) AS m1_4,
|
|
||||||
SUM(CASE WHEN quarter = 1 THEN actual_m2 END) AS m2_1,
|
|
||||||
SUM(CASE WHEN quarter = 2 THEN actual_m2 END) AS m2_2,
|
|
||||||
SUM(CASE WHEN quarter = 3 THEN actual_m2 END) AS m2_3,
|
|
||||||
SUM(CASE WHEN quarter = 4 THEN actual_m2 END) AS m2_4,
|
|
||||||
SUM(CASE WHEN quarter = 1 THEN actual_m3 END) AS m3_1,
|
|
||||||
SUM(CASE WHEN quarter = 2 THEN actual_m3 END) AS m3_2,
|
|
||||||
SUM(CASE WHEN quarter = 3 THEN actual_m3 END) AS m3_3,
|
|
||||||
SUM(CASE WHEN quarter = 4 THEN actual_m3 END) AS m3_4,
|
|
||||||
SUM(CASE WHEN quarter = 4 THEN actual_spod END) AS spod4,
|
|
||||||
SUM(CASE WHEN quarter = 2 THEN corrected_plan END) AS cp2,
|
|
||||||
SUM(CASE WHEN quarter = 3 THEN corrected_plan END) AS cp3,
|
|
||||||
SUM(CASE WHEN quarter = 4 THEN corrected_plan END) AS cp4
|
|
||||||
FROM report_lines
|
|
||||||
GROUP BY eid
|
|
||||||
),
|
|
||||||
tree_nodes AS (
|
|
||||||
SELECT t.id, t.item_id, t.name, t.desc_ids
|
|
||||||
FROM v3.mv_expense_item_tree AS t
|
|
||||||
JOIN v3.expense_item_form_type AS eft
|
|
||||||
ON eft.expense_item_id = t.id
|
|
||||||
AND eft.form_type_code = 'FORM_3'
|
|
||||||
WHERE t.depth < 3
|
|
||||||
),
|
|
||||||
rolled AS (
|
|
||||||
SELECT
|
|
||||||
tn.item_id,
|
|
||||||
tn.name,
|
|
||||||
SUM(a.bp1) AS bp1, SUM(a.bp2) AS bp2, SUM(a.bp3) AS bp3, SUM(a.bp4) AS bp4,
|
|
||||||
SUM(a.m1_1) AS m1_1, SUM(a.m1_2) AS m1_2, SUM(a.m1_3) AS m1_3, SUM(a.m1_4) AS m1_4,
|
|
||||||
SUM(a.m2_1) AS m2_1, SUM(a.m2_2) AS m2_2, SUM(a.m2_3) AS m2_3, SUM(a.m2_4) AS m2_4,
|
|
||||||
SUM(a.m3_1) AS m3_1, SUM(a.m3_2) AS m3_2, SUM(a.m3_3) AS m3_3, SUM(a.m3_4) AS m3_4,
|
|
||||||
SUM(a.spod4) AS spod4,
|
|
||||||
SUM(a.cp2) AS cp2, SUM(a.cp3) AS cp3, SUM(a.cp4) AS cp4
|
|
||||||
FROM tree_nodes AS tn
|
|
||||||
LEFT JOIN agg AS a ON a.eid = ANY (tn.desc_ids)
|
|
||||||
GROUP BY tn.item_id, tn.name
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
r.item_id AS section_code,
|
|
||||||
r.name,
|
|
||||||
COALESCE(r.bp1, 0) AS plan_development_q1,
|
|
||||||
COALESCE(r.bp2, 0) AS plan_development_q2,
|
|
||||||
COALESCE(r.bp3, 0) AS plan_development_q3,
|
|
||||||
COALESCE(r.bp4, 0) AS plan_development_q4,
|
|
||||||
COALESCE(r.bp1, 0) + COALESCE(r.bp2, 0) + COALESCE(r.bp3, 0) + COALESCE(r.bp4, 0) AS plan_development_year,
|
|
||||||
COALESCE(r.bp1, 0) AS approved_development_q1,
|
|
||||||
COALESCE(r.bp2, 0) AS approved_development_q2,
|
|
||||||
COALESCE(r.bp3, 0) AS approved_development_q3,
|
|
||||||
COALESCE(r.bp4, 0) AS approved_development_q4,
|
|
||||||
COALESCE(r.bp1, 0) + COALESCE(r.bp2, 0) + COALESCE(r.bp3, 0) + COALESCE(r.bp4, 0) AS approved_development_year,
|
|
||||||
COALESCE(r.m1_1, 0) + COALESCE(r.m2_1, 0) + COALESCE(r.m3_1, 0) AS fact_development_q1,
|
|
||||||
COALESCE(r.m1_2, 0) + COALESCE(r.m2_2, 0) + COALESCE(r.m3_2, 0) AS fact_development_q2,
|
|
||||||
COALESCE(r.m1_3, 0) + COALESCE(r.m2_3, 0) + COALESCE(r.m3_3, 0) AS fact_development_q3,
|
|
||||||
COALESCE(r.m1_4, 0) + COALESCE(r.m2_4, 0) + COALESCE(r.m3_4, 0) + COALESCE(r.spod4, 0) AS fact_development_q4,
|
|
||||||
COALESCE(r.m1_1, 0) + COALESCE(r.m2_1, 0) + COALESCE(r.m3_1, 0)
|
|
||||||
+ COALESCE(r.m1_2, 0) + COALESCE(r.m2_2, 0) + COALESCE(r.m3_2, 0)
|
|
||||||
+ COALESCE(r.m1_3, 0) + COALESCE(r.m2_3, 0) + COALESCE(r.m3_3, 0)
|
|
||||||
+ COALESCE(r.m1_4, 0) + COALESCE(r.m2_4, 0) + COALESCE(r.m3_4, 0) + COALESCE(r.spod4, 0) AS fact_development_year,
|
|
||||||
COALESCE(r.cp2, 0) AS corrected_development_q2,
|
|
||||||
COALESCE(r.cp3, 0) AS corrected_development_q3,
|
|
||||||
COALESCE(r.cp4, 0) AS corrected_development_q4
|
|
||||||
FROM rolled AS r;
|
|
||||||
$function$;
|
|
||||||
@ -8,9 +8,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from src.services.org_unit_service import OrgUnitService
|
from src.services.org_unit_service import OrgUnitService
|
||||||
from src.db.models.form_type import FormTypeEnum
|
from src.db.models.form_type import FormTypeEnum
|
||||||
from src.db.models.role import UserRoleEnum
|
|
||||||
from src.services.sheet_service import SheetService
|
from src.services.sheet_service import SheetService
|
||||||
from src.domain.schemas import AddLineSchema, BaseListResponse, BaseSingleResponse, ResponseBase, BudgetFormResponse, CellPatch, CellsPatch, DirectionSchemaEnum, FormCreateSchema, SheetFormTypeResponse, SheetResponse
|
from src.domain.schemas import AddLineSchema, BaseListResponse, BaseSingleResponse, BudgetFormResponse, CellPatch, CellsPatch, DirectionSchemaEnum, FormCreateSchema, SheetFormTypeResponse, SheetResponse
|
||||||
from src.db.models.app_user import AppUser
|
from src.db.models.app_user import AppUser
|
||||||
from src.services.budget_form_service import BudgetFormService
|
from src.services.budget_form_service import BudgetFormService
|
||||||
from src.api.v1.deps import get_current_active_user_with_set_db, require_admin
|
from src.api.v1.deps import get_current_active_user_with_set_db, require_admin
|
||||||
@ -81,7 +80,6 @@ async def get_forms(
|
|||||||
org_units: str | None = None,
|
org_units: str | None = None,
|
||||||
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
delete_status: Literal["active", "marked", "all"] | None = None,
|
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: AppUser = Depends(get_current_active_user_with_set_db),
|
current_user: AppUser = Depends(get_current_active_user_with_set_db),
|
||||||
) -> BaseListResponse[BudgetFormResponse]:
|
) -> BaseListResponse[BudgetFormResponse]:
|
||||||
@ -97,7 +95,6 @@ async def get_forms(
|
|||||||
form_type=form_type,
|
form_type=form_type,
|
||||||
year=year,
|
year=year,
|
||||||
load_org=True,
|
load_org=True,
|
||||||
delete_status=delete_status,
|
|
||||||
)
|
)
|
||||||
return BaseListResponse(
|
return BaseListResponse(
|
||||||
result = [
|
result = [
|
||||||
@ -115,15 +112,12 @@ async def get_form(
|
|||||||
) -> BaseSingleResponse[BudgetFormResponse]:
|
) -> BaseSingleResponse[BudgetFormResponse]:
|
||||||
bf_service = BudgetFormService(db)
|
bf_service = BudgetFormService(db)
|
||||||
form = await bf_service.get(
|
form = await bf_service.get(
|
||||||
user=current_user,
|
user=current_user,
|
||||||
budget_form_id=form_id,
|
budget_form_id=form_id,
|
||||||
load_org=True,
|
load_org=True,
|
||||||
include_to_delete=current_user.role_id == UserRoleEnum.ADMIN,
|
|
||||||
)
|
)
|
||||||
if not form:
|
|
||||||
raise HTTPException(404, f"Форма {form_id} не найдена")
|
|
||||||
return BaseSingleResponse(
|
return BaseSingleResponse(
|
||||||
result=BudgetFormResponse.model_validate(form),
|
result = BudgetFormResponse.model_validate(form),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -470,7 +464,6 @@ async def add_form(
|
|||||||
form_type_code=form.form_type_code,
|
form_type_code=form.form_type_code,
|
||||||
year=form.year,
|
year=form.year,
|
||||||
org_unit_ids=org_unit_ids,
|
org_unit_ids=org_unit_ids,
|
||||||
user=current_user,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
db_ms = (time.perf_counter() - t0) * 1000
|
db_ms = (time.perf_counter() - t0) * 1000
|
||||||
@ -492,36 +485,3 @@ async def add_form(
|
|||||||
result=[BudgetFormResponse.model_validate(new_form) for new_form in new_forms],
|
result=[BudgetFormResponse.model_validate(new_form) for new_form in new_forms],
|
||||||
count=len(new_forms),
|
count=len(new_forms),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{form_id}/mark-delete")
|
|
||||||
async def mark_form_for_deletion(
|
|
||||||
form_id: int,
|
|
||||||
db: AsyncSession = Depends(get_db),
|
|
||||||
current_user: AppUser = Depends(require_admin),
|
|
||||||
) -> BaseSingleResponse[BudgetFormResponse]:
|
|
||||||
bf_service = BudgetFormService(db)
|
|
||||||
form = await bf_service.mark_for_deletion(budget_form_id=form_id, user=current_user)
|
|
||||||
return BaseSingleResponse(result=BudgetFormResponse.model_validate(form))
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{form_id}/restore")
|
|
||||||
async def restore_form(
|
|
||||||
form_id: int,
|
|
||||||
db: AsyncSession = Depends(get_db),
|
|
||||||
current_user: AppUser = Depends(require_admin),
|
|
||||||
) -> BaseSingleResponse[BudgetFormResponse]:
|
|
||||||
bf_service = BudgetFormService(db)
|
|
||||||
form = await bf_service.restore(budget_form_id=form_id, user=current_user)
|
|
||||||
return BaseSingleResponse(result=BudgetFormResponse.model_validate(form))
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{form_id}/hard")
|
|
||||||
async def hard_delete_form(
|
|
||||||
form_id: int,
|
|
||||||
db: AsyncSession = Depends(get_db),
|
|
||||||
current_user: AppUser = Depends(require_admin),
|
|
||||||
) -> ResponseBase:
|
|
||||||
bf_service = BudgetFormService(db)
|
|
||||||
await bf_service.hard_delete(budget_form_id=form_id, user=current_user)
|
|
||||||
return ResponseBase(success=True, message="Задача полностью удалена")
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, func
|
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from src.db.models.form_type import FormTypeEnum
|
from src.db.models.form_type import FormTypeEnum
|
||||||
@ -34,9 +34,6 @@ class BudgetForm(Base):
|
|||||||
org_unit_id: Mapped[int | None] = mapped_column(
|
org_unit_id: Mapped[int | None] = mapped_column(
|
||||||
Integer, ForeignKey("v3.org_unit.id", name="fk_v3_budget_form_org_unit")
|
Integer, ForeignKey("v3.org_unit.id", name="fk_v3_budget_form_org_unit")
|
||||||
)
|
)
|
||||||
to_delete: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
|
|
||||||
deleted_by: Mapped[int | None] = mapped_column(Integer, ForeignKey("v3.app_user.id"))
|
|
||||||
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
||||||
|
|
||||||
form_type: Mapped["FormType"] = relationship(
|
form_type: Mapped["FormType"] = relationship(
|
||||||
"FormType", #back_populates="budget_forms"
|
"FormType", #back_populates="budget_forms"
|
||||||
|
|||||||
@ -330,9 +330,6 @@ class BudgetFormResponse(BaseModel):
|
|||||||
form_type_code: FormTypeSchemaEnum
|
form_type_code: FormTypeSchemaEnum
|
||||||
year: int
|
year: int
|
||||||
org_unit_id: int
|
org_unit_id: int
|
||||||
to_delete: bool = False
|
|
||||||
deleted_by: int | None = None
|
|
||||||
deleted_at: datetime | None = None
|
|
||||||
org_unit: OrgUnitSchema | None = None
|
org_unit: OrgUnitSchema | None = None
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import func, select, text, update
|
from sqlalchemy import func, select, text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.db.models.form_type import FormTypeEnum
|
from src.db.models.form_type import FormTypeEnum
|
||||||
@ -6,20 +6,20 @@ from src.db.models.budget_form import BudgetForm
|
|||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BudgetFormRepository:
|
class BudgetFormRepository:
|
||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_list(
|
async def get_list(
|
||||||
self,
|
self,
|
||||||
offset: int | None = None,
|
offset: int | None = None,
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
org_unit: int | list[int] | None = None,
|
org_unit: int | list[int] | None = None,
|
||||||
with_count: bool = False,
|
with_count: bool = False,
|
||||||
load_org: bool = False,
|
load_org: bool = False,
|
||||||
form_type: FormTypeEnum | str | None = None,
|
form_type: FormTypeEnum | str | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
delete_status: str = "active",
|
|
||||||
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
||||||
if with_count:
|
if with_count:
|
||||||
query = select(func.count().over().label("total_count"), BudgetForm)
|
query = select(func.count().over().label("total_count"), BudgetForm)
|
||||||
@ -38,11 +38,6 @@ class BudgetFormRepository:
|
|||||||
if year is not None:
|
if year is not None:
|
||||||
where.append(BudgetForm.year == year)
|
where.append(BudgetForm.year == year)
|
||||||
|
|
||||||
if delete_status == "marked":
|
|
||||||
where.append(BudgetForm.to_delete.is_(True))
|
|
||||||
elif delete_status != "all":
|
|
||||||
where.append(BudgetForm.to_delete.is_(False))
|
|
||||||
|
|
||||||
query = query.where(*where)
|
query = query.where(*where)
|
||||||
if load_org:
|
if load_org:
|
||||||
query = query.options(
|
query = query.options(
|
||||||
@ -68,20 +63,19 @@ class BudgetFormRepository:
|
|||||||
return (await self.db.execute(count_query)).scalar(), []
|
return (await self.db.execute(count_query)).scalar(), []
|
||||||
else:
|
else:
|
||||||
return (await self.db.execute(query)).scalars().all()
|
return (await self.db.execute(query)).scalars().all()
|
||||||
|
|
||||||
async def get(
|
async def get(
|
||||||
self,
|
self,
|
||||||
budget_form_id: int | None = None,
|
budget_form_id: int | None = None,
|
||||||
org_unit: int | list[int] | None = None,
|
org_unit: int | list[int] | None = None,
|
||||||
form_type_code: FormTypeEnum | str | None = None,
|
form_type_code: FormTypeEnum | str | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
load_form_type: bool = False,
|
load_form_type: bool = False,
|
||||||
load_org: bool = False,
|
load_org: bool = False,
|
||||||
include_to_delete: bool = False,
|
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
assert budget_form_id or all((form_type_code, year, org_unit))
|
assert budget_form_id or all((form_type_code, year, org_unit))
|
||||||
query = select(BudgetForm)
|
query = select(BudgetForm)
|
||||||
|
|
||||||
where = []
|
where = []
|
||||||
if budget_form_id is not None:
|
if budget_form_id is not None:
|
||||||
where.append(BudgetForm.id == budget_form_id)
|
where.append(BudgetForm.id == budget_form_id)
|
||||||
@ -91,19 +85,16 @@ class BudgetFormRepository:
|
|||||||
where.append(BudgetForm.org_unit_id == org_unit)
|
where.append(BudgetForm.org_unit_id == org_unit)
|
||||||
else:
|
else:
|
||||||
where.append(BudgetForm.org_unit_id.in_(org_unit))
|
where.append(BudgetForm.org_unit_id.in_(org_unit))
|
||||||
|
|
||||||
if form_type_code is not None:
|
if form_type_code is not None:
|
||||||
if isinstance(form_type_code, FormTypeEnum):
|
if isinstance(form_type_code, FormTypeEnum):
|
||||||
where.append(BudgetForm.form_type_code == form_type_code.value)
|
where.append(BudgetForm.form_type_code == form_type_code.value)
|
||||||
else:
|
else:
|
||||||
where.append(BudgetForm.form_type_code == form_type_code)
|
where.append(BudgetForm.form_type_code == form_type_code)
|
||||||
|
|
||||||
if year is not None:
|
if year is not None:
|
||||||
where.append(BudgetForm.year == year)
|
where.append(BudgetForm.year == year)
|
||||||
|
|
||||||
if not include_to_delete:
|
|
||||||
where.append(BudgetForm.to_delete.is_(False))
|
|
||||||
|
|
||||||
query = query.where(*where)
|
query = query.where(*where)
|
||||||
if load_org:
|
if load_org:
|
||||||
query = query.options(
|
query = query.options(
|
||||||
@ -115,13 +106,12 @@ class BudgetFormRepository:
|
|||||||
joinedload(BudgetForm.form_type)
|
joinedload(BudgetForm.form_type)
|
||||||
)
|
)
|
||||||
return (await self.db.execute(query)).scalars().first()
|
return (await self.db.execute(query)).scalars().first()
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_id: int,
|
org_unit_id: int,
|
||||||
user_id: int | None = None,
|
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
form_id = (
|
form_id = (
|
||||||
await self.db.execute(
|
await self.db.execute(
|
||||||
@ -144,42 +134,6 @@ class BudgetFormRepository:
|
|||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
if form_id is None:
|
if form_id is None:
|
||||||
return None
|
return None
|
||||||
if user_id is not None:
|
|
||||||
await self.db.execute(
|
|
||||||
update(BudgetForm)
|
|
||||||
.where(BudgetForm.id == form_id)
|
|
||||||
.values(created_by=user_id, updated_by=user_id)
|
|
||||||
)
|
|
||||||
return await self.get(
|
return await self.get(
|
||||||
budget_form_id=form_id,
|
budget_form_id=form_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def mark_for_deletion(self, budget_form_id: int, user_id: int) -> None:
|
|
||||||
await self.db.execute(
|
|
||||||
text(
|
|
||||||
"""
|
|
||||||
UPDATE v3.budget_form
|
|
||||||
SET to_delete = true, deleted_by = :user_id, deleted_at = now(), updated_by = :user_id, updated_at = now()
|
|
||||||
WHERE id = :form_id
|
|
||||||
"""
|
|
||||||
),
|
|
||||||
{"form_id": budget_form_id, "user_id": user_id},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def restore(self, budget_form_id: int, user_id: int) -> None:
|
|
||||||
await self.db.execute(
|
|
||||||
text(
|
|
||||||
"""
|
|
||||||
UPDATE v3.budget_form
|
|
||||||
SET to_delete = false, deleted_by = NULL, deleted_at = NULL, updated_by = :user_id, updated_at = now()
|
|
||||||
WHERE id = :form_id
|
|
||||||
"""
|
|
||||||
),
|
|
||||||
{"form_id": budget_form_id, "user_id": user_id},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def hard_delete(self, budget_form_id: int) -> None:
|
|
||||||
await self.db.execute(
|
|
||||||
text("SELECT v3.hard_delete_budget_form(:form_id)"),
|
|
||||||
{"form_id": budget_form_id},
|
|
||||||
)
|
|
||||||
|
|||||||
@ -4,15 +4,12 @@ from typing import Literal
|
|||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.core.errors import AccessDeniedException, ValidationException
|
from src.repository.user_repository import UserRepository
|
||||||
from src.db.models.app_user import AppUser
|
from src.db.models.app_user import AppUser
|
||||||
from src.db.models.audit_log import AuditLog
|
|
||||||
from src.db.models.budget_form import BudgetForm
|
from src.db.models.budget_form import BudgetForm
|
||||||
from src.db.models.role import UserRoleEnum
|
from src.db.models.role import UserRoleEnum
|
||||||
from src.repository.budget_form_repository import BudgetFormRepository
|
from src.repository.budget_form_repository import BudgetFormRepository
|
||||||
from src.repository.user_repository import UserRepository
|
|
||||||
|
|
||||||
DeleteStatus = Literal["active", "marked", "all"]
|
|
||||||
|
|
||||||
|
|
||||||
class BudgetFormService:
|
class BudgetFormService:
|
||||||
@ -31,15 +28,8 @@ class BudgetFormService:
|
|||||||
org_unit: list[int] | None = None,
|
org_unit: list[int] | None = None,
|
||||||
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
|
||||||
year: int | None = None,
|
year: int | None = None,
|
||||||
delete_status: DeleteStatus | None = None,
|
|
||||||
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
|
||||||
if delete_status is not None and delete_status not in {"active", "marked", "all"}:
|
if user.role_id == UserRoleEnum.ADMIN:
|
||||||
raise ValidationException("delete_status должен быть active, marked или all")
|
|
||||||
|
|
||||||
is_admin = user.role_id == UserRoleEnum.ADMIN
|
|
||||||
effective_delete_status = (delete_status or "all") if is_admin else "active"
|
|
||||||
|
|
||||||
if is_admin:
|
|
||||||
return await self.bf_repo.get_list(
|
return await self.bf_repo.get_list(
|
||||||
offset=offset,
|
offset=offset,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
@ -48,7 +38,6 @@ class BudgetFormService:
|
|||||||
org_unit=org_unit,
|
org_unit=org_unit,
|
||||||
form_type=form_type,
|
form_type=form_type,
|
||||||
year=year,
|
year=year,
|
||||||
delete_status=effective_delete_status,
|
|
||||||
)
|
)
|
||||||
user = await self.user_repo.get(
|
user = await self.user_repo.get(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@ -69,7 +58,6 @@ class BudgetFormService:
|
|||||||
load_org=load_org,
|
load_org=load_org,
|
||||||
form_type=form_type,
|
form_type=form_type,
|
||||||
year=year,
|
year=year,
|
||||||
delete_status=effective_delete_status,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get(
|
async def get(
|
||||||
@ -78,15 +66,12 @@ class BudgetFormService:
|
|||||||
user: AppUser,
|
user: AppUser,
|
||||||
load_form_type: bool = False,
|
load_form_type: bool = False,
|
||||||
load_org: bool = False,
|
load_org: bool = False,
|
||||||
include_to_delete: bool = False,
|
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
is_admin = user.role_id == UserRoleEnum.ADMIN
|
if user.role_id == UserRoleEnum.ADMIN:
|
||||||
if is_admin:
|
|
||||||
return await self.bf_repo.get(
|
return await self.bf_repo.get(
|
||||||
budget_form_id=budget_form_id,
|
budget_form_id=budget_form_id,
|
||||||
load_form_type=load_form_type,
|
load_form_type=load_form_type,
|
||||||
load_org=load_org,
|
load_org=load_org,
|
||||||
include_to_delete=True,
|
|
||||||
)
|
)
|
||||||
user = await self.user_repo.get(
|
user = await self.user_repo.get(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@ -97,35 +82,30 @@ class BudgetFormService:
|
|||||||
org_unit=[ou.id for ou in user.org_units],
|
org_unit=[ou.id for ou in user.org_units],
|
||||||
load_form_type=load_form_type,
|
load_form_type=load_form_type,
|
||||||
load_org=load_org,
|
load_org=load_org,
|
||||||
include_to_delete=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_id: int,
|
org_unit_id: int,
|
||||||
user: AppUser | None = None,
|
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
return await self.bf_repo.create(
|
return await self.bf_repo.create(
|
||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
user_id=user.id if user else None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create_if_not_exists(
|
async def create_if_not_exists(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_id: int,
|
org_unit_id: int,
|
||||||
user: AppUser | None = None,
|
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
existing_bf = await self.bf_repo.get(
|
existing_bf = await self.bf_repo.get(
|
||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit=org_unit_id,
|
org_unit=org_unit_id,
|
||||||
include_to_delete=True,
|
|
||||||
)
|
)
|
||||||
if existing_bf:
|
if existing_bf:
|
||||||
return existing_bf
|
return existing_bf
|
||||||
@ -133,15 +113,13 @@ class BudgetFormService:
|
|||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
user_id=user.id if user else None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def bulk_create(
|
async def bulk_create(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_ids: list[int],
|
org_unit_ids: list[int],
|
||||||
user: AppUser | None = None,
|
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
return await asyncio.gather(
|
return await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
@ -149,18 +127,16 @@ class BudgetFormService:
|
|||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
user=user,
|
|
||||||
)
|
)
|
||||||
for org_unit_id in org_unit_ids
|
for org_unit_id in org_unit_ids
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
async def bulk_create_if_not_exists(
|
async def bulk_create_if_not_exists(
|
||||||
self,
|
self,
|
||||||
form_type_code: str,
|
form_type_code: str,
|
||||||
year: int,
|
year: int,
|
||||||
org_unit_ids: list[int],
|
org_unit_ids: list[int],
|
||||||
user: AppUser | None = None,
|
|
||||||
) -> BudgetForm | None:
|
) -> BudgetForm | None:
|
||||||
return await asyncio.gather(
|
return await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
@ -168,118 +144,7 @@ class BudgetFormService:
|
|||||||
form_type_code=form_type_code,
|
form_type_code=form_type_code,
|
||||||
year=year,
|
year=year,
|
||||||
org_unit_id=org_unit_id,
|
org_unit_id=org_unit_id,
|
||||||
user=user,
|
|
||||||
)
|
)
|
||||||
for org_unit_id in org_unit_ids
|
for org_unit_id in org_unit_ids
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
async def mark_for_deletion(self, budget_form_id: int, user: AppUser) -> BudgetForm:
|
|
||||||
self._ensure_admin(user)
|
|
||||||
form = await self._get_admin_form(budget_form_id)
|
|
||||||
if form.to_delete:
|
|
||||||
return form
|
|
||||||
await self.bf_repo.mark_for_deletion(budget_form_id=budget_form_id, user_id=user.id)
|
|
||||||
await self.db.refresh(form)
|
|
||||||
self._add_audit_log(
|
|
||||||
event="FORM_MARKED_FOR_DELETION",
|
|
||||||
user=user,
|
|
||||||
form=form,
|
|
||||||
event_data={
|
|
||||||
"entity_type": "form",
|
|
||||||
"entity_id": form.id,
|
|
||||||
"form_id": form.id,
|
|
||||||
"form_type_code": self._form_type_value(form.form_type_code),
|
|
||||||
"year": form.year,
|
|
||||||
"org_unit_id": form.org_unit_id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return form
|
|
||||||
|
|
||||||
async def restore(self, budget_form_id: int, user: AppUser) -> BudgetForm:
|
|
||||||
self._ensure_admin(user)
|
|
||||||
form = await self._get_admin_form(budget_form_id)
|
|
||||||
if not form.to_delete:
|
|
||||||
return form
|
|
||||||
snapshot = self._form_snapshot(form)
|
|
||||||
await self.bf_repo.restore(budget_form_id=budget_form_id, user_id=user.id)
|
|
||||||
await self.db.refresh(form)
|
|
||||||
self._add_audit_log(
|
|
||||||
event="FORM_RESTORED",
|
|
||||||
user=user,
|
|
||||||
form=form,
|
|
||||||
event_data={
|
|
||||||
"entity_type": "form",
|
|
||||||
"entity_id": form.id,
|
|
||||||
"before": snapshot,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return form
|
|
||||||
|
|
||||||
async def hard_delete(self, budget_form_id: int, user: AppUser) -> None:
|
|
||||||
self._ensure_admin(user)
|
|
||||||
form = await self._get_admin_form(budget_form_id)
|
|
||||||
if not form.to_delete:
|
|
||||||
raise ValidationException("Задача не помечена на удаление")
|
|
||||||
if form.deleted_by == user.id:
|
|
||||||
raise AccessDeniedException("Полностью удалить задачу должен другой администратор")
|
|
||||||
snapshot = self._form_snapshot(form)
|
|
||||||
self._add_audit_log(
|
|
||||||
event="FORM_HARD_DELETED",
|
|
||||||
user=user,
|
|
||||||
form=form,
|
|
||||||
event_data={
|
|
||||||
"entity_type": "form",
|
|
||||||
"entity_id": form.id,
|
|
||||||
"before": snapshot,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await self.db.flush()
|
|
||||||
await self.bf_repo.hard_delete(budget_form_id=budget_form_id)
|
|
||||||
|
|
||||||
def _ensure_admin(self, user: AppUser) -> None:
|
|
||||||
if user.role_id != UserRoleEnum.ADMIN:
|
|
||||||
raise AccessDeniedException("Недостаточно прав")
|
|
||||||
|
|
||||||
async def _get_admin_form(self, budget_form_id: int) -> BudgetForm:
|
|
||||||
form = await self.bf_repo.get(budget_form_id=budget_form_id, include_to_delete=True, load_org=True)
|
|
||||||
if not form:
|
|
||||||
raise ValidationException("Форма не найдена")
|
|
||||||
return form
|
|
||||||
|
|
||||||
def _add_audit_log(
|
|
||||||
self,
|
|
||||||
event: str,
|
|
||||||
user: AppUser,
|
|
||||||
form: BudgetForm,
|
|
||||||
event_data: dict,
|
|
||||||
) -> None:
|
|
||||||
self.db.add(
|
|
||||||
AuditLog(
|
|
||||||
user_id=user.id,
|
|
||||||
org_unit_id=form.org_unit_id,
|
|
||||||
form_id=form.id,
|
|
||||||
task_id=form.id,
|
|
||||||
event=event,
|
|
||||||
event_type="FORM",
|
|
||||||
event_data=event_data,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _form_type_value(self, form_type_code) -> str:
|
|
||||||
return getattr(form_type_code, "value", str(form_type_code))
|
|
||||||
|
|
||||||
def _form_snapshot(self, form: BudgetForm) -> dict:
|
|
||||||
return {
|
|
||||||
"id": form.id,
|
|
||||||
"form_type_code": self._form_type_value(form.form_type_code),
|
|
||||||
"year": form.year,
|
|
||||||
"org_unit_id": form.org_unit_id,
|
|
||||||
"created_by": form.created_by,
|
|
||||||
"created_at": form.created_at.isoformat() if form.created_at else None,
|
|
||||||
"updated_by": form.updated_by,
|
|
||||||
"updated_at": form.updated_at.isoformat() if form.updated_at else None,
|
|
||||||
"to_delete": form.to_delete,
|
|
||||||
"deleted_by": form.deleted_by,
|
|
||||||
"deleted_at": form.deleted_at.isoformat() if form.deleted_at else None,
|
|
||||||
}
|
|
||||||
|
|||||||
@ -31,15 +31,6 @@ export const TasksApi = {
|
|||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
markForDeletion(taskId) {
|
|
||||||
return api.post(`/form/${taskId}/mark-delete`).then((r) => r.data);
|
|
||||||
},
|
|
||||||
restore(taskId) {
|
|
||||||
return api.post(`/form/${taskId}/restore`).then((r) => r.data);
|
|
||||||
},
|
|
||||||
hardDelete(taskId) {
|
|
||||||
return api.delete(`/form/${taskId}/hard`).then((r) => r.data);
|
|
||||||
},
|
|
||||||
exportTasks(payload, params = {}) {
|
exportTasks(payload, params = {}) {
|
||||||
return api.post(`export/bulk`, payload, {
|
return api.post(`export/bulk`, payload, {
|
||||||
params,
|
params,
|
||||||
|
|||||||
@ -681,10 +681,6 @@ export const config = {
|
|||||||
color_type: yellowColumn,
|
color_type: yellowColumn,
|
||||||
accessorKey: "data.q4.adj_reserve",
|
accessorKey: "data.q4.adj_reserve",
|
||||||
},
|
},
|
||||||
"data.q4.adj_comment": {
|
|
||||||
color_type: yellowColumn,
|
|
||||||
accessorKey: "data.q4.adj_comment",
|
|
||||||
},
|
|
||||||
"data.q4.corrected_plan": {
|
"data.q4.corrected_plan": {
|
||||||
color_type: yellowColumn,
|
color_type: yellowColumn,
|
||||||
accessorKey: "data.q4.corrected_plan",
|
accessorKey: "data.q4.corrected_plan",
|
||||||
@ -3667,25 +3663,6 @@ export const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: "Комментарий",
|
|
||||||
accessorKey: "4_kvartal_2026_adj_kommentariy",
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
header: "Сумма (без НДС)",
|
|
||||||
accessorKey: "data.q4.adj_comment",
|
|
||||||
columnLetter: "GG",
|
|
||||||
size: 150,
|
|
||||||
filterFn: "contains",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
muiTableHeadCellProps: {
|
|
||||||
sx: {
|
|
||||||
backgroundColor: "#FFFF99",
|
|
||||||
color: "#000000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: "Скорректированный план 4 кв. {year:short}",
|
header: "Скорректированный план 4 кв. {year:short}",
|
||||||
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
||||||
|
|||||||
@ -681,10 +681,6 @@ export const config = {
|
|||||||
color_type: yellowColumn,
|
color_type: yellowColumn,
|
||||||
accessorKey: "data.q4.adj_reserve",
|
accessorKey: "data.q4.adj_reserve",
|
||||||
},
|
},
|
||||||
"data.q4.adj_comment": {
|
|
||||||
color_type: yellowColumn,
|
|
||||||
accessorKey: "data.q4.adj_comment",
|
|
||||||
},
|
|
||||||
"data.q4.corrected_plan": {
|
"data.q4.corrected_plan": {
|
||||||
color_type: yellowColumn,
|
color_type: yellowColumn,
|
||||||
accessorKey: "data.q4.corrected_plan",
|
accessorKey: "data.q4.corrected_plan",
|
||||||
@ -3663,25 +3659,6 @@ export const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: "Комментарий",
|
|
||||||
accessorKey: "4_kvartal_2026_adj_kommentariy",
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
header: "Сумма (без НДС)",
|
|
||||||
accessorKey: "data.q4.adj_comment",
|
|
||||||
columnLetter: "GG",
|
|
||||||
size: 150,
|
|
||||||
filterFn: "contains",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
muiTableHeadCellProps: {
|
|
||||||
sx: {
|
|
||||||
backgroundColor: "#FFFF99",
|
|
||||||
color: "#000000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: "Скорректированный план 4 кв. {year:short}",
|
header: "Скорректированный план 4 кв. {year:short}",
|
||||||
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
||||||
|
|||||||
@ -682,10 +682,6 @@ export const config = {
|
|||||||
color_type: yellowColumn,
|
color_type: yellowColumn,
|
||||||
accessorKey: "data.q4.adj_reserve",
|
accessorKey: "data.q4.adj_reserve",
|
||||||
},
|
},
|
||||||
"data.q4.adj_comment": {
|
|
||||||
color_type: yellowColumn,
|
|
||||||
accessorKey: "data.q4.adj_comment",
|
|
||||||
},
|
|
||||||
"data.q4.corrected_plan": {
|
"data.q4.corrected_plan": {
|
||||||
color_type: yellowColumn,
|
color_type: yellowColumn,
|
||||||
accessorKey: "data.q4.corrected_plan",
|
accessorKey: "data.q4.corrected_plan",
|
||||||
@ -3663,25 +3659,6 @@ export const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: "Комментарий",
|
|
||||||
accessorKey: "4_kvartal_2026_adj_kommentariy",
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
header: "Сумма (без НДС)",
|
|
||||||
accessorKey: "data.q4.adj_comment",
|
|
||||||
columnLetter: "GG",
|
|
||||||
size: 150,
|
|
||||||
filterFn: "contains",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
muiTableHeadCellProps: {
|
|
||||||
sx: {
|
|
||||||
backgroundColor: "#FFFF99",
|
|
||||||
color: "#000000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: "Скорректированный план 4 кв. {year:short}",
|
header: "Скорректированный план 4 кв. {year:short}",
|
||||||
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
||||||
|
|||||||
@ -3571,6 +3571,13 @@ export const config = {
|
|||||||
size: 260,
|
size: 260,
|
||||||
filterFn: "contains",
|
filterFn: "contains",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: "Сумма, тыс. руб. (без НДС)",
|
||||||
|
accessorKey: "data.q4.adj_comment",
|
||||||
|
columnLetter: "GG",
|
||||||
|
size: 250,
|
||||||
|
filterFn: "contains",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
muiTableHeadCellProps: {
|
muiTableHeadCellProps: {
|
||||||
sx: {
|
sx: {
|
||||||
@ -3681,25 +3688,6 @@ export const config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: "Комментарий",
|
|
||||||
accessorKey: "4_kvartal_2026_adj_kommentariy",
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
header: "Сумма, тыс. руб. (без НДС)",
|
|
||||||
accessorKey: "data.q4.adj_comment",
|
|
||||||
columnLetter: "GG",
|
|
||||||
size: 250,
|
|
||||||
filterFn: "contains",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
muiTableHeadCellProps: {
|
|
||||||
sx: {
|
|
||||||
backgroundColor: "#FFFF66",
|
|
||||||
color: "#000000",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: "Скорректированный план 4 кв. {year:short}",
|
header: "Скорректированный план 4 кв. {year:short}",
|
||||||
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
accessorKey: "4_kvartal_2026_skorr_plan_4_kv_26",
|
||||||
|
|||||||
@ -59,9 +59,6 @@ export const TaskCardComponent = ({
|
|||||||
onAddForExport,
|
onAddForExport,
|
||||||
onExportClick,
|
onExportClick,
|
||||||
isLastOpened = false,
|
isLastOpened = false,
|
||||||
isAdmin = false,
|
|
||||||
onChanged,
|
|
||||||
currentUserId = null,
|
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -70,14 +67,11 @@ export const TaskCardComponent = ({
|
|||||||
if (isLastOpened) cardRef.current?.scrollIntoView({ block: 'nearest' });
|
if (isLastOpened) cardRef.current?.scrollIntoView({ block: 'nearest' });
|
||||||
}, [isLastOpened]);
|
}, [isLastOpened]);
|
||||||
const { id, title, org_unit, form_type_code, year } = taskForm;
|
const { id, title, org_unit, form_type_code, year } = taskForm;
|
||||||
const canHardDelete = taskForm.to_delete && taskForm.deleted_by !== currentUserId;
|
|
||||||
|
|
||||||
const [isEditModalOpen, setEditModalOpen] = useState(false);
|
const [isEditModalOpen, setEditModalOpen] = useState(false);
|
||||||
const [editedTitle, setEditedTitle] = useState('');
|
const [editedTitle, setEditedTitle] = useState('');
|
||||||
const [currentTypeCode, setCurrentTypeCode] = useState(FORM_TYPE_TRANSLATE[form_type_code] || '');
|
const [currentTypeCode, setCurrentTypeCode] = useState(FORM_TYPE_TRANSLATE[form_type_code] || '');
|
||||||
const [isChecked, setIsChecked] = useState(false);
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
const [confirmAction, setConfirmAction] = useState(null);
|
|
||||||
const [actionLoading, setActionLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsChecked(false);
|
setIsChecked(false);
|
||||||
@ -108,52 +102,6 @@ export const TaskCardComponent = ({
|
|||||||
onExportClick?.(id, title);
|
onExportClick?.(id, title);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openConfirmAction = (event, action) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
setConfirmAction(action);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeConfirmAction = () => {
|
|
||||||
if (!actionLoading) setConfirmAction(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const runConfirmedAction = async () => {
|
|
||||||
if (!confirmAction) return;
|
|
||||||
try {
|
|
||||||
setActionLoading(true);
|
|
||||||
if (confirmAction === 'mark') {
|
|
||||||
await TasksApi.markForDeletion(id);
|
|
||||||
toast.success('Задача помечена на удаление');
|
|
||||||
}
|
|
||||||
if (confirmAction === 'restore') {
|
|
||||||
await TasksApi.restore(id);
|
|
||||||
toast.success('Задача возвращена');
|
|
||||||
}
|
|
||||||
if (confirmAction === 'hardDelete') {
|
|
||||||
await TasksApi.hardDelete(id);
|
|
||||||
toast.success('Задача полностью удалена');
|
|
||||||
}
|
|
||||||
setConfirmAction(null);
|
|
||||||
onChanged?.();
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(error?.response?.data?.detail || 'Не удалось выполнить действие');
|
|
||||||
} finally {
|
|
||||||
setActionLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const actionTitle = {
|
|
||||||
mark: 'Удалить задачу',
|
|
||||||
restore: 'Вернуть задачу',
|
|
||||||
hardDelete: 'Полностью удалить задачу',
|
|
||||||
}[confirmAction];
|
|
||||||
|
|
||||||
const actionText = {
|
|
||||||
mark: 'Задача будет скрыта от пользователей. Админы смогут вернуть её или удалить окончательно.',
|
|
||||||
restore: 'Задача снова станет доступна пользователям.',
|
|
||||||
hardDelete: 'Будут удалены задача и все связанные данные. Это действие нельзя отменить.',
|
|
||||||
}[confirmAction];
|
|
||||||
|
|
||||||
const updateTask = async (nextTitle) => {
|
const updateTask = async (nextTitle) => {
|
||||||
try {
|
try {
|
||||||
await TasksApi.editTask(id, { title: nextTitle });
|
await TasksApi.editTask(id, { title: nextTitle });
|
||||||
@ -199,7 +147,6 @@ export const TaskCardComponent = ({
|
|||||||
<TaskCard
|
<TaskCard
|
||||||
ref={cardRef}
|
ref={cardRef}
|
||||||
$fluidWidth={fluidWidth}
|
$fluidWidth={fluidWidth}
|
||||||
$marked={Boolean(taskForm.to_delete)}
|
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (exportMode) {
|
if (exportMode) {
|
||||||
handleCheckboxClick(event);
|
handleCheckboxClick(event);
|
||||||
@ -212,10 +159,7 @@ export const TaskCardComponent = ({
|
|||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ width: '100%' }}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<TaskId>ID: {id}</TaskId>
|
<TaskId>ID: {id}</TaskId>
|
||||||
<Stack direction='row' gap={0.75} alignItems='center'>
|
{isLastOpened && <LastOpenedBadge sx={{ mb: 0 }}>Последняя открытая</LastOpenedBadge>}
|
||||||
{taskForm.to_delete && <Chip sx={{ background: '#fde8e8', color: '#b42318' }}>На удаление</Chip>}
|
|
||||||
{isLastOpened && <LastOpenedBadge sx={{ mb: 0 }}>Последняя открытая</LastOpenedBadge>}
|
|
||||||
</Stack>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<TaskTitle>{org_unit?.title || 'Подразделение не указано'}</TaskTitle>
|
<TaskTitle>{org_unit?.title || 'Подразделение не указано'}</TaskTitle>
|
||||||
@ -238,47 +182,10 @@ export const TaskCardComponent = ({
|
|||||||
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|
||||||
<Stack direction='row' gap={0.75} alignItems='center' onClick={(event) => event.stopPropagation()}>
|
{!isForm && !exportMode && <ExportButton onClick={handleExportTask} />}
|
||||||
{isAdmin && !isForm && !exportMode && !taskForm.to_delete && (
|
{exportMode && <div>{isChecked ? <CheckBoxCheckSvg size='1.625rem' /> : <CheckBoxUncheckSvg size='1.625rem' />}</div>}
|
||||||
<Button size='small' variant='text' color='error' onClick={(event) => openConfirmAction(event, 'mark')}>
|
|
||||||
Удалить
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{isAdmin && !isForm && !exportMode && taskForm.to_delete && (
|
|
||||||
<>
|
|
||||||
<Button size='small' variant='text' onClick={(event) => openConfirmAction(event, 'restore')}>
|
|
||||||
Вернуть
|
|
||||||
</Button>
|
|
||||||
{canHardDelete && (
|
|
||||||
<Button size='small' variant='text' color='error' onClick={(event) => openConfirmAction(event, 'hardDelete')}>
|
|
||||||
Удалить
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!isForm && !exportMode && !taskForm.to_delete && <ExportButton onClick={handleExportTask} />}
|
|
||||||
{exportMode && <div>{isChecked ? <CheckBoxCheckSvg size='1.625rem' /> : <CheckBoxUncheckSvg size='1.625rem' />}</div>}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</TaskCard>
|
</TaskCard>
|
||||||
|
|
||||||
<Modal
|
|
||||||
open={Boolean(confirmAction)}
|
|
||||||
onClose={closeConfirmAction}
|
|
||||||
title={actionTitle}
|
|
||||||
size='small'
|
|
||||||
actions={
|
|
||||||
<>
|
|
||||||
<Button variant='grey' onClick={closeConfirmAction} disabled={actionLoading}>
|
|
||||||
Отменить
|
|
||||||
</Button>
|
|
||||||
<Button variant='contained' color={confirmAction === 'hardDelete' || confirmAction === 'mark' ? 'error' : 'primary'} onClick={runConfirmedAction} disabled={actionLoading}>
|
|
||||||
Подтвердить
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
}>
|
|
||||||
{actionText}
|
|
||||||
</Modal>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,13 +10,12 @@ export const TaskCard = styled.div`
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 0.06rem solid ${({ $marked }) => ($marked ? 'rgba(211, 47, 47, 0.35)' : 'var(--Stroke, rgba(0, 0, 0, 0.1))')};
|
border: 0.06rem solid var(--Stroke, rgba(0, 0, 0, 0.1));
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0rem 2rem 4rem -2rem rgba(0, 0, 0, 0.02),
|
0rem 2rem 4rem -2rem rgba(0, 0, 0, 0.02),
|
||||||
0rem 4rem 6rem -1rem rgba(0, 0, 0, 0.02);
|
0rem 4rem 6rem -1rem rgba(0, 0, 0, 0.02);
|
||||||
background: ${({ $marked }) => ($marked ? '#fff7f7' : 'var(--White, rgba(255, 255, 255, 1))')};
|
background: var(--White, rgba(255, 255, 255, 1));
|
||||||
opacity: ${({ $marked }) => ($marked ? 0.72 : 1)};
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -5,9 +5,6 @@ export const DEFAULT_ACTIONS = {
|
|||||||
ACCESS_WINDOW_CHANGE: 'Изменение этапа',
|
ACCESS_WINDOW_CHANGE: 'Изменение этапа',
|
||||||
USER_ROLE_CHANGE: 'Изменение роли пользователя',
|
USER_ROLE_CHANGE: 'Изменение роли пользователя',
|
||||||
CREATE_TASK: 'Создание задачи',
|
CREATE_TASK: 'Создание задачи',
|
||||||
FORM_MARKED_FOR_DELETION: 'Задача отмечена на удаление',
|
|
||||||
FORM_RESTORED: 'Задача возвращена',
|
|
||||||
FORM_HARD_DELETED: 'Задача полностью удалена',
|
|
||||||
// UPDATE_TASK: 'Обновление задачи', //сейчас нет события
|
// UPDATE_TASK: 'Обновление задачи', //сейчас нет события
|
||||||
// DELETE_TASK: 'Удаление задачи',
|
// DELETE_TASK: 'Удаление задачи',
|
||||||
USER_CREATE: 'Создание пользователя',
|
USER_CREATE: 'Создание пользователя',
|
||||||
|
|||||||
@ -58,21 +58,6 @@ export const EVENT_CONFIGS = {
|
|||||||
form_type: 'Форма',
|
form_type: 'Форма',
|
||||||
ssp_list: 'Создана для СПП/РФ',
|
ssp_list: 'Создана для СПП/РФ',
|
||||||
},
|
},
|
||||||
FORM_MARKED_FOR_DELETION: {
|
|
||||||
entity_id: 'Задача',
|
|
||||||
form_id: 'Задача',
|
|
||||||
form_type_code: 'Форма',
|
|
||||||
year: 'Год',
|
|
||||||
org_unit_id: 'ССП/РФ',
|
|
||||||
},
|
|
||||||
FORM_RESTORED: {
|
|
||||||
entity_id: 'Задача',
|
|
||||||
before: 'Данные до возврата',
|
|
||||||
},
|
|
||||||
FORM_HARD_DELETED: {
|
|
||||||
entity_id: 'Задача',
|
|
||||||
before: 'Удалённые данные',
|
|
||||||
},
|
|
||||||
ROW_CREATE: {
|
ROW_CREATE: {
|
||||||
sheet: 'Лист',
|
sheet: 'Лист',
|
||||||
expense_item_id: 'Статья расходов',
|
expense_item_id: 'Статья расходов',
|
||||||
|
|||||||
@ -23,12 +23,6 @@ const METRICS = [
|
|||||||
{ key: 'fact', label: 'Факт' },
|
{ key: 'fact', label: 'Факт' },
|
||||||
{ key: 'corrected', label: 'Скорректированный план' },
|
{ key: 'corrected', label: 'Скорректированный план' },
|
||||||
];
|
];
|
||||||
const FORM_3_METRIC_LABELS = {
|
|
||||||
plan: 'Расходы, планируемые РФ',
|
|
||||||
approved: 'Утвержденные базовые расходы',
|
|
||||||
fact: 'Фактические расходы',
|
|
||||||
corrected: 'Скорректированная смета расходов',
|
|
||||||
};
|
|
||||||
const DIRECTIONS = [
|
const DIRECTIONS = [
|
||||||
{ key: 'support', label: 'Поддержка' },
|
{ key: 'support', label: 'Поддержка' },
|
||||||
{ key: 'development', label: 'Развитие' },
|
{ key: 'development', label: 'Развитие' },
|
||||||
@ -40,13 +34,6 @@ const QUARTERS = [
|
|||||||
{ key: 'q4', label: 'IV кв.' },
|
{ key: 'q4', label: 'IV кв.' },
|
||||||
{ key: 'year', label: 'Год' },
|
{ key: 'year', label: 'Год' },
|
||||||
];
|
];
|
||||||
const FORM_3_QUARTERS = [
|
|
||||||
{ key: 'q1', label: 'I квартал' },
|
|
||||||
{ key: 'q2', label: 'II квартал' },
|
|
||||||
{ key: 'q3', label: 'III квартал' },
|
|
||||||
{ key: 'q4', label: 'IV квартал' },
|
|
||||||
{ key: 'year', label: 'ИТОГО Развитие' },
|
|
||||||
];
|
|
||||||
const numberFormatter = new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 });
|
const numberFormatter = new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 });
|
||||||
const valueAt = (row, path) => path.reduce((value, key) => value?.[key], row.data);
|
const valueAt = (row, path) => path.reduce((value, key) => value?.[key], row.data);
|
||||||
const tableSize = (rem) => rem * 16;
|
const tableSize = (rem) => rem * 16;
|
||||||
@ -79,42 +66,14 @@ const valueColumn = (metric, direction, period) => ({
|
|||||||
muiTableHeadCellProps: { align: 'right' },
|
muiTableHeadCellProps: { align: 'right' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const createForm3MetricColumns = (metric) => ({
|
|
||||||
id: metric.key,
|
|
||||||
header: FORM_3_METRIC_LABELS[metric.key],
|
|
||||||
columns: FORM_3_QUARTERS.filter(({ key }) =>
|
|
||||||
valueAt({ data: getShapeForSheet('FORM_3') }, [metric.key, 'development', key]) !== undefined,
|
|
||||||
).map((period) => ({
|
|
||||||
...valueColumn(metric, { key: 'development' }, period),
|
|
||||||
id: metric.key + '.' + period.key,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
const createColumns = (sheet) => {
|
const createColumns = (sheet) => {
|
||||||
let visibleDirections = DIRECTIONS;
|
let visibleDirections = DIRECTIONS;
|
||||||
if (sheet === 'FORM_2') visibleDirections = DIRECTIONS.filter(({ key }) => key === 'support');
|
if (sheet === 'FORM_2') visibleDirections = DIRECTIONS.filter(({ key }) => key === 'support');
|
||||||
if (['FORM_3', 'FORM_4'].includes(sheet)) visibleDirections = DIRECTIONS.filter(({ key }) => key === 'development');
|
if (['FORM_3', 'FORM_4'].includes(sheet)) visibleDirections = DIRECTIONS.filter(({ key }) => key === 'development');
|
||||||
const shape = getShapeForSheet(sheet);
|
const shape = getShapeForSheet(sheet);
|
||||||
if (sheet === 'FORM_3') {
|
|
||||||
return [
|
|
||||||
{ id: 'section_code', header: '№ п/п', accessorFn: (row) => row.data?.section_code, size: tableSize(5.75) },
|
|
||||||
{
|
|
||||||
id: 'name',
|
|
||||||
header: 'Перечень расходов и затрат',
|
|
||||||
accessorFn: (row) => row.data?.name,
|
|
||||||
size: tableSize(20.625),
|
|
||||||
Cell: ({ cell, row }) => (
|
|
||||||
<Box sx={{ pl: row.original.depth * 1.125 + 'rem', fontWeight: row.original.depth < 2 ? 600 : 400 }}>
|
|
||||||
{cell.getValue() || 'Без названия'}
|
|
||||||
</Box>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
...METRICS.map(createForm3MetricColumns),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const metricColumns = METRICS.map((metric) => ({
|
const metricColumns = METRICS.map((metric) => ({
|
||||||
id: metric.key,
|
id: metric.key,
|
||||||
header: sheet === 'FORM_3' ? FORM_3_METRIC_LABELS[metric.key] : metric.label,
|
header: metric.label,
|
||||||
columns: visibleDirections
|
columns: visibleDirections
|
||||||
.map((direction) => ({
|
.map((direction) => ({
|
||||||
id: `${metric.key}.${direction.key}`,
|
id: `${metric.key}.${direction.key}`,
|
||||||
|
|||||||
@ -39,7 +39,6 @@ export default function TasksPage() {
|
|||||||
const yearFilter = searchParams.get('year') || '';
|
const yearFilter = searchParams.get('year') || '';
|
||||||
const formTypeFilter = searchParams.get('form_type') || '';
|
const formTypeFilter = searchParams.get('form_type') || '';
|
||||||
const orgUnitsFilter = searchParams.get('org_units') || '';
|
const orgUnitsFilter = searchParams.get('org_units') || '';
|
||||||
const deleteStatusFilter = searchParams.get('delete_status') || 'all';
|
|
||||||
const orgUnitIdsFilter = useMemo(
|
const orgUnitIdsFilter = useMemo(
|
||||||
() =>
|
() =>
|
||||||
orgUnitsFilter
|
orgUnitsFilter
|
||||||
@ -186,7 +185,6 @@ export default function TasksPage() {
|
|||||||
...(yearFilter && { year: Number(yearFilter) }),
|
...(yearFilter && { year: Number(yearFilter) }),
|
||||||
...(formTypeFilter && { form_type: formTypeFilter }),
|
...(formTypeFilter && { form_type: formTypeFilter }),
|
||||||
...(orgUnitIdsFilter.length > 0 && { org_units: orgUnitIdsFilter.join(',') }),
|
...(orgUnitIdsFilter.length > 0 && { org_units: orgUnitIdsFilter.join(',') }),
|
||||||
...(user?.role_id === ROLES_NAME_ID.admin && deleteStatusFilter && { delete_status: deleteStatusFilter }),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await TasksApi.list(params);
|
const res = await TasksApi.list(params);
|
||||||
@ -221,7 +219,7 @@ export default function TasksPage() {
|
|||||||
if (currentRequest === requestId.current) setLoading(false);
|
if (currentRequest === requestId.current) setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[yearFilter, formTypeFilter, orgUnitIdsFilter, deleteStatusFilter, user?.role_id, setSearchParams],
|
[yearFilter, formTypeFilter, orgUnitIdsFilter, setSearchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePageChange = (_event, value) => {
|
const handlePageChange = (_event, value) => {
|
||||||
@ -268,7 +266,6 @@ export default function TasksPage() {
|
|||||||
nextParams.delete('year');
|
nextParams.delete('year');
|
||||||
nextParams.delete('form_type');
|
nextParams.delete('form_type');
|
||||||
nextParams.delete('org_units');
|
nextParams.delete('org_units');
|
||||||
nextParams.delete('delete_status');
|
|
||||||
return nextParams;
|
return nextParams;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -340,19 +337,6 @@ export default function TasksPage() {
|
|||||||
sx={{ width: 480, maxWidth: '100%' }}
|
sx={{ width: 480, maxWidth: '100%' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{user.role_id === ROLES_NAME_ID.admin && (
|
|
||||||
<Select
|
|
||||||
size='small'
|
|
||||||
displayEmpty
|
|
||||||
value={deleteStatusFilter}
|
|
||||||
onChange={(event) => handleFilterChange('delete_status', event.target.value)}
|
|
||||||
sx={{ width: 230 }}>
|
|
||||||
<MenuItem value='active'>Непомеченные</MenuItem>
|
|
||||||
<MenuItem value='marked'>Помеченные</MenuItem>
|
|
||||||
<MenuItem value='all'>Все задачи</MenuItem>
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ResetFiltersButton onClick={handleClearFilters} />
|
<ResetFiltersButton onClick={handleClearFilters} />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user