Как в запросе 1С выбрать поступления, формирующие текущий остаток по FIFO?

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

При построении аналитических отчетов и вычислении себестоимости часто возникает задача определения конкретных партий (документов прихода), которые формируют текущий количественный остаток номенклатуры на складе по принципу FIFO (First In, First Out). Рассмотрим логику решения этой задачи, особенности реализации в языке запросов 1С и альтернативные архитектурные варианты.

Анализ задачи и логика распределения остатка

Проанализируем ситуацию на простом примере движений по складу:

  1. Поступление 1: +10 шт.
  2. Поступление 2: +8 шт.
  3. Расход 3: -6 шт.
  4. Поступление 4: +2 шт.
  5. Расход 5: -10 шт.

Итоговый остаток на складе равен 4 шт. Согласно принципу FIFO, в первую очередь списываются самые старые партии. Посмотрим, как распределился расход объемом 16 шт. (6 + 10):

  1. Первое поступление (+10) полностью перекрыто расходами и в текущий остаток не входит.
  2. Второе поступление (+8) частично списано (расходовано 6 шт.). Из него на складе осталось 2 шт.
  3. Третье поступление (Поступление 4 на +2 шт.) расходом не затрагивалось и полностью входит в остаток — 2 шт.

Таким образом, текущий остаток 4 шт. формируется поступлением 4 (2 шт.) и частью поступления 2 (2 шт.). Выясним, как получить этот результат с помощью языка запросов 1С.

Способ 1. Расчет обратного нарастающего итога в запросе

Поскольку в языке запросов 1С отсутствуют аналитические оконные функции (такие как SUM(...) OVER(...)), нарастающие итоги вычисляются через связывание таблицы партий самой с собой по условию неравенства дат или моментов времени.

Разберем алгоритм по шагам:

  1. Рассчитываем текущий общий остаток номенклатуры по виртуальной таблице РегистрНакопления.ОстаткиНоменклатуры.Остатки().
  2. Формируем временную таблицу всех документов прихода с указанием их количества и момента времени.
  3. Соединяем временную таблицу приходов саму с собой, чтобы для каждого прихода получить сумму всех более поздних поступлений (обратный нарастающий итог).
  4. Отбираем те поступления, у которых обратный нарастающий итог строго меньше общего остатка.
  5. Вычисляем остаток по каждой партии. Для последней частично попавшей партии остаток рассчитывается как разница между общим остатком и суммой более поздних приходов.

Рассмотрим пример построения такого запроса:


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

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

ВЫБРАТЬ
	П1.Номенклатура КАК Номенклатура,
	П1.ДокументПоступления КАК ДокументПоступления,
	П1.КоличествоПриход КАК КоличествоПриход,
	ЕСТЬНУЛЛ(СУММА(П2.КоличествоПриход), 0) КАК НарастающийИтогПозже
ПОМЕСТИТЬ ВТ_ПриходыСИтогами
ИЗ
	ВТ_Приходы КАК П1
		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Приходы КАК П2
		ПО П1.Номенклатура = П2.Номенклатура
			И П1.Период < П2.Период
ГРУППИРОВАТЬ ПО
	П1.Номенклатура,
	П1.ДокументПоступления,
	П1.КоличествоПриход,
	П1.Период
;

ВЫБРАТЬ
	Т1.Номенклатура КАК Номенклатура,
	Т1.ДокументПоступления КАК ДокументПоступления,
	ВЫБОР
		КОГДА Т1.НарастающийИтогПозже + Т1.КоличествоПриход <= Т2.ВсегоОстаток
			ТОГДА Т1.КоличествоПриход
		ИНАЧЕ Т2.ВсегоОстаток - Т1.НарастающийИтогПозже
	КОНЕЦ КАК ОстатокПоПартии
ИЗ
	ВТ_ПриходыСИтогами КАК Т1
		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ОбщийОстаток КАК Т2
		ПО Т1.Номенклатура = Т2.Номенклатура
ГДЕ
	Т1.НарастающийИтогПозже < Т2.ВсегоОстаток

Ограничения чистого запроса и технические нюансы

При использовании данного подхода в реальных системах следует учитывать несколько важных факторов:

  1. Производительность: Соединение таблицы приходов «сама на себя» имеет квадратичную сложность O(N²). На больших объемах документов этот запрос может вызывать высокую нагрузку на СУБД.
  2. Коллизия времени документов: Если несколько документов поступления проведены одной и той же секундой, сортировка по полю Период не даст однозначного порядка. В условиях соединения необходимо дополнительно сравнивать МоментВремени или уникальную ссылку документа.

Альтернативные подходы к решению

1. Партионный учет в регистре накопления

Самый производительный и методически верный способ в архитектуре 1С — хранение аналитики партий непосредственно в регистре накопления остатков. Для этого в измерение регистра добавляется ссылка на документ поступления (Партия). При проведении документов расхода списки партий списываются алгоритмически по FIFO, а остаток по партиям получается простым запросом к виртуальной таблице Остатки() без использования сложной математики в запросе.

2. Комбинированный расчет (Запрос + Обработка в коде)

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


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

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

← На главную