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