Как реализовать алгоритм списания материалов по складам с приоритетом склада документа и в алфавитном порядке?

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

Анализ задачи и частые ошибки при списании

Рассмотрим частую задачу, с которой сталкиваются разработчики на платформах 1С: проведение документа (например, «Оказание услуг»), в процессе которого необходимо списать материалы. Главное требование — сначала списывать остаток с основного склада, указанного в реквизите шапки документа, а затем, если этого количества не хватает, списывать остатки с остальных складов в алфавитном порядке их наименований.

Разберем ошибки, которые часто допускают начинающие разработчики при реализации этой задачи:

  1. Выполнение запросов в цикле: Создание и выполнение объектов Запрос внутри цикла по табличной части документа критически снижает производительность системы. Попытки опрашивать остатки по каждому материалу или складу по очереди — неоптимальный подход.
  2. Использование некорректной виртуальной таблицы: Обращение к виртуальной таблице ОстаткиИОбороты вместо Остатки ведет к избыточной нагрузке на СУБД, так как считываются обороты за весь период, которые не требуются для проверки остатков.
  3. Отсутствие единой сортировки в запросе: Сортировку и приоритет складов можно и нужно задавать непосредственно в тексте запроса с помощью условных конструкций.

Правильная архитектура алгоритма проведения

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

  1. Передадим список номенклатуры из табличной части документа в запрос в качестве параметр-таблицы или поместим её во временную таблицу.
  2. Получим конечные остатки с помощью виртуальной таблицы Остатки регистра накопления.
  3. Отсортируем склады так, чтобы склад из шапки документа был первым в выборке, а остальные склады располагались по алфавиту.
  4. Рассчитаем итоги по номенклатуре для проверки общей обеспеченности остатками.
  5. В цикле обхода результатов сгенерируем движения по регистру накопления.

Пример реализации в процедуре ОбработкаПроведения

Проанализируем пример кода с использованием языка запросов 1С. В данном примере сначала выгружается табличная часть документа, передается в запрос для группировки и получения остатков, а затем производится распределение списания по складам.


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

    Запрос.УстановитьПараметр("ТаблицаТоваров", ПереченьНоменклатуры.Выгрузить());
    Запрос.УстановитьПараметр("СкладДокумента", Склад);
    Запрос.УстановитьПараметр("Период", Новый Граница(МоментВремени(), ВидГраницы.Исключая));

    РезультатЗапроса = Запрос.Выполнить();
    
    ВыборкаНоменклатура = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
    
    Пока ВыборкаНоменклатура.Следующий() Цикл
        
        // Проверяем, хватает ли общего остатка по всем складам
        Если ВыборкаНоменклатура.ОстатокНаСкладе < ВыборкаНоменклатура.ТребуемоеКоличество Тогда
            Отказ = Истина;
            Сообщить("Не хватает материала """ + ВыборкаНоменклатура.Номенклатура 
                + """ в количестве " + Строка(ВыборкаНоменклатура.ТребуемоеКоличество - ВыборкаНоменклатура.ОстатокНаСкладе) + " шт.");
            Продолжить;
        КонецЕсли;
        
        ОсталосьСписать = ВыборкаНоменклатура.ТребуемоеКоличество;
        ВыборкаДетали = ВыборкаНоменклатура.Выбрать();
        
        Пока ВыборкаДетали.Следующий() И ОсталосьСписать > 0 Цикл
            
            Если ВыборкаДетали.ОстатокНаСкладе <= 0 Тогда
                Продолжить;
            КонецЕсли;
            
            СписываемоеКоличество = Мин(ОсталосьСписать, ВыборкаДетали.ОстатокНаСкладе);
            
            // Формируем движение расхода по остаткам
            Движение = Движения.ОстаткиМатериалов.Добавить();
            Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
            Движение.Период = Дата;
            Движение.Материал = ВыборкаДетали.Номенклатура;
            Движение.Склад = ВыборкаДетали.Склад;
            Движение.Количество = СписываемоеКоличество;
            
            ОсталосьСписать = ОсталосьСписать - СписываемоеКоличество;
            
        КонецЦикла;
        
    КонецЦикла;

КонецПроцедуры

Разбор ключевых моментов алгоритма

Выясним подробнее, за счет чего достигается правильное и эффективное списание:

  1. Условие условной сортировки: Выражение ВЫБОР КОГДА Остатки.Склад = &СкладДокумента ТОГДА 0 ИНАЧЕ 1 КОНЕЦ присваивает складу из шапки документа наименьший порядок (0). Благодаря этому данный склад всегда оказывается первым в результатах выборки. Для всех прочих складов возвращается значение 1, и далее они упорядочиваются по алфавиту через выражение Остатки.Склад.Наименование АСЦ.
  2. Группировка по ИТОГАМ: Секция ИТОГИ ПО Номенклатура позволяет в рамках одной выборки получить общее суммарное количество остатка по всем складам и детальные строки по каждому отдельному складу. Это исключает необходимость совершать повторные обращения к базе данных.
  3. Использование функции Мин(): Позволяет быстро рассчитать доступный объем списания с текущего склада: списать либо весь остаток с этого склада, либо только то количество, которое осталось до полного закрытия потребности.
  4. Использование объекта Граница: В параметр Период виртуальной таблицы Остатки передается момент времени с указанием границы (исключая движения текущего документа), что обеспечивает корректный расчет остатка при неоперативном проведении или перепроведении.
← На главную