Andrey Epifancev 6fb42be8ff synthgen: ОСВ в оформлении 1С (#38)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 00:20:44 +04:00

173 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import timedelta
from decimal import Decimal
import openpyxl
import pytest
from synthgen import osv
from synthgen.periods import build_periods
ZERO = Decimal(0)
SAMPLE_MERGES = {
"A9:D9", "A10:D11", "E9:I9", "E10:G11", "H10:I11", "J9:N9", "J10:K11", "L10:N11", "O9:Q9", "O10:P11", "Q10:Q11",
}
def _pick(osv_world, account="62", inn=True):
cfg, group, periods, books = osv_world
member = next(m for m in group.members if (m.code not in group.no_inn) == inn)
period = periods[-1]
data = osv.rows(books[member.code], account, period, member, group)
return member, period, data, group, books
def _write(tmp_path, osv_world, account="62", inn=True):
member, period, data, group, books = _pick(osv_world, account, inn)
path = tmp_path / osv.file_name(account, period)
osv.write(path, member, account, period, data, with_inn=inn)
return openpyxl.load_workbook(path).active, member, period, data
def test_file_name():
ps = build_periods("2025-Q1", 4)
assert osv.file_name("60", ps[2]) == "сч 60_9 месяцев 2025 г.xlsx"
assert osv.file_name("62", ps[3]) == "сч 62_2025 г.xlsx"
def test_header_texts(tmp_path, osv_world):
ws, member, period, _ = _write(tmp_path, osv_world, "60")
assert ws["A1"].value == member.name
assert ws["A2"].value == f"Оборотно-сальдовая ведомость по счету 60 за {period.label_1c}"
text9 = " ".join(str(c.value) for c in ws[9] if c.value)
text10 = " ".join(str(c.value) for c in ws[10] if c.value)
assert "Счет" in text9 and "Сальдо на начало периода" in text9
assert "Обороты за период" in text9 and "Сальдо на конец периода" in text9
assert "Контрагенты" in text10 and "Дебет" in text10 + " ".join(str(c.value) for c in ws[11] if c.value)
assert "Кредит" in text10 + " ".join(str(c.value) for c in ws[11] if c.value)
def test_style_matches_sample(tmp_path, osv_world):
ws, _, _, data = _write(tmp_path, osv_world, "62", inn=False)
assert ws["A12"].font.name == "Arial" and ws["A12"].font.sz == 8
assert ws["A2"].font.name == "Arial" and ws["A2"].font.sz == 12 and ws["A2"].font.b
assert ws["A9"].fill.fgColor.rgb == "FFF4ECC5"
assert ws["A12"].fill.fgColor.rgb == "FFF8F2D8"
cp_row = next(i for i, r in enumerate(data, start=12) if r.is_cp)
sub_row = next(i for i, r in enumerate(data, start=12) if r.level == 1)
assert ws.cell(cp_row, 1).fill.fill_type is None
assert ws["A9"].border.left.style and ws.cell(cp_row, 5).border.bottom.style
assert ws.sheet_properties.outlinePr.summaryBelow is False
assert ws.row_dimensions[sub_row].outlineLevel == 1 and ws.row_dimensions[cp_row].outlineLevel == 2
assert ws.cell(sub_row, 1).alignment.indent == 1 and ws.cell(cp_row, 1).alignment.indent == 2
number = next(c for c in ws[cp_row] if isinstance(c.value, float))
assert number.number_format == "#,##0.00"
def test_merges_follow_sample(tmp_path, osv_world):
ws, *_ = _write(tmp_path, osv_world, "62", inn=False)
got = {str(m) for m in ws.merged_cells.ranges if m.min_row in (9, 10, 11)}
assert SAMPLE_MERGES <= got
(tmp_path / "x").mkdir()
ws2, *_ = _write(tmp_path / "x", osv_world, "62", inn=True)
got2 = {str(m) for m in ws2.merged_cells.ranges if m.min_row in (9, 10, 11)}
assert {"A9:D9", "A10:D11", "E10:E11", "F9:J9", "F10:H11", "I10:J11", "K10:L11", "M10:O11", "P10:Q11", "R10:R11"} <= got2
assert ws2["E10"].value == "ИНН"
def test_zero_is_empty(tmp_path, osv_world):
ws, _, _, data = _write(tmp_path, osv_world, "62", inn=False)
for i, row in enumerate(data, start=12):
spans = [(5, 7), (8, 9), (10, 11), (12, 14), (15, 16), (17, 17)]
for (a, _), value in zip(spans, row.six, strict=True):
cell = ws.cell(i, a).value
assert (cell is None) if value == 0 else (cell == float(value))
def test_total_row(tmp_path, osv_world):
ws, _, _, data = _write(tmp_path, osv_world, "62", inn=False)
last = 11 + len(data) + 1
assert ws.cell(last, 1).value == "Итого"
totals = osv.total_of(data)
account_row = next(r for r in data if r.level == 0)
assert totals == account_row.six
for (a, _), value in zip([(5, 7), (8, 9), (10, 11), (12, 14), (15, 16), (17, 17)], totals, strict=True):
assert (ws.cell(last, a).value or 0) == pytest.approx(float(value))
def test_no_inn_file(tmp_path, osv_world):
ws, member, _, data = _write(tmp_path, osv_world, "62", inn=False)
assert ws.max_column <= 17
assert all(not (isinstance(c.value, str) and c.value.isdigit() and len(c.value) == 10) for c in ws["A"])
assert all(r.inn is None for r in data)
assert all(ws.cell(i, 5).value is None or isinstance(ws.cell(i, 5).value, float) for i in range(12, 12 + len(data)))
def test_rows_match_ledger(osv_world):
member, period, data, group, books = _pick(osv_world, "62", inn=True)
ledger = books[member.code]
bal0 = ledger.balance(period.start - timedelta(days=1), "62")
bal1 = ledger.balance(period.end, "62")
turn = ledger.turnover(period.start, period.end, "62")
cps = [r for r in data if r.is_cp]
assert cps
for key, (td, tc) in turn.items():
assert key[1] is not None
assert sum(r.td for r in cps) == sum(t[0] for t in turn.values())
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())
assert sum(r.od - r.oc for r in cps) == sum(bal0.values())
def test_parent_sums(osv_world):
_, _, data, _, _ = _pick(osv_world, "76")
assert data[0].level == 0
subs = [r for r in data if r.level == 1]
for col in range(6):
assert data[0].six[col] == sum(r.six[col] for r in subs)
# у субсчёта контрагенты суммируются в его строку
for i, r in enumerate(data):
if r.level == 1:
kids = []
for k in data[i + 1:]:
if k.level <= 1:
break
if k.is_cp:
kids.append(k)
if kids:
assert r.six == tuple(sum(k.six[c] for k in kids) for c in range(6))
def test_group_names(osv_world):
cfg, group, periods, books = osv_world
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]
present = [m for m in others if m.name_1c in names or osv.variant_spelling(m) in names]
if member.code in group.no_inn:
variant = next(m for m in group.members if m.code == group.variant_name[member.code])
assert not any(m.name_1c in names for m in others if m is variant)
assert all(r.inn is None for r in data)
else:
assert all(m.name_1c in names for m in present)
assert all(r.inn for r in data if r.is_cp)
def test_osv_without_externals(tmp_path):
from synthgen import dictionary
from synthgen.config import Config
from synthgen.counterparties import plan as make_plan
from synthgen.group import build_group
from synthgen.intragroup import build
cfg = Config(group_size=3, periods=2, osv_counterparties_mean=0)
group = build_group(cfg, dictionary.load())
ps = build_periods(cfg.start, cfg.periods)
books, _ = build(cfg, group, ps, make_plan(cfg, group))
member = group.members[0]
data = osv.rows(books[member.code], "60", ps[-1], member, group)
path = tmp_path / "x.xlsx"
osv.write(path, member, "60", ps[-1], data, with_inn=member.code not in group.no_inn)
ws = openpyxl.load_workbook(path).active
assert any(c.value == "Итого" for c in ws["A"])