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

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

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

Анализ ошибок в исходном алгоритме

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


Процедура ОбработкаПроведения(Отказ, Режим)
    Движения.ОстаткиТоваров.Записывать = Истина;
    Движения.ОстаткиТоваров.Очистить();
    Для Каждого ТекСтрокаТовары Из Товары Цикл
        Движение = Движения.ОстаткиТоваров.Добавить();
        Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
        Движение.Период = Дата;
        Движение.Номенклатура = ТекСтрокаТовары.Товар;
        Движение.Склад = Склад;
        Движение.Количество = ТекСтрокаТовары.Количество;
        Движение.Сумма = ТекСтрокаТовары.Количество * РегистрыНакопления.ОстаткиТоваров.Остатки(Дата, "Номенклатура,Склад", , "Сумма") / РегистрыНакопления.ОстаткиТоваров.Остатки(Дата, "Номенклатура,Склад", , "Количество");
    КонецЦикла;
КонецПроцедуры

В этом коде допущено сразу несколько критических ошибок:

  1. Некорректный вызов метода остатков: Метод Остатки() менеджера регистра накопления принимает в качестве второго параметра отбора объект типа Структура, а не текстовую строку "Номенклатура,Склад". Передача строки приведет к ошибке выполнения.
  2. Несоответствие типов данных: Метод Остатки() возвращает объект типа ТаблицаЗначений, а не числовое значение. Попытка умножить или разделить таблицу значений на число вызывает ошибку платформы «Преобразование к типу Число не может быть выполнено».
  3. Обращение к базе данных в цикле: Вызов методов регистра внутри цикла Для Каждого создает критическую нагрузку на систему и существенно снижает производительность.
  4. Риск деления на ноль: При отсутствии остатков на складе делитель равен нулю, что вызывает аварийную остановку выполнения программы.
  5. Использование даты вместо момента времени: Передача реквизита Дата без учета момента времени не гарантирует получение актуальных остатков на секунду проведения документа.
  6. Дублирование циклов обхода: Для формирования единой записи по взаиморасчетам обходить табличную часть повторно не требуется — достаточно использовать метод Товары.Итог("Сумма").

Правильный подход: формирование движений с помощью запросов

Для корректного расчета средневзвешенной цены и формирования движений в платформе 1С принято использовать язык запросов. Выясним, как построить этот процесс по шагам.

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

Передадим табличную часть документа во временную таблицу запроса с помощью объекта МенеджерВременныхТаблиц. Обязательно сгруппируем строки по номенклатуре и складу, чтобы исключить дублирование позиций и ускорить соединение данных.

Шаг 2. Связывание с виртуальной таблицей остатков

Соединим временную таблицу с виртуальной таблицей РегистрНакопления.ОстаткиТоваров.Остатки с помощью левого соединения по полям номенклатуры и склада. В качестве точки расчета остатков передадим МоментВремени().

Шаг 3. Безопасный расчет себестоимости в запросе

Для обработки отсутствующих остатков и защиты от деления на ноль в языке запросов используем функции ЕСТЬНУЛЛ и конструкцию ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ:


ВЫБОР 
    КОГДА ЕСТЬНУЛЛ(ОстаткиТоваровОстатки.КоличествоОстаток, 0) = 0 ТОГДА 0
    ИНАЧЕ ДокТовары.Количество * (ОстаткиТоваровОстатки.СуммаОстаток / ОстаткиТоваровОстатки.КоличествоОстаток)
КОНЕЦ КАК СуммаСписания

Оптимизация записи взаиморасчетов

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


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

Обратите внимание: возникновение дебиторской задолженности покупателя при реализации товаров в 1С традиционно отражается видом движения Приход.

← На главную