Всех приветствую. Сайт, с которым я тестирую обмен: knigakid.ru.
Легкое предисловие
Ранее всегда избегал web-задач. Но когда у меня 100-й раз клиенты спрашивали про интернет магазин, я задумался, а почему бы и нет. 1С-программист по роду деятельности постоянно сталкивается со смежными направлениями. Кто-то дополнительно занимается консалтингом, кто-то железо собирает. И я не знаю что там на рынке web-разработок, но почему-то дефицит специалистов там явно присутствует, причем похлеще чем в 1с.
Почему 1sm? Хотя обмен вполне рабочий, но:
- Во-первых это не решение под ключ, здесь нужен спец, который грамотно все прикрутит.
- Во-вторых, цель этой обработки, чтобы пользователь нажал одну кнопку, и у него на сайте было все готово, и я ее еще не достиг.
Почему OpenCart
Понятно, что тема холиварная. Мне OpenCart подошел т.к.:
- Бесплатно.
- Заточено под интернет магазин.
- Одно из самых популярных решений для интернет магазина.
- Локализовано - есть большое русское сообщество + документация + расширения.
- Дистрибутив очень легкий, что-то около 40мб, для сравнения Битрикс раз в 10 больше.
- Нет ограничений на хостинг. Я выбрал hostland.ru.
На данном сайте я ничего не верстал, не PHP-шил, не JavaScript-ил. Уровень конструкторов современных CMS вполне достойный для стандартных задач. И в целом, для обмена с 1С не важно какая CMS, принцип хранения информации у всех похож. Мне дополнительно этой статьей хотелось показать, что обмен с сайтом как и его создание это вполне посильная задача для большинства программистов 1С.
Плагин NewStore 3
Т.к. пустой OpenCart это довольно скучное решение, я сразу начал искать тюнинг для него. Для меня важно было оформление заказа в 1-2 клика и список товаров списком. В этом шаблоне это есть. Ссылка на шаблон: opencartforum.com. Ссылка на демо: 3xns.waterfilter.in.ua.
Как это работает
Подразумеваем, что OpenCart вы уже установили, и у вас есть параметры сервера, на котором он крутится.
В 1С запускаем обработку. Так она выглядит при запуске:
Далее настраиваем подключение на соответствующей вкладке:
Можно заполнить вручную, можно подложить файл config.txt с настройками. Пример файла:
нс_АдресСервера = "хххххххххххххххххххххх ";
нс_MySQL_ИмяБазы = "хххххххххххххххххххххх ";
нс_MySQL_ПользовательЛогин = "хххххххххххххххххххххх ";
нс_MySQL_ПользовательПароль = "хххххххххххххххххххххх ";
нс_MySQL_ПортСервера = "3306 ";
нс_MySQL_ИмяODBCДрайвера = "MySQL ODBC 5.3 Unicode Driver ";
нс_FTP_ХостСервера = "хххххххххххххххххххххх ";
нс_FTP_ПортСервера = "21 ";
нс_FTP_ПользовательЛогин = "хххххххххххххххххххххх ";
нс_FTP_ПользовательПароль = "хххххххххххххххххххххх ";
В 1С обработка файла настройки подключения выглядит так:
// узнаем путь откуда открыта обработка
НашПутьОбработки = ИспользуемоеИмяФайла;
ТекущаяСтрокаПоиска = НашПутьОбработки;
пока Найти(ТекущаяСтрокаПоиска,"\") > 0 цикл
ИндексСлеша = Найти(ТекущаяСтрокаПоиска,"\");
ТекущаяСтрокаПоиска = Прав(ТекущаяСтрокаПоиска,СтрДлина(ТекущаяСтрокаПоиска) - ИндексСлеша);
КонецЦикла;
НашКаталог = Лев(НашПутьОбработки, СтрДлина(НашПутьОбработки) - СтрДлина(ТекущаяСтрокаПоиска));
ФайлНастройкиПодключения = НашКаталог + "config.txt";
//читаем настройки подключения
Текст = Новый ЧтениеТекста;
попытка
Текст.Открыть(ФайлНастройкиПодключения);
Исключение
Сообщить(ОписаниеОшибки());
Возврат;
КонецПопытки;
Стр = "";
Пока Стр <> Неопределено Цикл
Стр = Текст.ПрочитатьСтроку();
ИмяПараметра = СокрЛП(Лев(Стр,29));
ЗначениеПараметра = СокрЛП(Сред(Стр,33,30));
//присвоим параметры подключения
если ИмяПараметра = "нс_АдресСервера" тогда
нс_АдресСервера = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_MySQL_ИмяБазы" тогда
нс_MySQL_ИмяБазы = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_MySQL_ПользовательЛогин" тогда
нс_MySQL_ПользовательЛогин = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_MySQL_ПользовательПароль" тогда
нс_MySQL_ПользовательПароль = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_MySQL_ПортСервера" тогда
нс_MySQL_ПортСервера = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_MySQL_ИмяODBCДрайвера" тогда
нс_MySQL_ИмяODBCДрайвера = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_FTP_ХостСервера" тогда
нс_FTP_ХостСервера = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_FTP_ПортСервера" тогда
нс_FTP_ПортСервера = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_FTP_ПользовательЛогин" тогда
нс_FTP_ПользовательЛогин = ЗначениеПараметра;
иначеесли ИмяПараметра = "нс_FTP_ПользовательПароль" тогда
нс_FTP_ПользовательПароль = ЗначениеПараметра;
конецЕсли;
//Сообщить(ИмяПараметра + "-" + ЗначениеПараметра);
КонецЦикла;
Текст.Закрыть();
Далее нужно промаркировать номенклатуру:
Для этого я завел новое ПВХ: "Идентификатор номенклатуры на сайте". На вкладке "настройка номенклатуры" есть команды очиски ПВХ и присвоение новых ПВХ номенклатуре, у которой их еще нет. Этот ПВХ потом будет уникальным идентификатором номенклатуры на сайте. Стандартные коды не подошли, т.к. на сайте в коде не должно быть букв, а у нас могут быть префиксы.
Вкладка настройки номенклатуры:
Пример ПВХ:
Далее на сайте нужно прописать путь, где будут храниться картинки:
Я сделал на сайте каталог: "catalog/_PRODUCT_IMAGE/" и в нем для картинок категорий папку "Category", а для товара папки от "0" до "9", в которых также папки от "0" до "9". Смысл такой, коды у нас формата 00000, т.е. от 1 до 99999. Первая цифра кода это первая папка, вторая соответственно - вторая папка. Т.е. товар с кодом 34509 попадет в папку "catalog/_PRODUCT_IMAGE/3/4/".
Как это выглядит на хостинге:
Алгоритм в 1С для создания иерархии:
Если не ПодключитьсяFTP() тогда
Возврат;
КонецЕсли;
ФТПСоединение.УстановитьТекущийКаталог("/_PRODUCT_IMAGE/");
МассивФайлов = ФТПСоединение.НайтиФайлы("","*");
Если МассивФайлов.Количество() > 0 тогда
Сообщить("В указанной папке уже есть файлы: " + МассивФайлов.Количество() + ". Для генерации каталогов необходима пустая папка.");
возврат;
Иначе
Сообщить("Каталог пустой, начинаем запись...");
КонецЕсли;
ВремяСтарта = ТекущаяДата();
Для КаталогПервыйУровень = 0 по 9 цикл
если ФТПСоединение.ТекущийКаталог() <> "/_PRODUCT_IMAGE/" тогда
ФТПСоединение.УстановитьТекущийКаталог("/_PRODUCT_IMAGE/");
конецЕсли;
ФТПСоединение.СоздатьКаталог(Строка(КаталогПервыйУровень));
ФТПСоединение.УстановитьТекущийКаталог("/_PRODUCT_IMAGE/" + Строка(КаталогПервыйУровень)+"/");
Для КаталогВторойУровень = 0 по 9 цикл
ФТПСоединение.СоздатьКаталог(Строка(КаталогВторойУровень));
КонецЦикла;
КонецЦикла;
ЗатраченоСекунд = ТекущаяДата() - ВремяСтарта;
Час_Мин_Сек = Формат('00010101'+ЗатраченоСекунд, "ДФ=HH:mm:ss");
Сообщить("Время на создание каталогов затрачено: " + Час_Мин_Сек);
Далее выгружаем категории:
Далее можем приступать к выгрузке товара:
Далее планируется сделать автоматическое формирование панелей на главной странице. Пока это делается вручную в панели администратора сайта.
Примеры кода
Формирование SQL запросов передающих информацию о категориях:
Функция СформироватьSQL_ОбновлениеКаталога_category()
Запрос = новый Запрос;
Запрос.Текст = ПолучитьТекстЗапроса("ВсеГруппы");
ТЗ = Запрос.Выполнить().Выгрузить();
НашSQLЗапрос_category = "INSERT INTO `do_category` (`category_id`, `image`, `parent_id`, `top`, `column`, `sort_order`, `status`, `date_added`, `date_modified`) VALUES";
НашSQLЗапрос_category_description = "INSERT INTO `do_category_description` (`category_id`, `language_id`, `name`, `description`, `meta_title`, `meta_description`, `meta_keyword`) VALUES";
НашSQLЗапрос_category_path = "INSERT INTO `do_category_path` (`category_id`, `path_id`, `level`) VALUES";
НашSQLЗапрос_category_to_layout = "INSERT INTO `do_category_to_layout` (`category_id`, `store_id`, `layout_id`) VALUES";
НашSQLЗапрос_category_to_store = "INSERT INTO `do_category_to_store` (`category_id`, `store_id`) VALUES";
НашSQLЗапрос_do_seo_url = "INSERT INTO `do_seo_url` (`store_id`, `language_id`, `query`, `keyword`) VALUES";
ПервыйЗаход = Истина;
top = 0;
column = 1;
sort_order = 0;
status = 1;
language_id = 1;
level = 0;
store_id = 0;
layout_id = 0;
ПутьКартинкиКатегорий = "catalog/_PRODUCT_IMAGE/Category/";
//ФорматКода = "ЧЦ=11; ЧДЦ=0; ЧРГ=''; ЧН=0";
ФорматКода = "ЧЦ=11; ЧН=0";
ФорматДаты1 = "ДФ=""гггг-ММ-дд ЧЧ:мм:сс""";
//Для каждого стр из ТЗ цикл
Для каждого стр из ВыгружаемыеКатегории цикл
стрСсылка = стр.ГруппаНоменклатуры;
стрКод = стр.КодГруппыНоменклатуры;
если нн_НазваниеКатегорийВерхнийРегистр тогда
стрНаименование = ВРег(стр.ГруппаНоменклатуры.Наименование);
иначе
стрНаименование = стр.ГруппаНоменклатуры.Наименование;
КонецЕсли;
стрИмяКартинки = стр.ИмяФайлаКартинки;
стрРодительКод = "";
если ЗначениеЗаполнено(стр.РодительГруппыНоменклатуры.Код) тогда
стрРодительКод = стр.РодительГруппыНоменклатуры.Код;
КонецЕсли;
Если не ПервыйЗаход тогда
НашSQLЗапрос_category = НашSQLЗапрос_category + ",";
НашSQLЗапрос_category_description = НашSQLЗапрос_category_description + ",";
НашSQLЗапрос_category_path = НашSQLЗапрос_category_path + ",";
НашSQLЗапрос_category_to_layout = НашSQLЗапрос_category_to_layout + ",";
НашSQLЗапрос_category_to_store = НашSQLЗапрос_category_to_store + ",";
НашSQLЗапрос_do_seo_url = НашSQLЗапрос_do_seo_url + ",";
Иначе
ПервыйЗаход = Ложь;
КонецЕсли;
category_id = "9" + Прав("000000" + СтрЗаменить(Формат(Число(Прав(стрКод,6)),ФорматКода),Символы.НПП,""),6); /// Впереди "9" для псевдо уникальности
если стрИмяКартинки = "" тогда
image = "''"
Иначе
image = "'" + ПутьКартинкиКатегорий + стрИмяКартинки + "'";
КонецЕсли;
если стрРодительКод = "" тогда
parent_id = "0";
Иначе
parent_id = "9" + Прав("000000" + СтрЗаменить(Формат(Число(Прав(стрРодительКод,6)),ФорматКода),Символы.НПП,""),6);
КонецЕсли;
date_added = "'" + Формат('00010101010101',ФорматДаты1) + "'";
date_modified = "'" + Формат('00010101010101',ФорматДаты1) + "'";
name = "'" + стрНаименование + "'";
description = "''";
meta_title = "'" + стрНаименование + "'";
meta_description = "'" + стрНаименование + "'";
meta_keyword = "'" + стрНаименование + "'";
path_id = category_id;
query = "'category_id=" + category_id + "'";
keyword = "'" + глТранслитерация(стрНаименование) + "'";
НашSQLЗапрос_category = НашSQLЗапрос_category + Символы.ПС + "(" + category_id + ", " + image + ", " + parent_id + ", " +
top + ", " + column + ", " + sort_order + ", " + status + ", " +
date_added + ", " + date_modified + ")";
НашSQLЗапрос_category_description = НашSQLЗапрос_category_description + Символы.ПС + "(" + category_id + ", " + language_id + ", " +
name + ", " + description + ", " + meta_title + ", " + meta_description + ", " +
meta_keyword + ")";
если ЗначениеЗаполнено(стрСсылка.Родитель) тогда /// если есть вложенные категорий !!!!
ГлубинаВложенности = 1;
ТекущаяГруппа = стрСсылка.Родитель;
пока ЗначениеЗаполнено(ТекущаяГруппа.Родитель) цикл // посчитаем глубину вложенности иерархии
ГлубинаВложенности = ГлубинаВложенности + 1;
ТекущаяГруппа = ТекущаяГруппа.Родитель;
КонецЦикла;
НашSQLЗапрос_category_path = НашSQLЗапрос_category_path + Символы.ПС + "(" + category_id + ", " + path_id + ", " + Строка(ГлубинаВложенности) + ")";
ТекущаяГруппа = стрСсылка;
пока ЗначениеЗаполнено(ТекущаяГруппа.Родитель) цикл
ГлубинаВложенности = ГлубинаВложенности - 1;
ТекущаяГруппа = ТекущаяГруппа.Родитель;
path_id = "9" + Прав("000000" + СтрЗаменить(Формат(Число(Прав(ТекущаяГруппа.Код,6)),ФорматКода),Символы.НПП,""),6); /// Впереди "9" для псевдо уникальности
НашSQLЗапрос_category_path = НашSQLЗапрос_category_path + "," + Символы.ПС + "(" + category_id + ", " + path_id + ", " + Строка(ГлубинаВложенности) + ")";
КонецЦикла;
Иначе
НашSQLЗапрос_category_path = НашSQLЗапрос_category_path + Символы.ПС + "(" + category_id + ", " + path_id + ", " + "0" + ")";
конецесли;
НашSQLЗапрос_category_to_layout = НашSQLЗапрос_category_to_layout + Символы.ПС + "(" + category_id + ", " + store_id + ", " + layout_id + ")";
НашSQLЗапрос_category_to_store = НашSQLЗапрос_category_to_store + Символы.ПС + "(" + category_id + ", " + store_id + ")";
НашSQLЗапрос_do_seo_url = НашSQLЗапрос_do_seo_url + Символы.ПС + "(" + store_id + ", " + language_id + ", " + query + ", " + keyword + ")";
КонецЦикла;
НашSQLЗапрос_category = НашSQLЗапрос_category + Символы.ПС + "ON DUPLICATE KEY UPDATE `image` = VALUES (`image`), `parent_id` = VALUES (`parent_id`)";
НашSQLЗапрос_category_description = НашSQLЗапрос_category_description + Символы.ПС + "ON DUPLICATE KEY UPDATE `name` = VALUES (`name`), `description` = VALUES (`description`), `meta_title` = VALUES (`meta_title`), `meta_description` = VALUES (`meta_description`), `meta_keyword` = VALUES (`meta_keyword`)";
НашSQLЗапрос_category_path = НашSQLЗапрос_category_path + Символы.ПС + "ON DUPLICATE KEY UPDATE `path_id` = VALUES (`path_id`), `level` = VALUES (`level`)";
НашSQLЗапрос_category_to_layout = НашSQLЗапрос_category_to_layout + Символы.ПС + "ON DUPLICATE KEY UPDATE `store_id` = VALUES (`store_id`), `layout_id` = VALUES (`layout_id`)";
НашSQLЗапрос_category_to_store = НашSQLЗапрос_category_to_store + Символы.ПС + "ON DUPLICATE KEY UPDATE `store_id` = VALUES (`store_id`)";
НашSQLЗапрос_do_seo_url = НашSQLЗапрос_do_seo_url + Символы.ПС + "ON DUPLICATE KEY UPDATE `store_id` = VALUES (`store_id`), `language_id` = VALUES (`language_id`), `keyword` = VALUES (`keyword`)";
//НашSQLЗапрос_ИТОГ = //НашSQLЗапрос_присказка + ";" + Символы.ПС + // Так не работает, драйвер соединения с mysql не передает составные запросы //мифе 20171024
//НашSQLЗапрос_category; + ";" + Символы.ПС +
//НашSQLЗапрос_category_description + ";" + Символы.ПС +
//НашSQLЗапрос_category_path + ";" + Символы.ПС +
//НашSQLЗапрос_category_to_layout + ";" + Символы.ПС +
//НашSQLЗапрос_category_to_store + ";";
СписокЗапросов = новый СписокЗначений;
СписокЗапросов.Добавить(НашSQLЗапрос_category);
СписокЗапросов.Добавить(НашSQLЗапрос_category_description);
СписокЗапросов.Добавить(НашSQLЗапрос_category_path);
СписокЗапросов.Добавить(НашSQLЗапрос_category_to_layout);
СписокЗапросов.Добавить(НашSQLЗапрос_category_to_store);
СписокЗапросов.Добавить(НашSQLЗапрос_do_seo_url);
возврат СписокЗапросов;
КонецФункции
Подключение и передача изображения товара через ftp:
Функция ПодключитьсяFTP()
ФТП_Хост = нс_FTP_ХостСервера;
ФТП_Порт = нс_FTP_ПортСервера;
ФТП_Пользователь = нс_FTP_ПользовательЛогин;
ФТП_Пароль = нс_FTP_ПользовательПароль;
ФТП_ПассивноеСоединение = Истина; // ХЗ, может поменять?
Попытка
ВремяСтарта = ТекущаяДата();
ФТПСоединение = Новый FTPСоединение(ФТП_Хост,ФТП_Порт, ФТП_Пользователь, ФТП_Пароль,,ФТП_ПассивноеСоединение);
ЗатраченоСекунд = ТекущаяДата() - ВремяСтарта;
Час_Мин_Сек = Формат('00010101'+ЗатраченоСекунд, "ДФ=HH:mm:ss");
Сообщить("Время на подключение затрачено: " + Час_Мин_Сек);
Возврат Истина;
Исключение
Предупреждение("Не могу соединиться с FTP сервером");
Возврат Ложь;
КонецПопытки;
КонецФункции
Процедура ВыгрузитьИзображенияТоваровНаСайт(Элемент)
Если не ПодключитьсяFTP() тогда
Возврат;
КонецЕсли;
Для каждого стр из ВыгружаемыйТовар цикл
если не стр.КартинкаЕсть тогда
продолжить;
КонецЕсли;
РасширениеФайлаИзображения = "";
ПозицияТочки = Найти(стр.СсылкаНаКартинку.ИмяФайла,".");
Если ПозицияТочки > 0 тогда
РасширениеФайлаИзображения = Прав(стр.СсылкаНаКартинку.ИмяФайла, СтрДлина(стр.СсылкаНаКартинку.ИмяФайла) - ПозицияТочки);
КонецЕсли;
ИмяКартинки = Прав("00000"+СтрЗаменить(Строка(стр.КодНоменклатуры),Символы.НПП,""),5)+"."+РасширениеФайлаИзображения;
ВременныйФайлКартинки = КаталогВременныхФайлов() + ИмяКартинки;
БинарнаяКартинка = стр.СсылкаНаКартинку.Хранилище.Получить();
БинарнаяКартинка.Записать(ВременныйФайлКартинки);
Сообщить(ВременныйФайлКартинки);
ФТПСоединение.УстановитьТекущийКаталог("/_PRODUCT_IMAGE/" + Лев(ИмяКартинки,1) + "/"+ Сред(ИмяКартинки,2,1) + "/");
ФТПСоединение.Записать(ВременныйФайлКартинки,ИмяКартинки);
КонецЦикла;
КонецПроцедуры
Структура SQL таблиц OpenCart
Структура таблиц OpenCart напомнила мне старенькую 1С7. Тоже самое, только проще. После этого разбираться стало гораздо легче. Вот пример таблиц связанных с категориями:
/////////////////////////////////////////////////////////
// Таблица do_category (основная таблица категорий)
// Пример: (81, 'catalog/Tovar/Samokat2.jpg', 80, 0, 1, 0, 1, '2017-09-28 21:18:23', '2017-09-29 18:57:11'),
//`category_id` int(11) NOT NULL, - идентификатор записи, берем код из 1С
//`image` varchar(255) DEFAULT NULL, - путь к картинке, например "catalog/Tovar/Samokat2.jpg"
//`parent_id` int(11) NOT NULL DEFAULT '0', - идентификатор родительской категории
//`top` tinyint(1) NOT NULL, - показывать в главном меню, по умолчанию "0"
//`column` int(3) NOT NULL, - количество столбцов в меню, по умолчанию "1"
//`sort_order` int(3) NOT NULL DEFAULT '0', - порядок сортировки, по умолчанию "0"
//`status` tinyint(1) NOT NULL, - аля пометка на удаление, по умолчанию "1" (включена)
//`date_added` datetime NOT NULL, - дата создания
//`date_modified` datetime NOT NULL - дата модификации
/////////////////////////////////////////////////////////
// Таблица do_category_description (таблица с наименованием категорий на разных языках)
// Пример: (75, 1, 'Треки, железная дорога, парковки', '', 'Треки, железная дорога, парковки', '', ''),
//`category_id` int(11) NOT NULL, - идентификатор записи, берем код из 1С
//`language_id` int(11) NOT NULL, - язык, у нас пока один, значение = "1"
//`name` varchar(255) NOT NULL, - собственно наименование на нашем родном, например "Самокаты"
//`description` text NOT NULL, - описание категории, например "Самокаты нужны каждому, они бывают такие разные"
//`meta_title` varchar(255) NOT NULL, - значение для заполнения html тега title
//`meta_description` varchar(255) NOT NULL, - значение для заполнения html тега description
//`meta_keyword` varchar(255) NOT NULL) - значение для заполнения html тега keyword
/////////////////////////////////////////////////////////
// Таблица do_category_filter (для задания фильтров поиска) /// не используем
//`category_id` int(11) NOT NULL, - идентификатор записи, берем код из 1С
//`filter_id` int(11) NOT NULL
/////////////////////////////////////////////////////////
// Таблица do_category_path (вспомогательная таблица для хранения иерархии каталога)
// Пример: (81, 80, 0),
//`category_id` int(11) NOT NULL, - идентификатор записи, берем код из 1С
//`path_id` int(11) NOT NULL, - если есть родитель то две записи, свой идентификатор и родителя
//`level` int(11) NOT NULL - уровень вложенности
/////////////////////////////////////////////////////////
// Таблица do_category_to_layout (связь с макетом + магазином)
// Пример: (80, 0, 0),
//`category_id` int(11) NOT NULL, - идентификатор записи, берем код из 1С
//`store_id` int(11) NOT NULL,
//`layout_id` int(11) NOT NULL
/////////////////////////////////////////////////////////
// Таблица do_category_to_store (связь с магазином)
// Пример: (80, 0),
//`category_id` int(11) NOT NULL, - идентификатор записи, берем код из 1С
//`store_id` int(11) NOT NULL
Это все. Спасибо за внимание!