При разработке в среде 1С:Предприятие 8.3 начинающие программисты часто сталкиваются с проблемой, когда не удается программно записать значение в реквизит табличной части (ТЧ) документа или справочника. Типичный сценарий: при изменении количества товара в строке необходимо автоматически пересчитать общую стоимость (произведение цены на количество). Однако попытки сделать это «напрямую» вызывают ошибки выполнения или компиляции кода.
В рамках этой статьи мы подробно разберем, почему возникают такие ошибки, проанализируем внутреннее устройство управляемых форм и рассмотрим правильные методы работы с данными табличных частей.
Рассмотрим типичные ошибки, которые совершают разработчики при попытке решить эту задачу:
Элементы.ТЧ1Стоимость.РедактированиеТекста = ... ошибочен. Свойство Элементы отвечает исключительно за визуальное отображение на форме. Поле ввода на форме — это лишь средство визуализации, оно не хранит сами данные. Прямая запись в подобные свойства элементов интерфейса либо заблокирована платформой, либо не приводит к изменению реальных данных объекта.Объект.ТЧ1.Стоимость = ... вызывает ошибку, так как ТЧ1 — это коллекция строк (объект типа ДанныеФормыКоллекция). У самой коллекции нет и не может быть реквизита Стоимость. Этот реквизит принадлежит каждой конкретной строке таблицы отдельно.Объект (и его табличные части) прекрасно доступны для изменения на стороне клиента. Лишние контекстные вызовы сервера лишь замедляют работу интерфейса.Для успешного решения задачи нам необходимо четко разделять понятия данных (коллекции строк) и элементов отображения (интерфейса), а также научиться работать с конкретной строкой, которую в данный момент редактирует пользователь.
Самый простой, быстрый и правильный способ рассчитать значение в строке при изменении пользователем связанных полей (например, количества или цены) — это использование клиентского обработчика события ПриИзменении. При этом обращаться к редактируемой строке нужно через свойство ТекущиеДанные элемента таблицы формы.
Проанализируем ситуацию: когда пользователь редактирует строку таблицы на форме, у элемента таблицы (в нашем примере это Элементы.ТЧ1) свойство ТекущиеДанные содержит объект ДанныеФормыЭлемент. Этот объект представляет собой структуру, содержащую реквизиты именно той строки, в которой сейчас находится курсор.
Рассмотрим пошаговый алгоритм реализации автоматического пересчета стоимости на клиенте:
Шаг 1. Откройте управляемую форму документа в конфигураторе. Найдите элемент формы, соответствующий колонке количества вашей табличной части (например, ТЧ1Количество).
Шаг 2. В панели свойств этого элемента найдите событие ПриИзменении и нажмите на лупу для создания обработчика на клиенте.
Шаг 3. Напишите код расчета стоимости, как показано в примере ниже:
&НаКлиенте
Процедура ТЧ1КоличествоПриИзменении(Элемент)
// Получаем строку, в которой пользователь изменил значение
СтрокаТЧ = Элементы.ТЧ1.ТекущиеДанные;
// Обязательно проверяем, что строка действительно существует и выбрана
Если СтрокаТЧ <> Неопределено Тогда
// Производим расчет и записываем значение в реквизит строки
СтрокаТЧ.Стоимость = СтрокаТЧ.Цена * СтрокаТЧ.Количество;
КонецЕсли;
КонецПроцедуры
Аналогичный обработчик следует назначить и для события ПриИзменении колонки Цена, чтобы при корректировке цены сумма также моментально пересчитывалась:
&НаКлиенте
Процедура ТЧ1ЦенаПриИзменении(Элемент)
СтрокаТЧ = Элементы.ТЧ1.ТекущиеДанные;
Если СтрокаТЧ <> Неопределено Тогда
СтрокаТЧ.Стоимость = СтрокаТЧ.Цена * СтрокаТЧ.Количество;
КонецЕсли;
КонецПроцедуры
Использование этого метода гарантирует мгновенный расчет без задержек, так как вся математическая операция происходит на компьютере пользователя (клиенте) без отправки запросов на сервер базы данных.
Часто задача усложняется: при добавлении новой строки и выборе товара (номенклатуры) нам необходимо сначала получить актуальную цену этого товара из базы данных (например, из регистра сведений), подставить её в строку, а затем рассчитать стоимость.
Для получения данных из базы данных нам потребуется серверный вызов. Однако, следуя лучшим практикам разработки в 1С, этот вызов необходимо делать максимально легким. Для этого мы применим директиву &НаСервереБезКонтекста. Это позволит не передавать всю форму со всеми ее данными на сервер, что существенно сбережет сетевой трафик и ускорит работу программы.
Разберем по шагам реализацию этой схемы:
Шаг 1. Создадим обработчик события ПриИзменении для поля выбора номенклатуры в табличной части.
Шаг 2. Напишем вызов неконтекстной функции сервера для получения цены.
Рассмотрим пример кода в модуле управляемой формы:
&НаКлиенте
Процедура ТЧ1НоменклатураПриИзменении(Элемент)
СтрокаТЧ = Элементы.ТЧ1.ТекущиеДанные;
Если СтрокаТЧ <> Неопределено Тогда
// Проверяем, заполнена ли номенклатура
Если ЗначениеЗаполнено(СтрокаТЧ.Номенклатура) Тогда
// Вызываем серверную функцию для получения цены
// Передаем только нужные параметры: ссылку на товар и дату документа
СтрокаТЧ.Цена = ПолучитьЦенуНоменклатурыНаСервере(СтрокаТЧ.Номенклатура, Объект.Дата);
// Сразу же рассчитываем стоимость на клиенте
СтрокаТЧ.Стоимость = СтрокаТЧ.Цена * СтрокаТЧ.Количество;
Иначе
// Если номенклатуру очистили, обнуляем связанные поля
СтрокаТЧ.Цена = 0;
СтрокаТЧ.Стоимость = 0;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьЦенуНоменклатурыНаСервере(ВыбраннаяНоменклатура, ДатаДокумента)
// В реальной конфигурации здесь будет запрос к регистру сведений ЦеныНоменклатуры
// Для примера вернем условное значение цены, хранящееся в карточке товара
Возврат ВыбраннаяНоменклатура.РозничнаяЦена;
КонецФункции
Благодаря такому подходу, форма сохраняет высокую отзывчивость, а сервер выполняет лишь минимально необходимую работу по поиску цены в базе данных.
Если в вашей конфигурации присутствует много различных документов (например, «ПродажаТовара», «ПоступлениеТоваров», «ЗаказПокупателя»), в которых логика пересчета строки («Количество * Цена = Стоимость») абсолютно одинакова, дублировать один и тот же код в модуле каждой формы считается плохим тоном («anti-pattern»).
Правильным решением будет вынесение общего алгоритма расчета в неконтекстный клиентский общий модуль. Рассмотрим этот процесс подробнее:
Шаг 1. Создайте (или используйте существующий) общий модуль, например, с именем РаботаСДокументамиКлиент. В свойствах модуля обязательно установите флаг Клиент (управляемое приложение).
Шаг 2. Опишите в этом общем модуле процедуру расчета, не забыв указать ключевое слово Экспорт:
// Код в общем модуле РаботаСДокументамиКлиент
Процедура РассчитатьСуммуСтрокиТабличнойЧасти(СтрокаТаблицы) Экспорт
// Защита от деления на Null и проверка корректности данных
Если СтрокаТаблицы <> Неопределено Тогда
СтрокаТаблицы.Стоимость = СтрокаТаблицы.Цена * СтрокаТаблицы.Количество;
КонецЕсли;
КонецПроцедуры
Шаг 3. Теперь в модулях форм любых документов вам достаточно будет просто вызывать эту процедуру, передавая в нее текущую строку ТЧ:
// Код в модуле формы документа ПродажаТовара
&НаКлиенте
Процедура ТЧ1КоличествоПриИзменении(Элемент)
СтрокаТЧ = Элементы.ТЧ1.ТекущиеДанные;
// Вызываем централизованный метод расчета из общего модуля
РаботаСДокументамиКлиент.РассчитатьСуммуСтрокиТабличнойЧасти(СтрокаТЧ);
КонецПроцедуры
Применение общих модулей значительно упрощает дальнейшую поддержку и модификацию конфигурации. Если в будущем бизнес-логика изменится (например, потребуется внедрить систему автоматических скидок, уменьшающих сумму строки), вам потребуется внести изменения только в одном месте — в общем модуле РаботаСДокументамиКлиент, и расчет обновится во всех документах системы.
Резюмируем: Для успешного изменения реквизитов табличной части всегда получайте конкретную строку через свойство ТекущиеДанные элемента формы, производите базовые математические расчеты на стороне клиента и выносите повторяющиеся процедуры в общие модули конфигурации.