2026-10-03 21:41:07 +04:00

153 lines
4.9 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.

"""Модель проверенного набора seed и отчёта о применении (без доступа к БД)."""
import datetime
from dataclasses import dataclass, field
from src.models.enums import (
AccountKind,
AccountRowType,
BusinessSegment,
ConsolidationApproach,
DocumentType,
PermissionAction,
PermissionObject,
StatementType,
)
class SeedError(Exception):
"""Ошибка файлов seed (отсутствие, формат, ссылочная целостность); сообщение — по-русски, с файлом и записью."""
@dataclass(frozen=True)
class RegionalOfficeSeed:
name: str
is_head_office: bool
is_active: bool
@dataclass(frozen=True)
class RoleSeed:
code: str
name: str
description: str | None
is_system: bool
is_active: bool
@dataclass(frozen=True)
class PermissionSeed:
code: str
object_type: PermissionObject
action: PermissionAction
name: str
description: str | None
@dataclass(frozen=True)
class AccountSeed:
"""Строка плана счетов; `code` нормализован (60.01, 76.07.1), `parent_code` — код родителя или None."""
code: str
parent_code: str | None
name: str
kind: AccountKind | None
row_type: AccountRowType
is_off_balance: bool
@dataclass(frozen=True)
class RuleSideSeed:
"""Состав счетов одной стороны правила: `accounts` — включённые, `excluded` — исключения внутри маски."""
accounts: tuple[str, ...]
excluded: tuple[str, ...]
@dataclass(frozen=True)
class EliminationRuleSeed:
"""Правило элиминации версии `version_no` (§4); `sides` — company_no (1, 2) -> состав счетов."""
statement_type: StatementType
rule_no: int
version_no: int
article_1_line: str
article_2_line: str
side_1: str
measure: str
elimination_mode: str
vat_handling: str | None
excise_adjust: bool
comment: str | None
valid_from: datetime.date
sides: dict[int, RuleSideSeed]
@dataclass(frozen=True)
class RequiredDocumentRuleSeed:
"""Строка чек-листа; `account_code` задан только для OSV."""
approach: ConsolidationApproach
document_type: DocumentType
account_code: str | None
is_mandatory: bool
@dataclass(frozen=True)
class UserSeed:
"""Dev-пользователь; `regional_office` — имя филиала, `role_codes` — коды ролей (в dev-наборе ровно одна)."""
email: str
full_name: str
employee_no: str | None
regional_office: str
business_segment: BusinessSegment | None
is_active: bool
role_codes: tuple[str, ...]
@dataclass
class SeedData:
"""Проверенный набор seed; ничего не знает о БД."""
regional_offices: list[RegionalOfficeSeed] = field(default_factory=list)
roles: list[RoleSeed] = field(default_factory=list)
permissions: list[PermissionSeed] = field(default_factory=list)
#: код роли -> коды прав (порядок как в файле)
role_permissions: dict[str, list[str]] = field(default_factory=dict)
#: код счёта -> счёт; порядок файла (родитель раньше потомков). Реестр кодов для загрузчика правил: `account_codes`
accounts: dict[str, AccountSeed] = field(default_factory=dict)
elimination_rules: list[EliminationRuleSeed] = field(default_factory=list)
required_document_rules: list[RequiredDocumentRuleSeed] = field(default_factory=list)
#: только при `--dev` (seed/dev/users.yaml); при `--dev` dev-филиалы дописываются в `regional_offices`
users: list[UserSeed] = field(default_factory=list)
def account_codes(data: SeedData) -> frozenset[str]:
"""Коды плана счетов набора: правила элиминации и чек-лист проверяют свои ссылки на счета по этому реестру."""
return frozenset(data.accounts)
@dataclass
class TableStat:
inserted: int = 0
updated: int = 0
unchanged: int = 0
@dataclass
class SeedReport:
"""Итог применения: по таблицам — сколько строк вставлено / обновлено / не изменилось."""
tables: dict[str, TableStat] = field(default_factory=dict)
def stat(self, table: str) -> TableStat:
return self.tables.setdefault(table, TableStat())
def format(self) -> str:
lines = ["Seed применён (вставлено / обновлено / без изменений):"]
lines += [
f" {name}: {stat.inserted} / {stat.updated} / {stat.unchanged}" for name, stat in self.tables.items()
]
return "\n".join(lines)