четверг, 31 июля 2014 г.

Импорт товаров из 1С-Битрикс в 1С Предприятие УТ 11 - опыт использования стандартного функционала

В прошлый раз, когда я делала клиенту импорт товаров из 1С-Битрикс в 1С Предприятие УТ мы с 1С-программистом делали какую-то жуткую кастомизацию по его инициативе, и от стандартного импорта там остались только рожки да ножки. Сейчас со мной работает другой 1С-программист, и мы стараемся сначала выжать максимум из стандартного функционала, а уж потом, если этого мало — переходим к кастомизации.

Итак передо мной встала задача импортировать 36 000 с хвостом товаров из 1С-Битрикс в 1С УТ 11.1.6. Битрикс обновлен до самой свежей версии, на 1С УТ установлено битриксоидовское дополнение.

Начав настраивать импорт каталога с сайта заказчика в 1С УТ я, как обычно, сделала множество открытий:

1) Уникальность рабочих наименований.
Когда у нас на сайте 36 000 товаров в 300х разделах, их наименования, естественно, не уникальны. В 1С УТ же по-умолчанию включен контроль их уникальности, в следствии чего импорт товаров через помощник импорта товаров с сайта завершается с ошибкой.
Открываем конфигурацию 1С УТ в режиме предприятия, идем в Администрирование — Настройка параметров системы — Номенклатура Снимаем галочку «Контроль уникальности...»



2) Список свойств, которые могут быть импортированы — не описан пока в документации, поэтому пришлось выявить его, почитав код обработки «Помощник импорта товаров». Это:
CML2_ARTICLE — артикул
CML2_BASE_UNIT — базовая единица
Помимо этого в тексте обработки перебираются и записываются в дополнительные сведения о номенклатуре все остальные свойства с префиксом CML2_

Для того, чтобы сайт корректно отдал эти свойства 1С-ке, в настройке свойств инфоблоков свойствам нужно задать соответствующие внешние коды:


3) Меня так же интересовал момент, предусматривает ли обработка апдейт элемента номенклатуры — или только вставку и по какому полю контролируется наличие элемента в справочнике.
Апдейта нет — только вставка.
В качестве уникального идентификатора номенклатуры используется
Номенклатура.Б_Идентификатор = СтрокаТовара.Ид;

В это ИД товара Битрикс вписывает XML_ID товара, если оно задано и обычное ID товара, если XML_ID не задано. У меня  XML_ID были заданы, так я изначально наполняла сайт заказчика через свой модуль импорта из YML файлов, предоставленных поставщиком.
Если у вас на сайте не заданы XML_ID — рекомендую их сгенерировать — к примеру, можно сделать их равными ID. Какой смысл? Это решит в дальнейшем проблему обмена в другую сторону и проблему с обменом заказами.

4) Механизм  — нужно протестировать перед финальной загрузкой, которая даже на очень мощном сервере будет длиться очень долго.

На стороне сайта я тестирую выгрузку так:
- авторизуюсь на сайте
- вбиваю в строку браузера запрос:
http://адрес_сайта/bitrix/admin/1c_exchange.php?type=get_catalog&mode=init
получаю
zip=yes
- вбиваю следующий запрос:
http://адрес_сайта/bitrix/admin/1c_exchange.php?type=get_catalog&mode=query
Сайт отдает кусочек xml-файла. Он не выглядит как xml-файл — это нормально — он не целый и не завершенный. 1С-ка когда прихоит на сайт за каталогом посылает этот запрос до тех пор, пока не заберет весь файл по кусочкам.

Однако, то, как выгружаются товары — мы видим, проверяем, выгружается ли все так, как нужно.

Далее мне необходимо как-то сначала протестировать импорт на стороне 1С Предприятия на маленьком кусочке. Потому что, если все 36 000 товаров загрузятся неправильно, потом можно успеть состариться, пока дождешься их удаления — мы ведь проапдейтить их в следующий проход импорта не сможем. Я хочу протестировать обмен сначала на 100-200х товаров — думаю, этого будет достаточно. Для этого я копирую компонент экспорта каталога catalog.export.1c в свое пространство имен.

В своем кастомном компоненте я нахожу место

