synthgen: тесты ОСВ сверяют контрагентов с книгой и вариантные имена (#38)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6fb42be8ff
commit
7a4a44a6b3
@ -47,6 +47,7 @@ class OsvRow:
|
|||||||
cd: Decimal # сальдо на конец, Дт
|
cd: Decimal # сальдо на конец, Дт
|
||||||
cc: Decimal # сальдо на конец, Кт
|
cc: Decimal # сальдо на конец, Кт
|
||||||
is_cp: bool = False # строка контрагента
|
is_cp: bool = False # строка контрагента
|
||||||
|
cp: str | None = None # код аналитики контрагента в книге (для сверки с Ledger)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def six(self) -> Six:
|
def six(self) -> Six:
|
||||||
@ -196,7 +197,7 @@ def rows(ledger: Ledger, account: str, period: Period, member: Member, group: Gr
|
|||||||
for name, inn, cp in named:
|
for name, inn, cp in named:
|
||||||
six = by_cp[cp]
|
six = by_cp[cp]
|
||||||
total = _add(total, six)
|
total = _add(total, six)
|
||||||
out.append(OsvRow(level, name, inn, *six, is_cp=True))
|
out.append(OsvRow(level, name, inn, *six, is_cp=True, cp=cp))
|
||||||
return out, total
|
return out, total
|
||||||
|
|
||||||
def build(code: str, level: int) -> tuple[list[OsvRow], Six]:
|
def build(code: str, level: int) -> tuple[list[OsvRow], Six]:
|
||||||
|
|||||||
@ -103,19 +103,46 @@ def test_no_inn_file(tmp_path, osv_world):
|
|||||||
|
|
||||||
|
|
||||||
def test_rows_match_ledger(osv_world):
|
def test_rows_match_ledger(osv_world):
|
||||||
member, period, data, group, books = _pick(osv_world, "62", inn=True)
|
"""Каждая строка контрагента равна Ledger.balance/turnover по ключу (субсчёт, контрагент)."""
|
||||||
ledger = books[member.code]
|
cfg, group, periods, books = osv_world
|
||||||
bal0 = ledger.balance(period.start - timedelta(days=1), "62")
|
checked = 0
|
||||||
bal1 = ledger.balance(period.end, "62")
|
for member in group.members:
|
||||||
turn = ledger.turnover(period.start, period.end, "62")
|
ledger = books[member.code]
|
||||||
cps = [r for r in data if r.is_cp]
|
for period in periods:
|
||||||
assert cps
|
data = osv.rows(ledger, "62", period, member, group)
|
||||||
for key, (td, tc) in turn.items():
|
opening = ledger.balance(period.start - timedelta(days=1), "62")
|
||||||
assert key[1] is not None
|
closing = ledger.balance(period.end, "62")
|
||||||
assert sum(r.td for r in cps) == sum(t[0] for t in turn.values())
|
turn = ledger.turnover(period.start, period.end, "62")
|
||||||
assert sum(r.tc for r in cps) == sum(t[1] for t in turn.values())
|
|
||||||
assert sum(r.cd - r.cc for r in cps) == sum(bal1.values())
|
def fold(src, pick):
|
||||||
assert sum(r.od - r.oc for r in cps) == sum(bal0.values())
|
out = {}
|
||||||
|
for (code, an), v in src.items():
|
||||||
|
key = (code, an.cp if an else None)
|
||||||
|
out[key] = out.get(key, ZERO) + pick(v)
|
||||||
|
return out
|
||||||
|
|
||||||
|
want = {
|
||||||
|
"o": fold(opening, lambda v: v), "c": fold(closing, lambda v: v),
|
||||||
|
"td": fold(turn, lambda v: v[0]), "tc": fold(turn, lambda v: v[1]),
|
||||||
|
}
|
||||||
|
code = None
|
||||||
|
seen = set()
|
||||||
|
for r in data:
|
||||||
|
if not r.is_cp:
|
||||||
|
code = r.label
|
||||||
|
continue
|
||||||
|
key = (code, r.cp)
|
||||||
|
seen.add(key)
|
||||||
|
assert r.od - r.oc == want["o"].get(key, ZERO), (member.code, period.key, key)
|
||||||
|
assert r.cd - r.cc == want["c"].get(key, ZERO), (member.code, period.key, key)
|
||||||
|
assert r.td == want["td"].get(key, ZERO) and r.tc == want["tc"].get(key, ZERO), (member.code, period.key, key)
|
||||||
|
checked += 1
|
||||||
|
# обратное: всё ненулевое из книги попало в строки
|
||||||
|
for kind, src in want.items():
|
||||||
|
for key, v in src.items():
|
||||||
|
if v != 0:
|
||||||
|
assert key in seen, (member.code, period.key, kind, key)
|
||||||
|
assert checked > 20
|
||||||
|
|
||||||
|
|
||||||
def test_parent_sums(osv_world):
|
def test_parent_sums(osv_world):
|
||||||
@ -138,19 +165,30 @@ def test_parent_sums(osv_world):
|
|||||||
|
|
||||||
|
|
||||||
def test_group_names(osv_world):
|
def test_group_names(osv_world):
|
||||||
|
"""В ОСВ без ИНН ровно один член группы записан вариантным именем, остальные — `name_1c`; с ИНН — все `name_1c`."""
|
||||||
cfg, group, periods, books = osv_world
|
cfg, group, periods, books = osv_world
|
||||||
|
by_code = {m.code: m for m in group.members}
|
||||||
|
variants_seen = 0
|
||||||
for member in group.members:
|
for member in group.members:
|
||||||
data = osv.rows(books[member.code], "62", periods[-1], member, group)
|
|
||||||
names = [r.label for r in data if r.is_cp]
|
|
||||||
others = [m for m in group.members if m.code != member.code]
|
others = [m for m in group.members if m.code != member.code]
|
||||||
present = [m for m in others if m.name_1c in names or osv.variant_spelling(m) in names]
|
for account in ("60", "62", "76"):
|
||||||
if member.code in group.no_inn:
|
for period in periods:
|
||||||
variant = next(m for m in group.members if m.code == group.variant_name[member.code])
|
data = osv.rows(books[member.code], account, period, member, group)
|
||||||
assert not any(m.name_1c in names for m in others if m is variant)
|
cps = {r.cp: r for r in data if r.is_cp}
|
||||||
assert all(r.inn is None for r in data)
|
for m in others:
|
||||||
else:
|
if m.code not in cps:
|
||||||
assert all(m.name_1c in names for m in present)
|
continue
|
||||||
assert all(r.inn for r in data if r.is_cp)
|
r = cps[m.code]
|
||||||
|
if member.code in group.no_inn and group.variant_name[member.code] == m.code:
|
||||||
|
assert r.label == osv.variant_spelling(m) != m.name_1c
|
||||||
|
variants_seen += 1
|
||||||
|
else:
|
||||||
|
assert r.label == m.name_1c, (member.code, account, period.key, m.code)
|
||||||
|
assert r.inn == (None if member.code in group.no_inn else m.inn)
|
||||||
|
if member.code in group.no_inn:
|
||||||
|
variant = by_code[group.variant_name[member.code]]
|
||||||
|
assert sum(1 for r in data if r.is_cp and r.label == variant.name_1c) == 0
|
||||||
|
assert variants_seen > 0, "вариантное написание нигде не встретилось"
|
||||||
|
|
||||||
|
|
||||||
def test_osv_without_externals(tmp_path):
|
def test_osv_without_externals(tmp_path):
|
||||||
|
|||||||
@ -25,6 +25,7 @@ def _run(path: Path) -> dict:
|
|||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
[str(PY), "-m", "src.parser.cli", str(path)], cwd=TP_WT / "api", env=env, capture_output=True, text=True, check=False
|
[str(PY), "-m", "src.parser.cli", str(path)], cwd=TP_WT / "api", env=env, capture_output=True, text=True, check=False
|
||||||
)
|
)
|
||||||
|
assert proc.stdout.strip(), proc.stderr
|
||||||
return json.loads(proc.stdout)
|
return json.loads(proc.stdout)
|
||||||
|
|
||||||
|
|
||||||
@ -61,6 +62,7 @@ def test_parser_inn(parsed, osv_world):
|
|||||||
bad = []
|
bad = []
|
||||||
for member, account, period, data, with_inn, res in parsed:
|
for member, account, period, data, with_inn, res in parsed:
|
||||||
got = [r for r in res["documents"][0]["rows"] if r["group_type"] == "COUNTERPARTY" and r["data_type"] == "BU"]
|
got = [r for r in res["documents"][0]["rows"] if r["group_type"] == "COUNTERPARTY" and r["data_type"] == "BU"]
|
||||||
|
assert got, (member.code, account, period.key)
|
||||||
if with_inn:
|
if with_inn:
|
||||||
for r in got:
|
for r in got:
|
||||||
if r["counterparty_name"] in group_names and r["counterparty_inn"] != group_names[r["counterparty_name"]]:
|
if r["counterparty_name"] in group_names and r["counterparty_inn"] != group_names[r["counterparty_name"]]:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user