Как правильно сгруппировать строки запроса в 1С для учета пробегов, учитывая смену водителя, прицепа и последовательное заполнение одометра?

Программист 1С v8.3 (Управляемые формы) Управленческий учет
← На главную

Мы сталкиваемся с задачей, когда необходимо сгруппировать данные из путевых листов для заполнения документа "Регистрация пробегов за месяц". Особенность этой задачи заключается в том, что группировка должна учитывать не только уникальные комбинации "Тип прицепа", "Транспортное средство", "Водитель", но и их последовательность, а также зависимое заполнение показаний одометра, где "Одометр на начало месяца" для последующих строк зависит от "Одометра на конец месяца" предыдущей строки. При этом "Одометр на конец месяца" вводится пользователем вручную. Разберем эту ситуацию подробно и найдем эффективное решение.

Анализ проблемы: Сложности группировки и зависимых данных

Основная сложность заключается в том, что стандартная группировка в запросе 1С по полям ТипПрицепа, ТранспортноеСредство, Водитель может привести к потере промежуточных данных и нарушению необходимой последовательности. Например, если транспортное средство ТС1 сначала использовал Водитель1 с Прицепом1, затем Водитель2 с Прицепом2, а потом снова Водитель1 с Прицепом1, то обычная группировка схлопнет первую и третью запись, если они будут одинаковы по ключу. Нам же требуется сохранить все переключения, потому что каждое такое переключение представляет собой отдельную "серию" использования и должно быть отражено отдельной строкой в документе.

Еще одна критическая деталь — это расчет показателей одометра. ОдометрНаНачалоМесяца для первого появления конкретного транспортного средства в текущем месяце должен браться как максимальный ОдометрНаКонецМесяца из документов "Регистрация пробегов за месяц" за предыдущий период (например, предыдущий месяц). А для последующих записей ОдометрНаНачалоМесяца должен становиться равным ОдометруНаКонецМесяца предыдущей записи для этого же транспортного средства. При этом ОдометрНаКонецМесяца вводится вручную. Это создает цепочку зависимостей, которую крайне сложно, а иногда и невозможно, реализовать чисто запросом, поскольку запрос не может "заглянуть вперед" на еще не введенные вручную данные или выполнять итеративное обновление строк в рамках одного выполнения.

Общий подход к решению: Запрос для структуры + Программный цикл для логики

Для решения этой задачи мы будем использовать комбинированный подход, который сочетает мощь запросов 1С для подготовки данных и гибкость программного кода для реализации сложной логики:

  1. Запросом получим все уникальные комбинации "Транспортное средство", "Водитель", "Тип прицепа" за нужный период, учитывая их последовательность первого появления.
  2. Отдельным запросом определим начальные показания одометра для каждого транспортного средства из предыдущего периода.
  3. Программным циклом в модуле документа или обработки заполним табличную часть, последовательно рассчитывая ОдометрНаНачалоМесяца для каждой строки, основываясь на ОдометреНаКонецМесяца предыдущей строки (после того, как он будет введен вручную или для начальных значений).

Шаг 1: Получение уникальных комбинаций с сохранением последовательности

Нам нужно получить каждую уникальную смену "Водителя" или "Типа прицепа" для каждого "Транспортного средства" в той последовательности, в которой она фактически происходила. Для этого мы можем использовать данные из путевых листов. Мы будем определять "Дату первого появления" для каждой уникальной комбинации ТранспортноеСредство + Водитель + ТипПрицепа, что позволит нам правильно отсортировать результат. Создадим временную таблицу, которая соберет все уникальные сочетания и определит минимальную дату, когда такое сочетание было зафиксировано.


// Запрос для получения уникальных смен водителей/прицепов по ТС с их первым появлением
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ПутевыеЛисты.ТранспортноеСредство КАК ТранспортноеСредство,
    ПутевыеЛисты.Водитель КАК Водитель,
    ПутевыеЛисты.ТипПрицепа КАК ТипПрицепа,
    МИН(ПутевыеЛисты.Дата) КАК ДатаПервогоПоявления
ПОМЕСТИТЬ ВТ_УникальныеСмены
ИЗ
    Документ.ПутевойЛист КАК ПутевыеЛисты
ГДЕ
    ПутевыеЛисты.Дата МЕЖДУ &НачалоМесяца И &КонецМесяца
СГРУППИРОВАТЬ ПО
    ПутевыеЛисты.ТранспортноеСредство,
    ПутевыеЛисты.Водитель,
    ПутевыеЛисты.ТипПрицепа
;

Здесь &НачалоМесяца и &КонецМесяца — это параметры запроса, соответствующие периоду, за который мы формируем документ "Регистрация пробегов за месяц". Параметр ПутевыеЛисты.Дата должен включать время, если смена водителя или прицепа может происходить несколько раз в один день, и важна их последовательность в течение дня.

