feat(vsp): выгрузка по статусу и периоду, закрытые ВСП в dropdown по include_inactive (AURORA-1883, #330)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
972dce8994
commit
ca79fdedcf
@ -67,12 +67,15 @@ async def get_vsp(
|
|||||||
async def get_vsp_dropdown(
|
async def get_vsp_dropdown(
|
||||||
ssp_id: int | None = None,
|
ssp_id: int | None = None,
|
||||||
form_id: int | None = None,
|
form_id: int | None = None,
|
||||||
|
include_inactive: bool = False,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: AppUser = Depends(require_executor),
|
current_user: AppUser = Depends(require_executor),
|
||||||
):
|
):
|
||||||
"""Получение записей для выпадающего списка INFO."""
|
"""Получение записей для выпадающего списка INFO."""
|
||||||
service = VSPService(db)
|
service = VSPService(db)
|
||||||
result = await service.get_dropdown(user=current_user, ssp_id=ssp_id, form_id=form_id, with_count=True)
|
result = await service.get_dropdown(
|
||||||
|
user=current_user, ssp_id=ssp_id, form_id=form_id, with_count=True, include_inactive=include_inactive,
|
||||||
|
)
|
||||||
return BaseListResponse(
|
return BaseListResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message="Список INFO для выпадающего списка",
|
message="Список INFO для выпадающего списка",
|
||||||
@ -99,6 +102,9 @@ async def export_info(
|
|||||||
placement_type=body.placement_type,
|
placement_type=body.placement_type,
|
||||||
staff_count_min=body.staff_count_min,
|
staff_count_min=body.staff_count_min,
|
||||||
staff_count_max=body.staff_count_max,
|
staff_count_max=body.staff_count_max,
|
||||||
|
status=body.status,
|
||||||
|
close_date_start=body.close_date_start,
|
||||||
|
close_date_end=body.close_date_end,
|
||||||
)
|
)
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
stream,
|
stream,
|
||||||
|
|||||||
@ -784,6 +784,9 @@ class VSPExportRequest(BaseModel):
|
|||||||
placement_type: Optional[str] = None
|
placement_type: Optional[str] = None
|
||||||
staff_count_min: Optional[int] = None
|
staff_count_min: Optional[int] = None
|
||||||
staff_count_max: Optional[int] = None
|
staff_count_max: Optional[int] = None
|
||||||
|
status: VSPStatus = "active"
|
||||||
|
close_date_start: Optional[date] = None
|
||||||
|
close_date_end: Optional[date] = None
|
||||||
|
|
||||||
|
|
||||||
class ExpenseItemBaseSchema(BaseModel):
|
class ExpenseItemBaseSchema(BaseModel):
|
||||||
|
|||||||
@ -115,6 +115,7 @@ class VSPService:
|
|||||||
ssp_id: int | None = None,
|
ssp_id: int | None = None,
|
||||||
form_id: int | None = None,
|
form_id: int | None = None,
|
||||||
with_count: bool = False,
|
with_count: bool = False,
|
||||||
|
include_inactive: bool = False,
|
||||||
) -> list[Vsp]:
|
) -> list[Vsp]:
|
||||||
ssp_ids = await self._get_scoped_ssp_ids(user=user)
|
ssp_ids = await self._get_scoped_ssp_ids(user=user)
|
||||||
|
|
||||||
@ -136,9 +137,10 @@ class VSPService:
|
|||||||
ssp_ids = list(checking_ssps & set(ssp_ids))
|
ssp_ids = list(checking_ssps & set(ssp_ids))
|
||||||
|
|
||||||
await self._sync_closed_records()
|
await self._sync_closed_records()
|
||||||
|
# AURORA-1883: include_inactive — с закрытыми, для подписей ВСП в листах форм
|
||||||
return await self.vsp_repo.get_list(
|
return await self.vsp_repo.get_list(
|
||||||
ssp_ids=ssp_ids,
|
ssp_ids=ssp_ids,
|
||||||
is_active=True,
|
is_active=None if include_inactive else True,
|
||||||
with_count=with_count,
|
with_count=with_count,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -177,6 +179,9 @@ class VSPService:
|
|||||||
placement_type: str | None = None,
|
placement_type: str | None = None,
|
||||||
staff_count_min: int | None = None,
|
staff_count_min: int | None = None,
|
||||||
staff_count_max: int | None = None,
|
staff_count_max: int | None = None,
|
||||||
|
status: VSPStatus = "active",
|
||||||
|
close_date_start: date | None = None,
|
||||||
|
close_date_end: date | None = None,
|
||||||
) -> tuple[io.BytesIO, str]:
|
) -> tuple[io.BytesIO, str]:
|
||||||
data = await self.get_list(
|
data = await self.get_list(
|
||||||
user=user,
|
user=user,
|
||||||
@ -188,7 +193,9 @@ class VSPService:
|
|||||||
placement_type=placement_type,
|
placement_type=placement_type,
|
||||||
staff_count_min=staff_count_min,
|
staff_count_min=staff_count_min,
|
||||||
staff_count_max=staff_count_max,
|
staff_count_max=staff_count_max,
|
||||||
is_active=True,
|
is_active=vsp_status_to_is_active(status),
|
||||||
|
close_date_start=close_date_start,
|
||||||
|
close_date_end=close_date_end,
|
||||||
load_org_unit=True,
|
load_org_unit=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -457,3 +457,42 @@ def test_vsp_list_closed_executor_scope(client, admin_tokens, isp_tokens, auth_h
|
|||||||
regs = _list_reg_numbers(client, isp_tokens, auth_headers, f"?status={status}")
|
regs = _list_reg_numbers(client, isp_tokens, auth_headers, f"?status={status}")
|
||||||
assert own in regs
|
assert own in regs
|
||||||
assert foreign not in regs
|
assert foreign not in regs
|
||||||
|
|
||||||
|
|
||||||
|
def test_vsp_dropdown_include_inactive(client, admin_tokens, auth_headers):
|
||||||
|
closed = _create_named_vsp(client, admin_tokens, auth_headers, ssp_id=2, close_date="2021-03-15")
|
||||||
|
|
||||||
|
with_closed = client.get(
|
||||||
|
"/api/v1/dict/info/dropdown?form_id=2&include_inactive=true", headers=auth_headers(admin_tokens)
|
||||||
|
)
|
||||||
|
assert with_closed.status_code == 200
|
||||||
|
by_reg = {item["registration_number"]: item for item in with_closed.json()["result"]}
|
||||||
|
assert closed in by_reg
|
||||||
|
assert by_reg[closed]["is_active"] is False
|
||||||
|
|
||||||
|
default = client.get("/api/v1/dict/info/dropdown?form_id=2", headers=auth_headers(admin_tokens))
|
||||||
|
assert closed not in [item["registration_number"] for item in default.json()["result"]]
|
||||||
|
|
||||||
|
|
||||||
|
def _export_values(client, tokens, auth_headers, body: dict) -> set:
|
||||||
|
import io
|
||||||
|
|
||||||
|
from openpyxl import load_workbook
|
||||||
|
|
||||||
|
response = client.post("/api/v1/dict/info/export", json=body, headers=auth_headers(tokens))
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
sheet = load_workbook(io.BytesIO(response.content)).active
|
||||||
|
return {cell for row in sheet.iter_rows(values_only=True) for cell in row}
|
||||||
|
|
||||||
|
|
||||||
|
def test_vsp_export_status_closed(client, admin_tokens, auth_headers):
|
||||||
|
active = _create_named_vsp(client, admin_tokens, auth_headers)
|
||||||
|
closed = _create_named_vsp(client, admin_tokens, auth_headers, close_date="2021-03-15")
|
||||||
|
|
||||||
|
closed_values = _export_values(client, admin_tokens, auth_headers, {"ssp_ids": [1], "status": "closed"})
|
||||||
|
assert closed in closed_values
|
||||||
|
assert active not in closed_values
|
||||||
|
|
||||||
|
default_values = _export_values(client, admin_tokens, auth_headers, {"ssp_ids": [1]})
|
||||||
|
assert active in default_values
|
||||||
|
assert closed not in default_values
|
||||||
|
|||||||
@ -6,3 +6,53 @@ from src.services.vsp_service import vsp_status_to_is_active
|
|||||||
@pytest.mark.parametrize("status, expected", [("active", True), ("closed", False), ("all", None)])
|
@pytest.mark.parametrize("status, expected", [("active", True), ("closed", False), ("all", None)])
|
||||||
def test_vsp_status_to_is_active(status, expected):
|
def test_vsp_status_to_is_active(status, expected):
|
||||||
assert vsp_status_to_is_active(status) is expected
|
assert vsp_status_to_is_active(status) is expected
|
||||||
|
|
||||||
|
|
||||||
|
# --- выгрузка и dropdown (AURORA-1883, задача 2) ---
|
||||||
|
from datetime import date
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
|
from src.db.models import UserRoleEnum
|
||||||
|
from src.services.vsp_service import VSPService
|
||||||
|
|
||||||
|
|
||||||
|
def _admin():
|
||||||
|
user = MagicMock()
|
||||||
|
user.id = 1
|
||||||
|
user.role_id = UserRoleEnum.ADMIN
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def _dropdown_service() -> VSPService:
|
||||||
|
service = VSPService.__new__(VSPService)
|
||||||
|
service.vsp_repo = MagicMock()
|
||||||
|
service.vsp_repo.get_list = AsyncMock(return_value=(0, []))
|
||||||
|
service.form_repo = MagicMock()
|
||||||
|
service.form_repo.get = AsyncMock(return_value=None)
|
||||||
|
service._get_scoped_ssp_ids = AsyncMock(return_value=None)
|
||||||
|
service._sync_closed_records = AsyncMock()
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_dropdown_include_inactive_passes_none():
|
||||||
|
service = _dropdown_service()
|
||||||
|
await service.get_dropdown(user=_admin(), ssp_id=2, include_inactive=True, with_count=True)
|
||||||
|
assert service.vsp_repo.get_list.await_args.kwargs["is_active"] is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_dropdown_default_active_only():
|
||||||
|
service = _dropdown_service()
|
||||||
|
await service.get_dropdown(user=_admin(), ssp_id=2, with_count=True)
|
||||||
|
assert service.vsp_repo.get_list.await_args.kwargs["is_active"] is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_export_xlsx_status_closed():
|
||||||
|
service = VSPService.__new__(VSPService)
|
||||||
|
service.get_list = AsyncMock(return_value=[])
|
||||||
|
|
||||||
|
await service.export_xlsx(user=_admin(), status="closed", close_date_start=date(2021, 1, 1))
|
||||||
|
|
||||||
|
kwargs = service.get_list.await_args.kwargs
|
||||||
|
assert kwargs["is_active"] is False
|
||||||
|
assert kwargs["close_date_start"] == date(2021, 1, 1)
|
||||||
|
assert kwargs["close_date_end"] is None
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user