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.

245 lines
8.7 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014 Agile Business Group (<http://www.agilebg.com>)
  3. # Copyright 2015 Grupo ESOC <www.grupoesoc.es>
  4. # Copyright 2015 Antiun Ingenieria S.L. - Antonio Espinosa
  5. # Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
  6. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  7. import logging
  8. from openerp import api, fields, models
  9. from . import exceptions
  10. _logger = logging.getLogger(__name__)
  11. class ResPartner(models.Model):
  12. """Adds last name and first name; name becomes a stored function field."""
  13. _inherit = 'res.partner'
  14. firstname = fields.Char("First name")
  15. lastname = fields.Char("Last name")
  16. name = fields.Char(
  17. compute="_compute_name",
  18. inverse="_inverse_name_after_cleaning_whitespace",
  19. required=False,
  20. store=True)
  21. @api.model
  22. def create(self, vals):
  23. """Add inverted names at creation if unavailable."""
  24. context = dict(self.env.context)
  25. name = vals.get("name", context.get("default_name"))
  26. if name is not None:
  27. # Calculate the splitted fields
  28. inverted = self._get_inverse_name(
  29. self._get_whitespace_cleaned_name(name),
  30. vals.get("is_company",
  31. self.default_get(["is_company"])["is_company"]))
  32. for key, value in inverted.iteritems():
  33. if not vals.get(key) or context.get("copy"):
  34. vals[key] = value
  35. # Remove the combined fields
  36. if "name" in vals:
  37. del vals["name"]
  38. if "default_name" in context:
  39. del context["default_name"]
  40. return super(ResPartner, self.with_context(context)).create(vals)
  41. @api.multi
  42. def copy(self, default=None):
  43. """Ensure partners are copied right.
  44. Odoo adds ``(copy)`` to the end of :attr:`~.name`, but that would get
  45. ignored in :meth:`~.create` because it also copies explicitly firstname
  46. and lastname fields.
  47. """
  48. return super(ResPartner, self.with_context(copy=True)).copy(default)
  49. @api.model
  50. def default_get(self, fields_list):
  51. """Invert name when getting default values."""
  52. result = super(ResPartner, self).default_get(fields_list)
  53. inverted = self._get_inverse_name(
  54. self._get_whitespace_cleaned_name(result.get("name", "")),
  55. result.get("is_company", False))
  56. for field in inverted.keys():
  57. if field in fields_list:
  58. result[field] = inverted.get(field)
  59. return result
  60. @api.model
  61. def _names_order_default(self):
  62. return 'last_first'
  63. @api.model
  64. def _get_names_order(self):
  65. """Get names order configuration from system parameters.
  66. You can override this method to read configuration from language,
  67. country, company or other
  68. """
  69. return self.env['ir.config_parameter'].get_param(
  70. 'partner_names_order', self._names_order_default())
  71. @api.model
  72. def _get_computed_name(self, lastname, firstname):
  73. """Compute the 'name' field according to splitted data.
  74. You can override this method to change the order of lastname and
  75. firstname the computed name
  76. """
  77. order = self._get_names_order()
  78. if order == 'last_first_comma':
  79. return u", ".join((p for p in (lastname, firstname) if p))
  80. elif order == 'first_last':
  81. return u" ".join((p for p in (firstname, lastname) if p))
  82. else:
  83. return u" ".join((p for p in (lastname, firstname) if p))
  84. @api.one
  85. @api.depends("firstname", "lastname")
  86. def _compute_name(self):
  87. """Write the 'name' field according to splitted data."""
  88. self.name = self._get_computed_name(self.lastname, self.firstname)
  89. @api.one
  90. def _inverse_name_after_cleaning_whitespace(self):
  91. """Clean whitespace in :attr:`~.name` and split it.
  92. The splitting logic is stored separately in :meth:`~._inverse_name`, so
  93. submodules can extend that method and get whitespace cleaning for free.
  94. """
  95. # Remove unneeded whitespace
  96. clean = self._get_whitespace_cleaned_name(self.name)
  97. # Clean name avoiding infinite recursion
  98. if self.name != clean:
  99. self.name = clean
  100. # Save name in the real fields
  101. else:
  102. self._inverse_name()
  103. @api.model
  104. def _get_whitespace_cleaned_name(self, name, comma=False):
  105. """Remove redundant whitespace from :param:`name`.
  106. Removes leading, trailing and duplicated whitespace.
  107. """
  108. if name:
  109. name = u" ".join(name.split(None))
  110. if comma:
  111. name = name.replace(" ,", ",")
  112. name = name.replace(", ", ",")
  113. return name
  114. @api.model
  115. def _get_inverse_name(self, name, is_company=False):
  116. """Compute the inverted name.
  117. - If the partner is a company, save it in the lastname.
  118. - Otherwise, make a guess.
  119. This method can be easily overriden by other submodules.
  120. You can also override this method to change the order of name's
  121. attributes
  122. When this method is called, :attr:`~.name` already has unified and
  123. trimmed whitespace.
  124. """
  125. # Company name goes to the lastname
  126. if is_company or not name:
  127. parts = [name or False, False]
  128. # Guess name splitting
  129. else:
  130. order = self._get_names_order()
  131. # Remove redundant spaces
  132. name = self._get_whitespace_cleaned_name(
  133. name, comma=(order == 'last_first_comma'))
  134. parts = name.split("," if order == 'last_first_comma' else " ", 1)
  135. if len(parts) > 1:
  136. if order == 'first_last':
  137. parts = [u" ".join(parts[1:]), parts[0]]
  138. else:
  139. parts = [parts[0], u" ".join(parts[1:])]
  140. else:
  141. while len(parts) < 2:
  142. parts.append(False)
  143. return {"lastname": parts[0], "firstname": parts[1]}
  144. @api.one
  145. def _inverse_name(self):
  146. """Try to revert the effect of :meth:`._compute_name`."""
  147. parts = self._get_inverse_name(self.name, self.is_company)
  148. if parts["lastname"] != self.lastname:
  149. self.lastname = parts["lastname"]
  150. if parts["firstname"] != self.firstname:
  151. self.firstname = parts["firstname"]
  152. @api.one
  153. @api.constrains("firstname", "lastname")
  154. def _check_name(self):
  155. """Ensure at least one name is set."""
  156. if not (self.firstname or self.lastname):
  157. raise exceptions.EmptyNamesError(self)
  158. @api.one
  159. @api.onchange("firstname", "lastname")
  160. def _onchange_subnames(self):
  161. """Avoid recursion when the user changes one of these fields.
  162. This forces to skip the :attr:`~.name` inversion when the user is
  163. setting it in a not-inverted way.
  164. """
  165. # Modify self's context without creating a new Environment.
  166. # See https://github.com/odoo/odoo/issues/7472#issuecomment-119503916.
  167. self.env.context = self.with_context(skip_onchange=True).env.context
  168. @api.one
  169. @api.onchange("name")
  170. def _onchange_name(self):
  171. """Ensure :attr:`~.name` is inverted in the UI."""
  172. if self.env.context.get("skip_onchange"):
  173. # Do not skip next onchange
  174. self.env.context = (
  175. self.with_context(skip_onchange=False).env.context)
  176. else:
  177. self._inverse_name_after_cleaning_whitespace()
  178. @api.model
  179. def _install_partner_firstname(self):
  180. """Save names correctly in the database.
  181. Before installing the module, field ``name`` contains all full names.
  182. When installing it, this method parses those names and saves them
  183. correctly into the database. This can be called later too if needed.
  184. """
  185. # Find records with empty firstname and lastname
  186. records = self.search([("firstname", "=", False),
  187. ("lastname", "=", False)])
  188. # Force calculations there
  189. records._inverse_name()
  190. _logger.info("%d partners updated installing module.", len(records))
  191. @api.multi
  192. def write(self, vals):
  193. name = vals.get('name')
  194. if name and all(name == partner.name for partner in self):
  195. vals.pop('name', None)
  196. # If vals is empty (only write name field and with the same value)
  197. # Avoid access checking here
  198. # https://github.com/odoo/odoo/blob/
  199. # 8b83119fad7ccae9f091f12b6ac89c2c31e4bac3/openerp/addons/base/res/
  200. # res_partner.py#L569
  201. this = self.sudo() if not vals else self
  202. return super(ResPartner, this).write(vals)