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

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

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

Анализ типичных ошибок при ручной доработке движений

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

  1. Обращение к переменным табличной части вместо выборки запроса. При копировании шаблонного кода из конструктора внутри цикла обхода выборки разработчики часто забывают заменить обращение к переменной строки (например, ТекСтрокаТабличнаяЧасть1.Номенклатура) на поля результата запроса (Выборка.Номенклатура). В результате в цикл передаются некорректные или пустые значения.
  2. Закомментированные параметры запроса. Если в тексте запроса используются параметры (например, &МоментВремени и &Ссылка), а вызовы метода УстановитьПараметр() закомментированы, запрос завершится ошибкой или вернет пустой результат, из-за чего выполнение даже не зайдет в цикл Пока Выборка.Следующий().
  3. Неправильное управление флагом записи движений. Для формирования записей свойству набора Движения.ОстаткиТоваров.Записывать должно быть присвоено значение Истина. Если при нехватке остатка выставить Движения.ОстаткиТоваров.Записывать = Ложь, то даже при наличии других корректных позиций весь набор движений очистится или не запишется. Достаточно просто взвести признак Отказ = Истина.
  4. Поведение Конструктора движений. Конструктор движений предназначен только для создания простых типовых шаблонов. Если открыть конструктор повторно в модуле документа, где уже написан запрос и сложная логика, конструктор не сможет их распознать и перезапишет код внутри служебных комментариев.

Пошаговая реализация проведения с контролем остатков и расчетом себестоимости

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

  1. Устанавливаем флаг записи для тех регистров, по которым планируем формировать движения.
  2. Формируем и выполняем запрос, соединяющий табличную часть документа с виртуальной таблицей остатков на момент времени документа.
  3. Обходим результат запроса: если требуемое количество превышает остаток на складе, устанавливаем Отказ = Истина и выводим диагностическое сообщение.
  4. Рассчитываем себестоимость списания и заполняем свойства набора записей регистра.

Посмотрим на пример реализации процедуры проведения документа:


Процедура ОбработкаПроведения(Отказ, Режим)

    // Включаем флаги записи для формируемых регистров
    Движения.ОстаткиТоваров.Записывать = Истина;
    Движения.РегистрЦен.Записывать = Истина;

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

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

    Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
    Запрос.УстановитьПараметр("Ссылка", Ссылка);

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

    Пока Выборка.Следующий() Цикл

        // Проверка нехватки товара
        Если Выборка.Количество > Выборка.КоличествоОстаток Тогда
            Отказ = Истина;
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Недостаточно товара " + Строка(Выборка.Номенклатура) 
                + ". Требуется: " + Строка(Выборка.Количество) 
                + ", в наличии: " + Строка(Выборка.КоличествоОстаток);
            Сообщение.Сообщить();
        КонецЕсли;

        // Если обнаружена ошибка, продолжаем проверять остальные строки для вывода сообщений
        Если Отказ Тогда
            Продолжить;
        КонецЕсли;

        // Формирование движения расхода
        Движение = Движения.ОстаткиТоваров.Добавить();
        Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
        Движение.Период = Дата;
        Движение.Номенклатура = Выборка.Номенклатура;
        Движение.Количество = Выборка.Количество;

        // Расчет себестоимости с защитой от погрешностей деления (списание под ноль)
        Если Выборка.Количество = Выборка.КоличествоОстаток Тогда
            Движение.Стоимость = Выборка.СтоимостьОстаток;
        Иначе
            Движение.Стоимость = Окр(Выборка.Количество / Выборка.КоличествоОстаток * Выборка.СтоимостьОстаток, 2);
        КонецЕсли;

    КонецЦикла;

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

Важные нюансы и рекомендации

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

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