В процессе работы с 1С, особенно после переносов данных или слияния баз, часто возникают задачи по массовой корректировке данных. Представим ситуацию: у нас есть тысячи строк в табличных частях различных документов, в которых нужно заменить одно значение реквизита на другое, например, поменять старый договор на новый. Стандартный подход "в лоб" — перебрать все строки, для каждой получить объект документа, изменить значение и записать — может занять часы, а то и целую ночь. Рассмотрим, как решить эту задачу эффективно и быстро.
Проанализируем конкретную проблему: имеется обработка с таблицей изменений, содержащей около 20 000 строк. Каждая строка указывает, в каком документе, какой "старый договор" нужно заменить на "новый". Документов при этом значительно меньше, чем строк, то есть в одном документе нужно произвести несколько замен. Наша цель — создать алгоритм, который минимизирует количество обращений к базе данных, а именно — операций получения и записи объектов документов.
Ключ к оптимизации — перестать думать в терминах отдельных строк и начать мыслить в терминах целых документов. Вместо того чтобы обрабатывать каждую строку из нашей таблицы изменений индивидуально, мы сгруппируем все изменения, относящиеся к одному документу, и выполним их за один раз. Это позволит нам получить объект документа всего один раз, внести в него все необходимые правки и записать его тоже только один раз. Такой подход кардинально сокращает время выполнения обработки.
Разберем по шагам, как реализовать этот алгоритм.
Давайте посмотрим, как этот алгоритм выглядит в виде кода. Предположим, у нас есть таблица значений ТаблицаИзменений с колонками "Документ" (тип ДокументСсылка), "СтарыйДоговор" и "НовыйДоговор".
// Шаг 1: Сортируем таблицу изменений по колонке с ссылками на документы
ТаблицаИзменений.Сортировать("Документ Возр");
// Переменная для хранения объекта документа, с которым работаем в данный момент
ТекущийДокументОбъект = Неопределено;
// Шаг 2: Начинаем основной цикл по строкам таблицы изменений
Для Каждого СтрокаТЗ Из ТаблицаИзменений Цикл
// Шаг 3: Проверяем, перешли ли мы к новому документу
Если ТекущийДокументОбъект = Неопределено Или ТекущийДокументОбъект.Ссылка <> СтрокаТЗ.Документ Тогда
// Если это не первая итерация цикла, значит, у нас есть предыдущий документ,
// который нужно записать.
Если ТекущийДокументОбъект <> Неопределено Тогда
Попытка
// Здесь решаем, проводить или просто записывать.
// Для документов начисления почти всегда нужно перепроведение.
ТекущийДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Документ " + ТекущийДокументОбъект.Ссылка + " успешно изменен и проведен.");
Исключение
Сообщить("Не удалось провести документ " + ТекущийДокументОбъект.Ссылка + ". Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
// Теперь получаем объект нового документа из текущей строки ТЗ
Попытка
ТекущийДокументОбъект = СтрокаТЗ.Документ.ПолучитьОбъект();
// Сразу же модифицируем объект, чтобы не потерять блокировки и данные
ТекущийДокументОбъект.Заблокировать();
Исключение
Сообщить("Не удалось получить объект для документа " + СтрокаТЗ.Документ + ". Пропускаем. Ошибка: " + ОписаниеОшибки());
ТекущийДокументОбъект = Неопределено;
Продолжить; // Переходим к следующей строке ТЗ
КонецИсключения;
КонецЕсли;
// Вносим изменения в табличную часть полученного объекта.
// Находим нужную строку для замены. Логика поиска зависит от задачи.
// Например, ищем по старому договору и другим критериям.
НайденнаяСтрока = Неопределено;
Для Каждого СтрокаТЧ Из ТекущийДокументОбъект.ИмяВашейТабличнойЧасти Цикл
// Условие поиска может быть сложнее, например, по нескольким полям
Если СтрокаТЧ.Договор = СтрокаТЗ.СтарыйДоговор Тогда
СтрокаТЧ.Договор = СтрокаТЗ.НовыйДоговор;
// Если мы уверены, что такая строка одна, можно прервать внутренний цикл
// Прервать;
КонецЕсли;
КонецЦикла;
КонецЦикла;
// Шаг 4: Не забываем записать самый последний документ после выхода из цикла
Если ТекущийДокументОбъект <> Неопределено Тогда
Попытка
ТекущийДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Последний документ " + ТекущийДокументОбъект.Ссылка + " успешно изменен и проведен.");
Исключение
Сообщить("Не удалось провести последний документ " + ТекущийДокументОбъект.Ссылка + ". Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
Этот код выполняет задачу значительно быстрее, так как количество операций ПолучитьОбъект() и Записать() равно количеству уникальных документов, а не общему количеству строк для изменения.
Один из ключевых моментов, который нужно четко понимать при модификации документов, — это разница между простой записью и проведением. Выбор метода напрямую зависит от того, влияет ли изменяемый реквизит на движения документа по регистрам.
Документ.Записать(): Этот метод работает очень быстро. Он просто сохраняет новые значения реквизитов в таблицах базы данных, которые относятся к самому документу. Однако он не обновляет движения документа по регистрам (бухгалтерским, накопления, сведений). Этот способ подходит только в том случае, если изменяемый реквизит (в нашем случае "Договор") является чисто информационным, используется для печатных форм или отчетов, построенных непосредственно по данным документа, и не участвует в формировании проводок или записей в регистрах.Документ.Записать(РежимЗаписиДокумента.Проведение): Этот метод выполняет полный цикл жизни документа. Он не только сохраняет изменения, но и полностью перестраивает движения: старые движения удаляются, и на основе обновленных данных документа модуль проведения формирует новые. Это более медленная, но и более правильная операция, если изменяемый реквизит важен для учета. В случае с документом "Начисление" в 1С:Бухгалтерии, реквизит "Договор" почти наверняка влияет на субконто бухгалтерских счетов, а значит, перепроведение документов является обязательным для сохранения целостности учета.Прежде чем писать собственный код, всегда стоит проверить, не решается ли ваша задача стандартными средствами платформы или конфигурации. Зачастую они уже содержат мощные и оптимизированные механизмы.
НачатьТранзакцию();
Попытка
// ... здесь ваш код обработки документов ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить("Произошла ошибка, все изменения отменены!");
КонецПопытки;
В заключение, предложенный алгоритм с группировкой изменений по документам является самым оптимальным решением при написании собственной обработки. Он позволяет эффективно справиться с задачей, которая на первый взгляд кажется очень ресурсоемкой. Всегда помните о необходимости анализа влияния изменений на учетные данные, чтобы правильно выбрать между простой записью и проведением, и не забывайте о создании резервных копий перед любыми массовыми операциями с данными.