Перенос данных между различными информационными системами или загрузка документов из внешних источников — одна из самых частых задач, с которыми сталкивается разработчик 1С. Использование обычного текстового формата TXT часто выбирается в учебных целях или при интеграции с устаревшим ПО. Однако за внешней простотой текстового файла скрывается множество подводных камней: от правильной адресации файлов в клиент-серверной архитектуре до сложной типизации данных.
В этой статье мы подробно разберем, как написать универсальный механизм загрузки документов из файла TXT, проанализируем типичные ошибки начинающих разработчиков, рассмотрим по шагам корректный алгоритм и предложим более надежные альтернативные варианты интеграции.
Поставим перед собой задачу: на входе мы имеем текстовый файл, в первой строке которого указан тип создаваемого документа (например, РеализацияТоваровУслуг), а последующие строки содержат значения его реквизитов. Нам необходимо динамически создать документ соответствующего типа и корректно заполнить его реквизиты.
Рассмотрим по шагам ошибки, которые часто совершают начинающие программисты при решении этой задачи, и выясним причины их возникновения.
Ошибка №1: Прямое обращение к менеджеру через переменную
В исходном коде автора на форуме встречается следующая строка:
Док = Документы.Имясправочника.СоздатьДокумент();
Эта конструкция вызывает ошибку «Поле объекта не обнаружено (Имясправочника)». Проанализируем ситуацию: платформа 1С пытается найти в метаданных документ, который буквально называется «Имясправочника». Поскольку объекта с таким именем в конфигурации нет, система аварийно завершает работу. Для динамического обращения к объектам метаданных, когда имя типа хранится в строковой переменной, необходимо использовать квадратные скобки. Кроме того, важно не путать терминологию: если мы создаем документ, то обращаться нужно к коллекции Документы, а не к справочникам.
Правильный синтаксис динамического создания документа выглядит так:
ИмяДокумента = ТекстовыйФайл.ПолучитьСтроку(1); // Например, "РеализацияТоваровУслуг"
Док = Документы[ИмяДокумента].СоздатьДокумент();
Ошибка №2: Некорректное использование индексов при чтении файла
Вторая синтаксическая ошибка крылась в строке заполнения реквизитов:
Док[Реквизит.Имя] = Текстовыйфайл.ПолучитьСтроку(Реквизит);
Метод ПолучитьСтроку() объекта ТекстовыйДокумент ожидает на вход целочисленный номер строки (индекс), а не объект метаданных Реквизит. Передача объекта метаданных вместо числа вызывает ошибку «Несоответствие типов». Для обхода этой проблемы нам потребуется завести счетчик строк или использовать более эффективные механизмы последовательного чтения.
Ошибка №3: Нарушение клиент-серверной архитектуры
Посмотрим на строчку кода:
ТекстовыйФайл.Прочитать("C:\1с\Primer.txt");
Если этот код выполняется на сервере (в процедуре с директивой &НаСервере), система будет пытаться найти файл на диске серверного компьютера, а не на локальной машине пользователя. В клиент-серверном варианте работы системы это приведет к ошибке отсутствия файла, так как сервер физически не имеет доступа к локальному диску C:\ клиентского ПК.
Ошибка №4: Проблема типизации данных
Самая серьезная логическая ошибка заключается в попытке напрямую присвоить строковые значения из текстового файла реквизитам документа. Текстовый файл содержит только строки. Если реквизит документа имеет тип Булево, Число, Дата или является ссылкой на справочник (например, Организация или Контрагент), простая запись строки в этот реквизит приведет к ошибке или к записи пустого значения. Программа должна предварительно преобразовать строку из файла в соответствующий тип данных 1С.
---
Для того чтобы наша обработка работала как в файловом, так и в клиент-серверном варианте, мы должны сначала выбрать файл на клиенте, передать его во временное хранилище и затем прочитать на стороне сервера. Разберем этот процесс по шагам.
Напишем процедуру выбора файла и его отправки на сервер:
&НаКлиенте
Процедура ЗагрузитьОбъект(Команда)
// Настраиваем диалог выбора файла
ДиалогОткрытия = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ДиалогОткрытия.Фильтр = "Текстовые файлы (*.txt)|*.txt";
ДиалогОткрытия.МножественныйВыбор = Ложь;
ДиалогОткрытия.Заголовок = "Выберите файл для загрузки документа";
// Описываем обработчик завершения выбора файла
ОповещениеОЗавершении = Новый ОписаниеОповещения("ВыбратьФайлЗавершение", ЭтотОбъект);
ДиалогОткрытия.Показать(ОповещениеОЗавершении);
КонецПроцедуры
&НаКлиенте
Процедура ВыбратьФайлЗавершение(ВыбранныеФайлы, ДополнительныеПараметры) Экспорт
Если ВыбранныеФайлы = Неопределено Или ВыбранныеФайлы.Количество() = 0 Тогда
Возврат; // Пользователь отменил выбор
КонецЕсли;
ПутьКФайлу = ВыбранныеФайлы[0];
// Передаем файл во временное хранилище
ОписаниеФайла = Новый ОписаниеПередаваемогоФайла(ПутьКФайлу);
МассивФайлов = Новый Массив;
МассивФайлов.Добавить(ОписаниеФайла);
ОповещениеОПомещении = Новый ОписаниеОповещения("ПоместитьФайлЗавершение", ЭтотОбъект);
НачатьПомещениеФайловНаСервер(ОповещениеОПомещении, МассивФайлов, , Ложь, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПоместитьФайлЗавершение(РезультатПомещения, ДополнительныеПараметры) Экспорт
Если РезультатПомещения = Неопределено Или РезультатПомещения.Количество() = 0 Тогда
Возврат;
КонецЕсли;
ДанныеФайла = РезультатПомещения[0];
Если ДанныеФайла.ХранениеПомещено Тогда
// Файл успешно загружен в хранилище, вызываем серверную процедуру
ЗагрузитьОбъектНаСервере(ДанныеФайла.АдресФайлаВоВременномХранилище);
ПоказатьПредупреждение(, "Загрузка документа успешно завершена!");
КонецЕсли;
КонецПроцедуры
---
После того как файл оказался в хранилище на сервере, мы можем безопасно извлечь его, сохранить во временную директорию сервера и обработать. Вместо медленного объекта ТекстовыйДокумент мы применим более производительный и современный объект ЧтениеТекста, который считывает строки последовательно в режиме потока.
Рассмотрим серверную процедуру создания документа:
&НаСервере
Процедура ЗагрузитьОбъектНаСервере(АдресХранилища)
// Получаем двоичные данные файла из временного хранилища
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресХранилища);
// Создаем временный файл на сервере
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("txt");
ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
// Открываем файл для чтения
ТекстовыйФайл = Новый ЧтениеТекста;
ТекстовыйФайл.Открыть(ИмяВременногоФайла, КодировкаТекста.UTF8);
// Первая строка содержит тип документа
ИмяДокумента = ТекстовыйФайл.ПрочитатьСтроку();
Если ПустаяСтрока(ИмяДокумента) Тогда
ТекстовыйФайл.Закрыть();
УдалитьФайлы(ИмяВременногоФайла);
ВызватьИсключение "Файл пуст или имеет неверную структуру!";
КонецЕсли;
// Пытаемся динамически создать документ по его имени
Попытка
НовыйДок = Документы[ИмяДокумента].СоздатьДокумент();
Исключение
ТекстовыйФайл.Закрыть();
УдалитьФайлы(ИмяВременногоФайла);
ВызватьИсключение "Не удалось создать документ типа: " + ИмяДокумента + ". Проверьте правильность имени метаданных!";
КонецПопытки;
// Установим базовые реквизиты по умолчанию (дата и новый номер)
НовыйДок.Дата = ТекущаяДата();
НовыйДок.УстановитьНовыйНомер();
// Построчно считываем реквизиты шапки документа
Для каждого Реквизит Из НовыйДок.Метаданные().Реквизиты Цикл
СтрокаДанных = ТекстовыйФайл.ПрочитатьСтроку();
Если СтрокаДанных = Неопределено Тогда
Прервать; // Достигнут конец файла
КонецЕсли;
// Очищаем полученную строку от пробелов на концах
ЗначениеИзФайла = СокрЛП(СтрокаДанных);
// Преобразуем строковое значение к типу реквизита документа
НовыйДок[Реквизит.Имя] = ПривестиТипДанных(ЗначениеИзФайла, Реквизит.Тип);
КонецЦикла;
// Записываем заполненный документ в базу данных
Попытка
НовыйДок.Записать(РежимЗаписиДокумента.Запись);
Исключение
ТекстовыйФайл.Закрыть();
УдалитьФайлы(ИмяВременногоФайла);
ВызватьИсключение "Не удалось записать документ: " + ОписаниеОшибки();
КонецПопытки;
// Закрываем файл и подчищаем временные ресурсы
ТекстовыйФайл.Закрыть();
УдалитьФайлы(ИмяВременногоФайла);
КонецПроцедуры
---
Для корректного заполнения реквизитов документа строковые значения из файла необходимо преобразовать в соответствующие типы данных 1С (числа, даты, логические значения или ссылки на элементы справочников). Проанализируем типы метаданных и напишем универсальную функцию преобразования типов.
Для работы со ссылочными типами мы будем динамически определять имя справочника или перечисления из описания типов реквизита и искать подходящий элемент по его наименованию.
Рассмотрим код функции преобразования типов:
&НаСервере
Функция ПривестиТипДанных(ЗначениеСтрока, ОписаниеТипаРеквизита)
// Если строка в файле пустая, возвращаем пустое значение для данного типа
Если ПустаяСтрока(ЗначениеСтрока) Тогда
Возврат ОписаниеТипаРеквизита.ПривестиЗначение(Неопределено);
КонецЕсли;
// 1. Обработка логического типа (Булево)
Если ОписаниеТипаРеквизита.СодержитТип(Тип("Булево")) Тогда
ВРэгСтрока = ВРег(ЗначениеСтрока);
Если ВРэгСтрока = "ДА" Или ВРэгСтрока = "ИСТИНА" Или ВРэгСтрока = "1" Тогда
Возврат Истина;
Иначе
Возврат Ложь;
КонецЕсли;
КонецЕсли;
// 2. Обработка числового типа
Если ОписаниеТипаРеквизита.СодержитТип(Тип("Число")) Тогда
// Удаляем неразрывные пробелы и прочие разделители триад, которые могут быть в файле
ОчищеннаяСтрока = СтрЗаменить(ЗначениеСтрока, Символы.НПП, "");
ОчищеннаяСтрока = СтрЗаменить(ОчищеннаяСтрока, " ", "");
Попытка
Возврат Число(ОчищеннаяСтрока);
Исключение
Возврат 0;
КонецПопытки;
КонецЕсли;
// 3. Обработка дат
Если ОписаниеТипаРеквизита.СодержитТип(Тип("Дата")) Тогда
Попытка
Возврат Дата(ЗначениеСтрока);
Исключение
// Если дата некорректна, возвращаем пустую дату
Возврат '00010101000000';
КонецПопытки;
КонецЕсли;
// 4. Обработка ссылочных типов (Справочники)
Для каждого ТекущийТип Из ОписаниеТипаРеквизита.Типы() Цикл
// Проверяем, является ли тип ссылкой на справочник
Если Справочники.ТипВсеСсылки().СодержитТип(ТекущийТип) Тогда
// Получаем имя метаданных справочника (например, "Организации")
ИнформацияОТипе = Метаданные.НайтиПоТипу(ТекущийТип);
ИмяСправочника = ИнформацияОТипе.Имя;
// Ищем элемент справочника по наименованию
НайденнаяСсылка = Справочники[ИмяСправочника].НайтиПоНаименованию(ЗначениеСтрока);
Если ЗначениеЗаполнено(НайденнаяСсылка) Тогда
Возврат НайденнаяСсылка;
Иначе
// Если элемент не найден, можно создавать новый или возвращать пустую ссылку
Возврат Справочники[ИмяСправочника].ПустаяСсылка();
ЕслиКонец;
КонецЕсли;
// Проверяем, является ли тип перечислением
Если Перечисления.ТипВсеСсылки().СодержитТип(ТекущийТип) Тогда
ИнформацияОТипе = Метаданные.НайтиПоТипу(ТекущийТип);
ИмяПеречисления = ИнформацияОТипе.Имя;
// Ищем значение перечисления по его имени (идентификатору)
Попытка
Возврат Перечисления[ИмяПеречисления][ЗначениеСтрока];
Исключение
Возврат Неопределено;
КонецПопытки;
КонецЕсли;
КонецЦикла;
// Если тип не определен, возвращаем исходную строку
Возврат ЗначениеСтрока;
КонецФункции
---
Хотя чтение плоского текстового файла построчно отлично подходит в качестве учебного задания, на практике такой подход крайне ненадежен по следующим причинам:
РеализацияТоваровУслуг) всегда содержат табличные части со списком номенклатуры, цен и количества. Построчный плоский текстовый файл не предназначен для хранения таких иерархических данных.Для решения этих проблем в реальных проектах используются форматы JSON или XML. При этом файлы все так же могут сохраняться с расширением .txt, отвечая формальным условиям вашей задачи, но их обработка становится в разы надежнее.
Встроенный механизм XML-сериализации платформы 1С:Предприятие позволяет выгрузить объект целиком со всеми реквизитами и табличными частями буквально одной строкой кода.
Посмотрим, как выглядит выгрузка документа в XML:
// На стороне источника данных
Запись = Новый ЗаписьXML;
Запись.ОткрытьФайл("C:\1с\Primer.xml");
ЗаписатьXML(Запись, ДокументОбъект); // Передаем весь объект документа
Запись.Закрыть();
А теперь разберем по шагам, как просто происходит чтение и автоматическое создание документа из XML на стороне приемника:
// На стороне приемника данных
Чтение = Новый ЧтениеXML;
Чтение.ОткрытьФайл("C:\1с\Primer.xml");
// Платформа автоматически создаст заполненный документ нужного типа со всеми ТЧ!
НовыйДокументОбъект = ПрочитатьXML(Чтение);
НовыйДокументОбъект.Записать();
Чтение.Закрыть();
Если интеграция происходит со сторонней системой, которая не понимает внутренний формат XML 1С, оптимально использовать JSON. Этот формат компактен и легко читается человеком.
Рассмотрим пример чтения структурированного JSON-файла, где реквизиты привязаны к конкретным ключам (именам реквизитов):
// Пример структуры JSON-файла:
// {
// "ТипДокумента": "РеализацияТоваровУслуг",
// "Реквизиты": {
// "Организация": "ООО Конфетпром",
// "Склад": "Основной склад",
// "Комментарий": "Тестовая загрузка из JSON"
// }
// }
&НаСервере
Процедура ЗагрузитьИзJSON(ПутьКФайлу)
Чтение = Новый ЧтениеJSON;
Чтение.ОткрытьФайл(ПутьКФайлу);
ДанныеФайла = ПрочитатьJSON(Чтение);
Чтение.Закрыть();
ИмяДокумента = ДанныеФайла.ТипДокумента;
НовыйДок = Документы[ИмяДокумента].СоздатьДокумент();
// Заполняем реквизиты по ключам структуры JSON
Для каждого КлючЗначение Из ДанныеФайла.Реквизиты Цикл
ИмяРеквизита = КлючЗначение.Ключ;
ЗначениеТекст = КлючЗначение.Значение;
РеквизитМетаданных = НовыйДок.Метаданные().Реквизиты.Найти(ИмяРеквизита);
Если РеквизитМетаданных <> Неопределено Тогда
НовыйДок[ИмяРеквизита] = ПривестиТипДанных(ЗначениеТекст, РеквизитМетаданных.Тип);
КонецЕсли;
КонецЦикла;
НовыйДок.Записать();
КонецПроцедуры
Мы проанализировали основные ошибки, которые совершают новички при разработке процедур импорта данных в 1С. Выяснили, что для динамической работы с объектами метаданных необходимо использовать синтаксис квадратных скобок Документы[ИмяТипа], а также соблюдать правила клиент-серверной разработки и передавать локальные файлы на сервер через временное хранилище.
Для надежной интеграции в реальных проектах мы рекомендуем отказываться от построчного чтения сырого текста и использовать структурированные форматы обмена данных, такие как JSON или XML, которые поддерживаются платформой 1С на уровне ядра.