Сложный запрос к УТ 11.5 с помощью ИИ: остатки, резервы, себестоимость и продажи одним пакетом

Задача, которую в Управлении торговлей не решить одним отчетом: по каждому товару на каждом складе показать, сколько лежит, сколько уже обещано клиентам, сколько свободно, во что обходится остаток и как товар продавался за период. Четыре регистра, аналитика учета ключами, виртуальные таблицы и соединения. Разбираем, как агент через Жёлтый мост дошел от первой ошибки до пакета запросов, который выполняется за две с половиной секунды, и что в этом пакете важно, включая ловушку, в которую легко попасть: наличие на складе и количество в учете себестоимости в УТ не обязаны совпадать.

УТ 11.5язык запросоввременные таблицырегистры накопленияChatGPTClaude

Задача

Руководитель склада спрашивает: "Что у нас лежит без движения и сколько в этом денег?". Чтобы ответить, нужны сразу четыре источника. Физический остаток лежит в регистре ТоварыНаСкладах. Резервы под заказы клиентов в регистре ТоварыКОтгрузке. Стоимость остатка в регистре СебестоимостьТоваров. Продажи за период в регистре ВыручкаИСебестоимостьПродаж. Свободный остаток нигде не хранится, это разность первых двух.

Сложность не в объеме кода, а в устройстве конфигурации. Регистры себестоимости и продаж не хранят номенклатуру и склад напрямую: у них одно измерение АналитикаУчетаНоменклатуры, ссылка на служебный справочник ключей аналитики, в котором уже лежат Номенклатура, Характеристика, МестоХранения и еще десяток реквизитов. Кто пишет запросы к УТ 10.3 или Бухгалтерии, об этом не знает, и первые попытки заканчиваются ошибками.

Как шел агент

  1. Первая попытка по памяти. Модель пишет запрос к регистру СвободныеОстатки, как в старых редакциях УТ 11. Платформа отвечает через мост: "Таблица не найдена РегистрНакопления.СвободныеОстатки.Остатки", с позицией в тексте. Модель идет в справку по структуре базы и находит, что в 11.5 наличие и резервы разнесены по двум регистрам.
  2. Вторая ошибка, полезнее первой. Запрос к остаткам себестоимости с полем Себестоимость.Номенклатура падает: "Поле не найдено". В справке по регистру измерения Номенклатура нет, есть АналитикаУчетаНоменклатуры. Модель читает состав справочника ключей аналитики и понимает, что номенклатура и склад достаются оттуда.
  3. Первая рабочая версия. Пять временных таблиц, полное соединение остатков и резервов, разыменование аналитики через точку. На демо-базе пакет отработал за 4,6 секунды и вернул 342 строки. Ответ есть, но проверка синтаксиса выдала два замечания и девять предупреждений: соединения с виртуальными таблицами, разыменование ссылочных полей внутри запроса, полное внешнее соединение.
  4. Доводка по замечаниям. Виртуальные таблицы себестоимости и продаж сначала кладутся во временные таблицы и только потом соединяются со справочником ключей. Полное соединение заменено объединением ключей остатков и резервов с последующими левыми соединениями. Наименования подтягиваются соединением со справочниками, а не через точку. Тот же результат, 2,5 секунды, одно информационное сообщение анализатора, и оно оставлено сознательно.

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

Пакет запросов

Восемь запросов в одном пакете. Первые два собирают наличие и резервы по паре товар плюс склад. Третий и четвертый берут остатки себестоимости и раскрывают аналитику через справочник ключей. Пятый и шестой делают то же с продажами за период. Седьмой собирает список пар товар плюс склад из остатков и резервов. Восьмой сводит все левыми соединениями и считает свободный остаток и себестоимость единицы. В итоге одиннадцать колонок: наличие, резерв, свободно, количество и стоимость по учету себестоимости без НДС, себестоимость единицы без НДС, списано без расчета себестоимости, продано и выручка без НДС за период.

