вторник, 8 декабря 2015 г.

Делюсь контактами своих копирайтеров

Некоторое время назад от моей Студии отпочковалась команда профессиональных seo-копирайтеров под руководством Ерыгиных Александра и Ирины http://pro-txt.ru/
Если вы ищите, где заказать качественные тексты для сайта по разумной цене - обращайтесь в их агентство "Фабрика Контента" и не пожалеете. Могу сказать, что все, что я когда-либо публиковала на своем сайте и в своем блоге прошло через строгий взгляд корректора, филолога и языковеда Ерыгиной Ирины. А маркетолог Александр Ерыгин сгенерировал процентов 80% всех моих лидов.
Тексты для любых нужд по разумной цене.
PRO-TXT.RU

среда, 2 декабря 2015 г.

Расширенная детализация звонков в Битрикс24

Недавно мы внедряли Битрикс24 в компании, которая оказывает своим клиентам техническую поддержку по телефону, используя телефонию Битрикс24. Для этой компании мы решили интересную задачу - формирование расширенной детализации телефонных звонков, произведенных через портал.

Стандартная станица детализации телефонных звонков в Битрикс24 довольно бедна и не предполагает возможности сорировки и поиска по различным полям данных.


Этот недостаток мы исправили в своем кастомном решении для статистики и детализации телефонных звонков в Битрикс 24.

Были реализованы следующие возможности:

1) Фильтр (поиск) таблицы телефонных звонков по различным параметрам: поиск по номеру телефона, фильтр по дате звонка, позволяющий выбрать любой период времени, фильтр по типу и статусу звонка, по наличии записи телефонного разговора, по сотруднику, принявшему вызов, а так же по компании/контакту, от которых поступил данный телефонный звонок:


2) Сортировка как отфильтрованной, так и неотфильтрованной выборки звонков по любой из колонок детализации:


3) Возможность создать контакт в CRM Битрикс24 с данным телефонным номером прямо из контекстного меню в детализации звонков:


4) Возможность сгруппировать детализацию звонков по компании (контакту) и отсортировать детализацию в режиме группировки. Это позволяет, к примеру, наглядно видеть, от какого клиента поступает больше всего звонков, что очень важно, если клиентам оказывается телефонная техническая поддержка:



Сгруппированную выборку так же возможно фильтровать по любому из вышеперечисленных полей.

понедельник, 30 ноября 2015 г.

Статусы продуктов (товаров/услуг) в составе сделки Битрикс24

В очередной раз реализовали наш старый кейс>> со статусами товаров для новых клиентов.
Был немного улучшен интерфейс установки статуса товара.



Как и прежде, на событие перехода товара/услуги в конкретный статус в составе сделки в Битрикс24 можно повесить запуск отдельного бизнес-процесса.

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

суббота, 14 ноября 2015 г.

30й год моей жизни

Итоги года я подвожу обычно в ноябре, когда близится день моего рождения. Через неделю мне стукнет, страшно подумать, 30 лет.  Этот рубеж сам по себе заставляет оглядываться назад и сравнивать мечты 10-летней давности с реальностью...

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

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

Проанализировав ситуацию, я поняла, что должна улучшить свой английский язык и начать искать заказчиков на западе. На остатки сбережений я наняла себе репетитора, и, видимо, мотивация сыграла большую роль - за месяц я смогла вывести свой английский на уровень, которого я ранее не могла достичь за 15 лет изучения английского в школе и в ВУЗе.

В апреле я начала искать заказы на upwork. С моим опытом найти там заказы на мой рейт в 40$ в час оказалось совсем не сложно, и заказчиков даже не смущал мой адский акцент, но это преодоление языкового барьера давалось мне с таким огромным трудом, что после каждого скайп-колла у меня просто гудела голова от новых слов, о значении которых я догадывалась по контексту. Однако же к концу мая объем заказов от клиентов, с которыми я там познакомилась и от новых партнеров, которых я там завела, стал более, чем достаточным для того, чтобы собрать мою команду вновь.

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

С моими помощниками дела обстояли более грустно. Я знала, что мой помощник №1 Змеевский нашел отличную работу еще в декабре (он слишком крут для нашего города и все хотят его заполучить даже в кризис). Я даже постеснялась ему звонить. Но когда я разместила объявление о вакансии в интернете, он позвонил мне сам, и выразил готовность рискнуть и снова начать все с нуля вместе со мной пусть даже в этот раз без оборотного капитала. Это было приятно. Когда в тебя верит твоя команда - это внушает уверенность, хотя и накладывает определенную ответственность - страшно не оправдать ожиданий. Остальные подтянулись вслед за ним, и так или иначе, парт-тайм, попроектно, мы снова собрались практически в прежнем составе.

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

К счастью, в июне вновь оживился российский рынок (по моим ощущениям) и с июня по ноябрь мы успели набрать в 2 раза больше партнерских баллов, чем мы раньше обычно набирали за год. (Я не гонюсь за баллами, как таковыми, но считаю их важным показателем своей личной эффективности, как руководителя) 

Я пересмотрела список услуг, которые мы оказываем. Если раньше мы ориентировались на долгую дорогостоящую индивидуальную разработку, то теперь мы переориентировались на не менее дорогое по стоимости часа, но более дешевое по среднему чеку для конечного клиента внедрение готовых решений с кастомизацией под нужды клиента. Я перестала брать заказы, которые требуют участия дизайнеров - поняла, что мы увы не сильны в том, в чем не разбираюсь я лично. Мы начали активно внедрять Битрикс24 в коробке и мы сделали несколько  достаточно сложных внедрений Битрикс24, которыми я по-настоящему могу гордиться. 

