В практической работе торговых и производственных компаний часто возникает задача — ограничить менеджерам возможность изменять документы «задним числом». Например, если документ Реализация товаров и услуг был создан и проведен более двух дней назад, обычный пользователь не должен иметь возможности его перепроводить, редактировать цены, менять контрагентов или удалять строки. Это необходимо для защиты уже сданной отчетности и предотвращения злоупотреблений.
В этой статье мы подробно разберем, как решить эту задачу в системе «1С:Предприятие 8.3». Мы рассмотрим два основных сценария: использование готового функционала Библиотеки стандартных подсистем (БСП) и разработку собственного, более гибкого механизма с использованием регистров сведений и встроенного языка 1С.
---
Прежде чем приступать к написанию собственного кода, проанализируем возможности вашей конфигурации. Если вы работаете в любой современной типовой программе 1С (например, 1С:Управление торговлей, 1С:ERP, 1С:Комплексная автоматизация, 1С:Бухгалтерия предприятия и др.), в нее уже встроен мощный механизм Даты запрета изменения. Он позволяет решить задачу без привлечения программиста.
Рассмотрим по шагам, как настроить этот функционал:
Предыдущий день и установить параметр Вступает в действие с отсрочкой в размере 2 дн., то пользователи смогут изменять документы вчерашнего и сегодняшнего дня, а более ранние документы будут автоматически закрыты для редактирования.За автоматическое смещение дат отвечает специальное регламентное задание Пересчет значений относительных дат запрета изменения, которое работает на сервере в фоновом режиме. Это максимально надежный и штатный способ автоматизации контроля закрытых периодов.
---
Если вы работаете в самописной конфигурации, где БСП отсутствует, или бизнес-логика требует индивидуальных настроек (например, хранить количество разрешенных дней для каждого пользователя персонально в регистре сведений), нам потребуется написать собственный код контроля.
В Сообщении 5 автор темы пытается внедрить проверку в процедуре ПриОткрытии() формы документа. Давайте проанализируем, почему это является грубой ошибкой проектирования:
Золотое правило разработки в 1С: любые проверки на право изменения, удаления или проведения данных должны выполняться на стороне сервера в модуле объекта документа в событии ПередЗаписью()!
Рассмотрим правильный алгоритм создания кастомного механизма. Предположим, у нас в системе уже создан непериодический Регистр Сведений ЗапретРедактированияПользователей со следующей структурой:
Пользователь (Тип: СправочникСсылка.Пользователи)Дней (Тип: Число)Откроем Модуль объекта документа РеализацияТоваровУслуг (или любого другого целевого документа) и добавим следующий обработчик события:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// 1. Исключаем проверку при обмене данными, чтобы не нарушать синхронизацию
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// 2. Исключаем администраторов из проверки, чтобы не заблокировать систему
Если РольДоступна("ПолныеПрава") Тогда
Возврат;
КонецЕсли;
// 3. Проверка имеет смысл только для уже существующих в базе документов.
// Если документ новый, его редактирование разрешено в любом случае.
Если ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// 4. Получаем текущего пользователя системы
ТекущийПользователь = ПараметрыСеанса.ТекущийПользователь;
// 5. Запрашиваем настройки ограничений из регистра сведений
Отбор = Новый Структура;
Отбор.Вставить("Пользователь", ТекущийПользователь);
НастройкаЗапрета = РегистрыСведений.ЗапретРедактированияПользователей.Получить(Отбор);
// Если для пользователя найдена запись и количество дней ограничено
Если НастройкаЗапрета.Дней > 0 Тогда
// Получаем дату документа и текущее время сеанса
ДатаДокумента = ЭтотОбъект.Дата;
ТекущаяДатаСеанса = ТекущаяДатаСеанса();
// Вычисляем предельную дату, до которой документ можно изменять.
// Переводим дни в секунды: 1 день = 24 часа * 60 минут * 60 секунд = 86400 секунд.
ПредельнаяДатаРедактирования = НачалоДня(ДатаДокумента) + (НастройкаЗапрета.Дней * 86400);
// Если текущий день уже превысил максимально разрешенную дату
Если НачалоДня(ТекущаяДатаСеанса) > ПредельнаяДатаРедактирования Тогда
Отказ = Истина;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Ошибка! Редактирование документа заблокировано. "
+ "С момента создания документа прошло более " + НастройкаЗапрета.Дней
+ " дн. Изменение разрешено только администраторам.";
Сообщение.Сообщить();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Проанализируем ключевые элементы этого кода:
ТекущаяДатаСеанса() вместо стандартной ТекущаяДата() позволяет корректно обрабатывать часовые пояса, если сервер 1С находится в одном регионе (например, в Москве), а менеджер работает в другом (например, во Владивостоке).НачалоДня() нивелирует разницу в часах и минутах проведения. Документ считается доступным ровно указанное количество полных календарных суток.Отказ = Истина полностью прерывает транзакцию записи. Никакие изменения не сохранятся в базе данных, документ вернется в исходное состояние.---
В Сообщении 12 справедливо поднимается важный бизнес-нюанс: «А как учитывать выходные и праздники?». Если менеджер выписал документ в пятницу, то при ограничении в 2 календарных дня уже в понедельник он не сможет его отредактировать, так как суббота и воскресенье заблокируют период. В реальном бизнесе лимит в «2 дня» чаще всего означает «2 рабочих дня».
Для реализации логики рабочих дней нам понадобится использовать типовой регистр сведений ДанныеПроизводственногоКалендаря. Давайте напишем вспомогательную функцию, которая вычисляет разницу в рабочих днях между двумя датами.
Разместим эту функцию в общем модуле (например, в серверном модуле РаботаСДатамиПереопределяемый) или непосредственно в модуле объекта документа:
Функция ПолучитьКоличествоРабочихДней(ДатаНачала, ДатаОкончания) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ДанныеПроизводственногоКалендаря.Дата) КАК КолРабочихДней
|ИЗ
| РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
|ГДЕ
| ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
| И ДанныеПроизводственногоКалендаря.ВидДня В (ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий),
| ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный))";
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("ДатаОкончания", НачалоДня(ДатаОкончания));
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.КолРабочихДней;
КонецЕсли;
Возврат 0;
КонецФункции
Теперь мы можем изменить наш обработчик ПередЗаписью(), интегрировав в него проверку рабочих дней. Заменим блок сравнения дат на вызов нашей функции:
// Вместо прямого расчета секунд используем производственный календарь
Если НастройкаЗапрета.Дней > 0 Тогда
КоличествоРабочихДней = ПолучитьКоличествоРабочихДней(ЭтотОбъект.Дата, ТекущаяДатаСеанса());
// Если количество фактически прошедших рабочих дней превышает лимит из настроек
Если КоличествоРабочихДней > НастройкаЗапрета.Дней Тогда
Отказ = Истина;
ВызватьИсключение "Редактирование запрещено! Прошло " + КоличествоРабочихДней
+ " рабочих дней с момента создания документа при лимите в " + НастройкаЗапрета.Дней + " дн.";
КонецЕсли;
КонецЕсли;
Использование метода ВызватьИсключение вместо простого вывода сообщений гарантированно прервет выполнение любых связанных внешних обработок и транзакций, а также выведет пользователю понятное окно с ошибкой в интерфейсе.
---
Подведем итог проанализированных нами решений:
ПередЗаписью.