Контакты и компании¶
Раздел описывает связь контактов и компаний (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 (ручная правка)
│ └── Нет → обычное восстановление
└── Система никогда не падает с ошибкой уникального индекса