На данный момент внедрение Корпоративного портала Битрикс24 в коробке - это наша основная деятельность и специализация.

Планы на следующий год -  масштабные. У меня новые партнеры и новые рискованные проекты,  которыми они меня манят.  Но кризис научил меня экономить и не кидаться в новые начинания без детального бизнес-плана. Поэтому пишу бизнес-планы, а во что они выльются в следующем году - покажет время.

понедельник, 12 октября 2015 г.

Бизнес-процессы в Битрикс 24: сложности внедрения

Казалось бы, дизайнер бизнес-процессов в Битрикс 24 - мощнейший механизм, позволяющий автоматизировать все, что угодно. Никакого программирования - вытаскивай кубики-активити - и любой воркфлоу спроектирован за 2 часа.

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

Возьмем, к примеру, банальный бизнес-процесс оформления больничного листа, который не раз автоматизировал, наверное, каждый внедренец Битрикс 24. И в каждой новой компании -  по-разному.

Дано: трехэтапное утверждение больничного листа:

Сначала Больничный лист утверждает начальник отдела  Business Manager, потом ответственный из отдела кадров HR, а уже потом ответственный за начисление заработной платы Payroll.

Какие сложности могут возникнуть при проектировании такого простого бизнес-процесса?

Сложность №1: тестирование в условиях когда пользователи уже активно используют портал. Понятное дело: разработку БП приходится вести в девелоперской копии, перелогиниваясь для теста под различными участниками процесса. Перелогиниваться бывает достаточно муторно, а кроме того, должны быть протестированы и email-уведомления, к-е до поры - до времени не должны приходить реальным пользователям.

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


Естественно, для этого переменная, хранящая пользователей-тестеров, должна быть добавлена во все необходимые активити.


Сложность №2: когда пользователь должен что-то проаппрувить, у него появляется соответсвующее задание в бизнес-процессах (не путать с задачами). Клиенты хотят, чтобы пользователь получал емейл в тот момент, когда от него ожидается одобрение, емейл, содержащий ссылку не на весь раздел с заданиями БП, а на конкретную страничку задания.

Для решения этой проблемы шаблон БП для входа в состояние "Approve from HR" из нашего примера превращается вот в такую громоздкую конструкцию (картинка кликабельна):


Прикол в том, что пока в активити аппрува не произошел этот самый аппрув, стандартными средствами БП мы не можем узнать ИД таска, поэтому его приходится доставать, используя php-код:

CModule::IncludeModule("bizproc");
$arSelectFields = array("ID", "WORKFLOW_ID", "ACTIVITY", "ACTIVITY_NAME", "MODIFIED", "OVERDUE_DATE", "NAME", "DESCRIPTION", "PARAMETERS", "STATUS","USER_STATUS");
$dbRecordsList = CBPTaskService::GetList(
array("ID" => "DESC"),
array('WORKFLOW_ID'=>'{=Workflow:ID}'),
false,
false,
$arSelectFields
);
$arRecord = $dbRecordsList->getNext();
$rootActivity = $this->GetRootActivity();
$rootActivity->SetVariable("TaskLink", 'http://адреспортала/company/personal/bizproc/'.$arRecord['ID'].'/');

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

Сложность №3: электронного прогона по всем инстанциям недостаточно компании, где исторически использовался бумажный документооборот, и на этапе, когда больничный лист из примера попадает к Payroll  (к бухгалтеру), бухгалтер должен помимо всего прочего иметь возможность распечатать себе бумажку "для отчетности". Поэтому наш бизнес-процесс должен еще формировать PDF-файл и сохранять ее в соответствующем разделе библиотеки документов.

Сформировать PDF файл для данного примера можно следующей php-вставкой (спасибо, что в Битрикс 24 есть класс CSalePdf для работы с PDF):

CModule::IncludeModule("sale");
use Bitrix\Main\Type\DateTime;
$date = new DateTime('{=Document:DATE_CREATE}');
$date=$date->format("d-m-Y");
if (!CSalePdf::isPdfAvailable()) die();
$ID={=Document:ID};
$IBLOCK_ID={=Document:IBLOCK_ID};
$PROPS=array();
$db_props = CIBlockElement::GetProperty($IBLOCK_ID, $ID);
while($ar_props = $db_props->Fetch())
{
$PROPS[$ar_props['ID']]=$ar_props;
}
$pdf = new CSalePdf('P', 'pt', 'A4');
$pageWidth  = $pdf->GetPageWidth();
$pageHeight = $pdf->GetPageHeight();
$pdf->AddFont('Font', '', 'pt_sans-regular.ttf', true);
$pdf->AddFont('Font', 'B', 'pt_sans-bold.ttf', true);
$fontFamily = 'Font';
$fontSize   = 10.5;
$margin = array(
'top' => 15 * 72/25.4,
'right' => 15 * 72/25.4,
'bottom' => 15 * 72/25.4,
'left' => 15 * 72/25.4
);
$width = $pageWidth - $margin['left'] - $margin['right'];
$pdf->SetDisplayMode(100, 'continuous');
$pdf->SetMargins($margin['left'], $margin['top'], $margin['right']);
$pdf->SetAutoPageBreak(true, $margin['bottom']);
$pdf->AddPage();
$pdf->SetFont($fontFamily, 'B', $fontSize*2);
$pdf->Cell(0, 30, $pdf->prepareToPdf('{=Document:NAME}`s personal leave '.$date), 0, 0, 'C');
$pdf->Ln();
$pdf->Ln();
$pdf->Ln();
$pdf->Ln();
$pdf->SetFont($fontFamily, '', $fontSize);
$pdf->Cell(0, 15, $pdf->prepareToPdf('Request content:'), 0, 0, 'L');
$pdf->Ln();
$pdf->Ln();
$ROW1=150;
$Y=15;
$pdf->Cell($ROW1, $Y, $pdf->prepareToPdf('Employee Name:'), 0, 0, 'L');
$pdf->MultiCell(0, $Y, $pdf->prepareToPdf('{=Document:NAME}'), 0, 'L');
$pdf->Cell($ROW1, $Y, $pdf->prepareToPdf('First day of leave:'), 0, 0, 'L');
$pdf->MultiCell(0, $Y, $pdf->prepareToPdf('{=Document:PROPERTY_225}'), 0, 'L');
$pdf->Cell($ROW1, $Y, $pdf->prepareToPdf('Last day of leave:'), 0, 0, 'L');
$pdf->MultiCell(0, $Y, $pdf->prepareToPdf('{=Document:PROPERTY_226}'), 0, 'L');
$pdf->Cell($ROW1, $Y, $pdf->prepareToPdf('Type of leave:'), 0, 0, 'L');
$pdf->MultiCell(0, $Y, $pdf->prepareToPdf('{=Document:PROPERTY_227}'), 0, 'L');

