Public release from ruodoo-project: 19.0 - 2026-07-26 21:17:35 UTC

This commit is contained in:
CI Publish Bot
2026-07-26 21:17:45 +00:00
commit 4f7b594ec8
1335 changed files with 191620 additions and 0 deletions

View File

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