ВЫБРАТЬ
	Остатки.Номенклатура КАК Номенклатура,
	Остатки.Склад КАК Склад,
	СУММА(Остатки.ВНаличииОстаток) КАК ВНаличии
ПОМЕСТИТЬ втОстатки
ИЗ
	РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Остатки
СГРУППИРОВАТЬ ПО
	Остатки.Номенклатура,
	Остатки.Склад
ИНДЕКСИРОВАТЬ ПО
	Номенклатура,
	Склад
;
ВЫБРАТЬ
	Резервы.Номенклатура КАК Номенклатура,
	Резервы.Склад КАК Склад,
	СУММА(Резервы.ВРезервеОстаток) КАК ВРезерве
ПОМЕСТИТЬ втРезервы
ИЗ
	РегистрНакопления.ТоварыКОтгрузке.Остатки(&Дата) КАК Резервы
СГРУППИРОВАТЬ ПО
	Резервы.Номенклатура,
	Резервы.Склад
ИНДЕКСИРОВАТЬ ПО
	Номенклатура,
	Склад
;
// Партия выбирается намеренно, хотя дальше не нужна: виртуальная таблица
// остатков сворачивает измерения, которых нет в списке полей, и списания без
// партии (отрицательные остатки) схлопнулись бы с приходами еще до проверки знака.
ВЫБРАТЬ
	Себестоимость.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
	Себестоимость.Партия КАК Партия,
	Себестоимость.КоличествоОстаток КАК Количество,
	Себестоимость.СтоимостьБезНДСОстаток КАК Стоимость,
	ВЫБОР
		КОГДА Себестоимость.КоличествоОстаток < 0
			ТОГДА -Себестоимость.КоличествоОстаток
		ИНАЧЕ 0
	КОНЕЦ КАК СписаноБезРасчета
ПОМЕСТИТЬ втСебестоимостьПоАналитике
ИЗ
	РегистрНакопления.СебестоимостьТоваров.Остатки(
			&Дата,
			РазделУчета = ЗНАЧЕНИЕ(Перечисление.РазделыУчетаСебестоимостиТоваров.ТоварыНаСкладах)) КАК Себестоимость
;
ВЫБРАТЬ
	Аналитика.Номенклатура КАК Номенклатура,
	Аналитика.МестоХранения КАК Склад,
	СУММА(Себестоимость.Количество) КАК Количество,
	СУММА(Себестоимость.Стоимость) КАК Стоимость,
	СУММА(Себестоимость.СписаноБезРасчета) КАК СписаноБезРасчета
ПОМЕСТИТЬ втСебестоимость
ИЗ
	втСебестоимостьПоАналитике КАК Себестоимость
		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.КлючиАналитикиУчетаНоменклатуры КАК Аналитика
		ПО Себестоимость.АналитикаУчетаНоменклатуры = Аналитика.Ссылка
СГРУППИРОВАТЬ ПО
	Аналитика.Номенклатура,
	Аналитика.МестоХранения
ИНДЕКСИРОВАТЬ ПО
	Номенклатура,
	Склад
;
ВЫБРАТЬ
	Продажи.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
	Продажи.Склад КАК Склад,
	Продажи.КоличествоОборот КАК Продано,
	Продажи.СуммаВыручкиБезНДСОборот КАК Выручка
ПОМЕСТИТЬ втПродажиПоАналитике
ИЗ
	РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&Начало, &Конец) КАК Продажи
;
ВЫБРАТЬ
	Аналитика.Номенклатура КАК Номенклатура,
	Продажи.Склад КАК Склад,
	СУММА(Продажи.Продано) КАК Продано,
	СУММА(Продажи.Выручка) КАК Выручка
ПОМЕСТИТЬ втПродажи
ИЗ
	втПродажиПоАналитике КАК Продажи
		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.КлючиАналитикиУчетаНоменклатуры КАК Аналитика
		ПО Продажи.АналитикаУчетаНоменклатуры = Аналитика.Ссылка
