Browse Source

[IMP] Partner names order configurable

pull/663/head
Antonio Espinosa 9 years ago
committed by Jairo Llopis
parent
commit
8dee694e63
  1. 52
      partner_firstname/README.rst
  2. 10
      partner_firstname/__manifest__.py
  3. 5
      partner_firstname/models/__init__.py
  4. 66
      partner_firstname/models/base_config_settings.py
  5. 0
      partner_firstname/models/exceptions.py
  6. 40
      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

52
partner_firstname/README.rst

@ -1,12 +1,33 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg .. 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 :alt: License: AGPL-3
================================
Partner first name and last name Partner first name and last name
================================ ================================
This module was written to extend the functionality of contacts to support This module was written to extend the functionality of contacts to support
having separate last name and first name. 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 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 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, 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 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 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 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). 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: For further information, please visit:
* https://www.odoo.com/forum/help-1 * 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 Credits
======= =======
@ -61,6 +100,7 @@ Translations
* Danish: Hans Henrik Gabelgaard * Danish: Hans Henrik Gabelgaard
* Italian: Leonardo Donelli * Italian: Leonardo Donelli
* Spanish: Antonio Espinosa * Spanish: Antonio Espinosa
* Antonio Espinosa <antonioea@antiun.com>
Maintainer Maintainer
---------- ----------

10
partner_firstname/__manifest__.py

@ -7,17 +7,19 @@
{ {
'name': 'Partner first name and last name', 'name': 'Partner first name and last name',
'summary': "Split first name and last name for non company partners", '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, " 'author': "Camptocamp, "
"Grupo ESOC Ingeniería de Servicios, " "Grupo ESOC Ingeniería de Servicios, "
"Odoo Community Association (OCA)", "Odoo Community Association (OCA)",
'license': "AGPL-3", 'license': "AGPL-3",
'maintainer': 'Camptocamp, Acsone', 'maintainer': 'Camptocamp, Acsone',
'category': 'Extra Tools', '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': [ 'data': [
'views/base_config_view.xml',
'views/res_partner.xml', 'views/res_partner.xml',
'views/res_user.xml', 'views/res_user.xml',
'data/res_partner.yml', 'data/res_partner.yml',

5
partner_firstname/models/__init__.py

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- 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). # 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_partner
from . import res_user

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

40
partner_firstname/models/res_partner.py

@ -3,6 +3,7 @@
# © 2014 Agile Business Group (<http://www.agilebg.com>) # © 2014 Agile Business Group (<http://www.agilebg.com>)
# © 2015 Grupo ESOC (<http://www.grupoesoc.es>) # © 2015 Grupo ESOC (<http://www.grupoesoc.es>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging import logging
from odoo import api, fields, models from odoo import api, fields, models
from .. import exceptions from .. import exceptions
@ -73,11 +74,29 @@ class ResPartner(models.Model):
return result 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 @api.model
def _get_computed_name(self, lastname, firstname): def _get_computed_name(self, lastname, firstname):
"""Compute the 'name' field according to splitted data. """Compute the 'name' field according to splitted data.
You can override this method to change the order of lastname and You can override this method to change the order of lastname and
firstname the computed name""" firstname the computed name"""
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)) return u" ".join((p for p in (lastname, firstname) if p))
@api.one @api.one
@ -105,12 +124,17 @@ class ResPartner(models.Model):
self._inverse_name() self._inverse_name()
@api.model @api.model
def _get_whitespace_cleaned_name(self, name):
def _get_whitespace_cleaned_name(self, name, comma=False):
"""Remove redundant whitespace from :param:`name`. """Remove redundant whitespace from :param:`name`.
Removes leading, trailing and duplicated whitespace. 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 @api.model
def _get_inverse_name(self, name, is_company=False): def _get_inverse_name(self, name, is_company=False):
@ -131,7 +155,17 @@ class ResPartner(models.Model):
parts = [name or False, False] parts = [name or False, False]
# Guess name splitting # Guess name splitting
else: else:
parts = name.strip().split(" ", 1)
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: while len(parts) < 2:
parts.append(False) parts.append(False)
return {"lastname": parts[0], "firstname": parts[1]} return {"lastname": parts[0], "firstname": parts[1]}

3
partner_firstname/tests/__init__.py

@ -9,5 +9,6 @@ from . import (
test_empty, test_empty,
test_name, test_name,
test_onchange, test_onchange,
test_user_onchange
test_user_onchange,
test_order,
) )

2
partner_firstname/tests/base.py

@ -3,7 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
from .. import exceptions as ex
from ..models import exceptions as ex
class MailInstalled(): class MailInstalled():

1
partner_firstname/tests/test_create.py

@ -29,7 +29,6 @@ class PersonCase(TransactionCase):
self.record = (self.env[self.model] self.record = (self.env[self.model]
.with_context(self.context) .with_context(self.context)
.create(self.values)) .create(self.values))
for key, value in self.good_values.iteritems(): for key, value in self.good_values.iteritems():
self.assertEqual( self.assertEqual(
self.record[key], self.record[key],

2
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 odoo.tests.common import TransactionCase
from .base import MailInstalled from .base import MailInstalled
from .. import exceptions as ex
from ..models import exceptions as ex
class CompanyCase(TransactionCase): 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>
Loading…
Cancel
Save