При обработке и загрузке внешних файлов (например, файлов формата .csv) часто возникает ситуация, когда на каждую строку создаётся отдельный документ РеализацияТоваровУслуг. Это приводит к замусориванию базы данных лишними документами. Для оптимизации процесса необходимо объединять строки с одинаковыми параметрами — например, создавать единую накладную по каждому складу, а повторяющиеся позиции номенклатуры суммировать в табличной части.
Рассмотрим подробнее, как правильно подготовить структуру данных, свернуть Таблицу Значений и организовать цикл создания документов.
Выясним причину, почему документы дублируются. Если при обходе файла вызов метода СоздатьДокумент() расположен внутри цикла чтения строк .csv без предварительной группировки, платформа создает новую накладную на каждую прочитанную запись. Для решения этой задачи обработку загрузки следует разделить на два этапа:
Товары.Разберём по шагам заполнение Таблицы Значений. Чтобы метод Свернуть() корректно просуммировал показатели, для числовых колонок (число, количество, сумма) необходимо явно задавать тип данных с помощью объекта ОписаниеТипов. Если тип колонки не определен, платформа не сможет корректно выполнить математическое сложение.
Обратите внимание, что метод Свернуть() принимает два параметра:
Критически важно: колонку с ценой товара передавать в суммируемые поля нельзя, так как цены сложатся. Цены необходимо рассчитывать после свёртки по формуле Цена = Сумма / Количество.
Проанализируем пример кода для инициализации, заполнения и свёртки Таблицы Значений:
ОписаниеЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
тбОплат = Новый ТаблицаЗначений;
тбОплат.Колонки.Добавить("KKM_SERIAL_ID", Новый ОписаниеТипов("Строка"));
тбОплат.Колонки.Добавить("Номенклатура");
тбОплат.Колонки.Добавить("PRODUCT_COUNT", ОписаниеЧисло);
тбОплат.Колонки.Добавить("PRODUCT_SUMM", ОписаниеЧисло);
// В цикле чтения файла заполняем Таблицу Значений
Для ш = 1 По Текст.КоличествоСтрок() Цикл
Стр = СокрЛП(Текст.ПолучитьСтроку(ш));
Если Стр = "" Тогда
Продолжить;
КонецЕсли;
СтрТаблицы = тбОплат.Добавить();
СтрТаблицы.KKM_SERIAL_ID = СокрЛП(ДанныеКолонки(Стр, 2, ошибка, ш, "Строка"));
// Получение или поиск номенклатуры
ИмяТовара = СокрЛП(ДанныеКолонки(Стр, 10, ошибка, ш, "Строка"));
Если ИмяТовара = "Алтын (Altyn)" Тогда
СтрТаблицы.Номенклатура = СпрНом.НайтиПоКоду("00000000311");
Иначе
СтрТаблицы.Номенклатура = СпрНом.НайтиПоКоду("00000000312");
КонецЕсли;
СтрТаблицы.PRODUCT_COUNT = ДанныеКолонки(Стр, 11, ошибка, ш, "Число");
СтрТаблицы.PRODUCT_SUMM = ДанныеКолонки(Стр, 13, ошибка, ш, "Число");
КонецЦикла;
// Сворачиваем ТЗ: первый параметр — группировка, второй — суммирование
тбОплат.Свернуть("KKM_SERIAL_ID, Номенклатура", "PRODUCT_COUNT, PRODUCT_SUMM");
// Сортируем по складу для последовательного создания документов
тбОплат.Сортировать("KKM_SERIAL_ID Возр");
Посмотрим на алгоритм формирования реализаций. После свёртки и сортировки таблицы по идентификатору склада (KKM_SERIAL_ID) перейдём к её обходу в цикле. Создание объекта ДокументОбъект.РеализацияТоваровУслуг происходит только тогда, когда меняется склад или обрабатывается первая строка таблицы.
Рассмотрим пример реализации обхода сгруппированной Таблицы Значений:
ТекущийСклад = Неопределено;
ДокРасх = Неопределено;
Для Каждого СтрИзТЗ Из тбОплат Цикл
// Проверяем, изменился ли склад. Если да — записываем текущий документ и создаем новый
Если ТекущийСклад <> СтрИзТЗ.KKM_SERIAL_ID Тогда
// Записываем предыдущий документ, если он был создан
Если ДокРасх <> Неопределено Тогда
ДокРасх.УстановитьВремя(РежимАвтоВремя.ТекущееИлиПоследним);
ДокРасх.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
ТекущийСклад = СтрИзТЗ.KKM_SERIAL_ID;
// Поиск или создание нового склада
Скл = СпрСклад.НайтиПоРеквизиту("ID", ТекущийСклад);
Если Скл.Пустая() Тогда
НовыйСклад = СпрСклад.СоздатьЭлемент();
НовыйСклад.ID = ТекущийСклад;
НовыйСклад.Наименование = ТекущийСклад;
НовыйСклад.Записать();
Скл = НовыйСклад.Ссылка;
КонецЕсли;
// Создание нового документа Реализация товаров и услуг
ДокРасх = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
ДокРасх.Дата = ЭлементыФормы.ДатаДока.Значение;
ДокРасх.Организация = Справочники.Организации.НайтиПоКоду("000000001");
ДокРасх.Контрагент = СпрКлиент.НайтиПоКоду("000000241");
ДокРасх.ДоговорКонтрагента = ДокРасх.Контрагент.ОсновнойДоговорКонтрагента;
ДокРасх.Склад = Скл;
ДокРасх.Комментарий = "Загружено из WEB кассы";
ДокРасх.Ответственный = ПараметрыСеанса.ТекущийПользователь;
КонецЕсли;
// Заполняем табличную часть документа "Товары"
СтрокаТовара = ДокРасх.Товары.Добавить();
СтрокаТовара.Номенклатура = СтрИзТЗ.Номенклатура;
СтрокаТовара.Количество = СтрИзТЗ.PRODUCT_COUNT;
СтрокаТовара.Сумма = СтрИзТЗ.PRODUCT_SUMM;
// Пересчитываем цену на основании просуммированных данных
Если СтрокаТовара.Количество <> 0 Тогда
СтрокаТовара.Цена = СтрокаТовара.Сумма / СтрокаТовара.Количество;
КонецЕсли;
СтрокаТовара.ЕдиницаИзмерения = СтрИзТЗ.Номенклатура.БазоваяЕдиницаИзмерения;
СтрокаТовара.Коэффициент = 1;
КонецЦикла;
// Записываем последний сформированный документ после завершения цикла
Если ДокРасх <> Неопределено Тогда
ДокРасх.УстановитьВремя(РежимАвтоВремя.ТекущееИлиПоследним);
ДокРасх.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Проанализируем возможные ошибки и способы оптимизации работы с Таблицей Значений:
тбОплат, нажмите комбинацию клавиш Shift + F9, затем клавишу F2 для просмотра состава колонок и типов данных.НайтиПоКоду() или НайтиПоРеквизиту() внутри цикла по строкам обращения к СУБД снижает скорость обработки. Для ускорения рекомендуется сохранять уже найденные элементы в объект Соответствие или использовать запрос к временным таблицам.