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

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

В процессе интеграции и обмена данными между различными конфигурациями «1С:Предприятие» (например, при синхронизации «1С:Бухгалтерии» с внешней системой управления закупками) часто возникает задача: получить реестр платежей из одной базы, сопоставить его с документами в текущей базе и пакетно обновить их статусы. Типичный пример — проставить флаг «Оплачено» у заявок на закупку на основании выгруженной таблицы значений.

В этой статье мы подробно разберем, как правильно реализовать данный алгоритм на платформе «1С:Предприятие 8». Мы проанализируем различные подходы, выявим типичные ошибки производительности и напишем оптимальный, отказоустойчивый код с использованием временных таблиц и механизмов пакетной записи объектов.

Представим, что у нас на входе есть таблица значений ТЗИзБухгалтерии с колонками Номер (строка) и Состояние (булево, где ИСТИНА означает успешную оплату).

---

Почему не стоит перебирать все документы в цикле?

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


// НЕПРАВИЛЬНЫЙ И НЕОПТИМАЛЬНЫЙ ПОДХОД!
Выборка = Документы.ЗаявкаНаЗакупку.Выбрать();
Пока Выборка.Следующий() Цикл
    СтрокаСостояния = ТЗИзБухгалтерии.Найти(Выборка.Номер, "Номер");
    Если СтрокаСостояния <> Неопределено Тогда
        Если СтрокаСостояния.Состояние Тогда
            ДокОбъект = Выборка.ПолучитьОбъект();
            ДокОбъект.Оплачено = Истина;
            ДокОбъект.Записать();
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Давайте выясним причину, почему такой подход считается плохой практикой (anti-pattern):

  1. Огромная нагрузка на СУБД. Выбирая абсолютно все документы из базы данных, мы считываем гигабайты лишней информации. Если документов в базе сотни тысяч, система просто «зависнет».
  2. Низкая скорость поиска в памяти. Поиск методом Найти() по неиндексированной таблице значений при больших объемах данных работает крайне медленно.
  3. Лишние транзакции записи. База данных будет блокироваться на каждую запись объекта, что может парализовать работу других пользователей.

---

Оптимальное решение: Использование запроса и временных таблиц

Для минимизации обращений к базе данных и ускорения работы сопоставление информации необходимо переложить на сторону СУБД. Для этого мы поместим нашу таблицу значений ТЗИзБухгалтерии во временную таблицу внутри запроса, проиндексируем ее по ключевому полю и свяжем с реальной таблицей документов.

Рассмотрим по шагам, как это реализовать.

Шаг 1. Обязательная типизация колонок таблицы значений

Платформа 1С не позволит передать таблицу значений в качестве параметра запроса, если у её колонок явно не заданы типы данных. Перед отправкой ТЗ в запрос мы должны убедиться, что типы жестко определены:


// Создаем типизированную таблицу значений для запроса
ТЗДляЗапроса = Новый ТаблицаЗначений;
ТЗДляЗапроса.Колонки.Добавить("Номер", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(20)));
ТЗДляЗапроса.Колонки.Добавить("Состояние", Новый ОписаниеТипов("Булево"));

// Заполняем её данными из нашего источника
Для Каждого СтрокаИсточника Из ТЗИзБухгалтерии Цикл
    ЗаполнитьЗначенияСвойств(ТЗДляЗапроса.Добавить(), СтрокаИсточника);
КонецЦикла;

Шаг 2. Написание пакетного запроса со связью по номеру

Теперь мы можем составить запрос. Первым пакетом мы помещаем типизированную таблицу во временную таблицу и обязательно её индексируем. Вторым пакетом соединяем её с таблицей документов Документ.ЗаявкаНаЗакупку:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	втТЗ.Номер КАК Номер,
    |	втТЗ.Состояние КАК Состояние
    |ПОМЕСТИТЬ ВременнаяТаблицаНомеров
    |ИЗ
    |	&ТаблицаИсточника КАК втТЗ
    |
    |ИНДЕКСИРОВАТЬ ПО
    |	Номер
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	Док.Ссылка КАК Ссылка,
    |	ВременнаяТаблицаНомеров.Состояние КАК СостояниеОплаты,
    |	Док.Оплачено КАК ТекущееСостояниеОплаты
    |ИЗ
    |	Документ.ЗаявкаНаЗакупку КАК Док
    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВременнаяТаблицаНомеров КАК ВременнаяТаблицаНомеров
    |		ПО Док.Номер = ВременнаяТаблицаНомеров.Номер";

Запрос.УстановитьПараметр("ТаблицаИсточника", ТЗДляЗапроса);
РезультатЗапроса = Запрос.Выполнить();

Обратите внимание на использование ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Благодаря этому в выборку попадут только те документы, номера которых реально присутствуют во внешней таблице данных из Бухгалтерии. Это отсекает сотни тысяч лишних записей.

---

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

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

Проанализируем правила безопасной и быстрой перезаписи объектов в транзакции:

  1. Проверка перед записью. Никогда не записывайте объект, если его новое состояние совпадает с текущим в базе данных. Запись на диск — это «тяжелая» транзакционная операция. Всегда делайте проверку вида: Если ДокументОбъект.Оплачено <> НовоеЗначение.
  2. Использование режима загрузки. Если мы выполняем чисто техническую синхронизацию статусов и нам не нужно запускать тяжелые прикладные проверки (например, контроль остатков бюджета при проведении заявки), следует взводить признак ОбменДанными.Загрузка = Истина. Это ускорит запись в разы.
  3. Пакетное выполнение в транзакции. Объединение группы записей в единую транзакцию значительно снижает накладные расходы СУБД на фиксацию данных.

Ниже представлен готовый профессиональный шаблон кода для обхода результата и записи изменений:


ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();

// Открываем транзакцию для ускорения массовой записи
НачатьТранзакцию();

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

---

Важное замечание: Проверьте логику конфигурации!

Перед тем как внедрять данный код, обязательно выясните, как именно в вашей конфигурации устроена логика отметки об оплате.

Если вы работаете в типовом решении, таком как «1С:Комплексная автоматизация» или «1С:ERP Управление предприятием», колонка «Оплачено» в списках заявок на расходование ДС часто является расчетным полем. Оно вычисляется динамически на основании регистров накопления по фактическим платежным документам (например, по проведенным выпискам банка).

В этом случае простое изменение реквизита в документе «Заявка» не даст никакого эффекта. Вам потребуется либо создавать реальные платежные документы-регистраторы, либо использовать специализированные механизмы казначейства. Если же реквизит «Оплачено» является полностью кастомным (самописным) и добавленным вами вручную — предложенный выше код отработает идеально и мгновенно.

← На главную