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

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

Введение: Почему важен контроль одновременного редактирования?

Представьте ситуацию: два кладовщика одновременно открывают один и тот же документ "Расходная накладная". Один изменяет количество товара по одной позиции, другой – по другой. Кто-то первый записывает свои изменения, затем второй записывает свои. В результате, изменения первого могут быть потеряны, или документ может оказаться в некорректном состоянии. Для предотвращения таких сценариев и обеспечения целостности данных, крайне важно иметь механизм, который запрещает или хотя бы информирует пользователя о том, что документ уже находится в работе.

Проблема: Невозможность прямой проверки "открытости" документа

Первое, что мы должны уяснить, это фундаментальное ограничение платформы 1С:Предприятие: нет прямого, встроенного механизма для проверки того, "открыт ли документ" в форме у другого пользователя. Платформа не предоставляет функций вроде ДокументОткрытУПользователя(СсылкаНаДокумент). Попытки использовать методы блокировки для такой проверки, как это было предложено в сообщении 4, часто приводят к неработоспособным или некорректным результатам, потому что эти методы предназначены для установки блокировки, а не для её проверки без попытки установки.

Следовательно, мы должны подходить к решению этой задачи косвенно, используя доступные механизмы блокировки или создавая собственные инструменты.

Механизмы блокировок в 1С:Предприятии

Система 1С:Предприятие предлагает два основных вида объектных блокировок для обеспечения целостности данных при одновременной работе нескольких пользователей: пессимистическую и оптимистическую. Именно пессимистические блокировки будут нашим основным инструментом в решении поставленной задачи.

Пессимистическая блокировка

Этот механизм предназначен для полного запрета изменения данных объекта другими сеансами или текущим сеансом до тех пор, пока блокировка не будет снята. Она устанавливается, когда пользователь начинает модификацию объекта в форме, и снимается, когда форма объекта закрывается. Если другой пользователь попытается редактировать тот же объект, ему будет выдано сообщение о невозможности блокировки. Рассмотрим методы, которые позволяют работать с этим типом блокировки.

Метод Заблокировать()

Метод Заблокировать() объекта ссылочного типа (например, ДокументОбъект.Заблокировать()) выполняет блокировку объекта от изменения другими режимами или пользователями. Если объект уже заблокирован, вызов этого метода вызовет исключение. Это и есть та особенность, которую мы можем использовать для определения того, что документ "занят".

Важно понимать: этот метод пытается установить блокировку. Если блокировка уже установлена кем-то другим, он не просто "проверяет", а пытается заблокировать и выбрасывает исключение. Именно это исключение мы и ловим, чтобы понять, что документ уже "заблокирован" (и, как следствие, открыт на редактирование) другим пользователем.

Метод формы ЗаблокироватьДанныеФормыДляРедактирования()

В нестандартных формах объекта для обеспечения такого же поведения, как в стандартных формах (которые автоматически блокируют объект при открытии на редактирование), мы можем использовать метод формы ЗаблокироватьДанныеФормыДляРедактирования(). Этот метод устанавливает пессимистическую блокировку, ассоциированную с конкретной формой. Он вызывает исключение, если данные уже заблокированы (в том числе методом Заблокировать()).

Блокировка, установленная с указанием идентификатора формы, держится до закрытия формы или явного снятия с помощью РазблокироватьДанныеФормыДляРедактирования(). Если идентификатор формы не указан, блокировка снимается при завершении сеанса, возврате управления с сервера или завершении транзакции.

Оптимистическая блокировка

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

Варианты реализации запрета редактирования

Теперь, когда мы понимаем механизмы блокировки, рассмотрим различные подходы к реализации нашей задачи.

Вариант 1: Блокировка при открытии формы документа

Этот подход предполагает, что как только пользователь открывает форму документа, система пытается установить на него блокировку. Если это удается, пользователь может редактировать документ. Если нет (потому что документ уже заблокирован другим пользователем), документ открывается в режиме "только просмотр", и пользователь получает соответствующее сообщение.

Пример кода в обработчике ПриОткрытии

Этот код, как было предложено в сообщении 3, размещается в обработчике события ПриОткрытии формы документа (или другом подходящем серверном обработчике при создании формы):


Процедура ПриОткрытии()
    Попытка
        // Пытаемся заблокировать данные формы для редактирования.
        // Это установит пессимистическую блокировку на объект,
        // а также переведет форму в режим редактирования.
        ЗаблокироватьДанныеФормыДляРедактирования();
    Исключение
        // Если блокировка не удалась, значит, объект уже заблокирован
        // другим пользователем или другим сеансом.
        ЭтаФорма.ТолькоПросмотр = Истина; // Переводим форму в режим только просмотра.

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

