Обновление цен из Excel в 1С и затем через REST-API на 1С-Битрикс

Задача: обновлять цены в 1С УТ11 и на сайте 1С-Битрикс из Excel файла.

Создал обработчик 1С платформы который берет данные из Excel создает документ с новыми ценами для торговли и затем отправляет массив данных в JSON формате на REST-API сайта.

Реализация в 1С:

&НаКлиенте
Процедура ПриОткрытии(Отказ)
	ПриОткрытииСервер();
КонецПроцедуры

&НаСервере
Процедура ПриОткрытииСервер()
	ВидЦены = Справочники.ВидыЦен.НайтиПоНаименованию("Прайс-лист");
КонецПроцедуры

&НаКлиенте
Процедура ИмпортироватьЦены(Команда)
	Если ПутьКExcel = "" Тогда
		Сообщить("Не указан путь к файлу");
		Возврат;
	КонецЕсли;
	
	Если ПроверитьСуществованиеФайла(ПутьКExcel) = Ложь Тогда
		Сообщить("Не найден файл импорта");
		Возврат;
	КонецЕсли;
	
	ДвоичныеДанные = Новый ДвоичныеДанные(ПутьКExcel);
	Идентификатор  = Новый УникальныйИдентификатор;
	
	ИмпортироватьЦеныСервер(ПоместитьВоВременноеХранилище(ДвоичныеДанные, Идентификатор));
КонецПроцедуры

