В процессе интеграции и обмена данными между различными конфигурациями «1С:Предприятие» (например, при синхронизации «1С:Бухгалтерии» с внешней системой управления закупками) часто возникает задача: получить реестр платежей из одной базы, сопоставить его с документами в текущей базе и пакетно обновить их статусы. Типичный пример — проставить флаг «Оплачено» у заявок на закупку на основании выгруженной таблицы значений.
В этой статье мы подробно разберем, как правильно реализовать данный алгоритм на платформе «1С:Предприятие 8». Мы проанализируем различные подходы, выявим типичные ошибки производительности и напишем оптимальный, отказоустойчивый код с использованием временных таблиц и механизмов пакетной записи объектов.
Представим, что у нас на входе есть таблица значений ТЗИзБухгалтерии с колонками Номер (строка) и Состояние (булево, где ИСТИНА означает успешную оплату).
---
Первая идея, которая часто возникает у начинающих разработчиков — просто получить все документы базы данных циклом, а затем для каждого документа искать соответствие в таблице значений с помощью метода Найти(). Выглядит это примерно так:
// НЕПРАВИЛЬНЫЙ И НЕОПТИМАЛЬНЫЙ ПОДХОД!
Выборка = Документы.ЗаявкаНаЗакупку.Выбрать();
Пока Выборка.Следующий() Цикл
СтрокаСостояния = ТЗИзБухгалтерии.Найти(Выборка.Номер, "Номер");
Если СтрокаСостояния <> Неопределено Тогда
Если СтрокаСостояния.Состояние Тогда
ДокОбъект = Выборка.ПолучитьОбъект();
ДокОбъект.Оплачено = Истина;
ДокОбъект.Записать();
КонецЕсли;
КонецЕсли;
КонецЦикла;
Давайте выясним причину, почему такой подход считается плохой практикой (anti-pattern):
Найти() по неиндексированной таблице значений при больших объемах данных работает крайне медленно.---
Для минимизации обращений к базе данных и ускорения работы сопоставление информации необходимо переложить на сторону СУБД. Для этого мы поместим нашу таблицу значений ТЗИзБухгалтерии во временную таблицу внутри запроса, проиндексируем ее по ключевому полю и свяжем с реальной таблицей документов.
Рассмотрим по шагам, как это реализовать.
Платформа 1С не позволит передать таблицу значений в качестве параметра запроса, если у её колонок явно не заданы типы данных. Перед отправкой ТЗ в запрос мы должны убедиться, что типы жестко определены:
// Создаем типизированную таблицу значений для запроса
ТЗДляЗапроса = Новый ТаблицаЗначений;
ТЗДляЗапроса.Колонки.Добавить("Номер", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(20)));
ТЗДляЗапроса.Колонки.Добавить("Состояние", Новый ОписаниеТипов("Булево"));
// Заполняем её данными из нашего источника
Для Каждого СтрокаИсточника Из ТЗИзБухгалтерии Цикл
ЗаполнитьЗначенияСвойств(ТЗДляЗапроса.Добавить(), СтрокаИсточника);
КонецЦикла;
Теперь мы можем составить запрос. Первым пакетом мы помещаем типизированную таблицу во временную таблицу и обязательно её индексируем. Вторым пакетом соединяем её с таблицей документов Документ.ЗаявкаНаЗакупку:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| втТЗ.Номер КАК Номер,
| втТЗ.Состояние КАК Состояние
|ПОМЕСТИТЬ ВременнаяТаблицаНомеров
|ИЗ
| &ТаблицаИсточника КАК втТЗ
|
|ИНДЕКСИРОВАТЬ ПО
| Номер
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Док.Ссылка КАК Ссылка,
| ВременнаяТаблицаНомеров.Состояние КАК СостояниеОплаты,
| Док.Оплачено КАК ТекущееСостояниеОплаты
|ИЗ
| Документ.ЗаявкаНаЗакупку КАК Док
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВременнаяТаблицаНомеров КАК ВременнаяТаблицаНомеров
| ПО Док.Номер = ВременнаяТаблицаНомеров.Номер";
Запрос.УстановитьПараметр("ТаблицаИсточника", ТЗДляЗапроса);
РезультатЗапроса = Запрос.Выполнить();
Обратите внимание на использование ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Благодаря этому в выборку попадут только те документы, номера которых реально присутствуют во внешней таблице данных из Бухгалтерии. Это отсекает сотни тысяч лишних записей.
---
После того как мы получили точечную выборку документов, требующих обновления, нам необходимо обойти этот результат и изменить значения реквизитов. Но даже здесь кроются подводные камни, способные замедлить выполнение операции.
Проанализируем правила безопасной и быстрой перезаписи объектов в транзакции:
Если ДокументОбъект.Оплачено <> НовоеЗначение.ОбменДанными.Загрузка = Истина. Это ускорит запись в разы.Ниже представлен готовый профессиональный шаблон кода для обхода результата и записи изменений:
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
// Открываем транзакцию для ускорения массовой записи
НачатьТранзакцию();
Попытка
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Проверяем, отличается ли новое состояние от текущего в базе
Если ВыборкаДетальныеЗаписи.ТекущееСостояниеОплаты <> ВыборкаДетальныеЗаписи.СостояниеОплаты Тогда
// Получаем объект документа для изменения
ДокументОбъект = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
ДокументОбъект.Оплачено = ВыборкаДетальныеЗаписи.СостояниеОплаты;
// Включаем режим загрузки для отключения лишних проверок
ДокументОбъект.ОбменДанными.Загрузка = Истина;
// Записываем объект без проведения (просто сохраняем реквизит)
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЦикла;
// Если все прошло успешно, фиксируем изменения в базе данных
ЗафиксироватьТранзакцию();
Сообщить("Синхронизация статусов оплаты успешно завершена!");
Исключение
// В случае ошибки откатываем все изменения назад, чтобы не нарушить целостность данных
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("СинхронизацияОплаты", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Произошла ошибка при обновлении документов. Изменения отменены. Подробности в Журнале регистрации.";
КонецПопытки;
---
Перед тем как внедрять данный код, обязательно выясните, как именно в вашей конфигурации устроена логика отметки об оплате.
Если вы работаете в типовом решении, таком как «1С:Комплексная автоматизация» или «1С:ERP Управление предприятием», колонка «Оплачено» в списках заявок на расходование ДС часто является расчетным полем. Оно вычисляется динамически на основании регистров накопления по фактическим платежным документам (например, по проведенным выпискам банка).
В этом случае простое изменение реквизита в документе «Заявка» не даст никакого эффекта. Вам потребуется либо создавать реальные платежные документы-регистраторы, либо использовать специализированные механизмы казначейства. Если же реквизит «Оплачено» является полностью кастомным (самописным) и добавленным вами вручную — предложенный выше код отработает идеально и мгновенно.