Загрузка данных
Заменяет целиком #Область API_Берга в общем модуле кор_HTTPКоннектор.
// Область РегламентноеЗадание_кор_ЗагрузкаОстатковБерга отсюда удалена: загрузка в регистр
// и подмена остатков для БИТ.УМ - в модуле кор_ОстаткиПоставщиков.
#Область API_Берга
// Клиент API Берга (https://api.berg.ru, версия 1.0): остатки и цены.
#Область ПрограммныйИнтерфейс_Берг
// Параметры подключения по умолчанию.
//
// Возвращаемое значение:
// Структура:
// * Ключ - Строка - API-ключ (заголовок X-Berg-API-Key).
// * Сервер - Строка
// * Версия - Строка - версия API в пути запроса.
// * Таймаут - Число - секунды.
// * ТипыСкладов - Массив из Число - 1 - склад филиала, 2 - ЦС Берга, 3 - дополнительные;
// пустой массив - все типы складов.
//
Функция НовыеПараметрыПодключения() Экспорт
Параметры = Новый Структура;
Параметры.Вставить("Ключ", "");
Параметры.Вставить("Сервер", "api.berg.ru");
Параметры.Вставить("Версия", "v1.0");
Параметры.Вставить("Таймаут", 60);
Параметры.Вставить("ТипыСкладов", Новый Массив);
Возврат Параметры;
КонецФункции
Функция СохраненныйКлючAPI() Экспорт
УстановитьПривилегированныйРежим(Истина);
Возврат СокрЛП(Константы.кор_БергКлючAPI.Получить());
КонецФункции
Функция ИмяСобытияЖурнала() Экспорт
Возврат "Берг.API";
КонецФункции
// Запрашивает у Берга остатки по позициям пачками по 50.
//
// Параметры:
// ПараметрыПодключения - см. НовыеПараметрыПодключения
// Позиции - Массив из Структура:
// * Индекс - Число - номер позиции у вызывающего; возвращается в Предложения.ИндексПозиции.
// * ResourceID - Число - id товара Берга; 0 - искать по артикулу и бренду.
// * Артикул - Строка
// * BrandID - Число - 0, если неизвестен.
// * Бренд - Строка
//
// Возвращаемое значение:
// Структура:
// * Предложения - ТаблицаЗначений - см. НоваяТаблицаПредложений.
// Товар Берга без предложений даёт одну строку с СкладID = 0 и нулями:
// так вызывающий узнаёт его id и понимает, что остатка нет.
// * Ошибки - Массив из Строка
// * ИндексыСОшибкой - Массив из Число - Индекс позиций из пачек, которые не загрузились.
// * КоличествоПачек - Число
// * ОтветыСервера - Массив из Строка - сырые ответы для отладки.
//
Функция ПолучитьОстатки(ПараметрыПодключения, Позиции) Экспорт
Результат = Новый Структура;
Результат.Вставить("Предложения", НоваяТаблицаПредложений());
Результат.Вставить("Ошибки", Новый Массив);
Результат.Вставить("ИндексыСОшибкой", Новый Массив);
Результат.Вставить("КоличествоПачек", 0);
Результат.Вставить("ОтветыСервера", Новый Массив);
РазмерПачки = 50;
Пачка = Новый Массив;
Для Каждого Позиция Из Позиции Цикл
Пачка.Добавить(Позиция);
Если Пачка.Количество() = РазмерПачки Тогда
Результат.КоличествоПачек = Результат.КоличествоПачек + 1;
ЗагрузитьПачку(ПараметрыПодключения, Пачка, Результат.КоличествоПачек, Результат);
Пачка = Новый Массив;
КонецЕсли;
КонецЦикла;
Если Пачка.Количество() > 0 Тогда
Результат.КоличествоПачек = Результат.КоличествоПачек + 1;
ЗагрузитьПачку(ПараметрыПодключения, Пачка, Результат.КоличествоПачек, Результат);
КонецЕсли;
Возврат Результат;
КонецФункции
Функция НоваяТаблицаПредложений() Экспорт
ТипЧисло = Новый ОписаниеТипов("Число");
ТипСтрока = Новый ОписаниеТипов("Строка");
ТипБулево = Новый ОписаниеТипов("Булево");
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("ИндексПозиции", ТипЧисло);
Таблица.Колонки.Добавить("ResourceID", ТипЧисло);
Таблица.Колонки.Добавить("Артикул", ТипСтрока);
Таблица.Колонки.Добавить("Бренд", ТипСтрока);
Таблица.Колонки.Добавить("Наименование", ТипСтрока);
Таблица.Колонки.Добавить("СкладID", ТипЧисло);
Таблица.Колонки.Добавить("Количество", ТипЧисло);
Таблица.Колонки.Добавить("Цена", ТипЧисло);
Таблица.Колонки.Добавить("БольшеДоступно", ТипБулево);
Возврат Таблица;
КонецФункции
#КонецОбласти
#Область СлужебныеПроцедурыИФункции_Берг
Процедура ЗагрузитьПачку(ПараметрыПодключения, Пачка, НомерПачки, Результат)
// Берг не возвращает порядковый номер позиции запроса, поэтому ответ сопоставляется
// обратно: по id товара, если он был в запросе, иначе по нормализованному артикулу.
ИндексыПоID = Новый Соответствие;
ИндексыПоАртикулу = Новый Соответствие;
Items = Новый Массив;
Для Каждого Позиция Из Пачка Цикл
Элемент = Новый Соответствие;
Если Позиция.ResourceID <> 0 Тогда
Элемент.Вставить("resource_id", Позиция.ResourceID);
ДобавитьИндекс(ИндексыПоID, Позиция.ResourceID, Позиция.Индекс);
Иначе
Элемент.Вставить("resource_article", СокрЛП(Позиция.Артикул));
Если Позиция.BrandID <> 0 Тогда
Элемент.Вставить("brand_id", Позиция.BrandID);
ИначеЕсли ЗначениеЗаполнено(Позиция.Бренд) Тогда
Элемент.Вставить("brand_name", СокрЛП(Позиция.Бренд));
КонецЕсли;
ДобавитьИндекс(ИндексыПоАртикулу, КлючАртикула(Позиция.Артикул), Позиция.Индекс);
КонецЕсли;
Items.Добавить(Элемент);
КонецЦикла;
Тело = Новый Соответствие;
Тело.Вставить("analogs", 0);
Тело.Вставить("items", Items);
Если ПараметрыПодключения.ТипыСкладов.Количество() > 0 Тогда
Тело.Вставить("warehouse_types", ПараметрыПодключения.ТипыСкладов);
КонецЕсли;
Ответ = ВыполнитьЗапросБерг(ПараметрыПодключения, "POST", "ordering/get_stock", Тело);
Результат.ОтветыСервера.Добавить(СтрШаблон("Пачка %1, HTTP %2" + Символы.ПС + "%3",
НомерПачки, Ответ.КодСостояния, Ответ.Текст));
Если ЗначениеЗаполнено(Ответ.Ошибка) Тогда
Результат.Ошибки.Добавить(СтрШаблон(НСтр("ru = 'Пачка %1: %2'"), НомерПачки, Ответ.Ошибка));
Для Каждого Позиция Из Пачка Цикл
Результат.ИндексыСОшибкой.Добавить(Позиция.Индекс);
КонецЦикла;
Возврат;
КонецЕсли;
Для Каждого Товар Из МассивИзОтвета(Ответ.Данные, "resources") Цикл
ResourceID = ЧислоИзОтвета(Товар.Получить("id"));
Артикул = СтрокаИзОтвета(Товар.Получить("article"));
Индексы = ИндексыПоID.Получить(ResourceID);
Если Индексы = Неопределено Тогда
Индексы = ИндексыПоАртикулу.Получить(КлючАртикула(Артикул));
КонецЕсли;
Если Индексы = Неопределено Тогда
Продолжить;
КонецЕсли;
Бренд = "";
ДанныеБренда = Товар.Получить("brand");
Если ТипЗнч(ДанныеБренда) = Тип("Соответствие") Тогда
Бренд = СтрокаИзОтвета(ДанныеБренда.Получить("name"));
КонецЕсли;
ОписаниеТовара = Новый Структура("ResourceID, Артикул, Бренд, Наименование",
ResourceID, Артикул, Бренд, СтрокаИзОтвета(Товар.Получить("name")));
Предложения = МассивИзОтвета(Товар, "offers");
Для Каждого Индекс Из Индексы Цикл
Если Предложения.Количество() = 0 Тогда
Строка = Результат.Предложения.Добавить();
ЗаполнитьЗначенияСвойств(Строка, ОписаниеТовара);
Строка.ИндексПозиции = Индекс;
Продолжить;
КонецЕсли;
Для Каждого Предложение Из Предложения Цикл
Строка = Результат.Предложения.Добавить();
ЗаполнитьЗначенияСвойств(Строка, ОписаниеТовара);
Строка.ИндексПозиции = Индекс;
Строка.Количество = ЧислоИзОтвета(Предложение.Получить("quantity"));
Строка.Цена = ЧислоИзОтвета(Предложение.Получить("price"));
Строка.БольшеДоступно = (Предложение.Получить("available_more") = Истина);
Склад = Предложение.Получить("warehouse");
Если ТипЗнч(Склад) = Тип("Соответствие") Тогда
Строка.СкладID = ЧислоИзОтвета(Склад.Получить("id"));
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Процедура ДобавитьИндекс(Соответствие, Ключ, Индекс)
Индексы = Соответствие.Получить(Ключ);
Если Индексы = Неопределено Тогда
Индексы = Новый Массив;
Соответствие.Вставить(Ключ, Индексы);
КонецЕсли;
Индексы.Добавить(Индекс);
КонецПроцедуры
// Артикул без регистра, пробелов и разделителей: "ctr-0123 45" и "CTR012345" совпадут.
Функция КлючАртикула(Артикул)
Исходный = ВРег(СокрЛП(Артикул));
Результат = "";
Для Номер = 1 По СтрДлина(Исходный) Цикл
Символ = Сред(Исходный, Номер, 1);
Если СтрНайти(" -./\_", Символ) = 0 Тогда
Результат = Результат + Символ;
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Функция ЧислоИзОтвета(Значение)
Если ТипЗнч(Значение) = Тип("Число") Тогда
Возврат Значение;
ИначеЕсли ТипЗнч(Значение) = Тип("Строка") Тогда
Попытка
Возврат Число(СтрЗаменить(СокрЛП(Значение), ",", "."));
Исключение
Возврат 0;
КонецПопытки;
КонецЕсли;
Возврат 0;
КонецФункции
Функция СтрокаИзОтвета(Значение)
Возврат ?(Значение = Неопределено, "", СокрЛП(Строка(Значение)));
КонецФункции
Функция ВыполнитьЗапросБерг(ПараметрыПодключения, Метод, Ресурс, Тело = Неопределено)
Результат = Новый Структура("КодСостояния, Текст, Данные, Ошибка", 0, "", Неопределено, "");
Если ПустаяСтрока(ПараметрыПодключения.Ключ) Тогда
Результат.Ошибка = НСтр("ru = 'Не указан API-ключ Берга.'");
Возврат Результат;
КонецЕсли;
Адрес = СтрШаблон("/%1/%2.json", ПараметрыПодключения.Версия, Ресурс);
HTTPЗапрос = Новый HTTPЗапрос(Адрес);
HTTPЗапрос.Заголовки.Вставить("X-Berg-API-Key", ПараметрыПодключения.Ключ);
HTTPЗапрос.Заголовки.Вставить("Accept", "application/json");
Если Тело <> Неопределено Тогда
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
HTTPЗапрос.УстановитьТелоИзСтроки(ОбщегоНазначения.ЗначениеВJSON(Тело), КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
КонецЕсли;
Попытка
Соединение = Новый HTTPСоединение(ПараметрыПодключения.Сервер, 443,,,
ПолучениеФайловИзИнтернета.ПолучитьПрокси("https"),
ПараметрыПодключения.Таймаут,
ОбщегоНазначенияКлиентСервер.НовоеЗащищенноеСоединение());
Если Метод = "POST" Тогда
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
Иначе
HTTPОтвет = Соединение.Получить(HTTPЗапрос);
КонецЕсли;
Исключение
Результат.Ошибка = СтрШаблон(НСтр("ru = 'Не удалось выполнить запрос к %1: %2'"),
ПараметрыПодключения.Сервер, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ЗаписьЖурналаРегистрации(ИмяСобытияЖурнала(), УровеньЖурналаРегистрации.Ошибка,,,
Адрес + Символы.ПС + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Результат;
КонецПопытки;
Результат.КодСостояния = HTTPОтвет.КодСостояния;
Результат.Текст = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Попытка
Результат.Данные = ОбщегоНазначения.JSONВЗначение(Результат.Текст);
Исключение
Результат.Данные = Неопределено;
КонецПопытки;
ОписаниеОшибок = ОписаниеОшибокAPI(Результат.Данные);
Если Результат.КодСостояния <> 200 Или ЗначениеЗаполнено(ОписаниеОшибок) Тогда
Результат.Ошибка = СтрШаблон(НСтр("ru = 'HTTP %1. %2'"), Результат.КодСостояния,
?(ЗначениеЗаполнено(ОписаниеОшибок), ОписаниеОшибок, Лев(Результат.Текст, 500)));
ЗаписьЖурналаРегистрации(ИмяСобытияЖурнала(), УровеньЖурналаРегистрации.Ошибка,,,
Адрес + Символы.ПС + Результат.Ошибка);
ИначеЕсли Результат.Данные = Неопределено Тогда
Результат.Ошибка = НСтр("ru = 'Ответ сервера не является JSON.'");
КонецЕсли;
Возврат Результат;
КонецФункции
Функция ОписаниеОшибокAPI(Данные)
Тексты = Новый Массив;
Для Каждого Ошибка Из МассивИзОтвета(Данные, "errors") Цикл
Тексты.Добавить(СтрШаблон("%1: %2", ЗначениеПоля(Ошибка, "code", "сode"), Ошибка.Получить("text")));
КонецЦикла;
Возврат СтрСоединить(Тексты, "; ");
КонецФункции
Функция МассивИзОтвета(Данные, ИмяПоля)
Результат = Новый Массив;
Если ТипЗнч(Данные) <> Тип("Соответствие") Тогда
Возврат Результат;
КонецЕсли;
Значение = Данные.Получить(ИмяПоля);
Если ТипЗнч(Значение) <> Тип("Массив") Тогда
Возврат Результат;
КонецЕсли;
Для Каждого Элемент Из Значение Цикл
Если ТипЗнч(Элемент) = Тип("Соответствие") Тогда
Результат.Добавить(Элемент);
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Функция ЗначениеПоля(Данные, ИмяПоля, ЗапасноеИмяПоля)
Значение = Данные.Получить(ИмяПоля);
Если Значение = Неопределено Тогда
Значение = Данные.Получить(ЗапасноеИмяПоля);
КонецЕсли;
Возврат Значение;
КонецФункции
#КонецОбласти
#КонецОбласти