253 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
| # -*- coding: utf-8 -*-
 | |
| # © 2013 Nicolas Bessi (Camptocamp SA)
 | |
| # © 2014 Agile Business Group (<http://www.agilebg.com>)
 | |
| # © 2015 Grupo ESOC (<http://www.grupoesoc.es>)
 | |
| # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
 | |
| import logging
 | |
| from odoo import api, fields, models
 | |
| from .. import exceptions
 | |
| 
 | |
| _logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| class ResPartner(models.Model):
 | |
|     """Adds last name and first name; name becomes a stored function field."""
 | |
|     _inherit = 'res.partner'
 | |
| 
 | |
|     firstname = fields.Char(
 | |
|         "First name",
 | |
|         index=True,
 | |
|     )
 | |
|     lastname = fields.Char(
 | |
|         "Last name",
 | |
|         index=True,
 | |
|     )
 | |
|     name = fields.Char(
 | |
|         compute="_compute_name",
 | |
|         inverse="_inverse_name_after_cleaning_whitespace",
 | |
|         required=False,
 | |
|         store=True)
 | |
| 
 | |
|     @api.model
 | |
|     def create(self, vals):
 | |
|         """Add inverted names at creation if unavailable."""
 | |
|         context = dict(self.env.context)
 | |
|         name = vals.get("name", context.get("default_name"))
 | |
| 
 | |
|         if name is not None:
 | |
|             # Calculate the splitted fields
 | |
|             inverted = self._get_inverse_name(
 | |
|                 self._get_whitespace_cleaned_name(name),
 | |
|                 vals.get("is_company",
 | |
|                          self.default_get(["is_company"])["is_company"]))
 | |
| 
 | |
|             for key, value in inverted.items():
 | |
|                 if not vals.get(key) or context.get("copy"):
 | |
|                     vals[key] = value
 | |
| 
 | |
|             # Remove the combined fields
 | |
|             if "name" in vals:
 | |
|                 del vals["name"]
 | |
|             if "default_name" in context:
 | |
|                 del context["default_name"]
 | |
| 
 | |
|         return super(ResPartner, self.with_context(context)).create(vals)
 | |
| 
 | |
|     @api.multi
 | |
|     def copy(self, default=None):
 | |
|         """Ensure partners are copied right.
 | |
| 
 | |
|         Odoo adds ``(copy)`` to the end of :attr:`~.name`, but that would get
 | |
|         ignored in :meth:`~.create` because it also copies explicitly firstname
 | |
|         and lastname fields.
 | |
|         """
 | |
|         return super(ResPartner, self.with_context(copy=True)).copy(default)
 | |
| 
 | |
|     @api.model
 | |
|     def default_get(self, fields_list):
 | |
|         """Invert name when getting default values."""
 | |
|         result = super(ResPartner, self).default_get(fields_list)
 | |
| 
 | |
|         inverted = self._get_inverse_name(
 | |
|             self._get_whitespace_cleaned_name(result.get("name", "")),
 | |
|             result.get("is_company", False))
 | |
| 
 | |
|         for field in list(inverted.keys()):
 | |
|             if field in fields_list:
 | |
|                 result[field] = inverted.get(field)
 | |
| 
 | |
|         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'].sudo().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"""
 | |
|         order = self._get_names_order()
 | |
|         if order == 'last_first_comma':
 | |
|             return ", ".join((p for p in (lastname, firstname) if p))
 | |
|         elif order == 'first_last':
 | |
|             return " ".join((p for p in (firstname, lastname) if p))
 | |
|         else:
 | |
|             return " ".join((p for p in (lastname, firstname) if p))
 | |
| 
 | |
|     @api.multi
 | |
|     @api.depends("firstname", "lastname")
 | |
|     def _compute_name(self):
 | |
|         """Write the 'name' field according to splitted data."""
 | |
|         for record in self:
 | |
|             record.name = record._get_computed_name(
 | |
|                 record.lastname, record.firstname,
 | |
|             )
 | |
| 
 | |
|     @api.multi
 | |
|     def _inverse_name_after_cleaning_whitespace(self):
 | |
|         """Clean whitespace in :attr:`~.name` and split it.
 | |
| 
 | |
|         The splitting logic is stored separately in :meth:`~._inverse_name`, so
 | |
|         submodules can extend that method and get whitespace cleaning for free.
 | |
|         """
 | |
|         for record in self:
 | |
|             # Remove unneeded whitespace
 | |
|             clean = record._get_whitespace_cleaned_name(record.name)
 | |
| 
 | |
|             # Clean name avoiding infinite recursion
 | |
|             if record.name != clean:
 | |
|                 record.name = clean
 | |
| 
 | |
|             # Save name in the real fields
 | |
|             else:
 | |
|                 record._inverse_name()
 | |
| 
 | |
