РК_НастройкиПодключения 1.3.1.51: проверка соединения в менеджере.

ПроверитьСоединение по ссылке/объекту/структуре формы; GET /v1/health
через РК_ОбменСВнешнейБазой (X-Transfer-Token); README и CHANGELOG.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mihailkudravcev
2026-08-11 15:03:50 +03:00
parent b0d0324074
commit b472ff2470
7 changed files with 392 additions and 33 deletions
+22
View File
@@ -4,6 +4,28 @@
---
## 1.3.1.51 (2026-08-11)
### РК_НастройкиПодключенияКВнешнейБазе
- Проверка соединения вынесена в модуль менеджера: `ПроверитьСоединение(Источник)`.
- Источник: ссылка, объект справочника или структура полей формы (без проверки «Активно»).
- Возврат: `Успех`, `Текст`, `Ответ`, `Ошибка` — для UI и вызова из кода.
- `СоединениеАктивно(Ссылка)` — «Активно» + совпадение `ИмяТекущейБазы` с текущей ИБ.
- `ИмяТекущейИнформационнойБазы()` — общее API менеджера.
---
## 1.3.1.50 (2026-08-11)
### РК_НастройкиПодключенияКВнешнейБазе
- Форма элемента: «Обновить имя текущей базы» — из `Ref=` / `File=` строки соединения.
- Форма элемента: «Проверить» — `GET /v1/health` по полям формы (без записи, без проверки «Активно»).
- `РК_ОбменСВнешнейБазой`: `ПроверитьСоединение`, `ВыполнитьGET`; токен передаётся в `X-Transfer-Token` (совместимо с Basic Auth).
---
## 1.3.1.49 (2026-08-10)
### Обработка РК_ЗагрузкаОстатков — подбор складов и предварительные остатки
+24 -1
View File
@@ -2,7 +2,7 @@
Расширение РАНА для целевой конфигурации **crm3-26** (ИБ `crm3`, сервер `1c-srv.rhana.local`).
Текущая версия: см. [`VERSION`](VERSION) и реквизит `Version` в `src/Configuration.xml`.
Текущая версия: **1.3.1.51** см. [`VERSION`](VERSION) и `src/Configuration.xml`.
Префикс новых объектов расширения: **`РК_`**.
@@ -76,13 +76,36 @@
|--------|------------|
| `МенеджерОбменаЧерезУниверсальныйФормат` | синхронизация crm3-dev, защита учёта при получении |
| `ПартнерыИКонтрагентыЛокализация` | уникальность ИНН/КПП контрагентов |
| `РК_ОбменСВнешнейБазой` | HTTP-клиент Transfer API (`ws-rhana`) по настройкам справочника |
| `РК_ОбщегоНазначения` / `РК_ОбщегоНазначенияВызовСервера` / `РК_ОбщегоНазначенияКлиентСервер` | инфраструктура РАНА (режим отладки и др.) |
| `ОбщегоНазначенияКлиентСервер` | доработки отборов динамических списков |
### Справочники и HTTP к исходной ИБ (v1.3.1.49+)
| Объект | Назначение |
|--------|------------|
| `РК_НастройкиПодключенияКВнешнейБазе` | адрес `/hs/transfer`, токен, логин/пароль, таймаут, `ИмяТекущейБазы`, `Активно` |
| Менеджер справочника | `ПроверитьСоединение(Источник)`, `СоединениеАктивно(Ссылка)`, `ИмяТекущейИнформационнойБазы()` |
| `РК_РабочееПодключениеЧерезАПИ` | константа — текущая рабочая настройка |
| `РК_ЗагрузкаОстатков` | подбор складов / загрузка / сопоставление остатков из исходной ИБ |
**Проверка соединения** (форма элемента или любой код):
```bsl
// по ссылке
Р = Справочники.РК_НастройкиПодключенияКВнешнейБазе.ПроверитьСоединение(СсылкаНастройки);
// по полям формы / структуре (АдресПодключения|Адрес, Токен, …)
Р = Справочники.РК_НастройкиПодключенияКВнешнейБазе.ПроверитьСоединение(СтруктураПолей);
// Р.Успех, Р.Текст, Р.Ответ, Р.Ошибка — GET /v1/health через РК_ОбменСВнешнейБазой
```
Пара: расширение **`ws-rhana`** на исходной ИБ (≥ 0.0.0.30 для складов/остатков; health — с ранних версий).
### Обработки и формы
| Объект | Назначение |
|--------|------------|
| `РК_ЗагрузкаОстатков` | загрузка складских остатков из исходной ИБ через Transfer API |
| `РК_ОбработкаПарнеровКонтрагентов` | работа с партнёрами и контрагентами |
| `РК_ФормаНастроекРана` | настройки расширения |
| `ЗагрузкаДанныхИзФайла` | доработки загрузки НСИ |
+1 -1
View File
@@ -1 +1 @@
1.3.1.50
1.3.1.51
@@ -1,9 +1,250 @@
// TODO: Надо внести стандартные директивы и области в данный модуль
#Область ПрограммныйИнтерфейс
// Признак, что настройку можно использовать в текущей ИБ.
//
Функция СоединениеАктивно(НастройкаСсылка) Экспорт
Если ТипЗнч(НастройкаСсылка) = Тип("СправочникСсылка.РК_НастройкиПодключенияКВнешнейБазе") Тогда
// TODO: Тут надо проверить Активно у записи настройки и соответствие ИмяТекущейБазы имени данной базы и вернуть ЛОЖЬ если одно из этого не выполняется.
Иначе
Если ТипЗнч(НастройкаСсылка) <> Тип("СправочникСсылка.РК_НастройкиПодключенияКВнешнейБазе") Тогда
Возврат Неопределено;
КонецЕсли;
Если Не ЗначениеЗаполнено(НастройкаСсылка) Тогда
Возврат Ложь;
КонецЕсли;
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(НастройкаСсылка, "Активно, ИмяТекущейБазы");
Если Реквизиты.Активно <> Истина Тогда
Возврат Ложь;
КонецЕсли;
ИмяВНастройке = СокрЛП(Строка(Реквизиты.ИмяТекущейБазы));
Если ПустаяСтрока(ИмяВНастройке) Тогда
Возврат Ложь;
КонецЕсли;
Возврат ВРег(ИмяВНастройке) = ВРег(ИмяТекущейИнформационнойБазы());
КонецФункции
// Проверка Transfer API (GET /v1/health).
//
// Параметры:
// Источник - СправочникСсылка.РК_НастройкиПодключенияКВнешнейБазе,
// СправочникОбъект.РК_НастройкиПодключенияКВнешнейБазе
// или Структура с полями Адрес/АдресПодключения, Токен, Таймаут, Логин, Пароль.
// Для ссылки «Активно» не проверяется (можно тестировать черновик).
//
// Возвращаемое значение:
// Структура:
// * Успех - Булево
// * Текст - Строка (для показа пользователю)
// * Ответ - Структура/Соответствие ответа health или Неопределено
// * Ошибка - Строка
//
Функция ПроверитьСоединение(Источник) Экспорт
Результат = Новый Структура("Успех, Текст, Ответ, Ошибка", Ложь, "", Неопределено, "");
Попытка
Настройки = НастройкиДляПроверки(Источник);
ОтветHealth = РК_ОбменСВнешнейБазой.ПроверитьСоединение(Настройки);
Исключение
Результат.Ошибка = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
Результат.Текст = НСтр("ru = 'Ошибка соединения:'") + Символы.ПС + Результат.Ошибка;
Возврат Результат;
КонецПопытки;
Результат.Ответ = ОтветHealth;
Статус = ЗначениеПоляОтвета(ОтветHealth, "status");
Если НРег(СокрЛП(Строка(Статус))) <> "ok" Тогда
Результат.Ошибка = НСтр("ru = 'Неожиданный ответ health:'") + " " + Строка(Статус);
Результат.Текст = Результат.Ошибка;
Возврат Результат;
КонецЕсли;
Результат.Успех = Истина;
Результат.Текст = ТекстУспешнойПроверки(ОтветHealth);
Возврат Результат;
КонецФункции
// Имя текущей ИБ из строки соединения (Ref= / File=).
//
Функция ИмяТекущейИнформационнойБазы() Экспорт
СтрокаСоединения = СтрокаСоединенияИнформационнойБазы();
Позиция = СтрНайти(СтрокаСоединения, "Ref=""");
Если Позиция > 0 Тогда
Хвост = Сред(СтрокаСоединения, Позиция + 5);
ПозицияКонец = СтрНайти(Хвост, """");
Если ПозицияКонец > 0 Тогда
Возврат Лев(Хвост, ПозицияКонец - 1);
КонецЕсли;
КонецЕсли;
Позиция = СтрНайти(СтрокаСоединения, "File=""");
Если Позиция > 0 Тогда
Хвост = Сред(СтрокаСоединения, Позиция + 6);
ПозицияКонец = СтрНайти(Хвост, """");
Если ПозицияКонец > 0 Тогда
Путь = СтрЗаменить(Лев(Хвост, ПозицияКонец - 1), "\", "/");
Пока Прав(Путь, 1) = "/" Цикл
Путь = Лев(Путь, СтрДлина(Путь) - 1);
КонецЦикла;
Пока СтрНайти(Путь, "/") > 0 Цикл
Путь = Сред(Путь, СтрНайти(Путь, "/") + 1);
КонецЦикла;
Возврат Путь;
КонецЕсли;
КонецЕсли;
Возврат "";
КонецФункции
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
Функция НастройкиДляПроверки(Источник)
Если ТипЗнч(Источник) = Тип("СправочникСсылка.РК_НастройкиПодключенияКВнешнейБазе") Тогда
Если Не ЗначениеЗаполнено(Источник) Тогда
ВызватьИсключение НСтр("ru = 'Не указана настройка подключения'");
КонецЕсли;
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Источник,
"АдресПодключения, Токен, Таймаут, Логин, Пароль");
Возврат СтруктураНастроекHTTP(
Реквизиты.АдресПодключения,
Реквизиты.Токен,
Реквизиты.Таймаут,
Реквизиты.Логин,
Реквизиты.Пароль);
ИначеЕсли ТипЗнч(Источник) = Тип("СправочникОбъект.РК_НастройкиПодключенияКВнешнейБазе") Тогда
Возврат СтруктураНастроекHTTP(
Источник.АдресПодключения,
Источник.Токен,
Источник.Таймаут,
Источник.Логин,
Источник.Пароль);
ИначеЕсли ТипЗнч(Источник) = Тип("Структура") Тогда
Адрес = "";
Если Источник.Свойство("Адрес") Тогда
Адрес = Источник.Адрес;
ИначеЕсли Источник.Свойство("АдресПодключения") Тогда
Адрес = Источник.АдресПодключения;
КонецЕсли;
Токен = "";
Если Источник.Свойство("Токен") Тогда
Токен = Источник.Токен;
КонецЕсли;
Таймаут = 120;
Если Источник.Свойство("Таймаут") Тогда
Таймаут = Источник.Таймаут;
КонецЕсли;
Логин = "";
Если Источник.Свойство("Логин") Тогда
Логин = Источник.Логин;
КонецЕсли;
Пароль = "";
Если Источник.Свойство("Пароль") Тогда
Пароль = Источник.Пароль;
КонецЕсли;
Возврат СтруктураНастроекHTTP(Адрес, Токен, Таймаут, Логин, Пароль);
Иначе
ВызватьИсключение НСтр("ru = 'Недопустимый тип источника настройки подключения'");
КонецЕсли;
КонецФункции
Функция СтруктураНастроекHTTP(Адрес, Токен, Таймаут, Логин, Пароль)
ТаймаутЧисло = 120;
Попытка
ТаймаутЧисло = Число(Таймаут);
Исключение
ТаймаутЧисло = 120;
КонецПопытки;
Если ТаймаутЧисло <= 0 Тогда
ТаймаутЧисло = 120;
КонецЕсли;
Настройки = Новый Структура;
Настройки.Вставить("Адрес", СокрЛП(Строка(Адрес)));
Настройки.Вставить("Токен", СокрЛП(Строка(Токен)));
Настройки.Вставить("Таймаут", ТаймаутЧисло);
Настройки.Вставить("Логин", СокрЛП(Строка(Логин)));
Настройки.Вставить("Пароль", Строка(Пароль));
Возврат Настройки;
КонецФункции
Функция ТекстУспешнойПроверки(ОтветHealth)
ИБ = ЗначениеПоляОтвета(ОтветHealth, "ib");
Расширение = ЗначениеПоляОтвета(ОтветHealth, "extension");
ВерсияРасширения = ЗначениеПоляОтвета(ОтветHealth, "extension_version");
Конфигурация = ЗначениеПоляОтвета(ОтветHealth, "configuration_name");
ВерсияКонфигурации = ЗначениеПоляОтвета(ОтветHealth, "configuration_version");
ВремяСервера = ЗначениеПоляОтвета(ОтветHealth, "server_time");
Части = Новый Массив;
Части.Добавить(НСтр("ru = 'Соединение успешно.'"));
Если Не ПустаяСтрока(Строка(ИБ)) Тогда
Части.Добавить(НСтр("ru = 'ИБ:'") + " " + Строка(ИБ));
КонецЕсли;
Если Не ПустаяСтрока(Строка(Конфигурация)) Тогда
Части.Добавить(НСтр("ru = 'Конфигурация:'") + " " + Строка(Конфигурация)
+ ?(ПустаяСтрока(Строка(ВерсияКонфигурации)), "", " " + Строка(ВерсияКонфигурации)));
КонецЕсли;
Если Не ПустаяСтрока(Строка(Расширение)) Тогда
Части.Добавить(НСтр("ru = 'Transfer API:'") + " " + Строка(Расширение)
+ ?(ПустаяСтрока(Строка(ВерсияРасширения)), "", " " + Строка(ВерсияРасширения)));
КонецЕсли;
Если Не ПустаяСтрока(Строка(ВремяСервера)) Тогда
Части.Добавить(НСтр("ru = 'Время сервера:'") + " " + Строка(ВремяСервера));
КонецЕсли;
Возврат СтрСоединить(Части, Символы.ПС);
КонецФункции
Функция ЗначениеПоляОтвета(Источник, ИмяПоля)
Если Источник = Неопределено Тогда
Возврат "";
КонецЕсли;
Если ТипЗнч(Источник) = Тип("Структура") Тогда
Значение = Неопределено;
Если Источник.Свойство(ИмяПоля, Значение) Тогда
Возврат Значение;
КонецЕсли;
Возврат "";
КонецЕсли;
Если ТипЗнч(Источник) = Тип("Соответствие") Тогда
Значение = Источник.Получить(ИмяПоля);
Если Значение = Неопределено Тогда
Возврат "";
КонецЕсли;
Возврат Значение;
КонецЕсли;
Возврат "";
КонецФункции
#КонецОбласти
@@ -1,20 +1,52 @@
&НаСервере
Процедура ОбновитьИмяТекущейБазыНаСервере()
// TODO: Заполнить имя базы из текущей.
КонецПроцедуры
#Область ОбработчикиСобытийФормы
#КонецОбласти
#Область ОбработчикиСобытийЭлементовШапкиФормы
#КонецОбласти
#Область ОбработчикиКомандФормы
&НаКлиенте
Процедура ОбновитьИмяТекущейБазы(Команда)
ОбновитьИмяТекущейБазыНаСервере();
КонецПроцедуры
&НаСервере
Процедура КомандаПроверитьСоединениеНаСервере()
// TODO: Реализовать тестовое соединение со сторонней базой через API ws-rhana
ОбновитьИмяТекущейБазыНаСервере();
КонецПроцедуры
&НаКлиенте
Процедура КомандаПроверитьСоединение(Команда)
КомандаПроверитьСоединениеНаСервере();
ТекстРезультата = КомандаПроверитьСоединениеНаСервере();
ПоказатьПредупреждение(, ТекстРезультата);
КонецПроцедуры
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
&НаСервере
Процедура ОбновитьИмяТекущейБазыНаСервере()
Объект.ИмяТекущейБазы = Справочники.РК_НастройкиПодключенияКВнешнейБазе.ИмяТекущейИнформационнойБазы();
КонецПроцедуры
&НаСервере
Функция КомандаПроверитьСоединениеНаСервере()
Настройки = Новый Структура;
Настройки.Вставить("АдресПодключения", Объект.АдресПодключения);
Настройки.Вставить("Токен", Объект.Токен);
Настройки.Вставить("Таймаут", Объект.Таймаут);
Настройки.Вставить("Логин", Объект.Логин);
Настройки.Вставить("Пароль", Объект.Пароль);
РезультатПроверки = Справочники.РК_НастройкиПодключенияКВнешнейБазе.ПроверитьСоединение(Настройки);
Возврат РезультатПроверки.Текст;
КонецФункции
#КонецОбласти
@@ -42,6 +42,31 @@
КонецФункции
// GET /v1/health — проверка доступности Transfer API по явным настройкам формы/структуры.
// НастройкиПодключения: Адрес, Токен, Таймаут, Логин, Пароль (без проверки Активно).
//
Функция ПроверитьСоединение(НастройкиПодключения) Экспорт
Адрес = "";
Если НастройкиПодключения.Свойство("Адрес") Тогда
Адрес = СокрЛП(Строка(НастройкиПодключения.Адрес));
КонецЕсли;
Если ПустаяСтрока(Адрес) Тогда
ВызватьИсключение "Не заполнен адрес подключения";
КонецЕсли;
Токен = "";
Если НастройкиПодключения.Свойство("Токен") Тогда
Токен = СокрЛП(Строка(НастройкиПодключения.Токен));
КонецЕсли;
Если ПустаяСтрока(Токен) Тогда
ВызватьИсключение "Не заполнен токен Transfer API";
КонецЕсли;
Возврат ВыполнитьGET(НастройкиПодключения, "v1/health");
КонецФункции
// POST /v1/stock/warehouses на исходной ИБ.
//
Функция СписокСкладовВнешнейБазы(Подключение, ПараметрыЗапроса = Неопределено) Экспорт
@@ -66,11 +91,19 @@
КонецФункции
// Универсальный GET к Transfer API.
//
Функция ВыполнитьGET(НастройкиПодключения, ОтносительныйПуть) Экспорт
Возврат ВыполнитьHTTPЗапросЛокально(НастройкиПодключения, ОтносительныйПуть, Неопределено, "GET");
КонецФункции
// Универсальный POST к Transfer API.
//
Функция ВыполнитьPOST(НастройкиПодключения, ОтносительныйПуть, Тело) Экспорт
Возврат ВыполнитьHTTPЗапросЛокально(НастройкиПодключения, ОтносительныйПуть, Тело);
Возврат ВыполнитьHTTPЗапросЛокально(НастройкиПодключения, ОтносительныйПуть, Тело, "POST");
КонецФункции
@@ -78,7 +111,7 @@
#Область СлужебныеПроцедурыИФункции
Функция ВыполнитьHTTPЗапросЛокально(НастройкиПодключения, ОтносительныйПуть, Тело)
Функция ВыполнитьHTTPЗапросЛокально(НастройкиПодключения, ОтносительныйПуть, Тело, Метод)
АдресБазы = СокрЛП(Строка(НастройкиПодключения.Адрес));
Ключ = СокрЛП(Строка(НастройкиПодключения.Токен));
@@ -101,15 +134,6 @@
Пароль = Строка(НастройкиПодключения.Пароль);
КонецЕсли;
Если Тело = Неопределено Тогда
Тело = Новый Структура;
КонецЕсли;
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
ЗаписатьJSON(Запись, Тело);
ТекстТела = Запись.Закрыть();
Разбор = РазобратьURL(АдресБазы);
ПутьЗапроса = Разбор.Путь;
Если Не СтрЗаканчиваетсяНа(ПутьЗапроса, "/") Тогда
@@ -134,13 +158,30 @@
?(Разбор.HTTPS, Новый ЗащищенноеСоединениеOpenSSL(), Неопределено));
ЗапросHTTP = Новый HTTPЗапрос(ПутьЗапроса);
// X-Transfer-Token не конфликтует с Basic Auth пользователя публикации (см. README ws-rhana).
Если Не ПустаяСтрока(Ключ) Тогда
ЗапросHTTP.Заголовки.Вставить("Authorization", "Bearer " + Ключ);
ЗапросHTTP.Заголовки.Вставить("X-Transfer-Token", Ключ);
КонецЕсли;
МетодВР = ВРег(СокрЛП(Метод));
Если ПустаяСтрока(МетодВР) Тогда
МетодВР = "POST";
КонецЕсли;
Если МетодВР = "GET" Тогда
ОтветHTTP = Соединение.Получить(ЗапросHTTP);
Иначе
Если Тело = Неопределено Тогда
Тело = Новый Структура;
КонецЕсли;
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
ЗаписатьJSON(Запись, Тело);
ТекстТела = Запись.Закрыть();
ЗапросHTTP.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
ЗапросHTTP.УстановитьТелоИзСтроки(ТекстТела, КодировкаТекста.UTF8);
ОтветHTTP = Соединение.ОтправитьДляОбработки(ЗапросHTTP);
КонецЕсли;
ТекстОтвета = ОтветHTTP.ПолучитьТелоКакСтроку();
Если ОтветHTTP.КодСостояния < 200 Или ОтветHTTP.КодСостояния >= 300 Тогда
+1 -1
View File
@@ -62,7 +62,7 @@
<xr:Item xsi:type="xr:MDObjectRef">Role.РК_ОсновнаяРоль</xr:Item>
</DefaultRoles>
<Vendor/>
<Version>1.3.1.49</Version>
<Version>1.3.1.51</Version>
<DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>