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.

232 lines
8.5 KiB

6 years ago
  1. # Copyright 2013 Nicolas Bessi (Camptocamp SA)
  2. # Copyright 2014 Agile Business Group (<http://www.agilebg.com>)
  3. # Copyright 2015 Grupo ESOC (<http://www.grupoesoc.es>)
  4. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  5. import logging
  6. from odoo import _, api, fields, models
  7. from .. import exceptions
  8. _logger = logging.getLogger(__name__)
  9. class ResPartner(models.Model):
  10. """Adds last name and first name; name becomes a stored function field."""
  11. _inherit = "res.partner"
  12. firstname = fields.Char("First name", index=True)
  13. lastname = fields.Char("Last name", index=True)
  14. name = fields.Char(
  15. compute="_compute_name",
  16. inverse="_inverse_name_after_cleaning_whitespace",
  17. required=False,
  18. store=True,
  19. readonly=False,
  20. )
  21. @api.model
  22. def create(self, vals):
  23. """Add inverted names at creation if unavailable."""
  24. context = dict(self.env.context)
  25. is_contact_copied = (
  26. context.get("copy") and vals.get("firstname") and not vals.get("is_company")
  27. )
  28. if is_contact_copied and "name" in vals:
  29. vals.pop("name", None)
  30. context.pop("default_name", None)
  31. name = vals.get("name", context.get("default_name"))
  32. if name is not None:
  33. # Calculate the splitted fields
  34. inverted = self._get_inverse_name(
  35. self._get_whitespace_cleaned_name(name),
  36. vals.get("is_company", self.default_get(["is_company"])["is_company"]),
  37. )
  38. for key, value in inverted.items():
  39. if not vals.get(key) or context.get("copy"):
  40. vals[key] = value
  41. # Remove the combined fields
  42. if "name" in vals:
  43. del vals["name"]
  44. if "default_name" in context:
  45. del context["default_name"]
  46. return super(ResPartner, self.with_context(context)).create(vals)
  47. def copy(self, default=None):
  48. """Ensure partners are copied right.
  49. Odoo adds ``(copy)`` to the end of :attr:`~.name`, but that would get
  50. ignored in :meth:`~.create` because it also copies explicitly firstname
  51. and lastname fields.
  52. """
  53. if default is None:
  54. default = {}
  55. if self.firstname and not self.is_company:
  56. default["firstname"] = _("%s (copy)", self.firstname)
  57. return super(ResPartner, self.with_context(copy=True)).copy(default)
  58. @api.model
  59. def default_get(self, fields_list):
  60. """Invert name when getting default values."""
  61. result = super(ResPartner, self).default_get(fields_list)
  62. inverted = self._get_inverse_name(
  63. self._get_whitespace_cleaned_name(result.get("name", "")),
  64. result.get("is_company", False),
  65. )
  66. for field in list(inverted.keys()):
  67. if field in fields_list:
  68. result[field] = inverted.get(field)
  69. return result
  70. @api.model
  71. def _names_order_default(self):
  72. return "first_last"
  73. @api.model
  74. def _get_names_order(self):
  75. """Get names order configuration from system parameters.
  76. You can override this method to read configuration from language,
  77. country, company or other"""
  78. return (
  79. self.env["ir.config_parameter"]
  80. .sudo()
  81. .get_param("partner_names_order", self._names_order_default())
  82. )
  83. @api.model
  84. def _get_computed_name(self, lastname, firstname):
  85. """Compute the 'name' field according to splitted data.
  86. You can override this method to change the order of lastname and
  87. firstname the computed name"""
  88. order = self._get_names_order()
  89. if order == "last_first_comma":
  90. return ", ".join(p for p in (lastname, firstname) if p)
  91. elif order == "first_last":
  92. return " ".join(p for p in (firstname, lastname) if p)
  93. else:
  94. return " ".join(p for p in (lastname, firstname) if p)
  95. @api.depends("firstname", "lastname")
  96. def _compute_name(self):
  97. """Write the 'name' field according to splitted data."""
  98. for record in self:
  99. record.name = record._get_computed_name(record.lastname, record.firstname)
  100. def _inverse_name_after_cleaning_whitespace(self):
  101. """Clean whitespace in :attr:`~.name` and split it.
  102. The splitting logic is stored separately in :meth:`~._inverse_name`, so
  103. submodules can extend that method and get whitespace cleaning for free.
  104. """
  105. for record in self:
  106. # Remove unneeded whitespace
  107. clean = record._get_whitespace_cleaned_name(record.name)
  108. record.name = clean
  109. record._inverse_name()
  110. @api.model
  111. def _get_whitespace_cleaned_name(self, name, comma=False):
  112. """Remove redundant whitespace from :param:`name`.
  113. Removes leading, trailing and duplicated whitespace.
  114. """
  115. if isinstance(name, bytes):
  116. # With users coming from LDAP, name can be a byte encoded string.
  117. # This happens with FreeIPA for instance.
  118. name = name.decode("utf-8")
  119. try:
  120. name = " ".join(name.split()) if name else name
  121. except UnicodeDecodeError:
  122. # with users coming from LDAP, name can be a str encoded as utf-8
  123. # this happens with ActiveDirectory for instance, and in that case
  124. # we get a UnicodeDecodeError during the automatic ASCII -> Unicode
  125. # conversion that Python does for us.
  126. # In that case we need to manually decode the string to get a
  127. # proper unicode string.
  128. name = " ".join(name.decode("utf-8").split()) if name else name
  129. if comma:
  130. name = name.replace(" ,", ",")
  131. name = name.replace(", ", ",")
  132. return name
  133. @api.model
  134. def _get_inverse_name(self, name, is_company=False):
  135. """Compute the inverted name.
  136. - If the partner is a company, save it in the lastname.
  137. - Otherwise, make a guess.
  138. This method can be easily overriden by other submodules.
  139. You can also override this method to change the order of name's
  140. attributes
  141. When this method is called, :attr:`~.name` already has unified and
  142. trimmed whitespace.
  143. """
  144. # Company name goes to the lastname
  145. if is_company or not name:
  146. parts = [name or False, False]
  147. # Guess name splitting
  148. else:
  149. order = self._get_names_order()
  150. # Remove redundant spaces
  151. name = self._get_whitespace_cleaned_name(
  152. name, comma=(order == "last_first_comma")
  153. )
  154. parts = name.split("," if order == "last_first_comma" else " ", 1)
  155. if len(parts) > 1:
  156. if order == "first_last":
  157. parts = [" ".join(parts[1:]), parts[0]]
  158. else:
  159. parts = [parts[0], " ".join(parts[1:])]
  160. else:
  161. while len(parts) < 2:
  162. parts.append(False)
  163. return {"lastname": parts[0], "firstname": parts[1]}
  164. def _inverse_name(self):
  165. """Try to revert the effect of :meth:`._compute_name`."""
  166. for record in self:
  167. parts = record._get_inverse_name(record.name, record.is_company)
  168. record.lastname = parts["lastname"]
  169. record.firstname = parts["firstname"]
  170. @api.constrains("firstname", "lastname")
  171. def _check_name(self):
  172. """Ensure at least one name is set."""
  173. for record in self:
  174. if all(
  175. (
  176. record.type == "contact" or record.is_company,
  177. not (record.firstname or record.lastname),
  178. )
  179. ):
  180. raise exceptions.EmptyNamesError(record)
  181. @api.model
  182. def _install_partner_firstname(self):
  183. """Save names correctly in the database.
  184. Before installing the module, field ``name`` contains all full names.
  185. When installing it, this method parses those names and saves them
  186. correctly into the database. This can be called later too if needed.
  187. """
  188. # Find records with empty firstname and lastname
  189. records = self.search([("firstname", "=", False), ("lastname", "=", False)])
  190. # Force calculations there
  191. records._inverse_name()
  192. _logger.info("%d partners updated installing module.", len(records))
  193. # Disabling SQL constraint givint a more explicit error using a Python
  194. # contstraint
  195. _sql_constraints = [("check_name", "CHECK( 1=1 )", "Contacts require a name.")]