EXZOTRON knowledge base
ECOM TTelegram MMAX Вход
Умный инженер Exzotron knowledge
Здравствуйте. Задайте вопрос по оборудованию, настройке или документации Exzotron.

Алгоритмы и интеграции Exzotron ТРАНС (техническое руководство)

Техническое руководство Exzotron ТРАНС: алгоритмы расчёта факта и контроля маршрута, конструктор маршрута и оптимизация порядка точек, маршрутизация, геокодер, нормализатор трека, модель данных, интерфейсы, формат выгрузки в 1С, конфигурация, тесты и ограничения.

Версия ПО: Exzotron Server (сборка от 12.10.2026) · Проверено: 12.10.2026 · Ответственный компонент: расчёт факта транспорта, контроль маршрута, конструктор маршрута, REST API, выгрузка в 1С. Примеры — данные стенда Меридиан.

Статья для интегратора, технического специалиста и службы поддержки. Она описывает, как блок Exzotron ТРАНС считает факт и контролирует рейс, как строится план перевозки и маршрут, как устроены модель данных и REST API, что попадает в файл для 1С и какими настройками сервера управляется блок. Адреса серверов, учётные данные и ключи не приводятся; значения по умолчанию внешних служб названы по типу, без адресов. Обзорное, не техническое описание блока — в статье «Архитектура и устройство блока Exzotron ТРАНС».

Типы техники окна «Наряды» в коде: ROAD — закладка «Автотранспорт» (путевой лист грузового, TRUCK_WAYBILL), LIGHT_ROAD — закладка «Легковой» (CAR_WAYBILL). Два других типа (AGRICULTURE, SPECIAL) относятся к комплекту АГРО. Время в API и в расчётах — местное время предприятия («UTC компании», по умолчанию +3), если не сказано иное; сырые данные приборов хранятся в UTC.

1. Общая схема расчёта#

Расчёт факта транспорта опирается на три слоя:

  1. Телематика объекта. Приёмник сохраняет «сырые» точки в таблицу объекта ex_d_<imei>: время, координаты, скорость, число спутников, пробег (CAN или приращение), зажигание и др. Приёмник расчётов не делает.
  2. Расчётный конвейер датчиков (общий для Exzotron). Пул воркеров дополняет таблицу производными колонками (зажигание, уровень топлива, пробег, скорость) и событиями заправок и сливов. Без включённого конвейера колонки пусты и факт нулевой.
  3. Расчёт факта блока ТРАНС (AgroFactService, ExObjectService.getActionSegments). Читает готовые точки за окно наряда, строит отрезки движения и стоянки, превращает их в операции, считает итоги и запускает контроль маршрута (AgroRouteCheck).

Планировщик нарядов (AgroFactScheduler) раз в 15 минут создаёт и пересчитывает автонаряды (раздел 3.7); рейсовые (ручные) наряды считаются по событиям (раздел 3.8).

2. Окно расчёта#

Окно — интервал времени, за который берутся точки трека для наряда.

Наряд Окно
Автонаряд смена из графика машины: period_start–period_end; конец ограничен «сейчас»
Ручной наряд с плановым окончанием planned_start–planned_end
Ручной наряд без планового окончания смена машины, в которую попало плановое начало (смена может начинаться накануне и переходить через полночь); если графика нет или начало вне смен — календарные сутки планового начала (00:00 – 23:59:59)

Окно, которое ещё не наступило, не считается. Время — местное время предприятия; при чтении таблицы прибора оно переводится в UTC вычитанием смещения предприятия, а к клиенту трек возвращается со смещением предприятия обратно. График «Сутки, 24 часа» даёт окно ровно в сутки. Окно автонаряда определяется графиком объекта (ex_objects.ex_shift_id), а закрепления исполнителей — графиком карточки автомобиля; расхождение двух графиков — частая причина «неправильного исполнителя» в автонаряде.

3. Расчёт факта#

3.1. Критерий «машина реально работала» (создание автонаряда)#

Автонаряд создаётся, только если выполнено условие активности (AgroFactService.isActive):

  • путь по отрезкам «Движение» и «Работа в движении» не менее 0,3 км (MIN_ACTIVE_KM) либо работа механизма не менее 10 минут (ACTIVITY_MIN_SECONDS = 600; у транспорта работа возможна только при настроенном датчике механизма, поэтому на практике критерий — путь 0,3 км);
  • при наличии датчика зажигания дополнительно: двигатель включён непрерывно не менее «Мин. стоянки» (свойство объекта, по умолчанию 5 минут). Движение при выключенном зажигании не считается.

Если критерий не выполнен, наряд не создаётся. Уже созданный наряд, который стал «пустым», не принят и не имеет ручных правок (нет агрегата, комментария, простоев, нестандартного вида работ или исполнителя), удаляется. Вид работ автонаряда — самый частый у машины по прошлым нарядам, иначе первый у её модели; исполнитель — по закреплению смены, иначе водитель по умолчанию.

Подтверждённый на комплекте АГРО случай ошибки настройки: зажигание привязано к источнику, значения которого никогда не достигают порога, — по технике нет автонарядов.

3.2. Пороги и константы#

Параметр Значение Примечание
Цикл планировщика 15 минут (старт через 4 минуты после запуска) окна смен сегодняшних и двух прошлых суток, глубина 36 часов
Принудительный пересчёт окна раз в час окно без новых данных пропускается
Минимум пути для автонаряда 0,3 км либо 10 минут работы см. 3.1
«Мин. стоянка» свойство объекта, по умолчанию 5 минут стоянка и посещение короче — не операция и не посещение
«Неактивность» (разрыв данных) свойство объекта, по умолчанию 30 минут пауза дольше — отрезок «нет данных», кроме паузы после стоянки с заглушённым двигателем
Порог движения скорость ≥ порога датчика скорости, по умолчанию 2,0 км/ч
Потолок достоверной скорости 130 км/ч (ROAD), 160 км/ч (LIGHT_ROAD) либо «Макс. скорость» датчика, если она меньше для сельхоз- и спецтехники — 60 км/ч
Плохая навигация (факт) спутников меньше порога (по умолчанию 4; настраивается датчиком «мин. спутников») либо скорость вне диапазона [0; потолок] недостоверность держится ещё 10 минут после последней плохой точки
Движение при заглушённом двигателе считается стоянкой если зажигание настроено
Шаг пробега не быстрее 130 км/ч (+0,03 км) неправдоподобный шаг CAN-счётчика отбрасывается, считается по координатам
CAN-пробег при «холостом» отрезке рост счётчика ≥ 0,1 км (CAN_MOVEMENT_MIN_KM) — «Переезд» с пометкой «нет навигации» без CAN отрезок остаётся холостым ходом
Минимальный переезд 0,1 км короче — дрейф GPS: строки нет, время и путь в итоги не идут
Порог доли недостоверной навигации для статуса маршрута 30% (NO_NAV_SHARE = 0,3) см. 4.4
Окно возврата автосоздания 36 часов старше — только «Обработка периода»
Макс. точек маршрута / ходок 12 / 999 раздел 8

3.3. Отрезки телеметрии#

Из точек строятся отрезки следующих типов:

Двигатель Движение Тип отрезка
выкл да/нет стоянка (движение при выключенном двигателе приравнивается к стоянке, если зажигание настроено)
вкл да переезд (движение)
вкл нет холостой ход
— — нет данных (пауза дольше «Неактивности», кроме паузы после стоянки с заглушённым двигателем)

Без датчика зажигания двигатель считается включённым только при движении, а любая остановка — стоянкой; поэтому операции «Двигатель на стоянке» и «посещения с работающим мотором» без датчика зажигания не возникают. «Работа» (work_seconds) у транспорта всегда 0, площади нет (fact_area_ha пусто). Новый отрезок начинается при смене типа, признака навигации или геозоны.

3.4. Из отрезков в операции#

Отрезок телеметрии Операция в наряде Что идёт в итоги
Движение (в том числе «холостой» + рост CAN) Переезд (ROAD) / Поездка (LIGHT_ROAD), «из зоны → в зону», средняя скорость «В пути», «Пробег»
Стоянка (двигатель заглушён) в геозоне ≥ Мин. стоянки Стоянка в геозоне / Остановка (VISIT) —
Холостой ход в геозоне ≥ Мин. стоянки то же VISIT (разгрузка с работающим мотором — тоже посещение) «Простой»
Холостой ход вне геозон ≥ Мин. стоянки Двигатель на стоянке (только ROAD) «Простой»
Холостой ход короче Мин. стоянки без операции «Простой» и «Моточасы»
Заправка (событие датчика уровня) Заправка (литры, уровни до/после) «Заправка, л»
Стоянка вне геозон короче Мин. стоянки среди переезда не рвёт переезд —
Любая иная стоянка, «нет данных» граница операций —

Посещение короче «Мин. стоянки» удаляется после сборки: проезд мимо склада на малой скорости посещением не считается. Зона точки определяется по геозонам предприятия: берётся первая подходящая зона из списка (порядок при пересекающихся зонах не проверялся); при недостоверной навигации зона не определяется. Операции хранятся в ex_agro_work_order_op и пересоздаются при каждом пересчёте; тип операции — WORK/TRANSFER/VISIT/REFUEL, а также IDLE. Событие заправки с периодом не менее часа считается разрывом данных: заправка относится к концу периода, длительность — 5 минут. Слив операцией не является.

Итоги наряда: «В пути» — сумма переездов; «Моточасы» — сумма отрезков с работающим двигателем (шире суммы операций); «Пробег» — сумма путей переездов; «Заправка» — сумма заправок. Для легкового нет «Двигателя на стоянке», а пометка «нет навигации» практически не попадает в операции (см. раздел 17).

3.5. Недостоверная навигация и РЭБ в расчёте факта#