$myfile='temp.pdf';
$pdf->Output($myfile, 'F');
if (!copy($myfile,$_SERVER['DOCUMENT_ROOT'].'/docs/appforms/forms/personal_leave_requests/request_'.$ID.'.pdf'))
{  }
else{  unlink($myfile);  }

Можно ли реализовать  бизнес-процесс обработки больничного листа в облачной версии Битрикс24? Можно, но ущербно - отказавшись от перечисленных в пунктах 2 и 3 примочек, возможных, только в коробочной версии.

Битрикс 24 - опыт расширения функционала модуля "Техподдержка"

В сентябре у нас было несколько интересных внедрений коробочной версии Битрикс24. Об одном из них расскажу в данном посте. Клиент хотел расширить функционал модуля технической поддержки.

В стандартном модуле Битрикс24 нормативное время ответа на обращение в техподдержку зависит только от уровня поддержки (SLA). Критичность обращения и категория обращения не влияют на формирование крайнего срока реакции на обращение.

Нужно было сделать, чтобы подсчет времени для ответа на обращение вычислялся исходя из:

уровня поддержки
критичности обращения
категории обращения

Кроме того, в стандартном функционале «1С-Битрикс24: Корпоративный портал» сущности «Обращения в техподдержку» и «Задачи» никак не связаны. Необходимо было в интерфейсе обработки обращения техподдержки (в административной части) добавить кнопку «Создать задачу», при нажатии на которую должна была автоматически создаваться задача на проведение работ на основании данного обращения с возможностью включения данной задачи в отчеты и возможностью учета времени по данной задаче. 

Так как весь оставшийся функционал модуля технической поддержки должен был работать так же, как и раньше, а так же должна была оставаться возможность обновлять модуль и получать с обновлениями новый функционал модуля, кастомизировать поведение модуля было решено через перехват событий:

OnAfterTicketAdd - это событие возникает при добавлении нового обращения в ТП;
OnAfterTicketUpdate - это событие возникает при изменении обращения (в том числе при добавлении сообщения в обращение);


Апи модуля технической поддержки Битрикс 24 не отличается подробной документацией, поэтому исследовать то, как работает стандартный модуль ТП, вычисляя дедлайн для ответа на обращение было достаточно сложно. Анализируя то, как хранится обращение в БД модуля, я заметила, что дедлайн по обращению, к счастью, нигде не вычисляется динамически, а хранится в одной таблице с информацией о самом обращении и пересчитывается при апдейте обращения. Именно этот факт и позволили использовать обработчики событий OnAfterTicketAdd  и OnAfterTicketUpdate  для решения первой части задачи.

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


Благодаря тому, что в админке Битрикс24 реализован механизм фильтрации хайлоадблока по значениям полей, пользоваться данной настроечной таблицей - достаточно удобно.

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

Соответсвенно, задача свелась к тому, чтобы в вышеуказанных обработчиках получать из хайлоадблока нормативное время на основании данных о критичности/уровне/категории по данным обращения, затем вычислять новый дедлайн ответа на обращение с учетом расписания работы технической поддержки, а затем записывать найденный дедлайн в обращение.

Не обошлось без небольших курьезов: оказалось, что в обработчик события OnAfterTicketAdd не передаются данные SLA_ID для обращения, при том, что на самом деле в этот момент обращение уже записано и SLA_ID - известен - пришлось доставать его дополнительно.

Со второй частью задачи не возникло особых проблем - мы кастомизировали в папке local админские страницы редактирования обращения в техподдержку и списка обращений в техподдержку и добавили функционал создания задачи на основании обращения:

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

Задачи содержат ссылку на обращение непосредственно в своем описании:

Реализация данного кейса заняла около 20 человеко-часов, и это было бы невозможно сделать с такими же трудозатратами в облачной версии Битрикс 24. Покупая коробочную версию Битрикс 24 - клиенты могут серьезно сэкономить на дальнейшем расширении функционала.

среда, 23 сентября 2015 г.

