Мы сталкиваемся с задачей, когда необходимо сгруппировать данные из путевых листов для заполнения документа "Регистрация пробегов за месяц". Особенность этой задачи заключается в том, что группировка должна учитывать не только уникальные комбинации "Тип прицепа", "Транспортное средство", "Водитель", но и их последовательность, а также зависимое заполнение показаний одометра, где "Одометр на начало месяца" для последующих строк зависит от "Одометра на конец месяца" предыдущей строки. При этом "Одометр на конец месяца" вводится пользователем вручную. Разберем эту ситуацию подробно и найдем эффективное решение.
Основная сложность заключается в том, что стандартная группировка в запросе 1С по полям ТипПрицепа, ТранспортноеСредство, Водитель может привести к потере промежуточных данных и нарушению необходимой последовательности. Например, если транспортное средство ТС1 сначала использовал Водитель1 с Прицепом1, затем Водитель2 с Прицепом2, а потом снова Водитель1 с Прицепом1, то обычная группировка схлопнет первую и третью запись, если они будут одинаковы по ключу. Нам же требуется сохранить все переключения, потому что каждое такое переключение представляет собой отдельную "серию" использования и должно быть отражено отдельной строкой в документе.
Еще одна критическая деталь — это расчет показателей одометра. ОдометрНаНачалоМесяца для первого появления конкретного транспортного средства в текущем месяце должен браться как максимальный ОдометрНаКонецМесяца из документов "Регистрация пробегов за месяц" за предыдущий период (например, предыдущий месяц). А для последующих записей ОдометрНаНачалоМесяца должен становиться равным ОдометруНаКонецМесяца предыдущей записи для этого же транспортного средства. При этом ОдометрНаКонецМесяца вводится вручную. Это создает цепочку зависимостей, которую крайне сложно, а иногда и невозможно, реализовать чисто запросом, поскольку запрос не может "заглянуть вперед" на еще не введенные вручную данные или выполнять итеративное обновление строк в рамках одного выполнения.
Для решения этой задачи мы будем использовать комбинированный подход, который сочетает мощь запросов 1С для подготовки данных и гибкость программного кода для реализации сложной логики:
ОдометрНаНачалоМесяца для каждой строки, основываясь на ОдометреНаКонецМесяца предыдущей строки (после того, как он будет введен вручную или для начальных значений).Нам нужно получить каждую уникальную смену "Водителя" или "Типа прицепа" для каждого "Транспортного средства" в той последовательности, в которой она фактически происходила. Для этого мы можем использовать данные из путевых листов. Мы будем определять "Дату первого появления" для каждой уникальной комбинации ТранспортноеСредство + Водитель + ТипПрицепа, что позволит нам правильно отсортировать результат. Создадим временную таблицу, которая соберет все уникальные сочетания и определит минимальную дату, когда такое сочетание было зафиксировано.
// Запрос для получения уникальных смен водителей/прицепов по ТС с их первым появлением
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПутевыеЛисты.ТранспортноеСредство КАК ТранспортноеСредство,
ПутевыеЛисты.Водитель КАК Водитель,
ПутевыеЛисты.ТипПрицепа КАК ТипПрицепа,
МИН(ПутевыеЛисты.Дата) КАК ДатаПервогоПоявления
ПОМЕСТИТЬ ВТ_УникальныеСмены
ИЗ
Документ.ПутевойЛист КАК ПутевыеЛисты
ГДЕ
ПутевыеЛисты.Дата МЕЖДУ &НачалоМесяца И &КонецМесяца
СГРУППИРОВАТЬ ПО
ПутевыеЛисты.ТранспортноеСредство,
ПутевыеЛисты.Водитель,
ПутевыеЛисты.ТипПрицепа
;
Здесь &НачалоМесяца и &КонецМесяца — это параметры запроса, соответствующие периоду, за который мы формируем документ "Регистрация пробегов за месяц". Параметр ПутевыеЛисты.Дата должен включать время, если смена водителя или прицепа может происходить несколько раз в один день, и важна их последовательность в течение дня.
Для первой записи каждого транспортного средства в текущем месяце ОдометрНаНачалоМесяца должен браться из последнего известного ОдометраНаКонецМесяца из документов "Регистрация пробегов за месяц" за предыдущий период. Создадим еще одну временную таблицу, которая найдет максимальный ОдометрНаКонецМесяца для каждого транспортного средства за предыдущий месяц.
// Запрос для получения максимального одометра на конец месяца из предыдущего периода
ВЫБРАТЬ
РегПробегов.ТранспортноеСредство КАК ТранспортноеСредство,
МАКС(РегПробегов.ОдометрНаКонецМесяца) КАК ОдометрНаНачалоТекущегоМесяца
ПОМЕСТИТЬ ВТ_НачальныеОдометрыТС
ИЗ
Документ.РегистрацияПробеговЗаМесяц.ТабличнаяЧастьПробеги КАК РегПробегов
ГДЕ
РегПробегов.Ссылка.Период МЕЖДУ &НачалоПредыдущегоМесяца И &КонецПредыдущегоМесяца
СГРУППИРОВАТЬ ПО
РегПробегов.ТранспортноеСредство
;
Здесь &НачалоПредыдущегоМесяца и &КонецПредыдущегоМесяца — параметры, ограничивающие поиск предыдущим месяцем. Важно, чтобы поле РегПробегов.Ссылка.Период в документе "Регистрация пробегов за месяц" содержало период, соответствующий месяцу регистрации. Если документ содержит несколько записей для одного ТС за один месяц, мы берем максимальное значение ОдометрНаКонецМесяца, предполагая, что это последнее актуальное значение для данного ТС за предыдущий месяц.
Теперь нам необходимо объединить полученные уникальные смены с начальными показаниями одометра. Этот запрос даст нам базовую структуру табличной части, отсортированную в нужной последовательности, с начальными одометрами для первых записей каждого транспортного средства.
// Окончательный запрос для формирования основы табличной части
ВЫБРАТЬ
УникальныеСмены.ТранспортноеСредство,
УникальныеСмены.Водитель,
УникальныеСмены.ТипПрицепа,
ЕСТЬNULL(НачальныеОдометры.ОдометрНаНачалоТекущегоМесяца, 0) КАК ОдометрНаНачалоМесяца, // Установим 0, если данных за прошлый месяц нет
0 КАК ОдометрНаКонецМесяца // Это поле будет заполняться вручную
ИЗ
ВТ_УникальныеСмены КАК УникальныеСмены
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_НачальныеОдометрыТС КАК НачальныеОдометры
ПО УникальныеСмены.ТранспортноеСредство = НачальныеОдометры.ТранспортноеСредство
УПОРЯДОЧИТЬ ПО
УникальныеСмены.ТранспортноеСредство,
УникальныеСмены.ДатаПервогоПоявления
;
В этом запросе мы используем ЛЕВОЕ СОЕДИНЕНИЕ, чтобы получить начальные одометры. Если для какого-то транспортного средства данных за предыдущий месяц нет, ОдометрНаНачалоМесяца будет NULL, поэтому используем ЕСТЬNULL для установки значения по умолчанию (например, 0). ОдометрНаКонецМесяца инициализируем нулем, так как это поле будет заполняться вручную пользователем.
Ключевым моментом здесь является сортировка: сначала по УникальныеСмены.ТранспортноеСредство, а затем по УникальныеСмены.ДатаПервогоПоявления. Это гарантирует, что записи для одного и того же транспортного средства будут следовать в хронологическом порядке их первого появления в рамках смены "Водитель/Прицеп", что крайне важно для корректной цепочки одометров.
После того как мы получили основной набор строк с правильной сортировкой и начальными одометрами для первого появления ТС, нам необходимо программно заполнить табличную часть документа и реализовать логику последовательного расчета ОдометраНаНачалоМесяца. Это особенно важно, так как ОдометрНаКонецМесяца вводится вручную, и его значение должно влиять на ОдометрНаНачалоМесяца следующей строки для того же транспортного средства.
Разберем алгоритм первичного заполнения табличной части документа после выполнения запроса:
ЭтотОбъект.Пробеги.Добавить()).ТранспортноеСредство, Водитель, ТипПрицепа из соответствующей строки выборки.ТранспортноеСредство текущей строки отличается от ТранспортноеСредство предыдущей строки), установите ОдометрНаНачалоМесяца из результата запроса (значение ОдометрНаНачалоМесяца из СтрокаРезультата).ОдометрНаНачалоМесяца = 0. Это сделано намеренно, чтобы пользователь был вынужден ввести ОдометрНаКонецМесяца для предыдущей строки, чтобы значение "подтянулось" в следующую.ОдометрНаКонецМесяца = 0, так как это поле будет заполняться вручную.Пример программного кода для первичного заполнения (в модуле объекта или формы документа):
// Предполагаем, что у вас есть табличная часть "Пробеги" в документе
// и результат запроса уже загружен в ТаблицаЗначенийРезультатаЗапроса (или Выборка)
ПеременнаяПредыдущееТС = Неопределено;
Для Каждого СтрокаРезультата Из ТаблицаЗначенийРезультатаЗапроса Цикл
НоваяСтрока = ЭтотОбъект.Пробеги.Добавить(); // Добавляем строку в табличную часть документа
НоваяСтрока.ТранспортноеСредство = СтрокаРезультата.ТранспортноеСредство;
НоваяСтрока.Водитель = СтрокаРезультата.Водитель;
НоваяСтрока.ТипПрицепа = СтрокаРезультата.ТипПрицепа;
Если СтрокаРезультата.ТранспортноеСредство <> ПеременнаяПредыдущееТС Тогда
// Это первое появление данного ТС в текущем заполнении
НоваяСтрока.ОдометрНаНачалоМесяца = СтрокаРезультата.ОдометрНаНачалоМесяца;
Иначе
// Последующие появления того же ТС.
// ОдометрНаНачалоМесяца будет установлен в 0.
// Он обновится автоматически после ручного ввода ОдометраНаКонецМесяца
// в предыдущей строке с этим же ТС.
НоваяСтрока.ОдометрНаНачалоМесяца = 0;
КонецЕсли;
НоваяСтрока.ОдометрНаКонецМесяца = 0; // Инициализируем для ручного ввода
ПеременнаяПредыдущееТС = СтрокаРезультата.ТранспортноеСредство;
КонецЦикла;
Для того чтобы "Одометр на конец месяца" после ручного ввода обновлял "Одометр на начало месяца" следующей строки с таким же транспортным средством, нам потребуется обработчик события на форме документа. Этот обработчик будет срабатывать, когда пользователь изменяет значение поля ОдометрНаКонецМесяца в табличной части.
Логика обработчика:
ОдометраНаКонецМесяца.ТранспортноеСредство.ОдометрНаНачалоМесяца значением из предыдущей строки.Пример программного кода для интерактивного пересчета (в модуле формы документа):
// Эта процедура будет вызываться, например, из обработчика события
// "ПриИзменении" для колонки "ОдометрНаКонецМесяца" в табличной части "Пробеги" на форме.
Процедура ПробегиОдометрНаКонецМесяцаПриИзменении(Элемент)
// Получаем текущую строку табличной части, в которой произошло изменение
ТекущиеДанные = Элементы.Пробеги.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда Возврат КонецЕсли;
// Индекс текущей строки в табличной части документа
ИндексТекущейСтроки = Элементы.Пробеги.ТекущаяСтрота;
// Значение ОдометраНаКонецМесяца, введенное пользователем в текущей строке
ЗначениеОдометраНаКонецТекущейСтроки = ТекущиеДанные.ОдометрНаКонецМесяца;
// Транспортное средство текущей строки, для которого нужно продолжить цепочку
ТекущееТС = ТекущиеДанные.ТранспортноеСредство;
// Проходим по строкам табличной части, начиная со следующей
Для Индекс = ИндексТекущейСтроки + 1 По ЭтотОбъект.Пробеги.Количество() - 1 Цикл
СледующаяСтрока = ЭтотОбъект.Пробеги[Индекс];
// Если это та же транспортное средство, обновляем ОдометрНаНачалоМесяца
Если СледующаяСтрока.ТранспортноеСредство = ТекущееТС Тогда
СледующаяСтрока.ОдометрНаНачалоМесяца = ЗначениеОдометраНаКонецТекущейСтроки;
// Обновляем значение для следующей итерации, используя ОдометрНаКонецМесяца
// текущей "СледующейСтроки". Если оно уже введено, берем его, иначе - 0.
ЗначениеОдометраНаКонецТекущейСтроки = СледующаяСтрока.ОдометрНаКонецМесяца;
Иначе
// Сменилось транспортное средство, цепочка прервалась
Прервать;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важно помнить, что если пользователь изменит значение ОдометрНаКонецМесяца в середине цепочки, все последующие значения ОдометрНаНачалоМесяца для данного ТС будут пересчитаны. Для обеспечения стабильности данных, возможно, потребуется добавить проверки на то, что введенное значение ОдометрНаКонецМесяца не меньше ОдометраНаНачалоМесяца этой же строки.
Рассмотрим еще раз пример изначальной задачи для лучшего понимания того, как формируются данные и как они обрабатываются:
// Исходные данные путевых листов (упрощенно и отсортировано по дате):
// Дата ТС Водитель Прицеп ОдометрВыезда
// 01.03.2023 ТС1 Водитель1 Прицеп1 10000
// 02.03.2023 ТС1 Водитель2 Прицеп2 10050
// 03.03.2023 ТС1 Водитель1 Прицеп1 10120
// 04.03.2023 ТС1 Водитель1 Прицеп2 10200
// 05.03.2023 ТС1 Водитель2 Прицеп1 10280
// 06.03.2023 ТС1 Водитель2 Прицеп2 10350
// Результат запроса ВТ_УникальныеСмены (после сортировки по ДатаПервогоПоявления):
// ТС Водитель Прицеп ДатаПервогоПоявления
// ТС1 Водитель1 Прицеп1 01.03.2023
// ТС1 Водитель2 Прицеп2 02.03.2023
// ТС1 Водитель1 Прицеп2 04.03.2023 // Важно: комбинация Водитель1+Прицеп2 появилась позже, чем Водитель1+Прицеп1
// ТС1 Водитель2 Прицеп1 05.03.2023
// Допустим, ОдометрНаНачалоТекущегоМесяца для ТС1 из предыдущего периода = 9950.
// Результат окончательного запроса (перед программной обработкой):
// ТС Водитель Прицеп ОдометрНаНачалоМесяца ОдометрНаКонецМесяца
// ТС1 Водитель1 Прицеп1 9950 0
// ТС1 Водитель2 Прицеп2 0 0 // Здесь 0, так как ОдометрНаКонецМесяца первой строки еще не введен
// ТС1 Водитель1 Прицеп2 0 0 // Аналогично
// ТС1 Водитель2 Прицеп1 0 0 // Аналогично
// После ручного ввода ОдометрНаКонецМесяца и срабатывания обработчика (пример):
// ТС Водитель Прицеп ОдометрНаНачалоМесяца ОдометрНаКонецМесяца
// ТС1 Водитель1 Прицеп1 9950 10020
// ТС1 Водитель2 Прицеп2 10020 10180 // Обновлено после ввода 10020 в предыдущей строке
// ТС1 Водитель1 Прицеп2 10180 10270 // Обновлено после ввода 10180 в предыдущей строке
// ТС1 Водитель2 Прицеп1 10270 10390 // Обновлено после ввода 10270 в предыдущей строке
Этот пример наглядно демонстрирует, как запрос формирует структуру, а программный код обеспечивает логику зависимого заполнения. Такой подход позволяет достичь требуемого результата, сохраняя при этом гибкость ручного ввода данных. Дополнительно мы можем рассмотреть возможность использования оконных функций (например, LAG), если ваша версия платформы 1С и контекст запроса это позволяют. Однако в большинстве случаев для задач, где требуется цепочка, зависящая от ввода пользователя или динамического состояния, программный цикл является более надежным, гибким и управляемым решением.
Решение задачи по группировке строк путевых листов с учетом последовательности смен водителя и прицепа, а также цепного заполнения одометра, требует комбинации мощных возможностей запросов 1С и гибкости программного кода. Запросы позволяют эффективно извлечь и подготовить структурированные данные, отсортированные в нужной последовательности. В то время как программный цикл обеспечивает реализацию сложной логики зависимого расчета, особенно когда часть данных вводится вручную и требует интерактивного обновления. Используя описанный подход, мы сможем добиться корректного и удобного заполнения документа "Регистрация пробегов за месяц", минимизируя возможные ошибки и обеспечивая точность учета.