Перейти к основному контенту

Техническая документация: Telegram-бот системы согласований

Версия документа: 1.0.


Оглавление

    Техническое задание Структура репозитория (package map) Полная модель данных Пакет approvalflow — дерево согласования и реестр тем Пакет state — состояние диалога в Redis Handler — инициализация и главное меню Прохождение дерева — HandleCallback, HandleTextInput, HandleBackCallback Цепочка дополнительных шагов — continueFlowAfterInput Выбор согласующего (ручной и по роли) Наблюдатели Делегирование обязанностей submitApproval — создание заявки, построчно Решение согласующего — handleDecision processNextStepOrComplete Дополнительные согласующие (add_approver.go) Задачи исполнителей (tasks.go) Отзыв заявок Уведомления (Notifier) Устойчивость бота (прокси, ретраи) scheduler — ежедневная сводка Каталог callback_data Каталог ключей Redis Журнал багов с кодом до/после

    0. Техническое задание: дерево тем согласования

    Источник: карта согласований, содержащая полный перечень категорий и тем согласования с указанием инициаторов, согласующих и наблюдателей на момент постановки задачи.

    Обозначения: Инициаторы — кто может подавать заявку по данной ветке; Согласующий N — кто и в каком порядке принимает решение (порядковый номер соответствует шагу в последовательной цепочке согласования); Наблюдатели — кто получает уведомление о заявке без права решения.

    Итоговое дерево включает 26 категорий верхнего уровня:

    • Согласование отпусков\отгулов
    • Внебюджетные расходы
    • Внебюджетные расходы ПЭТ Лайн
    • Возвраты
    • Изменение клиентов в ОКБ и 1С
    • Заказ товаров
    • Отгрузки на СОХ
    • Паспорт сделки
    • Ставки логистика
    • Поступления по приходным документам
    • Доставка клиентам\на склад
    • В. Отгрузки
    • Менеджерские расходы
    • Отгрузки
    • Нестандартные условия договора поставки
    • Вакансии
    • Ставки доставки АП
    • Утверждение фрилансеров
    • Выход в нерабочий день
    • Долгосрочные условия ОТК
    • Остаток на кассах
    • Продажи по клиенту
    • Проведенные расходы по менеджеру
    • Согласование удаленной работы
    • Смена условий автоматического согласования сделок
    • Утверждение оплат безнал

    Полное дерево категорий и тем

    • Согласование отпусков\отгулов
      • Отгул
        • На один день
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
        • Диапазон дат
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
        • Несколько дней
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
      • Отпуск
        • На один день
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
        • Диапазон дат
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
        • Несколько дней
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
      • Больничный. Меняем на "официальный больничный". Следующим пунктом просим ввести дату открытия больничного листа.
        • На один день
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • Только оф. больничный
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
        • Диапазон дат
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • Официальный больничный
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
        • Несколько дней
          • Весь день
            • В счёт отпуска
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • За свой счёт
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
            • Официальный больничный
              • Инициаторы:
                • Все
              • Согласующий 1:
                • Непосредственный руководитель
              • Наблюдатели:
                • Демченко Елена
                • Вялкова Анна
                • Шепко Лия
          • Частично
            • Инициаторы:
              • Все
            • Согласующий 1:
              • Непосредственный руководитель
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Шепко Лия
    • Внебюджетные расходы
      • Ф1
        • ТМЦ (далее "Нужна ПП?")
          • Да
            • Инициаторы:
              • Турчина Варвара
              • Горбачёва Оксана
              • Демченко Елена
              • Жуков Дмитрий
              • Шведов Никита
              • Романов Александр
              • Свирень Максим
              • Танашевич Анифе
              • Щасливая Екатерина
              • Шепко Лия
              • Аблякимова Айше
              • Халилов Дамур
              • Анисимов Виталий
              • Храпач Анастасия
              • прикрепить файл
            • Согласующий 1:
              • Шепко Лия
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Жуков Дмитрий
              • Сергатова Светлана
              • Шведов Никита
              • Романов Александр
              • Рудник Алина
            • Исполнитель:
              • Рудь Анфиса
              • задача 1:
                • Оплатить счет
              • задача 2:
                • Отправить ПП сотруднику
                • прикрепить файл
          • Нет
            • Инициаторы:
              • Турчина Варвара
              • Горбачёва Оксана
              • Демченко Елена
              • Жуков Дмитрий
              • Шведов Никита
              • Романов Александр
              • Свирень Максим
              • Танашевич Анифе
              • Щасливая Екатерина
              • Шепко Лия
              • Аблякимова Айше
              • Халилов Дамур
              • Анисимов Виталий
              • Храпач Анастасия
              • прикрепить файл
            • Согласующий 1:
              • Шепко Лия
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Жуков Дмитрий
              • Сергатова Светлана
              • Шведов Никита
              • Романов Александр
              • Рудник Алина
            • Исполнитель:
              • Рудь Анфиса
              • задача 1:
                • Оплатить счет
        • Расход (далее "Нужна ПП?")
          • Да
            • Инициаторы:
              • Турчина Варвара
              • Горбачёва Оксана
              • Демченко Елена
              • Жуков Дмитрий
              • Шведов Никита
              • Романов Александр
              • Свирень Максим
              • Танашевич Анифе
              • Щасливая Екатерина
              • Шепко Лия
              • Аблякимова Айше
              • Халилов Дамур
              • Анисимов Виталий
              • Храпач Анастасия
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Жуков Дмитрий
              • Сергатова Светлана
              • Шведов Никита
              • Романов Александр
              • Рудник Алина
              • Шепко Лия
            • Исполнитель:
              • Рудь Анфиса
              • задача 1:
                • Оплатить счет
              • задача 2:
                • Отправить ПП сотруднику
                • прикрепить файл
          • Нет
            • Инициаторы:
              • Турчина Варвара
              • Горбачёва Оксана
              • Демченко Елена
              • Жуков Дмитрий
              • Шведов Никита
              • Романов Александр
              • Свирень Максим
              • Танашевич Анифе
              • Щасливая Екатерина
              • Шепко Лия
              • Аблякимова Айше
              • Халилов Дамур
              • Анисимов Виталий
              • Храпач Анастасия
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Вялкова Анна
              • Жуков Дмитрий
              • Сергатова Светлана
              • Шведов Никита
              • Романов Александр
              • Рудник Алина
              • Шепко Лия
            • Исполнитель:
              • Рудь Анфиса
              • задача 1:
                • Оплатить счет
      • Ф2
        • ТМЦ
          • Инициаторы:
            • Турчина Варвара
            • Горбачёва Оксана
            • Демченко Елена
            • Жуков Дмитрий
            • Шведов Никита
            • Романов Александр
            • Свирень Максим
            • Танашевич Анифе
            • Щасливая Екатерина
            • Шепко Лия
            • Аблякимова Айше
            • Халилов Дамур
            • Анисимов Виталий
            • Храпач Анастасия
          • Согласующий 1:
            • Шепко Лия
          • Согласующий 2:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
        • Расход
          • Инициаторы:
            • Турчина Варвара
            • Горбачёва Оксана
            • Демченко Елена
            • Жуков Дмитрий
            • Шведов Никита
            • Романов Александр
            • Свирень Максим
            • Танашевич Анифе
            • Щасливая Екатерина
            • Шепко Лия
            • Аблякимова Айше
            • Халилов Дамур
            • Анисимов Виталий
            • Храпач Анастасия
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
            • Шепко Лия
    • Внебюджетные расходы ПЭТ Лайн
      • Ф1
        • ТМЦ
          • Инициаторы:
            • Пирожников Павел
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
        • Расход
          • Инициаторы:
            • Пирожников Павел
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
      • Ф2
        • ТМЦ
          • Инициаторы:
            • Пирожников Павел
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
        • Расход
          • Инициаторы:
            • Пирожников Павел
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
    • Возвраты
      • Возврат поставщику
        • Крым
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Гацолаева Наталья
            • Федосенко Вероника
            • Федосенко Иван
            • Свирень Максим
            • Бояджан Сергей
          • Согласующий 1:
            • Щасливая Екатерина
          • Согласующий 2:
            • Тищенко Владимир
          • Наблюдатели:
            • Великожон Татьяна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
            • Демченко Елена
            • Вялкова Анна
        • Краснодар
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Стуков Павел
            • Бондаренко Михаил
            • Гацолаева Наталья
            • Сыпков Станислав
            • Федосенко Вероника
            • Федосенко Иван
            • Пирожников Павел
          • Согласующий 1:
            • Щасливая Екатерина
          • Согласующий 2:
            • Макаренко Александр
          • Согласующий 3:
            • Тищенко Владимир
          • Наблюдатели:
            • Великожон Татьяна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
            • Демченко Елена
            • Вялкова Анна
      • Продажа другим менеджером
        • Крым
          • выбор из списка менеджеров Крыма:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Петрунин Кирилл
            • Федосенко Иван
            • Свирень Максим
            • Бояджан Сергей
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
            • Согласующий 1:
              • выбранный в прошлом шаге менеджер
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Великожон Татьяна
              • Жуков Дмитрий
              • Сергатова Светлана
              • Шведов Никита
              • Романов Александр
              • Рудник Алина
              • Демченко Елена
              • Вялкова Анна
        • Краснодар
          • выбор из списка менеджеров Краснодара:
            • Макаренко Александр
            • Петрунин Кирилл
            • Стуков Павел
            • Бондаренко Михаил
            • Сыпков Станислав
            • Федосенко Иван
            • Пирожников Павел
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
            • Согласующий 1:
              • выбранный в прошлом шаге менеджер
            • Согласующий 2:
              • Макаренко Александр
            • Согласующий 3:
              • Тищенко Владимир
            • Наблюдатели:
              • Великожон Татьяна
              • Жуков Дмитрий
              • Сергатова Светлана
              • Шведов Никита
              • Романов Александр
              • Рудник Алина
              • Демченко Елена
              • Вялкова Анна
      • Самостоятельная продажа
        • Крым
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Гацолаева Наталья
            • Федосенко Вероника
            • Федосенко Иван
            • Свирень Максим
            • Бояджан Сергей
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Великожон Татьяна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
            • Демченко Елена
            • Вялкова Анна
        • Краснодар
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Стуков Павел
            • Бондаренко Михаил
            • Гацолаева Наталья
            • Сыпков Станислав
            • Федосенко Вероника
            • Федосенко Иван
            • Пирожников Павел
          • Согласующий 1:
            • Макаренко Александр
          • Согласующий 2:
            • Тищенко Владимир
          • Наблюдатели:
            • Великожон Татьяна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
            • Демченко Елена
            • Вялкова Анна
    • Изменение клиентов в ОКБ и 1С
      • Поменять название
        • Крым
          • ОКБ
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести изменение в название клиента
          • Битрикс
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести изменение в название клиента
          • 1С
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Вялкова Анна
              • задача 1:
                • Внести изменение в название клиента
          • Подтвердить выбор
            • при выборе нескольких, ставить совокупность всех задач по выбранным кнопкам
        • Краснодар
          • ОКБ
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести изменение в название клиента
          • Битрикс
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести изменение в название клиента
          • 1С
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Вялкова Анна
              • задача 1:
                • Внести изменение в название клиента
          • Подтвердить выбор
            • при выборе нескольких, ставить совокупность всех задач по выбранным кнопкам
      • Сменить менеджера
        • Крым
          • ОКБ
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Сменить менеджера клиента
          • Битрикс
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Сменить менеджера клиента
          • 1С
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Вялкова Анна
              • задача 1:
                • Сменить менеджера клиента
          • Подтвердить выбор
            • при выборе нескольких, ставить совокупность всех задач по выбранным кнопкам
        • Краснодар
          • ОКБ
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Сменить менеджера клиента
          • Битрикс
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Сменить менеджера клиента
          • 1С
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Вялкова Анна
              • задача 1:
                • Сменить менеджера клиента
          • Подтвердить выбор
            • при выборе нескольких, ставить совокупность всех задач по выбранным кнопкам
      • Внести нового клиента
        • Крым
          • ОКБ
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести нового клиента
          • Битрикс
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести нового клиента
          • 1С
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Гацолаева Наталья
              • Федосенко Вероника
              • Федосенко Иван
              • Свирень Максим
              • Бояджан Сергей
              • прикрепить файл
            • Согласующий 1:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Вялкова Анна
              • задача 1:
                • Внести нового клиента
          • Подтвердить выбор
            • при выборе нескольких, ставить совокупность всех задач по выбранным кнопкам
        • Краснодар
          • ОКБ
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести нового клиента
          • Битрикс
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Руднева Надежда
              • задача 1:
                • Внести нового клиента
          • 1С
            • Инициаторы:
              • Чекальский Александр
              • Шевченко Виталий
              • Макаренко Александр
              • Михайлова Татьяна
              • Петрунин Кирилл
              • Стуков Павел
              • Бондаренко Михаил
              • Гацолаева Наталья
              • Сыпков Станислав
              • Федосенко Вероника
              • Федосенко Иван
              • Пирожников Павел
              • прикрепить файл
            • Согласующий 1:
              • Макаренко Александр
            • Согласующий 2:
              • Тищенко Владимир
            • Наблюдатели:
              • Демченко Елена
              • Татьяна Савойская
              • Степанов Сергей
              • Селифанова Эльза
            • Исполнитель:
              • Вялкова Анна
              • задача 1:
                • Внести нового клиента
          • Подтвердить выбор
            • при выборе нескольких, ставить совокупность всех задач по выбранным кнопкам
    • Заказ товаров
      • На месяц
        • Крым
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Гацолаева Наталья
            • Федосенко Вероника
            • Федосенко Иван
            • прикрепить файлы
          • Согласующий 1:
            • Великожон Татьяна
            • внести текст
          • Согласующий 2:
            • Тищенко Владимир
          • Наблюдатели:
            • Щасливая Екатерина
            • Степанов Сергей
          • Исполнитель:
            • Великожон Татьяна
            • задача 1:
              • Внести заказ в таблицу
              • отправить менеджеру сообщение НЕ "исполнитель завершил задачу", а "Заказ принят, следи за статусом в гугл-таблице"
        • Краснодар
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Стуков Павел
            • Бондаренко Михаил
            • Гацолаева Наталья
            • Сыпков Станислав
            • Федосенко Вероника
            • Федосенко Иван
            • Пирожников Павел
            • прикрепить файлы
          • Согласующий 1:
            • Великожон Татьяна
            • внести текст
          • Согласующий 2:
            • Макаренко Александр
          • Наблюдатели:
            • Щасливая Екатерина
            • Степанов Сергей
          • Исполнитель:
            • Великожон Татьяна
            • задача 1:
              • Внести заказ в таблицу
              • отправить менеджеру сообщение НЕ "исполнитель завершил задачу", а "Заказ принят, следи за статусом в гугл-таблице"
      • Дополнительный заказ
        • Крым
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Гацолаева Наталья
            • Федосенко Вероника
            • Федосенко Иван
            • прикрепить скриншоты
          • Согласующий 1:
            • Великожон Татьяна
            • внести текст
          • Согласующий 2:
            • Тищенко Владимир
          • Наблюдатели:
            • Щасливая Екатерина
            • Степанов Сергей
          • Исполнитель:
            • Великожон Татьяна
            • задача 1:
              • Внести заказ в таблицу
              • отправить менеджеру сообщение НЕ "исполнитель завершил задачу", а "Заказ принят, следи за статусом в гугл-таблице"
        • Краснодар
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Стуков Павел
            • Бондаренко Михаил
            • Гацолаева Наталья
            • Сыпков Станислав
            • Федосенко Вероника
            • Федосенко Иван
            • Пирожников Павел
            • прикрепить скриншоты
          • Согласующий 1:
            • Великожон Татьяна
            • внести текст
          • Согласующий 2:
            • Макаренко Александр
          • Наблюдатели:
            • Щасливая Екатерина
            • Степанов Сергей
          • Исполнитель:
            • Великожон Татьяна
            • задача 1:
              • Внести заказ в таблицу
              • отправить менеджеру сообщение НЕ "исполнитель завершил задачу", а "Заказ принят, следи за статусом в гугл-таблице"
    • Отгрузки на СОХ
      • Реальная
        • Крым
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Гацолаева Наталья
            • Федосенко Вероника
            • Федосенко Иван
          • Согласующий 1:
            • Шепко Лия
            • внести текст
          • Согласующий 2:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Рудь Анфиса
            • Романов Александр
            • Рудник Алина
            • Шепко Лия
          • Исполнитель 1:
            • Сергатова Светлана
            • задача 1:
              • Составить договор СОХ и акт приема-передачи с клиентом
            • задача 2:
              • Подписать договор СОХ и акт приема-передачи с клиентом и прикрепить этот файл далее
              • прикрепить файл
                • Исполнитель 2:
                  • Шепко Лия
                  • задача 1:
                    • Сверить договор и внести данные о лимите в таблицу
                    • Исполнитель 3:
                      • Романов Александр
                      • задача 1:
                        • Убедиться, что отгрузка на СОХ согласована, проведена и не задублирована в 1С
        • Краснодар
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Стуков Павел
            • Бондаренко Михаил
            • Гацолаева Наталья
            • Сыпков Станислав
            • Федосенко Вероника
            • Федосенко Иван
            • Пирожников Павел
          • Согласующий 1:
            • Шепко Лия
            • внести текст
          • Согласующий 2:
            • Макаренко Александр
          • Согласующий 3:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Рудь Анфиса
            • Романов Александр
            • Рудник Алина
            • Шепко Лия
          • Исполнитель 1:
            • Сергатова Светлана
            • задача 1:
              • Составить договор СОХ и акт приема-передачи с клиентом
            • задача 2:
              • Подписать договор СОХ и акт приема-передачи с клиентом и прикрепить этот файл далее
              • прикрепить файл
                • Исполнитель 2:
                  • Шепко Лия
                  • задача 1:
                    • Сверить договор и внести данные о лимите в таблицу
                    • Исполнитель 3:
                      • Романов Александр
                      • задача 1:
                        • Убедиться, что отгрузка на СОХ согласована, проведена и не задублирована в 1С
      • Для лимита
        • Крым
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Гацолаева Наталья
            • Федосенко Вероника
            • Федосенко Иван
          • Согласующий 1:
            • Шепко Лия
            • внести текст
          • Согласующий 2:
            • Тищенко Владимир
          • Наблюдатели:
            • Шепко Лия
          • Исполнитель 1:
            • Сергатова Светлана
            • задача 1:
              • Составить договор СОХ и акт приема-передачи с клиентом
            • задача 2:
              • Подписать договор СОХ и акт приема-передачи с клиентом и прикрепить этот файл далее
              • прикрепить файл
                • Исполнитель 2:
                  • Шепко Лия
                  • задача 1:
                    • Сверить договор и внести данные о лимите в таблицу
        • Краснодар
          • Инициаторы:
            • Чекальский Александр
            • Шевченко Виталий
            • Макаренко Александр
            • Михайлова Татьяна
            • Петрунин Кирилл
            • Стуков Павел
            • Бондаренко Михаил
            • Гацолаева Наталья
            • Сыпков Станислав
            • Федосенко Вероника
            • Федосенко Иван
            • Пирожников Павел
          • Согласующий 1:
            • Шепко Лия
            • внести текст
          • Согласующий 2:
            • Макаренко Александр
          • Согласующий 3:
            • Тищенко Владимир
          • Наблюдатели:
            • Шепко Лия
          • Исполнитель 1:
            • Сергатова Светлана
            • задача 1:
              • Составить договор СОХ и акт приема-передачи с клиентом
            • задача 2:
              • Подписать договор СОХ и акт приема-передачи с клиентом и прикрепить этот файл далее
              • прикрепить файл
                • Исполнитель 2:
                  • Шепко Лия
                  • задача 1:
                    • Сверить договор и внести данные о лимите в таблицу
    • Паспорт сделки
      • Крым
        • Инициаторы:
          • Чекальский Александр
          • Шевченко Виталий
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Гацолаева Наталья
          • Федосенко Вероника
          • Федосенко Иван
          • прикрепить файлы
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • Коршунова Александра
          • Шепко Лия
          • Танашевич Анифе
      • Краснодар
        • Инициаторы:
          • Чекальский Александр
          • Шевченко Виталий
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Стуков Павел
          • Бондаренко Михаил
          • Гацолаева Наталья
          • Сыпков Станислав
          • Федосенко Вероника
          • Федосенко Иван
          • Пирожников Павел
          • прикрепить файлы
        • Согласующий 1:
          • Макаренко Александр
        • Согласующий 2:
          • Тищенко Владимир
        • Наблюдатели:
          • Коршунова Александра
          • Шепко Лия
          • Танашевич Анифе
      • Беларусь
        • Инициаторы:
          • Пирожников Павел
          • Гацолаева Наталья
          • прикрепить файлы
        • Согласующий 1:
          • Пирожников Павел
        • Согласующий 2:
          • Тищенко Владимир
        • Наблюдатели:
          • Коршунова Александра
          • Шепко Лия
          • Танашевич Анифе
    • Ставки логистика
      • Разово
        • Инициаторы:
          • Танашевич Анифе
          • Минаджиева Залина
          • Чалышева Ление
          • прикрепить файлы (не обяз.)
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • Танашевич Анифе
          • Минаджиева Залина
          • Чалышева Ление
          • Храпач Анастасия
          • Шепко Лия
      • Надолго
        • Инициаторы:
          • Танашевич Анифе
          • Минаджиева Залина
          • Чалышева Ление
          • прикрепить файлы (не обяз.)
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • Танашевич Анифе
          • Минаджиева Залина
          • Чалышева Ление
          • Храпач Анастасия
          • Шепко Лия
        • Исполнитель 1:
          • Коршунова Александра
          • задача 1:
            • Внести изменения в гугл-таблицу Ставки логистики (с обязательным указанием, с какого срока начинает действовать ставка)
    • Поступления по приходным документам
      • Ф1
        • Инициаторы:
          • Щасливая Екатерина
          • Великожон Татьяна
          • Сергатова Светлана
          • Широков Михаил
          • прикрепить файлы (обяз.)
        • Согласующий 1:
          • Щасливая Екатерина
          • внести текст, который будет виден и исполнителю тоже
        • Наблюдатели:
          • Щасливая Екатерина
          • Великожон Татьяна
          • Демченко Елена
          • Вялкова Анна
          • Шведов Никита
        • Исполнитель 1:
          • Сергатова Светлана
          • задача 1:
            • Проверить и подтвердить, что приход проведен по 1С:Бухгалтерия
        • Исполнитель 2:
          • Романов Александр
          • задача 1:
            • Проверить и подтвердить, что приход проведен по 1С:УТ
      • Ф2
        • Инициаторы:
          • Щасливая Екатерина
          • Великожон Татьяна
          • Широков Михаил
          • прикрепить файлы (обяз.)
        • Согласующий 1:
          • Щасливая Екатерина
          • внести текст, который будет виден и исполнителю тоже
        • Наблюдатели:
          • Щасливая Екатерина
          • Великожон Татьяна
          • Демченко Елена
          • Вялкова Анна
          • Шведов Никита
        • Исполнитель 1:
          • Романов Александр
          • задача 1:
            • Проверить и подтвердить, что приход проведен по 1С:УТ
    • Доставка клиентам\на склад
      • выбор менеджера/зав склада из списка согласующих:
        • все менеджеры, помощники и зав складов
        • Инициаторы:
          • Танашевич Анифе
          • Чалышева Ление
          • Минаджиева Залина
          • прикрепить файлы
        • Согласующий 1 (на выбор):
          • Макаренко Александр
          • Петрунин Кирилл
          • Стуков Павел
          • Федосенко Иван
          • Сыпков Станислав
          • Бондаренко Михаил
          • Михайлова Татьяна
          • Широков Михаил
          • Чекальский Александр
          • Шевченко Виталий
          • Халилов Дамур
        • Наблюдатели:
          • Танашевич Анифе
          • Щасливая Екатерина
          • Великожон Татьяна
          • Рудник Алина
          • Жуков Дмитрий
          • Сергатова Светлана
        • Исполнитель 1:
          • Романов Александр
          • задача 1:
            • Клиент забрал товар, менеджер подтвердил. Проверить и подтвердить, что отгрузка записана И ПРОВЕДЕНА в 1С
    • В. Отгрузки
      • Инициаторы:
        • Чекальский Александр
        • Шевченко Виталий
        • Макаренко Александр
        • Михайлова Татьяна
        • Петрунин Кирилл
        • Стуков Павел
        • Бондаренко Михаил
        • Сыпков Станислав
        • Федосенко Иван
      • Согласующий 1:
        • Тищенко Владимир
      • Наблюдатели:
        • Демченко Елена
        • Вялкова Анна
        • Жуков Дмитрий
        • Шепко Лия
      • Исполнитель 1:
        • Сергатова Светлана
        • задача 1:
          • Создать реализацию и отправить клиенту на подпись
          • задача 2:
            • Убедиться, что клиент подписал реализацию
            • задача 3:
              • Перепроверить, что клиент подписал реализацию и поступила оплата. Если подписано И оплата поступила, нажмите, чтобы подтвердить (менеджеру придет уведомление о том, что документ подписан и деньги получены)
    • Менеджерские расходы
      • Крым
        • Инициаторы:
          • Чекальский Александр
          • Шевченко Виталий
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Гацолаева Наталья
          • Федосенко Вероника
          • Федосенко Иван
          • Свирень Максим
          • Бояджан Сергей
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • Демченко Елена
          • Вялкова Анна
          • Романов Александр
          • Рудник Алина
          • Шепко Лия
      • Краснодар
        • Инициаторы:
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Стуков Павел
          • Бондаренко Михаил
          • Гацолаева Наталья
          • Сыпков Станислав
          • Федосенко Вероника
          • Федосенко Иван
        • Согласующий 1:
          • Макаренко Александр
        • Согласующий 2:
          • Тищенко Владимир
        • Наблюдатели:
          • Демченко Елена
          • Вялкова Анна
          • Романов Александр
          • Рудник Алина
          • Шепко Лия
    • Отгрузки
      • МНОЖЕСТВЕННЫЙ выбор менеджера из списка наблюдателей:
        • все менеджеры, помощники
        • Инициаторы:
          • Романов Александр
          • Рудник Алина
          • прикрепить файлы (обяз.)
        • Согласующий 1:
          • Коршунова Александра
          • сделать текстовую приписку для этого согласующего:
            • Если не входит в рамки согласованного лимита, добавь руководителя соответствующего филиала к согласованию через кнопку "Добавить согласующего"
        • Наблюдатели (всегда логисты и Лия + на выбор из кнопки менеджер):
          • Танашевич Анифе
          • Чалышева Ление
          • Минаджиева Залина
          • Шепко Лия
          • на выбор из предыдущего шага:
          • Макаренко Александр
          • Петрунин Кирилл
          • Стуков Павел
          • Федосенко Иван
          • Сыпков Станислав
          • Бондаренко Михаил
          • Михайлова Татьяна
          • Гацолаева Наталья
          • Чекальский Александр
          • Шевченко Виталий
    • Нестандартные условия договора поставки
      • Крым
        • Инициаторы:
          • Чекальский Александр
          • Шевченко Виталий
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Гацолаева Наталья
          • Федосенко Вероника
          • Федосенко Иван
          • Свирень Максим
          • Бояджан Сергей
          • прикрепить файлы
        • Согласующий 1:
          • Аблякимова Айше
          • внести текст
        • Согласующий 2:
          • Тищенко Владимир
        • Наблюдатели:
          • Жуков Дмитрий
          • Сергатова Светлана
          • Шепко Лия
      • Краснодар
        • Инициаторы:
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Стуков Павел
          • Бондаренко Михаил
          • Гацолаева Наталья
          • Сыпков Станислав
          • Федосенко Вероника
          • Федосенко Иван
          • прикрепить файлы
        • Согласующий 1:
          • Аблякимова Айше
          • внести текст
        • Согласующий 2:
          • Макаренко Александр
        • Согласующий 3:
          • Тищенко Владимир
        • Наблюдатели:
          • Жуков Дмитрий
          • Сергатова Светлана
          • Михайлова Татьяна
          • Шепко Лия
      • Беларусь
        • Инициаторы:
          • Пирожников Павел
          • Гацолаева Наталья
          • прикрепить файлы
        • Согласующий 1:
          • Аблякимова Айше
          • внести текст
        • Согласующий 2:
          • Тищенко Владимир
        • Наблюдатели:
          • Жуков Дмитрий
          • Сергатова Светлана
          • Михайлова Татьяна
          • Шепко Лия
    • Вакансии
      • выбор рук отдела из списка согласующих
        • Инициаторы:
          • Турчина Варвара
          • прикрепить файлы
        • Согласующий 1 (на выбор):
          • Щасливая Екатерина
          • Танашевич Анифе
          • Халилов Дамур
          • Ковтун Игорь
          • Шепко Лия
          • Свирень Максим
          • Макаренко Александр
          • Жуков Дмитрий
          • Демченко Елена
          • Савойская Татьяна
          • Родюшкин Артем
          • Тищенко Владимир
        • Наблюдатели:
          • Горбачёва Оксана
          • Стрижак Виолетта
          • Демченко Елена
          • Вялкова Анна
          • Шепко Лия
    • Ставки доставки АП
      • Инициаторы:
        • Макаренко Александр
        • Михайлова Татьяна
        • Петрунин Кирилл
        • Стуков Павел
        • Бондаренко Михаил
        • Гацолаева Наталья
        • Сыпков Станислав
        • Федосенко Вероника
        • Федосенко Иван
      • Согласующий 1:
        • Танашевич Анифе
      • Наблюдатели:
        • Танашевич Анифе
        • Чалышева Ление
        • Минаджиева Залина
    • Утверждение фрилансеров
      • Инициаторы:
        • Халилов Дамур
      • Согласующий 1:
        • Тищенко Владимир
      • Наблюдатели:
        • Танашевич Анифе
        • Вялкова Анна
        • Храпач Анастасия
    • Выход в нерабочий день
      • Инициаторы:
        • Танашевич Анифе
        • Романов Александр
        • Шведов Никита
      • Согласующий 1:
        • Тищенко Владимир
      • Наблюдатели:
        • Халилов Дамур
        • Танашевич Анифе
        • Вялкова Анна
        • Храпач Анастасия
    • Долгосрочные условия ОТК
      • Крым
        • Инициаторы:
          • Чекальский Александр
          • Шевченко Виталий
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Гацолаева Наталья
          • Федосенко Вероника
          • Федосенко Иван
          • Свирень Максим
          • Бояджан Сергей
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • Шепко Лия
        • Исполнитель 1:
          • Ковтун Игорь
          • задача 1:
            • Зафиксировать согласованные условия в файле ОТК
      • Краснодар
        • Инициаторы:
          • Макаренко Александр
          • Михайлова Татьяна
          • Петрунин Кирилл
          • Стуков Павел
          • Бондаренко Михаил
          • Гацолаева Наталья
          • Сыпков Станислав
          • Федосенко Вероника
          • Федосенко Иван
        • Согласующий 1:
          • Макаренко Александр
        • Согласующий 2:
          • Тищенко Владимир
        • Наблюдатели:
          • Шепко Лия
        • Исполнитель 1:
          • Ковтун Игорь
          • задача 1:
            • Зафиксировать согласованные условия в файле ОТК
      • Беларусь
        • Инициаторы:
          • Пирожников Павел
          • Гацолаева Наталья
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • Шепко Лия
        • Исполнитель 1:
          • Ковтун Игорь
          • задача 1:
            • Зафиксировать согласованные условия в файле ОТК
    • Остаток на кассах
      • выбор сотрудника из списка согласующих
        • Инициаторы:
          • Романов Александр
          • Рудник Алина
          • прикрепить файлы
        • Согласующий 1 (на выбор):
          • все сотрудники
        • Наблюдатели:
          • Демченко Елена
          • Вялкова Анна
    • Продажи по клиенту
      • выбор менеджера из списка согласующих
        • Инициаторы:
          • Романов Александр
          • Рудник Алина
          • Вялкова Анна
          • прикрепить файлы
        • Согласующий 1 (на выбор):
          • Макаренко Александр
          • Петрунин Кирилл
          • Стуков Павел
          • Федосенко Иван
          • Сыпков Станислав
          • Бондаренко Михаил
          • Чекальский Александр
          • Шевченко Виталий
          • Свирень Максим
          • Бояджан Сергей
          • Пирожников Павел
        • Наблюдатели:
          • Демченко Елена
          • Вялкова Анна
    • Проведенные расходы по менеджеру
      • выбор менеджера из списка согласующих
        • Инициаторы:
          • Вялкова Анна
          • прикрепить файлы
        • Согласующий 1 (на выбор):
          • Макаренко Александр
          • Петрунин Кирилл
          • Стуков Павел
          • Федосенко Иван
          • Сыпков Станислав
          • Бондаренко Михаил
          • Чекальский Александр
          • Шевченко Виталий
          • Свирень Максим
          • Бояджан Сергей
          • Пирожников Павел
        • Наблюдатели:
          • Демченко Елена
          • Вялкова Анна
    • Согласование удаленной работы
    • Смена условий автоматического согласования сделок
      • повысить лимит дебеторской задолженности от Владимира лично
        • Инициаторы:
          • Макаренко Александр
          • Петрунин Кирилл
          • Стуков Павел
          • Федосенко Иван
          • Сыпков Станислав
          • Бондаренко Михаил
          • Чекальский Александр
          • Шевченко Виталий
          • Свирень Максим
          • Бояджан Сергей
          • Пирожников Павел
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • нет
        • Исполнитель:
          • Шепко Лия
          • задача 1:
            • Внести изменения в таблицу "Условия автоматического согласования"
      • увеличить кол-во возможных долгов по документам, сумм этих документов, кол-во возможных дней просрочки оплаты
        • Инициаторы:
          • Макаренко Александр
          • Петрунин Кирилл
          • Стуков Павел
          • Федосенко Иван
          • Сыпков Станислав
          • Бондаренко Михаил
          • Чекальский Александр
          • Шевченко Виталий
          • Свирень Максим
          • Бояджан Сергей
          • Пирожников Павел
        • Согласующий 1:
          • Тищенко Владимир
        • Наблюдатели:
          • нет
        • Исполнитель:
          • Ковтун Игорь
          • задача 1:
            • Внести изменения в гугл-таблицу
    • Утверждение оплат безнал
      • Нужна ПП?
        • Да
          • Инициаторы:
            • Жуков Дмитрий
            • Свирень Максим
            • Танашевич Анифе
            • Шепко Лия
            • прикрепить файл (обяз)
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
            • Шепко Лия
          • Исполнитель:
            • Рудь Анфиса
            • задача 1:
              • Оплатить счет
            • задача 2:
              • Отправить ПП сотруднику
              • прикрепить файл
        • Нет
          • Инициаторы:
            • Жуков Дмитрий
            • Свирень Максим
            • Танашевич Анифе
            • Шепко Лия
            • прикрепить файл (обяз)
          • Согласующий 1:
            • Тищенко Владимир
          • Наблюдатели:
            • Демченко Елена
            • Вялкова Анна
            • Жуков Дмитрий
            • Сергатова Светлана
            • Шведов Никита
            • Романов Александр
            • Рудник Алина
            • Шепко Лия
          • Исполнитель:
            • Рудь Анфиса
            • задача 1:
              • Оплатить счет

    1. Структура репозитория (package map)

    telegram_service/
    ├── approvalflow/
    │   ├── FlowNode / BuildTree / FindNode         — модель дерева
    │   ├── DynamicTopic.go                          — тема категории, шаблоны, подписи
    │   └── registry.go                              — Registry, LoadFromDB, алиасы
    ├── state/
    │   └── state.go                                 — ApprovalFlowState, StateManager (Redis)
    ├── keyboard/
    │   ├── *.go (BuildApprovalMenu, BuildApprovalStep, BuildMultiSelectStep, ...)
    │   └── delegation_keyboard.go                   — 🆕 клавиатуры делегирования
    ├── handlers/approval/
    │   ├── handler.go                                — Handler, NewHandler, HandleMenu*, HandleCallback, HandleTextInput, HandleBackCallback, continueFlowAfterInput
    │   ├── decision.go                                — HandleDecision, handleDecision, processNextStepOrComplete
    │   ├── add_approver.go                            — доп. согласующие: добавление, список категорий/заявок
    │   ├── tasks.go                                   — задачи исполнителей, отзыв связанных задач
    │   ├── delegation.go                              — 🆕 делегирование обязанностей
    │   ├── helpers.go (submitApproval и прочие помощники — по смыслу; фактическое имя файла не фиксировано в этой сессии)
    │   └── revoke.go (performActualRevoke, HandleRevokeWithComment — по смыслу)
    ├── lib/internally/redis/                          — обёртка над Redis-клиентом
    └── ...
    
    core/lib/external/tgkit/
    └── client.go                                      — HTTP-клиент бота, прокси, ретраи
    
    proxy/
    └── main.go                                        — внешний прокси-сервис к Telegram Bot API
    
    scheduler/
    └── daily_reminder.go                               — ежедневная сводка (9:00 МСК, будни)
    

    2. Полная модель данных

    Ниже — все поля, встретившиеся за сессию, по каждой структуре. Где структура не была показана целиком — явно отмечено «частично известна».

    2.1. ApprovalTopicCategory

    type ApprovalTopicCategory struct {
        ID          *uint64
        Name        *string
        Code        *string
        Description *string
        IsActive    *bool
        CreatedAt   *time.Time
        UpdatedAt   *time.Time
    }
    

    Примеры реальных категорий, встретившихся в данных: sickleave («Больничный», ID=3), vacation («Отпуск», ID=2), timeoff («Отгул», ID=1). Алиас absence в Registry объединяет все три для целей проверки прав доступа в главном меню.

    2.2. ApprovalTopic (частично известна, поля из реального использования)

    type ApprovalTopic struct {
        ID           *uint64
        CategoryID   *uint64
        Name         *string
        Code         *string
        ApprovalMode *string   // "sequential" | "any_of"
    
        DurationType          *string   // single_day | multi_day | date_range | sl_single_day | sl_date_range
        TimeType              *string   // full_day | partial
        PaymentType           *string   // own_expense | vacation_account | official
        Template              *string   // текст-подсказка формата ввода, напр. "22.02.2022"
        FirstApproverRoleCode *string
    
        AllowsFile             *bool
        AllowApproverSelection *bool
        AllowObserverSelection *bool
        // 🆕 добавлено в этой сессии, по паттерну AllowObserverSelection:
        AllowDelegationSelection *bool
    
        RequireApproverComment   *bool
        RequiresFile             *bool
        RequireObserverSelection *bool
    
        IsActive     *bool
        CreatedAt    *time.Time
        UpdatedAt    *time.Time
        Instructions *string
        Category     *ApprovalTopicCategory   // populated-связь
    }
    

    Реальный пример из БД (тема id=19, категория «Больничный»):

    {
      "id": 19, "category_id": 3,
      "name": "Больничный — диапазон официальный",
      "code": "sickleave_date_range_full_day_official",
      "ApprovalMode": "sequential",
      "duration_type": "date_range", "time_type": "full_day",
      "payment_type": "official",
      "template": "22.02.2022 - 25.02.2022",
      "allows_file": true, "requires_file": false,
      "allow_approver_selection": false, "allow_observer_selection": false,
      "require_approver_comment": false, "require_observer_selection": false,
      "is_active": true
    }
    

    2.3. ApprovalFlowNode (дерево согласования)

    type ApprovalFlowNode struct {
        ID              *uint64
        ParentID        *uint64   // nil = корневой узел
        CategoryID      *uint64
        Key             string    // технический ключ, участвует в callback_data
        Label           string    // видимый текст кнопки / источник {{.key_label}}
        DataKey         string    // "" если не кнопка с данными
        DataValue       string
        IsInput         bool
        InputKey        string    // обычно "content"
        InputHint       string
        IsMultiSelect   bool
        MultiSelectKey  string
        MultiSelectValue string
        TopicCode       string    // опционально: ограничение видимости узла по правам
        MessageTemplate *string   // только на корневом узле
        CodeTemplate    *string   // только на корневом узле
        Children        []*ApprovalFlowNode  // заполняется BuildTree()
    }
    

    Инвариант: Key корневого узла (ParentID == nil) обязан совпадать с ApprovalTopicCategory.Code — иначе HandleCallback не найдёт начало дерева при нажатии кнопки раздела в главном меню (симптом в логах: node not found).

    2.4. Approval

    type Approval struct {
        ID            *uint64
        InitiatorID   *uint64
        TopicID       *uint64
        DepartmentID  *uint64
        Content       *string           // свободный текст, финальный ввод сотрудника
        Status        *string           // draft|pending|approved|rejected|revoked|revision
        Metadata      *string           // JSON: {"extra": {...}}
        RevisionCount int
        RevokedByID   *uint64
        RevokedAt     *time.Time
        CreatedAt     *time.Time
        UpdatedAt     *time.Time
    
        // populated-связи (не всегда присутствуют, зависит от запроса)
        Initiator  *User
        Topic      *ApprovalTopic
        Department *Department
        Steps      []*ApprovalStep
        Files      []*ApprovalFile
        RevokedBy  *User
    }
    

    Metadata — JSON вида {"extra": {"time_type": "full_day", "absence_type": "sickleave", "payment_type": "official", "duration_type": "sl_date_range", "open_date": "11.07.2026"}}. Обратите внимание: open_date присутствует не всегда и не является надёжным источником полного диапазона дат — единственный надёжный источник дат — свободнотекстовое поле Content (см. раздел про разбор дат в scheduler, §19).

    2.5. ApprovalStep

    type ApprovalStep struct {
        ID                    *uint64
        ApprovalID            *uint64
        ApproverID            *uint64
        StepOrder             *int
        Status                *string   // waiting|pending|approved|rejected|cancelled_any_of|cancelled
        ApproverNameSnapshot  *string   // ФИО на момент назначения — не меняется при делегации
        ActedAt               *time.Time
        Comment                *string
        CreatedAt              *time.Time
        UpdatedAt               *time.Time
    }
    

    ApproverNameSnapshot заполняется реальным исполнившим действие пользователем (actualExecutor.FullName) в момент решения — то есть после одобрения делегатом в снапшоте окажется имя делегата, а не исходного согласующего.

    2.6. ApprovalAdditionalApprover

    type ApprovalAdditionalApprover struct {
        ID          *uint64
        ApprovalID  *uint64
        ApproverID  *uint64
        AddedByID   *uint64
        Status      *string    // pending|approved|rejected
        Comment     *string
        ActedAt     *time.Time
    }
    

    Независим от ApprovalStep — у доп. согласующего нет собственной строки в ApprovalStep (источник одного из ключевых багов, §22).

    2.7. ApprovalTask и ApprovalTopicExecutor

    type ApprovalTask struct {
        ID              *uint64
        ApprovalID      *uint64
        ExecutorID      *uint64
        TaskText        *string
        Status          *string  // pending|waiting|completed|cancelled
        StepOrder       *int
        RequireFile     *bool
        TopicExecutorID *uint64
        CreatedAt       *time.Time
        CompletedAt     *time.Time
    }
    
    type ApprovalTopicExecutor struct {
        ID            *uint64
        TopicID       *uint64
        UserID        *uint64
        TargetSystem  *string   // "all" | "okb" | "1c" — фильтр по выбранной сотрудником системе
        TaskText      *string
        RequireFile   *bool
    }
    

    createTasksForApproval создаёт по одной задаче на каждую (система × исполнитель) комбинацию, где TargetSystem совпадает ("all" matчит любую систему). Первая созданная задача сразу pending, остальные waiting — активируются последовательно по мере выполнения предыдущей (activateNextTask).

    2.8. UserHierarchy

    type UserHierarchy struct {
        ID              *uint64
        SubordinateID   *uint64
        ApproverID      *uint64
        DepartmentID    *uint64
        TopicCategoryID *uint64   // взаимоисключающе с TopicID
        TopicID         *uint64
        Priority        int
    }
    

    Область действия правила определяется по заполненным полям: TopicID задан → правило только для этой темы; иначе TopicCategoryID задан → для всей категории; оба nil → глобальное правило («для всех тем согласований»).

    2.9. ApprovalDelegation (реальная структура, прислана пользователем)

    type ApprovalDelegation struct {
        ID          *uint64
        DelegatorID *uint64    // not null
        DelegateID  *uint64    // not null
        CategoryID  *uint64    // nullable
        TopicID     *uint64    // nullable
        ApprovalID  *uint64    // 🆕 nullable — привязка к заявке-отсутствию
        ValidUntil  *time.Time // not null
        IsActive    *bool      // not null, default true
        NotifiedDay *bool      // not null, default false
        Notified2h  *bool      // not null, default false
        CreatedAt   *time.Time
        UpdatedAt   *time.Time
    
        Delegator *User
        Delegate  *User
        Category  *ApprovalTopicCategory
        Topic     *ApprovalTopic
    }
    

    NotifiedDay/Notified2h указывают на существование отдельного, незадействованного в этой сессии механизма напоминаний об истечении делегирования (за день / за 2 часа до ValidUntil) — реализация этого механизма не входила в объём данной сессии.

    2.10. Прочие сущности (кратко, по факту использования)

    • ApprovalComment { ApprovalID, UserID, Comment }
    • ApprovalObserver / ApprovalAdditionalObserver { ApprovalID, UserID, AddedByID }
    • ApprovalFile { ApprovalID, TaskID(nullable), FileBase64, FileName, AttachedByID }
    • User { ID, TelegramID, Username, FullName, LanguageCode, DepartmentID, IsBlocked, IsFired }
    • Department { ID, Name, Code, Description, IsActive }

    3. Пакет approvalflow

    3.1. FlowNode.BuildTree() / FindNode(tree, key)

    Registry хранит плоский список узлов на категорию; BuildTree() превращает его в дерево по ParentID. FindNode — рекурсивный поиск по Key внутри уже построенного дерева (используется и в HandleCallback, и в HandleBackCallback, и в HandleTextInput при переходе к следующему уровню после текстового ввода).

    3.2. DynamicTopic

    type DynamicTopic struct {
        category *typescore.ApprovalTopicCategory
        nodes    []*typescore.ApprovalFlowNode
    
        codeTemplate    string
        messageTemplate string
    
        // 🆕 dataValueLabels: "data_key\x00data_value" -> Label узла с такими
        // DataKey/DataValue (или MultiSelectKey/MultiSelectValue).
        dataValueLabels map[string]string
    }
    

    NewDynamicTopic(category, nodes) — единственный проход по всем узлам: если узел корневой — забирает CodeTemplate/MessageTemplate; независимо от этого (без break, в отличие от исходной версии до правки) — если у узла есть Label и DataKey+DataValue (или MultiSelectKey+MultiSelectValue) — кладёт подпись в dataValueLabels под составным ключом data_key + "\x00" + data_value.

    applyMessageTemplate(tmpl, initiatorName, data):

    1. Копирует data (собранная карта data_key → data_value) в templateData.
    2. Добавляет initiator_name, topic_name.
    3. addLabelMappings(templateData, data) — старый захардкоженный список соответствий (region: crimea→«Крым» и т.п., branch: f1→«Ф1» и т.п. — полный список хардкода не воспроизведён дословно в этой документации, см. исходный файл DynamicTopic.go).
    4. addTreeLabelMappings(templateData, data) — 🆕 перекрывает значения из п.3, если для конкретного (key, value) есть узел дерева с Label. Для мультивыбора (значение вида "a,b") при отсутствии точного совпадения разбивает по запятой и подписывает каждую часть отдельно, объединяя через ", ".
    5. Выполняет Go-шаблон (text/template), при ошибке парсинга/выполнения — defaultMessage(initiatorName, data) (запасной вариант).

    TopicCodeFromCollected(collected) — если code_template непустой, выполняет его как Go-шаблон над collected; иначе — запасной вариант: сортирует ключи collected алфавитно и склеивает значения через _.

    3.3. Registry

    type Registry struct {
        topics             map[string]Topic       // код темы → DynamicTopic
        order               []string
        topicCodeToCategory map[string]string      // алиасы
    }
    

    NewRegistry(ipc) — единственное место, где заполняются алиасы:

    r.topicCodeToCategory["timeoff"]   = "absence"
    r.topicCodeToCategory["vacation"]  = "absence"
    r.topicCodeToCategory["sickleave"] = "absence"
    

    LoadFromDB(ctx) — пересобирает topics/order с нуля, не трогает topicCodeToCategory — алиасы, заданные в NewRegistry, переживают любое количество перезагрузок автоматически.

    watchRegistryReload() (горутина, запускается в NewHandler) — опрашивает Redis-ключ approval_flow:version каждые 5 секунд; при изменении значения вызывает LoadFromDB. Админ-панель обязана вызывать bumpFlowVersion() после любой мутации дерева/тем/категорий — иначе бот продолжит работать со старой версией дерева до перезапуска.


    4. Пакет state

    4.1. ApprovalFlowState (реальная структура, прислана пользователем)

    type ApprovalFlowState struct {
        TopicCode               string
        NodeKey                 string
        Collected               map[string]string
        AwaitInput               bool
        InputKey                 string
        History                  []string
        AwaitFileInput            bool
        Files                     []FileAttachment
        FileStatusMsgID           int
        FilesStepCompleted        bool
        MultiSelected             map[string][]string
        AwaitMultiSelectConfirm   bool
        MultiSelectNodeKey        string
    
        AwaitFirstApproverSelection bool
        SelectedFirstApproverID     uint64
    
        AwaitApproverSelection bool
        SelectedApproverID     uint64
    
        AwaitObserverSelection bool
        ObserverSelectionDone  bool
        SelectedObserverIDs    []uint64
    
        // Делегирование
        AwaitDelegationChoice   bool
        AwaitDelegateSelection  bool
        DelegationSelectionDone bool
        SelectedDelegateID      uint64
    }
    
    type FileAttachment struct {
        Base64    string
        FileName  string
        MessageID int
    }
    

    4.2. StateManager

    • Redis-ключ: approval_flow:{telegramID}.
    • TTL: 30 минут (stateTTL) — если пользователь бросил диалог дольше этого времени, состояние теряется, при попытке продолжить бот просит начать заново («⚠️ Состояние утеряно. Начните заново.»).
    • Get трактует отсутствие ключа как штатную ситуацию (nil, nil), не как ошибку.
    • Помимо ApprovalFlowState, StateManager управляет и другими, отдельными видами состояний, встретившимися по коду: AddApproverFlowState (флоу добавления доп. согласующего, ключи SetAddApprover/GetAddApprover/ ClearAddApprover), AttachmentSession (сессия прикрепления файла к задаче, поля ApprovalID, TaskID, Files, Stage), состояния комментариев (GetCommentFlow/GetCommentReply, Stage: "await_text") — их полные структуры не входили в состав присланных в этой сессии файлов, здесь зафиксирован только факт существования и используемые поля.

    5. Handler — инициализация

    type Handler struct {
        ipc      *types.InternalProviderControl
        Registry *Registry
        States   *state.StateManager
    }
    
    func NewHandler(ipc *types.InternalProviderControl) *Handler {
        h := &Handler{
            ipc:      ipc,
            Registry: NewRegistry(ipc),
            States:   state.NewStateManager(ipc.RedisClient),
        }
        ctx, cancel := context.WithTimeout(context.Background(), variables.ContextTimeoutLong)
        if err := h.Registry.LoadFromDB(ctx); err != nil {
            logrus.Errorf("Failed to load topics from DB: %v", err)
        }
        cancel()
    
        go h.watchRegistryReload()   // см. §3.3
        return h
    }
    

    Обратите внимание: ошибка первичной загрузки дерева только логируется, не приводит к остановке бота — при полностью пустом/ неудачно загруженном реестре бот запустится, но не сможет провести ни одного согласования до успешной перезагрузки через watchRegistryReload.

    5.1. Меню и подменю

    • HandleMenu(bot, chatID, telegramID) → HandleMenuPage(..., page=0, prevMessageID=0).
    • HandleMenuPage: находит пользователя, вызывает getAllowedTopicCodes, фильтрует h.Registry.All() по разрешённым кодам, строит клавиатуру keyboard.BuildApprovalMenu(visibleTopics, page). Если разрешённых тем нет — сообщение «У вас нет доступных тем для согласования» без клавиатуры.
    • HandleInitiatorSubmenu / HandleApproverSubmenu / HandleObserverSubmenu — статичные подменю без БД-логики, просто разные клавиатуры (BuildInitiatorSubmenu/BuildApproverSubmenu/BuildObserverSubmenu).

    5.2. getAllowedTopicCodes(ctx, user)

    Возвращает map[string]bool. Логика:

    1. Тянет разрешения (Permission, action="create") пользователя.
    2. Если разрешений нет вообще — возвращает пустую карту (доступа нет никуда).
    3. Для каждой темы из реестра ищет категорию по коду; если это код "absence" (агрегирующий алиас) — отдельно проверяет три дочерние категории (timeoff/vacation/sickleave) — по прямому разрешению на категорию или по разрешению на конкретную тему внутри неё.
    4. Для остальных (не-absence) тем — обычная проверка: разрешение на категорию целиком, либо на конкретную тему.

    6. Прохождение дерева

    6.1. HandleCallback(bot, cq, topicCode, nodeKey)

    Формат входящего callback_data: appr:{topicCode}:{nodeKey}.

    1. Отвечает на callback, удаляет предыдущее сообщение с кнопками.
    2. topic := Registry.Get(topicCode)  — если не найдено, warn-лог и return.
    3. flowState := States.Get(telegramID)
       isNew := flowState == nil
       если isNew — создаёт новый ApprovalFlowState{TopicCode, NodeKey, Collected: {}}
    4. tree := topic.BuildTree(); node := FindNode(tree, nodeKey)
       если nil — warn-лог "node not found", return (см. инвариант §2.3 про Key корня)
    5. если НЕ isNew — History = append(History, flowState.NodeKey)  (пуш текущего перед переходом)
    6. если node.DataKey != "" — Collected[node.DataKey] = node.DataValue
    7. если у node нет детей И это не IsInput — warn "leaf node without IsInput", return
       (защита от некорректно построенного дерева)
    8. Особый случай: у узла РОВНО один ребёнок, и это IsInput —
       пропускает промежуточный экран, сразу переходит в режим ожидания
       текста: AwaitInput=true, InputKey=inputNode.InputKey, NodeKey=nodeKey (!),
       показывает InputHint, клавиатура BuildApprovalInputPrompt(topicCode).
       (NodeKey указывает на РОДИТЕЛЯ input-узла, не на сам input — важно
       для последующей логики в HandleTextInput, см. §6.2)
    9. Иначе: NodeKey = nodeKey, State.Set(...)
       visibleChildren := filterNodesByAccess(ctx, telegramID, node.Children)
       если пусто после фильтрации — "⚠️ У вас нет доступа к подкатегориям
       этого раздела.", States.Clear (!) — весь диалог сбрасывается.
    10. Детект мультивыбора: если у первого видимого ребёнка IsMultiSelect —
        переходит в режим AwaitMultiSelectConfirm, MultiSelectNodeKey=nodeKey,
        клавиатура BuildMultiSelectStep(..., nil, nil).
    11. Иначе — обычная клавиатура BuildApprovalStep(topicCode, visibleChildren).
    

    6.2. HandleTextInput(bot, msg) bool

    Вызывается на любое текстовое сообщение от пользователя (не callback), возвращает bool — обработано ли сообщение этим хендлером (если false — сообщение не относится к диалогу согласования, дальше по цепочке обработчиков может пойти что-то другое).

    1. Проверка Redis-ключа appr_approve_comment_pending:{telegramID} —
       если есть, это ввод ОБЯЗАТЕЛЬНОГО комментария к решению (см. §12.4),
       не к созданию заявки. Удаляет ключ, вызывает HandleDecisionWithChat
       с approved=true и введённым текстом. return true.
    2. Проверка состояния комментариев к заявке (GetCommentFlow) —
       делегирует в HandleCommentTextInput, если Stage == "await_text".
    3. Проверка состояния ответа на комментарий (GetCommentReply) —
       аналогично, HandleCommentReplyInput.
    4. flowState := States.Get(telegramID) — если nil или !AwaitInput —
       return false (не наш случай).
    5. Collected[flowState.InputKey] = msg.Text;  AwaitInput = false
    6. Особый случай "текст → сразу следующий уровень кнопок, минуя лишний
       Set+показ":
         topic := Registry.Get(flowState.TopicCode)
         parentNode := FindNode(tree, flowState.NodeKey)  ← это тот самый
           родитель input-узла, сохранённый в п.8 §6.1
         если у parentNode ровно один ребёнок IsInput и у ЭТОГО inputNode
         ЕСТЬ дети — сразу показывает их (с фильтрацией по правам,
         NodeKey переключается на inputNode.Key), return true.
    7. Иначе (input — реальный лист дерева, дальше веток нет):
         States.Set(...); continueFlowAfterInput(...) — переход к
         дополнительным шагам (§7). return true.
    

    6.3. HandleBackCallback(bot, cq, topicCode)

    • Если flowState.AwaitFileInput — сбрасывает режим файлового ввода, удаляет статусное сообщение (FileStatusMsgID), если было.
    • Если flowState.AwaitInput — сбрасывает режим текстового ввода.
    • Если History пуста — полный сброс состояния, HandleMenu (возврат к выбору темы).
    • Иначе — pop() из History, FindNode по этому ключу, показывает его детей (с фильтрацией по правам). Важно: при возврате назад фильтр мультивыбора/input повторно не применяется — всегда показывает обычную клавиатуру кнопок, даже если по факту это узел с одним input-ребёнком. (Асимметрия с вперёд-навигацией, зафиксирована как наблюдение, не как заведомый баг — не тестировалось отдельно в этой сессии.)

    7. continueFlowAfterInput — цепочка дополнительных шагов

    Единая точка, куда сходятся: конец ввода текста (§6.2), любой из дополнительных шагов ниже (каждый в конце сам вызывает эту функцию снова, чтобы проверить следующий шаг в цепочке).

    func (h *Handler) continueFlowAfterInput(bot, ctx, chatID, telegramID, flowState) {
        topic := Registry.Get(flowState.TopicCode)
        если topic == nil { States.Clear(); return }
    
        topicCode, err := topic.TopicCodeFromCollected(flowState.Collected)
        если err != nil { warn-лог; States.Clear(); return }
    
        approvalTopic := GetApprovalTopicByCodeDB(ctx, topicCode)
        // approvalTopic может остаться nil при ошибке — часть проверок ниже
        // построена как `approvalTopic != nil && approvalTopic.X != nil && *approvalTopic.X`,
        // то есть при ошибке получения темы бот тихо пропускает ВСЕ
        // дополнительные шаги и уходит сразу к финализации.
    
        // --- Шаг 1: файл ---
        если approvalTopic != nil && !flowState.FilesStepCompleted:
            requiresFile := RequiresFile
            allowsFile   := AllowsFile
            isOfficial   := Collected["payment_type"] == "official"
            needFileStep := requiresFile || allowsFile || isOfficial
            если needFileStep && !AwaitFileInput:
                AwaitFileInput = true; FilesStepCompleted = false
                States.Set(...)
                showFileStatus(bot, chatID, telegramID, flowState, required=requiresFile||isOfficial)
                return
    
        // --- Шаг 2: первый согласующий по роли ---
        если approvalTopic != nil && FirstApproverRoleCode не пусто
            && SelectedFirstApproverID == 0 && !AwaitFirstApproverSelection:
            AwaitFirstApproverSelection = true; States.Set(...)
            показать список пользователей с этой ролью (showFirstApproverSelection)
            return
    
        // --- Шаг 3: ручной выбор согласующего ---
        если approvalTopic != nil && AllowApproverSelection
            && SelectedApproverID == 0 && !AwaitApproverSelection:
            AwaitApproverSelection = true; States.Set(...)
            showApproverSelection(...)   // §8
            return
    
        // --- Шаг 4: наблюдатели ---
        если approvalTopic != nil && AllowObserverSelection && !ObserverSelectionDone:
            ObserverSelectionDone = true; AwaitObserverSelection = true; States.Set(...)
            showObserverSelection(...)   // §9
            return
    
        // --- Шаг 5 (🆕): делегирование ---
        если approvalTopic != nil && AllowDelegationSelection && !DelegationSelectionDone:
            AwaitDelegationChoice = true; States.Set(...)
            showDelegationChoice(...)    // §10
            return
    
        // --- Финализация ---
        States.Clear(ctx, telegramID)
        finalizeAndSubmit(bot, ctx, chatID, telegramID, flowState)
    }
    

    Порядок фиксирован и важен: файл → первый согласующий по роли → ручной выбор согласующего → наблюдатели → делегирование → отправка. Каждый шаг проверяет соответствующий флаг ApprovalTopic независимо — можно включить любую комбинацию на конкретной теме, они не исключают друг друга.


    8. Выбор согласующего

    8.1. Ручной выбор (showApproverSelection)

    getAvailableApproversForTopic(ctx, userID, departmentID, approvalTopic):
        hierarchies := GetUserHierarchyListDB(Filtering: {SubordinateID: userID, DepartmentID: departmentID})
        approverIDs := {}
        для каждой hier:
            если hier.TopicID == approvalTopic.ID → approverIDs[hier.ApproverID] = true; continue
            если hier.TopicCategoryID == approvalTopic.CategoryID → approverIDs[hier.ApproverID] = true
        подгружает User по каждому approverID
    

    ⚠️ Обратите внимание: здесь фильтрация по иерархии идёт только по TopicID/TopicCategoryID — глобальные правила иерархии (оба поля nil в UserHierarchy) не учитываются этой функцией. Это согласуется с общей логикой (глобальное правило — это правило «по умолчанию для всех тем», а не «выбор из списка вариантов»), но стоит иметь в виду при диагностике «почему в списке выбора согласующего никого нет», если для сотрудника настроено только глобальное правило.

    Если список пуст — «⚠️ Нет доступных согласующих для этой темы.», States.Clear.

    HandleApproverSelectionCallback(bot, cq, approverID) — сохраняет SelectedApproverID, AwaitApproverSelection=false, показывает подтверждение с ФИО, вызывает continueFlowAfterInput.

    HandleApproverSelectionPageCallback — пагинация того же списка, перестраивает GetApprovalTopicByCodeDB заново по актуальному TopicCodeFromCollected (не кэширует).

    8.2. Первый согласующий по роли (showFirstApproverSelection)

    Упоминается в continueFlowAfterInput, вызывается с параметрами (bot, ctx, chatID, telegramID, roleCode, roleLabel) — полная реализация не входила в присланные в этой сессии файлы; известно, что результат сохраняется в flowState.SelectedFirstApproverID, и что при последующей сборке approvers в submitApproval (§11) этот согласующий вставляется первым в списке, то есть шагом 0 в последовательности, перед автоматически определёнными через иерархию.


    9. Наблюдатели

    9.1. showObserverSelection

    observers := getAvailableObservers(ctx)   // все пользователи с ролью "observer"
    isRequired := RequireObserverSelection == true
    
    если observers пуст:
        если isRequired:
            "⚠️ Для этой темы обязательно выбрать наблюдателя, но в системе
             нет пользователей с ролью наблюдателя. Обратитесь к администратору."
            AwaitObserverSelection=false; ObserverSelectionDone=false (!) — специально
            НЕ помечается выполненным, чтобы не позволить создать заявку в
            нарушение обязательного требования; States.Set; return (диалог
            застревает здесь до вмешательства администратора)
        иначе:
            AwaitObserverSelection=false; ObserverSelectionDone=true
            States.Clear(); finalizeAndSubmit(...)   — пропускает шаг, сразу отправляет
        return
    
    иначе показывает мультивыбор (BuildObserverSelectionMenu(observers,
    SelectedObserverIDs, page=0, isRequired)), текст различается в
    зависимости от isRequired (для обязательного — с уточнением
    «Выберите менеджера, чья это отгрузка», это доменная формулировка,
    специфичная для конкретных тем с обязательными наблюдателями).
    

    9.2. Остальные хендлеры наблюдателей

    • HandleObserverToggleCallback — toggle одного ID в SelectedObserverIDs (добавить, если не было; убрать, если было), перерисовывает клавиатуру через EditMessageReplyMarkup (не новое сообщение).
    • HandleObserverPageCallback — пагинация, та же логика isRequired, пересчитанная заново по текущему flowState.
    • HandleObserverConfirmCallback — если isRequired и выбор пуст — показывает alert через callback («⚠️ Выберите хотя бы одного наблюдателя»), не закрывает экран выбора. Иначе — States.Clear, finalizeAndSubmit.
    • getAvailableObservers — GetUsersByRoleCodeDB(ctx, "observer").
    • saveSelectedObservers(ctx, approvalID, addedByID, observerIDs) — вызывается из submitApproval после создания заявки, создаёт по одной строке ApprovalAdditionalObserver на каждого выбранного.
    • getAdditionalObserversForApproval(ctx, approvalID) — используется админкой/карточкой заявки для отображения списка наблюдателей.

    10. Делегирование обязанностей (детально)

    10.1. showDelegationChoice

    Простой экран Да/Нет (keyboard.BuildDelegationChoiceMenu(), callback_data appr_deleg_yes/appr_deleg_no), с пояснением, что делегирование активируется только после согласования этой заявки и отменяется при её отзыве.

    10.2. HandleDelegationChoiceCallback(bot, cq, wantsDelegate)

    • wantsDelegate == false: DelegationSelectionDone=true, сообщение «обязанности не делегируются», continueFlowAfterInput.
    • wantsDelegate == true:
      • getAvailableDelegateCandidates — все активные сотрудники (не заблокированы, не уволены, есть TelegramID), кроме самого пользователя.
      • Если кандидатов нет — сообщение «Нет доступных сотрудников для делегирования», DelegationSelectionDone=true, идёт дальше без делегирования.
      • Иначе — AwaitDelegateSelection=true, показывает BuildDelegateSelectionMenu(candidates, page=0).

    10.3. HandleDelegateSelectionCallback(bot, cq, delegateID)

    Важно: здесь запись в БД НЕ создаётся. Только сохраняет выбор (SelectedDelegateID), показывает предварительный расчёт delegationValidUntil для UX («Ориентировочно до: ...») и вызывает continueFlowAfterInput. Реальная запись появляется позже, вместе с самой заявкой (§11.4) — потому что flowState в Redis живёт всего 30 минут (§4.2), а решение по заявке может занять дни, поэтому долгосрочные данные (делегирование) не могут храниться только в flowState — они переносятся в постоянное хранилище (БД) при первой возможности, то есть в момент отправки заявки.

    10.4. delegationValidUntil(collected map[string]string) (time.Time, bool)

    Проходит по всем значениям flowState.Collected (не только по ключу "content" — на случай нестандартной настройки дерева, где дата могла осесть под другим ключом), ищет через regexp (\d{1,2})[.,](\d{1,2})\.(\d{4}) (запятая вместо точки после дня учтена специально — реальная опечатка встречалась в проде). Берёт максимальную найденную дату, время 23:59:59. Если дат не найдено — возвращает now + 30 дней и false (вызывающий код логирует warning).

    10.5. createPendingDelegation / activatePendingDelegation / deactivatePendingDelegation

    См. документ 1 (версия 1.0), раздел 5 — логика не изменилась, здесь фиксируется дополнительно точная последовательность вызовов:

    Функция Вызывается из Момент
    createPendingDelegation submitApproval сразу после создания Approval и сохранения файлов, до создания ApprovalStep
    activatePendingDelegation processNextStepOrComplete в ветке финального approved (НЕ в ветке передачи следующему шагу)
    deactivatePendingDelegation performActualRevoke между UpdateApprovalDB (статус → revoked) и handleRevokeCancellationTask

    Каждая из активации/деактивации сначала делает GetApprovalDelegationsListDB(Filtering: {ApprovalID: approval.ID}) — если пусто, тихо ничего не делает (делегирование не настраивалось при подаче этой заявки).


    Техническая документация: Telegram-бот (детальная версия) — часть 3

    Продолжение частей 1–2. Разделы 11–23.


    11. submitApproval построчно

    func (h *Handler) submitApproval(bot, ctx, chatID, telegramID, flowState) {
    
    1. Пользователь и тема. initiator := GetUserByTelegramIDDB; topic := Registry.Get(flowState.TopicCode); topicCode := topic.TopicCodeFromCollected(Collected); approvalTopic := GetApprovalTopicByCodeDB(ctx, topicCode). Если approvalTopic == nil — сообщение об ошибке пользователю, States.Clear, заявка не создаётся.

    2. Формирование Content и Metadata. Из flowState.Collected в Approval.Content уходит только значение под ключом content (если есть). Все остальные пары key: value из Collected уходят в metadata.extra как есть (сериализуется в JSON).

    3. Создание Approval — статус сразу pending (не draft), InitiatorID, TopicID = approvalTopic.ID, DepartmentID = initiator.DepartmentID, CreatedAt = now.

    4. Сохранение файлов (flowState.Files, если были) — ApprovalFile на каждый, AttachedByID = initiator.ID, TaskID = nil (файлы уровня заявки, не задачи — в отличие от файлов, прикрепляемых исполнителем к конкретной задаче, см. §15). Подгружает файлы обратно в created.Files для последующих уведомлений (чтобы не делать отдельный запрос).

    5.  createPendingDelegation(ctx, initiator, flowState, created) — см. §10.5. Вызывается безусловно; сама функция не делает ничего, если flowState.SelectedDelegateID == 0.

    6. Определение списка согласующих (var approvers []*User):

      • Если flowState.SelectedApproverID != 0 (ручной выбор, §8.1) — единственный согласующий, весь дальнейший блок иерархии пропускается.
      • Иначе: GetUserHierarchyListDB(Filtering: {SubordinateID: initiator.ID, DepartmentID: initiator.DepartmentID}), из них отбираются подходящие под тему записи (по тому же принципу TopicID → TopicCategoryID → глобальное — в этой ветке уже включая глобальные правила, в отличие от §8.1, где глобальные не участвуют).
      • Если flowState.SelectedFirstApproverID != 0 (§8.2) — вставляется первым элементом списка, перед определёнными через иерархию (дедупликация по ID, если случайно совпал).
    7. Группировка по приоритету (priorityGroups) — сортировка найденных UserHierarchy по Priority по возрастанию, элементы с одинаковым Priority попадают в одну группу.

      • ApprovalMode == "any_of": все согласующие (из всех групп) помещаются в группу 0 — любой один может решить за всех.
      • Иначе ("sequential", по умолчанию): группы обрабатываются по порядку индекса — сначала все из группы 0 параллельно, затем группа 1, и т.д. (в текущей реализации внутри одной группы больше одного согласующего на практике не наблюдалось, но код группировки это допускает).
    8. Создание ApprovalStep — по одному на каждого согласующего. Первая группа (индекс 0) сразу получает Status = "pending", остальные группы — "waiting". ApproverNameSnapshot заполняется сразу по имени назначенного (не делегата — см. §2.5, снапшот переписывается только в момент решения).

    9. Наблюдатели — saveSelectedObservers(ctx, created.ID, initiator.ID, flowState.SelectedObserverIDs) (§9.2), если список непуст.

    10. Уведомление контролёров о создании новой заявки (см. §17.5).

    11. Уведомление согласующих первой группы — для каждого: сначала проверка активного делегирования по теме/категории (GetActiveDelegationForUser-подобная логика, тот же приоритет TopicID → CategoryID, что и в поиске согласующих) — если есть, сообщение о новой заявке уходит делегату, а не исходному согласующему; в остальных случаях — обычному согласующему.

    12. Сообщение инициатору — подтверждение отправки, с итоговым текстом (через topic.FormatApproverMessage/аналог), список назначенных согласующих первой группы.

    13. States.Clear — на этом этапе уже вызван из continueFlowAfterInput перед вызовом finalizeAndSubmit (см. §7), повторного вызова здесь не требуется.


    12. handleDecision (решение согласующего)

    12.1. Определение контекста вызова

    func (h *Handler) handleDecision(bot, ctx, chatID, telegramID, approvalID, approved bool, comment string) {
        approval := GetApprovalByIDDB(ctx, approvalID)
        currentUser := GetUserByTelegramIDDB(ctx, telegramID)
    
        // Проверка: это доп. согласующий?
        additionalRecord := найти pending ApprovalAdditionalApprover для
            (ApprovalID: approvalID, ApproverID: currentUser.ID)
        isAdditionalApprover := additionalRecord != nil
    
        mainStep := GetActiveStepDB(ctx, approvalID)  // текущий активный ОСНОВНОЙ шаг, вне зависимости от того, кто сейчас решает
    

    12.2. Ветка «дополнительный согласующий»

    если isAdditionalApprover:
        если approved:
            additionalRecord.Status = "approved"; UpdateApprovalAdditionalApproverDB
            actualExecutorID := currentUser.ID  // 🩹 см. §22, п.3 — раньше сюда
                                                  // ошибочно попадал не тот ID
            если allAdditionalApproved(ctx, approvalID) И mainStep.Status == "approved":
                processNextStepOrComplete(ctx, bot, approval, mainStep, actualExecutorID)
                // 🩹 §22 п.1: раньше вместо mainStep сюда передавался результат
                // findStepForCurrentApprover(currentUserID) — всегда nil для
                // доп. согласующего (у него нет строки в ApprovalStep, есть
                // только в ApprovalAdditionalApprover) → заявка «зависала»
            иначе:
                // ждём либо основного согласующего, либо остальных доп.
                уведомление доп. согласующему "Ваше решение принято, заявка
                продолжит движение после решения остальных участников"
        иначе (rejected):
            additionalRecord.Status = "rejected"; UpdateApprovalAdditionalApproverDB
            approval.Status = "rejected"; UpdateApprovalDB
            // отклонение ЛЮБОГО доп. согласующего — блокирующее для всей заявки,
            // в отличие от основной последовательности, где при any_of
            // достаточно одного одобрения
            уведомления инициатору, остальным согласующим (заявка закрыта)
        return
    

    12.3. Ветка «основной согласующий»

    иначе (не доп. согласующий):
        если approved:
            если RequireApproverComment == true И mainStep.Comment пуст И comment == "":
                // запрашиваем комментарий отдельным сообщением, СОХРАНЯЕМ
                // решение "в подвешенном" виде через Redis-ключ
                // appr_approve_comment_pending:{telegramID} = approvalID
                // (см. §6.2, п.1 — HandleTextInput ловит следующий текстовый
                // ввод и вызывает HandleDecisionWithChat(approved=true, text))
                return
    
            mainStep.Status = "approved"; mainStep.Comment = comment;
            mainStep.ActedAt = now; mainStep.ApproverNameSnapshot = currentUser.FullName
            UpdateApprovalStepDB
    
            actualExecutorID := currentUser.ID
            если allAdditionalApproved(ctx, approvalID):
                processNextStepOrComplete(ctx, bot, approval, mainStep, actualExecutorID)
            иначе:
                // основной шаг одобрен, но есть неотвеченные доп. согласующие —
                // заявка НЕ двигается дальше, ждём их
        иначе (rejected):
            mainStep.Status = "rejected"; ...; UpdateApprovalStepDB
            если approval ApprovalMode == "any_of":
                // отменяет ОСТАЛЬНЫЕ шаги той же группы приоритета
                для каждого другого step в той же priority-группе со
                статусом pending/waiting:
                    step.Status = "cancelled_any_of"; UpdateApprovalStepDB
                    уведомление этому согласующему "участие больше не требуется"
            approval.Status = "rejected"; UpdateApprovalDB
            уведомления инициатору, наблюдателям, директорам
    

    12.4. Обязательный комментарий (детали флоу)

    Redis-ключ appr_approve_comment_pending:{telegramID} хранит approvalID (строкой). При следующем текстовом сообщении от этого пользователя (независимо от того, есть ли активный ApprovalFlowState — проверка в HandleTextInput идёт первой, до проверки flowState, см. §6.2 п.1) — ключ удаляется, вызывается HandleDecisionWithChat(bot, chatID, telegramID, approvalID, approved=true, comment=msg.Text), которая и приводит к обычной ветке §12.3 уже с непустым comment.


    13. processNextStepOrComplete

    func (h *Handler) processNextStepOrComplete(ctx, bot, approval, currentStep, actualExecutorID uint64) {
        nextStep := следующий ApprovalStep этой заявки с наименьшим
                    StepOrder среди тех, что ещё Status == "waiting"
                    (то есть первый шаг следующей приоритетной группы)
    
        если nextStep != nil:
            // --- Заявка ПЕРЕДАНА следующему согласующему, ещё НЕ одобрена целиком ---
            nextStep.Status = "pending"; UpdateApprovalStepDB
            // проверка делегирования следующего согласующего — та же логика,
            // что при первичной отправке (§11 п.11)
            уведомление следующему согласующему (или его делегату)
            return  // ⚠️ activatePendingDelegation НЕ вызывается здесь —
                    // делегирование активируется только при полном
                    // одобрении, см. ниже
    
        // --- nextStep == nil: заявка ПОЛНОСТЬЮ одобрена ---
        approvedStatus := "approved"
        approval.Status = &approvedStatus
        UpdateApprovalDB(ctx, nil, approval)
    
        // 🆕 Активация делегирования — см. §10.5
        activatePendingDelegation(ctx, approval)
    
        lastStepIsDelegate := currentStep.ApproverID != nil &&
                               *currentStep.ApproverID != actualExecutorID
        // ^ используется для формулировки уведомлений: "решение принято
        // делегатом Х вместо назначенного Y" против обычного "одобрено Y"
    
        createTasksForApproval(ctx, bot, approval)   // §14, ApprovalTopicExecutor → ApprovalTask
        уведомление инициатору (одобрено)
        уведомления директорам, подписанным на категорию
        уведомления наблюдателям
    }
    

    14. Дополнительные согласующие (add_approver.go)

    14.1. HandleAddApproverFromApproval(bot, chatID, telegramID, approvalID)

    • Проверяет права добавляющего (должен сам быть согласующим этой заявки — основным или дополнительным).
    • Показывает список кандидатов, исключая уже назначенных (и основных, и дополнительных).

    14.2. getApproverCategoriesWithPendingApprovals (🩹 переписан в этой сессии)

    Было: проверяло только topic-scoped иерархию (пропускало правила, заданные на уровне категории или глобально), и если у пользователя была хоть одна pending-запись где угодно — ошибочно помечало сразу все категории как имеющие ожидающие заявки (некорректная агрегация).

    Стало: переписано по образцу соседней рабочей функции getPendingApprovalsByCategory — категории определяются напрямую из фактических pending-шагов пользователя (ApprovalStep + ApprovalAdditionalApprover со статусом pending, у которых сама заявка ещё pending), без обращения к иерархии вообще — иерархия для этой задачи была лишним, ошибочным уровнем косвенности.


    15. Задачи исполнителей (tasks.go)

    Полный список хендлеров и их назначение:

    Функция Назначение
    HandleMyTasks Список pending-задач текущего исполнителя (проверка роли через IsExecutorDB)
    sendTaskCard Карточка одной задачи: инициатор, тема, текст заявки, дата, текст задачи, прикреплённые именно к этой задаче файлы (TaskID совпадает), секция комментариев
    HandleCompleteTask Проверка RequireFile (если файл обязателен и не прикреплён — блокирует завершение, просит прикрепить), Status → "completed", CompletedAt = now, activateNextTask, уведомление инициатору (включая текст самой задачи — 🩹 см. §22 последняя строка)
    activateNextTask Следующая задача этой же заявки с StepOrder = завершённая.StepOrder + 1 и статусом waiting → переводит в pending, уведомляет её исполнителя
    handleRevokeCancellationTask Вызывается при отзыве заявки: отменяет pending/waiting ApprovalStep/ApprovalAdditionalApprover; по каждой задаче — если completed → createReversalTask, если pending → askExecutorAboutChanges, если waiting → просто cancelled с уведомлением
    createReversalTask Новая задача «Обратить изменения по заявке #N», тот же исполнитель, Status = pending сразу
    askExecutorAboutChanges Вопрос исполнителю Да/Нет «вносили ли уже изменения» через BuildRevokeTaskConfirmation
    HandleRevokeTaskNoChanges / HandleRevokeTaskHasChanges Обработка ответа: первое — просто cancelled; второе — создаёт reversal-задачу (§ выше)
    HandleExecutorAttachStart Начало сессии прикрепления файла к задаче (state.AttachmentSession)

    16. Отзыв заявок

    • HandleRevokeWithComment — точка входа с комментарием причины отзыва.
    • Правила, кто может отозвать:
      • pending/revision — инициатор или директор.
      • approved, отзывает не-директор-инициатор — startApprovalRevocationRequest — отдельный флоу запроса согласия у всех, кто участвовал в согласовании (упоминается как существующий механизм; полная реализация не входила в объём этой сессии).
      • approved, отзывает директор — сразу, без запроса согласия.
    • performActualRevoke — единственное место, где Approval.Status реально становится "revoked":
      approval.Status = "revoked"; RevokedByID = revoker.ID; RevokedAt = nowUpdateApprovalDBdeactivatePendingDelegation(ctx, approval)   // 🆕 §10.5handleRevokeCancellationTask(bot, ctx, approval)  // §15уведомления инициатору, директорам, согласующим, наблюдателям
      

    17. Уведомления

    Список типов уведомлений, встретившихся по коду (точные сигнатуры Notifier-методов не были показаны как единый файл в этой сессии — здесь зафиксированы смысловые точки отправки, привязанные к конкретным функциям):

    # Кому Когда Откуда
    1 Согласующему первой группы (или его делегату) Заявка создана submitApproval
    2 Инициатору Заявка отправлена (подтверждение) submitApproval
    3 Контролёрам категории Заявка создана submitApproval
    4 Следующему согласующему (или делегату) Предыдущий шаг одобрен, есть следующий processNextStepOrComplete
    5 Инициатору Заявка полностью одобрена processNextStepOrComplete
    6 Директорам, подписанным на категорию Заявка полностью одобрена processNextStepOrComplete
    7 Наблюдателям Заявка полностью одобрена processNextStepOrComplete
    8 Инициатору, наблюдателям, директорам Заявка отклонена handleDecision (rejected-ветки)
    9 Остальным согласующим той же any_of-группы Их участие больше не требуется handleDecision
    10 Исполнителю следующей задачи Предыдущая задача выполнена activateNextTask
    11 Инициатору Исполнитель завершил задачу (включая текст задачи) HandleCompleteTask
    12 Исполнителю Заявка отозвана — просьба обратить/подтвердить изменения handleRevokeCancellationTask
    13 Инициатору, директорам, согласующим, наблюдателям Заявка отозвана performActualRevoke

    18. Устойчивость бота

    • buildHTTPClient — сборка http.Client для Telegram Bot API, подключает CustomAPIProxyTransport, если задан proxyCfg (🩹 ранее конфиг прокси передавался, но фактически не использовался при сборке транспорта — исправлено).
    • Ретраи на старте — при недоступности Telegram API во время запуска бот повторяет попытку подключения вместо немедленного падения (конкретное число попыток/интервал не зафиксированы в объёме сессии).
    • Отдельный proxy/main.go — вынесенный прокси-сервис перед реальным Telegram Bot API, с собственной логикой ретраев на запросы (doWithRetry — упоминается как существующий, детали реализации не входили в присланные файлы).

    19. scheduler/daily_reminder.go — построчно

    19.1. Start()

    func (dr *DailyReminder) Start() {
        go dr.sendReminders()   // 🧪 см. журнал — временная строка для
                                  // тестирования, должна быть удалена после
                                  // проверки в проде (немедленный запуск при
                                  // старте сервиса, а не ожидание 9:00 МСК)
        go dr.scheduleLoop()
    }
    

    19.2. scheduleLoop()

    Бесконечный цикл: вычисляет ближайшие 9:00 по Europe/Moscow (fallback — фиксированная зона UTC+3, если LoadLocation не удался), переносит на понедельник, если выпадает на субботу/воскресенье (skipWeekends), спит до этого момента, вызывает sendReminders(), повторяет.

    19.3. sendReminders()

    1. Получает всех пользователей (GetUsersListDB, без фильтра — все, в т.ч. потенциально заблокированные/уволенные — фильтрация по пригодности отправки происходит ниже, по наличию TelegramID).
    2. Один раз на всю рассылку — getAbsentEmployeesToday (§19.4).
    3. Для каждого пользователя с непустым TelegramID — sendReminderToUser.
    4. 🧪 Временный фильтр по конкретному telegramID — использовался для тестирования на одном аккаунте без риска разослать всем; должен быть удалён из продовой версии.

    19.4. getAbsentEmployeesToday(ctx, mskLocation) []AbsentEmployeeInfo

    type AbsentEmployeeInfo struct {
        Name            string
        TimeDescription string
        Contact         string   // делегат, если есть активное делегирование, иначе сам сотрудник
    }
    
    1. GetApprovalsListDB(Filtering: {Status: "approved"}) — все одобренные заявки компании одним запросом (не по пользователю — так один раз на всю рассылку, а не N запросов).
    2. Для каждой заявки:
      • Пропуск, если Metadata/Content пусты, или metadata.extra.absence_type пуст (не заявка на отсутствие — поле есть только у отгула/отпуска/больничного), или инициатор уже встречен ранее (дедупликация — сотрудник с несколькими пересекающимися заявками попадёт в сводку один раз).
      • parseAbsenceDates(content) — все совпадения regex (\d{1,2})[.,](\d{1,2})\.(\d{4}). Если ни одной даты не найдено — warning в лог с ID заявки и содержимым, пропуск.
      • absenceCoversDate(dates, content, today) — если дат ровно 2 и в тексте есть дефис — трактует как диапазон (с меньшей по большую дату, независимо от порядка написания); иначе — сверяет точное совпадение с любой из распознанных дат (список отдельных дней).
      • Формулировка TimeDescription по absence_type:
        • timeoff + time_type == "partial" → парсит время через (\d{1,2}):(\d{2}).*?(\d{1,2}):(\d{2}) (строго через двоеточие, не точку — специально, чтобы не путать с датами), формат "отгул с HH:MM до HH:MM"; при неудаче парсинга времени — "отгул весь день".
        • timeoff (не partial) → "отгул весь день".
        • vacation → "отпуск до " + maxDate(dates).
        • sickleave → "больничный до " + maxDate(dates).
      • Contact — поиск активных ApprovalDelegation по DelegatorID = InitiatorID, IsActive = true, с дополнительной проверкой ValidUntil (подстраховка на случай, если фоновая задача деактивации по времени ещё не отработала) — первая подходящая даёт имя делегата, иначе — имя самого сотрудника.

    19.5. sendReminderToUser

    Считает 4 персональных счётчика (доработки/задачи/согласования, плюс неявный «есть ли вообще что показать»), формирует текст, добавляет блок «Сотрудники в отгуле/больничном/отпуске» если absentToday непуст. Отправляет, если есть хоть один личный пункт или список отсутствующих непуст (осознанное решение — раньше отправляли только при личных пунктах).


    20. Каталог callback_data

    Префикс/формат Обработчик Назначение
    appr:{topicCode}:{nodeKey} HandleCallback Переход по дереву
    appr_back:{topicCode} HandleBackCallback Кнопка «Назад»
    appr_decision:{approvalID}:1|0 handleDecision (через диспетчер) Одобрить/отклонить
    appr_approver_sel:{userID} HandleApproverSelectionCallback Выбор согласующего вручную
    appr_approver_pg:{page} HandleApproverSelectionPageCallback Пагинация списка согласующих
    appr_obs_toggle:{userID} HandleObserverToggleCallback Toggle наблюдателя
    appr_obs_pg:{page} HandleObserverPageCallback Пагинация наблюдателей
    appr_obs_confirm HandleObserverConfirmCallback Подтверждение выбора наблюдателей
    appr_deleg_yes / appr_deleg_no HandleDelegationChoiceCallback 🆕 Да/Нет делегирования
    appr_deleg_sel:{userID} HandleDelegateSelectionCallback 🆕 Выбор делегата
    appr_deleg_pg:{page} HandleDelegateSelectionPageCallback 🆕 Пагинация делегатов
    add_appr_to_approval:{approvalID} HandleAddApproverFromApproval Добавление доп. согласующего (кнопка сейчас закомментирована)
    revoke_task_no_changes:{taskID}:{approvalID} HandleRevokeTaskNoChanges Подтверждение отсутствия изменений при отзыве
    revoke_task_has_changes:{taskID}:{approvalID} HandleRevokeTaskHasChanges Подтверждение наличия изменений

    ⚠️ Все ключи держатся в пределах 64 байт вручную (лимит Telegram) — особенно важно для Key узлов дерева (§2.3), которые входят в appr:{topicCode}:{nodeKey}.


    21. Каталог ключей Redis

    Ключ Формат/TTL Назначение
    approval_flow:{telegramID} JSON ApprovalFlowState, TTL 30 мин Состояние диалога создания заявки
    approval_flow:version Произвольное значение, без TTL Триггер перезагрузки Registry (опрос раз в 5 сек)
    appr_approve_comment_pending:{telegramID} approvalID строкой Ожидание обязательного комментария к одобрению
    (без точного имени) — AddApproverFlowState, AttachmentSession, comment-flow состояния JSON, управляются через StateManager Прочие второстепенные диалоговые состояния (§4.2)

    22. Полный журнал багов с кодом до/после

    22.1. handleDecision: тема не найдена для timeoff/vacation/sickleave

    Симптом: при попытке согласующего принять решение по заявке категории «Отгул»/«Отпуск»/«Больничный» — ошибка «unknown topic code».

    Причина: topicCodeToCategory в Registry инициализировалась пустой картой, алиасы никогда не заполнялись.

    Исправление — в NewRegistry:

    r := &Registry{
        ipc: ipc, topics: make(...), order: nil,
        topicCodeToCategory: map[string]string{
            "timeoff":   "absence",
            "vacation":  "absence",
            "sickleave": "absence",
        },
    }
    

    22.2. 64-байтный лимит callback_data — падение конструктора/бота

    Причина: r.Route("/api/approvals/{id}", ...) в chi перекрывал уже существующий GET-хендлер базового пути /api/approvals — новые под-маршруты (/comments, /additional-approvers и т.п.) логически переносили туда весь роутинг, ломая существующий список заявок.

    Исправление: заменено на r.Group(...) с полными абсолютными путями вместо вложенного r.Route.

    22.3. Заявка «зависает» после одобрения доп. согласующим

    Причина: ветка доп. согласующего в handleDecision искала «свой» шаг через findStepForCurrentApprover(ctx, approvalID, currentUserID) — эта функция ищет запись в ApprovalStep, а у доп. согласующего такой записи никогда не было (он есть только в ApprovalAdditionalApprover) → всегда nil → заявка молча переставала двигаться дальше после его одобрения, при этом никакой ошибки не логировалось.

    Было:

    step := findStepForCurrentApprover(ctx, approvalID, currentUserID)
    processNextStepOrComplete(ctx, bot, approval, step, currentUserID)
    // step == nil здесь всегда для доп. согласующего
    

    Стало: переиспользование уже корректно полученного mainStep (результат GetActiveStepDB, взятого в начале функции, см. §12.1):

    processNextStepOrComplete(ctx, bot, approval, mainStep, currentUserID)
    

    Заодно исправлен передаваемый actualExecutorID — раньше туда уходил ID самого доп. согласующего, из-за чего в processNextStepOrComplete (lastStepIsDelegate проверка, §13) он ошибочно трактовался как «делегат основного согласующего», хотя это два независимых, не связанных друг с другом человека.

    22.4. Прокси бота фактически не использовался

    Причина: buildHTTPClient принимал proxyCfg аргументом, но не подключал его к транспорту http.Client.

    Исправление: подключён CustomAPIProxyTransport, добавлены ретраи на подключение при старте.

    22.5. getApproverCategoriesWithPendingApprovals — неверные категории

    См. §14.2 — полностью переписана функция.

    22.6. Уведомление о выполненной задаче не содержало текст задачи

    Было: сообщение инициатору формировалось только из approval.Content (текст всей заявки).

    Стало: добавлена отдельная строка 🎯 Задача: {task.TaskText} — конкретно то, что исполнитель фактически делал.

    22.7. DynamicTopic — подписи {{.key_label}} только для захардкоженных ключей

    Причина: addLabelMappings содержала фиксированный список соответствий «ключ+значение → подпись», не расширяемый без правки кода.

    Исправление: добавлен dataValueLabels, заполняемый при NewDynamicTopic из Label любого узла дерева с соответствующими DataKey/DataValue, и метод addTreeLabelMappings, вызываемый после addLabelMappings (приоритет — у дерева). Теперь переименование узла в конструкторе автоматически меняет и текст в шаблоне сообщения, без правки Go-кода.