Если нужно найти что-то в хайлоад блоке Битрикс по XML_ID списочного поля - себе на память


<?if (CModule::IncludeModule("highloadblock")){

CModule::IncludeModule("main");
//Уровни поддержки
$arSLAIDbyXMLID=array();
$rsSLA = CUserFieldEnum::GetList(array(), array(
            "USER_FIELD_ID" => 85,
        ));
while($arSLA = $rsSLA->GetNext()){
 $arSLAIDbyXMLID[$arSLA['XML_ID']]=$arSLA['ID'];
}


//Критичность
$arCritIDbyXMLID=array();
$rsCrit = CUserFieldEnum::GetList(array(), array(
            "USER_FIELD_ID" => 87,
        ));
while($arCrit = $rsCrit->GetNext()){
 $arCritIDbyXMLID[$arCrit['XML_ID']]=$arCrit['ID'];
}

//Категория
$arCatIDbyXMLID=array();
$rsCat = CUserFieldEnum::GetList(array(), array(
            "USER_FIELD_ID" => 89,
        ));
while($arCat = $rsCat->GetNext()){
 $arCatIDbyXMLID[$arCat['XML_ID']]=$arCat['ID'];
}

$HLData = \Bitrix\Highloadblock\HighloadBlockTable::getList(array('filter'=>array('TABLE_NAME'=>"ts_time")));

if ($HLBlock = $HLData->fetch())
    {
     $HLBlock_entity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity($HLBlock);
     $main_query = new \Bitrix\Main\Entity\Query($HLBlock_entity);
     $main_query->setSelect(array('*'));
    
     $main_query->setFilter(array('UF_TS_LEVEL'=>$arSLAIDbyXMLID[1],'UF_CRITICALITY'=>$arCritIDbyXMLID[5],'UF_SECTION'=>$arCatIDbyXMLID[37]));

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

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

       print_r($row );
      }
     else{
      print_r(array('UF_TS_LEVEL'=>$arFields[ 'SLA_ID'],"UF_SECTION"=>$arFields[ 'CATEGORY_ID'],"UF_CRITICALITY"=>$arFields['CRITICALITY_ID']));

     }

    }
  }?>


воскресенье, 19 июля 2015 г.

Битрикс24 - опыт расширения функционала за счет бизнес-процессов (коробочная версия)

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

В этом июне мне посчастливилось реализовать на базе Битрикс24 один очень интересный кейс, о котором я хочу рассказать в этом посте.

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

Стандартными средствами Битрикс24 продукту было добавлено множественное свойство "Возможные статусы":


После этого я кастомизировала компонент формы редактирования продукта в каталоге, для того, чтобы каждому продукту можно было задавать свой набор статусов и ставить каждому статусу в соответсвие бизнес-процесс из раздела Сервисы - Бизнес-процессы:



Для хранения привязки бизнес-процесса к статусу товара использовано служебное поле Описания значения свойства инфоблока:


Далее был кастомизирован шаблон компонента отображения продуктов сделки - был добавлен вывод статуса для каждого продукта сделки в виде статус-бара.


С этой частью пришлось повозиться больше всего. 

Для хранения привязки стейджа к продукту и владельцу (а владельцем может быть не только сделка) был заведен отдельный хайлоадблок:


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

//первым делом получаем список доступных стейджей для продукта (по ID продукта) и текущий стейдж в хайлоадблоке состояний продуктов, если стейдж уже и так последний - ничего не делаем//если стейдж не последний - находим тот, в который нам нужно перевести продукт//сохранение стейджа продукта - это апдейт или добавление элемента в хайлоадблок состояний
    //интересное начнается при запуске соответсвуюещего БПCModule::IncludeModule('bizproc');//В $UF_STAGE у нас порядковый номер текущего стейджа$BP_ID = $arStagesProperty['DESCRIPTION'][$UF_STAGE]; $STAGE_NAME = $arStagesProperty['VALUE'][$UF_STAGE];$STAGE_ID=$arStagesProperty['PROPERTY_VALUE_ID'][$UF_STAGE]; //В $arIB заранее собрана привязка информационных блоков БП и темплейтов БП (эти ID разные, и разработчики часто их путают)$IB_ID=$arIB[$BP_ID];