Признак «нет навигации» ставится на отрезки «на месте» (холостой ход) и на переезд — по значению первого отрезка. Доля noNav в контроле маршрута считается как длительность операций с пометкой, делённая на длительность всех операций наряда (раздел 4.4). Серверный пробег считается по исходным данным; нормализатор трека (раздел 11) на него не влияет.

Три разных порога достоверности — не единый порог:

Где Порог Что делает
Серверный фильтр трека для карты спутников ≥ 5 отбрасывает точки до показа на карте
Расчёт факта спутников < 4 (по умолчанию; настраивается до 12) или скорость вне диапазона помечает навигацию недостоверной
Нормализатор трека (клиент) спутников ≤ 7, либо скорость 94–104 км/ч при спутниках ≤ 9 отбрасывает точку на карте

«Залипшая» скорость 94–104 км/ч для грузовика серверным признаком не считается (потолок 130, спутников ≥ 4): такие участки не получают пометку noNav и не учитываются в правиле 30%.

3.6. Ориентиры точности пробега (данные стенда)#

У одного из грузовиков за 02.10 до учёта CAN было 39,7 км, после — 158,3 км при показании CAN-счётчика 158,7 км. Это нельзя читать как «пробег подтверждён одометром»: CAN-счётчик в данных стенда оказался вычисленным по тем же координатам и независимой проверкой не служит. Без CAN пробег по подменённым участкам — оценка.

3.7. Планировщик автонарядов#

  • Расписание: initialDelay = 240000 мс, fixedRate = 900000 мс (15 минут). Метод spawnShiftOrders обходит машины с графиком смен и тип которых входит в {ROAD, LIGHT_ROAD, SPECIAL, AGRICULTURE}; автонаряд создаётся только если ex_objects.ex_shift_id IS NOT NULL.
  • Окна: смены сегодняшних и двух прошлых суток, глубина LOOKBACK_HOURS = 36.
  • Пропуск окон без новых данных: карта lastSeen в памяти (до 100 000 записей, сбрасывается при перезапуске); принудительный пересчёт окна раз в час (RECHECK_MS). Для этого используется время последнего пакета объекта (ex_objects.last_time).
  • Наряд «На проверке»: досчитанный не раньше чем через 30 минут после конца смены планировщиком не пересчитывается — до ручного «Пересчитать».
  • «Завершённые»: не пересчитываются ни планировщиком, ни «Обработкой периода».
  • Журнал: отдельный файл logs/agro-fact.log с суточной ротацией (14 дней); итог прохода — строка «Агро-факт: проход завершён за N с: объектов, окон (пересчитано, без новых данных), медленные объекты». Замер на стенде: 118 объектов, холодный проход около 4 с.

3.8. Ручные (рейсовые) наряды#

Факт ручного наряда считается: при переводе в «На проверке» или «Завершён» (сбой расчёта пишется в лог и не срывает смену статуса), кнопкой «Пересчитать» (POST /agro_orders/{id}/recalc), при закрытии дня. Правка ручного наряда (окно, маршрут, техника) пересчёт не запускает — в журнале до пересчёта прочерк. Ручной факт (PUT /{id}/fact) снимает операции телеметрии и оставляет прежние route_status, trips_done, route_note (см. раздел 17).

3.9. Простои и топливо в наряде#

Простои — отдельные записи ex_agro_work_order_downtime (причина, минуты > 0, комментарий); из рабочего времени не вычитаются и привязки к интервалу не имеют. Слив топлива в журнале берётся из ex_fuel_level_events (event_type = 'DRAIN') по моменту начала события в окне наряда; слив считается одним запросом по предприятию, а не расчётом по каждой машине. Расчёт по баку: расход = уровень на начало − уровень на конец + заправки; слив не вычитается. Если для заправки есть транзакция выдачи по карте, в формуле берётся её литраж, а не показание датчика. Вкладка «Топливо» (GET /agro_orders/{id}/fuel) строится только для автонарядов: нужен period_start, который у ручного наряда не записывается. Детали — в статье «Топливо и пробег по рейсам».

4. Контроль маршрута#

Схема 4. Контроль маршрута (приёмка рейса)

Контроль выполняется для ROAD и LIGHT_ROAD (и спецтехники с погрузкой) при наличии плана в наряде; без плана route_status пуст. Данные: геозоны «откуда» (from_geozone_id) и «куда» (geozone_id) либо точки плана (ex_agro_work_order_point, без BASE); посещения — операции VISIT. Рейс — это повторение цепочки «побывала в погрузке → затем в назначении», а не число переездов.

4.1. Выбор алгоритма#

если в плане наряда больше двух точек (не считая BASE):
    checkSequence(точки по порядку, посещения)
иначе:
    check(откуда, куда, посещения)
затем — сброс по навигации (раздел 4.4)

4.2. check(откуда, куда)#

Статусы: OK, PARTIAL, NONE, NO_DATA. Псевдокод (поясняющий; порядок действий соответствует поведению, описанному в проверенных автотестах):

check(from, to, visits, ops):
    если from == пусто и to == пусто:        вернуть нет контроля (null)
    если нет активности (все отрезки «нет данных» или отрезков нет):
        вернуть NO_DATA("данных навигации за окно наряда нет — маршрут не подтверждён")
    loaded = false; trips = 0
    для каждого v в visits (по времени):
        если from == to и v.зона == to:       trips++            // склад → склад: каждое посещение — рейс
        иначе:
            если v.зона == from:              loaded = true; запомнить первое посещение погрузки
            если v.зона == to:
                если loaded или from == пусто: trips++; loaded = false
                запомнить первое посещение назначения
    если заданы обе точки:
        OK        — trips >= 1
        PARTIAL   — trips == 0, но одна из точек посещена
        NONE      — ни одна не посещена
    если задана только «куда»:   OK при любом посещении, trips = число посещений
    если задана только «откуда»: OK при посещении, trips = 1

4.3. checkSequence (развоз из ≥ 3 точек)#

Указатель на очередную точку плана; посещение подходящей зоны сдвигает указатель; посторонние посещения игнорируются:

checkSequence(plan[1..K], visits):          // K > 2, без BASE
    i = 0; trips = 0; best = 0
    для каждого v в visits:
        если v.зона == plan[i+1]:
            i++; best = max(best, i)
            если i == K: trips++; i = 0      // рейс завершён, отсчёт заново
    OK       — trips >= 1
    PARTIAL  — trips == 0 и best >= 1
    NONE     — иначе
    если OK и i > 1: в примечание — «ещё один рейс не завершён: пройдено i из K»

Пример: план [1, 2, 3], посещения 1, 9, 2, 3, 1, 2 — OK, 1 рейс, «рейсов 1 (по всем 3 точкам маршрута); ещё один рейс не завершён: пройдено 2 из 3». После 1, 2, 3, 1 хвоста нет: возврат на первую точку не считается начатым рейсом.

4.4. Сброс по навигации#

Доля недостоверной навигации = длительность операций с пометкой «нет навигации» ÷ суммарная длительность всех операций наряда. При доле ≥ 30%:

  • NONE → NO_DATA (с пояснением про подмену GPS);
  • у PARTIAL дописывается пояснение;
  • OK не меняется.

Доля считается по длительности операций, а не окна наряда; стоянки с заглушённым двигателем вне зон, «нет данных» и короткие холостые в знаменатель не входят.

4.5. Точные тексты примечания (route_note)#

Статус / случай Текст
Нет данных за окно данных навигации за окно наряда нет — маршрут не подтверждён
OK, пара погрузка {N} мин ({чч:мм}–{чч:мм}), разгрузка {N} мин ({чч:мм}–{чч:мм}); при ≥ 2 рейсах в конце ; рейсов {N}; без деталей — маршрут подтверждён геозонами. Если «откуда» = «куда», печатается только «разгрузка …». N — округлённые минуты, не меньше 1; в скобках — первый заезд
PARTIAL, была только погрузка была погрузка, но в пункте назначения машина не останавливалась
PARTIAL, был только пункт назначения в пункте назначения была, а погрузка в окне наряда не подтверждена
PARTIAL, оба пункта, не в том порядке пункты посещены не в том порядке (назначение раньше погрузки)
NONE, пара ни погрузка, ни пункт назначения не подтверждены геозонами — проверьте рейс
NO_DATA по навигации навигация недостоверна на {P}% времени (подмена GPS): остановки в геозонах не определяются, рейс по геозонам подтвердить нельзя — проверьте по путевому листу
Добавка к PARTIAL при навигации ; навигация недостоверна на {P}% времени — часть остановок могла не определиться
OK, последовательность рейсов {N} (по всем {K} точкам маршрута); при начатом ещё одном рейсе и пройденных больше одной точке — ; ещё один рейс не завершён: пройдено {i} из {K}
PARTIAL, последовательность пройдено точек маршрута по порядку: {лучший} из {K} — рейс не завершён
NONE, последовательность ни одна точка маршрута не подтверждена геозонами — проверьте рейс

Подписи статусов в интерфейсе: OK — «✓ рейс выполнен», PARTIAL — «⚠ частично», NONE — «✕ не подтверждён», NO_DATA — «нет данных навигации».

Реальный пример стенда (одиночный рейс, один из нарядов Меридиан за 08.10): «✓ рейс выполнен · погрузка 19 мин (08:07–08:26), разгрузка 10 мин (14:38–14:48)», рейсов 1. Развоз из трёх точек: «✓ рейс выполнен · рейсов 1 (по всем 3 точкам маршрута)», «Перевезено (груз × рейсы)» 4,0 т.

4.6. Что не контролируется#

Число плановых ходок и плановый километраж против факта (план выводится строкой «План перевозки … · факт: N рейс., X км» без сигнализации); соответствие количества груза грузоподъёмности (только подсказка при создании наряда); время подачи; пересечение окон при создании наряда (контролируется при закрытии дня).

5. «Перевезено»#

