diff --git a/api/src/api/endpoints/consolidations/goodwill.py b/api/src/api/endpoints/consolidations/goodwill.py index c2537f7..4a53a8a 100644 --- a/api/src/api/endpoints/consolidations/goodwill.py +++ b/api/src/api/endpoints/consolidations/goodwill.py @@ -10,8 +10,7 @@ from src.api.dependencies.auth import require from src.api.dependencies.consolidations import ConsolidationServiceDep, GoodwillServiceDep from src.common.filenames import content_disposition from src.schemas.goodwill import ( - GoodwillRowIn, - GoodwillRowOut, + GoodwillBatchIn, GoodwillSettingsIn, GoodwillSign, GoodwillTableOut, @@ -28,6 +27,14 @@ async def table(version_id: UUID, service: GoodwillServiceDep) -> GoodwillTableO return await service.table(version_id) +@router.put("/{version_id}/goodwill", response_model=GoodwillTableOut, dependencies=EDIT) +async def save( + version_id: UUID, data: GoodwillBatchIn, service: GoodwillServiceDep, audit: AuditDep +) -> GoodwillTableOut: + """Пакет строк одной транзакцией; ошибка любой строки — 404/422 с её ключом, ничего не сохраняется.""" + return await service.save_rows(version_id, data.rows, audit) + + @router.put("/{version_id}/goodwill-settings", response_model=GoodwillTableOut, dependencies=EDIT) async def settings( version_id: UUID, data: GoodwillSettingsIn, service: GoodwillServiceDep, audit: AuditDep @@ -64,19 +71,3 @@ async def export( media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": content_disposition(filename, ascii_fallback="goodwill.xlsx")}, ) - - -@router.put( - "/{version_id}/goodwill/{head_legal_entity_id}/{subsidiary_legal_entity_id}", - response_model=GoodwillRowOut, - dependencies=EDIT, -) -async def save( # noqa: PLR0913, PLR0917 - ключ пары, тело и зависимости FastAPI - version_id: UUID, - head_legal_entity_id: UUID, - subsidiary_legal_entity_id: UUID, - data: GoodwillRowIn, - service: GoodwillServiceDep, - audit: AuditDep, -) -> GoodwillRowOut: - return await service.save(version_id, head_legal_entity_id, subsidiary_legal_entity_id, data, audit) diff --git a/api/src/schemas/goodwill.py b/api/src/schemas/goodwill.py index d55059b..8410669 100644 --- a/api/src/schemas/goodwill.py +++ b/api/src/schemas/goodwill.py @@ -70,6 +70,20 @@ class GoodwillRowIn(BaseModel): comment: str | None = Field(default=None, max_length=1000) +class GoodwillRowKeyIn(GoodwillRowIn): + """Строка пакетного сохранения: пара ГО → ДО и ручной ввод.""" + + head_legal_entity_id: UUID + subsidiary_legal_entity_id: UUID + + +class GoodwillBatchIn(BaseModel): + """Пакет строк таблицы гудвилла; сохраняется целиком или не сохраняется.""" + + model_config = ConfigDict(extra="forbid") + rows: list[GoodwillRowKeyIn] = Field(min_length=1) + + class GoodwillSettingsIn(BaseModel): model_config = ConfigDict(extra="forbid") goodwill_enabled: bool diff --git a/api/src/services/goodwill.py b/api/src/services/goodwill.py index cae44d7..7cfef8d 100644 --- a/api/src/services/goodwill.py +++ b/api/src/services/goodwill.py @@ -1,7 +1,8 @@ -"""Таблица и построчные решения гудвилла в транзакции запроса.""" +"""Таблица гудвилла, пакетное сохранение решений и переключатель — в транзакции запроса (§8, DOC-259025).""" import re from decimal import Decimal +from http import HTTPStatus from uuid import UUID, uuid4 from fastapi.concurrency import run_in_threadpool @@ -11,7 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from src.api.dependencies.audit import RequestAudit from src.api.dependencies.auth import Principal from src.common.clock import utc_now -from src.common.exceptions import ConflictError, DomainValidationError, NotFoundError +from src.common.exceptions import ConflictError, DomainError, DomainValidationError from src.domain import goodwill as domain from src.domain.policy import VisibilityScope, ensure_version_editable from src.models.access import AppUser @@ -24,6 +25,7 @@ from src.schemas.consolidations import RefOut from src.schemas.goodwill import ( GoodwillGroupOut, GoodwillRowIn, + GoodwillRowKeyIn, GoodwillRowOut, GoodwillSign, GoodwillTableOut, @@ -150,87 +152,80 @@ class GoodwillService: totals=GoodwillTotalsOut(**domain.totals(rows, ctx.version.goodwill_enabled)), ) - async def save( - self, version_id: UUID, head: UUID, subsidiary: UUID, data: GoodwillRowIn, audit: RequestAudit - ) -> GoodwillRowOut: + async def save_rows(self, version_id: UUID, rows: list[GoodwillRowKeyIn], audit: RequestAudit) -> GoodwillTableOut: + """Пакет строк одной транзакцией (DOC-259025 §5.1 п. 4): любая ошибка — откат всего пакета. + + Сначала проверяются все строки, потом пишутся только изменённые; аудит `GOODWILL_EDIT` — по каждой изменённой. + """ await lock_editable_version(self.session, self.scope, version_id, self.principal.user.id) table = await self.table(version_id) - row = next( - (r for r in table.rows if (r.head_legal_entity.id, r.subsidiary_legal_entity.id) == (head, subsidiary)), - None, - ) - if row is None: - raise NotFoundError("Пара не найдена") - if not row.has_investments: - # PDF §10: отсутствующий ввод не затирает сохранённые данные неактивной пары. - data = data.model_copy( - update={ - "ownership_share": data.ownership_share - if data.ownership_share is not None - else str(row.ownership_share) - if row.ownership_share is not None - else None, - "comment": data.comment if data.comment is not None else row.comment, - } + current = {(r.head_legal_entity.id, r.subsidiary_legal_entity.id): r for r in table.rows} + saved = { + (r.head_legal_entity_id, r.subsidiary_legal_entity_id): r + for r in await repo.saved(self.session, version_id) + } + _check_duplicates(rows) + planned: list[tuple[GoodwillRowOut, domain.Decision]] = [] + for item in rows: + key = item.head_legal_entity_id, item.subsidiary_legal_entity_id + row = current.get(key) + if row is None: + raise DomainError(HTTPStatus.NOT_FOUND, "NOT_FOUND", "Пара не найдена", extra=_key_extra(key)) + decision = _decision(row, item, key) + entity = saved.get(key) + previous = ( + domain.Decision(entity.ownership_share, entity.include_in_kfo, entity.comment) + if entity + else domain.Decision() ) - decision = validate_decision(data) - saved = await repo.saved(self.session, version_id) - entity = next( - (r for r in saved if (r.head_legal_entity_id, r.subsidiary_legal_entity_id) == (head, subsidiary)), None - ) - before = row.model_dump(mode="json") if entity else None + if decision != previous: + planned.append((row, decision)) now = utc_now() - if entity is None: - entity = Goodwill( - id=uuid4(), - consolidation_version_id=version_id, - head_legal_entity_id=head, - subsidiary_legal_entity_id=subsidiary, - created_by=self.principal.user.id, - created_at=now, + written: list[tuple[Goodwill, dict | None]] = [] + for row, decision in planned: + key = row.head_legal_entity.id, row.subsidiary_legal_entity.id + entity = saved.get(key) + before = row.model_dump(mode="json") if entity else None + if entity is None: + entity = Goodwill( + id=uuid4(), + consolidation_version_id=version_id, + head_legal_entity_id=key[0], + subsidiary_legal_entity_id=key[1], + created_by=self.principal.user.id, + created_at=now, + ) + self.session.add(entity) + entity.ownership_share, entity.include_in_kfo, entity.comment = ( + decision.ownership_share, + decision.include_in_kfo, + decision.comment, ) - self.session.add(entity) - entity.ownership_share, entity.include_in_kfo, entity.comment = ( - decision.ownership_share, - decision.include_in_kfo, - decision.comment, - ) - entity.updated_by, entity.updated_at = self.principal.user.id, now - if row.has_investments: - assert row.financial_investment_amount is not None - p = ( - domain.participation(row.charter_capital_amount, decision.ownership_share) - if decision.include_in_kfo - and row.charter_capital_amount is not None - and decision.ownership_share is not None - else None - ) - amounts = dict( - financial_investment_amount=row.financial_investment_amount, - charter_capital_amount=row.charter_capital_amount, - additional_capital_amount=row.additional_capital_amount, - participation_amount=p, - goodwill_amount=row.financial_investment_amount - p if p is not None else None, - ) - for name, amount in amounts.items(): - if amount is not None and abs(amount) >= Decimal("10000000000000000"): - raise DomainValidationError("MONEY_OUT_OF_RANGE", "Сумма не помещается в numeric(18,2)") - setattr(entity, name, amount) + entity.updated_by, entity.updated_at = self.principal.user.id, now + if row.has_investments: + for name, amount in _amounts(row, decision).items(): + if amount is not None and abs(amount) >= MONEY_LIMIT: + raise DomainValidationError( + "MONEY_OUT_OF_RANGE", "Сумма не помещается в numeric(18,2)", **_key_extra(key) + ) + setattr(entity, name, amount) + written.append((entity, before)) + if not written: + return table await self.session.flush() - result = next( - r - for r in (await self.table(version_id)).rows - if (r.head_legal_entity.id, r.subsidiary_legal_entity.id) == (head, subsidiary) - ) - await audit.record( - action=AuditAction.GOODWILL_EDIT, - section=AuditSection.CONSOLIDATION, - object_type="goodwill", - object_id=entity.id, - consolidation_version_id=version_id, - before_value=before, - after_value=result.model_dump(mode="json"), - ) + result = await self.table(version_id) + after = {(r.head_legal_entity.id, r.subsidiary_legal_entity.id): r for r in result.rows} + for entity, before in written: + key = entity.head_legal_entity_id, entity.subsidiary_legal_entity_id + await audit.record( + action=AuditAction.GOODWILL_EDIT, + section=AuditSection.CONSOLIDATION, + object_type="goodwill", + object_id=entity.id, + consolidation_version_id=version_id, + before_value=before, + after_value=after[key].model_dump(mode="json"), + ) return result async def settings(self, version_id: UUID, enabled: bool, audit: RequestAudit) -> GoodwillTableOut: @@ -264,3 +259,53 @@ class GoodwillService: after_value={"report": "goodwill", "rows": sum(matches_sign(row, sign) for row in table.rows)}, ) return content + + +MONEY_LIMIT = Decimal("10000000000000000") + + +def _key_extra(key: tuple[UUID, UUID]) -> dict[str, str]: + return {"head_legal_entity_id": str(key[0]), "subsidiary_legal_entity_id": str(key[1])} + + +def _check_duplicates(rows: list[GoodwillRowKeyIn]) -> None: + seen: set[tuple[UUID, UUID]] = set() + for item in rows: + key = item.head_legal_entity_id, item.subsidiary_legal_entity_id + if key in seen: + raise DomainValidationError("DUPLICATE_ROW", "Пара встречается в пакете дважды", **_key_extra(key)) + seen.add(key) + + +def _decision(row: GoodwillRowOut, item: GoodwillRowKeyIn, key: tuple[UUID, UUID]) -> domain.Decision: + """Проверенный ввод строки; ошибка несёт ключ пары.""" + share, comment = item.ownership_share, item.comment + if not row.has_investments: + # DOC-259025 §10 п. 9: отсутствующий ввод не затирает сохранённые данные неактивной пары. + if share is None and row.ownership_share is not None: + share = str(row.ownership_share) + if comment is None: + comment = row.comment + try: + return validate_decision( + GoodwillRowIn(ownership_share=share, include_in_kfo=item.include_in_kfo, comment=comment) + ) + except DomainValidationError as error: + raise DomainValidationError(error.code, error.message, **_key_extra(key)) from error + + +def _amounts(row: GoodwillRowOut, decision: domain.Decision) -> dict[str, Decimal | None]: + """Снимок сумм действующей строки при сохранении (поля `goodwill` DBML).""" + assert row.financial_investment_amount is not None + p = ( + domain.participation(row.charter_capital_amount, decision.ownership_share) + if decision.include_in_kfo and row.charter_capital_amount is not None and decision.ownership_share is not None + else None + ) + return { + "financial_investment_amount": row.financial_investment_amount, + "charter_capital_amount": row.charter_capital_amount, + "additional_capital_amount": row.additional_capital_amount, + "participation_amount": p, + "goodwill_amount": row.financial_investment_amount - p if p is not None else None, + } diff --git a/api/tests/test_api_goodwill.py b/api/tests/test_api_goodwill.py index 2ee22af..943d07d 100644 --- a/api/tests/test_api_goodwill.py +++ b/api/tests/test_api_goodwill.py @@ -39,6 +39,11 @@ HTTP_CREATED = 201 pytestmark = [pytest.mark.integration, pytest.mark.fixtures] +def batch(head, subsidiary, **body) -> dict: + """Тело пакетного сохранения из одной строки.""" + return {"rows": [{"head_legal_entity_id": str(head), "subsidiary_legal_entity_id": str(subsidiary), **body}]} + + async def goodwill_world(session, fixtures_dir: Path): w = await _world(session) raw = investment_rows(fixtures_dir)[0] @@ -344,11 +349,11 @@ async def test_null_closing_debit_does_not_block_pair_calculation(db_session, au base = f"/consolidations/versions/{world.version.id}" assert (await client.post(base + "/lock")).status_code == HTTP_CREATED response = await client.put( - base + f"/goodwill/{world.a.id}/{world.b.id}", - json=dict(ownership_share="100", include_in_kfo=True, comment="Подтверждено"), + base + "/goodwill", + json=batch(world.a.id, world.b.id, ownership_share="100", include_in_kfo=True, comment="Подтверждено"), ) assert response.status_code == HTTP_OK, response.text - row = response.json() + row = response.json()["rows"][0] assert row["financial_investment_amount"] == "269997300.00" assert row["participation_amount"] == "100000000.00" assert row["goodwill_amount"] == "169997300.00" diff --git a/api/tests/test_api_goodwill_mutations.py b/api/tests/test_api_goodwill_mutations.py index fca4028..85e5638 100644 --- a/api/tests/test_api_goodwill_mutations.py +++ b/api/tests/test_api_goodwill_mutations.py @@ -16,7 +16,7 @@ from src.models.statements import FinancialStatementValue from src.schemas.goodwill import GoodwillRowIn from src.services.goodwill import validate_decision from tests.test_api_consolidated_osv import KA_RF -from tests.test_api_goodwill import goodwill_world +from tests.test_api_goodwill import batch, goodwill_world @pytest.mark.parametrize( @@ -52,22 +52,22 @@ async def test_save_settings_and_disappearing_pair(db_session, auth_client, fixt w = await goodwill_world(db_session, fixtures_dir) client = await auth_client(KA_RF) base = f"/consolidations/versions/{w.version.id}" - path = base + f"/goodwill/{w.a.id}/{w.b.id}" + path = base + "/goodwill" body = dict(ownership_share="80.12", include_in_kfo=True, comment="Подтверждено") - response = await client.put(path, json=body) + response = await client.put(path, json=batch(w.a.id, w.b.id, **body)) assert response.status_code == HTTPStatus.CONFLICT and response.json()["detail"]["code"] == "LOCK_REQUIRED" assert (await client.post(base + "/lock")).status_code == HTTPStatus.CREATED other = await auth_client("rka@armka.local") - rejected = await other.put(path, json=body) + rejected = await other.put(path, json=batch(w.a.id, w.b.id, **body)) assert rejected.status_code == HTTPStatus.CONFLICT assert rejected.json()["detail"]["code"] == "LOCKED_BY_OTHER" - response = await client.put(path, json=body) + response = await client.put(path, json=batch(w.a.id, w.b.id, **body)) assert response.status_code == HTTPStatus.OK, response.text - row = response.json() + row = response.json()["rows"][0] assert row["ownership_share"] == "80.12" assert row["participation_amount"] == "80120000.00" assert row["goodwill_amount"] == "189877300.00" - again = await client.put(path, json=body) + again = await client.put(path, json=batch(w.a.id, w.b.id, **body)) assert again.status_code == HTTPStatus.OK assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 1 on = await client.put(base + "/goodwill-settings", json=dict(goodwill_enabled=True)) @@ -86,7 +86,7 @@ async def test_save_settings_and_disappearing_pair(db_session, auth_client, fixt assert off.json()["totals"]["goodwill_in_kfo_amount"] == "0.00" assert off.json()["rows"][0]["ownership_share"] == "80.12" body["include_in_kfo"] = False - assert (await client.put(path, json=body)).status_code == HTTPStatus.OK + assert (await client.put(path, json=batch(w.a.id, w.b.id, **body))).status_code == HTTPStatus.OK assert (await client.get(base + "/goodwill")).json()["totals"]["goodwill_amount"] == "0.00" entries = list( ( @@ -101,12 +101,12 @@ async def test_save_settings_and_disappearing_pair(db_session, auth_client, fixt row = (await client.get(base + "/goodwill")).json()["rows"][0] assert not row["has_investments"] and row["goodwill_amount"] is None assert row["comment"] == "Подтверждено" - assert (await client.put(path, json=body)).status_code == HTTPStatus.OK + assert (await client.put(path, json=batch(w.a.id, w.b.id, **body))).status_code == HTTPStatus.OK body["include_in_kfo"] = True - response = await client.put(path, json=body) + response = await client.put(path, json=batch(w.a.id, w.b.id, **body)) assert response.status_code == HTTPStatus.OK - assert response.json()["financial_investment_amount"] is None - assert response.json()["goodwill_amount"] is None + assert response.json()["rows"][0]["financial_investment_amount"] is None + assert response.json()["rows"][0]["goodwill_amount"] is None assert await db_session.scalar(select(func.count()).select_from(KfoResult)) == 0 @@ -117,16 +117,16 @@ async def test_mutation_validation_and_rollback(db_session, auth_client, fixture w = await goodwill_world(db_session, fixtures_dir) client = await auth_client(KA_RF) base = f"/consolidations/versions/{w.version.id}" - path = base + f"/goodwill/{w.a.id}/{w.b.id}" + path = base + "/goodwill" assert (await client.post(base + "/lock")).status_code == HTTPStatus.CREATED body = dict(ownership_share="100", include_in_kfo=True, comment="") - response = await client.put(path, json=body) + response = await client.put(path, json=batch(w.a.id, w.b.id, **body)) assert ( response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY and response.json()["detail"]["code"] == "COMMENT_REQUIRED" ) body.update(ownership_share="100.00001", comment="Да") - assert (await client.put(path, json=body)).json()["detail"]["code"] == "INVALID_SHARE" + assert (await client.put(path, json=batch(w.a.id, w.b.id, **body))).json()["detail"]["code"] == "INVALID_SHARE" assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0 async def reject_audit(*args, **kwargs): @@ -134,7 +134,7 @@ async def test_mutation_validation_and_rollback(db_session, auth_client, fixture monkeypatch.setattr(RequestAudit, "record", reject_audit) body["ownership_share"] = "100" - response = await client.put(path, json=body) + response = await client.put(path, json=batch(w.a.id, w.b.id, **body)) assert response.status_code == HTTPStatus.CONFLICT assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0 @@ -150,8 +150,81 @@ async def test_money_overflow_keeps_database_unchanged(db_session, auth_client, value.amount = Decimal("9999999999999999.99") await db_session.flush() response = await client.put( - base + f"/goodwill/{w.a.id}/{w.b.id}", json=dict(ownership_share="100", include_in_kfo=True, comment="Проверка") + base + "/goodwill", json=batch(w.a.id, w.b.id, ownership_share="100", include_in_kfo=True, comment="Проверка") ) assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY assert response.json()["detail"]["code"] == "MONEY_OUT_OF_RANGE" assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0 + + +async def _locked_world(db_session, auth_client, fixtures_dir): + w = await goodwill_world(db_session, fixtures_dir) + client = await auth_client(KA_RF) + base = f"/consolidations/versions/{w.version.id}" + assert (await client.post(base + "/lock")).status_code == HTTPStatus.CREATED + return w, client, base + + +async def _goodwill_audit_count(db_session) -> int: + return await db_session.scalar( + select(func.count()).select_from(AuditEvent).where(AuditEvent.action == AuditAction.GOODWILL_EDIT) + ) + + +@pytest.mark.integration +@pytest.mark.fixtures +async def test_batch_rolls_back_when_one_row_invalid(db_session, auth_client, fixtures_dir): + w, client, base = await _locked_world(db_session, auth_client, fixtures_dir) + valid = batch(w.a.id, w.b.id, ownership_share="100", include_in_kfo=True, comment="Да")["rows"][0] + invalid = batch(w.b.id, w.a.id, ownership_share="50", include_in_kfo=True, comment=" ")["rows"][0] + # обратная пара Б → А в таблице не существует: сперва проверяем порядок ошибок на существующей паре + response = await client.put(base + "/goodwill", json={"rows": [valid, {**valid, "comment": " "}]}) + assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + assert response.json()["detail"]["code"] == "DUPLICATE_ROW" + response = await client.put(base + "/goodwill", json={"rows": [valid, invalid]}) + assert response.status_code == HTTPStatus.NOT_FOUND + assert response.json()["detail"]["subsidiary_legal_entity_id"] == str(w.a.id) + assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0 + assert await _goodwill_audit_count(db_session) == 0 + + +@pytest.mark.integration +@pytest.mark.fixtures +async def test_batch_reports_invalid_row_key(db_session, auth_client, fixtures_dir): + w, client, base = await _locked_world(db_session, auth_client, fixtures_dir) + body = batch(w.a.id, w.b.id, ownership_share="100", include_in_kfo=True, comment=" ") + response = await client.put(base + "/goodwill", json=body) + assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY + detail = response.json()["detail"] + assert detail["code"] == "COMMENT_REQUIRED" + assert (detail["head_legal_entity_id"], detail["subsidiary_legal_entity_id"]) == (str(w.a.id), str(w.b.id)) + assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0 + assert await _goodwill_audit_count(db_session) == 0 + + +@pytest.mark.integration +@pytest.mark.fixtures +async def test_unchanged_rows_are_not_rewritten(db_session, auth_client, fixtures_dir): + w, client, base = await _locked_world(db_session, auth_client, fixtures_dir) + body = batch(w.a.id, w.b.id, ownership_share="80,5", include_in_kfo=True, comment="Устав") + first = await client.put(base + "/goodwill", json=body) + assert first.status_code == HTTPStatus.OK, first.text + saved_at = first.json()["rows"][0]["updated_at"] + again = await client.put( + base + "/goodwill", json=batch(w.a.id, w.b.id, ownership_share="80.50", include_in_kfo=True, comment="Устав") + ) + assert again.status_code == HTTPStatus.OK + assert again.json()["rows"][0]["updated_at"] == saved_at + assert await _goodwill_audit_count(db_session) == 1 + + +@pytest.mark.integration +@pytest.mark.fixtures +async def test_new_row_with_defaults_is_not_created(db_session, auth_client, fixtures_dir): + w, client, base = await _locked_world(db_session, auth_client, fixtures_dir) + body = batch(w.a.id, w.b.id, ownership_share=None, include_in_kfo=False, comment=None) + response = await client.put(base + "/goodwill", json=body) + assert response.status_code == HTTPStatus.OK, response.text + assert response.json()["rows"][0]["include_in_kfo"] is False + assert await db_session.scalar(select(func.count()).select_from(Goodwill)) == 0 + assert await _goodwill_audit_count(db_session) == 0 diff --git a/api/tests/test_goodwill_export.py b/api/tests/test_goodwill_export.py index 54529b3..72def3f 100644 --- a/api/tests/test_goodwill_export.py +++ b/api/tests/test_goodwill_export.py @@ -11,7 +11,7 @@ from src.models.audit_ex import AuditEvent from src.models.enums import AuditAction from src.models.osv import OsvEntry from tests.test_api_consolidated_osv import KA_RF -from tests.test_api_goodwill import goodwill_world +from tests.test_api_goodwill import batch, goodwill_world INVESTMENT_AMOUNT = 269997300 FRONTEND_COLUMN_COUNT = 8 @@ -51,8 +51,9 @@ async def test_export_matches_server_table(db_session, auth_client, fixtures_dir assert (await client.get(base + "/goodwill/export?sign=unknown")).status_code == HTTPStatus.UNPROCESSABLE_ENTITY assert (await client.post(base + "/lock")).status_code == HTTPStatus.CREATED body = dict(ownership_share="100", include_in_kfo=True, comment="=1+1") - path = base + f"/goodwill/{world.a.id}/{world.b.id}" - assert (await client.put(path, json=body)).status_code == HTTPStatus.OK + assert ( + await client.put(base + "/goodwill", json=batch(world.a.id, world.b.id, **body)) + ).status_code == HTTPStatus.OK response = await client.get(base + "/goodwill/export") sheet = load_workbook(BytesIO(response.content)).active assert sheet.cell(3, 8).data_type == "s" diff --git a/api/tests/test_openapi_routes.py b/api/tests/test_openapi_routes.py index 053e62c..5114654 100644 --- a/api/tests/test_openapi_routes.py +++ b/api/tests/test_openapi_routes.py @@ -83,11 +83,8 @@ EXPECTED_ROUTES: dict[str, set[str]] = { "/api/v1/consolidations/versions/{version_id}/corrections": {"get"}, "/api/v1/consolidations/versions/{version_id}/corrections/export": {"get"}, # Таблица гудвилла (goodwill-table) - "/api/v1/consolidations/versions/{version_id}/goodwill": {"get"}, + "/api/v1/consolidations/versions/{version_id}/goodwill": {"get", "put"}, "/api/v1/consolidations/versions/{version_id}/goodwill-settings": {"put"}, - "/api/v1/consolidations/versions/{version_id}/goodwill/{head_legal_entity_id}/{subsidiary_legal_entity_id}": { - "put" - }, "/api/v1/consolidations/versions/{version_id}/goodwill/export": {"get"}, # шахматка ВГО и правила элиминации "/api/v1/consolidations/versions/{version_id}/elimination-rules": {"get"},