Битрикс24 - приём задач от клиентов

, Михаил

В нашем Битриксе мы просим всех клиентов писать в ленту, что бы менеджеры проекта сами создавали на основе записи в ленте задачу, назначали отвественного и собирали все данные о задаче в ... задачу) Или несколько задач на основе диалога.

Когда клиенты сами ставят задачи - они могут:

  1. Написать в общую ленту.
  2. Написать самому себе в ленту.
  3. Поставить задачу самому себе.
  4. Написать в личное сообщение. себе.
  5. Поставить ответственных всех участников (что отвлекает сотрудников).
  6. Поставить задачу менеджеру, которого нет в онлайне.
  7. Напишут ещё куда нибудь, но только не в ленту группы.

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

Дизайн в мелочах

, Александра Потапова

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

Кстати, вот код rgb на тот самый деревянный цвет: #b3a28f. Теперь мы знаем, как он выглядит :)

Возвращаем блок "Предложите покупателю" в детальный заказ 1С-Битрикс

, Михаил
Добавляем в /bitrix/php_interface/admin_header.php

<?php 
<? if (stripos($_SERVER['REQUEST_URI'], '/bitrix/admin/sale_order_view.php') !== false) : ?>
    <script type="text/javascript">
        $(function () {
	            $.ajax({  method: "GET", url: "/bitrix/diva/ajaxGetBasket.php", data: {ID: <?= intval($_REQUEST['ID']) ?>}
		}).done(function (msg) {
		                $('.adm-s-result-container').prepend(msg);
		});
	});
    </script>
<?endif;
?>
?>

Создаём обработчик /bitrix/diva/ajaxGetBasket.php

<?php 

