Browse Source

partner_second_lastname: Partner names order configurable

14.0
Antonio Espinosa 9 years ago
committed by Luis Torres
parent
commit
69be88a04c
  1. 39
      partner_second_lastname/README.rst
  2. 9
      partner_second_lastname/__openerp__.py
  3. 97
      partner_second_lastname/models.py
  4. 6
      partner_second_lastname/models/__init__.py
  5. 29
      partner_second_lastname/models/base_config_settings.py
  6. 113
      partner_second_lastname/models/res_partner.py
  7. 31
      partner_second_lastname/tests/test_name.py
  8. 7
      partner_second_lastname/tests/test_onchange.py

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

9
partner_second_lastname/__openerp__.py

@ -4,12 +4,14 @@
{
"name": "Partner second last name",
"version": "8.0.4.0.0",
"author": "Grupo ESOC, Odoo Community Association (OCA)",
"summary": "Have split first and second lastnames",
"version": "8.0.4.1.0",
"license": "AGPL-3",
"website": "https://grupoesoc.es",
"author": "Grupo ESOC Ingeniería de Servicios, "
"Odoo Community Association (OCA)",
"maintainer": "Odoo Community Association (OCA)",
"category": "Extra Tools",
"website": "http://www.grupoesoc.es",
"depends": [
"partner_firstname"
],
@ -18,4 +20,5 @@
"views/res_user.xml",
],
"installable": True,
'images': [],
}

97
partner_second_lastname/models.py

@ -1,97 +0,0 @@
# -*- 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.
from openerp import api, fields, models
from openerp.addons.partner_firstname import exceptions
class ResPartner(models.Model):
"""Adds a second last name."""
_inherit = "res.partner"
lastname2 = fields.Char("Second last name")
@api.model
def _get_computed_name(self, lastname, firstname, lastname2=None):
"""Compute the name combined with the second lastname too.
We have 2 lastnames, so lastnames and firstname will be separated by a
comma.
"""
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)
return u" ".join(names)
@api.one
@api.depends("firstname", "lastname", "lastname2")
def _compute_name(self):
"""Write :attr:`~.name` according to splitted data."""
self.name = self._get_computed_name(self.lastname,
self.firstname,
self.lastname2)
@api.one
def _inverse_name(self):
"""Try to revert the effect of :meth:`._compute_name`."""
parts = self._get_inverse_name(self.name, self.is_company)
# Avoid to hit :meth:`~._check_name` with all 3 fields being ``False``
before, after = dict(), dict()
for key, value in parts.iteritems():
(before if value else after)[key] = value
self.update(before)
self.update(after)
@api.model
def _get_inverse_name(self, name, is_company=False):
"""Compute the inverted name.
- If the partner is a company, save it in the lastname.
- 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]}
@api.one
@api.constrains("firstname", "lastname", "lastname2")
def _check_name(self):
"""Ensure at least one name is set."""
try:
super(ResPartner, self)._check_name()
except exceptions.EmptyNamesError as error:
if not self.lastname2:
raise error
@api.one
@api.onchange("firstname", "lastname", "lastname2")
def _onchange_subnames(self):
"""Trigger onchange with :attr:`~.lastname2` too."""
super(ResPartner, self)._onchange_subnames()

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

113
partner_second_lastname/models/res_partner.py

@ -0,0 +1,113 @@
# -*- 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.models import exceptions
class ResPartner(models.Model):
"""Adds a second last name."""
_inherit = "res.partner"
lastname2 = fields.Char("Second last name", oldname="lastname_second")
@api.model
def _get_computed_name(self, lastname, firstname, lastname2=None):
"""Compute the name combined with the second lastname too.
We have 2 lastnames, so lastnames and firstname will be separated by a
comma.
"""
order = self._get_names_order()
names = list()
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
@api.depends("firstname", "lastname", "lastname2")
def _compute_name(self):
"""Write :attr:`~.name` according to splitted data."""
self.name = self._get_computed_name(
self.lastname, self.firstname, self.lastname2)
@api.one
def _inverse_name(self):
"""Try to revert the effect of :meth:`._compute_name`."""
parts = self._get_inverse_name(self.name, self.is_company)
# Avoid to hit :meth:`~._check_name` with all 3 fields being ``False``
before, after = dict(), dict()
for key, value in parts.iteritems():
(before if value else after)[key] = value
if any([before[k] != self[k] for k in before.keys()]):
self.update(before)
if any([after[k] != self[k] for k in after.keys()]):
self.update(after)
@api.model
def _get_inverse_name(self, name, is_company=False):
"""Compute the inverted name.
- If the partner is a company, save it in the lastname.
- Otherwise, make a guess.
"""
# Company name goes to the lastname
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")
def _check_name(self):
"""Ensure at least one name is set."""
try:
super(ResPartner, self)._check_name()
except exceptions.EmptyNamesError as error:
if not self.lastname2:
raise error
@api.one
@api.onchange("firstname", "lastname", "lastname2")
def _onchange_subnames(self):
"""Trigger onchange with :attr:`~.lastname2` too."""
super(ResPartner, self)._onchange_subnames()

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