diff --git a/partner_firstname/README.rst b/partner_firstname/README.rst index 7c9bf10e9..224535347 100644 --- a/partner_firstname/README.rst +++ b/partner_firstname/README.rst @@ -1,12 +1,33 @@ .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 +================================ Partner first name and last name ================================ This module was written to extend the functionality of contacts to support having separate last name and first name. +Configuration +============= + +You can configure some common name patterns for the inverse function +in Settings > Configuration > General settings: + +* Lastname Firstname: For example 'Anderson Robert' +* Lastname, Firstname: For example 'Anderson, Robert' +* Firstname Lastname: For example 'Robert Anderson' + +After applying the changes, you can recalculate all partners name clicking +"Recalculate names" button. Note: This process could take so much time depending +how many partners there are in database. + +You can use *_get_inverse_name* method to get lastname and firstname from a simple string +and also *_get_computed_name* to get a name form the lastname and firstname. +These methods can be overridden to change the format specified above. + + Usage ===== @@ -16,22 +37,40 @@ and the *first name*. This avoids breaking compatibility with other modules. Users should fulfill manually the separate fields for *last name* and *first name*, but in case you edit just the *name* field in some unexpected module, there is an inverse function that tries to split that automatically. It assumes -that you write the *name* in format *"Lastname Firstname"*, but it could lead to -wrong splitting (because it's just blindly trying to guess what you meant), so -you better specify it manually. +that you write the *name* in format configured (*"Lastname Firstname"*, by default), +but it could lead to wrong splitting (because it's just blindly trying to +guess what you meant), so you better specify it manually. For the same reason, after installing, previous names for contacts will stay in the *name* field, and the first time you edit any of them you will be asked to supply the *last name* and *first name* (just once per contact). -You can use *_get_inverse_name* method to get lastname and firstname from a simple string -and also *_get_computed_name* to get a name form the lastname and firstname. -These methods can be overridden to change the format specified above +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/134/8.0 For further information, please visit: * https://www.odoo.com/forum/help-1 + +Known issues / Roadmap +====================== + +Patterns for the inverse function are configurable only at system level. Maybe +this configuration could depend on partner language, country or company, +as discussed at `this OCA issue `_ + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback `here +`_. + Credits ======= @@ -61,6 +100,7 @@ Translations * Danish: Hans Henrik Gabelgaard * Italian: Leonardo Donelli * Spanish: Antonio Espinosa +* Antonio Espinosa Maintainer ---------- diff --git a/partner_firstname/__manifest__.py b/partner_firstname/__manifest__.py index 086f313b0..c14de355c 100644 --- a/partner_firstname/__manifest__.py +++ b/partner_firstname/__manifest__.py @@ -7,17 +7,19 @@ { 'name': 'Partner first name and last name', 'summary': "Split first name and last name for non company partners", - 'version': '10.0.1.0.0', + 'version': '10.0.2.0.0', 'author': "Camptocamp, " "Grupo ESOC Ingeniería de Servicios, " "Odoo Community Association (OCA)", 'license': "AGPL-3", 'maintainer': 'Camptocamp, Acsone', 'category': 'Extra Tools', - 'website': - 'http://www.camptocamp.com, http://www.acsone.eu, http://grupoesoc.es', - 'depends': ['base'], + 'website': 'http://www.camptocamp.com, ' + 'http://www.acsone.eu, ' + 'http://grupoesoc.es', + 'depends': ['base_setup'], 'data': [ + 'views/base_config_view.xml', 'views/res_partner.xml', 'views/res_user.xml', 'data/res_partner.yml', diff --git a/partner_firstname/models/__init__.py b/partner_firstname/models/__init__.py index 95688f355..aa04cb891 100644 --- a/partner_firstname/models/__init__.py +++ b/partner_firstname/models/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -# © 2013 Nicolas Bessi (Camptocamp SA) +# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import base_config_settings from . import res_partner -from . import res_user diff --git a/partner_firstname/models/base_config_settings.py b/partner_firstname/models/base_config_settings.py new file mode 100644 index 000000000..e79b9d5dd --- /dev/null +++ b/partner_firstname/models/base_config_settings.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +from openerp import models, fields, api +_logger = logging.getLogger(__name__) + + +class BaseConfigSettings(models.TransientModel): + _inherit = 'base.config.settings' + + partner_names_order = fields.Selection( + string="Partner names order", + selection="_partner_names_order_selection", + help="Order to compose partner fullname", + required=True) + partner_names_order_changed = fields.Boolean( + readonly=True, compute="_compute_names_order_changed") + + def _partner_names_order_selection(self): + return [ + ('last_first', 'Lastname Firstname'), + ('last_first_comma', 'Lastname, Firstname'), + ('first_last', 'Firstname Lastname'), + ] + + def _partner_names_order_default(self): + return self.env['res.partner']._names_order_default() + + @api.multi + def get_default_partner_names_order(self): + return { + 'partner_names_order': self.env['ir.config_parameter'].get_param( + 'partner_names_order', self._partner_names_order_default()), + } + + @api.multi + def _compute_names_order_changed(self): + current = self.env['ir.config_parameter'].get_param( + 'partner_names_order', self._partner_names_order_default()) + return self.partner_names_order != current + + @api.onchange('partner_names_order') + def _onchange_partner_names_order(self): + self.partner_names_order_changed = self._compute_names_order_changed() + + @api.multi + def set_partner_names_order(self): + self.env['ir.config_parameter'].set_param( + 'partner_names_order', self.partner_names_order) + + @api.multi + def _partners_for_recalculating(self): + return self.env['res.partner'].search([ + ('is_company', '=', False), + ('firstname', '!=', False), ('lastname', '!=', False), + ]) + + @api.multi + def action_recalculate_partners_name(self): + partners = self._partners_for_recalculating() + _logger.info("Recalculating names for %d partners.", len(partners)) + partners._compute_name() + _logger.info("%d partners updated.", len(partners)) + return True diff --git a/partner_firstname/exceptions.py b/partner_firstname/models/exceptions.py similarity index 100% rename from partner_firstname/exceptions.py rename to partner_firstname/models/exceptions.py diff --git a/partner_firstname/models/res_partner.py b/partner_firstname/models/res_partner.py index 8beed1c87..ff721d462 100644 --- a/partner_firstname/models/res_partner.py +++ b/partner_firstname/models/res_partner.py @@ -3,6 +3,7 @@ # © 2014 Agile Business Group () # © 2015 Grupo ESOC () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + import logging from odoo import api, fields, models from .. import exceptions @@ -73,12 +74,30 @@ class ResPartner(models.Model): return result + @api.model + def _names_order_default(self): + return 'last_first' + + @api.model + def _get_names_order(self): + """Get names order configuration from system parameters. + You can override this method to read configuration from language, + country, company or other""" + return self.env['ir.config_parameter'].get_param( + 'partner_names_order', self._names_order_default()) + @api.model def _get_computed_name(self, lastname, firstname): """Compute the 'name' field according to splitted data. You can override this method to change the order of lastname and firstname the computed name""" - return u" ".join((p for p in (lastname, firstname) if p)) + order = self._get_names_order() + if order == 'last_first_comma': + return u", ".join((p for p in (lastname, firstname) if p)) + elif order == 'first_last': + return u" ".join((p for p in (firstname, lastname) if p)) + else: + return u" ".join((p for p in (lastname, firstname) if p)) @api.one @api.depends("firstname", "lastname") @@ -105,12 +124,17 @@ class ResPartner(models.Model): self._inverse_name() @api.model - def _get_whitespace_cleaned_name(self, name): + def _get_whitespace_cleaned_name(self, name, comma=False): """Remove redundant whitespace from :param:`name`. Removes leading, trailing and duplicated whitespace. """ - return u" ".join(name.split(None)) if name else name + if name: + name = u" ".join(name.split(None)) + if comma: + name = name.replace(" ,", ",") + name = name.replace(", ", ",") + return name @api.model def _get_inverse_name(self, name, is_company=False): @@ -131,9 +155,19 @@ class ResPartner(models.Model): parts = [name or False, False] # Guess name splitting else: - parts = name.strip().split(" ", 1) - while len(parts) < 2: - parts.append(False) + order = self._get_names_order() + # Remove redundant spaces + name = self._get_whitespace_cleaned_name( + name, comma=(order == 'last_first_comma')) + parts = name.split("," if order == 'last_first_comma' else " ", 1) + if len(parts) > 1: + if order == 'first_last': + parts = [u" ".join(parts[1:]), parts[0]] + else: + parts = [parts[0], u" ".join(parts[1:])] + else: + while len(parts) < 2: + parts.append(False) return {"lastname": parts[0], "firstname": parts[1]} @api.one diff --git a/partner_firstname/tests/__init__.py b/partner_firstname/tests/__init__.py index 1fed38100..e85089b63 100644 --- a/partner_firstname/tests/__init__.py +++ b/partner_firstname/tests/__init__.py @@ -9,5 +9,6 @@ from . import ( test_empty, test_name, test_onchange, - test_user_onchange + test_user_onchange, + test_order, ) diff --git a/partner_firstname/tests/base.py b/partner_firstname/tests/base.py index c6236c866..a5faaa958 100644 --- a/partner_firstname/tests/base.py +++ b/partner_firstname/tests/base.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo.tests.common import TransactionCase -from .. import exceptions as ex +from ..models import exceptions as ex class MailInstalled(): diff --git a/partner_firstname/tests/test_create.py b/partner_firstname/tests/test_create.py index 810383808..6d80abdad 100644 --- a/partner_firstname/tests/test_create.py +++ b/partner_firstname/tests/test_create.py @@ -29,7 +29,6 @@ class PersonCase(TransactionCase): self.record = (self.env[self.model] .with_context(self.context) .create(self.values)) - for key, value in self.good_values.iteritems(): self.assertEqual( self.record[key], diff --git a/partner_firstname/tests/test_empty.py b/partner_firstname/tests/test_empty.py index 00e51f930..a36642677 100644 --- a/partner_firstname/tests/test_empty.py +++ b/partner_firstname/tests/test_empty.py @@ -8,7 +8,7 @@ To have more accurate results, remove the ``mail`` module before testing. """ from odoo.tests.common import TransactionCase from .base import MailInstalled -from .. import exceptions as ex +from ..models import exceptions as ex class CompanyCase(TransactionCase): diff --git a/partner_firstname/tests/test_order.py b/partner_firstname/tests/test_order.py new file mode 100644 index 000000000..afc978b05 --- /dev/null +++ b/partner_firstname/tests/test_order.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp.tests.common import TransactionCase + + +class PartnerNamesOrder(TransactionCase): + def order_set(self, order): + return self.env['ir.config_parameter'].set_param( + 'partner_names_order', order) + + def test_get_computed_name(self): + lastname = u"García Lorca" + firstname = u"Federico" + cases = ( + ('last_first', u"García Lorca Federico"), + ('last_first_comma', u"García Lorca, Federico"), + ('first_last', u"Federico García Lorca"), + ) + + for order, name in cases: + self.order_set(order) + result = self.env['res.partner']._get_computed_name( + lastname, firstname) + self.assertEqual(result, name) + + def test_get_inverse_name(self): + lastname = u"Flanker" + firstname = u"Petër" + cases = ( + ('last_first', u"Flanker Petër"), + ('last_first_comma', u"Flanker, Petër"), + ('first_last', u"Petër Flanker"), + ) + for order, name in cases: + self.order_set(order) + result = self.env['res.partner']._get_inverse_name(name) + self.assertEqual(result['lastname'], lastname) + self.assertEqual(result['firstname'], firstname) diff --git a/partner_firstname/views/base_config_view.xml b/partner_firstname/views/base_config_view.xml new file mode 100644 index 000000000..f78b0a10f --- /dev/null +++ b/partner_firstname/views/base_config_view.xml @@ -0,0 +1,34 @@ + + + + + + + Add partner_names_order config parameter + base.config.settings + + + + + + + + + + + +