Один і той самий український номер користувачі вводять у Odoo як завгодно:
+380671234567, 0671234567, 067 123 45 67, (067) 123-45-67,
380671234567. Поки рядки не зведені до одного вигляду, пошук ловить лише
точний збіг, а контрагенти дублюються. За це в KitWorks відповідає
kw_phone_number_ua, залежний від kw_phone — а той порожній: жодної моделі,
тільки маніфест і залежність на phone_validation.
Де саме нормалізується номер
Нормалізація прив'язана до create/write res.partner, а не до поля
форми. Ось класична пастка Odoo, у яку легко влучити, реалізуючи щось
подібне:
@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if 'phone' in val:
val['phone'] = self._format_phone(val['phone'])
if 'mobile' in vals_list:
val['mobile'] = self._format_phone(val['mobile'])
return super().create(vals_list)
Неправильно. vals_list тут — список словників
(@api.model_create_multi передає список), а не поточний запис, тож
'mobile' in vals_list завжди хибне: phone нормалізується, а mobile
лишається як ввів користувач.
Правильно — перевіряти по val, як зроблено рядком вище:
if 'mobile' in val:
val['mobile'] = self._format_phone(val['mobile'])
Мовчання тут не випадкове: 'ключ' in <список словників> — валідний
Python, in порівнює зі самими елементами списку (словниками), а не з
їхніми ключами, тож результат — просто False. Ні винятку, ні
попередження: поле лишається необробленим, і різницю видно тільки якщо
звірити вхід і вихід вручну.
Нормалізацію одного номера виконує kw_get_formatted_number. Саме
форматування робить kw_format_number — звіряючись зі списком мобільних
кодів:
UA_CODES_3 = ['050', '063', '066', '067', '068', '073', '089', '091', '092',
'093', '094', '095', '096', '097', '098', '099', ]
@api.model
def kw_format_number(self, val):
try:
val = ''.join(i for i in val if i.isdigit())
except Exception as e:
_logger.debug('Numbers "%s" has error %s', val, e)
return ''
if not val:
return ''
if len(val) == 10 and val[:3] in UA_CODES_3:
return '+38{}'.format(val)
if len(val) == 9 and val[:2] in UA_CODES_2:
return '+380{}'.format(val)
if len(val) == 12 and val[:3] == '380':
'+{}'.format(val)
return val
У гілці для 12-значних номерів (380671234567) вираз '+{}'.format(val)
нікуди не йде — обчислення в порожнечу. Ну і на щастя, не критично:
phone_format з ядрового phone_validation (phonenumbers) сам перепарсює
задовгий номер, додавши +. Я перевірив усі п'ять варіантів із першого
абзацу — сходяться в один рядок: +380 67 123 4567.
Номери, які не схожі на українські
Спробуємо подати на вхід щось, що під українську маску не підпадає:
>>> kw_get_formatted_number('+14155552671') # валідний номер США
'+1 415-555-2671'
>>> kw_get_formatted_number('12345') # цифри, але не номер
'12345'
>>> kw_get_formatted_number('0000000000') # 10 цифр, коду немає
'0000000000'
>>> kw_get_formatted_number('текст без цифр')
''
Закордонний номер kw_format_number не впізнає за довжиною, але
phone_format розпізнає його через phonenumbers — та сама логіка
перепарсингу з +. А от справді невалідний цифровий рядок
phone_format не розпізнає: виключення ловиться (raise_exception=False), і
назовні йде той самий рядок голих цифр — без помилки, без +. Рядок без
жодної цифри kw_format_number перетворює на '' ще до виклику
phone_format.
Важливо! Живий onchange у формі тут ні до чого. onchange_phone/
onchange_mobile оголошені через @api.depends('phone'), а не
@api.onchange('phone') — а @api.depends спрацьовує лише для методу,
підписаного як compute якогось поля. Такого поля немає, тож при введенні
номера в формі ці методи не викликаються. Реально вони запускаються лише
кнопкою «Re-format contacts phones» в Налаштуваннях:
def kw_phone_format_contacts(self):
for obj in self.env['res.partner'].search([]):
obj.onchange_phone()
obj.onchange_mobile()
Це єдиний спосіб перенормалізувати вже наявні в базі номери — автоматично цього не робить ніхто.
Пошук за телефоном
kw_phone_search (і похідні kw_phone_search_crm, kw_phone_search_sale)
самі нічого не нормалізують — залежать тільки від kw_phone, без зв'язку з
kw_phone_number_ua. Пошук будується на окремому індексованому полі:
kw_phone_number_name = fields.Char(
compute='_compute_kw_phone_number_name', store=True, index=True,
compute_sudo=True, )
@api.depends('phone', 'mobile', 'name', 'email', 'parent_id',
'parent_id.name', )
def _compute_kw_phone_number_name(self):
kw_cleanup_string = self.env['kw.clean.up.mixin'].kw_cleanup_string
for obj in self:
obj.kw_phone_number_name = '{}{}{}{}{}'.format(
kw_cleanup_string(obj.phone),
kw_cleanup_string(obj.mobile),
kw_cleanup_string(obj.name),
kw_cleanup_string(obj.email),
kw_cleanup_string(
obj.parent_id.name if obj.parent_id else ''))
kw_cleanup_string лишає тільки цифри й літери, все це склеюється в один
рядок без розділювачів. Якщо phone вже +380 67 123 4567, у
kw_phone_number_name осяде 380671234567, і 0671234567 знайдеться через
ilike — «380» закінчується тією ж «0», що й національний формат. Але це
працює тільки тому, що номер уже нормалізований kw_phone_number_ua. Без
нього в базі можуть лежати «0671234567» і «+380671234567» як два різні рядки
— пошук за одним не знайде контакт у другому форматі.