1.3.1.122: ED — партнёр контрагента по РК_КодСтаройБазы + Transfer API (#67).

Поиск по доп. реквизиту; если нет — export через РК_РабочееПодключениеЧерезАПИ и load через РВС_TransferAPIПартнеры.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mihailkudravcev
2026-10-07 11:25:36 +03:00
parent be2c3479b4
commit 8e6a8ce653
5 changed files with 491 additions and 2 deletions
+16
View File
@@ -4,6 +4,22 @@
---
## 1.3.1.122 (2026-10-07)
### ED: fallback партнёра через Transfer API ([#67](https://git.p7net.ru/rhana/crm3-migration/issues/67))
- Если партнёр не найден по `РК_КодСтаройБазы`: `partners/export` на исходной ИБ через константу `РК_РабочееПодключениеЧерезАПИ` (как в `РК_ЗагрузкаОстатков`), затем `РВС_TransferAPIПартнеры.ВыполнитьЗагрузку` в целевой (без create контрагентов).
- `РК_ОбменСВнешнейБазой.ПартнерыВнешнейБазы` — обёртка `POST /v1/partners/export`.
- Кеш загруженных партнёров по коду в рамках сессии обмена.
## 1.3.1.121 (2026-10-07)
### Обмен ED crm3-dev → crm3-26: партнёр контрагента без дублей ([#67](https://git.p7net.ru/rhana/crm3-migration/issues/67))
- При получении контрагента: код партнёра исходной ИБ из `AdditionalInfo.РК_КодСтаройБазы` (кеш сессии обмена).
- `&Перед` `ОтложеннаяОбработка_Контрагенты`: поиск партнёра по доп. реквизиту `РК_КодСтаройБазы`, подстановка ссылки до типового создания.
- `&После`: запись `РК_КодСтаройБазы` на партнёре, созданном обменом (только если пусто).
## 1.3.1.120 (2026-09-04)
### РК_ЗагрузкаОстатков — ДатаСрокаГодностиПоУмолчанию (#46)
+1 -1
View File
@@ -1 +1 @@
1.3.1.120
1.3.1.122
@@ -1111,4 +1111,464 @@
КонецПроцедуры
// --- Контрагенты: партнёр по РК_КодСтаройБазы (crm3-dev → crm3-26) ---
// Типовая ОтложеннаяОбработка_Контрагенты создаёт партнёра при пустом Партнер.
// См. https://git.p7net.ru/rhana/crm3-migration/issues/67 (#54 п. 4).
Функция РК_КлючAdditionalInfoКодПартнераИсточника()
Возврат "РК_КодСтаройБазы";
КонецФункции
Процедура РК_ИнициализироватьКешКодПартнераКонтрагентов(КомпонентыОбмена)
Если КомпонентыОбмена = Неопределено Или Не КомпонентыОбмена.Свойство("ПараметрыКонвертации") Тогда
Возврат;
КонецЕсли;
Если Не КомпонентыОбмена.ПараметрыКонвертации.Свойство("РК_КодПартнераПоКонтрагентам") Тогда
КомпонентыОбмена.ПараметрыКонвертации.Вставить("РК_КодПартнераПоКонтрагентам", Новый Соответствие);
КонецЕсли;
КонецПроцедуры
Функция РК_КлючКонтрагентаВКеше(Объект)
Попытка
Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
Возврат НРег(XMLСтрока(Объект.Ссылка.УникальныйИдентификатор()));
КонецЕсли;
Исключение
КонецПопытки;
Попытка
СсылкаНового = Объект.ПолучитьСсылкуНового();
Если ЗначениеЗаполнено(СсылкаНового) Тогда
Возврат НРег(XMLСтрока(СсылкаНового.УникальныйИдентификатор()));
КонецЕсли;
Исключение
КонецПопытки;
Возврат "";
КонецФункции
Процедура РК_СохранитьКодПартнераИзAdditionalInfo(ДанныеXDTO, ПолученныеДанные, КомпонентыОбмена)
Если ДанныеXDTO = Неопределено Или ПолученныеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
Если Не ДанныеXDTO.Свойство("AdditionalInfo")
Или ТипЗнч(ДанныеXDTO.AdditionalInfo) <> Тип("Структура") Тогда
Возврат;
КонецЕсли;
КодПартнера = "";
Если Не ДанныеXDTO.AdditionalInfo.Свойство(РК_КлючAdditionalInfoКодПартнераИсточника(), КодПартнера)
Или Не ЗначениеЗаполнено(КодПартнера) Тогда
Возврат;
КонецЕсли;
КодПартнера = СокрЛП(Строка(КодПартнера));
Попытка
ПолученныеДанные.ДополнительныеСвойства.Вставить(РК_КлючAdditionalInfoКодПартнераИсточника(), КодПартнера);
Исключение
КонецПопытки;
РК_ИнициализироватьКешКодПартнераКонтрагентов(КомпонентыОбмена);
Ключ = РК_КлючКонтрагентаВКеше(ПолученныеДанные);
Если ЗначениеЗаполнено(Ключ) Тогда
КомпонентыОбмена.ПараметрыКонвертации.РК_КодПартнераПоКонтрагентам.Вставить(Ключ, КодПартнера);
КонецЕсли;
КонецПроцедуры
Функция РК_КодПартнераИсточникаДляКонтрагента(Объект, КомпонентыОбмена)
КодПартнера = "";
Попытка
Объект.ДополнительныеСвойства.Свойство(РК_КлючAdditionalInfoКодПартнераИсточника(), КодПартнера);
Исключение
КонецПопытки;
Если ЗначениеЗаполнено(КодПартнера) Тогда
Возврат СокрЛП(Строка(КодПартнера));
КонецЕсли;
Если КомпонентыОбмена = Неопределено Тогда
Возврат "";
КонецЕсли;
РК_ИнициализироватьКешКодПартнераКонтрагентов(КомпонентыОбмена);
Ключ = РК_КлючКонтрагентаВКеше(Объект);
Если Не ЗначениеЗаполнено(Ключ) Тогда
Возврат "";
КонецЕсли;
КодИзКеша = КомпонентыОбмена.ПараметрыКонвертации.РК_КодПартнераПоКонтрагентам.Получить(Ключ);
Если КодИзКеша = Неопределено Тогда
Возврат "";
КонецЕсли;
Возврат СокрЛП(Строка(КодИзКеша));
КонецФункции
Функция РК_СвойствоКодСтаройБазыПартнера()
Запрос = Новый Запрос(
"ВЫБРАТЬ ПЕРВЫЕ 1
| Св.Ссылка КАК Ссылка
|ИЗ
| ПланВидовХарактеристик.ДополнительныеРеквизитыИСведения КАК Св
|ГДЕ
| НЕ Св.ПометкаУдаления
| И (Св.Имя = &Имя ИЛИ Св.Наименование = &Наименование)");
Запрос.УстановитьПараметр("Имя", "РК_КодСтаройБазы");
Запрос.УстановитьПараметр("Наименование", "Код старой базы");
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Функция РК_НайтиПартнераПоКодуСтаройБазы(Код)
Код = СокрЛП(Код);
Если ПустаяСтрока(Код) Тогда
Возврат Неопределено;
КонецЕсли;
Свойство = РК_СвойствоКодСтаройБазыПартнера();
Если Не ЗначениеЗаполнено(Свойство) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос(
"ВЫБРАТЬ ПЕРВЫЕ 1
| П.Ссылка КАК Ссылка
|ИЗ
| Справочник.Партнеры.ДополнительныеРеквизиты КАК ДР
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Партнеры КАК П
| ПО ДР.Ссылка = П.Ссылка
|ГДЕ
| ДР.Свойство = &Свойство
| И ВЫРАЗИТЬ(ДР.Значение КАК СТРОКА(150)) = &Код
| И НЕ П.ПометкаУдаления
|УПОРЯДОЧИТЬ ПО
| П.Наименование");
Запрос.УстановитьПараметр("Свойство", Свойство);
Запрос.УстановитьПараметр("Код", Код);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Процедура РК_ЗаписатьКодСтаройБазыПартнеру(ПартнерСсылка, Код, ТолькоЕслиПусто = Истина)
Код = СокрЛП(Код);
Если ПустаяСтрока(Код) Или Не ОбщегоНазначения.СсылкаСуществует(ПартнерСсылка) Тогда
Возврат;
КонецЕсли;
Свойство = РК_СвойствоКодСтаройБазыПартнера();
Если Не ЗначениеЗаполнено(Свойство) Тогда
Возврат;
КонецЕсли;
Если ТолькоЕслиПусто Тогда
Запрос = Новый Запрос(
"ВЫБРАТЬ ПЕРВЫЕ 1
| ВЫРАЗИТЬ(ДР.Значение КАК СТРОКА(150)) КАК Значение
|ИЗ
| Справочник.Партнеры.ДополнительныеРеквизиты КАК ДР
|ГДЕ
| ДР.Ссылка = &Ссылка
| И ДР.Свойство = &Свойство");
Запрос.УстановитьПараметр("Ссылка", ПартнерСсылка);
Запрос.УстановитьПараметр("Свойство", Свойство);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() И ЗначениеЗаполнено(СокрЛП(Строка(Выборка.Значение))) Тогда
Возврат;
КонецЕсли;
КонецЕсли;
ТабСвойств = Новый ТаблицаЗначений;
ТабСвойств.Колонки.Добавить("Свойство");
ТабСвойств.Колонки.Добавить("Значение");
СтрокаСвойства = ТабСвойств.Добавить();
СтрокаСвойства.Свойство = Свойство;
СтрокаСвойства.Значение = Код;
УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(ПартнерСсылка, ТабСвойств);
КонецПроцедуры
// &После ПКО_Справочник_Контрагенты_Получение_ПриКонвертацииДанныхXDTO.
//
&После("ПКО_Справочник_Контрагенты_Получение_ПриКонвертацииДанныхXDTO")
Процедура РК_ПКО_Контрагенты_Получение_СохранитьКодПартнера(ДанныеXDTO, ПолученныеДанные, КомпонентыОбмена)
Если Не РК_ЭтоОбменС_Crm3Dev(КомпонентыОбмена) Тогда
Возврат;
КонецЕсли;
РК_СохранитьКодПартнераИзAdditionalInfo(ДанныеXDTO, ПолученныеДанные, КомпонентыОбмена);
КонецПроцедуры
// &Перед ОтложеннаяОбработка_Контрагенты.
// Подставляет партнёра по РК_КодСтаройБазы; если нет — export(dev)+load(target) через Transfer API.
//
&Перед("ОтложеннаяОбработка_Контрагенты")
Процедура РК_ОтложеннаяОбработка_Контрагенты_ПодобратьПартнера(Объект, КомпонентыОбмена, ОбъектМодифицирован)
Если Не РК_ЭтоОбменС_Crm3Dev(КомпонентыОбмена) Тогда
Возврат;
КонецЕсли;
Если ЗначениеЗаполнено(Объект.Партнер) Тогда
Возврат;
КонецЕсли;
КодПартнера = РК_КодПартнераИсточникаДляКонтрагента(Объект, КомпонентыОбмена);
Если ПустаяСтрока(КодПартнера) Тогда
Возврат;
КонецЕсли;
ПартнерСсылка = РК_НайтиПартнераПоКодуСтаройБазы(КодПартнера);
Если Не ЗначениеЗаполнено(ПартнерСсылка) Тогда
ПартнерСсылка = РК_ОбеспечитьПартнераЧерезTransferAPI(КодПартнера, КомпонентыОбмена);
КонецЕсли;
Если Не ЗначениеЗаполнено(ПартнерСсылка) Тогда
Возврат;
КонецЕсли;
Объект.Партнер = ПартнерСсылка;
ОбъектМодифицирован = Истина;
КонецПроцедуры
Процедура РК_ИнициализироватьКешПартнеровПоКоду(КомпонентыОбмена)
Если КомпонентыОбмена = Неопределено Или Не КомпонентыОбмена.Свойство("ПараметрыКонвертации") Тогда
Возврат;
КонецЕсли;
Если Не КомпонентыОбмена.ПараметрыКонвертации.Свойство("РК_ПартнерыПоКодуСтаройБазы") Тогда
КомпонентыОбмена.ПараметрыКонвертации.Вставить("РК_ПартнерыПоКодуСтаройБазы", Новый Соответствие);
КонецЕсли;
КонецПроцедуры
Функция РК_ЗначениеСвойстваОтвета(Источник, ИмяСвойства, ЗначениеПоУмолчанию = Неопределено)
Если ТипЗнч(Источник) = Тип("Структура") Тогда
Значение = Неопределено;
Если Источник.Свойство(ИмяСвойства, Значение) Тогда
Возврат Значение;
КонецЕсли;
Возврат ЗначениеПоУмолчанию;
ИначеЕсли ТипЗнч(Источник) = Тип("Соответствие") Тогда
Значение = Источник.Получить(ИмяСвойства);
Если Значение = Неопределено Тогда
Возврат ЗначениеПоУмолчанию;
КонецЕсли;
Возврат Значение;
КонецЕсли;
Возврат ЗначениеПоУмолчанию;
КонецФункции
// Export с исходной ИБ (константа РК_РабочееПодключениеЧерезАПИ) + load через РВС_TransferAPIПартнеры.
// Как sync в transfer_partners.py для одного кода. Контрагентов из пакета не создаём (грузятся ED).
//
Функция РК_ОбеспечитьПартнераЧерезTransferAPI(КодПартнера, КомпонентыОбмена)
КодПартнера = СокрЛП(КодПартнера);
Если ПустаяСтрока(КодПартнера) Тогда
Возврат Неопределено;
КонецЕсли;
РК_ИнициализироватьКешПартнеровПоКоду(КомпонентыОбмена);
Если КомпонентыОбмена <> Неопределено
И КомпонентыОбмена.Свойство("ПараметрыКонвертации")
И КомпонентыОбмена.ПараметрыКонвертации.Свойство("РК_ПартнерыПоКодуСтаройБазы") Тогда
ИзКеша = КомпонентыОбмена.ПараметрыКонвертации.РК_ПартнерыПоКодуСтаройБазы.Получить(КодПартнера);
Если ЗначениеЗаполнено(ИзКеша) Тогда
Возврат ИзКеша;
КонецЕсли;
КонецЕсли;
СсылкаПослеПоиска = РК_НайтиПартнераПоКодуСтаройБазы(КодПартнера);
Если ЗначениеЗаполнено(СсылкаПослеПоиска) Тогда
РК_ЗапомнитьПартнераВКешеСессии(КомпонентыОбмена, КодПартнера, СсылкаПослеПоиска);
Возврат СсылкаПослеПоиска;
КонецЕсли;
Подключение = Неопределено;
Попытка
Подключение = Константы.РК_РабочееПодключениеЧерезАПИ.Получить();
Исключение
Подключение = Неопределено;
КонецПопытки;
Если Не ЗначениеЗаполнено(Подключение) Тогда
ЗаписатьОшибкуВЖурналРегистрации(
Неопределено,
"РК_РабочееПодключениеЧерезАПИ не заполнено — нельзя выгрузить партнёра "
+ КодПартнера + " из исходной ИБ.",
КомпонентыОбмена,
НСтр("ru = 'загрузить партнёра через Transfer API'"),
УровеньЖурналаРегистрации.Предупреждение);
Возврат Неопределено;
КонецЕсли;
Если Метаданные.ОбщиеМодули.Найти("РВС_TransferAPIПартнеры") = Неопределено Тогда
ЗаписатьОшибкуВЖурналРегистрации(
Неопределено,
"Модуль РВС_TransferAPIПартнеры недоступен (нужен ws-rhana) — партнёр "
+ КодПартнера + " не загружен.",
КомпонентыОбмена,
НСтр("ru = 'загрузить партнёра через Transfer API'"),
УровеньЖурналаРегистрации.Предупреждение);
Возврат Неопределено;
КонецЕсли;
Коды = Новый Массив;
Коды.Добавить(КодПартнера);
ПараметрыExport = Новый Структура;
ПараметрыExport.Вставить("limit", 1);
ПараметрыExport.Вставить("include_contractors", Ложь);
ПараметрыExport.Вставить("add_contacts", Ложь);
ПараметрыExport.Вставить("codes", Коды);
Попытка
ОтветExport = РК_ОбменСВнешнейБазой.ПартнерыВнешнейБазы(Подключение, ПараметрыExport);
Исключение
ЗаписатьОшибкуВЖурналРегистрации(
Неопределено,
"partners/export код=" + КодПартнера + ": " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()),
КомпонентыОбмена,
НСтр("ru = 'выгрузить партнёра из исходной ИБ'"),
УровеньЖурналаРегистрации.Предупреждение);
Возврат Неопределено;
КонецПопытки;
ПартнерыПакета = РК_ЗначениеСвойстваОтвета(ОтветExport, "partners");
Если ТипЗнч(ПартнерыПакета) <> Тип("Массив") Или ПартнерыПакета.Количество() = 0 Тогда
ЗаписатьОшибкуВЖурналРегистрации(
Неопределено,
"partners/export: партнёр с кодом " + КодПартнера + " не найден в исходной ИБ.",
КомпонентыОбмена,
НСтр("ru = 'выгрузить партнёра из исходной ИБ'"),
УровеньЖурналаРегистрации.Предупреждение);
Возврат Неопределено;
КонецЕсли;
ПараметрыLoad = Новый Структура;
ПараметрыLoad.Вставить("dry_run", Ложь);
ПараметрыLoad.Вставить("create_missing", Истина);
ПараметрыLoad.Вставить("update_existing", Ложь);
ПараметрыLoad.Вставить("bind_contractors", Ложь);
ПараметрыLoad.Вставить("add_contacts", Ложь);
ПараметрыLoad.Вставить("old_code_mode", "renew");
ПараметрыLoad.Вставить("partners", ПартнерыПакета);
Попытка
МодульПартнеров = ОбщегоНазначения.ОбщийМодуль("РВС_TransferAPIПартнеры");
ОтветLoad = МодульПартнеров.ВыполнитьЗагрузку(ПараметрыLoad);
Исключение
ЗаписатьОшибкуВЖурналРегистрации(
Неопределено,
"partners/load код=" + КодПартнера + ": " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()),
КомпонентыОбмена,
НСтр("ru = 'загрузить партнёра в целевую ИБ'"),
УровеньЖурналаРегистрации.Предупреждение);
Возврат Неопределено;
КонецПопытки;
ПартнерСсылка = РК_ПартнерИзОтветаLoad(ОтветLoad, КодПартнера);
Если Не ЗначениеЗаполнено(ПартнерСсылка) Тогда
ПартнерСсылка = РК_НайтиПартнераПоКодуСтаройБазы(КодПартнера);
КонецЕсли;
Если Не ЗначениеЗаполнено(ПартнерСсылка) Тогда
ЗаписатьОшибкуВЖурналРегистрации(
Неопределено,
"partners/load: после загрузки партнёр с РК_КодСтаройБазы=" + КодПартнера + " не найден.",
КомпонентыОбмена,
НСтр("ru = 'загрузить партнёра в целевую ИБ'"),
УровеньЖурналаРегистрации.Предупреждение);
Возврат Неопределено;
КонецЕсли;
РК_ЗапомнитьПартнераВКешеСессии(КомпонентыОбмена, КодПартнера, ПартнерСсылка);
Возврат ПартнерСсылка;
КонецФункции
Процедура РК_ЗапомнитьПартнераВКешеСессии(КомпонентыОбмена, КодПартнера, ПартнерСсылка)
РК_ИнициализироватьКешПартнеровПоКоду(КомпонентыОбмена);
Если КомпонентыОбмена = Неопределено
Или Не КомпонентыОбмена.Свойство("ПараметрыКонвертации")
Или Не КомпонентыОбмена.ПараметрыКонвертации.Свойство("РК_ПартнерыПоКодуСтаройБазы") Тогда
Возврат;
КонецЕсли;
КомпонентыОбмена.ПараметрыКонвертации.РК_ПартнерыПоКодуСтаройБазы.Вставить(КодПартнера, ПартнерСсылка);
КонецПроцедуры
Функция РК_ПартнерИзОтветаLoad(ОтветLoad, КодПартнера)
Результаты = РК_ЗначениеСвойстваОтвета(ОтветLoad, "results");
Если ТипЗнч(Результаты) <> Тип("Массив") Тогда
Возврат Неопределено;
КонецЕсли;
Для Каждого СтрокаРез Из Результаты Цикл
Статус = СокрЛП(Строка(РК_ЗначениеСвойстваОтвета(СтрокаРез, "status", "")));
Если НРег(Статус) = "error" Тогда
Продолжить;
КонецЕсли;
GUIDСтрока = СокрЛП(Строка(РК_ЗначениеСвойстваОтвета(СтрокаРез, "target_guid", "")));
Если ПустаяСтрока(GUIDСтрока) Тогда
Продолжить;
КонецЕсли;
Попытка
Ссылка = Справочники.Партнеры.ПолучитьСсылку(Новый УникальныйИдентификатор(GUIDСтрока));
Если ОбщегоНазначения.СсылкаСуществует(Ссылка) Тогда
Возврат Ссылка;
КонецЕсли;
Исключение
КонецПопытки;
КонецЦикла;
Возврат Неопределено;
КонецФункции
// &После ОтложеннаяОбработка_Контрагенты.
// Если партнёр создан обменом — записать РК_КодСтаройБазы для последующего transfer_partners.
//
&После("ОтложеннаяОбработка_Контрагенты")
Процедура РК_ОтложеннаяОбработка_Контрагенты_КодСтаройБазы(Объект, КомпонентыОбмена, ОбъектМодифицирован)
Если Не РК_ЭтоОбменС_Crm3Dev(КомпонентыОбмена) Тогда
Возврат;
КонецЕсли;
КодПартнера = РК_КодПартнераИсточникаДляКонтрагента(Объект, КомпонентыОбмена);
Если ПустаяСтрока(КодПартнера) Или Не ЗначениеЗаполнено(Объект.Партнер) Тогда
Возврат;
КонецЕсли;
РК_ЗаписатьКодСтаройБазыПартнеру(Объект.Партнер, КодПартнера, Истина);
КонецПроцедуры
#КонецОбласти
@@ -155,6 +155,19 @@
КонецФункции
// POST /v1/partners/export на исходной ИБ (ws-rhana Transfer API).
// ПараметрыЗапроса: codes[], limit, include_contractors, add_contacts и др. — как transfer_partners.py.
//
Функция ПартнерыВнешнейБазы(Подключение, ПараметрыЗапроса = Неопределено) Экспорт
Если ПараметрыЗапроса = Неопределено Тогда
ПараметрыЗапроса = Новый Структура;
КонецЕсли;
Настройки = НастройкиПодключения(Подключение);
Возврат ВыполнитьPOST(Настройки, "v1/partners/export", ПараметрыЗапроса);
КонецФункции
// Универсальный GET к Transfer API.
//
Функция ВыполнитьGET(НастройкиПодключения, ОтносительныйПуть) Экспорт
+1 -1
View File
@@ -62,7 +62,7 @@
<xr:Item xsi:type="xr:MDObjectRef">Role.РК_ОсновнаяРоль</xr:Item>
</DefaultRoles>
<Vendor/>
<Version>1.3.1.120</Version>
<Version>1.3.1.122</Version>
<DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>