Шаг 2: Определение начальных показаний одометра для транспортных средств

Для первой записи каждого транспортного средства в текущем месяце ОдометрНаНачалоМесяца должен браться из последнего известного ОдометраНаКонецМесяца из документов "Регистрация пробегов за месяц" за предыдущий период. Создадим еще одну временную таблицу, которая найдет максимальный ОдометрНаКонецМесяца для каждого транспортного средства за предыдущий месяц.


// Запрос для получения максимального одометра на конец месяца из предыдущего периода
ВЫБРАТЬ
    РегПробегов.ТранспортноеСредство КАК ТранспортноеСредство,
    МАКС(РегПробегов.ОдометрНаКонецМесяца) КАК ОдометрНаНачалоТекущегоМесяца
ПОМЕСТИТЬ ВТ_НачальныеОдометрыТС
ИЗ
    Документ.РегистрацияПробеговЗаМесяц.ТабличнаяЧастьПробеги КАК РегПробегов
ГДЕ
    РегПробегов.Ссылка.Период МЕЖДУ &НачалоПредыдущегоМесяца И &КонецПредыдущегоМесяца
СГРУППИРОВАТЬ ПО
    РегПробегов.ТранспортноеСредство
;

Здесь &НачалоПредыдущегоМесяца и &КонецПредыдущегоМесяца — параметры, ограничивающие поиск предыдущим месяцем. Важно, чтобы поле РегПробегов.Ссылка.Период в документе "Регистрация пробегов за месяц" содержало период, соответствующий месяцу регистрации. Если документ содержит несколько записей для одного ТС за один месяц, мы берем максимальное значение ОдометрНаКонецМесяца, предполагая, что это последнее актуальное значение для данного ТС за предыдущий месяц.

Шаг 3: Объединение данных и окончательный запрос для формирования основы табличной части

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


// Окончательный запрос для формирования основы табличной части
ВЫБРАТЬ
    УникальныеСмены.ТранспортноеСредство,
    УникальныеСмены.Водитель,
    УникальныеСмены.ТипПрицепа,
    ЕСТЬNULL(НачальныеОдометры.ОдометрНаНачалоТекущегоМесяца, 0) КАК ОдометрНаНачалоМесяца, // Установим 0, если данных за прошлый месяц нет
    0 КАК ОдометрНаКонецМесяца // Это поле будет заполняться вручную
ИЗ
    ВТ_УникальныеСмены КАК УникальныеСмены
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_НачальныеОдометрыТС КАК НачальныеОдометры
    ПО УникальныеСмены.ТранспортноеСредство = НачальныеОдометры.ТранспортноеСредство
УПОРЯДОЧИТЬ ПО
    УникальныеСмены.ТранспортноеСредство,
    УникальныеСмены.ДатаПервогоПоявления
;

В этом запросе мы используем ЛЕВОЕ СОЕДИНЕНИЕ, чтобы получить начальные одометры. Если для какого-то транспортного средства данных за предыдущий месяц нет, ОдометрНаНачалоМесяца будет NULL, поэтому используем ЕСТЬNULL для установки значения по умолчанию (например, 0). ОдометрНаКонецМесяца инициализируем нулем, так как это поле будет заполняться вручную пользователем.

Ключевым моментом здесь является сортировка: сначала по УникальныеСмены.ТранспортноеСредство, а затем по УникальныеСмены.ДатаПервогоПоявления. Это гарантирует, что записи для одного и того же транспортного средства будут следовать в хронологическом порядке их первого появления в рамках смены "Водитель/Прицеп", что крайне важно для корректной цепочки одометров.

Шаг 4: Программное заполнение табличной части и расчет последовательных одометров

После того как мы получили основной набор строк с правильной сортировкой и начальными одометрами для первого появления ТС, нам необходимо программно заполнить табличную часть документа и реализовать логику последовательного расчета ОдометраНаНачалоМесяца. Это особенно важно, так как ОдометрНаКонецМесяца вводится вручную, и его значение должно влиять на ОдометрНаНачалоМесяца следующей строки для того же транспортного средства.

4.1. Первичное заполнение табличной части

Разберем алгоритм первичного заполнения табличной части документа после выполнения запроса:

  1. Выполнение запроса: Выполните окончательный запрос (Шаг 3) и получите результат, например, в виде Выборки из результата запроса или ТаблицыЗначений.
  2. Перебор строк: Обходите полученные данные в цикле. Для каждого элемента выборки создавайте новую строку в табличной части документа (например, ЭтотОбъект.Пробеги.Добавить()).
  3. Заполнение полей: Заполняйте поля ТранспортноеСредство, Водитель, ТипПрицепа из соответствующей строки выборки.
  4. ОдометрНаНачалоМесяца:
    • Для первой строки каждого нового транспортного средства (т.е. когда ТранспортноеСредство текущей строки отличается от ТранспортноеСредство предыдущей строки), установите ОдометрНаНачалоМесяца из результата запроса (значение ОдометрНаНачалоМесяца из СтрокаРезультата).
    • Для последующих строк того же транспортного средства, установите ОдометрНаНачалоМесяца = 0. Это сделано намеренно, чтобы пользователь был вынужден ввести ОдометрНаКонецМесяца для предыдущей строки, чтобы значение "подтянулось" в следующую.
  5. ОдометрНаКонецМесяца: Инициализируйте ОдометрНаКонецМесяца = 0, так как это поле будет заполняться вручную.

