493 lines
21 KiB
Python
493 lines
21 KiB
Python
"""Seed правил элиминации ББ (§4) и чек-листа обязательных документов (spec reference-data-seed, задача 14).
|
||
|
||
Составы счетов правил §4 и чек-лист (ТЗ 5.1, OQ-46) переписаны в тест заново, независимо от yaml в `seed/core/`.
|
||
"""
|
||
|
||
import copy
|
||
import datetime
|
||
import shutil
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
import pytest
|
||
import yaml
|
||
from sqlalchemy import func, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from scripts.seed import SeedError, load_seed, run_seed
|
||
from scripts.seed_lib.loaders_rules import load_elimination_rules, load_required_document_rules
|
||
from src.models.access import Permission, Role, RolePermission
|
||
from src.models.accounting import ChartOfAccount, EliminationRule, EliminationRuleAccount
|
||
from src.models.audit_ex import RequiredDocumentRule
|
||
from src.models.directories import RegionalOffice
|
||
from src.models.enums import ConsolidationApproach, DocumentType, StatementType
|
||
|
||
API_DIR = Path(__file__).resolve().parent.parent
|
||
SEED_DIR = API_DIR / "seed"
|
||
|
||
# docs/context/05-domain-rules.md §4, таблица ББ. Состав: rule_no -> (статья А, статья Б, mode,
|
||
# счета А, исключения А, счета Б, исключения Б). «Кроме» — исключения; «01 — резервный» включён в счета А правила 1;
|
||
# «76.07.1 − 76.07.5 − 76.07.9» (Б, правило 1) — 76.07.1 с исключениями 76.07.5, 76.07.9.
|
||
RECEIVABLES_A = ["60.02", "60.22", "60.32", "62.01", "62.03", "62.21", "62.31", "76"]
|
||
RULES_S4: dict[int, dict[str, Any]] = {
|
||
1: {
|
||
"lines": ("1150", "1450/1550"),
|
||
"mode": "manual",
|
||
"a": ["01", "03", "76.07.9"],
|
||
"a_excl": [],
|
||
"b": ["76.07.1"],
|
||
"b_excl": ["76.07.5", "76.07.9"],
|
||
},
|
||
2: {
|
||
"lines": ("1170", "1310/1350"),
|
||
"mode": "manual",
|
||
"a": ["58.01"],
|
||
"a_excl": [],
|
||
"b": ["80", "83"],
|
||
"b_excl": [],
|
||
},
|
||
3: {"lines": ("1170", "1410"), "mode": "min", "a": ["58.03"], "a_excl": [], "b": ["67.03"], "b_excl": []},
|
||
4: {"lines": ("1240", "1510"), "mode": "min", "a": ["58.03"], "a_excl": [], "b": ["66.03"], "b_excl": []},
|
||
5: {
|
||
"lines": ("1230", "1510"),
|
||
"mode": "min",
|
||
"a": ["76"],
|
||
"a_excl": ["76.07.5", "76.07.9"],
|
||
"b": ["66.04", "67.04"],
|
||
"b_excl": [],
|
||
},
|
||
6: {
|
||
"lines": ("1230", "1520"),
|
||
"mode": "min",
|
||
"a": RECEIVABLES_A,
|
||
"a_excl": ["76.07.5", "76.07.9"],
|
||
"b": ["60.01", "60.03", "60.21", "60.31", "62.02", "62.22", "62.32", "76"],
|
||
"b_excl": ["76.07.1"],
|
||
},
|
||
7: {
|
||
"lines": ("1230", "1410"),
|
||
"mode": "min",
|
||
"a": RECEIVABLES_A,
|
||
"a_excl": ["76.07.5", "76.07.9"],
|
||
"b": ["67"],
|
||
"b_excl": [],
|
||
},
|
||
8: {
|
||
"lines": ("1230", "1510"),
|
||
"mode": "min",
|
||
"a": RECEIVABLES_A,
|
||
"a_excl": ["76.07.5", "76.07.9"],
|
||
"b": ["66"],
|
||
"b_excl": [],
|
||
},
|
||
}
|
||
|
||
# ТЗ 5.1 / spec «Первый запуск»: BASIC — ББ, ОФР и ОСВ по 8 счетам; EXTENDED — BASIC + 17 ОСВ (ASSUMPTION(OQ-46))
|
||
BASIC_OSV = {"01", "58", "60", "62", "66", "67", "76", "90"}
|
||
EXTENDED_ONLY_OSV = {
|
||
"02",
|
||
"03",
|
||
"08",
|
||
"44",
|
||
"26",
|
||
"91",
|
||
"51",
|
||
"52",
|
||
"59",
|
||
"80",
|
||
"83",
|
||
"86",
|
||
"97",
|
||
"001",
|
||
"008",
|
||
"009",
|
||
"011",
|
||
}
|
||
BASIC_CHECKLIST = {("BALANCE", None), ("INCOME", None)} | {("OSV", code) for code in BASIC_OSV}
|
||
EXTENDED_CHECKLIST = BASIC_CHECKLIST | {("OSV", code) for code in EXTENDED_ONLY_OSV}
|
||
|
||
#: таблицы, затрагиваемые seed: «ничего не записано» и «повтор не плодит строки» проверяются по ним всем
|
||
SEEDED_TABLES = (
|
||
RegionalOffice,
|
||
Role,
|
||
Permission,
|
||
RolePermission,
|
||
ChartOfAccount,
|
||
EliminationRule,
|
||
EliminationRuleAccount,
|
||
RequiredDocumentRule,
|
||
)
|
||
RULES_COUNT = 8
|
||
MAIN_PAIR_NO = 6
|
||
COMPANY_2 = 2
|
||
NULL_ACCOUNT_ROWS = 4 # BALANCE и INCOME для BASIC и EXTENDED
|
||
BASIC_COUNT = 10
|
||
EXTENDED_COUNT = 27
|
||
|
||
|
||
def _copy_seed(tmp_path: Path) -> Path:
|
||
target = tmp_path / "seed"
|
||
shutil.copytree(SEED_DIR, target)
|
||
return target
|
||
|
||
|
||
def _edit_yaml(path: Path, change: Any) -> None:
|
||
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||
change(data)
|
||
path.write_text(yaml.safe_dump(data, allow_unicode=True, sort_keys=False), encoding="utf-8")
|
||
|
||
|
||
def _rules_error(tmp_path: Path, change: Any, known: set[str] | None = None) -> str:
|
||
"""Копия yaml правил с правкой -> текст SeedError загрузчика."""
|
||
core = tmp_path / "core"
|
||
core.mkdir(exist_ok=True)
|
||
target = core / "elimination_rules_v1.yaml"
|
||
target.write_text((SEED_DIR / "core" / "elimination_rules_v1.yaml").read_text(encoding="utf-8"), encoding="utf-8")
|
||
_edit_yaml(target, change)
|
||
with pytest.raises(SeedError) as info:
|
||
load_elimination_rules(core, tmp_path, known if known is not None else set(load_seed(SEED_DIR).accounts))
|
||
return str(info.value)
|
||
|
||
|
||
def _checklist_error(tmp_path: Path, change: Any) -> str:
|
||
core = tmp_path / "core"
|
||
core.mkdir(exist_ok=True)
|
||
target = core / "required_document_rules.yaml"
|
||
target.write_text(
|
||
(SEED_DIR / "core" / "required_document_rules.yaml").read_text(encoding="utf-8"), encoding="utf-8"
|
||
)
|
||
_edit_yaml(target, change)
|
||
with pytest.raises(SeedError) as info:
|
||
load_required_document_rules(core, tmp_path, set(load_seed(SEED_DIR).accounts))
|
||
return str(info.value)
|
||
|
||
|
||
# ---------- файлы репозитория (без БД) ----------
|
||
|
||
|
||
def test_repository_rules_match_s4_literal() -> None:
|
||
rules = {r.rule_no: r for r in load_seed(SEED_DIR).elimination_rules}
|
||
assert set(rules) == set(RULES_S4)
|
||
for no, expected in RULES_S4.items():
|
||
rule = rules[no]
|
||
assert (rule.article_1_line, rule.article_2_line) == expected["lines"], no
|
||
assert rule.elimination_mode == expected["mode"], no
|
||
assert (rule.statement_type, rule.version_no, rule.measure, rule.side_1) == (
|
||
StatementType.balance,
|
||
1,
|
||
"closing",
|
||
"D",
|
||
)
|
||
assert rule.valid_from == datetime.date(2024, 1, 1)
|
||
assert rule.vat_handling is None and rule.excise_adjust is False
|
||
assert set(rule.sides[1].accounts) == set(expected["a"]), no
|
||
assert set(rule.sides[1].excluded) == set(expected["a_excl"]), no
|
||
assert set(rule.sides[2].accounts) == set(expected["b"]), no
|
||
assert set(rule.sides[2].excluded) == set(expected["b_excl"]), no
|
||
|
||
|
||
def test_repository_checklist_matches_spec() -> None:
|
||
rules = load_seed(SEED_DIR).required_document_rules
|
||
for approach, expected in (("BASIC", BASIC_CHECKLIST), ("EXTENDED", EXTENDED_CHECKLIST)):
|
||
actual = {(r.document_type.value, r.account_code) for r in rules if r.approach.value == approach}
|
||
assert actual == expected
|
||
assert len([r for r in rules if r.approach.value == approach]) == len(expected)
|
||
assert len(rules) == BASIC_COUNT + EXTENDED_COUNT
|
||
assert all(r.is_mandatory for r in rules)
|
||
|
||
|
||
def test_every_rule_has_accounts_on_both_sides() -> None:
|
||
for rule in load_seed(SEED_DIR).elimination_rules:
|
||
assert rule.sides[1].accounts and rule.sides[2].accounts, rule.rule_no
|
||
|
||
|
||
# ---------- валидатор правил ----------
|
||
|
||
|
||
def test_unknown_account_in_rule_names_account_and_rule(tmp_path: Path) -> None:
|
||
def change(data: dict[str, Any]) -> None:
|
||
data["rules"][5]["company_1"]["accounts"].append("99.99")
|
||
|
||
message = _rules_error(tmp_path, change)
|
||
assert "99.99" in message and "№ 6" in message and "сторона 1" in message
|
||
assert "не найден в плане счетов" in message
|
||
|
||
|
||
def test_unknown_excluded_account_is_rejected(tmp_path: Path) -> None:
|
||
def change(data: dict[str, Any]) -> None:
|
||
data["rules"][0]["company_2"]["excluded"].append("76.99")
|
||
|
||
message = _rules_error(tmp_path, change)
|
||
assert "76.99" in message and "№ 1" in message and "сторона 2" in message
|
||
|
||
|
||
def test_account_written_without_quotes_is_rejected(tmp_path: Path) -> None:
|
||
def change(data: dict[str, Any]) -> None:
|
||
data["rules"][2]["company_1"]["accounts"] = [58.03]
|
||
|
||
assert "не строка" in _rules_error(tmp_path, change)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("change", "fragment"),
|
||
[
|
||
(lambda d: d["rules"][1].update(side_1="X"), "side_1"),
|
||
(lambda d: d["rules"][1].update(measure="avg"), "measure"),
|
||
(lambda d: d["rules"][1].update(elimination_mode="max"), "elimination_mode"),
|
||
(lambda d: d["rules"][1].pop("article_1_line"), "article_1_line"),
|
||
(lambda d: d["rules"][1].update(excise_adjust="да"), "excise_adjust"),
|
||
(lambda d: d["rules"][1].update(unknown=1), "unknown"),
|
||
(lambda d: d["rules"][1].update(rule_no=1), "№ 1"),
|
||
(lambda d: d["rules"][1].update(rule_no=0), "rule_no"),
|
||
(lambda d: d.update(statement_type="profit"), "statement_type"),
|
||
(lambda d: d.update(version_no=0), "version_no"),
|
||
(lambda d: d.update(valid_from="2024-01-01"), "valid_from"),
|
||
(lambda d: d.pop("rules"), "rules"),
|
||
(lambda d: d["rules"][1].pop("company_2"), "company_2"),
|
||
(lambda d: d["rules"][1].update(company_2={"accounts": []}), "нет счетов стороны"),
|
||
(lambda d: d["rules"][1].update(company_2={"accounts": ["80", "80"]}), "больше одного раза"),
|
||
(lambda d: d["rules"][1].update(company_2={"accounts": ["80"], "excluded": ["80"]}), "включён и исключён"),
|
||
(lambda d: d["rules"][1].update(company_2={"accounts": ["80"], "extra": []}), "extra"),
|
||
(lambda d: d["rules"][1].update(company_3={"accounts": ["80"]}), "company_3"),
|
||
],
|
||
)
|
||
def test_rules_validator_rejects(tmp_path: Path, change: Any, fragment: str) -> None:
|
||
assert fragment in _rules_error(tmp_path, change)
|
||
|
||
|
||
def test_rules_file_missing(tmp_path: Path) -> None:
|
||
(tmp_path / "core").mkdir()
|
||
with pytest.raises(SeedError, match="elimination_rules_v1.yaml: файл не найден"):
|
||
load_elimination_rules(tmp_path / "core", tmp_path, set())
|
||
|
||
|
||
# ---------- валидатор чек-листа ----------
|
||
|
||
|
||
def test_checklist_unknown_account_names_approach_and_account(tmp_path: Path) -> None:
|
||
def change(data: list[dict[str, Any]]) -> None:
|
||
data.append({"approach": "EXTENDED", "document_type": "OSV", "account": "95", "is_mandatory": True})
|
||
|
||
message = _checklist_error(tmp_path, change)
|
||
assert "чек-лист (EXTENDED)" in message and "счёт 95 не найден в плане счетов" in message
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("change", "fragment"),
|
||
[
|
||
(lambda d: d[0].update(approach="FULL"), "approach"),
|
||
(lambda d: d[0].update(document_type="PASSPORT"), "document_type"),
|
||
(lambda d: d[0].update(account="01"), "account допустим только для OSV"),
|
||
(lambda d: d[2].pop("account"), "для OSV обязателен account"),
|
||
(lambda d: d[2].update(account=1), "обязателен account"),
|
||
(lambda d: d.append(copy.deepcopy(d[0])), "больше одного раза"),
|
||
(lambda d: d.append(copy.deepcopy(d[2])), "больше одного раза"),
|
||
(lambda d: d[0].update(is_mandatory="yes"), "is_mandatory"),
|
||
(lambda d: d[0].update(extra=1), "extra"),
|
||
(lambda d: d[0].pop("document_type"), "document_type"),
|
||
],
|
||
)
|
||
def test_checklist_validator_rejects(tmp_path: Path, change: Any, fragment: str) -> None:
|
||
assert fragment in _checklist_error(tmp_path, change)
|
||
|
||
|
||
def test_checklist_file_missing(tmp_path: Path) -> None:
|
||
(tmp_path / "core").mkdir()
|
||
with pytest.raises(SeedError, match="required_document_rules.yaml: файл не найден"):
|
||
load_required_document_rules(tmp_path / "core", tmp_path, set())
|
||
|
||
|
||
def test_load_seed_fails_on_unknown_account_without_database(tmp_path: Path) -> None:
|
||
seed = _copy_seed(tmp_path)
|
||
_edit_yaml(
|
||
seed / "core" / "elimination_rules_v1.yaml", lambda d: d["rules"][5]["company_1"]["accounts"].append("99.99")
|
||
)
|
||
with pytest.raises(SeedError, match=r"№ 6.*99\.99"):
|
||
load_seed(seed)
|
||
|
||
|
||
# ---------- БД ----------
|
||
|
||
|
||
async def _counts(session: AsyncSession) -> dict[str, int]:
|
||
return {
|
||
model.__tablename__: await session.scalar(select(func.count()).select_from(model)) or 0
|
||
for model in SEEDED_TABLES
|
||
}
|
||
|
||
|
||
async def _rule_sides(session: AsyncSession) -> set[tuple[int, int, str, bool]]:
|
||
rows = (
|
||
await session.execute(
|
||
select(
|
||
EliminationRule.rule_no,
|
||
EliminationRuleAccount.company_no,
|
||
ChartOfAccount.code,
|
||
EliminationRuleAccount.is_excluded,
|
||
)
|
||
.join(EliminationRuleAccount, EliminationRuleAccount.rule_id == EliminationRule.id)
|
||
.join(ChartOfAccount, ChartOfAccount.id == EliminationRuleAccount.account_id)
|
||
)
|
||
).all()
|
||
return {(no, company, code, excluded) for no, company, code, excluded in rows}
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_first_run_inserts_rules_and_checklist(db_session: AsyncSession) -> None:
|
||
report = await run_seed(db_session)
|
||
assert report.tables["elimination_rule"].inserted == RULES_COUNT
|
||
assert report.tables["required_document_rule"].inserted == BASIC_COUNT + EXTENDED_COUNT
|
||
assert report.tables["elimination_rule_account"].inserted == sum(
|
||
len(r["a"]) + len(r["a_excl"]) + len(r["b"]) + len(r["b_excl"]) for r in RULES_S4.values()
|
||
)
|
||
rules = (await db_session.scalars(select(EliminationRule).order_by(EliminationRule.rule_no))).all()
|
||
assert [r.rule_no for r in rules] == list(range(1, RULES_COUNT + 1))
|
||
assert all(r.statement_type is StatementType.balance and r.version_no == 1 for r in rules)
|
||
assert all(r.valid_from == datetime.date(2024, 1, 1) and r.valid_to is None for r in rules)
|
||
assert all(r.measure == "closing" and r.side_1 == "D" and r.excise_adjust is False for r in rules)
|
||
assert all(r.vat_handling is None for r in rules)
|
||
sides = await _rule_sides(db_session)
|
||
for rule in rules:
|
||
companies = {company for no, company, _, _ in sides if no == rule.rule_no}
|
||
assert companies == {1, 2}, rule.rule_no
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_rule_6_main_pair_scenario(db_session: AsyncSession) -> None:
|
||
await run_seed(db_session)
|
||
rule = (await db_session.scalars(select(EliminationRule).where(EliminationRule.rule_no == MAIN_PAIR_NO))).one()
|
||
assert rule.elimination_mode == "min"
|
||
assert rule.measure == "closing"
|
||
assert rule.side_1 == "D"
|
||
sides = {(c, code, excluded) for no, c, code, excluded in await _rule_sides(db_session) if no == MAIN_PAIR_NO}
|
||
side_1 = {code for c, code, excluded in sides if c == 1 and not excluded}
|
||
side_1_excluded = {code for c, code, excluded in sides if c == 1 and excluded}
|
||
side_2 = {code for c, code, excluded in sides if c == COMPANY_2 and not excluded}
|
||
side_2_excluded = {code for c, code, excluded in sides if c == COMPANY_2 and excluded}
|
||
assert side_1 == {"60.02", "60.22", "60.32", "62.01", "62.03", "62.21", "62.31", "76"}
|
||
assert side_1_excluded == {"76.07.5", "76.07.9"}
|
||
assert side_2 == {"60.01", "60.03", "60.21", "60.31", "62.02", "62.22", "62.32", "76"}
|
||
assert side_2_excluded == {"76.07.1"}
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_rule_accounts_equal_s4_literal(db_session: AsyncSession) -> None:
|
||
await run_seed(db_session)
|
||
expected = {
|
||
(no, company, code, excluded)
|
||
for no, rule in RULES_S4.items()
|
||
for company, included, excluded_codes in ((1, rule["a"], rule["a_excl"]), (2, rule["b"], rule["b_excl"]))
|
||
for code, excluded in [(c, False) for c in included] + [(c, True) for c in excluded_codes]
|
||
}
|
||
assert await _rule_sides(db_session) == expected
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_rule_columns_equal_s4(db_session: AsyncSession) -> None:
|
||
await run_seed(db_session)
|
||
rules = {r.rule_no: r for r in (await db_session.scalars(select(EliminationRule))).all()}
|
||
for no, expected in RULES_S4.items():
|
||
assert (rules[no].article_1_line, rules[no].article_2_line) == expected["lines"], no
|
||
assert rules[no].elimination_mode == expected["mode"], no
|
||
assert rules[1].comment and "76.07.5" in rules[1].comment
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_checklist_rows(db_session: AsyncSession) -> None:
|
||
await run_seed(db_session)
|
||
rows = (
|
||
await db_session.execute(
|
||
select(
|
||
RequiredDocumentRule.approach,
|
||
RequiredDocumentRule.document_type,
|
||
ChartOfAccount.code,
|
||
RequiredDocumentRule.is_mandatory,
|
||
).outerjoin(ChartOfAccount, ChartOfAccount.id == RequiredDocumentRule.account_id)
|
||
)
|
||
).all()
|
||
by_approach: dict[ConsolidationApproach, set[tuple[str, str | None]]] = {}
|
||
for approach, document_type, code, mandatory in rows:
|
||
assert mandatory is True
|
||
assert (document_type is DocumentType.OSV) == (code is not None)
|
||
by_approach.setdefault(approach, set()).add((document_type.value, code))
|
||
assert by_approach[ConsolidationApproach.BASIC] == BASIC_CHECKLIST
|
||
assert by_approach[ConsolidationApproach.EXTENDED] == EXTENDED_CHECKLIST
|
||
assert len(rows) == BASIC_COUNT + EXTENDED_COUNT
|
||
null_account = await db_session.scalar(
|
||
select(func.count()).select_from(RequiredDocumentRule).where(RequiredDocumentRule.account_id.is_(None))
|
||
)
|
||
assert null_account == NULL_ACCOUNT_ROWS
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_second_run_keeps_all_row_counts(db_session: AsyncSession) -> None:
|
||
await run_seed(db_session)
|
||
first = await _counts(db_session)
|
||
assert first["required_document_rule"] == BASIC_COUNT + EXTENDED_COUNT
|
||
report = await run_seed(db_session)
|
||
assert await _counts(db_session) == first
|
||
for table in ("elimination_rule", "elimination_rule_account", "required_document_rule"):
|
||
stat = report.tables[table]
|
||
assert (stat.inserted, stat.updated) == (0, 0), table
|
||
assert stat.unchanged == first[table]
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_unknown_account_aborts_before_any_write(db_session: AsyncSession, tmp_path: Path) -> None:
|
||
seed = _copy_seed(tmp_path)
|
||
_edit_yaml(
|
||
seed / "core" / "elimination_rules_v1.yaml", lambda d: d["rules"][5]["company_1"]["accounts"].append("99.99")
|
||
)
|
||
before = await _counts(db_session)
|
||
assert set(before.values()) == {0} # свежая БД: ничего не загружено
|
||
with pytest.raises(SeedError) as info:
|
||
await run_seed(db_session, seed_dir=seed)
|
||
assert "99.99" in str(info.value) and "№ 6" in str(info.value)
|
||
assert await _counts(db_session) == before
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_unknown_account_in_checklist_aborts_before_any_write(db_session: AsyncSession, tmp_path: Path) -> None:
|
||
seed = _copy_seed(tmp_path)
|
||
_edit_yaml(
|
||
seed / "core" / "required_document_rules.yaml",
|
||
lambda d: d.append({"approach": "EXTENDED", "document_type": "OSV", "account": "95", "is_mandatory": True}),
|
||
)
|
||
with pytest.raises(SeedError, match=r"чек-лист \(EXTENDED\).*95"):
|
||
await run_seed(db_session, seed_dir=seed)
|
||
assert set((await _counts(db_session)).values()) == {0}
|
||
|
||
|
||
@pytest.mark.integration
|
||
async def test_changed_rule_and_flags_are_updated_without_duplicates(db_session: AsyncSession, tmp_path: Path) -> None:
|
||
await run_seed(db_session)
|
||
before = await _counts(db_session)
|
||
seed = _copy_seed(tmp_path)
|
||
|
||
def change(data: dict[str, Any]) -> None:
|
||
data["rules"][2]["comment"] = "уточнено"
|
||
side = data["rules"][5]["company_2"]
|
||
side["accounts"].remove("62.32") # убран из файла — в БД остаётся
|
||
side["excluded"].append("76.07.2")
|
||
data["rules"][4]["company_1"][
|
||
"excluded"
|
||
] = [] # было исключение, теперь нет — строка остаётся, флаг не меняется
|
||
data["rules"][4]["company_1"]["accounts"].append("76.07.5") # теперь включён: is_excluded true -> false
|
||
|
||
_edit_yaml(seed / "core" / "elimination_rules_v1.yaml", change)
|
||
_edit_yaml(seed / "core" / "required_document_rules.yaml", lambda d: d[2].update(is_mandatory=False))
|
||
report = await run_seed(db_session, seed_dir=seed)
|
||
assert report.tables["elimination_rule"].updated == 1
|
||
assert report.tables["elimination_rule_account"].inserted == 1 # 76.07.2 в исключения правила 6
|
||
assert report.tables["elimination_rule_account"].updated == 1 # 76.07.5 правила 5: исключён -> включён
|
||
assert report.tables["required_document_rule"].updated == 1
|
||
after = await _counts(db_session)
|
||
assert after["elimination_rule"] == before["elimination_rule"]
|
||
assert after["required_document_rule"] == before["required_document_rule"]
|
||
assert after["elimination_rule_account"] == before["elimination_rule_account"] + 1
|
||
sides = await _rule_sides(db_session)
|
||
assert (6, 2, "62.32", False) in sides # без удалений
|
||
assert (6, 2, "76.07.2", True) in sides
|
||
assert (5, 1, "76.07.5", False) in sides and (5, 1, "76.07.5", True) not in sides
|
||
assert (5, 1, "76.07.9", True) in sides # убранное из excluded не отзывается
|