Compare commits
4 Commits
c4dd35b6bd
...
5537b8229e
| Author | SHA1 | Date | |
|---|---|---|---|
| 5537b8229e | |||
|
|
5eb73ddb37 | ||
|
|
4d5a39ef12 | ||
|
|
ccba554b86 |
63
api/alembic/versions/0004_vsp_delete_with_close_date.py
Normal file
63
api/alembic/versions/0004_vsp_delete_with_close_date.py
Normal 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
|
||||||
@ -11,6 +11,7 @@ from src.repository.budget_form_repository import BudgetFormRepository
|
|||||||
from src.core.errors import AccessDeniedException, ValidationException
|
from src.core.errors import AccessDeniedException, ValidationException
|
||||||
from src.db.models import AppUser, UserRoleEnum, Vsp
|
from src.db.models import AppUser, UserRoleEnum, Vsp
|
||||||
from src.domain.schemas import VSPCreate, VSPUpdate
|
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.user_repository import UserRepository
|
||||||
from src.repository.vsp_repository import VSPRepository
|
from src.repository.vsp_repository import VSPRepository
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ class VSPService:
|
|||||||
self.db = db
|
self.db = db
|
||||||
self.vsp_repo = VSPRepository(db)
|
self.vsp_repo = VSPRepository(db)
|
||||||
self.form_repo = BudgetFormRepository(db)
|
self.form_repo = BudgetFormRepository(db)
|
||||||
|
self.org_unit_repo = OrgUnitRepository(db)
|
||||||
self.user_repo = UserRepository(db)
|
self.user_repo = UserRepository(db)
|
||||||
|
|
||||||
async def get(self, vsp_id: int, user: AppUser, load_org_unit: bool = False) -> Optional[Vsp]:
|
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:
|
async def create(self, payload: VSPCreate, user: AppUser, load_org_unit: bool = False) -> Vsp:
|
||||||
if not self._can_modify(user):
|
if not self._can_modify(user):
|
||||||
raise AccessDeniedException()
|
raise AccessDeniedException()
|
||||||
|
await self._ensure_active_branch(payload.branch_id)
|
||||||
try:
|
try:
|
||||||
created = await self.vsp_repo.create(payload=payload, created_by=user.id, load_org_unit=load_org_unit)
|
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():
|
if created.closed_at is not None and created.closed_at <= date.today():
|
||||||
@ -245,6 +248,9 @@ class VSPService:
|
|||||||
if not data:
|
if not data:
|
||||||
return vsp
|
return vsp
|
||||||
|
|
||||||
|
if "branch_id" in data:
|
||||||
|
await self._ensure_active_branch(data["branch_id"])
|
||||||
|
|
||||||
if user.role_id != UserRoleEnum.ADMIN:
|
if user.role_id != UserRoleEnum.ADMIN:
|
||||||
ssp_ids = await self._get_scoped_ssp_ids(user=user)
|
ssp_ids = await self._get_scoped_ssp_ids(user=user)
|
||||||
if not ssp_ids or vsp.branch_id not in set(ssp_ids):
|
if not ssp_ids or vsp.branch_id not in set(ssp_ids):
|
||||||
@ -291,3 +297,10 @@ class VSPService:
|
|||||||
return
|
return
|
||||||
await self.vsp_repo.deactivate_by_close_date(as_of=today)
|
await self.vsp_repo.deactivate_by_close_date(as_of=today)
|
||||||
_last_info_close_sync_run = 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="Указан неактивный или отсутствующий ССП/РФ")
|
||||||
|
|||||||
@ -150,6 +150,36 @@ def test_vsp_create_forbidden_for_executor(client, isp_tokens, auth_headers):
|
|||||||
assert "message" in payload
|
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):
|
def test_vsp_update_smoke(client, admin_tokens, auth_headers):
|
||||||
reg_number = _unique_reg_number()
|
reg_number = _unique_reg_number()
|
||||||
created = client.post(
|
created = client.post(
|
||||||
@ -195,7 +225,33 @@ def test_vsp_delete_smoke(client, admin_tokens, auth_headers):
|
|||||||
json={
|
json={
|
||||||
"registration_number": reg_number,
|
"registration_number": reg_number,
|
||||||
"address": "To Be Deleted",
|
"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),
|
headers=auth_headers(admin_tokens),
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user