При разработке интерфейсов на платформе 1С:Предприятие 8.3 часто возникает задача предоставить пользователю инструмент для массового редактирования данных. Типичный сценарий: пользователь выбирает несколько строк в табличной части документа и хочет одним нажатием кнопки изменить значение определенного реквизита (например, «Качество» или «Статус») во всех связанных элементах справочника. В процессе реализации этой задачи начинающие разработчики часто сталкиваются с ошибкой Обращение к процедуре объекта как к функции (записать). В этой статье мы подробно разберем, почему возникает эта ошибка, и пошагово реализуем правильный алгоритм группового изменения.
Для начала проанализируем ситуацию, в которой оказывается разработчик. Ошибка чаще всего возникает в двух случаях. Первый — это попытка использовать метод Записать() в правой части оператора присваивания или в условии. Например, если написать Результат = Объект.Записать(), система выдаст ошибку, так как в 1С метод Записать() для справочников и документов является процедурой, а не функцией, и он не возвращает никакого значения.
Второй, более коварный случай связан с работой отладчика. Рассмотрим подробнее: если вы остановились на точке останова и в окне «Вычислить выражение» (Shift+F9) ввели Объект.Записать(), система выдаст ровно ту же ошибку. Отладчик ожидает, что выражение вернет результат, который можно отобразить в табло. Поскольку процедура ничего не возвращает, выполнение прерывается с технической ошибкой. Проанализируем ситуацию: если ваш код выполняется, данные записываются, но ошибка видна только в окне отладки — значит, с самим программным кодом всё в порядке, и вы просто пытаетесь «вычислить» невычисляемое.
Разберем процесс реализации задачи с самого начала. Прежде всего, нам нужно понять, какие именно строки пользователь выделил в табличной части документа. Для этого мы будем использовать свойство таблицы формы ВыделенныеСтроки. Важно помнить, что это коллекция идентификаторов, а не сами данные строк.
Рассмотрим пример кода на клиенте, который инициирует процесс:
&НаКлиенте
Процедура ИзменитьКачество(Команда)
// Получаем массив идентификаторов выделенных строк
ВыделенныеСтроки = Элементы.Товары.ВыделенныеСтроки;
Если ВыделенныеСтроки.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Пожалуйста, выделите хотя бы одну строку в списке!");
Возврат;
КонецЕсли;
// Запрашиваем у пользователя новое значение
// Используем асинхронный ввод для обеспечения работы в веб-клиенте
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораЗначенияКачества", ЭтотОбъект, ВыделенныеСтроки);
ПоказатьВводЗначения(ОписаниеОповещения, ПредопределенноеЗначение("Перечисление.Качество.Новый"), "Выберите новое качество");
КонецПроцедуры
После того как пользователь выбрал значение в диалоговом окне, управление передается в процедуру обратного вызова. Здесь мы проверим, было ли выбрано значение (пользователь мог нажать «Отмена»), и если всё в порядке — вызовем серверную процедуру для непосредственного изменения данных в базе.
&НаКлиенте
Процедура ПослеВыбораЗначенияКачества(ВыбранноеЗначение, ДополнительныеПараметры) Экспорт
Если ВыбранноеЗначение = Неопределено Тогда
Возврат; // Пользователь отменил выбор
КонецЕсли;
// Передаем массив идентификаторов и новое значение на сервер
ИзменитьКачествоНаСервере(ДополнительныеПараметры, ВыбранноеЗначение);
// Обновляем отображение данных на форме
ОповеститьОбИзменении(Тип("СправочникСсылка.ХарактеристикиНоменклатуры"));
КонецПроцедуры
Теперь перейдем к самой ответственной части — работе с объектами базы данных. Нам необходимо итерировать массив переданных идентификаторов, находить соответствующие ссылки на характеристики и изменять их реквизиты. Посмотрим на пример реализации серверной функции:
&НаСервере
Процедура ИзменитьКачествоНаСервере(МассивИдентификаторов, НовоеЗначение)
Для Каждого Идентификатор Из МассивИдентификаторов Цикл
// Получаем данные конкретной строки табличной части документа
СтрокаТЧ = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
Если СтрокаТЧ = Неопределено Тогда
Продолжить;
КонецЕсли;
СсылкаНаХарактеристику = СтрокаТЧ.Характеристика;
Если ЗначениеЗаполнено(СсылкаНаХарактеристику) Тогда
// Важный момент: для изменения реквизита нужно получить ОБЪЕКТ
ОбъектХарактеристики = СсылкаНаХарактеристику.ПолучитьОбъект();
// Устанавливаем новое значение реквизита
ОбъектХарактеристики.Качество = НовоеЗначение;
// Используем попытку для обработки возможных блокировок
Попытка
// Здесь мы просто вызываем процедуру Записать()
ОбъектХарактеристики.Записать();
Исключение
ОбщаяИнформацииОбОшибке = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("ГрупповоеИзменение", УровеньЖурналаРегистрации.Ошибка, , , ОбщаяИнформацииОбОшибке);
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Рассмотрим подробнее вопросы производительности и надежности. Если строк для изменения много (например, сотни или тысячи), выполнение записи каждого объекта по отдельности может занять значительное время и нагрузить систему. Проанализируем ситуацию: каждая запись Записать() инициирует отдельную транзакцию в СУБД, вызывает проверки в модуле объекта и обновляет индексы.
Чтобы ускорить процесс, воспользуемся следующими приемами:
НачатьТранзакцию() — ЗафиксироватьТранзакцию(), мы добьемся того, что база данных выполнит все изменения как одну операцию. Это значительно быстрее и гарантирует целостность: либо изменятся все характеристики, либо ни одна (в случае ошибки).ОбменДанными.Загрузка = Истина. Это позволит миновать прикладные проверки в процедурах ПередЗаписью модуля справочника, что существенно ускорит запись.Посмотрим на оптимизированный код:
&НаСервере
Процедура ИзменитьКачествоНаСервереОптимизировано(МассивИдентификаторов, НовоеЗначение)
НачатьТранзакцию();
Попытка
Для Каждого Идентификатор Из МассивИдентификаторов Цикл
СтрокаТЧ = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
Если СтрокаТЧ = Неопределено Тогда Продолжить; КонецЕсли;
Ссылка = СтрокаТЧ.Характеристика;
Если Не ЗначениеЗаполнено(Ссылка) Тогда Продолжить; КонецЕсли;
ОбъектХар = Ссылка.ПолучитьОбъект();
ОбъектХар.Качество = НовоеЗначение;
// Включаем режим загрузки для скорости
ОбъектХар.ОбменДанными.Загрузка = Истина;
ОбъектХар.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение "Не удалось выполнить групповое изменение: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Проанализируем последствия наших действий. Справочник ХарактеристикиНоменклатуры является общим для всей системы. Это означает, что если одна и та же характеристика (например, «Красный, размер 42») выбрана в десяти разных документах (поступлениях, реализациях, заказах), то изменение её реквизита «Качество» отразится во всех этих документах одновременно.
Если ваша задача состоит в том, чтобы изменить качество товара только в рамках текущей накладной (например, пришел бракованный товар конкретно в этой поставке), то реквизит Качество должен находиться не в справочнике, а непосредственно в табличной части документа. Изменение реквизита справочника — это глобальное действие, которое меняет нормативно-справочную информацию всей компании. Всегда проверяйте бизнес-логику задачи перед тем, как реализовывать групповое изменение объектов справочников.
Подведем итоги нашего разбора:
Записать() — это процедура. Не пытайтесь присваивать её результат переменной или вычислять в отладчике как функцию.Элементы.ИмяТаблицы.ВыделенныеСтроки на клиенте.ПолучитьОбъект()), изменить его и сохранить.ОбменДанными.Загрузка.Следуя этим правилам, вы сможете создавать надежные и быстрые инструменты для автоматизации работы пользователей в 1С.