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