Как в запросе или СКД распределить суммарный остаток товара по заказам?

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

В практике разработчика 1С часто возникает задача распределения некоторого общего ресурса (например, остатка товара на складе) по списку потребностей (заказам). Ситуация осложняется, когда общего остатка недостаточно для удовлетворения всех заказов, и требуется распределить его по определенному правилу: например, первым пришедшим (FIFO) или, наоборот, начиная с самых последних (LIFO). В этой статье мы подробно разберем, как реализовать подобную логику без использования циклов в коде 1С, переложив вычисления на сторону SQL-сервера или механизмы Системы компоновки данных (СКД).

Постановка задачи и выбор стратегии

Представим ситуацию: у нас есть три заказа. Заказ №1 требует 5 штук товара, Заказ №2 — 10 штук, Заказ №3 — 7 штук. Итого общая потребность составляет 22 штуки. Однако на складе в данный момент имеется всего 8 штук. Нам необходимо распределить эти 8 штук по заказам, начиная с самого последнего (Заказ №3). Проанализируем ситуацию: Заказ №3 должен получить свои 7 штук полностью, а оставшаяся 1 штука должна отойти Заказу №2. Заказ №1 в этом случае не получит ничего.

Для решения этой задачи мы рассмотрим два основных метода:

  1. Использование запроса с механизмом самосоединения для расчета нарастающего итога.
  2. Использование функций СКД (ВычислитьВыражение), что позволяет динамически менять порядок распределения в настройках отчета.

Метод 1: Распределение остатка в запросе через нарастающий итог

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

Разберем по шагам алгоритм создания такого запроса:

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

Посмотрим на пример кода запроса:


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Распределение.Номенклатура,
    ВТ_Распределение.ЗаказПокупателя,
    ВТ_Распределение.ВЗаказе,
    ВЫБОР
        КОГДА ВТ_Распределение.ОбщийОстаток - (ВТ_Распределение.НарастающийИтог - ВТ_Распределение.ВЗаказе) > ВТ_Распределение.ВЗаказе
            ТОГДА ВТ_Распределение.ВЗаказе
        КОГДА ВТ_Распределение.ОбщийОстаток - (ВТ_Распределение.НарастающийИтог - ВТ_Распределение.ВЗаказе) > 0
            ТОГДА ВТ_Распределение.ОбщийОстаток - (ВТ_Распределение.НарастающийИтог - ВТ_Распределение.ВЗаказе)
        ИНАЧЕ 0
    КОНЕЦ КАК РаспределенныйОстаток
ИЗ
    ВТ_Распределение КАК ВТ_Распределение

В данном примере функция ВЫБОР реализует математическую логику: мы проверяем, сколько остатка "дошло" до текущей строки, вычитая из общего остатка сумму всех предыдущих строк. Если остатка больше, чем нужно на текущую строку — берем полное количество заказа. Если меньше — берем то, что осталось. Если остаток уже исчерпан — выводим 0.

Метод 2: Реализация через функции СКД

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

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


ВЫБОР 
    КОГДА ЕстьNULL(ОстатокТовара, 0) - (ВычислитьВыражение("Сумма(КоличествоЗаказа)", , , "Первая", "Текущая") - КоличествоЗаказа) > КоличествоЗаказа 
        ТОГДА КоличествоЗаказа 
    КОГДА ЕстьNULL(ОстатокТовара, 0) - (ВычислитьВыражение("Сумма(КоличествоЗаказа)", , , "Первая", "Текущая") - КоличествоЗаказа) > 0 
        ТОГДА ЕстьNULL(ОстатокТовара, 0) - (ВычислитьВыражение("Сумма(КоличествоЗаказа)", , , "Первая", "Текущая") - КоличествоЗаказа) 
    ИНАЧЕ 0 
КОНЕЦ

В этой формуле параметры "Первая" и "Текущая" указывают СКД, что суммирование должно идти от самой первой строки группировки до текущей включительно. Это и есть классический нарастающий итог.

Важный момент: если нам нужно распределять с конца (LIFO), как просил автор темы, нам достаточно в настройках СКД установить обратный порядок сортировки для детальных записей или группировок. Механизм СКД автоматически пересчитает нарастающий итог в соответствии с заданным порядком.

Нюансы производительности и точности вычислений

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

Математически универсальная формула, которую можно использовать в 1С для лаконичности, выглядит так:

Распределено = МАКСИМУМ(0, МИНИМУМ(КоличествоВЗаказе, ОбщийОстаток - (НарастающийИтог - КоличествоВЗаказе)))

Она заменяет громоздкую конструкцию ВЫБОР и работает идентично, корректно отсекая отрицательные значения и значения, превышающие потребность заказа.

Резюме

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

Оба метода позволяют избежать "циклов в циклах", что положительно сказывается на стабильности системы и считается признаком высокого профессионализма разработчика 1С.

← На главную