СГРУППИРОВАТЬ ПО
	Аналитика.Номенклатура,
	Продажи.Склад
ИНДЕКСИРОВАТЬ ПО
	Номенклатура,
	Склад
;
// ОБЪЕДИНИТЬ без ВСЕ намеренно: одна и та же пара товар + склад бывает
// и в остатках, и в резервах, а нужна она один раз.
ВЫБРАТЬ
	Остатки.Номенклатура КАК Номенклатура,
	Остатки.Склад КАК Склад
ПОМЕСТИТЬ втКлючи
ИЗ
	втОстатки КАК Остатки

ОБЪЕДИНИТЬ

ВЫБРАТЬ
	Резервы.Номенклатура,
	Резервы.Склад
ИЗ
	втРезервы КАК Резервы
;
ВЫБРАТЬ
	СправочникНоменклатура.Наименование КАК Номенклатура,
	СправочникСклады.Наименование КАК Склад,
	ЕСТЬNULL(Остатки.ВНаличии, 0) КАК ВНаличии,
	ЕСТЬNULL(Резервы.ВРезерве, 0) КАК ВРезерве,
	ЕСТЬNULL(Остатки.ВНаличии, 0) - ЕСТЬNULL(Резервы.ВРезерве, 0) КАК Свободно,
	ЕСТЬNULL(Себестоимость.Количество, 0) КАК КоличествоПоУчетуСебестоимости,
	ЕСТЬNULL(Себестоимость.Стоимость, 0) КАК СтоимостьБезНДСПоУчету,
	ВЫБОР
		КОГДА ЕСТЬNULL(Себестоимость.Количество, 0) > 0
			ТОГДА Себестоимость.Стоимость / Себестоимость.Количество
		ИНАЧЕ 0
	КОНЕЦ КАК СебестоимостьЕдиницыБезНДС,
	ЕСТЬNULL(Себестоимость.СписаноБезРасчета, 0) КАК СписаноБезРасчетаСебестоимости,
	ЕСТЬNULL(Продажи.Продано, 0) КАК ПроданоЗаПериод,
	ЕСТЬNULL(Продажи.Выручка, 0) КАК ВыручкаБезНДСЗаПериод
ИЗ
	втКлючи КАК Ключи
		ЛЕВОЕ СОЕДИНЕНИЕ втОстатки КАК Остатки
		ПО Ключи.Номенклатура = Остатки.Номенклатура
			И Ключи.Склад = Остатки.Склад
		ЛЕВОЕ СОЕДИНЕНИЕ втРезервы КАК Резервы
		ПО Ключи.Номенклатура = Резервы.Номенклатура
			И Ключи.Склад = Резервы.Склад
		ЛЕВОЕ СОЕДИНЕНИЕ втСебестоимость КАК Себестоимость
		ПО Ключи.Номенклатура = Себестоимость.Номенклатура
			И Ключи.Склад = Себестоимость.Склад
		ЛЕВОЕ СОЕДИНЕНИЕ втПродажи КАК Продажи
		ПО Ключи.Номенклатура = Продажи.Номенклатура
			И Ключи.Склад = Продажи.Склад
		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
		ПО Ключи.Номенклатура = СправочникНоменклатура.Ссылка
		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Склады КАК СправочникСклады
		ПО Ключи.Склад = СправочникСклады.Ссылка
УПОРЯДОЧИТЬ ПО
	СтоимостьБезНДСПоУчету УБЫВ,
	Номенклатура,
	Склад
;
УНИЧТОЖИТЬ втОстатки
;
УНИЧТОЖИТЬ втРезервы
;
УНИЧТОЖИТЬ втСебестоимостьПоАналитике
;
УНИЧТОЖИТЬ втСебестоимость
;
УНИЧТОЖИТЬ втПродажиПоАналитике
;
УНИЧТОЖИТЬ втПродажи
;
УНИЧТОЖИТЬ втКлючи

