Параметры, которые сотрудник заполняет в произвольном обращении 1С:Кабинета сотрудника, не приходят в «1С:ЗУП» отдельными полями — в учётную систему попадает само обращение и PDF заявления. Но все значения в этом PDF есть. Поэтому рабочее решение без доработки сервиса такое: в расширении ЗУП извлекать текст из PDF утилитой pdftotext, разбирать его по меткам из настроек шаблона и заполнять нужные реквизиты, после чего автоматически создавать документы или отклонять обращения.

Ниже — как устроено такое решение, код на встроенном языке и подводные камни, которые стоит учесть заранее.

Задача: внутренние заявления через кабинет сотрудника

Типовой кабинет покрывает основные кадровые заявления: отпуск, справки, сведения о доходах. Но у многих компаний десятки внутренних форм — заявление на перенос отпуска по внутренним правилам, на компенсацию, на изменение графика. Для них в кабинете есть произвольное обращение с настраиваемым шаблоном.

Проблема — дальше. Чтобы ЗУП мог автоматически обработать заявление (создать документ, проверить даты, отклонить некорректное), ему нужны значения полей. А в обмене их нет: приходит обращение и присоединённый файл заявления.

Варианты, которые я рассматривал:

ВариантПлюсыМинусы
Доработать сам кабинет сотрудникаДанные придут «правильно»Риски при обновлениях сервиса, зависимость от его внутреннего устройства
Обрабатывать заявления вручнуюНичего не дорабатыватьНе масштабируется, ошибки кадровиков
Читать PDF заявления в ЗУП расширениемСервис не меняется, ЗУП остаётся на поддержкеНужна утилита и аккуратные настройки шаблонов

Выбран третий вариант: всё, что нужно, уже приходит, осталось научиться это читать.

Как прочитать PDF в 1С

Кабинет сотрудника произвольное обращение: извлечение параметров из PDF заявления
Кабинет сотрудника произвольное обращение: извлечение параметров из PDF заявления

Платформа формирует PDF, но извлекать из него текст не умеет. Для этого есть открытая консольная утилита pdftotext (входит в пакеты Poppler и Xpdf, есть сборки под Windows и Linux). Она превращает PDF в текстовый файл, если в документе есть текстовый слой. Заявления из кабинета формируются из текста, а не из сканов, так что условие выполняется.

Практические моменты:

  • Утилиту устанавливаю на сервер 1С администратором, а путь храню в константе. Выгружать исполняемый файл из макета во временный каталог при каждом вызове — плохая идея: это медленно, а антивирусы и политики безопасности справедливо блокируют запуск программ из временных каталогов.
  • Запуск — через ЗапуститьПриложение с ожиданием завершения, без WScript.Shell и вспомогательных программ вроде hstart. Так код работает и на Windows, и на Linux-сервере.
  • Кодировка вывода — UTF-8, ключ -enc UTF-8. Ключ -layout сохраняет расположение текста, что упрощает поиск по меткам.

Код: извлечение текста из PDF

Функция размещается в серверном общем модуле расширения.