&НаСервере
Процедура ИмпортироватьЦеныСервер(ФайлВоВремХранилище)
	ДвоичныеДанные = ПолучитьИзВременногоХранилища(ФайлВоВремХранилище);
	ВремФайл = ПолучитьИмяВременногоФайла("xlsx");
	ДвоичныеДанные.Записать(ВремФайл);
	ИмпортТаблица = Новый ТабличныйДокумент;
	
	ИмпортТаблица.Прочитать(ВремФайл);
	
	СписокЦен = Новый Массив;
	
	ДанныеДляЗапроса = Новый Структура;
	ДанныеДляЗапроса.Вставить("items", Новый Массив);
	
	Если НЕ ИмпортТаблица.Область("R1C1").Текст = "Наименование"
		ИЛИ НЕ ИмпортТаблица.Область("R1C2").Текст = "Цена"
		ИЛИ НЕ ИмпортТаблица.Область("R1C3").Текст = "УникальныйИдентификатор"
	Тогда
		Сообщить("Не верный формат файла");
		Возврат;
	КонецЕсли;
	
	НумСтр = 2;
	
	МассивСтроки = Новый Массив;
	МассивСтроки.Добавить(ИмпортТаблица.Область("R" + НумСтр + "C1").Текст);
	МассивСтроки.Добавить(ИмпортТаблица.Область("R" + НумСтр + "C2").Текст);
	МассивСтроки.Добавить(ИмпортТаблица.Область("R" + НумСтр + "C3").Текст);
	
	Пока МассивСтроки[2] <> "" Цикл
		НоменклатураСсылка = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(МассивСтроки[2]));
		
		Если ПустаяСсылка(НоменклатураСсылка) Тогда
			Сообщить("Не найдена номенклатура: " + МассивСтроки[0] + "; " + МассивСтроки[2]);
			Возврат;
		КонецЕсли;
		
		Попытка
			НоменклатураЦена = Число(СтрЗаменить(СтрЗаменить(Формат(МассивСтроки[1], "ЧГ=0"), ",", "."), " ", ""));
			
			Если НЕ НоменклатураЦена = "" Тогда
				Если НоменклатураЦена > 0 Тогда
					ТекущаяЦена = ЦенаНоменклатуры(НоменклатураСсылка, ВидЦены);
						
					НоваяСтрока2 = Новый Структура;
					НоваяСтрока2.Вставить("uid", МассивСтроки[2]);
					НоваяСтрока2.Вставить("price", НоменклатураЦена);
					
					ДанныеДляЗапроса.items.Добавить(НоваяСтрока2);
					
					Если НоменклатураЦена <> ТекущаяЦена Тогда
						НоваяСтрока = Новый Структура;
						НоваяСтрока.Вставить("Номенклатура", НоменклатураСсылка);
						НоваяСтрока.Вставить("Цена", НоменклатураЦена);
						
						СписокЦен.Добавить(НоваяСтрока);
						
						Сообщить("" + НоменклатураСсылка + "; Цена текущая: " + ТекущаяЦена + "; Цена новая: " + НоменклатураЦена);
						//Возврат;
					КонецЕсли;
				КонецЕсли;
			КонецЕсли;
		Исключение
			Сообщить("" + НоменклатураСсылка + "; Не читается цена");
		КонецПопытки;
		
		НумСтр = НумСтр + 1;
		МассивСтроки = Новый Массив;
		МассивСтроки.Добавить(ИмпортТаблица.Область("R" + НумСтр + "C1").Текст);
		МассивСтроки.Добавить(ИмпортТаблица.Область("R" + НумСтр + "C2").Текст);
		МассивСтроки.Добавить(ИмпортТаблица.Область("R" + НумСтр + "C3").Текст);
	КонецЦикла;
	
	Если СписокЦен.Количество() > 0 Тогда
		УстановкаЦенНоменклатурыПоступ = Документы.УстановкаЦенНоменклатуры.СоздатьДокумент();
		УстановкаЦенНоменклатурыПоступ.Согласован = Истина;
		УстановкаЦенНоменклатурыПоступ.Дата = ТекущаяДата();
		УстановкаЦенНоменклатурыПоступ.Статус = Перечисления.СтатусыУстановокЦенНоменклатуры.Согласован;
		//УстановкаЦенНоменклатурыПоступ.Ответственный = ПараметрыСеанса.ТекущийПользователь;
		УстановкаЦенНоменклатурыПоступ.Записать();
		Для Каждого стр Из СписокЦен Цикл
			ЦеныНомен = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
			ЦеныНомен.Отбор.Регистратор.Установить(УстановкаЦенНоменклатурыПоступ.Ссылка);
			ЦеныНомен.Прочитать();
			ЗаписьНабора = ЦеныНомен.Добавить();
			ЗаписьНабора.Период = УстановкаЦенНоменклатурыПоступ.Дата;
			ЗаписьНабора.ВидЦены = ВидЦены;
			ЗаписьНабора.Номенклатура = стр.Номенклатура;
			ЗаписьНабора.Цена = стр.Цена;
			ЗаписьНабора.Валюта = Константы.ВалютаУправленческогоУчета.Получить();
			ЦеныНомен.Записать();
		КонецЦикла;
	КонецЕсли;
	
	Если ДанныеДляЗапроса.items.Количество() > 0 Тогда
		Сообщить(ПреобразоватьВJSON(ДанныеДляЗапроса));
		
		РезультатЗапроса = ЗапросJSON("crm.kremen.ru",
							"/rest/1/lx13a28s0wggda3s/kremen.catalog.price.update.json",
							ДанныеДляЗапроса,
							Неопределено,
							Неопределено,
							Ложь);
		
		Сообщить(ПреобразоватьВJSON(РезультатЗапроса));
							
		Если ПроверкаРезультата(РезультатЗапроса) = Ложь Тогда
			Сообщить("Ошибка обновления цен на сайте.");
			
			Возврат;
		КонецЕсли;
		
		Сообщить("Цены успешно обновлены.");
	КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПутьКExcelНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
	СтандартнаяОбработка = Ложь;
	ДиалогВыбора = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
	ДиалогВыбора.ПолноеИмяФайла = "Номенклатура.xlsx";
	ДиалогВыбора.Фильтр = "Файл данных (*.xlsx)|*.xlsx";
	ДиалогВыбора.Заголовок = "Выберите файл";
	Если ДиалогВыбора.Выбрать() Тогда
		ПутьКExcel = ДиалогВыбора.ПолноеИмяФайла;
	КонецЕсли;
КонецПроцедуры

&НаКлиенте
Функция ПроверитьСуществованиеФайла(ПутьКФайлу) Экспорт
	ФайлНаДиске = Новый Файл(ПутьКФайлу);
	Если ФайлНаДиске.Существует() Тогда
		Возврат Истина;
	КонецЕсли;
	Возврат Ложь;
