You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
7.3 KiB

  1. # -*- coding: utf-8 -*-
  2. # Author: Nicolas Bessi. Copyright Camptocamp SA
  3. # Copyright (C)
  4. # 2014: Agile Business Group (<http://www.agilebg.com>)
  5. # 2015: Grupo ESOC <www.grupoesoc.es>
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. import logging
  20. from openerp import api, fields, models
  21. from . import exceptions
  22. _logger = logging.getLogger(__name__)
  23. class ResPartner(models.Model):
  24. """Adds last name and first name; name becomes a stored function field."""
  25. _inherit = 'res.partner'
  26. firstname = fields.Char("First name")
  27. lastname = fields.Char("Last name")
  28. name = fields.Char(
  29. compute="_compute_name",
  30. inverse="_inverse_name_after_cleaning_whitespace",
  31. required=False,
  32. store=True)
  33. @api.model
  34. def create(self, vals):
  35. """Add inverted names at creation if unavailable."""
  36. context = dict(self.env.context)
  37. name = vals.get("name", context.get("default_name"))
  38. if name is not None:
  39. # Calculate the splitted fields
  40. inverted = self._get_inverse_name(
  41. self._get_whitespace_cleaned_name(name),
  42. vals.get("is_company",
  43. self.default_get(["is_company"])["is_company"]))
  44. for key, value in inverted.iteritems():
  45. if not vals.get(key) or context.get("copy"):
  46. vals[key] = value
  47. # Remove the combined fields
  48. if "name" in vals:
  49. del vals["name"]
  50. if "default_name" in context:
  51. del context["default_name"]
  52. return super(ResPartner, self.with_context(context)).create(vals)
  53. @api.multi
  54. def copy(self, default=None):
  55. """Ensure partners are copied right.
  56. Odoo adds ``(copy)`` to the end of :attr:`~.name`, but that would get
  57. ignored in :meth:`~.create` because it also copies explicitly firstname
  58. and lastname fields.
  59. """
  60. return super(ResPartner, self.with_context(copy=True)).copy(default)
  61. @api.model
  62. def default_get(self, fields_list):
  63. """Invert name when getting default values."""
  64. result = super(ResPartner, self).default_get(fields_list)
  65. inverted = self._get_inverse_name(
  66. self._get_whitespace_cleaned_name(result.get("name", "")),
  67. result.get("is_company", False))
  68. for field in inverted.keys():
  69. if field in fields_list:
  70. result[field] = inverted.get(field)
  71. return result
  72. @api.model
  73. def _get_computed_name(self, lastname, firstname):
  74. """Compute the 'name' field according to splitted data.
  75. You can override this method to change the order of lastname and
  76. firstname the computed name"""
  77. return u" ".join((p for p in (lastname, firstname) if p))
  78. @api.one
  79. @api.depends("firstname", "lastname")
  80. def _compute_name(self):
  81. """Write the 'name' field according to splitted data."""
  82. self.name = self._get_computed_name(self.lastname, self.firstname)
  83. @api.one
  84. def _inverse_name_after_cleaning_whitespace(self):
  85. """Clean whitespace in :attr:`~.name` and split it.
  86. The splitting logic is stored separately in :meth:`~._inverse_name`, so
  87. submodules can extend that method and get whitespace cleaning for free.
  88. """
  89. # Remove unneeded whitespace
  90. clean = self._get_whitespace_cleaned_name(self.name)
  91. # Clean name avoiding infinite recursion
  92. if self.name != clean:
  93. self.name = clean
  94. # Save name in the real fields
  95. else:
  96. self._inverse_name()
  97. @api.model
  98. def _get_whitespace_cleaned_name(self, name):
  99. """Remove redundant whitespace from :param:`name`.
  100. Removes leading, trailing and duplicated whitespace.
  101. """
  102. return u" ".join(name.split(None)) if name else name
  103. @api.model
  104. def _get_inverse_name(self, name, is_company=False):
  105. """Compute the inverted name.
  106. - If the partner is a company, save it in the lastname.
  107. - Otherwise, make a guess.
  108. This method can be easily overriden by other submodules.
  109. You can also override this method to change the order of name's
  110. attributes
  111. When this method is called, :attr:`~.name` already has unified and
  112. trimmed whitespace.
  113. """
  114. # Company name goes to the lastname
  115. if is_company or not name:
  116. parts = [name or False, False]
  117. # Guess name splitting
  118. else:
  119. parts = name.strip().split(" ", 1)
  120. while len(parts) < 2:
  121. parts.append(False)
  122. return {"lastname": parts[0], "firstname": parts[1]}
  123. @api.one
  124. def _inverse_name(self):
  125. """Try to revert the effect of :meth:`._compute_name`."""
  126. parts = self._get_inverse_name(self.name, self.is_company)
  127. self.lastname, self.firstname = parts["lastname"], parts["firstname"]
  128. @api.one
  129. @api.constrains("firstname", "lastname")
  130. def _check_name(self):
  131. """Ensure at least one name is set."""
  132. if not (self.firstname or self.lastname):
  133. raise exceptions.EmptyNamesError(self)
  134. @api.one
  135. @api.onchange("firstname", "lastname")
  136. def _onchange_subnames(self):
  137. """Avoid recursion when the user changes one of these fields.
  138. This forces to skip the :attr:`~.name` inversion when the user is
  139. setting it in a not-inverted way.
  140. """
  141. # Modify self's context without creating a new Environment.
  142. # See https://github.com/odoo/odoo/issues/7472#issuecomment-119503916.
  143. self.env.context = self.with_context(skip_onchange=True).env.context
  144. @api.one
  145. @api.onchange("name")
  146. def _onchange_name(self):
  147. """Ensure :attr:`~.name` is inverted in the UI."""
  148. if self.env.context.get("skip_onchange"):
  149. # Do not skip next onchange
  150. self.env.context = (
  151. self.with_context(skip_onchange=False).env.context)
  152. else:
  153. self._inverse_name_after_cleaning_whitespace()
  154. @api.model
  155. def _install_partner_firstname(self):
  156. """Save names correctly in the database.
  157. Before installing the module, field ``name`` contains all full names.
  158. When installing it, this method parses those names and saves them
  159. correctly into the database. This can be called later too if needed.
  160. """
  161. # Find records with empty firstname and lastname
  162. records = self.search([("firstname", "=", False),
  163. ("lastname", "=", False)])
  164. # Force calculations there
  165. records._inverse_name()
  166. _logger.info("%d partners updated installing module.", len(records))