Как запретить ввод на основании модифицированного (несохраненного) документа в 1С?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

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

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

Анализ решения через блокировку объекта (метод с форума)

На форумах часто предлагают простой способ: перед заполнением нового документа попытаться заблокировать объект-основание в базе данных. Логика этого метода заключается в том, что если документ открыт другим пользователем или редактируется, система вызовет исключение. Давайте посмотрим, как выглядит этот код в модуле нового документа:


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

Выясним причину, почему данный метод не гарантирует 100% защиты. Метод Заблокировать() использует механизм пессимистической блокировки 1С. Эта блокировка отлично защищает объект от редактирования другими пользователями (в других сеансах). Однако, если создание на основании выполняет тот же самый пользователь, в том же самом сеансе, где у него открыта измененная форма заказа, система 1С распознает владельца транзакции и позволит повторно заблокировать объект без генерации ошибки. В итоге модифицированный документ не будет определен как заблокированный, и пользователь все равно создаст документ на основании старых данных.

Решение 1: Проверка модифицированности через форму (для обычных форм)

В Обычном приложении (ОФ) мы имеем прямой доступ к клиентским формам из кода. Если форма документа-источника открыта на клиенте, метод ПолучитьФорму() вернет нам ссылку на уже существующий и активный экземпляр этой формы. Мы можем легко проверить свойство Модифицированность этой формы напрямую.

Рассмотрим пример кода, который необходимо разместить в модуле создаваемого документа (в обработчике заполнения) или в модуле его формы перед открытием:


// В модуле создаваемого документа (например, РеализацияТоваровУслуг)
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
    
    Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
        
        СсылкаНаОснование = ДанныеЗаполнения;
        ФормаОснования = СсылкаНаОснование.ПолучитьФорму();
        
        Если ФормаОснования.Открыта() И ФормаОснования.Модифицированность Тогда
            ВызватьИсключение "Внимание! Документ-основание был изменен, но не записан. 
            |Перед созданием подчиненного документа необходимо записать изменения в Заказе покупателя!";
        КонецЕсли;
        
        // Дальнейшая логика стандартного заполнения...
        
    КонецЕсли;
    
КонецПроцедуры

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

Важный нюанс: как правильно прервать создание документа

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

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

Решение 2: Переопределение кнопки на стороне источника (Рекомендуемый UX-подход)

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

Разберем этот подход по шагам:

  1. Откройте форму документа-источника (например, ЗаказПокупателя).
  2. Скройте или отключите видимость стандартной панели «Ввести на основании».
  3. Создайте свою собственную кнопку (или меню действий) для ввода на основании.
  4. В обработчике нажатия этой кнопки пропишите интерактивный сценарий взаимодействия с пользователем.

Посмотрим на пример реализации обработчика нажатия нашей кастомной кнопки на форме ЗаказПокупателя в обычном приложении:


Процедура ДействияФормыВвестиНа основанииРеализации(Кнопка)
    
    // Шаг 1. Проверяем модифицированность текущей формы
    Если Модифицированность Тогда
        
        РежимДиалога = РежимДиалогаВопрос.ДаНетОтмена;
        ТекстВопроса = "Документ был изменен! Записать изменения перед созданием Реализации?";
        Ответ = Вопрос(ТекстВопроса, РежимДиалога);
        
        Если Ответ = КодВозвратаДиалога.Да Тогда
            // Пытаемся записать текущий документ
            Если Не ЗаписатьВФорме() Тогда
                Возврат; // Если запись не удалась (например, не заполнены реквизиты), прерываем процесс
            КонецЕсли;
        ИначеЕсли Ответ = КодВозвратаДиалога.Отмена Или Ответ = КодВозвратаДиалога.Нет Тогда
            // Если пользователь отказался или нажал Отмену - прерываем операцию ввод на основании
            Возврат;
        КонецЕсли;
        
    КонецЕсли;
    
    // Шаг 2. Если документ успешно записан или не был изменен, выполняем стандартный ввод на основании
    НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
    НовыйДокумент.Заполнить(Ссылка);
    
    // Открываем форму нового документа
    ФормаНовогоДокумента = НовыйДокумент.ПолучитьФорму();
    ФормаНовогоДокумента.Открыть();
    
КонецПроцедуры

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

Решение 3: Особенности работы в Управляемых формах (УФ)

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

Тем не менее, в управляемом интерфейсе платформа сама частично решает эту проблему:

Рассмотрим пример реализации проверки модифицированности на клиенте в Управляемых формах:


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

&НаКлиенте
Процедура ПослеВопросаОЗаписи(РезультатВопроса, ДополнительныеПараметры) Экспорт
    
    Если РезультатВопроса = КодВозвратаДиалога.Да Тогда
        // Записываем документ на сервере и в случае успеха открываем форму
        Если Записать() Тогда
            ОткрытьФормуСозданияНаОсновании();
        КонецЕсли;
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ОткрытьФормуСозданияНаОсновании()
    
    ПараметрыЗаполнения = Новый Структура("Основание", Объект.Ссылка);
    ОткрытьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", Новый Структура("ЗначенияЗаполнения", ПараметрыЗаполнения));
    
КонецПроцедуры

Использование асинхронных методов ПоказатьВопрос() и ОписаниеОповещения позволяет реализовать этот функционал в полном соответствии со стандартами разработки под управляемый интерфейс 1С.

Заключение

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

← На главную