КонецФункции

// ************************************************
// Сервисные

&НаСервере
Функция ПустаяСсылка(ОбъектСсылка) 
	Если Лев("" + ОбъектСсылка, 18) = "<Объект не найден>" Тогда
		Возврат Истина;
	ИначеЕсли "" + ОбъектСсылка = "" Тогда
		Возврат Истина;
	КонецЕсли;
	Возврат Ложь;
КонецФункции

&НаСервере
Функция ЦенаНоменклатуры(Номенклатура, ТипЦены)
	Дата = ТекущаяДата();
	
	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
		|    ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
		|ИЗ
		|    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ВидЦены=&ВидЦены)
		|КАК 
		|    ЦеныНоменклатурыСрезПоследних
		|ГДЕ
		|    ЦеныНоменклатурыСрезПоследних.Номенклатура = &Номенклатура";
			   
	Запрос.УстановитьПараметр("Номенклатура",Номенклатура);
	Запрос.УстановитьПараметр("Дата", Дата);
	Запрос.УстановитьПараметр("ВидЦены", ТипЦены);

	РезультатЗапроса=Запрос.Выполнить();
	Выборка = РезультатЗапроса.Выбрать();
	Если Выборка.Следующий() тогда
		Возврат Выборка.Цена;
	Иначе
		Возврат 0;
	КонецЕсли;
КонецФункции

&НаСервере
Функция ПроверкаРезультата(Результат)
	Если Не ТипЗнч(Результат) = Тип("Структура") Тогда
		Сообщить("Ошибка ответа; Тип значения ответа: " + ТипЗнч(Результат));
		Возврат Ложь;
	КонецЕсли;
	
	Если Результат.Свойство("result") = Ложь Тогда
		Сообщить("Ошибка ответа; result не найден");
		КлючиСтруктуры = "";
		Для Каждого стр из Результат Цикл
			Если КлючиСтруктуры <> "" Тогда
				КлючиСтруктуры = КлючиСтруктуры + ", ";
			КонецЕсли;
			КлючиСтруктуры = КлючиСтруктуры + стр.Ключ;
		КонецЦикла;
		Если КлючиСтруктуры <> "" Тогда
			Сообщить("Ключи ответа: " + КлючиСтруктуры);
		КонецЕсли;
		Возврат Ложь;
	КонецЕсли;
	
	Возврат Истина;
КонецФункции

&НаСервере
процедура Запрос(ХостЗапроса, АдресЗапроса, ВыходнойФайл = неопределено, КодСостояния = неопределено, ЗаголовкиHTTP = неопределено, ФайлЗапроса = неопределено)
	НТТР = Новый HTTPСоединение(ХостЗапроса,, , , , 1200, Новый ЗащищенноеСоединениеOpenSSL(,));
	если ВыходнойФайл = неопределено тогда
		ВыходнойФайл = ПолучитьимяВременногоФайла();
	конецесли;
	Попытка
		если ФайлЗапроса <> неопределено тогда
		    ОтветHTTP = НТТР.ОтправитьДляОбработки(ФайлЗапроса, АдресЗапроса, ВыходнойФайл, ЗаголовкиHTTP);
		иначе
			Запрос = Новый HTTPЗапрос(АдресЗапроса, ЗаголовкиHTTP);
			ОтветHTTP = НТТР.Получить(Запрос, ВыходнойФайл);
		конецесли;
	Исключение
		Сообщить("Неудачная попытка соединения с сервером" + Символы.ПС + ХостЗапроса + Символы.ПС + АдресЗапроса + Символы.ПС + ОписаниеОшибки());
		возврат;
	КонецПопытки;
	
	Попытка
		КодСостояния = ОтветHTTP.КодСостояния;
	исключение
		КодСостояния = неопределено;
	конецпопытки;
	
	если КодСостояния = неопределено тогда
		сообщить("Не удалось получить код состояния" + Символы.ПС + ХостЗапроса + Символы.ПС + АдресЗапроса);	
	конецесли;
