fix
This commit is contained in:
parent
d6b3e73cb7
commit
9968d84e3b
@ -14,4 +14,3 @@ repos:
|
|||||||
rev: v0.12.7
|
rev: v0.12.7
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
- id: ruff-format
|
|
||||||
|
|||||||
@ -102,6 +102,13 @@ class StructuredGroupRule:
|
|||||||
|
|
||||||
|
|
||||||
_INVALID_TAGS = {"", "-", "Источник", "путь", "подразумеваются", "тэга", "нашла"}
|
_INVALID_TAGS = {"", "-", "Источник", "путь", "подразумеваются", "тэга", "нашла"}
|
||||||
|
_INDEXED_PATH_SEGMENT = re.compile(r"\[\d+\]")
|
||||||
|
_EIO_BLOCK_NAMES = (
|
||||||
|
"СведЕИО",
|
||||||
|
"БенефициарЮЛ",
|
||||||
|
"БенефициарФЛИП",
|
||||||
|
"БенефициарИНБОЮЛ",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _bank_bik_candidate_keys(bank_block: str) -> tuple[str, ...]:
|
def _bank_bik_candidate_keys(bank_block: str) -> tuple[str, ...]:
|
||||||
@ -307,6 +314,7 @@ def build_fixed_row_by_index(
|
|||||||
operation_index: int,
|
operation_index: int,
|
||||||
operation_fields: dict[str, str],
|
operation_fields: dict[str, str],
|
||||||
participant_fields: dict[str, str],
|
participant_fields: dict[str, str],
|
||||||
|
is_continuation: bool = False,
|
||||||
) -> dict[int, str]:
|
) -> dict[int, str]:
|
||||||
row: dict[int, str] = {
|
row: dict[int, str] = {
|
||||||
COL_FILE_NAME: file_name,
|
COL_FILE_NAME: file_name,
|
||||||
@ -315,14 +323,17 @@ def build_fixed_row_by_index(
|
|||||||
merged = _merge_fields(operation_fields, participant_fields)
|
merged = _merge_fields(operation_fields, participant_fields)
|
||||||
merged_suffix_index = _build_suffix_index(merged)
|
merged_suffix_index = _build_suffix_index(merged)
|
||||||
operation_suffix_index = _build_suffix_index(operation_fields)
|
operation_suffix_index = _build_suffix_index(operation_fields)
|
||||||
participant_suffix_index = _build_suffix_index(participant_fields)
|
|
||||||
has_eio_block = _has_any_prefixed_key(
|
has_eio_block = _has_any_prefixed_key(
|
||||||
participant_fields,
|
participant_fields,
|
||||||
(
|
(
|
||||||
"УчастникЮЛ.СведЕИО.",
|
"УчастникЮЛ.СведЕИО.",
|
||||||
"УчастникЮЛ.БенефициарЮЛ.",
|
"УчастникЮЛ.БенефициарЮЛ.",
|
||||||
|
"УчастникФЛИП.БенефициарФЛИП.",
|
||||||
|
"УчастникИНБОЮЛ.БенефициарИНБОЮЛ.",
|
||||||
"СведЕИО.",
|
"СведЕИО.",
|
||||||
"БенефициарЮЛ.",
|
"БенефициарЮЛ.",
|
||||||
|
"БенефициарФЛИП.",
|
||||||
|
"БенефициарИНБОЮЛ.",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
has_cp_block = _has_any_prefixed_key(
|
has_cp_block = _has_any_prefixed_key(
|
||||||
@ -339,8 +350,11 @@ def build_fixed_row_by_index(
|
|||||||
row[rule.column_index] = ""
|
row[rule.column_index] = ""
|
||||||
continue
|
continue
|
||||||
if rule.source == "participant":
|
if rule.source == "participant":
|
||||||
source_payload = participant_fields
|
source_payload = _scope_participant_payload(
|
||||||
suffix_index = participant_suffix_index
|
participant_fields,
|
||||||
|
eio_only=rule.column_index in BLOCK_EIO,
|
||||||
|
)
|
||||||
|
suffix_index = _build_suffix_index(source_payload)
|
||||||
elif rule.source == "operation":
|
elif rule.source == "operation":
|
||||||
source_payload = operation_fields
|
source_payload = operation_fields
|
||||||
suffix_index = operation_suffix_index
|
suffix_index = operation_suffix_index
|
||||||
@ -362,13 +376,17 @@ def build_fixed_row_by_index(
|
|||||||
rule.candidate_keys,
|
rule.candidate_keys,
|
||||||
allow_short_lookup=rule.allow_short_lookup,
|
allow_short_lookup=rule.allow_short_lookup,
|
||||||
)
|
)
|
||||||
_apply_participant_identity_rules(row, participant_fields)
|
if not is_continuation:
|
||||||
|
_apply_participant_identity_rules(row, participant_fields)
|
||||||
_apply_structured_group_rules(row)
|
_apply_structured_group_rules(row)
|
||||||
_apply_business_rules(
|
if not is_continuation:
|
||||||
row,
|
_apply_business_rules(
|
||||||
has_eio_block=has_eio_block,
|
row,
|
||||||
has_cp_block=has_cp_block,
|
has_eio_block=has_eio_block,
|
||||||
)
|
has_cp_block=has_cp_block,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_apply_continuation_validation_rules(row)
|
||||||
for meta in COLUMNS.ordered_columns():
|
for meta in COLUMNS.ordered_columns():
|
||||||
row.setdefault(meta.index, "")
|
row.setdefault(meta.index, "")
|
||||||
return row
|
return row
|
||||||
@ -381,6 +399,7 @@ def build_fixed_row(
|
|||||||
operation_index: int,
|
operation_index: int,
|
||||||
operation_fields: dict[str, str],
|
operation_fields: dict[str, str],
|
||||||
participant_fields: dict[str, str],
|
participant_fields: dict[str, str],
|
||||||
|
is_continuation: bool = False,
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
by_index = build_fixed_row_by_index(
|
by_index = build_fixed_row_by_index(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
@ -388,6 +407,7 @@ def build_fixed_row(
|
|||||||
operation_index=operation_index,
|
operation_index=operation_index,
|
||||||
operation_fields=operation_fields,
|
operation_fields=operation_fields,
|
||||||
participant_fields=participant_fields,
|
participant_fields=participant_fields,
|
||||||
|
is_continuation=is_continuation,
|
||||||
)
|
)
|
||||||
by_name: dict[str, str] = {}
|
by_name: dict[str, str] = {}
|
||||||
for meta in COLUMNS.ordered_columns():
|
for meta in COLUMNS.ordered_columns():
|
||||||
@ -412,7 +432,6 @@ def _apply_structured_group_rules(row: dict[int, str]) -> None:
|
|||||||
if _get(row, component_column_index).strip()
|
if _get(row, component_column_index).strip()
|
||||||
]
|
]
|
||||||
if not values:
|
if not values:
|
||||||
_set(row, rule.structured_column, "")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
separator = " " if rule.join_with_space else ", "
|
separator = " " if rule.join_with_space else ", "
|
||||||
@ -432,13 +451,18 @@ def _merge_fields(
|
|||||||
def _has_payload_block(payload: dict[str, str], block_name: str) -> bool:
|
def _has_payload_block(payload: dict[str, str], block_name: str) -> bool:
|
||||||
prefix = f"{block_name}."
|
prefix = f"{block_name}."
|
||||||
nested_marker = f".{block_name}."
|
nested_marker = f".{block_name}."
|
||||||
return any(key.startswith(prefix) or nested_marker in key for key in payload)
|
return any(
|
||||||
|
(normalized := _normalize_indexed_path(key)).startswith(prefix)
|
||||||
|
or nested_marker in normalized
|
||||||
|
for key in payload
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _pick_scoped_payload_value(payload: dict[str, str], scoped_path: str) -> str:
|
def _pick_scoped_payload_value(payload: dict[str, str], scoped_path: str) -> str:
|
||||||
nested_suffix = f".{scoped_path}"
|
nested_suffix = f".{scoped_path}"
|
||||||
for key, value in payload.items():
|
for key, value in payload.items():
|
||||||
if value and (key == scoped_path or key.endswith(nested_suffix)):
|
normalized = _normalize_indexed_path(key)
|
||||||
|
if value and (normalized == scoped_path or normalized.endswith(nested_suffix)):
|
||||||
return str(value).strip()
|
return str(value).strip()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -478,7 +502,33 @@ def _apply_participant_identity_rules(
|
|||||||
|
|
||||||
|
|
||||||
def _has_any_prefixed_key(payload: dict[str, str], prefixes: tuple[str, ...]) -> bool:
|
def _has_any_prefixed_key(payload: dict[str, str], prefixes: tuple[str, ...]) -> bool:
|
||||||
return any(any(key.startswith(prefix) for prefix in prefixes) for key in payload)
|
return any(
|
||||||
|
any(_normalize_indexed_path(key).startswith(prefix) for prefix in prefixes)
|
||||||
|
for key in payload
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_indexed_path(path: str) -> str:
|
||||||
|
"""Убирает индексы повторов, сохраняя логическую структуру XML-пути."""
|
||||||
|
return _INDEXED_PATH_SEGMENT.sub("", path)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_eio_path(path: str) -> bool:
|
||||||
|
normalized = _normalize_indexed_path(path)
|
||||||
|
return any(
|
||||||
|
f".{block_name}." in f".{normalized}." for block_name in _EIO_BLOCK_NAMES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _scope_participant_payload(
|
||||||
|
payload: dict[str, str],
|
||||||
|
*,
|
||||||
|
eio_only: bool,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Изолирует поля участника от вложенного блока ЕИО/бенефициара."""
|
||||||
|
return {
|
||||||
|
key: value for key, value in payload.items() if _is_eio_path(key) is eio_only
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _build_suffix_index(payload: dict[str, str]) -> dict[str, str]:
|
def _build_suffix_index(payload: dict[str, str]) -> dict[str, str]:
|
||||||
@ -501,6 +551,14 @@ def _pick_value(
|
|||||||
for key in candidates:
|
for key in candidates:
|
||||||
if value := payload.get(key):
|
if value := payload.get(key):
|
||||||
return value
|
return value
|
||||||
|
normalized_key = _normalize_indexed_path(key)
|
||||||
|
for payload_key, value in payload.items():
|
||||||
|
normalized_payload_key = _normalize_indexed_path(payload_key)
|
||||||
|
if value and (
|
||||||
|
normalized_payload_key == normalized_key
|
||||||
|
or normalized_payload_key.endswith(f".{normalized_key}")
|
||||||
|
):
|
||||||
|
return value
|
||||||
if not allow_short_lookup:
|
if not allow_short_lookup:
|
||||||
continue
|
continue
|
||||||
short = key.rsplit(".", maxsplit=1)[-1]
|
short = key.rsplit(".", maxsplit=1)[-1]
|
||||||
@ -613,6 +671,13 @@ def _apply_business_rules(
|
|||||||
_clear_columns(row, BLOCK_CP)
|
_clear_columns(row, BLOCK_CP)
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_continuation_validation_rules(row: dict[int, str]) -> None:
|
||||||
|
"""Проверяет локальные коды повтора без подстановки значений основной строки."""
|
||||||
|
_rule_ftr_sign(row)
|
||||||
|
_rule_suspension_basis(row)
|
||||||
|
_rule_esp_sign(row)
|
||||||
|
|
||||||
|
|
||||||
def _rule_ftr_sign(row: dict[int, str]) -> None:
|
def _rule_ftr_sign(row: dict[int, str]) -> None:
|
||||||
"""Правило 10: оставляем только допустимые значения признака ФТР."""
|
"""Правило 10: оставляем только допустимые значения признака ФТР."""
|
||||||
if _get(row, COL_FTR_SIGN) not in VALID_FTR_SIGNS:
|
if _get(row, COL_FTR_SIGN) not in VALID_FTR_SIGNS:
|
||||||
|
|||||||
@ -3126,7 +3126,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "НаимЮЛ/ФИОФЛИП/НаимУчредитель",
|
"xml_tag": "НаимЮЛ/ФИОФЛИП/НаимУчредитель",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/НаимЮЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ФИОФЛИП/Фам | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ФИОФЛИП/Имя | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ФИОФЛИП/Отч | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/НаимУчредитель/ЮрЛицо | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/НаимУчредитель/ФизЛицо/Фам | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/НаимУчредитель/ФизЛицо/Имя | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/НаимУчредитель/ФизЛицо/Отч",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/НаимЮЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ФИОФЛИП/Фам | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ФИОФЛИП/Имя | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ФИОФЛИП/Отч | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ФИОФЛИП/Фам | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ФИОФЛИП/Имя | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ФИОФЛИП/Отч",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": true,
|
"allow_short_lookup": true,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3146,7 +3146,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "ФИОФЛИП",
|
"xml_tag": "ФИОФЛИП",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ФИОФЛИП/ФИОСтрока",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ФИОФЛИП/ФИОСтрока | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ФИОФЛИП/ФИОСтрока",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3166,7 +3166,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Фам",
|
"xml_tag": "Фам",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ФИОФЛИП/Фам",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ФИОФЛИП/Фам | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ФИОФЛИП/Фам",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3186,7 +3186,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Имя",
|
"xml_tag": "Имя",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ФИОФЛИП/Имя",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ФИОФЛИП/Имя | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ФИОФЛИП/Имя",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3206,7 +3206,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Отч",
|
"xml_tag": "Отч",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ФИОФЛИП/Отч",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ФИОФЛИП/Отч | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ФИОФЛИП/Отч",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3226,14 +3226,14 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "ИННЮЛ/ИННФЛИП",
|
"xml_tag": "ИННЮЛ/ИННФЛИП",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/ИННЮЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ИННФЛИП",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/ИННЮЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ИННФЛИП | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ИННФЛИП",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
"structured_value": false,
|
"structured_value": false,
|
||||||
"structured_group": "legacy_157",
|
"structured_group": "",
|
||||||
"structured_role": "component",
|
"structured_role": "",
|
||||||
"structured_order": 4,
|
"structured_order": 0,
|
||||||
"join_with_space": false,
|
"join_with_space": false,
|
||||||
"multiplies": "нет",
|
"multiplies": "нет",
|
||||||
"required": "обязательно",
|
"required": "обязательно",
|
||||||
@ -3246,7 +3246,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "ОГРНЮЛ|ОГРНИП|ТипФЛЧастнаяПрактика",
|
"xml_tag": "ОГРНЮЛ|ОГРНИП|ТипФЛЧастнаяПрактика",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/ОГРНЮЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ОГРНИП | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ТипФЛЧастнаяПрактика",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/ОГРНЮЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ОГРНИП | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ОГРНИП | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ТипФЛЧастнаяПрактика | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ТипФЛЧастнаяПрактика",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3285,10 +3285,10 @@
|
|||||||
"column_name": "Код страны гражданства ЕИО/Бенефициара",
|
"column_name": "Код страны гражданства ЕИО/Бенефициара",
|
||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "",
|
"xml_tag": "КодОКСМ",
|
||||||
"xml_path": "",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/КодОКСМ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/КодОКСМ",
|
||||||
"source_scope": "any",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": true,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
"structured_value": false,
|
"structured_value": false,
|
||||||
"structured_group": "",
|
"structured_group": "",
|
||||||
@ -3306,7 +3306,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "ПризнакПубЛицо",
|
"xml_tag": "ПризнакПубЛицо",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникФЛИП/СведФЛИП/ПризнакПубЛицо",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ПризнакПубЛицо | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ПризнакПубЛицо",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3326,7 +3326,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "АдресСтрока|АдрУчредитель",
|
"xml_tag": "АдресСтрока|АдрУчредитель",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/АдресСтрока | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/АдресСтрока",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/АдресСтрока | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/АдресСтрока | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/АдресСтрока",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": true,
|
"allow_short_lookup": true,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3346,7 +3346,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Индекс|КодОКСМ|КодСубъектаПоОКАТО|Район|Пункт|Улица|Дом|Корп|Оф|АдрУчредитель",
|
"xml_tag": "Индекс|КодОКСМ|КодСубъектаПоОКАТО|Район|Пункт|Улица|Дом|Корп|Оф|АдрУчредитель",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": true,
|
"allow_short_lookup": true,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3366,7 +3366,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Индекс",
|
"xml_tag": "Индекс",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/Индекс | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/Индекс",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/Индекс | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/Индекс | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/Индекс",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3386,7 +3386,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "КодОКСМ",
|
"xml_tag": "КодОКСМ",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/КодОКСМ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/КодОКСМ",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/КодОКСМ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/КодОКСМ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/КодОКСМ",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3406,7 +3406,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "КодСубъектаПоОКАТО",
|
"xml_tag": "КодСубъектаПоОКАТО",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/КодСубъектаПоОКАТО | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/КодСубъектаПоОКАТО",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/КодСубъектаПоОКАТО | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/КодСубъектаПоОКАТО | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/КодСубъектаПоОКАТО",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3426,7 +3426,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Район",
|
"xml_tag": "Район",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/Район | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/Район",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/Район | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/Район | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/Район",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3446,7 +3446,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Пункт",
|
"xml_tag": "Пункт",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/Пункт | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/Пункт",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/Пункт | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/Пункт | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/Пункт",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3466,7 +3466,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Улица",
|
"xml_tag": "Улица",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/Улица | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/Улица",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/Улица | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/Улица | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/Улица",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3486,7 +3486,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Дом",
|
"xml_tag": "Дом",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/Дом | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/Дом",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/Дом | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/Дом | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/Дом",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3506,7 +3506,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Корп",
|
"xml_tag": "Корп",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/Корп | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/Корп",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/Корп | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/Корп | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/Корп",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -3526,7 +3526,7 @@
|
|||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "Оф",
|
"xml_tag": "Оф",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЮЛ/АдрРегЮЛ/Оф | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникИНБОЮЛ/СведИНБОЮЛ/Учредитель/АдрУчредитель/Оф",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ЮЛЕИО/АдрЮЛ/Оф | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/АдрРег/Оф | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/АдрРег/Оф",
|
||||||
"source_scope": "participant",
|
"source_scope": "participant",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
|
|||||||
@ -1,11 +1,21 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
from collections import Counter
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from .config import validate_file_name as validate_pipeline_file_name
|
from .config import validate_file_name as validate_pipeline_file_name
|
||||||
|
|
||||||
|
_INDEXED_SEGMENT = re.compile(r"(?P<segment>[^.]+\[(?P<index>\d+)\])")
|
||||||
|
_EIO_SEGMENT = re.compile(
|
||||||
|
r"(?P<segment>"
|
||||||
|
r"(?:СведЕИО|БенефициарЮЛ|БенефициарФЛИП|БенефициарИНБОЮЛ)"
|
||||||
|
r"(?:\[(?P<index>\d+)\])?"
|
||||||
|
r")"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ParticipantRow:
|
class ParticipantRow:
|
||||||
@ -14,6 +24,7 @@ class ParticipantRow:
|
|||||||
record_id: str
|
record_id: str
|
||||||
operation_fields: dict[str, str]
|
operation_fields: dict[str, str]
|
||||||
participant_fields: dict[str, str]
|
participant_fields: dict[str, str]
|
||||||
|
is_continuation: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -69,28 +80,30 @@ def parse_xml_content(file_name: str, xml_content: bytes | str) -> FileParseResu
|
|||||||
)
|
)
|
||||||
operation_fields = {**common_fields, **operation_fields}
|
operation_fields = {**common_fields, **operation_fields}
|
||||||
record_id = _extract_record_id(operation_fields, index)
|
record_id = _extract_record_id(operation_fields, index)
|
||||||
|
operation_rows = _expand_indexed_fields(operation_fields)
|
||||||
participants = _find_children_by_name(operation, "УчастникОп")
|
participants = _find_children_by_name(operation, "УчастникОп")
|
||||||
|
|
||||||
if participants:
|
if participants:
|
||||||
for participant in participants:
|
for participant in participants:
|
||||||
participant_fields = _extract_direct_fields(participant)
|
participant_fields = _extract_direct_fields(participant)
|
||||||
result.rows.append(
|
participant_rows = _expand_indexed_fields(participant_fields)
|
||||||
ParticipantRow(
|
result.rows.extend(
|
||||||
|
_build_expanded_rows(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
operation_index=index,
|
operation_index=index,
|
||||||
record_id=record_id,
|
record_id=record_id,
|
||||||
operation_fields=operation_fields,
|
operation_rows=operation_rows,
|
||||||
participant_fields=participant_fields,
|
participant_rows=participant_rows,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result.rows.append(
|
result.rows.extend(
|
||||||
ParticipantRow(
|
_build_expanded_rows(
|
||||||
file_name=file_name,
|
file_name=file_name,
|
||||||
operation_index=index,
|
operation_index=index,
|
||||||
record_id=record_id,
|
record_id=record_id,
|
||||||
operation_fields=operation_fields,
|
operation_rows=operation_rows,
|
||||||
participant_fields={},
|
participant_rows=[{}],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@ -126,16 +139,117 @@ def _extract_direct_fields(
|
|||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
excluded = set(excluded_tags or ())
|
excluded = set(excluded_tags or ())
|
||||||
fields: dict[str, str] = {}
|
fields: dict[str, str] = {}
|
||||||
|
children = [child for child in element if _normalize_tag(child.tag) not in excluded]
|
||||||
|
counts = Counter(_normalize_tag(child.tag) for child in children)
|
||||||
|
positions: Counter[str] = Counter()
|
||||||
|
|
||||||
for child in element:
|
for child in children:
|
||||||
tag = _normalize_tag(child.tag)
|
tag = _normalize_tag(child.tag)
|
||||||
if tag in excluded:
|
position = positions[tag]
|
||||||
continue
|
positions[tag] += 1
|
||||||
_collect_leaf_fields(child, tag, fields)
|
path = f"{tag}[{position}]" if counts[tag] > 1 else tag
|
||||||
|
_collect_leaf_fields(child, path, fields)
|
||||||
|
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
def _build_expanded_rows(
|
||||||
|
*,
|
||||||
|
file_name: str,
|
||||||
|
operation_index: int,
|
||||||
|
record_id: str,
|
||||||
|
operation_rows: list[dict[str, str]],
|
||||||
|
participant_rows: list[dict[str, str]],
|
||||||
|
) -> list[ParticipantRow]:
|
||||||
|
"""Строит основную и независимые continuation-строки без декартова умножения."""
|
||||||
|
rows = [
|
||||||
|
ParticipantRow(
|
||||||
|
file_name=file_name,
|
||||||
|
operation_index=operation_index,
|
||||||
|
record_id=record_id,
|
||||||
|
operation_fields=operation_rows[0],
|
||||||
|
participant_fields=participant_rows[0],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
rows.extend(
|
||||||
|
ParticipantRow(
|
||||||
|
file_name=file_name,
|
||||||
|
operation_index=operation_index,
|
||||||
|
record_id=record_id,
|
||||||
|
operation_fields=operation_fields,
|
||||||
|
participant_fields={},
|
||||||
|
is_continuation=True,
|
||||||
|
)
|
||||||
|
for operation_fields in operation_rows[1:]
|
||||||
|
)
|
||||||
|
rows.extend(
|
||||||
|
ParticipantRow(
|
||||||
|
file_name=file_name,
|
||||||
|
operation_index=operation_index,
|
||||||
|
record_id=record_id,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields=participant_fields,
|
||||||
|
is_continuation=True,
|
||||||
|
)
|
||||||
|
for participant_fields in participant_rows[1:]
|
||||||
|
)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_indexed_fields(
|
||||||
|
fields: dict[str, str],
|
||||||
|
*,
|
||||||
|
group_eio: bool = True,
|
||||||
|
) -> list[dict[str, str]]:
|
||||||
|
"""Разворачивает индексированные sibling-блоки в аддитивные наборы полей."""
|
||||||
|
base_fields: dict[str, str] = {}
|
||||||
|
families: dict[str, dict[int, dict[str, str]]] = {}
|
||||||
|
eio_occurrences: dict[str, int] = {}
|
||||||
|
|
||||||
|
for key, value in fields.items():
|
||||||
|
eio_match = _EIO_SEGMENT.search(key) if group_eio else None
|
||||||
|
match = eio_match or _INDEXED_SEGMENT.search(key)
|
||||||
|
if match is None:
|
||||||
|
base_fields[key] = value
|
||||||
|
continue
|
||||||
|
|
||||||
|
indexed_segment = match.group("segment")
|
||||||
|
family_segment = re.sub(r"\[\d+\]$", "", indexed_segment)
|
||||||
|
if eio_match is not None:
|
||||||
|
occurrence_key = f"{key[: match.start()]}{indexed_segment}"
|
||||||
|
occurrence = eio_occurrences.setdefault(
|
||||||
|
occurrence_key,
|
||||||
|
len(eio_occurrences),
|
||||||
|
)
|
||||||
|
family = "__eio_or_beneficiary__"
|
||||||
|
else:
|
||||||
|
family = f"{key[: match.start()]}{family_segment}"
|
||||||
|
occurrence = int(match.group("index"))
|
||||||
|
deindexed_key = f"{key[: match.start()]}{family_segment}{key[match.end() :]}"
|
||||||
|
families.setdefault(family, {}).setdefault(occurrence, {})[
|
||||||
|
deindexed_key
|
||||||
|
] = value
|
||||||
|
|
||||||
|
if not families:
|
||||||
|
return [base_fields]
|
||||||
|
|
||||||
|
primary = dict(base_fields)
|
||||||
|
continuations: list[dict[str, str]] = []
|
||||||
|
for occurrences in families.values():
|
||||||
|
for position, occurrence in enumerate(sorted(occurrences)):
|
||||||
|
expanded_occurrence = _expand_indexed_fields(
|
||||||
|
occurrences[occurrence],
|
||||||
|
group_eio=False,
|
||||||
|
)
|
||||||
|
if position == 0:
|
||||||
|
primary.update(expanded_occurrence[0])
|
||||||
|
continuations.extend(expanded_occurrence[1:])
|
||||||
|
else:
|
||||||
|
continuations.extend(expanded_occurrence)
|
||||||
|
|
||||||
|
return [primary, *continuations]
|
||||||
|
|
||||||
|
|
||||||
def _extract_record_id(operation_fields: dict[str, str], operation_index: int) -> str:
|
def _extract_record_id(operation_fields: dict[str, str], operation_index: int) -> str:
|
||||||
possible_keys = (
|
possible_keys = (
|
||||||
"ИдентификаторЗаписи",
|
"ИдентификаторЗаписи",
|
||||||
@ -206,9 +320,16 @@ def _collect_leaf_fields(
|
|||||||
_append_value(fields, current_path, (element.text or "").strip())
|
_append_value(fields, current_path, (element.text or "").strip())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
counts = Counter(_normalize_tag(child.tag) for child in children)
|
||||||
|
positions: Counter[str] = Counter()
|
||||||
for child in children:
|
for child in children:
|
||||||
child_name = _normalize_tag(child.tag)
|
child_name = _normalize_tag(child.tag)
|
||||||
_collect_leaf_fields(child, f"{current_path}.{child_name}", fields)
|
position = positions[child_name]
|
||||||
|
positions[child_name] += 1
|
||||||
|
child_path = (
|
||||||
|
f"{child_name}[{position}]" if counts[child_name] > 1 else child_name
|
||||||
|
)
|
||||||
|
_collect_leaf_fields(child, f"{current_path}.{child_path}", fields)
|
||||||
|
|
||||||
|
|
||||||
def _collect_attributes(
|
def _collect_attributes(
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class ReportRow:
|
|||||||
operation_index: int
|
operation_index: int
|
||||||
operation_fields: dict[str, str]
|
operation_fields: dict[str, str]
|
||||||
participant_fields: dict[str, str]
|
participant_fields: dict[str, str]
|
||||||
|
is_continuation: bool = False
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -93,6 +94,7 @@ class StreamingReportWriter:
|
|||||||
operation_index=row.operation_index,
|
operation_index=row.operation_index,
|
||||||
operation_fields=row.operation_fields,
|
operation_fields=row.operation_fields,
|
||||||
participant_fields=row.participant_fields,
|
participant_fields=row.participant_fields,
|
||||||
|
is_continuation=row.is_continuation,
|
||||||
)
|
)
|
||||||
for output_index, column_name in enumerate(self.columns, start=1):
|
for output_index, column_name in enumerate(self.columns, start=1):
|
||||||
zero_based_index = output_index - 1
|
zero_based_index = output_index - 1
|
||||||
|
|||||||
@ -329,6 +329,7 @@ def _process_files(
|
|||||||
operation_index=row.operation_index,
|
operation_index=row.operation_index,
|
||||||
operation_fields=row.operation_fields,
|
operation_fields=row.operation_fields,
|
||||||
participant_fields=row.participant_fields,
|
participant_fields=row.participant_fields,
|
||||||
|
is_continuation=row.is_continuation,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
stage_timings["report_append_seconds"] += (
|
stage_timings["report_append_seconds"] += (
|
||||||
|
|||||||
660
docs/xsd/SKO115FZ_OPER_v2.xsd
Normal file
660
docs/xsd/SKO115FZ_OPER_v2.xsd
Normal file
@ -0,0 +1,660 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<xs:include schemaLocation="data_types_v2.xsd"/>
|
||||||
|
|
||||||
|
<xs:element name="СообщОперКО">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сообщение об операциях, обязательному контролю, о подозрительных операциях, а также об операциях,
|
||||||
|
приостановленных в соответствии с пунктом 10 статьи 7 и (или) пунктом 8 статьи 7.5 Федерального закона № 115-ФЗ
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="СлужЧасть" type="СлужЧастьТип">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Служебная часть электронного сообщения</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ИнформЧасть">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Информационная часть электронного сообщения</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="ИнфБанк" type="Банк">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Информация о кредитной организации (филиале кредитной организации), передающей (передающем) сведения
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведКО" maxOccurs="unbounded">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="ПризнакПредставСвед">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Признак представления сведений в уполномоченный орган филиалом кредитной организации
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="0|1"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ИнфФилиал" type="Филиал" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Информация о филиале кредитной организации, представляющем сведения
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="Операция" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения об операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="НомерЗаписи" type="ИдентификаторЗаписи">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер записи в ФЭС</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ТипЗаписи">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Тип записи</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[1-4]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НомерОперация" type="Т100" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Уникальный номер операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнФТр" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Признак операции, связанной с финансированием терроризма
|
||||||
|
или распространения оружия массового уничтожения
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[0-5]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ОснПриостановление" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Основания приостановления операции</xs:documentation>
|
||||||
|
<xs:documentation>!!! Показатель введен, начиная с версии 2.3 формата ФЭС !!!</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[1-3]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ДатаОперации" type="ДатаТип">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Дата совершения (приостановления) операции </xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ДатаВыявления" type="ДатаТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Дата выявления (приостановления) операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="КодПризнОперации">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код признака операции (сделки)</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[1-3]|[5-9]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнакОперацияЭСП" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Признак совершения операции с использованием ЭСП</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[1-4]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НаимПлатежнаяСистема1" type="Т100" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Наименование платежной системы на стороне лица, совершающего операцию</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НаимПлатежнаяСистема2" type="Т100" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Наименование платежной системы на стороне получателя по операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ВремяТранзакцияЭСП" type="ВЧПТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Время совершения операции с использованием ЭСП</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="КодОперации">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код вида операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[0-9]{4}"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ДопКодОперации" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Дополнительный код вида операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[0-9]{4}"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнНеобОперации" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код признака необычной операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[0-9]{4}"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнВалОперации" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Признак валютной операции.!!! Показатель введен, начиная с версии 2.0 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="VO"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ИдентификаторПД" type="ИдентификаторЗаписи" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Идентификатор подозрительной деятельности.!!! Показатель введен, начиная с версии 2.0 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведЦП" type="ЦифровыеПрава" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о цифровых правах</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="КодВал" type="КодВалТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код валюты</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СумОперации" type="СуммаТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сумма операции в валюте ее проведения</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СумРуб" type="СуммаТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сумма операции в рублевом эквиваленте</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="КодВалКонверсия" type="КодВалТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код продаваемой валюты</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СумКонверсия" type="СуммаТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сумма продаваемой валюты</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведДрагМеталл" type="ДрагМеталл" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о драгоценных металлах, драгоценных камнях, ювелирных изделиях и ломе из таких изделий
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ОснованиеОп" type="Документ" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Основание совершения операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НазначениеПлатежа" type="Т2000П">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Назначение платежа</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ХарактерОп" type="Т2000П">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Характеристика операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="УчастникОп" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения об участнике операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="СтатусУчастника">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код статуса участника операции (сделки)</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[1-4]|6|8"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ТипУчастника">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Тип участника операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[0-5]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнУчастника">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Признак резидента (нерезидента) участника операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="0|1|9"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="IDППЦР" type="IDППЦРТип" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Уникальный идентификатор пользователя платформы цифрового рубля
|
||||||
|
!!! Показатель введен, начиная с версии 2.2 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнКлиент">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Признак участника операции (сделки)</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[0-2]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="КодЮЛФЛ" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Идентификатор участника операции, включенного в Перечень, Решение, и (или) Перечень ФРОМУ
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[0-9]{1,10}"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="УчастникЮЛ" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о юридическом лице (филиале юридического лица, представительстве) и его единоличном исполнительном органе
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="СведЮЛ" type="СведенияЮЛ">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о юридическом лице (филиале (представительстве) юридического лица)
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнакИдентификацияЮЛ" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Признак идентификации бенефициарного владельца.
|
||||||
|
!!! Показатель введен, начиная с версии 2.0 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="0|1"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="БенефициарЮЛ" type="СведенияБенефициар" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о бенефициарном владельце. !!! Показатель введен, начиная с версии 2.0 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведЕИО" type="СведенияЕИО" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о единоличном исполнительном органе юридического лица нерезидента
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="УчастникФЛИП" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о физическом лице, индивидуальном предпринимателе, физическом лице,
|
||||||
|
занимающемся в установленном законодательством Российской Федерации порядке частной практикой
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="ИдентификацияФЛ" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Признак идентификации физического лица</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="1|2|3"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведФЛИП" type="СведенияФЛИП">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о физическом лице, индивидуальном предпринимателе</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="БенефициарФЛИП" type="СведенияБенефициар" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о бенефициарном владельце. !!! Показатель введен, начиная с версии 2.0 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="УчастникИНБОЮЛ" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения об иностранной структуре без образования юридического лица
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="СведИНБОЮЛ" type="СведенияИНБОЮЛ">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения об иностранной структуре без образования юридического лица</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнакИдентификацияИНБОЮЛ" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Признак идентификации бенефициарного владельца.
|
||||||
|
!!! Показатель введен, начиная с версии 2.0 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="0|1"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="БенефициарИНБОЮЛ" type="СведенияБенефициар" minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о бенефициарном владельце. !!! Показатель введен, начиная с версии 2.0 формата ФЭС !!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="КомментУчастник" type="Т2000" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Дополнительная информация об участнике операции (сделки)</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведенияПереводыДС" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о переводах денежных средств, в том числе электронных денежных средств
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="ВидПереводДС">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Вид перевода денежных средств</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[1-9]|1[0-4]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="КодТерИнГос" minOccurs="0" type="Т500">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код территории</xs:documentation>
|
||||||
|
<xs:documentation>
|
||||||
|
!!!!! Показатель добавлен в версии формата 1.2 в соответствии с Федеральным законом от 28.06.2021 № 230-ФЗ !!!!!
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ТипОператорДС">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Тип оператора по переводу денежных средств</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="[1-5]"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НомерСчетПлательщик" type="Т40" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер счета плательщика</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ИдентЭСППлательщик" type="Т100" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Идентификатор ЭСП плательщика</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведБанкПлательщик" type="БанкПлательщикПолучатель" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о банке плательщика</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведБанкПолучатель" type="БанкПлательщикПолучатель" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о банке получателя</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СчетБанкПлательщик" type="Т40" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер счета банка плательщика</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СчетБанкПолучатель" type="Т40" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер счета банка получателя</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НомерСчетПолучатель" type="Т40" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер счета получателя</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ИдентЭСППолучателя" type="Т100" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Идентификатор ЭСП получателя</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведПриемНалДС" type="МестоПриемаВыдача" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о месте приема наличных денежных средств</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СтатусПеревод" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Статус перевода</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="0|1"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведВыдачаНалДС" type="МестоПриемаВыдача" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о месте выдачи наличных денежных средств</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="АдресIP" type="Т40" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>IP-адрес сетевого оборудования плательщика</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="АдресMAC" type="Т23" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>MAC-адрес сетевого оборудования плательщика</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведенияВнесениеПолучениеНалДС" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения о переводах денежных средств, в том числе электронных денежных средств
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="КодХарактерОп">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код характера операции</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="1|2"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НомерСчетКлиент" type="Т40">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер счета клиента</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НомерКарта" type="Т40" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер платежной карты</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведПриемВыдача" type="МестоПриемаВыдача" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о месте приема (выдачи) наличных денежных средств</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведенияКартаИнБанк" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>
|
||||||
|
Сведения об операции с использованием платежной карты, эмитированной иностранным банком,
|
||||||
|
зарегистрированным на территории иностранного государства или административно-территориальной
|
||||||
|
единицы иностранного государства, обладающей самостоятельной правоспособностью, входящих в перечень,
|
||||||
|
утвержденный уполномоченным органом (Росфинмониторингом)
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="КодТерИнБанк" type="Т500">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Код территории</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведМестоОперация" type="МестоПриемаВыдача">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о месте совершения операции с денежными средствами в наличной форме</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НомерКарта" type="Т40">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Номер платежной карты</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СведДержательКарты" type="Т2000">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Сведения о держателе платежной карты</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="ПризнакСотрудник">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Признак совершения операции с участием уполномоченного сотрудника кредитной организации (филиала кредитной организации)</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="0|1"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="НаимИнБанк" type="Т500">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Наименование иностранного банка</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="СВИФТИнБанк" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>СВИФТ иностранного банка</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:simpleType>
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:pattern value="([A-Z]{4}[A-Z]{2}[0-9A-Z]{2}([0-9A-Z]{3}|))|НР"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="Коммент" type="Т2000" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Дополнительные сведения</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
||||||
1440
docs/xsd/data_types_v2.xsd
Normal file
1440
docs/xsd/data_types_v2.xsd
Normal file
File diff suppressed because it is too large
Load Diff
@ -775,6 +775,80 @@ def test_eio_block_is_empty_when_eio_and_beneficiary_are_absent() -> None:
|
|||||||
assert row[index] == ""
|
assert row[index] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_participant_and_beneficiary_inn_are_scoped_regardless_of_field_order() -> None:
|
||||||
|
own_inn = ("УчастникЮЛ.СведЮЛ.ИННЮЛ", "2820000210")
|
||||||
|
beneficiary_fields = (
|
||||||
|
("УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.ФИОФЛИП.Фам", "Иванов"),
|
||||||
|
("УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.ФИОФЛИП.Имя", "Иван"),
|
||||||
|
("УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.ФИОФЛИП.Отч", "Иванович"),
|
||||||
|
("УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.ИННФЛИП", "111111111111"),
|
||||||
|
)
|
||||||
|
|
||||||
|
for items in (
|
||||||
|
(own_inn, *beneficiary_fields),
|
||||||
|
(*reversed(beneficiary_fields), own_inn),
|
||||||
|
):
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields=dict(items),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert row[120] == "2820000210"
|
||||||
|
assert row[157] == "Иванов Иван Иванович"
|
||||||
|
assert row[162] == "111111111111"
|
||||||
|
assert "111111111111" not in row[157]
|
||||||
|
|
||||||
|
|
||||||
|
def test_flip_beneficiary_is_scoped_to_eio_columns() -> None:
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={
|
||||||
|
"УчастникФЛИП.СведФЛИП.ИННФЛИП": "222222222222",
|
||||||
|
"УчастникФЛИП.БенефициарФЛИП.ФЛБенефициар.ИННФЛИП": "111111111111",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert row[120] == "222222222222"
|
||||||
|
assert row[162] == "111111111111"
|
||||||
|
|
||||||
|
|
||||||
|
def test_legal_entity_eio_name_is_preserved_in_structured_column() -> None:
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={
|
||||||
|
"УчастникЮЛ.СведЕИО.ЮЛЕИО.НаимЮЛ": "ООО Управляющая компания",
|
||||||
|
"УчастникЮЛ.СведЕИО.ЮЛЕИО.ИННЮЛ": "7707083893",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert row[157] == "ООО Управляющая компания"
|
||||||
|
assert row[162] == "7707083893"
|
||||||
|
|
||||||
|
|
||||||
|
def test_continuation_row_validates_local_codes_without_filling_defaults() -> None:
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={"ПризнФТр": "9"},
|
||||||
|
participant_fields={},
|
||||||
|
is_continuation=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert row[10] == ""
|
||||||
|
assert row[44] == ""
|
||||||
|
assert row[45] == ""
|
||||||
|
|
||||||
|
|
||||||
def test_cp_block_is_empty_without_sved_cp_and_uses_emitent_inn_when_present() -> None:
|
def test_cp_block_is_empty_without_sved_cp_and_uses_emitent_inn_when_present() -> None:
|
||||||
row_without_cp = build_fixed_row_by_index(
|
row_without_cp = build_fixed_row_by_index(
|
||||||
file_name="f.xml",
|
file_name="f.xml",
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from app.pipeline.parser import parse_xml_content
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from app.pipeline.parser import _extract_direct_fields, parse_xml_content
|
||||||
|
|
||||||
|
|
||||||
def _base_xml(operations: str) -> str:
|
def _base_xml(operations: str) -> str:
|
||||||
@ -120,3 +122,105 @@ def test_parse_xml_collects_legal_entity_registration_address_attributes() -> No
|
|||||||
assert (
|
assert (
|
||||||
participant_fields["УчастникЮЛ.СведЮЛ.АдрРегЮЛ.Район"] == "Благовещенский р-н"
|
participant_fields["УчастникЮЛ.СведЮЛ.АдрРегЮЛ.Район"] == "Благовещенский р-н"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_direct_fields_indexes_repeated_siblings_at_every_level() -> None:
|
||||||
|
participant = ET.fromstring(
|
||||||
|
"""
|
||||||
|
<УчастникОп>
|
||||||
|
<Документ><НомДок>DOC-1</НомДок></Документ>
|
||||||
|
<Документ><НомДок>DOC-2</НомДок></Документ>
|
||||||
|
<УчастникЮЛ>
|
||||||
|
<СведЕИО><ФЛЕИО><ИННФЛИП>111111111111</ИННФЛИП></ФЛЕИО></СведЕИО>
|
||||||
|
<СведЕИО><ФЛЕИО><ИННФЛИП>222222222222</ИННФЛИП></ФЛЕИО></СведЕИО>
|
||||||
|
</УчастникЮЛ>
|
||||||
|
</УчастникОп>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fields = _extract_direct_fields(participant)
|
||||||
|
|
||||||
|
assert fields["Документ[0].НомДок"] == "DOC-1"
|
||||||
|
assert fields["Документ[1].НомДок"] == "DOC-2"
|
||||||
|
assert fields["УчастникЮЛ.СведЕИО[0].ФЛЕИО.ИННФЛИП"] == "111111111111"
|
||||||
|
assert fields["УчастникЮЛ.СведЕИО[1].ФЛЕИО.ИННФЛИП"] == "222222222222"
|
||||||
|
assert all("; " not in value for value in fields.values())
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_direct_fields_keeps_single_sibling_path_unindexed() -> None:
|
||||||
|
participant = ET.fromstring(
|
||||||
|
"<УчастникОп><Документ><НомДок>DOC-1</НомДок></Документ></УчастникОп>"
|
||||||
|
)
|
||||||
|
|
||||||
|
fields = _extract_direct_fields(participant)
|
||||||
|
|
||||||
|
assert fields == {"Документ.НомДок": "DOC-1"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_xml_expands_documents_and_eio_into_additive_rows() -> None:
|
||||||
|
xml = _base_xml(
|
||||||
|
"""
|
||||||
|
<Операция>
|
||||||
|
<ИдентификаторЗаписи>OP6</ИдентификаторЗаписи>
|
||||||
|
<УчастникОп>
|
||||||
|
<ТипУчастника>1</ТипУчастника>
|
||||||
|
<Документ><НомДок>DOC-1</НомДок></Документ>
|
||||||
|
<Документ><НомДок>DOC-2</НомДок></Документ>
|
||||||
|
<Документ><НомДок>DOC-3</НомДок></Документ>
|
||||||
|
<УчастникЮЛ>
|
||||||
|
<СведЕИО><ФЛЕИО><ИННФЛИП>111111111111</ИННФЛИП></ФЛЕИО></СведЕИО>
|
||||||
|
<СведЕИО><ЮЛЕИО><ИННЮЛ>2222222222</ИННЮЛ></ЮЛЕИО></СведЕИО>
|
||||||
|
</УчастникЮЛ>
|
||||||
|
</УчастникОп>
|
||||||
|
</Операция>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
result = parse_xml_content("SKO115FZ_01_123456789_20260616_X00001.xml", xml)
|
||||||
|
|
||||||
|
assert not result.is_fatal
|
||||||
|
assert len(result.rows) == 4
|
||||||
|
base_row, second_document, third_document, second_eio = result.rows
|
||||||
|
assert base_row.participant_fields["ТипУчастника"] == "1"
|
||||||
|
assert base_row.participant_fields["Документ.НомДок"] == "DOC-1"
|
||||||
|
assert (
|
||||||
|
base_row.participant_fields["УчастникЮЛ.СведЕИО.ФЛЕИО.ИННФЛИП"]
|
||||||
|
== "111111111111"
|
||||||
|
)
|
||||||
|
assert second_document.operation_fields == {}
|
||||||
|
assert second_document.is_continuation
|
||||||
|
assert second_document.participant_fields == {"Документ.НомДок": "DOC-2"}
|
||||||
|
assert third_document.participant_fields == {"Документ.НомДок": "DOC-3"}
|
||||||
|
assert second_eio.participant_fields == {
|
||||||
|
"УчастникЮЛ.СведЕИО.ЮЛЕИО.ИННЮЛ": "2222222222"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_xml_separates_single_eio_and_single_beneficiary() -> None:
|
||||||
|
xml = _base_xml(
|
||||||
|
"""
|
||||||
|
<Операция>
|
||||||
|
<ИдентификаторЗаписи>OP7</ИдентификаторЗаписи>
|
||||||
|
<УчастникОп>
|
||||||
|
<ТипУчастника>1</ТипУчастника>
|
||||||
|
<УчастникЮЛ>
|
||||||
|
<СведЕИО><ЮЛЕИО><ИННЮЛ>2222222222</ИННЮЛ></ЮЛЕИО></СведЕИО>
|
||||||
|
<БенефициарЮЛ>
|
||||||
|
<ФЛБенефициар><ИННФЛИП>111111111111</ИННФЛИП></ФЛБенефициар>
|
||||||
|
</БенефициарЮЛ>
|
||||||
|
</УчастникЮЛ>
|
||||||
|
</УчастникОп>
|
||||||
|
</Операция>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
result = parse_xml_content("SKO115FZ_01_123456789_20260616_X00001.xml", xml)
|
||||||
|
|
||||||
|
assert len(result.rows) == 2
|
||||||
|
assert (
|
||||||
|
result.rows[0].participant_fields["УчастникЮЛ.СведЕИО.ЮЛЕИО.ИННЮЛ"]
|
||||||
|
== "2222222222"
|
||||||
|
)
|
||||||
|
assert result.rows[1].participant_fields == {
|
||||||
|
"УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.ИННФЛИП": "111111111111"
|
||||||
|
}
|
||||||
|
|||||||
@ -302,6 +302,46 @@ def test_write_report_does_not_assume_kpp_when_participant_type_is_missing(
|
|||||||
assert sheet.cell(row=3, column=122).value == "1"
|
assert sheet.cell(row=3, column=122).value == "1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_report_keeps_only_repeat_fields_on_continuation_row(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
destination = tmp_path / "report.xlsx"
|
||||||
|
rows = [
|
||||||
|
ReportRow(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="RID",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={"КодОперации": "5007"},
|
||||||
|
participant_fields={
|
||||||
|
"ТипУчастника": "1",
|
||||||
|
"УчастникЮЛ.СведЮЛ.ИННЮЛ": "2820000210",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ReportRow(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="RID",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={
|
||||||
|
"УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.ИННФЛИП": "111111111111"
|
||||||
|
},
|
||||||
|
is_continuation=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
write_report(rows, destination)
|
||||||
|
|
||||||
|
workbook = load_workbook(destination)
|
||||||
|
sheet = workbook["Отчет"]
|
||||||
|
assert sheet.cell(row=3, column=19).value == "5007"
|
||||||
|
assert sheet.cell(row=3, column=120).value == "2820000210"
|
||||||
|
assert sheet.cell(row=4, column=19).value is None
|
||||||
|
assert sheet.cell(row=4, column=120).value in (None, "")
|
||||||
|
assert sheet.cell(row=4, column=162).value == "111111111111"
|
||||||
|
assert sheet.cell(row=4, column=44).value in (None, "")
|
||||||
|
assert sheet.cell(row=4, column=45).value in (None, "")
|
||||||
|
|
||||||
|
|
||||||
def _column_letter(index: int) -> str:
|
def _column_letter(index: int) -> str:
|
||||||
value = index
|
value = index
|
||||||
result = ""
|
result = ""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user