Весов в системе нет. Перевезённый объём определяется по контролю маршрута:

перевезено = количество груза × trips_done
    если route_status == OK
     и trips_done > 0
     и единица груза совпадает с единицей вида работ (т)
     и факт не ручной (fact_source != MANUAL)

Недостоверная навигация основанием для расчёта не является (источник формулирует так). В файле выгрузки значение передаётся как factVolume с factVolumeSource = "ROUTE_CONTROL" (AgroExportService.routeFactVolume). Карточка наряда показывает «Перевезено (груз × рейсы)» без проверки единиц и источника факта, поэтому значения карточки и файла могут расходиться. Плановый объём (planned_area_ha) у грузового пуст и подставляется равным количеству груза (только если поле пусто: правка количества груза оставит прежний план); в журнале и выгрузке это planVolume.

6. Замена автонаряда рейсовым#

Правило (решение заказчика 11.10.2026): если у машины транспорта есть ручной наряд, окно которого пересекается с окном автонаряда, автонаряд не создаётся, а созданный, не завершённый и не выгруженный удаляется вместе с операциями и простоями.

supersedeAutoOrders(manual):                    // окно ручного — manualWindow
    для автонарядов той же машины с пересечением окон:
        если status != DONE и export_batch_id IS NULL: удалить (операции, простои, наряд)
    новые автонаряды на это окно не создаются   // coveredByManualOrder

Выполняется при создании, правке и пересчёте ручного наряда и при каждом проходе планировщика (processWindow). Последствия: удаление безусловное, в том числе ручных правок автонаряда и всей его части суток вне окна рейса; принятый автонаряд остаётся, при закрытии дня — замечание DUPLICATE; удалённый автонаряд возвращается «Обработкой периода» (окна старше 36 часов) или планировщиком (моложе), если рейсовый наряд удалили. Правило: на каждый рейс — наряд с окном, на сутки без рейсов автонаряд остаётся.

7. Закрытие дня#

7.1. Алгоритм#

Единица закрытия — день × подразделение (ex_agro_day_close; division_id пуст — всё предприятие). Принадлежность наряда дню: DATE(COALESCE(period_start, planned_start)) в местном времени предприятия.

  1. GET /agro_day_close/check?date=&divisionId= — для каждого наряда дня вычисляются коды замечаний (7.2); возвращаются только наряды с замечаниями (до 300; остальные покрываются общим решением).
  2. Для каждого наряда с замечаниями, требующими решения, диспетчер выбирает CLOSE («Закрыть как есть») или MOVE («Перенести на следующий день» — только ручной не завершённый наряд); общее решение: CLOSE либо MOVE_OR_CLOSE («перенести, что можно, остальное закрыть как есть»); строгий MOVE в интерфейсе не предлагается, но API его принимает.
  3. POST /agro_day_close {date, divisionId, decisions{id: CLOSE|MOVE}, defaultDecision} — в одной транзакции: проверяются условия (день не в будущем, не закрыт, в нём есть наряды); подставляются исполнители по закреплению смены в наряды без исполнителя (на всё предприятие дня, не только на подразделение); MOVE сдвигает плановые начало и окончание на сутки; остальные наряды получают DONE (у ручных считается факт; заявка-основание → «Выполнена»); строится сводка и сохраняется запись закрытия.
  4. Закрытие диапазона POST /agro_day_close/range — общее решение для всех прошлых незакрытых дней (до 32 дат); каждый день в своей транзакции, ошибки по дням возвращаются; сегодняшний день не затрагивается.

7.2. Коды замечаний#

Код Условие Решение Текст в диалоге
NOT_CLOSED статус ≠ «Завершён» нужно не завершён
NO_EXECUTOR нет employee_id и нет исполнителя по закреплению смены нужно нет исполнителя
NO_FACT факт не ручной и (нет расчёта либо моточасы = 0 и работа = 0) нужно нет факта по телеметрии (введите вручную или примите)
DUPLICATE окна двух нарядов одной машины пересекаются нужно пересекается по времени с другим нарядом этой техники — часы и топливо посчитаются дважды (№…)
ROUTE_NONE route_status = NONE нужно маршрут по заявке не подтверждён геозонами — проверьте рейс (если рейс был, закройте как есть)
MANUAL_FACT fact_source = MANUAL не нужно факт введён вручную
SUSPECT_GPS факт не ручной, пробег > 1 км, расчёт есть, моточасы = 0 не нужно пробег при выключенном двигателе — проверьте достоверность навигации
FUEL_DRAIN в окне наряда есть слив (ex_fuel_level_events, DRAIN) не нужно слив топлива по датчику уровня за время наряда — проверьте расход (вторая строка «⛽ слив топлива 56,2 л (2 раз) — проверьте расход»)
ROUTE_PARTIAL PARTIAL не нужно маршрут по заявке выполнен частично
ROUTE_NODATA NO_DATA не нужно маршрут по заявке не подтверждён: навигация недостоверна или данных нет — проверьте по путевому листу

Окна пересечения для DUPLICATE: автонаряд — смена; ручной — плановое начало/окончание либо смена машины, иначе сутки. Под замечаниями маршрута выводится «🛣 {route_note}»; для предупреждений ручного факта — «⚠ …». Наряд без замечаний (уже «Завершён», маршрут OK) в списке не показывается. Так как NOT_CLOSED требует решения, любой наряд «Новый / На исполнении / На проверке» попадает в таблицу, а «Закрыть как есть» для него — принятие наряда.

7.3. Сообщения закрытия и блокировки#

  • Ошибки закрытия: «Укажите дату дня»; «День ещё не наступил — закрыть можно только сегодняшний или прошедший день»; «День уже закрыт»; «В этом дне нет нарядов — закрывать нечего»; «Нет решения по нарядам: №…» (для строгого MOVE добавляется пояснение про автоматические и завершённые наряды); «Наряд №N нельзя перенести: автоматический или уже завершён».
  • Диапазон: «Для закрытия нескольких дней выберите общее решение: закрыть как есть либо перенести, что можно»; «В выбранном периоде нет прошедших дней»; «За один раз можно закрыть не больше 31 дня» (проверка допускает 32 даты включительно).
  • Блокировка: «День ДД.ММ.ГГГГ закрыт — {действие}. Руководитель может открыть день в окне «Закрыть день».», где действие: «создать наряд на эту дату нельзя», «перенести наряд на эту дату нельзя», «наряд изменить нельзя». Закрытый день запрещает правку, пересчёт, удаление, простои, ручной факт, возврат расчёта, смену статуса, создание наряда на дату (в том числе из заявки) и перенос на неё.
  • Запрет завершения до даты: «Наряд запланирован на ДД.ММ.ГГГГ — завершить его до наступления даты нельзя. Измените плановое начало или дождитесь даты.» (ручные наряды; автонаряды не ограничены).
  • Закрытие по подразделению блокирует только наряды этого подразделения; закрытие «всех подразделений» — все. Открыть день: DELETE /agro_day_close/{id} — ROLE_ADMIN, ROLE_ADVANCED_USER; статусы нарядов не возвращаются. При нескольких доступных подразделениях выбор одного обязателен: «Выберите подразделение: у вас доступно несколько».
  • Счётчик незакрытых дней — последние 14 суток (GET /agro_day_close/pending).

7.4. Сводка дня#

Сводка (JSON, MEDIUMTEXT в ex_agro_day_close.summary) содержит: предприятие, дату, подразделение; итоги (наряды, моточасы, работа, простой, переезды, пробег, заправка, простои в минутах, число ручных фактов); строки по паре «машина + исполнитель»; простои по причинам («Без причины» при её отсутствии); ручные факты; «suspicious» (слив, маршрут, подозрение на подмену); «exceptions» (закрыто как есть); «moved» (перенесено). Перенесённые наряды в итоги не входят. Предупреждения ручного факта (хранятся ≤ 500 символов): «моточасы (X ч) больше длительности смены (Y ч)»; «время работы (X ч) больше моточасов (Y ч)»; «работа + простой + переезды (X ч) больше моточасов (Y ч)» (допуск +6 минут).

8. Конструктор маршрута#

Схема 3. Три модели перевозки

8.1. Модели и хранение#

В заявке и наряде хранятся: transport_model (SINGLE/SHUTTLE/MULTI, по умолчанию SINGLE), planned_trips, route_km (км за ходку или круг), planned_km (всего), round_trip (возврат в пункт погрузки, по умолчанию «да»); в заявке дополнительно required_time, external_ref (≤ 100 символов) и vehicle_requirement (≤ 150).

Модель Поля формы Что записывается
SINGLE Одиночный рейс «Откуда», «Куда» planned_trips = 1; route_km — один проход (с возвратом, если включён); planned_km = route_km; точки не хранятся
SHUTTLE Ходки (челнок) «Откуда», «Куда», «Число ходок», «Груз за ходку, т», «Расстояние за ходку» (по умолчанию 2 ходки) planned_trips = N; planned_km = N × route_km; точки не хранятся
MULTI Развоз «Откуда» + список точек выгрузки (геозона, количество, контакт), «Число кругов», «Расстояние за круг» точки хранятся (LOAD + UNLOAD…); geozone_id = первая выгрузка; planned_trips = число кругов (по умолчанию 1)

У легкового нет модели SHUTTLE. Точки маршрута хранятся в ex_agro_work_request_point и ex_agro_work_order_point (seq, kind ∈ {BASE, LOAD, UNLOAD, STOP, WORK}, geozone_id, cargo_quantity, contact) только если модель MULTI или точек больше двух; иначе список очищается, а маршрут восстанавливается из пары from_geozone_id → geozone_id. «Откуда» = первая точка вида LOAD; «Куда» = первая UNLOAD/WORK/STOP, а если таких нет — последняя точка. «База» (BASE) посещением не контролируется. Конструктор формирует точки только LOAD/UNLOAD; остальные виды в модели есть, но интерфейсом не задаются (несколько погрузок и разные грузы по точкам — в backlog).

