Как запретить редактирование документа в 1С спустя несколько дней после его создания?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практической работе торговых и производственных компаний часто возникает задача — ограничить менеджерам возможность изменять документы «задним числом». Например, если документ Реализация товаров и услуг был создан и проведен более двух дней назад, обычный пользователь не должен иметь возможности его перепроводить, редактировать цены, менять контрагентов или удалять строки. Это необходимо для защиты уже сданной отчетности и предотвращения злоупотреблений.

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

---

Способ №1: Использование типового механизма БСП «Даты запрета изменения»

Прежде чем приступать к написанию собственного кода, проанализируем возможности вашей конфигурации. Если вы работаете в любой современной типовой программе 1С (например, 1С:Управление торговлей, 1С:ERP, 1С:Комплексная автоматизация, 1С:Бухгалтерия предприятия и др.), в нее уже встроен мощный механизм Даты запрета изменения. Он позволяет решить задачу без привлечения программиста.

Рассмотрим по шагам, как настроить этот функционал:

  1. Перейдем в раздел Администрирование — Настройки пользователей и прав и установим флаг Даты запрета изменения.
  2. Откроем ссылку Настроить. Перед нами откроется окно настройки дат запрета.
  3. В поле Установка даты мы можем выбрать один из вариантов:
    • Для всех пользователей — ограничение будет действовать на всю базу без исключения.
    • По пользователям — мы сможем задать индивидуальные правила для каждого менеджера или группы пользователей (например, запретить редактирование менеджерам, но оставить доступным для бухгалтеров и администраторов).
  4. В табличной части выберем способ указания даты запрета. Вместо указания фиксированной даты (которую пришлось бы менять вручную каждый день) мы можем использовать относительные даты. Нам доступны такие варианты, как:
    • Конец прошлого года
    • Конец прошлого квартала
    • Конец прошлого месяца
    • Конец прошлой недели
    • Предыдущий день
  5. Настройка отсрочки: Чтобы реализовать логику «разрешить изменение в течение 2 дней», мы можем настроить отсрочку. Например, если выбрать относительную дату Предыдущий день и установить параметр Вступает в действие с отсрочкой в размере 2 дн., то пользователи смогут изменять документы вчерашнего и сегодняшнего дня, а более ранние документы будут автоматически закрыты для редактирования.

За автоматическое смещение дат отвечает специальное регламентное задание Пересчет значений относительных дат запрета изменения, которое работает на сервере в фоновом режиме. Это максимально надежный и штатный способ автоматизации контроля закрытых периодов.

---

Способ №2: Программная реализация в самописных конфигурациях

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

Анализ архитектурной ошибки: проверка в форме

В Сообщении 5 автор темы пытается внедрить проверку в процедуре ПриОткрытии() формы документа. Давайте проанализируем, почему это является грубой ошибкой проектирования:

Золотое правило разработки в 1С: любые проверки на право изменения, удаления или проведения данных должны выполняться на стороне сервера в модуле объекта документа в событии ПередЗаписью()!

Реализация программной проверки «ПередЗаписью»

Рассмотрим правильный алгоритм создания кастомного механизма. Предположим, у нас в системе уже создан непериодический Регистр Сведений ЗапретРедактированияПользователей со следующей структурой:

Откроем Модуль объекта документа РеализацияТоваровУслуг (или любого другого целевого документа) и добавим следующий обработчик события:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // 1. Исключаем проверку при обмене данными, чтобы не нарушать синхронизацию
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Исключаем администраторов из проверки, чтобы не заблокировать систему
    Если РольДоступна("ПолныеПрава") Тогда
        Возврат;
    КонецЕсли;
    
    // 3. Проверка имеет смысл только для уже существующих в базе документов.
    // Если документ новый, его редактирование разрешено в любом случае.
    Если ЭтоНовый() Тогда
        Возврат;
    КонецЕсли;
    
    // 4. Получаем текущего пользователя системы
    ТекущийПользователь = ПараметрыСеанса.ТекущийПользователь;
    
    // 5. Запрашиваем настройки ограничений из регистра сведений
    Отбор = Новый Структура;
    Отбор.Вставить("Пользователь", ТекущийПользователь);
    
    НастройкаЗапрета = РегистрыСведений.ЗапретРедактированияПользователей.Получить(Отбор);
    
    // Если для пользователя найдена запись и количество дней ограничено
    Если НастройкаЗапрета.Дней > 0 Тогда
        
        // Получаем дату документа и текущее время сеанса
        ДатаДокумента = ЭтотОбъект.Дата;
        ТекущаяДатаСеанса = ТекущаяДатаСеанса();
        
        // Вычисляем предельную дату, до которой документ можно изменять.
        // Переводим дни в секунды: 1 день = 24 часа * 60 минут * 60 секунд = 86400 секунд.
        ПредельнаяДатаРедактирования = НачалоДня(ДатаДокумента) + (НастройкаЗапрета.Дней * 86400);
        
        // Если текущий день уже превысил максимально разрешенную дату
        Если НачалоДня(ТекущаяДатаСеанса) > ПредельнаяДатаРедактирования Тогда
            Отказ = Истина;
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Ошибка! Редактирование документа заблокировано. "
                + "С момента создания документа прошло более " + НастройкаЗапрета.Дней 
                + " дн. Изменение разрешено только администраторам.";
            Сообщение.Сообщить();
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Проанализируем ключевые элементы этого кода:

---

Продвинутый уровень: учет выходных дней по производственному календарю

В Сообщении 12 справедливо поднимается важный бизнес-нюанс: «А как учитывать выходные и праздники?». Если менеджер выписал документ в пятницу, то при ограничении в 2 календарных дня уже в понедельник он не сможет его отредактировать, так как суббота и воскресенье заблокируют период. В реальном бизнесе лимит в «2 дня» чаще всего означает «2 рабочих дня».

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

Разместим эту функцию в общем модуле (например, в серверном модуле РаботаСДатамиПереопределяемый) или непосредственно в модуле объекта документа:


Функция ПолучитьКоличествоРабочихДней(ДатаНачала, ДатаОкончания) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ДанныеПроизводственногоКалендаря.Дата) КАК КолРабочихДней
        |ИЗ
        |	РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
        |ГДЕ
        |	ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
        |	И ДанныеПроизводственногоКалендаря.ВидДня В (ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий), 
        |                                                 ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный))";
    
    Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаНачала));
    Запрос.УстановитьПараметр("ДатаОкончания", НачалоДня(ДатаОкончания));
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат Выборка.КолРабочихДней;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Теперь мы можем изменить наш обработчик ПередЗаписью(), интегрировав в него проверку рабочих дней. Заменим блок сравнения дат на вызов нашей функции:


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

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

---

Резюме и рекомендации

Подведем итог проанализированных нами решений:

← На главную