Browse Source

[IMP] Partner names order configurable

pull/227/head
Antonio Espinosa 9 years ago
parent
commit
cd9027fc26
  1. 52
      partner_firstname/README.rst
  2. 10
      partner_firstname/__openerp__.py
  3. 6
      partner_firstname/models/__init__.py
  4. 66
      partner_firstname/models/base_config_settings.py
  5. 0
      partner_firstname/models/exceptions.py
  6. 46
      partner_firstname/models/res_partner.py
  7. 3
      partner_firstname/tests/__init__.py
  8. 2
      partner_firstname/tests/base.py
  9. 1
      partner_firstname/tests/test_create.py
  10. 2
      partner_firstname/tests/test_empty.py
  11. 40
      partner_firstname/tests/test_order.py
  12. 34
      partner_firstname/views/base_config_view.xml
  13. 39
      partner_second_lastname/README.rst
  14. 3
      partner_second_lastname/__openerp__.py
  15. 6
      partner_second_lastname/models/__init__.py
  16. 29
      partner_second_lastname/models/base_config_settings.py
  17. 73
      partner_second_lastname/models/res_partner.py
  18. 31
      partner_second_lastname/tests/test_name.py
  19. 7
      partner_second_lastname/tests/test_onchange.py

52
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 <https://github.com/OCA/partner-contact/issues/210>`_
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/partner-contact/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
<https://github.com/OCA/partner-contact/issues/new?body=module:%20partner_firstname%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
@ -44,6 +83,7 @@ Contributors
* Hans Henrik Gabelgaard <hhg@gabelgaard.org>
* Jairo Llopis <j.llopis@grupoesoc.es>
* Adrien Peiffer <adrien.peiffer@acsone.eu>
* Antonio Espinosa <antonioea@antiun.com>
Maintainer
----------

10
partner_firstname/__openerp__.py

@ -21,17 +21,19 @@
{
'name': 'Partner first name and last name',
'summary': "Split first name and last name for non company partners",
'version': '8.0.2.1.0',
'version': '8.0.2.2.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',

6
partner_firstname/models/__init__.py

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 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

66
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

0
partner_firstname/exceptions.py → partner_firstname/models/exceptions.py

46
partner_firstname/models.py → partner_firstname/models/res_partner.py

@ -4,6 +4,7 @@
# Copyright (C)
# 2014: Agile Business Group (<http://www.agilebg.com>)
# 2015: Grupo ESOC <www.grupoesoc.es>
# 2015: Antiun Ingenieria S.L. - Antonio Espinosa
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -88,12 +89,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")
@ -120,12 +139,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):
@ -146,9 +170,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

3
partner_firstname/tests/__init__.py

@ -35,5 +35,6 @@ from . import (
test_defaults,
test_empty,
test_name,
test_onchange
test_onchange,
test_order,
)

2
partner_firstname/tests/base.py

@ -26,7 +26,7 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from openerp.tests.common import TransactionCase
from .. import exceptions as ex
from ..models import exceptions as ex
class MailInstalled():

1
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],

2
partner_firstname/tests/test_empty.py

@ -22,7 +22,7 @@ To have more accurate results, remove the ``mail`` module before testing.
from openerp.tests.common import TransactionCase
from .base import MailInstalled
from .. import exceptions as ex
from ..models import exceptions as ex
class CompanyCase(TransactionCase):

40
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)

34
partner_firstname/views/base_config_view.xml

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © 2015 Antiun Ingenieria S.L. - Antonio Espinosa
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<openerp>
<data>
<record id="view_general_configuration" model="ir.ui.view">
<field name="name">Add partner_names_order config parameter</field>
<field name="model">base.config.settings</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
<field name="arch" type="xml">
<xpath expr="//label[@string='Email']/.." position='after'>
<group>
<label for="partner_names_order" />
<div>
<div>
<field name="partner_names_order" class="oe_inline" />
<field name="partner_names_order_changed" invisible="1"/>
<button name="action_recalculate_partners_name"
string="Recalculate names"
icon="gtk-execute"
type="object"
help="Recalculate names for all partners. This process could take so much time if there are more than 10,000 active partners"
attrs="{'invisible': [('partner_names_order_changed', '=', True)]}"/>
</div>
</div>
</group>
</xpath>
</field>
</record>
</data>
</openerp>

39
partner_second_lastname/README.rst