конецпроцедуры

&НаСервере
функция ЗапросJSON(ХостЗапроса, АдресЗапроса, СтруктураЗапроса = неопределено, ЗаголовкиHTTP = неопределено, ПриставкаДляКлючей = неопределено, postSend = false)
	if ЗаголовкиHTTP = неопределено then
		ЗаголовкиHTTP = Новый Соответствие;
	endif;
	
	ЗаголовкиHTTP.Вставить("Accept", "application/json");
	
	Если СтруктураЗапроса = неопределено Тогда
		ФайлЗапроса = неопределено;
		//Сообщить("неопределено");
	Иначе
		if postSend = false then
			if ПриставкаДляКлючей = неопределено then
				ПриставкаДляКлючей = новый Структура;
			endif;
			
			ЗаголовкиHTTP.Вставить("Content-Type", "application/json");
			
			ФайлЗапроса = ПолучитьимяВременногоФайла("json");
			//Сообщить("JSON");
			Текст = Новый ЗаписьТекста;
			Текст.Открыть(ФайлЗапроса, "CESU-8");
			Текст.ЗаписатьСтроку(ПреобразоватьВJSON(СтруктураЗапроса, ПриставкаДляКлючей));
			Текст.Закрыть();
		else
			ЗаголовкиHTTP.Вставить("Content-Type", "application/x-www-form-urlencoded");
			
			sendPost = "";
			for each str in СтруктураЗапроса do
				sendPost = sendPost + ?(sendPost <> "", "&", "") + str.Key + "=" + str.Value;
			enddo;
			//Сообщить("POST: " + sendPost);
			ФайлЗапроса = ПолучитьимяВременногоФайла();
			ФайлОтправки = Новый ЗаписьДанных(ФайлЗапроса);
			ФайлОтправки.ЗаписатьСтроку(sendPost);
			ФайлОтправки.Закрыть();
		endif;
	КонецЕсли;
	//Текст = Новый ЧтениеТекста;Текст.Открыть(ФайлЗапроса, "CESU-8");message(Текст.ПрочитатьСтроку());Текст.Закрыть();
	ВыходнойФайл = ПолучитьимяВременногоФайла("json");
	
	КодСостояния = неопределено;
	
	Запрос(ХостЗапроса, АдресЗапроса, ВыходнойФайл, КодСостояния, ЗаголовкиHTTP, ФайлЗапроса);
	
	если КодСостояния = неопределено тогда
		РезультатЗапроса = неопределено;
	иначе
		Попытка
			ЧтениеJSON = Новый ЧтениеJSON;
			ЧтениеJSON.ОткрытьФайл(ВыходнойФайл);
			РезультатЗапроса = ПрочитатьJSON(ЧтениеJSON);
			ЧтениеJSON.Закрыть();
		Исключение
			Сообщить("Ошибка чтения JSON" + Символы.ПС + ХостЗапроса + Символы.ПС + АдресЗапроса + Символы.ПС + ОписаниеОшибки());
			Сообщить(ПрочитатьФайлОшибки(ВыходнойФайл));
		КонецПопытки;
	конецесли;
	
	Попытка
		УдалитьФайлы(ВыходнойФайл);
		УдалитьФайлы(ФайлЗапроса);
	Исключение
	КонецПопытки;
	
	возврат РезультатЗапроса;
конецфункции

&НаСервере
Функция ПрочитатьФайлОшибки(имяВыходногоФайла)
	Текст = Новый ЧтениеТекста(имяВыходногоФайла, КодировкаТекста.UTF8);

	Стр = текст.прочитатьСтроку();
	ТекстОшибки = "" + Стр+ символы.ПС;
	Пока Стр <> Неопределено Цикл 
		Стр = Текст.ПрочитатьСтроку();
		ТекстОшибки = ТекстОшибки + ?(Стр = Неопределено,"",Стр)+ символы.ПС;
	КонецЦикла;

	возврат ТекстОшибки;
КонецФункции

