feat(parsers): добавлен API клиент для checko.ru
- Реализован CheckoClient с поддержкой всех 10 эндпоинтов API v2 - Frozen dataclass модели для запросов и ответов - Справочники ОКВЭД2, ОКФС, ОКОПФ, ОКПД, статусы компаний - Маппинг русских полей API на английские имена - Unit тесты с моками - E2E тесты с реальными запросами - Настройка CHECKO_API_KEY в settings.py
This commit is contained in:
62
src/apps/parsers/clients/checko/datasets/__init__.py
Normal file
62
src/apps/parsers/clients/checko/datasets/__init__.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Справочники для расшифровки данных Checko API.
|
||||
|
||||
Справочники загружаются лениво при первом обращении и кэшируются.
|
||||
|
||||
Использование:
|
||||
from apps.parsers.clients.checko.datasets import OKVED2, OKFS, OKOPF, AccountCodes
|
||||
|
||||
# ОКВЭД-2
|
||||
okved_name = OKVED2.get_name("62.01")
|
||||
okved_item = OKVED2.get("62.01")
|
||||
|
||||
# ОКФС
|
||||
okfs_name = OKFS.get_name("16")
|
||||
|
||||
# ОКОПФ
|
||||
okopf_name = OKOPF.get_name("12300")
|
||||
|
||||
# Коды финансовой отчетности
|
||||
line_name = AccountCodes.get_name("1100")
|
||||
"""
|
||||
|
||||
from apps.parsers.clients.checko.datasets.account_codes import (
|
||||
AccountCodeItem,
|
||||
AccountCodes,
|
||||
)
|
||||
from apps.parsers.clients.checko.datasets.base import BaseDataset, DatasetItem
|
||||
from apps.parsers.clients.checko.datasets.okfs import OKFS, OkfsItem
|
||||
from apps.parsers.clients.checko.datasets.okopf import OKOPF, OkopfItem
|
||||
from apps.parsers.clients.checko.datasets.okpd import OKPD, OKPD2, OkpdItem
|
||||
from apps.parsers.clients.checko.datasets.okved import OKVED2, OkvedItem
|
||||
from apps.parsers.clients.checko.datasets.statuses import (
|
||||
CompanyStatuses,
|
||||
EntrepreneurStatuses,
|
||||
StatusItem,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base
|
||||
"BaseDataset",
|
||||
"DatasetItem",
|
||||
# ОКВЭД
|
||||
"OKVED2",
|
||||
"OkvedItem",
|
||||
# ОКФС
|
||||
"OKFS",
|
||||
"OkfsItem",
|
||||
# ОКОПФ
|
||||
"OKOPF",
|
||||
"OkopfItem",
|
||||
# ОКПД
|
||||
"OKPD",
|
||||
"OKPD2",
|
||||
"OkpdItem",
|
||||
# Коды отчетности
|
||||
"AccountCodes",
|
||||
"AccountCodeItem",
|
||||
# Статусы
|
||||
"CompanyStatuses",
|
||||
"EntrepreneurStatuses",
|
||||
"StatusItem",
|
||||
]
|
||||
91
src/apps/parsers/clients/checko/datasets/account_codes.py
Normal file
91
src/apps/parsers/clients/checko/datasets/account_codes.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Коды строк финансовой (бухгалтерской) отчетности.
|
||||
|
||||
Используются для расшифровки данных из /finances эндпоинта.
|
||||
|
||||
Использование:
|
||||
from apps.parsers.clients.checko.datasets import AccountCodes
|
||||
|
||||
# Получить название строки по коду
|
||||
name = AccountCodes.get_name("1100") # -> "Итого внеоборотных активов"
|
||||
|
||||
# Получить полный объект
|
||||
item = AccountCodes.get("2110")
|
||||
|
||||
# Поиск по названию
|
||||
items = AccountCodes.search("прибыль")
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from apps.parsers.clients.checko.datasets.base import BaseDataset
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AccountCodeItem:
|
||||
"""Элемент справочника кодов строк отчетности."""
|
||||
|
||||
code: str
|
||||
"""Код строки (например: '1100', '2110')."""
|
||||
|
||||
name: str
|
||||
"""Наименование строки."""
|
||||
|
||||
|
||||
class AccountCodes(BaseDataset[AccountCodeItem]):
|
||||
"""
|
||||
Справочник кодов строк финансовой отчетности.
|
||||
|
||||
Коды соответствуют:
|
||||
- Форма №1 (Бухгалтерский баланс): 1100-1700
|
||||
- Форма №2 (Отчет о финансовых результатах): 2100-2500
|
||||
- Форма №3 (Отчет об изменениях капитала): 3100-3600
|
||||
- Форма №4 (Отчет о движении денежных средств): 4100-4500
|
||||
- Форма №6 (Отчет о целевом использовании средств): 6100-6400
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, AccountCodeItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "account_codes.json"
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> AccountCodeItem:
|
||||
return AccountCodeItem(
|
||||
code=raw.get("code", ""),
|
||||
name=raw.get("name", ""),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_form_codes(cls, form_number: int) -> list[AccountCodeItem]:
|
||||
"""
|
||||
Получить все коды указанной формы отчетности.
|
||||
|
||||
Args:
|
||||
form_number: Номер формы (1, 2, 3, 4 или 6).
|
||||
|
||||
Returns:
|
||||
Список кодов указанной формы.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
prefix = str(form_number)
|
||||
return [i for i in cls._data.values() if i.code.startswith(prefix)]
|
||||
|
||||
@classmethod
|
||||
def get_balance_codes(cls) -> list[AccountCodeItem]:
|
||||
"""Получить коды Формы №1 (Бухгалтерский баланс)."""
|
||||
return cls.get_form_codes(1)
|
||||
|
||||
@classmethod
|
||||
def get_profit_loss_codes(cls) -> list[AccountCodeItem]:
|
||||
"""Получить коды Формы №2 (Отчет о финансовых результатах)."""
|
||||
return cls.get_form_codes(2)
|
||||
|
||||
@classmethod
|
||||
def get_capital_codes(cls) -> list[AccountCodeItem]:
|
||||
"""Получить коды Формы №3 (Отчет об изменениях капитала)."""
|
||||
return cls.get_form_codes(3)
|
||||
|
||||
@classmethod
|
||||
def get_cash_flow_codes(cls) -> list[AccountCodeItem]:
|
||||
"""Получить коды Формы №4 (Отчет о движении денежных средств)."""
|
||||
return cls.get_form_codes(4)
|
||||
179
src/apps/parsers/clients/checko/datasets/base.py
Normal file
179
src/apps/parsers/clients/checko/datasets/base.py
Normal file
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
Базовые классы для работы со справочниками Checko.
|
||||
|
||||
Справочники загружаются лениво (lazy loading) при первом обращении.
|
||||
Данные кэшируются на уровне класса (singleton pattern).
|
||||
"""
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# Путь к директории с JSON файлами
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DatasetItem:
|
||||
"""Базовый элемент справочника."""
|
||||
|
||||
code: str
|
||||
name: str
|
||||
|
||||
|
||||
class BaseDataset(Generic[T]):
|
||||
"""
|
||||
Базовый класс для работы со справочниками.
|
||||
|
||||
Реализует:
|
||||
- Lazy loading данных при первом обращении
|
||||
- Кэширование на уровне класса
|
||||
- Поиск по коду и названию
|
||||
|
||||
Наследники должны определить:
|
||||
- _json_filename: имя JSON файла
|
||||
- _parse_item: метод парсинга элемента из JSON
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, T] | None] = None
|
||||
_json_filename: ClassVar[str] = ""
|
||||
|
||||
@classmethod
|
||||
def _get_json_path(cls) -> Path:
|
||||
"""Получить путь к JSON файлу."""
|
||||
return DATA_DIR / cls._json_filename
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> T:
|
||||
"""Распарсить элемент из JSON. Переопределяется в наследниках."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def _get_item_code(cls, raw: dict) -> str:
|
||||
"""Получить код элемента из JSON."""
|
||||
return raw.get("code", "")
|
||||
|
||||
@classmethod
|
||||
def _ensure_loaded(cls) -> None:
|
||||
"""Загрузить данные если ещё не загружены."""
|
||||
if cls._data is not None:
|
||||
return
|
||||
|
||||
json_path = cls._get_json_path()
|
||||
if not json_path.exists():
|
||||
cls._data = {}
|
||||
return
|
||||
|
||||
with open(json_path, encoding="utf-8") as f:
|
||||
raw_data = json.load(f)
|
||||
|
||||
cls._data = {}
|
||||
for raw in raw_data:
|
||||
item = cls._parse_item(raw)
|
||||
code = cls._get_item_code(raw)
|
||||
cls._data[code] = item
|
||||
|
||||
@classmethod
|
||||
def get(cls, code: str) -> T | None:
|
||||
"""
|
||||
Получить элемент по коду.
|
||||
|
||||
Args:
|
||||
code: Код элемента справочника.
|
||||
|
||||
Returns:
|
||||
Элемент справочника или None если не найден.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
return cls._data.get(code)
|
||||
|
||||
@classmethod
|
||||
def get_name(cls, code: str) -> str | None:
|
||||
"""
|
||||
Получить название по коду.
|
||||
|
||||
Args:
|
||||
code: Код элемента справочника.
|
||||
|
||||
Returns:
|
||||
Название элемента или None если не найден.
|
||||
"""
|
||||
item = cls.get(code)
|
||||
if item is None:
|
||||
return None
|
||||
return getattr(item, "name", None) or getattr(item, "full_name", None)
|
||||
|
||||
@classmethod
|
||||
def all(cls) -> list[T]:
|
||||
"""
|
||||
Получить все элементы справочника.
|
||||
|
||||
Returns:
|
||||
Список всех элементов.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
return list(cls._data.values())
|
||||
|
||||
@classmethod
|
||||
def codes(cls) -> list[str]:
|
||||
"""
|
||||
Получить все коды справочника.
|
||||
|
||||
Returns:
|
||||
Список всех кодов.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
return list(cls._data.keys())
|
||||
|
||||
@classmethod
|
||||
def search(cls, query: str) -> list[T]:
|
||||
"""
|
||||
Поиск по названию (регистронезависимый).
|
||||
|
||||
Args:
|
||||
query: Поисковый запрос.
|
||||
|
||||
Returns:
|
||||
Список найденных элементов.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
q = query.lower()
|
||||
results = []
|
||||
for item in cls._data.values():
|
||||
name = getattr(item, "name", "") or getattr(item, "full_name", "")
|
||||
if name and q in name.lower():
|
||||
results.append(item)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def exists(cls, code: str) -> bool:
|
||||
"""
|
||||
Проверить существование кода.
|
||||
|
||||
Args:
|
||||
code: Код элемента справочника.
|
||||
|
||||
Returns:
|
||||
True если код существует.
|
||||
"""
|
||||
return cls.get(code) is not None
|
||||
|
||||
@classmethod
|
||||
def count(cls) -> int:
|
||||
"""
|
||||
Получить количество элементов в справочнике.
|
||||
|
||||
Returns:
|
||||
Количество элементов.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
return len(cls._data)
|
||||
|
||||
@classmethod
|
||||
def reload(cls) -> None:
|
||||
"""Перезагрузить данные из JSON файла."""
|
||||
cls._data = None
|
||||
cls._ensure_loaded()
|
||||
46
src/apps/parsers/clients/checko/datasets/okfs.py
Normal file
46
src/apps/parsers/clients/checko/datasets/okfs.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
ОКФС (ОК 027-99) - Общероссийский классификатор форм собственности.
|
||||
|
||||
Использование:
|
||||
from apps.parsers.clients.checko.datasets import OKFS
|
||||
|
||||
# Получить название по коду
|
||||
name = OKFS.get_name("16") # -> "Частная собственность"
|
||||
|
||||
# Получить полный объект
|
||||
item = OKFS.get("16")
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from apps.parsers.clients.checko.datasets.base import BaseDataset
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OkfsItem:
|
||||
"""Элемент справочника ОКФС."""
|
||||
|
||||
code: str
|
||||
"""Код формы собственности."""
|
||||
|
||||
name: str
|
||||
"""Наименование формы собственности."""
|
||||
|
||||
|
||||
class OKFS(BaseDataset[OkfsItem]):
|
||||
"""
|
||||
Справочник ОКФС (формы собственности).
|
||||
|
||||
Данные: ОК 027-99.
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, OkfsItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "okfs.json"
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> OkfsItem:
|
||||
return OkfsItem(
|
||||
code=raw.get("code", ""),
|
||||
name=raw.get("name", ""),
|
||||
)
|
||||
76
src/apps/parsers/clients/checko/datasets/okopf.py
Normal file
76
src/apps/parsers/clients/checko/datasets/okopf.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
ОКОПФ (ОК 028-2012) - Общероссийский классификатор организационно-правовых форм.
|
||||
|
||||
Использование:
|
||||
from apps.parsers.clients.checko.datasets import OKOPF
|
||||
|
||||
# Получить название по коду
|
||||
name = OKOPF.get_name("12300") # -> "Акционерные общества"
|
||||
|
||||
# Получить полный объект
|
||||
item = OKOPF.get("12300")
|
||||
|
||||
# Получить дочерние элементы
|
||||
children = OKOPF.get_children("12300")
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from apps.parsers.clients.checko.datasets.base import BaseDataset
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OkopfItem:
|
||||
"""Элемент справочника ОКОПФ."""
|
||||
|
||||
code: str
|
||||
"""Код ОПФ."""
|
||||
|
||||
full_name: str
|
||||
"""Полное наименование ОПФ."""
|
||||
|
||||
singular_name: str | None = None
|
||||
"""Наименование в единственном числе."""
|
||||
|
||||
parent_code: str | None = None
|
||||
"""Код родительского элемента."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Алиас для full_name (для совместимости с BaseDataset)."""
|
||||
return self.full_name
|
||||
|
||||
|
||||
class OKOPF(BaseDataset[OkopfItem]):
|
||||
"""
|
||||
Справочник ОКОПФ (организационно-правовые формы).
|
||||
|
||||
Данные: ОК 028-2012.
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, OkopfItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "okopf.json"
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> OkopfItem:
|
||||
return OkopfItem(
|
||||
code=raw.get("code", ""),
|
||||
full_name=raw.get("full_name", ""),
|
||||
singular_name=raw.get("singular_name"),
|
||||
parent_code=raw.get("parent_code") or None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_children(cls, code: str) -> list[OkopfItem]:
|
||||
"""
|
||||
Получить дочерние элементы.
|
||||
|
||||
Args:
|
||||
code: Код родительского элемента.
|
||||
|
||||
Returns:
|
||||
Список дочерних элементов.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
return [i for i in cls._data.values() if i.parent_code == code]
|
||||
116
src/apps/parsers/clients/checko/datasets/okpd.py
Normal file
116
src/apps/parsers/clients/checko/datasets/okpd.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
ОКПД и ОКПД-2 - Общероссийские классификаторы продукции по видам экономической деятельности.
|
||||
|
||||
ОКПД (ОК 005-93) - старый классификатор.
|
||||
ОКПД-2 (ОК 034-2014) - актуальный классификатор.
|
||||
|
||||
Использование:
|
||||
from apps.parsers.clients.checko.datasets import OKPD, OKPD2
|
||||
|
||||
# Получить название по коду
|
||||
name = OKPD2.get_name("62.01.1")
|
||||
|
||||
# Получить полный объект
|
||||
item = OKPD2.get("62.01.1")
|
||||
|
||||
# Поиск по названию
|
||||
items = OKPD2.search("программное")
|
||||
|
||||
# Получить дочерние коды
|
||||
children = OKPD2.get_children("62.01")
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from apps.parsers.clients.checko.datasets.base import BaseDataset
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OkpdItem:
|
||||
"""Элемент справочника ОКПД/ОКПД-2."""
|
||||
|
||||
code: str
|
||||
"""Код продукции."""
|
||||
|
||||
name: str
|
||||
"""Наименование продукции."""
|
||||
|
||||
parent_code: str | None = None
|
||||
"""Код родительского элемента."""
|
||||
|
||||
comment: str | None = None
|
||||
"""Пояснения к коду."""
|
||||
|
||||
|
||||
class OKPD(BaseDataset[OkpdItem]):
|
||||
"""
|
||||
Справочник ОКПД (классификатор продукции, ОК 005-93).
|
||||
|
||||
Устаревший справочник, используйте OKPD2.
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, OkpdItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "okpd.json"
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> OkpdItem:
|
||||
return OkpdItem(
|
||||
code=raw.get("code", ""),
|
||||
name=raw.get("name", ""),
|
||||
parent_code=raw.get("parent_code") or None,
|
||||
comment=raw.get("comment") or None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_children(cls, code: str) -> list[OkpdItem]:
|
||||
"""Получить дочерние элементы."""
|
||||
cls._ensure_loaded()
|
||||
return [i for i in cls._data.values() if i.parent_code == code]
|
||||
|
||||
|
||||
class OKPD2(BaseDataset[OkpdItem]):
|
||||
"""
|
||||
Справочник ОКПД-2 (классификатор продукции, ОК 034-2014).
|
||||
|
||||
Актуальный справочник для госзакупок.
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, OkpdItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "okpd_2.json"
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> OkpdItem:
|
||||
return OkpdItem(
|
||||
code=raw.get("code", ""),
|
||||
name=raw.get("name", ""),
|
||||
parent_code=raw.get("parent_code") or None,
|
||||
comment=raw.get("comment") or None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_children(cls, code: str) -> list[OkpdItem]:
|
||||
"""Получить дочерние элементы."""
|
||||
cls._ensure_loaded()
|
||||
return [i for i in cls._data.values() if i.parent_code == code]
|
||||
|
||||
@classmethod
|
||||
def get_parent(cls, code: str) -> OkpdItem | None:
|
||||
"""Получить родительский элемент."""
|
||||
item = cls.get(code)
|
||||
if item and item.parent_code:
|
||||
return cls.get(item.parent_code)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_hierarchy(cls, code: str) -> list[OkpdItem]:
|
||||
"""Получить иерархию от корня до указанного кода."""
|
||||
result = []
|
||||
current = cls.get(code)
|
||||
while current:
|
||||
result.insert(0, current)
|
||||
if current.parent_code:
|
||||
current = cls.get(current.parent_code)
|
||||
else:
|
||||
break
|
||||
return result
|
||||
132
src/apps/parsers/clients/checko/datasets/okved.py
Normal file
132
src/apps/parsers/clients/checko/datasets/okved.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
ОКВЭД-2 (ОК 029-2014) - Общероссийский классификатор видов экономической деятельности.
|
||||
|
||||
Использование:
|
||||
from apps.parsers.clients.checko.datasets import OKVED2
|
||||
|
||||
# Получить название по коду
|
||||
name = OKVED2.get_name("62.01")
|
||||
|
||||
# Получить полный объект
|
||||
item = OKVED2.get("62.01")
|
||||
|
||||
# Поиск по названию
|
||||
items = OKVED2.search("программное")
|
||||
|
||||
# Получить все коды раздела
|
||||
section_j = OKVED2.get_section("J")
|
||||
|
||||
# Получить дочерние коды
|
||||
children = OKVED2.get_children("62")
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from apps.parsers.clients.checko.datasets.base import BaseDataset
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OkvedItem:
|
||||
"""Элемент справочника ОКВЭД-2."""
|
||||
|
||||
code: str
|
||||
"""Код ОКВЭД (например: '62.01')."""
|
||||
|
||||
name: str
|
||||
"""Наименование вида деятельности."""
|
||||
|
||||
section: str | None = None
|
||||
"""Раздел классификатора (A-U)."""
|
||||
|
||||
parent_code: str | None = None
|
||||
"""Код родительского элемента."""
|
||||
|
||||
comment: str | None = None
|
||||
"""Пояснения к коду."""
|
||||
|
||||
|
||||
class OKVED2(BaseDataset[OkvedItem]):
|
||||
"""
|
||||
Справочник ОКВЭД-2 (виды экономической деятельности).
|
||||
|
||||
Данные: ОК 029-2014 (КДЕС Ред. 2).
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, OkvedItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "okved_2.json"
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> OkvedItem:
|
||||
return OkvedItem(
|
||||
code=raw.get("code", ""),
|
||||
name=raw.get("name", ""),
|
||||
section=raw.get("section"),
|
||||
parent_code=raw.get("parent_code"),
|
||||
comment=raw.get("comment"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_section(cls, section: str) -> list[OkvedItem]:
|
||||
"""
|
||||
Получить все коды раздела.
|
||||
|
||||
Args:
|
||||
section: Код раздела (A-U).
|
||||
|
||||
Returns:
|
||||
Список элементов раздела.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
return [i for i in cls._data.values() if i.section == section.upper()]
|
||||
|
||||
@classmethod
|
||||
def get_children(cls, code: str) -> list[OkvedItem]:
|
||||
"""
|
||||
Получить дочерние коды.
|
||||
|
||||
Args:
|
||||
code: Родительский код ОКВЭД.
|
||||
|
||||
Returns:
|
||||
Список дочерних элементов.
|
||||
"""
|
||||
cls._ensure_loaded()
|
||||
return [i for i in cls._data.values() if i.parent_code == code]
|
||||
|
||||
@classmethod
|
||||
def get_parent(cls, code: str) -> OkvedItem | None:
|
||||
"""
|
||||
Получить родительский элемент.
|
||||
|
||||
Args:
|
||||
code: Код ОКВЭД.
|
||||
|
||||
Returns:
|
||||
Родительский элемент или None.
|
||||
"""
|
||||
item = cls.get(code)
|
||||
if item and item.parent_code:
|
||||
return cls.get(item.parent_code)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_hierarchy(cls, code: str) -> list[OkvedItem]:
|
||||
"""
|
||||
Получить иерархию от корня до указанного кода.
|
||||
|
||||
Args:
|
||||
code: Код ОКВЭД.
|
||||
|
||||
Returns:
|
||||
Список от корневого раздела до указанного кода.
|
||||
"""
|
||||
result = []
|
||||
current = cls.get(code)
|
||||
while current:
|
||||
result.insert(0, current)
|
||||
if current.parent_code:
|
||||
current = cls.get(current.parent_code)
|
||||
else:
|
||||
break
|
||||
return result
|
||||
139
src/apps/parsers/clients/checko/datasets/statuses.py
Normal file
139
src/apps/parsers/clients/checko/datasets/statuses.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
СЮЛСТ и СИПСТ - справочники статусов юридических лиц и индивидуальных предпринимателей.
|
||||
|
||||
Используются для расшифровки кодов статусов из ЕГРЮЛ/ЕГРИП.
|
||||
|
||||
Использование:
|
||||
from apps.parsers.clients.checko.datasets import CompanyStatuses, EntrepreneurStatuses
|
||||
|
||||
# Получить название статуса ЮЛ по коду
|
||||
name = CompanyStatuses.get_name("1") # -> "Действующее"
|
||||
|
||||
# Получить название статуса ИП по коду
|
||||
name = EntrepreneurStatuses.get_name("65")
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from apps.parsers.clients.checko.datasets.base import BaseDataset
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StatusItem:
|
||||
"""Элемент справочника статусов."""
|
||||
|
||||
code: str
|
||||
"""Код статуса."""
|
||||
|
||||
name: str
|
||||
"""Наименование статуса."""
|
||||
|
||||
|
||||
class CompanyStatuses(BaseDataset[StatusItem]):
|
||||
"""
|
||||
Справочник СЮЛСТ - статусы юридических лиц.
|
||||
|
||||
Основные статусы:
|
||||
- 1: Действующее
|
||||
- 3: Ликвидировано
|
||||
- 4: Реорганизовано
|
||||
- 5: Исключено из ЕГРЮЛ
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, StatusItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "statuses_company.json"
|
||||
|
||||
# Встроенные данные (fallback если JSON не найден)
|
||||
_builtin_data: ClassVar[dict[str, str]] = {
|
||||
"1": "Действующее",
|
||||
"2": "В процессе ликвидации",
|
||||
"3": "Ликвидировано",
|
||||
"4": "Реорганизовано",
|
||||
"5": "Исключено из ЕГРЮЛ по решению регистрирующего органа",
|
||||
"6": "В процессе реорганизации в форме присоединения к нему другого юридического лица",
|
||||
"7": "В процессе реорганизации в форме слияния",
|
||||
"8": "В процессе реорганизации в форме разделения",
|
||||
"9": "В процессе реорганизации в форме выделения",
|
||||
"10": "В процессе реорганизации в форме преобразования",
|
||||
"11": "В процессе реорганизации в форме присоединения к другому юридическому лицу",
|
||||
"12": "В процессе банкротства",
|
||||
"20": "Признано несостоятельным (банкротом)",
|
||||
"21": "Находится в стадии ликвидации",
|
||||
"30": "Прекращение деятельности",
|
||||
"40": "Деятельность не осуществляется",
|
||||
"50": "Сведения признаны недостоверными",
|
||||
"60": "На стадии исключения из ЕГРЮЛ",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _ensure_loaded(cls) -> None:
|
||||
"""Загрузить данные или использовать встроенные."""
|
||||
if cls._data is not None:
|
||||
return
|
||||
|
||||
json_path = cls._get_json_path()
|
||||
if json_path.exists():
|
||||
super()._ensure_loaded()
|
||||
else:
|
||||
# Использовать встроенные данные
|
||||
cls._data = {
|
||||
code: StatusItem(code=code, name=name)
|
||||
for code, name in cls._builtin_data.items()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> StatusItem:
|
||||
return StatusItem(
|
||||
code=raw.get("code", ""),
|
||||
name=raw.get("name", ""),
|
||||
)
|
||||
|
||||
|
||||
class EntrepreneurStatuses(BaseDataset[StatusItem]):
|
||||
"""
|
||||
Справочник СИПСТ - статусы индивидуальных предпринимателей.
|
||||
|
||||
Основные статусы:
|
||||
- 1: Действующее
|
||||
- 3: Прекратил деятельность
|
||||
"""
|
||||
|
||||
_data: ClassVar[dict[str, StatusItem] | None] = None
|
||||
_json_filename: ClassVar[str] = "statuses_entrepreneur.json"
|
||||
|
||||
# Встроенные данные (fallback если JSON не найден)
|
||||
_builtin_data: ClassVar[dict[str, str]] = {
|
||||
"1": "Действующее",
|
||||
"2": "В процессе прекращения деятельности",
|
||||
"3": "Прекратил деятельность",
|
||||
"4": "Прекратил деятельность по решению регистрирующего органа",
|
||||
"5": "Прекратил деятельность в связи со смертью",
|
||||
"10": "Признан несостоятельным (банкротом)",
|
||||
"20": "Сведения признаны недостоверными",
|
||||
"60": "На стадии исключения из ЕГРИП",
|
||||
"65": "Прекращение деятельности ИП в связи с признанием несостоятельным",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _ensure_loaded(cls) -> None:
|
||||
"""Загрузить данные или использовать встроенные."""
|
||||
if cls._data is not None:
|
||||
return
|
||||
|
||||
json_path = cls._get_json_path()
|
||||
if json_path.exists():
|
||||
super()._ensure_loaded()
|
||||
else:
|
||||
# Использовать встроенные данные
|
||||
cls._data = {
|
||||
code: StatusItem(code=code, name=name)
|
||||
for code, name in cls._builtin_data.items()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _parse_item(cls, raw: dict) -> StatusItem:
|
||||
return StatusItem(
|
||||
code=raw.get("code", ""),
|
||||
name=raw.get("name", ""),
|
||||
)
|
||||
Reference in New Issue
Block a user