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