|     @api.model
 | |
|     def _get_whitespace_cleaned_name(self, name, comma=False):
 | |
|         """Remove redundant whitespace from :param:`name`.
 | |
| 
 | |
|         Removes leading, trailing and duplicated whitespace.
 | |
|         """
 | |
|         try:
 | |
|             name = " ".join(name.split()) if name else name
 | |
|         except UnicodeDecodeError:
 | |
|             # with users coming from LDAP, name can be a str encoded as utf-8
 | |
|             # this happens with ActiveDirectory for instance, and in that case
 | |
|             # we get a UnicodeDecodeError during the automatic ASCII -> Unicode
 | |
|             # conversion that Python does for us.
 | |
|             # In that case we need to manually decode the string to get a
 | |
|             # proper unicode string.
 | |
|             name = ' '.join(name.decode('utf-8').split()) if name else name
 | |
| 
 | |
|         if comma:
 | |
|             name = name.replace(" ,", ",")
 | |
|             name = name.replace(", ", ",")
 | |
|         return name
 | |
| 
 | |
|     @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.
 | |
| 
 | |
|         This method can be easily overriden by other submodules.
 | |
|         You can also override this method to change the order of name's
 | |
|         attributes
 | |
| 
 | |
|         When this method is called, :attr:`~.name` already has unified and
 | |
|         trimmed whitespace.
 | |
|         """
 | |
|         # Company name goes to the lastname
 | |
|         if is_company or not name:
 | |
|             parts = [name or False, False]
 | |
|         # Guess name splitting
 | |
|         else:
 | |
|             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 = [" ".join(parts[1:]), parts[0]]
 | |
|                 else:
 | |
|                     parts = [parts[0], " ".join(parts[1:])]
 | |
|             else:
 | |
|                 while len(parts) < 2:
 | |
|                     parts.append(False)
 | |
|         return {"lastname": parts[0], "firstname": parts[1]}
 | |
| 
 | |
|     @api.multi
 | |
|     def _inverse_name(self):
 | |
|         """Try to revert the effect of :meth:`._compute_name`."""
 | |
|         for record in self:
 | |
|             parts = record._get_inverse_name(record.name, record.is_company)
 | |
|             record.lastname = parts['lastname']
 | |
|             record.firstname = parts['firstname']
 | |
| 
 | |
|     @api.multi
 | |
|     @api.constrains("firstname", "lastname")
 | |
|     def _check_name(self):
 | |
|         """Ensure at least one name is set."""
 | |
|         for record in self:
 | |
|             if all((
 | |
|                 record.type == 'contact' or record.is_company,
 | |
|                 not (record.firstname or record.lastname)
 | |
|             )):
 | |
|                 raise exceptions.EmptyNamesError(record)
 | |
| 
 | |
|     @api.onchange("firstname", "lastname")
 | |
|     def _onchange_subnames(self):
 | |
|         """Avoid recursion when the user changes one of these fields.
 | |
| 
 | |
|         This forces to skip the :attr:`~.name` inversion when the user is
 | |
|         setting it in a not-inverted way.
 | |
|         """
 | |
|         # Modify self's context without creating a new Environment.
 | |
|         # See https://github.com/odoo/odoo/issues/7472#issuecomment-119503916.
 | |
|         self.env.context = self.with_context(skip_onchange=True).env.context
 | |
| 
 | |
|     @api.onchange("name")
 | |
|     def _onchange_name(self):
 | |
|         """Ensure :attr:`~.name` is inverted in the UI."""
 | |
|         if self.env.context.get("skip_onchange"):
 | |
|             # Do not skip next onchange
 | |
|             self.env.context = (
 | |
|                 self.with_context(skip_onchange=False).env.context)
 | |
|         else:
 | |
|             self._inverse_name_after_cleaning_whitespace()
 | |
| 
 | |
|     @api.model
 | |
|     def _install_partner_firstname(self):
 | |
|         """Save names correctly in the database.
 | |
| 
 | |
|         Before installing the module, field ``name`` contains all full names.
 | |
|         When installing it, this method parses those names and saves them
 | |
|         correctly into the database. This can be called later too if needed.
 | |
|         """
 | |
|         # Find records with empty firstname and lastname
 | |
|         records = self.search([("firstname", "=", False),
 | |
|                                ("lastname", "=", False)])
 | |
| 
 | |
|         # Force calculations there
 | |
|         records._inverse_name()
 | |
|         _logger.info("%d partners updated installing module.", len(records))
 | |
| 
 | |
|     # Disabling SQL constraint givint a more explicit error using a Python
 | |
|     # contstraint
 | |
|     _sql_constraints = [(
 | |
|         'check_name',
 | |
|         "CHECK( 1=1 )",
 | |
|         'Contacts require a name.'
 | |
|     )]
 |