Как правильно получить остатки товара при изменении номенклатуры в управляемой форме 1С: выбор между &НаСервере и &НаСервереБезКонтекста

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При переходе на управляемое приложение разработчики часто сталкиваются с трудностями понимания клиент-серверного взаимодействия. Типичная задача — вывод актуальных остатков товара в табличную часть документа при выборе или изменении номенклатуры. Для её решения необходимо четко понимать, где должен выполняться код, какие данные передавать между клиентом и сервером и как оптимизировать этот процесс для обеспечения максимального быстродействия системы.

В этой статье мы подробно рассмотрим, как правильно организовать клиент-серверное взаимодействие, какую директиву выполнения выбрать, как спроектировать оптимальный алгоритм работы программы и как написать быстрый запрос к базе данных без потери производительности.

Понимание директив выполнения: &НаКлиенте и &НаСервере

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

Для выполнения любых операций с базой данных нам требуется обращаться к серверу. Для этого в модуле управляемой формы используются специальные директивы компиляции:

Для решения нашей задачи — получения остатков при изменении номенклатуры — мы обязаны использовать связку: на клиенте перехватывать событие изменения строки, а на сервере выполнять запрос к регистру остатков.

Контекстный (&НаСервере) и внеконтекстный (&НаСервереБезКонтекста) вызовы: в чем разница?

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

1. Контекстный вызов (директива &НаСервере)

При использовании этой директивы серверной процедуре доступен весь контекст формы, включая реквизит Объект (содержимое шапки документа, все строки табличной части) и параметры формы. Вы можете напрямую написать в коде Объект.Склад или Объект.Товары. Однако за это удобство приходится платить производительностью. При каждом таком вызове платформа автоматически выполняет следующие действия:

  1. Сериализует (упаковывает в XML или JSON) абсолютно все данные формы на клиенте.
  2. Передает этот объемный пакет данных по сети на сервер.
  3. Распаковывает данные на сервере, воссоздавая состояние формы.
  4. Выполняет ваш код.
  5. Снова упаковывает измененную форму и отправляет ее обратно клиенту.

Если в табличной части вашего документа содержатся сотни строк, контекстный вызов приведет к передаче огромных объемов избыточных данных по сети, что вызовет заметные задержки («зависания» интерфейса) у конечного пользователя.

2. Внеконтекстный вызов (директива &НаСервереБезКонтекста)

При использовании этой директивы серверная процедура или функция полностью изолирована от контекста формы. Ей недоступен реквизит Объект. Все необходимые для расчета данные вы должны явно передать в качестве параметров при вызове функции. Платформа отправляет на сервер только эти параметры и возвращает обратно только результат вычислений. Это работает в разы быстрее, экономит сетевой трафик и серверные ресурсы.

Вывод: для получения остатка конкретного товара по складу и дате идеальным решением является использование внеконтекстного вызова &НаСервереБезКонтекста.

Правильная архитектурная схема работы

Поскольку при использовании внеконтекстного вызова у сервера нет доступа к элементам формы, мы должны построить алгоритм взаимодействия следующим образом:

  1. На клиенте срабатывает стандартный обработчик события ПриИзменении для поля номенклатуры в строке табличной части.
  2. В коде обработчика мы получаем текущую строку табличной части, считываем из нее выбранную Номенклатуру, а из шапки документа — Склад и Дату.
  3. Передаем эти три значения в качестве параметров во внеконтекстную функцию сервера.
  4. На сервере функция принимает параметры, выполняет запрос к виртуальной таблице остатков регистра и возвращает полученное число (остаток).
  5. На клиенте мы принимаем возвращенное сервером число и записываем его в соответствующую колонку («Остаток») текущей строки.

Рассмотрим детальный пример реализации этого алгоритма:


&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    
    // Получаем строку табличной части, в которой произошло изменение
    СтрокаТЧ = Элементы.Товары.ТекущиеДанные;
    
    Если СтрокаТЧ <> Неопределено Тогда
        
        // Проверяем заполненность ключевых реквизитов
        Если ЗначениеЗаполнено(СтрокаТЧ.Номенклатура) Тогда
            
            // Вызываем внеконтекстную функцию сервера, передавая параметры
            ОстатокТовара = ПолучитьОстатокТовараНаСервере(
                СтрокаТЧ.Номенклатура, 
                Объект.Склад, 
                Объект.Дата
            );
            
            // Заполняем полученное значение в строку на клиенте
            СтрокаТЧ.Остаток = ОстатокТовара;
            
        Иначе
            СтрокаТЧ.Остаток = 0;
        КонецЕсли;
        
    КонецЕсли;
    
