Compare commits
No commits in common. "8b86735828270cbf4dd15367ab03b6ca4ba157c0" and "f5ddba7005aa70c591d936f95f2011792da9dd54" have entirely different histories.
8b86735828
...
f5ddba7005
@ -335,51 +335,33 @@ class LegalEntityFilter(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RelatedGroupCreate(BaseModel):
|
class RelatedGroupCreate(BaseModel):
|
||||||
"""Создание ГСК/ГСЗ в ГК: `code` (`GSK-`/`GSZ-NNNNNN`) назначает сервис.
|
"""Создание ГСК/ГСЗ в ГК: `code` (`GSK-`/`GSZ-NNNNNN`) назначает сервис."""
|
||||||
|
|
||||||
`crm_id` («ID ГСК/ГСЗ в CRM» форм 51-03/51-04) принимается и отбрасывается: колонки в DBML нет (arm-core #112).
|
|
||||||
"""
|
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
kind: RelatedGroupKind
|
kind: RelatedGroupKind
|
||||||
name: str = Field(min_length=1, max_length=NAME_MAX_LENGTH)
|
name: str = Field(min_length=1, max_length=NAME_MAX_LENGTH)
|
||||||
created_date: datetime.date | None = None
|
created_date: datetime.date | None = None
|
||||||
crm_id: str | None = None
|
|
||||||
|
|
||||||
@field_validator("name")
|
@field_validator("name")
|
||||||
@classmethod
|
@classmethod
|
||||||
def _strip_name(cls, value: str) -> str | None:
|
def _strip_name(cls, value: str) -> str | None:
|
||||||
return _strip_required_name(value)
|
return _strip_required_name(value)
|
||||||
|
|
||||||
@field_validator("crm_id")
|
|
||||||
@classmethod
|
|
||||||
def _normalize_crm_id(cls, value: str | None) -> str | None:
|
|
||||||
return _blank_to_none(value)
|
|
||||||
|
|
||||||
|
|
||||||
class RelatedGroupUpdate(BaseModel):
|
class RelatedGroupUpdate(BaseModel):
|
||||||
"""Изменение ГСК/ГСЗ: `name`, `created_date` (вид и ГК не меняются); переданные поля — `model_fields_set`.
|
"""Изменение ГСК/ГСЗ: `name`, `created_date` (вид и ГК не меняются); переданные поля — `model_fields_set`."""
|
||||||
|
|
||||||
`crm_id` (форма 77-02) принимается и отбрасывается, как при создании (arm-core #112).
|
|
||||||
"""
|
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
name: str | None = Field(default=None, min_length=1, max_length=NAME_MAX_LENGTH)
|
name: str | None = Field(default=None, min_length=1, max_length=NAME_MAX_LENGTH)
|
||||||
created_date: datetime.date | None = None
|
created_date: datetime.date | None = None
|
||||||
crm_id: str | None = None
|
|
||||||
|
|
||||||
@field_validator("name")
|
@field_validator("name")
|
||||||
@classmethod
|
@classmethod
|
||||||
def _strip_name(cls, value: str | None) -> str | None:
|
def _strip_name(cls, value: str | None) -> str | None:
|
||||||
return _strip_required_name(value)
|
return _strip_required_name(value)
|
||||||
|
|
||||||
@field_validator("crm_id")
|
|
||||||
@classmethod
|
|
||||||
def _normalize_crm_id(cls, value: str | None) -> str | None:
|
|
||||||
return _blank_to_none(value)
|
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def _name_is_not_null(self) -> Self:
|
def _name_is_not_null(self) -> Self:
|
||||||
if "name" in self.model_fields_set and self.name is None:
|
if "name" in self.model_fields_set and self.name is None:
|
||||||
@ -396,7 +378,7 @@ class MemberAdd(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RelatedGroupOut(BaseModel):
|
class RelatedGroupOut(BaseModel):
|
||||||
"""ГСК/ГСЗ; `members_count` — число текущих членов; `crm_id` — всегда `null`, пока нет колонки (arm-core #112)."""
|
"""ГСК/ГСЗ; `members_count` — число текущих членов."""
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
@ -406,7 +388,6 @@ class RelatedGroupOut(BaseModel):
|
|||||||
kind: RelatedGroupKind
|
kind: RelatedGroupKind
|
||||||
name: str
|
name: str
|
||||||
created_date: datetime.date | None
|
created_date: datetime.date | None
|
||||||
crm_id: str | None = None
|
|
||||||
members_count: int = 0
|
members_count: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
"""`crm_id` ГСК/ГСЗ в API без колонки в БД (arm-core #111; колонка — #112).
|
|
||||||
|
|
||||||
Формы 51-03 / 51-04 / 77-02 шлют «ID ГСК/ГСЗ в CRM»: POST/PATCH его принимают, значение отбрасывается,
|
|
||||||
в ответах `crm_id` всегда `null`, в аудит не попадает.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
import pytest
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from src.models.audit_ex import AuditEvent
|
|
||||||
from src.models.enums import AuditAction
|
|
||||||
from tests.conftest import AuthClientFactory
|
|
||||||
from tests.factories import make_company_group
|
|
||||||
|
|
||||||
HTTP_OK = 200
|
|
||||||
HTTP_CREATED = 201
|
|
||||||
HTTP_UNPROCESSABLE = 422
|
|
||||||
GROUPS = "/directories/company-groups"
|
|
||||||
RELATED = "/directories/related-groups"
|
|
||||||
GO = "ka_go@armka.local"
|
|
||||||
|
|
||||||
|
|
||||||
async def _events(session: AsyncSession) -> Sequence[AuditEvent]:
|
|
||||||
stmt = select(AuditEvent).where(
|
|
||||||
AuditEvent.action == AuditAction.MEMBERSHIP_CHANGE, AuditEvent.object_type == "related_group"
|
|
||||||
)
|
|
||||||
return (await session.execute(stmt.order_by(AuditEvent.occurred_at, AuditEvent.id))).scalars().all()
|
|
||||||
|
|
||||||
|
|
||||||
async def _create(client: httpx.AsyncClient, group_id: Any, **extra: Any) -> httpx.Response:
|
|
||||||
return await client.post(f"{GROUPS}/{group_id}/related-groups", json={"kind": "gsk", "name": "ГСК А", **extra})
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration
|
|
||||||
async def test_create_accepts_crm_id_and_drops_it(auth_client: AuthClientFactory, seeded: AsyncSession) -> None:
|
|
||||||
group = await make_company_group(seeded)
|
|
||||||
client = await auth_client(GO)
|
|
||||||
|
|
||||||
response = await _create(client, group.id, crm_id=" 3-9CGSK01 ")
|
|
||||||
assert response.status_code == HTTP_CREATED, response.text
|
|
||||||
body = response.json()
|
|
||||||
assert body["crm_id"] is None
|
|
||||||
assert body["name"] == "ГСК А"
|
|
||||||
events = await _events(seeded)
|
|
||||||
assert len(events) == 1
|
|
||||||
assert "crm_id" not in events[0].after_value
|
|
||||||
|
|
||||||
for value in ("", None):
|
|
||||||
assert (await _create(client, group.id, crm_id=value)).status_code == HTTP_CREATED
|
|
||||||
assert (await _create(client, group.id, crm_id=123)).status_code == HTTP_UNPROCESSABLE
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration
|
|
||||||
async def test_patch_accepts_crm_id_without_changes(auth_client: AuthClientFactory, seeded: AsyncSession) -> None:
|
|
||||||
group = await make_company_group(seeded)
|
|
||||||
client = await auth_client(GO)
|
|
||||||
gsk = (await _create(client, group.id)).json()
|
|
||||||
|
|
||||||
only_crm = await client.patch(f"{RELATED}/{gsk['id']}", json={"crm_id": "3-9CGSK01"})
|
|
||||||
assert only_crm.status_code == HTTP_OK, only_crm.text
|
|
||||||
assert only_crm.json()["crm_id"] is None
|
|
||||||
assert len(await _events(seeded)) == 1 # только создание: crm_id не изменение
|
|
||||||
|
|
||||||
with_name = await client.patch(f"{RELATED}/{gsk['id']}", json={"crm_id": None, "name": "ГСК Б"})
|
|
||||||
assert with_name.status_code == HTTP_OK, with_name.text
|
|
||||||
assert (with_name.json()["name"], with_name.json()["crm_id"]) == ("ГСК Б", None)
|
|
||||||
events = await _events(seeded)
|
|
||||||
assert len(events) == 2 # noqa: PLR2004 - создание и смена имени
|
|
||||||
assert events[1].after_value == {"name": "ГСК Б"}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.integration
|
|
||||||
async def test_list_and_detail_return_null_crm_id(auth_client: AuthClientFactory, seeded: AsyncSession) -> None:
|
|
||||||
group = await make_company_group(seeded)
|
|
||||||
client = await auth_client(GO)
|
|
||||||
gsk = (await _create(client, group.id, crm_id="3-9CGSK01")).json()
|
|
||||||
|
|
||||||
listed = await client.get(f"{GROUPS}/{group.id}/related-groups")
|
|
||||||
assert [row["crm_id"] for row in listed.json()] == [None]
|
|
||||||
assert (await client.get(f"{RELATED}/{gsk['id']}")).json()["crm_id"] is None
|
|
||||||
@ -83,7 +83,6 @@ async def test_create_gsk_and_gsz_codes_and_audit(auth_client: AuthClientFactory
|
|||||||
"kind": "gsk",
|
"kind": "gsk",
|
||||||
"name": "ГСК Бисквит",
|
"name": "ГСК Бисквит",
|
||||||
"created_date": "2025-01-15",
|
"created_date": "2025-01-15",
|
||||||
"crm_id": None,
|
|
||||||
"members_count": 0,
|
"members_count": 0,
|
||||||
}
|
}
|
||||||
assert gsz_body["created_date"] is None
|
assert gsz_body["created_date"] is None
|
||||||
|
|||||||
@ -152,13 +152,7 @@ SHALL установить `company_group_id = NULL`, при `CLOSE` — ещё
|
|||||||
`DIFFERENT_COMPANY_GROUP`); если у ЮЛ уже есть текущее членство в группе того же вида — 409 `MEMBER_OF_OTHER_GROUP`
|
`DIFFERENT_COMPANY_GROUP`); если у ЮЛ уже есть текущее членство в группе того же вида — 409 `MEMBER_OF_OTHER_GROUP`
|
||||||
(с `related_group_id`); повтор в той же группе — 409 `MEMBER_EXISTS`. `DELETE …/members/{legal_entity_id}` SHALL закрывать
|
(с `related_group_id`); повтор в той же группе — 409 `MEMBER_EXISTS`. `DELETE …/members/{legal_entity_id}` SHALL закрывать
|
||||||
членство (`valid_to`, `is_current = false`). Все операции пишут `MEMBERSHIP_CHANGE`. `GET …/related-groups` и
|
членство (`valid_to`, `is_current = false`). Все операции пишут `MEMBERSHIP_CHANGE`. `GET …/related-groups` и
|
||||||
`GET …/related-groups/{id}` SHALL отдавать группы с числом текущих членов и списком членов. Тела `POST` и `PATCH` SHALL
|
`GET …/related-groups/{id}` SHALL отдавать группы с числом текущих членов и списком членов.
|
||||||
принимать необязательный `crm_id` («ID ГСК/ГСЗ в CRM» форм 51-03/51-04/77-02) и отбрасывать его: колонки у `related_group`
|
|
||||||
нет (arm-core #112); в ответах `crm_id` всегда `null`, в аудит и в список изменений `PATCH` не попадает.
|
|
||||||
|
|
||||||
#### Scenario: `crm_id` ГСК принимается и не сохраняется
|
|
||||||
- **WHEN** ГСК создаётся с `crm_id = "3-9CGSK01"`, затем `PATCH` передаёт только `crm_id`
|
|
||||||
- **THEN** создание — 201 с `crm_id = null`; `PATCH` — 200 без изменений и без события аудита
|
|
||||||
|
|
||||||
#### Scenario: Одновременно в ГСК и ГСЗ
|
#### Scenario: Одновременно в ГСК и ГСЗ
|
||||||
- **WHEN** ЮЛ добавлено в ГСК своей ГК, затем в ГСЗ той же ГК
|
- **WHEN** ЮЛ добавлено в ГСК своей ГК, затем в ГСЗ той же ГК
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user