При разработке отчетов по обеспечению потребностей часто возникает задача сопоставления строк заказа клиента с закрывающими их заказами поставщикам. Типичный сценарий: по заказу клиента требуется 10 штук номенклатуры, а от поставщиков оформлено два заказа — на 5 и 4 штуки. При обычном левом соединении таблиц в запросе строка заказа клиента дублируется целиком (10 и 5, 10 и 4), что искажает аналитику. Требуется получить три четкие строки: 5 шт. (заказано 5), 4 шт. (заказано 4) и 1 шт. (не обеспечено, заказано 0).
Разберем причины возникновения этой проблемы, ограничения языка запросов 1С и эффективные варианты реализации сопоставления.
Проанализируем, почему простое соединение табличных частей документов не дает желаемого результата:
Документ.ЗаказПоставщику.Товары не учитывает признаки отмены строк (Отменено = ИСТИНА), фактическое поступление товаров (документом «Приобретение товаров и услуг») и корректировки заказов. Заказ поставщику со временем перестает быть источником актуального обеспечения.SUM() OVER() или ROW_NUMBER()).Рассмотрим наиболее производительный и гибкий подход. Вместо сложного запроса с эмуляцией нарастающих итогов мы получаем простые плоские выборки по потребностям и обеспечениям, сопоставляем их программно в цикле за один проход ($O(N)$) и передаем готовый результат в СКД через ВнешниеНаборыДанных.
Разберем структуру кода для формирования итоговой таблицы:
// Создаем структуру результирующей таблицы
ТаблицаРезультат = Новый ТаблицаЗначений;
ТаблицаРезультат.Колонки.Добавить("ЗаказКлиента", Новый ОписаниеТипов("ДокументСсылка.ЗаказКлиента"));
ТаблицаРезультат.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаРезультат.Колонки.Добавить("КоличествоПотребность", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТаблицаРезультат.Колонки.Добавить("КоличествоОбеспечено", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТаблицаРезультат.Колонки.Добавить("ЗаказПоставщику", Новый ОписаниеТипов("ДокументСсылка.ЗаказПоставщику"));
// Допустим, мы получили отсортированные таблицы: ТЗ_Потребности и ТЗ_Поставки
// Алгоритм распределения остатка (ФИФО):
Для Каждого СтрПотребность Из ТЗ_Потребности Цикл
ОсталосьРаспределить = СтрПотребность.Количество;
// Находим связанные строки поставок
Отбор = Новый Структура("Номенклатура", СтрПотребность.Номенклатура);
СтрокиПоставок = ТЗ_Поставки.НайтиСтроки(Отбор);
Для Каждого СтрПоставка Из СтрокиПоставок Цикл
Если ОсталосьРаспределить <= 0 Тогда
Прервать;
КонецЕсли;
Если СтрПоставка.КоличествоОстаток <= 0 Тогда
Продолжить;
КонецЕсли;
ВзятьКоличество = Мин(ОсталосьРаспределить, СтрПоставка.КоличествоОстаток);
НоваяСтрока = ТаблицаРезультат.Добавить();
НоваяСтрока.ЗаказКлиента = СтрПотребность.ЗаказКлиента;
НоваяСтрока.Номенклатура = СтрПотребность.Номенклатура;
НоваяСтрока.КоличествоПотребность = ВзятьКоличество;
НоваяСтрока.КоличествоОбеспечено = ВзятьКоличество;
НоваяСтрока.ЗаказПоставщику = СтрПоставка.ЗаказПоставщику;
ОсталосьРаспределить = ОсталосьРаспределить - ВзятьКоличество;
СтрПоставка.КоличествоОстаток = СтрПоставка.КоличествоОстаток - ВзятьКоличество;
КонецЦикла;
// Если остался необеспеченный дефицит - добавляем строку с нулевым обеспечением
Если ОсталосьРаспределить > 0 Тогда
НоваяСтрока = ТаблицаРезультат.Добавить();
НоваяСтрока.ЗаказКлиента = СтрПотребность.ЗаказКлиента;
НоваяСтрока.Номенклатура = СтрПотребность.Номенклатура;
НоваяСтрока.КоличествоПотребность = ОсталосьРаспределить;
НоваяСтрока.КоличествоОбеспечено = 0;
НоваяСтрока.ЗаказПоставщику = Неопределено;
КонецЕсли;
КонецЦикла;
Передадим сформированную таблицу в схему компоновки данных. Для этого в СКД создаем «Набор данных - Объект» с именем ТаблицаРезультат и выводим отчет программно:
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаРезультат", ТаблицаРезультат);
СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Настройки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных);
ТабличныйДокумент = Новый ТабличныйДокумент;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабличныйДокумент);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Если задача решается в типовой конфигурации (1С:ERP, 1С:Комплексная автоматизация или 1С:Управление торговлей), крайне не рекомендуется обращаться напрямую к табличным частям документов. В системе уже присутствуют специализированные регистры накопления и сведений, хранящие актуальное состояние обеспечения:
ОбеспечениеЗаказов / ГрафикДвиженияТоваров: Позволяет отследить плановые даты поступления и распределение запасов по конкретным заказам.РаспределениеЗапасов: В современных версиях конфигураций содержит готовые связки между источниками обеспечения и заказами-потребителями с учетом назначения.РегистрНакопления.ЗаказыКлиентов.Остатки с фильтрацией по измерению Назначение позволяет точно определить актуальный непокрытый остаток без риска посчитать отмененные или закрытые позиции.Изучим типовой отчет «Обеспечение заказов». В нем реализованы механизмы контроля дефицита, на которые можно опираться при проектировании собственной архитектуры сопоставления заказов.