1С
// Извлекает текст из PDF с помощью утилиты pdftotext.
//
// Параметры:
//   ДанныеPDF - ДвоичныеДанные - содержимое файла.
//
// Возвращаемое значение:
//   Строка - текст документа; пустая строка, если извлечь не удалось.
//
Функция ТекстИзPDF(ДанныеPDF) Экспорт

	ПутьКУтилите = Константы.ПутьКPdfToText.Получить();
	Если Не ЗначениеЗаполнено(ПутьКУтилите) Тогда
		ВызватьИсключение НСтр("ru = 'Не указан путь к утилите pdftotext.'");
	КонецЕсли;

	ИмяФайлаPDF = ПолучитьИмяВременногоФайла("pdf");
	ИмяФайлаТекста = ПолучитьИмяВременногоФайла("txt");
	ДанныеPDF.Записать(ИмяФайлаPDF);

	СтрокаКоманды = СтрШаблон("""%1"" -enc UTF-8 -layout ""%2"" ""%3""",
		ПутьКУтилите, ИмяФайлаPDF, ИмяФайлаТекста);

	Результат = "";
	Попытка
		КодВозврата = Неопределено;
		ЗапуститьПриложение(СтрокаКоманды, , Истина, КодВозврата);
		Если КодВозврата = 0 Тогда
			Текст = Новый ТекстовыйДокумент;
			Текст.Прочитать(ИмяФайлаТекста, КодировкаТекста.UTF8);
			Результат = Текст.ПолучитьТекст();
		Иначе
			ЗаписьЖурналаРегистрации("КабинетСотрудника.РазборPDF", УровеньЖурналаРегистрации.Ошибка, , ,
				СтрШаблон("pdftotext вернул код %1", КодВозврата));
		КонецЕсли;
	Исключение
		ЗаписьЖурналаРегистрации("КабинетСотрудника.РазборPDF", УровеньЖурналаРегистрации.Ошибка, , ,
			ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
	КонецПопытки;

	УдалитьФайлыБезОшибок(ИмяФайлаPDF);
	УдалитьФайлыБезОшибок(ИмяФайлаТекста);

	Возврат Результат;

КонецФункции

Процедура УдалитьФайлыБезОшибок(ИмяФайла)
	Попытка
		УдалитьФайлы(ИмяФайла);
	Исключение
		// Временный файл не критичен, ошибку не поднимаем.
	КонецПопытки;
КонецПроцедуры

Двоичные данные заявления берутся из присоединённого файла обращения средствами БСП: РаботаСФайлами.ДвоичныеДанныеФайла(СсылкаНаФайл). Имя справочника присоединённых файлов обращения зависит от версии ЗУП — посмотрите его в дереве метаданных.

Разбор текста: метки и даты

Параметры заявления делятся на две группы.

Текстовые значения ищутся между двумя метками: «что стоит перед значением» и «что стоит после». Например, между «прошу перенести» и «в связи с».

1С
// Возвращает текст между двумя метками или пустую строку.
Функция ЗначениеМеждуМетками(Текст, НачальнаяМетка, КонечнаяМетка) Экспорт

	ПозицияНачала = СтрНайти(НРег(Текст), НРег(НачальнаяМетка));
	Если ПозицияНачала = 0 Тогда
		Возврат "";
	КонецЕсли;
	ПозицияНачала = ПозицияНачала + СтрДлина(НачальнаяМетка);

	ПозицияКонца = СтрНайти(НРег(Текст), НРег(КонечнаяМетка), , ПозицияНачала);
	Если ПозицияКонца = 0 Тогда
		Возврат "";
	КонецЕсли;

	Возврат СокрЛП(Сред(Текст, ПозицияНачала, ПозицияКонца - ПозицияНачала));

КонецФункции

Даты в заявлении пишутся прописью месяца: «1 мая 2026 года». Ищем название месяца в родительном падеже: слово перед ним — число, слово после — год. Найденные даты нумеруются по порядку, а в настройке шаблона указывается, что первая дата — дата заявления, вторая — начало, третья — окончание.

1С
// Возвращает массив дат вида «1 мая 2026 года» в порядке появления в тексте.
Функция ДатыИзТекста(Текст) Экспорт

	Месяцы = Новый Соответствие;
	ИменаМесяцев = СтрРазделить("января,февраля,марта,апреля,мая,июня,июля,августа,сентября,октября,ноября,декабря", ",");
	Для Номер = 1 По ИменаМесяцев.Количество() Цикл
		Месяцы.Вставить(ИменаМесяцев[Номер - 1], Номер);
	КонецЦикла;

	Разделители = " " + Символы.ПС + Символы.Таб + Символы.ВК + ",.«»""";
	Слова = СтрРазделить(НРег(Текст), Разделители, Ложь);

	Даты = Новый Массив;
	Для Индекс = 1 По Слова.Количество() - 2 Цикл
		НомерМесяца = Месяцы.Получить(Слова[Индекс]);
		Если НомерМесяца = Неопределено Тогда
			Продолжить;
		КонецЕсли;

		ДеньСтрокой = СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке(Слова[Индекс - 1]);
		ГодСтрокой = Лев(СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке(Слова[Индекс + 1]), 4);
		Если СтрДлина(ДеньСтрокой) = 0 Или СтрДлина(ГодСтрокой) <> 4 Тогда
			Продолжить;
		КонецЕсли;

		Попытка
			Даты.Добавить(Дата(Число(ГодСтрокой), НомерМесяца, Число(ДеньСтрокой)));
		Исключение
			// Некорректная дата, например «31 июня», пропускаем.
		КонецПопытки;
	КонецЦикла;

	Возврат Даты;

КонецФункции

Настройки шаблонов вместо кода

Метки и номера дат я храню не в коде, а в регистре сведений расширения с измерениями «Шаблон обращения» и «Параметр» и ресурсами «Начальная метка», «Конечная метка», «Номер даты». Форма настройки открывается из шаблона произвольного обращения. Плюсы:

  • новый вид заявления подключается без программиста;
  • при изменении текста шаблона достаточно поправить метки;
  • одна и та же процедура разбора работает для всех шаблонов.

Сам разбор вызывается при загрузке обращения: в расширении подключаюсь к обработчику записи объекта обращения аннотацией &После, проверяю, что обращение произвольное и для его шаблона есть настройки, и выполняю разбор. Если разбор долгий или файлов много, выношу его в регламентное задание, чтобы не замедлять обмен.

Что делать с полученными параметрами

Когда значения заполнены, открывается автоматизация:

  • проверки и автоотклонение — например, дата начала раньше даты заявления или пересечение с уже оформленным отпуском;
  • создание документов — черновик кадрового документа с заполненными полями для проверки кадровиком;
  • маршрутизация — разные виды заявлений уходят разным ответственным.

Все доработки ЗУП делаю расширением: конфигурация остаётся на поддержке, кабинет сотрудника не меняется, и обновления обеих сторон проходят спокойно. Подробнее о подходе — на страницах доработка 1С:ЗУП и расширения 1С.

Типичные ошибки

  • PDF без текстового слоя. Если файл — скан, pdftotext вернёт пустую строку. Такие обращения помечайте для ручной обработки.
  • Утилита не найдена на сервере. Код выполняется на сервере 1С, а не на компьютере программиста; в кластере из нескольких рабочих серверов утилита нужна на каждом.
  • Права пользователя службы. У учётной записи сервера 1С должно быть право запуска утилиты и записи во временный каталог.
  • Изменился шаблон заявления. Метки перестают находиться — нужна проверка «все обязательные параметры заполнены» и понятное сообщение в журнале.
  • Регистр и пробелы. -layout может добавить лишние пробелы; ищите без учёта регистра и обрезайте результат.

Итог

  • Параметры произвольного обращения не приходят в ЗУП полями, но есть в PDF заявления.
  • Текст из PDF извлекается утилитой pdftotext, установленной на сервер 1С и запускаемой через ЗапуститьПриложение.
  • Значения находятся по меткам, даты — по названиям месяцев; метки хранятся в настройках шаблона.
  • Решение живёт в расширении ЗУП, кабинет сотрудника остаётся без изменений.
  • Обязательно предусмотрите ручную обработку нераспознанных заявлений.