Skip to Content

Нормалізація українських номерів телефону в Odoo

Один і той самий український номер користувачі вводять у 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» як два різні рядки — пошук за одним не знайде контакт у другому форматі.

Нормалізація українських номерів телефону в Odoo
KitWorks, Volodymyr Karabanov 25 серпня 2026 р.
Поділитися цією публікацією
Архів