135 lines
4.8 KiB
Python
135 lines
4.8 KiB
Python
import unicodedata
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.fields import Domain
|
|
from odoo.tools import escape_psql
|
|
|
|
from odoo.addons.mail.tools.discuss import Store
|
|
|
|
"""
|
|
█
|
|
█
|
|
█
|
|
┌────────────────────────┐
|
|
│ ██████████████████████ │
|
|
│ █ 10:22 PM █ │
|
|
│ █ YO LA TEAM █ │
|
|
│ ██████████████████████ │ T9 "ENCODING" (ITU E.161)
|
|
│ │ =========================
|
|
│ ╔══════╦══════╦══════╗ │
|
|
│ ║ 1 ║ 2 ║ 3 ║ │ Each letter of the Latin alphabet is mapped to a
|
|
│ ║ ║ ABC ║ DEF ║ │ number, which is used to encode a word using only
|
|
│ ╠══════╬══════╬══════╣ │ digits, like on an old cell phone keypad.
|
|
│ ║ 4 ║ 5 ║ 6 ║ │
|
|
│ ║ GHI ║ JKL ║ MNO ║ │
|
|
│ ╠══════╬══════╬══════╣ │
|
|
│ ║ 7 ║ 8 ║ 9 ║ │
|
|
│ ║ PQRS ║ TUV ║ WXYZ ║ │
|
|
│ ╠══════╬══════╬══════╣ │
|
|
│ ║ * ║ 0 ║ # ║ │
|
|
│ ║ ║ ║ ║ │
|
|
│ ╚══════╩══════╩══════╝ │
|
|
└────────────────────────┘
|
|
"""
|
|
T9_MAPPING = {
|
|
letter: digit
|
|
for letters, digit in [
|
|
("abc", "2"),
|
|
("def", "3"),
|
|
("ghi", "4"),
|
|
("jkl", "5"),
|
|
("mno", "6"),
|
|
("pqrs", "7"),
|
|
("tuv", "8"),
|
|
("wxyz", "9"),
|
|
]
|
|
for letter in letters
|
|
}
|
|
|
|
LIGATURES = {
|
|
"Æ": "Ae",
|
|
"æ": "ae",
|
|
"Œ": "Oe",
|
|
"œ": "oe",
|
|
"IJ": "IJ",
|
|
"ij": "ij",
|
|
}
|
|
|
|
|
|
def expand_ligatures(text):
|
|
for ligature, replacement in LIGATURES.items():
|
|
text = text.replace(ligature, replacement)
|
|
return text
|
|
|
|
|
|
def unaccent(text):
|
|
return "".join(
|
|
c for c in unicodedata.normalize("NFKD", text) if unicodedata.category(c) != "Mn"
|
|
)
|
|
|
|
|
|
class ResPartner(models.Model):
|
|
_name = "res.partner"
|
|
_inherit = ["res.partner", "voip.country.code.mixin", "voip.queue.mixin"]
|
|
|
|
t9_name = fields.Char(
|
|
compute="_compute_t9_name",
|
|
export_string_translation=False,
|
|
help=(
|
|
"The partner's name, encoded as the digits that correspond to the letters on an old cell phone keypad.\n"
|
|
"Useful for searching for partners based on this field.\n"
|
|
"Spaces are preserved, characters that can't be encoded are replaced with an 'x'.\n"
|
|
"T9 stands for Text on 9 keys, it comes from the name of the original technology on old cell phones."
|
|
),
|
|
store=True,
|
|
)
|
|
|
|
@api.depends("name")
|
|
def _compute_t9_name(self):
|
|
def encode(letter):
|
|
if letter in T9_MAPPING:
|
|
return T9_MAPPING[letter]
|
|
if letter in "0123456789 ":
|
|
return letter
|
|
return "x"
|
|
|
|
for partner in self:
|
|
if not partner.name:
|
|
partner.t9_name = False
|
|
continue
|
|
normalized_name = expand_ligatures(unaccent(partner.name)).casefold()
|
|
partner.t9_name = "".join(encode(letter) for letter in normalized_name)
|
|
# Add a space at the beginning so you can search for matches at the
|
|
# beginning of each word using a pattern like '% 234%'.
|
|
partner.t9_name = " " + partner.t9_name
|
|
|
|
@api.model
|
|
def get_contacts(self, offset, limit, search_terms, t9_search=False):
|
|
domain = Domain("phone", "!=", False)
|
|
if search_terms:
|
|
escaped_search_terms = escape_psql(search_terms)
|
|
subdomain = Domain.OR([
|
|
[("complete_name", "ilike", escaped_search_terms)],
|
|
[("email", "ilike", escaped_search_terms)],
|
|
])
|
|
if len(search_terms) >= self._phone_search_min_length:
|
|
subdomain |= Domain("phone_mobile_search", "like", escaped_search_terms)
|
|
if t9_search:
|
|
subdomain = Domain.OR([subdomain, [("t9_name", "ilike", f" {escaped_search_terms}")]])
|
|
domain = Domain.AND([domain, subdomain])
|
|
contacts = self.search(domain, offset=offset, limit=limit)
|
|
return Store().add(contacts, self._voip_get_store_fields()).get_result()
|
|
|
|
def _voip_get_store_fields(self):
|
|
return [
|
|
"commercial_company_name",
|
|
"email",
|
|
"function",
|
|
"is_company",
|
|
"name",
|
|
"phone",
|
|
"phone_sanitized",
|
|
Store.One("phone_country_id", self.env["res.country"]._voip_get_store_fields()),
|
|
"t9_name",
|
|
]
|