&НаСервере
функция ПреобразоватьВJSON(ДанныеДляПреобразования, ПриставкаДляКлючей = неопределено)
	если ПриставкаДляКлючей = неопределено тогда
		ПриставкаДляКлючей = новый Структура;
	конецесли;
	если ТипЗнч(ДанныеДляПреобразования) = Тип("Массив") тогда
		возврат ПреобразоватьВJSONМассив(ДанныеДляПреобразования, ПриставкаДляКлючей);
	иначеесли ТипЗнч(ДанныеДляПреобразования) = Тип("Структура") тогда
		возврат ПреобразоватьВJSONСтруктура(ДанныеДляПреобразования, ПриставкаДляКлючей);
	иначе
		возврат ложь;
	конецесли;
конецфункции

&НаСервере
функция ПреобразоватьВJSONМассив(ДанныеДляПреобразования, ПриставкаДляКлючей)
	Результат = "[";
	для каждого стр из ДанныеДляПреобразования цикл
		если не Результат = "[" тогда
			Результат = Результат + ",";
		конецесли;
		если ТипЗнч(стр) = Тип("Массив") тогда
			Результат = Результат + ПреобразоватьВJSONМассив(стр, ПриставкаДляКлючей);
		иначеесли ТипЗнч(стр) = Тип("Структура") тогда
			Результат = Результат + ПреобразоватьВJSONСтруктура(стр, ПриставкаДляКлючей);
		иначеесли ТипЗнч(стр) = Тип("Булево") тогда
			Результат = Результат + ?(стр, "true", "false");
		иначеесли ТипЗнч(стр) = Тип("Число") тогда
			ЧислоВСтроке = "" + СтрЗаменить("" + Формат(стр, "ЧГ=0"), ",", ".");
			если ЧислоВСтроке = "" тогда
				ЧислоВСтроке = "0";
			конецесли;
			Результат = Результат + ЧислоВСтроке;
		иначеесли стр = неопределено тогда
			Результат = Результат + "null";
		иначе
			Результат = Результат + """" + СтрЗаменить(СтрЗаменить(СтрЗаменить(СтрЗаменить(Строка(стр), "\", "\\"), Символ(13)+Символ(10), "\n"), Символ(10), "\n"), """", "\""") + """";
		конецесли;
	конеццикла;
	Результат = Результат + "]";
	возврат Результат;
конецфункции

&НаСервере
функция ПреобразоватьВJSONСтруктура(ДанныеДляПреобразования, ПриставкаДляКлючей)
	Результат = "{";
	для каждого стр из ДанныеДляПреобразования цикл
		стрКлюч = стр.Ключ;
		для каждого стрПриставка из ПриставкаДляКлючей цикл
			если стрПриставка.Ключ = стрКлюч тогда
				стрКлюч = стрПриставка.Значение + стрКлюч;
				прервать;
			конецесли;
		конеццикла;
		если не Результат = "{" тогда
			Результат = Результат + ",";
		конецесли;
		если ТипЗнч(стр.Значение) = Тип("Массив") тогда
			Результат = Результат + """" + стрКлюч + """:" + ПреобразоватьВJSONМассив(стр.Значение, ПриставкаДляКлючей);
		иначеесли ТипЗнч(стр.Значение) = Тип("Структура") тогда
			Результат = Результат + """" + стрКлюч + """:" + ПреобразоватьВJSONСтруктура(стр.Значение, ПриставкаДляКлючей);
		иначеесли ТипЗнч(стр.Значение) = Тип("Булево") тогда
			Результат = Результат + """" + стрКлюч + """:" + ?(стр.Значение, "true", "false");
		иначеесли ТипЗнч(стр.Значение) = Тип("Число") тогда
			ЧислоВСтроке = "" + СтрЗаменить("" + Формат(стр.Значение, "ЧГ=0"), ",", ".");
			если ЧислоВСтроке = "" тогда
				ЧислоВСтроке = "0";
			конецесли;
			Результат = Результат + """" + стрКлюч + """:" + ЧислоВСтроке;
		иначеесли стр.Значение = неопределено тогда
			Результат = Результат + """" + стрКлюч + """:null";
		иначе
			Результат = Результат + """" + стрКлюч + """:" + """" + СтрЗаменить(СтрЗаменить(СтрЗаменить(СтрЗаменить(Строка(стр.Значение), "\", "\\"), Символ(13)+Символ(10), "\n"), Символ(10), "\n"), """", "\""") + """";
		конецесли;
	конеццикла;
	Результат = Результат + "}";
	возврат Результат;