//Далее мы собираем виртуальный документ, над которым будет запущен потом экземпляр БП//Через свойства этого виртуального документа можно передать все, что нам понадобится на этапе выполнения БП//эти свойства доступны в свойствах документа при настройке различных активити в дизайнере БП
$documentId = CBPVirtualDocument::CreateDocument(0,array( "IBLOCK_ID" => $IB_ID, "NAME" => "Create Notification", "CREATED_BY" => "user_1", //Запускаем от имени админа "PROPERTY_STAGE_ID"=>$STAGE_ID, "PROPERTY_STAGE_NAME"=>$STAGE_NAME, "PROPERTY_DEAL_ID"=>$UF_OWNER, "PROPERTY_PRODUCT_ID"=>$UF_PRODUCT, "PROPERTY_PRODUCT_NAME"=>$UF_PRODUCT_NAME, "PROPERTY_HEAD_TASK_ID"=>$HEAD_TASK_ID,   )     );
 $arErrorsTmp = array();//Запаскаем нужный БП CBPDocument::StartWorkflow($BP_ID,   array("bizproc","CBPVirtualDocument",$documentId),  array(), $arErrorsTmp);


Естественно, для запуска данных БП описанным способом предварительно в админке необходимо добавлять соответсвующие свойства соответсвующему данному бизнес-процессу инфоблоку. 



Далее возникла сложность с режимом редактирования списка товаров сделки. Нужно было, чтобы сразу после добавления товара в сделку отображался кликабельный стейдж-бар именно для этого товара. Для этого я написала еще один скрипт для вызова аяксом, который отрисовывает стейдж-бар для определенного продукта.


Некоторые статусы продуктов предполагают, что оператор переводит продукт в данный статус вручную, а в некоторые продукт переходит автоматически по завершении бизнес-процесса другого статуса. То есть по завершении предыдущего БП в таких случаях должен не только запуститься следующий, но и соответсвующий продукт в соответсвующей сделке должен сменить статус. Это было реализовано php-вставкой на уровне соответсвующего БП.


Тут закономерно возникает вопрос (который я задавала и заказчику на этапе внедрения): а почему бы не реализовать это одним большим и сложным БП, к-й переводит продукт от одного статуса к другому. С таким сложным БП ИТ-специалисту заказчика, к-й будет поддерживать проект было бы тяжело работать. А с выбранной реализацией для того, чтобы изменить поведение системы при переводе конкретного продукта в конкретный статус, ИТ-специалисту заказчика нужно сделать простые и понятные действия на уровне дизайнера бизнес-процессов в очень простых и маленьких БП.

php-вставка для перевода продукта из одного стейджа в другой и запуска соответсвующего БП выглядела примерно так же, как и скрипт перевода продукта из одного стейджа в другой, за исключением того, что переменные приходили не из POST-запроса, а из переменных БП и из свойств виртуального документа:

$product_id='{=Document:PROPERTY_PRODUCT_ID}';
$owner_id='{=Document:PROPERTY_DEAL_ID}';
$head_task_id='{=Variable:HEADTASKID_printable}';

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

среда, 10 июня 2015 г.

Простой и интересный на мой взгляд кейс бизнес-процесса (Битрикс24 коробка)

На одном из проектов понадобилось настроить такой бизнес-процесс: в ходе БП создается рабочая группа, далее создаются задачи и прикрепляются к данной рабочей группе. У некоторых задач есть подзадачи.

Создание рабочей группы и подзаачи - сделаны как вставки php кода:


Для рабочей группы:

CModule::IncludeModule('socialnetwork');
CModule::IncludeModule('disk');
CModule::IncludeModule('webdav');

$SocGroup=new CSocNetGroup;
global $USER;

$arFieldsSG=array(
   "NAME"=>"{=Document:TITLE}",
   "SITE_ID" => "s1",
   "DESCRIPTION"=>"{=Document:TITLE} Workgroup",
   "ACTIVE"=>"Y",
   "VISIBLE"=>"Y",
   "OPENED"=>"Y",
   "CLOSED"=>"N",
   "SUBJECT_ID"=>4,
   "OWNER_ID"=>1,
   "INITIATE_PERMS"=>A,
   "SPAM_PERMS"=>"N",
   "SUBJECT_NAME"=>"Management Board"

);

$GroupID=CSocNetGroup::CreateGroup($USER->GetID(),$arFieldsSG);

if (CModule::IncludeModule("disk"))
{
 \Bitrix\Disk\Driver::getInstance()->addGroupStorage($GroupID);
}

$this->SetVariable('WORKGROUPID',$GroupID);

Для подзадачи:

if (CModule::IncludeModule("tasks"))
{
global $USER;

    $arFields = Array(
        "TITLE" => "Task title",
        "DESCRIPTION" => "Task description",
        "RESPONSIBLE_ID" => $USER->GetID(),
        "GROUP_ID" => $this->GetVariable('WORKGROUPID'),
        "PARENT_ID"=>'{=A2629_10558_53461_99205:TaskId}',
    );

    $obTask = new CTasks;
    $ID = $obTask->Add($arFields);
    $success = ($ID>0);
}

Создание подзадачи в БП в активити - вставка php кода

if (CModule::IncludeModule("tasks"))
{
global $USER;

    $arFields = Array(
        "TITLE" => "Task title",
        "DESCRIPTION" => "Task description",
        "RESPONSIBLE_ID" => $USER->GetID(),
        "GROUP_ID" => $this->GetVariable('WORKGROUPID'),
        "PARENT_ID"=>'{=A2629_10558_53461_99205:TaskId}',
    );

    $obTask = new CTasks;
    $ID = $obTask->Add($arFields);
    $success = ($ID>0);
}

Создание рабочей группы в БП в активити - вставка php кода с последующим созданием диска

CModule::IncludeModule('socialnetwork');
CModule::IncludeModule('disk');
CModule::IncludeModule('webdav');


$SocGroup=new CSocNetGroup;
global $USER;

$arFieldsSG=array(
   "NAME"=>"{=Document:TITLE}",
   "SITE_ID" => "s1",
   "DESCRIPTION"=>"{=Document:TITLE} Workgroup",
   "ACTIVE"=>"Y",
   "VISIBLE"=>"Y",
   "OPENED"=>"Y",
   "CLOSED"=>"N",
   "SUBJECT_ID"=>4,
   "OWNER_ID"=>1,
   "INITIATE_PERMS"=>A,
   "SPAM_PERMS"=>"N",
   "SUBJECT_NAME"=>"Management Board"

);

$GroupID=CSocNetGroup::CreateGroup($USER->GetID(),$arFieldsSG);

if (CModule::IncludeModule("disk"))
{
\Bitrix\Disk\Driver::getInstance()->addGroupStorage($GroupID);
}

$this->SetVariable('WORKGROUPID',$GroupID);

вторник, 26 мая 2015 г.

Интеграция корпортала Битрикс24 (коробка) с AD. О подводных камнях и о том, как я их обходила.

С детства я очень везучий человек - это передо мной не открываются автоматические двери, это я всегда вытягиваю число 13 при любой жеребьевке и это я сажусь на единственный сломанный стул из множества. Поэтому, когда я внедряю какой-либо программный продукт, обстоятельства обязательно складываются так, что всплывают, если не все его "баги" и "фичи", то добрые 90%. Я уже привыкла к этому - и это делает мою жизнь похожей на фильм.

Мое сказочное везение не подвело меня и на моем недавнем внедрении коробки Битрикс24. Внедряла в  строительной компании средней величины. Пользователей AD (Active Directory) около 400 человек, и 105 из них являются так же пользователями корпортала, остальным корпортал для работы не нужен.

Как известно, корпортал коробка лицензируется по количеству активных пользователей, поэтому импортировать в портал лишних пользователей было нельзя. Это обстоятельство в корпортативном портале Битрикс24, слава богу, предусмотрено - в настройках AD/LDAP интеграции можно исключить определенные группы пользователей AD из импорта. Исключили, пользователи синхронизировались отлично, однако структура компании построилась не правильно:



Присутствовали пустые департаменты, у пользователей - тесок - перемешались подчиненные, а один отдел был создан 2 раза.

Стала разбираться, вставлять отладочные скрипты, писать в техническую поддержку и даже Юрию Волошину (спасибо ему за оперативные ответы).

Оказалось, что с тесками - известный баг, а у меня - первый такой реальный случай в истории. (Хотя это странно. Полные тески на руководящих должностях - далеко не редкость, особенно, когда они - отец и сын. Когда я работала в одном из подразделений РЖД, у нас там чуть ли не у каждого крупного начальника был сын, названный в честь отца, работающий начальником помельче. Мы обычно называли их между собой "старший" и "младший", но ведь в документах так писать не будешь, поэтому программное обеспечение должно предусматривать такие случаи).

С тесками мы расправились, добавив пробел к имени одного из них на стороне AD.

Дальше - интереснее. Стала я отлаживать скрипт импорта пользователей из AD, и поняла, от куда появляются пустые департаменты. Это департаменты, в которых работают пользователи, которые входят в те группы AD, которые в настройках интеграции записаны в исключения.

Дело в том, что до импорта каждого пользователя под него создается раздел в структуре компании, и только потом, когда пользователь уже непосредственно импортируется происходит проверка на его вхождение в группы - исключения. Этот момент я кастомизировала.

Скопировала скрипт /bitrix/modules/main/admin/user_import.php, переименовала его в user_customimport.php, в скрипте /bitrix/admin/user_import.php подключила свой скрипт вместо стандартного. глубоко переписывать алгоритм не стала - просто вставила после импорта всех пользователей удаление пустых департаментов:

$arUsersDeps=array();
$rsDepUsers = CUser::GetList(); 

while ($arDepUser = $rsDepUsers->Fetch()) 
{
$arUsersDeps[]=$arDepUser['WORK_DEPARTMENT'];    
}

$arDepFilter = Array('IBLOCK_ID'=>$ib_id);
$db_Deplist = CIBlockSection::GetList(Array($by=>$order), $arDepFilter, true);
 
$id_section_fd=array();
while($arDepSection = $db_Deplist->Fetch())
{
if (!in_array($arDepSection['NAME'],$arUsersDeps) && $arDepSection['ID']!=773){ 
$id_section_fd[]=$arDepSection['ID']; 
}

}
 
foreach($id_section_fd as $dep_id){ 

$DB->StartTransaction();
if(!CIBlockSection::Delete($dep_id))
{
$strWarning .= 'Error.';
$DB->Rollback();
}
else
$DB->Commit();
}

Вместо одного отдела ITC упорно создавались 2, сколько мы ни перепроверяли на стороне AD заполненность менеджеров и департаментов для каждого пользователя по всей иерархии. Да. на каком-то этапе мы с админом нашли множество ошибок на стороне AD, но их исправление так и не повлияло на импорт структуры.

Тогда в своем скрипте импорта пользователей я завела класс-наследник

class CLDAPCustom extends CLDAP
     {...}

И переопределила функцию GetDepartmentIdForADUser
функция бешеная - нечитабельная, а кроме того - рекурсивная, легче умереть, чем ее отладить, поэтому я просто изменила механизм проверки перед вставкой нового департамента. В оригинале поиск совпадения департамента велся только в определенном поддереве (а искало, как показала отладка, не в том поддереве, почему - так и осталось не ясно). Так как в моей компании все подразделения названы уникальными именами, сделала, чтобы искало совпадение по всей структуре.

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



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



среда, 20 мая 2015 г.

Себе на память: как программно добавить событие в календарь пользователя в корпортале битрикс24

$fromTs=MakeTimeStamp($_POST['from'], "MM/DD/YYYYS");
$toTs=MakeTimeStamp($_POST['to'], "MM/DD/YYYYS");


$arFieldsCalend = array(
"CAL_TYPE" =>'user',
"NAME" => $_POST['eventName'],
"DESCRIPTION" => $_POST['description'],
"SKIP_TIME" => date('H:i', $fromTs) == '00:00' && date('H:i', $toTs) == '00:00',
"IS_MEETING" => false,
"RRULE" => false
);
$arFieldsCalend['DT_FROM_TS'] = $fromTs;
$arFieldsCalend['DT_TO_TS'] = $toTs; 

$arFieldsCalend["OWNER_ID"] = 1;
$CalEventId = CCalendar::SaveEvent(
array(
'arFields' => $arFieldsCalend,
'autoDetectSection' => true
)
);

вторник, 19 мая 2015 г.

Себе на память: как опубликовать сообщение в живую ленту корпоративного портала Битрикс24 из внешнего источника.

На стороне битрикс24 делаем так:




На стороне внешнего источника делаем так:

if( $curl = curl_init() ) {
    curl_setopt($curl, CURLOPT_URL, 'https://адрес_портала/bitrix/tools/xdi_livefeed.php');
    curl_setopt($curl, CURLOPT_RETURNTRANSFER,true);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, "hash=f62167de71b87d31fed721a7e80b90a6&title=".htmlspecialchars($_POST['eventName'])."&message=".htmlspecialchars($_POST['description'])."&text_message=".htmlspecialchars($_POST['description']));
    $out = curl_exec($curl);
    echo $out;
    curl_close($curl);
  }

Себе на память: программное создание рабочей группы в корпортале Битрикс24

CModule::IncludeModule('socialnetwork');

$SocGroup=new CSocNetGroup;
global $USER;

$arFieldsSG=array(
   "NAME"=>$_POST['eventName'],
   "SITE_ID" => "s1",
   "DESCRIPTION"=>$_POST['eventName'],
   "ACTIVE"=>"Y",
   "VISIBLE"=>"Y",
   "OPENED"=>"Y",
   "CLOSED"=>"N",
   "SUBJECT_ID"=>4,
   "OWNER_ID"=>1,
   "INITIATE_PERMS"=>A,
   "SPAM_PERMS"=>"N",
   "SUBJECT_NAME"=>"Management Board"

);

$GroupID=CSocNetGroup::CreateGroup($USER->GetID(),$arFieldsSG);

Себе на память: программный запуск бизнес-процесса в корпортале Битрикс24

Для элемента CRM:

CModule::IncludeModule('bizproc');

CBPDocument::StartWorkflow(
      4,
      array("bizproc","CBPVirtualDocument",$LidID),
      array(),
      $arErrorsTmp
);

Для бизнес-процесса, не привзянного к CRM

$documentId = CBPVirtualDocument::CreateDocument(
    0,
    array(
     "IBLOCK_ID" => 27,
     "NAME" => "Create Notification",
     "CREATED_BY" => "user_".$GLOBALS["USER"]->GetID(),
    )
   );

   $arErrorsTmp = array();

   $wfId = CBPDocument::StartWorkflow(
   27,
    array("bizproc", "CBPVirtualDocument", $documentId),
    array_merge(array(), array("TargetUser" => "user_".intval($GLOBALS["USER"]->GetID()))),
    $arErrorsTmp
   );



Себе на память: программное создание лида в корпортале Битрикс 24

CModule::IncludeModule('crm');

$oLead = new CCrmLead;

$arFields = Array(
    "TITLE" => "EVENT CREATION ".$_POST['eventName'],
    "COMPANY_TITLE" => "EVENT CREATION ".$_POST['eventName'],
// "NAME" => "ИмяКонтакта",
// "LAST_NAME" => "ФамилияКонтакта",
// "SECOND_NAME" => "ОтчествоКонтакта",
// "POST" => "ДолжностьКонтакта",
    "ADDRESS" => $_POST['address'],
// "COMMENTS" => "КомментарийКомментарийКомментарий",
    "SOURCE_DESCRIPTION" => $_POST['description'],
// "STATUS_DESCRIPTION" => "",
//"OPPORTUNITY" => 123456,
"CURRENCY_ID" => "USD",
// "PRODUCT_ID" => "PRODUCT_1",
"SOURCE_ID" => "SELF",
    "STATUS_ID" => "NEW",
    "ASSIGNED_BY_ID" => $_POST['owner'],
    "UF_CRM_1431935906" => true,
    "UF_CRM_1431950264" => $eventID,
);

$LidID=$oLead->Add($arFields);

среда, 18 марта 2015 г.

Как вешать профиль импорта/экспорта Битрикс на крон

Оболочкой для выполнения по расписанию битриксовских импортов/экспортов на cron является файл

путь_от_корня_сервера_до_корня_сайта/bitrix/php_interface/include/catalog_export/cron_frame.php 

Этот файл нужно отредактировать специальным образом, а затем добавить  команду в расписание crona

1) Узнайте у хостера путь к интерпретатору php
К примеру, на серверах без забот Русоникс это   /usr/bin/php Тогда в первой строке файла cron_frame.php дожно быть записано: #!/usr/bin/php ‐q

