row_json_select(strip_nulls) оборачивает data в jsonb_strip_nulls внутри json_build_object; флаг пробрасывается через get_json/get_svod1_json/get_svod2_json, роутеры листа и сводов включают его. Проекты, PATCH/POST/DELETE не затронуты. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
306 lines
9.8 KiB
Python
306 lines
9.8 KiB
Python
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.db.models.form4_project import Form4ProjectLevelEnum
|
|
from src.db.models.role import UserRoleEnum
|
|
from src.repository.budget_form_repository import BudgetFormRepository
|
|
from src.repository.form4_project_repository import Form4ProjectRepository
|
|
from src.repository.user_repository import UserRepository
|
|
from src.db.models.app_user import AppUser
|
|
from src.db.models.budget_form import BudgetForm
|
|
from src.repository.sheet_repository import SheetRepository, SheetValidationError
|
|
|
|
|
|
|
|
class SheetService:
|
|
def __init__(self, db: AsyncSession):
|
|
self.db = db
|
|
self.sheet_repo = SheetRepository(db)
|
|
self.bf_repo = BudgetFormRepository(db)
|
|
self.user_repo = UserRepository(db)
|
|
self.f4_project_repo = Form4ProjectRepository(db)
|
|
|
|
async def get(
|
|
self,
|
|
sheet: str,
|
|
direction: str | None,
|
|
sections: list[str] | None = None,
|
|
form_id: int | None = None,
|
|
form: BudgetForm | None = None
|
|
) -> list[tuple]:
|
|
return await self.sheet_repo.get(
|
|
form_id=form_id,
|
|
sheet=sheet,
|
|
direction=direction,
|
|
sections=sections,
|
|
)
|
|
|
|
async def get_json(
|
|
self,
|
|
sheet: str,
|
|
direction: str | None,
|
|
sections: list[str] | None = None,
|
|
form_id: int | None = None,
|
|
form: BudgetForm | None = None,
|
|
strip_nulls: bool = False,
|
|
) -> list[str]:
|
|
return await self.sheet_repo.get_json(
|
|
form_id=form_id,
|
|
sheet=sheet,
|
|
direction=direction,
|
|
sections=sections,
|
|
strip_nulls=strip_nulls,
|
|
)
|
|
|
|
async def update_cells(
|
|
self,
|
|
form_id: int,
|
|
sheet: str,
|
|
direction: str | None,
|
|
sections: list[str] | None,
|
|
changes: list[dict],
|
|
user: AppUser | None = None,
|
|
) -> list[tuple]:
|
|
return await self.sheet_repo.update_cells(
|
|
form_id=form_id,
|
|
sheet=sheet,
|
|
direction=direction,
|
|
sections=sections,
|
|
changes=changes,
|
|
user_id=user.id if user else None,
|
|
)
|
|
|
|
async def update_cell(
|
|
self,
|
|
form_id: int,
|
|
sheet: str,
|
|
direction: str | None,
|
|
sections: list[str] | None,
|
|
line_id: int,
|
|
column: str,
|
|
value: str | int | float | None = None,
|
|
user: AppUser | None = None,
|
|
) -> list[tuple]:
|
|
return await self.sheet_repo.update_cell(
|
|
form_id=form_id,
|
|
sheet=sheet,
|
|
direction=direction,
|
|
sections=sections,
|
|
line_id=line_id,
|
|
column=column,
|
|
value=value,
|
|
user_id=user.id if user else None,
|
|
)
|
|
|
|
|
|
async def add_line(
|
|
self,
|
|
form_id: int,
|
|
sheet: str,
|
|
expense_item_id: int | None = None,
|
|
item_id: str | None = None,
|
|
section_code: str | None = None,
|
|
direction: str | None = None,
|
|
name: str | None = None,
|
|
internal_order: str | None = None,
|
|
vsp_id: int | None = None,
|
|
project_id: int | None = None,
|
|
justification: str | None = None,
|
|
contract_number: str | None = None,
|
|
contract_end_date: str | None = None,
|
|
user: AppUser | None = None,
|
|
) -> list[tuple]:
|
|
return await self.sheet_repo.add_line(
|
|
form_id=form_id,
|
|
sheet=sheet,
|
|
expense_item_id=expense_item_id,
|
|
item_id=item_id,
|
|
section_code=section_code,
|
|
direction=direction,
|
|
name=name,
|
|
internal_order=internal_order,
|
|
vsp_id=vsp_id,
|
|
project_id=project_id,
|
|
justification=justification,
|
|
contract_number=contract_number,
|
|
contract_end_date=contract_end_date,
|
|
user_id=user.id if user else None,
|
|
)
|
|
|
|
async def add_program(
|
|
self,
|
|
form_id: int,
|
|
sheet: str,
|
|
name: str,
|
|
user: AppUser | None = None,
|
|
) -> tuple[int, list[tuple]]:
|
|
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=form_id,
|
|
org_unit=[ou.id for ou in user.org_units],
|
|
)
|
|
if not form:
|
|
raise SheetValidationError("Форма не найдена")
|
|
|
|
result = await self.sheet_repo.add_program(
|
|
form_id=form_id,
|
|
sheet=sheet,
|
|
name=name,
|
|
)
|
|
if result:
|
|
return (result[0][0], [tuple(r[1:]) for r in result])
|
|
return None
|
|
|
|
async def add_project(
|
|
self,
|
|
name: str,
|
|
program_id: int,
|
|
user: AppUser | None = None,
|
|
) -> tuple[int, list[tuple]]:
|
|
if user.role_id != UserRoleEnum.ADMIN:
|
|
program = await self.f4_project_repo.get(
|
|
project_id=program_id,
|
|
level=Form4ProjectLevelEnum.PROGRAM,
|
|
)
|
|
if not program:
|
|
raise SheetValidationError("Программа не найдена")
|
|
|
|
user = await self.user_repo.get(
|
|
user_id=user.id,
|
|
load_orgs=True,
|
|
)
|
|
form = await self.bf_repo.get(
|
|
budget_form_id=program.form_id,
|
|
org_unit=[ou.id for ou in user.org_units],
|
|
)
|
|
if not form:
|
|
raise SheetValidationError("Программа не найдена")
|
|
result = await self.sheet_repo.add_project(
|
|
program_id=program_id,
|
|
name=name,
|
|
)
|
|
if result:
|
|
return (result[0][0], [tuple(r[1:]) for r in result])
|
|
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(
|
|
self,
|
|
form_id: int,
|
|
sheet: str,
|
|
row_id: int,
|
|
direction: str | None = None,
|
|
user: AppUser | None = None,
|
|
) -> list[tuple]:
|
|
return await self.sheet_repo.delete_line(
|
|
sheet=sheet,
|
|
row_id=row_id,
|
|
direction=direction,
|
|
)
|
|
|
|
async def get_form2_ahr_satellite_delta(
|
|
self,
|
|
form_id: int,
|
|
source_sheet: str,
|
|
source_row_id: int,
|
|
) -> list[tuple]:
|
|
return await self.sheet_repo.get_form2_ahr_satellite_delta(
|
|
form_id=form_id,
|
|
source_sheet=source_sheet,
|
|
source_row_id=source_row_id,
|
|
)
|