# DaData Connector **name**: dadata_connector **version**: 19.0.2025.12.03 **author**: MK.Lab **depends**: `base`, `web`, `contacts`, `account`, `l10n_ru_doc` **external_dependencies**: `dadata==21.10.1` ## Архитектура Модуль состоит из трёх частей: серверная логика запроса к DaData и разбор ответа, визард подтверждения найденных данных, и OWL-компонент (виджет) который добавляет кнопку поиска к полям Tax ID (ИНН) и ОГРН прямо в форме. ``` dadata_connector ├── res.partner (расширение) — запрос к DaData, разбор ответа ├── res.partner.auto_data.wizard — диалог подтверждения данных ├── res.config.settings (расширение) — хранение токена DaData └── SearchField (OWL-компонент) — виджет с кнопкой поиска в форме ``` --- ## Модели ### `res.partner` — Расширение карточки контрагента **Файл:** `models/res_partner.py` Добавляет логику поиска и обработки данных из DaData. Новых полей в базу не добавляет — только методы и переопределение виджета. #### Переопределение виджета (`_get_view`) При загрузке формы контрагента метод `_get_view` программно устанавливает виджет `dadata_search` для поля `vat` (ИНН). Это сделано через код, а не через XML, чтобы установить приоритет над стандартным виджетом `partner_autocomplete` от Odoo. Он подключается к полю первым, и XML-переопределение его не вытесняет. Для поля `ogrn` виджет устанавливается через XML (`views/res_partner_views.xml`). #### Метод `get_legal_entity_data(vat, widget=True)` Точка входа вызывается из JS-компонента при нажатии кнопки поиска. Выполняет три шага: 1. Получает токен через `get_dadata_token()` 2. Отправляет запрос в DaData: `dadata.find_by_id("party", vat, branch_type="MAIN")` — ищет только головные организации, без филиалов 3. Разбирает ответ через `_parse_dadata_response()` и открывает визард подтверждения Параметр `widget=True` означает режим работы через интерфейс (возвращает действие открытия визарда). При `widget=False` возвращает словарь с данными напрямую, что используется в тестах. #### Метод `get_dadata_token()` Читает токен из системных параметров (`dadata_connector.dadata_token`). Если токен не задан, возникает `ValidationError` с подсказкой, где его найти в настройках. #### Метод `_parse_dadata_response(data)` Разбирает JSON-ответ от DaData и возвращает два словаря: - **`wizard_data`** — данные для показа в диалоге подтверждения: статус организации, тип (юрлицо / ИП), название, полный адрес строкой - **`new_data`** — данные для записи в карточку контрагента: все поля ниже **Маппинг полей DaData → Odoo:** | Поле Odoo | Поле DaData | Примечание | |---|---|---| | `vat` | `inn` | ИНН | | `ogrn` | `ogrn` | ОГРН | | `kpp` | `kpp` | Только для юрлиц | | `okpo` | `okpo` | ОКПО | | `arceat` | `okved` | Основной ОКВЭД | | `company_form` | `opf.code` → таблица `okopf` | Код ОКОПФ переводится в тип: `sp`, `plc`, `jsc` и т.д. | | `sp_register_number` | `documents.fts_registration` | Серия и номер через пробел | | `sp_register_date` | `documents.fts_registration.issue_date` | Дата регистрации в налоговой | | `name` | `name.short_with_opf` (юрлицо) или `fio` (ИП) | | | `country_id` | `address.data.country_iso_code` | Поиск по коду страны | | `state_id` | `address.data.region_iso_code` | Поиск по коду региона | | `city` | `address.data.city` | | | `street` | Собирается из нескольких полей | `street_with_type`, `house_type_full`, `house`, `flat_type_full`, `flat` через запятую | | `zip` | `address.data.postal_code` | | | `management` | `management.name` и `management.post` | Не поле, а вложенный словарь для создания контакта | **Таблица ОКОПФ** (словарь `okopf` в начале файла): переводит цифровые коды организационно-правовых форм в технические коды Odoo. Например, `50102` → `sp` (индивидуальный предприниматель), `12300` → `plc` (ООО). --- ### `res.partner.auto_data.wizard` — Диалог подтверждения **Файл:** `wizard/res_partner_auto_data_wizard.py` Временная модель (`TransientModel`), работает только пока открыт диалог. Показывает пользователю найденные данные перед тем, как они будут применены к карточке. | Поле | Тип | Описание | |---|---|---| | `partner_id` | Many2one → `res.partner` | Контрагент, к которому применяются данные | | `status` | Selection | Статус: active / liquidating / liquidated / bankrupt / reorganizing | | `organization_type` | Selection | Тип: legal (юрлицо) / individual (ИП) | | `name` | Char | Название организации | | `full_address` | Text | Полный юридический адрес строкой | Форма открывается в режиме только для чтения (`create="0" delete="0" edit="0"`), редактировать данные в диалоге нельзя. **Метод `button_yes()`** — при нажатии «Да» закрывает диалог и передаёт во фронтенд сигнал `{"update": True}`. JS-компонент перехватывает этот сигнал и применяет данные к карточке. --- ### `res.config.settings` — Расширение настроек **Файл:** `models/res_config_settings.py` Добавляет одно поле в раздел «Интеграции» в настройках Odoo. Значение хранится в системных параметрах. | Поле | Параметр | Описание | |---|---|---| | `dadata_token` | `dadata_connector.dadata_token` | API-токен сервиса DaData | --- ## OWL-компонент: виджет `dadata_search` **Файл:** `static/src/views/fields/search/search_field.js` Кастомный виджет для полей типа `char`. Визуально выглядит как стандартное текстовое поле с иконкой поиска справа. Зарегистрирован в реестре виджетов Odoo как `dadata_search`. #### Логика метода `search()` Вызывается при нажатии иконки поиска. Последовательность: 1. Вызывает серверный метод `res.partner.get_legal_entity_data()` через ORM с текущим значением поля 2. Получает в ответ действие открытия визарда подтверждения 3. Открывает визард и ждёт его закрытия 4. При закрытии проверяет флаг `closeInfo.update`: - Если `True` — применяет данные к карточке и сохраняет - Если `False` (нажали «Нет») — ничего не делает #### Применение данных к карточке Перед обновлением карточки фильтрует только те поля, которые реально существуют в модели (`record.fields`). Many2one-поля (например, `country_id`, `state_id`) конвертируются в формат `[id, ""]` — так требует OWL. После обновления полей принудительно устанавливает `company_type: "company"` — контрагент всегда сохраняется как организация. #### Создание контакта-руководителя Если в ответе DaData есть данные об управляющем (`management`), компонент проверяет нет ли уже контакта с таким именем и должностью среди дочерних записей контрагента. Если нет — создаёт новый контакт через `res.partner.create()` с привязкой `parent_id` к текущему контрагенту. --- ## Представления **Файл:** `views/res_partner_views.xml` Два расширения формы контрагента: - Расширение `l10n_ru_doc.view_partner_ru_form` — добавляет виджет `dadata_search` к полю `ogrn` - Расширение `base.view_partner_form` — добавляет виджет `dadata_search` к полю `vat`. Приоритет `2`, но виджет также устанавливается программно через `_get_view` для гарантированного перебития `partner_autocomplete` **Файл:** `views/res_config_settings_view.xml` Добавляет поле токена в настройки. --- ## Тесты **Файл:** `tests/` | Файл | Что покрывает | |---|---| | `test_res_partner.py` | Запрос к DaData, разбор ответа, маппинг полей | | `test_wizard.py` | Создание визарда, отображение полей, кнопка подтверждения | | `common.py` | Общие фикстуры: клиент DaData, тестовый контрагент |