94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
import csv
|
||
import os
|
||
from datetime import date
|
||
from decimal import Decimal
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from synthgen import chart
|
||
from synthgen.ledger import An, Ledger, opening
|
||
|
||
D = Decimal
|
||
|
||
|
||
def test_double_entry():
|
||
lg = Ledger("AGRO")
|
||
lg.post(date(2025, 1, 10), "62.01", An("AGRO"), "90.01", None, D("1200.00"), "продажа")
|
||
lg.post(date(2025, 2, 10), "51", None, "62.01", An("AGRO"), D("200.00"), "оплата")
|
||
opening(lg, date(2025, 1, 1), "51", None, D("500.00"))
|
||
opening(lg, date(2025, 1, 1), "60.01", An("AGRO"), D("-70.00"))
|
||
for d in (date(2025, 1, 1), date(2025, 1, 31), date(2025, 12, 31)):
|
||
assert sum(lg.balance(d).values()) == 0
|
||
|
||
|
||
def test_balance_signs_and_prefix():
|
||
lg = Ledger("X")
|
||
lg.post(date(2025, 1, 10), "62.01", An("AGRO"), "90.01", None, D("1200.00"), "")
|
||
lg.post(date(2025, 2, 10), "51", None, "62.01", An("AGRO"), D("200.00"), "")
|
||
assert lg.balance(date(2025, 3, 31), "62") == {("62.01", An("AGRO")): D("1000.00")}
|
||
assert lg.balance(date(2025, 1, 31), "62") == {("62.01", An("AGRO")): D("1200.00")}
|
||
|
||
|
||
def test_turnover_window():
|
||
lg = Ledger("X")
|
||
lg.post(date(2024, 12, 31), "62.01", An("A"), "90.01", None, D("10.00"), "")
|
||
lg.post(date(2025, 1, 1), "62.01", An("A"), "90.01", None, D("20.00"), "")
|
||
lg.post(date(2025, 3, 31), "51", None, "62.01", An("A"), D("5.00"), "")
|
||
lg.post(date(2025, 4, 1), "62.01", An("A"), "90.01", None, D("40.00"), "")
|
||
t = lg.turnover(date(2025, 1, 1), date(2025, 3, 31), "62")
|
||
assert t == {("62.01", An("A")): (D("20.00"), D("5.00"))}
|
||
|
||
|
||
def test_csv_deterministic(tmp_path):
|
||
def build(p: Path):
|
||
lg = Ledger("X")
|
||
lg.post(date(2025, 1, 10), "62.01", An("A", contract="Д-1"), "90.01", None, D("1.50"), "м")
|
||
lg.to_csv(p)
|
||
return p.read_bytes()
|
||
|
||
a = build(tmp_path / "a.csv")
|
||
assert a == build(tmp_path / "b.csv")
|
||
rows = list(csv.reader(a.decode("utf-8").splitlines(), delimiter=";"))
|
||
assert rows[0] == "day;dt;dt_cp;dt_contract;dt_item;ct;ct_cp;ct_contract;ct_item;amount;memo".split(";")
|
||
assert rows[1][:3] == ["2025-01-10", "62.01", "A"]
|
||
|
||
|
||
def test_rejects_bad_amount():
|
||
lg = Ledger("X")
|
||
with pytest.raises(ValueError):
|
||
lg.post(date(2025, 1, 1), "51", None, "62.01", None, D("0"), "")
|
||
|
||
|
||
def test_chart_helpers():
|
||
assert chart.parent("76.07.1") == "76.07"
|
||
assert chart.parent("76.07") == "76"
|
||
assert chart.parent("58.01") == "58"
|
||
assert chart.is_off_balance("001") and chart.is_off_balance("011")
|
||
assert not chart.is_off_balance("01")
|
||
|
||
|
||
def test_chart_codes_in_core_seed():
|
||
ws = Path(os.environ.get("ARM_KA_WORKSPACE", "/Users/andrey.epifancev/projects/awp-ca"))
|
||
seed = ws / "arm-core/api/seed/core/chart_of_accounts.csv"
|
||
if not seed.exists():
|
||
pytest.skip("нет seed плана счетов")
|
||
codes = {r["code"] for r in csv.DictReader(seed.open(encoding="utf-8"))}
|
||
missing = [c for c in chart.ACCOUNTS if c != "000" and c not in codes]
|
||
assert not missing
|
||
|
||
|
||
def test_closing_postings_have_explicit_kind(osv_world):
|
||
"""T3.8: закрывающие проводки различаются полем kind, а не разбором memo."""
|
||
from synthgen.ledger import KIND_CLOSE_90, KIND_CLOSE_99, KIND_CLOSE_COST, KIND_ORDINARY
|
||
|
||
_, _, _, books = osv_world
|
||
kinds = {"закрытие на 90.09": KIND_CLOSE_90, "закрытие на 99": KIND_CLOSE_99, "закрытие затрат": KIND_CLOSE_COST}
|
||
seen = set()
|
||
for lg in books.values():
|
||
for p in lg.postings:
|
||
want = kinds.get(p.memo, KIND_ORDINARY)
|
||
assert p.kind == want, (p.memo, p.kind)
|
||
seen.add(p.kind)
|
||
assert {KIND_CLOSE_90, KIND_CLOSE_99, KIND_CLOSE_COST, KIND_ORDINARY} <= seen
|