Рассмотрим частую проблему, с которой сталкиваются начинающие разработчики 1С: вывод дублирующихся сообщений при обходе табличных частей документов в цикле. Проанализируем типичную задачу. Допустим, мы читаем данные из внешнего источника (например, DBF файла) и построчно проверяем, есть ли указанная номенклатура в документе. Если номенклатура есть — обновляем реквизиты, если ее нет — нужно вывести пользователю сообщение.
Часто разработчики пишут конструкцию проверки непосредственно внутри цикла перебора строк табличной части:
Для каждого ДокТов Из ДокОбъект.Корректировка Цикл
Если СокрЛП(ДокТов.Номенклатура) = СокрЛП(БД.NomB) Тогда
ДокТов.НаименованиеБух = СокрЛП(БД.bYX);
ДокТов.ОстатокБух = СокрЛП(БД.OstB);
Иначе
Сообщить("Не найдена: " + СокрЛП(БД.bYX));
КонецЕсли;
КонецЦикла;
Выясним причину многократного вывода сообщений. Логика такого кода такова: система берет искомую номенклатуру и сравнивает ее с каждой строкой табличной части. Если в документе 246 строк, и только одна совпадает с нашей номенклатурой, то условие Иначе отработает 245 раз! В результате лог сообщений заспамливается. Разберем по шагам, как правильно и оптимально решить эту задачу.
Самый простой способ избежать дублирования, если вы всё же используете цикл — создать специальную булеву переменную перед началом перебора. Посмотрим на пример, где мы присваиваем переменной значение Ложь, а внутри цикла меняем его, если данные не найдены.
ПеременнаяДляПроверки = Ложь;
Для каждого ДокТов Из ДокОбъект.Корректировка Цикл
Если СокрЛП(ДокТов.Номенклатура) = СокрЛП(БД.NomB) Тогда
ДокТов.НаименованиеБух = СокрЛП(БД.bYX);
ДокТов.ОстатокБух = СокрЛП(БД.OstB);
ПеременнаяДляПроверки = Ложь;
Прервать; // Нашли нужную строку, дальше искать нет смысла
Иначе
ПеременнаяДляПроверки = Истина;
КонецЕсли;
КонецЦикла;
Если ПеременнаяДляПроверки Тогда
Сообщить(СокрЛП(БД.NomB) + " Остаток в бух " + СокрЛП(БД.OstB));
КонецЕсли;
Это рабочее решение, но с точки зрения платформы 1С оно не является самым оптимальным.
Вместо перебора всей табличной части (ТЧ) в цикле для поиска одной строки, в 1С используется встроенный и гораздо более быстрый метод Найти. Он ищет значение в указанной колонке и возвращает саму строку или Неопределено.
// Ищем строку один раз без циклов по ТЧ
СтрокаТЧ = ДокОбъект.Корректировка.Найти(БД.NomB, "Номенклатура");
Если СтрокаТЧ <> Неопределено Тогда
// Номенклатура найдена, заполняем данные в найденной строке
СтрокаТЧ.НаименованиеБух = СокрЛП(БД.bYX);
СтрокаТЧ.ОстатокБух = СокрЛП(БД.OstB);
Иначе
// Номенклатура не найдена во всем документе — сообщаем только ОДИН раз
Сообщить("Не найдена номенклатура: " + БД.NomB);
КонецЕсли;
Этот код работает гораздо быстрее и избавляет от логической ошибки множественного вывода в ветке Иначе.
Если мы обрабатываем большой массив данных, даже метод Найти может генерировать много сообщений, если одной и той же отсутствующей номенклатуры в файле несколько. Рассмотрим варианты накопления и фильтрации сообщений.
1. Использование строковой переменной:
Можно использовать функцию, которая собирает все ошибки в одну большую строку с переносами.
ТекстОшибки = "";
Если УсловиеНеВыполнено Тогда
ТекстОшибки = ?(ТекстОшибки = "", "", ТекстОшибки + Символы.ПС) + "Номенклатура не найдена!";
КонецЕсли;
// После завершения всех проверок выводим разом
Если ЗначениеЗаполнено(ТекстОшибки) Тогда
Сообщить(ТекстОшибки);
КонецЕсли;
2. Фильтрация дублей через Массив:
Для отслеживания уже отправленных сообщений отлично подходит объект Массив. Мы просто проверяем, есть ли уже этот артикул в отправленных.
ОтправленныеНоменклатуры = Новый Массив;
ИскомыйКод = СокрЛП(БД.NomB);
// Проверяем, не выводили ли мы сообщение по этому коду ранее
Если ОтправленныеНоменклатуры.Найти(ИскомыйКод) = Неопределено Тогда
Сообщить("Не найдено: " + ИскомыйКод);
ОтправленныеНоменклатуры.Добавить(ИскомыйКод);
КонецЕсли;
3. Использование Таблицы Значений и метода Свернуть():
Идеальный вариант для формирования красивого отчета по ошибкам в конце обработки.
ТЗ_Ошибок = Новый ТаблицаЗначений;
ТЗ_Ошибок.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("Строка"));
// Внутри цикла, если номенклатура не найдена:
НоваяСтрока = ТЗ_Ошибок.Добавить();
НоваяСтрока.Номенклатура = СокрЛП(БД.NomB);
// После цикла сворачиваем дубли
ТЗ_Ошибок.Свернуть("Номенклатура");
Для Каждого СтрокаОшибки Из ТЗ_Ошибок Цикл
Сообщить("Отсутствует номенклатура: " + СтрокаОшибки.Номенклатура);
КонецЦикла;
Проанализируем еще одну серьезную проблему, часто встречающуюся в подобных задачах. В коде может использоваться конструкция выборки всех документов в базе внутри цикла обхода файла:
Док = Документы.Корректировка6Кладовой.Выбрать();
Пока Док.Следующий() Цикл
Если СокрЛП(Док.Номер) = СокрЛП(БД.NOM) Тогда...
Вызов метода Выбрать() без параметров считывает абсолютно все документы этого типа из базы. Если вы делаете это для каждой строки из внешней базы DBF, производительность упадет до нуля. Разберем правильный подход:
Документы.Корректировка6Кладовой.НайтиПоНомеру(НомерДок).В (&МассивНомеров).В завершение разберем, как правильно сообщать пользователю об ошибках в современных конфигурациях 1С на обычных и управляемых формах. Метод Сообщить() считается устаревшим. Вместо него рекомендуется использовать объект СообщениеПользователю, который позволяет привязать текст ошибки к конкретному полю документа.
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не найдена номенклатура!";
// Привязываем сообщение к конкретной строке (индекс начинается с нуля)
Сообщение.Поле = "Корректировка[" + ИндексСтроки + "].Номенклатура";
Сообщение.УстановитьДанные(ДокОбъект);
Сообщение.Сообщить();
Такой подход позволит пользователю дважды кликнуть по сообщению, после чего система сама откроет нужный документ и спозиционируется на проблемной строке. Применяя эти рекомендации, вы сделаете свой код производительнее, а логику работы обработок — понятной и удобной для пользователей.