При разработке модулей проведения документов в системе 1С часто возникает задача расчета списываемой себестоимости или формирования движений по регистру с учетом текущих остатков на складах. Частая ошибка начинающих разработчиков — попытка выполнять запросы к остаткам внутри цикла обхода табличной части документа или выносить расчет за пределы цикла с неверной группировкой. Рассмотрим подробнее, как правильно организовать алгоритм проведения документа, получить остатки с помощью запроса и корректно рассчитать средневзвешенную цену.
Разберем общую логику решения задачи. Если в документе реализации или списания указано несколько строк с номенклатурой и складами, нам необходимо вычислить себестоимость каждой позиции по формуле:
СредневзвешеннаяЦена = ОстатокСумма / ОстатокКоличество
Для реализации этого алгоритма необходимо придерживаться следующих ключевых правил:
МоментВремени() проводимого документа, а не простая дата. Это исключает искажения при учете документов в пределах одной секунды.ОстатокКоличество равно нулю, деление выполнять нельзя.Проанализируем структуру запроса, который выбирает актуальные остатки суммы и количества по номенклатуре и складам, присутствующим в проводимом документе. Отбор по номенклатуре строится на основании табличной части документа.
Рассмотрим пример запроса к виртуальной таблице остатков:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Склад КАК Склад,
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток,
ТоварыНаСкладахОстатки.СуммаОстаток КАК СуммаОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
&МоментВремени,
Номенклатура В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧТовары.Номенклатура КАК Номенклатура
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ТЧТовары
ГДЕ
ТЧТовары.Ссылка = &Ссылка)
) КАК ТоварыНаСкладахОстатки
В данном запросе параметр &Ссылка передает текущий документ, а &МоментВремени обеспечивает получение остатков строго на момент проведения. Результат запроса выгружается в таблицу значений или обрабатывается с помощью объекта ВыборкаИзРезультатаЗапроса.
Разберем по шагам процесс обработки полученных данных в модуле объекта документа. После выполнения запроса мы обходим строки табличной части документа, находим соответствующие остатки и формируем записи в регистр накопления.
Посмотрим на пример кода обработки проведения в 1С:
// Очищаем существующие движения документа
Движения.ОстаткиТоваров.Записать = Истина;
Движения.ОстаткиТоваров.Очистить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыОстатки.Склад КАК Склад,
| ТоварыОстатки.Номенклатура КАК Номенклатура,
| ТоварыОстатки.КоличествоОстаток КАК КоличествоОстаток,
| ТоварыОстатки.СуммаОстаток КАК СуммаОстаток
|ИЗ
| РегистрНакопления.ОстаткиТоваров.Остатки(&МоментВремени, Номенклатура В
| (ВЫБРАТЬ РАЗЛИЧНЫЕ ТЧ.Номенклатура ИЗ Документ.РеализацияТоваровУслуг.Товары КАК ТЧ ГДЕ ТЧ.Ссылка = &Ссылка)) КАК ТоварыОстатки";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
ТаблицаОстатков = Запрос.Выполнить().Выгрузить();
ОбщаяСуммаДокумента = 0;
Для Каждого ТекСтрока Из Товары Цикл
// Поиск остатка по складу и номенклатуре текущей строки
Отбор = Новый Структура("Склад, Номенклатура", ТекСтрока.Склад, ТекСтрока.Номенклатура);
НайденныеСтроки = ТаблицаОстатков.НайтиСтроки(Отбор);
ОстатокКоличество = 0;
ОстатокСумма = 0;
Если НайденныеСтроки.Количество() > 0 Тогда
ОстатокКоличество = НайденныеСтроки[0].КоличествоОстаток;
ОстатокСумма = НайденныеСтроки[0].СуммаОстаток;
КонецЕсли;
// Расчет средневзвешенной цены с защитой от деления на ноль
Если ОстатокКоличество > 0 Тогда
СредневзвешеннаяЦена = ОстатокСумма / ОстатокКоличество;
Иначе
СредневзвешеннаяЦена = 0;
КонецЕсли;
СуммаСписания = ТекСтрока.Количество * СредневзвешеннаяЦена;
ОбщаяСуммаДокумента = ОбщаяСуммаДокумента + СуммаСписания;
// Формирование движения по товарам
Движение = Движения.ОстаткиТоваров.ДобавитьПриход();
Движение.Период = Дата;
Движение.Склад = ТекСтрока.Склад;
Движение.Номенклатура = ТекСтрока.Номенклатура;
Движение.Количество = ТекСтрока.Количество;
Движение.Сумма = СуммаСписания;
КонецЦикла;
Согласно условиям задачи, движение по регистру ВзаиморасчетыСКонтрагентами формируется отдельной общей записью вне цикла по товарам. Для этого используется итоговая сумма, рассчитанная при обходе строк или взятая из итогов документа.
Выясним, как правильно записать такое движение:
// Формирование единого движения по взаиморасчетам
ДвижениеВзаиморасчеты = Движения.ВзаиморасчетыСКонтрагентами.ДобавитьПриход();
ДвижениеВзаиморасчеты.Период = Дата;
ДвижениеВзаиморасчеты.Контрагент = Контрагент;
ДвижениеВзаиморасчеты.Сумма = ОбщаяСуммаДокумента;
Движения.ВзаиморасчетыСКонтрагентами.Записать = Истина;
Применив описанную методику, мы получаем корректное распределение сумм по средневзвешенной стоимости, высокую скорость проведения за счет единого запроса к базе данных и защиту от деления на ноль при отсутствии остатков.