Обновление цен из 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("example.com",
							"/rest/1/htrhrthrthtr/disweb.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 присваения цен товарам
// *********************************************************

// ...