# Changelog разработки CRM TECH

История разработки проекта с Сентября 2025 по 18.04.2026.

---

## 18.04.2026: Виджет контактных данных — исправление синхронизации между модалами

### 🐛 Исправление: контакты не отображались в форме сделки после сохранения через карточку клиента

- **Причина:** После нажатия «💾 В систему» в карточке клиента данные уходили на сервер, но `DB.db.clients[clientId]` в памяти не обновлялся и `state.allDeals` не перезаписывался. При открытии формы сделки `record` брался из `state.currentData` — старые ссылки, без изменений.
- **Исправление:** В `client_card.js` и `form.js` после успешного `update_client_info` теперь **мутируются** существующие объекты в `state.allDeals` (не пересоздаётся массив), что автоматически обновляет `state.currentData`, поскольку оба массива разделяют одни и те же объектные ссылки.
- **Эффект:** Контакт, добавленный в карточке клиента, немедленно виден при открытии формы сделки — без перезагрузки страницы.

**Файлы:** `js_modules/ui/form.js`, `js_modules/ui/client_card.js`

---

## 15–18.04.2026: Виджет контактных данных клиента (ИНН + Контакты)

### 📇 Новый виджет в форме сделки (правая колонка)

- Добавлена карточка **«Контактные данные»** в правую колонку формы сделки (`section-contact-info`) под блоком «Ответственные».
- **ИНН**: отображение / редактирование через inline-форму. При изменении — обновляется скрытый `[data-key="ИНН"]`, подхватывается при обычном сохранении сделки.
- **Список контактов**: каждый контакт содержит поля «Имя», «Должность», «Телефон», «Email». Полное CRUD: добавление, редактирование, удаление.
- **Предупреждение ⚠️**: если телефон, email или ИНН не заполнены — выводится плашка с перечислением недостающих полей.
- **Legacy-режим**: если структурированных контактов нет, но есть телефон/email из старых данных или синхронизации — отображается в виде «Данные из карточки» (серая запись).
- **Кнопка «💾 В систему»**: сохраняет ИНН и контакты напрямую в `clients` через `update_client_info` — без необходимости сохранять сделку целиком. Полезно для обогащения данных клиента от руководителей и бренд-менеджеров.
- Скрытые поля `[data-key="Контакты"]` и `[data-key="ИНН"]` автоматически подхватываются при сохранении формы.

### 📇 Тот же виджет в карточке клиента (`clientCardModal`)

- Аналогичный виджет встроен в сайдбар `clientCardModal` (метод `_mountContactWidget`).
- Отображает ИНН, список контактов, предупреждение о незаполненных данных.
- Полное редактирование с сохранением через «💾 В систему».
- После сохранения — обновляет `DB.db.clients[clientId]` и все объекты в `state.allDeals` с данным `clientId`.

### 🔧 Исправление: данные пропадали после перезагрузки

- **Причина:** `getAllFlatDeals()` читал `Контакты` исключительно из `deals.data_json` (через `...deal`). При сохранении через «💾 В систему» контакты записывались в `clients.data_json`, но при перезагрузке не подхватывались.
- **Исправление:** В `crm_architecture.js` добавлен явный маппинг `'Контакты': resolvedContacts` — если в сделке нет непустых контактов, берётся запись из профиля клиента.
- **PHP `save_deal`:** при сохранении сделки контакты из `deals.data_json` теперь синхронизируются и в `clients.data_json` — оба пути записи всегда обновляют профиль клиента.

**CSS:** добавлены классы `.cw-*` — `.cw-inn-row`, `.cw-inn-val`, `.cw-warn`, `.cw-section-title`, `.cw-contact-item`, `.cw-contact-form`, `.cw-form-row`, `.cw-footer`, `.cw-btn-save`, `.cw-add-btn`, `.cw-system-btn`, `.cw-edit-btn`, `.cw-del-btn`, `.cw-legacy-item`, `.cw-card-modal`

**Файлы:** `js_modules/ui/form.js`, `js_modules/ui/client_card.js`, `crm_architecture.js`, `getapi/api_sqlite.php`, `style.css`, `js_modules/config.js`

---

## 12–15.04.2026: Синхронизация контрагентов из 1С (2 скрипта)

### 🔄 Этап 1 — Точное совпадение (`sync_clients_json.php`)

