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

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

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

Давайте рассмотрим эту проблему подробнее. Автор столкнулся с ситуацией, когда его код в модуле набора записей регистра сведений, предназначенный для запрета удаления пустых наборов (что является признаком удаления записи), блокировал удаление даже при распроведении документа, поскольку реквизит ДокументРегистратор всё ещё был заполнен. Наша задача — найти способ передать контекст операции удаления, чтобы различать эти два сценария.

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

1. Использование ДополнительныхСвойств набора записей

Один из наиболее чистых и рекомендуемых способов для передачи контекста операции является использование свойства ДополнительныеСвойства у объекта НаборЗаписей регистра сведений. Это свойство представляет собой структуру, которую мы можем заполнить произвольными данными перед записью или удалением набора записей. В модуле набора записей мы затем сможем проверить эти свойства.

  1. Заполнение ДополнительныхСвойств при программном удалении:

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

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

    Обратите внимание, что для регистров без регистратора, чтобы обеспечить "удаление при отмене проведения", мы должны использовать реквизит, который логически связывает записи регистра с документом, например, ДокументИсточник или, как в исходном примере, ДокументРегистратор, даже если он не является стандартным регистратором.

  2. Проверка ДополнительныхСвойств в модуле набора записей:

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

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

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

2. Применение флага ОбменДанными.Загрузка

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

  1. Установка флага перед программным удалением:

    Перед тем как вызвать операцию удаления набора записей, мы устанавливаем глобальный флаг ОбменДанными.Загрузка в Истина, а после операции возвращаем его в исходное состояние.

    
    // Пример кода в модуле объекта документа при отмене проведения
    Процедура ПриОтменеПроведения()
    
        // Сохраняем текущее состояние флага ОбменДанными.Загрузка
        ТекущееЗначениеЗагрузки = ОбменДанными.Загрузка;
        ОбменДанными.Загрузка = Истина; // Устанавливаем флаг программной операции
    
        Попытка
            МенеджерЗаписи = РегистрыСведений.МойПериодическийРегистр.СоздатьНаборЗаписей();
            МенеджерЗаписи.Отбор.ДокументРегистратор.Установить(ЭтотОбъект.Ссылка);
            МенеджерЗаписи.Очистить();
            МенеджерЗаписи.Записать();
        Исключение
            ВызватьИсключение; // Перевызываем исключение, если что-то пошло не так
        КонецПопытки;
    
        ОбменДанными.Загрузка = ТекущееЗначениеЗагрузки; // Восстанавливаем флаг
    
    КонецПроцедуры
    
  2. Проверка флага в модуле набора записей:

    В процедуре ПередЗаписью мы проверяем значение ОбменДанными.Загрузка.

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

    Этот способ прост в реализации, но имеет потенциальный недостаток: флаг ОбменДанными.Загрузка может быть установлен другими механизмами платформы или конфигурации, что может привести к непреднамеренному обходу проверок в других местах.

3. Разграничение удаления через роли и ПривилегированныйРежим

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

  1. Создание специальной роли:

    Мы создаем новую роль, например, "РазрешитьУдалениеРегистра", и даем ей право на "Редактирование" нашего регистра сведений. Основным пользователям, которым мы хотим запретить ручное удаление, эту роль не назначаем.

  2. Использование ПривилегированныйРежим и проверки роли:

    В модуле документа при отмене проведения мы устанавливаем ПривилегированныйРежим(Истина), что позволяет выполнять операции с обходом проверок прав. В модуле набора записей мы проверяем, находится ли система в привилегированном режиме, или имеет ли текущий пользователь специальную роль для удаления.

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

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

4. Обработка интерактивного удаления на уровне форм

Для предотвращения исключительно интерактивного удаления мы можем использовать события формы списка или формы записи регистра сведений. Эти события срабатывают только при попытке пользователя удалить запись через интерфейс.

  1. Процедура ПередУдалением в форме списка:

    В форме списка регистра сведений мы можем обработать событие ПередУдалением. Это событие получает параметр Отказ, установив который в Истина, мы предотвратим удаление.

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

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

5. Передача контекста через временное хранилище или менеджер временных значений

В более сложных сценариях, когда контекст операции необходимо передать через несколько вызовов функций или процедур, и нет прямого доступа к ДополнительнымСвойствам или глобальным флагам, мы можем использовать временное хранилище или глобальные переменные модуля сеанса/приложения. Этот подход позволяет "пронести" нужную информацию через цепочку вызовов.

  1. Сохранение признака во временном хранилище:

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

    
    // Пример кода в модуле объекта документа при отмене проведения
    Процедура ПриОтменеПроведения()
    
        АдресВременногоХранилища = ПоместитьВоВременноеХранилище(Новый Структура("ПрограммноеУдаление", Истина));
    
        Попытка
            // Передаем адрес как параметр в функцию, которая выполняет удаление
            // Или сохраняем в глобальной переменной модуля сеанса, доступной из модуля набора записей
            // ... Ваш код удаления ...
            МенеджерЗаписи = РегистрыСведений.МойПериодическийРегистр.СоздатьНаборЗаписей();
            МенеджерЗаписи.Отбор.ДокументРегистратор.Установить(ЭтотОбъект.Ссылка);
                    
            // Здесь можно было бы использовать ДополнительныеСвойства, как в первом примере,
            // Но если бы мы передавали через промежуточные функции, то могли бы передать адрес хранилища.
            // Например, так: МенеджерЗаписи.ДополнительныеСвойства.Вставить("АдресКонтекстаУдаления", АдресВременногоХранилища);
                    
            МенеджерЗаписи.Очистить();
            МенеджерЗаписи.Записать();
    
        Исключение
            ВызватьИсключение;
        КонецПопытки;
    
        УдалитьИзВременногоХранилища(АдресВременногоХранилища); // Очищаем временное хранилище
    
    КонецПроцедуры
    
  2. Чтение признака в модуле набора записей:

    В модуле набора записей мы могли бы получить этот адрес из ДополнительныхСвойств (если бы он туда передавался), а затем получить значение из временного хранилища.

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

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

Заключение

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

В большинстве случаев для задачи, поставленной автором, оптимальным будет использование ДополнительныхСвойств набора записей, возможно, в комбинации с проверкой ОбменДанными.Загрузка для дополнительной надежности или использования в контексте обмена данными. Всегда анализируйте требования к безопасности и производительности, выбирая наиболее подходящий метод.

← На главную