Перед нами стоит задача: есть текстовый файл с тысячами строк вида "ФИО ИНН СНИЛС". Необходимо автоматизировать загрузку этих данных в «Бухгалтерию государственного учреждения, редакция 2.0», чтобы у существующих контрагентов-физлиц заполнились поля ИНН и СНИЛС. Ручной ввод такого объема данных займет много времени и неизбежно приведет к ошибкам, поэтому разработаем специальную обработку.
Рассмотрим несколько подходов к решению этой задачи, от самого простого до наиболее корректного, учитывающего все нюансы конфигурации и производительности.
Прежде чем писать собственный код, всегда стоит проверить, нет ли в конфигурации стандартного инструмента для решения нашей задачи. И он есть! Во многих программах 1С, включая БГУ, встроена универсальная обработка Загрузка данных из табличного документа.
Преимущества этого подхода:
Этот способ является предпочтительным для пользователей и бухгалтеров, так как он не требует навыков программирования и является штатным, поддерживаемым фирмой "1С" механизмом.
Если по каким-то причинам стандартный инструмент не подходит или требуется более сложная логика, можно написать собственную обработку. Проанализируем этот процесс по шагам, от наивного первого варианта до финального рабочего кода.
Начнем с простого алгоритма: читаем файл построчно, для каждой строки ищем контрагента по ФИО и обновляем его данные.
В качестве отправной точки можно использовать следующий код:
Процедура ЗагрузитьДанныеИзФайла(ПутьКФайлу)
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать(ПутьКФайлу);
МассивСтрок = СтрРазделить(Текст.ПолучитьТекст(), Символы.ПС);
Для Каждого Строка Из МассивСтрок Цикл
Если ПустаяСтрока(Строка) Тогда
Продолжить;
КонецЕсли;
// Разделителем может быть пробел или табуляция
МассивЧастей = СтрРазделить(Строка, " ");
Если МассивЧастей.Количество() >= 3 Тогда
ФИО = МассивЧастей[0] + " " + МассивЧастей[1] + " " + МассивЧастей[2]; // Пример для Ф И О
ИНН = МассивЧастей[3];
СНИЛС = МассивЧастей[4];
// Проверка на существование контрагента
СсылкаНаКонтрагента = Справочники.Контрагенты.НайтиПоНаименованию(ФИО);
Если НЕ СсылкаНаКонтрагента.Пустая() Тогда
// Обновляем существующую запись
ОбъектКонтрагент = СсылкаНаКонтрагента.ПолучитьОбъект();
ОбъектКонтрагент.ИНН = ИНН;
ОбъектКонтрагент.СНИЛС = СНИЛС; // В БГУ реквизита СНИЛС у контрагента нет
ОбъектКонтрагент.Записать();
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот код уже автоматизирует процесс, но у него есть серьезные недостатки, которые мы разберем дальше.
Главный недостаток первого варианта — использование метода НайтиПоНаименованию(). Этот метод возвращает ссылку на первый найденный элемент с указанным наименованием. Если в базе есть несколько контрагентов с одинаковым ФИО (полные тезки), данные всегда будут записываться только одному из них, что приведет к ошибкам в учете.
Правильный подход — использовать запрос для поиска. Запрос позволяет не просто найти, а посчитать, сколько элементов соответствует условию.
Усовершенствуем наш код, чтобы он пропускал загрузку для тезок и выводил их список для ручной обработки.
Функция НайтиКонтрагентовПоФИО(ФИО)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Наименование = &ФИО";
Запрос.УстановитьПараметр("ФИО", ФИО);
Возврат Запрос.Выполнить().Выгрузить(); // Возвращаем таблицу с результатами
КонецФункции
// Основной цикл загрузки
ПропущенныеКонтрагенты = Новый СписокЗначений;
Для Каждого Строка Из МассивСтрок Цикл
// ... получение ФИО, ИНН, СНИЛС из строки ...
Данные = ПолучитьДанныеИзСтроки(Строка);
НайденныеКонтрагенты = НайтиКонтрагентовПоФИО(Данные.ФИО);
Если НайденныеКонтрагенты.Количество() = 1 Тогда
// Найден ровно один контрагент - можно обновлять
СсылкаНаКонтрагента = НайденныеКонтрагенты[0].Ссылка;
// ... код записи данных ...
ИначеЕсли НайденныеКонтрагенты.Количество() > 1 Тогда
// Найдено несколько тезок, пропускаем
ПропущенныеКонтрагенты.Добавить(Данные.ФИО);
Иначе
// Контрагент не найден, ничего не делаем
КонецЕсли;
КонецЦикла;
// Выводим список пропущенных для ручной проверки
Если ПропущенныеКонтрагенты.Количество() > 0 Тогда
Сообщить("Следующие контрагенты не были загружены из-за дублирования ФИО:");
// ... цикл вывода списка ...
КонецЕсли;
Такой подход делает нашу обработку гораздо надежнее.
На этом этапе мы сталкиваемся с главной особенностью конфигурации БГУ. Личные данные физического лица (включая ИНН и СНИЛС) хранятся не в справочнике Контрагенты, а в связанном с ним справочнике ФизическиеЛица.
Элемент справочника Контрагенты имеет реквизит ЮридическоеФизическоеЛицо, который как раз и ссылается на соответствующий элемент справочника ФизическиеЛица. Именно объект физического лица нам и нужно изменять.
Еще один важный момент: после программной записи объекта данные не всегда сразу обновляются в динамических списках. Чтобы ИНН появился в списке контрагентов, нужно после записи связанного физлица пересохранить и сам объект контрагента.
Вот финальный, корректный код для БГУ 2.0:
Сначала функция поиска. Добавим в нее отбор по виду контрагента, чтобы искать только физических лиц.
Функция НайтиКонтрагентаПоФИО(ФИО)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КонтрагентыСправочник.Ссылка КАК КонтрагентСсылка
|ИЗ
| Справочник.Контрагенты КАК КонтрагентыСправочник
|ГДЕ
| КонтрагентыСправочник.Наименование = &ФИО
| И КонтрагентыСправочник.ВидКонтрагента = ЗНАЧЕНИЕ(Перечисление.ЮрФизЛицо.ФизЛицо)";
Запрос.УстановитьПараметр("ФИО", ФИО);
Возврат Запрос.Выполнить().Выбрать();
КонецФункции
Теперь процедура изменения данных, которая учитывает связь контрагента и физлица.
Процедура ИзменитьКонтрагента(КонтрагентСсылка, Данные)
// Получаем объект контрагента
ОбъектКонтрагент = КонтрагентСсылка.ПолучитьОбъект();
// Через него получаем объект связанного физлица
ОбъектФизЛицо = ОбъектКонтрагент.ЮридическоеФизическоеЛицо.ПолучитьОбъект();
// Заполняем реквизиты у физлица
ОбъектФизЛицо.ИНН = Данные.ИНН;
ОбъектФизЛицо.СтраховойНомерПФР = Данные.СНИЛС; // Правильное имя реквизита для СНИЛС
// Записываем сначала объект физлица
ОбъектФизЛицо.Записать();
// Затем пересохраняем объект контрагента, чтобы обновились все данные
ОбъектКонтрагент.Записать();
КонецПроцедуры
И основной цикл, который теперь вызывает эти функции:
Функция ЗаполнитьИННСНИЛС(МассивСтрок)
ПропущенныеКонтрагенты = Новый СписокЗначений;
Для Каждого Строка Из МассивСтрок Цикл
//...пропускаем пустые строки...
Данные = ПолучитьДанныеИзСтроки(Строка); // Функция парсит строку в структуру
ВыборкаКонтрагентов = НайтиКонтрагентаПоФИО(Данные.ФИО);
Если ВыборкаКонтрагентов.Количество() = 1 Тогда
ВыборкаКонтрагентов.Следующий();
ИзменитьКонтрагента(ВыборкаКонтрагентов.КонтрагентСсылка, Данные);
ИначеЕсли ВыборкаКонтрагентов.Количество() > 1 Тогда
ПропущенныеКонтрагенты.Добавить(Данные.ФИО);
КонецЕсли;
КонецЦикла;
Возврат ПропущенныеКонтрагенты;
КонецФункции
Объект.Записать()), которая является более ресурсоемкой операцией. Поэтому для задачи на 1000-2000 строк запрос в цикле является допустимым компромиссом.ВидКонтрагента = "Физическое лицо", но и, для корректного формирования отчетности (например, форм 169/769), рекомендуется проверять, что установлен Тип контрагента = "7 - Физическое лицо".В результате мы прошли путь от простой, но неполноценной идеи до надежного решения, которое учитывает дубликаты, особенности архитектуры конкретной конфигурации и обеспечивает корректную запись данных.