Буду краток. Ситуация:
Заказчику нужно дописать типовые правила обмена.
Для кого: статья подразумевает, что вы хорошо знакомы с программированием и имеете опыт работы с КД.
Дилемма: использовать КД, модифицировать правила, загрузить их как актуальные, поддерживать эти измененные правила актуальными при каждом обновлении на новый релиз (иначе однажды обмен может перестать работать).
Альтернатива (решение): написать код, который при нажати кнопки сам модифицирует типовые правила. Таким образом, для поддержания актуальности правил при очередном обновлении на релиз достаточно нажать эту кнопку.
Как это работает: любая типовая конфигурация — Синхронизация — Настройка синхронизации — Загрузить правила — «Из конфигурации» — кнопка «Записать и закрыть». В момент нажатия в регистр загружаются типовые правила обмена и далее используются как актуальные.
Данный код загрузки типовых правил мы и будем менять. В результате при нажатии на кнопку «Записать и закрыть» мы получим типовые правила обмена, но модифицированные согласно заложенному коду.
Реализация: Конфигуратор — обработки — КонвертацияОбъектовИнформационныхБаз — модуль объекта — процедура ЗагрузитьПравилаОбмена():
Процедура ЗагрузитьПравилаОбмена(Источник="",
ТипИсточника="XMLФайл",
СтрокаСообщенияОбОшибке = "",
ЗагружатьТолькоЗаголовокПравил = Ложь
) Экспорт
//...
СоздатьСтруктуруКонвертации();
ТаблицаПравилКонвертацииСвойств = Новый ТаблицаЗначений;
ИнициализацияТаблицыПравилКонвертацииСвойств(ТаблицаПравилКонвертацииСвойств);
// Возможно выбраны встроенные правила обмена (один из макетов).
ИмяВременногоФайлаПравилОбмена = "";
Если ПустаяСтрока(Источник) Тогда
Источник = ИмяФайлаПравилОбмена;
КонецЕсли;
//+изменения
Если ЭтотОбъект.ИмяПланаОбменаВРО = "ОбменУправлениеНебольшойФирмойБухгалтерия30" Тогда
ПреобразоватьПравилаОбмена(Источник);
КонецЕсли;
//-изменения
Если ТипИсточника="XMLФайл" Тогда
//...
КонецПроцедуры
Конструцией //… обозначен некий участок кода, который в данном листинге пропущен.
Изменения в типовом коде помечены //+изменения и //-изменения
Комментарий по коду: после того, как мы проинициализировали типовые правила (Источник = ИмяФайлаПравилОбмена), мы эти правила модифицируем: ПреобразоватьПравилаОбмена(Источник)
Теперь код процедуры ПреобразоватьПравилаОбмена:
//+изменения
Процедура ПреобразоватьПравилаОбмена(Источник)
// Чтение XML в дерево
Чтение = Новый ЧтениеXML;
ПараметрыЧтения = Новый ПараметрыЧтенияXML(,,,,Ложь,,,Ложь);
Чтение.ОткрытьФайл(Источник,ПараметрыЧтения);
СтруктураУзла = Новый ДеревоЗначений;
СтруктураУзла.Колонки.Добавить("Имя");
СтруктураУзла.Колонки.Добавить("Значение");
СтруктураУзла.Колонки.Добавить("Атрибуты");
СтруктураУзла.Колонки.Добавить("Тип");
СтрокаУзла = СтруктураУзла.Строки.Добавить();
СтрокаУзла.Значение = СокрЛП(Источник);
СтрокаУзла.Атрибуты = Новый Структура;
ПрочитатьОбъектXMLВДерево(Чтение,СтрокаУзла);
Чтение.Закрыть();
// !!!
ПравилоПослеЗагрузкиДанных = СтруктураУзла.Строки.Найти("ПослеЗагрузкиДанных","Имя",Истина);
Если ПравилоПослеЗагрузкиДанных <> Неопределено Тогда
ПравилоПослеЗагрузкиДанныхСПроведением = "Объект.Записать(РежимЗаписиДокумента.Проведение);";
ПравилоПослеЗагрузкиДанныхБезПроведения = "//Объект.Записать(РежимЗаписиДокумента.ПравилоПослеЗагрузкиДанныхСПроведением); //-изменения";
ПравилоПослеЗагрузкиДанных.Значение = СтрЗаменить(ПравилоПослеЗагрузкиДанных.Значение,ПравилоПослеЗагрузкиДанныхСПроведением,ПравилоПослеЗагрузкиДанныхБезПроведения);
КонецЕсли;
// !!!
ПравилаКонвертацииОбъектов = СтруктураУзла.Строки.Найти("ПравилаКонвертацииОбъектов","Имя",Истина);
ПреобразоватьСтрокиПравил(ПравилаКонвертацииОбъектов);
// Запись XML из дерева
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xml");
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяВременногоФайла);
ПрочитатьОбъектXMLИзДерева(ЗаписьXML,СтруктураУзла.Строки[0]);
ЗаписьXML.Закрыть();
Источник = ИмяВременногоФайла;
КонецПроцедуры // ПреобразоватьПравилаОбмена()
Процедура ПреобразоватьСтрокиПравил(ПравилаКонвертацииОбъектов)
Если Ложь Тогда ПравилаКонвертацииОбъектов = Новый ДеревоЗначений; КонецЕсли;
Для Каждого ПравилоКО Из ПравилаКонвертацииОбъектов.Строки Цикл
Источник = ПравилоКО.Строки.Найти("Источник","Имя");
Если Источник <> Неопределено Тогда
// Номенклатуру синхронизируем по наименованию
Если Источник.Значение = "СправочникСсылка.Номенклатура" Тогда
Для Каждого РеквизитОбъекта Из ПравилоКО.Строки.Найти("Свойства","Имя").Строки Цикл
ИмяРеквизита = РеквизитОбъекта.Строки.Найти("Источник","Имя");
Если ИмяРеквизита.Атрибуты.Имя <> "Наименование" И ИмяРеквизита.Атрибуты.Имя <> "ЭтоГруппа" Тогда
РеквизитОбъекта.Атрибуты.Удалить("Поиск");
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Банковские счета
Если Источник.Значение = "СправочникСсылка.БанковскиеСчета" Тогда
ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Найти("ПродолжитьПоискПоПолямПоискаЕслиПоИдентификаторуНеНашли","Имя");
Если ПравилоПоискПоПолямПоиска = Неопределено Тогда
ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Добавить();
ПравилоПоискПоПолямПоиска.Имя = "ПродолжитьПоискПоПолямПоискаЕслиПоИдентификаторуНеНашли";
ПравилоПоискПоПолямПоиска.Тип = ТипУзлаXML.Текст;
КонецЕсли;
ПравилоПоискПоПолямПоиска.Значение = "true";
КонецЕсли;
Если Источник.Значение = "СправочникСсылка.ДоговорыКонтрагентов" Тогда
ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Найти("ПродолжитьПоискПоПолямПоискаЕслиПоИдентификаторуНеНашли","Имя");
Если ПравилоПоискПоПолямПоиска = Неопределено Тогда
ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Добавить();
ПравилоПоискПоПолямПоиска.Имя = "ПродолжитьПоискПоПолямПоискаЕслиПоИдентификаторуНеНашли";
ПравилоПоискПоПолямПоиска.Тип = ТипУзлаXML.Текст;
КонецЕсли;
ПравилоПоискПоПолямПоиска.Значение = "true";
Для Каждого РеквизитОбъекта Из ПравилоКО.Строки.Найти("Свойства","Имя").Строки Цикл
ИмяРеквизита = РеквизитОбъекта.Строки.Найти("Источник","Имя");
Если ИмяРеквизита.Атрибуты.Имя = "Организация" Тогда
РеквизитОбъекта.Атрибуты.Удалить("Поиск");
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Контрагентов синхронизируем по ИНН/КПП
Если Источник.Значение = "СправочникСсылка.Контрагенты" Тогда
//ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Найти("СинхронизироватьПоИдентификатору","Имя");
//Если ПравилоПоискПоПолямПоиска = Неопределено Тогда
// ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Добавить();
// ПравилоПоискПоПолямПоиска.Имя = "СинхронизироватьПоИдентификатору";
// ПравилоПоискПоПолямПоиска.Тип = ТипУзлаXML.Текст;
//КонецЕсли;
//ПравилоПоискПоПолямПоиска.Значение = "false";
ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Найти("ПродолжитьПоискПоПолямПоискаЕслиПоИдентификаторуНеНашли","Имя");
Если ПравилоПоискПоПолямПоиска = Неопределено Тогда
ПравилоПоискПоПолямПоиска = ПравилоКО.Строки.Добавить();
ПравилоПоискПоПолямПоиска.Имя = "ПродолжитьПоискПоПолямПоискаЕслиПоИдентификаторуНеНашли";
ПравилоПоискПоПолямПоиска.Тип = ТипУзлаXML.Текст;
КонецЕсли;
ПравилоПоискПоПолямПоиска.Значение = "true";
ПравилоНеЗамещать = ПравилоКО.Строки.Найти("НеЗамещать","Имя");
Если ПравилоНеЗамещать = Неопределено Тогда
ПравилоНеЗамещать = ПравилоКО.Строки.Вставить(0);
ПравилоНеЗамещать.Имя = "НеЗамещать";
ПравилоНеЗамещать.Тип = ТипУзлаXML.Текст;
КонецЕсли;
ПравилоНеЗамещать.Значение = "true";
ПравилоПоляПоиска = ПравилоКО.Строки.Найти("ПоследовательностьПолейПоиска","Имя");
Если ПравилоПоляПоиска = Неопределено Тогда
ПравилоПоляПоиска = ПравилоКО.Строки.Добавить();
ПравилоПоляПоиска.Имя = "ПоследовательностьПолейПоиска";
ПравилоПоляПоиска.Тип = ТипУзлаXML.Текст;
КонецЕсли;
ПравилоПоляПоиска.Значение = "//+изменения
|Если НомерВариантаПоиска = 1 Тогда
| Если СвойстваПоиска[""ЭтоГруппа""] = Истина Тогда
| СтрокаИменСвойствПоиска = ""Наименование,ЭтоГруппа"";
| Иначе
| Если ЗначениеЗаполнено(СвойстваПоиска[""ИНН""]) Тогда
| СтрокаИменСвойствПоиска = ""ИНН,КПП,ЭтоГруппа"";
| Иначе
| СтрокаИменСвойствПоиска = ""Наименование,ИНН,КПП,ЭтоГруппа"";
| КонецЕсли;
| КонецЕсли;
|КонецЕсли;
|//-изменения";
КонецЕсли;
// Документы выгружаем непроведенными
Если Найти(Источник.Значение,"ДокументСсылка.") > 0 Тогда
Для Каждого РеквизитОбъекта Из ПравилоКО.Строки.Найти("Свойства","Имя").Строки Цикл
ИмяРеквизита = РеквизитОбъекта.Строки.Найти("Источник","Имя");
Если ИмяРеквизита.Атрибуты.Имя = "Проведен" Тогда
ПравилоПередВыгрузкой = РеквизитОбъекта.Строки.Добавить();
ПравилоПередВыгрузкой.Имя = "ПередВыгрузкой";
ПравилоПередВыгрузкой.Значение = "Значение = Ложь; //+изменения";
ПравилоПередВыгрузкой.Тип = ТипУзлаXML.Текст;
//ПравилоНеЗамещать = РеквизитОбъекта.Строки.Найти("НеЗамещать","Имя");
//Если ПравилоНеЗамещать = Неопределено Тогда
// ПравилоНеЗамещать = РеквизитОбъекта.Строки.Вставить(0);
// ПравилоНеЗамещать.Имя = "НеЗамещать";
// ПравилоНеЗамещать.Тип = ТипУзлаXML.Текст;
//КонецЕсли;
//ПравилоНеЗамещать.Значение = "true";
КонецЕсли;
ИмяРеквизита = РеквизитОбъекта.Строки.Найти("Приемник","Имя");
Если ИмяРеквизита.Атрибуты.Имя = "СпособЗачетаАвансов" Тогда
ПравилоПередВыгрузкой = РеквизитОбъекта.Строки.Найти("ПередВыгрузкой","Имя");
Если ПравилоПередВыгрузкой <> Неопределено Тогда
Если Найти(ПравилоПередВыгрузкой.Значение,"Выражение = ""Перечисления.СпособыЗачетаАвансов.") > 0 Тогда
ПравилоПередВыгрузкой.Значение = ПравилоПередВыгрузкой.Значение + "
|
|Выражение = ""Перечисления.СпособыЗачетаАвансов.Автоматически""; //+изменения";
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если Найти(Источник.Значение,"ДокументСсылка.ПоступлениеНаСчет") > 0 Тогда
ПравилоПередВыгрузкой = ПравилоКО.Строки.Найти("ПередВыгрузкой","Имя");
Если ПравилоПередВыгрузкой = Неопределено Тогда
ПравилоПередВыгрузкой = ПравилоКО.Строки.Добавить();
ПравилоПередВыгрузкой.Имя = "ПередВыгрузкой";
ПравилоПередВыгрузкой.Тип = ТипУзлаXML.Текст;
КонецЕсли;
ПравилоПередВыгрузкой.Значение = "Отказ = Истина;";
КонецЕсли;
Если Найти(Источник.Значение,"ДокументСсылка.РасходнаяНакладная") > 0 Тогда
Для Каждого РеквизитОбъекта Из ПравилоКО.Строки.Найти("Свойства","Имя").Строки Цикл
ИмяРеквизита = РеквизитОбъекта.Строки.Найти("Источник","Имя");
Если ИмяРеквизита.Атрибуты.Имя = "Предоплата" Тогда
ПравилоПередВыгрузкой = РеквизитОбъекта.Строки.Найти("ПередВыгрузкой","Имя");
Если ПравилоПередВыгрузкой = Неопределено Тогда
ПравилоПередВыгрузкой = РеквизитОбъекта.Строки.Добавить();
ПравилоПередВыгрузкой.Имя = "ПередВыгрузкой";
ПравилоПередВыгрузкой.Тип = ТипУзлаXML.Текст;
КонецЕсли;
ПравилоПередВыгрузкой.Значение = ПравилоПередВыгрузкой.Значение + "
|
|Отказ = Истина; //+изменения";
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЕсли;
ПреобразоватьСтрокиПравил(ПравилоКО);
КонецЦикла;
КонецПроцедуры // ПреобразоватьСтрокиПравил()
Функция ПрочитатьОбъектXMLВДерево(ЭлементЧтениеXML, Дерево)
текСтрока = Дерево;
Пока ЭлементЧтениеXML.Прочитать() Цикл
Если ЭлементЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
НоваяСтрока = текСтрока.Строки.Добавить();
НоваяСтрока.Имя = ЭлементЧтениеXML.Имя;
Атрибуты = Новый Структура;
Пока ЭлементЧтениеXML.ПрочитатьАтрибут() Цикл
Атрибуты.Вставить(ЭлементЧтениеXML.Имя, ЭлементЧтениеXML.Значение);
КонецЦикла;
НоваяСтрока.Атрибуты = Атрибуты;
НоваяСтрока.Тип = ТипУзлаXML.НачалоЭлемента;
ПрочитатьОбъектXMLВДерево(ЭлементЧтениеXML, НоваяСтрока);
ИначеЕсли ЭлементЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда
текСтрока.Значение = ЭлементЧтениеXML.Значение;
текСтрока.Тип = ТипУзлаXML.Текст;
ИначеЕсли ЭлементЧтениеXML.ТипУзла = ТипУзлаXML.Комментарий Тогда
НоваяСтрока = текСтрока.Строки.Добавить();
Атрибуты = Новый Структура;
НоваяСтрока.Значение = ЭлементЧтениеXML.Значение;
НоваяСтрока.Атрибуты = Атрибуты;
НоваяСтрока.Тип = ТипУзлаXML.Комментарий;
ИначеЕсли ЭлементЧтениеXML.ТипУзла = ТипУзлаXML.ОбъявлениеXML Тогда
НоваяСтрока = текСтрока.Строки.Добавить();
Атрибуты = Новый Структура;
НоваяСтрока.Значение = "<?xml version=""" + ЭлементЧтениеXML.ВерсияXML + """" + ?(ПустаяСтрока(ЭлементЧтениеXML.КодировкаXML), "", " encoding=""" + ЭлементЧтениеXML.КодировкаXML + """") + " ?>";
НоваяСтрока.Атрибуты = Атрибуты;
НоваяСтрока.Тип = ТипУзлаXML.ОбъявлениеXML;
ПрочитатьОбъектXMLВДерево(ЭлементЧтениеXML, НоваяСтрока);
ИначеЕсли ЭлементЧтениеXML.ТипУзла = ТипУзлаXML.КонецЭлемента Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Возврат Дерево;
КонецФункции
Процедура ПрочитатьОбъектXMLИзДерева(ЗаписьXML, Дерево) Экспорт
Если Дерево.Тип = ТипУзлаXML.ОбъявлениеXML Тогда
ЗаписьXML.ЗаписатьОбъявлениеXML();
Для Каждого текСтрока Из Дерево.Строки Цикл
ПрочитатьОбъектXMLИзДерева(ЗаписьXML, текСтрока);
КонецЦикла;
ИначеЕсли Дерево.Тип = ТипУзлаXML.Комментарий Тогда
ЗаписьXML.ЗаписатьКомментарий(Дерево.Значение);
Для Каждого текСтрока Из Дерево.Строки Цикл
ПрочитатьОбъектXMLИзДерева(ЗаписьXML, текСтрока);
КонецЦикла;
ИначеЕсли Дерево.Тип = ТипУзлаXML.НачалоЭлемента Тогда
ЗаписьXML.ЗаписатьНачалоЭлемента(Дерево.Имя);
Если ЗначениеЗаполнено(Дерево.Атрибуты) Тогда
Для Каждого текАтрибут Из Дерево.Атрибуты Цикл
ЗаписьXML.ЗаписатьАтрибут(текАтрибут.Ключ, текАтрибут.Значение);
КонецЦикла;
КонецЕсли;
Для Каждого текСтрока Из Дерево.Строки Цикл
ПрочитатьОбъектXMLИзДерева(ЗаписьXML, текСтрока);
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента();
ИначеЕсли Дерево.Тип = ТипУзлаXML.Текст Тогда
ЗаписьXML.ЗаписатьНачалоЭлемента(Дерево.Имя);
Если ЗначениеЗаполнено(Дерево.Атрибуты) Тогда
Для Каждого текАтрибут Из Дерево.Атрибуты Цикл
ЗаписьXML.ЗаписатьАтрибут(текАтрибут.Ключ, текАтрибут.Значение);
КонецЦикла;
КонецЕсли;
ЗаписьXML.ЗаписатьТекст(Дерево.Значение);
ЗаписьXML.ЗаписатьКонецЭлемента();
Для Каждого текСтрока Из Дерево.Строки Цикл
ПрочитатьОбъектXMLИзДерева(ЗаписьXML, текСтрока);
КонецЦикла;
Иначе
Для Каждого текСтрока Из Дерево.Строки Цикл
ПрочитатьОбъектXMLИзДерева(ЗаписьXML, текСтрока);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
//-изменения
Комментарий по коду:
Приведен полностью листинг одного реально модифицированного набора правил. Вначале производится чтение правил и помещение их в дерево значений, затем дерево значений преобразуется (добавляются/удаляются/модифицируются правила), затем дерево обратно собирается в xml и выдается обратно в вызывающую функцию.
В листинге есть примеры:
— как в справочнике Номенклатура оставить синхронизацию только по Наименованию и Группе (см. РеквизитОбъекта.Атрибуты.Удалить(«Поиск»))
— как включить синхронизацию по полям поиска, если по внутреннему идентификатору объект не найден (см. ПравилоКО.Строки.Найти(«ПродолжитьПоискПоПолямПоискаЕслиПоИдентификаторуНеНашли»,»Имя»);)
— как задать алгоритм поиска объектов (см. ПравилоКО.Строки.Найти(«ПоследовательностьПолейПоиска»,»Имя»);)
— как включить режим «Не замещать свойства если объект найден» (см. ПравилоКО.Строки.Найти(«НеЗамещать»,»Имя»);)
— как изменить значения реквизитов выгружаемых объектов (см. Если ИмяРеквизита.Атрибуты.Имя = «Проведен» Тогда )
— и некоторые другие примеры (по аналогии с приведенными выше будет понятно, для чего они предназначены).
И последнее: не забывайте, что иногда в конфигурации-приемнике поиск объектов по полям поиска может не сработать, т.к. в конфигурацию-приемник могут быть загружены правила корреспонденции. Если хотим, чтобы заработали правила поиска по полям поиска в этом случае, можем просто загрузить в приемник пустые правила корреспонденции.
А если сущности какой не станет, или названия реквизитов изменятся… Чета как-то, грабли :-/
(1) Этот же вопрос должен возникать, если модифицировать через КД типовые правила обмена и загружать их как актуальные. Поэтому считаю его общим и не имеющим отношения к методике.
Т.е. если рассуждать абстрактно над таким вопросом, то ответ банально такой: при каждом обновлении релиза нужно проверять доработки типовой 🙂
Может и грабли, а для расширения кругозора XML — самое то.
Согласен с Мишей 🙂
Каждый имеет право на собственное мнение 🙂
Как по мне, то я лучше нажму одну кнопку (проверив неизменность реквизитов), чем буду загружать в КД измененные автором правил обмена типовые правила из нового релиза и заново накатывать свои доработки. Конечно, этот процесс можно автоматизировать разными методами (как всегда все ограничивается только фантазией) — мой метод лишь один из возможных.
в типовых конфигурациях есть возможность использовать нетиповые правила КД, которые хранятся в базе
проще использовать такой механизм
(6) Механизм есть, но что вы будете делать когда после обновления релиза и изменения правил поставщиком, система выдаст сообщение что правила устарели и необходимо обновить правила до актуальных?
(7) обновлю конфигурацию в КД и выгружу правила