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