"""Фаза 2: правила элиминации и чек-лист обязательных документов — upsert без удалений и без commit.""" import uuid from typing import Any from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from scripts.seed_lib.model import SeedData, TableStat from src.models.accounting import ChartOfAccount, EliminationRule, EliminationRuleAccount from src.models.audit_ex import RequiredDocumentRule _RULE_VALUE_FIELDS = ( "article_1_line", "article_2_line", "side_1", "measure", "elimination_mode", "vat_handling", "excise_adjust", "comment", "valid_from", ) async def _account_ids(session: AsyncSession) -> dict[str, uuid.UUID]: return {code: id_ for id_, code in (await session.execute(select(ChartOfAccount.id, ChartOfAccount.code))).all()} async def apply_elimination_rules( session: AsyncSession, data: SeedData, rule_stat: TableStat, account_stat: TableStat ) -> None: """Правила — upsert по (statement_type, rule_no, version_no); затем состав счетов приводится к файлу.""" if not data.elimination_rules: return rule_ids = await _upsert_rules(session, data, rule_stat) await _upsert_rule_accounts(session, data, rule_ids, account_stat) async def _upsert_rules( session: AsyncSession, data: SeedData, stat: TableStat ) -> dict[tuple[Any, int, int], uuid.UUID]: stored = { (rule.statement_type, rule.rule_no, rule.version_no): rule for rule in (await session.scalars(select(EliminationRule))).all() } rule_ids: dict[tuple[Any, int, int], uuid.UUID] = {} for seed in data.elimination_rules: key = (seed.statement_type, seed.rule_no, seed.version_no) values = {name: getattr(seed, name) for name in _RULE_VALUE_FIELDS} current = stored.get(key) if current is None: rule_id = rule_ids[key] = uuid.uuid4() session.add( EliminationRule( id=rule_id, statement_type=seed.statement_type, rule_no=seed.rule_no, version_no=seed.version_no, **values, ) ) stat.inserted += 1 continue rule_ids[key] = current.id if any(getattr(current, name) != value for name, value in values.items()): for name, value in values.items(): setattr(current, name, value) stat.updated += 1 else: stat.unchanged += 1 await session.flush() return rule_ids async def _upsert_rule_accounts( session: AsyncSession, data: SeedData, rule_ids: dict[tuple[Any, int, int], uuid.UUID], stat: TableStat ) -> None: """Ключ внутри шага — (rule_id, company_no, account_id): отсутствующие вставляются, у существующих при расхождении обновляется `is_excluded`. Уникального индекса в БД нет (DBML), поэтому выбор и сравнение — в Python. Удалений нет: счета, убранные из файла, остаются в БД. """ account_ids = await _account_ids(session) existing: dict[tuple[uuid.UUID, int, uuid.UUID], tuple[uuid.UUID, bool]] = {} for row in (await session.execute(select(EliminationRuleAccount))).scalars().all(): existing.setdefault((row.rule_id, row.company_no, row.account_id), (row.id, row.is_excluded)) for seed in data.elimination_rules: rule_id = rule_ids[(seed.statement_type, seed.rule_no, seed.version_no)] for company_no, side in seed.sides.items(): wanted = [(code, False) for code in side.accounts] + [(code, True) for code in side.excluded] for code, is_excluded in wanted: account_id = account_ids[code] current = existing.get((rule_id, company_no, account_id)) if current is None: session.add( EliminationRuleAccount( rule_id=rule_id, company_no=company_no, account_id=account_id, is_excluded=is_excluded ) ) stat.inserted += 1 elif current[1] != is_excluded: await session.execute( update(EliminationRuleAccount) .where(EliminationRuleAccount.id == current[0]) .values(is_excluded=is_excluded) ) stat.updated += 1 else: stat.unchanged += 1 await session.flush() async def apply_required_document_rules(session: AsyncSession, data: SeedData, stat: TableStat) -> None: """Чек-лист — upsert по (approach, document_type, account_id) с учётом NULL в `account_id` (BALANCE / INCOME). Уникальный индекс трактует NULL как различные значения и допускает дубли, поэтому `ON CONFLICT` не годится: существующие строки читаются, ключ сравнивается в Python (None == None). Удалений нет. """ if not data.required_document_rules: return account_ids = await _account_ids(session) existing: dict[tuple[Any, Any, uuid.UUID | None], RequiredDocumentRule] = {} for row in (await session.scalars(select(RequiredDocumentRule))).all(): existing.setdefault((row.approach, row.document_type, row.account_id), row) for seed in data.required_document_rules: account_id = account_ids[seed.account_code] if seed.account_code is not None else None current = existing.get((seed.approach, seed.document_type, account_id)) if current is None: session.add( RequiredDocumentRule( approach=seed.approach, document_type=seed.document_type, account_id=account_id, is_mandatory=seed.is_mandatory, ) ) stat.inserted += 1 elif current.is_mandatory != seed.is_mandatory: current.is_mandatory = seed.is_mandatory stat.updated += 1 else: stat.unchanged += 1 await session.flush()