8.2. Формулы плана (клиент)#

  • Ходки: trips = 1 для SINGLE; иначе max(1, floor(число ходок)).
  • Км за ходку = totalKm ответа сервера (1 знак после запятой) либо введённое вручную значение (помечается «Расстояние введено вручную»; «Пересчитать маршрут» возвращает расчёт по дорогам). Изменение состава маршрута, флажка «Возврат» или кнопка «Пересчитать» снимают ручной режим.
  • Всего по плану, км = round₁(км за ходку × trips); при trips > 1 показывается формула «(N × X км)».
  • Ходки по грузоподъёмности: только для SHUTTLE, если количество груза > 0, «Груз за ходку» > 0 и число ходок не вводилось вручную: trips = ⌈количество груза ÷ груз за ходку⌉. В форме заявки «Груз за ходку» вводится вручную; в форме наряда подставляется из load_capacity_t модели выбранной машины. В окне «Сформировать наряд» для SHUTTLE и MULTI ходки (круги) = ⌈груз заявки ÷ грузоподъёмность машины⌉, пока поле не трогали. Единица «т» зашита в подписи и не сверяется с единицей груза.
  • Время в пути = durationMin × trips (минуты OSRM по профилю «авто» либо оценка 40 км/ч); «В пути ≈ Ч ч М мин (без погрузки/выгрузки)». Погрузка, выгрузка и стоянки в расчёт не входят.
  • Предупреждение о смене: если durationMin × trips > 660 минут (RT_SHIFT_MINUTES = 11 × 60), показывается «⚠ Не уложится в одну смену (≈ N ч): добавьте машину или разнесите по дням». Это константа: она не учитывает график смен машины и время на точку.
  • Участки (легенда): «A → B — N км»; последний при возврате ведёт в первую точку.
  • При формировании наряда: planned_km = round₁(route_km × planned_trips) — клиент считает сам; сервер делает то же, если ходки пришли, а plannedKm нет.

Пример стенда: Симферополь → Ялта 87,4 км туда и 88,1 км обратно — 175,5 км за ходку; груз 12 т при «груз за ходку» 2 т — 6 ходок; всего 1053 км; «В пути ≈ 18 ч 36 мин», предупреждение «⚠ Не уложится в одну смену (≈ 19 ч)». Развоз из трёх точек: участки 2,2 + 5,5 + 76,2 + 80,6 км, итого по кругу 164,6 км.

8.3. Оптимизация порядка точек#

Оптимизация доступна только для модели MULTI и только если у всех точек есть координаты; в интерфейсе — кнопка «Оптимизировать порядок» и флажок «Авто-порядок» (ручное ↑/↓ отключает «Авто-порядок»). Сервер вызывается как POST /agro_orders/route с optimize = true, fixedHead = 1 (погрузка неподвижна) и roundTrip.

order(points, fixedHead, roundTrip, dist):
    free = points[fixedHead:]            // «голова» (погрузка) остаётся первой
    если len(free) <= 8:                 // EXACT_LIMIT = 8
        перебрать все перестановки (до 8! = 40 320), выбрать кратчайшую
    иначе:
        ближайший сосед от конца головы, затем 2-opt (разворот участков)
        до отсутствия улучшений
        результат принимается, только если короче исходного порядка
    при roundTrip в стоимость входит возврат в точку 0

Матрица расстояний — OSRM table (annotations=distance, метры); если матрицы нет — по прямой (гаверсинус), и результат помечается как оценка. Оптимизация запускается, если свободных точек (всего − fixedHead) не меньше двух.

8.4. Пределы и проверки#

Параметр Значение
Точек маршрута при сохранении не более 12 в сумме, включая погрузку (при заданном «Откуда» — погрузка и 11 выгрузок); сообщение «В маршруте не больше 12 точек»
Точек в запросе /route от 2 до 13 (MAX_POINTS = 13); при > 13 тоже «В маршруте не больше 12 точек»
Число ходок 0…999 (форма даёт 1…999)
Расстояние за ходку 0…99 999 км (DECIMAL(10,2))
Всего по плану 0…999 999 км (DECIMAL(10,1))
Количество в точке 0…9 999 999 (DECIMAL(12,3))
Контакт в точке до 200 символов (обрезается молча)
Приоритет 1…3, по умолчанию 2
«Подать к времени» формат ЧЧ:ММ

Расчёт дорог построит маршрут из 13 точек (погрузка + 12 выгрузок), но сохранение откажет: предел 12. В интерфейсе предел не показывается — сообщение появится только при сохранении.

9. Маршрутизация (OSRM)#

POST /api/v1/agro_orders/route (RouteService.plan, роли ADMIN/ADVANCED_USER/USER). Тело: {points:[{lat,lon}], fixedHead, optimize, roundTrip}; ответ: order[], legsKm[], totalKm, durationMin, geometry[], source (osrm или estimate).

  1. Проверки: минимум две точки («Для маршрута нужно минимум две точки»); не более 13; координаты числовые и в пределах ±90/±180 («Некорректные координаты точки маршрута»). Нечисловые значения вызывают ошибку разбора, а не бизнес-ошибку.
  2. Оптимизация (если optimize и свободных точек ≥ 2): матрица OSRM table; при её отсутствии — матрица по прямой; порядок — раздел 8.3.
  3. Маршрут в найденном порядке (плюс возврат в точку 0 при roundTrip) — запрос /route/v1/driving/…?overview=full&geometries=geojson&steps=false; участки в км (1 знак), общий км, минуты (округление), линия прореживается до 600 вершин.
  4. Запасной адрес: если основной OSRM не ответил, пробуется route.osrm.fallback-url.
  5. Оценка. Если дорожный сервис выключен (route.osrm.enabled = false) или не ответил, по каждому участку считается: км = расстояние по прямой × route.detour.factor (1,3), время = км ÷ 40 км/ч, линия — ломаная по точкам; source = "estimate". В форме серая пометка «Расстояние приблизительное (по прямой с поправкой на дороги): сервис дорог недоступен — при необходимости введите км вручную».

Таймауты: соединение 5 с, ответ 8 с на запрос. Худший случай (основной и запасной не отвечают) — около 26 с на один запрос (2 × (5 + 8)); при оптимизации запросов два (матрица и маршрут). Серверного кэша маршрутов нет: кэш только в браузере (planCache, normRouteCache, на время открытого окна). Профиль OSRM — driving (легковой): ограничения по массе и габаритам грузовиков не учитываются; время — «в среднем», без пробок и погрузки. По умолчанию основным является публичный демонстрационный сервер OSRM — без гарантий и лимитов частоты. Каждое изменение маршрута в форме вызывает запрос после паузы 350 мс.

Свой OSRM. Комплект docs/osrm (docker-compose.yml, prepare.sh, update.sh): образ osrm-backend:v5.27.1, osrm-routed --algorithm mld --mmap --max-table-size 100, порт, привязанный к локальному интерфейсу; подготовка данных России — 24–32 ГБ оперативной памяти, около 40 ГБ диска, 2–4 часа; работа — 12–16 ГБ. Автор комплекта отмечает, что команды в среде не запускались; развёрнут ли собственный OSRM на каком-либо сервере — не подтверждено. Сборка по региону (рекомендация README) потребует правки имени файла данных в docker-compose.yml: в нём зашито имя файла всей России.

10. Геокодер#

GET /api/v1/service_orders/geocode (q обязателен; lat, lon — смещение поиска, область ±0,8° × ±0,6° без жёсткого ограничения; роли ADMIN/ADVANCED_USER/USER) возвращает до 6 вариантов {name, lat, lon}; в форме варианты в радиусе 40 м склеиваются. GET /service_orders/reverse_geocode (lat, lon) возвращает {lat, lon, address, displayAddress}: сначала название геозоны предприятия, затем внешний адрес; результат кэшируется в ex_geocode_cache.

  • Прямой поиск всегда идёт в Nominatim (адрес из свойства address.geocoder.nominatim.url с заменой /reverse на /search; читается из файла настроек, а не из настроек в базе). Метод синхронизирован: запросы всех диспетчеров идут по одной очереди с интервалом 1100 мс (лимит публичного сервера — 1 запрос в секунду). При ответе 503 или 429 делается один повтор через 1,6 с. При сбое сервиса — HTTP 400 «Сервис поиска адресов недоступен…» или «Сервис поиска адресов временно недоступен (код N)…».
  • Яндекс действует только для обратного геокодирования (при наличии ключа в настройках сервера); address.geocoder.provider любое значение, кроме yandex, приводит к nominatim. Настройка в базе (PUT /api/v1/admin/geocoder_settings, таблица ex_app_settings) доступна только суперпользователю и имеет приоритет над файлом для обратного геокодирования.
  • address.geocoder.enabled = false отключает внешний обратный геокодер; прямой поиск не отключается.
  • Прямой поиск не кэшируется.

Быстрое место в заявке использует геокодер так: адрес → до 6 вариантов → круг радиусом 10–5000 м (по умолчанию 150 м для ROAD, 60 м для LIGHT_ROAD, 100 м для SPECIAL) или многоугольник (минимум 3 вершины; предупреждение при площади > 2000 га) → POST /api/v1/geozones (type=point, radiusMeters, useForAddress=false) в корневую группу «Места заявок».

11. Нормализатор трека#

Схема 5. Нормализация трека при подмене GPS

Нормализатор — только отображение на карте. Он работает в браузере (track_normalizer.js), не меняет данные в базе и не влияет на пробег, рейсы и топливо; серверной версии нет. Признак «нормализация включена» хранится на машине: ex_objects.track_normalize (миграция 216, TINYINT(1) NOT NULL DEFAULT 0), переключается PUT /api/v1/agro_orders/objects/{objectId}/track_normalize с телом {enabled}; один признак для всех пользователей и нарядов машины; журнала изменений нет. Кнопка «Нормализация трека» появляется только если нормализатор нашёл искажения (removed > 0).

