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

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

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

Разберем типичную проблему: при попытке скопировать документ через метод Скопировать() в цикле разработчики часто сталкиваются со сложностями фильтрации строк и корректного сохранения данных. Рассмотрим типовой рабочий алгоритм и важные нюансы платформы 1С:Предприятие при реализации такой логики.

Анализ проблемы и логика решения

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

Оптимальная логика расщепления строится по следующим шагам:

  1. Получаем объект исходного документа и организуем цикл по строкам его табличной части Товары.
  2. На каждой итерации создаем новый документ-копию методом Д.Скопировать().
  3. Очищаем табличную часть нового документа методом Очистить(), чтобы избавиться от всех продублированных позиций.
  4. Добавляем в табличную часть нового документа одну пустую строку через Добавить().
  5. Копируем реквизиты текущей обрабатываемой строки исходного документа в новую строку с помощью процедуры ЗаполнитьЗначенияСвойств().
  6. Корректируем дату, специфические реквизиты шапки и записываем созданный документ.

Реализация через обход строк табличной части

Посмотрим на практический пример кода обработки, выполняющей разделение документов по отмеченным строкам списка:


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

Рассмотрим подробнее преимущества такого подхода:

  1. Простота и лаконичность: конструкция Для Каждого ... Из Д.Товары избавляет от необходимости вручную объявлять числовые счетчики, контролировать индексы массивов и уменьшать переменные шага цикла.
  2. Точное соответствие типов: процедура ЗаполнитьЗначенияСвойств() автоматически сопоставляет и переносит все одноименные реквизиты строки (номенклатуру, количество, цену, ставку НДС и другие), исключая ручное перечисление полей.

Альтернативный вариант: создание документа без полного копирования

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


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

Важные технические нюансы при расщеплении документов

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

  1. Пересчет сумм шапки: При вызове Скопировать() реквизиты шапки (например, СуммаДокумента, СуммаВключаетНДС) сохраняют старые значения всего исходного документа. Если конфигурация хранит итоговые суммы в шапке, после оставления одной строки обязательно вызовите типовой пересчет итогов документа или рассчитайте сумму строки вручную перед записью.
  2. Подчиненные табличные части: Если документ использует связанные табличные части (например, серии, акцизные марки или штрихкоды, привязанные к строке по полю КлючСвязи), метод Скопировать() продублирует их полностью. Их также потребуется корректно очистить или отфильтровать по ключу переносимой строки.
  3. Транзакционность записи: Чтобы избежать ситуации, когда при ошибке на последней строке в базе останется только часть созданных документов, групповую запись рекомендуется оборачивать в транзакцию с помощью методов НачатьТранзакцию(), ЗафиксироватьТранзакцию() и блока Попытка ... Исключение.

Ограничение изменения исходного документа

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

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

  1. Программный запрет в обработчиках: установка флага или статуса в документе с последующей проверкой в событии ПередЗаписью модуля объекта или в подписке на событие (с установкой Отказ = Истина).
  2. Интерфейсный запрет: установка свойства ЭтаФорма.ТолькоПросмотр = Истина в процедуре ПриЧтенииНаСервере модуля формы документа.
  3. Административный запрет: настройка прав доступа (ролей/RLS) или использование механизма даты запрета изменения данных.
← На главную