]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
fix(task_22): расширить окно выборки админов с 1 до 4 месяцев origin/feature_filippov_task22_widen_sync_window_to_4months
authorAleksey Filippov <Aleksey.Filippov@erp-flowers.ru>
Wed, 29 Apr 2026 20:29:06 +0000 (23:29 +0300)
committerAleksey Filippov <Aleksey.Filippov@erp-flowers.ru>
Wed, 29 Apr 2026 20:29:06 +0000 (23:29 +0300)
Сотрудники с пустым guid выпадали из синхронизации с 1С после месяца —
расширяем окно date_add до 4 месяцев. Добавлена документация по каналу
ERP→1С create_employee (pull через API2 DataController::actionRequest).

erp24/docs/integrations/1c/employee-sync.md [new file with mode: 0644]
erp24/scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php

diff --git a/erp24/docs/integrations/1c/employee-sync.md b/erp24/docs/integrations/1c/employee-sync.md
new file mode 100644 (file)
index 0000000..559dff0
--- /dev/null
@@ -0,0 +1,200 @@
+# Интеграция ERP → 1С: создание сотрудников
+
+**Назначение.** Канал передачи новых сотрудников (кассиров) из ERP в 1С. Работает по **pull-модели**: ERP накапливает кандидатов в БД через шедулер, 1С опрашивает API2 и забирает их.
+
+---
+
+## 1. Цепочка вызовов
+
+```
+[cron]
+  └─ erp24/scripts/scheduler.php  (раз в минуту)
+       └─ glob(scripts/tasks/*.php) → exec(php scheduler.php $time {file})
+            └─ task_22_create_employee_for_1c_with_admins_with_empty_guid.php
+                 ├─ выбор Admin без guid
+                 ├─ создание EmployeeOnShift (status_source = NOT_CREATED_IN_1C)
+                 ├─ generation guid через DataHelper::createGuidMy("06")
+                 ├─ Admin.guid := новый guid
+                 └─ запись в ExportImportTable (entity='admin', export_id=1)
+
+[1С опрашивает]
+  POST /api2/data/request
+    └─ DataController::actionRequest
+         └─ getCreateEmployee()
+              └─ EmployeeOnShift WHERE status_source=NOT_CREATED_IN_1C, status=ACCEPT, active=ON
+                   → $mess['create_employee'] = [...]
+```
+
+---
+
+## 2. Условия выборки кандидатов
+
+[task_22_create_employee_for_1c_with_admins_with_empty_guid.php:72-82](../../../scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php#L72)
+
+```php
+$compareDate = date('Y-m-d H:i:s', strtotime("-1 month", time()));
+
+$entityCityStore = ExportImportService::getEntityByType();
+$exportCityStore = ArrayHelper::map($entityCityStore, 'entity_id', 'export_val');
+
+$admins = Admin::find()
+    ->select(['id', 'guid', 'name', 'store_id', 'mobile as phone'])
+    ->where(['and', ['guid' => ''], ['!=', 'mobile', '']])
+    ->andWhere(['in', 'group_id', AdminGroup::getGroupsForEmployeeOnCashbox()])
+    ->andWhere(['>=', 'date_add', $compareDate])
+    ->all();
+```
+
+### Что фильтруется
+
+| Условие | Смысл |
+|---|---|
+| `guid = ''` | Сотрудник ещё не выгружался в 1С (пустой GUID — маркер «новый») |
+| `mobile != ''` | Без телефона в 1С создавать нельзя (это бизнес-ключ) |
+| `group_id IN getGroupsForEmployeeOnCashbox()` | Только кассиры — другие группы (админы, бухгалтеры, ИТ) не отправляются |
+| `date_add >= now() - 4 month` | Скользящее окно: учитываются только сотрудники, добавленные за последние 4 месяца |
+
+### Маппинг магазинов
+
+`ExportImportService::getEntityByType()` (без аргумента — дефолт = `city_store`) даёт массив соответствий «store_id ERP → guid магазина в 1С». Через `ArrayHelper::map()` строится lookup `entity_id → export_val`, который позже подставляется в `EmployeeOnShift.store_id` ([task_22:96-98](../../../scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php#L96)). Если магазин не найден в маппинге — пишется заглушка `'--'`.
+
+---
+
+## 3. Выводы по логике выборки
+
+### 3.1. Окно «–4 месяца» — ловушка остаётся, но шире
+
+Сотрудник с пустым `guid`, добавленный **более 4 месяцев назад**, в 1С **никогда не попадёт** — он навсегда выпадает из выборки. Окно расширено с 1 до 4 месяцев, чтобы покрыть типовые провалы шедулера и поздно валидируемые телефоны, но проблема «потерянных» в принципе сохраняется.
+
+Возможные причины «зависания» сотрудника:
+
+- Сотрудника зарегистрировали, но `mobile` стал валидным позже (например, исправили опечатку — но `date_add` старая и за пределы окна уехала).
+- Шедулер был выключен / упал на несколько месяцев.
+- В прошлом запуске возникла ошибка валидации `EmployeeOnShift` — запись в `Admin.guid` не сохранилась.
+
+**Рекомендация.** Помимо расширенного окна, завести разовую console-команду для добивки «потерянных»: тот же запрос, но без `date_add >= compareDate`, с явным флагом `--dry-run`.
+
+### 3.2. `guid = ''` vs `guid IS NULL`
+
+Используется строгое сравнение со строкой. По схеме [Admin](../../../records/Admin.php) поле `guid` — `string`, валидация `max:36`, default — пустая строка ([Admin.php:291](../../../records/Admin.php#L291): `$admin->guid = ''` в фабричном методе). Если в БД появятся `NULL`-значения (через прямой SQL/миграцию) — они **не попадут в выборку**. На текущей схеме безопасно, но фрагилно.
+
+### 3.3. `mobile != ''` не отсекает `NULL`
+
+В Yii2 Query Builder `['!=', 'mobile', '']` транслируется в SQL `mobile != ''`, и `NULL != ''` даёт `UNKNOWN` (т.е. строка отбрасывается). На практике поле `mobile` валидируется как `required` ([Admin.php:114](../../../records/Admin.php#L114)) и проходит `PhoneValidator`, поэтому `NULL` не должен встречаться. Но если запись попадёт через миграцию или `save(false)` — она будет молча пропущена.
+
+### 3.4. Группы кассиров — единственный источник
+
+Метод `AdminGroup::getGroupsForEmployeeOnCashbox()` определяет, кого вообще считать «сотрудником, которого нужно завести в 1С». Любая новая группа кассиров требует **обязательного** добавления в этот метод — иначе её сотрудники в 1С не появятся.
+
+### 3.5. Производительность
+
+- Запрос один, без N+1.
+- `select` ограничен 5 полями.
+- Скользящее окно в месяц + фильтр по `guid=''` отсекают подавляющее большинство записей.
+- На таблице `admin` за месяц новых сотрудников — десятки/сотни. Безопасно.
+- **Индексы**: для эффективности нужен составной индекс `(guid, date_add)` или `(group_id, date_add) WHERE guid = ''`. Проверить в [database/SCHEMA.md](../../database/SCHEMA.md).
+
+### 3.6. Идемпотентность
+
+После успешной обработки `Admin.guid` заполняется новым GUID ([task_22:121-122](../../../scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php#L121)). Повторный запуск той же задачи — no-op для уже обработанных Admin (не пройдут условие `guid = ''`). **Безопасно запускать руками много раз.**
+
+### 3.7. Конфликт с `EmployeeOnShift`
+
+[task_22:85](../../../scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php#L85) дополнительно отсекает админов, чей `phone` уже присутствует в активных `EmployeeOnShift`. Логика: если сотрудник уже в смене — не создавать ему дубль. Однако фильтрация **в PHP**, а не в SQL — вся выборка `Admin` грузится в память. На текущих объёмах не страшно, но на росте таблицы стоит переписать через `NOT EXISTS`.
+
+---
+
+## 4. Структура payload в 1С
+
+[DataController::getCreateEmployee():775-816](../../../api2/controllers/DataController.php#L775)
+
+```json
+{
+  "create_employee": [
+    {
+      "id": "06...guid...",
+      "admin_id": 12345,
+      "store_id": "<guid магазина в 1С>",
+      "first_name": "Имя",
+      "last_name": "",
+      "phone": "+79991234567",
+      "comment": "Добавлен через чат-бот сотрудником <создатель> <дата>",
+      "sex": "f",
+      "bday": "16.08.2000"
+    }
+  ]
+}
+```
+
+### Известные технические долги
+
+| Проблема | Где | Что сделать |
+|---|---|---|
+| `sex` захардкожено `'f'` | [DataController.php:811](../../../api2/controllers/DataController.php#L811) | Брать из `Admin.pol` |
+| `bday` захардкожено `'16.08.2000'` | [DataController.php:812](../../../api2/controllers/DataController.php#L812) | Брать из `Admin.birthdate` |
+| `last_name` всегда пустое | [task_22:102](../../../scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php#L102) | Парсить из `Admin.name` или из отдельного поля |
+| `$force = true` в задаче | [task_22:38](../../../scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php#L38) | Убрать после стабилизации, оставить только модуль 5 минут |
+
+---
+
+## 5. Запуск задачи вручную
+
+### CLI на dev (Docker)
+```bash
+docker exec yii_erp24-php-yii_erp24-1 bash -c \
+  "cd /www && php erp24/scripts/scheduler.php $(date +%s) \
+   /www/erp24/scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php"
+```
+
+### CLI на prod
+```bash
+cd /var/www/erp24_deploy && php erp24/scripts/scheduler.php $(date +%s) \
+  /var/www/erp24_deploy/erp24/scripts/tasks/task_22_create_employee_for_1c_with_admins_with_empty_guid.php
+```
+
+### Через веб-UI
+`/scheduler_task/test` — поле `task_num` = `task_22_create_employee_for_1c_with_admins_with_empty_guid` (полное имя файла без `.php`). Реализация: [SchedulerTaskController::actionRunTask:132-159](../../../controllers/SchedulerTaskController.php#L132).
+
+---
+
+## 6. Проверка результата
+
+```sql
+-- Лог шедулера
+SELECT task_num, date_start, date_stop, error, log
+FROM erp24.scheduler_task_log
+WHERE task_num = 22
+ORDER BY id DESC LIMIT 3;
+
+-- Новые сотрудники, ждущие выгрузки в 1С
+SELECT id, guid, phone, store_id, status_source, created_at
+FROM erp24.employee_on_shift
+WHERE status_source = 0  -- STATUS_SOURCE_NOT_CREATED_IN_1C
+ORDER BY created_at DESC LIMIT 20;
+
+-- Проверка outbox
+SELECT * FROM erp24.export_import_table
+WHERE entity = 'admin' AND export_id = 1
+ORDER BY id DESC LIMIT 20;
+
+-- Сотрудники, которым GUID был проставлен
+SELECT id, name, mobile, guid, date_add
+FROM erp24.admin
+WHERE guid != '' AND date_add >= NOW() - INTERVAL '1 day'
+ORDER BY date_add DESC;
+```
+
+---
+
+## 7. Связанная документация
+
+- [database/SCHEMA.md](../../database/SCHEMA.md) — схема таблиц `admin`, `employee_on_shift`, `export_import_table`
+- [optimization/1c-sync-optimization.md](../../optimization/1c-sync-optimization.md) — оптимизации входящих потоков из 1С
+- [services/UploadService.md](../../services/UploadService.md) — обработка ВХОДЯЩИХ данных из 1С
+- [models/ApiCron.md](../../models/ApiCron.md) — очередь запросов от 1С
+- [models/ExportImport.md](../../models/ExportImport.md) — outbox таблица для всех сущностей
+
+---
+
+_Версия: 1.0.0_
+_Создано: 2026-04-29 на основе анализа task_22 и DataController::getCreateEmployee_
index 9aa1e849486344a442b7f1455798af24a432c2ea..3ed7ee79f5c3e1bef551870640d793f13352f648 100644 (file)
@@ -69,7 +69,7 @@ try {
 
         //////////////////////////////////////////////
 
-        $compareDate = date('Y-m-d H:i:s', strtotime("-1 month", time()));
+        $compareDate = date('Y-m-d H:i:s', strtotime("-4 month", time()));
 
         $entityCityStore = ExportImportService::getEntityByType();
         $exportCityStore = ArrayHelper::map($entityCityStore, 'entity_id', 'export_val');