Отвечу на первый и очевидный вопрос: "Зачем всё это?!".
Я очень люблю писать сам и наблюдать у коллег универсальный код. Это код, который можно использовать как заготовку для решения однотипных задач. Данная публикация - это продолжение других публикаций, в которых написан универсальный код. Вот о каких публикациях речь:
Чтение данных из Excel. Шаблон кода В этой публикации меня чуть не склевали за устаревший якобы подход для считывания данных из Excel. Так вот в этой публикации описан "правильный" или "современный" (хотя ему уже 100 лет в обед) способ чтения файлов Excel. Причём способ очень универсальный.
Пример работы с файлами odt в клиент-серверной модели работы. Здесь пример вывода на печать макета с заполнением параметров.
Просмотр регистров по НДФЛ. В этой публикации показано, как работать с динамическими списками + удобное отображение важнейших регистров по НДФЛ.
В самом начале разработки инструмента ставил перед собой 2 задачи:
1. Переходить максимально универсально с ЗУП 2.5/УПП (для этого использовал старое доброе COM-соединение, чтоб без файлов выполнять загрузку)
2. Загружать данные всё равно из какой системы, выгруженные в нужном формате в Excel
На текущий момент обе эти цели достигнуты, есть полное описание таблиц, необходимых для загрузки данных.
Т.к. таблиц много (почти 70 в кадровом учете и около 30 в расчетных данных), а многие ссылки повторяются, нужно было что-то универсальное, куда передашь список полей и типов и будет выполнен подбор нужных ссылок. Также хотелось, чтоб все таблицы всё время обрабатывались запросами.
Эту цель также удалось достичь.
Для этого разделил все ссылки, которые мне необходимы, на несколько категорий:
1. Перечисления и справочники с предопределенными элементами.
2. Вспомогательные справочники с условно постоянной информацией.
3. Основные справочники
4. Прочие справочники
Поясню каждую категорию:
1. Перечисления и справочники с предопределенными элементами.
Самое часто используемое в моей обработке перечисление - "Виды занятости". В переносе данных во всех таблицах, где есть сотрудник обязательно есть это поле.
Для того, чтобы не плодить лишний код и не вызывать эту функцию, перечисление запросом записано во временную таблицу ВТ_СоответствиеВидыЗанятости.
Всего в обработке получилось около 50 соответствий. В документации на перенос данных все они описаны в отдельном файле. При необходимости можно как расширить состав значений перечислений, так и добавить новое соответствие по перечислению. Просто необходимо сделать по аналогии с видами занятости.
Вот так выглядит эта процедура:
Процедура ПолучитьСоответствиеВидыЗанятости()
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблицБезCOM;
Запрос.Текст =
"ВЫБРАТЬ
| ""Основное место работы"" КАК ВидЗанятостиСтрока,
| ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.ОсновноеМестоРаботы) КАК ВидЗанятости
|ПОМЕСТИТЬ ВТ_СоответствиеВидыЗанятости
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Внешнее совместительство"",
| ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.Совместительство)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Внутреннее совместительство"",
| ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.ВнутреннееСовместительство)";
Запрос.Выполнить();
КонецПроцедуры // ПолучитьСоответствиеВидыЗанятости()
Если загрузка идёт из сторонних систем, здесь мы видим, какие варианты предусмотрены для этого поля.
Есть некоторые справочники, в которых нужно выбирать только предопределенные элементы. Как пример, это справочник "ВидыТарифовСтраховыхВзносов".
Для него также создана отдельная процедура и временная таблица ВТ_СоответствиеВидыТарифовСтраховыхВзносов:
Процедура ПолучитьСоответствиеВидыТарифовСтраховыхВзносов()
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблицБезCOM;
Запрос.Текст =
"ВЫБРАТЬ
| ""Организации, применяющие ОСН, кроме с/х производителей"" КАК ПрименяемыйЛьготныйТерриториальныйТарифСтрока,
| ЗНАЧЕНИЕ(Справочник.ВидыТарифовСтраховыхВзносов.ОбщийНалоговыйРежим) КАК ПрименяемыйЛьготныйТерриториальныйТариф
|ПОМЕСТИТЬ ВТ_СоответствиеВидыТарифовСтраховыхВзносов
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Организации, применяющие УСН, кроме указанных в подпункте 5 пункта 1 статьи 427 НК РФ (ранее - в пункте 8 части 1 статьи 58 ФЗ от 24.07.2009 № 212-ФЗ)"",
| ЗНАЧЕНИЕ(Справочник.ВидыТарифовСтраховыхВзносов.УпрощенныйНалоговыйРежим)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Организации, уплачивающие ЕНВД"",
| ЗНАЧЕНИЕ(Справочник.ВидыТарифовСтраховыхВзносов.ЕНВД)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Организации, работающие в области информационных технологий"",
| ЗНАЧЕНИЕ(Справочник.ВидыТарифовСтраховыхВзносов.ITОрганизации)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ""Организации-с/х производители, применяющие ОСН"",
| ЗНАЧЕНИЕ(Справочник.ВидыТарифовСтраховыхВзносов.СельХозПроизводители)";
Запрос.Выполнить();
КонецПроцедуры // ПолучитьСоответствиеВидыТарифовСтраховыхВзносов()
2. Вспомогательные справочники с условно постоянной информацией.
Есть справочники, которые редко меняются, т.е. являются классификаторами. Со временем, в старых базах, они сильно захламляются из-за невнимательности пользователей. Переход на новую систему - это отличный способ причесать такие справочники. На данный момент это 13 справочников и планы видов расчета "Начисления" и "Удержания" О чём идёт речь понятно по вызываемым процедурам:
//Вспомогательные справочники
ПолучитьСоответствиеВидовВычетовНДФЛИзРегистра();
ПолучитьСоответствиеВидовОбразованияИзРегистра();
ПолучитьСоответствиеВидовОтпусковИзРегистра();
ПолучитьСоответствиеВидовСтажаИзРегистра();
ПолучитьСоответствиеГрафиковРаботыИзРегистра();
ПолучитьСоответствиеКодовПозицийСпискаИзРегистра();
ПолучитьСоответствиеОснованийУвольненияИзРегистра();
ПолучитьСоответствиеПрожиточныхМинимумовИзРегистра();
ПолучитьСоответствиеСостоянийВБракеИзРегистра();
ПолучитьСоответствиеСтепенейРодстваИзРегистра();
ПолучитьСоответствиеТарифовАгентскогоСбораИзРегистра();
ПолучитьСоответствиеУченыхЗванийИзРегистра();
ПолучитьСоответствиеУченыхСтепенейИзРегистра();
//Планы видов расчета
ПолучитьСоответствиеНачисленийИзРегистра();
ПолучитьСоответствиеУдержанийИзРегистра();
Для каждого такого справочника создан в расширении регистр соответствий. Например:
Регистры соответствий заполняются заранее, на основе данных исторической системы или выгруженных в Excel данных. В момент заполнения соответствий заполняется текстовое измерение. Ресурсы заполняются вручную. Это необходимо сделать один раз в момент создания пустой базы, в которую будут загружаться данные. Соответствия можно дополнять или полностью обновлять.
Все таблицы соответствий также помещаются во временные таблицы, чтоб можно было по ним связать ссылки. Например, выделенная таблица помещается в запрос вот так:
Процедура ПолучитьСоответствиеВидовОтпусковИзРегистра()
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблицБезCOM;
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Соответствие_ВидовОтпусков.ВидОтпускаСтрока КАК ВидОтпускаСтрока,
| ВТ_Соответствие_ВидовОтпусков.ВидОтпуска КАК ВидОтпуска,
| ВТ_Соответствие_ВидовОтпусков.ЗаВредность КАК ЗаВредность
|ПОМЕСТИТЬ ВТ_СоответствиеВидовОтпусков
|ИЗ
| РегистрСведений.Соответствие_ВидовОтпусков КАК ВТ_Соответствие_ВидовОтпусков";
Запрос.Выполнить();
КонецПроцедуры // ПолучитьСоответствиеВидовОтпусковИзРегистра()
Наименование таблицы здесь немного изменено, после слова Соответствие есть "_" (нижнее подчеркивание). Это показывает в коде, что данные будут заполнены из регистра соответствий. Как и в случае с перечислениями, можно добавить ещё регистры соответствий по необходимости. В коде они добавляются по аналогии, поиск по названию соответствия ("ВТ_Соответствие_ВидовОтпусков") помогает понять что нужно скопировать.
3. Основные справочники
Для ЗУП основными справочниками являются Сотрудники, Физические лица, Подразделения, Должности. Ссылки на эти справочники есть во многих таблицах. Логично было бы добавить сюда ещё справочник "Организации", но нет. Причина в том, что нет никаких особенностей в поиске ссылки, просто по наименованию, и в одной таблице может быть несколько полей с этим типом с разным наименованием. Для основных полей важно, что название реквизита во всех таблицах должно быть одинаковым. Тогда есть возможность универсальным запросом получить нужную ссылку. Также причина в том, что Организации не загружаются, а вносятся и настраиваются вручную при создании пустой базы.
Для поиска основных справочников применяется поиск по нескольким полям. Т.к. поиск по наименованию или коду не даёт стопроцентного результата. Для справочников "Сотрудники" и "Должности" созданы в расширении регистры соответствий. Это позволяет свернуть дубли и обеспечить возможность загрузки из любых систем. Ведь далеко не в каждой системе зарплатной есть разделение на Сотрудники и Физические лица.
Ещё одной особенностью основных полей является то, что не может быть дублей по ключевым полям. В моём опыте дубли часто встречаются в справочнике "ФизическиеЛица". Возникают они при повторном приёме ранее уволенного сотрудника из-за невнимательности пользователя. С ними необходимо разбираться на стороне исходной системы. Для проекта, на котором инструмент был написан дубли исправлялись в исходной базе раза 4 (очень талантливые пользователи!). Вот по каким полям выполняется поиск:
-- Сотрудники
- ФизическоеЛицо (ссылка)
- ДатаПриема
- ВидЗанятости
-- ФизическиеЛица
- ФИО (полное без сокращений и лишнего текста)
- Дата рождения
-- Подразделения
- Код (можно заменить на код из внешней системы)
- Наименование
-- Должности
- Наименование
- КодПозицииСписка
- ОсобыеУсловияТрудаПФР
- ВзимаютсяВзносыЗаЗанятыхНаРаботахСДосрочнойПенсией
Из опыта работы именно по такому набору полей можно четко идентифицировать ссылку. При этом не важно, откуда загружаются данные.
Чтоб постоянно не обращаться к физической таблице этих справочников, все они помещены во временные таблицы. Вот пример получения подразделений:
Процедура ПолучитьПодразделения()
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблицБезCOM;
Если Не МенеджерВременныхТаблицБезCOM.Таблицы.Найти("ВТ_СоответствиеПодразделений") = Неопределено Тогда
Запрос.Текст =
"УНИЧТОЖИТЬ ВТ_СоответствиеПодразделений";
Запрос.Выполнить();
КонецЕсли;
Запрос.Текст =
"ВЫБРАТЬ
| ПодразделенияОрганизаций.Ссылка КАК Подразделение,
| СОКРЛП(ПодразделенияОрганизаций.Код) КАК Код,
| СОКРЛП(ПодразделенияОрганизаций.Наименование) КАК Наименование
|ПОМЕСТИТЬ ВТ_СоответствиеПодразделений
|ИЗ
| Справочник.ПодразделенияОрганизаций КАК ПодразделенияОрганизаций
|ГДЕ
| ПодразделенияОрганизаций.Владелец = &Организация";
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.Выполнить();
КонецПроцедуры // ПолучитьПодразделения()
Уничтожение временной таблицы здесь используется, т.к. в момент загрузки сначала получаем существующие ссылки, потом создаем недостающие элементы. После создания новых элементов нужно обновить временную таблицу, чтоб она содержала вновь загруженные элементы справочников.
4. Прочие справочники
В прочие справочники ушли все, которые не попали в основные и не нужно заполнять только предопределенные справочники.
Здесь возможен поиск по коду, по наименованию, по нескольким полям. Но те поля, по которым мы ищем элемент справочника всё равно должны обеспечивать уникальность записи. В противном случае запрос найдёт несколько записей и появится дубль в загружаемой таблице. По этой причине например, появился регистр соответствий для справочника "КодыПозицийСписка". Одному коду соответствует несколько элементов и поиск по коду/наименованию не даёт гарантии отсутствия дублей.
Как уже писал выше, для этой категории справочников можно в одной таблице иметь несколько реквизитов с одним и тем же типом значения.
Самый часто используемый пример в моём инструменте это поля "Организация" и "ГоловнаяОрганизация", которые имеют один тип значения.
Есть одна категория, поиск данных по которой в таком универсальном виде не реализован - это документы. Причина этого в том, что лишь в одном месте мне нужен поиск документа "Исполнительный лист". Поэтому поиск ссылки этого документа реализован запросом в момент загрузки конкретной таблицы. Если бы была потребность искать документы во многих таблицах - появилась бы ещё одна категория ссылок и соответствующий универсальный поиск через запрос.
Как устроен механизм поиска ссылок?
Для того чтоб механизм чтения таблиц и их преобразования из Excel таблицы или из COMОбъекта в тип "ТаблицаЗначений" был универсален, процесс чтения и преобразования полей, а далее получения ссылок разбит на следующие этапы:
1. Создание структуры таблиц по настройкам на форме загрузки. Определяются загружаемые таблицы.
2. Создание таблиц. Для каждой таблицы созданы набор полей с типом значения и соответствие полей.
3. Чтение таблиц. Через COM - выполнение запросов, через Excel - считывание файлов.
4. Преобразование таблиц на основании полей и соответствия полей.
5. Получение ссылок по каждой из 4-х выше описанных категорий.
Рассмотрим подробнее каждый этап:
1. Создание структуры таблиц по настройкам на форме загрузки.
На форму обработки выведены флаги, каждому из которых соответствуют одна или несколько таблиц. Вот пример кода для таблиц организационной структуры:
Функция ПолучитьСтруктуруТаблицСправочников(ПолучениеСоответствий)
Если ЗагружатьИзExcel Тогда
СтруктураТаблицКадровогоУчета = Новый Структура;
Иначе
СтруктураТаблицКадровогоУчета = ПодключениеКБазе.NewObject("Структура");
КонецЕсли;
СтруктураТаблицКадровогоУчета.Вставить("Подразделения");
СтруктураТаблицКадровогоУчета.Вставить("Должности");
Если ВыгружатьВоенкоматы Тогда
СтруктураТаблицКадровогоУчета.Вставить("Военкоматы");
КонецЕсли;
Если ВыгружатьКонтрагентов Тогда
СтруктураТаблицКадровогоУчета.Вставить("Контрагенты");
КонецЕсли;
Если ВыгружатьРаботодателей Тогда
СтруктураТаблицКадровогоУчета.Вставить("Работодатели");
КонецЕсли;
КонецФункции
Как видно, если грузим не из Excel, то создаем COMОбъект. Все считываемые таблицы будут также иметь тип COMОбъект, которые далее заменим на ТаблицаЗначений.
2. Создание таблиц. Для каждой таблицы создан набор полей с типом значения и соответствие полей.
Теперь в полученной структуре нужно создать необходимые таблицы. Для этого вызывается такой код:
//Для каждой таблицы определен и создан список колонок
Функция СоздатьТаблицуКадровогоУчета(ИмяТаблицы, ПолучениеСоответствий)
Если ЗагружатьИзExcel Тогда
ТаблицаСправочника = Новый ТаблицаЗначений;
Иначе
ТаблицаСправочника = ПодключениеКБазе.NewObject("ТаблицаЗначений");
КонецЕсли;
//Основные таблицы
Если ИмяТаблицы = "Контрагенты" И ВыгружатьКонтрагентов Тогда
ДобавитьПоляКонтрагенты(ТаблицаСправочника);
ИначеЕсли ИмяТаблицы = "Работодатели" И ВыгружатьРаботодателей Тогда
ДобавитьПоляРаботодатели(ТаблицаСправочника);
ИначеЕсли ИмяТаблицы = "Подразделения" Тогда
ДобавитьПоляПодразделения(ТаблицаСправочника);
ИначеЕсли ИмяТаблицы = "Должности" Тогда
ДобавитьПоляДолжности(ТаблицаСправочника);
ИначеЕсли ИмяТаблицы = "Военкоматы" И ВыгружатьВоенкоматы Тогда
ДобавитьПоляВоенкоматы(ТаблицаСправочника);
КонецЕсли;
КонецФункции
Для примера приведу текст процедуры "ДобавитьПоляПодразделения":
Процедура ДобавитьПоляПодразделения(ТаблицаСправочника)
ТаблицаСправочника.Колонки.Добавить("Владелец", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ПометкаУдаления", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("Код", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("Наименование", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РеквизитДопУпорядочивания", СтруктураОписанияТипов.ОписаниеТиповЧисла6_0);
ТаблицаСправочника.Колонки.Добавить("ОбособленноеПодразделение", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("РайонныйКоэффициент", СтруктураОписанияТипов.ОписаниеТиповЧисла6_2);
ТаблицаСправочника.Колонки.Добавить("РайонныйКоэффициентРФ", СтруктураОписанияТипов.ОписаниеТиповЧисла6_2);
ТаблицаСправочника.Колонки.Добавить("ТерриториальныеУсловияПФР", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ГоловнаяОрганизация", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РодительКод", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РодительНаименование", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("НомерТерриториальногоОрганаРосстата", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("ИмеетНомерТерриториальногоОрганаРосстата", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("РегистрацияВладелец", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РегистрацияКод", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РегистрацияНаименование", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РегистрацияКодПоОКТМО", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РегистрацияКодПоОКАТО", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РегистрацияКПП", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("РегистрацияНаименованиеИФНС", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
ТаблицаСправочника.Колонки.Добавить("Сформировано", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("ДатаСоздания", СтруктураОписанияТипов.ОписаниеТиповДаты);
ТаблицаСправочника.Колонки.Добавить("Расформировано", СтруктураОписанияТипов.ОписаниеТиповБулево);
ТаблицаСправочника.Колонки.Добавить("ДатаРасформирования", СтруктураОписанияТипов.ОписаниеТиповДаты);
ТаблицаСправочника.Колонки.Добавить("ПрименяемыйЛьготныйТерриториальныйТариф", СтруктураОписанияТипов.ОписаниеТиповСтроки150);
КонецПроцедуры // ДобавитьПоляПодразделения()
Наверняка возник вопрос: "Почему длина строк 150 символов, особенно там где она реально меньше". Длина строки выбрана, как максимально возможная длина наименования. Больше платформа не позволяет сделать. Текстовые поля могут быть и больше, для этого используется строка 350 символов. Если длина строки сильно меньше, то это ни на то особо не влияет! При выгрузке данных, строка отображается в том размере, который по факту занимает. При выполнении запросов цифра нужна просто для типизации полей. В то же время долбаться и для каждого поля устанавливать его реальную длину - это долго. Поэтому принят такой универсальный способ.
Если загрузка происходит из ЗУП 2.5/УПП, то выгружаются именно эти поля с указанным типом значения. Если загрузка происходит из других систем, то необходимо заполнить шаблон с этим набором полей. Если какое-то поле невозможно заполнить, нужно оставить его пустым. Удалять нельзя, т.к. далее есть соответствие полей, если какого-то поля не будет, то будет ошибка в момент преобразования таблиц.
Также в этот список можно добавлять свои поля. Можно и новые таблицы добавлять. Их необходимо делать по аналогии. Код обработки чётко структурирован по областям. Внутри каждой области также сохраняется одинаковый порядок следования процедур/функций по шаблонам. Поэтому добавление новой таблицы является простой задачей!
Для каждой загружаемой таблицы есть соответствие полей. Причина в том, что сначала была разработана обработка выгрузки данных по кадровому блоку (примерно 70 таблиц). Только потом разрабатывался механизм загрузки данных. И на этапе выгрузки названия полей брались "как есть" из исторической системы. На этапе загрузки данных выполнялось сопоставление полей УПП и ЗУП 3.1. В соответствие переносились названия полей как они есть в новой системе, т.е. в ЗУП 3.1. Также в нём задаются правила подбора ссылок, исходя из разделения ссылок на 4 категории. Соответствие полей для таблицы подразделений выглядит следующим образом:
Функция ПолучитьСоответствиеКолонокПодразделения(ИмяТаблицы)
СоответствиеКолонок = Новый Соответствие;
//Организация
СоответствиеКолонок.Вставить("Владелец", "ВладелецБезСсылки");
СоответствиеКолонок.Вставить("ПометкаУдаления", "ПометкаУдаления");
//Поиск по коду и наименованию
СоответствиеКолонок.Вставить("Код", "Код");
СоответствиеКолонок.Вставить("Наименование", "Наименование");
СоответствиеКолонок.Вставить("РеквизитДопУпорядочивания", "РеквизитДопУпорядочивания");
СоответствиеКолонок.Вставить("ОбособленноеПодразделение", "ОбособленноеПодразделение");
СоответствиеКолонок.Вставить("РайонныйКоэффициент", "РайонныйКоэффициент");
СоответствиеКолонок.Вставить("РайонныйКоэффициентРФ", "РайонныйКоэффициентРФ");
СоответствиеКолонок.Вставить("ТерриториальныеУсловияПФР", "ТерриториальныеУсловияПФРБезСсылки");
СоответствиеКолонок.Вставить("ГоловнаяОрганизация", "ГоловнаяОрганизацияБезСсылки");
//Поиск по коду и наименованию
СоответствиеКолонок.Вставить("РодительКод", "РодительБезСсылки");
СоответствиеКолонок.Вставить("РодительНаименование", "РодительНаименование");
СоответствиеКолонок.Вставить("НомерТерриториальногоОрганаРосстата", "НомерТерриториальногоОрганаРосстата");
СоответствиеКолонок.Вставить("ИмеетНомерТерриториальногоОрганаРосстата", "ИмеетНомерТерриториальногоОрганаРосстата");
//Регистрация в налоговом органе
//Организация
СоответствиеКолонок.Вставить("РегистрацияВладелец", "РегистрацияВладелецБезСсылки");
СоответствиеКолонок.Вставить("РегистрацияКод", "РегистрацияБезСсылки");
СоответствиеКолонок.Вставить("РегистрацияНаименование", "РегистрацияНаименование");
СоответствиеКолонок.Вставить("РегистрацияКодПоОКТМО", "РегистрацияКодПоОКТМО");
СоответствиеКолонок.Вставить("РегистрацияКодПоОКАТО", "РегистрацияКодПоОКАТО");
СоответствиеКолонок.Вставить("РегистрацияКПП", "РегистрацияКПП");
СоответствиеКолонок.Вставить("РегистрацияНаименованиеИФНС", "РегистрацияНаименованиеИФНС");
СоответствиеКолонок.Вставить("Сформировано", "Сформировано");
СоответствиеКолонок.Вставить("ДатаСоздания", "ДатаСоздания");
СоответствиеКолонок.Вставить("Расформировано", "Расформировано");
СоответствиеКолонок.Вставить("ДатаРасформирования", "ДатаРасформирования");
//В исходной базе это перечисление. Необходимо сопоставить с предопределенными элементами справочника
//Сопоставление по таблице ВТ_СоответствиеВидыТарифовСтраховыхВзносов
СоответствиеКолонок.Вставить("ПрименяемыйЛьготныйТерриториальныйТариф", "ПрименяемыйЛьготныйТерриториальныйТарифБезСсылки");
//Заполним данные для поиска соответствий
СоответствияПолей = Новый Соответствие;
СоответствияПолей.Вставить("ПрименяемыйЛьготныйТерриториальныйТарифБезСсылки", "ВТ_СоответствиеВидыТарифовСтраховыхВзносов");
СтруктураНеобходимыхСоответствий[ИмяТаблицы] = СоответствияПолей;
//Соответствия для справочников
СоответствияПолей = Новый Соответствие;
СоответствияПолей.Вставить("ГоловнаяОрганизацияБезСсылки", "Справочник.Организации");
СоответствияПолей.Вставить("ВладелецБезСсылки", "Справочник.Организации");
СоответствияПолей.Вставить("ТерриториальныеУсловияПФРБезСсылки", "Справочник.ТерриториальныеУсловияПФР");
СоответствияПолей.Вставить("РегистрацияВладелецБезСсылки", "Справочник.Организации");
СоответствияПолей.Вставить("РегистрацияБезСсылки", "Справочник.РегистрацииВНалоговомОргане");
СтруктураСоответствийДляСправочников[ИмяТаблицы] = СоответствияПолей;
Возврат СоответствиеКолонок;
КонецФункции // ПолучитьСоответствиеКолонокПодразделения()
3. Чтение таблиц. Через COM - выполнение запросов, через Excel - считывание файлов.
Получение данных через COM соединение описывать нет смысла. Выполняется запрос и в цикле по выборке через заполнить значения свойств заполняются данные в таблице. Запрос.Выполнить().Выгрузить() не используется, т.к. теряется типизация полей. Она понадобится дальше для преобразования таблицы из типа COMОбъект в ТаблицаЗначений.
Считывание файлов делаю сразу на сервере. Для этого мне админ дал папку, которая доступна на сервере. При загрузке из файлов Excel, необходимо такую папку готовить. Файлы считываются сначала в массив:
МассивМасок = Новый Массив;
МассивМасок.Добавить("*.xls");
МассивМасок.Добавить("*.xlsx");
МассивФайлов = Новый Массив;
Для Каждого Маска Из МассивМасок Цикл
ОбщегоНазначенияКлиентСервер.ДополнитьМассив(МассивФайлов, НайтиФайлы(ПутьКФайламВыгрузки, Маска, Ложь), Истина);
КонецЦикла;
Какие файлы нам нужно считать, определяется по созданным в структуре таблиц элементам. В наименовании файла обязательно должно быть наименование таблицы. Причем может быть несколько файлов для одной таблицы. Код ниже дописывает новые записи в таблицу из дополнительных файлов.
Считывание происходит "правильным" методом без использования приложения Excel. Полученную таблицу переписываем в ранее созданную таблицу, чтоб сохранить типы значений.
Процедура ПрочитатьФайлыПоСтруктуреТаблиц(СтруктураТаблиц, МассивФайлов)
//В первом цикле получаем таблицу
Для Каждого ТекТаблица Из СтруктураТаблицКадровогоУчета Цикл
НаименованиеДляПоискаФайла = ТекТаблица.Ключ;
КоличествоКолонок = ТекТаблица.Значение.Колонки.Количество();
//Во втором цикле ищем соответствующий ей файл
Для Каждого ТекФайл Из МассивФайлов Цикл
//Если не нашли, ищем дальше
Если СтрНайти(ТекФайл.ИмяБезРасширения, НаименованиеДляПоискаФайла) = 0 Тогда
Продолжить;
КонецЕсли;
//Переведем файл в двоичные данные
ДвоичныеДанные = Новый ДвоичныеДанные(ТекФайл.ПолноеИмя);
ФайлЭксель = ПолучитьИмяВременногоФайла(ТекФайл.Расширение);
ДвоичныеДанные.Записать(ФайлЭксель);
//Считываем данные в табличный документ
ТабличныйДокумент = Новый ТабличныйДокумент;
ТабличныйДокумент.Прочитать(ФайлЭксель);
ТабличныйДокумент.ФиксацияСверху = 1;
ТабличныйДокумент.ОриентацияСтраницы = ОриентацияСтраницы.Ландшафт;
//Удаляем временный файл
УдалитьФайлы(ФайлЭксель);
//Определяем область в табличном документе для считывания в запрос
//Т.к. в первой строке записаны названия колонок, в результате все колонки будут с правильными названиями
ОбластьЧтения = ТабличныйДокумент.Область(1, 1, ТабличныйДокумент.ВысотаТаблицы, КоличествоКолонок);
ПостроительЗапроса = Новый ПостроительЗапроса;
ПостроительЗапроса.ИсточникДанных = Новый ОписаниеИсточникаДанных(ОбластьЧтения);
ПостроительЗапроса.Выполнить();
//Переносим данные в структуру
ТаблицаСДанными = ПостроительЗапроса.Результат.Выгрузить();
ПеренестиРезультатВСтруктуру(СтруктураТаблиц, НаименованиеДляПоискаФайла, ТаблицаСДанными);
КонецЦикла;
КонецЦикла;
КонецПроцедуры // ПрочитатьФайлыПоСтруктуреТаблиц()
Процедура ПеренестиРезультатВСтруктуру(СтруктураТаблиц, НаименованиеТаблицы, ТаблицаСДанными);
ТекущаяТаблица = СтруктураТаблиц[НаименованиеТаблицы];
Для Каждого ТекСтрока Из ТаблицаСДанными Цикл
НовСтрока = ТекущаяТаблица.Добавить();
ЗаполнитьЗначенияСвойств(НовСтрока, ТекСтрока);
КонецЦикла;
КонецПроцедуры // ПеренестиРезультатВСтруктуру()
4. Преобразование таблиц на основании полей и соответствия полей.
На этом этапе у нас есть либо COMОбъект, в котором куча таблиц, либо просто структура с таблицами. Задача преобразовать её не только в тип "ТаблицаЗначений", но и поля сделать такими, какие нужны для загрузки данных. Указать правила, по которым будут подобраны все ссылки. Для этого написаны универсальные процедуры для преобразования таблиц. Над их написанием пришлось прям очень подумать. Не сразу получилось. Преобразование происходит в 4 шага.
// Преобразует таблицы кадрового учета из COM-объекта в тип ТаблицаЗначений
Процедура ПреобразоватьТаблицыКадровогоУчета() Экспорт
//1. Создаем новую структуру
СтруктураТаблицКадровогоУчетаБезCOM = СоздатьСтруктуруТаблицКадровогоУчетаБезCOM();
//2. Создаем в цикле таблицы
СоздатьКолонкиТаблицКадровогоУчетаБезCOM();
//3. Перенесём данные из таблиц COM в обычные
ЗаполнитьТаблицыКадровогоУчетаБезCOM();
//4. Поместим все таблицы в запрос
МенеджерВременныхТаблицБезCOM = ПоместитьТаблицыКадровогоУчетаВЗапрос();
КонецПроцедуры // ПреобразоватьТаблицыКадровогоУчета()
На первом этапе у нас есть структура, которая имеет тип значения "COMОбъект". Поэтому нам необходимо перезаписать её в тип "Структура".
Функция СоздатьСтруктуруТаблицКадровогоУчетаБезCOM()
СтруктураТаблиц = Новый Структура;
Для Каждого ТекТаблица Из СтруктураТаблицКадровогоУчета Цикл
Если СтрНайти(ТекТаблица.Ключ, "Настройки") = 1 Или СтрНайти(ТекТаблица.Ключ, "Список") = 1 Или СтрНайти(ТекТаблица.Ключ, "Отбор") = 1 Тогда
Продолжить;
КонецЕсли;
СтруктураТаблиц.Вставить(ТекТаблица.Ключ);
КонецЦикла;
Возврат СтруктураТаблиц;
КонецФункции // СоздатьСтруктуруТаблицКадровогоУчетаБезCOM()
На втором этапе решаем самую сложную задачу. В новой структуре нужно создать таблицы и колонки основываясь на соответствии колонок. Причём тип значения устанавливается исходя из типа данных в колонках исходной таблицы. А имена колонок устанавливаются исходя из соответствия полей, которое задается для каждой таблицы. Имена таблиц не меняются.
Процедура СоздатьКолонкиТаблицКадровогоУчетаБезCOM()
СтруктураНеобходимыхСоответствий = Новый Структура;
СтруктураСоответствийДляСправочников = Новый Структура;
СтруктураСоответствийОсновныхСправочников = Новый Структура;
Для Каждого ТекТаблица Из СтруктураТаблицКадровогоУчета Цикл
Если СтрНайти(ТекТаблица.Ключ, "Настройки") = 1 Или СтрНайти(ТекТаблица.Ключ, "Список") = 1 Или СтрНайти(ТекТаблица.Ключ, "Отбор") = 1 Тогда
Продолжить;
КонецЕсли;
Если ЗагружатьИзExcel Тогда
ИсходнаяТаблицаСТипамиКолонок = СоздатьТаблицуКадровогоУчета(ТекТаблица.Ключ, Ложь);
КонецЕсли;
ТаблицаБезCOM = Новый ТаблицаЗначений;
//Создаем элемент, в котором ключом будет имя текущей таблицы
//Значением будет массив соответствий.
//Ключ соответствия - Имя поля, для которого необходимо получить соответствие
//Значением будет имя временной таблицы, в которой хранится соответствие
СтруктураНеобходимыхСоответствий.Вставить(ТекТаблица.Ключ);
СтруктураСоответствийДляСправочников.Вставить(ТекТаблица.Ключ);
СтруктураСоответствийОсновныхСправочников.Вставить(ТекТаблица.Ключ);
//3. Получаем соответствие колонок
СоответствиеКолонок = ПолучитьСоответствиеКолонокПоТаблице(ТекТаблица.Ключ);
Если ЗагружатьИзExcel Тогда
КоллекцияКолонок = ИсходнаяТаблицаСТипамиКолонок.Значение.Колонки;
Иначе
КоллекцияКолонок = ТекТаблица.Значение.Колонки;
КонецЕсли;
//4. В цикле по колонкам ...
Для Каждого ТекКолонка Из КоллекцияКолонок Цикл
НовыйТипКолонки = Неопределено;
//-- Определяем тип колонки. Длину строки и числа определяем по квалификаторам
ТипКолонки = ТекКолонка.ValueType.Types().Получить(0);
Если Не СтруктураОписанияТипов.ОписаниеТиповБулево.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Булево
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповБулево;
ИначеЕсли Не СтруктураОписанияТипов.ОписаниеТиповДаты.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Дата
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповДаты;
ИначеЕсли Не СтруктураОписанияТипов.ОписаниеТиповСтроки150.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Строка
Если ТекКолонка.ValueType.StringQualifiers.Length = 150 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки150;
ИначеЕсли ТекКолонка.ValueType.StringQualifiers.Length = 350 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки350;
ИначеЕсли ТекКолонка.ValueType.StringQualifiers.Length = 9 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки9;
КонецЕсли;
//Тип строки по умолчанию
Если НовыйТипКолонки = Неопределено Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповСтроки150;
КонецЕсли;
ИначеЕсли Не СтруктураОписанияТипов.ОписаниеТиповЧисла6_0.Types().Find(ТипКолонки) = Неопределено Тогда
//Тип Число
Если ТекКолонка.ValueType.NumberQualifiers.Digits = 15 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла15_2;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 3 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла15_3;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 4 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 0 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла4_0;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 3 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 0 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла3_0;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 6 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 0 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла6_0;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла6_2;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 10 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 0 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла10_0;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла10_2;
КонецЕсли;
ИначеЕсли ТекКолонка.ValueType.NumberQualifiers.Digits = 8 Тогда
Если ТекКолонка.ValueType.NumberQualifiers.FractionDigits = 2 Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла8_2;
КонецЕсли;
КонецЕсли;
//Тип числа по умолчанию
Если НовыйТипКолонки = Неопределено Тогда
НовыйТипКолонки = СтруктураОписанияТиповБезCOM.ОписаниеТиповЧисла6_0;
КонецЕсли;
КонецЕсли;
//-- Получаем соответствие колонки
НовоеНаименованиеКолонки = СоответствиеКолонок.Получить(ТекКолонка.Name);
Если Не ЗначениеЗаполнено(НовоеНаименованиеКолонки) Тогда
Продолжить;
КонецЕсли;
//-- Создаем новую колонку
ТаблицаБезCOM.Колонки.Добавить(НовоеНаименованиеКолонки, НовыйТипКолонки);
КонецЦикла;
//Вставляем полученную таблицу в структуру
СтруктураТаблицКадровогоУчетаБезCOM[ТекТаблица.Ключ] = ТаблицаБезCOM;
КонецЦикла;
КонецПроцедуры // СоздатьКолонкиТаблицКадровогоУчетаБезCOM()
На третьем шаге необходимо переписать данные из COMОбъекта в новые таблицы. Некоторая сложность этой процедуры в том, что имя колонки откуда брать данные - старое, а куда положить - новое, из соответствия колонок.
Процедура ЗаполнитьТаблицыКадровогоУчетаБезCOM()
//Цикл по таблицам
Для Каждого ТекТаблица Из СтруктураТаблицКадровогоУчета Цикл
ТаблицаБезCOM = Неопределено;
ЕстьТаблицаБезCOM = СтруктураТаблицКадровогоУчетаБезCOM.Свойство(ТекТаблица.Ключ, ТаблицаБезCOM);
Если ТаблицаБезCOM = Неопределено Тогда
Продолжить;
КонецЕсли;
СоответствиеКолонок = ПолучитьСоответствиеКолонокПоТаблице(ТекТаблица.Ключ);
//Цикл по строкам таблицы
Для Каждого ТекСтрокаТаблицы Из ТекТаблица.Значение Цикл
НовСтрокаТаблицы = ТаблицаБезCOM.Добавить();
//Цикл по колонкам
Для Каждого ТекКолонка Из СоответствиеКолонок Цикл
Если ТипЗнч(ТекСтрокаТаблицы[ТекКолонка.Ключ]) = Тип("Строка") Тогда
НовСтрокаТаблицы[ТекКолонка.Значение] = СокрЛП(ТекСтрокаТаблицы[ТекКолонка.Ключ]);
Иначе
НовСтрокаТаблицы[ТекКолонка.Значение] = ТекСтрокаТаблицы[ТекКолонка.Ключ];
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецПроцедуры // ЗаполнитьТаблицыБезCOM()
На последнем шаге помещаем все преобразованные таблицы во временные таблицы. Дальнейшая работа с данными будет через запросы.
Функция ПоместитьТаблицыКадровогоУчетаВЗапрос()
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
//К таким полям добавляем в название "БезСсылки" для упрощения дальнейшего заполнения ссылочных полей
СписокКлючевыхСсылочныхПолей = ПолучитьСписокКлючевыхПолей();
Для Каждого ТекТаблица Из СтруктураТаблицКадровогоУчетаБезCOM Цикл
Если СтрНайти(ТекТаблица.Ключ, "Настройки") = 1 Или СтрНайти(ТекТаблица.Ключ, "Список") = 1 Или СтрНайти(ТекТаблица.Ключ, "Отбор") = 1 Тогда
Продолжить;
КонецЕсли;
ИмяВременнойТаблицы = "ВТ_" + ТекТаблица.Ключ;
ПоместитьТаблицуВЗапрос(ТекТаблица.Значение, Запрос.МенеджерВременныхТаблиц, ИмяВременнойТаблицы, СписокКлючевыхСсылочныхПолей);
КонецЦикла;
Возврат Запрос.МенеджерВременныхТаблиц;
КонецФункции // ПоместитьТаблицыКадровогоУчетаВЗапрос()
Как видно, все процедуры и функции написаны универсально и не привязаны к какой-то конфигурации. На их основе можно построить абсолютно любой перенос данных! Благодаря наличию этих процедур сильно упрощается код, становится понятным, нет необходимости этот код копировать 100 раз (по количеству загружаемых таблиц). Если нужно добавить новую таблицу или новые поля к существующим таблицам, нет необходимости модифицировать код преобразования таблиц.
Именно эти процедуры стали для меня решением задач, которые я описал в самом начале статьи!
Осталось описать, какими способами я получаю ссылки по тем четырём категориям ссылок, которые описаны в начале статьи.
Способы получения ссылок
Рассмотрим этот вопрос на примере таблицы "РегистрРасчетаНачисления"
Функция ПолучитьСоответствиеКолонокРегистрРасчетаНачисления(ИмяТаблицы)
СоответствиеКолонок = Новый Соответствие;
//Поиск по наименованию Справочник.Организации
СоответствиеКолонок.Вставить("ОбособленноеПодразделение", "ОбособленноеПодразделениеБезСсылки");
СоответствиеКолонок.Вставить("Организация", "ОрганизацияБезСсылки");
//Поиск по реквизитам ВидЗанятости и ДатаПриема
//При загрузке данных эти значения будут браться из регистра сведений Соответствие_Сотрудников
//Соответствие по ВТ_СоответствиеСотрудников
СоответствиеКолонок.Вставить("ФИОСотрудника", "СотрудникБезСсылки");
//Соответствие по ВТ_СоответствиеВидыЗанятости
СоответствиеКолонок.Вставить("ВидЗанятости", "ВидЗанятостиБезСсылки");
СоответствиеКолонок.Вставить("ДатаПриема", "ДатаПриема");
//Поиск запросом по соответствию 2х параметров
СоответствиеКолонок.Вставить("ФИО", "ФизическоеЛицоБезСсылки");
СоответствиеКолонок.Вставить("ДатаРождения", "ДатаРождения");
СоответствиеКолонок.Вставить("ДатаНачалаСобытия", "ДатаНачалаСобытия");
СоответствиеКолонок.Вставить("ПериодРегистрации", "ПериодРегистрации");
//Соответствие по ВТ_СоответствиеНачислений
СоответствиеКолонок.Вставить("ВидРасчета", "НачислениеБезСсылки");
СоответствиеКолонок.Вставить("Результат", "Результат");
СоответствиеКолонок.Вставить("ДатаНачалаСобытия", "ДатаНачалаСобытия");
СоответствиеКолонок.Вставить("Сторно", "Сторно");
СоответствиеКолонок.Вставить("ПериодДействия", "ПериодДействия");
СоответствиеКолонок.Вставить("ПериодДействияНачало", "ПериодДействияНачало");
СоответствиеКолонок.Вставить("ПериодДействияКонец", "ПериодДействияКонец");
СоответствиеКолонок.Вставить("БазовыйПериодНачало", "БазовыйПериодНачало");
СоответствиеКолонок.Вставить("БазовыйПериодКонец", "БазовыйПериодКонец");
//Поиск через РС из расширения ВТ_СоответствиеГрафиковРаботы
СоответствиеКолонок.Вставить("ГрафикРаботы", "ГрафикРаботыБезСсылки");
СоответствиеКолонок.Вставить("ОтработаноДней", "ОтработаноДней");
СоответствиеКолонок.Вставить("ОтработаноЧасов", "ОтработаноЧасов");
СоответствиеКолонок.Вставить("ОплаченоДнейЧасов", "ОплаченоДнейЧасов");
СоответствиеКолонок.Вставить("РезультатВТомЧислеЗаСчетФБ", "РезультатВТомЧислеЗаСчетФБ");
//Поиск через РС из расширения ВТ_СоответствиеГрафиковРаботы
СоответствиеКолонок.Вставить("ГрафикРаботыНорма", "ГрафикРаботыНормаБезСсылки");
СоответствиеКолонок.Вставить("РасчетнаяБазаЗаЕдиницуНормыВремени", "РасчетнаяБазаЗаЕдиницуНормыВремени");
//Заполним данные для поиска соответствий
СоответствияПолей = Новый Соответствие;
СоответствияПолей.Вставить("ВидЗанятостиБезСсылки", "ВТ_СоответствиеВидыЗанятости");
СоответствияПолей.Вставить("ГрафикРаботыБезСсылки", "ВТ_СоответствиеГрафиковРаботы");
СоответствияПолей.Вставить("ГрафикРаботыНормаБезСсылки", "ВТ_СоответствиеГрафиковРаботы");
СоответствияПолей.Вставить("НачислениеБезСсылки", "ВТ_СоответствиеНачислений");
СтруктураНеобходимыхСоответствий[ИмяТаблицы] = СоответствияПолей;
//Соответствия для справочников
СоответствияПолей = Новый Соответствие;
СоответствияПолей.Вставить("ФизическоеЛицоБезСсылки", "Справочник.ФизическиеЛица");
СоответствияПолей.Вставить("ОбособленноеПодразделениеБезСсылки", "Справочник.Организации");
СоответствияПолей.Вставить("ОрганизацияБезСсылки", "Справочник.Организации");
СтруктураСоответствийДляСправочников[ИмяТаблицы] = СоответствияПолей;
//Соответствия для основных справочников
СоответствияПолей = Новый Соответствие;
СоответствияПолей.Вставить("СотрудникБезСсылки", "ВТ_СоответствиеСотрудников");
СоответствияПолей.Вставить("ПодразделениеБезСсылки", "Справочник.ПодразделенияОрганизаций");
СтруктураСоответствийОсновныхСправочников[ИмяТаблицы] = СоответствияПолей;
Возврат СоответствиеКолонок;
КонецФункции // ПолучитьСоответствиеКолонокРегистрРасчетаНачисления()
В конце функции заполняются для данной таблицы 3 соответствия. Все 3 структуры были созданы в момент преобразования таблиц из типа COMОбъект в тип "ТаблицаЗначений". В каждой структуре количество элементов совпадает с количеством загружаемых таблиц. Ключ в каждой структуре - это наименование таблицы.
Значением структуры является соответствие. Ключ в соответствии - это имя поля, которому мы подбираем ссылку. В наименовании такого поля в конце должен быть постфикс "БезСсылки". Это необходимо для того, чтоб после присоединения ссылки запросом, поле называлось нормально.
Например, есть поле "ВидЗанятостиБезСсылки" (ключ соответствия). Это перечисление, которое мы положили во временную таблицу "ВТ_СоответствиеВидыЗанятости" (значение соответствия). После выполнения универсального запроса добавится поле "ВидЗанятости". Оно то и будет в дальнейшем использоваться.
Вот для чего используются эти структуры:
- СтруктураНеобходимыхСоответствий
Первая структура позволяет за один запрос подобрать ссылки перечислений, предопределенных элементов справочников и вспомогательных справочников, для которых созданы регистры соответствий. Этот запрос выполняется в первую очередь, т.к. полученные данные (например ВидЗанятости) используется как одно из полей для получения сотрудников. Как видно из кода, может быть несколько полей с одним типом значения, но с разным наименованием.
- СтруктураСоответствийДляСправочников
Вторая структура позволяет подобрать ссылки для прочих справочников. Ключом соответствия также является имя поля (например "ОбособленноеПодразделениеБезСсылки"), а значением является название таблицы, в которой хранится этот тип значения (например "Справочник.Организации"). Здесь также может быть несколько полей с одним и тем же типом значения.
- СтруктураСоответствийОсновныхСправочников
Третья структура необходима для подбора ссылок для справочников "ПодразделенияОрганизаций", "Должности" и "Сотрудники". Почему же справочник "ФизическиеЛица" во второй структуре!? Всё просто, поле "ФизическоеЛицо" является одним из ключевых полей для поиска сотрудника в регистре сведений.
Т.к. запрос выполняется один раз для таблицы, ссылка на физлицо должна быть заполнена на момент выполнения запроса. И среди этих таблиц нет возможности указать разные наименования полей. Но это ограничение является особенностью ЗУП. Сотрудник, Должность и Подразделение всегда называется одинаково во всех объектах метаданных.
Т.к. для должностей и сотрудников созданы регистры сведений (чтоб не допустить в них дубликаты), данные получаются не из физической таблицы справочника, а из "ВТ_СоответствиеДолжностей" и "ВТ_СоответствиеСотрудников".
Рассмотрим, как в коде реализован подбор ссылок для перечислений, справочников и вспомогательных справочников.
Сами универсальные функции выглядят так:
Процедура ЗаполнитьСсылкиПеречисленийИВспомогательныхСправочниковВЗапросах()
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблицБезCOM;
Для Каждого ТекТаблица Из СтруктураНеобходимыхСоответствий Цикл
Если ТекТаблица = Неопределено Тогда
Продолжить;
КонецЕсли;
Если ТекТаблица.Значение = Неопределено Тогда
Продолжить;
КонецЕсли;
Запрос.Текст = ПолучитьТекстЗапросаПоТаблице(ТекТаблица.Ключ, ТекТаблица.Значение);
Запрос.Выполнить();
КонецЦикла;
КонецПроцедуры // ЗаполнитьСсылкиПеречисленийИВспомогательныхСправочниковВЗапросах()
Функция ПолучитьТекстЗапросаПоТаблице(ИмяТаблицы, СоответствиеСсылочныхПолей)
//Получаем соответствие полей
СоответствиеПолейТаблицы = ПолучитьСоответствиеКолонокПоТаблице(ИмяТаблицы);
ИмяВТ = "ВТ_" + ИмяТаблицы;
ТекстЗапроса =
"ВЫБРАТЬ";
//К таким полям добавляем в название "БезСсылки" для упрощения дальнейшего заполнения ссылочных полей
СписокКлючевыхСсылочныхПолей = ПолучитьСписокКлючевыхПолей();
//Добавляем поля таблицы в запрос
Для Каждого ТекПоле Из СоответствиеПолейТаблицы Цикл
ЭтоКлючевоеПоле = Не СписокКлючевыхСсылочныхПолей.Найти(ТекПоле.Значение) = Неопределено;
ИмяПоля = ?(ЭтоКлючевоеПоле, ТекПоле.Значение + "БезСсылки", ТекПоле.Значение);
ТекстЗапроса = ТекстЗапроса + "
| " + ИмяВТ + "." + ИмяПоля + " КАК " + ИмяПоля + ",";
КонецЦикла;
//Т.к. в одной таблице есть несколько реквизитов с одинаковым типов введем нумерацию таблиц
//Это позволит избежать повторяющихся псевдонимов в запросе
НомерСоединения = 1;
//Получим и добавим поля найденных ссылок в запрос
КоличествоПолей = СоответствиеСсылочныхПолей.Количество();
Для Каждого ТекПоле Из СоответствиеСсылочныхПолей Цикл
ИмяПоляСоединения = ПолучитьИмяПоляПоВременнойТаблице(ТекПоле.Значение);
ИмяПоля = СтрЗаменить(ТекПоле.Ключ, "БезСсылки", "");
ТекстЗапроса = ТекстЗапроса + "
| " + ТекПоле.Значение + НомерСоединения + "." + ИмяПоляСоединения + " КАК " + ИмяПоля;
КоличествоПолей = КоличествоПолей - 1;
Если Не КоличествоПолей = 0 Тогда
ТекстЗапроса = ТекстЗапроса + ",";
КонецЕсли;
НомерСоединения = НомерСоединения + 1;
КонецЦикла;
ТекстЗапроса = ТекстЗапроса + "
|ПОМЕСТИТЬ " + ИмяВТ + "БезСсылки
|ИЗ
| " + ИмяВТ + " КАК " + ИмяВТ;
//Добавим связи с таблицами соответствий
//Т.к. в одной таблице есть несколько реквизитов с одинаковым типов введем нумерацию таблиц
//Это позволит избежать повторяющихся псевдонимов в запросе
НомерСоединения = 1;
Для Каждого ТекПоле Из СоответствиеСсылочныхПолей Цикл
ИмяПоляСоединения = ПолучитьИмяПоляПоВременнойТаблице(ТекПоле.Значение);
ТекстЗапроса = ТекстЗапроса + "
| ЛЕВОЕ СОЕДИНЕНИЕ " + ТекПоле.Значение + " КАК " + ТекПоле.Значение + НомерСоединения + "
| ПО " + ИмяВТ + "." + ТекПоле.Ключ + " = " + ТекПоле.Значение + НомерСоединения + "." + ИмяПоляСоединения + "Строка";
НомерСоединения = НомерСоединения + 1;
КонецЦикла;
ТекстЗапроса = ТекстЗапроса + "
|;
|
|////////////////////////////////////////////////////////////////////////////////
|УНИЧТОЖИТЬ " + ИмяВТ + "
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| *
|ПОМЕСТИТЬ " + ИмяВТ + "
|ИЗ
| " + ИмяВТ + "БезСсылки КАК " + ИмяВТ + "БезСсылки
|;
|
|////////////////////////////////////////////////////////////////////////////////
|УНИЧТОЖИТЬ " + ИмяВТ + "БезСсылки";
Возврат ТекстЗапроса;
КонецФункции // ПолучитьТекстЗапросаПоТаблице()
Функция ПолучитьИмяПоляПоВременнойТаблице(ИмяВременнойТаблицы)
ИмяПоляСоединения = "";
//Перечисления и справочники
Если ИмяВременнойТаблицы = "ВТ_СоответствиеОсобыеУсловияТрудаПФР" Тогда
ИмяПоляСоединения = "ОсобыеУсловияТрудаПФР";
ИначеЕсли ИмяВременнойТаблицы = "ВТ_СоответствиеВидыТарифовСтраховыхВзносов" Тогда
ИмяПоляСоединения = "ПрименяемыйЛьготныйТерриториальныйТариф";
ИначеЕсли ИмяВременнойТаблицы = "ВТ_СоответствиеВидыЗанятости" Тогда
ИмяПоляСоединения = "ВидЗанятости";
КонецЕсли;
//Регистры соответствий
Если ИмяВременнойТаблицы = "ВТ_СоответствиеВидовОтпусков" Тогда
ИмяПоляСоединения = "ВидОтпуска";
ИначеЕсли ИмяВременнойТаблицы = "ВТ_СоответствиеГрафиковРаботы" Тогда
ИмяПоляСоединения = "ГрафикРаботы";
ИначеЕсли ИмяВременнойТаблицы = "ВТ_СоответствиеНачислений" Тогда
ИмяПоляСоединения = "Начисление";
ИначеЕсли ИмяВременнойТаблицы = "ВТ_СоответствиеУдержаний" Тогда
ИмяПоляСоединения = "Удержание";
Иначе
КонецЕсли;
Возврат ИмяПоляСоединения;
КонецФункции // ПолучитьИмяПоляПоВременнойТаблице()
Опишу каждую процедуру/функцию:
- ЗаполнитьСсылкиПеречисленийИВспомогательныхСправочниковВЗапросах
В этой процедуре для каждой таблицы, которая участвует в переносе, мы получаем соответствие полей таблицы и временной таблицы, в которой содержатся ссылки. Для каждого элемента соответствия будет сделано ЛЕВОЕ СОЕДИНЕНИЕ с временной таблицей. Из временной таблицы в запрос будет добавлено поле, содержащее ссылку. Это происходит в следующей функции.
- ПолучитьТекстЗапросаПоТаблице
Сначала получаем список всех полей, которые содержит текущая таблица и название временной таблицы, в которой хранятся считанные данные.
В цикле формируем список полей запроса. Вторым циклом добавляем список новых ссылочных полей. Для этого в функциях получения соответствия полей мы заполняли "СтруктураНеобходимыхСоответствий".
Следующим шагом указываем в какой временной таблице сохраним полученную информацию и укажем основную таблицу запроса. Её имя мы получили в самом начале.
Осталось в цикле присоединить те временные таблицы, которые хранят ссылки на перечисления и вспомогательные справочники. Поле по которому будем связывать основную таблицу с временной определим в следующей функции.
- ПолучитьИмяПоляПоВременнойТаблице
По названию временной таблицы получаем поле, по которому можем их соединить.
После того, как присоединили все ссылки, нужно заменить исходную таблицу. Это также происходит запросом.
Благодаря описанным действиям мы универсально к основной таблице добавили поля со ссылками перечислений и вспомогательных справочников, для которых создавались регистры соответствий.
Похожим способом заполняются ссылки остальных справочников. Но для них написаны другие процедуры/функции. Т.к. инструмент коммерческий и тиражный, эти процедуры не будут приведены в данной статье) Но принцип тот же самый!
Если Вы не знаете, как поставить плюс или зачем это вообще делать, для Вас статья:
Другие "давно забытые", но актуальные статьи на технические темы:
Работа с контактной информацией
Особенности работы с COM-соединением
Зачем и как читать чужой код? Основные подходы