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:
parent
37e7f40890
commit
2d21a416d6
@ -127,7 +127,9 @@ async def statement_amounts(
|
|||||||
session: AsyncSession, meta_id: uuid.UUID
|
session: AsyncSession, meta_id: uuid.UUID
|
||||||
) -> tuple[list[int], list[tuple[str, dict[int, Decimal | None]]]]:
|
) -> tuple[list[int], list[tuple[str, dict[int, Decimal | None]]]]:
|
||||||
"""Индексы колонок и `(код строки, {колонка: сумма})` по возрастанию кода — вход `run_checks`. Порядок строк
|
"""Индексы колонок и `(код строки, {колонка: сумма})` по возрастанию кода — вход `run_checks`. Порядок строк
|
||||||
формы в БД не хранится (DBML не хранит), поэтому порядок строк — лексикографический порядок кодов."""
|
формы в БД не хранится (DBML не хранит), поэтому порядок строк — лексикографический порядок кодов.
|
||||||
|
Созданные правкой строки (`line_code_original_value IS NULL`) без единой суммы не участвуют
|
||||||
|
(spec statement-checks, design D-5)."""
|
||||||
columns = (
|
columns = (
|
||||||
await session.execute(
|
await session.execute(
|
||||||
select(FinancialStatementColumn.id, FinancialStatementColumn.column_index)
|
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}
|
index_by_id = {column_id: index for column_id, index in columns}
|
||||||
rows = (
|
rows = (
|
||||||
await session.execute(
|
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)
|
.join(FinancialStatementValue, FinancialStatementValue.line_id == FinancialStatementLine.id)
|
||||||
.where(FinancialStatementLine.meta_id == meta_id)
|
.where(FinancialStatementLine.meta_id == meta_id)
|
||||||
.order_by(FinancialStatementLine.line_code)
|
.order_by(FinancialStatementLine.line_code)
|
||||||
)
|
)
|
||||||
).all()
|
).all()
|
||||||
by_code: dict[str, dict[int, Decimal | None]] = {}
|
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
|
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]]:
|
async def value_flags(session: AsyncSession, meta_id: uuid.UUID) -> list[tuple[bool, bool | None, Decimal | None]]:
|
||||||
|
|||||||
@ -1286,3 +1286,39 @@ async def test_patch_duplicate_line_code_rejected(
|
|||||||
)
|
)
|
||||||
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
|
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
|
||||||
assert response.json()["detail"]["code"] == "DUPLICATE_CELL"
|
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}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user