Files
public/dadata_connector/TECHNICAL.md

166 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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