feat(statements): созданные пустые строки вне проверок §7 (#80)

statement_amounts не отдаёт проверкам строки с line_code_original_value IS NULL,
у которых все суммы пустые; заполненная хотя бы в одной колонке строка
проверяется по всем колонкам.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-09 12:06:34 +04:00
parent 37e7f40890
commit 2d21a416d6
2 changed files with 55 additions and 4 deletions

View File

@ -127,7 +127,9 @@ async def statement_amounts(
session: AsyncSession, meta_id: uuid.UUID
) -> tuple[list[int], list[tuple[str, dict[int, Decimal | None]]]]:
"""Индексы колонок и `(код строки, {колонка: сумма})` по возрастанию кода — вход `run_checks`. Порядок строк
формы в БД не хранится (DBML не хранит), поэтому порядок строк — лексикографический порядок кодов."""
формы в БД не хранится (DBML не хранит), поэтому порядок строк — лексикографический порядок кодов.
Созданные правкой строки (`line_code_original_value IS NULL`) без единой суммы не участвуют
(spec statement-checks, design D-5)."""
columns = (
await session.execute(
select(FinancialStatementColumn.id, FinancialStatementColumn.column_index)
@ -138,16 +140,29 @@ async def statement_amounts(
index_by_id = {column_id: index for column_id, index in columns}
rows = (
await session.execute(
select(FinancialStatementLine.line_code, FinancialStatementValue.column_id, FinancialStatementValue.amount)
select(
FinancialStatementLine.line_code,
FinancialStatementLine.line_code_original_value,
FinancialStatementValue.column_id,
FinancialStatementValue.amount,
)
.join(FinancialStatementValue, FinancialStatementValue.line_id == FinancialStatementLine.id)
.where(FinancialStatementLine.meta_id == meta_id)
.order_by(FinancialStatementLine.line_code)
)
).all()
by_code: dict[str, dict[int, Decimal | None]] = {}
for code, column_id, amount in rows:
added: set[str] = set() # строки, созданные правкой аналитика (design D-5)
for code, original, column_id, amount in rows:
by_code.setdefault(code or "", {})[index_by_id[column_id]] = amount
return [index for _, index in columns], list(by_code.items())
if original is None:
added.add(code or "")
lines = [
(code, amounts)
for code, amounts in by_code.items()
if code not in added or any(amount is not None for amount in amounts.values())
]
return [index for _, index in columns], lines
async def value_flags(session: AsyncSession, meta_id: uuid.UUID) -> list[tuple[bool, bool | None, Decimal | None]]:

View File

@ -1286,3 +1286,39 @@ async def test_patch_duplicate_line_code_rejected(
)
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
assert response.json()["detail"]["code"] == "DUPLICATE_CELL"
async def test_template_line_checks_follow_filled_amounts(
seeded: AsyncSession, fixtures_dir: Path, auth_client: AuthClientFactory
) -> None:
"""Review Focus 4 (spec statement-checks): заполненная в одной колонке строка проверяется по всем колонкам,
очищенная — снова не участвует."""
document = await form(seeded, fixtures_dir)
sums = await drop_line(seeded, document, "1700")
assert sums[1] not in (None, Decimal(0)) and sums[2] not in (None, Decimal(0)) # колонки 1, 2 непустые
client = await auth_client(KA_RF)
col0 = await _column(client, document.id, 0)
response = await client.patch(
f"/documents/{document.id}/statement",
json={"values": [{"line_code": "1700", "column_id": col0, "amount": str(sums[0])}]},
)
failed = response.json()["checks"]["failed"]
relation = "1700 = 1300 + 1400 + 1500"
assert sorted(f["column_index"] for f in failed if f["relation"] == relation) == [1, 2]
assert all(f["column_index"] != 0 for f in failed)
response = await client.patch(
f"/documents/{document.id}/statement",
json={
"values": [
{
"line_id": next(i["id"] for i in response.json()["lines"] if i["line_code"] == "1700"),
"column_id": col0,
"amount": None,
}
]
},
)
assert response.status_code == HTTPStatus.OK, response.text
after_clear = response.json()["checks"]["failed"]
assert not any(f["relation"].startswith("1700 =") for f in after_clear) # очищенная 1700 снова не цель
assert {f["column_index"] for f in after_clear if f["relation"] == "1600 = 1700"} == {0, 1, 2}