diff --git a/api/src/schemas/consolidations.py b/api/src/schemas/consolidations.py index 6b602cd..90c9d03 100644 --- a/api/src/schemas/consolidations.py +++ b/api/src/schemas/consolidations.py @@ -498,6 +498,11 @@ class IntercompanyResultAmountOut(BaseModel): company_id: uuid.UUID amount: Decimal + @field_validator("amount") + @classmethod + def _two_decimals(cls, value: Decimal) -> Decimal: + return value.quantize(CENT, rounding=ROUND_HALF_UP) + class IntercompanyResultArticleOut(BaseModel): """Статья ВГО и её суммы по компаниям и группе.""" @@ -507,6 +512,11 @@ class IntercompanyResultArticleOut(BaseModel): amounts: list[IntercompanyResultAmountOut] group_total: Decimal + @field_validator("group_total") + @classmethod + def _two_decimals(cls, value: Decimal) -> Decimal: + return value.quantize(CENT, rounding=ROUND_HALF_UP) + class IntercompanyResultTableOut(BaseModel): """Фиксированная результирующая таблица ВГО выбранной версии.""" diff --git a/api/tests/test_api_intercompany_matrix.py b/api/tests/test_api_intercompany_matrix.py index c732c74..d20672a 100644 --- a/api/tests/test_api_intercompany_matrix.py +++ b/api/tests/test_api_intercompany_matrix.py @@ -690,6 +690,10 @@ async def test_result_table_export_matches_json(auth_client: AuthClientFactory, export = await _request(client, world.version.id, "result-table/export", world.selection) assert result.status_code == export.status_code == OK data = result.json() + receivables = next(article for article in data["articles"] if article["code"] == "receivables") + assert sorted(item["amount"] for item in receivables["amounts"]) == ["0.00", "100.25"] + assert receivables["group_total"] == "100.25" + assert all(article["group_total"] == "0.00" for article in data["articles"] if article != receivables) sheet = load_workbook(BytesIO(export.content))["Результирующая таблица"] assert [sheet.cell(7, col).value for col in range(2, len(data["companies"]) + 2)] == [ company["name"] for company in data["companies"] diff --git a/api/tests/test_schemas_consolidations.py b/api/tests/test_schemas_consolidations.py index e6249bf..dac5fab 100644 --- a/api/tests/test_schemas_consolidations.py +++ b/api/tests/test_schemas_consolidations.py @@ -115,6 +115,22 @@ def test_consolidated_row_money_has_two_decimals() -> None: assert (dumped["opening_debit"], dumped["turnover_debit"], dumped["closing_debit"]) == ("0.00", "30.00", "7.00") +def test_result_table_money_has_two_decimals_with_half_up_rounding() -> None: + company_id = uuid.uuid4() + article = schemas.IntercompanyResultArticleOut( + code="receivables", + name="Дебиторская задолженность", + amounts=[ + schemas.IntercompanyResultAmountOut(company_id=company_id, amount=Decimal("0")), + schemas.IntercompanyResultAmountOut(company_id=uuid.uuid4(), amount=Decimal("1.005")), + ], + group_total=Decimal("1.005"), + ) + dumped = article.model_dump(mode="json") + assert [item["amount"] for item in dumped["amounts"]] == ["0.00", "1.01"] + assert dumped["group_total"] == "1.01" + + def test_entity_files_state_is_frozen() -> None: state = EntityFilesState(has_in_progress=True, last_file_status=FileStatus.ERROR) with pytest.raises(dataclasses.FrozenInstanceError): diff --git a/openspec/specs/consolidations-api/spec.md b/openspec/specs/consolidations-api/spec.md index 2ba2b83..7bf1ada 100644 --- a/openspec/specs/consolidations-api/spec.md +++ b/openspec/specs/consolidations-api/spec.md @@ -258,11 +258,13 @@ account, status, missing_quarters, document_ids}`: `document_type` и `account` Пять фиксированных статей §6: дебиторская и кредиторская задолженность, финансовые вложения, долгосрочные и краткосрочные займы. Сумма компании SHALL считаться по всем остальным компаниям периметра из конечного сальдо нужной стороны и выбранных счетов статьи; `group_total` — сумма по компаниям. +`amount` и `group_total` SHALL возвращаться строками с двумя знаками после округления `ROUND_HALF_UP` +(например, `"0.00"`). Отсутствие любого необходимого кода счёта SHALL давать 422 `RESULT_ACCOUNTS_MISSING` с перечнем кодов. #### Scenario: Компания без ОСВ - **WHEN** в периметре есть компания без подходящих строк ОСВ -- **THEN** она есть в `companies`, её сумма по каждой статье — строка `"0"` +- **THEN** она есть в `companies`, её сумма по каждой статье — строка `"0.00"` #### Scenario: Нет обязательного счёта - **WHEN** в плане счетов отсутствует счёт 58.03