from dataclasses import replace from datetime import date from decimal import Decimal as D from arm_statements import status_input_from_contract, to_contract from arm_statements.header import parse_header from arm_statements.issues import make_warning from arm_statements.loader import Rules from arm_statements.model import ( DocStatus, DocumentType, EnvelopeStatus, FormVariant, HeaderField, LineStatus, PeriodKind, Severity, StatementChecks, StatementColumn, StatementDocument, StatementLine, StatementValue, ) from arm_statements.status import ( StatusInput, StatusLine, StatusValue, document_status, envelope_status, status_input, ) from arm_statements.tests.builders import hdr OK, DETAIL, UNREADABLE = LineStatus.OK, LineStatus.DETAIL, LineStatus.UNREADABLE DONE, CHECK = DocStatus.DONE, DocStatus.NEEDS_CHECK BASE = StatusInput( document_type=DocumentType.BALANCE, layout_found=True, has_organization_or_inn=True, period_known=True, needs_review_fields=(), lines=(StatusLine(OK, False),), values=(StatusValue(OK, False),), hard_failures=0, accepted=False, warning_severities=(Severity.INFO,), ) def test_clean_form_done() -> None: assert document_status(BASE) is DONE def test_unreadable_line_blocks() -> None: assert document_status(replace(BASE, lines=(StatusLine(OK, False), StatusLine(UNREADABLE, False)))) is CHECK def test_detail_value_does_not_block() -> None: lines = (StatusLine(OK, False), StatusLine(DETAIL, False)) values = (StatusValue(OK, False), StatusValue(DETAIL, True)) assert document_status(replace(BASE, lines=lines, values=values)) is DONE def test_blocking_conditions() -> None: hard = 2 assert document_status(replace(BASE, values=(StatusValue(OK, True),))) is CHECK assert document_status(replace(BASE, lines=(StatusLine(OK, True),))) is CHECK assert document_status(replace(BASE, needs_review_fields=("report_date",))) is CHECK assert document_status(replace(BASE, hard_failures=hard)) is CHECK assert document_status(replace(BASE, hard_failures=hard, accepted=True)) is DONE assert document_status(replace(BASE, warning_severities=(Severity.ERROR,))) is CHECK assert document_status(replace(BASE, layout_found=False)) is CHECK assert document_status(replace(BASE, has_organization_or_inn=False)) is CHECK assert document_status(replace(BASE, period_known=False)) is CHECK def test_disclosure_always_done() -> None: disclosure = replace(BASE, document_type=DocumentType.DISCLOSURE, layout_found=False, lines=(), values=()) assert document_status(disclosure) is DONE def test_envelope(rules: Rules) -> None: assert envelope_status([DONE, CHECK], []) is EnvelopeStatus.NEEDS_CHECK assert envelope_status([DONE], []) is EnvelopeStatus.DONE lost = make_warning("TABLE_LOST", "таблица без колонки кодов", rules, locator="file:a.json;table:1") assert envelope_status([DONE], [lost]) is EnvelopeStatus.NEEDS_CHECK assert envelope_status([], []) is EnvelopeStatus.ERROR def test_status_input_from_document(rules: Rules) -> None: head = parse_header(hdr(organization_name="ООО «Пример»", units="тыс. руб."), rules) column = StatementColumn(2, "На 30.09.2025", 100.0, date(2025, 9, 30), PeriodKind.ABSOLUTE) value = StatementValue(2, D("5"), None, "5", 60.0, True, needs_review=True) detail = StatementLine(1, "row:1", DETAIL, None, None, None, "в т. ч.", 100.0, "row:0", False, (value,)) line = StatementLine( 0, "row:0", OK, "1210", "1210", 100.0, "Запасы", 100.0, None, False, (replace(value, needs_review=False),) ) doc = StatementDocument( DocumentType.BALANCE, FormVariant.FULL, "file:t.json;table:0", head, (column,), (line, detail), StatementChecks((), ()), DocStatus.DONE, (), ) data = status_input(doc) assert (data.layout_found, data.has_organization_or_inn, data.period_known, data.hard_failures) == ( True, True, True, 0, ) assert data.values == (StatusValue(OK, False), StatusValue(DETAIL, True)) assert document_status(data) is DONE empty = replace(doc, columns=(), lines=(), checks=None) assert (status_input(empty).layout_found, status_input(empty).period_known) == (False, False) def test_empty_string_organization_and_inn_mean_absent(rules: Rules) -> None: """M-1: пустая строка в шапке — «нет»; совпадает с расчётом по контракту.""" empty = HeaderField("", 100.0, "") head = replace(parse_header(hdr(), rules), organization_name=empty, inn=empty) doc = StatementDocument(DocumentType.BALANCE, None, "l", head, (), (), None, DONE, ()) assert status_input(doc).has_organization_or_inn is False assert status_input_from_contract(to_contract(doc)).has_organization_or_inn is False