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

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

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

Давайте подробно разберем, как реализовать такую защиту комплексно. Мы проанализируем решения на уровне интерфейса (для удобства пользователя) и на уровне объекта (для надежности данных), а также выясним, почему некоторые очевидные методы могут не сработать.

Уровни защиты данных

Чтобы защита была эффективной и эргономичной, нам нужно действовать на двух уровнях:

  1. Интерфейсный уровень (Client): Визуально показать пользователю, что строку нельзя трогать, и блокировать интерактивные попытки ввода.
  2. Объектный уровень (Server): Гарантировать целостность данных перед записью в базу, защищаясь от групповых обработок, кнопок "Заполнить" и программных изменений.

Шаг 1. Визуальная блокировка (Условное оформление)

Первое, что мы должны сделать — дать пользователю понять, какие строки недоступны. Для этого идеально подходит механизм УсловноеОформление формы. При необходимости можно воспользоваться готовым расширением для окраски строк по группам номенклатуры в УТ 11.

Настроим условное оформление динамически при создании формы или статически в конфигураторе. Рассмотрим логику действий:

  1. Создаем элемент условного оформления.
  2. Устанавливаем условие: например, поле ВариантОбеспечения равно значению "Отгрузить" (или "Отгружено").
  3. Устанавливаем оформляемые поля: вся табличная часть или конкретные колонки (Номенклатура, Количество, Цена).
  4. Устанавливаем параметр оформления ТолькоПросмотр в значение Истина. Дополнительно можно изменить ЦветФона на серый, чтобы визуально выделить заблокированные строки.

Это решение улучшает User Experience (UX), но не гарантирует безопасность. Пользователь не сможет кликнуть в ячейку, но кнопки командной панели (например, "Заполнить по...", "Обеспечить") могут программно изменить данные в этих строках, игнорируя настройки формы.

Шаг 2. Интерактивная защита в событиях формы

Чтобы предотвратить попытки удаления или начала редактирования (если условное оформление не сработало или нужно вывести предупреждение), используем события элементов формы.

Нам понадобятся события таблицы формы: ПередНачаломИзменения и ПередУдалением.

Пример кода для модуля формы:


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

КонецПроцедуры

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

КонецПроцедуры

Важное ограничение: Эти события срабатывают только при действиях пользователя "мышкой" или клавиатурой. Если пользователь нажмет кнопку "Заполнить -> Пересчитать цены", данные события не вызовутся, и защищенные строки будут изменены.

Шаг 3. Хардкор защита: Проверка перед записью объекта

Это самый надежный способ. Логика заключается в следующем: в момент записи документа (событие ПередЗаписью в модуле объекта) мы должны сравнить то, что сейчас находится в памяти (измененный объект), с тем, что записано в базе данных.

В теме на форуме предлагалось использовать хеширование строк (сериализовать строку в XML/строку и считать хеш). Рассмотрим, почему этот метод не идеален:

Более правильный подход — прямое сравнение данных через запрос. Мы должны получить "старое" состояние документа из базы данных и проверить, не исчезли ли или не изменились ли критичные строки.

Алгоритм реализации в модуле объекта (или расширении)

Разберем алгоритм проверки в событии ПередЗаписью:

  1. Если документ новый (ЭтоНовый()), проверку можно пропустить (так как защищать еще нечего).
  2. Делаем запрос к базе данных к табличной части этого документа по Ссулка.
  3. Выбираем только те строки, которые должны быть защищены (например, ГДЕ ВариантОбеспечения = &Отгрузить).
  4. Выгружаем результат запроса в таблицу значений или выборку.
  5. Обходим текущую табличную часть объекта (которая в памяти) и пытаемся сопоставить строки.

Сопоставление строк — тонкий момент. В 1С строки табличных частей имеют КлючСвязи (в некоторых конфигурациях) или НомерСтроки. Однако НомерСтроки может меняться при удалении/вставке строк в середину. Самый надежный вариант для УТ 11 — использовать КлючСвязи (если он ведется) или комбинацию ключевых полей, если мы допускаем перемещение строк.

Рассмотрим пример кода проверки целостности защищенных строк:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Если ОбменДанными.Загрузка Или ЭтоНовый() Тогда
        Возврат;
    КонецЕсли;

    // 1. Получаем эталонные данные из базы - то, что НЕЛЬЗЯ менять
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ЗаказКлиентаТовары.КлючСвязи КАК КлючСвязи,
        |   ЗаказКлиентаТовары.Номенклатура КАК Номенклатура,
        |   ЗаказКлиентаТовары.Количество КАК Количество,
        |   ЗаказКлиентаТовары.НомерСтроки КАК НомерСтроки
        |ИЗ
        |   Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
        |ГДЕ
        |   ЗаказКлиентаТовары.Ссылка = &Ссылка
        |   И ЗаказКлиентаТовары.ВариантОбеспечения = ЗНАЧЕНИЕ(Перечисление.ВариантыОбеспечения.Отгрузить)";
    
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    ТаблицаЗапрещенных = Запрос.Выполнить().Выгрузить();
    ТаблицаЗапрещенных.Индексы.Добавить("КлючСвязи"); // Для быстрого поиска

    // 2. Проверяем текущую ТЧ
    Для Каждого ЭталоннаяСтрока Из ТаблицаЗапрещенных Цикл
        
        // Попытка найти строку в текущем документе по ключу связи
        // Если КлючаСвязи нет, придется искать по Номенклатуре+Характеристике и т.д., что сложнее
        НайденныеСтроки = Товары.НайтиСтроки(Новый Структура("КлючСвязи", ЭталоннаяСтрока.КлючСвязи));
        
        Если НайденныеСтроки.Количество() = 0 Тогда
            // ОШИБКА: Строка была удалена!
            Отказ = Истина;
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = СтрШаблон("Ошибка! Удалена защищенная строка №%1 с товаром %2", 
                                        ЭталоннаяСтрока.НомерСтроки, ЭталоннаяСтрока.Номенклатура);
            Сообщение.Сообщить();
        Иначе
            ТекущаяСтрока = НайденныеСтроки[0];
            
            // 3. Проверяем, не изменились ли данные
            Если ТекущаяСтрока.Номенклатура <> ЭталоннаяСтрока.Номенклатура 
               Или ТекущаяСтрока.Количество <> ЭталоннаяСтрока.Количество Тогда
                
                Отказ = Истина;
                Сообщение = Новый СообщениеПользователю;
                Сообщение.Текст = СтрШаблон("Ошибка! Изменены данные в защищенной строке (было №%1). Товар уже отгружен/обеспечен.", 
                                            ЭталоннаяСтрока.НомерСтроки);
                Сообщение.Сообщить();
            КонецЕсли;
        КонецЕсли;
        
    КонецЦикла;

КонецПроцедуры

Нюансы реализации в УТ 11

Работая с конфигурацией "Управление торговлей 11", стоит учитывать следующие моменты:

Резюме

Для полноценного решения задачи:

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