11.1. Предобработка на сервере (до нормализатора)#

Трек для карты проходит серверные правила (TrackProcessingRuleService, таблица ex_track_processing_rules, значения по умолчанию): высота −500…10 000 м, скорость ≤ 300, спутников ≥ 5, скачок ≤ 10 км при паузе ≤ 600 с. Затем прореживание до не более 1800 точек: в движении точка каждые ≥ 25 м или 45 с, на стоянке ≥ 20 м или 300 с. Правила включаются флагом enabled и могут отличаться на разных стендах. Нормализатор не видит точки, отброшенные на этом шаге.

11.2. Параметры по умолчанию (track_normalizer.js)#

Клиент вызывает normalize(valid) без переопределений.

Параметр Значение Смысл
vmax 100 км/ч предельная скорость перемещения между соседними точками цепочки
tolKm 0,15 км допуск на погрешность позиции в проверке достижимости
satLow 7 спутников ≤ 7 — точка «нижней достоверности»
flatSat 9 «залипшая» скорость подозрительна при спутниках ≤ 9
flatLo, flatHi 94 и 104 км/ч диапазон «залипшей» скорости (включительно)
minReliableShare 0,3 если доля «надёжных» точек < 30% — отбор по спутникам не применяется
routeMinKm 0,4 км убрано ≥ 2 точек подряд и расстояние между оставшимися соседями ≥ 0,4 км — участок достраивается
singleMinKm 1,0 км убрана одна точка — достраивается, только если соседи дальше 1 км
gapMinKm, gapMinSec 1,5 км и 180 с «разрыв» без удалений: пауза > 3 мин и уход > 1,5 км
maxLook 400 сколько предыдущих точек перебирать при построении цепочки
maxGapSec 10 800 с (3 ч) пауза длиннее 3 ч не ограничивает достижимость
вес точки 1,5 при sat ≥ 10, иначе 1
NORM_MAX_ROUTES 40 максимум участков, для которых запрашивается дорожный маршрут
NORM_PARALLEL 3 параллельных запросов /route с клиента
обновление картинки каждые 4 ответа перерисовка трека по мере поступления маршрутов

11.3. Алгоритм#

isLow(p) = (sat ≤ 7) или (94 ≤ speed ≤ 104 и sat ≤ 9)      // отсутствующие sat/speed = 0
надёжные = точки, у которых не isLow
если доля(надёжные) ≥ 0,3: кандидаты = надёжные  иначе кандидаты = все точки
динамическое программирование по кандидатам, O(n·400):
    для точки i — лучшая цепочка из предыдущих j (не далее maxLook),
    у которых haversine(j, i) ≤ vmax·Δt/3600 + 0,15
    пауза > 3 ч: связь разрешена без проверки, дальше не перебирается
    результат — цепочка максимального суммарного веса (при равенстве — более поздняя)
точки вне цепочки удаляются
по соседям оставшихся строятся участки (spans):
    spoof — между ними что-то убрано; gap — долгая пауза; edge — убраны начало или конец
участок достраивается маршрутом, если:
    spoof — по порогам routeMinKm / singleMinKm; gap — всегда; edge — отбрасывается без достройки