На что обратить внимание в этом тексте.

  • Раздел учета в параметрах виртуальной таблицы. Регистр СебестоимостьТоваров хранит не только товары на складах, но и товары в пути, у комиссионеров, материалы в переработке. Без отбора по разделу учета стоимость остатка завысится.
  • Индексы временных таблиц по полям соединения. На демо-базе разница не видна, на базе с десятками тысяч позиций она решает, займет запрос секунды или минуты.
  • Наличие и количество по учету себестоимости это разные цифры. Складской учет ведется в регистре ТоварыНаСкладах, финансовый учет себестоимости в регистре СебестоимостьТоваров, и сходятся они только после расчета себестоимости и при проведении всех движений по обоим контурам. На демо-базе расхождения заметные: по одному складу в наличии 475 штук, а в учете себестоимости 512, по другому 545 против 65. Поэтому количество по учету себестоимости выведено отдельной колонкой, а себестоимость единицы считается от него, а не от наличия. Первая версия запроса этого не показывала, и стоимость остатка читалась как стоимость того, что лежит на складе. Это было бы неверно.
  • Себестоимость единицы верна только после расчета себестоимости. Пока расчет за период не выполнен, списания лежат в регистре без партии: количество ушло, а стоимость нет. На демо-базе по одному складу таких списаний 121 штука, и себестоимость единицы получалась вдвое выше реальной. Поэтому в пакете есть колонка "списано без расчета себестоимости": если она не ноль, себестоимости в этой строке верить нельзя, сначала закрытие месяца.
  • Виртуальная таблица остатков сворачивает невыбранные измерения. Первая версия колонки "списано без расчета" всегда давала ноль: в запросе к остаткам себестоимости не была выбрана партия, и платформа сложила приходы по партиям с списаниями без партии еще до проверки знака. Стоило добавить партию в список полей, и по тому же товару появились 121 штука списаний. Это свойство любой виртуальной таблицы Остатки: она группирует по тем измерениям, которые вы попросили, и никакими другими.
  • Стоимость и выручка без НДС. В регистрах есть оба варианта. Сравнивать себестоимость с выручкой можно только в одинаковой базе, иначе маржа врет на ставку налога.
  • Свободный остаток может быть отрицательным. На демо-базе есть одна позиция, где резерв больше наличия. Это не ошибка запроса, а сигнал: клиенту обещали то, чего на складе нет. Такие строки стоит показывать первыми.
  • ОБЪЕДИНИТЬ без ВСЕ оставлено намеренно. Пара товар плюс склад часто есть и в остатках, и в резервах, а в списке ключей она нужна один раз. Комментарий в тексте запроса об этом говорит, чтобы следующий читатель не "исправил" его на ОБЪЕДИНИТЬ ВСЕ.

Функция для обработки

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

