При разработке обработок по загрузке данных из внешних источников (например, Excel) в документы 1С, одной из самых частых задач является проверка на наличие товара в документе. Если не предусмотреть такую проверку, одна и та же позиция может добавиться в накладную несколько раз, что приведет к пересортице и ошибкам в учете. В этой статье мы подробно разберем, как правильно реализовать поиск соответствий в табличной части документа на платформе 1С 8.3 (Обычные формы), используя как простые программные методы, так и мощные запросы.
Рассмотрим типичную ошибку начинающих разработчиков: выполнение запроса к базе данных внутри цикла перебора строк. Проанализируем ситуацию: если в вашем Excel-файле 1000 строк, программа выполнит 1000 отдельных обращений к серверу БД. Это крайне неэффективно и значительно замедляет работу системы. Профессиональный подход заключается в том, чтобы получить все необходимые данные одним запросом до начала цикла или использовать встроенные коллекции объектов для быстрого поиска.
Если производительность не является критическим фактором (например, в документе не более пары сотен строк), самым простым и понятным решением будет использование метода Найти() табличной части документа. Рассмотрим, как это реализовать программно:
// Предположим, Накладная — это ссылка на документ
ДокОбъект = Накладная.ПолучитьОбъект();
Для Каждого СтрокаИзExcel Из Товары Цикл
Если ЗначениеЗаполнено(СтрокаИзExcel.Номенклатура) Тогда
// Ищем номенклатуру в табличной части "Товары" документа
НайденнаяСтрока = ДокОбъект.Товары.Найти(СтрокаИзExcel.Номенклатура, "Номенклатура");
Если НайденнаяСтрока = Неопределено Тогда
// Если товар не найден, добавляем новую строку
НоваяСтрока = ДокОбъект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИзExcel);
// Дозаполняем специфические реквизиты
НоваяСтрока.ЕдиницаИзмерения = СтрокаИзExcel.Номенклатура.БазоваяЕдиницаИзмерения;
Иначе
// Товар уже есть, можно пропустить или обновить количество
Сообщить("Товар " + СтрокаИзExcel.Номенклатура + " уже есть в документе. Пропускаем.");
КонецЕсли;
КонецЕсли;
КонецЦикла;
ДокОбъект.Записать();
Этот метод хорош тем, что не требует написания сложных текстов запросов и легко отлаживается. Мы используем ЗаполнитьЗначенияСвойств для быстрого копирования данных между строками, если имена колонок в вашей таблице и в документе совпадают.
Более продвинутый метод — выяснить, каких товаров из списка загрузки еще нет в документе, с помощью одного запроса. Для этого мы передаем список номенклатуры из нашей таблицы в параметры запроса. Выясним причину популярности этого метода: он позволяет за один раз отсечь все ненужные позиции.
// Сначала соберем весь список номенклатуры из загрузки
СписокНоменклатуры = Товары.ВыгрузитьКолонку("Номенклатура");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура
|ИЗ
| Документ.ПриходнаяНакладная.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка
| И Товары.Номенклатура В (&СписокНоменклатуры)";
Запрос.УстановитьПараметр("Ссылка", Накладная);
Запрос.УстановитьПараметр("СписокНоменклатуры", СписокНоменклатуры);
// Получаем список товаров, которые уже есть в документе
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
СписокСуществующих = РезультатЗапроса.ВыгрузитьКолонку("Номенклатура");
Для Каждого СтрокаИзExcel Из Товары Цикл
// Проверяем, есть ли текущая номенклатура в списке существующих
Если СписокСуществующих.Найти(СтрокаИзExcel.Номенклатура) = Неопределено Тогда
// Добавляем, так как товара еще нет в накладной
НоваяСтрока = ДокОбъект.Товары.Добавить();
// ... заполнение реквизитов ...
КонецЕсли;
КонецЦикла;
Когда данных очень много, лучшим решением будет использование МенеджерВременныхТаблиц. Мы можем поместить нашу таблицу из Excel (которая уже находится в памяти 1С как ТаблицаЗначений) во временную таблицу внутри запроса и соединить ее с данными документа. Проанализируем пример такого подхода:
Запрос = Новый Запрос;
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Помещаем таблицу товаров из Excel в запрос
Запрос.Текст =
"ВЫБРАТЬ
| ВнешниеТовары.Номенклатура,
| ВнешниеТовары.Количество,
| ВнешниеТовары.Цена
|ПОМЕСТИТЬ ВТ_Загрузка
|ИЗ
| &ТаблицаТоваров КАК ВнешниеТовары";
Запрос.УстановитьПараметр("ТаблицаТоваров", Товары);
Запрос.Выполнить();
// Теперь выбираем только те товары, которых НЕТ в документе
Запрос.Текст =
"ВЫБРАТЬ
| ВТ.Номенклатура,
| ВТ.Количество,
| ВТ.Цена
|ИЗ
| ВТ_Загрузка КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПриходнаяНакладная.Товары КАК ДокТовары
| ПО ВТ.Номенклатура = ДокТовары.Номенклатура
| И (ДокТовары.Ссылка = &Ссылка)
|ГДЕ
| ДокТовары.Номенклатура ЕСТЬ NULL";
Запрос.УстановитьПараметр("Ссылка", Накладная);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НовСтр = ДокОбъект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НовСтр, Выборка);
КонецЦикла;
Использование ЛЕВОЕ СОЕДИНЕНИЕ с проверкой на NULL — это классический и самый быстрый способ в SQL и 1С найти отсутствующие записи в одной таблице относительно другой.
Работа с обычными формами и конфигурациями типа "Далион" накладывает свои особенности. Посмотрим на важные нюансы, которые помогут сделать вашу обработку профессиональной:
Артикулу или Штрихкоду. Если вы ищете по штрихкоду, помните, что это может потребовать обращения к регистру сведений Штрихкоды.Добавить() используйте найденную строку: НайденнаяСтрока.Количество = НайденнаяСтрока.Количество + СтрокаИзExcel.Количество;.НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Это обезопасит данные, если в процессе записи произойдет сбой — система просто откатит изменения к исходному состоянию.ОткрытьФорму() или обновить текущую, если код выполняется в контексте формы.Подводя итог, можно сказать, что выбор метода зависит от объема данных. Для небольших накладных идеально подходит метод Найти(), а для массовой загрузки тысяч позиций — временные таблицы в запросах. Разберем эти примеры на практике, и вы увидите, насколько стабильнее станет работать ваша загрузка данных.