Compare commits

..

4 Commits

Author SHA1 Message Date
5537b8229e Merge pull request 'AURORA-1174-73' (#29) from AURORA-1174-73 into test
Reviewed-on: #29
Reviewed-by: tsygankoviva <tsygankov.itis@gmail.com>
2026-07-09 10:30:22 +03:00
Raykov-MS
5eb73ddb37 clean 004 migration 2026-07-08 12:54:17 +03:00
Raykov-MS
4d5a39ef12 Merge remote-tracking branch 'origin/test' into AURORA-1174-73 2026-07-08 12:50:26 +03:00
Raykov-MS
ccba554b86 solve bugs 2026-07-08 12:50:21 +03:00
3 changed files with 133 additions and 1 deletions

View File

@ -0,0 +1,63 @@
from alembic import op
revision = "0004"
down_revision = "0003"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute(
"""
CREATE OR REPLACE FUNCTION v3.del_vsp(p_vsp_id integer, p_deleted_by integer)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_old RECORD;
v_usr RECORD;
BEGIN
SELECT vsp.id, vsp.branch_id
INTO v_old
FROM v3.vsp vsp WHERE vsp.id = p_vsp_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'vsp #% не существует', p_vsp_id;
END IF;
SELECT usr.id
INTO v_usr
FROM v3.app_user usr WHERE usr.id = p_deleted_by;
IF NOT FOUND THEN
RAISE EXCEPTION 'user #% не существует', p_deleted_by;
END IF;
UPDATE v3.vsp
SET is_deleted = true,
is_active = false,
updated_by = p_deleted_by
WHERE id = p_vsp_id;
UPDATE v3.vsp
SET closed_at = CURRENT_DATE
WHERE id = p_vsp_id
AND closed_at IS NULL;
PERFORM v3.log_event(
'DELETE_VSP', 'VSP',
jsonb_build_object(
'vsp_id', p_vsp_id,
'entity_id', p_vsp_id,
'branch_id', v_old.branch_id,
'updated_by', p_deleted_by
),
null, null, v_old.branch_id
);
RETURN p_vsp_id;
END;
$function$;
"""
)
def downgrade() -> None:
pass

View File

@ -11,6 +11,7 @@ from src.repository.budget_form_repository import BudgetFormRepository
from src.core.errors import AccessDeniedException, ValidationException
from src.db.models import AppUser, UserRoleEnum, Vsp
from src.domain.schemas import VSPCreate, VSPUpdate
from src.repository.org_unit_repository import OrgUnitRepository
from src.repository.user_repository import UserRepository
from src.repository.vsp_repository import VSPRepository
@ -31,6 +32,7 @@ class VSPService:
self.db = db
self.vsp_repo = VSPRepository(db)
self.form_repo = BudgetFormRepository(db)
self.org_unit_repo = OrgUnitRepository(db)
self.user_repo = UserRepository(db)
async def get(self, vsp_id: int, user: AppUser, load_org_unit: bool = False) -> Optional[Vsp]:
@ -128,6 +130,7 @@ class VSPService:
async def create(self, payload: VSPCreate, user: AppUser, load_org_unit: bool = False) -> Vsp:
if not self._can_modify(user):
raise AccessDeniedException()
await self._ensure_active_branch(payload.branch_id)
try:
created = await self.vsp_repo.create(payload=payload, created_by=user.id, load_org_unit=load_org_unit)
if created.closed_at is not None and created.closed_at <= date.today():
@ -245,6 +248,9 @@ class VSPService:
if not data:
return vsp
if "branch_id" in data:
await self._ensure_active_branch(data["branch_id"])
if user.role_id != UserRoleEnum.ADMIN:
ssp_ids = await self._get_scoped_ssp_ids(user=user)
if not ssp_ids or vsp.branch_id not in set(ssp_ids):
@ -291,3 +297,10 @@ class VSPService:
return
await self.vsp_repo.deactivate_by_close_date(as_of=today)
_last_info_close_sync_run = today
async def _ensure_active_branch(self, branch_id: int | None) -> None:
if branch_id is None:
return
org_unit = await self.org_unit_repo.get(org_unit_id=branch_id)
if org_unit is None or not org_unit.is_active:
raise ValidationException(description="Указан неактивный или отсутствующий ССП/РФ")

View File

@ -150,6 +150,36 @@ def test_vsp_create_forbidden_for_executor(client, isp_tokens, auth_headers):
assert "message" in payload
def test_vsp_create_with_inactive_ssp_fails(client, admin_tokens, auth_headers):
created_org = client.post(
"/api/v1/org-unit/",
json={"title": f"Inactive SSP {uuid.uuid4().hex[:6]}", "is_ssp": True},
headers=auth_headers(admin_tokens),
)
assert created_org.status_code == 200
org_unit_id = created_org.json()["result"]["id"]
deactivate = client.patch(
f"/api/v1/org-unit/{org_unit_id}",
json={"is_active": False},
headers=auth_headers(admin_tokens),
)
assert deactivate.status_code == 200
response = client.post(
"/api/v1/dict/info",
json={
"registration_number": _unique_reg_number(),
"address": "Should Fail",
"ssp_id": org_unit_id,
},
headers=auth_headers(admin_tokens),
)
assert response.status_code == 400
payload = response.json()
assert "message" in payload
def test_vsp_update_smoke(client, admin_tokens, auth_headers):
reg_number = _unique_reg_number()
created = client.post(
@ -195,7 +225,33 @@ def test_vsp_delete_smoke(client, admin_tokens, auth_headers):
json={
"registration_number": reg_number,
"address": "To Be Deleted",
"ssp_id": 1,
"ssp_id": 2,
},
headers=auth_headers(admin_tokens),
)
assert created.status_code == 201
vsp_id = created.json()["result"]["id"]
response = client.delete(
f"/api/v1/dict/info/{vsp_id}",
headers=auth_headers(admin_tokens),
)
assert response.status_code == 200
payload = response.json()
assert payload["success"] is True
assert payload["message"] == "Запись INFO удалена"
def test_vsp_delete_with_close_date_smoke(client, admin_tokens, auth_headers):
reg_number = _unique_reg_number()
created = client.post(
"/api/v1/dict/info",
json={
"registration_number": reg_number,
"address": "To Be Deleted with close_date",
"ssp_id": 2,
"open_date": "2026-01-01",
"close_date": "2030-01-01",
},
headers=auth_headers(admin_tokens),
)