Как при проведении документа списать ингредиенты из табличной части справочника Номенклатура?

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

При автоматизации учета производства или сферы услуг (например, в общепите или автосервисе) часто возникает задача: в документе указывается готовое изделие или услуга, а со склада требуется списать компоненты, входящие в его состав. Если состав изделия хранится в подчиненной табличной части справочника Номенклатура (например, Ингредиенты), стандартный механизм конструктора движений документа не позволяет напрямую связать строки документа с составом справочника без использования запроса.

Разберем по шагам, как правильно составить запрос и сформировать движения регистра накопления в процедуре ОбработкаПроведения.

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

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

Для правильного решения задачи требуется:

  1. Выгрузить табличную часть документа во временную таблицу или передать ее в параметры запроса.
  2. Связать данные документа с табличной частью Ингредиенты справочника Номенклатура.
  3. Рассчитать общее количество списываемых материалов с учетом коэффициента расхода на единицу продукции прямо в запросе.
  4. Сгруппировать результат по номенклатуре материалов и сформировать записи в регистре накопления.

Шаг 1. Передача табличной части документа в запрос

Рассмотрим построение оптимального запроса. Чтобы работать с актуальными данными документа (в том числе до момента их физической записи в базу), передадим табличную часть документа в запрос в виде таблицы значений. Посмотрим на реализацию:


// В процедуре ОбработкаПроведения(Отказ, Режим)
Движения.ОстаткиМатериалов.Записывать = Истина;

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

Запрос.УстановитьПараметр("ТаблицаТоваров", ПереченьНоменклатуры.Выгрузить());
РезультатЗапроса = Запрос.Выполнить();

Шаг 2. Формирование движений регистра накопления

Разберем процесс записи движений. После того как запрос сгруппировал списываемые материалы и рассчитал их итоговое количество, выполним обход выборки и заполним набор записей регистра:


Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    Движение = Движения.ОстаткиМатериалов.Добавить();
    Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
    Движение.Период = Дата;
    Движение.Склад = Склад;
    Движение.Номенклатура = Выборка.Номенклатура;
    Движение.Количество = Выборка.Количество;
КонецЦикла;

Шаг 3. Обработка позиций без ингредиентов

Проанализируем ситуацию, когда в документе могут присутствовать как составные позиции (требующие списания ингредиентов), так и простые товары/материалы без состава. Если использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ, позиции без табличной части Ингредиенты не попадут в движения.

Для универсального списания применим ЛЕВОЕ СОЕДИНЕНИЕ и конструкцию ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ:


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

Важно: При таком подходе, если у номенклатуры заполнен состав, со склада списываются ингредиенты с умножением на норму. Если же состав не задан, списывается сама номенклатура в исходном количестве.

Рекомендации по оптимизации и архитектуре

Выясним, какие ограничения накладывает хранение состава в табличной части справочника Номенклатура:

  1. Отсутствие версионности: Если рецептура блюда или состав изделия меняются со временем, изменение табличной части элемента номенклатуры приведет к тому, что при перепроведении старых документов расход материалов рассчитается по новым нормам. В сложных конфигурациях для этого создается отдельный справочник СпецификацииНоменклатуры с привязкой по датам или версиям.
  2. Блокировка данных: В высоконагруженных базах перед выполнением движений рекомендуется устанавливать управляемую блокировку на регистр ОстаткиМатериалов по списку номенклатуры и складу с помощью объекта БлокировкаДанных, чтобы исключить чтение неактуальных остатков другими транзакциями.
← На главную