Закрываем период в 1С из кода: регистр БСП, модуль документа и ограничения доступа

· 8 мин

Содержание

В типовых конфигурациях на БСП (Бухгалтерия 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 проверяет платформа, а не прикладной код. Поэтому запрет работает даже для внешней обработки, которую пользователь запустил без привилегированного режима.

Порядок настройки:

  1. Создайте параметр сеанса ДатаЗапретаРедактирования с типом «Дата».
  2. В модуле сеанса заполните его в УстановкаПараметровСеанса:
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)

	ПараметрыСеанса.ДатаЗапретаРедактирования =
		КонецДня(Константы.ДатаЗапретаРедактирования.Получить());

КонецПроцедуры
  1. В роли, через которую пользователи редактируют документы, у права «Изменение» для нужного документа задайте ограничение:
ГДЕ Дата > &ДатаЗапретаРедактирования
  1. Такое же ограничение поставьте на «Добавление» — иначе документ закрытым числом можно создать заново.

Для права «Изменение» платформа проверяет и исходную, и новую версию объекта. Перенос документа из закрытого периода в открытый 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.

Можно ли запретить редактирование только для части пользователей?
В БСП — да: запись в регистр с конкретным пользователем или группой в измерении «Пользователь». В своей конфигурации проверяйте роль через РольДоступна() перед сравнением дат.
Почему администратор всё равно меняет закрытые документы?
Проверка БСП и ваш код могут пропускать пользователей с полными правами, а RLS к ним не применяется. Если запрет нужен для всех, не делайте исключений в коде и выдайте администратору отдельную рабочую учётную запись.
Влияет ли дата запрета на регистры сведений и справочники?
Справочники без даты по ней не проверяются. Независимые периодические регистры проверяют по полю «Период» — в своей конфигурации для этого нужна отдельная подписка на «Перед записью» набора записей.
Что быстрее: проверка в коде или RLS?
Для одиночной записи разница незаметна. На больших выборках RLS добавляет условие в каждый запрос к таблице, поэтому держите его простым: одно сравнение даты с параметром сеанса.