define("NO_KEEP_STATISTIC"true);
//define("NOT_CHECK_PERMISSIONS", true);
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_before.php");
$ID intval($_REQUEST['ID']);
if (!$USER->IsAdmin() || $ID == 0) {
	    die();
	}
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/sale/general/admin_tool.php");
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/sale/lang/ru/admin/order_detail.php");
CModule::IncludeModule("iblock");
CModule::IncludeModule("catalog");
CModule::IncludeModule("sale");
$dbOrder CSaleOrder::GetList(
                array("ID" => "DESC"), array("ID" => $ID), falsefalse, array(
            "ID""LID""PERSON_TYPE_ID",
            "PAYED""DATE_PAYED""EMP_PAYED_ID""PAY_VOUCHER_NUM""PAY_VOUCHER_DATE",
            "CANCELED""DATE_CANCELED""EMP_CANCELED_ID""REASON_CANCELED",
            "STATUS_ID""DATE_STATUS""EMP_STATUS_ID""PRICE_DELIVERY",
            "ALLOW_DELIVERY""DATE_ALLOW_DELIVERY""EMP_ALLOW_DELIVERY_ID",
            "DEDUCTED""DATE_DEDUCTED""EMP_DEDUCTED_ID""REASON_UNDO_DEDUCTED",
            "MARKED""DATE_MARKED""EMP_MARKED_ID""REASON_MARKED",
            "PRICE""CURRENCY""DISCOUNT_VALUE""SUM_PAID""USER_ID""PAY_SYSTEM_ID",
            "DELIVERY_ID""DATE_INSERT""DATE_INSERT_FORMAT""DATE_UPDATE""USER_DESCRIPTION",
            "ADDITIONAL_INFO""PS_STATUS""PS_STATUS_CODE""PS_STATUS_DESCRIPTION",
            "PS_STATUS_MESSAGE""PS_SUM""PS_CURRENCY""PS_RESPONSE_DATE""COMMENTS",
            "TAX_VALUE""STAT_GID""RECURRING_ID""AFFILIATE_ID""LOCK_STATUS",
            "USER_LOGIN""USER_NAME""USER_LAST_NAME""USER_EMAIL""DELIVERY_DOC_NUM",
            "DELIVERY_DOC_DATE""STORE_ID""ACCOUNT_NUMBER""TRACKING_NUMBER",
                )
);
if (($arOrder $dbOrder->Fetch())) {
	    ?>
	    <div class="load_product order_summary" style="float: left;
	">
	        <table width="100%" class="itog_header"><tr><td>Предложите покупателю</td></tr></table>
	        <div id="tabs">
	            <?
	            $crmMode false;
	            $displayNone "block";
	            $displayNoneBasket "block";
	            $displayNoneViewed "block";
	            $arFilterRecomendet = array();
	            $arBasketItems = array();
	            $dbBasketTmp CSaleBasket::GetList(array("ID" => "ASC"), array("ORDER_ID" => $arOrder["ID"]), falsefalse, array("ID""PRODUCT_ID"));
	            while ($arBasketTmp $dbBasketTmp->GetNext()) {
		                $arBasketItems[] = $arBasketTmp;
		            }
	            //pr($arBasketItems);
	            foreach ($arBasketItems as $arItem) {
		                if (!CSaleBasketHelper::isSetItem($arItem)) {
			                    $arFilterRecomendet[] = $arItem["PRODUCT_ID"];
			                }
		            }
	            $arRecommendedResult CSaleProduct::GetRecommendetProduct($arOrder["USER_ID"], $arOrder["LID"], $arFilterRecomendet);
	            $recomCnt count($arRecommendedResult);
	            if ($recomCnt 2) {
		                $arTmp = array();
		                $arTmp[] = $arRecommendedResult[0];
		                $arTmp[] = $arRecommendedResult[1];
		                $arRecommendedResult $arTmp;
		            }
	            if ($recomCnt <= 0)
	                $displayNone "none";
	            $arErrors = array();
	            $arFuserItems CSaleUser::GetList(array("USER_ID" => intval($arOrder["USER_ID"])));
	            $arCartWithoutSetItems = array();
	            $arTmpShoppingCart CSaleBasket::DoGetUserShoppingCart($arOrder["LID"], $arOrder["USER_ID"], $arFuserItems["ID"], $arErrors, array());
	            if (is_array($arTmpShoppingCart)) {
		                foreach ($arTmpShoppingCart as $arCartItem) {
			                    if (CSaleBasketHelper::isSetItem($arCartItem))
			                        continue;
			                    $item findPositionsByID($arCartItem["PRODUCT_ID"]);
			                    if ($item['IBLOCK_ID'] != CATALOG_IBLOCK_ID) {
				                        if ($item['PROPS']['CML2_LINK']['VALUE'] != "") {
					                            $item findPositionsByID($item['PROPS']['CML2_LINK']['VALUE']);
					                        }
				                    }
			                    $arCartItem['MASTER'] = $item;
			                    $arCartItem['NAME'] = "[" $item['PROPS']['CML2_ARTICLE']['VALUE'] . "] " $arCartItem['NAME'];
			                    $arCartWithoutSetItems[] = $arCartItem;
			                }
		            }
	            $basketCnt count($arCartWithoutSetItems);
	            if ($basketCnt 2) {
		                $arTmp = array();
		                $arTmp[] = $arCartWithoutSetItems[0];
		                $arTmp[] = $arCartWithoutSetItems[1];
		                $arCartWithoutSetItems $arTmp;
		            }
	            if ($basketCnt <= 0)
	                $displayNoneBasket "none";
	            ///
	            $arViewed = array();
	            $arViewedIds = array();
	            $viewedCount 0;
	            $mapViewed = array();
	            if (CModule::includeModule("catalog")) {
		                $viewedIterator = \Bitrix\Catalog\CatalogViewedProductTable::getList(array(
		                            'order' => array("DATE_VISIT" => "DESC"),
		                            'filter' => array('FUSER_ID' => $arFuserItems["ID"], "SITE_ID" => $arOrder["LID"]),
		                            'select' => array("ID""FUSER_ID""DATE_VISIT""PRODUCT_ID""LID" => "SITE_ID""NAME" => "ELEMENT.NAME""PREVIEW_PICTURE" => "ELEMENT.PREVIEW_PICTURE""DETAIL_PICTURE" => "ELEMENT.DETAIL_PICTURE")
		                ));
		                while ($viewed $viewedIterator->fetch()) {
			                    $viewed['MODULE'] = 'catalog';
			                    $arViewed[$viewedCount] = $viewed;
			                    $arViewedIds[] = $viewed['PRODUCT_ID'];
			                    $mapViewed[$viewed['PRODUCT_ID']] = $viewedCount;
			                    $viewedCount++;
			                }
		                unset($viewedCount);
		                $baseGroup CCatalogGroup::getBaseGroup();
		                if (!empty($arViewedIds)) {
			                    $priceIterator CPrice::getList(
			                                    array(), array("PRODUCT_ID" => $arViewedIds'CATALOG_GROUP_ID' => $baseGroup['ID']), falsefalse, array("PRODUCT_ID""PRICE""CURRENCY"));
			                    while ($productPrice $priceIterator->fetch()) {
				                        if (isset($mapViewed[$productPrice['PRODUCT_ID']])) {
					                            $key $mapViewed[$productPrice['PRODUCT_ID']];
					                            $arViewed[$key]["PRICE"] = $productPrice["PRICE"];
					                            $arViewed[$key]["CURRENCY"] = $productPrice["CURRENCY"];
					                        }
				                    }
			                }
		                $viewedCnt count($arViewed);
		                $arViewed array_slice($arViewed02);
		                if (count($arViewed) <= 0)
		                    $displayNoneViewed "none";
		            }
	            else {
		                $displayNoneViewed "none";
		            }
	            $tabBasket "tabs";
	            $tabViewed "tabs";
	            if ($displayNoneBasket == 'none' && $displayNone == 'none' && $displayNoneViewed == 'block')
	                $tabViewed .= " active";
	            if ($displayNoneBasket == 'block' && $displayNone == 'none')
	                $tabBasket .= " active";
	            ?>
	            <div id="tab_1" style="display:<?= $displayNone ?>"       class="tabs active"     onClick="fTabsSelect('buyer_recmon', this);
	" ><?= GetMessage('SOD_SUBTAB_RECOMENET'?></div>
	            <div id="tab_2" style="display:<?= $displayNoneBasket ?>" class="<?= $tabBasket ?>" onClick="fTabsSelect('buyer_basket', this);
	"><?= GetMessage('SOD_SUBTAB_BASKET'?></div>
	            <div id="tab_3" style="display:<?= $displayNoneViewed ?>" class="<?= $tabViewed ?>" onClick="fTabsSelect('buyer_viewed', this);
	"><?= GetMessage('SOD_SUBTAB_LOOKED'?></div>
	            <?
	            if ($displayNone == 'block') {
		                $displayNoneBasket 'none';
		                $displayNoneViewed 'none';
		            }
	            if ($displayNoneBasket == 'block') {
		                $displayNone 'none';
		                $displayNoneViewed 'none';
		            }
	            if ($displayNoneViewed == 'block') {
		                $displayNone 'none';
		                $displayNoneBasket 'none';
		            }
	            ?>
	            <div id="buyer_recmon" class="tabstext active" style="display:<?= $displayNone ?>">
	                <? echo fGetFormatedProductData($arOrder["USER_ID"], $arOrder["LID"], $arRecommendedResult$recomCnt$arOrder["CURRENCY"], 'recom'$crmMode);
	 ?>
	            </div>
	            <div id="buyer_basket" class="tabstext active" style="display:<?= $displayNoneBasket ?>">
	                if (count($arCartWithoutSetItems) > 0)
	                echo fGetFormatedProductData($arOrder["USER_ID"], $arOrder["LID"], $arCartWithoutSetItems, $basketCnt, $arOrder["CURRENCY"], 'basket', $crmMode);
	                ?>
	            </div>
	            <div id="buyer_viewed" class="tabstext active" style="display:<?= $displayNoneViewed ?>">
	                <?
	                if (count($arViewed) > 0)
	                    echo fGetFormatedProductData($arOrder["USER_ID"], $arOrder["LID"], $arViewed$viewedCnt$arOrder["CURRENCY"], 'viewed'$crmMode);
	                ?>
	            </div>
	        </div>
	        <script type="text/javascript">
	            function fTabsSelect(tabText, el)
	            {
		                BX('tab_1').className = "tabs";
		                BX('tab_2').className = "tabs";
		                BX('tab_3').className = "tabs";
		                BX(el).className = "tabs active";
		                BX(el).className = "tabs active";
		                BX(el).style.display = 'block';
		                BX('buyer_recmon').className = "tabstext";
		                BX('buyer_basket').className = "tabstext";
		                BX('buyer_viewed').className = "tabstext";
		                BX('buyer_recmon').style.display = 'none';
		                BX('buyer_basket').style.display = 'none';
		                BX('buyer_viewed').style.display = 'none';
		                BX(tabText).style.display = 'block';
		                BX(tabText).className = "tabstext active";
		            }
	        </script>
	        <script type="text/javascript">
	            /*
	             * click on recommendet More
	             */
	            function fGetMoreProduct(type)
	            {
		                BX.showWait();
		                productData = <? echo CUtil::PhpToJSObject($arFilterRecomendet);
		 ?>;
		                var userId = '<?= $arOrder["USER_ID"?>';
		                var fUserId = '<?= $arFuserItems["ID"?>';
		                var currency = '<?= $arOrder["CURRENCY"?>';
		                var lid = '<?= $arOrder["LID"?>';
		                BX.ajax.post('/bitrix/admin/sale_order_detail.php', '<?= CUtil::JSEscape(bitrix_sessid_get()) ?>&ORDER_AJAX=Y&type=' + type + '&arProduct=' + productData + '&currency=' + currency + '&LID=' + lid + '&userId=' + userId + '&fUserId=' + fUserId + '&ID=<?= $ID ?>', fGetMoreProductResult);
		            }
	            function fGetMoreProductResult(res)
	            {
		                BX.closeWait();
		                var rs = eval('(' + res + ')');
		                if (rs["ITEMS"].length > 0)
		                {
			                    if (rs["TYPE"] == 'basket')
			                        BX("buyer_basket").innerHTML = rs["ITEMS"];
			                    if (rs["TYPE"] == 'recom')
			                        BX("buyer_recmon").innerHTML = rs["ITEMS"];
			                    if (rs["TYPE"] == 'viewed')
			                        BX("buyer_viewed").innerHTML = rs["ITEMS"];
			                }
		            }
	        </script>    
	    </div>
	    <?
	}
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_after.php");

