Как автоматически обновить цены в табличной части документа при изменении даты в 1С 8.3

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

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

Анализ проблемы и выбор подхода

Рассмотрим стандартную ситуацию. У нас есть документ ОказаниеУслуг с табличной частью МатериалыУслуги. В табличной части есть колонки Номенклатура, Цена и Сумма. Также существует периодический регистр сведений Цены. Нам нужно, чтобы при смене реквизита Дата программа автоматически нашла актуальные цены на новый момент времени и подставила их в строки.

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

  1. Сложность передачи данных: объекты типа ВыборкаИзРезультатаЗапроса или ТаблицаЗначений не могут быть переданы с сервера на клиент напрямую.
  2. Лишний трафик: передача данных туда-обратно замедляет работу интерфейса, особенно если в табличной части много строк.

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

Реализация пересчета цен на сервере

Разберем самый надежный способ реализации. Мы создадим обработчик события на клиенте, который вызовет серверную процедуру. В этой процедуре мы выполним запрос и обновим данные документа.

Шаг 1. Создание обработчика события на клиенте

В модуле формы для реквизита Дата создадим событие ПриИзменении:


&НаКлиенте
Процедура ДатаПриИзменении(Элемент)
    // Вызываем серверную процедуру для обработки данных
    ОбновитьЦеныНаСервере();
КонецПроцедуры

Шаг 2. Описание серверной логики

Теперь проанализируем код серверной процедуры. Мы будем использовать виртуальную таблицу СрезПоследних, которая идеально подходит для получения актуальных цен на определенную дату.


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

Важные нюансы оптимизации запроса

Проанализируем работу с виртуальной таблицей СрезПоследних. Крайне важно передавать фильтр по номенклатуре именно в параметры виртуальной таблицы (первые скобки после имени таблицы), а не в секцию ГДЕ.

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

Альтернативный метод: использование &НаСервереБезКонтекста

Если в вашем документе сотни или тысячи строк, стандартный вызов &НаСервере может работать медленно, так как при каждом вызове на сервер передается весь контекст формы (все данные объекта). В этом случае рассмотрим более продвинутый метод — использование внеконтекстного вызова.

Разберем логику этого метода по шагам:

  1. На клиенте собираем массив ссылок на номенклатуру.
  2. Передаем этот массив и дату в функцию &НаСервереБезКонтекста.
  3. Сервер возвращает соответствие (Ключ — Номенклатура, Значение — Цена).
  4. На клиенте обходим строки и подставляем цены из полученного соответствия.

Пример реализации:


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

&НаКлиенте
Процедура ДатаПриИзменении(Элемент)
    
    МассивНоменклатуры = Новый Массив;
    Для Каждого Строка Из Объект.МатериалыУслуги Цикл
        МассивНоменклатуры.Добавить(Строка.Номенклатура);
    КонецЦикла;
    
    Цены = ПолучитьЦеныНаДату(МассивНоменклатуры, Объект.Дата);
    
    Для Каждого Строка Из Объект.МатериалыУслуги Цикл
        НоваяЦена = Цены.Получить(Строка.Номенклатура);
        Если НоваяЦена <> Неопределено Тогда
            Строка.Цена = НоваяЦена;
            // Пересчет суммы на клиенте
            Строка.Сумма = Строка.Количество * Строка.Цена;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Дополнительные рекомендации

При реализации данного механизма стоит учитывать еще несколько важных аспектов:

  1. Проверка характеристик: Если в вашей конфигурации используется учет по характеристикам, обязательно добавьте поле Характеристика в запрос и условия поиска. Цена может отличаться для разных характеристик одного и того же товара.
  2. Валюта документа: Если цены в регистре хранятся в валюте, отличной от валюты документа, необходимо добавить код для конвертации цен по курсу на дату документа.
  3. Юзабилити (UX): Автоматическая смена цен при случайном изменении даты может расстроить пользователя, если он уже внес цены вручную. Хорошим тоном считается использование функции ПоказатьВопрос, чтобы уточнить: "Дата документа изменена. Пересчитать цены согласно актуальному прайс-листу?".
  4. Типовые механизмы: В современных конфигурациях (например, УТ 11 или ERP) рекомендуется использовать функции общих модулей (например, ЦенообразованиеВызовСервера.ПолучитьЦеныНоменклатуры), которые уже учитывают все нюансы (валюты, скидки, упаковки).

Мы проанализировали основные способы передачи данных и обновления цен. Использование серверного контекста формы является наиболее простым и эффективным решением для большинства задач, обеспечивая целостность данных и корректность расчетов в табличной части.

← На главную