elseif($_GET["mode"] == "query")
{

И дописываю:

if ($_SESSION["BX_CML2_EXPORT"]["current"]>100) $_SESSION["BX_CML2_EXPORT"]["step"]=4;

Делаю свой скрипт обмена 1c_exchange2.php, в котором подключаю свой компонент вместо стандартного.

elseif($type=="get_catalog")
{
$APPLICATION->IncludeComponent("bedrosova:catalog.export.1c", "", Array(
"IBLOCK_ID" => COption::GetOptionString("catalog", "1CE_IBLOCK_ID", ""),
"INTERVAL" => COption::GetOptionString("catalog", "1CE_INTERVAL", "-"),
"ELEMENTS_PER_STEP" => COption::GetOptionString("catalog", "1CE_ELEMENTS_PER_STEP", 100),
"GROUP_PERMISSIONS" => explode(",", COption::GetOptionString("catalog", "1CE_GROUP_PERMISSIONS", "1")),
"USE_ZIP" => COption::GetOptionString("catalog", "1CE_USE_ZIP", "Y"),
)
);
}

Мой скрипт  1c_exchange2.php будет отдавать 1С-ке только 100+N товаров. N – количество товаров, выгружаемых за 1 шаг. Вписываю в помощнике импорта адрес своего скрипта и запускаю импорт.

После того, как товары загружены — начинается загрузка картинок. На этом этапе может вывалиться ошибка «Реквизит для группы не найден» - она может возникнуть, если есть группа с таким же уникальным айдишником, как товар (на стороне сайта придется дополнительно перепроверять сквозную уникальность XML_ID групп и товаров).

Радуемся - товары успешно загрузились в 1С Предприятие УТ. У них есть наименование, артикул, привзяка к группе, картинка, а так же дополнительные свойства - в дополнительных сведениях. Когда потом с сайта придут заказы - 1С однозначно идентифицирует товары заказа по XML_ID, а складовщик посмотрит на картинку - и не ошибется при отгрузке.


Помощник импорта товаров в 1с с сайта на Битрикс — в целом пригоден для одноразовой загрузки номенклатуры  без дополнительной кастомизации. Единственное, плохо, что обработка выполняется очень медленно. Я расстрою тех, кто собирался развернуть 1С УТ на своем ноутбуке.

Но для москвичей это не проблема. Ведь в вашем городе живет и работает мой надежный  партнер Вячеслав Мишаков (тел. 8 499 70 77 118) - руководитель компании КМ Системс. Специалисты КМ Системс соберут для вас сервер под 1С Предприятие УТ, идеально подходящий для последующей синхронизации с 1С Битрикс. Ребята сами привезут к вам сервер, настроят его, установят 1С Предприятие, подключат его к локальной сети, разграничат доступ для ваших сотрудников, будут оказывать регулярную техподдержку сервера. Для тех, кто еще не созрел для покупки собственного сервера, КМ Системс предлагает разместить 1С Предприятие в их датацентре. Захожу на их сервера, чтобы интегрировать 1С Предприятие клиентов с 1С-Битрикс и радуюсь - не работа, а удовольствие.

воскресенье, 20 июля 2014 г.

Программное создание higload блока 1С-Битрикс - себе на память

$data = array(
'NAME' => str_replace('_','',trim("H".$tableName)),
'TABLE_NAME' => trim($tableName)
);
$result = \Bitrix\Highloadblock\HighloadBlockTable::add($data);
if ($result->isSuccess())
{
$ID = $result->getId();
$oUserTypeEntity    = new CUserTypeEntity();
$aUserFields    = array(
'ENTITY_ID'         => 'HLBLOCK_'.$ID,
'FIELD_NAME'        => 'UF_MYFIELD',
'USER_TYPE_ID'      => 'string',
'MULTIPLE'          => 'N',
'MANDATORY'         => 'N',
'SHOW_FILTER'       => 'I',
'EDIT_FORM_LABEL'   => array(
'ru'    => 'MYFIELD',
'en'    => 'MYFIELD',
)
);

$iUserFieldId   = $oUserTypeEntity->Add( $aUserFields );
                .....
        }
else{
       $errors = $result->getErrorMessages();
print_r($errors);
}



суббота, 19 июля 2014 г.

Как я обычно настраиваю Git репозиторий для работы с 1С-Битрикс

В простейшем варианте организации командной работы над проектом нам необходим — один основной сайт на сервере — будем называть его продакшеном, один сайт для тестирования (девелоперский сайт) — на сервере рядом с ним (дев. сайт) + по локальной копии сайта у каждого разработчика. (Девелоперских сайтов на сервере может быть несколько, но по поводу дополнительных дев. сайтов необходимо предварительно консультироваться с технической поддержкой Битрикс по поводу правомерности такой системы с точки зрения лицензионного договора). 

Каждый разработчик будет работать над проектом в своей локальной копии сайта, в отдельной ветке Git для каждой решаемой задачи. В конце дня каждый разработчик делает коммит в репозиторий, хранящийся на github.com и только один разработчик — ведущий, проверяет и объединяет ветки сначала на девелоперском сайте на сервере, а потом выкатывает полученный результат на продакшен. (По моему субъективному мнению, двоевластие на интернет-проекте — недопустимо. Даже если слияние веток и тестирование будет отнимать все рабочее время ведущего разработчика проекта — никто кроме него не должен вносить изменения на продакшен — сайт) В начале дня — разработчики обновляют свои репозитории с гитхаба. 

В случае, когда необходимо изменение структуры БД, разработчики сначала вносят изменения в своих локальных копиях, пишут список изменений ведущему разработчику, ведущий разработчик вносит их на продакшене, делает бекап БД продакшена, который затем накатывается на дев-сайт и на все локальные дев-сайты. Дополнительно настраивается репликация БД (но это уже тема для другой статьи). 

Итак, мы имеем выделенный сервер, два настроенных виртуальных хоста и установленное серверное ПО git — это, как правило, могут сделать для нас сотрудники хостинга, на котором мы покупаем сервер. 

В директории основного сайта разворачиваем бекап сайта стандартными средствами Битрикс (или руками), директория девелоперского сайта пока пусть будет пустой. 

Регистириуем корпоративный тариф на github.com Для системы, рассмотренной в данной статье, нам хватит тарифа за 25$ в месяц. Заводим приватный репозиторий, добавляем существующих пользователей githab к организации (или заводим новых пользователей) — это все делается очень прозрачно и удобно, поэтому не будем подробно заостряться на этом моменте. 

После того, как корпоративный приватный репозиторий заведен, нам нужно сделать первый коммит в него. 

Заходим по ssh на наш сервер под пользователем с правами на запись в папки сайтов, переходим в директорию, в которой мы намерены инициировать репозиторий. 

Репозиторий можно инициировать непосредственно в папке сайта (тогда важно не забыть потом защитить папку .git в файле .htaccess от скачивания), а можно инициировать его в наддиректории (вариант, когда файлы каждого сайта лежат не сразу в директории www или директории dev, а в подкатологе, к примеру, dev/httpfiles, www/httpfiles А репозитории мы инициируем соответственно в директориях www и dev. Если сервер настраивается с нуля или если админу не лень перепрописать виртуальные хосты— этот вариант предпочтительнее). 

Перед тем, как инициировать репозиторий для основного сайта, необходимо определиться, какие папки и файлы необходимо включать в репозиторий, а какие — нет — сформировать файл .gitignore. 

Пример (упрощенный) файла .gitignore для 1С-Битрикс для случая, когда репозиторий инициируется в директории сайта: 

/bitrix/backup 
/bitrix/cache 
/bitrix/crontab 
/bitrix/managed_cache 
/bitrix/managed_flags 
/bitrix/modules/*.log 
/bitrix/php_interface/crontab 
/bitrix/php_interface/dbconn.php 
/bitrix/stack_cache 
/logs 
/upload 
/.gitignore 
/.htaccess 
/urlrewrite.php 
/*.log 
/*.sql 
/*.txt 
/*.xml 
/*.dt 
То есть мы не будем контролировать изменения в папке бекапов (/bitrix/backup), папках кеша (/bitrix/cache, /bitrix/managed_cache, /bitrix/managed_flags, /bitrix/stack_cache), папках с заданиями крону (/bitrix/crontab), папке с логами (/logs), папке с загрузками (/upload). 
Папку с загрузками /upload на всех девелоперских копиях сайта, расположенных на одном с ним сервере, имеет смысл делать симлинком на папку /upload продакшен-сайта. 

Символическую ссылку в директории /dev/ — корневой директории девелоперского сайта создаем командой 

ln -s /path/to/www/upload 

К слову о символических ссылках. При конфигурировании многосайтовой системы на одном ядре 1С-Битрикс по 2му типу, папку bitrix мы так же должны будем исключить из основного репозитория. Потому что физически эта директория будет существовать только на одном сайте системы, а на других — будут символические ссылки на нее. Но так как контролировать изменения файлов этой папки все равно нужно, целесообразно будет завести для нее отдельный репозиторий. 

Файл /bitrix/php_interface/dbconn.php содержит индивидуальные для каждого виртуального хоста настройки (в частности настройки соединения с БД), поэтому этот файл мы добавляем в исключения git. 

Файл /urlrewrite.php — это файл с правилами обработки ЧПУ адресов Битрикс — он может быть перезаписан (сортируется внутри) самим движком, поэтому исключим и его. 

Файлы .gitignore и .htaccess тоже должны лежать в исключениях. В исключения так же стоит добавить файлы логов, текстовые файлы, xml-файлы – все то, что не относится непосредственно к программному коду. 

После того, как мы определились с содержимым .gitignore и создали файл .gitignore в директории будущего репозитория можно инициировать репозиторий для продакшен-сайта. Для этого нам сначала необходимо позаботиться об авторизации нашего серверного ssh-пользователя на githab — сгенерировать для него ключ. 

Выполняем команду 

ssh-keygen -t rsa -C "bitrix@имя.сайта" 

email можно вписать любой, но с email вида bitrix@имя.сайта легче идентифицировать ключ на гитхабе. 

Переходим на github.com/settings/ssh и добавляем новый ключ — данные самого ключа берём из файла /path/to/.ssh/id_rsa.pub 

Проверяем подключение: 

ssh-T git@github.com 

Инициируем репозиторий в директории основного сайта (или в наддиректории): 
переходим в /path/to/www/ и выполняем команды: 

git add README.md 
git commit -m "first commit" 
git remote add origin git@github.com:организация/имя_репо.git 
git push -u origin master 

После того, как репозиторий для основного сайта инициирован, зальем все файлы проекта в репозиторий гитхаб: 

git add . git commit -m “second commit” git push 

Теперь клонируем репозиторий в директорию девелоперского сайта: 

git clone git@github.com:организация/имя_репо.git dev 

После этого в директории девелоперского сайта создадим символическую ссылку на папку upload как было описано выше. Затем стандартными средства Битрикс сделаем бекап БД основного сайта, и развернем его на девелоперском — файл, хранящий подключение к БД при этом создастся для дев сайта автоматически. 

Теперь аналогичным образом можно создать клоны репозитория на локальных машинах разработчиков. Для этого им необходимо поставить любой комплект ПО git на свою локальную машину (командная Git строка идет в любом комплекте), запустить командную строку, сгенерировать ключ, добавить его на гитхаб, клонировать репозиторий в директорию виртуального хоста на локальной машине, а папку upload просто слить с основного сайта, накатить дамп БД с основного сайта. 

После этого можно расслабиться и наслаждаться командной работой, выстраивая с коллегами дружеские отношения без ссор и споров, оперативно откатывая некорректные изменения — без поиска виноватых. 

P/S: с некоторых пор я предпочитаю гитхабу битбукет

P/S2: 
Недавно уже холиварила на тему того, нужно ли включать папку bitrix в репозиторий после того, как появилась возможность разрабатывать темплейты, компоненты и модули в папке local. 

Меня попрекали тем, что я транслирую "устаревшие" понятия, и я даже чуть было не согласилась. Да что там - даже согласилась. Однако на проектах, которые веду - продолжала включать папку bitrix в репозиторий на всякий пожарный - оказалось - не зря. 

Вот мои новые аргументы для любителей похоливарить (кого я имею в виду - тот знает): 


1) Папка local-то появилась, а вот многие решения из Маркетплейс.Битрикс продолжают устанавливаться в папку /bitrix/modules/ Вы не считаете нужным контролировать их код? 

2) В папке local пока нельзя разрабатывать кастомные гаджеты для рабочего стола админки Битрикс.

Добавление элемента в highload блок 1С-Битрикс с проверкой существования и апдейтом - пример себе на память

function AddToHI($tableName='bedrosova_filter_sef',$parent_xml_id,$child_xml_id, $code){

$fp = fopen($_SERVER['DOCUMENT_ROOT'].'/upload/bedrosova.log', 'a');
fwrite($fp, print_r('----------------- '.date("F j, Y, g:i a").". Поступившие данные:".PHP_EOL, true));
fwrite($fp, print_r(array('parent_xml_id'=>$parent_xml_id, 'child_xml_id'=>$child_xml_id, 'code'=>$code,), true));


if (strlen($code)<1)
{
fwrite($fp, print_r("Не передан символьный код!".PHP_EOL, true));
fclose($fp);
return false;
}

if (!preg_match("#^[aA-zZ0-9_]+$#",$code))
{
fwrite($fp, print_r("Символьный код содержит недопустимые символы!".PHP_EOL, true));
fclose($fp);
return false;
}

$HLData = \Bitrix\Highloadblock\HighloadBlockTable::getList(array('filter'=>array('TABLE_NAME'=>$tableName)));
      if ($HLBlock = $HLData->fetch())
      {
       //found highloadiblock
         
       $HLBlock_entity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity($HLBlock);
 
 
  if ($child_xml_id!="root"){
       

  //Зададим параметры запроса, любой параметр можно опустить
 

$main_query = new \Bitrix\Main\Entity\Query($HLBlock_entity);
$main_query->setSelect(array('*'));
$main_query->setFilter(array('UF_CATEGORY_XML_ID'=> $parent_xml_id,"UF_SEF"=>$code));

//Выполним запрос
$res_query = $main_query->exec();

//Получаем результат по привычной схеме
$res_query = new CDBResult($res_query);  
if (!$row = $res_query->Fetch())
{
//create record in Highload IBlock
$HLBlock_entity_data_class = $HLBlock_entity->getDataClass();
$arBxData = array
(
 'UF_CATEGORY_XML_ID' => $parent_xml_id,
 'UF_VALUE_XML_ID' => $child_xml_id,
 'UF_SEF'=> $code,
);
$res_query = $HLBlock_entity_data_class::add($arBxData);
}
else{
//апдейтим

$HLBlock_entity_data_class = $HLBlock_entity->getDataClass();
$arBxData = array
(
 'UF_CATEGORY_XML_ID' => $parent_xml_id,
 'UF_VALUE_XML_ID' => $child_xml_id,
 'UF_SEF'=> $code,
);
$res_query = $HLBlock_entity_data_class::update($row['ID'],$arBxData);

}

}
else{

$main_query2 = new \Bitrix\Main\Entity\Query($HLBlock_entity);
$main_query2->setSelect(array('*'));
$main_query2->setFilter(array("UF_SEF"=>$code));

//Выполним запрос
$res_query2 = $main_query2->exec();

//Получаем результат по привычной схеме
$res_query2 = new CDBResult($res_query2);  
if (!$row2 = $res_query2->Fetch())
{

$main_query = new \Bitrix\Main\Entity\Query($HLBlock_entity);
$main_query->setSelect(array('*'));
$main_query->setFilter(array('UF_CATEGORY_XML_ID'=> $parent_xml_id));

//Выполним запрос
$res_query = $main_query->exec();

//Получаем результат по привычной схеме
$res_query = new CDBResult($res_query);  
if (!$row = $res_query->Fetch())
{
//create record in Highload IBlock
$HLBlock_entity_data_class = $HLBlock_entity->getDataClass();
$arBxData = array
(
 'UF_CATEGORY_XML_ID' => $parent_xml_id,
 'UF_VALUE_XML_ID' => $child_xml_id,
 'UF_SEF'=> $code,
);
$res_query = $HLBlock_entity_data_class::add($arBxData);
}
else{
//апдейтим

$HLBlock_entity_data_class = $HLBlock_entity->getDataClass();
$arBxData = array
(
 'UF_CATEGORY_XML_ID' => $parent_xml_id,
 'UF_VALUE_XML_ID' => $child_xml_id,
 'UF_SEF'=> $code,
);
$res_query = $HLBlock_entity_data_class::update($row['ID'],$arBxData);

}



}


}
     
      }
fclose($fp);
}