synthgen: закрытие 90 через 90.09, тест детерминизма книг (#38)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-11-12 23:51:28 +04:00
parent 418f066bd8
commit f0f60b1839
2 changed files with 43 additions and 2 deletions

View File

@ -7,7 +7,7 @@
open_books(...) входящие остатки на book_start (31.12 перед первым периодом); open_books(...) входящие остатки на book_start (31.12 перед первым периодом);
для каждого месяца: post_month(...) внешние операции месяца, для каждого месяца: post_month(...) внешние операции месяца,
hook(...) операции внутри группы (extra), hook(...) операции внутри группы (extra),
close_month(...) закрытие 26/44 → 90, 90/91 → 99, налог, в декабре 99 → 84. close_month(...) закрытие 26/44 → 90, 90.01..90.08 → 90.09 → 99, 91 → 99, налог, в декабре 99 → 84.
`build_ledgers` — эта последовательность целиком. `build_ledgers` — эта последовательность целиком.
Аналитика контрагента: `An(cp=<ИНН Org>)` для внешних, `An(cp=<код члена>)` для компаний группы (последние — Task 4). Аналитика контрагента: `An(cp=<ИНН Org>)` для внешних, `An(cp=<код члена>)` для компаний группы (последние — Task 4).
@ -359,6 +359,26 @@ def _close_all(lg: Ledger, day: date, prefix: str) -> Decimal:
return result return result
def _close_90(lg: Ledger, day: date) -> Decimal:
"""Как в 1С: сальдо 90.01..90.08 переносятся на 90.09 (проводки с memo «закрытие на 90.09»), затем 90.09 — на 99
(memo «закрытие на 99»). Обороты 90.09 — только закрывающие; свёртка ОФР различает их по memo."""
result = D(0)
for (acct, an), bal in sorted(lg.balance(day, "90").items(), key=lambda kv: (kv[0][0], str(kv[0][1]))):
if acct == "90.09":
continue
if bal > 0:
lg.post(day, "90.09", None, acct, an, bal, "закрытие на 90.09")
else:
lg.post(day, acct, an, "90.09", None, -bal, "закрытие на 90.09")
result -= bal
bal09 = lg.balance(day, "90.09").get(("90.09", None), D(0))
if bal09 > 0:
lg.post(day, "99", None, "90.09", None, bal09, "закрытие на 99")
elif bal09 < 0:
lg.post(day, "90.09", None, "99", None, -bal09, "закрытие на 99")
return result
def close_month(ledgers: dict[str, Ledger], year: int, month: int) -> None: def close_month(ledgers: dict[str, Ledger], year: int, month: int) -> None:
day = last_day(year, month) day = last_day(year, month)
for code in sorted(ledgers): for code in sorted(ledgers):
@ -370,7 +390,7 @@ def close_month(ledgers: dict[str, Ledger], year: int, month: int) -> None:
for (acct, an), bal in lg.balance(day, cost).items(): for (acct, an), bal in lg.balance(day, cost).items():
if bal > 0: if bal > 0:
lg.post(day, target, None, acct, an, bal, "закрытие затрат") lg.post(day, target, None, acct, an, bal, "закрытие затрат")
result = _close_all(lg, day, "90") + _close_all(lg, day, "91") result = _close_90(lg, day) + _close_all(lg, day, "91")
tax = q(result * PROFIT_TAX) tax = q(result * PROFIT_TAX)
if tax > 0: if tax > 0:
lg.post(day, "99", None, "68.04", FNS, tax, "налог на прибыль") lg.post(day, "99", None, "68.04", FNS, tax, "налог на прибыль")

View File

@ -118,3 +118,24 @@ def test_deterministic_and_zero_mean():
b = _build(mean=0)[4] b = _build(mean=0)[4]
assert [p for p in a["C01"].postings] == [p for p in b["C01"].postings] assert [p for p in a["C01"].postings] == [p for p in b["C01"].postings]
assert a["C01"].postings assert a["C01"].postings
def test_90_closes_via_909():
cfg, g, ps, pl, books = _build()
for lg in books.values():
day = date(2025, 3, 31)
assert lg.balance(day, "90") == {}
t = lg.turnover(date(2025, 1, 1), day, "90.09")
assert t and all(dt and ct for dt, ct in t.values())
for p in lg.postings:
if p.ct == "90.09" or p.dt == "90.09":
assert p.memo.startswith("закрытие на ")
assert not [p for p in lg.postings if p.memo == "закрытие на 99" and p.dt == "99" and p.ct.startswith("90.0") and p.ct != "90.09"]
def test_build_ledgers_deterministic_with_counterparties():
a = _build(mean=30, seed=3)[4]
b = _build(mean=30, seed=3)[4]
assert a.keys() == b.keys()
for code in a:
assert a[code].postings == b[code].postings