feat: use authoritative organization directory
This commit is contained in:
@@ -58,12 +58,63 @@ class Organization(models.Model):
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
)
|
||||
rn = models.PositiveBigIntegerField(
|
||||
_("регистрационный номер"),
|
||||
null=True,
|
||||
blank=True,
|
||||
unique=True,
|
||||
db_index=True,
|
||||
help_text=_("Регистрационный номер организации из перечня"),
|
||||
)
|
||||
gk_code = models.CharField(
|
||||
_("код государственной корпорации"),
|
||||
max_length=16,
|
||||
blank=True,
|
||||
db_column="_gk",
|
||||
db_index=True,
|
||||
help_text=_("Отношение к ГК Росатом и ГК Роскосмос"),
|
||||
)
|
||||
gk_name = models.CharField(
|
||||
_("государственная корпорация"),
|
||||
max_length=255,
|
||||
blank=True,
|
||||
help_text=_("Расшифровка кода государственной корпорации"),
|
||||
)
|
||||
in_korp_code = models.CharField(
|
||||
_("код положения внутри ГК"),
|
||||
max_length=16,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text=_("Положение внутри ГК Росатом и ГК Роскосмос"),
|
||||
)
|
||||
in_korp_name = models.CharField(
|
||||
_("положение внутри ГК"),
|
||||
max_length=255,
|
||||
blank=True,
|
||||
help_text=_("Расшифровка положения внутри ГК"),
|
||||
)
|
||||
name = models.CharField(
|
||||
_("наименование"),
|
||||
max_length=1024,
|
||||
db_index=True,
|
||||
help_text=_("Наименование организации или ИП"),
|
||||
)
|
||||
full_name = models.TextField(
|
||||
_("полное наименование"),
|
||||
blank=True,
|
||||
)
|
||||
short_name = models.TextField(
|
||||
_("краткое наименование"),
|
||||
blank=True,
|
||||
)
|
||||
pn_name = models.TextField(
|
||||
_("приведенное наименование"),
|
||||
blank=True,
|
||||
)
|
||||
pn_name_en = models.TextField(
|
||||
_("приведенное наименование на английском"),
|
||||
blank=True,
|
||||
)
|
||||
inn = models.CharField(
|
||||
_("ИНН"),
|
||||
max_length=12,
|
||||
@@ -85,6 +136,25 @@ class Organization(models.Model):
|
||||
db_index=True,
|
||||
help_text=_("ОГРН только для юридических лиц"),
|
||||
)
|
||||
okpo = models.CharField(
|
||||
_("ОКПО"),
|
||||
max_length=32,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
filial = models.CharField(
|
||||
_("признак филиала из источника"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
is_branch = models.BooleanField(
|
||||
_("филиал"),
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text=_("Нормализованный признак filial: .T. = филиал, .F. = головная"),
|
||||
)
|
||||
ogrip = models.CharField(
|
||||
_("ОГРИП"),
|
||||
max_length=15,
|
||||
@@ -92,6 +162,184 @@ class Organization(models.Model):
|
||||
db_index=True,
|
||||
help_text=_("ОГРИП только для индивидуальных предпринимателей"),
|
||||
)
|
||||
registration_date = models.DateField(
|
||||
_("дата регистрации"),
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
create_date = models.CharField(
|
||||
_("год или дата создания"),
|
||||
max_length=32,
|
||||
blank=True,
|
||||
)
|
||||
organizational_legal_form = models.CharField(
|
||||
_("код организационно-правовой формы"),
|
||||
max_length=32,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
organizational_legal_form1 = models.CharField(
|
||||
_("организационно-правовая форма"),
|
||||
max_length=512,
|
||||
blank=True,
|
||||
)
|
||||
ownership_form = models.CharField(
|
||||
_("код формы собственности"),
|
||||
max_length=32,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
ownership_form1 = models.CharField(
|
||||
_("форма собственности"),
|
||||
max_length=512,
|
||||
blank=True,
|
||||
)
|
||||
authorized_capital = models.DecimalField(
|
||||
_("уставный капитал"),
|
||||
max_digits=24,
|
||||
decimal_places=2,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
legal_address = models.TextField(
|
||||
_("юридический адрес"),
|
||||
blank=True,
|
||||
)
|
||||
business_act_cod = models.CharField(
|
||||
_("код вида деятельности"),
|
||||
max_length=32,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
business_activity = models.CharField(
|
||||
_("вид деятельности"),
|
||||
max_length=255,
|
||||
blank=True,
|
||||
)
|
||||
general_director = models.TextField(
|
||||
_("руководитель"),
|
||||
blank=True,
|
||||
)
|
||||
general_director_tax_id = models.CharField(
|
||||
_("ИНН руководителя"),
|
||||
max_length=32,
|
||||
blank=True,
|
||||
)
|
||||
uk = models.TextField(
|
||||
_("управляющая компания"),
|
||||
blank=True,
|
||||
)
|
||||
inn_uk = models.CharField(
|
||||
_("ИНН управляющей компании"),
|
||||
max_length=12,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
appointment_date = models.DateField(
|
||||
_("дата назначения руководителя"),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
cf_fl_rn = models.CharField(
|
||||
_("регистрационный номер руководителя"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
)
|
||||
akc_fs = models.CharField(
|
||||
_("акционерный капитал федеральная собственность"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
)
|
||||
akc_sf = models.CharField(
|
||||
_("акционерный капитал собственность субъекта РФ"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
)
|
||||
re_za = models.BooleanField(
|
||||
_("реестр организаций-заказчиков"),
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
re_zasf = models.BooleanField(
|
||||
_("реестр организаций-заказчиков субъекта РФ"),
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
goz_participation = models.BooleanField(
|
||||
_("участие в ГОЗ"),
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
opk_registry_membership = models.BooleanField(
|
||||
_("членство в реестре ОПК"),
|
||||
null=True,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
ropk_num = models.CharField(
|
||||
_("номер в реестре ОПК"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
ropk_razdel_num = models.CharField(
|
||||
_("номер раздела реестра ОПК"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
)
|
||||
ropk_razdel_name = models.TextField(
|
||||
_("раздел реестра ОПК"),
|
||||
blank=True,
|
||||
)
|
||||
min = models.TextField(
|
||||
_("министерство"),
|
||||
blank=True,
|
||||
)
|
||||
dep = models.TextField(
|
||||
_("департамент"),
|
||||
blank=True,
|
||||
)
|
||||
otr = models.TextField(
|
||||
_("отрасль"),
|
||||
blank=True,
|
||||
)
|
||||
integrated_structure = models.TextField(
|
||||
_("интегрированная структура"),
|
||||
blank=True,
|
||||
db_column="is",
|
||||
)
|
||||
state_sector_code = models.CharField(
|
||||
_("код госсектора"),
|
||||
max_length=16,
|
||||
blank=True,
|
||||
db_column="_k",
|
||||
db_index=True,
|
||||
)
|
||||
state_sector_name = models.CharField(
|
||||
_("госсектор"),
|
||||
max_length=255,
|
||||
blank=True,
|
||||
)
|
||||
directory_source_file_hash = models.CharField(
|
||||
_("хеш файла перечня"),
|
||||
max_length=64,
|
||||
blank=True,
|
||||
db_index=True,
|
||||
)
|
||||
directory_source_row_number = models.PositiveIntegerField(
|
||||
_("номер строки файла перечня"),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
directory_imported_at = models.DateTimeField(
|
||||
_("дата импорта перечня"),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
identity_status = models.CharField(
|
||||
_("полнота реквизитов"),
|
||||
max_length=16,
|
||||
@@ -116,20 +364,13 @@ class Organization(models.Model):
|
||||
ordering = ["name"]
|
||||
indexes = [
|
||||
models.Index(fields=["inn", "kpp"]),
|
||||
models.Index(fields=["ogrn", "kpp"]),
|
||||
models.Index(fields=["inn", "ogrn"]),
|
||||
models.Index(fields=["inn", "ogrip"]),
|
||||
models.Index(fields=["okpo"]),
|
||||
models.Index(fields=["is_branch", "inn", "ogrn"]),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["inn", "kpp"],
|
||||
condition=~Q(inn="") & ~Q(kpp=""),
|
||||
name="unique_org_inn_kpp_not_blank",
|
||||
),
|
||||
models.UniqueConstraint(
|
||||
fields=["inn"],
|
||||
condition=~Q(inn="") & Q(kpp="") & Q(ogrip=""),
|
||||
name="unique_org_inn_without_kpp",
|
||||
),
|
||||
models.UniqueConstraint(
|
||||
fields=["ogrip"],
|
||||
condition=~Q(ogrip=""),
|
||||
@@ -147,17 +388,69 @@ class Organization(models.Model):
|
||||
return f"{self.name} ({identifier})"
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Compatibility alias for code paths that expect a model id attribute."""
|
||||
return self.pk
|
||||
|
||||
@staticmethod
|
||||
def _legacy_digits(value, *, max_length: int, zfill: int | None = None) -> str:
|
||||
digits = "".join(char for char in str(value or "") if char.isdigit())
|
||||
if zfill and 0 < len(digits) < zfill:
|
||||
digits = digits.zfill(zfill)
|
||||
return digits[:max_length]
|
||||
|
||||
@property
|
||||
def mn_inn(self) -> str:
|
||||
"""Compatibility alias for the removed registers.Organization field."""
|
||||
return self.inn
|
||||
|
||||
@mn_inn.setter
|
||||
def mn_inn(self, value) -> None:
|
||||
self.inn = self._legacy_digits(value, max_length=12, zfill=10)
|
||||
|
||||
@property
|
||||
def mn_ogrn(self) -> str:
|
||||
"""Compatibility alias for the removed registers.Organization field."""
|
||||
return self.ogrn
|
||||
|
||||
@mn_ogrn.setter
|
||||
def mn_ogrn(self, value) -> None:
|
||||
self.ogrn = self._legacy_digits(value, max_length=13)
|
||||
|
||||
@property
|
||||
def in_kpp(self) -> str:
|
||||
"""Compatibility alias for the removed registers.Organization field."""
|
||||
return self.kpp
|
||||
|
||||
@in_kpp.setter
|
||||
def in_kpp(self, value) -> None:
|
||||
self.kpp = self._legacy_digits(value, max_length=9, zfill=9)
|
||||
|
||||
@property
|
||||
def mn_okpo(self) -> str:
|
||||
"""Compatibility alias for the removed registers.Organization field."""
|
||||
return self.okpo
|
||||
|
||||
@mn_okpo.setter
|
||||
def mn_okpo(self, value) -> None:
|
||||
self.okpo = self._legacy_digits(value, max_length=32)
|
||||
|
||||
@property
|
||||
def normalized_name(self) -> str:
|
||||
return normalize_organization_name(self.name)
|
||||
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
if not self.name:
|
||||
self.name = self.pn_name or self.short_name or self.full_name
|
||||
self.identity_status = self._resolve_identity_status()
|
||||
self.primary_identity = self._resolve_primary_identity()
|
||||
update_fields = kwargs.get("update_fields")
|
||||
if update_fields is not None:
|
||||
kwargs["update_fields"] = list(
|
||||
dict.fromkeys([*update_fields, "identity_status", "primary_identity"])
|
||||
dict.fromkeys(
|
||||
[*update_fields, "name", "identity_status", "primary_identity"]
|
||||
)
|
||||
)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@@ -517,63 +810,12 @@ class OrganizationSourceRecord(models.Model):
|
||||
|
||||
@property
|
||||
def registry_organization(self):
|
||||
"""Best-effort active registry organization matched by canonical identity."""
|
||||
from django.db.models import CharField
|
||||
from django.db.models.functions import Cast
|
||||
from registers.models import Organization as RegistryOrganization
|
||||
from registers.models import RegistryMembershipPeriod
|
||||
|
||||
def _registry_numeric_values(value: str) -> list[str]:
|
||||
stripped = str(value or "").lstrip("0")
|
||||
return [value, stripped] if stripped and stripped != value else [value]
|
||||
|
||||
identity_filter = Q()
|
||||
inn = self.inn
|
||||
ogrn = self.ogrn
|
||||
ogrip = self.ogrip
|
||||
if inn:
|
||||
identity_filter |= Q(registry_inn_text__in=_registry_numeric_values(inn))
|
||||
if ogrn:
|
||||
identity_filter |= Q(registry_ogrn_text__in=_registry_numeric_values(ogrn))
|
||||
if ogrip:
|
||||
identity_filter |= Q(registry_ogrn_text__in=_registry_numeric_values(ogrip))
|
||||
if not identity_filter:
|
||||
return None
|
||||
|
||||
membership = (
|
||||
RegistryMembershipPeriod.objects.filter(ended_at__isnull=True)
|
||||
.select_related("organization")
|
||||
.annotate(
|
||||
registry_inn_text=Cast(
|
||||
"organization__mn_inn",
|
||||
output_field=CharField(),
|
||||
),
|
||||
registry_ogrn_text=Cast(
|
||||
"organization__mn_ogrn",
|
||||
output_field=CharField(),
|
||||
),
|
||||
)
|
||||
.filter(identity_filter)
|
||||
.order_by("organization__pn_name", "organization_id")
|
||||
.first()
|
||||
)
|
||||
if membership is not None:
|
||||
return membership.organization
|
||||
|
||||
return (
|
||||
RegistryOrganization.objects.annotate(
|
||||
registry_inn_text=Cast("mn_inn", output_field=CharField()),
|
||||
registry_ogrn_text=Cast("mn_ogrn", output_field=CharField()),
|
||||
)
|
||||
.filter(identity_filter)
|
||||
.order_by("pn_name", "id")
|
||||
.first()
|
||||
)
|
||||
"""Compatibility alias for callers that still use registry wording."""
|
||||
return self.extension.organization
|
||||
|
||||
@property
|
||||
def registry_organization_id(self):
|
||||
registry_organization = self.registry_organization
|
||||
return registry_organization.id if registry_organization is not None else None
|
||||
return self.extension.organization_id
|
||||
|
||||
|
||||
class OrganizationSourceFinancialLine(models.Model):
|
||||
|
||||
Reference in New Issue
Block a user