Загрузка данных из внешних файлов Excel — одна из самых частых задач при автоматизации кадрового учета в 1С:ЗУП. Однако стандартный линейный перебор строк не всегда применим, особенно когда файл имеет сложную структуру: например, когда данные об одном сотруднике занимают две строки (в одной — виды времени, в другой — часы), а в одной ячейке могут быть указаны сразу несколько видов работ через разделитель. В этой статье мы проанализируем, как реализовать производительный и «чистый» код для такой задачи.
Рассмотрим типичную ситуацию: в файле Excel табельный номер указан в объединенной ячейке, а справа от него идут колонки по дням месяца. При этом данные организованы парами строк: нечетная строка содержит коды видов времени (например, «Я/Н»), а четная строка — соответствующее количество часов (например, «8/4»).
Основными точками оптимизации здесь являются:
НайтиПоКоду на каждой итерации крайне негативно сказывается на производительности.Выясним причину медленной работы большинства загрузчиков. Главная ошибка — обращение к базе данных внутри цикла. Выполним предварительный сбор всех табельных номеров из файла, а затем найдем всех сотрудников одним запросом. Проанализируем пример реализации:
// 1. Собираем все уникальные табельные номера из таблицы значений (ТЗЗагрузки)
МассивТН = Новый Массив;
Для Каждого СтрокаТЗ Из ТЗЗагрузки Цикл
ТН = СокрЛП(СтрокаТЗ.ТабельныйНомер);
Если ЗначениеЗаполнено(ТН) И МассивТН.Найти(ТН) = Неопределено Тогда
МассивТН.Добавить(ТН);
КонецЕсли;
КонецЦикла;
// 2. Находим сотрудников одним запросом и помещаем в Соответствие
СоответствиеСотрудников = Новый Соответствие;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Сотрудники.Ссылка КАК Ссылка,
| Сотрудники.Код КАК Код
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Код В(&МассивТН)";
Запрос.УстановитьПараметр("МассивТН", МассивТН);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СоответствиеСотрудников.Вставить(Выборка.Код, Выборка.Ссылка);
КонецЦикла;
Использование объекта Соответствие (Map) позволяет мгновенно получать ссылку на сотрудника по его коду без обращения к серверу БД на каждом шаге цикла.
Разберем, как правильно обрабатывать двухстрочную структуру. Вместо сложной логики с пропусками строк внутри цикла, мы можем использовать цикл с шагом 2. Это позволит нам всегда иметь доступ и к строке видов времени, и к строке часов одновременно. Рассмотрим структуру кода:
// ИндексТаблицы начинается с данных (пропуская заголовок)
Для Индекс = НомерПервойСтрокиДанных По ТЗЗагрузки.Количество() - 1 Цикл
// Проверяем, является ли строка нечетной (строка с сотрудником и видами времени)
// В данном формате мы можем просто проверять заполнение Табельного номера
СтрокаВидов = ТЗЗагрузки[Индекс];
ТабНомер = СокрЛП(СтрокаВидов.ТабельныйНомер);
Если ПустаяСтрока(ТабНомер) Тогда
Продолжить; // Пропускаем четную строку, так как мы обработаем ее вместе с нечетной
КонецЕсли;
// Четная строка находится сразу за текущей
СтрокаЧасов = ТЗЗагрузки[Индекс + 1];
Сотрудник = СоответствиеСотрудников.Получить(ТабНомер);
Если Сотрудник = Неопределено Тогда
Сообщить("Сотрудник не найден: " + ТабНомер);
Продолжить;
КонецЕсли;
// Переходим к обработке колонок с днями месяца
ОбработатьДанныеСотрудника(Сотрудник, СтрокаВидов, СтрокаЧасов);
КонецЦикла;
Рассмотрим подробнее ситуацию, когда в одной ячейке указано несколько видов времени (например, «Я/Н» в верхней строке и «8/4» в нижней). Для корректного разбора необходимо использовать функцию СтрРазделить. Это позволит превратить строку в массив и сопоставить элементы по индексам. Посмотрим на пример функции обработки:
Процедура ОбработатьДанныеСотрудника(Сотрудник, СтрокаВидов, СтрокаЧасов)
Для НомерДня = 1 По 31 Цикл
ИмяКолонки = "День" + НомерДня; // Предположим, колонки называются так
ТекстВидов = СтрокаВидов[ИмяКолонки];
ТекстЧасов = СтрокаЧасов[ИмяКолонки];
Если ПустаяСтрока(ТекстВидов) Тогда
Продолжить;
КонецЕсли;
// Разбиваем строки по разделителю "/"
МассивВидов = СтрРазделить(ТекстВидов, "/");
МассивЧасов = СтрРазделить(ТекстЧасов, "/");
// Важно: количество видов времени должно совпадать с количеством значений часов
Для Инд = 0 По МассивВидов.ВГраница() Цикл
ВидВремениКод = СокрЛП(МассивВидов[Инд]);
Часы = ?(МассивЧасов.ВГраница() >= Инд, Число(МассивЧасов[Инд]), 0);
// Здесь мы записываем данные в промежуточную ТЗ для заполнения табеля
НоваяСтрока = ТЗРезультат.Добавить();
НоваяСтрока.Сотрудник = Сотрудник;
НоваяСтрока.Дата = НачалоМесяца(Объект.ПериодРегистрации) + (НомерДня - 1) * 86400;
НоваяСтрока.ВидВремени = ПолучитьВидВремениПоКоду(ВидВремениКод);
НоваяСтрока.Часы = Часы;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
После того как мы проанализировали весь файл и собрали данные в «плоскую» таблицу (где одна строка — это один вид времени сотрудника за конкретный день), нам необходимо перенести эти данные в документ Табель. Программа 1С:ЗУП 3.1 требует специфической структуры табличной части «Данные о времени».
Вместо ручного заполнения ВидВремени1, ВидВремени2 и так далее, рекомендуется:
Объект.ДанныеОВремени.Добавить(), помня, что в ЗУП 3.1 для одного сотрудника в табеле может быть несколько строк, если у него в течение месяца менялись условия труда или графики.Важный совет: Для ускорения работы с Excel на стороне сервера (без установленного Microsoft Office) используйте метод ТабличныйДокумент.Прочитать(ПутьКФайлу, СпособЧтенияТабличногоДокумента.XLSX). Это работает значительно быстрее, чем COMОбъект, и не зависит от стороннего ПО.
Таким образом, комбинируя работу с Соответствием для поиска объектов, потоковое чтение файла и парсинг строк через СтрРазделить, мы получаем производительный и легко читаемый код, который справится с загрузкой табеля любой сложности.