Веб-панель поверх Битрикс24 и 1С:УНФ: закрытые часы сотрудников, доска задач по проектам, дашборд, проверка качества задач, KPI и создание документов в УНФ одной кнопкой.
Развитие проекта task2bitrix24: сохранён весь функционал «Закрытых часов», сверху добавлена навигация с разделами.
Выбираете месяц — получаете .xlsx со списанными часами по каждому сотруднику. Отчётный месяц определяется датой выполнения работ из результата задачи, а не датой её закрытия: работы часто сдают в другом месяце, и в счёт должен попасть тот месяц, когда работа фактически сделана.
В книге: лист Свод, сразу за ним Все задачи и дальше по листу на каждого сотрудника. В детальных листах есть колонка «Дата списания» — когда часы фактически потрачены.
Тот же период, но только задачи выбранных клиентов, и в файле единственный лист Все задачи. Компании выбираются чипами (крестик — убрать), в выпадающем списке остаются только невыбранные. Целевые клиенты подставляются сразу (REPORT_DEFAULT_COMPANY_IDS). Справочник CRM кешируется в data/companies.json на сутки.
Колонки — проекты (рабочие группы Битрикс), листаются вправо; задачи внутри колонки листаются вниз. Две вкладки: «Активные задачи» и «Закрытые задачи» за выбранный месяц. Показываются задачи всей команды.
Один и тот же месяц в трёх разрезах, переключаются вкладками:
- По сотрудникам — кто сколько закрыл часов;
- По организациям — сколько часов ушло каждому клиенту;
- По проектам — сколько часов съел каждый проект (рабочая группа Битрикс).
Часы берутся из списаний, задачи считаются уникальными: задача с тремя списаниями — это одна задача. Строки «Без компании» и «Без проекта» всегда в конце.
Рядом с часами видно, что изменилось с прошлого обновления: зелёный +2.50 ч у того, кто прибавил, красный −1.00 ч у того, у кого стало меньше. Сравнение идёт с предыдущим снимком того же месяца, поэтому у самого первого снимка изменений нет — сравнивать не с чем. Считается в минутах, чтобы округление не рисовало «+0».
Страница не ходит в Битрикс — читает снимок data/dashboard-YYYY-MM.json. Снимок пересоздаётся при выгрузке Excel, при создании учётов времени в УНФ и вручную кнопкой «Обновить за этот месяц». Снимки, снятые до появления вкладок, содержат только сотрудников — организации и проекты появятся после следующего обновления.
Проверяет задачи, закрытые в выбранном месяце, и показывает, какие нельзя выставить в счёт как есть. Результат виден на экране и выгружается в Excel (листы Свод и Задачи с замечаниями).
Красный флаг:
- не списаны часы;
- нет результата задачи или в нём нет даты выполнения работ;
- месяц в результате не совпадает с месяцем закрытия;
- не заполнен элемент CRM;
- не заполнен проект у ключевого клиента (
QUALITY_RED_FLAG_COMPANY_IDS/..._NAMES). У остальных компаний проект не проверяется.
Жёлтое предупреждение:
- результат длиннее
QUALITY_RESULT_LEN_WARN(300) символов — менеджер не вставит такой текст в счёт; - дата выполнения работ записана не в каноническом формате
(дата выполнения работ ДД.ММ.ГГГГ).
Последний результат кешируется в data/quality-YYYY-MM.json, поэтому выгрузка Excel не опрашивает Битрикс повторно.
Раздел из трёх вкладок, заменяющий ручной Excel: KPI по задачам (с галочкой, кому его начислить), Условия, оклады и ставка (схема оплаты по каждому сотруднику) и Расчёт ЗП (часы + KPI + оклад + ручные премии и штрафы = сумма к выплате).
Четыре схемы оплаты: несгораемый оклад, оклад + ставка, только ставка, фикс. Условия хранятся помесячно с журналом «кто и что поменял». Доступ только у администратора.
Подробно — Расчёт зарплаты.
Считает премию постановщика задач по трём формулам за выбранный месяц: объём, отсутствие просрочек и доля задач по шаблону. Результат — таблица по людям и список задач; выгружается в Excel, кешируется в data/kpi-YYYY-MM.json.
Две кнопки создают документы прямо из отчёта:
- «Создать Учеты времени» —
Документ.УчетВремениза первую полную неделю месяца, часы разложены по дням со временем «с»/«по». Если часов больше 168, в одну строку они не помещаются — добавляется вторая строка табличной части, и остаток раскладывается в ней снова с понедельника; - «Создать Задания на работу» —
Документ.ЗаданиеНаРаботуна 1-е число следующего месяца, план = факт × 1.1.
Документы проводятся автоматически (UNF_TIME_POST_DOCUMENTS). Повторный запуск не создаёт дублей. Подробно — Интеграция с 1С:УНФ через OData.
| Роль | Просмотр | Excel | Документы УНФ | Расчёт ЗП | Админка | Запросы в Битрикс | Данные |
|---|---|---|---|---|---|---|---|
| Администратор | да | да | да | да | да | да | открытые |
| Сотрудник | да | да | нет | нет | нет | да | открытые |
| Внешний | да | нет | нет | нет | нет | да | обезличенные |
| Гость (без пароля) | да | нет | нет | нет | нет | нет | обезличенные |
Пользователи хранятся в data/users.json (пароли — password_hash), учётка из APP_AUTH_USER / APP_AUTH_PASSWORD — всегда администратор.
Сайт открывается любому: без логина посетитель получает роль Гость — данные обезличены, кнопки недоступны, скачать или создать он ничего не может. На месте «Администрирования» у него кнопка ХОЧУ ТАКЖЕ (GUEST_TELEGRAM_URL, по умолчанию https://t.me/DED_GENA).
Обезличивание не превращает страницу в тарабарщину: вместо ФИО и названий подставляются «Сотрудник #3», «Компания #7», «Проект #2», «Задача #15» — понятно, что за данные, но не видно, какие именно. Номер выдаётся по порядку первого появления в пределах страницы, поэтому одно и то же имя везде на странице выглядит одинаково, а разные сущности не сливаются. Часы и счётчики скрыты как •••, изменения на дашборде показываются только направлением (▲/▼) без величины.
Своим войти: кнопка «Войти» справа в меню (она ведёт на ?login=1 — браузер покажет форму пароля только в ответ на 401, поэтому нужен отдельный адрес).
Гостю запрещены живые запросы в Битрикс — страница открыта всему интернету, а каждая сборка доски стоит десятков REST-запросов:
- доска задач отдаётся из общего кеша
data/board-<режим>-<месяц>.json, он пересобирается не чаще раза вBOARD_CACHE_TTL(15 минут), причём пересобирает всегда только один процесс — остальные в это время получают предыдущую версию; - KPI и проверка задач показывают последний сохранённый снимок, пересчёт по
?runигнорируется; - дашборд и так читает снимок, поэтому работает у гостя в полном объёме.
Выключить гостевой режим и вернуть закрытый сайт: APP_GUEST_ACCESS=0 (или LOCAL_APP_GUEST_ACCESS в config.local.php).
Используется входящий webhook Bitrix24 REST API. Реальный URL в репозитории не хранится и задаётся одним из двух способов:
# переменная окружения (рекомендуется на сервере)
BITRIX_WEBHOOK="https://<portal>.bitrix24.ru/rest/<user-id>/<webhook-code>/"либо константой LOCAL_BITRIX_WEBHOOK в config.local.php (файл в .gitignore). Переменная окружения приоритетнее — см. configValue() в config.php.
Транспорт — bitrixRequest() в lib.php:
- POST,
application/x-www-form-urlencoded, URL склеивается как<webhook>/<метод>; curl, если расширение доступно, иначе fallback наfile_get_contents()со stream context;- у
bitrix24.ruмного A-записей, и часть IP периодически не отвечает: включёнCURLOPT_DNS_SHUFFLE_ADDRESSES, а сетевые ошибки (6, 7, 28, 35, 52, 55, 56) ретраятся до 6 раз с нарастающей паузой — один обрыв не должен ронять часовую выгрузку; QUERY_LIMIT_EXCEEDEDретраится до 8 раз с паузой в секунду;- любая другая
errorиз ответа поднимается как исключение с текстом Битрикса.
Массовые запросы — bitrixBatch() / bitrixBatchAll(): команды режутся по 50 штук на batch, halt=0 (одна упавшая команда не рвёт пачку), ошибки внутри пачки собираются и поднимаются одним сообщением. Пагинация — через start и поле next.
Права, которые нужны webhook:
| Метод | Зачем |
|---|---|
tasks.task.list |
задачи для отчёта, доски, проверки и KPI |
tasks.task.result.list |
результат задачи — из него берётся дата выполнения работ |
task.elapseditem.getlist |
списанное время, основа всех часов |
user.get |
ФИО сотрудников |
crm.company.list |
названия компаний по UF_CRM_TASK |
sonet_group.get |
названия проектов для доски — необязателен |
Если у webhook нет прав на sonet_group.get, доска продолжает работать: названия проектов берутся из объекта group, который отдаёт сам tasks.task.list, а в худшем случае колонка называется Проект #<id>.
Проверить настройку целиком, ничего не меняя:
php tools/preflight.php --online- Пользователь выбирает месяц.
- Берутся задачи, закрытые в окне ±
REPORT_CLOSED_WINDOW_MONTHS(по умолчанию 2) месяца вокруг отчётного — работы могли сдать заметно позже или раньше закрытия. - Из них только полностью завершённые:
REAL_STATUS=REPORT_CLOSED_STATUSES(по умолчанию[5]). Статус4«условно завершена» ждёт одобрения и в счёт не идёт. - По всем задачам разом читаются результаты (
tasks.task.result.listпачками). - Ключевой шаг: в отчёт попадает только задача, в тексте результата которой есть дата, попадающая в выбранный месяц. Приоритет — у результатов со
status = 1(завершающих), среди них берётся последний по времени. Дату ищут два регулярных выражения: сначала конструкция «дата выполнения работ …», и только если её нет — любая дата в тексте. Понимаются форматыДД.ММ.ГГГГ,ДД/ММ/ГГ,Д.М.ГГГГ. - По прошедшим фильтр задачам читается списанное время (
task.elapseditem.getlist, пачками по задачам, с добором страниц если записей больше 50). - Часы относятся к тому, кто списал время (
USER_IDзаписи), а не к ответственному. Фильтра по списку пользователей здесь нет — поэтому новые сотрудники попадают в отчёт автоматически. - Дата списания —
DATE_STARTзаписи, при пустом —CREATED_DATE. - Итоги суммируются по сотрудникам и по задачам; сотрудники без часов и задач отбрасываются.
Необязательный фильтр по компаниям применяется после шага 5 — по UF_CRM_TASK задачи.
Фильтр по RESPONSIBLE_ID из reportUserIds() (закреплённый список REPORT_USER_IDS + активные сотрудники портала, чтобы новички появлялись сразу) и по REAL_STATUS:
- «Активные»:
[2, 3, 6]— ждёт выполнения, выполняется, отложена; без ограничения по датам; - «Закрытые»:
[4, 5]иCLOSED_DATEвнутри выбранного месяца.
Задачи группируются по GROUP_ID в колонки-проекты, не более BOARD_TASKS_PER_PROJECT_LIMIT (200) карточек на колонку.
Берёт задачи, закрытые внутри выбранного месяца, со статусами [4, 5] (здесь «условно завершена» тоже проверяется), читает результат и списания и прогоняет через taskQualityIssues() — правила перечислены выше в разделе про функциональность.
buildKpiReport() считает премию постановщика задач, а не исполнителя.
Выборка: задачи с CREATED_DATE внутри выбранного месяца и REAL_STATUS [4, 5] — то есть созданные и уже закрытые за этот месяц, по всему порталу. Группировка — по CREATED_BY.
По каждой задаче считается:
- часы — сумма
MINUTESвсех списаний; - просрочка по дате —
CLOSED_DATEпозжеDEADLINE; без срока задача не просрочена; - просрочка по тегу — среди тегов есть
KPI_OVERDUE_TAG(Просрочил), это ручная отметка; - по шаблону — в описании встречается
KPI_TEMPLATE_MARKER(Название базы).
Дальше на сотрудника:
зачётные = все задачи − задачи без списанных часов
просрочки = просрочки по дате + просрочки по тегу
по шаблону = задачи по шаблону − задачи без часов (не меньше нуля)
в срок = зачётные − просрочки (не меньше нуля)
KPI 1 = зачётные / KPI_TASKS_BASE (100) × KPI_TASKS_RATE (30 000 ₽)
KPI 2 = в срок / зачётные × KPI_OVERDUE_RATE (25 000 ₽)
KPI 3 = по шаблону / зачётные × KPI_TEMPLATE_RATE (25 000 ₽) (доля не больше 1)
Итого = KPI 1 + KPI 2 + KPI 3
Задача без списанных часов не идёт в зачёт нигде — это главное правило, оно повторяет рабочий файл «РезультатыМесяца». Ставки и пороги переопределяются через KPI_* в конфиге. Сотрудники без зачётных задач уходят вниз списка.
.xlsx собирается без сторонних библиотек — напрямую как ZIP-пакет с XML-частями (createXlsxFromSheets() в lib.php), нужно только расширение zip. Отсюда же берутся стили: рамки, чередующаяся заливка, ширина колонок, числовые форматы.
| Кнопка | Листы |
|---|---|
| Закрытые часы | Свод, Все задачи, далее по листу на сотрудника |
| Отчёт по выбранным компаниям | только Все задачи |
| Проверка задач | Свод, Задачи с замечаниями |
| KPI | KPI по сотрудникам, Задачи |
Особенности листа Все задачи: поля задачи продублированы в каждой строке, чтобы фильтры Excel работали по всем строкам списаний; задачи отделены рамками и чередующейся заливкой; рядом с Затрачено стоит Итого по задаче.
Имена листов чистятся от запрещённых символов и обрезаются до 31 знака, дубликаты разводятся суффиксом (uniqueWorksheetName()).
Создание УчетВремени и ЗаданиеНаРаботу через OData вынесено на отдельную страницу:
→ docs/ODATA.md — как подключаемся, что создаём, как это устроено.
cp config.local.example.php config.local.phpЗаполните LOCAL_BITRIX_WEBHOOK, LOCAL_APP_AUTH_PASSWORD и, если нужны кнопки УНФ, LOCAL_UNF_ODATA_USER / LOCAL_UNF_ODATA_PASSWORD.
Проверить, что всё настроено (ничего не меняет, только читает):
php tools/preflight.phpЛокальный сервер из папки проекта:
php -S 127.0.0.1:8080Открыть http://127.0.0.1:8080/. Логин по умолчанию: admin.
Папка data/ должна быть доступна на запись веб-серверу и закрыта от прямой раздачи — в снимках ФИО, часы и хеши паролей.
index.php— главная (отчёт, кнопки УНФ, полезные кнопки);tasks.php— доска;dashboard.php— дашборд;quality.php— проверка задач;kpi.php— KPI;admin.php— управление пользователями;report.php,unf_time.php,unf_workorder.php,dashboard_refresh.php— обработчики;lib.php— Битрикс REST, отчёт, доска, дашборд, проверка, KPI и генерация.xlsx;unf.php— интеграция с 1С:УНФ по OData;auth.php— роли и Basic Auth;config.php— конфигурация;partials/— общая разметка;effects.js,style.css;tools/preflight.php— предполётная проверка настройки.