- Скрипт обновляет клиентов CRM данными из файла `clean_data.json` (выгрузка из 1С) по **точному совпадению** `client.name === json["Ссылка"]`.
- **Режимы:**
  - `?mode=dry` — предварительный анализ (по умолчанию): показывает что будет обновлено, не изменяя базу
  - `?mode=run` — применение изменений
  - `?overwrite=0` — обновлять только пустые поля (по умолчанию)
  - `?overwrite=1` — перезаписывать существующие значения
- **Обновляемые поля:** `inn` (в отдельную колонку `clients.inn`), `phone`, `email`, `data_json["ИНН"]`.
- Столбец `inn` добавляется в таблицу автоматически через `ALTER TABLE … ADD COLUMN` (безопасно при повторном запуске).
- Токен авторизации: `?token=crm_json_sync`.
- Лог-файл: `sync_clients_json.log`.
- Результат: JSON с `matched`, `updated`, `skipped`, `total_json` и `errors`.

### 🔄 Этап 2 — Нормализация + ручное подтверждение (`sync_stage2.php`)

- Для клиентов, не нашедших точного совпадения в Этапе 1, применяется **нормализация** имён через `normalize_key()`: приведение к CamelCase, удаление стоп-слов (ООО, АО, ИП и т.д.), алфавитная сортировка слов.
- **GET-запрос** → HTML-таблица кандидатов с чекбоксами «Да / Нет» по каждой строке. Ячейки `diffCell()` показывают текущее значение → новое значение.
- **POST-запрос** → применение только подтверждённых обновлений (по отмеченным чекбоксам).
- Клиенты, обработанные в Этапе 1 (точное совпадение), пропускаются автоматически.
- Токен авторизации: тот же `?token=crm_json_sync`.
- Лог-файл: `sync_stage2.log`.

**Файлы:** `sync_clients_json.php`, `sync_stage2.php`, `миграция.txt` (ТЗ)

---

## 08–12.04.2026: Модуль управления КПИ и вкладка анализа заполненности

### 📈 Новый модуль «КПИ» (`kpi.js`)

- Новая вкладка **«КПИ»** в навигации (доступна администраторам и руководителям).
- Администратор устанавливает целевые показатели для каждого менеджера за выбранный месяц.
- **5 типов KPI:**
  - 📞 Звонки (план в день / в месяц)
  - 📋 Коммерческие предложения (КП) отправлено (в день / в месяц)
  - 🏭 Единицы продаж по бренду (план на месяц / год)
  - 🗓️ Встречи с клиентом (план / факт)
- **Визуализация:** таблица «план vs факт» с прогресс-барами для каждого менеджера (🟢 ≥80%, 🟡 ≥50%, 🔴 <50%).
- **Сводная строка:** общее кол-во менеджеров, среднее выполнение плана звонков.
- **Фильтр периода:** выбор месяца через `<input type="month">`.
- Кнопка **«↻ Обновить»** для принудительного обновления данных.
- Виджет КПИ встроен в рабочее место — доступен через быструю кнопку в Zone-1.
- **Быстрый модал** (`openModal()`) — без перехода на вкладку: открывается поверх любого контента.
- **Бэкенд:** `get_kpi_targets` (GET) — список менеджеров + их таргеты + фактические значения; `save_kpi_target` (POST) — сохранение плановых значений.
- Фактические значения рассчитываются автоматически из `contact_messages` (звонки, КП, встречи) и `deals` (проданные единицы).

**Файлы:** `js_modules/ui/kpi.js`, `getapi/api_sqlite.php`, `style.css`, `index.php`

---

### 📊 Новый модуль «Анализ заполненности» (`fill_rate.js`)

- Новая вкладка **«Анализ заполн.»** в навигации (только администраторам).
- Браузерный аналог скрипта `analyze_fill_rate.py` — полный расчёт заполненности полей прямо в интерфейсе без запуска Python.
- **52 поля** формы сделки сгруппированы по 5 секциям (Данные о клиенте, Потребности и Техника, Финансы и Условия, Данные о сделке, Результат).
- Условные поля (`CONDITIONAL_FIELDS`) учитываются только по применимым сделкам — например «SKU навигации» считается только среди сделок с «Требуется навигация = да».
- Числовые поля (`NUMERIC_FIELDS`) нормализуются перед проверкой: пробелы, запятые, символ `%` — обрабатываются корректно.
- Итоговая таблица: **Поле | Применимо | Заполнено | % | Тип**.
- Обязательные поля выделены отдельно.
- Сводка по секциям с процентом заполненности каждой группы.