?>

«Сказка»

, Ashe Gentle

От Недоброго сказочника чудная вещь не могу не поделиться. Во избежание потерь, так сказать.

— В объявлении написано, что у вас можно взять квест,— сказал Полуэльф бургомистру.— Но там не объясняется, в чём суть. Вы не могли бы уточнить?
— Да всё просто,— пожал плечами бургомистр.— Видите вон тот холм? На нём засел гоблин с гранатомётом. И периодически обстреливает город. Вот, собственно, и вся проблема.
— Ага, понятно. Надо убить гоблина...
— Что вы, что вы!?— бургомистр вытаращил глаза и замахал руками.— Его ни в коем случае нельзя убивать!
— Почему?— удивился Гном.— Это же гоблин!
— Вот именно! Если мы его убьём, мировая общественность скажет, что это геноцид, а мы расисты.
— Ну и что? Пусть говорит что хочет.
— И введёт войска,— мрачно закончил свою мысль бургомистр.
— Хм...— задумался Полуэльф.— То есть, этот засранец стреляет по вам из гранатомёта, а вы терпите и не смеете дать сдачи?
— Не смеем,— развёл руками бургомистр.— Иначе нас назовут агрессорами.
— Ну хорошо, а если, допустим, не убивать гоблина, а прогнать его куда-нибудь подальше?
— С его холма? Невозможно. Тогда нас назовут оккупантами.
— Поймать и отобрать гранатомёт?
— Экспроприаторами.
— Посадить под замок вместе с гранатомётом?.. Ладно-ладно, не отвечайте,— быстро проговорил Полуэльф, когда бургомистр открыл было рот.— Я всё понял. Действительно, интересный случай.
— Ну так чего же вы от нас хотите?— не выдержала Принцесса.— Убивать нельзя, разоружать нельзя, ловить и прогонять тоже нельзя, а что тогда остаётся? Перевоспитывать? Это не наш профиль.
— Нет, что вы... Для такой работы мы бы позвали психолога. Но, кстати, тогда мировая общественность обвинила бы нас в оказании психологического давления.
— И в осквернении самобытных традиций,— добавил Гном, солидно качнув головой.— Пострелять из гранатомёта по людишкам — это же для гоблинов святое!
— Вот-вот,— радостно воскликнул бургомистр,— вы меня понимаете.
— Ну а от нас-то что требуется?— снова встряла Принцесса.
— Отнести посылку,— вздохнул бургомистр.
— Кому? Гоблину?
— Ну да. Ведь там, на холме, нет никаких запасов еды. Через час гоблин проголодается, объявит перемирие и начнёт переговоры. Он так каждый день делает. Требует, чтобы ему приносили еду, вино, оружие, иногда ещё чего-нибудь... А потом, когда наестся, заявляет, что мирные переговоры зашли в тупик и он вынужден возобновить огонь. Мировая общественность ему очень сочувствует. Считает, что он принципиальный.
— А если вы откажетесь предоставлять ему еду и оружие...
— Тогда про нас скажут, что...
— Ладно-ладно, мы поняли,— замахал руками Полуэльф.
— ...и введут войска,— пробубнил бургомистр.
— Ну хорошо, а мы-то вам зачем? Послали бы кого-нибудь из своих отнести мешок.
— Посылали уже. Никто не вернулся.
— Что, гоблин их всех убил?
— Он утверждает, что нет.
— А...
— А мировая общественность ему верит.
— А...
— А тогда скажут, что мы провокаторы. Понимаете, это ведь он, гоблин, проявляет мирную инициативу, это его жест доброй воли. И если что-то пошло не так, то только по нашей вине. Очевидно же! А вы... ну вроде как посторонние, вас он, может, и не тронет.
— Ну хорошо,— подытожил Полуэльф.— Если отбросить всякую политическую шелуху, то от нас требуется взять посылку у заказчика и отнести её клиенту, верно? Обычный почтовый квест. А всё остальное — только ваши проблемы. Так?
— Всё верно,— подтвердил бургомистр,— значит, договорились?
— По рукам,— кивнул Полуэльф. Бургомистр облегчённо вздохнул.
— Можно вопрос?— подняла руку Принцесса.— Вот вы так боитесь, что мировая общественность назовёт вас агрессорами, или милитаристами, или ещё чем похуже — а как она вас называет сейчас?
— Идиотами,— печально ответил бургомистр.

