Как автоматически заполнить «Вид операции» нового документа при создании из журнала в обычных формах 1С?

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

При работе в конфигурациях на обычных формах (например, Управление торговлей 10.3, Управление производственным предприятием 1.3 или Комплексная автоматизация 1.1) разработчики часто сталкиваются с ограничениями платформы при автоматическом заполнении реквизитов новых объектов. Типичный сценарий: в журнале документов настроен программный отбор по реквизиту ВидОперации с видом сравнения «В списке», содержащий несколько значений из разных перечислений (например, для документов «Реализация товаров и услуг» и «Перемещение товаров»).

Когда пользователь создает новый документ непосредственно из такого журнала, платформа 1С не может автоматически определить, какое именно значение из списка отбора сопоставить новому документу. В результате реквизит ВидОперации остается пустым. В этой статье мы подробно разберем, почему возникает эта проблема, и рассмотрим несколько надежных практических способов ее решения.

Почему не работает стандартный механизм автозаполнения?

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

  1. Отбор должен быть активен (свойство Использование установлено в значение Истина).
  2. Вид сравнения в отборе должен быть строго «Равно» (ВидСравнения.Равно).
  3. Тип значения в отборе должен совпадать с типом реквизита создаваемого документа.

Если в вашем журнале применен отбор с видом сравнения «В списке» (ВидСравнения.ВСписке), платформа не в состоянии угадать, какое именно значение из предложенного множества нужно подставить. Ситуация усложняется тем, что у документов разного вида (например, у Реализации и Перемещения) за реквизит «Вид операции» отвечают совершенно разные типы перечислений. Автозаполнение «из коробки» здесь бессильно. Нам потребуется прибегнуть к программным методам.

Ниже мы подробно разберем четыре практических метода решения этой задачи: от классического перехвата события создания до использования современных механизмов заполнения.

---

Решение 1. Перехват события «ПередНачаломДобавления» в журнале (Рекомендуемый метод)

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

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

Давайте разберем по шагам логику этого обработчика:


Процедура ДокументСписокПередНачаломДобавления(Элемент, Отказ, Копирование, Родитель, Группа)
    
    // Если пользователь копирует существующий документ, 
    // стандартное поведение платформы менять не нужно
    Если Копирование Тогда 
        Возврат; 
    КонецЕсли;
    
    // Отменяем стандартное интерактивное создание документа платформой
    Отказ = Истина; 
    
    // 1. Извлекаем программный отбор по виду операции из табличного поля
    ОтборВидОперации = Элемент.Значение.Отбор.Найти("ВидОперации");
    
    ВыбранныйВидОперации = Неопределено;
    СписокЗначенийОтбора = Новый СписокЗначений;
    
    Если ОтборВидОперации <> Неопределено И ОтборВидОперации.Использование Тогда
        Если ОтборВидОперации.ВидСравнения = ВидСравнения.ВСписке Тогда
            СписокЗначенийОтбора = ОтборВидОперации.Значение;
        ИначеЕсли ОтборВидОперации.ВидСравнения = ВидСравнения.Равно Тогда
            СписокЗначенийОтбора.Добавить(ОтборВидОперации.Значение);
        КонецЕсли;
    КонецЕсли;
    
    // 2. Интерактивно определяем, какой именно документ хочет создать пользователь.
    // Так как в журнале отображаются разные типы документов, предоставим выбор.
    СписокТипов = Новый СписокЗначений;
    СписокТипов.Добавить("РеализацияТоваровУслуг", "Реализация товаров и услуг");
    СписокТипов.Добавить("ПеремещениеТоваров", "Перемещение товаров");
    
    ВыбранныйТип = СписокТипов.ВыбратьЭлемент("Выберите вид создаваемого документа");
    Если ВыбранныйТип = Неопределено Тогда
        Возврат; // Пользователь отказался от создания
    КонецЕсли;
    
    ИмяДокумента = ВыбранныйТип.Значение;
    
    // 3. Создаем объект документа программно
    НовыйДокументОбъект = Документы[ИмяДокумента].СоздатьДокумент();
    НовыйДокументОбъект.Дата = РабочаяДата; // Устанавливаем дату по умолчанию
    
    // 4. Анализируем список отборов и ищем подходящий тип перечисления
    Для Каждого ЭлементСписка Из СписокЗначенийОтбора Цикл
        ЗначениеОтбора = ЭлементСписка.Значение;
        
        Если ИмяДокумента = "РеализацияТоваровУслуг" Тогда
            // Проверяем соответствие типу перечисления видов операций реализации
            Если ТипЗнч(ЗначениеОтбора) = Тип("ПеречислениеСсылка.ВидыОперацийРеализацииТоваровУслуг") Тогда
                ВыбранныйВидОперации = ЗначениеОтбора;
                Прервать;
            КонецЕсли;
        ИначеЕсли ИмяДокумента = "ПеремещениеТоваров" Тогда
            // Проверяем соответствие типу перечисления видов операций перемещения
            Если ТипЗнч(ЗначениеОтбора) = Тип("ПеречислениеСсылка.ВидыОперацийПеремещениеТоваров") Тогда
                ВыбранныйВидОперации = ЗначениеОтбора;
                Прервать;
            КонецЕсли;
        КонецЕсли;
    КонецДля;
    
    // 5. Заполняем реквизит документа найденным значением
    Если ВыбранныйВидОперации <> Неопределено Тогда
        НовыйДокументОбъект.ВидОперации = ВыбранныйВидОперации;
    КонецЕсли;
    
    // 6. Получаем и открываем форму нового документа
    ФормаДокумента = НовыйДокументОбъект.ПолучитьФорму("ФормаДокумента", Элемент);
    ФормаДокумента.Открыть();
    
