Как реализовать ввод на основании массива документов и настроить структуру подчиненности через расширение в 1С:ERP?

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

При разработке в крупных конфигурациях на платформе 1С:Предприятие 8.3, таких как 1С:ERP Управление предприятием или 1С:Комплексная автоматизация, программисты часто сталкиваются с необходимостью автоматизации ввода одних документов на основании других. Стандартный механизм платформы отлично справляется с задачей, когда один документ создается на основании другого единичного документа. Но что делать, если перед нами стоит задача создать один итоговый документ (например, ЗаказПоставщику) на основании целого массива документов-источников (например, нескольких документов ПланЗакупок)?

В этой статье мы подробно разберем, как решить две ключевые проблемы, возникающие при реализации такой задачи через расширение конфигурации:

  1. Как корректно связать созданный документ с несколькими исходными документами, чтобы вся цепочка отображалась в стандартном отчете «Структура подчиненности» (связанные документы).
  2. Как правильно интегрировать команду создания на основании в типовое динамическое меню «Создать на основании», избежав появления «одиноких» лишних кнопок на командной панели формы.

Часть 1. Настройка структуры подчиненности для нескольких оснований

Стандартный реквизит ДокументОснование, присутствующий во многих типовых документах, имеет простой ссылочный тип. Это значит, что в него физически невозможно записать более одного документа-источника. Если мы создаем один ЗаказПоставщику на основании пяти документов ПланЗакупок, запись ссылки только на один из них в этот реквизит приведет к потере связи с остальными четырьмя. В отчете «Связанные документы» мы увидим только одну цепочку.

Для решения этой проблемы мы применим подход с использованием табличной части документа-приемника. Рассмотрим этот процесс по шагам.

Шаг 1. Создание табличной части в расширении

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

Шаг 2. Добавление реквизита в критерий отбора «СвязанныеДокументы»

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

  1. Найдем в основной конфигурации критерий отбора СвязанныеДокументы и добавим его в наше расширение.
  2. Перейдем на вкладку «Состав» этого критерия отбора в расширении.
  3. Найдем в списке наш документ ЗаказПоставщику, развернем его табличные части, найдем созданную нами ТЧ ДокументыОснования и установим флажок напротив реквизита ДокументОснование.

Теперь платформа при формировании дерева связанных документов будет анализировать не только стандартный реквизит шапки, но и строки нашей новой табличной части. Все указанные там документы ПланЗакупок станут полноправными участниками структуры подчиненности.

Шаг 3. Регистрация документа в подсистеме БСП

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


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

Часть 2. Правильная интеграция динамической команды создания на основании

Частой ошибкой разработчиков является создание обычной Общей команды в дереве метаданных расширения и ручная настройка ее свойства «Группа» в значение «Командная панель формы.Создать на основании». В БСП меню «Создать на основании» формируется динамически на клиенте при создании формы. Физическое добавление команды в метаданных в эту группу приведет к тому, что кнопка отобразится отдельно на командной панели, нарушая интерфейсные стандарты решений фирмы 1С.

Правильный путь — зарегистрировать команду программно в модуле менеджера документа-источника (в нашем случае это ПланЗакупок).

Рассмотрим, как это реализовать через расширение. Добавим в расширение модуль менеджера документа ПланЗакупок и переопределим процедуру ДобавитьКомандыСозданияНаОсновании:


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

Разберем ключевые свойства программной команды:

Часть 3. Реализация клиентского обработчика в общем модуле

Теперь нам необходимо создать общий модуль в расширении. Назовем его хх_РаботаСДокументамиКлиент, установим у него свойства «Клиент (управляемое приложение)». Внутри этого модуля напишем экспортную процедуру-обработчик:


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

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

Часть 4. Обработка заполнения на сервере в документе-приемнике

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

Добавим в расширение модуль объекта документа ЗаказПоставщику и напишем обработчик:


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

Вспомогательный метод ЗаполнитьТоварыПоПлануЗакупок может выглядеть следующим образом (в зависимости от ваших бизнес-требований к переносу табличных частей):


Процедура ЗаполнитьТоварыПоПлануЗакупок(ПланЗакупокСсылка)
    
    // Пример запроса для получения спецификации или товаров из Плана закупок
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ПланЗакупокТовары.Номенклатура КАК Номенклатура,
        |	ПланЗакупокТовары.Количество КАК Количество,
        |	ПланЗакупокТовары.Цена КАК Цена
        |ИЗ
        |	Документ.ПланЗакупок.Товары КАК ПланЗакупокТовары
        |ГДЕ
        |	ПланЗакупокТовары.Ссылка = &Ссылка";
    
    Запрос.УстановитьПараметр("Ссылка", ПланЗакупокСсылка);
    РезультатЗапроса = Запрос.Выполнить();
    
    Если Не РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            
            // Ищем, нет ли уже такой номенклатуры в заказе, чтобы объединить строки
            ПараметрыПоиска = Новый Структура("Номенклатура", Выборка.Номенклатура);
            НайденныеСтроки = Товары.НайтиСтроки(ПараметрыПоиска);
            
            Если НайденныеСтроки.Количество() > 0 Тогда
                СтрокаТовара = НайденныеСтроки[0];
                СтрокаТовара.Количество = СтрокаТовара.Количество + Выборка.Количество;
            Иначе
                СтрокаТовара = Товары.Добавить();
                СтрокаТовара.Номенклатура = Выборка.Номенклатура;
                СтрокаТовара.Количество = Выборка.Количество;
                СтрокаТовара.Цена = Выборка.Цена;
            КонецЕсли;
            
            // Рассчитываем сумму и другие зависимые реквизиты строки
            СтрокаТовара.Сумма = СтрокаТовара.Количество * СтрокаТовара.Цена;
            
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

Заключение

Комбинируя возможности платформы и стандарты БСП, мы получили чистое и расширяемое решение. Мы избежали изменения типовой конфигурации на уровне кода, сохранив полную поддержку обновлений. Теперь пользователи могут выделить в списке несколько планов закупок, нажать стандартную кнопку «Создать на основании» -> «Заказ поставщику (на основании нескольких планов)», и система автоматически создаст один общий заказ, заполнив его спецификацию и сохранив корректные ссылки на все документы-основания в структуре подчиненности.

← На главную