«Спутник». Обзор и личное мнение

, Ashe Gentle

Невероятно, но факт. Сегодня запущена российская поисковая система «Спутник», разработанная Ростелекомом под эгидой государства. Её даже можно посмотреть и попробовать на sputnik.ru.

Главная страница sputnik.ru

Пользователям рунета предлагается поиск идеологически правильных материалов. Особенно мне понравилась фраза «Спутник делает проще доступ к официальной информации». Неприкрытая правда, как ни странно. Шутить на эту тему можно долго. Однако мои коллеги предпочли воздержаться от комментариев. Все мало-мальски разумные пользователи понимают, к чему ведёт ввод государственной поисковой системы. Особенно на фоне обострившихся законов против интернета.

Мы же в этой статье попытаемся найти плюсы

Было бы здорово, если бы «Спутник» позволил прекратить бесконечную бумажную волокиту по простейшим делам. Для этого, наверное, и предназначен сервис «Удобная Россия». Но увы, он не оправдывает ожиданий и представляет собой обычный поиск по заранее выбранной теме. Система выводит на первом месте наиболее релевантный материал (по её мнению) и предлагает другие варианты. На этом всё. Где обещанное «удобно»?

Далее сервис «Мой дом». Вот это уже поинтереснее. Он предлагает найти информацию обо всех организациях, обслуживающих конкретное здание, а также о находящихся поблизости. Плюс, опять таки, релевантные результаты поиска. Увы, информация пока не полная, о чём предупреждает сама система. Надеемся на будущее развитие. Ах да, строка поиска никак не хочет запоминать, что вы уже искали.

