diff --git a/src/CommonModules/РК_TransferAPIКлиент.xml b/src/CommonModules/РК_TransferAPIКлиент.xml
new file mode 100644
index 0000000..ac618ac
--- /dev/null
+++ b/src/CommonModules/РК_TransferAPIКлиент.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ РК_TransferAPIКлиент
+
+
+ ru
+ Transfer API клиент (партнёрская ИБ)
+
+
+ HTTP-вызовы Transfer API партнёрской информационной базы
+ false
+ false
+ true
+ true
+ false
+ true
+ false
+ DontUse
+
+
+
diff --git a/src/CommonModules/РК_TransferAPIКлиент/Ext/Module.bsl b/src/CommonModules/РК_TransferAPIКлиент/Ext/Module.bsl
new file mode 100644
index 0000000..0a48328
--- /dev/null
+++ b/src/CommonModules/РК_TransferAPIКлиент/Ext/Module.bsl
@@ -0,0 +1,126 @@
+
+#Область ПрограммныйИнтерфейс
+
+// Выполняет именованный запрос на партнёрской ИБ (исходная crm3_placentapharm).
+//
+// Параметры:
+// ИмяЗапроса - Строка - series_export, partners_by_inn, ...
+// Параметры - Структура - параметры запроса
+//
+// Возвращаемое значение:
+// Структура - meta, columns, rows (как в JSON API)
+//
+Функция ВыполнитьЗапросПартнёра(ИмяЗапроса, Параметры = Неопределено) Экспорт
+
+ Если Параметры = Неопределено Тогда
+ Параметры = Новый Структура;
+ КонецЕсли;
+
+ АдресБазы = "";
+ Ключ = "";
+ Попытка
+ АдресБазы = СокрЛП(Константы.РК_TransferAPIАдресПартнёра.Получить());
+ Ключ = СокрЛП(Константы.РК_TransferAPIКлюч.Получить());
+ Исключение
+ ВызватьИсключение "Константы Transfer API не настроены";
+ КонецПопытки;
+
+ Если ПустаяСтрока(АдресБазы) Тогда
+ ВызватьИсключение "Не задан РК_TransferAPIАдресПартнёра";
+ КонецЕсли;
+
+ Возврат ВыполнитьHTTPЗапрос(АдресБазы, Ключ, ИмяЗапроса, Параметры);
+
+КонецФункции
+
+#КонецОбласти
+
+#Область СлужебныеПроцедурыИФункции
+
+Функция ВыполнитьHTTPЗапрос(АдресБазы, Ключ, ИмяЗапроса, Параметры)
+
+ URL = СокрЛП(АдресБазы);
+
+ Тело = Новый Структура;
+ Тело.Вставить("query", ИмяЗапроса);
+ Тело.Вставить("params", Параметры);
+ Тело.Вставить("format", "json");
+
+ Запись = Новый ЗаписьJSON;
+ Запись.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
+ ЗаписатьJSON(Запись, Тело);
+ ТекстТела = Запись.Закрыть();
+
+ Разбор = РазобратьURL(АдресБазы);
+ ПутьЗапроса = Разбор.Путь;
+ Если Не СтрЗаканчиваетсяНа(ПутьЗапроса, "/") Тогда
+ ПутьЗапроса = ПутьЗапроса + "/";
+ КонецЕсли;
+ ПутьЗапроса = ПутьЗапроса + "v1/query";
+
+ Соединение = Новый HTTPСоединение(
+ Разбор.Хост,
+ Разбор.Порт,
+ Разбор.Пользователь,
+ Разбор.Пароль,
+ ,
+ 120,
+ ?(Разбор.HTTPS, Новый ЗащищенноеСоединениеOpenSSL(), Неопределено));
+
+ ЗапросHTTP = Новый HTTPЗапрос(ПутьЗапроса);
+ ЗапросHTTP.Заголовки.Вставить("Authorization", "Bearer " + Ключ);
+ ЗапросHTTP.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
+ ЗапросHTTP.УстановитьТелоИзСтроки(ТекстТела, КодировкаТекста.UTF8);
+
+ ОтветHTTP = Соединение.ОтправитьДляОбработки(ЗапросHTTP);
+ ТекстОтвета = ОтветHTTP.ПолучитьТелоКакСтроку();
+
+ Если ОтветHTTP.КодСостояния < 200 Или ОтветHTTP.КодСостояния >= 300 Тогда
+ ЗаписьЖурналаРегистрации("РК_TransferAPI.Партнёр", УровеньЖурналаРегистрации.Ошибка, , ,
+ "HTTP " + ОтветHTTP.КодСостояния + ": " + ТекстОтвета);
+ ВызватьИсключение "Ошибка HTTP " + ОтветHTTP.КодСостояния + ": " + ТекстОтвета;
+ КонецЕсли;
+
+ Чтение = Новый ЧтениеJSON;
+ Чтение.УстановитьСтроку(ТекстОтвета);
+ Результат = ПрочитатьJSON(Чтение);
+ Чтение.Закрыть();
+
+ ЗаписьЖурналаРегистрации("РК_TransferAPI.Партнёр", УровеньЖурналаРегистрации.Информация, , ,
+ ИмяЗапроса + " → OK");
+
+ Возврат Результат;
+
+КонецФункции
+
+Функция РазобратьURL(Адрес)
+
+ Результат = Новый Структура("Хост, Порт, Путь, HTTPS, Пользователь, Пароль", "", 0, "", Ложь, "", "");
+
+ СтрокаURL = СокрЛП(Адрес);
+ HTTPS = СтрНачинаетсяС(НРег(СтрокаURL), "https://");
+ Если СтрНачинаетсяС(НРег(СтрокаURL), "http://") Тогда
+ СтрокаURL = Сред(СтрокаURL, 8);
+ ИначеЕсли HTTPS Тогда
+ СтрокаURL = Сред(СтрокаURL, 9);
+ КонецЕсли;
+ Результат.HTTPS = HTTPS;
+
+ ПозицияПути = СтрНайти(СтрокаURL, "/");
+ ХостПорт = ?(ПозицияПути > 0, Лев(СтрокаURL, ПозицияПути - 1), СтрокаURL);
+ Результат.Путь = ?(ПозицияПути > 0, Сред(СтрокаURL, ПозицияПути), "/");
+
+ ПозицияПорта = СтрНайти(ХостПорт, ":");
+ Если ПозицияПорта > 0 Тогда
+ Результат.Хост = Лев(ХостПорт, ПозицияПорта - 1);
+ Результат.Порт = Число(Сред(ХостПорт, ПозицияПорта + 1));
+ Иначе
+ Результат.Хост = ХостПорт;
+ Результат.Порт = ?(HTTPS, 443, 80);
+ КонецЕсли;
+
+ Возврат Результат;
+
+КонецФункции
+
+#КонецОбласти
diff --git a/src/CommonModules/РК_TransferAPIСервер.xml b/src/CommonModules/РК_TransferAPIСервер.xml
new file mode 100644
index 0000000..8f5f7d6
--- /dev/null
+++ b/src/CommonModules/РК_TransferAPIСервер.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ РК_TransferAPIСервер
+
+
+ ru
+ Transfer API сервер
+
+
+ Логика read-only HTTP API переноса данных
+ false
+ false
+ true
+ true
+ false
+ false
+ false
+ DontUse
+
+
+
diff --git a/src/CommonModules/РК_TransferAPIСервер/Ext/Module.bsl b/src/CommonModules/РК_TransferAPIСервер/Ext/Module.bsl
new file mode 100644
index 0000000..56ba6f5
--- /dev/null
+++ b/src/CommonModules/РК_TransferAPIСервер/Ext/Module.bsl
@@ -0,0 +1,543 @@
+
+#Область ПрограммныйИнтерфейс
+
+Функция ОбработатьHealth(Запрос) Экспорт
+
+ РезультатПроверки = ПроверитьТокен(Запрос, Ложь);
+ Если РезультатПроверки <> Неопределено Тогда
+ Возврат РезультатПроверки;
+ КонецЕсли;
+
+ Данные = Новый Структура;
+ Данные.Вставить("status", "ok");
+ Данные.Вставить("ib", ИмяТекущейИБ());
+ Данные.Вставить("extension", "crm326RHANA");
+ Данные.Вставить("server_time", Формат(ТекущаяДатаСеанса(), "ДФ=yyyy-MM-ddTHH:mm:ss"));
+
+ Возврат ОтветJSON(200, Данные);
+
+КонецФункции
+
+Функция ОбработатьQuery(Запрос) Экспорт
+
+ РезультатПроверки = ПроверитьТокен(Запрос, Истина);
+ Если РезультатПроверки <> Неопределено Тогда
+ Возврат РезультатПроверки;
+ КонецЕсли;
+
+ Тело = ПрочитатьТелоJSON(Запрос);
+ Если ТипЗнч(Тело) <> Тип("Структура") Тогда
+ Возврат ОтветОшибка(400, "INVALID_BODY", "Ожидается JSON-объект");
+ КонецЕсли;
+
+ ИмяЗапроса = "";
+ Если Тело.Свойство("query") Тогда
+ ИмяЗапроса = СокрЛП(Строка(Тело.query));
+ КонецЕсли;
+
+ Если ПустаяСтрока(ИмяЗапроса) Тогда
+ Возврат ОтветОшибка(400, "MISSING_QUERY", "Не указан параметр query");
+ КонецЕсли;
+
+ Параметры = Новый Структура;
+ Если Тело.Свойство("params") И ТипЗнч(Тело.params) = Тип("Структура") Тогда
+ Параметры = Тело.params;
+ КонецЕсли;
+
+ Формат = "json";
+ Если Тело.Свойство("format") Тогда
+ Формат = НРег(СокрЛП(Строка(Тело.format)));
+ КонецЕсли;
+
+ Возврат СформироватьОтветЗапроса(ИмяЗапроса, Параметры, Формат);
+
+КонецФункции
+
+Функция ОбработатьExport(Запрос) Экспорт
+
+ РезультатПроверки = ПроверитьТокен(Запрос, Истина);
+ Если РезультатПроверки <> Неопределено Тогда
+ Возврат РезультатПроверки;
+ КонецЕсли;
+
+ Отчет = "";
+ Если Запрос.ПараметрыURL.Свойство("report") Тогда
+ Отчет = НРег(СокрЛП(Строка(Запрос.ПараметрыURL["report"])));
+ КонецЕсли;
+
+ Если ПустаяСтрока(Отчет) Тогда
+ Возврат ОтветОшибка(404, "MISSING_REPORT", "Не указан report в URL");
+ КонецЕсли;
+
+ Параметры = ПараметрыИзQueryString(Запрос);
+ Формат = "json";
+ Если Параметры.Свойство("format") Тогда
+ Формат = НРег(СокрЛП(Строка(Параметры.format)));
+ КонецЕсли;
+
+ ИмяЗапроса = ИмяЗапросаПоОтчету(Отчет);
+ Если ПустаяСтрока(ИмяЗапроса) Тогда
+ Возврат ОтветОшибка(404, "UNKNOWN_REPORT", "Неизвестный отчёт: " + Отчет);
+ КонецЕсли;
+
+ Возврат СформироватьОтветЗапроса(ИмяЗапроса, Параметры, Формат);
+
+КонецФункции
+
+Функция ВыполнитьИменованныйЗапрос(ИмяЗапроса, Параметры) Экспорт
+
+ Имя = НРег(СокрЛП(ИмяЗапроса));
+
+ Если Имя = "series_export" Тогда
+ Возврат ЗапросSeriesExport(Параметры);
+ ИначеЕсли Имя = "partners_by_inn" Тогда
+ Возврат ЗапросPartnersByInn(Параметры);
+ Иначе
+ ВызватьИсключение "Неизвестный запрос: " + ИмяЗапроса;
+ КонецЕсли;
+
+КонецФункции
+
+#КонецОбласти
+
+#Область СлужебныеПроцедурыИФункции
+
+Функция ПроверитьТокен(Запрос, ПривилегированныйДоступ)
+
+ ОжидаемыйКлюч = "";
+ Попытка
+ ОжидаемыйКлюч = СокрЛП(Константы.РК_TransferAPIКлюч.Получить());
+ Исключение
+ Возврат ОтветОшибка(503, "NOT_CONFIGURED", "Константа РК_TransferAPIКлюч недоступна");
+ КонецПопытки;
+
+ Если ПустаяСтрока(ОжидаемыйКлюч) Тогда
+ Возврат ОтветОшибка(503, "KEY_NOT_SET", "Не задан ключ API (РК_TransferAPIКлюч)");
+ КонецЕсли;
+
+ Переданный = ИзвлечьТокен(Запрос);
+ Если ПустаяСтрока(Переданный) Тогда
+ Возврат ОтветОшибка(401, "UNAUTHORIZED", "Требуется заголовок Authorization: Bearer");
+ КонецЕсли;
+
+ Если Переданный <> ОжидаемыйКлюч Тогда
+ ЗаписьЖурналаРегистрации("РК_TransferAPI.Авторизация", УровеньЖурналаРегистрации.Предупреждение, , ,
+ "Неверный токен API");
+ Возврат ОтветОшибка(401, "UNAUTHORIZED", "Неверный токен");
+ КонецЕсли;
+
+ Если ПривилегированныйДоступ Тогда
+ УстановитьПривилегированныйРежим(Истина);
+ КонецЕсли;
+
+ Возврат Неопределено;
+
+КонецФункции
+
+Функция ИзвлечьТокен(Запрос)
+
+ Заголовок = Запрос.Заголовки.Получить("Authorization");
+ Если Заголовок = Неопределено Тогда
+ Заголовок = Запрос.Заголовки.Получить("authorization");
+ КонецЕсли;
+
+ Если Заголовок <> Неопределено Тогда
+ Заголовок = СокрЛП(Строка(Заголовок));
+ Префикс = "Bearer ";
+ Если СтрНачинаетсяС(ВРег(Заголовок), ВРег(Префикс)) Тогда
+ Возврат СокрЛП(Сред(Заголовок, СтрДлина(Префикс) + 1));
+ КонецЕсли;
+ КонецЕсли;
+
+ Параметры = ПараметрыИзQueryString(Запрос);
+ Если Параметры.Свойство("token") Тогда
+ Возврат СокрЛП(Строка(Параметры.token));
+ КонецЕсли;
+
+ Возврат "";
+
+КонецФункции
+
+Функция ПараметрыИзQueryString(Запрос)
+
+ Результат = Новый Структура;
+ Для Каждого КлючИЗначение Из Запрос.ПараметрыЗапроса Цикл
+ Результат.Вставить(Строка(КлючИЗначение.Ключ), Строка(КлючИЗначение.Значение));
+ КонецЦикла;
+ Возврат Результат;
+
+КонецФункции
+
+Функция ПрочитатьТелоJSON(Запрос)
+
+ Текст = Запрос.ПолучитьТелоКакСтроку();
+ Если ПустаяСтрока(Текст) Тогда
+ Возврат Новый Структура;
+ КонецЕсли;
+
+ Чтение = Новый ЧтениеJSON;
+ Чтение.УстановитьСтроку(Текст);
+ Результат = ПрочитатьJSON(Чтение);
+ Чтение.Закрыть();
+ Возврат Результат;
+
+КонецФункции
+
+Функция СформироватьОтветЗапроса(ИмяЗапроса, Параметры, Формат)
+
+ Попытка
+ ДанныеЗапроса = ВыполнитьИменованныйЗапрос(ИмяЗапроса, Параметры);
+ Исключение
+ ТекстОшибки = ОписаниеОшибки();
+ ЗаписьЖурналаРегистрации("РК_TransferAPI.Запрос", УровеньЖурналаРегистрации.Ошибка, , ,
+ ИмяЗапроса + ": " + ТекстОшибки);
+ Если СтрНайти(ТекстОшибки, "Неизвестный запрос") > 0 Тогда
+ Возврат ОтветОшибка(400, "UNKNOWN_QUERY", ТекстОшибки);
+ КонецЕсли;
+ Возврат ОтветОшибка(500, "QUERY_ERROR", ТекстОшибки);
+ КонецПопытки;
+
+ Если Формат = "csv" Тогда
+ Возврат ОтветCSV(ДанныеЗапроса);
+ КонецЕсли;
+
+ Мета = Новый Структура;
+ Мета.Вставить("query", ИмяЗапроса);
+ Мета.Вставить("ib", ИмяТекущейИБ());
+ Мета.Вставить("count", ДанныеЗапроса.rows.Количество());
+ Мета.Вставить("generated_at", Формат(ТекущаяДатаСеанса(), "ДФ=yyyy-MM-ddTHH:mm:ss"));
+
+ ОтветДанные = Новый Структура;
+ ОтветДанные.Вставить("meta", Мета);
+ ОтветДанные.Вставить("columns", ДанныеЗапроса.columns);
+ ОтветДанные.Вставить("rows", ДанныеЗапроса.rows);
+
+ Возврат ОтветJSON(200, ОтветДанные);
+
+КонецФункции
+
+Функция ИмяЗапросаПоОтчету(Отчет)
+
+ Если Отчет = "series" Тогда
+ Возврат "series_export";
+ ИначеЕсли Отчет = "partners_by_inn" Тогда
+ Возврат "partners_by_inn";
+ КонецЕсли;
+ Возврат "";
+
+КонецФункции
+
+Функция ИмяТекущейИБ()
+
+ СтрокаСоединения = СтрокаСоединенияИнформационнойБазы();
+ Позиция = СтрНайти(СтрокаСоединения, "Ref=""");
+ Если Позиция > 0 Тогда
+ Хвост = Сред(СтрокаСоединения, Позиция + 5);
+ ПозицияКонец = СтрНайти(Хвост, """");
+ Если ПозицияКонец > 0 Тогда
+ Возврат Лев(Хвост, ПозицияКонец - 1);
+ КонецЕсли;
+ КонецЕсли;
+ Возврат "crm3";
+
+КонецФункции
+
+#Область ИменованныеЗапросы
+
+Функция ЗапросSeriesExport(Параметры)
+
+ Лимит = ПараметрЧисло(Параметры, "limit", 0);
+ ВидGUID = ПараметрСтрока(Параметры, "vid_guid");
+
+ Колонки = Новый Массив;
+ Колонки.Добавить("GUID");
+ Колонки.Добавить("Наименование");
+ Колонки.Добавить("Номер");
+ Колонки.Добавить("ГоденДо");
+ Колонки.Добавить("ДатаПроизводства");
+ Колонки.Добавить("ВидНоменклатуры_GUID");
+ Колонки.Добавить("ВидНоменклатуры");
+ Колонки.Добавить("ИспНомерСерии");
+ Колонки.Добавить("ИспСрокГодности");
+ Колонки.Добавить("СерияЦО_GUID");
+ Колонки.Добавить("СерияЦО");
+ Колонки.Добавить("ПометкаУдаления");
+ Колонки.Добавить("СсылокДвижений");
+
+ ЕстьСерииЦО = Метаданные.Справочники.Найти("СерииНоменклатурыДляЦенообразования") <> Неопределено;
+
+ ТекстЗапроса =
+ "ВЫБРАТЬ
+ | Сер.Ссылка КАК Ссылка,
+ | Сер.Наименование КАК Наименование,
+ | Сер.Номер КАК Номер,
+ | Сер.ГоденДо КАК ГоденДо,
+ | Сер.ДатаПроизводства КАК ДатаПроизводства,
+ | Сер.ВидНоменклатуры КАК ВидНоменклатуры,
+ | Сер.ВидНоменклатуры.Наименование КАК ВидНаименование,
+ | Сер.ПометкаУдаления КАК ПометкаУдаления";
+
+ Если ЕстьСерииЦО Тогда
+ ТекстЗапроса = ТекстЗапроса + ",
+ | Сер.СерияНоменклатурыДляЦенообразования КАК СерияЦО";
+ КонецЕсли;
+
+ ТекстЗапроса = ТекстЗапроса + "
+ |ИЗ
+ | Справочник.СерииНоменклатуры КАК Сер
+ |ГДЕ
+ | ИСТИНА";
+
+ Если ЗначениеЗаполнено(ВидGUID) Тогда
+ ТекстЗапроса = ТекстЗапроса + "
+ | И Сер.ВидНоменклатуры.УникальныйИдентификатор() = &ВидGUID";
+ КонецЕсли;
+
+ ТекстЗапроса = ТекстЗапроса + "
+ |УПОРЯДОЧИТЬ ПО
+ | ВидНаименование,
+ | Номер,
+ | Наименование";
+
+ Запрос = Новый Запрос(ТекстЗапроса);
+ Если ЗначениеЗаполнено(ВидGUID) Тогда
+ Запрос.УстановитьПараметр("ВидGUID", Новый УникальныйИдентификатор(ВидGUID));
+ КонецЕсли;
+
+ Выб = Запрос.Выполнить().Выбрать();
+ Строки = Новый Массив;
+ Сч = 0;
+
+ Пока Выб.Следующий() Цикл
+
+ Если Лимит > 0 И Сч >= Лимит Тогда
+ Прервать;
+ КонецЕсли;
+
+ Вид = Выб.ВидНоменклатуры;
+ ИспНомер = Ложь;
+ ИспСрок = Ложь;
+ Если ЗначениеЗаполнено(Вид) Тогда
+ Попытка
+ Настр = Справочники.ВидыНоменклатуры.НастройкиИспользованияСерий(Вид);
+ ИспНомер = Настр.ИспользоватьНомерСерии;
+ ИспСрок = Настр.ИспользоватьСрокГодностиСерии;
+ Исключение
+ КонецПопытки;
+ КонецЕсли;
+
+ Ссылок = 0;
+ ЗапросДв = Новый Запрос(
+ "ВЫБРАТЬ
+ | КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Дв.Регистратор) КАК Кол
+ |ИЗ
+ | РегистрНакопления.ДвиженияСерийТоваров КАК Дв
+ |ГДЕ
+ | Дв.Серия = &Серия");
+ ЗапросДв.УстановитьПараметр("Серия", Выб.Ссылка);
+ Попытка
+ ВыбДв = ЗапросДв.Выполнить().Выбрать();
+ Если ВыбДв.Следующий() Тогда
+ Ссылок = ВыбДв.Кол;
+ КонецЕсли;
+ Исключение
+ КонецПопытки;
+
+ СтрокаДанных = Новый Массив;
+ СтрокаДанных.Добавить(GUIDСтрока(Выб.Ссылка));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.Наименование));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.Номер));
+ СтрокаДанных.Добавить(ДатаСтрока(Выб.ГоденДо));
+ СтрокаДанных.Добавить(ДатаСтрока(Выб.ДатаПроизводства));
+ СтрокаДанных.Добавить(GUIDСтрока(Вид));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.ВидНаименование));
+ СтрокаДанных.Добавить(?(ИспНомер, "Да", "Нет"));
+ СтрокаДанных.Добавить(?(ИспСрок, "Да", "Нет"));
+
+ Если ЕстьСерииЦО Тогда
+ СтрокаДанных.Добавить(GUIDСтрока(Выб.СерияЦО));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.СерияЦО));
+ Иначе
+ СтрокаДанных.Добавить("");
+ СтрокаДанных.Добавить("");
+ КонецЕсли;
+
+ СтрокаДанных.Добавить(?(Выб.ПометкаУдаления, "Да", "Нет"));
+ СтрокаДанных.Добавить(Формат(Ссылок, "ЧГ=0"));
+
+ Строки.Добавить(СтрокаДанных);
+ Сч = Сч + 1;
+
+ КонецЦикла;
+
+ Возврат Новый Структура("columns, rows", Колонки, Строки);
+
+КонецФункции
+
+Функция ЗапросPartnersByInn(Параметры)
+
+ ИНН = ПараметрСтрока(Параметры, "inn");
+ КПП = ПараметрСтрока(Параметры, "kpp");
+
+ Если ПустаяСтрока(ИНН) Тогда
+ ВызватьИсключение "Не указан параметр inn";
+ КонецЕсли;
+
+ Колонки = Новый Массив;
+ Колонки.Добавить("GUID");
+ Колонки.Добавить("Наименование");
+ Колонки.Добавить("ИНН");
+ Колонки.Добавить("КПП");
+ Колонки.Добавить("ПометкаУдаления");
+ Колонки.Добавить("Партнер_GUID");
+ Колонки.Добавить("Партнер");
+ Колонки.Добавить("ЮрФизЛицо");
+
+ ТекстЗапроса =
+ "ВЫБРАТЬ
+ | Контр.Ссылка КАК Ссылка,
+ | Контр.Наименование КАК Наименование,
+ | Контр.ИНН КАК ИНН,
+ | Контр.КПП КАК КПП,
+ | Контр.ПометкаУдаления КАК ПометкаУдаления,
+ | Контр.Партнер КАК Партнер,
+ | Контр.ЮрФизЛицо КАК ЮрФизЛицо
+ |ИЗ
+ | Справочник.Контрагенты КАК Контр
+ |ГДЕ
+ | Контр.ИНН = &ИНН";
+
+ Если ЗначениеЗаполнено(КПП) Тогда
+ ТекстЗапроса = ТекстЗапроса + "
+ | И Контр.КПП = &КПП";
+ КонецЕсли;
+
+ ТекстЗапроса = ТекстЗапроса + "
+ |УПОРЯДОЧИТЬ ПО
+ | Контр.ПометкаУдаления,
+ | Контр.Наименование";
+
+ Запрос = Новый Запрос(ТекстЗапроса);
+ Запрос.УстановитьПараметр("ИНН", ИНН);
+ Если ЗначениеЗаполнено(КПП) Тогда
+ Запрос.УстановитьПараметр("КПП", КПП);
+ КонецЕсли;
+
+ Выб = Запрос.Выполнить().Выбрать();
+ Строки = Новый Массив;
+
+ Пока Выб.Следующий() Цикл
+ СтрокаДанных = Новый Массив;
+ СтрокаДанных.Добавить(GUIDСтрока(Выб.Ссылка));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.Наименование));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.ИНН));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.КПП));
+ СтрокаДанных.Добавить(?(Выб.ПометкаУдаления, "Да", "Нет"));
+ СтрокаДанных.Добавить(GUIDСтрока(Выб.Партнер));
+ СтрокаДанных.Добавить(ЭкранироватьCSV(Выб.Партнер));
+ СтрокаДанных.Добавить(Строка(Выб.ЮрФизЛицо));
+ Строки.Добавить(СтрокаДанных);
+ КонецЦикла;
+
+ Возврат Новый Структура("columns, rows", Колонки, Строки);
+
+КонецФункции
+
+#КонецОбласти
+
+#Область ОтветыHTTP
+
+Функция ОтветJSON(КодСостояния, Данные)
+
+ Запись = Новый ЗаписьJSON;
+ Запись.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
+ ЗаписатьJSON(Запись, Данные);
+ ТекстJSON = Запись.Закрыть();
+
+ Ответ = Новый HTTPСервисОтвет(КодСостояния);
+ Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
+ Ответ.УстановитьТелоИзСтроки(ТекстJSON, КодировкаТекста.UTF8);
+ Возврат Ответ;
+
+КонецФункции
+
+Функция ОтветОшибка(КодСостояния, КодОшибки, Сообщение)
+
+ Тело = Новый Структура("error", Новый Структура("code, message", КодОшибки, Сообщение));
+ Возврат ОтветJSON(КодСостояния, Тело);
+
+КонецФункции
+
+Функция ОтветCSV(ДанныеЗапроса)
+
+ СтрокиТекста = Новый Массив;
+ СтрокиТекста.Добавить(СтрСоединить(ДанныеЗапроса.columns, ";"));
+
+ Для Каждого СтрокаДанных Из ДанныеЗапроса.rows Цикл
+ СтрокиТекста.Добавить(СтрСоединить(СтрокаДанных, ";"));
+ КонецЦикла;
+
+ ТекстCSV = СтрСоединить(СтрокиТекста, Символы.ПС);
+
+ Ответ = Новый HTTPСервисОтвет(200);
+ Ответ.Заголовки.Вставить("Content-Type", "text/csv; charset=utf-8");
+ Ответ.УстановитьТелоИзСтроки(ТекстCSV, КодировкаТекста.UTF8);
+ Возврат Ответ;
+
+КонецФункции
+
+#КонецОбласти
+
+#Область Вспомогательные
+
+Функция ПараметрСтрока(Параметры, Имя)
+
+ Если ТипЗнч(Параметры) = Тип("Структура") И Параметры.Свойство(Имя) Тогда
+ Возврат СокрЛП(Строка(Параметры[Имя]));
+ КонецЕсли;
+ Возврат "";
+
+КонецФункции
+
+Функция ПараметрЧисло(Параметры, Имя, ЗначениеПоУмолчанию)
+
+ Стр = ПараметрСтрока(Параметры, Имя);
+ Если ПустаяСтрока(Стр) Тогда
+ Возврат ЗначениеПоУмолчанию;
+ КонецЕсли;
+ Попытка
+ Возврат Число(Стр);
+ Исключение
+ Возврат ЗначениеПоУмолчанию;
+ КонецПопытки;
+
+КонецФункции
+
+Функция GUIDСтрока(Ссылка)
+
+ Если Не ЗначениеЗаполнено(Ссылка) Тогда
+ Возврат "";
+ КонецЕсли;
+ Возврат НРег(Строка(Ссылка.УникальныйИдентификатор()));
+
+КонецФункции
+
+Функция ДатаСтрока(ДатаЗнач)
+
+ Если Не ЗначениеЗаполнено(ДатаЗнач) Или ДатаЗнач <= Дата(2000, 1, 1) Тогда
+ Возврат "";
+ КонецЕсли;
+ Возврат Формат(ДатаЗнач, "ДФ=yyyy-MM-dd");
+
+КонецФункции
+
+Функция ЭкранироватьCSV(Значение)
+
+ Текст = СтрЗаменить(Строка(Значение), ";", ",");
+ Возврат СокрЛП(Текст);
+
+КонецФункции
+
+#КонецОбласти
+
+#КонецОбласти
diff --git a/src/Configuration.xml b/src/Configuration.xml
index 1698bcb..d07a432 100644
--- a/src/Configuration.xml
+++ b/src/Configuration.xml
@@ -92,7 +92,10 @@
РК_ОсновнаяРоль
РК_ОтладкаОтображение
РК_ОтладкаУправление
+ РК_ВызовTransferAPI
РК_ОбщегоНазначения
+ РК_TransferAPIСервер
+ РК_TransferAPIКлиент
РК_ОбщегоНазначенияКлиентСервер
РК_ОбщегоНазначенияВызовСервера
ОбщегоНазначенияКлиентСервер
@@ -103,6 +106,9 @@
РК_ФункциональнаяОпцияРежимаОтладки
РаспоряжениеНаОтгрузку
РК_РежимОтладкиРана
+ РК_TransferAPIКлюч
+ РК_TransferAPIАдресПартнёра
+ РК_TransferAPI
РК_ФормаНастроекРана
ДоговорыКонтрагентов
РеализацияТоваровУслуг
diff --git a/src/Constants/РК_TransferAPIАдресПартнёра.xml b/src/Constants/РК_TransferAPIАдресПартнёра.xml
new file mode 100644
index 0000000..7343707
--- /dev/null
+++ b/src/Constants/РК_TransferAPIАдресПартнёра.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ a1b2c3d4-e5f6-4789-a021-000000000011
+ a1b2c3d4-e5f6-4789-a021-000000000012
+
+
+ a1b2c3d4-e5f6-4789-a021-000000000013
+ a1b2c3d4-e5f6-4789-a021-000000000014
+
+
+ a1b2c3d4-e5f6-4789-a021-000000000015
+ a1b2c3d4-e5f6-4789-a021-000000000016
+
+
+
+ РК_TransferAPIАдресПартнёра
+
+
+ ru
+ Transfer API — адрес партнёрской ИБ
+
+
+ Базовый URL API исходной ИБ, напр. https://host/crm3_placentapharm/hs/transfer
+
+ xs:string
+
+ 0
+ Variable
+
+
+ true
+ false
+ false
+ false
+ DontCheck
+ Managed
+
+
+
diff --git a/src/Constants/РК_TransferAPIКлюч.xml b/src/Constants/РК_TransferAPIКлюч.xml
new file mode 100644
index 0000000..7d3f02f
--- /dev/null
+++ b/src/Constants/РК_TransferAPIКлюч.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ a1b2c3d4-e5f6-4789-a020-000000000011
+ a1b2c3d4-e5f6-4789-a020-000000000012
+
+
+ a1b2c3d4-e5f6-4789-a020-000000000013
+ a1b2c3d4-e5f6-4789-a020-000000000014
+
+
+ a1b2c3d4-e5f6-4789-a020-000000000015
+ a1b2c3d4-e5f6-4789-a020-000000000016
+
+
+
+ РК_TransferAPIКлюч
+
+
+ ru
+ Transfer API — ключ доступа
+
+
+ Bearer-токен для HTTP API переноса данных
+
+ xs:string
+
+ 0
+ Variable
+
+
+ true
+ true
+ false
+ false
+ DontCheck
+ Managed
+
+
+
diff --git a/src/HTTPServices/РК_TransferAPI.xml b/src/HTTPServices/РК_TransferAPI.xml
new file mode 100644
index 0000000..8ee3d17
--- /dev/null
+++ b/src/HTTPServices/РК_TransferAPI.xml
@@ -0,0 +1,107 @@
+
+
+
+
+ РК_TransferAPI
+
+
+ ru
+ Transfer API (перенос данных)
+
+
+ Read-only HTTP API для анализа и переноса данных
+ transfer
+ Use
+ 20
+
+
+
+
+ Health
+
+
+ ru
+ Health
+
+
+
+ /v1/health
+
+
+
+
+ GET
+
+
+ ru
+ GET
+
+
+
+ GET
+ HealthGETЗапрос
+
+
+
+
+
+
+ Query
+
+
+ ru
+ Query
+
+
+
+ /v1/query
+
+
+
+
+ POST
+
+
+ ru
+ POST
+
+
+
+ POST
+ QueryPOSTЗапрос
+
+
+
+
+
+
+ Export
+
+
+ ru
+ Export
+
+
+
+ /v1/export/{report}
+
+
+
+
+ GET
+
+
+ ru
+ GET
+
+
+
+ GET
+ ExportGETЗапрос
+
+
+
+
+
+
+
diff --git a/src/HTTPServices/РК_TransferAPI/Ext/Module.bsl b/src/HTTPServices/РК_TransferAPI/Ext/Module.bsl
new file mode 100644
index 0000000..7f4c237
--- /dev/null
+++ b/src/HTTPServices/РК_TransferAPI/Ext/Module.bsl
@@ -0,0 +1,16 @@
+
+#Область ОбработчикиHTTP
+
+Функция HealthGETЗапрос(Запрос)
+ Возврат РК_TransferAPIСервер.ОбработатьHealth(Запрос);
+КонецФункции
+
+Функция QueryPOSTЗапрос(Запрос)
+ Возврат РК_TransferAPIСервер.ОбработатьQuery(Запрос);
+КонецФункции
+
+Функция ExportGETЗапрос(Запрос)
+ Возврат РК_TransferAPIСервер.ОбработатьExport(Запрос);
+КонецФункции
+
+#КонецОбласти
diff --git a/src/Roles/РК_ВызовTransferAPI.xml b/src/Roles/РК_ВызовTransferAPI.xml
new file mode 100644
index 0000000..958ed19
--- /dev/null
+++ b/src/Roles/РК_ВызовTransferAPI.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ РК_ВызовTransferAPI
+
+
+ ru
+ Вызов Transfer API (перенос данных)
+
+
+ HTTP read-only API для анализа и переноса
+
+
+
diff --git a/src/Roles/РК_ВызовTransferAPI/Ext/Rights.xml b/src/Roles/РК_ВызовTransferAPI/Ext/Rights.xml
new file mode 100644
index 0000000..1c99335
--- /dev/null
+++ b/src/Roles/РК_ВызовTransferAPI/Ext/Rights.xml
@@ -0,0 +1,27 @@
+
+
+ false
+ true
+ false
+
+
+
+