tasks-filter: фильтры для задач форм 1 2 4 и обратная сортировка

This commit is contained in:
tsygankoviva 2026-09-02 12:30:14 +03:00
parent 0591217dcb
commit b34f1f7cad
4 changed files with 79 additions and 5 deletions

View File

@ -1,6 +1,6 @@
import logging import logging
import time import time
from typing import Optional from typing import Literal, Optional
from fastapi import APIRouter, Depends, HTTPException, Response from fastapi import APIRouter, Depends, HTTPException, Response
@ -41,6 +41,20 @@ def _parse_sections_csv(sections: Optional[str]) -> Optional[list[str]]:
return parsed or None return parsed or None
def _parse_org_units_csv(org_units: Optional[str]) -> Optional[list[int]]:
if org_units is None:
return None
values = [value.strip() for value in org_units.split(",") if value.strip()]
if not values:
return None
try:
return [int(value) for value in values]
except ValueError as exc:
raise HTTPException(400, "org_units должен содержать ID через запятую") from exc
def _validate_sheet_query_params( def _validate_sheet_query_params(
form_type_code: FormTypeEnum, form_type_code: FormTypeEnum,
sheet: str, sheet: str,
@ -63,16 +77,23 @@ def _validate_sheet_query_params(
async def get_forms( async def get_forms(
offset: int = 0, offset: int = 0,
limit: int = 100, limit: int = 100,
org_units: str | None = None,
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
year: int | 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]:
"""Все строки v3.budget_form для UI-выпадашки.""" """Все строки v3.budget_form для UI-выпадашки."""
org_unit_ids = _parse_org_units_csv(org_units)
bf_service = BudgetFormService(db) bf_service = BudgetFormService(db)
count, forms = await bf_service.get_list( count, forms = await bf_service.get_list(
user=current_user, user=current_user,
with_count=True, with_count=True,
offset=offset, offset=offset,
limit=limit, limit=limit,
org_unit=org_unit_ids,
form_type=form_type,
year=year,
load_org=True, load_org=True,
) )
return BaseListResponse( return BaseListResponse(

View File

@ -18,6 +18,8 @@ class BudgetFormRepository:
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,
year: int | None = None,
) -> 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)
@ -30,13 +32,18 @@ class BudgetFormRepository:
else: else:
where.append(BudgetForm.org_unit_id.in_(org_unit)) where.append(BudgetForm.org_unit_id.in_(org_unit))
if form_type is not None:
where.append(BudgetForm.form_type_code == form_type)
if year is not None:
where.append(BudgetForm.year == year)
query = query.where(*where) query = query.where(*where)
if load_org: if load_org:
query = query.options( query = query.options(
joinedload(BudgetForm.org_unit) joinedload(BudgetForm.org_unit)
) )
query = query.order_by(BudgetForm.id) query = query.order_by(BudgetForm.id.desc())
if offset is not None: if offset is not None:
query = query.offset(offset) query = query.offset(offset)
@ -129,4 +136,4 @@ class BudgetFormRepository:
return None return None
return await self.get( return await self.get(
budget_form_id=form_id, budget_form_id=form_id,
) )

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
from typing import Literal
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -24,6 +25,9 @@ class BudgetFormService:
limit: int | None = None, limit: int | None = None,
with_count: bool = False, with_count: bool = False,
load_org: bool = False, load_org: bool = False,
org_unit: list[int] | None = None,
form_type: Literal["FORM_1", "FORM_2", "FORM_4"] | None = None,
year: int | None = None,
) -> list[BudgetForm] | tuple[int, list[BudgetForm]]: ) -> list[BudgetForm] | tuple[int, list[BudgetForm]]:
if user.role_id == UserRoleEnum.ADMIN: if user.role_id == UserRoleEnum.ADMIN:
return await self.bf_repo.get_list( return await self.bf_repo.get_list(
@ -31,17 +35,29 @@ class BudgetFormService:
limit=limit, limit=limit,
with_count=with_count, with_count=with_count,
load_org=load_org, load_org=load_org,
org_unit=org_unit,
form_type=form_type,
year=year,
) )
user = await self.user_repo.get( user = await self.user_repo.get(
user_id=user.id, user_id=user.id,
load_orgs=True, load_orgs=True,
) )
allowed_org_units = [ou.id for ou in user.org_units]
if org_unit is not None:
allowed_org_units = [
org_unit_id
for org_unit_id in org_unit
if org_unit_id in allowed_org_units
]
return await self.bf_repo.get_list( return await self.bf_repo.get_list(
offset=offset, offset=offset,
limit=limit, limit=limit,
org_unit=[ou.id for ou in user.org_units], org_unit=allowed_org_units,
with_count=with_count, with_count=with_count,
load_org=load_org, load_org=load_org,
form_type=form_type,
year=year,
) )
async def get( async def get(

View File

@ -10,6 +10,36 @@ def test_forms_list_smoke(client, admin_tokens, auth_headers):
assert isinstance(payload["result"], list) assert isinstance(payload["result"], list)
def test_forms_list_filters_and_orders_by_created_at_desc(
client, admin_tokens, auth_headers
):
response = client.get(
"/api/v1/form/",
params={"org_units": "1,2", "form_type": "FORM_2", "year": 2026},
headers=auth_headers(admin_tokens),
)
assert response.status_code == 200
payload = response.json()
assert payload["count"] == 1
assert [form["id"] for form in payload["result"]] == [4]
response = client.get("/api/v1/form/", headers=auth_headers(admin_tokens))
assert response.status_code == 200
forms = response.json()["result"]
assert [form["id"] for form in forms] == sorted(
(form["id"] for form in forms), reverse=True
)
def test_forms_list_rejects_invalid_org_units(client, admin_tokens, auth_headers):
response = client.get(
"/api/v1/form/",
params={"org_units": "1,invalid"},
headers=auth_headers(admin_tokens),
)
assert response.status_code == 400
def test_form_smoke(client, admin_tokens, auth_headers): def test_form_smoke(client, admin_tokens, auth_headers):
response = client.get("/api/v1/form/1", headers=auth_headers(admin_tokens)) response = client.get("/api/v1/form/1", headers=auth_headers(admin_tokens))
assert response.status_code == 200 assert response.status_code == 200