Как правильно пересчитывать и обновлять остатки в табличной части управляемой формы 1С?

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

При разработке документов в 1С часто возникает задача: отображать в табличной части актуальный свободный остаток номенклатуры с учетом уже введенных в документ строк. Если одна и та же позиция указана в таблице несколько раз, изменение количества или выбор номенклатуры в одной строке должны сразу обновлять остаток во всех связанных строках. Разберем типичные ошибки реализации этой задачи, причины зависания интерфейса и способы корректного построения алгоритма.

Анализ проблемы и причин зацикливания формы

Рассмотрим частую ситуацию: при попытке обновить остатки разработчики используют обработчик ПриАктивизацииСтроки в связке с серверными методами РеквизитФормыВЗначение и ЗначениеВРеквизитФормы. В результате форма намертво зависает в бесконечном цикле. Выясним причину такого поведения:

  1. Пересоздание коллекции на клиенте: Вызов ЗначениеВРеквизитФормы полностью перегружает табличную часть из прикладного объекта обратно в реквизит формы. Это сбрасывает фокус таблицы и инициирует событие ПриАктивизацииСтроки.
  2. Бесконечная рекурсия событий: Если внутри события ПриАктивизацииСтроки снова вызывается серверный пересчет с последующим ЗначениеВРеквизитФормы, платформа входит в непрерывный цикл вызовов.
  3. Нецелевое назначение событий: Событие ПриАктивизацииСтроки предназначено исключительно для визуальной реакции интерфейса, а не для тяжелых серверных запросов и изменения данных таблицы при каждом клике или перемещении стрелками.
  4. Избыточная конвертация данных: Для обхода и изменения строк табличной части управляемой формы нет необходимости переводить ДанныеФормыКоллекция в прикладную ТаблицаЗначений. Коллекция данных формы сама по себе поддерживает метод НайтиСтроки() и прямой обход через цикл Для Каждого.

Способ 1. Прямой пересчет остатков в коллекции формы без зацикливания

Разберем по шагам правильную организацию пересчета. Перенесем расчет остатков исключительно в события изменения данных: при выборе номенклатуры (ТоварыНоменклатураПриИзменении) и при изменении количества (ТоварыКоличествоПриИзменении).

Посмотрим на пример кода серверной процедуры, которая получает остаток по регистру и обновляет все строки с выбранной позицией напрямую через коллекцию формы Объект.Товары:


&НаСервере
Процедура ПересчитатьОстаткиПоНоменклатуреНаСервере(ВыбНоменклатура)
    
    Если Не ЗначениеЗаполнено(ВыбНоменклатура) Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем фактический остаток на складе из регистра
    ОстатокНаСкладе = МТ_ОперативныйУчетВСкладах.МТ_ПолучитьОстатокНоменклатуры(
        ВыбНоменклатура, 
        Объект.СкладОтправитель, 
        Объект.Организация, 
        Объект.Ссылка
    );
    
    // Выполняем отбор строк непосредственно в данных формы
    ПараметрыОтбора = Новый Структура("Номенклатура", ВыбНоменклатура);
    НайденныеСтроки = Объект.Товары.НайтиСтроки(ПараметрыОтбора);
    
    // Считаем суммарное количество по выбранной номенклатуре в документе
    ВсегоВДокументе = 0;
    Для Каждого СтрТЧ Из НайденныеСтроки Цикл
        ВсегоВДокументе = ВсегоВДокументе + СтрТЧ.Количество;
    КонецЦикла;
    
    // Вычисляем свободный доступный остаток
    СвободныйОстаток = ОстатокНаСкладе - ВсегоВДокументе;
    
    // Заполняем результат во всех найденных строках
    Для Каждого СтрТЧ Из НайденныеСтроки Цикл
        СтрТЧ.Остаток = СвободныйОстаток;
    КонецЦикла;
    
КонецПроцедуры

Теперь подключим вызов этой процедуры в обработчиках событий на клиенте:


&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    
    ТекДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекДанные <> Неопределено Тогда
        ПересчитатьОстаткиПоНоменклатуреНаСервере(ТекДанные.Номенклатура);
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
    
    ТекДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекДанные <> Неопределено Тогда
        ПересчитатьОстаткиПоНоменклатуреНаСервере(ТекДанные.Номенклатура);
    КонецЕсли;
    
КонецПроцедуры

Важное преимущество подхода: поскольку мы не используем ЗначениеВРеквизитФормы, табличное поле на клиенте не сбрасывает фокус и не перерисовывается заново целиком. Изменяются только значения реквизитов конкретных строк коллекции, что предотвращает появление паразитных вызовов ПриАктивизацииСтроки.

Способ 2. Оптимизация интерфейса с помощью информационной панели остатков

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

  1. Размещаем на форме под табличной частью элемент вида Поле надписи (декорация) или реквизит формы строкового типа (например, ТекстИнформацииОбОстатках).
  2. В обработчике ПриАктивизацииСтроки на клиенте считываем номенклатуру текущей выделенной строки.
  3. Для исключения частых серверных вызовов при быстрой навигации стрелками по списку подключаем вызов расчета остатка через фоновое обновление или обработчик ожидания.
  4. На сервере получаем остаток только для одной активной позиции и выводим его в информационное поле в формате: "В наличии: 10 шт. | В документе: 4 шт. | Доступно: 6 шт.".

Посмотрим на пример такой реализации:


&НаКлиенте
Процедура ТоварыПриАктивизацииСтроки(Элемент)
    
    ТекДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекДанные = Неопределено Тогда
        ТекстИнформацииОбОстатках = "";
        Возврат;
    КонецЕсли;
    
    // Обновляем информационную панель для активной номенклатуры
    ОбновитьИнформациюОбОстаткеНаСервере(ТекДанные.Номенклатура);
    
КонецПроцедуры

&НаСервере
Процедура ОбновитьИнформациюОбОстаткеНаСервере(ВыбНоменклатура)
    
    Если Не ЗначениеЗаполнено(ВыбНоменклатура) Тогда
        ТекстИнформацииОбОстатках = "";
        Возврат;
    КонецЕсли;
    
    ОстатокНаСкладе = МТ_ОперативныйУчетВСкладах.МТ_ПолучитьОстатокНоменклатуры(
        ВыбНоменклатура, 
        Объект.СкладОтправитель, 
        Объект.Организация, 
        Объект.Ссылка
    );
    
    ПараметрыОтбора = Новый Структура("Номенклатура", ВыбНоменклатура);
    НайденныеСтроки = Объект.Товары.НайтиСтроки(ПараметрыОтбора);
    
    ВсегоВДокументе = 0;
    Для Каждого СтрТЧ Из НайденныеСтроки Цикл
        ВсегоВДокументе = ВсегоВДокументе + СтрТЧ.Количество;
    КонецЦикла;
    
    Доступно = ОстатокНаСкладе - ВсегоВДокументе;
    
    ТекстИнформацииОбОстатках = СтрШаблон(
        "На складе: %1 | В документе: %2 | Доступно: %3", 
        ОстатокНаСкладе, 
        ВсегоВДокументе, 
        Доступно
    );
    
КонецПроцедуры

Рекомендации по повышению производительности

Проанализируем ключевые правила, которые следует соблюдать при работе с табличными частями на управляемых формах:

  1. Не вызывайте РеквизитФормыВЗначение и ЗначениеВРеквизитФормы без строгой необходимости. Данные методы создают полную копию объекта в памяти и приводят к полному обновлению отображения формы.
  2. Обрабатывайте удаление строк. Если пользователь удалит одну из строк с повторяющейся номенклатурой, остаток в оставшихся строках станет неактуальным. Для обработки этой ситуации вызовите процедуру пересчета остатков в событии ТоварыПослеУдаления.
  3. Избегайте серверных запросов внутри циклов. Если требуется пересчитать остатки по всей табличной части (например, при смене склада в шапке документа), получайте остатки одним общим запросом по массиву всей номенклатуры документа, а затем заполняйте строки за один проход.
← На главную