Как свернуть Таблицу Значений и сгруппировать строки по складам и номенклатуре при загрузке из CSV в 1С 8.2?

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При обработке и загрузке внешних файлов (например, файлов формата .csv) часто возникает ситуация, когда на каждую строку создаётся отдельный документ РеализацияТоваровУслуг. Это приводит к замусориванию базы данных лишними документами. Для оптимизации процесса необходимо объединять строки с одинаковыми параметрами — например, создавать единую накладную по каждому складу, а повторяющиеся позиции номенклатуры суммировать в табличной части.

Рассмотрим подробнее, как правильно подготовить структуру данных, свернуть Таблицу Значений и организовать цикл создания документов.

Причина проблемы при построчной загрузке

Выясним причину, почему документы дублируются. Если при обходе файла вызов метода СоздатьДокумент() расположен внутри цикла чтения строк .csv без предварительной группировки, платформа создает новую накладную на каждую прочитанную запись. Для решения этой задачи обработку загрузки следует разделить на два этапа:

  1. Сбор и подготовка данных: Чтение файла, заполнение временной Таблицы Значений с указанием типов колонок и её свёртка по ключевым реквизитам.
  2. Формирование документов: Обход сгруппированных данных с отслеживанием смены склада и заполнением табличной части Товары.

Шаг 1. Подготовка и свёртка Таблицы Значений

Разберём по шагам заполнение Таблицы Значений. Чтобы метод Свернуть() корректно просуммировал показатели, для числовых колонок (число, количество, сумма) необходимо явно задавать тип данных с помощью объекта ОписаниеТипов. Если тип колонки не определен, платформа не сможет корректно выполнить математическое сложение.

Обратите внимание, что метод Свернуть() принимает два параметра:

Критически важно: колонку с ценой товара передавать в суммируемые поля нельзя, так как цены сложатся. Цены необходимо рассчитывать после свёртки по формуле Цена = Сумма / Количество.

Проанализируем пример кода для инициализации, заполнения и свёртки Таблицы Значений:


ОписаниеЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(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 Возр");

Шаг 2. Создание документов по сгруппированным данным

Посмотрим на алгоритм формирования реализаций. После свёртки и сортировки таблицы по идентификатору склада (KKM_SERIAL_ID) перейдём к её обходу в цикле. Создание объекта ДокументОбъект.РеализацияТоваровУслуг происходит только тогда, когда меняется склад или обрабатывается первая строка таблицы.

Рассмотрим пример реализации обхода сгруппированной Таблицы Значений:


ТекущийСклад = Неопределено;
ДокРасх = Неопределено;

Для Каждого СтрИзТЗ Из тбОплат Цикл

    // Проверяем, изменился ли склад. Если да — записываем текущий документ и создаем новый
    Если ТекущийСклад <> СтрИзТЗ.KKM_SERIAL_ID Тогда
        
        // Записываем предыдущий документ, если он был создан
        Если ДокРасх <> Неопределено Тогда
            ДокРасх.УстановитьВремя(РежимАвтоВремя.ТекущееИлиПоследним);
            ДокРасх.Записать(РежимЗаписиДокумента.Запись);
        КонецЕсли;
        
        ТекущийСклад = СтрИзТЗ.KKM_SERIAL_ID;
        
        // Поиск или создание нового склада
        Скл = СпрСклад.НайтиПоРеквизиту("ID", ТекущийСклад);
        Если Скл.Пустая() Тогда
            НовыйСклад = СпрСклад.СоздатьЭлемент();
            НовыйСклад.ID = ТекущийСклад;
            НовыйСклад.Наименование = ТекущийСклад;
            НовыйСклад.Записать();
            Скл = НовыйСклад.Ссылка;
        КонецЕсли;
        
        // Создание нового документа Реализация товаров и услуг
        ДокРасх = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
        ДокРасх.Дата = ЭлементыФормы.ДатаДока.Значение;
        ДокРасх.Организация = Справочники.Организации.НайтиПоКоду("000000001");
        ДокРасх.Контрагент = СпрКлиент.НайтиПоКоду("000000241");
        ДокРасх.ДоговорКонтрагента = ДокРасх.Контрагент.ОсновнойДоговорКонтрагента;
        ДокРасх.Склад = Скл;
        ДокРасх.Комментарий = "Загружено из WEB кассы";
        ДокРасх.Ответственный = ПараметрыСеанса.ТекущийПользователь;
    КонецЕсли;
    
    // Заполняем табличную часть документа "Товары"
    СтрокаТовара = ДокРасх.Товары.Добавить();
    СтрокаТовара.Номенклатура = СтрИзТЗ.Номенклатура;
    СтрокаТовара.Количество = СтрИзТЗ.PRODUCT_COUNT;
    СтрокаТовара.Сумма = СтрИзТЗ.PRODUCT_SUMM;
    
    // Пересчитываем цену на основании просуммированных данных
    Если СтрокаТовара.Количество <> 0 Тогда
        СтрокаТовара.Цена = СтрокаТовара.Сумма / СтрокаТовара.Количество;
    КонецЕсли;
    
    СтрокаТовара.ЕдиницаИзмерения = СтрИзТЗ.Номенклатура.БазоваяЕдиницаИзмерения;
    СтрокаТовара.Коэффициент = 1;

КонецЦикла;

// Записываем последний сформированный документ после завершения цикла
Если ДокРасх <> Неопределено Тогда
    ДокРасх.УстановитьВремя(РежимАвтоВремя.ТекущееИлиПоследним);
    ДокРасх.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;

Рекомендации по отладке и производительности

Проанализируем возможные ошибки и способы оптимизации работы с Таблицей Значений:

← На главную