Как создать несколько документов на основании табличной части одного документа в 1С?

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

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

Ограничения стандартного механизма «Ввод на основании»

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

Решение 1: Создание документов через команду на форме

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

Пошаговая реализация команды на форме

  1. Создание команды на форме:

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

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

    
    &НаКлиенте
    Процедура СоздатьДокументыПоТаблице(Команда)
        СоздатьДокументыПоТаблицеНаСервере();
    КонецПроцедуры
    
  2. Реализация серверной процедуры:

    Теперь создайте серверную процедуру СоздатьДокументыПоТаблицеНаСервере(). Именно здесь будет происходить основная логика.

    
    &НаСервере
    Процедура СоздатьДокументыПоТаблицеНаСервере()
        // Шаг 1: Проверка сохранения исходного документа
        // Перед началом работы с данными табличной части, убедитесь, что документ-основание записан.
        // Это гарантирует, что мы работаем с актуальными данными, а не с несохраненными изменениями.
        Если Объект.ЭтоНовый() Или Объект.Модифицирован Тогда
            Сообщить("Сначала необходимо записать документ-основание для работы с его актуальными данными.", СтатусСообщения.Внимание);
            Возврат;
        КонецЕсли;
    
        // Шаг 2: Итерация по строкам табличной части
        // Мы будем перебирать каждую строку табличной части документа-основания.
        // Предположим, что имя вашей табличной части — 'Таблица'. Замените его на актуальное имя.
        Для Каждого СтрокаТЧ Из Объект.Таблица Цикл 
            Попытка
                // Шаг 3: Создание нового документа
                // Для каждой строки табличной части создаем новый экземпляр документа "Входной" (или ваше имя документа).
                НовыйДокументВК = Документы.Входной.СоздатьДокумент();
    
                // Шаг 4: Заполнение реквизитов нового документа
                // Заполнение реквизитов нового документа - ключевой момент. 
                // Мы можем копировать данные как из шапки документа-основания, так и из текущей строки табличной части.
    
                // 4.1. Заполнение из шапки документа-основания
                // Используем метод ЗаполнитьЗначенияСвойств для быстрого копирования одноименных реквизитов.
                // Например, если в документе "Входной" есть реквизиты 'Объект', 'Составил', 'Дата', 
                // и в документе "ПриходДокументов" есть 'ОбъектСтроительство', 'Ответственный', 'Дата', 
                // то ЗаполнитьЗначенияСвойств поможет перенести данные. 
                // Указываем список свойств через запятую.
                // Если имена не совпадают, используем прямое присвоение.
    
                // Пример с ЗаполнитьЗначенияСвойств:
                // Предполагаем, что НовыйДокументВК имеет реквизиты 'Дата', 'Комментарий', 'Обьект', 'Составил'
                // и они должны быть заполнены из одноименных или похожих реквизитов 'Дата', 'Комментарий', 'ОбьектСтроительство', 'Ответственный' документа-основания.
                // В данном примере, поскольку ОбьектСтроительство и Обьект могут иметь разные типы/имена,
                // лучше заполнять их явно. Для Дата и Комментарий можно использовать ЗаполнитьЗначенияСвойств.
                ЗаполнитьЗначенияСвойств(НовыйДокументВК, Объект, "Дата, Комментарий"); 
    
                // 4.2. Прямое присвоение для специфических или разноименных реквизитов
                // Заполняем реквизит 'Обьект' нового документа из 'ОбьектСтроительство' документа-основания.
                НовыйДокументВК.Обьект = Объект.ОбьектСтроительство; 
                // Заполняем реквизит 'Составил' нового документа из 'Ответственный' документа-основания.
                НовыйДокументВК.Составил = Объект.Ответственный; 
    
                // 4.3. Заполнение из текущей строки табличной части
                // Эти данные уникальны для каждого нового документа.
                // Предположим, в табличной части документа-основания есть колонки 'Номенклатура' и 'Количество'.
                НовыйДокументВК.Номенклатура = СтрокаТЧ.Номенклатура; 
                НовыйДокументВК.Количество = СтрокаТЧ.Количество; 
                // ... Добавьте другие реквизиты, которые нужно заполнить из СтрокаТЧ
                // Например, если в ТЧ есть поле 'ЕдиницаИзмерения', а в новом документе есть 'Единица'
                // НовыйДокументВК.Единица = СтрокаТЧ.ЕдиницаИзмерения;
    
                // 4.4. Заполнение реквизитов фиксированными значениями или перечислениями
                НовыйДокументВК.СостояниеАкта = Перечисления.Состояние.Новый; // Пример заполнения перечислением
    
                // Шаг 5: Запись созданного документа
                // После заполнения всех необходимых реквизитов, записываем новый документ в базу данных.
                НовыйДокументВК.Записать();
    
                // Шаг 6: Информирование пользователя
                // Выводим сообщение об успешном создании документа.
                Сообщить("Документ входного контроля №" + НовыйДокументВК.Номер + " от " + НовыйДокументВК.Дата + " успешно создан.");
    
                // Опционально: Открытие формы созданного документа
                // Если необходимо сразу открыть форму каждого созданного документа для просмотра или редактирования.
                // Будьте осторожны с этой функцией при большом количестве документов, так как это может замедлить работу.
                // ОткрытьФорму("Документ.Входной.ФормаДокумента", Новый Структура("Ключ", НовыйДокументВК.Ссылка));
    
            Исключение
                // Шаг 7: Обработка ошибок
                // Важно предусмотреть обработку ошибок на случай, если какой-либо документ не удалось создать или записать.
                // Это позволяет продолжить процесс для других строк и информировать пользователя о проблемах.
                Сообщить("Не удалось создать документ входного контроля для строки " + СтрокаТЧ.НомерСтроки + " (" + СтрокаТЧ.Номенклатура + "): " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
            КонецПопытки;
        КонецЦикла;
    
        Сообщить("Процесс создания документов входного контроля завершен.");
    КонецПроцедуры
    

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

Метод ЗаполнитьЗначенияСвойств()

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

Пример использования:


// Копируем все одноименные реквизиты из 'Источник' в 'Приемник'
ЗаполнитьЗначенияСвойств(Приемник, Источник);

// Копируем только указанные реквизиты 'Дата' и 'Сумма'
ЗаполнитьЗначенияСвойств(Приемник, Источник, "Дата, Сумма");

// Копируем все, кроме 'Автор' и 'Комментарий'
ЗаполнитьЗначенияСвойств(Приемник, Источник, , "Автор, Комментарий");

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

Решение 2: Использование дополнительной внешней обработки

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

Принцип работы

  1. Создание обработки: Разработайте внешнюю обработку (файл .epf).
  2. Интерфейс обработки: В обработке может быть форма, где пользователь выбирает документ-основание, задает дополнительные параметры (например, префикс для новых документов, период создания и т.д.) и нажимает кнопку «Создать документы».
  3. Логика создания: В модуле обработки реализуется та же логика перебора табличной части и создания документов, что и в команде формы, но уже с учетом выбранных пользователем параметров.
  4. Подключение в конфигурацию: Зарегистрируйте эту обработку в конфигурации как дополнительную обработку (через раздел «Администрирование» -> «Печатные формы, отчеты и обработки» -> «Дополнительные отчеты и обработки»). После этого она может быть доступна из списка связанных обработок документа-основания.

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

Решение 3: Массовое создание документов с использованием фоновых заданий

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

Пошаговая реализация фонового задания

  1. Размещение логики в общем модуле:

    Вынесите всю логику создания документов в отдельную экспортную процедуру или функцию в общем модуле. Общий модуль должен быть с флагами «Сервер» и «Вызов сервера».

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

    Из команды на форме (или из внешней обработки) вызываем эту процедуру как фоновое задание. Для этого используем методы объекта ФоновыеЗадания.

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

Использование фоновых заданий особенно удобно для:

Обеспечение производительности и целостности данных

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

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

← На главную