Как удалить из заказа покупателя в 1С:УНФ позиции с отсутствующим остатком на складе?

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

В процессе работы с заказами в конфигурации «1С:Управление нашей фирмой» (УНФ) часто возникает задача автоматической корректировки состава документа. Одной из самых популярных операций является удаление позиций, по которым на текущий момент нет остатков на складе. Несмотря на кажущуюся простоту, начинающие разработчики часто сталкиваются с логическими ошибками в запросах и проблемами при программном удалении строк из табличной части.

В этой статье мы подробно разберем, как правильно составить запрос к регистру ЗапасыНаСкладах, как избежать «ловушки NULL» при соединении таблиц и как безопасно удалить строки из объекта документа.

Анализ проблемы: почему простой запрос не работает?

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

Когда мы соединяем Заказ (левая таблица) и Остатки <= 0 (правая таблица):

  1. Если товара в заказе нет на складе, он попадет в правую таблицу, и соединение вернет данные.
  2. Если товар в заказе есть в наличии (например, 10 шт.), он не попадет в правую таблицу (так как там только нули и минусы).
  3. При левом соединении для товара с остатком 10 шт. в поле остатка вернется NULL.

В итоге система не сможет отличить товар, которого «совсем нет в регистре», от товара, который «просто не попал в выборку из-за фильтра». Для обеих позиций результат запроса будет содержать NULL в колонке остатка. Именно поэтому важно накладывать условия фильтрации на конечном этапе или использовать правильную логику проверки на NULL.

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

Согласно стандартам разработки «1С», наиболее эффективным способом является использование временных таблиц. Это позволяет избежать прямого соединения реальной таблицы документа с тяжелой виртуальной таблицей остатков, что критично для производительности системы. Разберем по шагам правильный алгоритм запроса.

Сначала мы подготовим данные из документа, поместив их во временную таблицу и проиндексировав их. Это «подскажет» оптимизатору СУБД, как быстрее связать данные.


// Часть 1: Собираем номенклатуру из заказа во временную таблицу
ВЫБРАТЬ
    ЗаказПокупателяЗапасы.Номенклатура КАК Номенклатура,
    ЗаказПокупателяЗапасы.Характеристика КАК Характеристика,
    ЗаказПокупателяЗапасы.НомерСтроки КАК НомерСтроки,
    ЗаказПокупателяЗапасы.Ссылка.Организация КАК Организация
ПОМЕСТИТЬ вт_ДанныеЗаказа
ИЗ
    Документ.ЗаказПокупателя.Запасы КАК ЗаказПокупателяЗапасы
ГДЕ
    ЗаказПокупателяЗапасы.Ссылка = &Ссылка
ИНДЕКСИРОВАТЬ ПО
    Номенклатура,
    Характеристика,
    Организация
;

Далее мы получаем остатки. Обратите внимание, что мы передаем список номенклатуры из нашей временной таблицы в параметры виртуальной таблицы. Это заставляет 1С рассчитывать остатки не по всему складу, а только по тем позициям, которые есть в конкретном заказе.


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

И, наконец, соединяем данные и выводим только те строки заказа, где остаток либо равен 0 (отсутствует в регистре, что дает NULL), либо отрицательный. Важный момент: мы упорядочиваем результат по НомерСтроки в убывающем порядке. Это необходимо для корректного удаления строк в коде 1С.


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

Шаг 2: Программная обработка удаления строк

Выясним причину, по которой мы использовали сортировку УБЫВ. В 1С табличная часть — это индексированная коллекция. Если мы удалим первую строку (индекс 0), то вторая строка автоматически станет первой, третья — второй и так далее. Если наш цикл идет по возрастанию, он просто перескочит через строку или выйдет за границы массива. Проанализируем правильный подход к удалению.


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

Дополнительные рекомендации и технические нюансы

Рассмотрим несколько важных моментов, которые помогут сделать ваше решение более профессиональным:

  1. Учет характеристик и складов. В УНФ остатки хранятся в разрезе характеристик и организаций. Обязательно включайте их в условия соединения, иначе запрос может ошибочно показать наличие товара, если на складе лежит та же номенклатура, но с другой характеристикой.
  2. Резервирование. Если ваша логика предполагает, что удалять нужно только те позиции, которые нельзя зарезервировать, используйте виртуальную таблицу Запасы.Остатки вместо ЗапасыНаСкладах.Остатки. Вторая показывает фактическое наличие, тогда как первая учитывает резервы других покупателей.
  3. Транзакционная целостность. Если вы работаете в высоконагруженной базе, перед выполнением удаления стоит установить Управляемую блокировку на регистр остатков. Это гарантирует, что пока ваш код проверяет наличие товара, другой пользователь не успеет его продать.
  4. Альтернатива удалению в цикле. Если строк в заказе очень много (тысячи), удаление по одной может работать медленно. Проанализируем другой вариант: выгрузить нужные строки (те, где остаток > 0) в таблицу значений, полностью очистить табличную часть документа методом ЗаказОбъект.Запасы.Очистить() и загрузить обратно отфильтрованные строки методом Загрузить().

Подведем итог. Для корректного решения задачи мы должны использовать Левое соединение, обязательно обрабатывать NULL через функцию ЕСТЬNULL, чтобы учесть товары, по которым в регистре вообще нет записей, и выполнять удаление строго с конца списка строк. Использование временных таблиц обеспечит высокую скорость работы даже при больших объемах данных в базе УНФ.

← На главную