From dffad1e8e7cbc92b42449685461936630e6b1d07 Mon Sep 17 00:00:00 2001 From: Carlos Dauden Date: Thu, 24 Oct 2019 00:09:49 +0200 Subject: [PATCH] =?UTF-8?q?[IMP]=20l10n=5Fes=5Faeat:=20A=C3=B1adir=20campo?= =?UTF-8?q?=20aeat=5Fanonymous=5Fcash=5Fcustomer=20y=20funcion=20parse=5Fv?= =?UTF-8?q?at=5Finfo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- l10n_es_aeat/__manifest__.py | 2 + l10n_es_aeat/i18n/es.po | 123 ++++++++------------- l10n_es_aeat/i18n/l10n_es_aeat.pot | 19 ++++ l10n_es_aeat/models/__init__.py | 2 +- l10n_es_aeat/models/l10n_es_aeat_report.py | 3 +- l10n_es_aeat/models/res_partner.py | 53 +++++++++ l10n_es_aeat/tests/test_l10n_es_aeat.py | 71 ++++++++++++ l10n_es_aeat/views/res_partner_view.xml | 14 +++ 8 files changed, 208 insertions(+), 79 deletions(-) create mode 100644 l10n_es_aeat/models/res_partner.py create mode 100644 l10n_es_aeat/views/res_partner_view.xml diff --git a/l10n_es_aeat/__manifest__.py b/l10n_es_aeat/__manifest__.py index ac1ac266479..058b151f26b 100644 --- a/l10n_es_aeat/__manifest__.py +++ b/l10n_es_aeat/__manifest__.py @@ -5,6 +5,7 @@ # Copyright 2016 Antonio Espinosa # Copyright 2013-2018 Pedro M. Baeza # Copyright 2018 Juan Vicente Pascual +# Copyright 2019 Tecnativa - Carlos Dauden # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl { @@ -41,6 +42,7 @@ 'views/aeat_tax_code_mapping_view.xml', 'views/account_move_line_view.xml', 'views/report_template.xml', + 'views/res_partner_view.xml', ], 'installable': True, } diff --git a/l10n_es_aeat/i18n/es.po b/l10n_es_aeat/i18n/es.po index 20cc7e00500..2baf05ba34a 100644 --- a/l10n_es_aeat/i18n/es.po +++ b/l10n_es_aeat/i18n/es.po @@ -8,16 +8,16 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-10 03:41+0000\n" -"PO-Revision-Date: 2019-02-04 16:50+0000\n" -"Last-Translator: Marta Vázquez Rodríguez \n" +"POT-Creation-Date: 2019-10-24 00:59+0200\n" +"PO-Revision-Date: 2019-10-24 01:01+0200\n" +"Last-Translator: Carlos Dauden \n" "Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.4\n" +"X-Generator: Poedit 1.8.7.1\n" #. module: l10n_es_aeat #: code:addons/l10n_es_aeat/wizard/export_to_boe.py:142 @@ -60,12 +60,8 @@ msgstr "Responsable AEAT" #. module: l10n_es_aeat #: code:addons/l10n_es_aeat/models/l10n_es_aeat_report.py:261 #, python-format -msgid "" -"AEAT model sequence not found. You can try to restart your Odoo service for " -"recreating the sequences." -msgstr "" -"No se ha encontrado la secuencia del modelo AEAT. Puede intentar reiniciar " -"su servicio Odoo para recrear las secuencias." +msgid "AEAT model sequence not found. You can try to restart your Odoo service for recreating the sequences." +msgstr "No se ha encontrado la secuencia del modelo AEAT. Puede intentar reiniciar su servicio Odoo para recrear las secuencias." #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.view_l10n_es_aeat_report_form @@ -109,7 +105,7 @@ msgstr "Asiento contable" #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_aeat_model_export_config_active msgid "Active" -msgstr "" +msgstr "Activo" #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.aeat_model_export_config_line_form @@ -159,6 +155,12 @@ msgstr "Alfanumérico" msgid "Amount" msgstr "Importe" +#. module: l10n_es_aeat +#: model:ir.model.fields,field_description:l10n_es_aeat.field_res_partner_aeat_anonymous_cash_customer +#: model:ir.model.fields,field_description:l10n_es_aeat.field_res_users_aeat_anonymous_cash_customer +msgid "AEAT - Anonymous customer" +msgstr "AEAT - Cliente anónimo" + #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_aeat_model_export_config_line_apply_sign msgid "Apply sign" @@ -220,7 +222,7 @@ msgstr "Booleano" #. module: l10n_es_aeat #: selection:l10n.es.aeat.map.tax.line,field_type:0 msgid "Both" -msgstr "" +msgstr "Ambos" #. module: l10n_es_aeat #: selection:l10n.es.aeat.map.tax.line,sum_type:0 @@ -283,6 +285,12 @@ msgstr "Cancelada" msgid "Cancelled models" msgstr "Declaraciones canceladas" +#. module: l10n_es_aeat +#: model:ir.model.fields,help:l10n_es_aeat.field_res_partner_aeat_anonymous_cash_customer +#: model:ir.model.fields,help:l10n_es_aeat.field_res_users_aeat_anonymous_cash_customer +msgid "Check this for anonymous cash customer. AEAT communication" +msgstr "Marque esto para clientes de caja anónimos. Comunicación con AEAT" + #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_aeat_export #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_compare_boe_file @@ -291,10 +299,8 @@ msgstr "Cerrar" #. module: l10n_es_aeat #: model:ir.model,name:l10n_es_aeat.model_res_company -#, fuzzy -#| msgid "Company" msgid "Companies" -msgstr "Compañía" +msgstr "Compañías" #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_l10n_es_aeat_mod111_report_company_id @@ -399,6 +405,11 @@ msgstr "Confirmar" msgid "Confirmed models" msgstr "Declaraciones confirmadas" +#. module: l10n_es_aeat +#: model:ir.model,name:l10n_es_aeat.model_res_partner +msgid "Contact" +msgstr "Contacto" + #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.view_l10n_es_aeat_report_form msgid "Contact data" @@ -601,19 +612,12 @@ msgstr "Fecha final" #: code:addons/l10n_es_aeat/models/l10n_es_aeat_map_tax.py:45 #, python-format msgid "Error! The dates of the record overlap with an existing record." -msgstr "" -"Error! Las fechas de los registros se solapan con un registro existente." +msgstr "Error! Las fechas de los registros se solapan con un registro existente." #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_compare_boe_file -msgid "" -"Escoja el archivo para comparar con el actual formato de exportación. NOTA: " -"Solo es válido de momento para formatos sin partes condicionales ni " -"subpartes con bucle de repetición." -msgstr "" -"Escoja el archivo para comparar con el formato actual de exportación. NOTA: " -"Por el momento sólo es válido para formatos sin partes condicionales ni sub-" -"partes con bucle de repetición." +msgid "Escoja el archivo para comparar con el actual formato de exportación. NOTA: Solo es válido de momento para formatos sin partes condicionales ni subpartes con bucle de repetición." +msgstr "Escoja el archivo para comparar con el formato actual de exportación. NOTA: Por el momento sólo es válido para formatos sin partes condicionales ni sub-partes con bucle de repetición." #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_l10n_es_aeat_map_tax_line_exigible_type @@ -771,30 +775,19 @@ msgstr "ID" #. module: l10n_es_aeat #: model:ir.model.fields,help:l10n_es_aeat.field_aeat_model_export_config_line_repeat_expression -msgid "" -"If set, this expression will be used for getting the list of elements to " -"iterate on" -msgstr "" -"Si está establecida, esta expresión se usará para obtener una lista de los " -"elementos por los que iterar" +msgid "If set, this expression will be used for getting the list of elements to iterate on" +msgstr "Si está establecida, esta expresión se usará para obtener una lista de los elementos por los que iterar" #. module: l10n_es_aeat #: model:ir.model.fields,help:l10n_es_aeat.field_aeat_model_export_config_line_conditional_expression -msgid "" -"If set, this expression will be used to evaluate if this line should be added" -msgstr "" -"Si está establecida, esta expresión será usada para evaluar si la línea debe " -"ser añadida" +msgid "If set, this expression will be used to evaluate if this line should be added" +msgstr "Si está establecida, esta expresión será usada para evaluar si la línea debe ser añadida" #. module: l10n_es_aeat #: code:addons/l10n_es_aeat/models/l10n_es_aeat_report.py:200 #, python-format -msgid "" -"If this declaration is complementary or substitutive, a previous declaration " -"number should be provided." -msgstr "" -"Si esta declaración es complementaria o substitutiva, debe proporcionar el " -"número de la declaración anterior." +msgid "If this declaration is complementary or substitutive, a previous declaration number should be provided." +msgstr "Si esta declaración es complementaria o substitutiva, debe proporcionar el número de la declaración anterior." #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.view_l10n_es_aeat_report_search @@ -808,11 +801,8 @@ msgstr "Declaraciones en proceso" #. module: l10n_es_aeat #: model:ir.model,name:l10n_es_aeat.model_l10n_es_aeat_report_tax_mapping -msgid "" -"Inheritable abstract model to add taxes by code mapping in any AEAT report" -msgstr "" -"Modelo abstracto heredable para añadir impuestos por mapeo de código in " -"cualquier declaración AEAT" +msgid "Inheritable abstract model to add taxes by code mapping in any AEAT report" +msgstr "Modelo abstracto heredable para añadir impuestos por mapeo de código in cualquier declaración AEAT" #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_l10n_es_aeat_map_tax_line_inverse @@ -1089,8 +1079,7 @@ msgstr "Sólo cantidades no exigibles" #: code:addons/l10n_es_aeat/models/l10n_es_aeat_report.py:368 #, python-format msgid "Only reports in 'draft' or 'cancelled' state can be removed" -msgstr "" -"Sólo los informes en estado 'borrador' o 'cancelado' pueden ser eliminados" +msgstr "Sólo los informes en estado 'borrador' o 'cancelado' pueden ser eliminados" #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_l10n_es_aeat_map_tax_line_move_type @@ -1155,16 +1144,8 @@ msgstr "Teléfono" #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_aeat_export -msgid "" -"Ponga este archivo dentro de su carpeta personal de la AEAT, y úselo en el " -"programa Informativas o pulsando en el botón " -"Optativo: Importar datos de fichero en el formulario on-" -"line." -msgstr "" -"Ponga este archivo dentro de su carpeta personal de la AEAT, y úselo en el " -"programa Informativas o pulsando en el botón " -"Optativo: Importar datos de fichero en el formulario on-" -"line." +msgid "Ponga este archivo dentro de su carpeta personal de la AEAT, y úselo en el programa Informativas o pulsando en el botón Optativo: Importar datos de fichero en el formulario on-line." +msgstr "Ponga este archivo dentro de su carpeta personal de la AEAT, y úselo en el programa Informativas o pulsando en el botón Optativo: Importar datos de fichero en el formulario on-line." #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_aeat_model_export_config_line_position @@ -1229,12 +1210,8 @@ msgstr "Procesada" #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_aeat_export -msgid "" -"Pulse el botón Exportar para iniciar el proceso de " -"exportación del archivo BOE de la AEAT." -msgstr "" -"Pulse el botón Exportar para iniciar el proceso de " -"exportación del archivo BOE de la AEAT." +msgid "Pulse el botón Exportar para iniciar el proceso de exportación del archivo BOE de la AEAT." +msgstr "Pulse el botón Exportar para iniciar el proceso de exportación del archivo BOE de la AEAT." #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.view_l10n_es_aeat_report_form @@ -1498,18 +1475,13 @@ msgstr "La cadena formateada debe satisfacer el tamaño dado." #: model:ir.model.fields,help:l10n_es_aeat.field_l10n_es_aeat_report_counterpart_account_id #: model:ir.model.fields,help:l10n_es_aeat.field_l10n_es_aeat_report_tax_mapping_counterpart_account_id #: model:ir.model.fields,help:l10n_es_aeat.field_l10n_es_vat_book_counterpart_account_id -msgid "" -"This account will be the counterpart for all the journal items that are " -"regularized when posting the report." -msgstr "" -"Esta cuenta será la contrapartida para todos los elementos del diario que " -"están regularizados al contabilizar el informe." +msgid "This account will be the counterpart for all the journal items that are regularized when posting the report." +msgstr "Esta cuenta será la contrapartida para todos los elementos del diario que están regularizados al contabilizar el informe." #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.view_l10n_es_aeat_report_form msgid "This button creates the regularization move for the selected report" -msgstr "" -"Este botón crea el movimiento de regularización para el informe seleccionado" +msgstr "Este botón crea el movimiento de regularización para el informe seleccionado" #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_l10n_es_aeat_map_tax_date_to @@ -1639,6 +1611,3 @@ msgstr "abierto" #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_aeat_export msgid "or" msgstr "o" - -#~ msgid "Exception message" -#~ msgstr "Mensaje de excepción" diff --git a/l10n_es_aeat/i18n/l10n_es_aeat.pot b/l10n_es_aeat/i18n/l10n_es_aeat.pot index aac8cdfba33..6eb99afd2f0 100644 --- a/l10n_es_aeat/i18n/l10n_es_aeat.pot +++ b/l10n_es_aeat/i18n/l10n_es_aeat.pot @@ -6,6 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-23 22:58+0000\n" +"PO-Revision-Date: 2019-10-23 22:58+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -149,6 +151,12 @@ msgstr "" msgid "Amount" msgstr "" +#. module: l10n_es_aeat +#: model:ir.model.fields,field_description:l10n_es_aeat.field_res_partner_aeat_anonymous_cash_customer +#: model:ir.model.fields,field_description:l10n_es_aeat.field_res_users_aeat_anonymous_cash_customer +msgid "AEAT - Anonymous customer" +msgstr "" + #. module: l10n_es_aeat #: model:ir.model.fields,field_description:l10n_es_aeat.field_aeat_model_export_config_line_apply_sign msgid "Apply sign" @@ -273,6 +281,12 @@ msgstr "" msgid "Cancelled models" msgstr "" +#. module: l10n_es_aeat +#: model:ir.model.fields,help:l10n_es_aeat.field_res_partner_aeat_anonymous_cash_customer +#: model:ir.model.fields,help:l10n_es_aeat.field_res_users_aeat_anonymous_cash_customer +msgid "Check this for anonymous cash customer. AEAT communication" +msgstr "" + #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_aeat_export #: model:ir.ui.view,arch_db:l10n_es_aeat.wizard_compare_boe_file @@ -387,6 +401,11 @@ msgstr "" msgid "Confirmed models" msgstr "" +#. module: l10n_es_aeat +#: model:ir.model,name:l10n_es_aeat.model_res_partner +msgid "Contact" +msgstr "" + #. module: l10n_es_aeat #: model:ir.ui.view,arch_db:l10n_es_aeat.view_l10n_es_aeat_report_form msgid "Contact data" diff --git a/l10n_es_aeat/models/__init__.py b/l10n_es_aeat/models/__init__.py index 4b2d2c6a5f7..d16f89e02f1 100644 --- a/l10n_es_aeat/models/__init__.py +++ b/l10n_es_aeat/models/__init__.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import res_company +from . import res_partner from . import res_partner_bank from . import l10n_es_aeat_map_tax from . import l10n_es_aeat_map_tax_line diff --git a/l10n_es_aeat/models/l10n_es_aeat_report.py b/l10n_es_aeat/models/l10n_es_aeat_report.py index f9bfa298d09..4e8f98a7634 100644 --- a/l10n_es_aeat/models/l10n_es_aeat_report.py +++ b/l10n_es_aeat/models/l10n_es_aeat_report.py @@ -129,7 +129,8 @@ def _default_export_config_id(self): ('posted', 'Posted'), ('cancelled', 'Cancelled'), ], string='State', default='draft', readonly=True) - name = fields.Char(string="Report identifier", size=13, oldname='sequence') + name = fields.Char(string="Report identifier", size=13, oldname='sequence', + copy=False) model_id = fields.Many2one( comodel_name="ir.model", string="Model", compute='_compute_report_model', oldname='model') diff --git a/l10n_es_aeat/models/res_partner.py b/l10n_es_aeat/models/res_partner.py new file mode 100644 index 00000000000..644b8605c13 --- /dev/null +++ b/l10n_es_aeat/models/res_partner.py @@ -0,0 +1,53 @@ +# Copyright 2019 Tecnativa - Carlos Dauden +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models +from odoo.tools import ormcache + + +class ResPartner(models.Model): + _inherit = "res.partner" + + aeat_anonymous_cash_customer = fields.Boolean( + string='AEAT - Anonymous customer', + help='Check this for anonymous cash customer. AEAT communication', + ) + + def map_country_code(self, country_code): + country_code_map = { + 'RE': 'FR', + 'GP': 'FR', + 'MQ': 'FR', + 'GF': 'FR', + 'EL': 'GR', + } + return country_code_map.get(country_code, country_code) + + @ormcache('self.env') + def get_europe_codes(self): + europe = self.env.ref('base.europe', raise_if_not_found=False) + if not europe: + europe = self.env["res.country.group"].search( + [('name', '=', 'Europe')], limit=1) + return europe.country_ids.mapped('code') + + def parse_vat_info(self): + """ """ + self.ensure_one() + identifier_type = '' + vat_number = self.vat and self.vat or '' + prefix = self.map_country_code(vat_number[:2]) + if prefix in self.get_europe_codes(): + country_code = prefix + vat_number = vat_number[2:] + if country_code != 'ES': + identifier_type = '02' + return country_code, identifier_type, vat_number + # ¿Se podria establecer por defecto el valor 'ES' cuando no hay país? + country_code = self.map_country_code(self.country_id.code) or '' + if country_code in self.get_europe_codes(): + if country_code != 'ES': + identifier_type = '02' + else: + identifier_type = '04' + return country_code, identifier_type, vat_number diff --git a/l10n_es_aeat/tests/test_l10n_es_aeat.py b/l10n_es_aeat/tests/test_l10n_es_aeat.py index 13c7885d152..b282c7e36a9 100644 --- a/l10n_es_aeat/tests/test_l10n_es_aeat.py +++ b/l10n_es_aeat/tests/test_l10n_es_aeat.py @@ -8,9 +8,80 @@ class TestL10nEsAeat(common.TransactionCase): def setUp(self): super(TestL10nEsAeat, self).setUp() self.export_model = self.env["l10n.es.aeat.report.export_to_boe"] + self.partner = self.env['res.partner'].create({ + 'name': 'test partner', + }) def test_format_string(self): text = " &'(),-./01:;abAB_ÇÑ\"áéíóúÁÉÍÓÚ+!" self.assertEqual( self.export_model._format_string(text, len(text)), " &'(),-./01:;ABAB_ÇÑAEIOUAEIOU ".encode('iso-8859-1')) + + def test_parse_vat_info_es_wo_prefix(self): + self.partner.vat = '12345678Z' + self.partner.country_id = self.env.ref('base.es') + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, 'ES') + self.assertEqual(identifier_type, '') + self.assertEqual(vat_number, '12345678Z') + + def test_parse_vat_info_es_w_prefix(self): + self.partner.vat = 'ES12345678Z' + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, 'ES') + self.assertEqual(identifier_type, '') + self.assertEqual(vat_number, '12345678Z') + + def test_parse_vat_info_fr_wo_prefix(self): + self.partner.vat = '61954506077' + self.partner.country_id = self.env.ref('base.fr') + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, 'FR') + self.assertEqual(identifier_type, '02') + self.assertEqual(vat_number, '61954506077') + + def test_parse_vat_info_fr_w_prefix(self): + self.partner.vat = 'FR61954506077' + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, 'FR') + self.assertEqual(identifier_type, '02') + self.assertEqual(vat_number, '61954506077') + + def test_parse_vat_info_gf_wo_prefix(self): + self.partner.vat = '61954506077' + self.partner.country_id = self.env.ref('base.gf') + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, 'FR') + self.assertEqual(identifier_type, '02') + self.assertEqual(vat_number, '61954506077') + + def test_parse_vat_info_gf_w_prefix(self): + self.partner.vat = 'GF61954506077' + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, 'FR') + self.assertEqual(identifier_type, '02') + self.assertEqual(vat_number, '61954506077') + + def test_parse_vat_info_cu_wo_prefix(self): + self.partner.vat = '12345678Z' + self.partner.country_id = self.env.ref('base.cu') + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, 'CU') + self.assertEqual(identifier_type, '04') + self.assertEqual(vat_number, '12345678Z') + + def test_parse_vat_info_cu_w_prefix(self): + self.partner.vat = 'CU12345678Z' + country_code, identifier_type, vat_number = ( + self.partner.parse_vat_info()) + self.assertEqual(country_code, '') + self.assertEqual(identifier_type, '04') + self.assertEqual(vat_number, 'CU12345678Z') diff --git a/l10n_es_aeat/views/res_partner_view.xml b/l10n_es_aeat/views/res_partner_view.xml new file mode 100644 index 00000000000..e09b017047c --- /dev/null +++ b/l10n_es_aeat/views/res_partner_view.xml @@ -0,0 +1,14 @@ + + + + + res.partner + + + + + + + + +