Для каждого участка клиент запрашивает /agro_orders/route с points=[A,B], fixedHead:1, optimize:false, roundTrip:false, вставляет линию розовым пунктиром (#d6336c, 3,5 px); сначала рисуется прямая, затем (пакетами по 4) заменяется дорогой. Информационная строка под картой: «Трек: N точек · нормализация: убрано K точек, достроено участков: M (маршруты i/n)». «Старт/Финиш», стрелки направления и стоянки «P» строятся уже по очищенному набору точек. Результаты запросов кэшируются в браузере (ключ — координаты с 5 знаками).

11.4. Ограничения нормализатора#

  1. Порог 100 км/ч подходит грузовику; при движении легковой машины со скоростью 110–130 км/ч и редких точках цепочка может рваться, и нормальные точки удаляются. На данных не проверялось.
  2. Нет данных о спутниках (sat = 0): отбор по ним отключается (доля надёжных 0 < 30%), остаются только телепорты; от «залипшей» скорости нормализатор бесполезен.
  3. Участок gap без удалённых точек в интерфейсе не показывается (кнопка работает только при removed > 0).
  4. До 40 участков на трек — остальные рисуются прямым розовым пунктиром, подписанным «Достроено», как и при недоступном OSRM: клиент не проверяет source: "estimate", поэтому оценка по прямой выглядит так же, как дорожный маршрут.
  5. Публичный OSRM может отказать (429/503) при трёх параллельных запросах × 40 участков.
  6. Стоянки «P» на карте (клиент, по прореженному/нормализованному треку) и «Стоянка в геозоне» в операциях (сервер, по сырым данным) считаются по-разному и могут не совпасть.
  7. Слой карты не запоминается; по умолчанию «Google карта».

11.5. Результаты на данных стенда#

Анализ 46 нарядов Меридиан с 01.10.2026: у «чистой» машины точек с ≤ 8 спутниками 2–6%, у трёх других — до 40–58% искажённых точек; признаки: спутников 5–9 против 12–18, «залипшая» скорость 94–104 км/ч, телепорты на 10–60 км за минуты. На одном из нарядов без нормализации трек уходит на ~30 км в море; с нормализацией убрано 424 точки, достроено 24 участка, путь идёт по дороге. Нельзя утверждать, что система «определяет подмену», «восстанавливает реальный маршрут» или «исправляет пробег».

12. Модель данных#

Внешних ключей между таблицами блока почти нет (только в миграциях 200 и 201); целостность держится кодом.

12.1. Связи#

Заявка (ex_agro_work_request) → наряд (ex_agro_work_order.request_id) → операции (ex_agro_work_order_op.order_id), точки (…_request_point.request_id, …_order_point.order_id), простои (ex_agro_work_order_downtime.order_id) → закрытие дня (ex_agro_day_close, по дате и подразделению) → пакет выгрузки (ex_agro_export_batch; наряд помечается export_batch_id). Техника в наряде — object_id (ex_objects.object_id) и imei; карточка ex_vehicles связывается с объектом только по (ex_users_company, imei).

12.2. Таблицы блока#

ex_agro_work_request — заявка (миграции 191, 195, 196, 197, 215): id, company_id, division_id, status (NEW/PLANNED/DONE/CANCELLED), object_type (ROAD/LIGHT_ROAD/…; NULL = сельхоз), geozone_id (куда), from_geozone_id (откуда), work_object_id, work_type_id, cargo_nomenclature_id, cargo_quantity, planned_area_ha (плановый объём в единице вида работ; для транспорта подставляется из количества груза), required_date, required_time, priority (1–3), transport_model, planned_trips, route_km, planned_km, round_trip, external_ref, vehicle_requirement, comment (VARCHAR(1000)), created_*, updated_*. Индексы: предприятие, статус, геозона, объект работ.

ex_agro_work_order — наряд (191, 192, 193, 195–199, 202, 204, 207, 215):

Группа Столбцы
Идентификация id, company_id, division_id, status (NEW/IN_PROGRESS/REVIEW/DONE), source (AUTO/MANUAL), request_id
Техника object_id, imei, object_name, object_type, trailer_id, employee_id, shift_id, shift_name
Место и груз geozone_id, from_geozone_id, work_object_id, work_type_id, cargo_nomenclature_id, cargo_quantity
План planned_start, planned_end (204; окно факта), planned_area_ha, planned_trips, route_km, planned_km, round_trip, transport_model
Окно автонаряда period_start, period_end (местное время); уникальность uq_awo_shift (object_id, shift_id, period_start) (192)
Факт fact_area_ha (у транспорта — «объём»), engine_seconds (193), work_seconds, transfer_seconds, idle_seconds, distance_km, refuel_liters, op_count, calc_at, started_at, completed_at
Ручной факт fact_source (NULL — телеметрия, MANUAL), fact_reason, fact_warnings (202, 204)
Контроль маршрута route_status (OK/PARTIAL/NONE/NO_DATA), trips_done, route_note (207)
Выгрузка export_batch_id, exported_at (199)

Индексы: idx_awo_company_period (company_id, period_start), idx_awo_company_planned (company_id, planned_start) (205), idx_awo_company_imei (company_id, imei, work_type_id) (206) и др.

ex_agro_work_request_point / ex_agro_work_order_point (215): id, request_id/order_id, seq, kind (BASE/LOAD/UNLOAD/STOP/WORK), geozone_id, cargo_quantity (DECIMAL(12,3)), contact (≤ 200).

ex_agro_work_order_op — операции факта (192, 193): order_id, seq, kind, start_dt, end_dt, duration_seconds, work_seconds, idle_seconds, engine_seconds, distance_km, avg_speed_kmh, area_ha, refuel_liters, level_before, level_after, zone_id, zone_name, from_zone_name, to_zone_name, no_nav. Пересоздаются при каждом пересчёте.

Простои: ex_downtime_reasons (company_id, name, code, external_guid, is_active, sort_order) и ex_agro_work_order_downtime (order_id, reason_id, minutes, note) — 198.

ex_agro_day_close (203): company_id, division_id (NULL — всё предприятие), day, closed_at, closed_by_user_id, summary (MEDIUMTEXT, JSON), reopened_at, reopened_by_user_id. Индекс (company_id, day, reopened_at).

ex_agro_export_batch (199): company_id, created_at, created_by_user_id, date_from, date_to, include_exported, order_count, document_count, payload (LONGTEXT — сам файл).

ex_work_objects (196, 214): id, company_id, parent_id, name, code, address, customer (текст), contract (текст), partner_id (контрагент, ex_company.id), geozone_id, external_guid, is_active, sort_order, description.

ex_vehicle_shift_drivers (201): vehicle_id, shift_interval_id (внешний ключ на ex_shift_intervals, каскад), employee_id; уникальность (vehicle_id, shift_interval_id).

Модели и виды работ: ex_object_brand_models (041, 042; расширение 200: code, equipment_kind ENUM(BUS, TRUCK, COMBINE, CAR, TRAILER, SPECIAL, AGRI_MACHINE, TRACTOR), tractor_conversion_coef, implement_width_m, load_capacity_t DECIMAL(10,1), is_active, sort_order, external_guid); ex_object_brand_model_work_types (200: model_id, work_type_id). Таблицы прежних «марок» (ex_equipment_brands) оставлены архивом.

Справочники-основания: ex_work_types (113, 115: company_id NULL = общий, unit_id; 199: external_guid), ex_nomenclature (096, 115), ex_units_of_measure (114), ex_shifts и ex_shift_intervals (073: start_time, duration_minutes), ex_employee (+external_guid, 199), ex_division_company (+external_guid), ex_company_geozones (017: zone_type point/line/polygon/group, radius_meters, geometry_json, use_for_address), ex_vehicles (imei, name, ex_division_id, garage_number, license_plate, work_type, parking_stop_minutes, fuel_consumption_norm, fuel_refill_threshold_liters, fuel_drain_threshold_liters, ex_object_brand_model_id, shift_id, driver_employee_id, inventory_number, external_guid; 120, 199, 206), ex_geocode_cache (012).

ex_objects — поля, значимые для блока: object_type (005, по умолчанию 'ROAD'), ex_shift_id (073), ex_division_id, parking_stop_minutes (003/004, по умолчанию 5), inactivity_minutes, work_width_m (192, для сельхоза), ex_object_brand_model_id (041), track_normalize (216), last_time.

Создаётся не миграциями: ex_user_allowed_divisions (при старте сервера); пункт меню «Наряды» (ex_menu_bot) — данные меню; колонки датчиков ex_d_<imei> — расчётный конвейер.

12.3. Миграции 190–216#

№ Суть Нужна транспорту
190 порог датчика «механизм» нет (спец/агро)
191 заявка и наряд да
192 source, окна смен, итоги, операции; ex_objects.work_width_m да
193 моточасы (engine_seconds) да
194 файла нет и в истории не было (пропуск нумерации) —
195 object_type в заявке да
196 «Объекты работ», work_object_id да
197 «откуда», груз да
198 исполнитель, причины простоев и простои да
199 пакеты выгрузки, GUID да
200 объединение марок и моделей, load_capacity_t, equipment_kind, виды работ модели да
201 исполнители по сменам да
202 ручной факт да
203 закрытие дня да
204 плановое окончание да
205–206 индексы да
207 контроль маршрута да (только транспорт)
208–212 планы, календарь культур, площадь поля нет (агро)
213 дробные литры (только новые датчики уровня) топливо
214 заказчик-контрагент объекта работ да
215 конструктор маршрута: модель, ходки, километры, точки, время подачи да
216 признак нормализации трека на машине да

Все файлы подключены в db.changelog-master.xml (проверка — автотест MigrationChangelogTest); миграции идемпотентны (MARK_RAN, precondition-sql-check).

13. REST API#

Общие правила: префикс /api/v1; JSON UTF-8; предприятие определяется по сессии (параметра «предприятие» нет); бизнес-ошибки — HTTP 400 {"message": "…"}; всё под /api/** требует входа (сессия веб-входа или HTTP Basic). Даты — ГГГГ-ММ-ДД, время — ГГГГ-ММ-ДД ЧЧ:ММ[:СС] (местное время предприятия). Роли: A — ROLE_ADMIN, V — ROLE_ADVANCED_USER, U — ROLE_USER, C — ROLE_CUSTOMER. Подробная OpenAPI-спецификация публикуется не для всех контроллеров (справочные контроллеры скрыты); доступность страницы Swagger на стенде не подтверждена.

13.1. Заявки и наряды — /agro_orders#

Все методы доступны ролям A, V, U, кроме отмеченных.

Метод и путь Роли Параметры / тело Назначение
GET /form_data A,V,U — справочники форм: виды работ, объекты работ, грузы, исполнители, причины простоев, техника (object_type, shift_id, driver_employee_id, default_work_type_id, load_capacity_t, shift_drivers[]), подразделения, прицепы, предприятие
GET /requests A,V,U divisionId, dateFrom, dateTo, q журнал заявок (с points[], utc); поиск по месту, виду, комментарию, объекту, заказчику, «откуда», грузу
GET /requests/last A,V,U workObjectId, objectType (оба обязательны) последняя не отменённая заявка по объекту и типу либо {}
GET /requests/{id} A,V,U — заявка
POST /requests A,V,U objectType, workObjectId, geozoneId, fromGeozoneId, workTypeId, cargoNomenclatureId, cargoQuantity, plannedAreaHa, requiredDate, requiredTime, priority, divisionId, comment, externalRef, vehicleRequirement, transportModel, plannedTrips, routeKm, plannedKm, roundTrip, points[{kind,geozoneId,cargoQuantity,contact}] создание заявки, ответ {id}
PUT /requests/{id} A,V,U то же правка (только NEW)
PUT /requests/{id}/status A,V,U {status} NEW или CANCELLED вручную; PLANNED/DONE ставятся по наряду
DELETE /requests/{id} A,V,U — удаление (только без наряда)
POST /requests/{id}/form_order A,V,U objectId, employeeId, plannedStart, plannedEnd, note, plannedTrips, routeKm, plannedKm формирование наряда, ответ {id}
GET / A,V,U divisionId, dateFrom, dateTo, q журнал нарядов (не более 5000 свежих)
GET /{id} A,V,U — наряд (+ points, plan_points с посещениями)
GET /{id}/ops, /{id}/track, /{id}/fuel A,V,U — операции / точки трека / топливо за период
POST /{id}/recalc A,V,U — пересчёт факта
POST /, PUT /{id} A,V,U geozoneId, workObjectId, fromGeozoneId, cargo…, workTypeId, objectId, employeeId, plannedStart, plannedEnd, plannedAreaHa, divisionId, trailerId, note + поля плана создание / правка; у AUTO правятся только вид работ, агрегат, исполнитель, подразделение, комментарий
PUT /{id}/status A,V,U NEW/IN_PROGRESS/REVIEW/DONE смена статуса
DELETE /{id} A,V,U — удаление; заявка-основание возвращается в NEW
PUT /{id}/fact, DELETE /{id}/fact A,V,U часы, factVolume, distanceKm, refuelLiters, reason ручной факт (ответ {warnings[]}) / возврат к телеметрии
POST /{id}/split A,V,U splitAt, employeeId разделение наряда, ответ {id} второй части
GET/POST /{id}/downtimes, DELETE /{id}/downtimes/{downtimeId} A,V,U reasonId, minutes, note простои
GET/POST /downtime_reasons, PUT/DELETE /downtime_reasons/{id} A,V,U name, code, active причины простоев
POST /route A,V,U points[{lat,lon}] (2–13), fixedHead, optimize, roundTrip маршрут (раздел 9)
PUT /objects/{objectId}/track_normalize A,V,U {enabled} признак нормализации трека
POST /construction_template A,V — типовой строительный набор справочников
POST /backfill, GET /backfill/status A,V dateFrom, dateTo (до 32 дат) «Обработать период» и его состояние

13.2. Закрытие дня — /agro_day_close#

Метод и путь Роли Тело / ответ
GET /pending A,V,U {today, notClosed, days[{date, orders, unfinished, closed}]}, последние 14 суток
GET /check?date&divisionId A,V,U closed, orders[{id, machine, executor, issues[], needsDecision, canMove, routeNote, drain…}], ordersTotal, withIssues, truncated, blocking
POST / A,V,U {date, divisionId, decisions{id: CLOSE|MOVE}, defaultDecision}
POST /range A,V,U {from, to, divisionId, defaultDecision}
GET /{id} A,V,U сводка для печати
DELETE /{id} A,V открыть день заново

13.3. Выгрузка в 1С — /agro_export (роли A, V, U)#

Метод и путь Параметры Ответ
GET /preview dateFrom, dateTo, includeExported orders, documents, alreadyExported, warnings[]
POST / {dateFrom, dateTo, includeExported} {id, orders, documents}; период ≤ 93 дней
GET / — до 50 последних пакетов
GET /{id}/file — application/json, вложение exzotron-agro-export-<id>.json; тот же файл при каждой загрузке
GET /print/{orderId} — данные печатной формы (документ «машина + исполнитель + дата»); страница agro_print.html?order=

13.4. Справочники блока#

Метод и путь Роли Параметры Примечание
GET/POST/PUT/DELETE /work-objects, PUT /work-objects/{id}/link A,V,U,C name, parentId, code, address, partnerId, contract, geozoneId; link — {geozoneId, partnerId, contract} DELETE → {deleted: true|false} (деактивация, если используется)
GET /partner A,V,U,C type список контрагентов
POST /partner/quick A,V,U {name, itn} возвращает существующего (по названию без регистра) либо создаёт тип «Покупатели»
POST/PUT/DELETE /partner, GET /partner/type A (type — A,V) Partner
GET/POST/PUT/DELETE /nomenclature A,V,U,C name, unitId, article… общие записи только на чтение
GET/POST/PUT /work-types, DELETE /work-types/{id} A,V,U,C name, code, unitId, active, sortOrder
GET/POST/PUT/DELETE /equipment-brands, GET/PUT /equipment-brands/{id}/work-types A,V,U,C loadCapacityT, equipmentKind, implementWidthM…; {workTypeIds[]} «Модели техники»
GET/POST/PUT /shifts, DELETE /shifts/{id} чтение A,V,U; запись A {name, intervals[{name,startTime,durationMinutes}]} сумма смен = 1440 минут
GET /vehicle, GET /vehicle/{id}, GET /vehicle/{id}/shift-drivers A,V,U search, control
POST/PUT/DELETE /vehicle A,V Vehicle, shiftDrivers[{shiftIntervalId, employeeId}] PUT синхронизирует график с объектом
GET/POST/PUT/DELETE /employee, /employee/positions чтение A,V,U; запись A,V
GET/POST/PUT/DELETE /division чтение 4 роли; запись A
GET/POST/PUT/DELETE /units-of-measure 4 роли
GET/POST/PUT/DELETE /geozones, /geozones/export_kml, /geozones/import_kml[/preview] любая вошедшая роль {type: point|line|polygon|group, name, parentId, radiusMeters, geometry, useForAddress…} быстрое место: type=point, geometry=[lon,lat], radiusMeters 10–5000 (проверка на клиенте)

13.5. Геокодер — /service_orders (A, V, U)#

GET /service_orders/geocode и GET /service_orders/reverse_geocode — раздел 10.

14. Выгрузка в 1С#

14.1. Условия попадания наряда#

В файл попадают наряды предприятия со статусом «Завершён», чья дата документа DATE(COALESCE(period_start, planned_start, started_at)) (местное время) попадает в период включительно (разность дат не более 93 дней), и по умолчанию ещё не выгруженные (export_batch_id IS NULL). Флажок includeExported отдаёт все завершённые наряды периода и переносит их в новый пакет. Ограниченному по подразделениям пользователю — только его наряды. Пустая выборка: «За выбранный период нет принятых нарядов для выгрузки». Ошибки периода: «Укажите период выгрузки», «Дата «по» раньше даты «с»», «Период выгрузки не больше 93 дней», «Неверный формат даты (ожидается ГГГГ-ММ-ДД)».

Предупреждения предпросмотра («Не заполнено», выгрузка не блокируется — 1С получит пустые ссылки): «Наряд №N: не назначен исполнитель»; «Наряд №N: не указан вид работ» (для легкового не выдаётся); «Наряд №N: техника «…» не заведена в справочнике транспорта» (нет карточки автомобиля с тем же IMEI в предприятии).

14.2. Структура файла#

Файл — JSON, схема exzotron.agro.export, версия 1. Документ = дата + вид документа + машина + исполнитель; вид — TRUCK_WAYBILL (ROAD) или CAR_WAYBILL (LIGHT_ROAD). GUID справочников создаются при первой выгрузке и не меняются.

Уровень Поля
Корень schema, schemaVersion, company {id, name}, period {from, to}, documents[]
Документ number (порядок в файле), kind, date, vehicle {id, guid, inventoryNumber, licensePlate, garageNumber, name}, employee {id, guid, code (табельный), name}, division, rows[], totals {orders, engineHours, workHours, idleHours, distanceKm, refuelLiters, downtimeMinutes}
Строка (наряд) orderId, source (AUTO/MANUAL), shift, periodStart, periodEnd, workType, workObject, geozone («куда»), fromGeozone («откуда»), cargo {id, name, quantity, unit}, hours {engine, work, idle, transfer}, unit, planVolume, factVolume, factVolumeSource, factSource, factReason, routeCheck {status, tripsDone, note}, areaHa, distanceKm, refuelLiters, fields[], downtimes[], note

Особенности строки для транспорта:

  • periodEnd: автонаряд — конец смены; ручной — плановое окончание, иначе конец смены машины, иначе конец суток.
  • hours — пустые (null, а не 0), пока факт не рассчитан.
  • planVolume = planned_area_ha; у грузового равен количеству груза («т»).
  • factVolume + factVolumeSource = "ROUTE_CONTROL" — груз × рейсы при подтверждённом маршруте (раздел 5); иначе по единице вида работ: «га» — площадь, «ч» — работа (у транспорта 0), «км» — пробег; при ручном факте — введённое «Выполнено».
  • factSource: MANUAL / NO_DATA (расчёт пуст: моточасы, работа, пробег и заправка нулевые) / TELEMETRY; factReason — причина ручного факта.
  • routeCheck {status, tripsDone, note} — контроль маршрута; без плана — null.
  • areaHa при ручном факте содержит «Выполнено» в единице вида работ (т, м³), а не гектары; factVolume при единице «ч» у транспорта равно 0,0 (work_seconds всегда 0). Интеграторам для транспорта рекомендуется брать factVolume и routeCheck.
  • fields[] для транспорта пуст (только рабочие операции полей).
  • downtimes[]: {reason {id, guid, code, name}, minutes, note}.

Иллюстративный фрагмент строки (значения условные):

{
  "orderId": 359, "source": "MANUAL", "workType": "…", "workObject": "…",
  "geozone": "…", "fromGeozone": "…",
  "cargo": {"id": 1, "name": "…", "quantity": 4.0, "unit": "т"},
  "hours": {"engine": 9.4, "work": 0.0, "idle": 1.1, "transfer": 7.0},
  "unit": "т", "planVolume": 4.0,
  "factVolume": 4.0, "factVolumeSource": "ROUTE_CONTROL",
  "factSource": "TELEMETRY", "factReason": null,
  "routeCheck": {"status": "OK", "tripsDone": 1,
                 "note": "погрузка 19 мин (08:07–08:26), разгрузка 10 мин (14:38–14:48)"},
  "areaHa": null, "distanceKm": 75.5, "refuelLiters": 0.0,
  "fields": [], "downtimes": [], "note": null
}

Пакет хранится в ex_agro_export_batch.payload; повторная загрузка отдаёт тот же файл. Ранее выпущенное описание схемы файла этих полей (factSource, factReason, factVolumeSource, routeCheck) не содержит: ориентируйтесь на настоящую статью. Реальная загрузка файла в 1С не проверялась, формат с разработчиком 1С не согласован; передачи по сети, обратной загрузки, статуса «принято в 1С» и расценок нет. Для транспорта ключ сопоставления inventoryNumber в интерфейсе карточки автомобиля заполнить нельзя (поле убрано из формы) — сопоставляйте по госномеру либо заводите значение через API.

14.3. Печать путевого листа#

GET /agro_export/print/{orderId} → страница agro_print.html?order=ID. Документ собирается по принципу «машина + исполнитель + дата» из всех нарядов этого дня в любом статусе (наряд-основание всегда включён); структура та же, что в файле выгрузки; номер документа условный.

Форма Колонки
Грузовой (TRUCK_WAYBILL) № · Наряд · Время · Объект работ · Откуда · Куда · Груз, количество · Контроль маршрута · Вид работ · Моточасы, ч · Пробег, км · Заправка, л
Легковой (CAR_WAYBILL) № · Наряд · Время · Откуда · Куда · Контроль маршрута · Пробег, км · Заправка, л

Колонка «Контроль маршрута»: {рейс выполнен | частично | не подтверждён | нет данных навигации} (рейсов: N) — {примечание}. Ниже: «Простои (всего N мин)», «Примечания», «Факт введён вручную (без данных навигации): №N — причина», подписи «Водитель», «Диспетчер», «Ответственный». Не печатаются: «Перевезено», плановые ходки и километры, точки плана, вкладка топлива. Бланк произвольной формы, не унифицированная форма. Печатная сводка дня — agro_day_print.html?id=… («Сводка закрытия дня № N»; номер сквозной по базе).

15. Конфигурация#

Свойство / механизм По умолчанию Назначение и оговорки
scheduler.enabled false включает все @Scheduled, в том числе автонаряды. Включать только на одном процессе из пары; на стенде включён в процессе, где работает окно
Расписание автонарядов initialDelay 240 000 мс, fixedRate 900 000 мс AgroFactService.spawnShiftOrders: окна сегодня и два прошлых дня, LOOKBACK_HOURS = 36, принудительный пересчёт окна раз в час
spring.task.scheduling.pool.size 10 пул планировщика
calc-pipeline.enabled / workers / batch-size / lease-minutes / profile-cache-ttl-ms true / 8 / 25 / 5 / 300 000 без конвейера факта нет; calc-pipeline.max-window-hours по умолчанию 6
route.osrm.enabled true false — только оценка «по прямой × коэффициент»
route.osrm.url публичный демонстрационный OSRM основной OSRM; переменная окружения ROUTE_OSRM_URL
route.osrm.fallback-url пусто запасной OSRM; используется, только если основной не ответил; ROUTE_OSRM_FALLBACK_URL
route.detour.factor 1.3 коэффициент извилистости для оценки
Лимиты клиента OSRM соединение 5 с, запрос 8 с; не более 13 точек; средняя скорость оценки 40 км/ч
address.geocoder.enabled true отключает внешний обратный геокодер; прямой поиск не отключается; переопределяется настройкой в базе
address.geocoder.provider nominatim любое значение, кроме yandex, → Nominatim; Яндекс — только обратное геокодирование
address.geocoder.nominatim.url публичный сервер Nominatim лимит 1 запрос/с (NOMINATIM_MIN_INTERVAL_MS = 1100), один повтор через 1,6 с при 503/429
address.geocoder.yandex.url / api-key / user-agent / accept-language …/ пусто / служебное значение / ru ключ Яндекса хранить только в настройке сервера
Статические файлы max-age=1d версия окна «Наряды» задаётся в трёх местах: шаблон head.html (боковое меню), sidebar.controller.js (адрес окна) и agro_orders.html (css/js); текущая версия 20261013-qp14. При правке окна забытое место приводит к показу старого кода
Пояс предприятия ex_users_company.work_fact_utc, пусто = +3 границы смен, «сегодня» при закрытии дня

Кэши. Права по подразделениям — 10 с в памяти; состояние пропуска окон lastSeen — в памяти, до 100 000 записей, сбрасывается при перезапуске; адреса обратного геокодирования — ex_geocode_cache; прямой поиск не кэшируется; маршруты — только в браузере. В браузере (localStorage) хранятся agroOrdersType, agroRequestsType, agroWorkObjectRecent, состояние карты «скрыта/показана».

Внешние зависимости окна. Библиотеки карты (MapLibre 5.24.0) и графиков (ECharts 5.5.1) загружаются с внешнего CDN (unpkg); подложки карты — внешние серверы тайлов (OSM, Google, Esri, Yandex). В закрытом контуре без интернета окно не получит карту; работа в изолированной сети не проверялась.

Логи. Отдельный файл logs/agro-fact.log — только AgroFactService и AgroFactScheduler; RouteService, AddressResolveService, AgroDayCloseService пишут только в консоль процесса.

16. Тесты и покрытие#

Всего по блоку: 69 Java-тестов в 8 классах и один JS-скрипт; тесты работают на синтетических данных, на реальных треках не гонялись.

Класс Тестов Что покрывает
AgroAccessTest 6 ограничения по подразделениям (без ограничений, фиксированное, список, единственное разрешённое, несколько — явный выбор, скрытие чужих и записей без подразделения)
AgroDayCloseServiceTest 20 коды замечаний, решения CLOSE/MOVE, общее решение, перенос автонаряда, пересечение окон, закрытие диапазона, будущий день, блокировка закрытого дня
AgroExportServiceTest 5 routeFactVolume (груз × рейсы только при подтверждённом маршруте и совпадении единиц), factSource, отнесение переездов к полю назначения
AgroFactServiceTest 12 окна смен (дневная, ночная через полночь, две смены, без графика), пропуск пересчёта без новых данных и перепроверка раз в час, фильтр полей, склейка, перескок через пропуск данных, холостой ход транспорта в геозоне как посещение, замена суточного автонаряда рейсовым
AgroRouteCheckTest 13 check для пары: нет плана, один рейс, два рейса, только погрузка/разгрузка, нарушенный порядок, ни одна точка, недостоверная навигация (NONE → NO_DATA), нет телеметрии, одна зона
AgroWorkServiceTest 4 предупреждения правдоподобия ручного факта
RouteOptimizerTest 8 порядок точек развоза, неподвижная «голова», круговой маршрут, эвристика для многих точек, checkSequence
MigrationChangelogTest 1 каждый файл миграции подключён в db.changelog-master.xml
track_normalizer.test.js 6 сценариев чистый трек, телепорты, мало спутников и «залипшая» скорость, почти полная подмена (надёжных < 30%), длинная пауза, хвост подмены; запуск node docs/agro-dispatch/track_normalizer.test.js

Не покрыто тестами: RouteService (OSRM, оценка, запасной адрес), AddressResolveService (поиск адреса, повтор при 503), быстрый заказчик и объекты работ, сохранение карточки автомобиля и закрепления по сменам, правило CAN (idleMovedByCanMileage), потолки скорости по типу техники, критерий активности, замена автонарядов на уровне SQL, формирование наряда из заявки и нормализация плана, разделение наряда, запрет завершения до даты, печатная форма, интерфейс, проверки доступа REST по ролям, расчёт километража и ходок на клиенте.

17. Известные ограничения#

Расчёт и контроль маршрута

  1. Закладку определяет только ex_objects.object_type; автонарядов нет без графика смен.
  2. Один наряд — один исполнитель; замена — разделение наряда (только ручные не завершённые); во вторую часть не копируются ходки, километры, модель, точки и количество груза.
  3. Контроль маршрута — по посещению геозон. При ≥ 30% недостоверной навигации «не подтверждён» превращается в «нет данных навигации»; «N % времени» — доля длительности операций, а не окна.
  4. Развоз без точки погрузки с двумя точками выгрузки контролируется по первой выгрузке (ветка checkSequence включается при плане > 2 точек без базы). В развозе указывать погрузку.
  5. Факт ручного наряда без планового окончания считается за смену машины либо за сутки.
  6. Ручной факт не обнуляет route_status, trips_done, route_note: замечания ROUTE_* и routeCheck остаются от прежнего расчёта.
  7. У легкового (и у ROAD при коротких холостых стоянках) пометка noNav практически не попадает в операции — правило 30% там не срабатывает: NONE остаётся NONE.
  8. Пересекающиеся геозоны: точка относится к первой подходящей по списку.
  9. Замена автонарядов удаляет автонаряд целиком, включая ручные правки и всё время суток вне окна рейса; принятый автонаряд не заменяется (DUPLICATE).
  10. Для легкового заявка допускает пустой вид работ, а наряд — нет («Выберите вид работ»): нужен технический вид работ.
  11. Правки рейсового наряда не пересчитывают факт; «Открыть день заново» не возвращает статусы; при удалении наряда его операции остаются в таблице (внешнего ключа нет) — на логику не влияет, но таблица растёт.
  12. Нумерация заявок, нарядов и сводок сквозная по базе.

Конструктор и справочники

  1. План объёма не обновляется при правке количества груза (подставляется только в пустое поле).
  2. Если «Откуда» выбрано, а «Куда» пусто, заявка сохраняется с «Куда» = «Откуда» (при одной точке список берёт последнюю точку как назначение).
  3. Предел сохранения (12 точек) не совпадает с пределом расчёта дорог (13 точек); интерфейс предела не показывает.
  4. «Подать к времени», № заявки заказчика и «Требования к технике» в наряд, печать и выгрузку не переходят; план (модель, ходки, км) в файл 1С не входит.
  5. Единица «т» зашита в подсказках и не сверяется с единицей груза; грузоподъёмность недоступна в форме заявки и для машины без карточки с моделью.
  6. Удаление геозон и грузов не проверяет использование; деактивированный объект работ пропадает из формы — правка старой заявки сотрёт ссылку на него.
  7. Дубли контрагентов: сопоставление по названию без регистра по любому типу; проверки по ИНН нет.
  8. Серия «повтор N дней» не атомарна; проверка дубля — только для первой даты. Журнал заявок без ограничения числа записей. Данные справочников загружаются один раз при открытии окна.

Интеграция и инфраструктура

  1. Выгрузка в 1С — файл JSON версии 1; обратной загрузки, статуса «принято в 1С» и расценок нет.
  2. Профиль OSRM — car; время в пути — ориентир. Публичный OSRM и Nominatim — демонстрационные.
  3. Права: почти все операции окна доступны ROLE_USER; ограничены лишь «Открыть день заново», «Обработать период» и «Типовой набор». Ролей «диспетчер/руководитель» нет.
  4. У пользователя с несколькими доступными подразделениями запись без выбранного подразделения исчезает из его журнала.
  5. Заявки подаёт только диспетчер; уведомлений и мобильного приложения нет.
  6. Ограничения числа: журнал нарядов — 5000 свежих; окно «Закрыть день» — 300 нарядов с замечаниями; закрытие диапазона — «до 31 дня» (практически 32 даты); «Обработать период» — до 32 дат; выгрузка — «до 93 дней» (практически 94 даты включительно); список пакетов — последние 50.

Открытое по прогону на стенде: добавление автомобиля «короткой строкой» сохраняет его без подразделения, госномера и гаражного номера; старые автонаряды без вида работ дают предупреждения в выгрузке (завершённые правятся после открытия дня); «рейс не состоялся» оформляется удалением наряда, отдельной кнопки с причиной нет.

18. Backlog (этап 2)#

Не реализовано, зафиксировано в проектных документах:

  • несколько машин за раз («бригада»), нормы выработки и график занятости техники;
  • несколько погрузок и разные грузы по точкам (в модели есть kind), время на погрузку в расчёте смены;
  • фактическое развёртывание собственного OSRM;
  • серверный пробег по нормализованному треку и применение нормализатора в других окнах трека;
  • время/окно прибытия, привязка нового вида работ к типу техники (сейчас связь идёт через модель), фильтр журнала по заказчику, последние и частые места, правка радиуса зоны «Места заявок» в окне, контакт на объекте и ссылка в навигатор, список договоров контрагента, проверка дублей контрагентов по ИНН, мобильный режим окна;
  • экран «Смена сегодня», наряды на смену одним действием, план/факт/отклонение в журнале, контроль пересечений при создании (у техники и у исполнителя), пакетная печать, история изменений наряда, отчёты (сменный, недельный, по объектам), выгрузка журнала в Excel;
  • нагрузочная проверка на боевом сервере.

Сделано 12.10.2026 (ранее было в backlog): «как в прошлый раз» (GET /agro_orders/requests/last), предупреждение о дубле заявки, точки плана с отметкой факта, запасной адрес OSRM и комплект своего OSRM.

19. Не подтверждено#

Помимо ограничений раздела 17, не подтверждены:

  • закладка «Легковой» на живых данных (отдельного прогона нет);
  • работа при часовом поясе предприятия не +3; многосменный график и смена через полночь у транспорта (только по коду);
  • права роли ROLE_USER на живых данных: отказ в «Открыть день заново», «Обработать период» и «Типовой набор» под обычным пользователем не проверялся;
  • загрузка файла выгрузки в 1С; запуск команд комплекта собственного OSRM; работа окна без доступа в интернет;
  • состояние боевого сервера (планировщик, пункт меню «Наряды», применённые миграции) после 10.10.2026;
  • предупреждение о радиусе геозоны < 100 м для грузовиков (в коде нет; есть радиус по умолчанию 150 м);
  • влияние порогов заправки и слива из карточки автомобиля на наряд (в расчёте наряда не найдено; события задаются настройками датчика уровня: fuel_min_refuel_liters, fuel_min_drain_liters, по умолчанию 20 л, для «бака» 200 л) и связь «Нормы на 100 км» карточки с нормой объекта в плитке топлива (в коде не найдена);
  • «+20 минут на точку» в расчёте времени и серверный кэш маршрутов — в проектных документах упоминались, в коде отсутствуют.

20. Что читать дальше#

Вложения