Почему обработка загрузки из Excel в 1С сворачивает данные в файловой базе, а на сервере создает лишние документы?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Нередко разработчики сталкиваются с ситуацией, когда обработка загрузки табличных данных из Microsoft Excel идеально отрабатывает на локальной файловой копии, но при запуске в клиент-серверной базе (SQL) начинает вести себя непредсказуемо. Вместо одного свернутого документа за день создается множество документов за одни и те же даты, причем созданные документы идут вразброс.

Проанализируем детально причины такого поведения платформы 1С:Предприятие и разберем надежные способы решения проблемы с правильными примерами кода.

Причина 1. Особенности работы метода ТаблицаЗначений.Свернуть() и отсутствие сортировки

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

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

Разберем, как исправить эту проблему. После выполнения метода Свернуть() необходимо обязательно вызвать явную сортировку:


// Сворачиваем строки по аналитике и дате
ТЗ.Свернуть("Номенклатура, СтавкаНДС, ЕдИзм, Цена, Дата", "Количество");

// Обязательная явная сортировка перед созданием документов
ТЗ.Сортировать("Дата Возр");

Только при наличии явного вызова ТЗ.Сортировать("Дата Возр") строки за один день гарантированно будут идти последовательно друг за другом.

Причина 2. Ошибочный парсинг даты и региональные стандарты сервера

Посмотрим на типичный фрагмент строкового преобразования даты:


Врем.Дата = Лев(Строка(Excel.Cells(НС, 2).Value), 10);
...
текДата = СокрЛП(ход.Дата);
текДата = СтрЗаменить(текДата, ".", "");
текДатаДень = Лев(текДата, 2); 
текДатаМесяц = Лев(текДата, 4);
текДатаМесяц = Прав(текДатаМесяц, 2);
текДатаГод = Прав(текДата, 4);
текстр.Дата = Дата(текДатаГод + ТекДатаМесяц + текДатаДень + "000000");

Выясним причину, почему такой код дает сбой. Свойство ячейки Excel.Cells().Value для даты возвращает не строку, а внутренний тип VT_DATE, который COM-соединение 1С автоматически преобразует в примитивный тип Дата. Преобразование даты в строку через Строка() жестко завязано на региональные настройки операционной системы.

Если на локальном компьютере установлен формат ДД.ММ.ГГГГ, то на сервере (особенно без русскоязычного языкового пакета) формат может быть ГГГГ-ММ-ДД или М/Д/ГГГГ. В результате строковые функции Лев() и Прав() вырезают случайные фрагменты времени и разделителей, а конструктор Дата() инициализирует пустые даты либо неверные года, что полностью ломает группировку строк.

Для приведения даты из Excel к началу дня строковый разбор не нужен вовсе. Достаточно использовать встроенную функцию НачалоДня():


// Прямое получение даты без строковых преобразований
ЗначениеЯчейки = Excel.Cells(НС, 2).Value;
Если ЗначениеЗаполнено(ЗначениеЯчейки) Тогда
    ТекСтрока.Дата = НачалоДня(ЗначениеЯчейки);
КонецЕсли;

Причина 3. Нетипизированные колонки таблицы значений

Если таблица значений создается без указания типов колонок (через простой вызов ТЗ.Колонки.Добавить("Дата")), колонка может принимать значения произвольного типа. Если в одну строку попадет значение типа Дата, а в другую — строковое представление, метод Свернуть() не объединит их, так как значения разных типов считаются не равными.

Всегда задавайте явное описание типов при создании промежуточных таблиц:


ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("ЕдИзм", Новый ОписаниеТипов("СправочникСсылка.КлассификаторЕдиницИзмерения"));
ТЗ.Колонки.Добавить("СтавкаНДС", Новый ОписаниеТипов("ПеречислениеСсылка.СтавкиНДС"));
ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата", , Новый КвалификаторыДаты(ЧастиДаты.Дата)));

Альтернативное решение: группировка с помощью объекта «Запрос»

Наиболее надежным и отказоустойчивым способом группировки данных в 1С является передача таблицы значений в качестве параметра во временную таблицу запроса. Механизм запросов работает абсолютно одинаково как в файловом, так и в серверном вариантах, полностью исключая влияние менеджера памяти.

Рассмотрим практическую реализацию:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Т.Номенклатура КАК Номенклатура,
    |   Т.ЕдИзм КАК ЕдИзм,
    |   Т.СтавкаНДС КАК СтавкаНДС,
    |   Т.Цена КАК Цена,
    |   НАЧАЛОПЕРИОДА(Т.Дата, ДЕНЬ) КАК Дата,
    |   СУММА(Т.Количество) КАК Количество
    |ПОМЕСТИТЬ ВТ_Данные
    |ИЗ
    |   &ТаблицаДанных КАК Т
    |СГРУППИРОВАТЬ ПО
    |   Т.Номенклатура,
    |   Т.ЕдИзм,
    |   Т.СтавкаНДС,
    |   Т.Цена,
    |   НАЧАЛОПЕРИОДА(Т.Дата, ДЕНЬ)
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |   ВТ.Дата КАК Дата,
    |   ВТ.Номенклатура КАК Номенклатура,
    |   ВТ.ЕдИзм КАК ЕдИзм,
    |   ВТ.СтавкаНДС КАК СтавкаНДС,
    |   ВТ.Цена КАК Цена,
    |   ВТ.Количество КАК Количество
    |ИЗ
    |   ВТ_Данные КАК ВТ
    |ИТОГИ ПО
    |   Дата";

Запрос.УстановитьПараметр("ТаблицаДанных", ТЗ);
Результат = Запрос.Выполнить();

// Обход с группировкой по датам гарантирует создание ровно одного документа на каждый день
ВыборкаДата = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДата.Следующий() Цикл
    
    ДокументОбъект = Документы.ОтчетОРозничныхПродажах.СоздатьДокумент();
    ДокументОбъект.Дата = ВыборкаДата.Дата;
    
    ВыборкаСтроки = ВыборкаДата.Выбрать();
    Пока ВыборкаСтроки.Следующий() Цикл
        НоваяСтрока = ДокументОбъект.Товары.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаСтроки);
    КонецЦикла;
    
    ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЦикла;

Рекомендации по чтению файлов Excel в клиент-серверной архитектуре

Разберем по шагам ключевые правила построения архитектуры загрузки:

  1. Отказ от COM-объекта Excel.Application: COM-объект требует установленного MS Office, зависит от разрядности сервера 1С (на 64-битном сервере 32-битный Excel напрямую не запустится) и оставляет зависшие процессы в памяти. Используйте штатный объект платформы ТабличныйДокумент с методом Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение).
  2. Разделение контекста клиента и сервера: Диалог выбора файла должен выполняться в процедуре &НаКлиенте. Выбранный файл помещается во временное хранилище через метод ПоместитьФайлНаСервер(), а серверная процедура распаковывает двоичные данные и заполняет объекты базы данных, не обращаясь напрямую к локальным дискам клиентских ПК.
← На главную