**Файлы:** `js_modules/ui/fill_rate.js`, `js_modules/main.js`, `index.php`

---

## 08.04.2026: Реструктуризация рабочего места — новые вкладки

### 🗂️ Переименование и добавление вкладок рабочего места

По итогам встречи 08.04.2026 проведена реструктуризация навигации рабочего пространства:

- Вкладка **«Рабочее место»** → переименована в **«Рабочее место менеджера»**
- Добавлена вкладка **«Рабочее место БМ»** (рабочее место бренд-менеджера)
- Добавлена вкладка **«Рабочее место Руководителя»**
- Добавлена вкладка **«Рабочее место рег. Руководителя»**
- Каждая вкладка отображается только пользователям с соответствующей ролью

**Файлы:** `index.php`, `js_modules/main.js`

---

## 11.04.2026: Планировщик × Коммуникация — интеграция и редизайн

### 🔗 Связь задач "До сделки" с разделом "Коммуникация"

**Суть:** Задачи типа «До сделки» в Планировщике теперь полностью интегрированы с вкладкой «Коммуникация». Менеджер может привязать задачу к существующему контакту чата — и она автоматически появится в истории переговоров как карточка.

**Схема данных (миграция безопасна при повторном запуске):**
- `tasks.contact_id TEXT` — ссылка на контакт из чата
- `tasks.chat_msg_id TEXT` — id карточки в `contact_messages` (обратная ссылка)
- `contact_messages.planner_task_id TEXT` — ссылка на задачу в планировщике
- `contacts.client_id TEXT` — привязка контакта к CRM-клиенту

**Планировщик → "До сделки":**
- Поле контакта с autocomplete (portal dropdown, выход за пределы `overflow:hidden`)
- Выбор из существующих контактов чата по имени / компании
- Кнопка ➕ — создание нового контакта прямо из формы задачи (callback-паттерн)
- При сохранении с выбранным контактом: автоматически создаётся карточка в чате (`call_task` / `meeting_task` / `kp` / `message`)
- Защита от дублей: если `chat_msg_id` уже заполнен — повторная карточка не создаётся
- Если контакт не выбран из списка — задача сохраняется как «до сделки» без связи с чатом + предупреждение ⚠️

**Завершение из Планировщика:**
- Чекбокс ✅ → inline-форма результата (вместо устаревшего `prompt()`)
- `toggle_task(id, result_text)` → `is_done=1` + запись `task_result` в чат
- Параметр `force_done=true` предотвращает toggle loop при двусторонней синхронизации
- Защита от дублей `task_result`: проверка `COUNT(*) WHERE type='task_result' AND deal_id=chat_msg_id`

**Завершение из Чата:**
- Кнопка «Завершить» на карточке → POST `task_result` + `toggle_task(planner_task_id, force_done=true)`
- Задача в Планировщике отмечается выполненной автоматически

**Чат — новые возможности:**
- Кнопка 🔗 «Клиент CRM» в шапке чата — привязка контакта к существующей CRM-сделке
- После привязки кнопка заменяется зелёным бейджем с именем клиента
- `link_contact_to_client` action в PHP и Python

**Бэкенд:**
- `api_sqlite.php`: расширены `save_task`, `toggle_task`, `get_workspace_data`, `get_contact_messages`, `get_contacts`, `get_clients_quick_list`; добавлен `link_contact_to_client`
- `python_server.py`: полное зеркало PHP — все новые handlers, миграции, routing

---

### 🎨 Редизайн списков задач

**Планировщик (`ws-task-list`):**
- Задачи из плоских строк превратились в **карточки** (`border-radius: 8px`, тень при hover)
- Цветная левая полоска по типу: 🔵 Звонок · 🟢 Встреча · 🟠 КП · ⚫ Другое
- Дата — бейдж-пилюля; просроченные — красный бейдж с жирным шрифтом
- Кнопка "✕" скрыта и появляется только при наведении на карточку
- Секция результата — зелёный фон `#f0faf6` с подписью «Результат:»
- Кнопка "↗ Открыть сделку" на задачах типа «По сделке» → `FormUI.openModal(deal_id)`

**Форма сделки (`deal-task-list`):**
- Аналогичный редизайн: карточки с цветным бордером по типу, badge для даты, hover-эффект
- Кнопка удаления скрыта до наведения
- Секция результата — стиль `deal-task-result` с зелёным фоном

