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

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

При автоматизации бизнес-процессов в 1С:Предприятие часто возникает задача разграничения прав доступа при работе с закрытыми периодами. Типичный сценарий: пользователю необходимо разрешить переносить непроведенные документы (например, «Реализация товаров и услуг»), созданные задним числом, на текущую дату. После того как документ перенесен в «сегодняшний» день, пользователь должен получить возможность полноценно его редактировать. Однако при реализации этой задачи разработчики сталкиваются с двумя серьезными препятствиями:

  1. Блокировка формы на уровне платформы: При установке свойства ЭтаФорма.ТолькоПросмотр = Истина управляемая форма полностью блокирует все элементы управления, включая кнопку «Записать» и поле выбора даты.
  2. Контроль даты запрета изменения: Попытка программно перенести дату непроведенного документа на сервере натыкается на стандартные механизмы защиты закрытого периода.

В этой статье мы подробно разберем, как правильно обойти эти ограничения, почему популярные «обходные пути» опасны для базы данных и как реализовать задачу в соответствии со стандартами разработки 1С и Библиотеки стандартных подсистем (БСП).

Проблема 1: Ловушка свойства ТолькоПросмотр в управляемых формах

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


Если Объект.Дата < НачалоДня(ТекущаяДата) Тогда
    ЭтаФорма.ТолькоПросмотр = Истина;
    Элементы.Дата.Доступность = Истина; // Попытка оставить доступным поле Даты
КонецЕсли;

Проанализируем, почему этот код не работает. Свойство управляемой формы ТолькоПросмотр имеет абсолютный приоритет на уровне платформы «1С:Предприятие». Когда вы устанавливаете его в значение Истина, платформа принудительно отключает доступность всех элементов формы, у которых свойство ИзменяетДанные установлено в Истина (к ним относятся практически все поля ввода и стандартные команды записи). Любые локальные попытки вернуть доступность отдельным кнопкам или полям игнорируются.

Выясним, как решить эту проблему. Чтобы оставить поле даты и кнопку записи доступными, нам нужно отказаться от свойства ЭтаФорма.ТолькоПросмотр = Истина и пойти по пути выборочной блокировки элементов формы.

Для этого мы можем применить рекурсивный обход всех элементов формы. Ниже представлен проверенный алгоритм, который обходит коллекцию элементов и блокирует только те, которые не входят в список разрешенных:


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

Вызов этой процедуры на форме документа будет выглядеть следующим образом:


СписокДоступныхРеквизитов = Новый Массив;
СписокДоступныхРеквизитов.Добавить("Дата");
СписокДоступныхРеквизитов.Добавить("Записать"); // Имя стандартной или кастомной кнопки записи
УстановитьДоступностьФормы(Элементы, СписокДоступныхРеквизитов);

Альтернативный вариант в типовых конфигурациях: Если вы работаете в конфигурации на базе БСП (например, УТ 11, ERP, КА), вам не обязательно писать собственный обход элементов. Рекомендуется использовать штатный программный интерфейс БСП:


ЗапретРедактированияРеквизитовОбъектовКлиент.УстановитьРазрешенностьРедактированияРеквизитов(Форма, СписокРазрешенныхРеквизитов);

Проблема 2: Опасность использования ОбменДанными.Загрузка = Истина (Антипаттерн)

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


// ВНИМАНИЕ: Данный код является опасным антипаттерном!
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Дата = ТекущаяДата();
ДокументОбъект.ОбменДанными.Загрузка = Истина; 
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Рассмотрим подробнее, почему свойство ОбменДанными.Загрузка = Истина категорически запрещено использовать в стандартной бизнес-логике приложения. Этот флаг предназначен исключительно для работы механизмов синхронизации данных (обменов) и миграции данных между базами. Установка этого флага приводит к следующим критическим последствиям:

  1. Отключение бизнес-логики: По стандартам 1С, в начале всех важных процедур-обработчиков (ПередЗаписью, ПриЗаписи, ОбработкаПроведения) и в подписках на события прописывается условие отсечки: Если ОбменДанными.Загрузка Тогда Возврат; КонецЕсли;. Это означает, что документ запишется без проведения необходимых проверок заполнения полей, лимитов, остатков и правильности аналитики.
  2. Сбои в планах обмена: Документы, записанные с флагом загрузки, во многих конфигурациях не регистрируются к выгрузке в другие базы. Если ваша «Управление торговлей» синхронизирована с «1С:Бухгалтерией», то измененный документ просто не улетит в бухгалтерию, что вызовет жесткий рассинхрон учета.
  3. Нарушение регистров: При изменении даты в режиме загрузки связанные движения по регистрам сведений и накопления не пересчитаются и не обновятся, что приведет к коллизиям в отчетах.

Безопасные варианты записи документа в закрытом периоде по стандартам БСП

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

Вариант А: Использование Дополнительных Свойств объекта

Подсистема дат запрета изменений БСП проверяет документы перед записью. Мы можем передать специальный маркер в свойства самого объекта. БСП увидит этот параметр и пропустит проверку периода конкретно для данного вызова записи, при этом все остальные контроли и движения документа отработают в штатном режиме:


ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Дата = ТекущаяДата();

// Указываем подсистеме БСП пропустить проверку даты запрета
ДокументОбъект.ДополнительныеСвойства.Вставить("ПропуститьПроверкуЗапретаИзменения", Истина);

// Записываем документ штатным образом
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Вариант Б: Временное отключение проверки на уровне сеанса

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


// Сохраняем текущее состояние проверки в сеансе
ПроверкаДатЗапретаОтключена = ДатыЗапретаИзменения.ПроверкаДатЗапретаОтключена(); 

// Отключаем проверку дат запрета изменений
ДатыЗапретаИзменения.ОтключитьПроверкуДатЗапрета(Истина); 

Попытка
    ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
    ДокументОбъект.Дата = ТекущаяДата();
    ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Исключение
    // Обязательно восстанавливаем проверку в случае падения кода
    ДатыЗапретаИзменения.ОтключитьПроверкуДатЗапрета(ПроверкаДатЗапретаОтключена);
    ВызватьИсключение;
КонецПопытки;

// Восстанавливаем состояние проверки после успешного выполнения
ДатыЗапретаИзменения.ОтключитьПроверкуДатЗапрета(ПроверкаДатЗапретаОтключена); 

Пошаговое руководство по реализации переноса даты документов

Объединим полученные знания и реализуем красивую и безопасную схему работы. Мы добавим в форму списка документов кнопку «Актуализировать дату», при нажатии на которую система будет безопасно переносить непроведенный документ на текущий день, обходя закрытый период без риска повредить данные.

Шаг 1. Создание общего модуля.

Создадим новый общий модуль (или используем существующий серверный модуль вызова). Назовем его, например, УправлениеДокументамиСервер. Установим свойства модуля: Сервер и Вызов сервера.

Пропишем в нем процедуру изменения даты:


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

Шаг 2. Размещение команды на форме списка.

Откроем форму списка документов «Реализация товаров и услуг». Создадим новую команду формы с именем АктуализироватьДату и вынесем её на командную панель формы списка. В обработчике команды на клиенте напишем следующий код:


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

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

← На главную