"""Условия подтверждения формы ББ/ОФР — общие для ручного и автоматического пути (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