**Файлы:** `js_modules/ui/workspace.js`, `js_modules/ui/workspace_chat.js`, `js_modules/ui/form.js`, `getapi/api_sqlite.php`, `../python_server.py`, `style.css`

---

## 02.04.2026: Утечки памяти Chart.js — исправление (задача 3.3)

### 🛡️ 3.3 Уничтожение Chart-инстансов при пересоздании

- Добавлен метод `_safeCreateChart(canvasOrCtx, config)`: перед `new Chart(...)` вызывает `Chart.getChart(canvas)` и уничтожает висячий инстанс, если он есть.
- Добавлен метод `destroyAllCharts()`: полное уничтожение всех графиков из `state.charts` со сбросом словаря.
- Все 13 точек создания Chart-инстансов (`ensureChart` + `renderProductAnalysis` + `renderRegionAnalysis` + `renderCompetitorAnalysis` + `renderBMParticipation`) переведены на `_safeCreateChart`.
- Исключена ошибка «Canvas is already in use» при многократных переходах на вкладку «Аналитика».
- Файл: `js_modules/ui/analytics.js`.

---

## 02.04.2026: Форма сделки + фильтр по году в списке (задачи 2.2–2.4)

### 📅 2.4 Фильтр по году на вкладке «Список»

- Добавлен выпадающий список «Год» (2025 / 2026) в блок фильтров таблицы.
- По умолчанию выставляется текущий год — показываются только актуальные переговоры.
- Фильтрация по полю «Дата создания сделки» (`getFullYear()`).
- Опция «Все годы» доступна для просмотра полной истории.
- Изменено в: `index.php`, `index.html`, `js_modules/main.js`.

---

## 02.04.2026: Форма сделки — автоматическая логика при проигрыше (задачи 2.2–2.3)

### 🔒 2.2 Автоматическая вероятность 0% при «Купил у конкурента»

- Исправлен баг в `lockProbabilityForLostDeal`: `setAttribute('readonly')` заменён на `disabled = true` — атрибут `readonly` не блокирует `<select>`, пользователь мог изменить значение.
- Теперь поле «Вероятность» корректно заблокировано при статусе «Купил у конкурента» и разблокируется при смене статуса.

### 🚫 2.3 Логика новой сделки после проигрыша

- Добавлен метод `lockStatusForLostDeal(status)` в `FormUI`.
- При открытии существующей сделки со статусом «Купил у конкурента»: поле «Итоговый статус сделки» блокируется (`disabled`), в начале формы появляется предупреждение «Для повторной работы с клиентом создайте новую сделку».
- При смене статуса на любой другой — блокировка и баннер снимаются.
- Новые сделки (`isNewDeal = true`) не затрагиваются.

---

## 31.03.2026: Аналитика — рефакторинг KPI и структуры (задачи 1.1–1.5)

### 📊 1.1 KPI-карточки «Воронка продаж»

- Карточка «Воронка сделок»: «Закрыто» → «Выиграно» (теперь показывает wins, не wins+lost); добавлен sub-item «Проиграно».
- Карточка «Результаты»: убран sub-item «Win Rate» (дублировал блок конверсии).
- Карточка «Конверсия»: label «в продажу» → «по сделкам»; убраны sub-items «Win Rate» и «Ср. единиц» (поле заполнено не у всех сделок).
- Карточка «Продажи» → переименована в «Продано»; убраны sub-items «Клиентов» и «Ср. на сделку».
- Добавлен блок **«Вероятность совершения сделки»** — таблица с разбивкой по bucket'ам 0/10/30/50/70/100%, итог = общее кол-во сделок.
- В `mapDealsToAnalyticsItems` добавлено поле `вероятность` из `'Вероятность совершения сделки в %'`.
- Шрифты KPI: `kpi-value` 1.35rem → **2rem** (font-weight 700); `kpi-label` 0.8 → 0.9rem; `kpi-sub-item` 0.75 → 0.85rem.

### 📋 1.2 Карта эффективности менеджеров