Путь к интерпретатору php на некоторых хостингах можно узнать вот как:



2) Узнайте значение константы $_SERVER["DOCUMENT_ROOT"]  для сайта
К примеру, на Русониксе это: var/www/vhosts/название_сайта/httpdocs

Пропишите это значение в файле cron_frame.php

 5.3 Для каждого профиля импорта/экспорта добавьте в таблицу cron (в расписание) в панели  управления хостингом следующую команду (пример - для Русоникса):

/usr/bin/php ‐f /var/www/vhosts/имя_сайта/httpdocs/bitrix/php_interface/include/catalog_export/cron_f rame.php 7 >/var/www/vhosts/имя_сайта/httpdocs/bitrix/php_interface/include/catalog_export/logs/ 7.txt 

Где   /usr/bin/php – путь к интерпретатору php
7 – идентификатор профиля импорта/экспорта

А вот в панели управления хостинга timeweb.ru так удобно повесить импорты/экспорты на крон не получится, так как там не поддерживается передача скрипту аргументов. На таймвебе придется создать по отдельному файлу cron_frame.php для каждого профиля, который нужно повесить на крон, и внутри этих файлов зашить нужный ID профиля импорта или работать с кроном через ssh.

четверг, 5 марта 2015 г.

Программное создание реализации на основе заказа в 1С УТ

