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