В этом примере мы используем метод формы ЗаблокироватьДанныеФормыДляРедактирования(). Он более предпочтителен, чем прямой вызов Объект.Заблокировать() в контексте формы, так как он интегрирован с механизмом форм и управляет режимом редактирования формы. Если блокировка не удалась, мы переводим форму в режим "только просмотр" (ЭтаФорма.ТолькоПросмотр = Истина) и информируем пользователя.

В случае с Заблокировать(), как было предложено в сообщении 4, подход был следующим:


// В ПриСозданииНаСервере (или подобном)
ДокОбъект = Объект.Ссылка.ПолучитьОбъект();
Попытка
    ДокОбъект.Заблокировать(); // Пытаемся заблокировать сам объект
Исключение
    Сообщить("Документ уже открыт другим пользователем.");
    Отказ = Истина; // Попытка отказа от открытия, но это не всегда работает корректно с формой
КонецПопытки;

// И затем в ПриОткрытии &НаКлиенте
Процедура ПриОткрытии(Отказ)
    Если Заблокирован Тогда // Заблокирован - это некорректно реализованная функция, которая опять пытается заблокировать
        Отказ = Истина;
    КонецЕсли;
КонецПроцедуры

// &НаСервере Функция Заблокирован (неправильный подход)
Функция Заблокирован()
    ДокОбъект = Объект.Ссылка.ПолучитьОбъект();
    Попытка
        ДокОбъект.Заблокировать(); // Снова пытаемся заблокировать
        Возврат Ложь;
    Исключение
        Сообщить("Документ уже открыт другим пользователем.");
        Возврат Истина;
    КонецПопытки;
КонецФункции

Проблема с подходом из сообщения 4 в том, что функция Заблокирован() сама пытается заблокировать объект, вместо того чтобы просто проверить его состояние. Если объект свободен, она его блокирует и возвращает "Ложь" (что, по сути, ложно, так как теперь он *заблокирован*). Если объект уже заблокирован, она ловит исключение и возвращает "Истина", но при этом сообщение выводится некорректно. Более того, прямое присвоение Отказ = Истина в клиентском обработчике не всегда может предотвратить открытие формы, если блокировка уже произошла на сервере. Поэтому первый пример с ЗаблокироватьДанныеФормыДляРедактирования() является более надежным и правильным для форм.

Плюсы и минусы данного подхода
  1. Плюсы:
    • Простота реализации: Относительно небольшой объем кода.
    • Надежность: Использует встроенные механизмы платформы для блокировки.
  2. Минусы:
    • Неудобство для пользователя: Как было отмечено в сообщении 15, если один пользователь открыл документ просто для просмотра и забыл его закрыть, другому пользователю будет полностью закрыт доступ даже к просмотру документа. Это может привести к простоям и замедлению работы, особенно если блокирующий пользователь недоступен.
    • Отсутствие информации о блокирующем: По умолчанию система сообщает только, что документ заблокирован, но не всегда указывает, кем именно. Хотя это можно доработать, добавив вывод имени пользователя из контекста ошибки.

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

Вариант 2: Открытие в режиме "только просмотр" с кнопкой "Редактировать"

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

Суть идеи: по умолчанию документ всегда открывается в режиме "только просмотр". Пользователь может свободно просматривать данные, печатать, анализировать, не устанавливая никаких блокировок. Только когда пользователь явно решает внести изменения, он нажимает специальную кнопку "Редактировать". В этот момент система пытается установить блокировку. Если блокировка успешна, документ переходит в режим редактирования. Если нет (поскольку уже заблокирован), пользователь получает сообщение, и документ остается в режиме "только просмотр".

Алгоритм реализации
  1. При создании формы документа (например, в обработчике ПриСозданииНаСервере или ПриОткрытии), всегда устанавливаем режим ТолькоПросмотр = Истина.
  2. Добавляем на форму кнопку "Редактировать" (или "Включить редактирование").
  3. По обработчику нажатия этой кнопки вызываем серверный метод, который:
    1. Пытается установить пессимистическую блокировку на объект, например, с помощью ЗаблокироватьДанныеФормыДляРедактирования().
    2. Если блокировка успешна, устанавливает ЭтаФорма.ТолькоПросмотр = Ложь.
    3. Если блокировка не удалась (выброшено исключение), сообщает пользователю о том, что документ уже заблокирован другим пользователем.
  4. Когда пользователь заканчивает редактирование и закрывает форму, блокировка автоматически снимается. Можно также добавить кнопку "Отменить редактирование", которая снимет блокировку и вернет форму в режим "только просмотр".