@ -1,6 +1,8 @@
.. 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 second lastname
=======================
@ -12,6 +14,26 @@ In some countries, it's important to have a second last name for contacts.
Contact partners will need to fulfill at least one of the name fields
(*First name*, *First last name* or *Second last name*).
Configuration
=============
You can configure some common name patterns for the inverse function
in Settings > Configuration > General settings:
* Lastname SecondLastname Firstname: For example 'Anderson Lavarge Robert'
* Lastname SecondLastname, Firstname: For example 'Anderson Lavarge, Robert'
* Firstname Lastname SecondLastname: For example 'Robert Anderson Lavarge'
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 firstname, lastname and
second lastname from a simple string and also *_get_computed_name* to get a
name form the firstname, lastname and second lastname.
These methods can be overridden to change the format specified above.
Usage
=====
@ -24,11 +46,7 @@ To use this module, you need to:
If you directly enter the full name instead of entering the other fields
separately (maybe from other form), this module will try to guess the best
match for your input and split it between firstname, lastname and second
lastname.
If the name you enter is in the form *Firstname Lastname1 Lastname2*, it will
be split as such. If you use a comma, it will understand it as *Lastname1
Lastname2, Firstname*.
lastname using an inverse function.
If you can, always enter it manually please. Automatic guessing could fail for
you easily in some corner cases.
@ -37,6 +55,15 @@ you easily in some corner cases.
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/134/8.0
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 <https://github.com/OCA/partner-contact/issues/210>`_
Bug Tracker
===========
@ -55,6 +82,8 @@ Contributors
* `Grupo ESOC <http://grupoesoc.es>`_:
* `Jairo Llopis <mailto:j.llopis@grupoesoc.es>`_.
* `Antiun Ingeniería S.L. <http://www.antiun.com>`_:
* `Antonio Espinosa <mailto:antonioea@antiun.com>`_.
Maintainer
----------

3
partner_second_lastname/__openerp__.py

@ -5,7 +5,7 @@
{
"name": "Partner second last name",
"summary": "Have split first and second lastnames",
"version": "8.0.4.0.0",
"version": "8.0.4.1.0",
"license": "AGPL-3",
"website": "https://grupoesoc.es",
"author": "Grupo ESOC Ingeniería de Servicios, "
@ -20,4 +20,5 @@
"views/res_user.xml",
],
"installable": True,
'images': [],
}

6
partner_second_lastname/models/__init__.py

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 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

29
partner_second_lastname/models/base_config_settings.py

@ -0,0 +1,29 @@
# -*- 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 import models, api
class BaseConfigSettings(models.TransientModel):
_inherit = 'base.config.settings'
def _partner_names_order_selection(self):
options = super(
BaseConfigSettings, self)._partner_names_order_selection()
new_labels = {
'last_first': 'Lastname SecondLastname Firstname',
'last_first_comma': 'Lastname SecondLastname, Firstname',
'first_last': 'Firstname Lastname SecondLastname',
}
return [(k, new_labels[k]) if k in new_labels else (k, v)
for k, v in options]
@api.multi
def _partners_for_recalculating(self):
return self.env['res.partner'].search([
('is_company', '=', False),
'|', '&', ('firstname', '!=', False), ('lastname', '!=', False),
'|', '&', ('firstname', '!=', False), ('lastname2', '!=', False),
'&', ('lastname', '!=', False), ('lastname2', '!=', False),
])

73
partner_second_lastname/models.py → partner_second_lastname/models/res_partner.py

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U.
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa
from openerp import api, fields, models
from openerp.addons.partner_firstname import exceptions
from openerp.addons.partner_firstname.models import exceptions
class ResPartner(models.Model):
@ -20,17 +21,24 @@ class ResPartner(models.Model):
We have 2 lastnames, so lastnames and firstname will be separated by a
comma.
"""
order = self._get_names_order()
names = list()
if lastname:
names.append(lastname)
if lastname2:
names.append(lastname2)
if names and firstname:
names[-1] = names[-1] + ","
if firstname:
names.append(firstname)
if order == 'first_last':
if firstname:
names.append(firstname)
if lastname:
names.append(lastname)
if lastname2:
names.append(lastname2)
else:
if lastname:
names.append(lastname)
if lastname2:
names.append(lastname2)
if names and firstname and order == 'last_first_comma':
names[-1] = names[-1] + ","
if firstname:
names.append(firstname)
return u" ".join(names)
@api.one
@ -61,24 +69,31 @@ class ResPartner(models.Model):
- Otherwise, make a guess.
"""
# Company name goes to the lastname
if is_company or not name:
parts = [False, name or False, False]
# The comma separates the firstname
elif "," in name:
lastnames, firstname = name.split(",", 1)
parts = [firstname.strip()] + lastnames.split(" ", 1)
# Without comma, the user wrote the firstname first
else:
parts = name.split(" ", 2)
while len(parts) < 3:
parts.append(False)
return {"firstname": parts[0],
"lastname": parts[1],
"lastname2": parts[2]}
result = {
'firstname': False,
'lastname': name or False,
'lastname2': False,
}
if not is_company and name:
order = self._get_names_order()
result = super(ResPartner, self)._get_inverse_name(
name, is_company)
parts = []
if order == 'last_first':
if result['firstname']:
parts = result['firstname'].split(" ", 1)
while len(parts) < 2:
parts.append(False)
result['lastname2'] = parts[0]
result['firstname'] = parts[1]
else:
if result['lastname']:
parts = result['lastname'].split(" ", 1)
while len(parts) < 2:
parts.append(False)
result['lastname'] = parts[0]
result['lastname2'] = parts[1]
return result
@api.one
@api.constrains("firstname", "lastname", "lastname2")