КонецПроцедуры

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

---

Решение 2. Динамическое извлечение отбора через «ВладелецФормы» (Внутри документа)

Иногда требования архитектуры диктуют обратное: журнал документов должен оставаться максимально типовым, а логика заполнения должна находиться внутри самого документа. В этом случае мы можем проанализировать отбор журнала в процедуре ПередОткрытием формы документа.

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

Поместите следующий код в модуль формы документа (например, Реализации) в процедуру ПередОткрытием:


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

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

---

Решение 3. Использование механизма «ОбработкаЗаполнения» (Стандарт платформы 8.2 и выше)

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

Для реализации этого архитектурно правильного паттерна мы разделим работу на два этапа:

Шаг 1. Передача параметров из формы списка журнала

В модуле формы журнала документов переопределим создание объекта и передадим параметры заполнения в структуру:


Процедура ДокументСписокПередНачаломДобавления(Элемент, Отказ, Копирование, Родитель, Группа)
    
    Если Копирование Тогда 
        Возврат; 
    КонецЕсли;
    
    Отказ = Истина;
    
    // Вычисляем нужный ВидОперации на основании отборов (аналогично первому решению)
    НужныйВидОперации = Неопределено;
    ОтборВидОперации = Элемент.Value.Отбор.Найти("ВидОперации"); // Для обычных форм обращение идет через Значение/Value
    
    Если ОтборВидОперации <> Неопределено И ОтборВидОперации.Использование Тогда
        Если ОтборВидОперации.ВидСравнения = ВидСравнения.ВСписке И ОтборВидОперации.Значение.Количество() > 0 Тогда
            // Извлекаем первое значение подходящего типа
            НужныйВидОперации = ОтборВидОперации.Значение[0].Значение;
        КонецЕсли;
    КонецЕсли;
    
    // Формируем структуру параметров заполнения
    ПараметрыЗаполнения = Новый Структура;
    ПараметрыЗаполнения.Вставить("ВидОперации", НужныйВидОперации);
    
    // Создаем документ и инициируем обработку заполнения
    НовыйДок = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
    НовыйДок.Заполнить(ПараметрыЗаполнения); // Вызов обработчика в модуле объекта
    
    // Открываем форму для пользователя
    ФормаДок = НовыйДок.ПолучитьФорму("ФормаДокумента", Элемент);
    ФормаДок.Открыть();
    
КонецПроцедуры

Шаг 2. Прием параметров в Модуле объекта документа

Откроем модуль объекта документа (например, "РеализацияТоваровУслуг") и создадим стандартный обработчик события ОбработкаЗаполнения:


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

Этот подход признан эталонным в современной методологии разработки 1С. Логика инициализации данных инкапсулирована непосредственно внутри объекта бизнес-логики (в модуле объекта), что предотвращает дублирование кода при создании документов из разных мест системы (обработок, интеграций, других журналов).

---

Решение 4. Использование механизма оповещений (Слабая связь форм)

В процессе обсуждения на форуме (сообщение №8) был предложен интересный вариант взаимодействия форм с использованием механизма Оповестить и ОбработкаОповещения. Этот способ реализует концепцию "слабой связи" (Loose Coupling), когда формы общаются друг с другом посредством глобальных сигналов платформы.

Схема взаимодействия выглядит следующим образом:

  1. При открытии формы нового документа посылается глобальное оповещение о том, что документу требуются параметры отбора.
  2. Форма журнала перехватывает это событие, считывает свои текущие настройки отбора и отправляет ответное оповещение с данными отбора.
  3. Форма документа принимает ответное оповещение и заполняет реквизит ВидОперации.

Давайте посмотрим на программную реализацию этой схемы.

В модуле формы документа пропишем следующий код:


Процедура ПередОткрытием(Отказ, СтандартнаяОбработка)
    Если ЭтоНовый Тогда
        // Посылаем сигнал всем открытым формам: "Мне нужны отборы!"
        Оповестить("ЗапросОтбораЖурнала");
    КонецЕсли;
КонецПроцедуры

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

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


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

Анализ метода: Несмотря на оригинальность, данный метод имеет существенный недостаток. Оповещения рассылаются абсолютно всем открытым в данный момент формам в сеансе пользователя. Если у пользователя открыто несколько разных журналов документов, может возникнуть коллизия, когда на запрос новой формы ответят сразу несколько журналов с разными настройками отборов. Из-за этого метод оповещений рекомендуется использовать с осторожностью.

---

Какое решение выбрать?

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

← На главную