- Убраны все колонки с «единицами» из «Карты эффективности» и «Метрик менеджеров».
- Новый набор колонок: **Менеджер | Успешные | В работе | Отложено | Проигрыш | Возврат**.
- Добавлена колонка «Отложено» — сделки в статусе «Отложено» (ранее попадали в «В работе»).
- Сортировка по умолчанию — по количеству сделок (убывание) в обеих таблицах.
- `bucket` в `renderManagerReport`: добавлено поле `отложено`, убрано накопление `units`; убрана Конверсия из «Метрик менеджеров».
- Экспортные функции `exportManagerMetricsToExcel` и `exportHeatmapToExcel` обновлены синхронно.

### 🔀 1.3 Layout вкладки «Аналитика»

- «Карта эффективности» и «Воронка продаж & Топ конкурентов» поменяны местами (Карта — слева, Воронка — справа) через CSS `grid-column` + `grid-row: 1`.

### 🗑️ 1.4 Очистка структуры аналитики

- Удалена нижняя дублирующая таблица `reports-table-wrap` (с пагинацией) из обоих HTML-файлов.
- Удалена диаграмма «Матрица брендов» (bubble chart) — HTML-блок и ~150 строк JS (`renderProductAnalysis`).
- Исключён тестовый аккаунт «Битрикс» из всех таблиц аналитики — фильтр в `mapDealsToAnalyticsItems` по полю `Менеджер сделки` (case-insensitive).

### 🔍 1.5 Санки-диаграмма — фильтр по брендам

- Добавлен выпадающий список `#sankey-brand-filter` над санки-диаграммой.
- Список брендов формируется динамически из текущих проигранных сделок с учётом глобальных фильтров.
- Выбранный бренд фильтрует потоки до рендера; значение сохраняется при перерисовке (смена периода/менеджера).
- Старый `sankey-filter` (Топ-10/от 5 сделок) заменён бренд-фильтром.

---

## Сентябрь 2025 - Ноябрь 2025: Старт и Базовая архитектура
- Инициализация проекта.
- Обработка Excel файлов оперативного учета, конвертация в JSON.
- Тестовая разработка интерфейса, базовая логика.
- Верстка основных элементов и стилизация.

## Декабрь 2025: Канбан и Миграция на БД
- Доработка интерфейса Канбан-доски.
- Создание многопользовательского режима.
- Миграция данных на базу данных SQLite.
- Реализация `api_sqlite.php` как основной точки входа API.
- Обработка базовых запросов: `load_all`, `save_deal`, `delete_deal`.

## Январь 2026: Интеграция, UI/UX и Логика полей
- **Аутентификация и Авторизация**:
  - Интеграция с Bitrix через `bitrix_auth.php`.
  - Проверка ролей пользователей через файл `.env`.
- **Интерфейс**:
  - Обработка последовательности этапов сделки.
  - Создание режима подсказок (Tips Mode).
  - Функционал фильтрации и настройки столбцов.
  - Приведение поведения поля "Вероятность" в расширенном режиме к табличному виду (выпадающий список).
- **Логика формы**:
  - Поле "Продано единиц": скрыто по умолчанию, появляется и обязательно при статусе "Успешно".
  - Поле "Возврат": скрыто по умолчанию, появляется и обязательно при статусе "Возврат".
  - Поля "Менеджер сделки" и "Бренд-Менеджер" переведены на выпадающие списки.
  - Исправлено сохранение списка "Степень участия бренд менеджера".

## Февраль 2026 (по 18.02.2026): Ролевая модель и Финальные правки
- **Новая система ролей (конфигурация через `.env`)**:
  - `CRM_ADMINS`, `CRM_LEADERS`: Полный доступ, смена аккаунта.
  - `CRM_LEADERS_Region`: Региональные руководители (доступ к сделкам своего региона).
  - `CRM_BRAND_MANAGERS`: Доступ к своим сделкам и сделкам, где указаны как менеджер.
  - `CRM_MANAGERS`: Доступ только к своим сделкам.
- **Функциональность**:
  - Таблица привязки руководителей к регионам.
  - Механизм авторизации через сайт.
  - Сортировка сделок в табличном виде (по клику на заголовки: Этап, Статус, Итоговый статус, Ответственный, Вероятность).
  - Сортировка списка: Новые заявки всегда отображаются сверху.
- **Бизнес-правила**:
  - **Финансы**: Добавлено поле "№ Счета" (генерируется автоматически от "Условий договора") и "Сумма счета" — доступны только при статусе "Успешно".
  - **Отложено**: При переходе в статус "Отложено" появляется обязательное поле "Дата след. контакта".
  - **Отказ**: Для статуса "Отказ" поле "Причина проигрыша" заменено на "Причина Отказа" со специфическим списком причин (Нет денег, Ждет цену, Конкуренты и т.д.).