Удобная Россия Мой дом

«Лекарства» обещают показать инструкцию по применению и найти ближайшие аптеки (кстати, с ценами). Хорошо, если бы «не». Анальгин за 50 рублей в единственном месте в городе? Да ладно!

«Финансы». Что это? Курс валют и конвертер. Зачем выносить в отдельный сервис то, что можно запихать в поиск? Как это сделали google и яндекс. «Спутник» так не умеет и по запросу «1 доллар в рублях» выдаёт результаты, а не вычисления. Сомнительно.

«Телепрограмма». О боже, неужели действительно достойный сервис?! Он и правда показывает программу по всем каналам! Причём с удобной фильтрацией и возможностью добавить в избранное! И даже с онлайн-трансляцией! Здорово. Простите мой сарказм, действительно хорошо и достойно внимания. Уже отчаялась найти полезное.

«Карта» — список учреждений. Каких-то. Одному «Спутнику» известных. Потому что просто воспользоваться картой и узнать информацию по любому зданию нельзя. И снова тот же вопрос: «зачем»?

Лекарства Финансы Телепрограмма Карта

На этом всё, что я имею сказать по функционалу. Теперь про внешний вид. Удивительно, но сайт выглядит достаточно приятно. Светлый, просторный, всё как мы любим. Но неудобный. Непонятно назначение серого пространства справа при выдаче результатов. Сейчас там показывается реклама сервисов. Не очевидная, потому что случайно нажимается и уходит на другую страницу. Зато красиво и хорошо сделана страница «About». По веяниям последних технологий параллакса. Содержание вызывает улыбку, советую прочитать.

