В существующих, особенно самописных, конфигурациях 1С часто возникает ситуация, когда за годы разработки накапливается множество процедур и функций для получения остатков товаров. Они могут быть написаны разными программистами, дублировать друг друга и учитывать различные, порой неочевидные, бизнес-нюансы. Это приводит к проблемам с производительностью и усложняет поддержку системы. Рассмотрим, как можно решить эту задачу, создав единый, управляемый и быстрый механизм для получения остатков.
Основная цель — заменить зоопарк из 20+ процедур на единую точку входа, которая будет гибко настраиваться под разные нужды (например, кому-то нужны остатки с учетом серий, кому-то без, кому-то — с разложением до комплектующих) и при этом работать максимально быстро за счет одного обращения к базе данных.
Наиболее классический и рекомендуемый подход — это создание общего серверного модуля, в котором будет располагаться одна или несколько экспортных функций. Такая функция инкапсулирует всю сложную логику и предоставляет простой интерфейс для вызова.
Шаг 1. Проектирование параметров функции
Чтобы функция была универсальной, она должна принимать на вход структуру или соответствие с параметрами, которые определяют, какие именно данные и в каких разрезах нужно получить. Проанализируем, какие параметры могут понадобиться:
Шаг 2. Формирование пакетного запроса
Ключевая идея для оптимизации — получить все необходимые данные за одно обращение к базе данных. Для этого идеально подходят пакетные запросы и временные таблицы. Вместо того чтобы вызывать 3-4 разные функции (остатки, резервы, заказы), которые будут выполнять каждая свой запрос, мы соберем все в один пакет.
Рассмотрим примерную структуру такого запроса:
РегистрНакопления.ТоварыНаСкладах) и помещаем их во временную таблицу ВТ_Остатки. На этом этапе мы применяем все основные отборы по номенклатуре, складу и т.д.РегистрНакопления.РезервыТоваров) и помещаем их во временную таблицу ВТ_Резервы.ВТ_ОжидаемыеПоступления, ВТ_ОсобыеСостояния).ЛЕВОЕ СОЕДИНЕНИЕ) все созданные временные таблицы. Здесь мы получаем данные из ВТ_Остатки и вычитаем из них данные из ВТ_Резервы, прибавляем ВТ_ОжидаемыеПоступления и т.д. В результате получаем финальную таблицу со всеми рассчитанными показателями.Такой подход гарантирует, что все данные будут получены за один проход, что значительно сокращает время выполнения по сравнению с несколькими последовательными запросами.
Шаг 3. Динамическая сборка текста запроса
Поскольку требования к детализации могут меняться (например, нужны или не нужны серии), текст запроса нужно формировать динамически. Внутри нашей универсальной функции мы анализируем переданные параметры и "собираем" текст запроса как конструктор.
Посмотрим на условный пример:
Функция ПолучитьОстатки(Параметры) Экспорт
Запрос = Новый Запрос;
ТекстЗапроса = "";
// --- Пакет 1: Фактические остатки ---
ТекстЗапроса = ТекстЗапроса + "
|ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура,
| ТоварыНаСкладахОстатки.Склад,
| СУММА(ТоварыНаСкладахОстатки.КоличествоОстаток) КАК Количество
|{ВЫБРАТЬ
| Номенклатура.*,
| Склад.*,
| Количество}
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, {УСЛОВИЕ}) КАК ТоварыНаСкладахОстатки
|СГРУППИРОВАТЬ ПО
| ТоварыНаСкладахОстатки.Номенклатура,
| ТоварыНаСкладахОстатки.Склад
|;
|";
// --- Пакет 2: Резервы ---
ТекстЗапроса = ТекстЗапроса + "
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| РезервыТоваровОстатки.Номенклатура,
| РезервыТоваровОстатки.Склад,
| СУММА(РезервыТоваровОстатки.КоличествоОстаток) КАК КоличествоРезерв
|ПОМЕСТИТЬ ВТ_Резервы
|ИЗ
| РегистрНакопления.РезервыТоваров.Остатки(&Дата, {УСЛОВИЕ}) КАК РезервыТоваровОстатки
|СГРУППИРОВАТЬ ПО
| РезервыТоваровОстатки.Номенклатура,
| РезервыТоваровОстатки.Склад
|;
|";
// --- Итоговый запрос ---
ТекстЗапроса = ТекстЗапроса + "
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.Склад,
| ЕСТЬNULL(Остатки.Количество, 0) КАК Остаток,
| ЕСТЬNULL(Резервы.КоличествоРезерв, 0) КАК ВРезерве,
| ЕСТЬNULL(Остатки.Количество, 0) - ЕСТЬNULL(Резервы.КоличествоРезерв, 0) КАК СвободныйОстаток
|ИЗ
| ВТ_Остатки КАК Остатки
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Резервы КАК Резервы
| ПО Остатки.Номенклатура = Резервы.Номенклатура
| И Остатки.Склад = Резервы.Склад
|";
// Динамически добавляем условия и разрезы на основе структуры Параметры
// Например, если Параметры.Отбор.Склад Заполнено, то устанавливаем параметр запроса
Запрос.УстановитьПараметр("Дата", Параметры.Дата);
// ...установка других параметров
Запрос.Текст = ТекстЗапроса;
Результат = Запрос.Выполнить().Выгрузить();
Возврат Результат;
КонецФункции
В этом примере мы не учли динамическое добавление полей (например, серий), но принцип тот же: в зависимости от флага в структуре Параметры мы добавляем в текст запроса поле СерияНоменклатуры и соответствующую группировку.
Для высоконагруженных систем, где скорость получения остатков критична (например, в формах подбора, где данные обновляются при каждом действии пользователя), расчет остатков "на лету" может быть слишком медленным. В таких случаях применяют альтернативный архитектурный подход.
Суть метода:
Создается периодический РегистрСведений (независимый), в который записываются уже рассчитанные данные по остаткам. Например, регистр ОперативныеОстатки с измерениями (Номенклатура, Склад, Характеристика) и ресурсами (Остаток, Резерв, СвободныйОстаток).
Обновление данных в этом регистре происходит в момент проведения документов, которые влияют на остатки. В подписке на событие проведения документа (например, РеализацияТоваровУслуг, ПоступлениеТоваровУслуг) мы пересчитываем и записываем актуальные данные в наш регистр-снэпшот.
Плюсы:
Минусы:
Этот подход хорошо зарекомендовал себя в типовых конфигурациях (например, в УТ) и является мощным инструментом оптимизации, но требует более высокой квалификации для внедрения и поддержки.
Независимо от выбранного подхода, придерживайтесь следующих правил:
ГДЕ) и соединениях (ПО) запросов, в базе данных созданы индексы.Создание единой точки получения остатков — это правильный шаг к построению стабильной и производительной системы. Оптимальным решением в большинстве случаев будет комбинация параметризированных функций в общем модуле и грамотное использование пакетных запросов.