Public release from ruodoo-project: 19.0 - 2026-05-10 21:19:01 UTC
This commit is contained in:
110
dadata_connector/README.rst
Normal file
110
dadata_connector/README.rst
Normal file
@ -0,0 +1,110 @@
|
||||
================
|
||||
DaData Connector
|
||||
================
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Production-green.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
|
||||
|badge1|
|
||||
|
||||
Модуль интеграции с сервисом `DaData <https://dadata.ru>`_ для автоматического
|
||||
заполнения реквизитов контрагентов по ИНН или ОГРН.
|
||||
|
||||
**Возможности:**
|
||||
|
||||
- Поиск юридических лиц и ИП по ИНН через API DaData
|
||||
- Автоматическое заполнение реквизитов партнёра: название, адрес, КПП, ОКПО, ОКВЭД, ОГРН, организационно-правовая форма
|
||||
- Создание контактного лица (руководителя) при наличии данных об управлении
|
||||
- Виджет поиска ``dadata_search`` на полях ``vat`` и ``ogrn`` в форме партнёра
|
||||
- Настройка токена DaData через Настройки → Общие настройки → Интеграции
|
||||
|
||||
**Таблица содержания**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Использование
|
||||
=============
|
||||
|
||||
1. Перейдите в **Настройки → Общие настройки → Интеграции** и укажите токен DaData.
|
||||
2. Откройте форму контрагента.
|
||||
3. Введите ИНН в поле **Tax ID** (или ОГРН в поле **ОГРН**).
|
||||
4. Нажмите кнопку с лупой рядом с полем.
|
||||
5. В открывшемся диалоге проверьте найденные данные и нажмите **Да** для применения.
|
||||
|
||||
Конфигурация
|
||||
============
|
||||
|
||||
Токен DaData задаётся через системный параметр ``dadata_connector.dadata_token``
|
||||
или через интерфейс настроек.
|
||||
|
||||
Получить токен можно в личном кабинете `dadata.ru <https://dadata.ru>`_.
|
||||
|
||||
Известные ограничения
|
||||
=====================
|
||||
|
||||
- Поиск работает только для российских юридических лиц и ИП.
|
||||
- Требуется установленная Python-библиотека ``dadata==21.10.1``.
|
||||
- Модуль зависит от ``l10n_ru_doc`` для отображения российских реквизитов.
|
||||
|
||||
Тесты
|
||||
=====
|
||||
|
||||
Запуск::
|
||||
|
||||
python odoo-bin -d <db> --test-tags dadata_connector
|
||||
|
||||
**common.py** — базовый класс ``DadataConnectorCommon`` и фикстуры:
|
||||
|
||||
- ``DADATA_LEGAL_RESPONSE`` — эталонный ответ API для юридического лица (ПАО)
|
||||
- ``DADATA_INDIVIDUAL_RESPONSE`` — эталонный ответ API для индивидуального предпринимателя
|
||||
- ``DadataConnectorCommon.setUpClass`` — создаёт тестового партнёра и прописывает тестовый токен в системные параметры
|
||||
|
||||
**test_res_partner.py** — тесты модели ``res.partner``:
|
||||
|
||||
- ``test_get_dadata_token_returns_token`` — токен возвращается, если системный параметр задан
|
||||
- ``test_get_dadata_token_raises_when_missing`` — ``ValidationError`` при отсутствии токена
|
||||
- ``test_parse_legal_entity_basic_fields`` — парсинг ИНН, ОГРН, КПП, ОКПО, ОКВЭД, названия, города, индекса и улицы для юрлица
|
||||
- ``test_parse_legal_entity_company_form`` — код ОПФ ``12300`` маппится в ``plc``
|
||||
- ``test_parse_legal_entity_wizard_data`` — данные для wizard: статус, тип организации, название, адрес
|
||||
- ``test_parse_legal_entity_management`` — извлечение имени и должности руководителя
|
||||
- ``test_parse_legal_entity_fts_registration`` — серия и номер свидетельства ФНС склеиваются через пробел
|
||||
- ``test_parse_legal_entity_country_and_state`` — страна резолвится по ISO-коду из ``res.country``
|
||||
- ``test_parse_individual_name`` — ФИО ИП собирается из частей ``fio``
|
||||
- ``test_parse_individual_no_kpp`` — КПП не попадает в результат для ИП
|
||||
- ``test_parse_individual_no_management`` — ключ ``management`` отсутствует, если данных нет
|
||||
- ``test_parse_individual_no_fts_registration`` — ``sp_register_number`` отсутствует при ``fts_registration: null``
|
||||
- ``test_get_legal_entity_data_returns_action`` — при ``widget=True`` возвращается ``ir.actions.act_window`` с wizard (DaData замокан)
|
||||
- ``test_get_legal_entity_data_returns_dict_when_no_widget`` — при ``widget=False`` возвращается словарь с реквизитами
|
||||
- ``test_get_legal_entity_data_raises_when_empty`` — ``ValidationError`` при пустом ответе DaData
|
||||
- ``test_get_legal_entity_data_raises_on_http_error`` — ``ValidationError`` при ``HTTPStatusError`` от DaData
|
||||
- ``test_get_view_sets_dadata_search_widget_on_vat`` — ``_get_view`` проставляет ``widget="dadata_search"`` на поле ``vat``
|
||||
|
||||
**test_wizard.py** — тесты ``res.partner.auto_data.wizard``:
|
||||
|
||||
- ``test_wizard_creation`` — wizard создаётся с корректными значениями полей
|
||||
- ``test_button_yes_returns_close_action`` — ``button_yes`` возвращает ``act_window_close`` с флагом ``update: True``
|
||||
- ``test_wizard_status_selection_values`` — все пять значений статуса принимаются без ошибок
|
||||
- ``test_wizard_organization_type_individual`` — тип ``individual`` сохраняется корректно
|
||||
|
||||
Авторы
|
||||
======
|
||||
|
||||
* MK.lab
|
||||
|
||||
Разработчики
|
||||
============
|
||||
|
||||
* MK.lab
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
19.0.2026.04.10
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
* Портирование на Odoo 19
|
||||
* Замена ``_lt`` на ``_t`` (убран в Odoo 18+)
|
||||
* Виджет переименован в ``dadata_search`` во избежание конфликтов
|
||||
* Переопределение ``_get_view`` для перекрытия ``partner_autocomplete``
|
||||
* Удалено поле ``psrn_sp`` из логики парсинга
|
||||
2
dadata_connector/__init__.py
Normal file
2
dadata_connector/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizard
|
||||
22
dadata_connector/__manifest__.py
Normal file
22
dadata_connector/__manifest__.py
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "DaData Connector",
|
||||
"summary": """Obtaining data on legal entities from the DaData service""",
|
||||
"author": "MK.lab",
|
||||
"website": "#",
|
||||
"category": "Marketing",
|
||||
"version": "19.0.2025.12.03",
|
||||
"depends": ["base", "web", "contacts", "account", "l10n_ru_doc"],
|
||||
"external_dependencies": {"python": ["dadata==21.10.1"]},
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"views/res_partner_views.xml",
|
||||
"wizard/res_partner_auto_data_wizard_views.xml",
|
||||
"views/res_config_settings_view.xml",
|
||||
],
|
||||
"assets": {
|
||||
"web.assets_backend": [
|
||||
"dadata_connector/static/src/views/fields/search/*",
|
||||
],
|
||||
},
|
||||
"installable": True,
|
||||
}
|
||||
225
dadata_connector/i18n/ru.po
Normal file
225
dadata_connector/i18n/ru.po
Normal file
@ -0,0 +1,225 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * dadata_connector
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 19.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-07-18 05:07+0000\n"
|
||||
"PO-Revision-Date: 2024-07-18 05:07+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields.selection,name:dadata_connector.selection__res_partner_auto_data_wizard__status__active
|
||||
msgid "Active"
|
||||
msgstr "Действующая"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields.selection,name:dadata_connector.selection__res_partner_auto_data_wizard__status__bankrupt
|
||||
msgid "Bankrupt"
|
||||
msgstr "Банкротство"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model,name:dadata_connector.model_res_config_settings
|
||||
msgid "Config Settings"
|
||||
msgstr "Конфигурационные настройки"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model,name:dadata_connector.model_res_partner
|
||||
msgid "Contact"
|
||||
msgstr "Контакт"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__create_uid
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__create_date
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_config_settings__dadata_token
|
||||
msgid "DaData token"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#. odoo-javascript
|
||||
#: code:addons/dadata_connector/static/src/views/fields/search/search_field.js:0
|
||||
#: code:addons/local_addons/dadata_connector/static/src/views/fields/search/search_field.js:0
|
||||
#, python-format
|
||||
msgid "Data updated."
|
||||
msgstr "Данные обновлены."
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#. odoo-python
|
||||
#: code:addons/dadata_connector/models/res_partner.py:0
|
||||
#: code:addons/local_addons/dadata_connector/models/res_partner.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Failed to connect to DaData server. The token in the settings may be "
|
||||
"incorrect."
|
||||
msgstr ""
|
||||
"Не удалось подключиться к серверу DaData. Возможно, токен в настройках "
|
||||
"указан не верно."
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__full_address
|
||||
msgid "Full legal address"
|
||||
msgstr "Юридический адрес"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__name
|
||||
msgid "Full name"
|
||||
msgstr "Наименование"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__vat
|
||||
msgid "Identification Number"
|
||||
msgstr "ИНН"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,help:dadata_connector.field_res_partner_auto_data_wizard__vat
|
||||
msgid "Identification Number for selected type"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields.selection,name:dadata_connector.selection__res_partner_auto_data_wizard__organization_type__individual
|
||||
msgid "Individual entrepreneur"
|
||||
msgstr "Индивидуальный предприниматель"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields.selection,name:dadata_connector.selection__res_partner_auto_data_wizard__organization_type__legal
|
||||
msgid "Legal entity"
|
||||
msgstr "Юридическое лицо"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,help:dadata_connector.field_res_partner_auto_data_wizard__organization_type
|
||||
msgid "Legal entity or individual entrepreneur"
|
||||
msgstr "Юридическое лицо или индивидуальный предприниматель"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields.selection,name:dadata_connector.selection__res_partner_auto_data_wizard__status__liquidated
|
||||
msgid "Liquidated"
|
||||
msgstr "Ликвидирована"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields.selection,name:dadata_connector.selection__res_partner_auto_data_wizard__status__liquidating
|
||||
msgid "Liquidating"
|
||||
msgstr "Ликвидируется"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model_terms:ir.ui.view,arch_db:dadata_connector.res_partner_auto_data_wizard_view_form
|
||||
msgid "No"
|
||||
msgstr "Нет"
|
||||
|
||||
#. module: dadata_connector
|
||||
#. odoo-python
|
||||
#: code:addons/dadata_connector/models/res_partner.py:0
|
||||
#: code:addons/local_addons/dadata_connector/models/res_partner.py:0
|
||||
#, python-format
|
||||
msgid "No data found for the organization"
|
||||
msgstr "Не найдены данные для организации"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__partner_id
|
||||
msgid "Partner"
|
||||
msgstr "Контакт"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields.selection,name:dadata_connector.selection__res_partner_auto_data_wizard__status__reorganizing
|
||||
msgid "Reorganizing"
|
||||
msgstr ""
|
||||
"В процессе присоединения к другому юр. лицу, с последующей ликвидацией"
|
||||
|
||||
#. module: dadata_connector
|
||||
#. odoo-javascript
|
||||
#: code:addons/dadata_connector/static/src/views/fields/search/search_field.js:0
|
||||
#: code:addons/dadata_connector/static/src/views/fields/search/search_field.xml:0
|
||||
#: code:addons/dadata_connector/static/src/views/fields/search/search_field.xml:0
|
||||
#: code:addons/local_addons/dadata_connector/static/src/views/fields/search/search_field.js:0
|
||||
#: code:addons/local_addons/dadata_connector/static/src/views/fields/search/search_field.xml:0
|
||||
#: code:addons/local_addons/dadata_connector/static/src/views/fields/search/search_field.xml:0
|
||||
#, python-format
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
#. module: dadata_connector
|
||||
#. odoo-python
|
||||
#: code:addons/dadata_connector/models/res_partner.py:0
|
||||
#: code:addons/local_addons/dadata_connector/models/res_partner.py:0
|
||||
#, python-format
|
||||
msgid "Set these details for the current contact?"
|
||||
msgstr "Установить данные реквизиты для текущего контакта?"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__status
|
||||
msgid "Status"
|
||||
msgstr "Статус"
|
||||
|
||||
#. module: dadata_connector
|
||||
#. odoo-python
|
||||
#: code:addons/dadata_connector/models/res_partner.py:0
|
||||
#: code:addons/local_addons/dadata_connector/models/res_partner.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The token for DaData is not specified in the settings. (Settings - General "
|
||||
"settings - Integrations - DaData token)"
|
||||
msgstr ""
|
||||
"В настройках не указан токен для DaData. (Настройки - Общие настройки - "
|
||||
"Интеграции - DaData token)"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model.fields,field_description:dadata_connector.field_res_partner_auto_data_wizard__organization_type
|
||||
msgid "Type of organization"
|
||||
msgstr "Тип организации"
|
||||
|
||||
#. module: dadata_connector
|
||||
#. odoo-python
|
||||
#: code:addons/dadata_connector/models/res_partner.py:0
|
||||
#: code:addons/local_addons/dadata_connector/models/res_partner.py:0
|
||||
#, python-format
|
||||
msgid "Unknown organization type"
|
||||
msgstr "Неизвестный тип организации"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model:ir.model,name:dadata_connector.model_res_partner_auto_data_wizard
|
||||
msgid "Wizard for autofilling partner"
|
||||
msgstr "Визард для автозаполнения контактов"
|
||||
|
||||
#. module: dadata_connector
|
||||
#: model_terms:ir.ui.view,arch_db:dadata_connector.res_partner_auto_data_wizard_view_form
|
||||
msgid "Yes"
|
||||
msgstr "Да"
|
||||
2
dadata_connector/models/__init__.py
Normal file
2
dadata_connector/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
10
dadata_connector/models/res_config_settings.py
Normal file
10
dadata_connector/models/res_config_settings.py
Normal file
@ -0,0 +1,10 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
dadata_token = fields.Char(
|
||||
string="DaData token",
|
||||
config_parameter="dadata_connector.dadata_token",
|
||||
)
|
||||
165
dadata_connector/models/res_partner.py
Normal file
165
dadata_connector/models/res_partner.py
Normal file
@ -0,0 +1,165 @@
|
||||
from dadata import Dadata
|
||||
from httpx import HTTPStatusError
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
okopf = {
|
||||
"50102": "sp",
|
||||
"11000": "pshp",
|
||||
"11051": "pshp",
|
||||
"11064": "pshp",
|
||||
"20700": "pshp",
|
||||
"20701": "pshp",
|
||||
"20716": "pshp",
|
||||
"30006": "pshp",
|
||||
# "": "coop", # todo Cooperative
|
||||
"12300": "plc",
|
||||
"12200": "jsc",
|
||||
"12247": "pc",
|
||||
"12267": "сjsc",
|
||||
# "": "ga", # todo Government agency
|
||||
}
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
@api.model
|
||||
def _get_view(self, view_id=None, view_type="form", **options):
|
||||
arch, view = super()._get_view(view_id, view_type, **options)
|
||||
if view_type == "form":
|
||||
for node in arch.xpath("//field[@name='vat']"):
|
||||
node.set("widget", "dadata_search")
|
||||
return arch, view
|
||||
|
||||
def get_legal_entity_data(self, vat, widget=True):
|
||||
token = self.get_dadata_token()
|
||||
dadata = Dadata(token)
|
||||
try:
|
||||
result = dadata.find_by_id("party", vat, branch_type="MAIN")
|
||||
except HTTPStatusError:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Failed to connect to DaData server. The token in the settings may be incorrect."
|
||||
)
|
||||
)
|
||||
|
||||
if result:
|
||||
wizard_data, new_data = self._parse_dadata_response(result)
|
||||
|
||||
if widget:
|
||||
wizard = self.env["res.partner.auto_data.wizard"].create(wizard_data)
|
||||
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"target": "new",
|
||||
"name": _("Set these details for the current contact?"),
|
||||
"views": [(False, "form")],
|
||||
"view_mode": "form",
|
||||
"res_model": wizard._name,
|
||||
"res_id": wizard.id,
|
||||
"context": new_data,
|
||||
}
|
||||
else:
|
||||
return new_data
|
||||
else:
|
||||
raise ValidationError(_("No data found for the organization"))
|
||||
|
||||
@api.model
|
||||
def get_dadata_token(self):
|
||||
token = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("dadata_connector.dadata_token")
|
||||
)
|
||||
if token:
|
||||
return token
|
||||
else:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The token for DaData is not specified in the settings. (Settings - General settings - Integrations - DaData token)"
|
||||
)
|
||||
)
|
||||
|
||||
def _parse_dadata_response(self, data):
|
||||
result = {}
|
||||
wizard_data = {}
|
||||
data = data[0]["data"]
|
||||
|
||||
# Data for widget
|
||||
organization_type = data["type"].lower()
|
||||
wizard_data["partner_id"] = self.id
|
||||
wizard_data["status"] = data["state"]["status"].lower()
|
||||
wizard_data["organization_type"] = organization_type
|
||||
wizard_data["full_address"] = data["address"]["unrestricted_value"]
|
||||
|
||||
# Data for partner
|
||||
result["vat"] = data["inn"]
|
||||
result["okpo"] = data["okpo"]
|
||||
result["arceat"] = data["okved"]
|
||||
result["company_form"] = okopf.get(data["opf"]["code"])
|
||||
result["ogrn"] = data["ogrn"]
|
||||
if data["documents"] and data["documents"]["fts_registration"]:
|
||||
result[
|
||||
"sp_register_number"
|
||||
] = f'{data["documents"]["fts_registration"]["series"]} {data["documents"]["fts_registration"]["number"]}'
|
||||
result["sp_register_date"] = data["documents"]["fts_registration"][
|
||||
"issue_date"
|
||||
]
|
||||
if organization_type == "legal":
|
||||
result["kpp"] = data["kpp"]
|
||||
|
||||
# Name
|
||||
if organization_type == "legal":
|
||||
result["name"] = data["name"]["short_with_opf"]
|
||||
wizard_data["name"] = data["name"]["short_with_opf"]
|
||||
elif organization_type == "individual":
|
||||
result[
|
||||
"name"
|
||||
] = f'{data["fio"]["surname"]} {data["fio"]["name"]} {data["fio"]["patronymic"]}'
|
||||
wizard_data[
|
||||
"name"
|
||||
] = f'{data["fio"]["surname"]} {data["fio"]["name"]} {data["fio"]["patronymic"]}'
|
||||
else:
|
||||
raise ValidationError(_("Unknown organization type"))
|
||||
|
||||
# Address
|
||||
address = data["address"]["data"]
|
||||
|
||||
country = self.env["res.country"].search(
|
||||
[("code", "=", address["country_iso_code"])]
|
||||
)
|
||||
if country:
|
||||
result["country_id"] = country.id
|
||||
|
||||
region = self.env["res.country.state"].search(
|
||||
[
|
||||
("code", "=", address["region_iso_code"].split("-")[-1]),
|
||||
("country_id", "=", country.id),
|
||||
]
|
||||
)
|
||||
if region:
|
||||
result["state_id"] = region.id
|
||||
|
||||
result["city"] = address["city"]
|
||||
street = []
|
||||
for el in [
|
||||
address["street_with_type"],
|
||||
address["house_type_full"],
|
||||
address["house"],
|
||||
address["flat_type_full"],
|
||||
address["flat"],
|
||||
]:
|
||||
if el:
|
||||
street.append(el)
|
||||
result["street"] = ", ".join(street)
|
||||
result["zip"] = address["postal_code"]
|
||||
|
||||
if data.get("management"):
|
||||
result["management"] = {
|
||||
"manager_name": data["management"]["name"],
|
||||
"manager_position": data["management"]["post"],
|
||||
}
|
||||
|
||||
return wizard_data, result
|
||||
2
dadata_connector/security/ir.model.access.csv
Normal file
2
dadata_connector/security/ir.model.access.csv
Normal file
@ -0,0 +1,2 @@
|
||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_res_partner_auto_data_wizard","res_partner_auto_data_wizard user","model_res_partner_auto_data_wizard",,1,1,1,1
|
||||
|
105
dadata_connector/static/src/views/fields/search/search_field.js
Normal file
105
dadata_connector/static/src/views/fields/search/search_field.js
Normal file
@ -0,0 +1,105 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useInputField } from "@web/views/fields/input_field_hook";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
import { CharField } from "@web/views/fields/char/char_field";
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class SearchField extends Component {
|
||||
static template = "dadata_connector.SearchField";
|
||||
static props = {
|
||||
...standardFieldProps,
|
||||
placeholder: { type: String, optional: true },
|
||||
};
|
||||
|
||||
setup() {
|
||||
useInputField({ getValue: () => this.props.record.data[this.props.name] || "" });
|
||||
this.action = null;
|
||||
}
|
||||
|
||||
async search() {
|
||||
const record = this.props.record;
|
||||
this.action = await this.env.services.orm.call(
|
||||
"res.partner",
|
||||
"get_legal_entity_data",
|
||||
[record.resId],
|
||||
{
|
||||
vat: record.data[this.props.name],
|
||||
}
|
||||
);
|
||||
await this.env.services.action.doAction(this.action, {
|
||||
onClose: async (closeInfo) => {
|
||||
if (closeInfo && closeInfo.update) {
|
||||
const { management, ...rawData } = this.action.context;
|
||||
// Only update fields that exist in the current record's field definitions.
|
||||
// Many2one fields must be passed as [id, display_name] tuples for OWL record.update().
|
||||
const newRecordData = {};
|
||||
for (const [key, value] of Object.entries(rawData)) {
|
||||
const field = record.fields[key];
|
||||
if (!field) continue;
|
||||
if (field.type === "many2one" && typeof value === "number") {
|
||||
newRecordData[key] = [value, ""];
|
||||
} else {
|
||||
newRecordData[key] = value;
|
||||
}
|
||||
}
|
||||
await record.update({
|
||||
...newRecordData,
|
||||
company_type: "company",
|
||||
});
|
||||
await record.save();
|
||||
|
||||
const recordChildren = record.data.child_ids.records;
|
||||
if (management && !this._checkManagerExists(recordChildren, management)) {
|
||||
await this._createManager(management);
|
||||
}
|
||||
|
||||
await record.load();
|
||||
|
||||
this.env.services.notification.add(_t("Data updated."), {
|
||||
type: "info",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_checkManagerExists(recordChildren, management) {
|
||||
const managerName = management.manager_name;
|
||||
const managerFunction = management.manager_position;
|
||||
for (let rec of recordChildren) {
|
||||
if (
|
||||
rec.data.name.toUpperCase() === managerName.toUpperCase() &&
|
||||
rec.data.function.toUpperCase() === managerFunction.toUpperCase()
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async _createManager(management) {
|
||||
const record = this.props.record;
|
||||
await this.env.services.orm.call("res.partner", "create", [
|
||||
{
|
||||
name: management.manager_name,
|
||||
function: management.manager_position,
|
||||
parent_id: record.resId,
|
||||
type: "contact",
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export const searchField = {
|
||||
component: SearchField,
|
||||
displayName: _t("DaData Search"),
|
||||
supportedTypes: ["char"],
|
||||
extractProps: ({ attrs, placeholder }) => ({
|
||||
placeholder: attrs.placeholder || placeholder,
|
||||
}),
|
||||
};
|
||||
|
||||
registry.category("fields").add("dadata_search", searchField);
|
||||
@ -0,0 +1,4 @@
|
||||
.o_search_content small {
|
||||
overflow-wrap: normal;
|
||||
word-break: normal;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="dadata_connector.SearchField" owl="1">
|
||||
<div class="o_search_content d-inline-flex w-100">
|
||||
<t t-if="props.readonly">
|
||||
<span t-esc="props.record.data[props.name]"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<input class="o_input flex-grow-1"
|
||||
t-att-id="props.id"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
t-att-placeholder="props.placeholder"
|
||||
t-ref="input" />
|
||||
<button
|
||||
t-if="props.record.data[props.name]"
|
||||
class="btn btn-secondary fa fa-search ms-1"
|
||||
data-tooltip="Search in DaData"
|
||||
aria-label="Search in DaData"
|
||||
t-on-click="() => this.search()"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
2
dadata_connector/tests/__init__.py
Normal file
2
dadata_connector/tests/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import test_res_partner
|
||||
from . import test_wizard
|
||||
99
dadata_connector/tests/common.py
Normal file
99
dadata_connector/tests/common.py
Normal file
@ -0,0 +1,99 @@
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
# Minimal DaData API response fixture for a legal entity (ООО)
|
||||
DADATA_LEGAL_RESPONSE = [
|
||||
{
|
||||
"data": {
|
||||
"type": "LEGAL",
|
||||
"inn": "7707083893",
|
||||
"ogrn": "1027700132195",
|
||||
"kpp": "773601001",
|
||||
"okpo": "00032537",
|
||||
"okved": "64.19",
|
||||
"opf": {"code": "12300"},
|
||||
"name": {
|
||||
"short_with_opf": "ПАО Сбербанк",
|
||||
"full_with_opf": "Публичное акционерное общество «Сбербанк России»",
|
||||
},
|
||||
"state": {"status": "ACTIVE"},
|
||||
"address": {
|
||||
"unrestricted_value": "117997, г Москва, ул Вавилова, д 19",
|
||||
"data": {
|
||||
"country_iso_code": "RU",
|
||||
"region_iso_code": "RU-MOW",
|
||||
"city": "Москва",
|
||||
"street_with_type": "ул Вавилова",
|
||||
"house_type_full": "дом",
|
||||
"house": "19",
|
||||
"flat_type_full": None,
|
||||
"flat": None,
|
||||
"postal_code": "117997",
|
||||
},
|
||||
},
|
||||
"documents": {
|
||||
"fts_registration": {
|
||||
"series": "77",
|
||||
"number": "004599035",
|
||||
"issue_date": "2002-08-16",
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"name": "ГРЕФ ГЕРМАН ОСКАРОВИЧ",
|
||||
"post": "ПРЕЗИДЕНТ, ПРЕДСЕДАТЕЛЬ ПРАВЛЕНИЯ",
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# Minimal DaData API response fixture for an individual entrepreneur (ИП)
|
||||
DADATA_INDIVIDUAL_RESPONSE = [
|
||||
{
|
||||
"data": {
|
||||
"type": "INDIVIDUAL",
|
||||
"inn": "500100732259",
|
||||
"ogrn": "304500116000157",
|
||||
"kpp": None,
|
||||
"okpo": "0107544",
|
||||
"okved": "47.91",
|
||||
"opf": {"code": "50102"},
|
||||
"fio": {
|
||||
"surname": "ИВАНОВ",
|
||||
"name": "ИВАН",
|
||||
"patronymic": "ИВАНОВИЧ",
|
||||
},
|
||||
"state": {"status": "ACTIVE"},
|
||||
"address": {
|
||||
"unrestricted_value": "141001, Московская обл, г Мытищи",
|
||||
"data": {
|
||||
"country_iso_code": "RU",
|
||||
"region_iso_code": "RU-MOS",
|
||||
"city": "Мытищи",
|
||||
"street_with_type": None,
|
||||
"house_type_full": None,
|
||||
"house": None,
|
||||
"flat_type_full": None,
|
||||
"flat": None,
|
||||
"postal_code": "141001",
|
||||
},
|
||||
},
|
||||
"documents": {"fts_registration": None},
|
||||
"management": None,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class DadataConnectorCommon(TransactionCase):
|
||||
"""Base class for dadata_connector tests."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.partner = cls.env["res.partner"].create({
|
||||
"name": "Test Partner",
|
||||
"is_company": True,
|
||||
})
|
||||
cls.env["ir.config_parameter"].sudo().set_param(
|
||||
"dadata_connector.dadata_token", "test_token_123"
|
||||
)
|
||||
182
dadata_connector/tests/test_res_partner.py
Normal file
182
dadata_connector/tests/test_res_partner.py
Normal file
@ -0,0 +1,182 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
from .common import (
|
||||
DADATA_INDIVIDUAL_RESPONSE,
|
||||
DADATA_LEGAL_RESPONSE,
|
||||
DadataConnectorCommon,
|
||||
)
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestResPartnerDadata(DadataConnectorCommon):
|
||||
"""Tests for res.partner DaData integration methods."""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# get_dadata_token
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_get_dadata_token_returns_token(self):
|
||||
"""Token is returned when the system parameter is set."""
|
||||
token = self.partner.get_dadata_token()
|
||||
self.assertEqual(token, "test_token_123")
|
||||
|
||||
def test_get_dadata_token_raises_when_missing(self):
|
||||
"""ValidationError is raised when token is not configured."""
|
||||
self.env["ir.config_parameter"].sudo().set_param(
|
||||
"dadata_connector.dadata_token", ""
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
self.partner.get_dadata_token()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# _parse_dadata_response — legal entity
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_parse_legal_entity_basic_fields(self):
|
||||
"""Parsing a legal entity response fills basic partner fields."""
|
||||
wizard_data, result = self.partner._parse_dadata_response(DADATA_LEGAL_RESPONSE)
|
||||
|
||||
self.assertEqual(result["vat"], "7707083893")
|
||||
self.assertEqual(result["ogrn"], "1027700132195")
|
||||
self.assertEqual(result["kpp"], "773601001")
|
||||
self.assertEqual(result["okpo"], "00032537")
|
||||
self.assertEqual(result["arceat"], "64.19")
|
||||
self.assertEqual(result["name"], "ПАО Сбербанк")
|
||||
self.assertEqual(result["city"], "Москва")
|
||||
self.assertEqual(result["zip"], "117997")
|
||||
self.assertEqual(result["street"], "ул Вавилова, дом, 19")
|
||||
|
||||
def test_parse_legal_entity_company_form(self):
|
||||
"""OPF code 12300 maps to 'plc' company form."""
|
||||
_, result = self.partner._parse_dadata_response(DADATA_LEGAL_RESPONSE)
|
||||
self.assertEqual(result["company_form"], "plc")
|
||||
|
||||
def test_parse_legal_entity_wizard_data(self):
|
||||
"""Wizard data contains status, organization_type, name and address."""
|
||||
wizard_data, _ = self.partner._parse_dadata_response(DADATA_LEGAL_RESPONSE)
|
||||
|
||||
self.assertEqual(wizard_data["status"], "active")
|
||||
self.assertEqual(wizard_data["organization_type"], "legal")
|
||||
self.assertEqual(wizard_data["name"], "ПАО Сбербанк")
|
||||
self.assertIn("Вавилова", wizard_data["full_address"])
|
||||
|
||||
def test_parse_legal_entity_management(self):
|
||||
"""Management data is extracted when present."""
|
||||
_, result = self.partner._parse_dadata_response(DADATA_LEGAL_RESPONSE)
|
||||
|
||||
self.assertIn("management", result)
|
||||
self.assertEqual(result["management"]["manager_name"], "ГРЕФ ГЕРМАН ОСКАРОВИЧ")
|
||||
self.assertEqual(
|
||||
result["management"]["manager_position"],
|
||||
"ПРЕЗИДЕНТ, ПРЕДСЕДАТЕЛЬ ПРАВЛЕНИЯ",
|
||||
)
|
||||
|
||||
def test_parse_legal_entity_fts_registration(self):
|
||||
"""FTS registration series and number are concatenated correctly."""
|
||||
_, result = self.partner._parse_dadata_response(DADATA_LEGAL_RESPONSE)
|
||||
self.assertEqual(result["sp_register_number"], "77 004599035")
|
||||
self.assertEqual(result["sp_register_date"], "2002-08-16")
|
||||
|
||||
def test_parse_legal_entity_country_and_state(self):
|
||||
"""Country and state are resolved from ISO codes when they exist in DB."""
|
||||
russia = self.env["res.country"].search([("code", "=", "RU")], limit=1)
|
||||
if not russia:
|
||||
self.skipTest("Russia not found in res.country — l10n data not loaded")
|
||||
|
||||
_, result = self.partner._parse_dadata_response(DADATA_LEGAL_RESPONSE)
|
||||
self.assertEqual(result["country_id"][0], russia.id)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# _parse_dadata_response — individual entrepreneur
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_parse_individual_name(self):
|
||||
"""Individual entrepreneur name is built from fio parts."""
|
||||
_, result = self.partner._parse_dadata_response(DADATA_INDIVIDUAL_RESPONSE)
|
||||
self.assertEqual(result["name"], "ИВАНОВ ИВАН ИВАНОВИЧ")
|
||||
|
||||
def test_parse_individual_no_kpp(self):
|
||||
"""Individual entrepreneur response does not set kpp."""
|
||||
_, result = self.partner._parse_dadata_response(DADATA_INDIVIDUAL_RESPONSE)
|
||||
self.assertNotIn("kpp", result)
|
||||
|
||||
def test_parse_individual_no_management(self):
|
||||
"""No management key in result when management is absent."""
|
||||
_, result = self.partner._parse_dadata_response(DADATA_INDIVIDUAL_RESPONSE)
|
||||
self.assertNotIn("management", result)
|
||||
|
||||
def test_parse_individual_no_fts_registration(self):
|
||||
"""No sp_register_number when fts_registration is None."""
|
||||
_, result = self.partner._parse_dadata_response(DADATA_INDIVIDUAL_RESPONSE)
|
||||
self.assertNotIn("sp_register_number", result)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# get_legal_entity_data
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@patch("myaddons.dob.dadata_connector.models.res_partner.Dadata")
|
||||
def test_get_legal_entity_data_returns_action(self, MockDadata):
|
||||
"""get_legal_entity_data returns an act_window action when widget=True."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.find_by_id.return_value = DADATA_LEGAL_RESPONSE
|
||||
MockDadata.return_value = mock_instance
|
||||
|
||||
action = self.partner.get_legal_entity_data(vat="7707083893", widget=True)
|
||||
|
||||
self.assertEqual(action["type"], "ir.actions.act_window")
|
||||
self.assertEqual(action["res_model"], "res.partner.auto_data.wizard")
|
||||
self.assertEqual(action["target"], "new")
|
||||
|
||||
@patch("myaddons.dob.dadata_connector.models.res_partner.Dadata")
|
||||
def test_get_legal_entity_data_returns_dict_when_no_widget(self, MockDadata):
|
||||
"""get_legal_entity_data returns raw data dict when widget=False."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.find_by_id.return_value = DADATA_LEGAL_RESPONSE
|
||||
MockDadata.return_value = mock_instance
|
||||
|
||||
result = self.partner.get_legal_entity_data(vat="7707083893", widget=False)
|
||||
|
||||
self.assertIsInstance(result, dict)
|
||||
self.assertEqual(result["vat"], "7707083893")
|
||||
|
||||
@patch("myaddons.dob.dadata_connector.models.res_partner.Dadata")
|
||||
def test_get_legal_entity_data_raises_when_empty(self, MockDadata):
|
||||
"""ValidationError is raised when DaData returns no results."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.find_by_id.return_value = []
|
||||
MockDadata.return_value = mock_instance
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self.partner.get_legal_entity_data(vat="0000000000", widget=False)
|
||||
|
||||
@patch("myaddons.dob.dadata_connector.models.res_partner.Dadata")
|
||||
def test_get_legal_entity_data_raises_on_http_error(self, MockDadata):
|
||||
"""ValidationError is raised on HTTPStatusError from DaData."""
|
||||
from httpx import HTTPStatusError, Request, Response
|
||||
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.find_by_id.side_effect = HTTPStatusError(
|
||||
"401", request=MagicMock(spec=Request), response=MagicMock(spec=Response)
|
||||
)
|
||||
MockDadata.return_value = mock_instance
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self.partner.get_legal_entity_data(vat="7707083893", widget=False)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# _get_view
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_get_view_sets_dadata_search_widget_on_vat(self):
|
||||
"""_get_view replaces widget on vat field with dadata_search."""
|
||||
arch, _view = self.env["res.partner"]._get_view(view_type="form")
|
||||
vat_nodes = arch.xpath("//field[@name='vat']")
|
||||
for node in vat_nodes:
|
||||
self.assertEqual(
|
||||
node.get("widget"),
|
||||
"dadata_search",
|
||||
"vat field must use dadata_search widget",
|
||||
)
|
||||
47
dadata_connector/tests/test_wizard.py
Normal file
47
dadata_connector/tests/test_wizard.py
Normal file
@ -0,0 +1,47 @@
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
from .common import DadataConnectorCommon
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestResPartnerAutoDataWizard(DadataConnectorCommon):
|
||||
"""Tests for res.partner.auto_data.wizard."""
|
||||
|
||||
def _create_wizard(self, **kwargs):
|
||||
vals = {
|
||||
"partner_id": self.partner.id,
|
||||
"name": "ПАО Тест",
|
||||
"status": "active",
|
||||
"organization_type": "legal",
|
||||
"full_address": "117997, г Москва, ул Вавилова, д 19",
|
||||
}
|
||||
vals.update(kwargs)
|
||||
return self.env["res.partner.auto_data.wizard"].create(vals)
|
||||
|
||||
def test_wizard_creation(self):
|
||||
"""Wizard record is created with expected field values."""
|
||||
wizard = self._create_wizard()
|
||||
self.assertEqual(wizard.name, "ПАО Тест")
|
||||
self.assertEqual(wizard.status, "active")
|
||||
self.assertEqual(wizard.organization_type, "legal")
|
||||
self.assertEqual(wizard.partner_id, self.partner)
|
||||
|
||||
def test_button_yes_returns_close_action(self):
|
||||
"""button_yes returns act_window_close with update flag."""
|
||||
wizard = self._create_wizard()
|
||||
result = wizard.button_yes()
|
||||
|
||||
self.assertEqual(result["type"], "ir.actions.act_window_close")
|
||||
self.assertTrue(result["infos"]["update"])
|
||||
|
||||
def test_wizard_status_selection_values(self):
|
||||
"""All expected status values are accepted."""
|
||||
statuses = ["active", "liquidating", "liquidated", "bankrupt", "reorganizing"]
|
||||
for status in statuses:
|
||||
wizard = self._create_wizard(status=status)
|
||||
self.assertEqual(wizard.status, status)
|
||||
|
||||
def test_wizard_organization_type_individual(self):
|
||||
"""Wizard accepts individual organization type."""
|
||||
wizard = self._create_wizard(organization_type="individual")
|
||||
self.assertEqual(wizard.organization_type, "individual")
|
||||
16
dadata_connector/views/res_config_settings_view.xml
Normal file
16
dadata_connector/views/res_config_settings_view.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="dadata_res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.dadata</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="15" />
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//setting[@id='recaptcha']" position="after">
|
||||
<setting id="dadata_token" help="Dadata token value">
|
||||
<field name="dadata_token" />
|
||||
</setting>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
28
dadata_connector/views/res_partner_views.xml
Normal file
28
dadata_connector/views/res_partner_views.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
|
||||
<record id="partner_with_auto_data_view_form" model="ir.ui.view">
|
||||
<field name="name">Partner with auto data view form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="l10n_ru_doc.view_partner_ru_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='ogrn']" position="attributes">
|
||||
<attribute name="widget">dadata_search</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="partner_with_auto_data_view_form2" model="ir.ui.view">
|
||||
<field name="name">Partner with auto data view form 2</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="priority">2</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- vat widget is set programmatically via _get_view to override partner_autocomplete -->
|
||||
<xpath expr="//field[@name='vat']" position="attributes">
|
||||
<attribute name="widget">dadata_search</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
1
dadata_connector/wizard/__init__.py
Normal file
1
dadata_connector/wizard/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import res_partner_auto_data_wizard
|
||||
40
dadata_connector/wizard/res_partner_auto_data_wizard.py
Normal file
40
dadata_connector/wizard/res_partner_auto_data_wizard.py
Normal file
@ -0,0 +1,40 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartnerAutoDataWizard(models.TransientModel):
|
||||
_name = "res.partner.auto_data.wizard"
|
||||
_description = "Wizard for autofilling partner"
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
string="Partner",
|
||||
comodel_name="res.partner",
|
||||
)
|
||||
|
||||
vat = fields.Char(
|
||||
string="Identification Number", help="Identification Number for selected type"
|
||||
)
|
||||
|
||||
status = fields.Selection(
|
||||
string="Status",
|
||||
selection=[
|
||||
("active", "Active"),
|
||||
("liquidating", "Liquidating"),
|
||||
("liquidated", "Liquidated"),
|
||||
("bankrupt", "Bankrupt"),
|
||||
("reorganizing", "Reorganizing"),
|
||||
],
|
||||
)
|
||||
organization_type = fields.Selection(
|
||||
string="Type of organization",
|
||||
selection=[
|
||||
("legal", "Legal entity"),
|
||||
("individual", "Individual entrepreneur"),
|
||||
],
|
||||
help="Legal entity or individual entrepreneur",
|
||||
)
|
||||
name = fields.Char(string="Full name")
|
||||
|
||||
full_address = fields.Text(string="Full legal address")
|
||||
|
||||
def button_yes(self):
|
||||
return {"type": "ir.actions.act_window_close", "infos": {"update": True}}
|
||||
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_partner_auto_data_wizard_view_form" model="ir.ui.view">
|
||||
<field name="name">Partner with auto data wizard view form</field>
|
||||
<field name="model">res.partner.auto_data.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form create="0" delete="0" edit="0">
|
||||
<group>
|
||||
<field name="status"
|
||||
decoration-success="status == 'active'"
|
||||
decoration-danger="status != 'active'"/>
|
||||
<field name="organization_type"/>
|
||||
<field name="name"/>
|
||||
<field name="full_address"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Yes" class="oe_highlight" name="button_yes" type="object"/>
|
||||
<button string="No" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user