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