Условия подтверждения формы ББ/ОФР вынесены в statement_hard_failures и form_confirmable (модульные функции services/statements.py); ручной путь (documents._check_statement) использует тот же statement_hard_failures и отвечает 409. Тест с нарушением строгого соотношения на 2200 (соотношения с 2400 в словаре мягкие). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
3.9 KiB
Python
84 lines
3.9 KiB
Python
"""Условия подтверждения формы ББ/ОФР — общие для ручного и автоматического пути (OQ-157, design D-3; D-9 Среза 3b)."""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from src.models.directories import LegalEntity
|
||
from src.models.enums import DocumentType
|
||
from src.models.files import Document
|
||
from src.models.statements import FinancialStatementLine, FinancialStatementMeta, FinancialStatementValue
|
||
from src.services.statements import form_confirmable, statement_hard_failures
|
||
from tests.factories import make_legal_entity
|
||
from tests.fixtures_helpers import GOLDEN_STATEMENTS, map_golden_snapshot
|
||
|
||
pytestmark = [pytest.mark.integration, pytest.mark.fixtures]
|
||
|
||
MEZH = GOLDEN_STATEMENTS / "mezheninovskaya" / "2025-Q3"
|
||
SYNTH = GOLDEN_STATEMENTS / "synthetic"
|
||
|
||
|
||
async def _owner(session: AsyncSession, path: Path) -> LegalEntity:
|
||
"""Владелец с ИНН шапки формы (MATCH)."""
|
||
data = json.loads(path.read_text("utf-8"))
|
||
inn = next((d["header"].get("inn") for d in data["documents"] if d.get("header", {}).get("inn")), None)
|
||
return await make_legal_entity(session, name="ООО Владелец", inn=inn)
|
||
|
||
|
||
async def _income(session: AsyncSession, fixtures_dir: Path) -> Document:
|
||
path = fixtures_dir / MEZH / "1_Форма2_бух_09.json"
|
||
(income,) = await map_golden_snapshot(session, entity=await _owner(session, path), path=path, uploaded_by=None)
|
||
assert income.document_type is DocumentType.INCOME
|
||
return income
|
||
|
||
|
||
async def test_clean_form_is_confirmable(seeded: AsyncSession, fixtures_dir: Path) -> None:
|
||
assert await form_confirmable(seeded, await _income(seeded, fixtures_dir)) is True
|
||
|
||
|
||
async def test_form_without_period_is_not_confirmable(seeded: AsyncSession, fixtures_dir: Path) -> None:
|
||
income = await _income(seeded, fixtures_dir)
|
||
income.period_quarter = None
|
||
await seeded.flush()
|
||
assert await form_confirmable(seeded, income) is False
|
||
|
||
|
||
async def test_form_with_hard_failure_is_not_confirmable_until_accepted(
|
||
seeded: AsyncSession, fixtures_dir: Path
|
||
) -> None:
|
||
income = await _income(seeded, fixtures_dir)
|
||
meta = (
|
||
await seeded.execute(select(FinancialStatementMeta).where(FinancialStatementMeta.document_id == income.id))
|
||
).scalar_one()
|
||
# ломаем итог 2200 во всех колонках: строгое соотношение «2200 = 2100 - 2210 - 2220» перестаёт сходиться
|
||
# (соотношения с 2400 в словаре мягкие — strict: false, поэтому на них жёсткое нарушение не проверить)
|
||
values = (
|
||
await seeded.execute(
|
||
select(FinancialStatementValue)
|
||
.join(FinancialStatementLine, FinancialStatementLine.id == FinancialStatementValue.line_id)
|
||
.where(FinancialStatementLine.meta_id == meta.id, FinancialStatementLine.line_code == "2200")
|
||
)
|
||
).scalars()
|
||
for value in values:
|
||
value.amount = (value.amount or 0) + 1000
|
||
await seeded.flush()
|
||
|
||
assert await statement_hard_failures(seeded, income, meta) != ()
|
||
assert await form_confirmable(seeded, income) is False
|
||
|
||
meta.checks_accepted_at = income.created_at # акцепт: любые непустые дата и автор
|
||
await seeded.flush()
|
||
assert await form_confirmable(seeded, income) is True
|
||
|
||
|
||
async def test_disclosure_is_confirmable(seeded: AsyncSession, fixtures_dir: Path) -> None:
|
||
owner = await make_legal_entity(seeded, name="ООО Синтетика")
|
||
documents = await map_golden_snapshot(
|
||
seeded, entity=owner, path=fixtures_dir / SYNTH / "st06_header_variants.json", uploaded_by=None
|
||
)
|
||
disclosure = next(d for d in documents if d.document_type is DocumentType.DISCLOSURE)
|
||
assert await form_confirmable(seeded, disclosure) is True
|