Пример программного кода для первичного заполнения (в модуле объекта или формы документа):


// Предполагаем, что у вас есть табличная часть "Пробеги" в документе
// и результат запроса уже загружен в ТаблицаЗначенийРезультатаЗапроса (или Выборка)

ПеременнаяПредыдущееТС = Неопределено;

Для Каждого СтрокаРезультата Из ТаблицаЗначенийРезультатаЗапроса Цикл
    НоваяСтрока = ЭтотОбъект.Пробеги.Добавить(); // Добавляем строку в табличную часть документа

    НоваяСтрока.ТранспортноеСредство = СтрокаРезультата.ТранспортноеСредство;
    НоваяСтрока.Водитель = СтрокаРезультата.Водитель;
    НоваяСтрока.ТипПрицепа = СтрокаРезультата.ТипПрицепа;

    Если СтрокаРезультата.ТранспортноеСредство <> ПеременнаяПредыдущееТС Тогда
        // Это первое появление данного ТС в текущем заполнении
        НоваяСтрока.ОдометрНаНачалоМесяца = СтрокаРезультата.ОдометрНаНачалоМесяца;
    Иначе
        // Последующие появления того же ТС.
        // ОдометрНаНачалоМесяца будет установлен в 0.
        // Он обновится автоматически после ручного ввода ОдометраНаКонецМесяца
        // в предыдущей строке с этим же ТС.
        НоваяСтрока.ОдометрНаНачалоМесяца = 0;
    КонецЕсли;

    НоваяСтрока.ОдометрНаКонецМесяца = 0; // Инициализируем для ручного ввода

    ПеременнаяПредыдущееТС = СтрокаРезультата.ТранспортноеСредство;

КонецЦикла;

4.2. Интерактивное обновление одометров после ручного ввода

Для того чтобы "Одометр на конец месяца" после ручного ввода обновлял "Одометр на начало месяца" следующей строки с таким же транспортным средством, нам потребуется обработчик события на форме документа. Этот обработчик будет срабатывать, когда пользователь изменяет значение поля ОдометрНаКонецМесяца в табличной части.

Логика обработчика:

  1. Определяем текущую строку, в которой произошло изменение.
  2. Запоминаем новое значение ОдометраНаКонецМесяца.
  3. Начиная со следующей строки табличной части, ищем строки с тем же ТранспортноеСредство.
  4. Для каждой такой найденной строки обновляем ее ОдометрНаНачалоМесяца значением из предыдущей строки.
  5. Продолжаем до тех пор, пока не встретим смену транспортного средства или не достигнем конца табличной части.

Пример программного кода для интерактивного пересчета (в модуле формы документа):


// Эта процедура будет вызываться, например, из обработчика события
// "ПриИзменении" для колонки "ОдометрНаКонецМесяца" в табличной части "Пробеги" на форме.

Процедура ПробегиОдометрНаКонецМесяцаПриИзменении(Элемент)

    // Получаем текущую строку табличной части, в которой произошло изменение
    ТекущиеДанные = Элементы.Пробеги.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда Возврат КонецЕсли;

    // Индекс текущей строки в табличной части документа
    ИндексТекущейСтроки = Элементы.Пробеги.ТекущаяСтрота;

    // Значение ОдометраНаКонецМесяца, введенное пользователем в текущей строке
    ЗначениеОдометраНаКонецТекущейСтроки = ТекущиеДанные.ОдометрНаКонецМесяца;

    // Транспортное средство текущей строки, для которого нужно продолжить цепочку
    ТекущееТС = ТекущиеДанные.ТранспортноеСредство;

    // Проходим по строкам табличной части, начиная со следующей
    Для Индекс = ИндексТекущейСтроки + 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С и гибкости программного кода. Запросы позволяют эффективно извлечь и подготовить структурированные данные, отсортированные в нужной последовательности. В то время как программный цикл обеспечивает реализацию сложной логики зависимого расчета, особенно когда часть данных вводится вручную и требует интерактивного обновления. Используя описанный подход, мы сможем добиться корректного и удобного заполнения документа "Регистрация пробегов за месяц", минимизируя возможные ошибки и обеспечивая точность учета.

← На главную