Merge pull request 'remove-programs: обновление и удаление програм и проектов четвертой формы' (#133) from remove-programs into test
Reviewed-on: #133
This commit is contained in:
commit
dcbd133abd
14
api/alembic/versions/0024_upd_form4_projects.py
Normal file
14
api/alembic/versions/0024_upd_form4_projects.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from sql_migration import run_sql_file_migration
|
||||||
|
|
||||||
|
revision = "0024"
|
||||||
|
down_revision = "0023"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
run_sql_file_migration("0024_upd_form4_projects.sql")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
155
api/alembic/versions/sql/0024_upd_form4_projects.sql
Normal file
155
api/alembic/versions/sql/0024_upd_form4_projects.sql
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
DROP FUNCTION IF EXISTS v3.upd_form4_program(INT, VARCHAR, BOOLEAN);
|
||||||
|
DROP FUNCTION IF EXISTS v3.upd_form4_program(INT, VARCHAR);
|
||||||
|
|
||||||
|
CREATE FUNCTION v3.upd_form4_program(p_program_id INT, p_name VARCHAR)
|
||||||
|
RETURNS TABLE (project_id INT, row_type VARCHAR, depth INT, sort_order BIGINT, data JSONB)
|
||||||
|
AS $function$
|
||||||
|
#variable_conflict use_column
|
||||||
|
DECLARE
|
||||||
|
v_program v3.form4_project%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT * INTO v_program FROM v3.form4_project fp
|
||||||
|
WHERE fp.id = p_program_id AND fp.level = 'program';
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE EXCEPTION 'Программа id=% не найдена', p_program_id;
|
||||||
|
END IF;
|
||||||
|
IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
|
||||||
|
RAISE EXCEPTION 'p_name обязателен';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
UPDATE v3.form4_project SET name = trim(p_name) WHERE id = p_program_id;
|
||||||
|
PERFORM v3.log_event(
|
||||||
|
'PROJECT_UPDATE', 'PROJECT',
|
||||||
|
jsonb_build_object('project_id', p_program_id, 'name', trim(p_name),
|
||||||
|
'level', 'program', 'form_id', v_program.form_id, 'form_type', 'FORM_4'),
|
||||||
|
v_program.form_id, NULL, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT p_program_id, v.row_type, v.depth, v.sort_order, v.data
|
||||||
|
FROM v3.v_form4_sheet_jsonb(v_program.form_id, v_program.section_code, NULL) v
|
||||||
|
WHERE (v.row_type = 'GROUP' AND (v.data->'header'->>'program_id')::INT = p_program_id)
|
||||||
|
OR (v.row_type = 'ROOT' AND v.data->'header'->>'section_code' = v_program.section_code)
|
||||||
|
ORDER BY v.sort_order;
|
||||||
|
END;
|
||||||
|
$function$
|
||||||
|
LANGUAGE plpgsql VOLATILE;
|
||||||
|
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS v3.del_form4_program(INT);
|
||||||
|
|
||||||
|
CREATE FUNCTION v3.del_form4_program(p_program_id INT)
|
||||||
|
RETURNS TABLE (project_id INT, row_type VARCHAR, depth INT, sort_order BIGINT, data JSONB)
|
||||||
|
AS $function$
|
||||||
|
#variable_conflict use_column
|
||||||
|
DECLARE
|
||||||
|
v_program v3.form4_project%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT * INTO v_program FROM v3.form4_project fp
|
||||||
|
WHERE fp.id = p_program_id AND fp.level = 'program';
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE EXCEPTION 'Программа id=% не найдена', p_program_id;
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (SELECT 1 FROM v3.form4_project fp WHERE fp.parent_id = p_program_id) THEN
|
||||||
|
RAISE EXCEPTION 'Нельзя удалить программу, содержащую проекты';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DELETE FROM v3.form4_project WHERE id = p_program_id;
|
||||||
|
PERFORM v3.log_event(
|
||||||
|
'PROJECT_DELETE', 'PROJECT',
|
||||||
|
jsonb_build_object('project_id', p_program_id, 'name', v_program.name,
|
||||||
|
'level', 'program', 'form_id', v_program.form_id, 'form_type', 'FORM_4'),
|
||||||
|
v_program.form_id, NULL, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT p_program_id, v.row_type, v.depth, v.sort_order, v.data
|
||||||
|
FROM v3.v_form4_sheet_jsonb(v_program.form_id, v_program.section_code, NULL) v
|
||||||
|
WHERE v.row_type = 'ROOT'
|
||||||
|
AND v.data->'header'->>'section_code' = v_program.section_code
|
||||||
|
ORDER BY v.sort_order;
|
||||||
|
END;
|
||||||
|
$function$
|
||||||
|
LANGUAGE plpgsql VOLATILE;
|
||||||
|
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS v3.upd_form4_project(INT, VARCHAR, BOOLEAN);
|
||||||
|
DROP FUNCTION IF EXISTS v3.upd_form4_project(INT, VARCHAR);
|
||||||
|
|
||||||
|
CREATE FUNCTION v3.upd_form4_project(p_project_id INT, p_name VARCHAR)
|
||||||
|
RETURNS TABLE (project_id INT, row_type VARCHAR, depth INT, sort_order BIGINT, data JSONB)
|
||||||
|
AS $function$
|
||||||
|
#variable_conflict use_column
|
||||||
|
DECLARE
|
||||||
|
v_project v3.form4_project%ROWTYPE;
|
||||||
|
v_program v3.form4_project%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT * INTO v_project FROM v3.form4_project fp
|
||||||
|
WHERE fp.id = p_project_id AND fp.level = 'project';
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE EXCEPTION 'Проект id=% не найден', p_project_id;
|
||||||
|
END IF;
|
||||||
|
IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
|
||||||
|
RAISE EXCEPTION 'p_name обязателен';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT * INTO v_program FROM v3.form4_project fp WHERE fp.id = v_project.parent_id;
|
||||||
|
UPDATE v3.form4_project SET name = trim(p_name) WHERE id = p_project_id;
|
||||||
|
PERFORM v3.log_event(
|
||||||
|
'PROJECT_UPDATE', 'PROJECT',
|
||||||
|
jsonb_build_object('project_id', p_project_id, 'name', trim(p_name),
|
||||||
|
'level', 'project', 'parent_id', v_project.parent_id,
|
||||||
|
'form_id', v_project.form_id, 'form_type', 'FORM_4'),
|
||||||
|
v_project.form_id, NULL, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT p_project_id, v.row_type, v.depth, v.sort_order, v.data
|
||||||
|
FROM v3.v_form4_sheet_jsonb(v_project.form_id, v_program.section_code, NULL) v
|
||||||
|
WHERE (v.row_type = 'ITEM' AND (v.data->'header'->>'project_id')::INT = p_project_id)
|
||||||
|
OR (v.row_type = 'GROUP' AND (v.data->'header'->>'program_id')::INT = v_project.parent_id)
|
||||||
|
OR (v.row_type = 'ROOT' AND v.data->'header'->>'section_code' = v_program.section_code)
|
||||||
|
ORDER BY v.sort_order;
|
||||||
|
END;
|
||||||
|
$function$
|
||||||
|
LANGUAGE plpgsql VOLATILE;
|
||||||
|
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS v3.del_form4_project(INT);
|
||||||
|
|
||||||
|
CREATE FUNCTION v3.del_form4_project(p_project_id INT)
|
||||||
|
RETURNS TABLE (project_id INT, row_type VARCHAR, depth INT, sort_order BIGINT, data JSONB)
|
||||||
|
AS $function$
|
||||||
|
#variable_conflict use_column
|
||||||
|
DECLARE
|
||||||
|
v_project v3.form4_project%ROWTYPE;
|
||||||
|
v_program v3.form4_project%ROWTYPE;
|
||||||
|
BEGIN
|
||||||
|
SELECT * INTO v_project FROM v3.form4_project fp
|
||||||
|
WHERE fp.id = p_project_id AND fp.level = 'project';
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RAISE EXCEPTION 'Проект id=% не найден', p_project_id;
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (SELECT 1 FROM v3.budget_line bl WHERE bl.project_id = p_project_id) THEN
|
||||||
|
RAISE EXCEPTION 'Нельзя удалить проект, содержащий строки бюджета';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT * INTO v_program FROM v3.form4_project fp WHERE fp.id = v_project.parent_id;
|
||||||
|
DELETE FROM v3.form4_project WHERE id = p_project_id;
|
||||||
|
PERFORM v3.log_event(
|
||||||
|
'PROJECT_DELETE', 'PROJECT',
|
||||||
|
jsonb_build_object('project_id', p_project_id, 'name', v_project.name,
|
||||||
|
'level', 'project', 'parent_id', v_project.parent_id,
|
||||||
|
'form_id', v_project.form_id, 'form_type', 'FORM_4'),
|
||||||
|
v_project.form_id, NULL, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT p_project_id, v.row_type, v.depth, v.sort_order, v.data
|
||||||
|
FROM v3.v_form4_sheet_jsonb(v_project.form_id, v_program.section_code, NULL) v
|
||||||
|
WHERE (v.row_type = 'GROUP' AND (v.data->'header'->>'program_id')::INT = v_project.parent_id)
|
||||||
|
OR (v.row_type = 'ROOT' AND v.data->'header'->>'section_code' = v_program.section_code)
|
||||||
|
ORDER BY v.sort_order;
|
||||||
|
END;
|
||||||
|
$function$
|
||||||
|
LANGUAGE plpgsql VOLATILE;
|
||||||
@ -389,6 +389,26 @@ class FormEventProcess:
|
|||||||
event_data=event_data["data"],
|
event_data=event_data["data"],
|
||||||
user=curr_user,
|
user=curr_user,
|
||||||
)
|
)
|
||||||
|
case "program_updated":
|
||||||
|
return await self.__update_form4_program(
|
||||||
|
event_data=event_data["data"],
|
||||||
|
user=curr_user,
|
||||||
|
)
|
||||||
|
case "program_deleted":
|
||||||
|
return await self.__delete_form4_program(
|
||||||
|
event_data=event_data["data"],
|
||||||
|
user=curr_user,
|
||||||
|
)
|
||||||
|
case "project_updated":
|
||||||
|
return await self.__update_form4_project(
|
||||||
|
event_data=event_data["data"],
|
||||||
|
user=curr_user,
|
||||||
|
)
|
||||||
|
case "project_deleted":
|
||||||
|
return await self.__delete_form4_project(
|
||||||
|
event_data=event_data["data"],
|
||||||
|
user=curr_user,
|
||||||
|
)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -510,6 +530,48 @@ class FormEventProcess:
|
|||||||
program_id=event_data["program_id"],
|
program_id=event_data["program_id"],
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def __update_form4_program(
|
||||||
|
self,
|
||||||
|
event_data: dict,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
return await self.sheet_service.update_form4_program(
|
||||||
|
program_id=event_data["program_id"],
|
||||||
|
name=event_data["name"],
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def __delete_form4_program(
|
||||||
|
self,
|
||||||
|
event_data: dict,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
return await self.sheet_service.delete_form4_program(
|
||||||
|
program_id=event_data["program_id"],
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def __update_form4_project(
|
||||||
|
self,
|
||||||
|
event_data: dict,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
return await self.sheet_service.update_form4_project(
|
||||||
|
project_id=event_data["project_id"],
|
||||||
|
name=event_data["name"],
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def __delete_form4_project(
|
||||||
|
self,
|
||||||
|
event_data: dict,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
return await self.sheet_service.delete_form4_project(
|
||||||
|
project_id=event_data["project_id"],
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
async def __del_row(
|
async def __del_row(
|
||||||
self,
|
self,
|
||||||
@ -874,6 +936,10 @@ async def process_websocket(
|
|||||||
"row_added",
|
"row_added",
|
||||||
"program_added",
|
"program_added",
|
||||||
"project_added",
|
"project_added",
|
||||||
|
"program_updated",
|
||||||
|
"project_updated",
|
||||||
|
"program_deleted",
|
||||||
|
"project_deleted",
|
||||||
"row_deleted",
|
"row_deleted",
|
||||||
]:
|
]:
|
||||||
await manager.broadcast_to_all(
|
await manager.broadcast_to_all(
|
||||||
|
|||||||
@ -213,6 +213,92 @@ class SheetRepository:
|
|||||||
).all()
|
).all()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
async def update_form4_program(
|
||||||
|
self,
|
||||||
|
program_id: int,
|
||||||
|
name: str,
|
||||||
|
) -> list[tuple]:
|
||||||
|
return [
|
||||||
|
tuple(r) for r in (
|
||||||
|
await self.db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
SELECT project_id, row_type, depth, sort_order, data
|
||||||
|
FROM v3.upd_form4_program(
|
||||||
|
:p_program_id,
|
||||||
|
:p_name
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"p_program_id": program_id,
|
||||||
|
"p_name": name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
|
||||||
|
async def delete_form4_program(
|
||||||
|
self,
|
||||||
|
program_id: int,
|
||||||
|
) -> list[tuple]:
|
||||||
|
return [
|
||||||
|
tuple(r) for r in (
|
||||||
|
await self.db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
SELECT project_id, row_type, depth, sort_order, data
|
||||||
|
FROM v3.del_form4_program(:p_program_id)
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{"p_program_id": program_id},
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
|
||||||
|
async def update_form4_project(
|
||||||
|
self,
|
||||||
|
project_id: int,
|
||||||
|
name: str,
|
||||||
|
) -> list[tuple]:
|
||||||
|
return [
|
||||||
|
tuple(r) for r in (
|
||||||
|
await self.db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
SELECT project_id, row_type, depth, sort_order, data
|
||||||
|
FROM v3.upd_form4_project(
|
||||||
|
:p_project_id,
|
||||||
|
:p_name
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"p_project_id": project_id,
|
||||||
|
"p_name": name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
|
||||||
|
async def delete_form4_project(
|
||||||
|
self,
|
||||||
|
project_id: int,
|
||||||
|
) -> list[tuple]:
|
||||||
|
return [
|
||||||
|
tuple(r) for r in (
|
||||||
|
await self.db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
SELECT project_id, row_type, depth, sort_order, data
|
||||||
|
FROM v3.del_form4_project(:p_project_id)
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{"p_project_id": project_id},
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
|
||||||
async def add_line(
|
async def add_line(
|
||||||
self,
|
self,
|
||||||
form_id: int,
|
form_id: int,
|
||||||
@ -294,4 +380,3 @@ class SheetRepository:
|
|||||||
)
|
)
|
||||||
).all()
|
).all()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -170,6 +170,97 @@ class SheetService:
|
|||||||
return (result[0][0], [tuple(r[1:]) for r in result])
|
return (result[0][0], [tuple(r[1:]) for r in result])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
async def update_form4_program(
|
||||||
|
self,
|
||||||
|
program_id: int,
|
||||||
|
name: str,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
await self._validate_form4_node_access(
|
||||||
|
node_type="program",
|
||||||
|
node_id=program_id,
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
result = await self.sheet_repo.update_form4_program(
|
||||||
|
program_id=program_id,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
return (program_id, [tuple(r[1:]) for r in result])
|
||||||
|
|
||||||
|
async def delete_form4_program(
|
||||||
|
self,
|
||||||
|
program_id: int,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
await self._validate_form4_node_access(
|
||||||
|
node_type="program",
|
||||||
|
node_id=program_id,
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
result = await self.sheet_repo.delete_form4_program(program_id=program_id)
|
||||||
|
return (program_id, [tuple(r[1:]) for r in result])
|
||||||
|
|
||||||
|
async def update_form4_project(
|
||||||
|
self,
|
||||||
|
project_id: int,
|
||||||
|
name: str,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
await self._validate_form4_node_access(
|
||||||
|
node_type="project",
|
||||||
|
node_id=project_id,
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
result = await self.sheet_repo.update_form4_project(
|
||||||
|
project_id=project_id,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
return (project_id, [tuple(r[1:]) for r in result])
|
||||||
|
|
||||||
|
async def delete_form4_project(
|
||||||
|
self,
|
||||||
|
project_id: int,
|
||||||
|
user: AppUser,
|
||||||
|
) -> tuple[int, list[tuple]] | None:
|
||||||
|
await self._validate_form4_node_access(
|
||||||
|
node_type="project",
|
||||||
|
node_id=project_id,
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
result = await self.sheet_repo.delete_form4_project(project_id=project_id)
|
||||||
|
return (project_id, [tuple(r[1:]) for r in result])
|
||||||
|
|
||||||
|
async def _validate_form4_node_access(
|
||||||
|
self,
|
||||||
|
node_type: str,
|
||||||
|
node_id: int,
|
||||||
|
user: AppUser,
|
||||||
|
) -> None:
|
||||||
|
if node_type not in {"program", "project"}:
|
||||||
|
raise SheetValidationError("Некорректный тип узла")
|
||||||
|
|
||||||
|
level = (
|
||||||
|
Form4ProjectLevelEnum.PROGRAM
|
||||||
|
if node_type == "program"
|
||||||
|
else Form4ProjectLevelEnum.PROJECT
|
||||||
|
)
|
||||||
|
node = await self.f4_project_repo.get(project_id=node_id, level=level)
|
||||||
|
if not node:
|
||||||
|
raise SheetValidationError(
|
||||||
|
"Программа не найдена" if node_type == "program" else "Проект не найден"
|
||||||
|
)
|
||||||
|
|
||||||
|
if user.role_id != UserRoleEnum.ADMIN:
|
||||||
|
user = await self.user_repo.get(user_id=user.id, load_orgs=True)
|
||||||
|
form = await self.bf_repo.get(
|
||||||
|
budget_form_id=node.form_id,
|
||||||
|
org_unit=[ou.id for ou in user.org_units],
|
||||||
|
)
|
||||||
|
if not form:
|
||||||
|
raise SheetValidationError(
|
||||||
|
"Программа не найдена" if node_type == "program" else "Проект не найден"
|
||||||
|
)
|
||||||
|
|
||||||
async def delete_line(
|
async def delete_line(
|
||||||
self,
|
self,
|
||||||
form_id: int,
|
form_id: int,
|
||||||
@ -182,4 +273,4 @@ class SheetService:
|
|||||||
sheet=sheet,
|
sheet=sheet,
|
||||||
row_id=row_id,
|
row_id=row_id,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user