Не забывать, что данная операция не может быть выполнена на клиенте, а может быть выполнена только на сервере, то есть перед процедурой пишем директиву &НаСервере

ПараметрыОснования = Новый Структура();
ПараметрыОснования.Вставить("ДокументОснование", СсылкаНаДокумент);
ПараметрыОснования.Вставить("СкладОтгрузки",     СсылкаНаДокумент.Склад);
ПараметрыОснования.Вставить("ДатаОтгрузки",      СсылкаНаДокумент.ДатаОтгрузки);

НовРеализация = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
НовРеализация.Заполнить(ПараметрыОснования);

Попытка
НовРеализация.Записать(РежимЗаписиДокумента.Проведение);
Исключение
НовРеализация.Записать(РежимЗаписиДокумента.Запись);
КонецПопытки;

воскресенье, 22 февраля 2015 г.

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

Недавно столкнулась с интересным и  необычным вариантом организации номенклатурного учета в 1С Предприятии УТ.  Необычным, но допустимым в системе  и абсолютно корректным.

Предприятие – реализует одежду, которую заказывает непосредственно на фабрике производителя. Специфика документооборота с производителем такова, что товаром считается лекало, фасон платья или другой одежды, а материал, размер, цвет, принадлежность к  коллекции – это все реквизиты характеристики (свойства торгового предложения).

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