Преимущества и недостатки
  1. Преимущества:
    • Улучшенный пользовательский опыт: Пользователи всегда могут просмотреть документ, независимо от того, заблокирован он для редактирования или нет. Это очень важно для аналитической работы или поиска информации, как было отмечено в сообщении 15.
    • Явное управление блокировкой: Пользователь сам решает, когда ему нужна блокировка, делая процесс более прозрачным.
    • Снижение конфликтов: Меньше ситуаций, когда документ "висит" заблокированным из-за забывчивости пользователя.
  2. Недостатки:
    • Дополнительные "телодвижения": Некоторые пользователи могут считать лишним нажатие дополнительной кнопки, как показано в сообщении 13.
    • Сложность реализации: Требует немного большего объема кода для управления состоянием формы, кнопками и сохранением настроек формы.

Вариант 3: Использование специального регистра сведений

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

Как это работает?
  1. Создаем новый периодический или непериодический регистр сведений (например, "БлокировкиДокументов").
    • Измерения: Документ (тип - ДокументСсылка.ЛюбойДокумент), Пользователь (тип - СправочникСсылка.Пользователи).
    • Ресурсы: МоментБлокировки (тип - ДатаВремя).
  2. При открытии формы документа и попытке редактирования (или при нажатии кнопки "Редактировать"):
    1. Мы сначала проверяем этот регистр сведений: нет ли записи для текущего документа, указывающей на то, что он уже заблокирован другим пользователем.
    2. Если запись есть, мы сообщаем пользователю, кто заблокировал документ и когда. Форма открывается в режиме "только просмотр".
    3. Если записи нет, мы создаем новую запись в регистре сведений, указывая текущий документ, текущего пользователя и текущее время. Затем разрешаем редактирование документа.
  3. При закрытии формы документа или завершении редактирования, мы удаляем соответствующую запись из регистра сведений.
  4. Можно также предусмотреть регламентное задание, которое будет периодически очищать "зависшие" блокировки (например, если пользователь аварийно завершил работу).
Преимущества и сценарии использования
  1. Преимущества:
    • Гибкость: Позволяет хранить любую дополнительную информацию о блокировке (кто, когда, зачем).
    • Контроль извне: Можно проверять состояние блокировки из динамических списков, других обработок или регламентных заданий.
    • Надежность: Менее подвержен случайным сбоям, чем зависимости от активных сеансов.
    • Информирование: Легко реализовать вывод информации о том, кто именно заблокировал документ.
  2. Сценарии использования:
    • Комплексные блокировки, охватывающие несколько объектов одновременно.
    • Блокировки, которые должны быть устойчивы к кратковременным разрывам связи.
    • Когда требуется централизованное управление всеми блокировками.
  3. Недостатки:
    • Сложность реализации: Требует создания дополнительных объектов конфигурации и более объемного кода для управления записями регистра.
    • Возможные "зависшие" блокировки: Требует механизмов очистки (например, регламентного задания), если пользователь аварийно завершил работу.

Дополнительные рекомендации

Независимо от выбранного подхода, есть несколько общих рекомендаций, которые помогут улучшить решение.

Информирование пользователя о блокировке

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

Управляемые транзакционные блокировки

Платформа 1С 8.3 позволяет использовать управляемые блокировки, которые выполняются не на уровне СУБД, а в терминах предметной области 1С. Это может повысить параллельность работы пользователей. Важно учитывать, что если для документа установлен управляемый режим блокировок, то и все регистры, в которых он делает движения, должны быть переведены в управляемый режим блокировки. Этот механизм сложнее в освоении, но может дать существенные преимущества в высоконагруженных системах.

Дата запрета изменения данных

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

Роли и права доступа

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

Снятие блокировки

Блокировка, установленная программно, может быть снята явным образом методом РазблокироватьДанныеФормыДляРедактирования() или Разблокировать(). В большинстве случаев блокировка, установленная формой, автоматически снимается при закрытии этой формы. Однако, если мы используем регистр сведений, нам нужно явно позаботиться об удалении записи о блокировке при закрытии формы или при завершении транзакции.

Заключение

Выбор конкретного механизма для запрета одновременного редактирования документа зависит от требований к параллельности работы, пользовательского опыта и сложности реализации. Мы рассмотрели несколько подходов: от простой блокировки при открытии до более сложных решений с отдельным регистром сведений. Важно стремиться к балансу между надежной защитой данных и удобством работы пользователей. Самым сбалансированным с точки зрения пользовательского опыта и надежности часто оказывается подход с открытием документа в режиме "только просмотр" и предоставлением явной кнопки "Редактировать", которая устанавливает блокировку.

← На главную