EndProcedure

&НаСервереБезКонтекста
Функция ПолучитьОстатокТовараНаСервере(Знач Номенклатура, Знач Склад, Знач Дата)
    
    // Вызываем логику получения остатка из базы данных
    Возврат ПолучитьОстатокПоБазе(Номенклатура, Склад, Дата);
    
КонецФункции

Куда поместить код: Модуль формы или Общий модуль?

Проанализируем архитектурную сторону вопроса. В процессе разработки у вас может возникнуть вопрос: где именно должна располагаться функция получения остатка — непосредственно в модуле формы документа или в общем модуле системы?

Стандарты разработки фирмы «1С» рекомендуют руководствоваться правилом повторного использования кода:

Правильным решением будет вынесение всей логики расчета остатков в серверный Общий модуль (например, с именем ЗапасыСервер или РаботаСТоварамиСервер). У этого модуля в свойствах должен быть установлен флаг «Сервер» и снят флаг «Клиент». Сама функция внутри общего модуля должна быть объявлена с ключевым словом Экспорт. В этом случае вы сможете легко обращаться к ней из любой формы вашей конфигурации.

Оптимизация запроса к остаткам в базе данных

Вызов сервера без контекста — это лишь половина успеха. Если сам запрос к базе данных написан неоптимально, система будет работать медленно. Разберем основные правила построения запросов для расчета остатков:

  1. Работайте только с виртуальными таблицами. Никогда не делайте выборку из реальной таблицы движений регистра накопления для последующего ручного суммирования приходов и расходов. Используйте только виртуальную таблицу Остатки (например, РегистрНакопления.ТоварыНаСкладах.Остатки).
  2. Всегда передавайте отборы в параметры виртуальной таблицы. Это самая частая ошибка новичков. Если наложить условия на номенклатуру и склад в секции ГДЕ (WHERE) после обращения к таблице, СУБД сначала прочитает остатки по абсолютно всем товарам на всех складах компании, и только потом отсечет лишнее. Это приводит к колоссальной нагрузке на сервер. Условия нужно передавать строго внутрь параметров виртуальной таблицы.

Проанализируем правильный пример реализации серверной функции с запросом:


Функция ПолучитьОстатокПоБазе(Номенклатура, Склад, Дата) Экспорт
    
    // Если склад или номенклатура не заполнены, нет смысла делать запрос
    Если Не ЗначениеЗаполнено(Номенклатура) Или Не ЗначениеЗаполнено(Склад) Тогда
        Возврат 0;
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТоварыНаСкладахОстатки.В НаличииОстаток КАК Остаток
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура = &Номенклатура И Склад = &Склад) КАК ТоварыНаСкладахОстатки";
    
    // Заполняем параметры запроса
    Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
    Запрос.УстановитьПараметр("Склад", Склад);
    
    // Для получения оперативных (текущих) остатков передаем Неопределено в качестве периода, 
    // либо передаем конкретную дату документа
    Запрос.УстановитьПараметр("Период", ?(ЗначениеЗаполнено(Дата), Дата, Неопределено));
    
    РезультатЗапроса = Запрос.Выполнить();
    
    Если Не РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Выборка.Следующий();
        Возврат Выборка.Остаток;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Специфика работы с проведенными документами: объект Граница

Выясним причину еще одной неочевидной проблемы. Если пользователь редактирует уже существующий и ранее проведенный документ, его движения уже записаны в регистр накопления. Если мы просто передадим в параметры виртуальной таблицы остатков дату этого документа (Объект.Дата), то запрос вернет остаток товара, в который уже включены движения текущего документа.

Чтобы получить «чистый» остаток товара, который был на складе непосредственно до проведения текущего документа, необходимо использовать специальный объект платформы — Граница, указав вид границы «Исключая».

Посмотрим, как модифицировать код установки параметра периода в запросе:


// Корректная обработка даты для проведенного документа
Если ЗначениеЗаполнено(Дата) Тогда
    // Использование Границы исключает влияние движений самого документа на этой секунде
    ГраницаОстатков = Новый Граница(Дата, ВидГраницы.Исключая);
    Запрос.УстановитьПараметр("Период", ГраницаОстатков);
Иначе
    Запрос.УстановитьПараметр("Период", Неопределено);
КонецЕсли;

Использование объекта Граница гарантирует получение актуальных и достоверных данных об остатках при интерактивной корректировке табличных частей документов пользователями.

← На главную