В практике разработки на платформе 1С:Предприятие 8 часто возникает задача: при проведении или записи одного документа необходимо автоматически внести изменения в другой, связанный с ним документ. Чаще всего это касается корректировки статусов, обновления фактически выполненного количества или простановки отметок о выполнении в строках табличных частей. Для начинающего разработчика эта задача может обернуться трудностями, когда код вроде бы написан верно, данные в отладчике меняются, но в базе данных ничего не сохраняется. Рассмотрим подробно, как правильно реализовать этот механизм, избегая типичных ошибок.
Проанализируем ситуацию, с которой часто сталкиваются новички. Типичный сценарий выглядит так: разработчик получает объект документа, выгружает его табличную часть в таблицу значений с помощью метода Выгрузить(), вносит изменения в эту таблицу в цикле и вызывает метод Записать() у объекта документа.
В чем заключается ошибка? Метод Выгрузить() создает в оперативной памяти полную копию данных табличной части в виде объекта ТаблицаЗначений. Эта копия никак не связана с исходным объектом документа «живой» связью. Изменяя строки в таблице значений, мы меняем данные только в этой временной таблице, а табличная часть самого документа остается прежней. Поэтому при вызове метода Записать() платформа сохраняет старое состояние документа.
Рассмотрим, как выглядит ошибочный подход:
// ОШИБОЧНЫЙ ВАРИАНТ
ТекОбъект = СсылкаНаДокумент.ПолучитьОбъект();
ТЗ = ТекОбъект.Товары.Выгрузить(); // Создается независимая копия данных
Для Каждого СтрокаТЗ Из ТЗ Цикл
СтрокаТЗ.Количество = 10; // Меняем данные в копии
КонецЦикла;
ТекОбъект.Записать(); // В документе ничего не изменилось!
Для того чтобы изменения действительно применились к документу, мы должны работать непосредственно с коллекцией строк табличной части объекта документа. Разберем по шагам правильный алгоритм действий.
ПолучитьОбъект().Для каждого ... Из ... Цикл, обращаясь напрямую к реквизиту табличной части (например, ТекОбъект.Товары).Записать().Проанализируем исправленный код:
Процедура ОбновитьДанныеВЗаказе(СсылкаНаЗаказ, НоменклатураПоиска, НовоеКолВып)
// Проверяем, заполнена ли ссылка, чтобы избежать ошибки
Если Не ЗначениеЗаполнено(СсылкаНаЗаказ) Тогда
Возврат;
КонецЕсли;
// Получаем объект документа для редактирования
ОбъектЗаказа = СсылкаНаЗаказ.ПолучитьОбъект();
ИзмененияВнесены = Ложь;
// Работаем напрямую с табличной частью документа
Для каждого СтрокаТовары Из ОбъектЗаказа.Товары Цикл
// Сравниваем номенклатуру
Если СтрокаТовары.Номенклатура = НоменклатураПоиска Тогда
// Меняем статус и количество прямо в объекте
СтрокаТовары.Состояние = Перечисления.СтатусыЗаказа.ВПроизводстве;
СтрокаТовары.КолВып = НовоеКолВып;
ИзмененияВнесены = Истина;
КонецЕсли;
КонецЦикла;
// Записываем документ только если были реальные изменения
Если ИзмененияВнесены Тогда
// Определяем режим записи в зависимости от того, проведен ли документ
Если ОбъектЗаказа.Проведен Тогда
ОбъектЗаказа.Записать(РежимЗаписиДокумента.Проведение);
Иначе
ОбъектЗаказа.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Если табличная часть документа содержит сотни или тысячи строк, обычный перебор в цикле может работать медленно. Выясним, как ускорить этот процесс. Платформа предоставляет нам инструменты для быстрого поиска строк.
Использование метода НайтиСтроки(). Вместо того чтобы перебирать все строки и проверять условие через Если, мы можем передать структуру поиска. Это работает значительно быстрее, так как поиск оптимизирован на уровне платформы.
Отбор = Новый Структура;
Отбор.Вставить("Номенклатура", ТекНоменклатура);
НайденныеСтроки = ОбъектЗаказа.Товары.НайтиСтроки(Отбор);
Для каждого Строка Из НайденныеСтроки Цикл
Строка.КолВып = ТекКолВып;
Строка.Состояние = Перечисления.СтатусыЗаказа.Выполнено;
КонецЦикла;
Этот метод вернет массив строк, соответствующих условию. Если номенклатура в документе уникальна, в массиве будет всего один элемент. Если она встречается несколько раз — мы обновим все вхождения.
Проанализируем важный методологический момент. В системе 1С данные хранятся не только в документах, но и в регистрах (накопления, сведения, бухгалтерии). Когда документ проводится, он делает «движения» — записи в эти регистры.
Если мы просто записываем документ методом Записать(РежимЗаписиДокумента.Запись), мы меняем только данные в таблице самого документа. Но если документ уже был проведен, его движения в регистрах останутся старыми. Это приведет к ситуации, когда в экранной форме документа написано одно количество, а в отчетах (которые берут данные из регистров) — совсем другое.
Поэтому рекомендуется придерживаться следующей логики:
Проведен перед сохранением.Рассмотрим еще одну критическую ошибку производительности. Никогда не вызывайте метод Записать() внутри цикла по строкам. Каждая запись документа — это тяжелая транзакция в базе данных, которая блокирует таблицы и создает нагрузку на сервер.
Правило: Сначала соберите все необходимые изменения в объекте, находясь в памяти, и только после завершения всех циклов и проверок вызовите метод Записать() один раз.
При программном изменении объектов следует учитывать еще несколько аспектов, которые помогут сделать ваш код профессиональным и отказоустойчивым:
ЗначениеЗаполнено(Ссылка) перед вызовом ПолучитьОбъект(). Попытка получить объект от пустой ссылки вызовет фатальную ошибку исполнения.БлокировкаДанных.ОповеститьОбИзменении(Ссылка) после успешной записи.Подводя итог, можно сказать, что работа с табличными частями требует понимания разницы между объектом и его копией в виде таблицы значений. Работая напрямую с коллекцией строк объекта и соблюдая правила записи проведенных документов, вы обеспечите целостность данных и корректную работу системы.