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

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия
← На главную

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

Исторически платформа 1С всегда стремилась обеспечивать целостность данных. До определенной версии платформы (до 8.3.15) поведение по умолчанию было пессимистичным: если один пользователь открывал документ на редактирование, для других он автоматически блокировался, и они не могли начать его редактирование. Однако, начиная с версии 8.3.15, в поведении объектных блокировок произошли изменения. Теперь платформа позволяет пользователю "форсировать" редактирование, даже если документ уже открыт и изменяется другим. Это означает, что при попытке второго пользователя открыть и начать редактировать тот же документ, система может предложить "Начать редактирование документа?", и если он согласится, изменения первого пользователя могут быть потеряны при его попытке записи. Это создает серьезные проблемы, требующие программного вмешательства для восстановления желаемого строгого поведения.

Понимание типов блокировок в 1С:Предприятие 8.3

Прежде чем перейти к решению, важно различать основные типы блокировок, которые существуют в 1С:

  1. Объектные блокировки: предназначены для обеспечения целостности конкретных объектов (документов, справочников).
    • Пессимистические блокировки: запрещают изменение объекта другими сеансами до снятия блокировки. Они используются, когда пользователь интерактивно редактирует объект. Это именно то, что нам требуется.
    • Оптимистические блокировки: срабатывают при попытке записи объекта, если его версия в памяти не соответствует версии в базе данных (т.е. объект был изменен другим пользователем). В этом случае выдается предупреждение, и пользователю предлагается решить конфликт, что может привести к потере данных одного из пользователей.
  2. Транзакционные блокировки: работают на уровне СУБД и связаны с транзакциями. Могут быть автоматическими (управляются СУБД) или управляемыми (задаются разработчиком). Они обеспечивают целостность данных в рамках одной операции записи, но не предотвращают одновременное открытие и начало редактирования объекта разными пользователями.

Методы блокировки объектов для редактирования в 1С 8.3

Для программной реализации строгой блокировки в 1С 8.3 используются следующие методы:

Метод ДокументОбъект.Заблокировать()

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

Особенности использования:

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

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

Глобальная функция ЗаблокироватьДанныеДляРедактирования()

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

Преимущества ЗаблокироватьДанныеДляРедактирования():

  1. Привязка к форме: Одной из ключевых особенностей является возможность привязать блокировку к идентификатору управляемой формы. Если в метод передать ИдентификаторФормы, блокировка будет установлена на все время жизни этой формы.
  2. Автоматическое снятие блокировки: Привязка к форме обеспечивает автоматическое снятие блокировки в следующих случаях:
    • При закрытии привязанной формы.
    • При завершении сеанса пользователя.
    • По прошествии некоторого времени, если используется медленное соединение (обычно 1 минута после сброса признака модифицированности формы).
    • Явным вызовом РазблокироватьДанныеДляРедактирования() с указанием того же идентификатора формы.

    Эта функциональность значительно упрощает управление блокировками и снижает риск "зависших" блокировок по сравнению с ДокументОбъект.Заблокировать().

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

Реализация блокировки в модуле формы документа с использованием ЗаблокироватьДанныеДляРедактирования():

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

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

Добавим следующий код в обработчик события формы документа ПриСозданииНаСервере:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    // Определяем ссылку на текущий документ.
    // Если документ новый (не записан), то блокировать его бесполезно,
    // так как он еще не существует в базе данных и не имеет уникальной ссылки.
    Если Объект.ЭтоНовый() Тогда
        Возврат;
    КонецЕсли;

    // Попытка установить блокировку на данные документа
    Попытка
        // Использование глобального метода ЗаблокироватьДанныеДляРедактирования()
        // Ссылка: Ссылка на блокируемый объект (в нашем случае - документ)
        // ,: Опускаем второй параметр, если не нужно блокировать записи регистра сведений
        // ЭтаФорма.УникальныйИдентификатор: Идентификатор формы, к которой привязывается блокировка
        //                             Это обеспечит автоматическое снятие блокировки при закрытии формы.
        ЗаблокироватьДанныеДляРедактирования(Объект.Ссылка, , ЭтаФорма.УникальныйИдентификатор);

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

    Исключение
        // Если произошла ошибка (например, документ уже заблокирован другим пользователем)
        ОписаниеОшибкиТекст = ОписаниеОшибки();

        // Формируем сообщение для пользователя
        СообщениеПользователю = Новый СообщениеПользователю;
        СообщениеПользователю.Текст = "Не удалось открыть документ для редактирования!" + Символы.ПС;

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

        // Устанавливаем Отказ = Истина, чтобы предотвратить открытие формы для редактирования.
        // Можно также перевести форму в режим только для просмотра.
        Отказ = Истина; 
        // Если хотим открыть в режиме просмотра, то:
        // ЭтаФорма.ТолькоПросмотр = Истина;
        // Сообщить("Документ открыт в режиме только для просмотра.");
        // Отказ = Ложь; // чтобы форма открылась
    КонецПопытки;