31
partner_second_lastname/tests/test_name.py

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U.
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa
from openerp.tests.common import TransactionCase
from openerp.addons.partner_firstname.tests.base import MailInstalled
@ -8,6 +9,11 @@ from openerp.addons.partner_firstname.tests.base import MailInstalled
class CompanyCase(TransactionCase):
"""Test ``res.partner`` when it is a company."""
def setUp(self):
super(CompanyCase, self).setUp()
self.env['ir.config_parameter'].set_param(
'partner_names_order', 'first_last')
def tearDown(self):
try:
new = self.env["res.partner"].create({
@ -75,6 +81,8 @@ class PersonCase(TransactionCase):
def setUp(self):
super(PersonCase, self).setUp()
self.env['ir.config_parameter'].set_param(
'partner_names_order', 'last_first_comma')
self.firstname = u"Fírstname"
self.lastname = u"Làstname1"
@ -113,6 +121,9 @@ class PersonCase(TransactionCase):
def test_firstname_first(self):
"""Create a person setting his first name first."""
self.env['ir.config_parameter'].set_param(
'partner_names_order', 'first_last')
self.template = "%(first)s %(last1)s %(last2)s"
self.params = {
"is_company": False,
"name": "%s %s %s" % (self.firstname,
@ -121,7 +132,7 @@ class PersonCase(TransactionCase):
}
def test_firstname_last(self):
"""Create a persong setting his first name last."""
"""Create a person setting his first name last."""
self.params = {
"is_company": False,
"name": "%s %s, %s" % (self.lastname,
@ -130,25 +141,29 @@ class PersonCase(TransactionCase):
}
def test_firstname_only(self):
"""Create a persong setting his first name only."""
self.lastname = self.lastname2 = False
self.template = "%(first)s"
"""Create a person setting his first name only."""
self.env['ir.config_parameter'].set_param(
'partner_names_order', 'first_last')
self.firstname = self.lastname2 = False
self.template = "%(last1)s"
self.params = {
"is_company": False,
"name": self.firstname,
"name": self.lastname,
}
def test_firstname_lastname_only(self):
"""Create a persong setting his first name and last name 1 only."""
"""Create a person setting his first name and last name 1 only."""
self.env['ir.config_parameter'].set_param(
'partner_names_order', 'first_last')
self.lastname2 = False
self.template = "%(last1)s, %(first)s"
self.template = "%(first)s %(last1)s"
self.params = {
"is_company": False,
"name": "%s %s" % (self.firstname, self.lastname),
}
def test_lastname_firstname_only(self):
"""Create a persong setting his last name 1 and first name only."""
"""Create a person setting his last name 1 and first name only."""
self.lastname2 = False
self.template = "%(last1)s, %(first)s"
self.params = {

7
partner_second_lastname/tests/test_onchange.py

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U.
# © 2015 Antiun Ingenieria S.L. - Antonio Espinosa
"""These tests try to mimic the behavior of the UI form.
The form operates in onchange mode, with its limitations.
@ -12,6 +14,11 @@ from openerp.tests.common import TransactionCase
class OnChangeCase(TransactionCase):
is_company = False
def setUp(self):
super(OnChangeCase, self).setUp()
self.env['ir.config_parameter'].set_param(
'partner_names_order', 'last_first_comma')
def new_partner(self):
"""Create an empty partner. Ensure it is (or not) a company."""
new = self.env["res.partner"].new()

Loading…
Cancel
Save