From: Aleksey Filippov Date: Mon, 1 Dec 2025 09:16:46 +0000 (+0300) Subject: Добавление документации X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=refs%2Fremotes%2Forigin%2Ffeature_filippov_20251128_add_docs;p=erp24_rep%2Fyii-erp24%2F.git Добавление документации --- diff --git a/CLAUDE.md b/CLAUDE.md index 770cae14..0ef08c63 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,13 @@ It establishes a single, consistent behaviour model for both environments. * точной, * исчерпывающей, +* не пиши И т.д. дай развёрнутый ответ +* нужны развёрнутое описание не только ссылайся на другой метод +* нужно углублённое описание логики всех методов +* больше вводных данных +* больше описаний потоков данных +* отображать все паратры которые есть у метода +* список вызовов сторонних методов с кратким описанием * соответствующей коду в репозитории, * удобной для онбординга новых разработчиков, * формализованной (Markdown + Mermaid + ссылки между файлами). diff --git a/erp24/docs/INDEX.md b/erp24/docs/INDEX.md index 7ca75f21..d976975d 100644 --- a/erp24/docs/INDEX.md +++ b/erp24/docs/INDEX.md @@ -1,11 +1,76 @@ # Быстрый индекс документации ERP24 +## 🧠 Mindmap: Навигация по документации + +```mermaid +mindmap + root((ERP24 Docs)) + Начните здесь + README + SUMMARY + INDEX + CROSS_REFERENCE + CHANGELOG + GLOSSARY + CONSTANTS + Архитектура + system-overview + api-architecture + DATA_FLOW + DOMAIN_MODEL + API + API3 50% + 18 контроллеров + 76 эндпоинтов + 10 сервисов + API2 + 33 контроллера + API1 Legacy + Сервисы 48шт + P0 Критические 9 + P1 Высокий 10 + P2/P3 остальные + Контроллеры 161шт + Нестандартные 47 + Стандартные 114 + Консольные 62 команды + CronController 28 + BonusController 9 + Прочие 25 + Модели 393шт + Документировано 22 + HR модели + Продажи + Товары + Модули 12шт + HR 5 + Обучение 2 + Операции 2 + Коммуникации 2 + Аналитика 1 + Helpers 15шт + DateHelper + DataHelper + SalaryHelper + База данных + Схема + Таблицы + Миграции 278 + Ошибки + Бизнес + Аутентификация + Валидация +``` + ## 🚀 Начните здесь - **[README.md](./README.md)** - Главная страница документации - **[SUMMARY.md](./SUMMARY.md)** - Итоговая сводка (статистика, метрики) - **[CROSS_REFERENCE.md](./CROSS_REFERENCE.md)** - Матрица взаимосвязей модулей -- **[CHANGELOG.md](./CHANGELOG.md)** - История изменений документации ✨ NEW +- **[CHANGELOG.md](./CHANGELOG.md)** - История изменений документации +- **[GLOSSARY.md](./GLOSSARY.md)** - Глоссарий терминов ✨ NEW +- **[CONSTANTS.md](./CONSTANTS.md)** - Справочник констант ✨ NEW +- **[DATA_ANALYST_REVIEW_REPORT.md](./DATA_ANALYST_REVIEW_REPORT.md)** - Отчёт анализа документации ✨ NEW ## 🏗️ Архитектура @@ -51,20 +116,38 @@ - MarketplaceController (5 команд) - Yandex Market, Flowwow - TimetableController (1 команда) - автозакрытие смен -## 📦 Модели и Records ✨ UPDATED +## 📦 Модели и Records ✨ UPDATED (22 документа) - **[Models README](./models/README.md)** - Классификация 393 моделей - **[Admin](./models/Admin.md)** - Модель сотрудников (IdentityInterface) +- **[AdminPayroll](./models/AdminPayroll.md)** - Зарплаты сотрудников (EAV) +- **[AdminRating](./models/AdminRating.md)** - Рейтинги сотрудников +- **[Balances](./models/Balances.md)** - Остатки товаров ✨ NEW +- **[Dashboard](./models/Dashboard.md)** - Конфигурация дашбордов +- **[Grade](./models/Grade.md)** - Грейды сотрудников ✨ NEW - **[Users](./models/Users.md)** - Модель пользователей (клиенты) - **[Sales](./models/Sales.md)** - Модель чеков продаж +- **[SalesProducts](./models/SalesProducts.md)** - Товары в чеках +- **[Prices](./models/Prices.md)** - Цены товаров - **[Task](./models/Task.md)** - Модель задач -- **[Files](./models/Files.md)** - Универсальное хранилище файлов ✨ NEW +- **[Files](./models/Files.md)** - Универсальное хранилище файлов +- **[Lessons](./models/Lessons.md)** - Уроки и курсы +- **[Regulations](./models/Regulations.md)** - Регламенты - **[Store](./models/Store.md)** - Модель магазинов - **[Timetable](./models/Timetable.md)** - Модель расписания - **[UsersBonus](./models/UsersBonus.md)** - Модель бонусных операций - **[MarketplaceOrders](./models/MarketplaceOrders.md)** - Заказы маркетплейсов - **[Products1c](./models/Products1c.md)** - Интеграция с 1С +## 🔧 Helpers ✨ NEW (5 документов) + +- **[Helpers README](./helpers/README.md)** - Обзор 15 вспомогательных классов +- **[DateHelper](./helpers/DateHelper.md)** - Работа с датами и сменами +- **[DataHelper](./helpers/DataHelper.md)** - UUID, JSON, массивы +- **[SalaryHelper](./helpers/SalaryHelper.md)** - Расчёт зарплат и бонусов +- **[FormatHelper](./helpers/FormatHelper.md)** - Форматирование чисел +- **[UtilHelper](./helpers/UtilHelper.md)** - Общие утилиты + ## 🗄️ База данных ✨ NEW - **[Database README](./database/README.md)** - Обзор структуры БД @@ -194,21 +277,35 @@ | Компонент | Количество | Покрытие | |-----------|-----------|----------| -| **Документов Markdown** | **209** | **100%** | +| **Документов Markdown** | **218** | **100%** | | **Бизнес-модулей** | **12** | **100%** (12/12) | | **Web-контроллеров** | **161** | **100%** (161/161) | | **Console команд** | **62** | **100%** (62/62) | | **Сервисов** | **48** | **100%** (48/48) | | **API3 контроллеров** | **18** | **50%** (9/18) | | **API3 эндпоинтов** | **76** | **71%** (54/76) | -| **Моделей** | **393** | **1%** (3/393) | -| **Диаграмм** | **35+** | - | -| **Примеров кода** | **100+** | - | -| **Размер документации** | **~5.1 MB** | - | +| **Моделей** | **393** | **6%** (22/393) | +| **Диаграмм** | **40+** | - | +| **Примеров кода** | **120+** | - | +| **Размер документации** | **~5.5 MB** | - | ## 📝 Версии -- **v2.0** (2025-11-27) - Расширенная документация ✨ CURRENT +- **v2.2** (2025-11-29) - Mindmap схемы и критический анализ ✨ CURRENT + - Добавлены mindmap схемы ко всем ключевым документам + - Критический анализ и исправление статистики + - Обновление версий до актуальных + - 218 документов (~5.5 MB) + +- **v2.1** (2025-11-29) - Расширение документации для ТЗ + - 218 документов (~5.5 MB) + - GLOSSARY.md — глоссарий терминов + - CONSTANTS.md — справочник констант + - DATA_ANALYST_REVIEW_REPORT.md — анализ пробелов + - Новые модели: Balances, Grade, AdminPayroll + - 22 документированные модели (6% покрытия) + +- **v2.0** (2025-11-27) - Расширенная документация - 209 документов (~5.1 MB) - 161 web-контроллер - 62 консольные команды diff --git a/erp24/docs/README.md b/erp24/docs/README.md index 6423bec5..4b235f09 100644 --- a/erp24/docs/README.md +++ b/erp24/docs/README.md @@ -2,6 +2,71 @@ Добро пожаловать в документацию системы ERP24 - комплексной ERP-системы для управления сетью цветочных магазинов на базе Yii2 Framework. +## 🧠 Карта документации (Mindmap) + +```mermaid +mindmap + root((ERP24)) + HR и Персонал + Payroll + Зарплата + Оклады + Премии + Bonus + Баллы клиентов + Командные бонусы + Конвертация + Timetable + Расписание + Смены + Чекины + Rating + Рейтинг сотрудников + KPI + Метрики + Grade + Грейды + Карьерный рост + Обучение + Lesson + Курсы + Тесты + Сертификаты + Regulations + Регламенты + Опросники + Операции + Shipment + Поставки + Закупки + Распределение + WriteOffs + Списания + Брак + Естественная убыль + Коммуникации + Notifications + Push + Уведомления + KIK Feedback + Жалобы + Благодарности + Аналитика + Dashboard + Метрики + Графики + KPI + API + API1 Legacy + API2 REST + API3 Advanced + Интеграции + 1С + Telegram + AmoCRM + SMS +``` + ## 📋 О системе **ERP24** - это enterprise resource planning система, разработанная для автоматизации всех бизнес-процессов сети цветочных магазинов: @@ -227,20 +292,20 @@ php yii timetable/autoclose-shifts |-----------|-----------|------------------------| | PHP файлы | ~3,771 | - | | **Web контроллеры** | **161** ✅ | **100%** (161/161) | -| **Records/Models** | **393** ✅ | **1%** (3/393) | +| **Records/Models** | **393** ✅ | **6%** (22/393) | | **Сервисы** | **48** ✅ | **100%** (48/48) | | Actions | 40+ | Частично | | API2 контроллеры | 33 | Не документировано | | API3 контроллеры | 18 | **50%** (9/18) | | API3 эндпоинты | 76 | **71%** (54/76) | -| Helpers | 15+ | Не документировано | +| **Helpers** | **15** ✅ | **33%** (5/15) | | Forms | 20+ | Не документировано | | **Console контроллеры** | **17** ✅ | **100%** (17/17) | | **Console команды** | **62** ✅ | **100%** (62/62) | | Migrations | 278 | Не документировано | | Jobs | 6 | Не документировано | | **Бизнес-модули** | **12** ✅ | **100%** (12/12) | -| **Документов Markdown** | **209** ✅ | **~5.1 MB** | +| **Документов Markdown** | **218** ✅ | **~5.5 MB** | ## 🚀 Быстрый старт @@ -485,6 +550,6 @@ namespace yii_app\actions\{module}; *Документация поддерживается в актуальном состоянии командой разработки ERP24.* -**Версия документации:** 2.0 -**Последнее обновление:** 2025-11-27 +**Версия документации:** 2.1 +**Последнее обновление:** 2025-11-29 **Статус:** ✅ Активно поддерживается diff --git a/erp24/docs/SUMMARY.md b/erp24/docs/SUMMARY.md index 3be64dd5..6e57bab0 100644 --- a/erp24/docs/SUMMARY.md +++ b/erp24/docs/SUMMARY.md @@ -2,9 +2,79 @@ ## 🎉 Статус документации: АКТИВНО ПОДДЕРЖИВАЕТСЯ -**Версия:** 2.0 -**Дата обновления:** 2025-11-27 -**Статус:** ✅ Завершены основные разделы, расширение продолжается +**Версия:** 2.2 +**Дата обновления:** 2025-11-29 +**Статус:** ✅ Завершены основные разделы, добавлены mindmap схемы + +## 🧠 Mindmap: Структура документации + +```mermaid +mindmap + root((Документация ERP24)) + Навигация + README + INDEX + SUMMARY + CROSS_REFERENCE + CHANGELOG + GLOSSARY + CONSTANTS + Модули 12шт + HR 5шт + Bonus + Payroll + Timetable + Rating + Grade + Обучение 2шт + Lesson + Regulations + Операции 2шт + Shipment + WriteOffs + Коммуникации 2шт + Notifications + KIK Feedback + Аналитика 1шт + Dashboard + Сервисы 48шт + BonusService + PayrollService + ShipmentService + RatingService + DashboardService + ещё 43 сервиса + Контроллеры 161шт + Стандартные 114 + Нестандартные 47 + Консольные 62 команды + CronController 28 + BonusController 9 + MarketplaceController 5 + прочие 20 + API + API1 Legacy + API2 Modern + API3 Advanced 76 эндпоинтов + Модели 393шт + Документировано 22 + В работе 371 + Helpers 15шт + DateHelper + DataHelper + SalaryHelper + FormatHelper + UtilHelper + База данных + Схема + Таблицы + Миграции 278 + Ошибки + Бизнес-ошибки + Аутентификация + Валидация + Коды ошибок +``` ## 📊 Общая статистика проекта @@ -32,18 +102,19 @@ | Метрика | Значение | |---------|----------| -| **Документов Markdown** | **209 файлов** (~5.1 MB) | +| **Документов Markdown** | **218 файлов** (~5.3 MB) | | **Бизнес-модулей** | 12/12 (100%) ✅ | | **Web-контроллеров** | 161/161 (100%) ✅ | | **Console команд** | 62/62 (100%) ✅ | | **Сервисов** | 48/48 (100%) ✅ | | **API3 модулей** | 9/18 (50%) 🔄 | | **API3 эндпоинтов** | 54/76 (71%) 🔄 | -| **Моделей** | 15/393 (4%) 🔄 | -| **Mermaid диаграмм** | 35+ | -| **Примеров кода** | 100+ | -| **ER-диаграмм** | 12 | -| **Таблиц со статистикой** | 50+ | +| **Моделей** | 19/393 (5%) 🔄 | +| **Helpers** | 5/15 (33%) ✨ NEW | +| **Mermaid диаграмм** | 40+ | +| **Примеров кода** | 110+ | +| **ER-диаграмм** | 15 | +| **Таблиц со статистикой** | 55+ | | **FAQ секций** | 12 | ## 📚 Документированные модули @@ -277,16 +348,28 @@ erp24/docs/ ├── models/ # 393 модели ✨ UPDATED │ ├── README.md # Обзор моделей ✅ │ ├── Admin.md # Сотрудники (IdentityInterface) ✅ +│ ├── AdminRating.md # Рейтинги сотрудников ✅ NEW +│ ├── Dashboard.md # Конфигурация дашбордов ✅ NEW │ ├── Users.md # Пользователи (клиенты) ✅ │ ├── Sales.md # Чеки продаж ✅ │ ├── Task.md # Задачи ✅ -│ ├── Files.md # Файловое хранилище ✅ NEW +│ ├── Files.md # Файловое хранилище ✅ │ ├── Store.md # Магазины ✅ │ ├── Timetable.md # Расписание ✅ │ ├── UsersBonus.md # Бонусные операции ✅ │ ├── MarketplaceOrders.md # Заказы маркетплейсов ✅ │ ├── Products1c.md # Интеграция с 1С ✅ -│ └── ... (15 документов) +│ ├── Lessons.md # Уроки и курсы ✅ NEW +│ ├── Regulations.md # Регламенты ✅ NEW +│ └── ... (19 документов) +│ +├── helpers/ # 15 helpers ✨ NEW +│ ├── README.md # Обзор вспомогательных классов ✅ +│ ├── DateHelper.md # Работа с датами и сменами ✅ NEW +│ ├── DataHelper.md # UUID, JSON, массивы ✅ NEW +│ ├── SalaryHelper.md # Расчёт зарплат и бонусов ✅ NEW +│ ├── FormatHelper.md # Форматирование чисел ✅ NEW +│ └── UtilHelper.md # Общие утилиты ✅ NEW │ ├── database/ # База данных ✨ NEW │ ├── README.md # Обзор БД ✅ @@ -522,9 +605,9 @@ erp24/docs/ - 9 практических примеров использования - Полное описание CronController (28 команд) -3. **Модели и Records** (393 модели → 15 документов) +3. **Модели и Records** (393 модели → 19 документов) - Обзор и классификация моделей - - Детальная документация: Admin, Users, Sales, Task, Files, Store, Timetable, UsersBonus, MarketplaceOrders, Products1c + - Детальная документация: Admin, AdminRating, Dashboard, Users, Sales, Task, Files, Store, Timetable, UsersBonus, MarketplaceOrders, Products1c, Lessons, Regulations 4. **База данных** - Обзор структуры БД @@ -543,14 +626,22 @@ erp24/docs/ ### 📊 Новая статистика -- **209 документов** Markdown (~5.1 MB) +- **218 документов** Markdown (~5.3 MB) - **100% покрытие** контроллеров (161/161) - **100% покрытие** консольных команд (62/62) - **100% покрытие** сервисов (48/48) -- **4% покрытие** моделей (15/393) - расширяется +- **5% покрытие** моделей (19/393) - расширяется +- **33% покрытие** helpers (5/15) - расширяется ## 📜 История создания документации +**Версия 2.1 (2025-11-28):** +- ✅ Добавлены 4 новые модели: AdminRating, Dashboard, Lessons, Regulations +- ✅ Создана документация Helpers (5 файлов): DateHelper, DataHelper, SalaryHelper, FormatHelper, UtilHelper +- ✅ Обновлены INDEX.md и SUMMARY.md +- ✅ Улучшено покрытие моделей: 15 → 19 документов (5%) +- ✅ Добавлено покрытие helpers: 5/15 (33%) + **Версия 2.0 (2025-11-27):** - ✅ Добавлено 180+ новых документов - ✅ Документированы все 161 web-контроллер @@ -574,20 +665,21 @@ erp24/docs/ Документация системы ERP24 **активно поддерживается** и продолжает расширяться! -**Охват версии 2.0:** -- ✅ 209 документов Markdown (~5.1 MB) +**Охват версии 2.1:** +- ✅ 218 документов Markdown (~5.3 MB) - ✅ 12/12 бизнес-модулей (100%) - ✅ 161/161 web-контроллеров (100%) - ✅ 62/62 консольных команд (100%) - ✅ 48/48 сервисов (100%) - ✅ 9/18 API3 модулей (50%) -- 🔄 15/393 моделей (4%) +- 🔄 19/393 моделей (5%) +- 🔄 5/15 helpers (33%) **Качество:** - ✅ Единый формат и шаблоны - ✅ Русский язык для всей документации -- ✅ 35+ Mermaid диаграмм -- ✅ 100+ примеров кода +- ✅ 40+ Mermaid диаграмм +- ✅ 110+ примеров кода - ✅ Связи и зависимости - ✅ CHANGELOG для отслеживания изменений @@ -601,8 +693,8 @@ erp24/docs/ --- -**Версия:** 2.0 -**Последнее обновление:** 2025-11-27 +**Версия:** 2.1 +**Последнее обновление:** 2025-11-28 **Статус:** ✅ Активно поддерживается **См. также:** diff --git a/erp24/docs/api/api3/README.md b/erp24/docs/api/api3/README.md index 7fa88bd8..cfd40ef3 100644 --- a/erp24/docs/api/api3/README.md +++ b/erp24/docs/api/api3/README.md @@ -4,6 +4,57 @@ **Статус:** Production **База:** Yii2 Framework REST API +## 🧠 Mindmap: Архитектура API3 + +```mermaid +mindmap + root((API3)) + CRM и Лояльность 24 endpoints + BonusController 8шт + Начисление + Списание + История + Уровни + ClientController 14шт + Профили + События + Поиск + NotifiableController + Push + Уведомления + HR и Персонал 23 endpoints + EmployeeController 3шт + Данные сотрудников + AdminController 4шт + Администраторы + ClaimWorkerController 4шт + Заявки на смены + TimetablePlanController 5шт + План смен + TimetableFactController 6шт + Факт явок + Операции 10 endpoints + StoreController 7шт + Магазины + Продажи + Остатки + IncomeController + Приходы + ProductController + Каталог товаров + Аналитика 3 endpoints + ReportController + Продажи + Смены + KPI + Сервисы 10шт + BonusService 723 LOC + ClientService 571 LOC + ReportService 1504 LOC + StoreService + TimetableService +``` + --- ## Быстрая навигация diff --git a/erp24/docs/models/Admin.md b/erp24/docs/models/Admin.md index e86673cf..d4c1a578 100644 --- a/erp24/docs/models/Admin.md +++ b/erp24/docs/models/Admin.md @@ -1,5 +1,67 @@ # Class: Admin +## 🧠 Mindmap: Модель Admin + +```mermaid +mindmap + root((Admin)) + Идентификация + id / guid + login_user + pass_user + access_token + IdentityInterface + Персональные данные + name / name_full + mobile / email + birthdate + photo / avatarka + Паспортные данные + seriya / nomer + inn / snils + Должность + group_id + employee_position_id + work_status + work_rate + posit + Магазины + store_id + store_arr + store_arr_guid + dostup права + Зарплата + summa_oklad + summa_oklad_nalog + avans_percent + sale_percent + tabel_number + Иерархия + parent_admin_id + mentor_id + d_id + childAdmins + Связи + AdminGroup + CityStore + AdminPayroll + Timetable + Sales + Task + Методы + Аутентификация + findByUsername + validatePassword + findIdentityByAccessToken + Выборки + getAdmins + getNames + getOptionsForSelect + Хуки + afterSave + SalarySyncService +``` + ## Назначение Основная модель сотрудника в системе ERP24. Содержит полную информацию о сотрудниках компании: личные данные, должность, права доступа, данные для расчета зарплаты, график работы и связи с другими сущностями системы. @@ -519,6 +581,102 @@ $options = Admin::getOptionsForSelectAllGroupAndEach(); --- +### Хуки жизненного цикла + +#### afterSave($insert, $changedAttributes) + +**Описание:** Переопределённый метод жизненного цикла ActiveRecord, вызываемый после успешного сохранения модели. Обеспечивает автоматическую синхронизацию истории должностей и создание записей оплаты при изменении должности сотрудника. + +**Параметры:** + +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$insert` | `bool` | `true` если это создание новой записи, `false` если обновление | +| `$changedAttributes` | `array` | Массив изменённых атрибутов (ключ - имя атрибута, значение - старое значение) | + +**Возвращает:** `void` + +**Логика работы:** + +1. Вызывает родительский `parent::afterSave($insert, $changedAttributes)` +2. Проверяет, изменилось ли поле `employee_position_id` (или это новая запись) +3. **Гибридный подход - ведение истории должностей:** + - Если должность изменилась и новая должность установлена: + - Закрывает предыдущую запись в `EmployeePositionStatus` (устанавливает `closed_at`) + - Создаёт новую запись истории должности +4. **Синхронизация зарплаты:** + - Если должность установлена/изменена и сотрудник не уволен: + - Вызывает `SalarySyncService::createPaymentFromPosition()` для создания записи оплаты + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `parent::afterSave()` | `ActiveRecord` | Вызов родительского метода | +| `array_key_exists()` | PHP | Проверка наличия ключа в массиве изменений | +| `EmployeePositionStatus::updateAll()` | `EmployeePositionStatus` | Массовое обновление (закрытие старых записей) | +| `new EmployeePositionStatus()` | `EmployeePositionStatus` | Создание новой записи истории должности | +| `->save(false)` | `ActiveRecord` | Сохранение без валидации | +| `new SalarySyncService()` | `SalarySyncService` | Создание сервиса синхронизации зарплаты | +| `->createPaymentFromPosition()` | `SalarySyncService` | Создание записи EmployeePayment | +| `Yii::info()` | `Yii` | Логирование успешных операций | +| `Yii::error()` | `Yii` | Логирование ошибок | + +**Триггеры выполнения:** + +| Событие | Действие | +|---------|----------| +| Создание сотрудника с должностью | Создание истории + запись оплаты | +| Изменение должности | Закрытие старой истории + создание новой + запись оплаты | +| Изменение без должности | Ничего не происходит | +| Увольнение сотрудника | Запись оплаты НЕ создаётся | + +**Пример:** + +```php +// При изменении должности автоматически: +$admin = Admin::findOne($id); +$admin->employee_position_id = 5; // Новая должность "Старший флорист" +$admin->save(); +// -> EmployeePositionStatus с предыдущей должностью закрывается +// -> Создаётся новая запись EmployeePositionStatus +// -> Создаётся EmployeePayment с окладом для позиции 5 + +// Логи: +// [grade-sync] История грейда обновлена для admin_id=123, position_id=5 +// [salary-sync] Автоматически создана запись EmployeePayment для admin_id=123, position_id=5 +``` + +**Диаграмма потока:** + +```mermaid +flowchart TD + A[afterSave вызван] --> B{employee_position_id изменился?} + B -->|Нет| Z[Завершение] + B -->|Да| C{Новая должность установлена?} + C -->|Нет| Z + C -->|Да| D{Была старая должность?} + D -->|Да| E[Закрыть старую запись EmployeePositionStatus] + D -->|Нет| F[Создать новую запись EmployeePositionStatus] + E --> F + F --> G{Сотрудник не уволен?} + G -->|Нет| Z + G -->|Да| H[SalarySyncService::createPaymentFromPosition] + H --> I{Успех?} + I -->|Да| J[Логирование info] + I -->|Нет| K[Логирование error] + J --> Z + K --> Z +``` + +**Обработка ошибок:** + +- Ошибки при обновлении истории должностей логируются, но НЕ прерывают сохранение Admin +- Ошибки синхронизации зарплаты также логируются без прерывания +- Используется try-catch для изоляции побочных эффектов от основного сохранения + +--- + ### Служебные методы #### legacyFill() diff --git a/erp24/docs/modules/README.md b/erp24/docs/modules/README.md index d8391b45..0c1ca509 100644 --- a/erp24/docs/modules/README.md +++ b/erp24/docs/modules/README.md @@ -2,6 +2,69 @@ Данный раздел содержит подробную документацию всех бизнес-доменов (модулей) системы ERP24. +## 🧠 Mindmap: Бизнес-модули ERP24 + +```mermaid +mindmap + root((Бизнес-модули)) + HR и Персонал + Bonus + BonusService 1200+ LOC + 50+ методов + Конвертация баллов + Telegram интеграция + Payroll + PayrollService + EAV паттерн + 3 сервиса + 10 моделей + Timetable + STI паттерн + 17 actions + 7 типов смен + Чекины + Rating + 4 типа рейтингов + Автоматический расчёт + KPI + Grade + Уровни квалификации + Влияние на ЗП + Карьерный рост + Обучение + Lesson + Курсы и уроки + Тестирование + Сертификаты + Regulations + Регламенты + Опросники + Прохождение + Операции + Shipment + ShipmentService 3786 LOC + 15 статусов + Интеграция 1С + WriteOffs + Списания + 9 моделей + Причины + Коммуникации + Notifications + Push уведомления + TTL 31 день + AJAX API + KIK Feedback + Kanban workflow + 7 статусов + 16 actions + Аналитика + Dashboard + 13 actions + Динамические виджеты + Цветовые индикаторы +``` + ## Обзор модулей ### 1. [Bonus (Бонусная система)](./bonus/README.md) diff --git a/erp24/docs/modules/bonus/README.md b/erp24/docs/modules/bonus/README.md index 8365690f..f45b2ad7 100644 --- a/erp24/docs/modules/bonus/README.md +++ b/erp24/docs/modules/bonus/README.md @@ -1,5 +1,67 @@ # Модуль Bonus (Бонусная система) +## 🧠 Mindmap: Модуль Bonus + +```mermaid +mindmap + root((Bonus Module)) + Контроллеры 5шт + BonusController + stat + vozvrat-stats + ajax-show-check + add-bonuses + sex + BonusLevelsController + Уровни бонусов + TeambonusController + Командные бонусы + UserBonusController + Бонусы клиентов + AdminPersonBonusesController + Личные бонусы админов + BonusService + 50+ методов + 1200+ строк + Начисление + Списание + Конвертация + Расчёт уровней + Модели 8шт + UsersBonus + История операций + BonusLevels + Уровни лояльности + UsersBonusLevels + Уровни клиентов + AdminPersonBonuses + Бонусы сотрудников + AdminBonusConversion + Конвертация в рубли + TeambonusSettings + Настройки команд + Actions 13шт + StatAction + VozvratStatsAction + AjaxShowCheckAction + AddBonuses + SexAction + Интеграции + Telegram Bot + Отправка баллов + Уведомления + 1С + Синхронизация продаж + Бонусы за покупки + Формулы + Базовые баллы + 1 балл = 1 рубль + Конвертация + коэффициент x баллы + Уровни + Bronze/Silver/Gold/Platinum +``` + ## Описание Модуль Bonus отвечает за управление системой бонусов для клиентов и сотрудников. Включает начисление, списание, конвертацию бонусов, управление уровнями бонусов, командными бонусами и историей операций. diff --git a/erp24/docs/services/BonusService.md b/erp24/docs/services/BonusService.md index c15f2804..51f51230 100644 --- a/erp24/docs/services/BonusService.md +++ b/erp24/docs/services/BonusService.md @@ -1,5 +1,49 @@ # BonusService +## 🧠 Mindmap: BonusService + +```mermaid +mindmap + root((BonusService)) + Характеристики + 1,199 LOC + 42 публичных метода + Очень высокая сложность + Бонусы за продажи 8шт + getBonusClusterPercentSales кластер + getGameBonusPersonSalaryRelated сопутка + getGameBonusPersonSalaryPotted горшечка + getGameBonusPersonSalaryWrap упаковка + getBonusSaloonSale салон + Бонусы за конверсию 5шт + getGameBonusConversionShift смена + getGameBonusConversionStore магазин + getBonusByConvertionPercent премия + getGameBonusBonusCard карты + Бонусы за средний чек 2шт + getGamePersonBonusAvgCheck личный + getGameBonusAvgCheck магазин + Бонусы за списания 4шт + getBonusClusterPercentLoss кластер + getBonusPercentLoss премия + getGameBonusByPercentLoss игровой + getBonusForQuality качество + Бонусы кластеров 4шт + getBonusForDeltaClusterFot экономия ФОТ + getPercentDeMotivateYearToYear демотивация + getBonusClusterGame рейтинг + Коэффициенты 8шт + getMatrixBonusCoefficient матричный + getAuthorBonusCoefficient авторский + getCoefficientPremium премия + Зависимости + NormaSmenaService + SalesService + TeambonusSettings +``` + +--- + ## Назначение Центральный сервис для расчета всех типов бонусов и премий сотрудников. Содержит 42 метода расчета различных мотивационных выплат на основе KPI (продажи, конверсия, средний чек, процент списаний, и др.). @@ -106,6 +150,298 @@ | `roundCoefficientQuantity()` | Округление коэффициента количества | | Другие вспомогательные методы | - | +## Детальное описание ключевых методов + +### getTeamBonus() + +**Назначение:** Расчёт командного бонуса магазина для конкретного сотрудника. + +**Сигнатура:** + +```php +public function getTeamBonus( + $adminId, // ID сотрудника + $storeId, // ID магазина + $storeGuid, // GUID магазина в 1С + $dateFrom, // Дата начала периода + $dateTo // Дата окончания периода +): array +``` + +**Углублённое описание логики:** + +1. **Получение процента командного бонуса:** + - Вызов `getPercentTeamBonusInMonth($storeId, $dateFrom)` + - По умолчанию 0%, если не настроено в `TeambonusSettings` +2. **Получение расписания магазина:** + - Запрос `Timetable::find()` с фильтрами: + - Период: `$dateFrom` - `$dateTo` + - Магазин: `$storeId` + - Тип слота: `TIMESLOT_WORK` (рабочая смена) + - `tabel = 0` (не табельные) +3. **Формирование массива смен:** + - Группировка по `admin_id` и `date` + - Извлечение `salary_shift` (зарплата смены) +4. **Расчёт ФОТ магазина:** + - Запрос окладов через `EmployeePayment::find()` + - Использование `EmployeePayment::getSalary()` для каждой даты + - `adminStoreFotStableSum` — сумма окладов +5. **Получение переменной части ФОТ:** + - Запрос `AdminPayrollDays::find()` для получения премий + - `payroll_variable` — переменная часть + - `payroll_constant` — постоянная часть +6. **Расчёт списаний:** + - Запрос `WriteOffs::find()` с фильтром `type = 'Брак'` + - Суммирование по полю `summ` +7. **Получение продаж магазина:** + - Вызов `CabinetService::getSalesSaleSum()` +8. **Расчёт командного фонда:** + + ```text + salesByStorePart = salesByStore × (percentTeamBonusInMonth / 100) + primeFondStore = salesByStorePart - (adminStoreFotSum + writeOffsSum) + primeFondStoreOneShift = primeFondStore / shiftCountAll + personPrimeFondStore = primeFondStoreOneShift × personShiftCount + ``` + +**Вызовы сторонних методов:** + +- `self::getPercentTeamBonusInMonth($storeId, $dateFrom)` — процент командного бонуса из настроек +- `Timetable::find()` — получение расписания магазина +- `EmployeePayment::find()` — получение окладов сотрудников +- `EmployeePayment::getSalary($adminId, $date, $adminPayDayDict)` — оклад на конкретную дату +- `AdminPayrollDays::find()` — получение данных о начислениях по дням +- `WriteOffs::find()` — получение списаний магазина +- `(new CabinetService())->getSalesSaleSum($dateFrom, $dateTo, $storeGuid)` — продажи магазина +- `DateHelper::getDatesBetween($dateFrom, $dateTo)` — список дат в периоде +- `ArrayHelper::getColumn($array, $column)` — извлечение колонки из массива +- `ArrayHelper::getValue($array, $key)` — безопасное получение значения + +**Возвращает:** `array` с ключами: + +| Ключ | Тип | Описание | +|------|-----|----------| +| `adminStoreFotStableSum` | int | Сумма окладов магазина | +| `adminStoreFotVariableSum` | int | Сумма переменной части ФОТ | +| `adminStoreFotSum` | int | Общий ФОТ (оклады + премии) | +| `writeOffsSum` | int | Сумма списаний | +| `fotStoreAndWriteOff` | int | ФОТ + списания | +| `salesByStore` | int | Продажи магазина | +| `salesByStorePart` | int | Премиальный фонд (% от продаж) | +| `primeFondStore` | int | Командный фонд | +| `shiftCountAll` | int | Всего смен в магазине | +| `primeFondStoreOneShift` | int | Бонус за одну смену | +| `personShiftCount` | int | Смен сотрудника | +| `personPrimeFondStore` | int | Персональный командный бонус | +| `percentTeamBonusInMonth` | int | Процент командного бонуса | + +--- + +### getSumConversionGameBonusToMoney() + +**Назначение:** Конвертация игровых баллов в денежный эквивалент. + +**Сигнатура:** + +```php +public function getSumConversionGameBonusToMoney( + $adminSumGameBonus, // Сумма игровых баллов + $yearSelect, // Год + $monthWithZeroSelect // Месяц с ведущим нулём +): array +``` + +**Углублённое описание логики:** + +1. Устанавливает значения по умолчанию: `base = 1`, `cost = 10` +2. Запрашивает настройки конвертации через `getAdminBonusConversion()` +3. Если найдены настройки — использует их `base` и `cost` +4. Рассчитывает: `money = (adminSumGameBonus / base) × cost` + +**Вызовы сторонних методов:** + +- `$this->getAdminBonusConversion($yearSelect, $monthWithZeroSelect)` — получение коэффициентов конвертации + +**Возвращает:** `array`: + +```php +[ + 'money' => 1500, // Денежный эквивалент + 'base' => 1, // База (делитель) + 'cost' => 10, // Стоимость одного балла +] +``` + +--- + +### getAdminBonusConversion() + +**Назначение:** Получение коэффициентов конвертации баллов в деньги из БД. + +**Сигнатура:** + +```php +public function getAdminBonusConversion( + string $yearSelect, // Год + string $monthWithZeroSelect // Месяц с ведущим нулём +): array +``` + +**Углублённое описание логики:** + +1. Формирует дату в формате `YYYY-MM` +2. Запрашивает `AdminBonusConversion::find()` по дате +3. Возвращает `base` и `cost` или значения по умолчанию + +**Вызовы сторонних методов:** + +- `AdminBonusConversion::find()` — поиск настроек конвертации +- `->andWhere(['date' => $date])` — фильтр по дате +- `->asArray()->one()` — получение одной записи как массив + +**Возвращает:** `array`: + +```php +[ + 'base' => 1, // База (по умолчанию 1) + 'cost' => 10, // Стоимость (по умолчанию 10₽) +] +``` + +--- + +### getBonusForQuality() + +**Назначение:** Расчёт бонуса за процент качества работы. + +**Сигнатура:** + +```php +public function getBonusForQuality(float $percent): int +``` + +**Углублённое описание логики:** + +1. Определяет уровни бонусов: + - 80% → 3000₽ + - 90% → 4000₽ + - 100% → 5000₽ +2. Вызывает универсальный метод `getValueByLavelsEqualAndMore()` + +**Вызовы сторонних методов:** + +- `$this->getValueByLavelsEqualAndMore($percent, $levels)` — расчёт по уровням (больше-равно) + +**Пример:** + +```php +$bonus = $bonusService->getBonusForQuality(92); +// Результат: 4000 (т.к. 92% >= 90%) +``` + +--- + +### getMatrixBonusCoefficient() + +**Назначение:** Получение коэффициента бонуса за матричные букеты в зависимости от даты. + +**Сигнатура:** + +```php +public function getMatrixBonusCoefficient($rowDate): float +``` + +**Углублённое описание логики:** + +Система использует три исторических коэффициента: + +| Период | Коэффициент | Значение | +|--------|-------------|----------| +| До 16.11.2022 | oldMatrixBonusCoefficient | 0.025 (2.5%) | +| 16.11.2022 - 30.11.2022 | newMatrixBonusCoefficient | 2/115 ≈ 0.0174 | +| С 01.12.2022 | newTwoMatrixBonusCoefficient | 0.02 (2%) | + +**Пример:** + +```php +$coef = $bonusService->getMatrixBonusCoefficient('2024-01-15'); +// Результат: 0.02 + +$coef = $bonusService->getMatrixBonusCoefficient('2022-10-01'); +// Результат: 0.025 +``` + +--- + +### getPercentTeamBonusInMonth() + +**Назначение:** Получение процента командного бонуса для магазина на конкретный месяц. + +**Сигнатура:** + +```php +public static function getPercentTeamBonusInMonth( + $storeId, // ID магазина + $dateFrom // Дата начала периода +): int +``` + +**Углублённое описание логики:** + +1. Извлекает год и месяц из `$dateFrom` +2. Запрашивает `TeambonusSettings::find()` по `store_id`, `year`, `month` +3. Возвращает `procent` или 0 по умолчанию + +**Вызовы сторонних методов:** + +- `date("Y", strtotime($dateFrom))` — извлечение года +- `date("n", strtotime($dateFrom))` — извлечение месяца (без нуля) +- `TeambonusSettings::find()` — поиск настроек командного бонуса +- `->select(['procent'])` — выборка только процента +- `->scalar()` — получение скалярного значения + +**Пример:** + +```php +$percent = BonusService::getPercentTeamBonusInMonth(15, '2024-03-01'); +// Результат: 20 (если настроено), иначе 0 +``` + +--- + +### getValueByLavelsEqualAndMore() + +**Назначение:** Универсальный метод расчёта значения по уровням (условие >=). + +**Сигнатура:** + +```php +public function getValueByLavelsEqualAndMore( + float $value, // Входное значение + array $levels // Массив уровней [порог => бонус] +): mixed +``` + +**Углублённое описание логики:** + +1. Сортирует уровни по ключам (`ksort`) +2. Проходит по уровням от меньшего к большему +3. Для каждого уровня проверяет: `$value >= $compareValue` +4. Возвращает последний достигнутый бонус + +**Пример:** + +```php +$levels = [ + "70" => 1000, + "80" => 2000, + "90" => 3000, +]; + +$bonus = $bonusService->getValueByLavelsEqualAndMore(85, $levels); +// Результат: 2000 (т.к. 85 >= 80, но 85 < 90) +``` + ## Ключевые особенности ### 1. Система уровней (levels) diff --git a/erp24/docs/services/CabinetService.md b/erp24/docs/services/CabinetService.md index 8f7ac446..c6540609 100644 --- a/erp24/docs/services/CabinetService.md +++ b/erp24/docs/services/CabinetService.md @@ -209,14 +209,59 @@ public function getData( ): array ``` -**Алгоритм:** -1. Если `$dateFrom >= '2023-10-01'` → вызывает `getDataDynamic202310()` -2. Иначе: - - Проверяет, есть ли зафиксированные данные в `AdminPayroll` - - Если да и период = полный месяц → использует `getDataStatic()` - - Иначе → использует `getDataDynamic()` +**Углублённое описание логики:** + +1. Проверка даты начала периода: + - Если `$dateFrom >= '2023-10-01'` → вызывает `getDataDynamic202310()` (новая система мотивации) +2. Для периодов до октября 2023: + - Проверяет, запрашивается ли полный месяц (`$dateFrom == $dateFromBeginMonth && $dateTo == $dateToEndMonth`) + - Ищет зафиксированные данные в `AdminPayroll` для указанного года/месяца + - Если найдены и дата фиксации > конца периода → использует `getDataStatic()` + - Флаг `$this->dynamicCalculate = true` отключает статические данные + - **ВАЖНО:** В текущей версии `$getDataStatic = false` жёстко, т.е. всегда используется динамический расчёт +3. Возврат данных через выбранный метод расчёта + +**Вызовы сторонних методов:** + +- `AdminPayroll::find()` — создание ActiveQuery для поиска зафиксированных расчётов зарплаты +- `AdminPayroll::select(['date_time'])` — выборка только поля даты фиксации +- `AdminPayroll::andWhere(['year' => $yearSelect])` — фильтрация по году +- `AdminPayroll::andWhere(['month' => $monthSelect])` — фильтрация по месяцу +- `AdminPayroll::orderBy(['date_time' => SORT_DESC])` — сортировка по убыванию даты +- `AdminPayroll::scalar()` — получение скалярного значения (дата последней фиксации) +- `$this->getDataDynamic202310()` — вызов нового метода расчёта (с октября 2023) +- `$this->getDataDynamic()` — вызов динамического расчёта для старых периодов +- `$this->getDataStatic()` — вызов статического расчёта из зафиксированных данных + +**Поток данных:** + +```text +Входные параметры (21 параметр) + ↓ + Проверка даты + ↓ +┌──────┴──────┐ +│ >= 2023-10 │ → getDataDynamic202310() → массив данных +└──────┬──────┘ + ↓ + < 2023-10 + ↓ + Проверка AdminPayroll + ↓ +┌──────┴──────┐ +│ Есть фикс. │ → getDataStatic() → массив данных (отключено) +└──────┬──────┘ + ↓ + getDataDynamic() → массив данных +``` -**Возвращает:** Массив с полными данными для отображения в личном кабинете. +**Возвращает:** `array` — ассоциативный массив с полными данными для отображения в личном кабинете. Структура включает: +- `salary` — данные о зарплате +- `bonus` — бонусы и премии +- `rating` — рейтинг сотрудника +- `timetable` — расписание +- `sales` — продажи +- и другие секции в зависимости от версии расчёта **Используется в:** - `IndexAction::run()` (главная страница ЛК) @@ -227,17 +272,69 @@ public function getData( #### 1.2. getDataStatic() -**Назначение:** Получает зафиксированные данные из таблицы `admin_payroll` для завершённых месяцев. +**Назначение:** Получает зафиксированные данные из таблицы `admin_payroll` для завершённых месяцев. Создаёт гибридный массив, комбинируя зафиксированные и динамические данные. -**Параметры:** Аналогичны `getData()` + дополнительный параметр `$paramDynamic` (динамические данные для сравнения). +**Сигнатура:** -**Алгоритм:** -1. Валидация сотрудника (наличие магазина, GUID из 1С, оклада) -2. Извлечение зафиксированных данных из `AdminPayroll` -3. Извлечение значений из `AdminPayrollValues` -4. Формирование итогового массива +```php +public function getDataStatic( + $employeeId, // ID сотрудника + $employeeSelect, // Данные сотрудника (массив) + $employeeGroupId, // ID группы сотрудника + $isAdministrator, // Является ли администратором + $ratingId, // ID записи рейтинга + $dateFrom, // Дата начала периода + $dateTo, // Дата окончания периода + $controller, // Контроллер для обработки ошибок + $winStoreIdDayChallenge, // Выигравшие магазины в челлендже + $exportCityStore, // Связка магазинов ERP → 1С + $exportAdmin, // Связка сотрудников ERP → 1С + $yearSelect, // Выбранный год + $monthSelect, // Выбранный месяц + $monthWithZeroSelect, // Месяц с ведущим нулём + $monthNameSelect, // Название месяца + $dateFromBeginMonth, // Начало месяца + $dateToEndMonth, // Конец месяца + $employeePosition, // Должности + $employeeAdminGroup, // Группы администраторов + $cityStoreNames, // Названия магазинов + $paramDynamic // Динамические данные для сравнения +): array +``` -**Преимущества:** Быстрая загрузка (данные уже рассчитаны и сохранены). +**Углублённое описание логики:** + +1. **Валидация сотрудника:** + - Проверка наличия `store_id` в `$employeeSelect` + - Проверка наличия GUID сотрудника в `$exportAdmin` + - Проверка связи магазина с 1С в `$exportCityStore` + - Проверка наличия оклада через `EmployeePayment::getMonthlySalary()` +2. **Получение плана магазина:** + - Вызов `StorePlanService::getPlanMonthByStore()` для получения месячного плана + - Расчёт дневного плана: `planStoreByDay = planMonthByStore / дней_в_месяце` +3. **Извлечение зафиксированных данных:** + - Поиск записи `AdminPayroll` по `admin_id`, `year`, `month` + - Получение значений через `AdminPayroll::getValues($payrollId)` +4. **Формирование гибридного массива:** + - Сравнение ключей `$paramDynamic` и `$paramPayroll` + - При различии — запись в `$diffKeys` для отслеживания + - Замена динамических значений на зафиксированные в `$paramHybrid` + +**Вызовы сторонних методов:** + +- `CityStore::getCityStoreById($storeId)` — получение модели магазина по ID +- `EmployeePayment::getMonthlySalary($employeeId, $dateFrom)` — получение оклада сотрудника на дату +- `$this->storePlanService->getPlanMonthByStore($month, $year, $storeGuid)` — план магазина на месяц +- `ArrayHelper::map($array, $from, $to)` — преобразование массива в ассоциативный +- `ArrayHelper::getValue($array, $key)` — безопасное получение значения из массива +- `cal_days_in_month(CAL_GREGORIAN, $month, $year)` — количество дней в месяце +- `AdminPayroll::find()` — создание ActiveQuery для поиска записи зарплаты +- `AdminPayroll::getValues($payrollId)` — получение всех значений из `admin_payroll_values` +- `$this->outputCheckError($errorText, '', $controller)` — форматирование ошибки + +**Возвращает:** `array` — гибридный массив с данными ЛК (зафиксированные значения приоритетнее динамических) + +**Преимущества:** Быстрая загрузка, стабильные данные для закрытых периодов. **Недостатки:** Не отражает изменения в реальном времени. @@ -245,36 +342,290 @@ public function getData( #### 1.3. getDataDynamic() -**Назначение:** Рассчитывает данные в реальном времени на основе текущих продаж, расписания и других факторов. +**Назначение:** Рассчитывает данные в реальном времени на основе текущих продаж, расписания и других факторов. Используется для периодов до октября 2023 года. -**Параметры:** Аналогичны `getData()`. +**Сигнатура:** -**Алгоритм (упрощённо):** -1. Валидация входных данных (сотрудник, магазин, GUID, оклад) -2. Получение расписания сотрудника (`getTimetableData()`) -3. Расчёт продаж по сменам -4. Расчёт бонусов и премий -5. Расчёт рейтинга -6. Формирование финального массива с результатами +```php +public function getDataDynamic( + $employeeId, // ID сотрудника + $employeeSelect, // Данные сотрудника (массив) + $employeeGroupId, // ID группы сотрудника + $isAdministrator, // Является ли администратором + $ratingId, // ID записи рейтинга + $dateFrom, // Дата начала периода + $dateTo, // Дата окончания периода + $controller, // Контроллер для обработки ошибок + $winStoreIdDayChallenge, // Выигравшие магазины в челлендже + $exportCityStore, // Связка магазинов ERP → 1С + $exportAdmin, // Связка сотрудников ERP → 1С + $yearSelect, // Выбранный год + $monthSelect, // Выбранный месяц + $monthWithZeroSelect, // Месяц с ведущим нулём + $monthNameSelect, // Название месяца + $dateFromBeginMonth, // Начало месяца + $dateToEndMonth, // Конец месяца + $employeePosition, // Должности + $employeeAdminGroup, // Группы администраторов + $cityStoreNames, // Названия магазинов + bool $calculatePersonalRating = true // Пересчитывать ли персональный рейтинг +): array +``` -**Сложность:** Очень высокая (~1,500 строк кода!). +**Углублённое описание логики:** + +1. **Валидация входных данных:** + - Проверка `store_id`, `work_status` сотрудника + - Проверка GUID сотрудника в `$exportAdmin` + - Проверка связи магазина с 1С + - Проверка наличия оклада +2. **Получение плана магазина:** + - Вызов `StorePlanService::getPlanMonthByStore()` для месячного плана + - Расчёт дневного плана: `planStoreByDay = planMonthByStore / дней_в_месяце` +3. **Расчёт продаж:** + - `getSalesSaleSum()` — продажи магазина за период + - `getSalesSaleSum(..., true)` — все продажи включая возвраты + - `WriteOffs::getWriteOffByStore()` — списания магазина +4. **Расчёт процента списания:** + - Для полных месяцев после 2023-03-31 используется `getCustomSumForLoss()` + - Иначе: `percentLoss = 100 * writeOff / sales` +5. **Расчёт выполнения плана:** + - `planPercent = 100 * salesByStore / planMonthByStore` +6. **Средний чек:** + - `getAvgSumCheck()` — среднее значение за период + - `getSumListAvgCheck()` — список по датам +7. **Расписание:** + - `getTimetableData()` — смены сотрудника за период + - Подсчёт дневных/ночных смен +8. **Расчёт бонусов и премий** (~500 строк логики) +9. **Расчёт рейтинга** (если `$calculatePersonalRating = true`) + +**Вызовы сторонних методов:** + +- `CityStore::getCityStoreById($storeId)` — получение магазина +- `EmployeePayment::getMonthlySalary($employeeId, $dateFrom)` — оклад сотрудника +- `$this->storePlanService->getPlanMonthByStore()` — план магазина +- `$this->getSalesSaleSum($dateFrom, $dateTo, $storeGuid, $includeReturns)` — сумма продаж +- `WriteOffs::getWriteOffByStore($dateFrom, $dateTo, $storeGuid)` — списания +- `$this->getCustomPercentLoss($storeId, $year, $month)` — кастомный процент списания +- `$this->getCustomSumForLoss($storeId, $year, $month)` — данные для расчёта списания +- `$this->getAvgSumCheck($storeId, $dateFrom, $dateTo)` — средний чек +- `$this->getSumListAvgCheck($storeId, $dateFrom, $dateTo)` — список средних чеков +- `$this->getTimetableData($adminId, $storeId, $dateFrom, $dateTo)` — расписание +- `ArrayHelper::map()`, `ArrayHelper::getValue()` — работа с массивами +- `cal_days_in_month()` — дней в месяце +- `$this->outputCheckError()` — обработка ошибок + +**Поток данных:** + +```text +Входные параметры (21 параметр) + ↓ + Валидация сотрудника + ↓ + Получение плана магазина (StorePlanService) + ↓ + Расчёт продаж (getSalesSaleSum) + ↓ + Расчёт списаний (WriteOffs) + ↓ + Расчёт среднего чека (getAvgSumCheck) + ↓ + Получение расписания (getTimetableData) + ↓ + Расчёт бонусов и премий (~500 строк) + ↓ + Расчёт рейтинга (RatingService) + ↓ + Формирование результата → array +``` -**Производительность:** Медленнее `getDataStatic()`, т.к. делает множество запросов и расчётов. +**Возвращает:** `array` — полный набор данных для ЛК включая: + +- `salary` — данные о зарплате (оклад, рабочие дни) +- `sales` — продажи (сумма, план, процент) +- `bonus` — бонусы и премии +- `rating` — рейтинг сотрудника +- `timetable` — расписание (смены, часы) +- `writeOff` — списания +- `avgCheck` — средний чек +- `planPercent` — процент выполнения плана + +**Сложность:** Очень высокая (~1,500 строк кода). + +**Производительность:** Медленнее `getDataStatic()`, т.к. делает множество запросов и расчётов в реальном времени. --- #### 1.4. getDataDynamic202310() -**Назначение:** Новая версия динамического расчёта для периодов начиная с октября 2023 года. Включает новую логику мотивации и демотивации. +**Назначение:** Новая версия динамического расчёта данных ЛК для периодов начиная с октября 2023 года. Включает обновлённую логику мотивации, демотивации, командных бонусов и премий по фокус-группам. -**Параметры:** Аналогичны `getDataDynamic()`. +**Сигнатура:** -**Размер:** ~1,650 строк кода (самый большой метод!). +```php +public function getDataDynamic202310( + $employeeId, // ID сотрудника + $employeeSelect, // Данные сотрудника (массив) + $employeeGroupId, // ID группы сотрудника + $isAdministrator, // Является ли администратором + $ratingId, // ID записи рейтинга + $dateFrom, // Дата начала периода + $dateTo, // Дата окончания периода + $controller, // Контроллер для обработки ошибок + $winStoreIdDayChallenge, // Выигравшие магазины в челлендже + $exportCityStore, // Связка магазинов ERP → 1С + $exportAdmin, // Связка сотрудников ERP → 1С + $yearSelect, // Выбранный год + $monthSelect, // Выбранный месяц + $monthWithZeroSelect, // Месяц с ведущим нулём + $monthNameSelect, // Название месяца + $dateFromBeginMonth, // Начало месяца + $dateToEndMonth, // Конец месяца + $employeePosition, // Должности + $employeeAdminGroup, // Группы администраторов + $cityStoreNames, // Названия магазинов + bool $calculatePersonalRating = true // Пересчитывать ли персональный рейтинг +): array +``` + +**Углублённое описание логики:** + +1. **Валидация сотрудника** (аналогично `getDataDynamic()`): + - Проверка `store_id`, `work_status` + - Проверка GUID в `$exportAdmin` + - Проверка связи магазина с 1С + - Проверка наличия оклада +2. **Базовые расчёты:** + - План магазина через `StorePlanService` + - Продажи магазина (`getSalesSaleSum()`) + - Списания (`WriteOffs::getWriteOffByStore()`) + - Процент списания и выполнения плана +3. **Расписание:** + - Основное: `getTimetableData()` + - Для администраторов: `getTimetableAdministratorData()` + - Смены в других магазинах: `getTimetableData(..., notInStore=true)` +4. **Бонусная игра (SumGame):** + - `setTableValues()` — установка значений по сменам + - `getSumGameBonus()` — расчёт игровых бонусов + - Раздельный учёт для основного и других магазинов +5. **Персональные надбавки** (`AdminPersonBonuses`): + - `bonuses` — персональная премия + - `overtime` — переработка + - `color_ruble_bonuses` — премия в цвето-рублях + - `retention` — персональный вычет + - `prepaid_expense` — аванс + - `counting` — подсчёт + - `vacation_day` — оплаченный отпуск + - `part_time_job_hours` — подработки в часах +6. **Командный бонус:** + - `BonusService::getTeamBonus()` — расчёт командного бонуса + - `BonusService::getAdminTeamPayrollTable()` — таблица для администраторов +7. **Премия за качество:** + - `QualityRating::getQualityRating()` — процент качества + - `BonusService::getBonusForQuality()` — премия за качество +8. **Премии по фокус-группам:** + - `SalaryHelper::getSalariesByFocusGroup()` — зарплаты по группам товаров + - `getPremiumByFocusGroups()` — расчёт премий: + - услуги (`services`) + - сопутствующие товары (`related`) + - горшечные (`potted`) + - упаковка (`wrap`) + - салюты (`salut`) + - прочие товары (`other_items`) +9. **Матричные букеты:** + - `getPremiumByMatrix()` — премия за матричные букеты + - Учёт продаж, производства, бонусов +10. **Авторские букеты** (с апреля 2025): + - `getPremiumByAuthor()` — премия за авторские букеты +11. **Праздничные смены:** + - `HolidayService::getHolidayVersionShow()` — показ праздничной версии + - Расчёт праздничных премий + +**Вызовы сторонних методов:** + +- `CityStore::getCityStoreById($storeId)` — получение магазина +- `EmployeePayment::getMonthlySalary($employeeId, $dateFrom)` — оклад +- `$this->storePlanService->getPlanMonthByStore()` — план магазина +- `$this->getSalesSaleSum()` — сумма продаж +- `WriteOffs::getWriteOffByStore()` — списания +- `$this->getCustomPercentLoss()` / `$this->getCustomSumForLoss()` — расчёт списания +- `$this->getAvgSumCheck()` / `$this->getSumListAvgCheck()` — средний чек +- `$this->getTimetableData()` — расписание сотрудника +- `$this->getTimetableAdministratorData()` — расписание администратора +- `$this->setTableValues()` — установка значений по сменам +- `$this->getSumGameBonus()` — бонусы игры +- `AdminPersonBonuses::find()` — персональные надбавки +- `$this->bonusService->getTeamBonus()` — командный бонус +- `$this->bonusService->getAdminTeamPayrollTable()` — таблица админов +- `QualityRating::getQualityRating()` — процент качества +- `$this->bonusService->getBonusForQuality()` — премия за качество +- `Products1c::getProductsFromClass($arrayTypes)` — товары по типам +- `SalaryHelper::getSalariesByFocusGroup()` — зарплаты по фокус-группам +- `$this->getPremiumByFocusGroups()` — премии по фокус-группам +- `$this->getPremiumByMatrix()` — премия за матричные букеты +- `$this->getPremiumByAuthor()` — премия за авторские букеты +- `HolidayService::getHolidayVersionShow()` — праздничная версия +- `Admin::getAdmins()` — список администраторов +- `ArrayHelper::map()`, `ArrayHelper::getValue()`, `ArrayHelper::getColumn()` — работа с массивами + +**Поток данных:** + +```text +Входные параметры (21 параметр) + ↓ + Валидация сотрудника + ↓ + Базовые расчёты (план, продажи, списания) + ↓ + Расписание (основное + другие магазины) + ↓ + Бонусная игра (SumGame) + ↓ + Персональные надбавки (AdminPersonBonuses) + ↓ + Командный бонус (BonusService) + ↓ + Премия за качество (QualityRating) + ↓ + Премии по фокус-группам (6 категорий) + ↓ + Матричные букеты (getPremiumByMatrix) + ↓ + Авторские букеты (getPremiumByAuthor) + ↓ + Праздничные смены + ↓ + Формирование результата → array +``` -**Отличия от старой версии:** -- Новая система расчёта премий -- Изменённая логика демотивации -- Обновлённые формулы для администраторов и флористов +**Возвращает:** `array` — полный набор данных для ЛК включая: + +- `salary` — данные о зарплате (оклад, смены, часы) +- `sales` — продажи (сумма, план, процент) +- `bonus` — игровые бонусы (SumGame) +- `teamBonus` — командный бонус +- `quality` — качество и премия +- `focusGroups` — премии по фокус-группам +- `matrix` — матричные букеты +- `author` — авторские букеты (с апреля 2025) +- `personal` — персональные надбавки/вычеты +- `timetable` — расписание +- `holiday` — праздничные смены + +**Размер:** ~1,650 строк кода (самый большой метод в системе). + +**Отличия от `getDataDynamic()` (старая версия):** + +| Аспект | getDataDynamic | getDataDynamic202310 | +|--------|----------------|----------------------| +| Период | До октября 2023 | С октября 2023 | +| Командный бонус | Нет | Да (`BonusService::getTeamBonus`) | +| Фокус-группы | Базовые | Расширенные (6 категорий) | +| Матричные букеты | Нет | Да | +| Авторские букеты | Нет | Да (с апреля 2025) | +| Качество | Базовое | С премией | +| Размер кода | ~1,500 строк | ~1,650 строк | --- diff --git a/erp24/docs/services/DashboardService.md b/erp24/docs/services/DashboardService.md index a472611d..6fbda33c 100644 --- a/erp24/docs/services/DashboardService.md +++ b/erp24/docs/services/DashboardService.md @@ -84,6 +84,23 @@ $data_store_visitors = [ ] ``` +**Логика работы:** + +1. Создаёт пустой массив `$store_traffic` +2. Итерирует по входному массиву `$data_store_visitors` +3. Для каждой записи извлекает `date`, `counter`, `store_id` +4. Если `counter` не пустой — суммирует значение по ключу `[date][store_id]` +5. Возвращает агрегированный массив + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `ArrayHelper::getValue($row, 'date')` | `ArrayHelper` | Безопасное извлечение даты из записи | +| `ArrayHelper::getValue($row, 'counter')` | `ArrayHelper` | Безопасное извлечение счётчика | +| `ArrayHelper::getValue($row, 'store_id')` | `ArrayHelper` | Безопасное извлечение ID магазина | +| `ArrayHelper::getValue($store_traffic, $date . '.' . $storeId)` | `ArrayHelper` | Проверка существования ключа | + **Пример использования:** ```php $service = new DashboardService(); @@ -138,9 +155,41 @@ public function getSalesSumWithCityStoreId( ] ``` +**Параметры:** + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|--------------|----------| +| `$sales_sum` | `array` | - | Массив продаж `['store_id' => summ]` | +| `$plan` | `array` | - | Массив планов `['store_id' => plan_value]` | +| `$city_stores` | `array` | - | Массив названий магазинов `['store_id' => store_name]` | + +**Логика работы:** + +1. Инициализация пустых массивов `$sales` и `$sales_summ_all = 0` +2. Итерация по массиву `$sales_sum` (store_id => sum) +3. Для каждого магазина извлечение плана через `ArrayHelper::getValue($plan, $store_id)` +4. Если план существует — извлечение названия магазина из `$city_stores` +5. Расчёт процента выполнения плана: `($sum / $plan) * 100` +6. Округление процента до целого числа через `round($percent)` +7. Формирование записи с полями: store, store_id, summ, plan, percent +8. Накопление общей суммы `$sales_summ_all` +9. Сортировка результатов по убыванию процента через `uasort()` +10. Возврат массива с ключами 'sales' и 'sales_summ_all' + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `ArrayHelper::getValue($plan, $store_id)` | `ArrayHelper` | Безопасное извлечение плана магазина по ID | +| `ArrayHelper::getValue($city_stores, $store_id)` | `ArrayHelper` | Безопасное извлечение названия магазина | +| `round($sum, 2)` | PHP | Округление суммы до 2 знаков после запятой | +| `round($percent)` | PHP | Округление процента до целого числа | +| `uasort($sales, $callback)` | PHP | Сортировка массива с сохранением ключей по пользовательской функции | + **Особенности:** - Сортирует магазины по убыванию процента выполнения - Округляет суммы до 2 знаков после запятой +- Магазины без плана пропускаются (не включаются в результат) **Пример:** ```php @@ -274,6 +323,36 @@ public static function setData( | cumulative_total_stores | 30 | Нарастающий итог всего | | write_offs | 31 | Списания (брак) | +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `ExportImportService::getEntityByCityStore()` | `ExportImportService` | Получение маппинга entity_id ↔ export_val для магазинов | +| `date($format, $time)` | PHP | Форматирование текущей даты/времени | +| `DashboardFields::find()` | `DashboardFields` | Построение ActiveQuery для получения полей дашборда | +| `->select(['id', 'name'])` | `ActiveQuery` | Выборка только полей id и name | +| `->andWhere(['active' => 1])` | `ActiveQuery` | Фильтрация только активных полей | +| `->andWhere(['<>', 'name', 'sales_summ'])` | `ActiveQuery` | Исключение поля sales_summ | +| `->indexBy('id')` | `ActiveQuery` | Индексирование результата по id | +| `->asArray()` | `ActiveQuery` | Возврат результата как массива (не объектов) | +| `ArrayHelper::getColumn($fields_arr, 'name')` | `ArrayHelper` | Извлечение колонки name из массива полей | +| `array_combine($fieldsIds, $fieldsNames)` | PHP | Создание ассоциативного массива id => name | +| `array_flip($fieldsNames)` | PHP | Инверсия массива name => id | +| `self::return_sales_stores($dateFrom, $dateTo)` | `DashboardService` | Расчёт базовых метрик продаж | +| `self::return_sale_products_class(...)` | `DashboardService` | Расчёт продаж по классу товаров | +| `self::return_incoming_traffic_stores(...)` | `DashboardService` | Получение трафика посетителей | +| `self::insert_data_in_dashboard_sales(...)` | `DashboardService` | Сохранение агрегированных данных | +| `Yii::$app->getDb()` | `Yii` | Получение подключения к базе данных | +| `$connection->createCommand($sql, $params)` | `Connection` | Создание SQL-команды с параметрами | +| `$command->queryAll()` | `Command` | Выполнение запроса и получение всех строк | +| `DateHelper::getDateTimeStartDay($date, $format)` | `DateHelper` | Получение начала дня для даты | +| `DateHelper::getDateTimeEndDay($date, $format)` | `DateHelper` | Получение конца дня для даты | +| `Sales::OPERATION_SALE` | `Sales` | Константа операции "Продажа" | +| `Sales::OPERATION_RETURN` | `Sales` | Константа операции "Возврат" | +| `cal_days_in_month(CAL_GREGORIAN, $month, $year)` | PHP | Получение количества дней в месяце | +| `explode($delimiter, $string)` | PHP | Разбиение строки даты на компоненты | +| `round($value, $precision)` | PHP | Округление значений | + **Пример использования:** ```php // Расчет дашборда за последние 7 дней @@ -294,6 +373,18 @@ DashboardService::setData('2025-11-17', '2025-11-17', null, true); **Назначение:** Расчет базовых метрик продаж (офлайн, доставка, самовывоз). +**Сигнатура:** +```php +public static function return_sales_stores($dateFrom, $dateTo): array +``` + +**Параметры:** + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|--------------|----------| +| `$dateFrom` | `string` | - | Начальная дата периода (YYYY-MM-DD) | +| `$dateTo` | `string` | - | Конечная дата периода (YYYY-MM-DD) | + **Возвращает:** ```php [ @@ -305,13 +396,48 @@ DashboardService::setData('2025-11-17', '2025-11-17', null, true); ] ``` +**Логика работы:** + +1. **Запрос возвратов офлайн:** SQL-запрос к таблице `sales` с `operation = 'Возврат'` и `(order_id = '' OR order_id = '0')` +2. **Запрос продаж офлайн:** SQL-запрос к таблице `sales` с `operation = 'Продажа'` и `(order_id = '' OR order_id = '0')` +3. **Расчёт метрик офлайн:** Вычитание возвратов из продаж для получения чистых показателей +4. **Запрос возвратов доставки:** SQL-запрос с `operation = 'Возврат'` и `order_id > 0` +5. **Запрос продаж доставки:** SQL-запрос с `operation = 'Продажа'` и `order_id > 0` +6. **Расчёт метрик доставки:** Формирование delivery_sales_summ, delivery_checks_counter, delivery_sales_avg_check +7. **Запрос возвратов самовывоза:** SQL-запрос с `order_id > 0` и `store_id != '4'` +8. **Запрос продаж самовывоза:** SQL-запрос с `order_id > 0` и `store_id != '4'` +9. **Расчёт метрик самовывоза:** Формирование smovivoz_sales_summ, smovivoz_checks_counter +10. **Сохранение всех метрик:** Вызов `insert_data_in_dashboard_sales()` для каждого типа метрики + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Yii::$app->getDb()` | `Yii` | Получение подключения к базе данных PostgreSQL | +| `$connection->createCommand($sql, $params)` | `Connection` | Создание SQL-команды с prepared statements | +| `$command->queryAll()` | `Command` | Выполнение запроса и получение всех результатов | +| `DateHelper::getDateTimeStartDay($dateFrom, true)` | `DateHelper` | Форматирование начала дня для SQL | +| `DateHelper::getDateTimeEndDay($dateTo, true)` | `DateHelper` | Форматирование конца дня для SQL | +| `Sales::OPERATION_SALE` | `Sales` | Константа 'Продажа' | +| `Sales::OPERATION_RETURN` | `Sales` | Константа 'Возврат' | +| `round($value)` | PHP | Округление среднего чека до целого | +| `self::insert_data_in_dashboard_sales(...)` | `DashboardService` | Сохранение метрик в dashboard_sales | + +**SQL-агрегации:** + +- `count(*)` — количество чеков +- `sum(summ)` — общая сумма +- `sum(CASE WHEN matrix >= 15 THEN summ ELSE 0 END)` — сумма матричных товаров +- `sum(CASE WHEN phone IS NOT NULL THEN 1 ELSE 0 END)` — количество бонусных клиентов +- `TO_CHAR(date,'YYYY-MM-DD')` — форматирование даты для группировки + --- ### return_sale_products_class() **Назначение:** Расчет продаж по классу товаров (упаковка, услуги, горшечные). -**Параметры:** +**Сигнатура:** ```php public static function return_sale_products_class( $dateFrom, @@ -319,14 +445,70 @@ public static function return_sale_products_class( $fieldName, // 'wrap', 'services', 'potted' $fieldId, $store_id = "" -) +): array +``` + +**Параметры:** + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|--------------|----------| +| `$dateFrom` | `string` | - | Начальная дата периода | +| `$dateTo` | `string` | - | Конечная дата периода | +| `$fieldName` | `string` | - | Тип класса товаров: 'wrap', 'services', 'potted' | +| `$fieldId` | `int` | - | ID поля дашборда для сохранения | +| `$store_id` | `string` | `""` | Фильтр по магазину (пусто = все магазины) | + +**Возвращает:** +```php +[ + 'YYYY-MM-DD' => [ + 'store_id_1' => 15000.00, // Чистая сумма (продажи - возвраты) + 'store_id_2' => 22000.00, + ] +] ``` -**Алгоритм:** -1. Получение ID товаров класса из `products_class` WHERE `tip = $fieldName` -2. JOIN `sales_items` ON `sales_items.id_1c IN (products_guids)` -3. Суммирование для продаж и возвратов отдельно -4. Вычитание возвратов из продаж +**Логика работы:** + +1. **Получение ID товаров класса:** SQL-запрос к `products_1c` с JOIN `products_class` по `parent_id = category_id` и `tip = $fieldName` +2. **Формирование WHERE-условия:** Создание SQL IN-clause с ID товаров класса +3. **Запрос возвратов:** SQL с `operation = 'Возврат'`, JOIN `sales_items` по `check_id`, суммирование `sales_items.summa` +4. **Запрос продаж:** SQL с `operation = 'Продажа'`, JOIN `sales_items` по `check_id`, суммирование `sales_items.summa` +5. **Расчёт чистой суммы:** Для каждой даты и магазина: `$sum = $sales - $returns` +6. **Сохранение результатов:** Вызов `setDashboardSalesRow()` для каждой записи + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Yii::$app->getDb()` | `Yii` | Получение подключения к БД | +| `$connection->createCommand($sql, $params)` | `Connection` | Создание SQL-команды | +| `$command->queryAll()` | `Command` | Выполнение запроса | +| `$command->getRawSql()` | `Command` | Получение сырого SQL для отладки | +| `implode("','", $arrId)` | PHP | Формирование списка ID для IN-clause | +| `array_key_exists($key, $array)` | PHP | Проверка существования ключа в массиве | +| `explode(".", $date_d)` | PHP | Разбиение даты DD.MM.YYYY на компоненты | +| `self::setDashboardSalesRow(...)` | `DashboardService` | Сохранение одной записи в dashboard_sales | + +**SQL-структура:** +```sql +-- Запрос 1: Получение ID товаров класса +SELECT products_1c.id +FROM products_1c +RIGHT JOIN products_class ON products_1c.parent_id = products_class.category_id + AND products_class.tip = :type_name +WHERE products_1c.id IS NOT NULL + +-- Запрос 2: Возвраты по классу товаров +SELECT TO_CHAR(sales.date,'YYYY-MM-DD') as date_p, sales.store_id, sum(sales_items.summa) as summa +FROM sales +RIGHT JOIN sales_items ON (sales_items.check_id = sales.id AND sales_items.id_1c IN (...)) +WHERE sales.operation = 'Возврат' AND (order_id='' OR order_id='0') + AND sales.date >= ... AND sales.date <= ... +GROUP BY date_p, sales.store_id + +-- Запрос 3: Продажи по классу товаров (аналогичный) +``` --- @@ -334,20 +516,61 @@ public static function return_sale_products_class( **Назначение:** Получение трафика посетителей из таблицы `store_visitors`. +**Сигнатура:** ```php public static function return_incoming_traffic_stores( $dateFrom, $dateTo, $field_name, $field_id -) +): array +``` + +**Параметры:** + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|--------------|----------| +| `$dateFrom` | `string` | - | Начальная дата периода | +| `$dateTo` | `string` | - | Конечная дата периода | +| `$field_name` | `string` | - | Название поля дашборда ('incoming_traffic') | +| `$field_id` | `int` | - | ID поля дашборда (7) | + +**Возвращает:** +```php +[ + 'YYYY-MM-DD' => [ + 'store_id_1' => 150, // Суммарный трафик за день + 'store_id_2' => 200, + ] +] ``` +**Логика работы:** + +1. **Получение подключения к БД:** `Yii::$app->getDb()` +2. **Создание SQL-запроса:** Агрегация `sum(counter)` с группировкой по дате и магазину +3. **Выполнение запроса:** `$command->queryAll()` возвращает все строки результата +4. **Формирование массива:** Заполнение `$massivSQL[$row["dt"]][$row["store_id"]]` +5. **Сохранение в БД:** Вызов `insert_data_in_dashboard_sales()` для персистентности +6. **Возврат результата:** Массив с агрегированным трафиком + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Yii::$app->getDb()` | `Yii` | Получение подключения к PostgreSQL | +| `$connection->createCommand($sql, $params)` | `Connection` | Создание SQL-команды с параметрами | +| `$command->queryAll()` | `Command` | Выполнение запроса и получение результатов | +| `DateHelper::getDateTimeStartDay($dateFrom, true)` | `DateHelper` | Начало дня в формате для SQL | +| `DateHelper::getDateTimeEndDay($dateTo, true)` | `DateHelper` | Конец дня в формате для SQL | +| `self::insert_data_in_dashboard_sales(...)` | `DashboardService` | Сохранение данных в dashboard_sales | + **SQL:** ```sql SELECT sum(counter) as counter, store_id, + date, TO_CHAR(date,'YYYY-MM-DD') as dt FROM store_visitors @@ -356,7 +579,8 @@ WHERE AND date <= :date_to GROUP BY - date, store_id + date, + store_id ``` --- @@ -365,24 +589,60 @@ GROUP BY **Назначение:** Сохранение агрегированных данных в таблицу `dashboard_sales`. +**Сигнатура:** + ```php public static function insert_data_in_dashboard_sales( $massivSQL, $fieldName, $fieldId -) +): void ``` -**Процесс:** -1. Перебор массива данных `['date' => ['store_id' => value]]` -2. Конвертация даты из формата DD.MM.YYYY в YYYY-MM-DD -3. Вызов `setDashboardSalesRow()` для каждой записи +**Параметры:** + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|--------------|----------| +| `$massivSQL` | `array` | - | Массив данных `['date' => ['store_id' => value]]` | +| `$fieldName` | `string` | - | Название поля дашборда | +| `$fieldId` | `int` | - | ID поля дашборда | + +**Логика работы:** + +1. Внешний цикл по датам: `foreach ($massivSQL as $date_d => $array)` +2. Внутренний цикл по магазинам: `foreach ($array as $storeId => $sum)` +3. Конвертация даты из DD.MM.YYYY в YYYY-MM-DD через `explode(".", $date_d)` +4. Проверка наличия `$storeId` (пропуск пустых) +5. Вызов `setDashboardSalesRow()` для каждой записи + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `explode(".", $date_d)` | PHP | Разбиение даты DD.MM.YYYY на компоненты [DD, MM, YYYY] | +| `self::setDashboardSalesRow(...)` | `DashboardService` | Вставка/обновление одной записи в dashboard_sales | + +**Пример входных данных:** + +```php +$massivSQL = [ + '17.11.2025' => [ + '1' => 150000.00, // store_id => value + '2' => 200000.00, + ], + '18.11.2025' => [ + '1' => 180000.00, + ] +]; +``` --- ### setDashboardSalesRow() -**Назначение:** Вставка или обновление одной строки в `dashboard_sales`. +**Назначение:** Вставка или обновление одной строки в `dashboard_sales` (UPSERT паттерн). + +**Сигнатура:** ```php public static function setDashboardSalesRow( @@ -391,14 +651,61 @@ public static function setDashboardSalesRow( $fieldName, $fieldId, $sum -) +): void ``` -**Алгоритм:** -1. Поиск существующей записи по `date`, `store_id`, `field_name`, `field_id` -2. Если не найдена — создание новой записи -3. Установка `summ`, обновление `last_modified` -4. Валидация и сохранение +**Параметры:** + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|--------------|----------| +| `$date` | `string` | - | Дата в формате YYYY-MM-DD | +| `$storeId` | `string\|int` | - | ID магазина | +| `$fieldName` | `string` | - | Название поля дашборда | +| `$fieldId` | `int` | - | ID поля дашборда | +| `$sum` | `float` | - | Значение метрики | + +**Логика работы:** + +1. **Поиск существующей записи:** ActiveQuery к `DashboardSales` с условиями `date`, `store_id`, `field_name`, `field_id` +2. **Создание новой записи:** Если запись не найдена — `new DashboardSales()` с установкой всех полей +3. **Установка значения:** Вызов `->setSumm($sum)` для обновления метрики +4. **Обновление времени:** Вызов `->setLastModified()` для фиксации времени изменения +5. **Валидация:** Проверка корректности данных через `$dashboardSales->validate()` +6. **Сохранение:** При успешной валидации — `$dashboardSales->save()` + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `DashboardSales::find()` | `DashboardSales` | Построение ActiveQuery для поиска записи | +| `->andWhere(['date' => $date])` | `ActiveQuery` | Фильтрация по дате | +| `->andWhere(['store_id' => $storeId])` | `ActiveQuery` | Фильтрация по магазину | +| `->andWhere(['field_name' => $fieldName])` | `ActiveQuery` | Фильтрация по названию поля | +| `->andWhere(['field_id' => $fieldId])` | `ActiveQuery` | Фильтрация по ID поля | +| `->limit(1)` | `ActiveQuery` | Ограничение до 1 записи | +| `->one()` | `ActiveQuery` | Выполнение запроса и получение записи | +| `new DashboardSales()` | `DashboardSales` | Создание нового экземпляра модели | +| `->setDate($date)` | `DashboardSales` | Установка даты (fluent interface) | +| `->setStoreId($storeId)` | `DashboardSales` | Установка ID магазина | +| `->setFieldName($fieldName)` | `DashboardSales` | Установка названия поля | +| `->setFieldId($fieldId)` | `DashboardSales` | Установка ID поля | +| `->setSumm($sum)` | `DashboardSales` | Установка значения метрики | +| `->setLastModified()` | `DashboardSales` | Установка текущего времени изменения | +| `->validate()` | `ActiveRecord` | Валидация модели по правилам | +| `->save()` | `ActiveRecord` | Сохранение записи в БД | + +**Пример использования:** + +```php +// Сохранение метрики продаж за 17.11.2025 для магазина 1 +DashboardService::setDashboardSalesRow( + '2025-11-17', // date + '1', // store_id + 'sales_summ', // field_name + 14, // field_id + 150000.50 // sum +); +``` --- diff --git a/erp24/docs/services/MotivationService.md b/erp24/docs/services/MotivationService.md index 0fa16e6b..101c1e64 100644 --- a/erp24/docs/services/MotivationService.md +++ b/erp24/docs/services/MotivationService.md @@ -228,6 +228,22 @@ const CODE_CALCULATION_OF_PREMIUM = 1025; // Расчет премии 5. Добавление дополнительных элементов (расчетные показатели) 6. Сортировка по полю `order` +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Motivation::find()` | `Motivation` | Создание ActiveQuery для поиска записи мотивации | +| `->where(['store_id' => ..., 'year' => ..., 'month' => ...])` | `ActiveQuery` | Фильтрация по магазину, году и месяцу | +| `->one()` | `ActiveQuery` | Получение единственной записи | +| `MotivationValue::find()` | `MotivationValue` | Запрос значений показателей | +| `->where(['motivation_id' => ...])` | `ActiveQuery` | Фильтр по ID записи мотивации | +| `->indexBy('value_id')` | `ActiveQuery` | Индексация результатов по коду показателя | +| `->all()` | `ActiveQuery` | Получение всех записей | +| `MotivationCostsItem::find()` | `MotivationCostsItem` | Запрос справочника статей расходов | +| `->orderBy(['order' => SORT_ASC])` | `ActiveQuery` | Сортировка по полю order | +| `ArrayHelper::getValue($array, $key)` | `ArrayHelper` | Безопасное извлечение значения из массива | +| `ArrayHelper::merge($arr1, $arr2)` | `ArrayHelper` | Объединение массивов | + **Использование:** ```php @@ -236,6 +252,7 @@ $tableData = MotivationService::getMotivationDataTableSort(1, 2024, 1); ``` **Используется в:** + - `motivation/IndexAction` — отображение таблицы мотивации - `motivation/index.php` (view) — рендеринг таблицы @@ -278,6 +295,14 @@ switch ($motivationValue->value_type) { } ``` +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `MotivationValue::find()` | `MotivationValue` | Создание ActiveQuery для поиска значения | +| `->where(['motivation_id' => ..., 'motivation_group_id' => ..., 'value_id' => ...])` | `ActiveQuery` | Фильтрация по трём ключевым полям | +| `->one()` | `ActiveQuery` | Получение единственной записи | + **Использование:** ```php @@ -343,6 +368,17 @@ switch ($valueType) { $motivationValue->save(); ``` +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `MotivationValueGroup::find()` | `MotivationValueGroup` | Поиск группы по алиасу | +| `->where(['alias' => $groupAlias])` | `ActiveQuery` | Фильтрация по алиасу ('plan', 'fact', 'week1' и т.д.) | +| `->one()` | `ActiveQuery` | Получение записи группы | +| `MotivationValue::find()` | `MotivationValue` | Поиск существующего значения | +| `new MotivationValue()` | `MotivationValue` | Создание нового экземпляра при отсутствии записи | +| `->save()` | `ActiveRecord` | Сохранение записи в БД | + **Использование:** ```php @@ -412,7 +448,20 @@ self::saveOrUpdateMotivationValue($motivation->id, 'fact', self::CODE_ASSEMBLY_S self::saveOrUpdateMotivationValue($motivation->id, 'fact', self::CODE_DELIVERY_SERVICES, 'float', $deliveryServices); ``` +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `date("Y-m-d H:i:s", strtotime(...))` | PHP | Форматирование диапазона дат месяца | +| `Motivation::find()` | `Motivation` | Поиск записи мотивации | +| `self::getSalesAndReturns($monthStart, $monthEnd, $store_id)` | `MotivationService` | Получение сумм продаж и возвратов | +| `OrdersAmo::find()->sum('summ')` | `OrdersAmo` | Сумма онлайн продаж через AMO CRM | +| `self::getSalesProductsDetails(...)` | `MotivationService` | Детализация продаж по типам услуг | +| `array_sum(array_column($details, 'assembly_services'))` | PHP | Агрегация услуг сборки | +| `self::saveOrUpdateMotivationValue(...)` | `MotivationService` | Сохранение рассчитанных значений | + **Используется в:** + - `motivation/IndexAction` — расчет при загрузке страницы - `task_32_motivation_fact.php` — cron задача @@ -463,6 +512,19 @@ return [ ]; ``` +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Sales::find()` | `Sales` | Создание ActiveQuery для таблицы продаж | +| `->where(['store_id' => $storeId])` | `ActiveQuery` | Фильтрация по магазину | +| `->andWhere(['between', 'date', ...])` | `ActiveQuery` | Фильтрация по диапазону дат | +| `->andWhere(['operation' => 'Продажа'])` | `ActiveQuery` | Фильтр операции продажи | +| `->andWhere(['operation' => 'Возврат'])` | `ActiveQuery` | Фильтр операции возврата | +| `->andWhere(['held' => 1])` | `ActiveQuery` | Только проведённые чеки | +| `->andWhere(['!=', 'status', 'deleted'])` | `ActiveQuery` | Исключение удалённых | +| `->sum('summ')` | `ActiveQuery` | Агрегация суммы | + --- #### 6. `getSalesProductsDetails($startDate, $endDate, $storeId): array` diff --git a/erp24/docs/services/PayrollService.md b/erp24/docs/services/PayrollService.md index 73e20d8d..fdb279a8 100644 --- a/erp24/docs/services/PayrollService.md +++ b/erp24/docs/services/PayrollService.md @@ -1,5 +1,39 @@ # PayrollService +## 🧠 Mindmap: PayrollService + +```mermaid +mindmap + root((PayrollService)) + Характеристики + 72 LOC + 2 публичных метода + Standalone класс + Назначение + Зарплатные расчеты + Проверка прав доступа + Валидация временных окон + Методы + __construct инициализация + checkPayrollUpdateAllowed валидация + Зависимости модели + Admin сотрудники + AdminGroup группы + AdminRating рейтинг + CityStore магазины + Timetable табель + Sales продажи + WriteOffs списания + Зависимости хелперы + DateHelper + HtmlHelper + SalaryHelper + Сервисы + CabinetService вывод ошибок +``` + +--- + ## Назначение Сервис для работы с зарплатными расчетами и проверкой прав доступа к обновлению данных по зарплате. Обеспечивает валидацию временных окон для редактирования зарплатных данных определенными группами пользователей. @@ -62,12 +96,27 @@ use yii_app\records\WriteOffs; ### `__construct($config = [])` **Описание:** -Конструктор класса. Инициализирует зависимость от CabinetService. +Конструктор класса. Инициализирует зависимость от CabinetService через Dependency Injection via constructor. **Параметры:** -- `$config` (array) - массив конфигурации (не используется в текущей реализации) -**Возвращает:** void +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|--------------|----------| +| `$config` | `array` | `[]` | Массив конфигурации (не используется в текущей реализации) | + +**Возвращает:** `void` + +**Логика работы:** + +1. Принимает опциональный массив конфигурации +2. Создаёт новый экземпляр `CabinetService` +3. Присваивает его свойству `$this->cabinetService` + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `new CabinetService()` | `CabinetService` | Создаёт экземпляр сервиса для работы с кабинетом пользователя | **Пример:** ```php @@ -79,14 +128,36 @@ $payrollService = new PayrollService(); ### `outputCheckError(string $errorText, $buttonParams, $controller): array` **Описание:** -Делегирует вывод ошибки проверки в CabinetService. Используется для формирования стандартизированного вывода ошибок с кнопками действий. +Делегирует вывод ошибки проверки в CabinetService. Используется для формирования стандартизированного вывода ошибок с кнопками действий. Реализует паттерн Delegation. **Параметры:** -- `$errorText` (string) - текст ошибки для отображения пользователю -- `$buttonParams` (mixed) - параметры кнопки действия (URL, название) -- `$controller` (mixed) - контроллер для обработки действий -**Возвращает:** array - массив с данными ошибки +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$errorText` | `string` | Текст ошибки для отображения пользователю | +| `$buttonParams` | `array\|null` | Параметры кнопки действия (ключи: `url`, `name`) | +| `$controller` | `Controller` | Экземпляр контроллера для рендеринга | + +**Структура `$buttonParams`:** + +| Ключ | Тип | Описание | +|------|-----|----------| +| `url` | `string` | URL для перенаправления при нажатии кнопки | +| `name` | `string` | Текст на кнопке | + +**Возвращает:** `array` — массив с данными ошибки для передачи в view + +**Логика работы:** + +1. Получает параметры ошибки +2. Делегирует вызов методу `CabinetService::outputCheckError()` +3. Возвращает результат без изменений + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `$this->cabinetService->outputCheckError($errorText, $buttonParams, $controller)` | `CabinetService` | Формирует стандартизированный массив ошибки с кнопкой действия для рендеринга в шаблоне | **Пример:** ```php @@ -103,6 +174,13 @@ $result = $payrollService->outputCheckError( $buttonParams, $this ); + +// Результат: +// [ +// 'errorText' => 'Период редактирования закрыт', +// 'buttonParams' => ['url' => '/payroll/index', 'name' => 'Вернуться к списку'], +// // ... другие данные для view +// ] ``` --- @@ -112,19 +190,65 @@ $result = $payrollService->outputCheckError( **Описание:** Проверяет, разрешено ли обновление данных по зарплате для указанной группы пользователей и даты. Реализует бизнес-правила доступа к редактированию зарплатных данных. +**Параметры:** + +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$dateFrom` | `string` | Дата начала периода в формате 'Y-m-d' | +| `$groupId` | `int` | ID группы пользователя из таблицы admin_group | + +**Возвращает:** `bool` + +- `true` - редактирование разрешено +- `false` - редактирование запрещено + **Бизнес-логика:** + 1. Доступ разрешен только для определенных групп (1, 8, 9, 51, 81) 2. Можно редактировать текущий месяц до 16 числа следующего месяца 18:00 3. Можно редактировать предыдущий месяц до 16 числа текущего месяца 18:00 4. Более старые периоды редактировать нельзя -**Параметры:** -- `$dateFrom` (string) - дата начала периода в формате 'Y-m-d' -- `$groupId` (int) - ID группы пользователя +**Логика работы (детальная):** + +1. Устанавливает `$resultAllowed = true` по умолчанию +2. Проверяет, входит ли `$groupId` в белый список `[1, 8, 9, 51, 81]` +3. Если группа не в списке — сразу возвращает `false` +4. Если группа разрешена: + - Вычисляет последний день выбранного месяца (`$dateEndSelectMonth`) + - Вычисляет первый день выбранного месяца (`$dateFromBeginMonth`) + - Получает текущую дату/время (`$dateCurrent`) + - Вычисляет первый день текущего месяца (`$dateFromBeginCurrentMonth`) + - Вычисляет первый день предыдущего месяца (`$dateFromBeginPreviousMonth`) + - Вычисляет дедлайн: 16-е число текущего месяца 18:00 (`$dateStop`) +5. Проверяет три условия (t1, t2, t3): + - `t1`: выбранный месяц старше предыдущего месяца + - `t2`: выбранный месяц закончился до начала текущего месяца + - `t3`: текущая дата/время после дедлайна (16-е 18:00) +6. Если `t1` истинно ИЛИ (`t2` И `t3`) истинны — возвращает `false` + +**Вызовы сторонних методов:** + +| Метод | Описание | +|-------|----------| +| `in_array($groupId, [1, 8, 9, 51, 81])` | Проверка принадлежности группы к белому списку | +| `date("Y-m-t", strtotime($dateFrom))` | Последний день выбранного месяца | +| `date("Y-m-01", strtotime($dateFrom))` | Первый день выбранного месяца | +| `date("Y-m-d H:i:s", time())` | Текущая дата и время | +| `date("Y-m-01", time())` | Первый день текущего месяца | +| `date("Y-m-01", strtotime($date." -1 month"))` | Первый день предыдущего месяца | +| `date("Y-m-16 18:00:00", strtotime($dateCurrent))` | Дедлайн редактирования | +| `strtotime($dateString)` | Преобразование строки в Unix timestamp | +| `time()` | Текущий Unix timestamp | + +**Формула проверки:** -**Возвращает:** bool -- `true` - редактирование разрешено -- `false` - редактирование запрещено +``` +ЗАПРЕЩЕНО если: + (выбранный_месяц < предыдущий_месяц) + ИЛИ + (выбранный_месяц < текущий_месяц И текущее_время > 16-е_18:00) +``` **Примеры:** diff --git a/erp24/docs/services/README.md b/erp24/docs/services/README.md index 4b8ff6fe..8834a7ee 100644 --- a/erp24/docs/services/README.md +++ b/erp24/docs/services/README.md @@ -2,6 +2,70 @@ Документация всех сервисов ERP24 системы. +## 🧠 Mindmap: Архитектура сервисов + +```mermaid +mindmap + root((Сервисы ERP24)) + P0 Критические + CabinetService 8410 LOC + God Object + 72 метода + Требует рефакторинга + SalesService 1962 LOC + Продажи + Возвраты + Чеки + AutoPlannogrammaService 3217 LOC + Автопланограмма + 31 метод + MarketplaceService 2878 LOC + Flowwow + Yandex Market + DashboardService 1388 LOC + Метрики + Виджеты + UploadService 2349 LOC + Импорт 1С + MotivationService 2179 LOC + P&L мотивация + 36 методов + PayrollService 872 LOC + Зарплата + TimetableService 1200 LOC + Смены + Табель + P1 Высокий + FileService + ReportService + BonusService_API3 + ClientService_API3 + WhatsAppService + TelegramService + StorePlanService + Категории + Зарплата 11шт + PayrollService + RatingService + BonusService + Персонал 8шт + TimetableService + ShiftManagementService + Продажи 10шт + ShipmentService + SalesService + Аналитика 7шт + ReportService + DashboardService + Интеграции 8шт + 1С + AmoCRM + Telegram + Вспомогательные 7шт + CacheService + FileService +``` + ## Обзор Данная директория содержит полную документацию сервисного слоя приложения ERP24. Сервисы инкапсулируют бизнес-логику и предоставляют переиспользуемый функционал для контроллеров, API и фоновых задач. diff --git a/erp24/docs/services/RatingService.md b/erp24/docs/services/RatingService.md index 842668df..4cf0b72d 100644 --- a/erp24/docs/services/RatingService.md +++ b/erp24/docs/services/RatingService.md @@ -65,6 +65,37 @@ public function getData( 6. Подсчет игровых баллов (конверсия, средний чек, и т.д.) 7. Применение бонусов и штрафов +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `CityStore::getCityStoreById($storeId)` | `CityStore` | Получает данные магазина по ID для определения связки с 1С | +| `StorePlanService::getPlanMonthByStore($month, $year, $guid)` | `StorePlanService` | Получает плановые показатели продаж магазина на месяц | +| `EmployeePayment::getMonthlySalary($employeeId, $date)` | `EmployeePayment` | Возвращает оклад сотрудника на указанную дату | +| `WriteOffs::getWriteOffByStore($dateFrom, $dateTo, $storeGuid)` | `WriteOffs` | Получает сумму списаний по магазину за период | +| `$cabinetService->getSalesSaleSum($dateFrom, $dateTo, $storeGuid)` | `CabinetService` | Получает сумму продаж магазина за период | +| `$cabinetService->getSumListAvgCheck($storeId, $dateFrom, $dateTo)` | `CabinetService` | Получает список средних чеков по датам | +| `$cabinetService->getTimetableData($employeeId, $storeId, $dateFrom, $dateTo)` | `CabinetService` | Получает график смен сотрудника | +| `$cabinetService->setAdminStore($timetable)` | `CabinetService` | Устанавливает данные администраторов для смен | +| `$cabinetService->getGuidsByIds($ids, $exportAdmin)` | `CabinetService` | Преобразует ID сотрудников в GUID 1С | +| `$cabinetService->getSumByAdmin($adminGuid, $dateFrom, $dateTo, $isAdmin)` | `CabinetService` | Получает сумму продаж по конкретному сотруднику | +| `$cabinetService->rateStoreCategoryService->getRateInfo($storeId, $groupId, $dateFrom, $dateTo)` | `RateStoreCategoryService` | Получает нормативы смен по категории магазина | +| `SalaryHelper::$normalCountShift` | `SalaryHelper` | Статическое свойство с нормой смен по группам | +| `HtmlHelper::getWorkDays($month, $year)` | `HtmlHelper` | Возвращает количество рабочих дней в месяце | +| `$cabinetService->getAdministratorOklad($planOrSales)` | `CabinetService` | Рассчитывает оклад администратора по плану или продажам | +| `$cabinetService->getTimetableRate($timetable, ...)` | `CabinetService` | Рассчитывает показатели по каждой смене | +| `$cabinetService->getSumListConversion($storeId, $dateFrom, $dateTo)` | `CabinetService` | Получает данные конверсии по магазину | +| `$cabinetService->getConversionShift($dateFrom, $dateTo, $storeId)` | `CabinetService` | Получает конверсию по сменам (день/ночь) | +| `$cabinetService->getSumListConversionBonusClients($storeId, $dateFrom, $dateTo)` | `CabinetService` | Получает процент использования бонусных карт | +| `$cabinetService->getSumListStoreServicesPercent($storeId, $dateFrom, $dateTo)` | `CabinetService` | Получает процент услуг магазина | +| `Timetable::find()` | `Timetable` | ActiveRecord запрос для получения смен администратора | +| `$cabinetService->setGameValues($timetable, ...)` | `CabinetService` | Рассчитывает игровые баллы по сменам | +| `$cabinetService->bonusService->getGameBonusByPercentLoss($percent)` | `BonusService` | Получает бонус за процент списаний | +| `$cabinetService->getSumGameBonus($timetable, $possibleBonus)` | `CabinetService` | Суммирует игровые баллы за все смены | +| `$cabinetService->getSumBonus($array1, $array2)` | `CabinetService` | Объединяет баллы из разных магазинов | +| `ArrayHelper::map($array, $from, $to)` | `ArrayHelper` (Yii2) | Преобразует массив в ассоциативный | +| `ArrayHelper::getValue($array, $key)` | `ArrayHelper` (Yii2) | Безопасное получение значения из массива | + **Пример использования:** ```php $ratingService = new RatingService(); @@ -116,12 +147,31 @@ return $this->render('rating', ['data' => $result]); Проверяет, разрешен ли расчет рейтинга для указанного периода. Рейтинг можно рассчитать до 5 числа следующего месяца 18:00. **Параметры:** -- `$dateFrom` (string) - дата периода -**Возвращает:** bool +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$dateFrom` | `string` | Дата начала периода в формате 'Y-m-d' | + +**Возвращает:** `bool` - `true` - расчет разрешен - `false` - период закрыт +**Логика работы:** + +1. Вычисляет последний день выбранного месяца (`$dateEndSelectMonth`) +2. Получает текущую дату и время +3. Определяет дату закрытия расчёта — 5-е число текущего месяца в 18:00 +4. Если выбранный месяц раньше текущего И текущая дата после дедлайна — расчёт запрещён + +**Вызовы сторонних методов:** + +| Метод | Описание | +|-------|----------| +| `date("Y-m-t", strtotime($dateFrom))` | Получает последний день месяца из даты | +| `date("Y-m-d H:i:s", time())` | Текущая дата и время | +| `date("Y-m-01", time())` | Первый день текущего месяца | +| `date("Y-m-05 18:00:00", strtotime($dateCurrent))` | Дедлайн расчёта рейтинга | + **Пример:** ```php $dateFrom = '2024-01-01'; @@ -138,14 +188,35 @@ if (RatingService::getAllowedCalculateRating($dateFrom)) { ### 3. `getAllowedCalculateAdminRating($dateFrom, $dateTo, $employeeId): bool` (static) **Описание:** -Проверяет, разрешен ли расчет рейтинга для конкретного администратора с учетом запрещенных периодов. +Проверяет, разрешен ли расчет рейтинга для конкретного администратора с учетом запрещенных периодов (например, декретный отпуск, увольнение). **Параметры:** -- `$dateFrom` (string) - дата начала -- `$dateTo` (string) - дата окончания -- `$employeeId` (int) - ID сотрудника -**Возвращает:** bool +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$dateFrom` | `string` | Дата начала периода | +| `$dateTo` | `string` | Дата окончания периода | +| `$employeeId` | `int` | ID сотрудника | + +**Возвращает:** `bool` + +- `true` - расчёт разрешён +- `false` - сотрудник в запрещённом списке на указанный период + +**Логика работы:** + +1. Получает статический массив `SalesService::$forbiddenCalculateAdminRating` с запрещёнными периодами +2. Проверяет, есть ли сотрудник в списке +3. Если да — вызывает `SalesService::getAllowedStart()` для проверки пересечения дат +4. Возвращает `false` если период пересекается с запрещённым + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `SalesService::$forbiddenCalculateAdminRating` | `SalesService` | Статический массив с запрещёнными периодами расчёта по сотрудникам | +| `(new SalesService())->getAllowedStart($dateFrom, $dateTo, $forbidden)` | `SalesService` | Проверяет пересечение дат с запрещённым периодом | +| `array_key_exists($employeeId, $array)` | PHP | Проверка наличия сотрудника в списке | **Пример:** ```php @@ -163,15 +234,32 @@ if (!RatingService::getAllowedCalculateAdminRating($dateFrom, $dateTo, $employee ### 4. `getRatingId($employeeGroupId): int` (static) **Описание:** -Определяет ID типа рейтинга на основе группы сотрудника. +Определяет ID типа рейтинга на основе группы сотрудника. Используется для классификации рейтингов по категориям персонала. **Параметры:** -- `$employeeGroupId` (int) - ID группы сотрудника -**Возвращает:** int -- `1` - рейтинг администраторов -- `2` - рейтинг флористов (по умолчанию) -- `4` - рейтинг подработчиков +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$employeeGroupId` | `int` | ID группы сотрудника из таблицы admin | + +**Возвращает:** `int` + +- `1` - рейтинг администраторов (group_id = 50) +- `2` - рейтинг флористов (по умолчанию для всех остальных) +- `4` - рейтинг подработчиков (group_id = 81) + +**Логика работы:** + +1. По умолчанию возвращает `2` (флористы) +2. Если `$employeeGroupId == Admin::ADMINISTRATOR_GROUP_ID (50)` → возвращает `1` +3. Если `$employeeGroupId == Admin::PART_TIME_WORKER_GROUP_ID (81)` → возвращает `4` + +**Вызовы сторонних методов:** + +| Константа | Класс | Значение | Описание | +|-----------|-------|----------|----------| +| `Admin::ADMINISTRATOR_GROUP_ID` | `Admin` | `50` | ID группы администраторов | +| `Admin::PART_TIME_WORKER_GROUP_ID` | `Admin` | `81` | ID группы подработчиков | **Пример:** ```php @@ -189,11 +277,38 @@ $ratingId = RatingService::getRatingId($groupId); ### 5. `calculateRating($yearSelect, $monthWithZeroSelect): void` **Описание:** -Рассчитывает и обновляет рейтинговые позиции для всех сотрудников за указанный месяц. Сортирует по баллам и присваивает места. +Рассчитывает и обновляет рейтинговые позиции для всех сотрудников за указанный месяц. Сортирует по баллам и присваивает места (1, 2, 3...). **Параметры:** -- `$yearSelect` (int) - год -- `$monthWithZeroSelect` (string) - месяц с нулем ('01', '02', etc.) + +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$yearSelect` | `int` | Год расчёта (например, 2024) | +| `$monthWithZeroSelect` | `string` | Месяц с ведущим нулём ('01', '02', ..., '12') | + +**Возвращает:** `void` + +**Логика работы:** + +1. Итерирует по типам рейтинга (1-4) через `range(1, 4)` +2. Для типов 2 и 3 сортировка по `avg_value`, для остальных по `value` +3. Выбирает все записи `AdminRating` за месяц с сортировкой по убыванию +4. Присваивает места начиная с 1 +5. Сохраняет каждую запись после валидации + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `AdminRating::find()` | `AdminRating` | Создаёт ActiveQuery для поиска записей рейтинга | +| `->andWhere(['rating_id' => $ratingId])` | `ActiveQuery` | Фильтрует по типу рейтинга | +| `->andWhere(['date' => $yearSelect . '-' . $monthWithZeroSelect])` | `ActiveQuery` | Фильтрует по периоду | +| `->orderBy([$valueKey => SORT_DESC])` | `ActiveQuery` | Сортирует по баллам по убыванию | +| `->all()` | `ActiveQuery` | Выполняет запрос и возвращает массив моделей | +| `$itemAdminRating->validate()` | `AdminRating` | Валидирует модель перед сохранением | +| `$itemAdminRating->save()` | `AdminRating` | Сохраняет модель в БД | +| `range(1, 4)` | PHP | Создаёт массив [1, 2, 3, 4] для итерации | +| `in_array($ratingId, [2, 3])` | PHP | Проверяет тип рейтинга для выбора поля сортировки | **Пример:** ```php @@ -222,15 +337,57 @@ public function setRatingValue( ``` **Описание:** -Сохраняет или обновляет запись рейтинга сотрудника в базе данных. +Сохраняет или обновляет запись рейтинга сотрудника в базе данных. Реализует паттерн Upsert — создаёт новую запись или обновляет существующую. **Параметры:** -- `$employeeId` (int) - ID сотрудника -- `$adminSumGameBonusArray` (array) - массив с рассчитанными значениями -- `$ratingId` (int) - тип рейтинга -- `$yearSelect` (int) - год -- `$monthSelect` (int) - месяц -- `$monthWithZeroSelect` (string) - месяц с нулем + +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$employeeId` | `int` | ID сотрудника (admin.id) | +| `$adminSumGameBonusArray` | `array` | Массив с рассчитанными значениями рейтинга | +| `$ratingId` | `int` | Тип рейтинга (1-администраторы, 2-флористы, 4-подработчики) | +| `$yearSelect` | `int` | Год расчёта | +| `$monthSelect` | `int` | Месяц (числовой формат: 1-12) | +| `$monthWithZeroSelect` | `string` | Месяц с ведущим нулём ('01'-'12') | + +**Структура входного массива `$adminSumGameBonusArray`:** + +| Ключ | Тип | Описание | +|------|-----|----------| +| `adminSumGameBonusTotal` | `float` | Общая сумма игровых баллов | +| `adminSumGameCountShiftTotal` | `int` | Количество отработанных смен | +| `adminSumGameAvgSumTotal` | `float` | Средний балл за смену | +| `administratorsCount` | `int` | Количество администраторов в смене | + +**Возвращает:** `void` + +**Логика работы:** + +1. Извлекает значения из массива через `ArrayHelper::getValue()` +2. Проверяет существование записи через `AdminRating::find()->exists()` +3. Если запись не найдена — создаёт новую модель `AdminRating` +4. Если найдена — загружает существующую через `->one()` +5. Устанавливает значения полей: `value`, `count_shift`, `administrators_count`, `avg_value`, `date_time` +6. Валидирует и сохраняет модель + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `ArrayHelper::getValue($array, 'adminSumGameBonusTotal')` | `ArrayHelper` | Извлекает сумму баллов | +| `ArrayHelper::getValue($array, 'adminSumGameCountShiftTotal')` | `ArrayHelper` | Извлекает количество смен | +| `ArrayHelper::getValue($array, 'adminSumGameAvgSumTotal')` | `ArrayHelper` | Извлекает средний балл | +| `ArrayHelper::getValue($array, 'administratorsCount')` | `ArrayHelper` | Извлекает число админов | +| `AdminRating::find()` | `AdminRating` | Создаёт запрос для поиска записи | +| `->andWhere(['admin_id' => $employeeId])` | `ActiveQuery` | Фильтр по сотруднику | +| `->andWhere(['rating_id' => $ratingId])` | `ActiveQuery` | Фильтр по типу рейтинга | +| `->andWhere(['date' => $year . '-' . $month])` | `ActiveQuery` | Фильтр по периоду | +| `->exists()` | `ActiveQuery` | Проверяет наличие записи (boolean) | +| `->one()` | `ActiveQuery` | Возвращает одну модель | +| `new AdminRating()` | `AdminRating` | Создаёт новую модель рейтинга | +| `$adminRating->validate()` | `AdminRating` | Валидирует модель | +| `$adminRating->save()` | `AdminRating` | Сохраняет в БД | +| `date("Y-m-d H:i:s")` | PHP | Текущая дата и время | **Пример:** ```php @@ -267,9 +424,63 @@ public function getClusterRatingAdministrators( ``` **Описание:** -Получает рейтинги администраторов кластера (группы магазинов). +Получает рейтинги администраторов кластера (группы магазинов) с подгрузкой связанных данных о сотрудниках. + +**Параметры:** + +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$yearSelect` | `int` | Год расчёта | +| `$monthWithZeroSelect` | `string` | Месяц с ведущим нулём | +| `$adminIds` | `array` | Массив ID администраторов кластера | -**Возвращает:** array - массив рейтингов с данными админов +**Возвращает:** `array` — массив записей AdminRating с подгруженной связью `admin` + +**Структура возвращаемого массива:** + +```php +[ + [ + 'id' => 123, + 'admin_id' => 101, + 'rating_id' => 1, + 'value' => 850, + 'count_shift' => 20, + 'avg_value' => 42.5, + 'rating' => 1, + 'date' => '2024-01', + 'admin' => [ + 'id' => 101, + 'name' => 'Иванов Иван', + 'store_id' => 5, + // ... + ] + ], + // ... +] +``` + +**Логика работы:** + +1. Создаёт запрос к `AdminRating` с фильтром `rating_id = 1` (только администраторы) +2. Фильтрует по списку `admin_id` +3. Фильтрует по периоду `date = 'YYYY-MM'` +4. Подгружает связь `admin` через `->with('admin')` +5. Сортирует по `value` по убыванию +6. Возвращает массив через `->asArray()->all()` + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `AdminRating::find()` | `AdminRating` | Создаёт ActiveQuery для таблицы admin_rating | +| `->andWhere(['rating_id' => 1])` | `ActiveQuery` | Фильтр по типу рейтинга (администраторы) | +| `->andWhere(['admin_id' => $adminIds])` | `ActiveQuery` | Фильтр по списку ID сотрудников | +| `->andWhere(['date' => $year . '-' . $month])` | `ActiveQuery` | Фильтр по периоду | +| `->with('admin')` | `ActiveQuery` | Eager Loading связи с таблицей admin | +| `->orderBy(['value' => SORT_DESC])` | `ActiveQuery` | Сортировка по баллам (лучшие сверху) | +| `->asArray()` | `ActiveQuery` | Возврат в виде массива вместо объектов | +| `->all()` | `ActiveQuery` | Выполнение запроса | **Пример:** ```php @@ -292,10 +503,47 @@ foreach ($clusterRating as $rating) { ### 8. `getClusterAvgRatingAdministrators()` - Средний рейтинг кластера +**Сигнатура:** +```php +public function getClusterAvgRatingAdministrators( + $yearSelect, + $monthWithZeroSelect, + $adminIds +) +``` + **Описание:** -Рассчитывает средний рейтинг администраторов кластера. +Рассчитывает средний рейтинг администраторов кластера через SQL-агрегацию AVG(). + +**Параметры:** -**Возвращает:** float - среднее значение баллов +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$yearSelect` | `int` | Год расчёта | +| `$monthWithZeroSelect` | `string` | Месяц с ведущим нулём | +| `$adminIds` | `array` | Массив ID администраторов кластера | + +**Возвращает:** `float` — среднее значение баллов, округлённое до 1 знака + +**Логика работы:** + +1. Формирует SQL-запрос с `SELECT AVG(value) as summ` +2. Применяет те же фильтры, что и `getClusterRatingAdministrators()` +3. Выполняет `->scalar()` для получения одного значения +4. Округляет результат до 1 десятичного знака через `round(..., 1)` + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `AdminRating::find()` | `AdminRating` | Создаёт ActiveQuery | +| `->select(['summ' => new Expression("AVG(value)")])` | `ActiveQuery` | SQL-агрегация среднего | +| `new \yii\db\Expression("AVG(value)")` | `Expression` | Raw SQL выражение | +| `->andWhere(['rating_id' => 1])` | `ActiveQuery` | Фильтр по типу | +| `->andWhere(['admin_id' => $adminIds])` | `ActiveQuery` | Фильтр по ID | +| `->andWhere(['date' => $date])` | `ActiveQuery` | Фильтр по дате | +| `->scalar()` | `ActiveQuery` | Возвращает скалярное значение | +| `round($value, 1)` | PHP | Округление до 1 знака | **Пример:** ```php @@ -313,10 +561,74 @@ echo "Средний рейтинг кластера: " . $avgRating; ### 9. `getClusterGameSumValue()` - Сумма баллов кластера +**Сигнатура:** +```php +public function getClusterGameSumValue( + $clusterAdmin, + $yearSelect, + $monthWithZeroSelect +) +``` + **Описание:** -Рассчитывает суммарные игровые баллы кластера магазинов с учетом плана продаж. +Рассчитывает суммарные игровые баллы кластера магазинов с учетом плана продаж. Учитывает только магазины, для которых разрешён расчёт плана. + +**Параметры:** -**Возвращает:** array - массив с суммами и метриками +| Параметр | Тип | Описание | +|----------|-----|----------| +| `$clusterAdmin` | `array` | Данные кластер-администратора с ключами `id` и `store_arr` | +| `$yearSelect` | `int` | Год расчёта | +| `$monthWithZeroSelect` | `string` | Месяц с ведущим нулём | + +**Структура входного массива `$clusterAdmin`:** + +| Ключ | Тип | Описание | +|------|-----|----------| +| `id` | `int` | ID кластер-администратора | +| `store_arr` | `string` | Список ID магазинов через запятую ('1,2,3,4,5') | + +**Возвращает:** `array` — массив с агрегированными метриками кластера + +**Структура возвращаемого массива:** + +| Ключ | Тип | Описание | +|------|-----|----------| +| `adminSumGameBonusTotal` | `float` | Сумма баллов всех администраторов кластера | +| `adminSumGameCountShiftTotal` | `int` | Всегда 1 (кластер как единица) | +| `adminSumGameAvgSumTotal` | `float` | Средний балл = сумма / количество администраторов | +| `administratorsCount` | `int` | Количество администраторов в кластере | + +**Логика работы:** + +1. Извлекает `store_arr` и `id` из входного массива +2. Разбивает строку магазинов в массив через `explode(',')` +3. Вычисляет период: первый и последний день месяца +4. Фильтрует магазины через `CabinetService::getAllowedStorePlanCalculate()` +5. Получает список администраторов через `Admin::getAdmins()` с фильтром по магазинам +6. Суммирует баллы через `AdminRating::find()->select(['summ' => SUM(value)])` +7. Вычисляет количество администраторов (с особой логикой для 2022-12 и после 2023) +8. Рассчитывает средний балл = сумма / количество + +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `ArrayHelper::getValue($clusterAdmin, 'store_arr')` | `ArrayHelper` | Извлекает строку магазинов | +| `ArrayHelper::getValue($clusterAdmin, 'id')` | `ArrayHelper` | Извлекает ID кластер-админа | +| `explode(',', $storeIdsString)` | PHP | Разбивает строку в массив | +| `date("Y-m-t", strtotime($dateFrom))` | PHP | Последний день месяца | +| `(new CabinetService())->getAllowedStorePlanCalculate($storeId, $from, $to)` | `CabinetService` | Проверяет разрешение расчёта плана для магазина | +| `Admin::getAdmins('', $groupIds, 'ASC', $storeIds, '', Admin::NOT_IN_STORE_IDS)` | `Admin` | Получает список администраторов по магазинам | +| `ArrayHelper::getColumn($admins, 'id')` | `ArrayHelper` | Извлекает колонку ID | +| `array_values($ids)` | PHP | Переиндексирует массив | +| `AdminRating::find()` | `AdminRating` | Создаёт запрос | +| `->select(['summ' => new Expression("SUM(value)")])` | `ActiveQuery` | SQL-агрегация суммы | +| `->andWhere(['date' => $date])` | `ActiveQuery` | Фильтр по периоду | +| `->andWhere(['admin_id' => $adminIds])` | `ActiveQuery` | Фильтр по администраторам | +| `->scalar()` | `ActiveQuery` | Скалярное значение | +| `count($storeIds)` | PHP | Количество магазинов (для 2023+) | +| `round($sum / $count, 0)` | PHP | Округление среднего до целого | **Пример:** ```php diff --git a/erp24/docs/services/SalesService.md b/erp24/docs/services/SalesService.md index 501b5e5e..f94af48d 100644 --- a/erp24/docs/services/SalesService.md +++ b/erp24/docs/services/SalesService.md @@ -1,5 +1,46 @@ # Service: SalesService +## 🧠 Mindmap: SalesService + +```mermaid +mindmap + root((SalesService)) + Характеристики + 1,962 LOC + 29 публичных методов + 27 вызовов в системе + Высокая сложность + Основные задачи + Расчет сумм продаж + Статистика сотрудников + Бонусы за матрицу + Авторские букеты + Дашборды и отчеты + Ключевые методы + getSalesSum суммы продаж + getSalesReturn возвраты + getSalesByAdmin по сотруднику + getSalesByStore по магазину + getMatrixSales матрица + getAuthorSales авторские + Фильтры + По датам + По магазинам + По типам оплаты + По доставке + Зависимости модели + Sales продажи + SalesProducts товары + Admin сотрудники + AdminGroup группы + ProductsClass классы + Хелперы + DateHelper даты + ArrayHelper массивы +``` + +--- + ## Назначение SalesService — критический сервис для обработки продаж и возвратов в системе ERP24. Сервис отвечает за получение, расчет и анализ данных о продажах, возвратах, чеках и бонусных клиентах. Используется в личном кабинете сотрудников, дашбордах, отчетах и системе мотивации (начисление бонусов за матричные продажи, пиротехнику, авторские букеты). @@ -125,7 +166,26 @@ $result = $service->getSalesSum( 4. Если указан `$payType = '1'` — добавляются варианты: '1', '3', '1,3', '3,1' (наличные + бонусы) 5. Группировка по `store_id_1c` и `operation` +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Sales::find()` | `Sales` | Создание ActiveQuery для таблицы sales | +| `->alias('s')` | `ActiveQuery` | Установка алиаса для таблицы | +| `new \yii\db\Expression("SUM(s.summ - s.skidka)")` | `Expression` | SQL-выражение для суммирования | +| `->joinWith('saleCheck')` | `ActiveQuery` | JOIN с таблицей sale_checks | +| `DateHelper::getDateTimeStartDay($dateFrom, true)` | `DateHelper` | Начало дня для фильтра даты | +| `DateHelper::getDateTimeEndDay($dateTo, true)` | `DateHelper` | Конец дня для фильтра даты | +| `->andWhere(['>=', 's.date', $value])` | `ActiveQuery` | Условие >= для даты | +| `->andWhere(['<=', 's.date', $value])` | `ActiveQuery` | Условие <= для даты | +| `->leftJoin('create_checks cc', ...)` | `ActiveQuery` | LEFT JOIN с таблицей create_checks | +| `->andFilterWhere(['or', ...])` | `ActiveQuery` | Условный фильтр с OR | +| `->groupBy(['s.store_id_1c', 's.operation'])` | `ActiveQuery` | Группировка результатов | +| `->createCommand()->getRawSql()` | `Command` | Получение сырого SQL для отладки | +| `->asArray()->all()` | `ActiveQuery` | Выполнение запроса с возвратом массива | + **Производительность:** + - Сложность: O(n) по количеству чеков - Среднее время: 50-200 ms (зависит от периода) - Использует индексы по `date`, `store_id_1c`, `operation` @@ -169,7 +229,22 @@ public function getSalesShiftSum( 3. Для ночной: DateHelper::getDateTimeStartNightSmen() и EndNightShift() 4. SQL с фильтром по часам (extract(HOUR from date)) +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `in_array($shiftType, $shiftTypeValidate)` | PHP | Валидация типа смены | +| `DateHelper::getDateTimeStartSmen($dateFrom)` | `DateHelper` | Начало дневной смены (08:00) | +| `DateHelper::getDateTimeEndDaySmen($dateTo)` | `DateHelper` | Конец дневной смены (20:00) | +| `DateHelper::getDateTimeStartNightSmen($dateFrom)` | `DateHelper` | Начало ночной смены (20:00) | +| `DateHelper::getDateTimeEndNightShift($dateTo)` | `DateHelper` | Конец ночной смены (08:00 следующего дня) | +| `Sales::find()` | `Sales` | Создание ActiveQuery | +| `new \yii\db\Expression("SUM(summ - skidka)")` | `Expression` | SQL-выражение суммирования | +| `->groupBy(['store_id_1c', 'operation'])` | `ActiveQuery` | Группировка по магазину и операции | +| `->asArray()->all()` | `ActiveQuery` | Выполнение запроса | + **Пример:** + ```php // Продажи дневной смены $daySales = $service->getSalesShiftSum('2025-11-17', '2025-11-17', 'day'); @@ -210,7 +285,28 @@ public function getSalesByAdmin( - Если ночной — применяется сдвиг даты (ночь с 20:00 до 8:00 относится к предыдущему дню) - Для администраторов время не сдвигается +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `array_key_exists($adminGuid, $adminsGuids)` | PHP | Проверка существования сотрудника | +| `ArrayHelper::getValue($adminsGuids, $adminGuid)` | `ArrayHelper` | Получение ID сотрудника по GUID | +| `Admin::findOne($adminId)` | `Admin` | Поиск сотрудника по ID | +| `AdminGroup::GROUP_DAY()` | `AdminGroup` | Массив ID дневных групп | +| `in_array($groupId, AdminGroup::GROUP_DAY())` | PHP | Проверка дневной смены сотрудника | +| `DateHelper::getDateTimeEndDay($dateTo, $flag, $adminId)` | `DateHelper` | Конец дня с учётом смены сотрудника | +| `DateHelper::$hourStartDayShift` | `DateHelper` | Час начала дневной смены (8) | +| `new DateTime($dateTimeEndDayPrepared)` | `DateTime` | Создание объекта даты для форматирования | +| `->format('G')` | `DateTime` | Получение часа без ведущего нуля | +| `Sales::find()` | `Sales` | Создание ActiveQuery | +| `new \yii\db\Expression("TO_CHAR(date,'YYYY-MM-DD')")` | `Expression` | SQL-форматирование даты | +| `new \yii\db\Expression("CASE WHEN ... END")` | `Expression` | SQL CASE для сдвига ночных продаж на предыдущий день | +| `DateHelper::getDateTimeStartDay($dateFrom, $flag, $adminId)` | `DateHelper` | Начало дня с учётом смены | +| `->andWhere(['seller_id' => $adminGuid])` | `ActiveQuery` | Фильтр по продавцу | +| `->groupBy(['date', 'operation'])` | `ActiveQuery` | Группировка по дате и операции | + **Пример:** + ```php $adminGuid = 'guid-сотрудника'; $sales = $service->getSalesByAdmin($adminGuid, '2025-11-01', '2025-11-17', false); @@ -261,7 +357,38 @@ public function getMatrixSalesProducts( 3. Иначе — только офлайн (order_id = '' OR order_id = '0') 4. Продукты JOIN с `sales_products` для получения суммы продаж +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Yii::$app->getDb()` | `Yii` | Получение подключения к БД | +| `$connection->createCommand($sql, $params)` | `Connection` | Создание SQL-команды с параметрами | +| `ProductsClass::find()` | `ProductsClass` | Запрос к таблице классов товаров | +| `->where(['tip' => 'matrix'])` | `ActiveQuery` | Фильтр по типу 'matrix' | +| `DateHelper::getDateTimeStartDay($dateFrom, ...)` | `DateHelper` | Начало периода с учётом смены | +| `DateHelper::getDateTimeEndDay($dateTo, ...)` | `DateHelper` | Конец периода с учётом смены | +| `$command->queryAll()` | `Command` | Выполнение SQL-запроса | +| `array_key_exists($adminGuid, $adminsGuids)` | PHP | Проверка существования сотрудника | +| `Admin::findOne($adminId)` | `Admin` | Поиск данных сотрудника | +| `AdminGroup::GROUP_DAY()` | `AdminGroup` | Получение дневных групп | + +**SQL-структура запроса:** + +```sql +SELECT SUM(sales_products.summa) as summ, sales.seller_id +FROM sales +RIGHT JOIN sales_products ON sales_products.check_id = sales.id +RIGHT JOIN products_1c ON products_1c.id = sales_products.id_1c +RIGHT JOIN products_class ON products_class.category_id = products_1c.parent_id + AND products_class.tip = 'matrix' +WHERE sales.operation = 'Продажа' + AND sales.date >= :date_from AND sales.date <= :date_to + AND sales.seller_id = :admin_guid +GROUP BY sales.seller_id +``` + **Пример:** + ```php $matrixSales = $service->getMatrixSalesProducts( 'admin-guid', @@ -308,7 +435,19 @@ public function getAuthorSalesProducts( - Товары отбираются по `products_class.tip = 'author'` - JOIN с `sales` по `seller_id` (продавец) +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `max($dateFrom, '2025-04-01')` | PHP | Корректировка даты начала действия функционала | +| `Yii::$app->getDb()` | `Yii` | Получение подключения к БД | +| `ProductsClass::find()->where(['tip' => 'author'])` | `ProductsClass` | Получение авторских классов | +| `$connection->createCommand($sql, $params)` | `Connection` | SQL-команда с prepared statements | +| `DateHelper::getDateTimeStartDay(...)` | `DateHelper` | Форматирование начала периода | +| `DateHelper::getDateTimeEndDay(...)` | `DateHelper` | Форматирование конца периода | + **Пример:** + ```php $authorSales = $service->getAuthorSalesProducts('admin-guid', '2025-04-01', '2025-11-17', false); ``` @@ -345,7 +484,29 @@ public function getAuthorMakeProducts( - `getAuthorSalesProducts` — кто продал (sales.seller_id) - `getAuthorMakeProducts` — кто изготовил (sales_products.seller_id) +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `max($dateFrom, '2025-04-01')` | PHP | Корректировка даты начала | +| `Yii::$app->getDb()` | `Yii` | Подключение к БД | +| `ProductsClass::find()->where(['tip' => 'author'])` | `ProductsClass` | Авторские классы товаров | +| `DateHelper::getDateTimeStartDay(...)` | `DateHelper` | Форматирование периода | +| `DateHelper::getDateTimeEndDay(...)` | `DateHelper` | Форматирование периода | +| `$command->queryAll()` | `Command` | Выполнение SQL | + +**SQL-отличие от getAuthorSalesProducts:** + +```sql +-- getAuthorMakeProducts: JOIN по sales_products.seller_id (кто изготовил) +RIGHT JOIN sales_products ON ... AND sales_products.seller_id = :admin_guid + +-- getAuthorSalesProducts: JOIN по sales.seller_id (кто продал) +WHERE sales.seller_id = :admin_guid +``` + **Пример:** + ```php // Флорист создал букеты $madeProducts = $service->getAuthorMakeProducts('florist-guid', '2025-04-01', '2025-11-17', false); @@ -390,7 +551,37 @@ public function getSalesCountSum( ] ``` +**Вызовы сторонних методов:** + +| Метод | Класс | Описание | +|-------|-------|----------| +| `Yii::$app->getDb()` | `Yii` | Получение подключения к PostgreSQL | +| `$connection->createCommand($sql, $params)` | `Connection` | Создание SQL-команды | +| `DateHelper::getDateTimeStartDay($dateFrom, true)` | `DateHelper` | Начало дня для параметра :date_from | +| `DateHelper::getDateTimeEndDay($dateTo, true)` | `DateHelper` | Конец дня для параметра :date_to | +| `Sales::OPERATION_SALE` | `Sales` | Константа 'Продажа' | +| `Sales::OPERATION_RETURN` | `Sales` | Константа 'Возврат' | +| `$command->queryAll()` | `Command` | Выполнение запроса и получение результатов | + +**SQL-запрос:** + +```sql +SELECT + count(*) as cnt, + sum(case when phone is distinct from NULL THEN 1 ELSE 0 END) as bonus_clients_cnt, + sum(summ-skidka) as summ, + store_id, + to_char(date,'YYYY-MM-DD') as date_t +FROM sales +WHERE date >= :date_from AND date <= :date_to + AND operation = :operation + AND (order_id = '' OR order_id = '0') +GROUP BY date_t, store_id +ORDER BY date_t DESC, cnt DESC +``` + **Пример:** + ```php // Продажи за месяц $stats = $service->getSalesCountSum('2025-11-01', '2025-11-30', Sales::OPERATION_SALE); diff --git a/erp24/docs/services/TelegramService.md b/erp24/docs/services/TelegramService.md index 74cf5212..1931ad95 100644 --- a/erp24/docs/services/TelegramService.md +++ b/erp24/docs/services/TelegramService.md @@ -4,113 +4,775 @@ Сервис для интеграции с Telegram Bot API. Обеспечивает отправку сообщений через Telegram-ботов, рассылку уведомлений, промо-акций, статистики и управление inline-кнопками для интерактивного взаимодействия с пользователями. +## Пространство имён + +```php +namespace yii_app\services; +``` + ## Расположение + - **Файл:** `/erp24/services/TelegramService.php` - **Размер:** 441 LOC - **Приоритет:** P1 (высокий) - **Интеграция:** Telegram Bot API, EDNA (WhatsApp) -## Ключевые функции +## Зависимости + +| Компонент | Тип | Описание | +|-----------|-----|----------| +| `GuzzleHttp\Client` | Library | HTTP-клиент для API запросов | +| `Yii` | Framework | Логирование и доступ к приложению | +| `yii\helpers\Json` | Helper | Кодирование/декодирование JSON | +| `UsersTelegramMessage` | Model | Сохранение истории сообщений | + +## Константы + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TELEGRAM_BOT_DEV` | `8063257458:AAG...` | Токен бота для разработки | +| `TELEGRAM_BOT_PROD` | `5456741805:AAG...` | Токен бота для production | +| `TARGET_PROD_URL` | `erp.erp-flowers.ru` | URL production окружения | +| `CHAT_CHANNEL_ID` | `-1001861631125` | ID канала для dev-уведомлений | +| `CHAT_CHANNEL_ERP_ID` | `-1002338329426` | ID канала для prod-уведомлений | +| `CHATBOT_SALT` | `ASIUdgb762g...` | Соль для генерации hash авторизации | + +## Методы + +### sendMessage() + +**Назначение:** Отправка сообщения через внешний API2 с поддержкой inline-кнопок. + +**Сигнатура:** + +```php +public static function sendMessage( + $admin_id, // ID администратора (сотрудника) + $message, // Текст сообщения + $reply_markup = null // Разметка кнопок (опционально) +): \Psr\Http\Message\ResponseInterface +``` + +**Углублённое описание логики:** + +1. Формирует URL запроса к `api2.bazacvetov24.ru/telegram/send-message` +2. Добавляет параметры `admin_id` и `message` в query string +3. Если передан `$reply_markup` — добавляет JSON-кодированную разметку кнопок +4. Выполняет GET-запрос через GuzzleHttp\Client +5. Возвращает объект Response + +**Вызовы сторонних методов:** + +- `new \GuzzleHttp\Client()` — создание HTTP-клиента +- `$client->request('GET', $url)` — выполнение GET-запроса +- `json_encode($reply_markup, JSON_UNESCAPED_UNICODE)` — кодирование разметки кнопок в JSON с сохранением Unicode + +**Пример:** + +```php +// Простое сообщение +TelegramService::sendMessage(123, 'Привет!'); + +// Сообщение с кнопками +$buttons = [ + [['text' => 'Кнопка 1', 'callback_data' => 'btn1']] +]; +TelegramService::sendMessage(123, 'Выберите:', $buttons); +``` + +--- + +### isDevelopmentEnvironment() + +**Назначение:** Определение текущего окружения (dev/prod) по URL хоста. + +**Сигнатура:** + +```php +public static function isDevelopmentEnvironment( + $urlString = null // URL для проверки (опционально) +): bool +``` + +**Углублённое описание логики:** + +1. Получает текущий URL хоста через `Yii::$app->request->getHostInfo()` +2. При ошибке (например, в консоли) устанавливает `$ip = 'console'` +3. Если передан `$urlString` — использует его, иначе — полученный `$ip` +4. Проверяет, содержит ли URL строку `TARGET_PROD_URL` (erp.erp-flowers.ru) +5. Возвращает `true` если это НЕ production + +**Вызовы сторонних методов:** + +- `Yii::$app->request->getHostInfo()` — получение URL текущего хоста +- `str_contains($currentUrl, self::TARGET_PROD_URL)` — проверка вхождения подстроки (PHP 8.0+) + +**Пример:** + +```php +if (TelegramService::isDevelopmentEnvironment()) { + echo "Это dev окружение"; +} + +// Проверка конкретного URL +$isDev = TelegramService::isDevelopmentEnvironment('https://dev.example.com'); +``` + +--- + +### isDevEnv() + +**Назначение:** Определение dev-окружения через переменную окружения `APP_ENV`. + +**Сигнатура:** + +```php +public static function isDevEnv(): bool +``` + +**Углублённое описание логики:** + +1. Проверяет значение `getenv('APP_ENV')` на равенство `'development'` +2. Альтернативно проверяет `$_ENV['APP_ENV']` (fallback на `'development'` если не установлено) +3. Возвращает `true` если любое из условий выполнено + +**Вызовы сторонних методов:** + +- `getenv('APP_ENV')` — получение переменной окружения +- `$_ENV['APP_ENV'] ?? 'development'` — null coalescing для fallback значения + +**Пример:** + +```php +$botToken = TelegramService::isDevEnv() + ? TelegramService::TELEGRAM_BOT_DEV + : TelegramService::TELEGRAM_BOT_PROD; +``` + +--- + +### sendErrorToTelegramMessage() + +**Назначение:** Отправка сообщения об ошибке в Telegram-канал (dev или prod). + +**Сигнатура:** + +```php +public static function sendErrorToTelegramMessage( + $message, // Текст ошибки (в MarkdownV2) + $disableNotification, // Отключить звук уведомления (bool) + $isDev // Отправлять в dev-канал (bool) +): void +``` + +**Углублённое описание логики:** + +1. Использует токен `TELEGRAM_BOT_DEV` для авторизации +2. Определяет целевой канал: + - `$isDev = true` → `CHAT_CHANNEL_ID` (dev) + - `$isDev = false` → `CHAT_CHANNEL_ERP_ID` (prod) +3. Формирует URL: `https://api.telegram.org/bot{token}/sendMessage` +4. Отправляет POST-запрос через GuzzleHttp с параметрами: + - `chat_id` — ID канала + - `text` — текст сообщения + - `parse_mode` — `'MarkdownV2'` + - `disable_notification` — флаг тихого уведомления +5. При ошибке логирует через `Yii::error()` в категорию `'telegram'` + +**Вызовы сторонних методов:** + +- `new Client()` — создание GuzzleHttp клиента +- `$client->post($apiURL, ['json' => [...]])` — POST-запрос с JSON body +- `Yii::error($message, 'telegram')` — логирование ошибки + +**Пример:** + +```php +try { + // код... +} catch (\Exception $e) { + $errorMsg = "*Ошибка:* `" . $e->getMessage() . "`"; + TelegramService::sendErrorToTelegramMessage( + $errorMsg, + false, // со звуком + true // в dev канал + ); +} +``` + +--- + +### sendTargetStatToTelegramMessage() + +**Назначение:** Отправка статистики целей руководителям (Алексею и Владимиру). + +**Сигнатура:** + +```php +public static function sendTargetStatToTelegramMessage( + $message // Текст статистики +): void +``` + +**Углублённое описание логики:** + +1. Использует токен `TELEGRAM_BOT_DEV` +2. Определяет список получателей: `['337084327', '730432579']` (Алексей и Владимир) +3. Экранирует текст через `escapeMarkdown($message)` +4. Для каждого получателя: + - Генерирует кнопки через `getTgButtons($chatId)` + - Отправляет POST-запрос с: + - `chat_id` — ID получателя + - `text` — экранированный текст + - `parse_mode` — `'MarkdownV2'` + - `reply_markup` — JSON с inline-кнопками +5. При ошибке логирует через `Yii::error()` + +**Вызовы сторонних методов:** + +- `self::escapeMarkdown($message)` — экранирование спецсимволов MarkdownV2 +- `self::getTgButtons($chatId)` — генерация массива inline-кнопок +- `Json::encode(['inline_keyboard' => $buttons])` — кодирование разметки +- `new Client()` — создание HTTP клиента +- `$client->post($apiURL, ['json' => [...]])` — отправка запроса +- `Yii::error()` — логирование ошибок + +**Поток данных:** + +```text +$message + ↓ +escapeMarkdown() → экранированный текст + ↓ +foreach ($chats as $chatId) + ↓ +getTgButtons($chatId) → массив кнопок + ↓ +POST api.telegram.org/bot.../sendMessage + ↓ +Telegram отправляет сообщение получателю +``` + +--- + +### sendPromoMessageToTelegramDocument() + +**Назначение:** Отправка промо-рассылки с 3 изображениями через cURL. + +**Сигнатура:** + +```php +public static function sendPromoMessageToTelegramDocument( + $chatId // ID чата получателя +): string // 'OK' или 'false' +``` + +**Углублённое описание логики:** + +1. Выбирает токен бота в зависимости от окружения (`isDevEnv()`) +2. Формирует URL для MediaGroup и SendMessage +3. Создаёт файлы изображений через `curl_file_create()`: + - `pic_1.jpg`, `pic_2.jpg`, `pic_3.jpg` из `/web/images/` +4. **Первый запрос (MediaGroup):** + - Отправляет 3 фото через `sendMediaGroup` API + - Первое фото содержит caption с текстом акции +5. **Второй запрос (Кнопки):** + - Отправляет сообщение "Выберите действие:" с сокращённым меню + - Использует `getTgShortButtons()` для генерации кнопок +6. Возвращает `'OK'` при успехе + +**Вызовы сторонних методов:** + +- `self::isDevEnv()` — проверка окружения +- `curl_file_create($path, $mimeType, $filename)` — создание файла для загрузки +- `self::getTgShortButtons($chatId)` — генерация сокращённого меню +- `json_encode($media)` — кодирование массива медиа +- `curl_init()`, `curl_setopt()`, `curl_exec()`, `curl_close()` — работа с cURL +- `Json::encode(['inline_keyboard' => $buttons])` — кодирование кнопок +- `Yii::error()` — логирование ошибок + +**Структура MediaGroup:** + +```php +[ + ['type' => 'photo', 'media' => 'attach://pic_1.jpg', 'caption' => $text], + ['type' => 'photo', 'media' => 'attach://pic_2.jpg'], + ['type' => 'photo', 'media' => 'attach://pic_3.jpg'], +] +``` + +--- + +### sendPromo2MessageToTelegramDocument() + +**Назначение:** Отправка промо-рассылки с 3 изображениями через GuzzleHttp (альтернативная реализация). + +**Сигнатура:** + +```php +public static function sendPromo2MessageToTelegramDocument( + $chatId // ID чата получателя +): string // HTTP reason phrase, код ошибки или текст ошибки +``` + +**Углублённое описание логики:** + +1. Выбирает токен бота через `isDevEnv()` +2. Формирует пути к изображениям (без `curl_file_create`) +3. **Первый запрос (MediaGroup через GuzzleHttp):** + - Использует `multipart` формат для загрузки файлов + - Открывает файлы через `fopen($path, 'r')` + - Проверяет статус код ответа (200 = успех) +4. **Второй запрос (Кнопки через JSON):** + - Отправляет JSON с текстом и кнопками + - Возвращает `$response->getReasonPhrase()` при успехе +5. При ошибках возвращает код статуса или текст исключения + +**Вызовы сторонних методов:** + +- `self::isDevEnv()` — проверка окружения +- `self::getTgShortButtons($chatId)` — генерация кнопок +- `new Client()` — создание GuzzleHttp клиента +- `fopen($path, 'r')` — открытие файла для чтения +- `$client->post($url, ['multipart' => [...]])` — multipart запрос +- `$client->post($url, ['json' => [...]])` — JSON запрос +- `$response->getStatusCode()` — получение HTTP статуса +- `$response->getReasonPhrase()` — получение текста статуса (OK, Created и т.д.) +- `Yii::error()` — логирование + +**Отличия от sendPromoMessageToTelegramDocument():** -**1. Отправка сообщений:** -- Текстовые сообщения в чаты -- Уведомления об ошибках в канал dev/prod -- Промо-рассылки с 3 изображениями -- Inline-кнопки с WebApp и URL +| Аспект | sendPromoMessage... | sendPromo2Message... | +|--------|---------------------|----------------------| +| HTTP клиент | cURL | GuzzleHttp | +| Загрузка файлов | curl_file_create | fopen + multipart | +| Возврат | 'OK' / 'false' | ReasonPhrase / код / ошибка | +| Обработка ошибок | Базовая | Детальная с кодами | -**2. Определение окружения:** -- Dev/Prod по URL и ENV -- Автоматический выбор токена и канала +--- -**3. Генерация UI:** -- Полное меню (4 кнопки) -- Сокращенное меню для промо (2 кнопки) -- WebApp интеграция с hash-авторизацией +### sendMessageToTelegramClient() -**4. Форматирование:** -- Экранирование MarkdownV2 для файлов -- Экранирование для логов в БД +**Назначение:** Отправка сообщения клиенту с полным меню кнопок. -## Основные методы (9 методов) +**Сигнатура:** -### Отправка сообщений -1. `sendMessage()` — Через API2 с inline-кнопками -2. `sendMessageToTelegramClient()` — С полным меню -3. `sendErrorToTelegramMessage()` — Ошибка в канал -4. `sendTargetStatToTelegramMessage()` — Статистика руководителям +```php +public static function sendMessageToTelegramClient( + $chatId, // ID чата клиента + $message, // Текст сообщения + $isDev = true // Использовать dev-бота (по умолчанию) +): string|int // ReasonPhrase при успехе, код ошибки или текст исключения +``` -### Промо-рассылки -5. `sendPromoMessageToTelegramDocument()` — 3 фото (cURL) -6. `sendPromo2MessageToTelegramDocument()` — 3 фото (GuzzleHttp) +**Углублённое описание логики:** -### Генерация UI -7. `getTgButtons()` — Полное меню (4 кнопки) -8. `getTgShortButtons()` — Сокращенное меню (2 кнопки) +1. Выбирает токен бота: `TELEGRAM_BOT_DEV` если `$isDev`, иначе `TELEGRAM_BOT_PROD` +2. Экранирует текст через `escapeMarkdown($message)` +3. Генерирует полное меню через `getTgButtons($chatId)` +4. Отправляет POST-запрос с: + - `chat_id` — ID получателя + - `text` — экранированный текст + - `parse_mode` — `'MarkdownV2'` + - `reply_markup` — JSON с 4 кнопками +5. Проверяет статус ответа: + - 200 → возвращает `getReasonPhrase()` ('OK') + - Иначе → логирует и возвращает код +6. При исключении → логирует и возвращает текст ошибки -### Форматирование текста -9. `escapeMarkdown()` — Для текстов из файлов -10. `escapeMarkdownLog()` — Для логов из БД +**Вызовы сторонних методов:** -### Вспомогательные -11. `getHashTG()` — Hash для WebApp авторизации -12. `saveSentMessageToDB()` — Сохранение в логи -13. `getDateTwoWeekStartEnd()` (InfoTableService) — Даты недель -14. `isDevelopmentEnvironment()` — Проверка dev -15. `isDevEnv()` — Проверка через ENV +- `self::escapeMarkdown($message)` — экранирование спецсимволов +- `self::getTgButtons($chatId)` — генерация полного меню (4 кнопки) +- `new Client()` — создание HTTP клиента +- `$client->post($apiURL, ['json' => [...]])` — отправка запроса +- `Json::encode(['inline_keyboard' => $buttons])` — кодирование кнопок +- `$response->getStatusCode()` — проверка статуса +- `$response->getReasonPhrase()` — получение текста статуса +- `Yii::error()` — логирование ошибок -## Боты (константы) +**Пример:** -- **TELEGRAM_BOT_DEV** — для разработки -- **TELEGRAM_BOT_PROD** — для production -- **Каналы:** dev и prod для уведомлений +```php +// Dev окружение +$result = TelegramService::sendMessageToTelegramClient( + '123456789', + 'Ваш заказ готов!' +); -## Кнопки +// Production +$result = TelegramService::sendMessageToTelegramClient( + '123456789', + 'Ваш заказ готов!', + false // prod-бот +); +``` -**Полное меню (getTgButtons):** -1. Адреса магазинов (WebApp) -2. Заказ на сайте (URL) -3. Забрать 1800 руб (WebApp + промо) -4. Списание бонусов (WebApp) +--- -**Сокращенное меню (getTgShortButtons):** -1. Адреса магазинов (WebApp) -2. Заказ на сайте (URL) +### getHashTG() -## WebApp авторизация +**Назначение:** Генерация hash для авторизации в WebApp. -- Hash генерируется через `getHashTG(chat_id)` -- Base64-кодирование: `sha1 + "#" + JSON` -- Используется в URL с параметром `hash=...` +**Сигнатура:** + +```php +public static function getHashTG( + $chatId // ID чата пользователя +): string // Base64-кодированный hash +``` + +**Углублённое описание логики:** + +1. Формирует JSON-структуру: + + ```json + {"platform": "telegram", "user_id": "chatId"} + ``` + +2. Генерирует SHA1 hash от `CHATBOT_SALT + ";" + $data` +3. Конкатенирует: `sha1 + "#" + data` +4. Кодирует результат в Base64 +5. Используется в URL WebApp для безопасной авторизации + +**Вызовы сторонних методов:** + +- `Json::encode([...])` — кодирование данных в JSON +- `sha1($string)` — SHA1 хеширование +- `base64_encode($string)` — Base64 кодирование + +**Формат результата:** + +```text +Base64(SHA1(SALT + ";" + JSON) + "#" + JSON) +``` + +**Пример:** + +```php +$hash = TelegramService::getHashTG('123456789'); +// Результат: 'YTFiMmMzZDRlNWY2...#eyJwbGF0Zm9ybSI6InRlbGVncmFtIi...' + +// Использование в URL +$url = "https://chatbot.bazacvetov24.ru/bot/telegram?hash=$hash"; +``` + +--- + +### getTgButtons() + +**Назначение:** Генерация полного меню inline-кнопок (4 кнопки). + +**Сигнатура:** + +```php +public static function getTgButtons( + $chatId // ID чата для генерации hash +): array // Массив кнопок для inline_keyboard +``` + +**Углублённое описание логики:** + +1. Генерирует hash авторизации через `getHashTG($chatId)` +2. Формирует URL для WebApp: + - `$linkAppStore` — адреса магазинов (`?hash=...&start=store`) + - `$linkAppEvents` — промо-акция (`?hash=...&start=promo.1000`) + - `$linkAppBalance` — баланс бонусов (`?hash=...`) +3. Возвращает массив 2x2 кнопок: + - Строка 1: "Адреса магазинов" (WebApp), "Заказ на сайте" (URL) + - Строка 2: "Забрать 1800 руб" (WebApp), "Списание бонусов" (WebApp) + +**Вызовы сторонних методов:** + +- `self::getHashTG($chatId)` — генерация hash авторизации + +**Структура возвращаемого массива:** + +```php +[ + [ + ['text' => "Адреса магазинов", "web_app" => ['url' => $linkAppStore]], + ['text' => "Заказ на сайте", "url" => $siteBc24Url], + ], + [ + ['text' => "Забрать 1800 руб", "web_app" => ['url' => $linkAppEvents]], + ['text' => 'Списание бонусов', "web_app" => ['url' => $linkAppBalance]] + ] +] +``` + +--- + +### getTgShortButtons() + +**Назначение:** Генерация сокращённого меню inline-кнопок (2 кнопки). + +**Сигнатура:** + +```php +public static function getTgShortButtons( + $chatId // ID чата для генерации hash +): array // Массив кнопок для inline_keyboard +``` + +**Углублённое описание логики:** + +1. Генерирует hash через `getHashTG($chatId)` +2. Формирует URL (аналогично `getTgButtons`) +3. Возвращает массив с 1 строкой и 2 кнопками: + - "Адреса магазинов" (WebApp) + - "Заказ на сайте" (URL) + +**Вызовы сторонних методов:** + +- `self::getHashTG($chatId)` — генерация hash + +**Структура:** + +```php +[ + [ + ['text' => "Адреса магазинов", "web_app" => ['url' => $linkAppStore]], + ['text' => "Заказ на сайте", "url" => $siteBc24Url], + ] +] +``` + +**Отличия от getTgButtons():** + +- Только 1 строка вместо 2 +- Нет кнопок "Забрать 1800 руб" и "Списание бонусов" +- Используется для промо-рассылок (меньше отвлекающих кнопок) + +--- + +### escapeMarkdown() + +**Назначение:** Экранирование спецсимволов MarkdownV2 для текстов из файлов. + +**Сигнатура:** + +```php +public static function escapeMarkdown( + $text // Исходный текст +): string // Экранированный текст +``` + +**Углублённое описание логики:** + +1. Определяет список спецсимволов MarkdownV2: + `_ * [ ] ( ) ~ \` > # + - = | { } . !` +2. Для каждого символа применяет regex с callback: + - Паттерн: `/(?save()` для сохранения в БД +4. Возвращает результат сохранения (bool) + +**Вызовы сторонних методов:** + +- `new UsersTelegramMessage()` — создание модели +- `$userMessage->save()` — сохранение ActiveRecord в БД + +**Структура $messageData:** + +```php +[ + 'chat_id' => '123456789', + 'phone' => '79001234567', + 'message' => 'Текст сообщения', + 'kogort_date' => '2024-01-15', + 'target_date' => '2024-01-20', + 'type' => 1, // TYPE_FIRST_MESSAGE +] +``` + +**Пример:** + +```php +$saved = TelegramService::saveSentMessageToDB([ + 'chat_id' => $chatId, + 'phone' => $user->phone, + 'message' => $text, + 'kogort_date' => date('Y-m-d'), + 'target_date' => date('Y-m-d', strtotime('+7 days')), + 'type' => UsersTelegramMessage::TYPE_FIRST_MESSAGE, +]); +``` + +## Диаграмма архитектуры + +```mermaid +graph TB + subgraph "TelegramService" + SM[sendMessage] + SMTC[sendMessageToTelegramClient] + SETM[sendErrorToTelegramMessage] + STSTM[sendTargetStatToTelegramMessage] + SPMTD[sendPromoMessageToTelegramDocument] + SP2MTD[sendPromo2MessageToTelegramDocument] + end + + subgraph "Вспомогательные" + IDE[isDevelopmentEnvironment] + ISDE[isDevEnv] + GTB[getTgButtons] + GTSB[getTgShortButtons] + GHT[getHashTG] + EM[escapeMarkdown] + EML[escapeMarkdownLog] + SSMTD[saveSentMessageToDB] + end + + subgraph "Внешние сервисы" + API2[api2.bazacvetov24.ru] + TGAPI[api.telegram.org] + end + + subgraph "БД" + UTM[(users_telegram_message)] + end + + SM --> API2 + SMTC --> TGAPI + SETM --> TGAPI + STSTM --> TGAPI + SPMTD --> TGAPI + SP2MTD --> TGAPI + + SMTC --> GTB + STSTM --> GTB + SPMTD --> GTSB + SP2MTD --> GTSB + + GTB --> GHT + GTSB --> GHT + + SMTC --> EM + STSTM --> EM + + SPMTD --> ISDE + SP2MTD --> ISDE + + SSMTD --> UTM +``` ## Таблица БД -**users_telegram_message:** -- Логирование всех отправленных сообщений -- Поля: chat_id, phone, message, kogort_date, target_date, type, status +**users_telegram_message** — логирование отправленных сообщений: + +| Поле | Тип | Описание | +|------|-----|----------| +| `id` | int | PK | +| `chat_id` | varchar | ID чата Telegram | +| `phone` | varchar | Телефон клиента | +| `message` | text | Текст сообщения | +| `kogort_date` | date | Дата когорты клиента | +| `target_date` | date | Целевая дата рассылки | +| `type` | int | Тип рассылки (1 или 2) | +| `status` | int | Статус отправки | +| `created_at` | timestamp | Дата создания | ## Типы рассылок -- `TYPE_FIRST_MESSAGE = 1` — первая рассылка -- `TYPE_SECOND_MESSAGE = 2` — вторая рассылка +- `TYPE_FIRST_MESSAGE = 1` — первая рассылка (приветственная) +- `TYPE_SECOND_MESSAGE = 2` — вторая рассылка (напоминание) ## Лимиты Telegram Bot API -- Сообщений в секунду: 30 -- Максимум текста: 4096 символов -- Файлов в MediaGroup: 10 -- Кнопок в строке: 8 +| Параметр | Лимит | +|----------|-------| +| Сообщений в секунду | 30 | +| Максимум текста | 4096 символов | +| Файлов в MediaGroup | 10 | +| Кнопок в строке | 8 | +| Общий размер reply_markup | 64 KB | ## Интеграция -- **Telegram Bot API** — основной канал +- **Telegram Bot API** — основной канал коммуникации +- **API2 (bazacvetov24.ru)** — внутренний API для рассылок +- **WebApp** — интерактивный интерфейс в Telegram +- **Chatbot (chatbot.bazacvetov24.ru)** — ЛК клиента в браузере - **EDNA.ru** — дополнительный канал (WhatsApp) -- **WebApp** — интерактивный интерфейс в ТГ -- **Chatbot** — ЛК клиента в браузере -## Статус +## Связанные компоненты -**Размер документации:** ~2,600 строк -**Примеры:** 6+ -**Команды бота:** 4+ -**Диаграммы:** последовательность, архитектура, потоки -**Готовность:** 100% ✅ +| Компонент | Тип | Описание | +|-----------|-----|----------| +| [UsersTelegramMessage](../models/UsersTelegramMessage.md) | Model | Логирование сообщений | +| [Users](../models/Users.md) | Model | Данные клиентов | +| [InfoTableService](./InfoTableService.md) | Service | Статистика по датам | +| [Api2TelegramController](../api/api2/TelegramController.md) | Controller | API endpoint для отправки |