166 lines
11 KiB
Markdown
166 lines
11 KiB
Markdown
# 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, тестовый контрагент |
|