Версия ПО: Exzotron Server (сборка от 15.10.2026) · Проверено: 15.10.2026 · Ответственный компонент: заявки с составом техники и «План выезда», расчёт факта и контроль маршрута, закрытие дня, выгрузка в 1С, отчёт по объектам строительства, REST API и модель данных. Примеры — данные стенда «Тест строительной техники».
Статья для интегратора, технического специалиста и службы поддержки. Она описывает, как устроены модель данных и REST API блока Exzotron СТРОЙ, по каким правилам делится груз в «Плане выезда», как строится факт спецтехники и транспорта, как засчитывается проезд по участку дороги, как считается «Перевезено», как формируются закрытие дня, файл для 1С и отчёт по объектам строительства. Обзорное, не техническое описание — в статье «Архитектура и устройство Exzotron СТРОЙ». Общая для транспорта часть (окно расчёта, пороги отрезков, планировщик автонарядов, конструктор маршрута, маршрутизация, геокодер, нормализатор трека) подробно изложена в комплекте Exzotron ТРАНС, в статье «Алгоритмы и интеграции Exzotron ТРАНС (техническое руководство)»; здесь она повторяется только там, где у СТРОЯ есть отличия. Адреса серверов, учётные данные и ключи не приводятся.
Соглашения. Типы техники окна «Наряды»: SPECIAL — закладка «Спецтехника», ROAD — «Автотранспорт», LIGHT_ROAD — «Легковой», AGRICULTURE — «Сельхозтехника» (комплект АГРО). Роли: A — ROLE_ADMIN, V — ROLE_ADVANCED_USER, U — ROLE_USER, C — ROLE_CUSTOMER. Время в API и в расчётах — местное время предприятия (смещение «UTC компании», по умолчанию +3); сырые данные приборов хранятся в UTC. Помета «по аудиту, не проверено в коде» означает, что утверждение взято из аудитов комплекта (_audit/01–03) и при подготовке статьи кодом не перепроверялось; всё остальное сверено с кодом на HEAD fcf23f77 (15.10.2026).
1. Общая схема#
Блок работает поверх общих слоёв Exzotron:
- Телематика объекта. Приёмник пишет «сырые» точки в таблицу объекта (
ex_d_<imei>): время, координаты, скорость, спутники, показания датчиков. Расчётов приёмник не делает. - Расчётный конвейер датчиков (общий для всего Exzotron) дополняет таблицу производными колонками (зажигание, уровень топлива, пробег, скорость) и событиями заправок и сливов (
ex_fuel_level_events). Без включённого конвейера факт нулевой. - Расчёт факта блока (
AgroFactService,ExObjectService.getActionSegments) строит из отрезков телеметрии операции наряда и итоги; контроль маршрута (AgroRouteCheck) сверяет план с посещениями геозон. Планировщик (AgroFactScheduler) каждые 15 минут создаёт и досчитывает автонаряды; ручные наряды считаются по событиям. - Диспетчерский слой: заявки, состав, комплекты, «План выезда», наряды, простои, ручной факт, закрытие дня (
AgroWorkService,AgroPlanDao,AgroDayCloseService). - Результаты: печатные формы, выгрузка в 1С (
AgroExportService), отчёт по объектам строительства (ConstructionReportService, страницаreport_construction).
Доступ по подразделениям (AgroAccess) применяется в слое 4 и в выгрузке; отчёт его не применяет (раздел 5.14).
2. Модель данных#
2.1. Связи#
Внешних ключей между таблицами блока почти нет (только в миграциях 017, 200, 201: каскад вложенных геозон, привязка видов работ к модели, интервал смены); целостность держится кодом, поэтому «висячие» ссылки возможны.
контрагент (ex_company) ← ex_work_objects.partner_id
ex_work_objects ─ parent_id → ex_work_objects (объект → участок)
ex_work_objects ─ geozone_id → ex_company_geozones (point | line | polygon | group; parent_id — группа)
ex_agro_work_request ─ work_object_id → ex_work_objects
│ ─ geozone_id (куда), from_geozone_id (откуда) → ex_company_geozones
│ ─ work_type_id → ex_work_types ─ unit_id → ex_units_of_measure
│ ─ cargo_nomenclature_id → ex_nomenclature
│ ─ combo_id → ex_agro_work_combo (только ссылка-запоминание)
├── ex_agro_work_request_unit (request_id): состав — brand_model_id → ex_object_brand_models
├── ex_agro_work_request_point (request_id): точки маршрута
└── ex_agro_work_order (request_id, request_unit_id → строка состава)
├── ex_agro_work_order_op (order_id): операции факта
├── ex_agro_work_order_point (order_id): точки маршрута наряда
└── ex_agro_work_order_downtime (order_id) → ex_downtime_reasons
ex_agro_work_order ─ object_id, imei → ex_objects; imei + компания → ex_vehicles (карточка «Автотранспорт»)
ex_agro_work_order ─ employee_id → ex_employee
ex_agro_work_order ─ export_batch_id → ex_agro_export_batch
ex_agro_day_close: по (company_id, day, division_id) блокирует наряды дня
Карточка ex_vehicles связывается с объектом мониторинга только по паре «IMEI + предприятие» (ex_vehicles.imei = ex_objects.imei AND ex_vehicles.ex_users_company = ex_objects.company_id); для «Плана выезда» и колонки «Модель» отчёта нужна именно эта карточка.
2.2. Заявка и состав#
ex_agro_work_request (миграции 191, 195, 196, 197, 215, 236):
| Группа | Столбцы |
|---|---|
| Идентификация | id, company_id INT, division_id INT, status VARCHAR(20), object_type VARCHAR(20) (195; NULL = сельхоз) |
| Место | geozone_id (место работ / «куда»), from_geozone_id (197), work_object_id (196) |
| Работа и груз | work_type_id, cargo_nomenclature_id, cargo_quantity DOUBLE (197), planned_area_ha DOUBLE (плановый объём в единице вида работ) |
| Срок | required_date DATE, required_time TIME (215), window_from TIME, window_to TIME (236), priority TINYINT (1–3, по умолчанию 2) |
| План перевозки (215) | transport_model VARCHAR(10) (SINGLE/SHUTTLE/MULTI, по умолчанию SINGLE), planned_trips INT, route_km DECIMAL(10,2) (за ходку), planned_km DECIMAL(10,1), round_trip TINYINT(1) (по умолчанию 1) |
| Прочее | external_ref VARCHAR(100) (№ заявки заказчика), vehicle_requirement VARCHAR(150), comment VARCHAR(1000), combo_id (236), shift_id (236, не используется) |
| Служебные | created_at, created_by_user_id NOT NULL, updated_at, updated_by_user_id |
Индексы: idx_awr_company, idx_awr_status, idx_awr_geozone, idx_awr_work_object. Столбец planned_area_ha без единицы в названии (исторически «га»): единицу определяет вид работ. У грузовой заявки при пустом значении подставляется количество груза.
ex_agro_work_request_unit — состав заявки (236): id, request_id NOT NULL, seq INT, brand_model_id BIGINT NOT NULL (ex_object_brand_models.id_object_brand_model), qty INT (по умолчанию 1; 1–99), work_type_id, planned_volume DOUBLE («плановый объём на одну единицу техники в единице вида работ»), note VARCHAR(200); индекс idx_awru_request. При правке заявки строки удаляются и создаются заново (новые id).
ex_agro_work_combo (236): id, company_id, name VARCHAR(200), description VARCHAR(500), active TINYINT(1), external_guid VARCHAR(36) (при создании UUID()), created_at, created_by_user_id; ex_agro_work_combo_item: id, combo_id, seq, brand_model_id, qty, work_type_id, note. Столбца объёма на единицу в комплекте нет: введённое в окне комплекта значение теряется.
ex_agro_work_request_point / ex_agro_work_order_point (215): id, request_id / order_id, seq, kind VARCHAR(10) (BASE/LOAD/UNLOAD/STOP/WORK), geozone_id NOT NULL, cargo_quantity DECIMAL(12,3), contact VARCHAR(200). План выезда копирует точки заявки в каждый наряд целиком, включая количества.
2.3. Наряд, операции, простои#
ex_agro_work_order (191, 192, 193, 196–199, 202, 204, 207, 215, 236):
| Группа | Столбцы |
|---|---|
| Идентификация | id, company_id, division_id, status (NEW/IN_PROGRESS/REVIEW/DONE), source VARCHAR(10) (AUTO/MANUAL, по умолчанию MANUAL), request_id, request_unit_id (236; строка состава — по ней считается охват) |
| Техника | object_id, imei, object_name, object_type VARCHAR(32), trailer_id, employee_id (198), shift_id INT, 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) |
| Факт | 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 VARCHAR(12) (NULL — телеметрия, MANUAL), fact_reason VARCHAR(255), fact_warnings VARCHAR(500) |
| Контроль маршрута | route_status VARCHAR(12) (OK/PARTIAL/NONE/NO_DATA/NULL), trips_done INT, route_note VARCHAR(500) |
| Выгрузка | export_batch_id, exported_at (199) |
| Служебные | note VARCHAR(1000), created_at, created_by_user_id (у автонарядов — 0), updated_at, updated_by_user_id |
Индексы: idx_awo_company, _status, _object, _request, _geozone, _work_object, _employee, _export, 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), idx_awo_request_unit (236).
ex_agro_work_order_op — операции факта (192, 193): id, order_id, seq, kind (WORK, TRANSFER, VISIT, REFUEL, а также IDLE), 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 TINYINT. Строки пересоздаются при каждом пересчёте и при ручном факте очищаются. Признака «проезд по участку» (pass) в таблице нет: он существует только в памяти на время расчёта. При удалении наряда его операции остаются (нет внешнего ключа).
ex_agro_work_order_downtime (198): order_id, reason_id, minutes INT, note VARCHAR(500), created_at, created_by_user_id; ex_downtime_reasons: company_id, name VARCHAR(150), code VARCHAR(50), external_guid CHAR(36), is_active, sort_order.
2.4. Закрытие дня и выгрузка#
ex_agro_day_close (203): company_id, division_id (NULL — вся компания), day DATE, closed_at, closed_by_user_id, summary MEDIUMTEXT (JSON-снимок), reopened_at, reopened_by_user_id; индекс (company_id, day, reopened_at). Действующая запись — с reopened_at IS NULL.
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 (сам файл).
2.5. Объекты работ, геозоны, справочники#
ex_work_objects (196, 214): id, company_id, parent_id, name VARCHAR(255) NOT NULL, code VARCHAR(50), address VARCHAR(500), customer VARCHAR(255) (текст), contract VARCHAR(255) (текст), partner_id INT (контрагент, ex_company.id), geozone_id, external_guid CHAR(36), is_active, sort_order, description VARCHAR(500), created_by_user_id, created_at, updated_at. При выборе контрагента customer заполняется его названием; переименование контрагента обновляет customer у его объектов.
ex_company_geozones (017): id, company_id BIGINT, parent_id (внешний ключ на себя, ON DELETE CASCADE), zone_type VARCHAR(32), name VARCHAR(255), color, radius_meters DOUBLE, width_meters DOUBLE, geometry_json JSON (массив [lon, lat]; у точки — одна пара), show_on_map, use_for_address, description. Значения zone_type: point (круг: центр + радиус, подпись «Точка»; значения circle в коде нет), line, polygon, group.
Справочники-основания (создаются не этим блоком): ex_work_types (company_id, name, code, unit_id, is_active, sort_order, external_guid), ex_units_of_measure (name, short_name), ex_nomenclature (грузы; общие записи — company_id IS NULL), ex_object_brand_models (модели техники: id_object_brand_model, ex_users_company_id, name, 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 (model_id, work_type_id), ex_vehicles (карточка «Автотранспорт»: id_vehicles, imei, ex_users_company, name, license_plate, garage_number, inventory_number, ex_object_brand_model_id, shift_id, driver_employee_id, parking_stop_minutes, external_guid), ex_vehicle_shift_drivers (201; уникальность (vehicle_id, shift_interval_id)), ex_employee (id_employee, id_code — табельный номер, external_guid), ex_shifts и ex_shift_intervals, ex_division_company (external_guid), ex_objects (поля, значимые для блока: object_type по умолчанию ROAD, ex_shift_id, ex_division_id, parking_stop_minutes по умолчанию 5, work_width_m — только для сельхоза, ex_object_brand_model_id, track_normalize (216), last_time).
Создаётся не миграциями: пункт меню «Наряды» (данные меню), ex_user_allowed_divisions, колонки датчиков ex_d_<imei>.
2.6. Перечисления#
| Поле | Значения |
|---|---|
| Статус заявки | NEW «Новая», PLANNED «Запланирована», DONE «Выполнена», CANCELLED «Отменена» |
| Статус наряда | NEW «Новый», IN_PROGRESS «На исполнении», REVIEW «На проверке», DONE «Завершён» |
| Источник наряда | AUTO — создан по смене, MANUAL — ручной (в том числе из заявки и плана выезда) |
| Источник факта | NULL — телеметрия, MANUAL — введён вручную |
| Контроль маршрута | OK, PARTIAL, NONE, NO_DATA; NULL — плана «откуда → куда» нет |
| Модель перевозки | SINGLE, SHUTTLE («Ходки (челнок)»), MULTI («Развоз на несколько точек») |
| Операции | WORK, TRANSFER, VISIT, IDLE, REFUEL |
| Замечания закрытия дня | NOT_CLOSED, NO_EXECUTOR, NO_FACT, MANUAL_FACT, SUSPECT_GPS, DUPLICATE, FUEL_DRAIN, ROUTE_NONE, ROUTE_PARTIAL, ROUTE_NODATA |
| Вид документа в 1С | MECHANISM_REPORT (SPECIAL), TRUCK_WAYBILL (ROAD), CAR_WAYBILL (LIGHT_ROAD), AGRO_WAYBILL (прочее) |
| Тип техники объекта → закладка | equipment_kind модели: SPECIAL → SPECIAL; TRUCK, BUS → ROAD; CAR → LIGHT_ROAD; TRACTOR, COMBINE, AGRI_MACHINE → AGRICULTURE; TRAILER — не привязывается (для списка видов работ формы) |
2.7. Идентификаторы и GUID#
Для сопоставления с 1С в файл выгрузки уходят ссылки вида {id, guid, code, name}. GUID (external_guid) заводится при первой выгрузке у видов работ предприятия, объектов работ (на которые ссылаются выгружаемые наряды и на которых наряд отработал по рабочим операциям), сотрудников, карточек транспорта, подразделений и причин простоев (UUID()); общие виды работ (company_id IS NULL) не трогаются. Геозоны и грузы GUID не имеют: в файле они передаются как {id, name}. Естественные ключи: табельный номер сотрудника (code), инвентарный и гаражный номер и госномер машины, коды видов работ и объектов работ. Уникальность табельного номера и кодов не проверяется.
3. REST API#
Общие правила: префикс /api/v1; JSON UTF-8; предприятие определяется по сессии (параметра «предприятие» нет); бизнес-ошибки — HTTP 400 {"message": "…"}; всё под /api/** требует входа (сессия веб-входа или HTTP Basic). Даты — ГГГГ-ММ-ДД, дата-время — ГГГГ-ММ-ДД ЧЧ:ММ[:СС]. Часть справочных контроллеров скрыта из Swagger (@ApiIgnore). Все методы доступны ролям A, V, U, если не сказано иное.
3.1. Заявки, состав, комплекты, план выезда — /agro_orders#
| Метод и путь | Назначение | Роли |
|---|---|---|
GET /form_data |
справочники форм: workTypes, workObjects, cargos, employees, downtimeReasons, objects (с load_capacity_t, driver_employee_id, shift_drivers), divisions, trailers, brandModels, combos (при отсутствии миграции 236 — пусто), company |
A,V,U |
GET /requests?divisionId&dateFrom&dateTo&q |
журнал заявок; в каждой строке window_from, window_to («ЧЧ:ММ»), combo_id, order_count, order_id (наименьший номер наряда), units[], unit_total, unit_assigned, points[]; поиск q — по месту, виду работ, комментарию, объекту, заказчику, «откуда», грузу |
A,V,U |
GET /requests/last?workObjectId&objectType |
последняя не отменённая заявка по объекту и типу либо {} («как в прошлый раз») |
A,V,U |
GET /requests/{id} |
одна заявка; «Заявка не найдена» | A,V,U |
POST /requests |
создание: поля заявки + units[{brandModelId, qty, workTypeId, plannedVolume, note}], comboId, windowFrom, windowTo, shiftId; ответ {id} |
A,V,U |
PUT /requests/{id} |
правка, только в статусе NEW; units не прислан — состав не меняется, пустой список — очищается; windowFrom, windowTo, comboId не присланы — обнуляются |
A,V,U |
PUT /requests/{id}/status |
{status}: NEW и CANCELLED ставятся в любом состоянии; PLANNED/DONE — только если совпадают с текущим, иначе «Статус «PLANNED» ставится автоматически по наряду» |
A,V,U |
DELETE /requests/{id} |
удаление вместе со строками состава; ошибка «По заявке уже есть наряд — сначала удалите наряд» | A,V,U |
POST /requests/{id}/form_order |
один наряд из заявки (objectId, employeeId, plannedStart, plannedEnd, plannedTrips, routeKm, plannedKm, note); только NEW; request_unit_id не заполняется |
A,V,U |
GET /requests/{id}/plan_candidates |
подбор техники под строки состава (раздел 4.3) | A,V,U |
POST /requests/{id}/form_orders |
«План выезда»: наряды из состава (раздел 4.4) | A,V,U |
GET /requests/{id}/dispatch_sheet |
данные печатной разнарядки: {request, company{id,name}, workObject?, orders[]} |
A,V,U |
GET /combos, POST /combos, PUT /combos/{id}, DELETE /combos/{id} |
комплекты {name, description, items[{brandModelId, qty, workTypeId, note}]}, при правке ещё active (по умолчанию true); удаление физическое |
A,V,U |
POST /construction_template |
типовой набор справочников стройки (раздел 6.2) | A,V |
Пример тела POST /requests (идентификаторы условные):
{
"objectType": "SPECIAL",
"workObjectId": 51, "geozoneId": 847,
"requiredDate": "2026-10-17", "windowFrom": "08:00", "windowTo": "20:00",
"priority": 2, "externalRef": "Д-0417/26-Ф1", "comboId": 3,
"units": [
{"brandModelId": 11, "qty": 2, "workTypeId": 7, "plannedVolume": 3000},
{"brandModelId": 16, "qty": 3, "workTypeId": 2, "plannedVolume": 120},
{"brandModelId": 13, "qty": 1, "workTypeId": 9, "plannedVolume": 8}
]
}
3.2. Наряды, факт, простои — /agro_orders#
| Метод и путь | Назначение | Роли |
|---|---|---|
GET /?divisionId&dateFrom&dateTo&q |
журнал нарядов: не более 5 000 свежих; в строках drain_liters (слив), points[] |
A,V,U |
GET /{id}, /{id}/ops, /{id}/track, /{id}/fuel |
карточка (с points, plan_points — посещения точек плана), операции, трек за окно наряда, топливо (только для автонаряда) |
A,V,U |
POST /, PUT /{id} |
создание / правка наряда; у AUTO правятся только вид работ, агрегат, комментарий, подразделение, исполнитель (затем пересчёт) |
A,V,U |
POST /{id}/recalc |
пересчёт факта (для ручного факта и закрытого дня — отказ/пропуск) | A,V,U |
PUT /{id}/status |
{status}; ручной наряд — не раньше планового дня; затем расчёт факта (для REVIEW/DONE ручного) и пересчёт статуса заявки |
A,V,U |
DELETE /{id} |
удаление; статус заявки пересчитывается по оставшимся | A,V,U |
PUT /{id}/fact |
ручной факт: engineHours, workHours, idleHours, transferHours, factVolume, distanceKm, refuelLiters, tripsDone, reason; ответ {warnings[]} |
A,V,U |
DELETE /{id}/fact |
вернуть расчёт по телеметрии | A,V,U |
POST /{id}/split |
{splitAt, employeeId} — разделить ручной наряд по времени |
A,V,U |
GET/POST /{id}/downtimes, DELETE /{id}/downtimes/{downtimeId} |
простои {reasonId, minutes, note} |
A,V,U |
GET/POST /downtime_reasons, PUT/DELETE /downtime_reasons/{id} |
причины простоев | A,V,U |
POST /backfill, GET /backfill/status |
«Обработать период» (до 32 дат); запуск — A,V; статус — A,V,U | A,V (запуск) |
PUT /objects/{objectId}/track_normalize |
признак нормализации трека на машине | A,V,U |
POST /route |
маршрут по дорогам (комплект ТРАНС) | A,V,U |
3.3. Закрытие дня — /agro_day_close#
| Метод и путь | Назначение | Роли |
|---|---|---|
GET /pending |
последние 14 суток: {today, notClosed, days[{date, orders, unfinished, closed}]} |
A,V,U |
GET /check?date&divisionId |
замечания по нарядам дня: closed, orders[{id, machine, executor, workType, status, issues[], warnings, routeNote, overlaps, drain, needsDecision, canMove}], ordersTotal, withIssues, truncated, blocking |
A,V,U |
POST / |
{date, divisionId, decisions{"<id наряда>": "CLOSE"|"MOVE"}, defaultDecision} (defaultDecision: CLOSE, MOVE, MOVE_OR_CLOSE); ответ {id, summary} |
A,V,U |
POST /range |
{from, to, divisionId, defaultDecision}; допустимы CLOSE и MOVE_OR_CLOSE; ответ {closed[], errors[]} |
A,V,U |
GET /{id} |
сводка для печати | A,V,U |
DELETE /{id} |
открыть день заново | A,V |
Для пользователя с несколькими доступными подразделениями check, close и range требуют явного divisionId («Выберите подразделение: у вас доступно несколько»).
3.4. Выгрузка в 1С и печать — /agro_export#
| Метод и путь | Назначение | Роли |
|---|---|---|
GET /preview?dateFrom&dateTo&includeExported |
{orders, documents, alreadyExported, warnings[]} |
A,V,U |
POST / |
{dateFrom, dateTo, includeExported} → {id, orders, documents} |
A,V,U |
GET / |
до 50 последних пакетов {id, created_at, date_from, date_to, include_exported, order_count, document_count, created_by_name} |
A,V,U |
GET /{id}/file |
файл exzotron-agro-export-<id>.json, application/json |
A,V,U |
GET /print/{orderId} |
данные печати: документ «машина + исполнитель + дата» из всех нарядов дня в любом статусе (страница agro_print.html?order=) |
A,V,U |
3.5. Отчёт по объектам строительства — /report_construction#
| Метод и путь | Параметры | Ответ | Роли |
|---|---|---|---|
GET /rows |
dateFrom, dateTo (обязательны, ГГГГ-ММ-ДД), divisionId, workObjectId |
{company, from, to, rows[], truncated} |
A,V,U,C |
Ошибки: «Укажите начало периода», «Укажите конец периода», «Конец периода раньше начала», «Период не больше года». Метод скрыт из Swagger. Статус наряда на сервере не фильтруется.
3.6. Справочники, геозоны, объекты работ#
| Метод и путь | Роли | Примечание |
|---|---|---|
GET/POST/PUT /work-objects, PUT /work-objects/{id}/link, DELETE /work-objects/{id} |
A,V,U,C | PUT без идентификатора в пути (id — в теле); link — {geozoneId, partnerId, contract}; DELETE → {deleted: true|false} (деактивация, если объект используется) |
GET /partner; POST /partner/quick; POST/PUT/DELETE /partner; GET /partner/type |
чтение A,V,U,C; quick A,V,U; запись A; type A,V |
quick — {name, itn}: возвращает существующего контрагента (по названию без регистра) или создаёт тип «Покупатели» |
GET/POST/PUT/DELETE /work-types, /units-of-measure, /nomenclature |
A,V,U,C | общие записи — только чтение |
GET/POST/PUT/DELETE /equipment-brands, GET/PUT /equipment-brands/{id}/work-types |
A,V,U,C | модели техники; {loadCapacityT, equipmentKind, implementWidthM…}, {workTypeIds[]} |
GET/POST/PUT/DELETE /employee, /employee/positions |
чтение A,V,U; запись A,V | |
GET/POST/PUT/DELETE /vehicle, /vehicle/{id}/shift-drivers |
чтение A,V,U; запись A,V | |
GET/POST/PUT/DELETE /shifts |
чтение A,V,U; запись A | сумма смен = 1440 минут |
GET/POST/PUT/DELETE /division |
чтение A,V,U,C; запись A | |
GET/POST /geozones, PUT/DELETE /geozones/{id}, GET /geozones/export_kml, POST /geozones/import_kml[/preview] |
любая вошедшая роль (аннотации ролей нет) | {type: point|line|polygon|group, name, parentId, color, radiusMeters, widthMeters, geometry, visible, checked, useForAddress, description}; KML-загрузка создаёт только многоугольники |
PUT /update_object_name |
A | тип объекта, график смен, «Мин. стоянка», подразделение, модель |
4. Алгоритмы заявок и плана выезда#
4.1. Состав заявки: проверки#
При сохранении заявки и комплекта строки состава проходят одну проверку (AgroWorkService.cleanUnits):
- строка без
brandModelIdмолча отбрасывается; - модель должна принадлежать предприятию (
ex_object_brand_models.ex_users_company_id = :company; общая модель без предприятия не подходит): «Модель техники в составе заявки не найдена»; qty— целое 1…99, пусто — 1: «Количество техники: допустимо от 0 до 99», «Количество техники в составе — не меньше 1»;- вид работ строки — вид работ своего предприятия: «Вид работ в составе заявки не найден»; пусто допускается;
plannedVolume— число 0…99 999 999, до двух знаков: «Плановый объём в составе: допустимо от 0 до 99999999»;note— до 200 символов (в интерфейсе поля нет).
Для заявки в целом общий вид работ обязателен, если не у каждой строки состава задан свой вид («Выберите вид работ»); геозона обязательна («Выберите геозону» либо «У объекта работ не задана геозона — выберите геозону»). Состав сервер принимает у заявки любого типа, интерфейс отправляет его только для «Спецтехники» и «Автотранспорта». Серверной проверки окна работы нет (формат, «с < по»).
4.2. Статус заявки по нарядам (syncRequestStatus)#
Вызывается после смены статуса любого наряда заявки (в том числе закрытием дня, которое ставит DONE) и после удаления наряда; создание наряда кнопкой «Новый наряд» (без заявки) и правка наряда статус заявки не меняют.
total = число нарядов заявки; done = число нарядов в DONE; need = Σ qty строк состава (для заявки без состава 0)
total = 0 → NEW
done = total И total ≥ need → DONE
иначе → PLANNED
«Сформировать наряд» и «План выезда» ставят PLANNED напрямую. Из интерфейса CANCELLED ставится только из NEW, «Вернуть в работу» — только из CANCELLED; API разрешает NEW/CANCELLED в любом состоянии, поэтому «Запланированную» заявку с нарядами можно вручную вернуть в NEW (тогда «Редактировать» станет доступно, а замена состава осиротит request_unit_id нарядов). Охват в ответе заявки: unit_total = Σ qty, unit_assigned = Σ min(qty, число нарядов с request_unit_id = строке); в карточке по каждой строке — «наряды: assigned из qty».
4.3. Подбор техники (GET /requests/{id}/plan_candidates)#
Окно подбора (requestWindow): день — required_date (нет — сегодня по местному времени предприятия), начало — window_from (нет — 00:00), конец — window_to (нет — 23:59) с добавлением :59 секунд. Для каждой строки состава возвращаются все объекты предприятия, для которых карточка ex_vehicles (IMEI + предприятие) имеет ex_object_brand_model_id, равный модели строки, отсортированные по названию. Занятость (busy_count, busy_orders) — число и номера нарядов этой же машины, у которых одновременно: source <> 'AUTO', status <> 'DONE', задано planned_start, planned_start < конец окна и COALESCE(planned_end, planned_start + 12 ч) > начало окна. Тип техники (object_type) не фильтруется; машина без карточки или без модели в карточке в список не попадает, подсказки об этом нет.
{
"windowStart": "2026-10-17 08:00:00", "windowEnd": "2026-10-17 20:00:59",
"rows": [{
"unit": {"id": 101, "request_id": 49, "seq": 1, "brand_model_id": 11, "brand_model_name": "Каток дорожный",
"qty": 2, "work_type_id": 7, "work_type_name": "Уплотнение асфальтобетона (каток)",
"unit_name": "м²", "planned_volume": 3000.0, "note": null, "assigned": 0},
"candidates": [{"id": 3001, "name": "HAMM HD110 2658KB 82 (КАТОК)", "imei": "…", "object_type": "SPECIAL",
"busy_count": 0, "busy_orders": null}]
}]
}
Клиентский «Подобрать автоматически»: сбрасывает выбор и идёт по строкам сверху вниз, для каждой берёт первую по названию машину, не занятую в окне и не выбранную в предыдущих строках; если подходящей нет, строка остаётся «не назначена». Выполняется автоматически при открытии окна. Плановое начало по умолчанию — required_date + window_from (нет окна — 08:00), плановое окончание — required_date + window_to либо пусто. Исполнитель для выбранной машины подставляется клиентом: исполнитель по смене, в которую попадает плановое начало (смена может переходить через полночь), иначе driver_employee_id карточки.
4.4. «План выезда» (POST /requests/{id}/form_orders)#
Тело: {items: [{unitId, objectId, employeeId}], plannedStart, plannedEnd, note}; ответ {ids: […]}. Метод выполняется одной транзакцией (AgroWorkService.formOrdersForPlan): любое исключение откатывает все созданные наряды.
Порядок проверок и действий:
- Статус заявки
NEWилиPLANNED, иначе «Наряды формируются по новой или запланированной заявке». itemsне пуст: «Выберите технику хотя бы для одной строки состава».plannedStartобязателен («Укажите плановое начало»); если заданplannedEnd, он строго позже начала («Плановое окончание должно быть позже планового начала»).K = Σ qtyпо всем строкам состава заявки (не по выбранным машинам);shareOf = max(1, K).- Для каждого элемента:
unitIdобязателен («Не указана строка состава») и принадлежит заявке («Строка состава не найдена в заявке»); безobjectIdэлемент пропускается; повторobjectIdвнутри запроса — «Одна и та же техника выбрана в плане выезда дважды». - Значения наряда (см. таблицу ниже),
assertDayOpen(«…создать наряд на эту дату нельзя»), вставка наряда сrequest_id,request_unit_id, источникомMANUAL, статусомNEW; копирование точек маршрута заявки. - Ни одного наряда не создано: «Не выбрана ни одна единица техники».
- По каждой строке
уже_сформировано + добавляется ≤ qty, иначе «По строке «Модель» нужно N, уже сформировано M, добавляется K». - Заявка →
PLANNED; для каждого нового наряда транспорта (ROAD/LIGHT_ROAD) отменяются суточные автонаряды этой машины, пересекающиеся с окном рейса (supersedeAutoOrders).
| Поле наряда | Правило |
|---|---|
cargo_quantity |
K > 1 и груз заявки задан: round(груз ÷ K, 1) по HALF_UP; иначе весь груз заявки |
planned_area_ha |
1) planned_volume строки, если задан; 2) иначе, если есть груз заявки и вычислена его доля — доля груза; 3) иначе весь planned_area_ha заявки (не делится) |
planned_trips |
⌈planned_trips заявки ÷ K⌉; ходки не заданы — пусто |
route_km |
как в заявке (км за ходку) |
planned_km |
если есть ходки наряда и route_km: round(route_km × planned_trips наряда, 1); иначе planned_km заявки |
work_type_id |
вид работ строки, иначе общий вид заявки |
object_type |
берётся у машины, а не заявки: самосвал из заявки «Спецтехника» окажется на закладке «Автотранспорт» |
| точки маршрута | копируются в каждый наряд целиком, включая cargo_quantity точек (не делятся) |
planned_start, planned_end |
общие для всех нарядов плана |
division_id, geozone_id, from_geozone_id, work_object_id, cargo_nomenclature_id, transport_model, round_trip |
как в заявке |
note |
переданное note, иначе комментарий заявки |
employee_id |
employeeId элемента, проверенный на принадлежность предприятию; пусто допустимо (исполнитель подставится при закрытии дня по закреплению смены) |
Примеры (по формулам):
| Заявка | Состав | Результат на каждый наряд |
|---|---|---|
| груз 100 т | самосвал × 3, ходки не заданы | 33,3 т (в сумме 99,9 т) |
| 7 ходок | самосвал × 3 | по 3 ходки (всего 9) |
| заявка №47 стенда: 600 т, 1 рейс, 222,3 км | самосвал × 3 | по 200,0 т, 1 ходка, 222,3 км (в разнарядке «Плановый объём 200 т») |
| 600 т, 5 ходок, «Ходки (челнок)» | 3 машины | по 200,0 т, 2 ходки; «груз за рейс» 100 т вместо 120 т заявки (раздел 4.8) |
Не проверяется сервером: занятость машины другими нарядами; соответствие модели машины строке состава; принадлежность машины типу техники; повторный выбор машины, по которой этой заявке уже сделан наряд в предыдущем запуске плана; окно «с — по» на форму и порядок. Через API можно назначить любую технику предприятия. POST /form_order (один наряд) на заявку с составом переводит её в PLANNED с охватом «0 из N».
Ошибки полей «План выезда» («Одна и та же техника…», «Выберите технику…») ищут на клиенте поле, которого нет в окне, — подсветки нет, сообщение выводится красным внизу окна.
4.5. Печать разнарядки (dispatch_sheet)#
getDispatchSheet возвращает заявку (с составом), предприятие, объект работ (только активный, из списка getWorkObjects; поля customer, contract, address берутся у самого объекта, без наследования от родителя) и наряды заявки (listRequestOrders): номер, статус, техника, госномер, плановые начало и окончание, request_unit_id, исполнитель, вид работ, единица, brand_model_name, номер строки состава. Плановый объём в запросе: cargo_quantity, если задан груз (cargo_nomenclature_id и cargo_quantity не пусты), иначе planned_area_ha — поэтому у нарядов, созданных до введения деления груза и хранящих весь объём заявки, разнарядка показывает груз наряда. Вёрстка (колонки, подписи, пустые строки «не назначено», альбомный A4) — клиентская (agro_request_print.js), по аудиту, не проверено в коде.
5. Факт, маршрут, закрытие дня, 1С, отчёт#
5.1. Окно и общий порядок расчёта#
Окно автонаряда — смена (period_start–period_end); ручного наряда — planned_start–planned_end; без планового окончания — смена машины, в которую попало плановое начало (допускается переход через полночь), иначе календарные сутки (00:00–23:59:59). Окно в будущем не считается (calcEnd = min(end, now); для ручного при calcEnd ≤ start — выход). recalcOrder: (1) замена автонарядов рейсовым (для транспорта); (2) выход, если факт ручной; (3) отрезки getActionSegments; (4) для автонаряда — проверка активности, пустой наряд удаляется; (5) applyFact: buildFact → контроль маршрута → replaceOps → updateOrderFact.
Параметры вызова getActionSegments: флаг «ход в зоне — работа» (fieldMovementIsWork) включён только для AGRICULTURE; флаг «холостой ход — работа» (idleIsWork) — только для SPECIAL. Подробности порогов — в комплекте ТРАНС.
5.2. Спецтехника (SPECIAL)#
- Моточасы (
engine_seconds) — сумма отрезков с работающим двигателем. - Работа. Если у объекта есть датчик механизма — отрезки «работа на месте» / «работа в движении». Если датчика механизма нет, но есть датчик зажигания, тип «холостой ход» заменяется на «работа на месте» (
idleWork): комментарий кода — отличить копание от холостого хода по телеметрии нечем, поэтому время работы = моточасам. Движение без датчика механизма остаётся «Переездом» (для сельхоза движение в поле считается работой, для спецтехники — нет). - Простой — холостой ход, не ставший работой; строка
IDLEсоздаётся при длительности не меньше «Мин. стоянки» объекта (по умолчанию 5 минут); холостой ход внутри операции «Работа» той же геозоны добавляется в её простой. Если датчика механизма нет, а зажигание есть, холостой ход превращается в «работу на месте» (см. выше), и строкиIDLEпрактически не возникают. - Переезды — отрезки «Движение»; переезд короче
MIN_TRANSFER_KM = 0,1км удаляется как дрейф GPS (время и путь в итоги не идут). - Пробег — сумма путей
TRANSFERиWORKв движении. - Заправка — операция
REFUELиз событийREFUELдатчика уровня (сливDRAINв операцию не попадает). - Площадь/объём не считаются. Ширина захвата передаётся в
buildFactтолько дляAGRICULTURE; уSPECIALwidthM = null,fact_area_ha = NULL. Объём вносится только ручным фактом («Выполнено, <ед.>»). - Посещения геозон. Операции
VISITуSPECIALне создаются (тестidleInsideZoneOfTransportIsAVisit: у спецтехники — неVISIT). «Работа» привязывается к геозоне (zone_id); доминирующая зона записывается вgeozone_idнаряда, если тот пуст. Фильтра «работа только в рабочих зонах» дляSPECIALнет. - Подряд идущие операции одного вида и зоны склеиваются (
mergeAdjacent) после удаления коротких переездов.
5.3. Транспорт (ROAD, LIGHT_ROAD)#
Операции: TRANSFER («Переезд»), VISIT («Стоянка в геозоне») — стоянка (двигатель заглушён) либо холостой ход с работающим двигателем в геозоне, IDLE («Двигатель на стоянке», только ROAD, вне геозон), REFUEL. Пробег — сумма TRANSFER. После сборки: VISIT без признака pass короче «Мин. стоянки» удаляется; переезд короче 0,1 км удаляется. «Работа» у транспорта всегда 0, fact_area_ha пусто.
5.4. Проезд по линейной зоне (участку дороги), PASS_MIN_KM = 0,3#
Назначение: на участке дороги самосвал едет, а не стоит, поэтому без специального правила рейс «карьер → участок» не подтверждался («была погрузка, но в пункте назначения машина не останавливалась»). Реализация (AgroFactService.applyFact и buildFact):
- Для
ROADиLIGHT_ROADзагружаются идентификаторы всех геозон предприятия сzone_type = 'line'(AgroWorkDao.getLineZoneIds, не только зона плана наряда). Для остальных типов набор пуст (passZones = null). - Зона каждого отрезка определяется резолвером геозон (
GeozoneShape): для линии точка принадлежит зоне, если расстояние до ломаной не большеwidth_meters / 2(по умолчанию ширина 30 м, радиус круга 30 м); у линии меньше двух точек геометрия не используется. При пересекающихся зонах точка относится к первой подходящей по списку зон предприятия (порядок списка — по аудиту, не проверено в коде). - Для отрезка типа «Движение» (а для транспорта и для отрезков «Работа»), зона которого входит в набор линейных, создаётся операция
VISITсpass = trueиpassKm = километры отрезка; подряд идущие отрезки той же линейной зоны склеиваются (passKmнакапливается). Любой отрезок иного типа (стоянка, холостой ход, заправка, нет данных) либо смена зоны закрывает открытое посещение. - В конце расчёта все
pass-посещения сpassKm < 0,3удаляются («касание края зоны»). Порог «Мин. стоянки» к ним не применяется. - Оставшиеся
pass-посещения в контроле маршрута неотличимы от обычныхVISIT: рейс «погрузка → разгрузка» засчитывается, когда после посещения пункта погрузки встречено посещение пункта назначения — в том числе проезд по участку. Вroute_noteпроезд описывается обычной формулой «разгрузка N мин (чч:мм–чч:мм)».
Ограничения: (а) признак pass в ex_agro_work_order_op не сохраняется, в интерфейсе строка выглядит как «Стоянка в геозоне», пробег по ней пуст; (б) порог — константа, из интерфейса не настраивается; (в) каждый разрыв непрерывного проезда (другая зона, стоянка) начинает новое посещение, и порог проверяется для каждого отдельно; (г) при недостоверной навигации зона точки не определяется, поэтому проезд не засчитывается; (д) линия, наложенная на площадку погрузки, может «перехватить» точку (первая подходящая зона). Правило не действует для SPECIAL. Подтверждение: тесты passThroughLineZoneCountsAsVisit (без линейных зон — PARTIAL, с зоной — VISIT с pass и OK, 1 рейс) и shortTouchOfLineZoneIsNotVisit (0,1 км — не посещение); стенд — наряды №983, 984, 988 (замечание 15 журнала эмуляции).
5.5. Контроль маршрута#
AgroFactService.applyFact вызывает контроль для ROAD/LIGHT_ROAD и для SPECIAL, если в наряде задана from_geozone_id («возит груз»). Выбор ветки: зон в плане наряда (getOrderPlanZones) больше двух — checkSequence, иначе check(from, to).
check(from, to): нет ни «откуда», ни «куда» — результата нет (NULL); нет активности — NO_DATA, trips = 0; учитываются только операции VISIT с зоной. Посещение «откуда» взводит признак «загружен»; посещение «куда» при загруженном (или при пустом «откуда») даёт рейс и снимает признак; если «откуда» = «куда», каждое посещение — рейс. Статус: при обеих зонах OK при trips > 0, иначе PARTIAL при любом посещении, иначе NONE; при одной зоне — OK при любом посещении. Число плановых ходок в статус не входит. checkSequence: рейс — прохождение всех зон плана по порядку; лишние посещения игнорируются; затем счёт начинается заново.
Недостоверная навигация: доля длительности операций с no_nav не меньше NO_NAV_SHARE = 0,3 — NONE превращается в NO_DATA (текст «навигация недостоверна на N% времени (подмена GPS)…»), к PARTIAL добавляется оговорка, OK не меняется. Тексты route_note и остальные детали — комплект ТРАНС.
Расхождение, спецтехника «возит груз». Контроль вызывается, но VISIT у SPECIAL не создаются: статус будет NONE или NO_DATA, и замечание ROUTE_NONE потребует решения при закрытии дня. На стенде не проверялось; для самосвалов используйте закладку «Автотранспорт».
5.6. Ручной факт#
PUT /{id}/fact (AgroWorkService.setManualFact, AgroWorkDao.setManualFact): только для source <> 'AUTO' («Факт автоматического наряда считается по телеметрии»); день не закрыт. Причина обязательна, до 255 символов («Укажите причину ввода факта вручную (например, «прибор неисправен»)»). Часы (engineHours, workHours, idleHours, transferHours) — 0…24, переводятся в секунды; factVolume, distanceKm, refuelLiters — неотрицательные числа; ошибки: «{Поле}: некорректное число», «{Поле}: значение не может быть отрицательным», «{Поле}: за сутки не может быть больше 24 часов». Запись: значения записываются в факт, fact_source = 'MANUAL', fact_reason, fact_warnings, calc_at = NOW(), операции удаляются. Рейсы: trips_done = COALESCE(:trips, trips_done); route_status = 'OK' только при trips > 0, иначе без изменений; route_note = 'Рейсы введены диспетчером по путевому листу', если поле заполнено (в том числе 0). Рейсы округляются до целого.
Предупреждения правдоподобия (без запрета; в fact_warnings до 500 символов): моточасы больше длительности окна; работа больше моточасов; работа + простой + переезды > моточасы + 6 мин. recalcOrder для fact_source = MANUAL выходит сразу, кнопка «Пересчитать» в интерфейсе неактивна. DELETE /{id}/fact снимает fact_source, fact_reason, fact_warnings и пересчитывает факт; ручные route_status, trips_done, route_note затирает только этот пересчёт.
5.7. Статусы наряда и их побочные эффекты#
Переходы между любыми статусами разрешены. started_at ставится один раз при IN_PROGRESS; completed_at — при DONE, любой иной статус его обнуляет. Для ручного наряда в REVIEW/DONE выполняется расчёт факта (сбой расчёта не срывает смену статуса), затем пересчитывается статус заявки. Ручной наряд с плановой датой в будущем не принимается: «Наряд запланирован на ДД.ММ.ГГГГ — завершить его до наступления даты нельзя. Измените плановое начало или дождитесь даты.» Автонаряды не ограничены. Изменение наряда закрытого дня — «День ДД.ММ.ГГГГ закрыт — наряд изменить нельзя. Руководитель может открыть день в окне «Закрыть день».» При удалении наряда его простои и точки удаляются, операции остаются.
5.8. «Перевезено» и груз за рейс#
Весов на машине нет: «перевезено» — расчётная величина «груз за рейс × число рейсов».
| Место | Формула | Условия |
|---|---|---|
| Груз за рейс (все места) | SINGLE: cargo_quantity наряда; SHUTTLE и MULTI при planned_trips > 0: cargo_quantity ÷ planned_trips |
груз задан |
| Карточка наряда («Перевезено (груз за рейс × рейсы)») | груз за рейс × trips_done |
route_status = OK, trips_done > 0, задана единица груза; единицы груза и вида работ не сверяются; блок показывается только при расчёте по телеметрии (при ручном факте контроль маршрута в карточке не выводится) |
Файл 1С (AgroExportService.routeFactVolume) |
round2(груз за рейс × trips_done), источник factVolumeSource = ROUTE_CONTROL |
route_status = OK, fact_source ≠ MANUAL, trips_done > 0, единица вида работ = единице груза (сравнение без регистра, после обрезки пробелов); иначе null и дальше factVolume по правилам 5.11 |
| Отчёт по объектам строительства | груз за рейс × trips, trips = trips_done > 0 |
только транспорт (по правилу 5.13); и для ручных рейсов; единицы не сверяются |
Тест routeFactVolumeDividesShuttleCargoByPlannedTrips: 120 т, 2 ходки, 1 рейс → 60 т; SINGLE → 120 т. На стенде наряд №983: груз 120,0 т, «Ходки (челнок)», 2 ходки, всего 231,2 км (115,6 км за ходку), 1 подтверждённый рейс → «Перевезено» 60,0 т.
Следствие деления плана выезда. груз за рейс наряда = (груз ÷ K) ÷ ⌈ходки ÷ K⌉, что при неделимых ходках отличается от груз ÷ ходки заявки (600 т, 5 ходок, 3 машины: 200 т ÷ 2 ходки = 100 т за рейс вместо 120 т). Рекомендация: назначать число ходок кратным числу машин либо проверять «План перевозки» в карточке наряда.
Наряды, созданные до деления груза (№983–991, 992–994 на стенде), хранят planned_area_ha всей заявки; разнарядка, карточка и отчёт для грузового наряда берут cargo_quantity.
Расхождение отчёта и 1С при ручных рейсах. Отчёт посчитает груз × введённые рейсы; в файле 1С при ручном факте factVolume = введённое «Выполнено», а «перевезено» не вычисляется. Для 1С нужно вводить и «Рейсов по путевому листу», и «Выполнено, т».
5.9. Закрытие дня#
AgroDayCloseService; день определяется датой period_start (автонаряд) либо planned_start (ручной): period_start ≥ :d AND period_start < :d + 1 день, либо period_start IS NULL и то же по planned_start.
Проверка (check). Для каждого наряда дня собираются коды замечаний (issueCodes): NOT_CLOSED (статус не DONE); NO_EXECUTOR (нет employee_id и нет исполнителя по закреплению смены); MANUAL_FACT (факт ручной) либо NO_FACT (расчёта нет либо engine_seconds = 0 и work_seconds = 0); SUSPECT_GPS (не ручной факт, distance_km > 1, расчёт есть, engine_seconds = 0); ROUTE_NONE/ROUTE_PARTIAL/ROUTE_NODATA по сохранённому route_status — независимо от ручного факта; DUPLICATE (пересечение окон нарядов одной машины); FUEL_DRAIN (сливы по датчику уровня в окне наряда). Информационные (решение не требуется): MANUAL_FACT, SUSPECT_GPS, FUEL_DRAIN, ROUTE_PARTIAL, ROUTE_NODATA. Наряды без замечаний в список не попадают; список ограничен 300 нарядами (DISPLAY_LIMIT), остальные закрываются общим решением. Слив: событие DRAIN относится к наряду, если время его начала (с учётом смещения предприятия) попадает в [начало окна; конец окна); текст «слив топлива N,N л (k раз) — проверьте расход».
Закрытие (close), одна транзакция: (1) дата не позже сегодняшней по местному времени («День ещё не наступил…»); день не закрыт («День уже закрыт»); (2) подстановка исполнителя по закреплению смены в наряды дня без исполнителя; (3) нарядов нет — «В этом дне нет нарядов — закрывать нечего»; (4) для каждого наряда с требующим решения замечанием берётся решение из decisions[id], а при его отсутствии — общее defaultDecision: MOVE_OR_CLOSE → MOVE для ручного не завершённого наряда, иначе CLOSE; строгий MOVE оставляет решение пустым там, где перенос невозможен; нет решения — «Нет решения по нарядам: №…»; (5) MOVE допустим только для source <> 'AUTO' и status <> 'DONE' («Наряд №N нельзя перенести: автоматический или уже завершён»): плановые начало и окончание сдвигаются на +1 сутки; (6) CLOSE ставит статус DONE через setOrderStatus (у ручного — расчёт факта; заявка пересчитывается); (7) формируется сводка по нарядам, кроме перенесённых, и сохраняется в summary JSON; (8) запись закрытия блокирует правки.
Сводка (summary): company, date, division, totals {orders, engineHours, workHours, idleHours, transferHours, distanceKm, refuelLiters, downtimeMinutes, manualFacts}, machines[] (по паре «машина|исполнитель»), downtimes[] по причинам, manualFacts[], suspicious[] (сливы, маршрут, подозрительный пробег), exceptions[] (закрыто как есть), moved[]. В печатной форме сводки нет колонок пробега, рейсов и «Перевезено» (по аудиту).
Блокировка. isLocked(company, day, division): есть запись закрытия с reopened_at IS NULL и (division_id IS NULL или division_id <=> division наряда); закрытие всей компании блокирует все подразделения, закрытие подразделения — только его. Блокируются правка, пересчёт, смена статуса, удаление, ручной факт, простои, разделение, создание наряда (в том числе из заявки и плана выезда) и перенос на закрытую дату.
Диапазон (range). Допустимы CLOSE и MOVE_OR_CLOSE; конец ограничивается вчерашним днём; не более 32 дат («За один раз можно закрыть не больше 31 дня»); день без нарядов или уже закрытый пропускается; каждый день — своя транзакция, ошибка дня попадает в errors. Счётчик pending: последние DAYS_BACK = 14 суток, дни с нарядами и признак «закрыт» (закрытие по компании либо по доступному подразделению). Открыть заново (DELETE /{id}, A/V): ставит reopened_at и reopened_by_user_id; статусы нарядов не возвращаются; повтор — «День не найден или уже открыт». Сводка устаревшего закрытия помечается «день затем открыт заново».
5.10. Топливо в наряде#
Операции REFUEL строятся только из событий REFUEL (loadMechanismRefuels(…, includeDrains = false)); слив определяется отдельным запросом событий DRAIN по предприятию (AgroDayCloseDao.listDrains) — для красной строки в колонке «Заправка, л» журнала и для замечания FUEL_DRAIN. Вкладка «Топливо» (GET /{id}/fuel) строится только для автонарядов: нужен period_start. Удаление ложного события в «Отчёте по расходу топлива» (POST /report_fuel_consumption/delete_event?eventId=, только A) физически удаляет строку события (DELETE … WHERE id AND company_id); на закрытой сводке дня не отражается (снимок), слив из журнала и замечания исчезает сразу, а сохранённые в наряде операции «Заправка» и итог — только после пересчёта наряда (для закрытого дня — после «Открыть день заново»). Журнала удаления нет.
5.11. Выгрузка в 1С#
Условия попадания (AgroExportDao.listDoneOrders): status = 'DONE', дата документа DATE(COALESCE(period_start, planned_start, started_at)) входит в период включительно (разность дат не более 93, фактически до 94 календарных дат), по умолчанию export_batch_id IS NULL (флажок includeExported берёт и выгруженные и переносит их в новый пакет); для ограниченного пользователя — только его подразделения (filterRows). Пусто — «За выбранный период нет принятых нарядов для выгрузки». Ошибки периода: «Укажите период выгрузки», «Дата «по» раньше даты «с»», «Период выгрузки не больше 93 дней», «Неверный формат даты (ожидается ГГГГ-ММ-ДД)».
Предпросмотр (preview) добавляет предупреждения (выгрузка не блокируется — 1С получит пустые ссылки): «Наряд №N: не назначен исполнитель», «Наряд №N: не указан вид работ» (для LIGHT_ROAD не выдаётся), «Наряд №N: техника «…» не заведена в справочнике транспорта».
Файл — JSON, схема exzotron.agro.export, schemaVersion: 1, имя exzotron-agro-export-<id>.json, хранится целиком в payload; повторная загрузка отдаёт тот же файл. Перед построением заводятся GUID (раздел 2.7). Документ = дата + вид документа + машина + исполнитель (ключ группировки doc_date|kind|v<vehicle_id>|employee_id, а при отсутствии карточки машины — i<imei>).
| Уровень | Поля |
|---|---|
| Корень | 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 {id, guid, code, name}, rows[], totals {orders, engineHours, workHours, idleHours, distanceKm, refuelLiters, downtimeMinutes} |
| Строка (наряд) | orderId, source, shift, periodStart, periodEnd, workType {id, guid, code, name}, workObject {id, guid, code, name}, geozone {id, guid: null, code: null, name}, fromGeozone (так же), cargo {id, name, quantity, unit}, hours {engine, work, idle, transfer} (null, пока факт не рассчитан, а не 0), unit, planVolume, factVolume, factVolumeSource, factSource, factReason, routeCheck {status, tripsDone, note} (без плана — null), areaHa, fields[], distanceKm, refuelLiters, downtimes[{reason{id,guid,code,name}, minutes, note}], note |
Правила значений строки:
periodEnd: автонаряд — конец смены; ручной — плановое окончание, иначе конец смены машины, в которую попадает плановое начало, иначе начало следующих суток. Момент «Завершён» не используется.planVolume=planned_area_ha(у грузового наряда без заданного значения равен количеству груза).factVolume: еслиrouteFactVolumeнеnull— он (factVolumeSource = "ROUTE_CONTROL"); иначе при ручном факте с заполненнымfact_area_ha— введённое «Выполнено» (factVolumeSourceне выставляется); иначе по единице вида работ: «га» —fact_area_ha; «ч», «час», «моточас», «мч» — часы работы (work_seconds ÷ 3600); «км» — пробег; единицы «м²», «м³», «т» по телеметрии даютnull(следствие того, что объём спецтехники по телеметрии не считается).factSource:MANUAL;NO_DATA— расчёт пуст (моточасы, работа, пробег и заправка нулевые; по самому наряду нельзя отличить «нет данных» от «не работала»);TELEMETRY.fields[]— рабочие операцииWORKс зоной, сгруппированные по геозоне: уSPECIALс «Работой» в геозоне массив заполняется (areaHa = null,workHours,distanceKm,transferKm,transferHours), у транспорта пуст.areaHaпри ручном факте содержит «Выполнено» в единице вида работ, а не гектары.
Иллюстративная строка (значения условные; часть взята из наряда №983 стенда):
{
"orderId": 983, "source": "MANUAL", "shift": null,
"periodStart": "2026-10-14 07:00:00", "periodEnd": "2026-10-15 00:00:00",
"workType": {"id": 2, "guid": "…", "code": null, "name": "Перевозка щебня и инертных материалов"},
"workObject": {"id": 52, "guid": "…", "code": null, "name": "Подъездная дорога к логопарку, Новожиловка"},
"geozone": {"id": 847, "guid": null, "code": null, "name": "Подъездная дорога к логопарку, Новожиловка"},
"fromGeozone": {"id": 840, "guid": null, "code": null, "name": "Чернопольское 1"},
"cargo": {"id": 12, "name": "Щебень фр. 20–40", "quantity": 120.0, "unit": "т"},
"hours": {"engine": 6.57, "work": 0.0, "idle": 0.0, "transfer": 2.82},
"unit": "т", "planVolume": 120.0,
"factVolume": 60.0, "factVolumeSource": "ROUTE_CONTROL",
"factSource": "TELEMETRY", "factReason": null,
"routeCheck": {"status": "OK", "tripsDone": 1, "note": "погрузка 6 мин (10:10–10:16), разгрузка 1 мин (15:49–15:50)"},
"areaHa": null, "distanceKm": 97.7, "refuelLiters": 171.6,
"fields": [], "downtimes": [], "note": null
}
Загрузка файла в 1С при подготовке статьи не проверялась; документ EXPORT_1C_SCHEMA.md описывает схему неполно (нет factSource, factReason, factVolumeSource, routeCheck) — ориентируйтесь на настоящую статью. Сети, обратной загрузки, статуса «принято в 1С» и расценок нет.
5.12. Печатные формы наряда#
GET /agro_export/print/{orderId}: документ собирается по принципу «машина + исполнитель + дата» из всех нарядов этого дня в любом статусе (наряд-основание всегда включён: совпадают imei и employee_id, дата документа совпадает с датой наряда-основания); структура та же, что в файле выгрузки; номер условный. Формы (по аудиту, клиентский код печати не перечитывался): SPECIAL — «Рапорт о работе строительной машины (механизма)» (моточасы, работа, простой, «Выполнено» план/факт, заправка; пробега нет); ROAD — «Путевой лист грузового автомобиля» (откуда, куда, груз, контроль маршрута с числом рейсов, моточасы, пробег, заправка; без «Перевезено» и плановых ходок). Печатная сводка дня — страница agro_day_print.html.
5.13. Отчёт по объектам строительства#
Построение. Сервер (ConstructionReportDao.findRows) возвращает одну строку на наряд предприятия, дата наряда COALESCE(period_start, planned_start) которого лежит в [from; to + 1 день), с LIMIT 20000, сортировкой по дате и id; при divisionId — o.division_id = :id; при workObjectId — o.work_object_id = :id OR wo.parent_id = :id (объект и его прямые потомки). Соединения: объект мониторинга (госномер), карточка ex_vehicles (по IMEI и предприятию) → модель техники, исполнитель, подразделение, объект работ и его родитель, геозоны «куда»/«откуда», вид работ и его единица, груз и его единица. Поля строки: orderId, day, status, source, factSource, routeStatus, objectType, requestId, vehicleName, licensePlate, brandModelName, employeeName, divisionId, divisionName, workObjectId, workObjectName, workObjectParentId, workObjectParentName, customer, contract, geozoneName, fromGeozoneName, workTypeName, unitName, cargoName, cargoUnit, cargoQuantity, plannedVolume, factVolume, workSeconds, engineSeconds, idleSeconds, transferSeconds, distanceKm, refuelL, plannedTrips, tripsDone, plannedKm, routeKm, roundTrip, transportModel. truncated = (число строк ≥ 20000). Заказчик и договор берутся у самого объекта наряда (wo.customer, wo.contract), без наследования.
Сервер проверяет: даты заданы, to ≥ from, to − from ≤ 366 дней (то есть допустимо до 367 календарных дат включительно; сообщение «Период не больше года»). Статус на сервере не фильтруется, сортировка по возрастанию даты означает, что при превышении предела обрезаются самые поздние наряды периода.
Расчёт на клиенте (report_construction.js, prepare):
| Показатель | Правило |
|---|---|
| «Принятый» наряд | status ∈ {REVIEW, DONE} |
| Транспорт | objectType ∈ {ROAD, LIGHT_ROAD} либо задана геозона «откуда» либо задан груз; иначе спецтехника |
| Моточасы / работа / простой | engine_seconds, work_seconds, idle_seconds ÷ 3600; простои с причинами в отчёте не используются |
| Пробег, заправлено | distance_km, refuel_liters |
| Рейсы | только у транспорта: trips_done, если > 0 (включая введённые вручную) |
| Груз за рейс | SHUTTLE/MULTI при planned_trips > 0: груз ÷ ходки; иначе груз наряда |
| Перевезено | груз за рейс × рейсы |
| т·км | перевезено × гружёное плечо; плечо = route_km при round_trip 0/false, иначе route_km ÷ 2 (в том числе при NULL); только если единица груза — «т» (с учётом «т.») |
| Объём спецтехники | fact_area_ha (в единице вида работ); по телеметрии не считается, поэтому берётся из ручного факта |
| Плановый объём | транспорт: груз наряда, если задан; иначе и для спецтехники — planned_area_ha |
| Суммы разных единиц | не складываются: отдельными строками (5 600 м², 120 м³) |
Вкладки (7): «Сводка по объектам» (7 плиток, «Сводка по объектам затрат» с итогом, «Объекты и виды работ»; предупреждение об обрезке «Показаны не все наряды периода (предел выдачи) — сузьте период или выберите объект.» — только здесь и только при наличии принятых нарядов); «Перевозки» (маршруты откуда|куда|груз|единица, плечо = т·км ÷ тонны); «Техника» (загрузка — полоса относительно машины с максимальными моточасами, не норматив); «По дням» (четыре матрицы «дата × объект»: моточасы спецтехники, рейсы, пробег транспорта, заправлено); «План / заявка и факт» (по всем нарядам периода: план — сумма по всем нарядам группы, факт — по принятым; сравнение идёт по нарядам, а не по заявкам: заявка без наряда не видна); «Конструктор» (PivotTable 2.23.0 с jQuery 1.12.4 и jQuery UI 1.12.1 из публичного CDN; по умолчанию строки «Объект строительства», «Вид работ», столбцы «Вид», значение «Моточасы», агрегатор «Сумма»; агрегаторы «Сумма», «Количество», «Среднее», «Минимум», «Максимум»; русской локализации оболочки нет); «Ведомость по объекту» (отдельный лист на каждый объект, разрыв страницы после объекта; подписи «Диспетчер», «Руководитель работ», «Представитель заказчика»; колонка «Факт» помечает «вручную»).
Поля конструктора — 29 (toPivotRow): 19 справочных — «Организация», «Объект строительства», «Заказчик», «Договор», «Вид» («Транспорт»/«Спецтехника»), «Вид работ», «Техника», «Модель техники», «Исполнитель», «Подразделение», «Дата», «Месяц», «Статус наряда», «Факт» («вручную»/«по телеметрии»), «Откуда», «Куда / место», «Груз», «Заявка», «Единица объёма»; 10 мерных — «Объём работ», «Плановый объём», «Моточасы», «Часы работы», «Простой, ч», «Пробег, км», «Рейсов», «Перевезено», «т·км», «Заправлено, л». Мерные поля у непринятых нарядов равны null (кроме «Планового объёма»); пустые текстовые заменяются на «—». Аудит и бриф называют 28 полей: по коду их 29.
Печать и Excel. Печать — window.print() с A4 альбомная и полями 10 мм (вкладки и панель скрыты, заголовки таблиц повторяются). Excel — кнопка формирует HTML-файл с типом application/vnd.ms-excel и именем construction_<вкладка>.xls из всех таблиц активной вкладки (summary, haul, vehicles, days, plan, pivot, sheet); плитки не экспортируются; это не настоящий XLSX.
Отчёт против файла 1С (схема 5 клиентской статьи): отчёт берёт REVIEW и DONE, 1С — только DONE; «перевезено» в 1С требует route_status = OK и совпадения единиц, в отчёте — нет; для ручных рейсов отчёт считает груз × рейсы, 1С — введённое «Выполнено». Фильтр «наряды без объекта» применяется на клиенте.
5.14. Доступ по подразделениям#
AgroAccess читает права пользователя из БД с кэшем 10 секунд: список ex_user_allowed_divisions плюс «Зафиксировать подразделение» (homeDivisionId). Правила: пустой набор — без ограничений; запись без подразделения ограниченному недоступна; единственное доступное подразделение подставляется автоматически, чужое — «Нет доступа к другому подразделению»; при нескольких доступных и пустом запросе журнал фильтруется по набору, а действия над целым днём требуют явного выбора («Выберите подразделение: у вас доступно несколько»); assertRow — «Нет доступа: запись относится к другому подразделению». Применяется в журналах заявок и нарядов, карточках, закрытии дня, выгрузке (предпросмотр, формирование, печать). Не применяется: отчёт /report_construction/rows (получает наряды всех подразделений предприятия), справочники (объекты работ, геозоны, комплекты, карточки техники), список пакетов выгрузки, сводка закрытого дня по номеру (по аудиту).
6. Права, справочники, типовой набор#
6.1. Роли#
| Возможность | A | V | U | C |
|---|---|---|---|---|
| Заявки, комплекты, «План выезда», разнарядка, наряды, факт, простои, закрытие дня, выгрузка в 1С | да | да | да | нет |
| «Открыть день заново», «Обработать период», типовой набор для стройки | да | да | нет | нет |
| Отчёт по объектам строительства (метод API) | да | да | да | да (но справочники параметров /agro_orders/form_data для C закрыты) |
| Запись: объекты работ, виды работ, единицы, модели техники, номенклатура | да | да | да | да |
| Запись: сотрудники, карточки «Автотранспорт» | да | да | нет | нет |
| Запись: графики смен, подразделения, контрагенты (правка/удаление) | да | нет | нет | нет |
| Геозоны | да | да | да | да (аннотации ролей у контроллера нет) |
| Удаление события датчика уровня топлива | да | нет | нет | нет |
| Тип объекта, график смен, свойства объекта | да | нет | нет | нет |
Кнопки «Открыть день заново», «Обработать период» и «Типовой набор» видны всем ролям окна; для U результат — сообщение об ошибке после нажатия (точный текст отказа под U не проверялся). Пункт меню «Наряды» — данные меню, а не миграция; кнопка «Отчеты» с пунктом отчёта доступна на странице «Объекты» (showObjectReportsButton), в окне «Наряды» её нет.
6.2. Типовой набор для стройки#
POST /agro_orders/construction_template (A, V; идемпотентно: добавляется только отсутствующее по названию или коду): единицы — «Кубический метр» (м³), «Квадратный метр» (м²), «Тонна» (т), «Час» (ч); виды работ (8, с кодом и единицей) — «Работа экскаватора» (EXCAV, ч), «Разработка и погрузка грунта» (GRUNT, м³), «Планировка площадки» (PLAN, м²), «Работа бульдозера» (BULL, ч), «Работа фронтального погрузчика» (LOADER, ч), «Работа автокрана» (CRANE, ч), «Перевозка грунта и материалов» (HAUL, т), «Переезд (перебазировка) техники» (RELOC, ч); причины простоев (8) — WAIT_FUEL, WAIT_LOAD, WAIT_FRONT, WAIT_MATERIAL, WEATHER, BREAKDOWN, CLIENT, BREAK; модели (5) — «Экскаватор» (EXCAVATOR), «Бульдозер» (BULLDOZER), «Фронтальный погрузчик» (WHEEL_LOADER), «Автокран» (MOBILE_CRANE) — все SPECIAL, и «Самосвал» (DUMP_TRUCK) — TRUCK, с привязкой видов работ только для моделей, созданных этой загрузкой. Ответ: {models, modelWorkTypeLinks, units, workTypes, downtimeReasons}. В набор не входят дорожные катки, асфальтоукладчик, виды работ «Укладка асфальта» и «Уплотнение…», грузы, объекты работ, геозоны, комплекты, сотрудники: на стенде они заведены вручную (12 видов работ, 4 комплекта).
7. Конфигурация#
| Свойство / механизм | По умолчанию | Назначение и оговорки |
|---|---|---|
scheduler.enabled |
false |
включает все @Scheduled, в том числе автонаряды (AgroFactScheduler: старт через 240 000 мс, период 900 000 мс). Включать только на одном процессе из пары |
calc-pipeline.enabled / workers / batch-size / lease-minutes / profile-cache-ttl-ms |
true / 8 / 25 / 5 / 300 000 |
расчётный конвейер датчиков; без него факта нет |
route.detour.factor и свойства маршрутизации, геокодера |
см. комплект ТРАНС | в этой статье не повторяются |
| Окно автосоздания | LOOKBACK_HOURS = 36, принудительный пересчёт окна раз в час |
старше — только «Обработка периода» |
| Лимиты кода | журнал нарядов 5 000, отчёт 20 000, окно закрытия 300, диапазон 32 даты, выгрузка 93 дня, список пакетов 50, точки маршрута 12, состав 1–99, ходки до 999 | константы кода, из интерфейса не настраиваются |
| Пороги расчёта | MIN_TRANSFER_KM = 0,1; PASS_MIN_KM = 0,3; MIN_ACTIVE_KM = 0,3; ACTIVITY_MIN_SECONDS = 600; NO_NAV_SHARE = 0,3; SUSPECT_GPS_KM = 1,0 |
константы кода |
Версии статических файлов окна и отчёта меняются при выкате; браузерный кэш окна — сутки, после обновления версии нужна перезагрузка страницы.
8. Миграции и версии#
Все файлы подключены в db.changelog-master.xml (проверка — тест MigrationChangelogTest); миграции идемпотентны (MARK_RAN, предусловия по information_schema). Номер 194 пропущен (файла нет).
| № | Файл | Содержание | Нужна стройке |
|---|---|---|---|
| 017 | 017_company_geozones.sql |
геозоны: zone_type, radius_meters, width_meters, geometry_json, группы |
да (линия-участок) |
| 191 | 191_agro_work_orders.sql |
заявка и наряд | да |
| 192 | 192_agro_fact_orders.sql |
source, окна смен, итоги, операции; ex_objects.work_width_m |
да |
| 193 | 193_agro_order_engine_seconds.sql |
моточасы | да |
| 195 | 195_agro_request_object_type.sql |
object_type заявки |
да |
| 196 | 196_work_objects.sql |
объекты работ, work_object_id в заявке и наряде |
да |
| 197 | 197_agro_route_cargo.sql |
«откуда», груз, количество | да |
| 198 | 198_agro_executor_downtime.sql |
исполнитель, причины простоев, простои | да |
| 199 | 199_agro_export_1c.sql |
пакеты выгрузки, GUID | да |
| 200 | 200_merge_equipment_brands_into_models.sql |
модели техники: load_capacity_t, equipment_kind, виды работ модели |
да |
| 201 | 201_vehicle_shift_drivers.sql |
исполнители по сменам | да |
| 202 | 202_agro_order_manual_fact.sql |
ручной факт | да |
| 203 | 203_agro_day_close.sql |
закрытие дня | да |
| 204 | 204_agro_order_planned_end.sql |
плановое окончание, предупреждения ручного факта | да |
| 205–206 | индексы | журнал, вид работ по умолчанию, поиск автомобиля | да |
| 207 | 207_agro_order_route_check.sql |
route_status, trips_done, route_note |
да |
| 213 | 213_fuel_level_sensor_double.sql |
дробные литры уровня | топливо |
| 214 | 214_work_object_partner.sql |
ex_work_objects.partner_id |
да |
| 215 | 215_agro_plan_trips_km.sql |
ходки, км, модель перевозки, точки, время подачи | да |
| 216 | 216_object_track_normalize.sql |
признак нормализации на машине | да |
| 236 | 236_request_equipment_plan.sql |
5 changeset: 236-01 комплекты, 236-02 строки комплектов, 236-03 окно и combo_id в заявке, 236-04 состав заявки, 236-05 request_unit_id в наряде |
да (состав, план выезда, охват) |
Миграции 190, 208–212 (датчик механизма, технологический план, календарь культур), 217–235 (рассылки, прайс, партнёрский кабинет, карта) к блоку не относятся. Последняя в каталоге — 236 (db.changelog-master.xml, строка 242 по аудиту).
Версии блока (коммиты 15.10.2026, master): aacfda51 — заявка на спецтехнику v2: состав, окно, комплекты, «План выезда», миграция 236; a6830c7e — печать разнарядки; e2b72a69 — состав и план выезда у автотранспорта (деление груза, ходок и км); fd2619ba — отчёт по объектам строительства и поле «Рейсов по путевому листу»; bb81a990 — груз за рейс по ходкам и план транспорта из груза наряда в отчёте; 429adb91 — прочерки в пустых ячейках отчёта, разнарядка без пустых полей шапки; bfa5df13 — проезд по линейной зоне засчитывается посещением; d4617d61 — груз за рейс у «ходок» в карточке и в выгрузке; HEAD fcf23f77. Правки печати путевого листа грузового автомобиля и подсветки линии в окне «Подбор геозоны» подготовлены и ждут выката.
9. Тесты#
Покрытие — только синтетические данные; тесты в этой работе не запускались (описано по исходникам).
| Класс | Тестов | Что проверяет |
|---|---|---|
AgroFactServiceTest |
14 | окна смен; пропуск пересчёта без новых данных; фильтр рабочих зон и склейка для сельхоза; холостой ход транспорта в геозоне = посещение (у SPECIAL — нет); замена автонаряда рейсовым; passThroughLineZoneCountsAsVisit, shortTouchOfLineZoneIsNotVisit |
AgroRouteCheckTest |
13 | нет плана, один и два рейса, частично, ничего, недостоверная навигация, нет данных, одна зона, склад → склад |
RouteOptimizerTest |
8 | порядок точек развоза, checkSequence |
AgroExportServiceTest |
6 | routeFactVolume (в том числе деление груза «ходок» на плановые ходки), factSourceOf, отнесение переездов |
AgroDayCloseServiceTest |
20 | коды замечаний, решения (CLOSE, MOVE, MOVE_OR_CLOSE), общие решения, пересечение окон, диапазон, будущий день, блокировка закрытого дня |
AgroWorkServiceTest |
4 | предупреждения правдоподобия ручного факта |
AgroAccessTest |
6 | ограничения по подразделениям |
MigrationChangelogTest |
1 | каждый файл миграции подключён в основной файл |
Не покрыто тестами: состав и комплекты, подбор техники, деление в «Плане выезда» (формулы раздела 4.4), syncRequestStatus, печать разнарядки, поле tripsDone в SQL ручного факта, GeozoneShape для линий на реальной геометрии, haulingSpecial, отчёт по объектам строительства, удаление события топлива, интерфейс и проверки ролей. Подтверждение этих функций — живые прогоны на стенде.
10. Известные ограничения и расхождения#
Ограничения продукта.
- Объём спецтехники (м², м³) по телеметрии не считается;
factVolumeдля единиц «м²», «м³», «т» в файле 1С равенnull, если нет ручного факта. - Проезд по участку (от 0,3 км) — только транспорт; признак не сохраняется; порог не настраивается.
- План выезда делит груз, ходки и км на число единиц всего состава; груз — до 0,1 т, ходки — вверх; точки развоза не делятся.
- Занятость и дубли — подсказки; сервер проверяет только дубль в запросе; окно работы не проверяется; окно через полночь не поддерживается.
- Подбор — только через карточку
ex_vehiclesс «Моделью ТС». - Комплект не хранит объём на единицу и примечание строки; комплект нельзя пометить «не используется» из интерфейса (правка всегда ставит
active = true); уникальность названия не проверяется; после удаления комплектаcombo_idв заявках остаётся. - Нет нормативной выработки и расчётных моточасов по норме;
shift_idв заявке не используется; нет стоимости по тарифам. - Отчёт: нет ограничения по подразделениям; 20 000 строк; «Excel» — HTML под именем .xls; вид объекта (объект/погрузка/база) не хранится; заказчик и договор берутся без наследования.
- Геозоны доступны любой вошедшей роли; удаление зоны не проверяет использование.
- Двойной щелчок по заявке открывает форму любого статуса, но сохранить можно только
NEW. - Предупреждение о дубле заявки при составе сравнивает только место, «откуда», дату, тип и общий вид работ (у заявок с составом он часто пуст).
- Системные
alert()для ошибок смены статуса, пересчёта, удаления. - Спецтехника «возит груз»: контроль маршрута вызывается, но не подтверждается телеметрией (раздел 5.5).
Расхождения кода и сопутствующих документов.
| Что | Источник расхождения | Что верно |
|---|---|---|
| Число полей конструктора | бриф и аудит 03: 28 | в коде 29 (19 справочных + 10 мерных) |
| Тип геозоны «круг» | ТЗ: circle |
в коде point (подпись «Точка»); circle нет |
| Срок отчёта «до 366 дней» | ТЗ | разность дат ≤ 366, то есть до 367 календарных дат |
| Выгрузка «до 93 дней», закрытие «до 31 дня» | тексты ошибок | разность дат ≤ 93 / ≤ 31, то есть до 94 / 32 календарных дат |
| Посещение геозоны у спецтехники | схема 4 клиентского комплекта: «по обычному правилу» | операции VISIT у SPECIAL в коде не создаются |
| Предприятие стенда | ТЗ, части аудитов: «Автобаза №4» | «Тест строительной техники» |
| Тесты | аудит 02 | AgroAccessTest (6 тестов) относится к блоку и не упомянут в аудите; AgroFactServiceTest — 14, AgroExportServiceTest — 6 |
| Итоги эмуляции | PLAN.md §7 и EMULATION_LOG.md №20 расходятся |
брать цифры стенда из свежей съёмки (STAND_FACTS) |
Файл EXPORT_1C_SCHEMA.md |
описывает схему неполно | см. раздел 5.11 |
11. Не подтверждено#
| Что | Статус |
|---|---|
Контроль маршрута у SPECIAL с from_geozone_id |
в коде вызывается, посещений нет; на стенде не проверялся |
| Порядок резолвера геозон при пересечении | по аудиту: «первая подходящая»; не проверено в коде |
| Печатные формы (колонки, подписи) | по аудиту; клиентский код печати не перечитывался |
Отображение pass-посещений в route_note и карточке |
по коду описывается как обычная «разгрузка…»; специальной проверки на стенде не было |
| Загрузка файла в 1С; формат с разработчиком 1С не согласован | не выполнялась |
| Отказ роли U при «Открыть день заново», «Обработать период», «Типовой набор» | по коду; текст не проверялся |
| Пересчёт нарядов спецтехники после удаления события топлива | не проверялся |
Поведение конструктора отчёта без доступа к CDN; открытие .xls в разных версиях Excel |
не проверялось |
| Окно «Закрыть все N дней»; печать и Excel отчёта | не воспроизведены на стенде |
| Расхождение «пробег отчёта и пробег наряда» (в162хр: 492,8 км и 97,7 км) | требует отдельной проверки расчёта; в треки стенда намеренно подмешаны помехи GPS |
| Нагрузка на боевых объёмах | не проводилась |
12. Что читать дальше#
- «Архитектура и устройство Exzotron СТРОЙ»
- «Заявки на спецтехнику и перевозку: состав, комплекты, План выезда»
- «Управление нарядами на стройке: факт, рейсы, закрытие дня, печать и 1С»
- «Карта, участки дорог и контроль маршрута на стройке»
- «Топливо на стройке: заправки, сливы и расход»
- «Отчёты и печатные формы Exzotron СТРОЙ»
- «Справочники и подготовка данных Exzotron СТРОЙ»
- «Первичная настройка и ввод в эксплуатацию Exzotron СТРОЙ»
- Комплект Exzotron ТРАНС: «Алгоритмы и интеграции Exzotron ТРАНС (техническое руководство)», «Заявки на перевозку и конструктор маршрута Exzotron ТРАНС»