При работе в многопользовательских базах данных 1С часто возникает необходимость ограничить редактирование и проведение документов «задним числом». Если пользователи бесконтрольно меняют документы за прошлые периоды, это неизбежно приводит к искажению себестоимости, нарушению последовательности проведения документов и «съезжанию» остатков и бухгалтерской отчетности.
В этой статье мы подробно разберем два основных способа решения этой задачи: использование встроенного типового механизма конфигурации и создание собственного программного ограничения через механизм подписок на события.
Прежде чем приступать к написанию собственного программного кода, проанализируем возможности вашей конфигурации. Если вы работаете в любой современной типовой конфигурации 1С на базе БСП (Библиотеки стандартных подсистем), например, в 1С:Бухгалтерии, 1С:ERP, 1С:Комплексной автоматизации или 1С:Управлении торговлей, то нужный функционал уже заложен разработчиками фирмы «1С».
Для активации и настройки этого механизма выполним следующие шаги:
Если же ваша конфигурация сильно изменена, является самописной или перед вами стоит задача реализовать уникальную логику контроля, перейдем к созданию собственного программного алгоритма.
Прописывать код проверки даты в модуле каждого существующего документа нерационально. Это увеличивает трудозатраты при обновлении конфигурации и засоряет код. Вместо этого мы применим профессиональный подход — настроим единую подписку на событие.
Разберем по шагам, как правильно создать и запрограммировать подписку на событие для контроля даты документов.
Все процедуры, обслуживающие подписки на события, должны располагаться в неглобальных общих модулях. Создадим новый общий модуль со следующими свойствами:
КонтрольДокументовСобытияВ дереве конфигурации найдем ветку Общие -> Подписки на события, добавим новую подписку и настроим её свойства:
ЗапретРедактированияСтарыхДокументовПередЗаписьюДокументОбъект целиком (чтобы проверять вообще все документы в системе) либо отметить галочками только конкретные виды документов.ПередЗаписью. Это событие срабатывает до того, как данные будут записаны в базу данных, и выполняется внутри транзакции, что позволяет нам отменить запись в случае нарушения правил.ПроверитьДатуДокументаПередЗаписью (система сама предложит создать её шаблон).При написании обработчика подписки крайне важно избежать ошибок, которые часто допускают начинающие разработчики. Проанализируем эти нюансы:
ЭтотОбъект. Вместо нее используется первый параметр процедуры — Источник, который и представляет собой записываемый документ.Предупреждение() или Вопрос() приведет к критической ошибке и аварийному завершению сеанса пользователя. Для вывода сообщений мы должны использовать объект СообщениеПользователю.Источник.Ссылка.Дата).С учетом всех требований напишем правильный, отказоустойчивый код в общем модуле:
Процедура ПроверитьДатуДокументаПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
// Если документ загружается в рамках обмена данными (РИБ или интеграция), проверку выполнять не нужно
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Разрешаем запись без ограничений пользователям с полными правами
Если РольДоступна("ПолныеПрава") Тогда
Возврат;
КонецЕсли;
// Вычисляем границу запрета (текущая дата минус 30 дней в секундах)
СекундВМесяце = 30 * 24 * 60 * 60; // 2 592 000 секунд
ГраницаЗапрета = ТекущаяДатаСеанса() - СекундВМесяце;
// Проверка 1: Попытка провести или записать новый/изменяемый документ, дата которого уже в закрытом периоде
Если Источник.Дата <= ГраницаЗапрета Тогда
// Запрещаем проведение старого документа
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
Отказ = Истина;
ТекстОшибки = "Отказано в проведении! Дата документа (" + Формат(Источник.Дата, "ДФ=dd.MM.yyyy") + ") старше одного месяца.";
ВывестиСообщениеПроцедуры(ТекстОшибки, Источник);
Возврат;
КонецЕсли;
// Запрещаем отмену проведения или изменение старого проведенного документа
Если Источник.Проведен И РежимЗаписи = РежимЗаписиДокумента.Запись Тогда
Отказ = Истина;
ТекстОшибки = "Запрещено редактирование и перезапись проведенных документов старше одного месяца!";
ВывестиСообщениеПроцедуры(ТекстОшибки, Источник);
Возврат;
КонецЕсли;
КонецЕсли;
// Проверка 2: Защита от изменения даты существующего старого документа в базе
Если ЗначениеЗаполнено(Источник.Ссылка) Тогда
// Получаем дату документа напрямую из базы данных до начала записи изменений
СтараяДатаДокумента = Источник.Ссылка.Дата;
Если СтараяДатаДокумента <= ГраницаЗапрета Тогда
// Если документ изначально лежал в закрытом периоде, а пользователь пытается перенести его в открытый
Если Источник.Дата > ГраницаЗапрета Тогда
Отказ = Истина;
ТекстОшибки = "Запрещено изменять дату у документов, созданных более месяца назад!";
ВывестиСообщениеПроцедуры(ТекстОшибки, Источник);
Возврат;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Процедура ВывестиСообщениеПроцедуры(ТекстСообщения, ИсточникДокумент)
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = ТекстСообщения;
Сообщение.УстановитьДанные(ИсточникДокумент);
Сообщение.Сообщить();
КонецПроцедуры
Рассмотрим подробнее, как именно функционирует написанный код:
ТекущаяДатаСеанса() используется вместо обычной ТекущаяДата(). Это критично для клиент-серверных баз данных, где часовой пояс сервера может не совпадать с часовым поясом пользователя.РольДоступна("ПолныеПрава") гарантирует гибкость — администраторы системы всегда смогут провести документ задним числом в экстренных ситуациях, тогда как обычные пользователи получат отказ.УстановитьДанные(Источник) в объекте СообщениеПользователю автоматически привязывает сообщение об ошибке к конкретному документу. Пользователь наглядно увидит, из-за какого именно объекта проведение было отменено.Используя данный подход, мы надежно защитили информационную базу от нежелательного изменения исторических данных, минимизировали количество доработок в конфигурации и обеспечили корректную работу бизнес-логики программы.