конецфункции

Реализация на сайте (local/php_interface/init.php):

<?

// ...

// *********************************************************
// begin:REST API присваения цен товарам

AddEventHandler('rest', 'OnRestServiceBuildDescription', ['\DwRestApi', 'OnRestServiceBuildDescription']);

class DwRestApi
{
	public static function OnRestServiceBuildDescription()
	{
		return [
			'disweb' => [
				'disweb.catalog.price.update' => [
					'callback' => [__CLASS__, 'catalogPriceUpdate'],
					'options' => [],
				],
			],
		];
	}

	public static function catalogPriceUpdate($query, $nav, \CRestServer $server)
	{
		if ($query['error'])
		{
			throw new \Bitrix\Rest\RestException(
				'Message',
				'ERROR_CODE',
				\CRestServer::STATUS_PAYMENT_REQUIRED
			);
		}
		
		if (!isset($query['items']))
		{
			return [
				'error' => 'ERROR_ITEMS_VAR_NOT_FOUND',
				'error_description' => 'Items variable not found! ' . __LINE__,
			];
		}
		
		if (count($query['items']) <= 0)
		{
			return [
				'error' => 'ERROR_ITEMS_NOT_FOUND',
				'error_description' => 'Items not found! ' . __LINE__,
			];
		}
		
		if (!is_array($query['items']))
		{
			return [
				'error' => 'ERROR_ITEMS_VAR_NO_ARRAY',
				'error_description' => 'Items variable not array! ' . __LINE__,
			];
		}
		
		$items = [];
		
		foreach ($query['items'] as $item)
		{
			if (!isset($item['uid']) || !isset($item['price']))
			{
				continue;
				
				// return [
					// 'error' => 'ERROR_ITEM_DATA',
					// 'error_description' => 'Item data not found! ' . __LINE__,
				// ];
			}
			
			$uid = trim($item['uid']);
			$price = floatval(preg_replace('/[^0-9\.]/', '', $item['price']));
			
			if (!$price)
			{
				continue;
				
				// return [
					// 'error' => 'ERROR_ITEM_PRICE',
					// 'error_description' => 'Item price not found! ' . __LINE__,
				// ];
			}
			
			$el_res = \CIblockElement::GetList(
				[],
				[
					'XML_ID' => $uid,
				],
				false,
				false,
				['ID']
			);
			
			if (!$el = $el_res->Fetch())
			{
				continue;
				
				// return [
					// 'error' => 'ERROR_ITEM_NOT_FONT',
					// 'error_description' => 'Item guid: ' . $uid . ' not found! ' . __LINE__,
				// ];
			}
			
			$ar_fields = [
				'PRODUCT_ID' => $el['ID'],
				'PRICE' => $price,
				'CATALOG_GROUP_ID' => 1
			];
			
			$price_res = CPrice::GetList(
				[],
				[
					'PRODUCT_ID' => $el['ID'],
					'CATALOG_GROUP_ID' => 1,
				]
			);

			if ($row = $price_res->Fetch())
			{
				\CPrice::Update($row['ID'], $ar_fields);
			}
			else
			{
				\CPrice::Add($ar_fields);
			}
			
			$items[] = [
				'uid' => $uid,
				'price' => $price,
				'result' => 'ok',
			];
		}
		
		if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/bitrix/cache/s2/bitrix/catalog.section')
			|| file_exists($_SERVER['DOCUMENT_ROOT'] . '/bitrix/cache/s2/bitrix/catalog.section.list'))
		{
			exec('rm -rf ' . $_SERVER['DOCUMENT_ROOT'] . '/bitrix/cache/s2/bitrix/catalog*');
		}

		return [
			'status' => 'ok',
		];
	}
	
}

// end:REST API присваения цен товарам
// *********************************************************

// ...