// Остатки товаров по складам на дату с резервами, свободным остатком,
// себестоимостью остатка и продажами за период.
//
// Четыре регистра сведены одним пакетом запросов:
//  - ТоварыНаСкладах: что физически лежит на складе (ВНаличии);
//  - ТоварыКОтгрузке: что уже обещано клиентам (ВРезерве);
//  - СебестоимостьТоваров: стоимость остатка по разделу учета "Товары на складах";
//  - ВыручкаИСебестоимостьПродаж: сколько продано и на какую сумму за период.
// Ключ соединения везде пара Номенклатура + Склад. Характеристики и серии
// свернуты: если они нужны, добавьте их в группировки всех временных таблиц.
// Складской учет (ТоварыНаСкладах) и финансовый учет себестоимости
// (СебестоимостьТоваров) это разные контуры: количество в них расходится,
// пока не выполнен расчет себестоимости или движения проведены не по всем
// регистрам. Поэтому количество по учету себестоимости выводится отдельной
// колонкой, а себестоимость единицы считается от него, а не от наличия.
// Стоимость и выручка берутся без НДС, чтобы их можно было сравнивать между собой.
// Колонка СписаноБезРасчетаСебестоимости показывает количество, списанное без
// партии (отрицательные остатки по партиям): пока оно не ноль, расчет
// себестоимости за период не выполнен и себестоимость единицы завышена.
// Аналитика учета в регистрах себестоимости и продаж хранится ключом
// (справочник КлючиАналитикиУчетаНоменклатуры). Виртуальные таблицы этих
// регистров сначала кладутся во временные таблицы и только потом соединяются
// со справочником ключей: соединение напрямую с виртуальной таблицей платформа
// выполняет плохо. Строки итога собираются от объединения ключей остатков и
// резервов, полное соединение не используется.
//
// Параметры:
//  Дата   - Дата - момент остатков, берется конец этого дня;
//  Начало - Дата - начало периода продаж;
//  Конец  - Дата - конец периода продаж, берется конец дня.
//
// Возвращаемое значение:
//  ТаблицаЗначений - Номенклатура, Склад, ВНаличии, ВРезерве, Свободно,
//                    КоличествоПоУчетуСебестоимости, СтоимостьБезНДСПоУчету,
//                    СебестоимостьЕдиницыБезНДС, СписаноБезРасчетаСебестоимости,
//                    ПроданоЗаПериод, ВыручкаБезНДСЗаПериод.
&НаСервереБезКонтекста
Функция ОстаткиСРезервамиИПродажами(Знач Дата, Знач Начало, Знач Конец)

	Запрос = Новый Запрос;
	Запрос.Текст =
		"ВЫБРАТЬ
		|	Остатки.Номенклатура КАК Номенклатура,
		|	Остатки.Склад КАК Склад,
		|	СУММА(Остатки.ВНаличииОстаток) КАК ВНаличии
		|ПОМЕСТИТЬ втОстатки
		|ИЗ
		|	РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Остатки
		|СГРУППИРОВАТЬ ПО
		|	Остатки.Номенклатура,
		|	Остатки.Склад
		|ИНДЕКСИРОВАТЬ ПО
		|	Номенклатура,
		|	Склад
		|;
		|ВЫБРАТЬ
		|	Резервы.Номенклатура КАК Номенклатура,
		|	Резервы.Склад КАК Склад,
		|	СУММА(Резервы.ВРезервеОстаток) КАК ВРезерве
		|ПОМЕСТИТЬ втРезервы
		|ИЗ
		|	РегистрНакопления.ТоварыКОтгрузке.Остатки(&Дата) КАК Резервы
		|СГРУППИРОВАТЬ ПО
		|	Резервы.Номенклатура,
		|	Резервы.Склад
		|ИНДЕКСИРОВАТЬ ПО
		|	Номенклатура,
		|	Склад
		|;
		|// Партия выбирается намеренно, хотя дальше не нужна: виртуальная таблица
		|// остатков сворачивает измерения, которых нет в списке полей, и списания без
		|// партии (отрицательные остатки) схлопнулись бы с приходами еще до проверки знака.
		|ВЫБРАТЬ
		|	Себестоимость.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
		|	Себестоимость.Партия КАК Партия,
		|	Себестоимость.КоличествоОстаток КАК Количество,
		|	Себестоимость.СтоимостьБезНДСОстаток КАК Стоимость,
		|	ВЫБОР
		|		КОГДА Себестоимость.КоличествоОстаток < 0
		|			ТОГДА -Себестоимость.КоличествоОстаток
		|		ИНАЧЕ 0
		|	КОНЕЦ КАК СписаноБезРасчета
		|ПОМЕСТИТЬ втСебестоимостьПоАналитике
		|ИЗ
		|	РегистрНакопления.СебестоимостьТоваров.Остатки(
		|			&Дата,
		|			РазделУчета = ЗНАЧЕНИЕ(Перечисление.РазделыУчетаСебестоимостиТоваров.ТоварыНаСкладах)) КАК Себестоимость
		|;
		|ВЫБРАТЬ
		|	Аналитика.Номенклатура КАК Номенклатура,
		|	Аналитика.МестоХранения КАК Склад,
		|	СУММА(Себестоимость.Количество) КАК Количество,
		|	СУММА(Себестоимость.Стоимость) КАК Стоимость,
		|	СУММА(Себестоимость.СписаноБезРасчета) КАК СписаноБезРасчета
		|ПОМЕСТИТЬ втСебестоимость
		|ИЗ
		|	втСебестоимостьПоАналитике КАК Себестоимость
		|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.КлючиАналитикиУчетаНоменклатуры КАК Аналитика
		|		ПО Себестоимость.АналитикаУчетаНоменклатуры = Аналитика.Ссылка
		|СГРУППИРОВАТЬ ПО
		|	Аналитика.Номенклатура,
		|	Аналитика.МестоХранения
		|ИНДЕКСИРОВАТЬ ПО
		|	Номенклатура,
		|	Склад
		|;
		|ВЫБРАТЬ
		|	Продажи.АналитикаУчетаНоменклатуры КАК АналитикаУчетаНоменклатуры,
		|	Продажи.Склад КАК Склад,
		|	Продажи.КоличествоОборот КАК Продано,
		|	Продажи.СуммаВыручкиБезНДСОборот КАК Выручка
		|ПОМЕСТИТЬ втПродажиПоАналитике
		|ИЗ
		|	РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&Начало, &Конец) КАК Продажи
		|;
		|ВЫБРАТЬ
		|	Аналитика.Номенклатура КАК Номенклатура,
		|	Продажи.Склад КАК Склад,
		|	СУММА(Продажи.Продано) КАК Продано,
		|	СУММА(Продажи.Выручка) КАК Выручка
		|ПОМЕСТИТЬ втПродажи
		|ИЗ
		|	втПродажиПоАналитике КАК Продажи
		|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.КлючиАналитикиУчетаНоменклатуры КАК Аналитика
		|		ПО Продажи.АналитикаУчетаНоменклатуры = Аналитика.Ссылка
		|СГРУППИРОВАТЬ ПО
		|	Аналитика.Номенклатура,
		|	Продажи.Склад
		|ИНДЕКСИРОВАТЬ ПО
		|	Номенклатура,
		|	Склад
		|;
		|// ОБЪЕДИНИТЬ без ВСЕ намеренно: одна и та же пара товар + склад бывает
		|// и в остатках, и в резервах, а нужна она один раз.
		|ВЫБРАТЬ
		|	Остатки.Номенклатура КАК Номенклатура,
		|	Остатки.Склад КАК Склад
		|ПОМЕСТИТЬ втКлючи
		|ИЗ
		|	втОстатки КАК Остатки
		|
		|ОБЪЕДИНИТЬ
		|
		|ВЫБРАТЬ
		|	Резервы.Номенклатура,
		|	Резервы.Склад
		|ИЗ
		|	втРезервы КАК Резервы
		|;
		|ВЫБРАТЬ
		|	СправочникНоменклатура.Наименование КАК Номенклатура,
		|	СправочникСклады.Наименование КАК Склад,
		|	ЕСТЬNULL(Остатки.ВНаличии, 0) КАК ВНаличии,
		|	ЕСТЬNULL(Резервы.ВРезерве, 0) КАК ВРезерве,
		|	ЕСТЬNULL(Остатки.ВНаличии, 0) - ЕСТЬNULL(Резервы.ВРезерве, 0) КАК Свободно,
		|	ЕСТЬNULL(Себестоимость.Количество, 0) КАК КоличествоПоУчетуСебестоимости,
		|	ЕСТЬNULL(Себестоимость.Стоимость, 0) КАК СтоимостьБезНДСПоУчету,
		|	ВЫБОР
		|		КОГДА ЕСТЬNULL(Себестоимость.Количество, 0) > 0
		|			ТОГДА Себестоимость.Стоимость / Себестоимость.Количество
		|		ИНАЧЕ 0
		|	КОНЕЦ КАК СебестоимостьЕдиницыБезНДС,
		|	ЕСТЬNULL(Себестоимость.СписаноБезРасчета, 0) КАК СписаноБезРасчетаСебестоимости,
		|	ЕСТЬNULL(Продажи.Продано, 0) КАК ПроданоЗаПериод,
		|	ЕСТЬNULL(Продажи.Выручка, 0) КАК ВыручкаБезНДСЗаПериод
		|ИЗ
		|	втКлючи КАК Ключи
		|		ЛЕВОЕ СОЕДИНЕНИЕ втОстатки КАК Остатки
		|		ПО Ключи.Номенклатура = Остатки.Номенклатура
		|			И Ключи.Склад = Остатки.Склад
		|		ЛЕВОЕ СОЕДИНЕНИЕ втРезервы КАК Резервы
		|		ПО Ключи.Номенклатура = Резервы.Номенклатура
		|			И Ключи.Склад = Резервы.Склад
		|		ЛЕВОЕ СОЕДИНЕНИЕ втСебестоимость КАК Себестоимость
		|		ПО Ключи.Номенклатура = Себестоимость.Номенклатура
		|			И Ключи.Склад = Себестоимость.Склад
		|		ЛЕВОЕ СОЕДИНЕНИЕ втПродажи КАК Продажи
		|		ПО Ключи.Номенклатура = Продажи.Номенклатура
		|			И Ключи.Склад = Продажи.Склад
		|		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
		|		ПО Ключи.Номенклатура = СправочникНоменклатура.Ссылка
		|		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Склады КАК СправочникСклады
		|		ПО Ключи.Склад = СправочникСклады.Ссылка
		|УПОРЯДОЧИТЬ ПО
		|	СтоимостьБезНДСПоУчету УБЫВ,
		|	Номенклатура,
		|	Склад
		|;
		|УНИЧТОЖИТЬ втОстатки
		|;
		|УНИЧТОЖИТЬ втРезервы
		|;
		|УНИЧТОЖИТЬ втСебестоимостьПоАналитике
		|;
		|УНИЧТОЖИТЬ втСебестоимость
		|;
		|УНИЧТОЖИТЬ втПродажиПоАналитике
		|;
		|УНИЧТОЖИТЬ втПродажи
		|;
		|УНИЧТОЖИТЬ втКлючи";
	// Остатки на конец дня: иначе движения за сам день в остаток не попадут.
	Запрос.УстановитьПараметр("Дата", КонецДня(Дата));
	Запрос.УстановитьПараметр("Начало", НачалоДня(Начало));
	Запрос.УстановитьПараметр("Конец", КонецДня(Конец));

	// Нужен результат восьмого запроса пакета (индекс 7): временные таблицы
	// и УНИЧТОЖИТЬ строк не возвращают.
	Результаты = Запрос.ВыполнитьПакет();
	Возврат Результаты[7].Выгрузить();

КонецФункции

Комментарии в шапке объясняют не что делает код, а почему он сделан так: откуда берется аналитика, зачем виртуальные таблицы сначала кладутся во временные, почему нет полного соединения. Это тот минимум, который экономит час следующему разработчику.

Как приспособить под себя

  • Нужны характеристики или серии: добавьте их в поля и группировки первых шести запросов и в ключи объединения. Ключ соединения станет длиннее, логика не изменится.
  • Нужен один склад: добавьте параметр в условия виртуальных таблиц остатков и резервов и в отбор продаж по складу. Не фильтруйте итоговую таблицу, это дороже.
  • Нужны дни запаса: разделите свободный остаток на среднюю дневную продажу за период. Деление на ноль закройте конструкцией ВЫБОР, как сделано с себестоимостью единицы.
  • В Комплексной автоматизации 2 и ERP 2 регистры называются так же. Код не проверялся на них в рамках этой статьи, поэтому перед использованием прогоните его на своей базе.

Как устроен цикл "запрос, ошибка платформы, справка по структуре, исправление" и почему он делает ИИ полезным для разработчика, в статье Разработка кода 1С с ИИ через Жёлтый мост. Подключить свою базу и повторить: кабинет, первые дни бесплатно.