This commit is contained in:
Raykov-MS 2026-08-11 13:42:46 +03:00
parent bef38dedd8
commit 510d668ab8
4 changed files with 132 additions and 15 deletions

View File

@ -27,6 +27,7 @@ COL_SUSPICIOUS_ID = _index(28)
COL_METAL_CODE = _index(29) COL_METAL_CODE = _index(29)
COL_METAL_NAME = _index(30) COL_METAL_NAME = _index(30)
COL_ITEM_TYPE = _index(31) COL_ITEM_TYPE = _index(31)
COL_EXTRA_INFO = _index(34)
# Перевод. # Перевод.
COL_TRANSFER_TYPE = _index(35) COL_TRANSFER_TYPE = _index(35)
@ -101,7 +102,7 @@ PARTICIPANT_ADDRESS_OUTPUT_SOURCES: dict[int, int] = {
# Бизнес-константы. # Бизнес-константы.
ACCOUNT_PLACEHOLDER = "00000000000000000000" ACCOUNT_PLACEHOLDER = "00000000000000000000"
CURRENCY_OPERATION_CODES = frozenset(str(code) for code in range(6101, 6127)) EXPORT_SUBSIDIARY_COMMENT_CODES = frozenset(str(code) for code in range(6102, 6127))
CASHLESS_TRANSFER_TYPES = frozenset( CASHLESS_TRANSFER_TYPES = frozenset(
{"1", "2", "3", "4", "7", "10", "11", "12", "13", "14"} {"1", "2", "3", "4", "7", "10", "11", "12", "13", "14"}
) )

View File

@ -19,13 +19,13 @@ from .column_constants import (
COL_COUNTRY_CODE, COL_COUNTRY_CODE,
COL_CP_ISSUER_INN, COL_CP_ISSUER_INN,
COL_CURRENCY, COL_CURRENCY,
COL_CURRENCY_SIGN,
COL_EIO_ADDRESS_ONE_LINE, COL_EIO_ADDRESS_ONE_LINE,
COL_EIO_INN, COL_EIO_INN,
COL_EIO_REG_PLACE_ONE_LINE, COL_EIO_REG_PLACE_ONE_LINE,
COL_EMPLOYEE_SIGN, COL_EMPLOYEE_SIGN,
COL_ESP_SIGN, COL_ESP_SIGN,
COL_EXTRA_CODES, COL_EXTRA_CODES,
COL_EXTRA_INFO,
COL_FILE_NAME, COL_FILE_NAME,
COL_FOREIGN_BANK_NAME, COL_FOREIGN_BANK_NAME,
COL_FTR_SIGN, COL_FTR_SIGN,
@ -63,8 +63,8 @@ from .column_constants import (
COL_TRANSFER_STATUS, COL_TRANSFER_STATUS,
COL_TRANSFER_TYPE, COL_TRANSFER_TYPE,
COL_UNUSUAL_CODES, COL_UNUSUAL_CODES,
CURRENCY_OPERATION_CODES,
EMPLOYEE_ABSENT_CODE, EMPLOYEE_ABSENT_CODE,
EXPORT_SUBSIDIARY_COMMENT_CODES,
OKATO_COUNTRY_PAIRS, OKATO_COUNTRY_PAIRS,
PARTICIPANT_TYPE_FL, PARTICIPANT_TYPE_FL,
PARTICIPANT_TYPE_FLCHP, PARTICIPANT_TYPE_FLCHP,
@ -389,6 +389,8 @@ def build_fixed_row_by_index(
row, row,
has_eio_block=has_eio_block, has_eio_block=has_eio_block,
has_cp_block=has_cp_block, has_cp_block=has_cp_block,
operation_fields=operation_fields,
participant_fields=participant_fields,
) )
else: else:
_apply_continuation_validation_rules(row) _apply_continuation_validation_rules(row)
@ -472,6 +474,14 @@ def _pick_scoped_payload_value(payload: dict[str, str], scoped_path: str) -> str
return "" return ""
def _pick_direct_payload_value(payload: dict[str, str], field_name: str) -> str:
"""Возвращает только прямое поле текущего XML-блока без suffix-поиска."""
for key, value in payload.items():
if value and _normalize_indexed_path(key) == field_name:
return str(value).strip()
return ""
def _apply_participant_identity_rules( def _apply_participant_identity_rules(
row: dict[int, str], participant_fields: dict[str, str] row: dict[int, str], participant_fields: dict[str, str]
) -> None: ) -> None:
@ -633,6 +643,8 @@ def _apply_business_rules(
*, *,
has_eio_block: bool, has_eio_block: bool,
has_cp_block: bool, has_cp_block: bool,
operation_fields: dict[str, str],
participant_fields: dict[str, str],
) -> None: ) -> None:
codes = _operation_codes(row) codes = _operation_codes(row)
operation_sign = _get(row, COL_OPERATION_SIGN) operation_sign = _get(row, COL_OPERATION_SIGN)
@ -646,7 +658,7 @@ def _apply_business_rules(
_rule_unusual_codes(row, codes) _rule_unusual_codes(row, codes)
_rule_digital_rights_currency(row, operation_sign) _rule_digital_rights_currency(row, operation_sign)
_rule_sale_currency(row) _rule_sale_currency(row)
_rule_currency_sign(row, codes) _rule_extra_info(row, codes, operation_fields, participant_fields)
_rule_suspicious_activity(row, codes) _rule_suspicious_activity(row, codes)
_rule_metal_name(row) _rule_metal_name(row)
_rule_item_type(row, codes) _rule_item_type(row, codes)
@ -725,10 +737,26 @@ def _rule_sale_currency(row: dict[int, str]) -> None:
_set(row, COL_SALE_AMOUNT, "") _set(row, COL_SALE_AMOUNT, "")
def _rule_currency_sign(row: dict[int, str], codes: set[str]) -> None: def _rule_extra_info(
"""Правило 27: признак VO только для валютных операций 6101-6126.""" row: dict[int, str],
if not codes & CURRENCY_OPERATION_CODES: codes: set[str],
_set(row, COL_CURRENCY_SIGN, "") operation_fields: dict[str, str],
participant_fields: dict[str, str],
) -> None:
"""Правило 34: объединяет допустимые комментарии операции и участника."""
operation_comment = _pick_direct_payload_value(operation_fields, "Коммент")
participant_comment = ""
if codes & EXPORT_SUBSIDIARY_COMMENT_CODES:
participant_comment = _pick_direct_payload_value(
participant_fields, "КомментУчастник"
)
values = list(
dict.fromkeys(
value for value in (operation_comment, participant_comment) if value
)
)
_set(row, COL_EXTRA_INFO, "; ".join(values))
def _rule_suspicious_activity(row: dict[int, str], codes: set[str]) -> None: def _rule_suspicious_activity(row: dict[int, str], codes: set[str]) -> None:

View File

@ -9,7 +9,7 @@
"xml_path": "/ММ/ГГГГ", "xml_path": "/ММ/ГГГГ",
"source_scope": "any", "source_scope": "any",
"allow_short_lookup": true, "allow_short_lookup": true,
"allow_direct_mapping": true, "allow_direct_mapping": false,
"structured_value": false, "structured_value": false,
"structured_group": "", "structured_group": "",
"structured_role": "", "structured_role": "",
@ -665,11 +665,11 @@
"column_name": "Доп. сведения", "column_name": "Доп. сведения",
"block": "operation_base", "block": "operation_base",
"report_group": "operation_parameters", "report_group": "operation_parameters",
"xml_tag": "КомментУчастник", "xml_tag": "Коммент | КомментУчастник",
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОп/КомментУчастник", "xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/Коммент | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОп/КомментУчастник",
"source_scope": "any", "source_scope": "any",
"allow_short_lookup": true, "allow_short_lookup": true,
"allow_direct_mapping": true, "allow_direct_mapping": false,
"structured_value": false, "structured_value": false,
"structured_group": "", "structured_group": "",
"structured_role": "", "structured_role": "",

View File

@ -937,7 +937,7 @@ def test_cp_block_is_empty_without_sved_cp_and_uses_emitent_inn_when_present() -
assert row_with_cp[202] == "7707083893" assert row_with_cp[202] == "7707083893"
def test_currency_columns_apply_only_to_conversion_and_currency_operations() -> None: def test_currency_sign_is_preserved_independently_of_operation_code() -> None:
non_conversion = build_fixed_row_by_index( non_conversion = build_fixed_row_by_index(
file_name="f.xml", file_name="f.xml",
record_id="R1", record_id="R1",
@ -950,7 +950,7 @@ def test_currency_columns_apply_only_to_conversion_and_currency_operations() ->
participant_fields={}, participant_fields={},
) )
assert non_conversion[26] == "" assert non_conversion[26] == ""
assert non_conversion[27] == "" assert non_conversion[27] == "VO"
conversion = build_fixed_row_by_index( conversion = build_fixed_row_by_index(
file_name="f.xml", file_name="f.xml",
@ -993,7 +993,95 @@ def test_currency_columns_apply_only_to_conversion_and_currency_operations() ->
}, },
participant_fields={}, participant_fields={},
) )
assert boundary_row[27] == "" assert boundary_row[27] == "VO"
def test_extra_info_uses_operation_comment() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={"КодОперации": "9003", "Коммент": "Комментарий операции"},
participant_fields={},
)
assert row[34] == "Комментарий операции"
def test_extra_info_ignores_nested_operation_comment() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={
"КодОперации": "9003",
"СведенияПереводыДС.Коммент": "Комментарий вложенного блока",
},
participant_fields={},
)
assert row[34] == ""
def test_extra_info_rejects_participant_comment_outside_allowed_codes() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={"КодОперации": "9003"},
participant_fields={"КомментУчастник": "Посторонний комментарий"},
)
assert row[34] == ""
def test_extra_info_is_not_directly_mapped_on_continuation_row() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={"КодОперации": "9003"},
participant_fields={"КомментУчастник": "Посторонний комментарий"},
is_continuation=True,
)
assert row[34] == ""
def test_extra_info_accepts_participant_comment_for_main_or_extra_code() -> None:
for operation_fields in (
{"КодОперации": "6102"},
{"КодОперации": "9003", "ДопКодОперации": "6126"},
):
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields=operation_fields,
participant_fields={"КомментУчастник": "Комментарий участника"},
)
assert row[34] == "Комментарий участника"
def test_extra_info_joins_distinct_allowed_comments_without_duplicates() -> None:
combined = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={"КодОперации": "6102", "Коммент": "Общий комментарий"},
participant_fields={"КомментУчастник": "Комментарий участника"},
)
duplicate = build_fixed_row_by_index(
file_name="f.xml",
record_id="R2",
operation_index=2,
operation_fields={"КодОперации": "6102", "Коммент": "Одинаковый текст"},
participant_fields={"КомментУчастник": "Одинаковый текст"},
)
assert combined[34] == "Общий комментарий; Комментарий участника"
assert duplicate[34] == "Одинаковый текст"
def test_bank_bik_columns_accept_canonical_transfer_block_spelling() -> None: def test_bank_bik_columns_accept_canonical_transfer_block_spelling() -> None: