Как сопоставить строки заказов клиентов и заказов поставщикам с выделением дефицита в 1С?

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

При разработке отчетов по обеспечению потребностей часто возникает задача сопоставления строк заказа клиента с закрывающими их заказами поставщикам. Типичный сценарий: по заказу клиента требуется 10 штук номенклатуры, а от поставщиков оформлено два заказа — на 5 и 4 штуки. При обычном левом соединении таблиц в запросе строка заказа клиента дублируется целиком (10 и 5, 10 и 4), что искажает аналитику. Требуется получить три четкие строки: 5 шт. (заказано 5), 4 шт. (заказано 4) и 1 шт. (не обеспечено, заказано 0).

Разберем причины возникновения этой проблемы, ограничения языка запросов 1С и эффективные варианты реализации сопоставления.

Анализ проблемы и ограничения прямых запросов

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

  1. Прямое обращение к табличным частям: Обращение напрямую к табличной части Документ.ЗаказПоставщику.Товары не учитывает признаки отмены строк (Отменено = ИСТИНА), фактическое поступление товаров (документом «Приобретение товаров и услуг») и корректировки заказов. Заказ поставщику со временем перестает быть источником актуального обеспечения.
  2. Отсутствие оконных функций: В языке запросов 1С отсутствуют стандартные оконные функции для расчета нарастающих итогов (такие как SUM() OVER() или ROW_NUMBER()).
  3. Квадратичная сложность при соединении по неравенству: Попытка реализовать алгоритм ФИФО (FIFO) в запросе через самосоединение таблиц по неравенству (для накопления сумм) имеет сложность $O(N^2)$. Это приводит к созданию огромных временных таблиц в СУБД и критическому падению производительности при больших объемах данных.

Способ 1. Программное распределение через алгоритм двух указателей и СКД

Рассмотрим наиболее производительный и гибкий подход. Вместо сложного запроса с эмуляцией нарастающих итогов мы получаем простые плоские выборки по потребностям и обеспечениям, сопоставляем их программно в цикле за один проход ($O(N)$) и передаем готовый результат в СКД через ВнешниеНаборыДанных.

Разберем структуру кода для формирования итоговой таблицы:


// Создаем структуру результирующей таблицы
ТаблицаРезультат = Новый ТаблицаЗначений;
ТаблицаРезультат.Колонки.Добавить("ЗаказКлиента", Новый ОписаниеТипов("ДокументСсылка.ЗаказКлиента"));
ТаблицаРезультат.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаРезультат.Колонки.Добавить("КоличествоПотребность", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТаблицаРезультат.Колонки.Добавить("КоличествоОбеспечено", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТаблицаРезультат.Колонки.Добавить("ЗаказПоставщику", Новый ОписаниеТипов("ДокументСсылка.ЗаказПоставщику"));

// Допустим, мы получили отсортированные таблицы: ТЗ_Потребности и ТЗ_Поставки
// Алгоритм распределения остатка (ФИФО):
Для Каждого СтрПотребность Из ТЗ_Потребности Цикл
    ОсталосьРаспределить = СтрПотребность.Количество;
    
    // Находим связанные строки поставок
    Отбор = Новый Структура("Номенклатура", СтрПотребность.Номенклатура);
    СтрокиПоставок = ТЗ_Поставки.НайтиСтроки(Отбор);
    
    Для Каждого СтрПоставка Из СтрокиПоставок Цикл
        Если ОсталосьРаспределить <= 0 Тогда
            Прервать;
        КонецЕсли;
        
        Если СтрПоставка.КоличествоОстаток <= 0 Тогда
            Продолжить;
        КонецЕсли;
        
        ВзятьКоличество = Мин(ОсталосьРаспределить, СтрПоставка.КоличествоОстаток);
        
        НоваяСтрока = ТаблицаРезультат.Добавить();
        НоваяСтрока.ЗаказКлиента = СтрПотребность.ЗаказКлиента;
        НоваяСтрока.Номенклатура = СтрПотребность.Номенклатура;
        НоваяСтрока.КоличествоПотребность = ВзятьКоличество;
        НоваяСтрока.КоличествоОбеспечено = ВзятьКоличество;
        НоваяСтрока.ЗаказПоставщику = СтрПоставка.ЗаказПоставщику;
        
        ОсталосьРаспределить = ОсталосьРаспределить - ВзятьКоличество;
        СтрПоставка.КоличествоОстаток = СтрПоставка.КоличествоОстаток - ВзятьКоличество;
    КонецЦикла;
    
    // Если остался необеспеченный дефицит - добавляем строку с нулевым обеспечением
    Если ОсталосьРаспределить > 0 Тогда
        НоваяСтрока = ТаблицаРезультат.Добавить();
        НоваяСтрока.ЗаказКлиента = СтрПотребность.ЗаказКлиента;
        НоваяСтрока.Номенклатура = СтрПотребность.Номенклатура;
        НоваяСтрока.КоличествоПотребность = ОсталосьРаспределить;
        НоваяСтрока.КоличествоОбеспечено = 0;
        НоваяСтрока.ЗаказПоставщику = Неопределено;
    КонецЕсли;
КонецЦикла;

Передадим сформированную таблицу в схему компоновки данных. Для этого в СКД создаем «Набор данных - Объект» с именем ТаблицаРезультат и выводим отчет программно:


ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаРезультат", ТаблицаРезультат);

СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Настройки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;

КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки);

ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных);

ТабличныйДокумент = Новый ТабличныйДокумент;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабличныйДокумент);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);

Способ 2. Использование типовых регистров подсистемы обеспечения

Если задача решается в типовой конфигурации (1С:ERP, 1С:Комплексная автоматизация или 1С:Управление торговлей), крайне не рекомендуется обращаться напрямую к табличным частям документов. В системе уже присутствуют специализированные регистры накопления и сведений, хранящие актуальное состояние обеспечения:

  1. Регистр ОбеспечениеЗаказов / ГрафикДвиженияТоваров: Позволяет отследить плановые даты поступления и распределение запасов по конкретным заказам.
  2. Регистр сведений РаспределениеЗапасов: В современных версиях конфигураций содержит готовые связки между источниками обеспечения и заказами-потребителями с учетом назначения.
  3. Виртуальные таблицы остатков: Запрос к таблице РегистрНакопления.ЗаказыКлиентов.Остатки с фильтрацией по измерению Назначение позволяет точно определить актуальный непокрытый остаток без риска посчитать отмененные или закрытые позиции.

Изучим типовой отчет «Обеспечение заказов». В нем реализованы механизмы контроля дефицита, на которые можно опираться при проектировании собственной архитектуры сопоставления заказов.

← На главную