Перейти к содержанию

Контакты и компании

Раздел описывает связь контактов и компаний (many-to-many), identity hash, создание через Drawer, merge дублей и коллизии soft delete.


Many-to-many

Контакт ↔ Компания = many-to-many
  Иванов работает в «Альфа» и «Бета»
  В «Альфа» работают Иванов, Петрова, Сидоров

Identity Hash (подготовка к Org Master Data)

При создании контакта/компании система автоматически генерирует скрытый хеш для будущей кросс-проектной идентификации:

Contact.identity_hash = hash(normalize(email) + normalize(phone))
Company.identity_hash = hash(normalize(ИНН) + normalize(domain))

В v1: хеш хранится, но не используется в UI.
В v2+: при разработке Org Master Data хеш позволит мгновенно
  связать записи одного клиента из разных проектов в единый профиль.

Создание через Drawer

Принцип: минимум полей при создании, обогащение позже.

Drawer = скорость. Карточка = полнота. Не смешиваем.

Создание сделки через Drawer (6-8 полей максимум):
  ├── Обязательные: Название, Воронка, Стадия
  ├── Лёгкие контактные: Имя, Телефон, Email (текстовые поля сделки)
  ├── Компания: dropdown «Выбрать» / «Новая» → только Название
  ├── Продукт: dropdown
  └── Всё.

  ИНН, реквизиты, адрес, доп. контакты — это ОБОГАЩЕНИЕ.
  Менеджер заходит в карточку компании позже и дозаполняет.

Progressive Disclosure (аккордеон):
  Если при создании менеджер выбирает «Новая компания» —
  форма плавно раздвигается вниз, показывая только Название.
  Остальные поля (адрес, реквизиты) — позже в карточке.

Один drawer без вложенности. Для связанных сущностей — выпадающие списки (выбор существующей компании/контакта). Если нужно создать новое — дополнительные поля прямо в drawer (Progressive Disclosure).

Merge дублей

Механизм слияния дублей контактов и компаний:

Дедуп-ключи:
  Контакт: email, телефон (нормализованный)
  Компания: ИНН, домен email, название

Merge-процесс:
  1. Система предлагает кандидатов на слияние
  2. Менеджер выбирает master-запись
  3. Preview: что куда переедет (сделки, продажаы, активности, документы)
  4. Подтверждение → merge
  5. Shadow-копия хранится 30 дней (откат доступен Admin+)
  6. Аудит: кто, когда, какие записи, что изменилось

Минимальная роль для merge: Manager
Откат merge: Admin+

Снимок данных при привязке (Drift Detection)

Контакты и компании — это «записная книжка» проекта. Они переиспользуются в сделках, продажих и других сущностях. При привязке контакта/компании к сущности система делает снимок ключевых полей. Если данные позже изменятся — система подсвечивает расхождение.

Три слоя контактных данных

Слой 1: Лёгкие поля сделки (текст)
  «Иванов, +79205434343, ivan@mail.com»
  Заполняются при создании сделки. НЕ меняются.
  Не связаны с записью в базе контактов.
  Нужны для быстрого ввода до квалификации.

Слой 2: Ссылка на контакт/компанию (живая)
  deal.contact_id → Contact #42
  Всегда показывает АКТУАЛЬНЫЕ данные.
  Появляется после квалификации / ручной привязки.

Слой 3: Снимок на связи (drift detection)
  deal_contact_link.snapshot = { name, phone, email, linked_at }
  Хранится на связи между сделкой и контактом.
  Сравнивается с текущим состоянием Contact #42.
  Если есть расхождение → предупреждение в UI.

Что фиксируется в снимке

Связь Поля снимка
Сущность → Контакт name, phone, email
Сущность → Компания name, ИНН, phone

Где применяется

Связь Приоритет Зачем
Сделка → Контакт Should Have Менеджер работал с одним номером, а номер поменяли
Сделка → Компания Should Have Переименование юрлица, смена реквизитов
Продажа → Контакт Must Have Данные критичны для документов и webhook
Продажа → Компания Must Have Реквизиты уходят в документы и финальные действия

Поведение в UI

Карточка сделки / продажи → блок «Контакт»:

Вариант А (данные не изменились):
  Иванов Иван Иванович
  +7 920 543-43-43
  ivan@mail.com
  [Открыть контакт]

Вариант Б (данные изменились после привязки):
  Иванов Иван Иванович
  +7 916 123-45-67  ⚠ изменён
  ivan@mail.com
  ─────────────────────
  ⚠ Данные контакта изменены после привязки к сделке
  Телефон: +79205434343 → +79161234567 (Петрова, 15 янв)
  [Принять изменения]  [Открыть контакт]

«Принять изменения» — обновляет снимок до текущих данных. Предупреждение исчезает. В аудите — запись «подтверждение изменений контакта».

Особые случаи

Множественные изменения:
  Снимок сравнивается с текущим состоянием, не с промежуточными.
  Если телефон менялся 3 раза — видно только «было при привязке → сейчас».

Merge контактов:
  Если контакт был объединён с другим — снимок остаётся от исходного.
  Расхождение будет заметно → менеджер увидит предупреждение.

Продажи и документы:
  Для продаж система предупреждает перед генерацией документа:
  «Реквизиты компании изменились с момента создания продажи.
  Проверьте данные перед генерацией документа.»

Передача сделки между проектами:
  Снимок передаётся вместе со сделкой (в лёгких полях).
  В целевом проекте при новой привязке — создаётся новый снимок.

Коллизия Soft Delete и уникальных ключей

Сценарий А: Создание при наличии удалённого дубля

Контакт ivan@mail.com удалён (soft delete, в корзине 7 дней).
Кто-то создаёт нового с тем же email.

Поведение:
  ├── Система проверяет И живые И удалённые записи
  ├── Найден удалённый → «Контакт с таким email в корзине. Восстановить?»
  │   ├── Да → восстановление из корзины
  │   └── Нет, создать новый → старая запись hard-delete, новый создаётся
  └── На уровне БД: partial unique index (WHERE deleted_at IS NULL)

Сценарий Б: Восстановление при наличии живого дубля

Контакт ivan@mail.com удалён. Создан новый ivan@mail.com (живой).
Админ пытается восстановить старого из корзины.

Поведение:
  ├── Система проверяет: ключ занят живой записью?
  │   ├── Да → «Контакт с таким email уже существует. Объединить записи?»
  │   │   ├── Да → стандартный Merge (восстанавливаемый + живой)
  │   │   └── Нет → восстановить с очищенным email (ручная правка)
  │   └── Нет → обычное восстановление
  └── Система никогда не падает с ошибкой уникального индекса