- **Справочники**:
  - Обновлен список регионов (Омская обл., Новосибирская обл., Казахстан и др.).

## 18.02.2026: Табличный интерфейс, поиск и донастройка ролей
- **Ролевая модель и доступ**:
  - Уточнена логика определения ролей по группам Bitrix (`id 1/20/15/19/17/16`) с приоритетом ролей (региональный руководитель / бренд‑менеджер выше менеджера).
  - Для бренд‑менеджеров расширена видимость: помимо своих сделок и сделок как менеджер, они видят сделки по закреплённым брендам и регионам.
  - Для бренд‑менеджеров серверная фильтрация смещена на фронтенд: API отдаёт сделки без жёсткого ограничения, детальная фильтрация выполняется в браузере.
- **Поиск и список сделок**:
  - Заголовок "CRM System" заменён на универсальную строку поиска в шапке.
  - Реализован глобальный поиск по Клиенту / Контактам, Ответственному, Бренд‑менеджеру и Региону.
  - Исправлен поиск по контрагенту: используются поля "Клиент (ФИО/Название компании)", "ФИО контактного лица", "Телефон контакта источника".
- **Таблица сделок**:
  - Настроен предзагруженный порядок столбцов в табличном виде (Ред., Клиент / Контакты, Регион, Техника / Потребности, Бренд, Модель, Вид, Статус техники, Ответственный, БМ, Участие БМ, Степень участия БМ, Этап сделки, Статус этапа, Итог. статус, Вероятность, Дата создания).
  - Введён новый ключ сохранения настроек колонок `crm_visible_columns_v2`, чтобы не конфликтовать с предыдущими пользовательскими конфигурациями.
  - Доработана сортировка по составным колонкам: "Клиент / Контакты", "Техника / Потребности", "Источник / Детали" теперь сортируются по релевантным полям (имя + регион, бренд + модель + вид, источник лида).
- **UX и прокрутка**:
  - Эксперимент с отдельным горизонтальным ползунком под таблицей завернут: кастомный скролл удалён.
  - Таблица растянута на ширину контента, используется только дефолтный горизонтальный скролл браузера у нижней границы экрана.

## 13.03.2026: Обмен с 1С, аналитика, рабочее место и переговоры

### 🔄 Обмен с 1С — исправление критической ошибки

- **Выявлен и устранён баг в `api_sqlite.php`**: SQL-запросы в эндпоинтах `get_negotiations` и `load_all` обращались к колонкам `object_type` и `object_id` таблицы `outbox_events` как к реальным столбцам. Фактически эти поля хранятся исключительно внутри JSON-поля `payload` — отдельных колонок в схеме нет.
  - **Последствия**: `get_negotiations` → исключение SQLite `no such column: object_type` → HTTP 500 → переговоры по сделкам не отображались в интерфейсе после сохранения. В `load_all` ошибка поглощалась молча, статусы синхронизации 1С для сделок не вычислялись.
  - **Патч**: прямые обращения к колонкам заменены на `json_extract(payload, '$.object_type')` и `json_extract(payload, '$.object_id')` в обоих запросах.

- **Архитектура outbox-системы задокументирована**:
  - Таблица `outbox_events`: `id (evt_<hex16>)`, `event_type`, `payload (JSON)`, `status (pending/sent/failed)`, `retry_count`, `notified`.
  - Типы событий: `deal.created`, `deal.updated`, `deal.deleted`, `deal.negotiation_added`.
  - `sync/sync_1c.py` — батчевая отправка (до 100 событий) на HTTP-эндпоинт 1С, авторизация `basic` или `X-CRM-Token`, retry до 3 попыток, SMTP-уведомление администратора при превышении лимита.
  - `sync/start_sync.ps1` — регистрация задачи в Windows Task Scheduler (`CRM_1C_Sync`, триггер каждые 5 минут).
  - `GET get_sync_status` — мониторинг очереди (pending/sent/failed/last_sent_at), доступен только ролям `admin`/`leader`.

### 📊 Аналитика — полный анализ и документирование модуля

Модуль `js_modules/ui/analytics.js` (1 843 строки) полностью проанализирован:

