При построении отчетов или алгоритмов автоматического списания в конфигурациях на платформе 1С:Предприятие 7.7 часто возникает необходимость узнать, из каких именно документов поступления (партий) сформирован свободный остаток номенклатуры на определенную дату. Разберем, как правильно реализовать этот алгоритм с помощью встроенного языка запросов 1С 7.7.
Рассмотрим стандартную техническую задачу: на заданную дату требуется получить не просто суммарный (сводный) остаток по позиции, а детализированный список документов прихода, которые образуют данный остаток. При этом в учётной политике предприятия используется метод оценки себестоимости FIFO.
Главная трудность заключается в том, что в типовых конфигурациях 1С 7.7 (например, в Комплексной или Торговле и Складе) регистр резервов ТМЦ не хранит партионный разрез. Резерв формируется на уровне номенклатуры в целом. Встроенный язык запросов 1С 7.7 не поддерживает вложенные подзапросы и конструкции связывания JOIN между разными регистрами. Поэтому задачу приходится решать в два этапа: сначала запрашивать данные по регистрам, а затем распределять количества в коде обработки.
Проанализируем оптимальное решение задачи. Первым запросом сформируем таблицу зарезервированного товара по регистру РезервыТМЦ. Вторым запросом выгрузим партии из регистра ПартииНаличие и вычтем зарезервированное количество из первых попавшихся (или наибоее свежих) партий поступления.
Рассмотрим пример кода на встроенном языке 1С 7.7:
// Этап 1. Получаем остатки резервов в разрезе номенклатуры
ТекстЗапросаРезервы =
" |Номенклатура = Регистр.РезервыТМЦ.Номенклатура;
|Количество = Регистр.РезервыТМЦ.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка Номенклатура без групп;";
ЗапросРезервы = СоздатьОбъект("Запрос");
Если ЗапросРезервы.Выполнить(ТекстЗапросаРезервы) <> 1 Тогда
Возврат;
КонецЕсли;
ТаблицаРезервов = СоздатьОбъект("ТаблицаЗначений");
ЗапросРезервы.Выгрузить(ТаблицаРезервов, 0, 0);
// Этап 2. Получаем остатки по партиям
ТекстЗапросаПартии =
" |Номенклатура = Регистр.ПартииНаличие.Номенклатура;
|ПриходныйДокумент = Регистр.ПартииНаличие.Партия.ПриходныйДокумент;
|Количество = Регистр.ПартииНаличие.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка Номенклатура без групп;
|Группировка ПриходныйДокумент;";
ЗапросПартии = СоздатьОбъект("Запрос");
Если ЗапросПартии.Выполнить(ТекстЗапросаПартии) <> 1 Тогда
Возврат;
КонецЕсли;
// Этап 3. Обход результатов и распределение резерва
Пока ЗапросПартии.Группировка(1) = 1 Цикл
Строчка = 0;
Если ТаблицаРезервов.НайтиЗначение(ЗапросПартии.Номенклатура, Строчка, "Номенклатура") = 1 Тогда
КоличествоРезерва = ТаблицаРезервов.ПолучитьЗначение(Строчка, "КоличествоКонОст");
Иначе
КоличествоРезерва = 0;
КонецЕсли;
Пока ЗапросПартии.Группировка(2) = 1 Цикл
Если ЗапросПартии.КоличествоКонОст > 0 Тогда
СвободныйОстатокПоДокументу = ЗапросПартии.КоличествоКонОст - Макс(КоличествоРезерва, 0);
КоличествоРезерва = Макс(КоличествоРезерва - ЗапросПартии.КоличествоКонОст, 0);
Если СвободныйОстатокПоДокументу > 0 Тогда
// Здесь выполняем необходимые действия с позитивным свободным остатком
// Например, выгружаем в Таблицу Значений или выводим в печатную форму
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Выясним, как управлять сортировкой результатов группировок при их выгрузке. В методе Группировка() объекта Запрос предусмотрен второй необязательный параметр, определяющий направление выборки:
Посмотрим на пример применения обратной сортировки для анализа свежих партий:
Пока Запрос.Группировка(1) = 1 Цикл
// Обходим партии в обратном порядке (от самых свежих к старым)
Пока Запрос.Группировка(2, -1) = 1 Цикл
ДокументПрихода = Запрос.ПриходныйДокумент;
ОстатокПартии = Запрос.КоличествоКонОст;
КонецЦикла;
КонецЦикла;
Проанализируем ситуации, которые могут привести к получению неверных результатов в реальных базах данных 1С 7.7:
1С++ или ODBC к таблицам RG* и RA*.