Закрываем период в 1С из кода: регистр БСП, модуль документа и ограничения доступа
Содержание
В типовых конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) дату запрета редактирования программно устанавливают записью в регистр сведений
ДатыЗапретаИзменения, а проверку платформа выполняет сама через подписки подсистемы. В собственной конфигурации без БСП запрет делают иначе: хранят дату в константе и отменяют запись документа в обработчике ПередЗаписью, а для жёсткой защиты добавляют ограничение доступа (RLS) на право «Изменение».
Задача обычно возникает в двух случаях. Бухгалтерия хочет, чтобы закрытый месяц сдвигался автоматически, без ручного ввода. Или разработчик пишет свою конфигурацию и должен сделать так, чтобы никто не «поправил» документ задним числом после сдачи отчётности. Код ниже рассчитан на платформу 8.3 и управляемые формы.
Какие механизмы запрета периода уже есть в 1С
Встроенного в платформу свойства «дата запрета записи» нет. Ни у документа, ни у регистра такого реквизита не существует — запрет всегда реализует прикладной код. Отсюда три уровня защиты.
| Механизм | Где работает | Что даёт | Ограничения |
|---|---|---|---|
| Подсистема БСП «Даты запрета изменения» | Типовые конфигурации | Общая дата, даты по разделам и пользователям, относительные даты | Нужна БСП, править только данными регистра |
| Проверка в модуле объекта или подписке | Любая конфигурация | Полный контроль над условиями и текстом ошибки | Обходится кодом в привилегированном режиме и при загрузке обмена |
| RLS на право «Изменение» | Любая конфигурация | Защита на уровне платформы, работает и для внешних обработок | Сложнее отлаживать, дата фиксируется на сеанс |
В БСП механизм включается константой ИспользоватьДатыЗапретаИзменения. В интерфейсе это флажок «Даты запрета изменения» в разделе Администрирование → Пользователи и права. Без него записи в регистре есть, но проверка не выполняется.
Как установить дату запрета изменения программно через регистр БСП
Подсистема хранит настройки в регистре сведений ДатыЗапретаИзменения. Его измерения: Раздел, Объект, Пользователь. Ресурс — ДатаЗапрета. Общая дата для всех пользователей хранится с пустыми разделом и объектом, а в измерении Пользователь указано значение перечисления ВидыНазначенияДатЗапрета.ДляВсехПользователей.
Прежде чем писать код, установите дату один раз вручную и откройте регистр через Все функции (включается в параметрах программы флажком «Отображать команду “Все функции”»). Посмотрите, какие значения стоят в измерениях. Так вы увидите формат хранения именно вашего релиза БСП и не ошибётесь с пустыми ссылками.
Пример: процедура общего модуля (сервер) ставит общую дату запрета на конец прошлого месяца.
Процедура УстановитьОбщуюДатуЗапрета(НоваяДата) Экспорт
НаборЗаписей = РегистрыСведений.ДатыЗапретаИзменения.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Раздел.Установить(ПланыВидовХарактеристик.РазделыДатЗапретаИзменения.ПустаяСсылка());
НаборЗаписей.Отбор.Объект.Установить(ПланыВидовХарактеристик.РазделыДатЗапретаИзменения.ПустаяСсылка());
НаборЗаписей.Отбор.Пользователь.Установить(Перечисления.ВидыНазначенияДатЗапрета.ДляВсехПользователей);
Запись = НаборЗаписей.Добавить();
Запись.Раздел = ПланыВидовХарактеристик.РазделыДатЗапретаИзменения.ПустаяСсылка();
Запись.Объект = ПланыВидовХарактеристик.РазделыДатЗапретаИзменения.ПустаяСсылка();
Запись.Пользователь = Перечисления.ВидыНазначенияДатЗапрета.ДляВсехПользователей;
Запись.ДатаЗапрета = КонецДня(НоваяДата);
НаборЗаписей.Записать(); // перезаписывает старую запись с тем же отбором
КонецПроцедуры
Вызов из регламентного задания, которое срабатывает 10-го числа каждого месяца:
КонецПрошлогоМесяца = НачалоМесяца(ТекущаяДатаСеанса()) - 1;
УстановитьОбщуюДатуЗапрета(КонецПрошлогоМесяца);
Если сегодня 10 октября, НачалоМесяца вернёт 1 октября 00:00:00, минус одна секунда — 30 сентября 23:59:59. Сентябрь закрыт, октябрь открыт.
Не пишите в регистр без отбора: Записать() у пустого набора без отбора удалит все даты запрета, включая настройки по разделам и пользователям.
В БСП дата запрета может быть относительной («конец прошлого месяца» с допустимым числом дней). Бухгалтер выбирает её прямо в форме, и сдвиг идёт сам. Проверьте эту возможность у пользователей: часто регламентное задание вообще не нужно.
Даты по разделам и пользователям
Если нужно закрыть, например, только зарплатный раздел для одного сотрудника, в Раздел пишут элемент плана видов характеристик РазделыДатЗапретаИзменения, а в Пользователь — ссылку на пользователя или группу. Состав разделов зависит от конфигурации. Посмотреть его можно в самом плане видов характеристик через Все функции.
Проверка запрета в модуле объекта документа
В своей конфигурации проще всего держать дату в константе ДатаЗапретаРедактирования (тип «Дата», состав «Дата»). Проверять её нужно в ПередЗаписью: этот обработчик вызывается при записи, проведении, отмене проведения и пометке на удаление.
Главная ловушка — проверить только новую дату. Пользователь откроет сентябрьский документ, поменяет дату на октябрь и спокойно запишет: новая дата в открытом периоде. Поэтому сравнивают и старую дату из базы, и новую.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Если ДополнительныеСвойства.Свойство("НеПроверятьДатуЗапрета") Тогда
Возврат;
КонецЕсли;
ДатаЗапрета = Константы.ДатаЗапретаРедактирования.Получить();
Если Не ЗначениеЗаполнено(ДатаЗапрета) Тогда
Возврат;
КонецЕсли;
ДатаЗапрета = КонецДня(ДатаЗапрета);
ЗапретПоНовойДате = (Дата <= ДатаЗапрета);
ЗапретПоСтаройДате = Ложь;
Если Не ЭтоНовый() Тогда
ЗапретПоСтаройДате = (Ссылка.Дата <= ДатаЗапрета);
КонецЕсли;
Если ЗапретПоНовойДате Или ЗапретПоСтаройДате Тогда
Отказ = Истина;
ТекстОшибки = СтрШаблон("Период по %1 закрыт. Изменить документ нельзя.",
Формат(ДатаЗапрета, "ДЛФ=D"));
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = ТекстОшибки;
Сообщение.Поле = "Дата";
Сообщение.УстановитьДанные(ЭтотОбъект);
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
Ссылка.Дата читает значение из базы, а не из изменённого объекта: запись ещё не произошла. Для удаления непосредственно (без пометки) добавьте такую же проверку в ПередУдалением.
Одна подписка вместо кода в каждом документе
Копировать процедуру в 40 модулей не нужно. Создайте подписку на событие «Перед записью» с источником «ДокументОбъект» (все документы) и обработчиком в общем серверном модуле. Сигнатура обработчика: (Источник, Отказ, РежимЗаписи, РежимПроведения), внутри вместо Дата пишите Источник.Дата.
Для типовой конфигурации, где своя логика всё же нужна, тот же код кладут в расширение: в модуле объекта документа добавляют метод с аннотацией &После("ПередЗаписью"). Основная конфигурация остаётся на поддержке.
Как заблокировать редактирование в формах и списках
Проверка при записи срабатывает только по кнопке «Записать». Пользователь успевает поправить десяток полей и лишь потом видит отказ. Лучше сразу открыть закрытый документ только для просмотра.
В модуле формы документа:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
ДатаЗапрета = Константы.ДатаЗапретаРедактирования.Получить();
Если ЗначениеЗаполнено(ДатаЗапрета)
И ТекущийОбъект.Дата <= КонецДня(ДатаЗапрета) Тогда
ТолькоПросмотр = Истина;
КонецЕсли;
КонецПроцедуры
В формах типовых конфигураций за это отвечает вызов ДатыЗапретаИзменения.ОбъектПриЧтенииНаСервере(ЭтотОбъект, ТекущийОбъект). Если делаете свою форму документа в типовой, добавьте его в ПриЧтенииНаСервере.
В списке закрытые документы удобно выделить серым через условное оформление динамического списка:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ДатаЗапрета = Константы.ДатаЗапретаРедактирования.Получить();
Если Не ЗначениеЗаполнено(ДатаЗапрета) Тогда
Возврат;
КонецЕсли;
ЭлементОформления = Список.УсловноеОформление.Элементы.Добавить();
Отбор = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
Отбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Дата");
Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.МеньшеИлиРавно;
Отбор.ПравоеЗначение = КонецДня(ДатаЗапрета);
ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветТекста", WebЦвета.Серый);
КонецПроцедуры
ТолькоПросмотр и цвет в списке — это удобство, а не защита. Документ всё равно можно изменить обработкой или групповым перепроведением. Защиту даёт только проверка при записи или RLS.
Ограничение доступа (RLS) как жёсткий запрет редактирования
RLS проверяет платформа, а не прикладной код. Поэтому запрет работает даже для внешней обработки, которую пользователь запустил без привилегированного режима.
Порядок настройки:
- Создайте параметр сеанса
ДатаЗапретаРедактированияс типом «Дата». - В модуле сеанса заполните его в
УстановкаПараметровСеанса:
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
ПараметрыСеанса.ДатаЗапретаРедактирования =
КонецДня(Константы.ДатаЗапретаРедактирования.Получить());
КонецПроцедуры
- В роли, через которую пользователи редактируют документы, у права «Изменение» для нужного документа задайте ограничение:
ГДЕ Дата > &ДатаЗапретаРедактирования
- Такое же ограничение поставьте на «Добавление» — иначе документ закрытым числом можно создать заново.
Для права «Изменение» платформа проверяет и исходную, и новую версию объекта. Перенос документа из закрытого периода в открытый RLS тоже не пропустит.
Подводные камни RLS:
- 🔸 Права ролей складываются. Если у пользователя есть вторая роль с правом «Изменение» без ограничения, запрет пропадёт.
- 🔸 Параметр сеанса заполняется при входе. После сдвига даты пользователям нужно перезапустить программу.
- 🔸 Код в привилегированном режиме и пользователь с полными правами RLS не проверяют.
- 🔸 В типовых конфигурациях ограничения строит подсистема «Управление доступом». Ручные условия в типовых ролях ломают обновление — там используйте даты запрета БСП.
Исключения, обход проверки и журнал регистрации
Отказ через Отказ = Истина и исключение через ВызватьИсключение ведут себя по-разному. Отказ отменяет только эту запись. Исключение прерывает весь вызов и откатывает транзакцию, если она открыта.
| Ситуация | Что использовать |
|---|---|
| Интерактивная запись документа | Отказ = Истина + сообщение с полем Дата |
| Групповая обработка, нужно пропустить закрытые документы и идти дальше | Проверка даты до записи, Попытка … Исключение вокруг Записать() |
| Запись внутри транзакции, где частичный результат недопустим | ВызватьИсключение |
Служебный код иногда должен писать в закрытый период: например, регламентная обработка исправления последовательности. Для этого в примере выше есть флаг в ДополнительныеСвойства:
ДокументОбъект.ДополнительныеСвойства.Вставить("НеПроверятьДатуЗапрета", Истина);
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Каждый такой обход фиксируйте в журнале регистрации: иначе через полгода никто не объяснит, почему изменилась отчётность за закрытый квартал.
ЗаписьЖурналаРегистрации("ДатаЗапрета.Обход",
УровеньЖурналаРегистрации.Предупреждение,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
"Запись в закрытый период из служебной обработки");
Отказы пользователей тоже стоит писать в журнал с уровнем «Информация». По событию ДатаЗапрета.Отказ легко найти, кто и как часто пытается править прошлые месяцы. Обычно это сигнал, что период закрывают слишком рано.
Проверку отключают при ОбменДанными.Загрузка, иначе обмен с филиалом встанет на первом же старом документе. Для обменов в БСП есть отдельная дата запрета загрузки. В своей конфигурации контролируйте входящие данные в правилах обмена.
Тестирование сценариев блокировки
Проверьте запрет на копии базы, а не на рабочей. Минимальный набор сценариев:
| № | Действие | Ожидаемый результат |
|---|---|---|
| 1 | Изменить документ с датой в закрытом периоде | Отказ, сообщение у поля «Дата» |
| 2 | Перенести закрытый документ в открытый месяц | Отказ |
| 3 | Перенести открытый документ в закрытый месяц | Отказ |
| 4 | Создать новый документ задним числом | Отказ |
| 5 | Пометить закрытый документ на удаление | Отказ |
| 6 | Документ ровно на дату запрета, время 23:59:59 | Отказ (дата включительно) |
| 7 | Документ на следующий день, 00:00:00 | Запись проходит |
| 8 | Загрузка из обмена | Проверка пропущена или работает дата загрузки |
| 9 | Внешняя обработка без привилегированного режима | Отказ (если включён RLS) |
Сценарий 3 часто забывают. Именно им «подтягивают» остатки в закрытом месяце.
Автотест на встроенном языке для сценария 1:
Процедура ТестЗапретаЗаписиВЗакрытомПериоде() Экспорт
Константы.ДатаЗапретаРедактирования.Установить(Дата(2026, 9, 30));
ДокументОбъект = Документы.ПоступлениеТоваров.СоздатьДокумент();
ДокументОбъект.Дата = Дата(2026, 9, 15, 12, 0, 0);
ЗаписьПрошла = Истина;
Попытка
ДокументОбъект.Записать();
Исключение
ЗаписьПрошла = Ложь;
КонецПопытки;
Если ЗаписьПрошла Тогда
ВызватьИсключение "Документ записан в закрытый период";
КонецЕсли;
КонецПроцедуры
При Отказ = Истина метод Записать() выбрасывает исключение, поэтому Попытка ловит отказ. Имя документа подставьте своё. Интерактивные сценарии (форма открывается только для просмотра, серые строки в списке) удобно гонять в Vanessa Automation.
Можно ли запретить редактирование только для части пользователей?
РольДоступна() перед сравнением дат.