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