From 462a30fcf641c605b9cd31f7827b4da9bb5ac3ba Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 4 Oct 2026 21:46:57 +0400 Subject: [PATCH] =?UTF-8?q?fix(directories):=20PATCH=20=D0=AE=D0=9B=20?= =?UTF-8?q?=D1=81=20=D0=BF=D1=80=D0=B5=D0=B6=D0=BD=D0=B8=D0=BC=20=D1=87?= =?UTF-8?q?=D1=83=D0=B6=D0=B8=D0=BC=20=D1=84=D0=B8=D0=BB=D0=B8=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE=D0=BC=20=E2=80=94=20=D0=BD=D0=B5=20422=20(I-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `office_for_create` применяется к `regional_office_id` только при фактической смене филиала: тело с тем же значением — no-op (Review Focus 1), правка `name` в том же запросе проходит. Тест на ЮЛ в ГК своего филиала с чужим `regional_office_id`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- api/src/services/legal_entities.py | 4 +++- api/tests/test_api_legal_entities.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/api/src/services/legal_entities.py b/api/src/services/legal_entities.py index b90f1e9..dbab8c4 100644 --- a/api/src/services/legal_entities.py +++ b/api/src/services/legal_entities.py @@ -293,8 +293,10 @@ class LegalEntityService: if field not in data.model_fields_set: continue value = getattr(data, field) + if value == getattr(entity, field): + continue # то же значение — не изменение (Review Focus 1), даже если филиал ЮЛ чужой для пользователя if field == "regional_office_id": - value = office_for_create(self._scope, value) + value = office_for_create(self._scope, value) # policy — только для фактической смены филиала if value != getattr(entity, field): changes[field] = value if "entity_type" in changes: diff --git a/api/tests/test_api_legal_entities.py b/api/tests/test_api_legal_entities.py index 02bd935..9f36cab 100644 --- a/api/tests/test_api_legal_entities.py +++ b/api/tests/test_api_legal_entities.py @@ -533,3 +533,33 @@ async def test_failed_move_rolls_back_everything( assert reloaded_member.valid_to is None assert await _history_count(seeded) == 0 assert not await _events(seeded, AuditAction.LEGAL_ENTITY_CHANGE, AuditAction.MEMBERSHIP_CHANGE) + + +@pytest.mark.integration +async def test_branch_patch_with_unchanged_foreign_office_is_not_rejected( + auth_client: AuthClientFactory, seeded: AsyncSession +) -> None: + """I-1 (Review Focus 1): ЮЛ в ГК своего филиала с чужим `regional_office_id`; форма шлёт тот же филиал — не 422.""" + rostov, krasnodar = await _office(seeded, ROSTOV), await _office(seeded, KRASNODAR) + group = await make_company_group(seeded, name="ГК Ростов", office=rostov) + entity = await make_legal_entity(seeded, name="ООО Старое", group=group, office=krasnodar) # как создал бы ГО + client = await auth_client("ka_rf@armka.local") + + same_office_only = await client.patch(f"{ENTITIES}/{entity.id}", json={"regional_office_id": str(krasnodar.id)}) + assert same_office_only.status_code == HTTP_OK, same_office_only.text + assert same_office_only.json()["regional_office_id"] == str(krasnodar.id) + assert await _history_count(seeded) == 0 + assert not await _events(seeded, AuditAction.LEGAL_ENTITY_CHANGE) + + renamed = await client.patch( + f"{ENTITIES}/{entity.id}", json={"name": "ООО Новое", "regional_office_id": str(krasnodar.id)} + ) + assert renamed.status_code == HTTP_OK, renamed.text + assert (renamed.json()["name"], renamed.json()["regional_office_id"]) == ("ООО Новое", str(krasnodar.id)) + assert await _history_count(seeded) == 1 + (event,) = await _events(seeded, AuditAction.LEGAL_ENTITY_CHANGE) + assert (event.before_value, event.after_value) == ({"name": "ООО Старое"}, {"name": "ООО Новое"}) + + to_foreign = await client.patch(f"{ENTITIES}/{entity.id}", json={"regional_office_id": None}) + assert to_foreign.status_code == HTTP_OK # явный null у филиала — свой филиал (это изменение) + assert to_foreign.json()["regional_office_id"] == str(rostov.id)