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

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

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

Принцип расчета себестоимости при проведении документа

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

СредневзвешеннаяЦена = ОстатокСумма / ОстатокКоличество

Для реализации этого алгоритма необходимо придерживаться следующих ключевых правил:

  1. Запрос перед циклом: Запрос к виртуальной таблице остатков должен выполняться один раз до начала цикла обработки строк табличной части. Это существенно повышает производительность системы.
  2. Использование момента времени: В качестве параметра даты для виртуальной таблицы остатков передается МоментВремени() проводимого документа, а не простая дата. Это исключает искажения при учете документов в пределах одной секунды.
  3. Очистка движений: Перед выполнением запроса следует предварительно очистить и записать движения текущего документа по регистру, чтобы собственные старые записи не влияли на расчет остатков при перепроведении.
  4. Защита от деления на ноль: В алгоритме расчета обязательно проверяется значение остатка количества. Если ОстатокКоличество равно нулю, деление выполнять нельзя.

Составление запроса для получения остатков товаров

Проанализируем структуру запроса, который выбирает актуальные остатки суммы и количества по номенклатуре и складам, присутствующим в проводимом документе. Отбор по номенклатуре строится на основании табличной части документа.

Рассмотрим пример запроса к виртуальной таблице остатков:


ВЫБРАТЬ
	ТоварыНаСкладахОстатки.Склад КАК Склад,
	ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
	ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток,
	ТоварыНаСкладахОстатки.СуммаОстаток КАК СуммаОстаток
ИЗ
	РегистрНакопления.ТоварыНаСкладах.Остатки(
		&МоментВремени,
		Номенклатура В
			(ВЫБРАТЬ РАЗЛИЧНЫЕ
				ТЧТовары.Номенклатура КАК Номенклатура
			ИЗ
				Документ.РеализацияТоваровУслуг.Товары КАК ТЧТовары
			ГДЕ
				ТЧТовары.Ссылка = &Ссылка)
	) КАК ТоварыНаСкладахОстатки

В данном запросе параметр &Ссылка передает текущий документ, а &МоментВремени обеспечивает получение остатков строго на момент проведения. Результат запроса выгружается в таблицу значений или обрабатывается с помощью объекта ВыборкаИзРезультатаЗапроса.

Алгоритм формирования движений и защита от ошибок

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

Посмотрим на пример кода обработки проведения в 1С:


// Очищаем существующие движения документа
Движения.ОстаткиТоваров.Записать = Истина;
Движения.ОстаткиТоваров.Очистить();

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

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

ТаблицаОстатков = Запрос.Выполнить().Выгрузить();

ОбщаяСуммаДокумента = 0;

Для Каждого ТекСтрока Из Товары Цикл
	
	// Поиск остатка по складу и номенклатуре текущей строки
	Отбор = Новый Структура("Склад, Номенклатура", ТекСтрока.Склад, ТекСтрока.Номенклатура);
	НайденныеСтроки = ТаблицаОстатков.НайтиСтроки(Отбор);
	
	ОстатокКоличество = 0;
	ОстатокСумма = 0;
	
	Если НайденныеСтроки.Количество() > 0 Тогда
		ОстатокКоличество = НайденныеСтроки[0].КоличествоОстаток;
		ОстатокСумма = НайденныеСтроки[0].СуммаОстаток;
	КонецЕсли;
	
	// Расчет средневзвешенной цены с защитой от деления на ноль
	Если ОстатокКоличество > 0 Тогда
		СредневзвешеннаяЦена = ОстатокСумма / ОстатокКоличество;
	Иначе
		СредневзвешеннаяЦена = 0;
	КонецЕсли;
	
	СуммаСписания = ТекСтрока.Количество * СредневзвешеннаяЦена;
	ОбщаяСуммаДокумента = ОбщаяСуммаДокумента + СуммаСписания;
	
	// Формирование движения по товарам
	Движение = Движения.ОстаткиТоваров.ДобавитьПриход();
	Движение.Период = Дата;
	Движение.Склад = ТекСтрока.Склад;
	Движение.Номенклатура = ТекСтрока.Номенклатура;
	Движение.Количество = ТекСтрока.Количество;
	Движение.Сумма = СуммаСписания;

КонецЦикла;

Формирование движений по взаиморасчетам

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

Выясним, как правильно записать такое движение:


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

Движения.ВзаиморасчетыСКонтрагентами.Записать = Истина;

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

← На главную