Теперь совсем всё. Быть «Спутнику» или не быть? Ответ простой: через пару дней ажиотаж вокруг стихнет, и сервис умрёт. Пока существуют Яндекс и Гугл, конкуренцию им составить нереально. А ввиду того, что половина заявленного функционала не отвечает ожиданиям, и подавно. Ну или пока пресловутая «Чебурашка» не заработает.

Добро пожаловать на „Спутник“. Пользуйтесь интернетом безопасно и с пользой!

Как мы побывали под запретом Роскомнадзора

, Ashe Gentle

Однажды, в студёную зимнюю пору клиент сообщил, что не может зайти на наш сайт. «Как так?!» — переполошились мы? Вот же он, родной divasoft.ru, открывается спокойно. Что же такое? Может антивирус косячит? А клиент, к слову, из Москвы будет. Запросили мы у человека скрин, что же ему там браузер молвит. Получили, смотрим, переглядываемся, репу чешем, не понимаем.

Скриншот сайта

Забанил нас Роскомнадзор в самом деле. Да уж, а ведь знали, что не стоит такие картинки на сайт выкладывать, да и видео ещё добавлять. Вот и поплатились.

Но шутки шутками, а работать надо. Понятное дело, что на нашем сайте запрещённого контента нет, ищем дальше. Весь трафик проходит через cloudflare.com для защиты и кеширования сайта на уровне DNS. И вот там то, за общим адресом и прячется зловредный контент. Собственно, уже на следующий день проблема была решена и сайт заработал в обычном режиме. А мы получили бесценный опыт пребывания под баном.