КонецПроцеду

Пояснение к коду:

  1. Мы проверяем, что документ не является новым, так как новый документ еще не имеет ссылки и не может быть заблокирован в базе данных.
  2. Метод ЗаблокироватьДанныеДляРедактирования() вызывается с передачей ссылки на текущий документ (Объект.Ссылка) и ЭтаФорма.УникальныйИдентификатор. Это ключевой момент, так как он связывает блокировку со временем жизни открытой формы.
  3. Блок Попытка...Исключение необходим, потому что если документ уже заблокирован другим пользователем, вызов ЗаблокироватьДанныеДляРедактирования() вызовет исключение.
  4. В блоке Исключение мы перехватываем ошибку, получаем ее описание и формируем понятное сообщение для пользователя, указывая, кто и когда заблокировал документ.
  5. Установка Отказ = Истина в обработчике ПриСозданииНаСервере предотвращает открытие формы документа. Вы также можете вместо этого установить ЭтаФорма.ТолькоПросмотр = Истина;, чтобы документ открывался в режиме чтения, но без возможности редактирования.

Шаг 2: Разблокировка при закрытии формы

Благодаря использованию ЭтаФорма.УникальныйИдентификатор в методе ЗаблокироватьДанныеДляРедактирования(), блокировка будет снята автоматически при закрытии формы. Это сильно упрощает логику, так как вам не нужно вручную вызывать РазблокироватьДанныеДляРедактирования() в обработчике ПриЗакрытииНаСервере, что минимизирует риски "зависших" блокировок.

Однако, если вы хотите иметь явный контроль или предусмотреть нестандартные сценарии, вы можете добавить вызов РазблокироватьДанныеДляРедактирования(Объект.Ссылка, , ЭтаФорма.УникальныйИдентификатор); в обработчике ПриЗакрытииНаСервере, но в большинстве случаев это будет излишним.


&НаСервере
Процедура ПриЗакрытииНаСервере()
    // Если блокировка была успешно установлена с привязкой к форме,
    // она будет снята автоматически при закрытии формы.
    // Явный вызов РазблокироватьДанныеДляРедактирования() здесь, как правило, не требуется.
    // Однако, если вы хотите явно снять блокировку или если блокировка была установлена
    // без привязки к форме (что не рекомендуется для такого сценария), можно добавить:
    
    // Если Не Объект.ЭтоНовый() Тогда
    //     РазблокироватьДанныеДляРедактирования(Объект.Ссылка, , ЭтаФорма.УникальныйИдентификатор);
    // КонецЕсли;

КонецПроцеду

Важные нюансы и дополнительные советы

Снятие "зависших" блокировок

Несмотря на автоматическое снятие блокировок, привязанных к форме, в редких случаях (например, при аварийном завершении работы сервера 1С) блокировки могут "зависнуть". Для их снятия существуют следующие способы:

  1. Завершение сеанса: Через раздел "Администрирование – Обслуживание" в конфигурации 1С можно просмотреть список активных пользователей и принудительно завершить зависший сеанс.
  2. Консоль администратора: Для клиент-серверного варианта можно использовать консоль администратора 1С для просмотра и удаления зависших сеансов.
  3. Файловый режим: В файловой информационной базе, если блокировка мешает входу в систему ("Начало сеанса с информационной базой запрещено"), можно попробовать удалить файл 1Cv8.cdn из каталога информационной базы (предварительно сделав резервную копию).

Делегирование прав и настройки

Рассмотренный механизм обеспечивает строгую блокировку для всех пользователей, имеющих право на редактирование документа. Если вам требуется более сложная логика (например, разрешение редактирования только автору документа, или определенным ролям), то эту логику нужно будет добавить поверх блокировки. Например, в том же ПриСозданииНаСервере, до попытки установки блокировки, вы можете проверить права текущего пользователя или соответствие его идентификатора автору документа и, при необходимости, сразу установить ЭтаФорма.ТолькоПросмотр = Истина;.

Альтернативные подходы к совместному редактированию

Следует отметить, что подход со строгой блокировкой "один пользователь в один момент времени" не всегда подходит для сценариев, где действительно требуется совместная работа над документом (например, над его табличными частями). Для такого "совместного редактирования" обычно используют другие механизмы:

  1. Регистр сведений "ЗанятыеДокументы": Можно создать собственный регистр сведений, в котором фиксируется, какой пользователь и когда "занял" документ. Этот подход требует полностью ручной реализации логики установки/снятия записей в регистре и постоянных проверок. Это более трудоемкий и менее надежный метод, чем встроенные блокировки платформы.
  2. Разделение данных: Если разные части документа могут редактироваться независимо, можно рассмотреть реструктуризацию данных. Например, строки табличной части вынести в отдельный подчиненный справочник или регистр сведений, что позволит разным пользователям редактировать разные строки одного и того же "главного" документа без блокировки всего объекта. Однако это требует значительных изменений в конфигурации.

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

← На главную