При работе в системах 1С:Предприятие, особенно в конфигурациях, где множество пользователей одновременно взаимодействуют с одними и теми же данными, часто возникает задача предотвращения одновременного редактирования одного и того же документа. Например, несколько кладовщиков не должны иметь возможность работать в одном документе приемки или отгрузки товара одновременно, чтобы избежать коллизий и потери данных. Рассмотрим, как можно решить эту проблему.
Представьте ситуацию: два кладовщика одновременно открывают один и тот же документ "Расходная накладная". Один изменяет количество товара по одной позиции, другой – по другой. Кто-то первый записывает свои изменения, затем второй записывает свои. В результате, изменения первого могут быть потеряны, или документ может оказаться в некорректном состоянии. Для предотвращения таких сценариев и обеспечения целостности данных, крайне важно иметь механизм, который запрещает или хотя бы информирует пользователя о том, что документ уже находится в работе.
Первое, что мы должны уяснить, это фундаментальное ограничение платформы 1С:Предприятие: нет прямого, встроенного механизма для проверки того, "открыт ли документ" в форме у другого пользователя. Платформа не предоставляет функций вроде ДокументОткрытУПользователя(СсылкаНаДокумент). Попытки использовать методы блокировки для такой проверки, как это было предложено в сообщении 4, часто приводят к неработоспособным или некорректным результатам, потому что эти методы предназначены для установки блокировки, а не для её проверки без попытки установки.
Следовательно, мы должны подходить к решению этой задачи косвенно, используя доступные механизмы блокировки или создавая собственные инструменты.
Система 1С:Предприятие предлагает два основных вида объектных блокировок для обеспечения целостности данных при одновременной работе нескольких пользователей: пессимистическую и оптимистическую. Именно пессимистические блокировки будут нашим основным инструментом в решении поставленной задачи.
Этот механизм предназначен для полного запрета изменения данных объекта другими сеансами или текущим сеансом до тех пор, пока блокировка не будет снята. Она устанавливается, когда пользователь начинает модификацию объекта в форме, и снимается, когда форма объекта закрывается. Если другой пользователь попытается редактировать тот же объект, ему будет выдано сообщение о невозможности блокировки. Рассмотрим методы, которые позволяют работать с этим типом блокировки.
Заблокировать()Метод Заблокировать() объекта ссылочного типа (например, ДокументОбъект.Заблокировать()) выполняет блокировку объекта от изменения другими режимами или пользователями. Если объект уже заблокирован, вызов этого метода вызовет исключение. Это и есть та особенность, которую мы можем использовать для определения того, что документ "занят".
Важно понимать: этот метод пытается установить блокировку. Если блокировка уже установлена кем-то другим, он не просто "проверяет", а пытается заблокировать и выбрасывает исключение. Именно это исключение мы и ловим, чтобы понять, что документ уже "заблокирован" (и, как следствие, открыт на редактирование) другим пользователем.
ЗаблокироватьДанныеФормыДляРедактирования()В нестандартных формах объекта для обеспечения такого же поведения, как в стандартных формах (которые автоматически блокируют объект при открытии на редактирование), мы можем использовать метод формы ЗаблокироватьДанныеФормыДляРедактирования(). Этот метод устанавливает пессимистическую блокировку, ассоциированную с конкретной формой. Он вызывает исключение, если данные уже заблокированы (в том числе методом Заблокировать()).
Блокировка, установленная с указанием идентификатора формы, держится до закрытия формы или явного снятия с помощью РазблокироватьДанныеФормыДляРедактирования(). Если идентификатор формы не указан, блокировка снимается при завершении сеанса, возврате управления с сервера или завершении транзакции.
Оптимистическая блокировка основана на сравнении номера версии объекта. Перед записью объекта в базу данных система сравнивает номер версии, полученный при открытии, с текущим номером версии в базе данных. Если версии отличаются, выдается предупреждение о том, что объект был изменен или удален. Этот механизм не подходит для нашей задачи запрета открытия, так как он срабатывает только при записи и не предотвращает одновременное редактирование, лишь уведомляет о конфликте после его возникновения.
Теперь, когда мы понимаем механизмы блокировки, рассмотрим различные подходы к реализации нашей задачи.
Этот подход предполагает, что как только пользователь открывает форму документа, система пытается установить на него блокировку. Если это удается, пользователь может редактировать документ. Если нет (потому что документ уже заблокирован другим пользователем), документ открывается в режиме "только просмотр", и пользователь получает соответствующее сообщение.
ПриОткрытииЭтот код, как было предложено в сообщении 3, размещается в обработчике события ПриОткрытии формы документа (или другом подходящем серверном обработчике при создании формы):
Процедура ПриОткрытии()
Попытка
// Пытаемся заблокировать данные формы для редактирования.
// Это установит пессимистическую блокировку на объект,
// а также переведет форму в режим редактирования.
ЗаблокироватьДанныеФормыДляРедактирования();
Исключение
// Если блокировка не удалась, значит, объект уже заблокирован
// другим пользователем или другим сеансом.
ЭтаФорма.ТолькоПросмотр = Истина; // Переводим форму в режим только просмотра.
// Извлекаем и сообщаем пользователю информацию об ошибке.
// ОписаниеОшибки содержит подробную информацию о причине исключения.
Ошибка_Содержание = ОписаниеОшибки();
Сообщить("Документ используется другим пользователем!" + Символы.ПС +
Сред(Ошибка_Содержание, Найти(Ошибка_Содержание, "Объект уже заблокирован")));
КонецПопытки;
КонецПроцедуры
В этом примере мы используем метод формы ЗаблокироватьДанныеФормыДляРедактирования(). Он более предпочтителен, чем прямой вызов Объект.Заблокировать() в контексте формы, так как он интегрирован с механизмом форм и управляет режимом редактирования формы. Если блокировка не удалась, мы переводим форму в режим "только просмотр" (ЭтаФорма.ТолькоПросмотр = Истина) и информируем пользователя.
В случае с Заблокировать(), как было предложено в сообщении 4, подход был следующим:
// В ПриСозданииНаСервере (или подобном)
ДокОбъект = Объект.Ссылка.ПолучитьОбъект();
Попытка
ДокОбъект.Заблокировать(); // Пытаемся заблокировать сам объект
Исключение
Сообщить("Документ уже открыт другим пользователем.");
Отказ = Истина; // Попытка отказа от открытия, но это не всегда работает корректно с формой
КонецПопытки;
// И затем в ПриОткрытии &НаКлиенте
Процедура ПриОткрытии(Отказ)
Если Заблокирован Тогда // Заблокирован - это некорректно реализованная функция, которая опять пытается заблокировать
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
// &НаСервере Функция Заблокирован (неправильный подход)
Функция Заблокирован()
ДокОбъект = Объект.Ссылка.ПолучитьОбъект();
Попытка
ДокОбъект.Заблокировать(); // Снова пытаемся заблокировать
Возврат Ложь;
Исключение
Сообщить("Документ уже открыт другим пользователем.");
Возврат Истина;
КонецПопытки;
КонецФункции
Проблема с подходом из сообщения 4 в том, что функция Заблокирован() сама пытается заблокировать объект, вместо того чтобы просто проверить его состояние. Если объект свободен, она его блокирует и возвращает "Ложь" (что, по сути, ложно, так как теперь он *заблокирован*). Если объект уже заблокирован, она ловит исключение и возвращает "Истина", но при этом сообщение выводится некорректно. Более того, прямое присвоение Отказ = Истина в клиентском обработчике не всегда может предотвратить открытие формы, если блокировка уже произошла на сервере. Поэтому первый пример с ЗаблокироватьДанныеФормыДляРедактирования() является более надежным и правильным для форм.
Однако, как показывает опыт в сообщении 16, в некоторых командах такой подход может быть вполне рабочим, если пользователи дисциплинированы и понимают последствия своих действий.
Этот вариант, предложенный в сообщении 12, является более гибким и улучшает пользовательский опыт, нивелируя недостатки первого подхода.
Суть идеи: по умолчанию документ всегда открывается в режиме "только просмотр". Пользователь может свободно просматривать данные, печатать, анализировать, не устанавливая никаких блокировок. Только когда пользователь явно решает внести изменения, он нажимает специальную кнопку "Редактировать". В этот момент система пытается установить блокировку. Если блокировка успешна, документ переходит в режим редактирования. Если нет (поскольку уже заблокирован), пользователь получает сообщение, и документ остается в режиме "только просмотр".
ПриСозданииНаСервере или ПриОткрытии), всегда устанавливаем режим ТолькоПросмотр = Истина.ЗаблокироватьДанныеФормыДляРедактирования().ЭтаФорма.ТолькоПросмотр = Ложь.Как было предложено в сообщении 7, одним из универсальных и гибких способов решения проблемы является создание собственного регистра сведений для отслеживания "занятости" документов. Этот подход особенно полезен, когда требуется более сложная логика блокировки, например, для регламентных заданий или при необходимости хранить дополнительную информацию о блокировке.
Документ (тип - ДокументСсылка.ЛюбойДокумент), Пользователь (тип - СправочникСсылка.Пользователи).МоментБлокировки (тип - ДатаВремя).Независимо от выбранного подхода, есть несколько общих рекомендаций, которые помогут улучшить решение.
Всегда старайтесь максимально информировать пользователя о том, почему он не может редактировать документ. В идеале, сообщение должно содержать информацию о том, кто именно заблокировал документ и когда. Это позволит пользователям связаться друг с другом и решить ситуацию. Для получения информации о блокирующем пользователе часто приходится разбирать текст ошибки, возвращаемой платформой, или же использовать данные из пользовательского регистра сведений (если вы выбрали этот подход).
Платформа 1С 8.3 позволяет использовать управляемые блокировки, которые выполняются не на уровне СУБД, а в терминах предметной области 1С. Это может повысить параллельность работы пользователей. Важно учитывать, что если для документа установлен управляемый режим блокировок, то и все регистры, в которых он делает движения, должны быть переведены в управляемый режим блокировки. Этот механизм сложнее в освоении, но может дать существенные преимущества в высоконагруженных системах.
Для предотвращения случайного изменения документов, например, после сдачи отчетности или закрытия периода, мы можем установить "дату запрета редактирования". Это не заменяет блокировку одновременного редактирования, но служит дополнительным уровнем защиты. Дата запрета может быть настроена для всех пользователей, по организациям или даже по конкретным пользователям и группам.
Настройка прав доступа через роли пользователей может ограничивать возможность проведения или редактирования документов для определенных групп. Например, можно создать роль, которая разрешает только создание и редактирование, но не проведение документов. Это еще один уровень контроля, дополняющий механизмы блокировок.
Блокировка, установленная программно, может быть снята явным образом методом РазблокироватьДанныеФормыДляРедактирования() или Разблокировать(). В большинстве случаев блокировка, установленная формой, автоматически снимается при закрытии этой формы. Однако, если мы используем регистр сведений, нам нужно явно позаботиться об удалении записи о блокировке при закрытии формы или при завершении транзакции.
Выбор конкретного механизма для запрета одновременного редактирования документа зависит от требований к параллельности работы, пользовательского опыта и сложности реализации. Мы рассмотрели несколько подходов: от простой блокировки при открытии до более сложных решений с отдельным регистром сведений. Важно стремиться к балансу между надежной защитой данных и удобством работы пользователей. Самым сбалансированным с точки зрения пользовательского опыта и надежности часто оказывается подход с открытием документа в режиме "только просмотр" и предоставлением явной кнопки "Редактировать", которая устанавливает блокировку.