Модуль же обмена УТ, как стандартный, так и из битриксовского дополнения оказался не приспособленным к импорту в интернет-магазин товара, учтенного таким способом.

Фильтрация выгрузки по сегментам – предусмотрена в модуле импорта, но не учитывает того, что у сегмента бывает отбор по реквизиту характеристики. В оригинальном варианте модуля импорта (Управление торговлей, редакция 11.1 (11.1.9.70), версия модуля 4.0.5.2) при фильтрации по такому сегменту выбиралась та номенклатура, которая нужна, но выгружались все ее характеристики, а не только те, реквизиты которых соответствовали настройкам отбора сегмента.

Поэтому я решила кастомизировать битриксовский модуль обмена на стороне 1С.

Очевидным  и самым простым вариантом решения данной задачи было бы исправление запроса  на выборку номенклатуры с характеристиками в функции СформироватьВременныеТаблицыПоИнформацииОТоварахПакета . Но я посмотрела-посмотрела на этот километровый запрос, и поняла, что вклиниваться в него боюсь, тем более, что данный запрос битриксоиды видоизменяют с выходом новых обновлений версий УТ и модуля обмена, и поддерживать каждый раз его модификацию было бы для меня слишком трудоемко. (А вот если бы сами битриксоиды допилили его – было бы круто).

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

ИспользуетсяОтборПоСегментуНоменклатуры = Ложь;
Отбор = КомпоновщикНастроек.Настройки.Отбор;
ПолеСегмент = Новый ПолеКомпоновкиДанных("СегментНоменклатуры");

Для Каждого ЭлементОтбора Из Отбор.Элементы Цикл
Если ЭлементОтбора.ЛевоеЗначение = ПолеСегмент Тогда
Если ЭлементОтбора.Использование Тогда
СсылкаСегмент=ЭлементОтбора.ПравоеЗначение.Ссылка;
КодСегмента=ЭлементОтбора.ПравоеЗначение.Код;
НаименованиеСегмента=ЭлементОтбора.ПравоеЗначение.Наименование;
ИспользуетсяОтборПоСегментуНоменклатуры = Истина;
Прервать;
КонецЕсли;
КонецЕсли;
КонецЦикла;

Затем я выбрала номенклатуру и характеристики сегмента во вспомогательную таблицу значений, воспользовавшись функцией из ядра УТ.

ТнзСегмента=СегментыСервер.ТаблицаЗначений(СсылкаСегмент);

После этого я нашла пересечение этих двух таблиц, воспользовавшись функцией от сюда>>

Спасибо 1С-нику Кириллову Роману (его можно найти на 1clancer.ru, и он там, кстати, №1) за консультацию по этому вопросу.

ТнзСегмента=СегментыСервер.ТаблицаЗначений(СсылкаСегмент);
КолонкиДляСравнения= Новый ТаблицаЗначений;
КолонкиДляСравнения.Колонки.Добавить("Колонки1");
КолонкиДляСравнения.Колонки.Добавить("Колонки2");
Стр=КолонкиДляСравнения.Добавить();
Стр.Колонки1="Номенклатура";
Стр.Колонки2="Номенклатура";
Стр=КолонкиДляСравнения.Добавить();
Стр.Колонки1="Характеристика";
Стр.Колонки2="Характеристика";
ТзнПредложений=СравнитьДанные(ТзнПредложений,ТнзСегмента,КолонкиДляСравнения,Ложь);

И импорт заработал так, как нужно было на данном проекте, кстати, он в такой модификации – абсолютно универсален для фильтрации по какому угодно сегменту.