- **8 Chart.js-графиков**: тренд (line, 2 датасета — текущий/предыдущий период), бренды (horizontal bar), рейтинг менеджеров (horizontal bar), воронка (bar), матрица менеджер×статус (stacked bar), участие БМ (doughnut), активность БМ (stacked bar), матрица брендов (scatter/bubble — X: сделки/единицы, Y: конверсия %).
- **6 HTML-таблиц**: метрики менеджеров, тепловая карта (цветовая интенсивность по нормированным значениям), участие БМ, детали брендов, конкуренты, регионы.
- **4 аналитические вкладки**: Менеджеры, Продукты, Конкуренты, Регионы.
- **Трансформация данных** (`mapDealsToAnalyticsItems`): денормализация плоских сделок в аналитическую схему, разрешение email→ФИО через `resolveManagerName()`.
- **Двухуровневая фильтрация**: глобальная (период, менеджер, регион, бренд, drill-down по точке тренда) + табличная (текстовый поиск, select, range по единицам).
- **8 KPI-карточек**: всего сделок, успешных, конверсия %, единиц продано, в работе, отказов, лидирующий бренд, лучший менеджер.
- **Экспорт CSV** с корректным экранированием кавычек через Blob URL.

### 🖥️ Рабочее место — полный анализ и переговоры

**Дашборд (`workspace.js`, 517 строк)** проанализирован и задокументирован:
- KPI-панель (4 карточки): В работе / Закрыто в месяце / Сумма воронки / Требует внимания.
- Виджет воронки: 6 стадий с горизонтальными прогресс-барами и суммами в млн ₽.
- Планировщик: мини-календарь (7×N) с тултипами, три секции задач (просроченные/сегодня/предстоящие), CRUD через `save_task`/`toggle_task`/`delete_task`.
- Виджет «Требует внимания»: сделки без контакта > 7 дней + ошибки синхронизации 1С (`_1c_sync = 'failed'`).
- Быстрый поиск: debounce по имени клиента и телефону, до 8 результатов, клик → `FormUI.openModal()`.

**Переговоры (`workspace_chat.js`) — новая функциональность:**

- ➕ **Задание на звонок / Задание на встречу** — два новых типа сообщений (`call_task`, `meeting_task`) и тип результата (`task_result`).
- 📞🤝 **Кнопки в шапке чата**: «Звонок» и «Встреча» рядом с существующими действиями.
- **Bootstrap-модал создания задания**: заголовок, `datetime-local` (default = now+1h, округлено до 15 мин), телефон/ссылка (для звонка) или место (для встречи), комментарий. Данные сохраняются как JSON в поле `text`.
- **Карточка задания в чате**: отображает иконку типа, заголовок, дату/время, место/ссылку, комментарий. Кнопка «Завершить» → inline-форма результата под карточкой (Enter или →).
- **Карточка результата**: зелёная рамка, заголовок «✅ Результат звонка/встречи · дата», текст результата, автор и время. Связь с заданием через `deal_id = String(task_msg.id)`.
- **Логика завершённости**: `hasResult = messages.some(m => m.type === 'task_result' && m.deal_id === String(msg.id))` — если результат есть, кнопка «Завершить» заменяется значком ✅, карточка стилизуется как `.done`.
- **Без изменений схемы БД**: новые типы используют существующую таблицу `contact_messages`, поле `deal_id` переиспользовано как `parent_id` для результатов.
- **Исправлен белый список типов** в `save_contact_message`: добавлены `call_task`, `meeting_task`, `task_result` (ранее сервер возвращал HTTP 400 «Invalid type»).
- **Новые CSS-классы**: `.wsc-task-card`, `.wsc-task-card.done`, `.wsc-task-header`, `.wsc-task-datetime`, `.wsc-task-complete-btn`, `.wsc-task-done-badge`, `.wsc-inline-result`, `.wsc-result-card`.

---

## 14.03.2026: Карточка клиента и анализ заполненности полей

### 👤 Карточка клиента — новый модуль `ClientCardUI`

- **Новый файл** `js_modules/ui/client_card.js` — полноценная карточка клиента в стиле HubSpot/amoCRM.
- **Модальное окно `modal-xl`** с двухколоночной компоновкой:
  - **Левый сайдбар**: аватар с детерминированным цветом по имени, тип клиента, контакты (📍 регион, 📞 телефон, ✉️ email), статистика сделок по статусам с % успешности, список менеджеров.
  - **Правая панель**: хронология всех сделок клиента (по убыванию даты) — техника, менеджер, дата, сумма; клик по сделке открывает форму сделки.
