Копия документа из кода 1С: Скопировать(), ручной перенос реквизитов и движения
Содержание
Чтобы скопировать документ
программно в 1С, получите объект документа и вызовите у него Скопировать(): НовыйДок = Ссылка.ПолучитьОбъект().Скопировать(). Затем задайте дату, установите новый номер и запишите копию через Записать(). Реквизиты и табличные части перенесутся сами, движения — нет.
Это работает на сервере в любой конфигурации на платформе 8.3, потому что метод платформенный. Ниже код для трёх случаев: полная копия, копия с выборочным переносом полей и копия с движениями. Отдельно — как открыть копию в форме и как не уронить базу при копировании тысяч документов.
Какие есть способы получить копию документа
Способов три, и они не взаимозаменяемы. Выбор зависит от того, нужна ли точная копия или документ «по мотивам» исходного.
| Способ | Когда подходит | Что учесть |
|---|---|---|
ДокументОбъект.Скопировать() | Нужна копия того же вида документа со всеми данными | Срабатывает обработчик ПриКопировании в модуле объекта |
СоздатьДокумент() + ЗаполнитьЗначенияСвойств() | Часть полей переносить не нужно или вид документа другой | Табличные части переносите отдельно |
Параметр формы ЗначениеКопирования | Пользователь должен увидеть копию и сам решить, записывать ли её | Копия живёт в форме до записи |
Есть и четвёртый вариант — ввод на основании через обработку заполнения. Но это уже не копия: там срабатывает ОбработкаЗаполнения, и новый документ заполняется по своим правилам.
Отдельного метода ЗаписатьНовый() у объекта документа нет. Новый объект пишется тем же Записать(), а проверить, что он ещё не записан, можно через ЭтоНовый().
Все три способа требуют серверного контекста. На клиенте нет ДокументОбъект, есть только данные формы. Поэтому код копирования кладите в общий модуль с флагом «Сервер», в модуль менеджера или в серверную процедуру формы.
Копирование документа методом Скопировать(): пошаговый алгоритм
Метод вызывается у объекта, а не у ссылки. Вызов Ссылка.Скопировать() упадёт с ошибкой «Метод объекта не обнаружен
».
- Получите объект:
Источник = Ссылка.ПолучитьОбъект(). - Создайте копию:
Копия = Источник.Скопировать(). Объект копии новый, в базе его ещё нет. - Задайте дату. Иначе останется дата исходного документа.
- Очистите номер и вызовите
УстановитьНовыйНомер(). Так копия гарантированно получит свободный номер из нумератора. - Поправьте реквизиты, которые не должны переезжать: комментарий, документ-основание, ответственного.
- Запишите:
Копия.Записать(РежимЗаписиДокумента.Запись)или с проведением.
Готовая функция для общего модуля:
// Возвращает ссылку на записанную копию документа.
// Проводить - Булево: провести копию сразу после записи.
Функция СкопироватьДокумент(ДокументСсылка, Проводить = Ложь) Экспорт
Источник = ДокументСсылка.ПолучитьОбъект();
Копия = Источник.Скопировать();
Копия.Дата = ТекущаяДатаСеанса();
Копия.Номер = "";
Копия.УстановитьНовыйНомер();
Копия.Комментарий = СтрШаблон("Копия документа %1 от %2",
Источник.Номер, Формат(Источник.Дата, "ДФ=dd.MM.yyyy"));
Если Проводить Тогда
Копия.Записать(РежимЗаписиДокумента.Проведение);
Иначе
Копия.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Возврат Копия.Ссылка;
КонецФункции
Реквизит Комментарий есть почти во всех типовых документах. Если в вашем документе его нет, уберите эту строку.
Обработчик ПриКопировании в модуле объекта
Скопировать() вызывает в модуле объекта копии событие ПриКопировании(ОбъектКопирования). То же событие срабатывает при интерактивном копировании кнопкой в списке. Это правильное место для правил «что не копировать»:
Процедура ПриКопировании(ОбъектКопирования)
ДокументОснование = Неопределено;
Ответственный = Пользователи.ТекущийПользователь();
Для Каждого Строка Из Товары Цикл
Строка.КоличествоОтгружено = 0;
КонецЦикла;
КонецПроцедуры
Код условный: Пользователи.ТекущийПользователь() — функция БСП, реквизиты табличной части у вас свои. Логика простая: всё, что пишется в ПриКопировании, применится и к программной, и к ручной копии. Не дублируйте эти правила в коде вызова.
В типовых конфигурациях ПриКопировании уже заполнен. Перед доработкой откройте его в конфигураторе и посмотрите, что очищается. Иначе ваш код может вернуть значения, которые разработчики намеренно сбрасывают.
Перенос реквизитов и табличных частей вручную
Ручной перенос нужен в двух случаях. Первый — копия в документ другого вида, например из «Заказа клиента» в «Счёт на оплату». Второй — когда ПриКопировании делает лишнее, а трогать его нельзя.
Шапку переносят функцией ЗаполнитьЗначенияСвойств. Четвёртый параметр — список свойств, которые нужно исключить:
Функция КопияВручную(ДокументСсылка) Экспорт
Источник = ДокументСсылка.ПолучитьОбъект();
Копия = Документы.ЗаказПоставщику.СоздатьДокумент();
ЗаполнитьЗначенияСвойств(Копия, Источник, ,
"Номер, Дата, Проведен, ПометкаУдаления, ДокументОснование");
Копия.Дата = ТекущаяДатаСеанса();
Копия.УстановитьНовыйНомер();
// Табличные части: целиком, через таблицу значений
Копия.Товары.Загрузить(Источник.Товары.Выгрузить());
Копия.Услуги.Загрузить(Источник.Услуги.Выгрузить());
Копия.Записать(РежимЗаписиДокумента.Запись);
Возврат Копия.Ссылка;
КонецФункции
Если вид документа другой, колонки табличных частей не совпадут. Тогда переносите построчно:
Для Каждого СтрокаИсточника Из Источник.Товары Цикл
НоваяСтрока = Копия.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточника, , "НомерСтроки");
КонецЦикла;
ЗаполнитьЗначенияСвойств переносит только совпадающие по имени поля. Поле с другим именем в приёмнике останется пустым, ошибки не будет. Это частая причина «потерянной» суммы или ставки НДС: проверьте имена колонок в обоих документах.
Что делать со связанными табличными частями
Бывает, что строки двух табличных частей связаны ключом: товары и серийные номера, товары и коды маркировки. При Скопировать() и при Загрузить() значения ключей переносятся как есть, и связь сохраняется.
Связь рвётся, если строки переносить построчно с пересчётом ключа. В таком случае пересчитывайте ключ сразу в обеих табличных частях, по одному соответствию «старый ключ → новый ключ».
Движения документа при копировании
Ни Скопировать(), ни ЗаполнитьЗначенияСвойств движения не переносят. Движения принадлежат регистратору, а у копии регистратор другой.
Есть два пути.
- ✅ Провести копию.
Записать(РежимЗаписиДокумента.Проведение)вызоветОбработкаПроведения, и движения сформируются по правилам документа. Это правильный путь почти всегда. - 🔧 Перенести движения как есть. Нужно для документов с ручными движениями, например «Операция» в бухгалтерии, или если алгоритм проведения изменился, а нужна точная копия старых записей.
Код для второго случая. Сначала документ записывается, чтобы у него появилась ссылка, затем копируется каждый набор записей:
Процедура СкопироватьДвижения(Источник, Копия)
Для Каждого НаборИсточника Из Источник.Движения Цикл
ИмяРегистра = НаборИсточника.Метаданные().Имя;
НаборИсточника.Прочитать();
Если НаборИсточника.Количество() = 0 Тогда
Продолжить;
КонецЕсли;
НаборКопии = Копия.Движения[ИмяРегистра];
НаборКопии.Отбор.Регистратор.Установить(Копия.Ссылка);
Для Каждого Запись Из НаборИсточника Цикл
НоваяЗапись = НаборКопии.Добавить();
ЗаполнитьЗначенияСвойств(НоваяЗапись, Запись, , "Регистратор, НомерСтроки");
НоваяЗапись.Регистратор = Копия.Ссылка;
НоваяЗапись.Период = Копия.Дата;
КонецЦикла;
НаборКопии.Записать();
КонецЦикла;
КонецПроцедуры
Вызывайте процедуру после Копия.Записать(РежимЗаписиДокумента.Запись) и внутри одной транзакции с записью документа.
Скопированные движения без проведения не делают документ проведённым: флаг Проведен останется Ложь, а записи в регистрах уже будут. При следующем перепроведении такие движения перезапишутся. Используйте этот приём только для документов, у которых проведение не формирует движения само.
Отдельная опасность — копия, проведённая в рабочей базе. Она задваивает остатки и обороты так же, как реальный документ. Если копия создавалась для теста, после проверки отмените её проведение и пометьте на удаление.
Копия документа в управляемой форме
Пользователю часто нужна не записанная копия, а открытая форма с данными исходного документа. Для этого есть стандартный параметр формы ЗначениеКопирования:
&НаКлиенте
Процедура СкопироватьВФорме(Команда)
ПараметрыФормы = Новый Структура("ЗначениеКопирования", Объект.Ссылка);
ОткрытьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", ПараметрыФормы);
КонецПроцедуры
Форма откроется с новым документом. Платформа сама вызовет Скопировать() у исходного объекта, а значит отработает и ПриКопировании. Номер и ссылки в форме не будет, пока пользователь не запишет документ.
В ПриСозданииНаСервере формы признак копирования проверяется так:
Если ЗначениеЗаполнено(Параметры.ЗначениеКопирования) Тогда
// форма открыта как копия: дозаполняем поля
КонецЕсли;
Если копию нужно записать без открытия формы, например кнопкой в списке, вызывайте сервер. Клиентская процедура передаёт только ссылки:
&НаКлиенте
Процедура СоздатьКопии(Команда)
Ссылки = Элементы.Список.ВыделенныеСтроки;
СоздатьКопииНаСервере(Ссылки);
Элементы.Список.Обновить();
КонецПроцедуры
&НаСервереБезКонтекста
Процедура СоздатьКопииНаСервере(Ссылки)
Для Каждого Ссылка Из Ссылки Цикл
КопированиеДокументов.СкопироватьДокумент(Ссылка);
КонецЦикла;
КонецПроцедуры
Здесь КопированиеДокументов — имя вашего общего модуля с функцией из первого раздела. Серверный вызов без контекста не тянет на сервер данные формы и работает быстрее.
В динамическом списке с группировками ВыделенныеСтроки может вернуть не только ссылки. Перед копированием проверьте тип: ТипЗнч(Ссылка) = Тип("ДокументСсылка.РеализацияТоваровУслуг").
Ошибки при копировании документов кодом
Ещё одна неочевидная ошибка — копирование с ОбменДанными.Загрузка = Истина. В этом режиме не срабатывают ПередЗаписью, ПриЗаписи и проверка заполнения. Копия запишется быстро, но без служебных реквизитов, которые типовой код заполняет в этих обработчиках. Проведение в режиме загрузки тоже не выполняется.
Похожая ловушка — копия документа с другим видом операции. В типовых документах вид операции определяет, какие реквизиты и табличные части заполнены. Если поменять его у копии после Скопировать(), в документе останутся данные от старого вида операции. Проверка заполнения их не заметит, а проведение даст неожиданные записи.
Если копирование вызывается из регламентного задания, учтите права. Задание работает от имени пользователя, указанного в его настройках. Без прав на добавление документа запись упадёт с ошибкой нарушения прав доступа.
Массовое копирование: как не перегрузить базу
Копирование сотен документов в цикле упирается в три вещи: число транзакций, проведение и чтение объектов.
- 📦 Транзакции пачками. Одна транзакция на каждый документ — медленно, одна на десять тысяч — долгие блокировки. Рабочая середина: 50–200 документов в одной транзакции.
- ⏳ Проведение отдельно. Сначала запишите копии без проведения, затем проведите их вторым проходом в хронологическом порядке. Так проще найти документ, на котором упало проведение.
- 🔍 Меньше
ПолучитьОбъект(). Если реквизиты нужны для фильтрации, получите их одним запросом, а объект открывайте только у тех документов, которые действительно копируете. - 🧵 Фоновое задание. Долгую операцию запускайте через
ФоновыеЗадания.Выполнить(), чтобы пользователь не ждал с зависшим окном.
Каркас пачечной записи:
РазмерПачки = 100;
Счетчик = 0;
НачатьТранзакцию();
Попытка
Для Каждого Ссылка Из СписокСсылок Цикл
Копия = Ссылка.ПолучитьОбъект().Скопировать();
Копия.Номер = "";
Копия.УстановитьНовыйНомер();
Копия.Записать(РежимЗаписиДокумента.Запись);
Счетчик = Счетчик + 1;
Если Счетчик % РазмерПачки = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
При ошибке откатится только текущая пачка. Уже зафиксированные копии останутся в базе, поэтому ведите журнал созданных ссылок или пишите их в комментарий, чтобы при повторном запуске не создать дубли.
Массовое копирование в рабочей базе делайте после резервной копии и в нерабочее время. Пачечные транзакции блокируют регистры и нумераторы, и пользователи увидят ожидание на записи документов того же вида.
Проведение пачек тоже лучше вести в хронологии. Если копии датированы задним числом, в типовых конфигурациях может понадобиться последовательное перепроведение за период, иначе себестоимость и остатки разойдутся.
Частые вопросы
Копируются ли присоединённые файлы при Скопировать()?
Можно ли скопировать документ в другую базу кодом?
Сохраняется ли дата исходного документа?
Как в ПередЗаписью понять, что пишется программная копия?
Копия.ДополнительныеСвойства.Вставить("ЭтоКопия", Истина). В обработчиках модуля объекта проверьте её через ДополнительныеСвойства.Свойство(). Метка живёт только в памяти и в базу не пишется.