При доработке отчетов по продажам на платформе 1С 7.7 разработчики нередко сталкиваются с ситуацией, когда добавление реквизита цены или суммы из приходной накладной в запрос по расходным накладным приводит к тому, что цена поступления в отчете всегда равна нулю. Разберем причины такого поведения встроенного языка запросов 1С 7.7 и рассмотрим корректные подходы к решению этой задачи.
Проанализируем механику работы объекта Запрос в 1С:Предприятии 7.7 при обращении к документам. В отличие от современных реляционных баз данных и платформы 1С 8, во встроенном языке запросов 7.7 отсутствует конструкция соединения таблиц (LEFT JOIN).
Когда в тексте запроса одновременно указываются поля из разных документов, например Документ.РасходнаяНакладная.Колич и Документ.ПриходнаяНакладная.Цена0, механизм запросов платформы выполняет последовательный обход документов по хронологической оси. При этом записи не объединяются по товарным позициям в рамках одной строки: для строк расходных документов поля приходных накладных остаются пустыми (или равными нулю), а для приходных — пустыми остаются поля расхода.
Кроме того, если реквизит табличной части (например, Цена0) выбирается без функции агрегации и без группировки по конкретному документу, в переменную запроса попадает значение только из последней обработанной строки группы. Если последним был документ реализации, значение закупочной цены неизбежно окажется равным нулю.
Также важно учитывать логику складского учета: один и тот же товар может поступать несколько раз за период по разным ценам. Чтобы сопоставить расход с конкретным приходом, необходимо опираться на механизм партионного учета, регистры или бухгалтерские проводки списания себестоимости.
Если конфигурация построена на компоненте «Оперативный учет» (например, типовая «Торговля и Склад 7.7» или аналогичная самописная система), себестоимость и партии рассчитываются в момент проведения расходного документа и фиксируются в регистрах. Рассмотрим, как составить запрос к регистру продаж для одновременного получения выручки и себестоимости списания:
Процедура Сформировать()
Перем Запрос, Таб;
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Период с ДатаНач по ДатаКон;
|Товар = Регистр.Продажи.Номенклатура;
|Количество = Регистр.Продажи.Количество;
|СуммаПрод = Регистр.Продажи.ПродСтоимость;
|Себестоимость = Регистр.Продажи.Себестоимость;
|Группировка Товар упорядочить по Товар.Наименование;
|Функция ИтКол = Расход(Количество);
|Функция ИтСум = Расход(СуммаПрод);
|Функция ИтСеб = Расход(Себестоимость);
|";
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
Таб = СоздатьОбъект("Таблица");
Таб.ВывестиСекцию("Шапка");
Пока Запрос.Группировка("Товар") = 1 Цикл
Если Запрос.ИтКол <> 0 Тогда
СрЦенаПродажи = Запрос.ИтСум / Запрос.ИтКол;
СрЦенаПрихода = Запрос.ИтСеб / Запрос.ИтКол;
Таб.ВывестиСекцию("Строка");
КонецЕсли;
КонецЦикла;
Таб.ВывестиСекцию("Подвал");
Таб.Показать("Отчет по продажам");
КонецПроцедуры
В данном случае средняя закупочная цена проданного товара рассчитывается как отношение общей себестоимости списания к количеству проданных единиц (Запрос.ИтСеб / Запрос.ИтКол).
Если регистры учета себестоимости отсутствуют или требуется сопоставить данные продаж с усредненными ценами поступления за период, применим раздельный сбор данных в две таблицы значений с последующим их объединением.
Разберем пошаговый алгоритм реализации:
РасходнаяНакладная и выгружаем результат в таблицу значений ТЗ_Продажи.ПриходнаяНакладная (или регистра остатков) и выгружаем в таблицу ТЗ_Приход.НайтиЗначение() подтягиваем цену поступления для каждой номенклатурной позиции.
Процедура СформироватьЧерезТЗ()
Перем ЗапросПрод, ЗапросПрих, ТЗ_Прод, ТЗ_Прих, Таб;
// 1. Запрос по продажам
ЗапросПрод = СоздатьОбъект("Запрос");
ТекстПрод = "
|Период с ДатаНач по ДатаКон;
|Товар = Документ.РасходнаяНакладная.Товар;
|КолПрод = Документ.РасходнаяНакладная.Колич;
|СумПрод = Документ.РасходнаяНакладная.Сумма;
|Фирма = Документ.РасходнаяНакладная.Фирма;
|Группировка Товар упорядочить по Товар.Наименование;
|Условие (Фирма = ВыбФ);
|Функция ИтКолПрод = Сумма(КолПрод);
|Функция ИтСумПрод = Сумма(СумПрод);
|";
Если ЗапросПрод.Выполнить(ТекстПрод) = 0 Тогда
Возврат;
КонецЕсли;
ТЗ_Прод = СоздатьОбъект("ТаблицаЗначений");
ЗапросПрод.Выгрузить(ТЗ_Прод);
// 2. Запрос по поступлениям
ЗапросПрих = СоздатьОбъект("Запрос");
ТекстПрих = "
|Период с ДатаНач по ДатаКон;
|Товар = Документ.ПриходнаяНакладная.Товар;
|КолПрих = Документ.ПриходнаяНакладная.Колич;
|СумПрих = Документ.ПриходнаяНакладная.Сумма;
|Фирма = Документ.ПриходнаяНакладная.Фирма;
|Группировка Товар;
|Условие (Фирма = ВыбФ);
|Функция ИтКолПрих = Сумма(КолПрих);
|Функция ИтСумПрих = Сумма(СумПрих);
|";
Если ЗапросПрих.Выполнить(ТекстПрих) = 0 Тогда
Возврат;
КонецЕсли;
ТЗ_Прих = СоздатьОбъект("ТаблицаЗначений");
ЗапросПрих.Выгрузить(ТЗ_Прих);
// 3. Вывод данных в отчет
Таб = СоздатьОбъект("Таблица");
Таб.ВывестиСекцию("Отчет");
ТЗ_Прод.ВыбратьСтроки();
Пока ТЗ_Прод.ПолучитьСтроку() = 1 Цикл
Товар = ТЗ_Прод.Товар;
КолПрод = ТЗ_Прод.ИтКолПрод;
СумПрод = ТЗ_Прод.ИтСумПрод;
СрЦенаПрод = ?(КолПрод <> 0, СумПрод / КолПрод, 0);
// Поиск поступления товара
СрЦенаПрих = 0;
НомСтр = 0;
Если ТЗ_Прих.НайтиЗначение(Товар, НомСтр, "Товар") = 1 Тогда
ТЗ_Прих.ПолучитьСтрокуПоНомеру(НомСтр);
Если ТЗ_Прих.ИтКолПрих <> 0 Тогда
СрЦенаПрих = ТЗ_Прих.ИтСумПрих / ТЗ_Прих.ИтКолПрих;
КонецЕсли;
КонецЕсли;
Таб.ВывестиСекцию("Строка");
КонецЦикла;
Таб.ВывестиСекцию("Подвал");
Таб.Опции(0, 0, 0, 0);
Таб.Показать("", "");
КонецПроцедуры
Если в базе ведется справочник цен или закупочная цена зафиксирована в карточке номенклатуры как периодический реквизит, получать ее напрямую из приходных документов не требуется. Достаточно прочитать актуальное значение на дату формирования отчета:
Номенклатура:
ЦенаПрихода = Запрос.Товар.ЦенаПрихода.Получить(ДатаКон);
Справочник.Цены):
СпрЦены = СоздатьОбъект("Справочник.Цены");
СпрЦены.ИспользоватьВладельца(Запрос.Товар);
Если СпрЦены.НайтиПоРеквизиту("КатегорияЦены", Константа.ЗакупочнаяКатегорияЦены, 0) = 1 Тогда
ЦенаПрихода = СпрЦены.Цена.Получить(ДатаКон);
Иначе
ЦенаПрихода = 0;
КонецЕсли;
Важный нюанс: при выборе любого из способов убедитесь, что в коде предусмотрена проверка деления на ноль перед расчетом средних цен (Если Количество <> 0 Тогда).