- Имя клиента стало **кликабельной ссылкой** в табличном виде (`table.js`) и на канбан-карточках (`kanban.js`).
- Плавный переход: «Открыть →» в истории закрывает карточку клиента через `hidden.bs.modal` и открывает форму сделки без наложения модалов.
- Добавлены CSS-классы: `.client-card-sidebar`, `.client-avatar`, `.client-section-title`, `.client-stat-row`, `.client-deal-card`, `.cdc--*`, `.client-name-link`.

### 📊 Скрипт анализа заполненности полей `analyze_fill_rate.py`

- Анализ охватывает только поля **расширенного режима формы сделки** (`FORM_SECTIONS` / `FORM_FIELDS` — 52 поля).
- **Условные поля** (`CONDITIONAL_FIELDS`) учитываются только по применимым сделкам (напр. «SKU товара» — только для «Заявка с Сайта»).
- Экспорт в Excel: лист «По полям» сгруппирован по секциям формы с цветными заголовками разделов.
- Запуск: `python -X utf8 analyze_fill_rate.py --excel report.xlsx`

---

## 23.03.2026: Безопасность и стабильность

### 🔒 Авторизация — fail-closed при отсутствии конфигурации ролей

- **Задача 2.3.** Инвертирована логика доступа в `api_sqlite.php` при недоступности `.env`.
  - **Было**: если файл `.env` не найден или все списки ролей пусты (`$haveRoleConfig = false`) — все пользователи получали полный доступ ко всем сделкам.
  - **Стало**: при `$haveRoleConfig = false` сервер возвращает `HTTP 500` с телом `{"error": "Role config missing or empty. Access denied."}` и записывает событие в лог.
  - **Исключение**: режим локальной разработки (`$isLocal && !$hasAuthFile`) пропускает проверку, чтобы не блокировать отладку без Bitrix.
  - **Эффект**: случайное переименование `.env` больше не открывает данные всем пользователям.

### 🗂️ Дедупликация клиентов — учёт региона

- **Задача 2.4.** Исправлена логика поиска существующего клиента при сохранении сделки в `crm_architecture.js`.
  - **Было**: клиент искался только по `name.toLowerCase()` — два клиента с одинаковым названием в разных регионах сливались в одну запись, у второго терялись регион и контакты.
  - **Стало**: поиск ведётся по комбинации `name.toLowerCase() + region.toLowerCase()`. Если совпадает только имя, но регион отличается — создаётся новый клиент.
  - **Пример**: ООО «Агро» (Краснодарский кр.) и ООО «Агро» (Ростовская обл.) теперь хранятся как два независимых клиента.

### ⚡ Производительность — debounce глобального поиска

- **Задача 2.5.** В `js_modules/main.js` обработчик поля `globalSearchInput` обёрнут в debounce 300 мс.
  - **Было**: каждый введённый символ немедленно вызывал `applyAccountFilterAndRender()` — перебор 700+ сделок на каждое нажатие клавиши.
  - **Стало**: `state.globalSearch` обновляется мгновенно, а рендер запускается только через 300 мс после последнего символа — при быстром вводе выполняется ровно один проход.
  - **Реализация**: `clearTimeout / setTimeout` без сторонних библиотек.

---

## 08.03.2026: Форма сделки — удобство и подсказки

- 💰 **Автоматический расчёт финансовых полей.** Цена, скидка и сумма сделки считают друг друга в любом направлении.
- 📋 **Финансовый блок переструктурирован.** Цена / валюта / скидка — первая строка, ниже — сумма и условия, затем оплата и дата.
- 🌾 **«Культуры» и «Площадь посева» перенесены в карточку клиента.**
- 👤 **Новый блок «Ответственные»** — менеджер и БМ вынесены в отдельную карточку справа.
- 🔢 **Этапы сделки — кнопки вместо списка:** 1 · Выявление потребности → 2 · Работа с клиентом → 3 · Завершение сделки.
- 💡 **Умный режим подсказок.** При смене итогового статуса связанные поля подсвечиваются цветом: 🟢 Успешно / 🟡 Отказ / 🔵 Отложено / 🟠 Возврат. У обязательных полей — метка «обязательно для заполнения».
- 📝 **Расширены подсказки** для 14 полей, связанных с «Итоговым статусом сделки».
