commit 5ec2d7a22881e04337d40eec27d681aa257db36d Author: odoo-robot Date: Tue Feb 25 08:56:37 2025 +0000 automatic update repo with commit - 'Update .gitlab-ci.yml' from project dadata_connector diff --git a/README.md b/README.md new file mode 100644 index 0000000..660e4d3 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# DaData Connector +Obtaining data on legal entities from the DaData service. \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..9b42961 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..3bd79ce --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,21 @@ +{ + "name": "DaData Connector", + "summary": """Obtaining data on legal entities from the DaData service""", + "author": "RYDLAB", + "website": "http://rydlab.ru", + "category": "Marketing", + "version": "17.0.1.0.0", + "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/*", + ], + }, +} diff --git a/i18n/ru.po b/i18n/ru.po new file mode 100644 index 0000000..a075dd3 --- /dev/null +++ b/i18n/ru.po @@ -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 16.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 "Да" diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..449998f --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_config_settings +from . import res_partner diff --git a/models/res_config_settings.py b/models/res_config_settings.py new file mode 100644 index 0000000..776a6fb --- /dev/null +++ b/models/res_config_settings.py @@ -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", + ) diff --git a/models/res_partner.py b/models/res_partner.py new file mode 100644 index 0000000..934580d --- /dev/null +++ b/models/res_partner.py @@ -0,0 +1,162 @@ +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" + + 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"]) + if result["company_form"] == okopf["50102"]: + result["psrn_sp"] = data["ogrn"] + result["ogrn"] = "" + else: + result["psrn_sp"] = "" + 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, country.name) + + 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, region.name) + + 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 diff --git a/security/ir.model.access.csv b/security/ir.model.access.csv new file mode 100644 index 0000000..18b95d5 --- /dev/null +++ b/security/ir.model.access.csv @@ -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 diff --git a/static/src/views/fields/search/search_field.js b/static/src/views/fields/search/search_field.js new file mode 100644 index 0000000..878baee --- /dev/null +++ b/static/src/views/fields/search/search_field.js @@ -0,0 +1,121 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { _lt } from "@web/core/l10n/translation"; +import { useInputField } from "@web/views/fields/input_field_hook"; +import { standardFieldProps } from "@web/views/fields/standard_field_props"; + +import { Component } from "@odoo/owl"; + +export class SearchField extends Component { + setup() { + useInputField({ getValue: () => this.props.record.data[this.props.name] || "" }); + this.action = null; + } + + async search() { + this.action = await this.env.services.orm.call( + "res.partner", + "get_legal_entity_data", + [this.env.model.root.data.id], + { + vat: this.props.record.data[this.props.name], + } + ); + await this.env.services.action.doAction(this.action, { + onClose: async (closeInfo) => { + if (closeInfo && closeInfo.update) { + // Creates record + const record = this.props.record; + const { management, ...newRecordData } = this.action.context; + 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 this.env.model.load({ + resId: record._config.resId, + mode: record.mode, + }); + + + this.env.services.notification.add(_lt("Data updated."), { + type: "info", + }); + } + }, + }); + } + + _checkManagerExists(recordChildren, management) { + const managerName = management.manager_name; + const managerFunction = management.manager_position; + for (let record of recordChildren) { + if ( + record.data.name.toUpperCase() === managerName.toUpperCase() && + record.data.function.toUpperCase() === managerFunction.toUpperCase() + ) + return true; + } + return null; + } + + async _createManager(management) { + const record = this.props.record; + const point = this.env.model; + + const paren_id = record._config.resId + const params = { + resModel: "res.partner", + resIds: [], + fields: record.data.child_ids.fields, + activeFields: record.data.child_ids.activeFields, + context: { + default_name: management.manager_name, + default_function: management.manager_position, + default_parent_id: paren_id, + default_type: "contact", + }, + mode: "edit", + viewType: "form", + isMonoRecord: true, + groupBy: [], + }; + const newRecordData = await point._loadNewRecord(params); + const newRecord = await point._createRoot(params, newRecordData); + await newRecord.save(); + } +} + + + +SearchField.template = "dadata_connector.SearchField"; +SearchField.props = { + ...standardFieldProps, + placeholder: { type: String, optional: true }, +}; + +SearchField.displayName = _lt("Search"); +SearchField.supportedTypes = ["char"]; + +SearchField.extractProps = ({ attrs }) => { + return { + placeholder: attrs.placeholder, + }; +}; + +class FormSearchField extends SearchField { } +FormSearchField.template = "dadata_connector.FormSearchField"; + +registry.category("fields").add("search", { component: SearchField }); +registry.category("fields").add("form.search", { component: FormSearchField }); + diff --git a/static/src/views/fields/search/search_field.scss b/static/src/views/fields/search/search_field.scss new file mode 100644 index 0000000..040086f --- /dev/null +++ b/static/src/views/fields/search/search_field.scss @@ -0,0 +1,4 @@ +.o_search_content small { + overflow-wrap: normal; + word-break: normal; +} diff --git a/static/src/views/fields/search/search_field.xml b/static/src/views/fields/search/search_field.xml new file mode 100644 index 0000000..1e9173b --- /dev/null +++ b/static/src/views/fields/search/search_field.xml @@ -0,0 +1,31 @@ + + + + + + + + + +