diff --git a/synthgen/economy.py b/synthgen/economy.py index 8299336..98bef36 100644 --- a/synthgen/economy.py +++ b/synthgen/economy.py @@ -7,7 +7,7 @@ open_books(...) входящие остатки на book_start (31.12 перед первым периодом); для каждого месяца: post_month(...) внешние операции месяца, 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` — эта последовательность целиком. Аналитика контрагента: `An(cp=<ИНН Org>)` для внешних, `An(cp=<код члена>)` для компаний группы (последние — Task 4). @@ -359,6 +359,26 @@ def _close_all(lg: Ledger, day: date, prefix: str) -> Decimal: 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: day = last_day(year, month) 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(): if bal > 0: 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) if tax > 0: lg.post(day, "99", None, "68.04", FNS, tax, "налог на прибыль") diff --git a/synthgen/tests/test_economy.py b/synthgen/tests/test_economy.py index 2371e31..18a4afe 100644 --- a/synthgen/tests/test_economy.py +++ b/synthgen/tests/test_economy.py @@ -118,3 +118,24 @@ def test_deterministic_and_zero_mean(): b = _build(mean=0)[4] assert [p for p in a["C01"].postings] == [p for p in b["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