Jump to content
SpravkaCRM.ru - Ваш справочник по CRM

All Activity

This stream auto-updates     

  1. Today
  2. Добрый день! На скриншете не доработанная версия отчета по плановой загрузке сотрудника. Это не какой то отдельный модуль. Это CRM-система для разработчика: то, что я сам себе для своей компании пилю. Фактически сейчас там отображается плановое количество рабочих часов по дням для текущего сотрудника. Уже не помню что хотел вложить в этот отчет. Сейчас с точки зрения контроля работы сотрудников более удобным образом можно наблюдать следующую информацию: Слева распределение по сотрудникам сколько фактически отработано часов в день. Справа распределение фиктического относительно планового кол-ва часов. Тут календарь рабочих нагрузок для определения объема занятости сотрудника. Собирает в себе плановые нагрузки и фактически назначенные. Тут сублимированная информация по сотруднику из разных областей его деятельности. А тут у нас расчетная ведомость, где список фактически выполненных работ и суммарная стоимость к выплате сотрудникам в зависимости от выполненных работ. Как видите информацию в можно очень по разному представить к выводу. Я отталкивался от наиболее удобного мне формата для решения той или иной задачи. Вряд ли подобные отчеты можно найти где то уже готовыми, уж больно это все индивидуально. Потратив немного бюджета можно запилить именно то, что Вам будет удобно. Это не очень дорого, но очень удобно использовать.
  3. Вышеуказанные ошибки к отправке писем никакого отношения не имеют... Что в очереди на отправку в администрировании ? крон настроен? В планировщике стоит задача ? какой почтовый сервис используете (mail gmail yandex) ?
  4. Здравствуйте попробуйте создать файл custom/Extension/modules/Название модуля/Ext/Vardefs/любое название(на английском).php и добавить в него следующий код: $dictionary['название модуля']['fields']['название поля'][''audited'] = true; сделайте быстрое восстановление проверьте добавление этой строчки в файл custom/modules/Название модуля/Ext/Vardefs/vardefs.ext.php если она там не появилась пропешите ее в этом файле и удалите кеш
  5. Last week
  6. Подскажите, почему в панели администрирования в студии в модуле в настройках поля когда ставишь галочку Аудит и жмешь сохранить галочка не сохраняется? На локалке, после того как восстановление провел - начала работать. Мне еще кажется, что восстановление на удаленном сервере не происходит. С чем это может быть связано? Создается файл: custom/Extension/modules/Buildings/Ext/Vardefs/sugarfield_about_owner.php Когда галку ставлю. В логах: [FATAL] Could not write custom/modules/Buildings/language/ru_ru.lang.php Даю права на ru_ru.Lang.php, мне не жалко Делаю восстановление Пишет Таблица аудита для Buildingss уже существует, пропускаем... Новых изменений в файлах не происходит. Галочка так и не работает:( В cache удаляю Buildings, в новом файле 'about_owner' => array ( 'name' => 'about_owner', 'vname' => 'LBL_ABOUT_OWNER', 'type' => 'text', 'audited' => true, не появляется. Где в БД эти записи, так же не могу найти.
  7. Большое спасибо за проявленный интерес к моему обращению и за предоставленные советы. Но проблема не в кол-ве ответственных по которым надо график строить, проблема вообще с построением графиков стандартным функционалом Suite. Насколько я смог просмотреть тему на форумах и видео - грифики строят исключительно по ответственному в рамках сделок (где учитываются деньги), а у меня задача по проще, учитывать кол-во статусов по ответственному И вот с этим затруднение. Буду пробовать в более свежих версиях. @SpravkaCRM.ru, подскажите, пож-та, что это за модуль такой на скиншоте вашего сообщения от Tuesday в 10:48? Спасибо.
  8. И еще поправка к первому сообщению. Версия 7.7.4 Прошу извинить...
  9. Всем привет. Мы продолжаем искать решение в вышеуказанной проблемы. На данный момент выявлены следующие логи касающиеся попыток выполнить рассылку. По кейсу следующее: - Рассылка была запланирована на 8:15, но ошибки по маркетингу стали фиксироваться в 8:14, то есть еще до отправки. Заранее благодарю за любой совет. Fri May 18 08:14:11.388478 2018] [:error] [pid 1441] [client 10.1.29.145:55554] PHP Warning: Illegal string offset 'cookie_name' in /var/www/crm/cache/smarty/templates_c/%%E2^E2B^E2BC33C5%%SubPanelTiles.tpl .php on line 41, referer: https://crm.***.ru/index.php?action=WizardMarketing&module=Campaigns&return_module=Campaigns&return_action=WizardHome&return_id=5326ce95-f3ca-5cd3-0eed-5afe601de523&campaign_i d=5326ce95-f3ca-5cd3-0eed-5afe601de523&jump=3&show_wizard_marketing=1&marketing_id=4f667952-6968-9cbf-67c5-5afe61d27ced&record=4f667952-6968-9cbf-67c5-5afe61d27ced&campaign_type=Email [Fri May 18 08:14:13.850944 2018] [:error] [pid 1441] [client 10.1.29.145:55554] PHP Notice: Undefined index: type in /var/www/crm/include/SubPanel/SubPanel.php on line 450, referer: https://crm.***.ru.r u/index.php?module=Campaigns&action=TrackDetailView&record=5326ce95-f3ca-5cd3-0eed-5afe601de523 [Fri May 18 08:14:14.059107 2018] [:error] [pid 1441] [client 10.1.29.145:55554] PHP Notice: Undefined index: type in /var/www/crm/include/SubPanel/SubPanelDefinitions.php on line 98, referer: https://crm.vi pservice.ru/index.php?module=Campaigns&action=TrackDetailView&record=5326ce95-f3ca-5cd3-0eed-5afe601de523 [Fri May 18 08:14:14.061907 2018] [:error] [pid 1441] [client 10.1.29.145:55554] PHP Notice: Undefined index: type in /var/www/crm/include/SubPanel/SubPanel.php on line 403, referer: https://crm.***.ru.r u/index.php?module=Campaigns&action=TrackDetailView&record=5326ce95-f3ca-5cd3-0eed-5afe601de523 [Fri May 18 08:14:14.108314 2018] [:error] [pid 1441] [client 10.1.29.145:55554] PHP Notice: Undefined index: CampaignLog in /var/www/crm/include/SearchForm/SearchForm2.php on line 122, referer: https://crm. vipservice.ru/index.php?module=Campaigns&action=TrackDetailView&record=5326ce95-f3ca-5cd3-0eed-5afe601de523 [Fri May 18 08:14:14.116663 2018] [:error] [pid 1441] [client 10.1.29.145:55554] PHP Notice: Undefined index: type in /var/www/crm/include/SubPanel/SubPanel.php on line 403, referer: https://crm.***.ru.r u/index.php?module=Campaigns&action=TrackDetailView&record=5326ce95-f3ca-5cd3-0eed-5afe601de523
  10. Добрый день. Успешно пользуемся массовыми рассылками в модуле "Маркетинг", но внезапно письма перестали отправляться. В Статусе рассылок мы видим, что письма остаются в очереди на отправку. Посмотрели логи и увидели ошибку: "Error retrieving template for the email campaign. template_id = 9f0d0646-1bf4-3da9-500a-5ad6dcec5796" Ошибка указывает на невозможность взять/получить шаблон письма. Проверили всевозможные кейсы. На копии нашей произвдоственной CRM рассылки работают. Предположили, что может в базе большое количество созданных шаблонов - удалили несколько десятков, повторили рассылку результат такой же. Подскажите, пожалуйста, кто-либо сталкивался с подобной ошибкой? Если да, то как побеждали/решали? Заранее благодарен. Версия 7.4.4.
  11. Если говорить про каждого отдельного сотрудника, то тогда уж можно лучше сделать графики прямо в карточке сотрудника. Открыл сотрудника - там панель с графиком. Типа такого: И не нужно плодить кучу дашлетов на главной.
  12. На сколько я помню логика работы SuiteCRM подразумевает возможность работы с несколькими полями с типом "валюта", но валюта будет одна. То есть все значения будут в рублях, долларах, или любой другой присутствующей в CRM валюте. Но хранение нескольких полей в разной валюте в рамках одной карточки не предусмотрено. Обусловлено тем, что все добавленные поля ссылаются на едиснтвенное поле, которое будет обозначать код валюты. То есть добавляем первое поле с валютой в карточку: формируется спец.поле, хранящее код валюты. Добавляем второе поле - оно будет получать код валюты из первого поля. И так далее. Сделать мультивалютность внутри одной карты можно, но это уже программировать.
  13. Earlier
  14. Насколько я понял вы хотите сумму сделки отображать в нескольких валютах одновременно ?
  15. Всем привет! Столкнулся только что с ситуацией, когда в карточку Контрагента добавили поле с пользователем (связанная запись из Users), пытаемся в дашлете это поле использовать как фильтр, а оно не работает. Поле добавляли через файлы в /custom/Extensions/modules/Accounts/Ext/Vardefs/ В дашлете это выглядит примерно так: В карточке контрагента это же поле: После того, как в дашлете я выбираю это поле в виде фильтра, SuiteCRM формирует для выборки записей SQL-запрос примерно такого содержания: SELECT accounts.id , accounts.user_id2_c , LTRIM(RTRIM(CONCAT(IFNULL(jt0.first_name,''),' ',IFNULL(jt0.last_name,'')))) fixed_accountant_c , LTRIM(RTRIM(CONCAT(IFNULL(jt1.first_name,''),' ',IFNULL(jt1.last_name,'')))) fixed_accountant_c , jt1.created_by fixed_accountant_c_owner , 'Users' fixed_accountant_c_mod, accounts.assigned_user_id FROM accounts LEFT JOIN users jt0 ON accounts.user_id2_c = jt0.id AND jt0.deleted=0 LEFT JOIN users jt1 ON accounts.user_id2_c=jt1.id AND jt1.deleted=0 AND jt1.deleted=0 where (user_id2_c='d3c33565-3030-cebd-2db4-592308bd456b' ) AND accounts.deleted=0 Как мы видим ((accounts.id IN (''))) - совсем не то, что нам нужно. В результате анализа и поиска места, где это все собирается и как так получается был найден файл /include/Dashlets/DashletGeneric.php и в нем функция buildWhere() с таким участком: switch($widgetDef['type']) {// handle different types case 'date': case 'datetime': case 'datetimecombo': if(is_array($params) && !empty($params)) { if(!empty($params['date'])) $widgetDef['input_name0'] = $params['date']; $filter = 'queryFilter' . $params['type']; } else { $filter = 'queryFilter' . $params; } array_push($returnArray, $widgetClass->$filter($widgetDef, true)); break; case 'assigned_user_name': // This type runs through the SugarWidgetFieldname class, and needs a little extra help to make it through if ( ! isset($widgetDef['column_key']) ) { $widgetDef['column_key'] = $name; } // No break here, we want to run through the default handler case 'relate': if (isset($widgetDef['link']) && $this->seedBean->load_relationship($widgetDef['link'])) { $widgetLink = $widgetDef['link']; $widgetDef['module'] = $this->seedBean->$widgetLink->focus->module_name; $widgetDef['link'] = $this->seedBean->$widgetLink->getRelationshipObject()->name; } // No break - run through the default handler default: $widgetDef['input_name0'] = $params; if(is_array($params) && !empty($params)) { // handle array query array_push($returnArray, $widgetClass->queryFilterone_of($widgetDef, false)); } else { array_push($returnArray, $widgetClass->queryFilterStarts_With($widgetDef, true)); } $widgetDef['input_name0'] = $params; break; } Поле, по которому мы пытаемся в дашлете искать, создано при помощи такого массива: $dictionary['Account']['fields']['fixed_accountant_c'] = array ( 'required' => false, 'source' => 'non-db', 'name' => 'fixed_accountant_c', 'vname' => 'LBL_FIXED_ACCOUNTANT_C', 'type' => 'relate', 'massupdate' => 0, 'no_default' => false, 'comments' => '', 'help' => '', 'importable' => 'true', 'duplicate_merge' => 'disabled', 'duplicate_merge_dom_value' => '0', 'audited' => false, 'inline_edit' => true, 'reportable' => true, 'unified_search' => false, 'merge_filter' => 'disabled', 'len' => '255', 'size' => '20', 'id_name' => 'user_id2_c', 'ext2' => 'Users', 'module' => 'Users', 'rname' => 'name', 'quicksearch' => 'enabled', 'studio' => 'visible', ); Судя по всему получается так, что в функции buildWhere() срабатывает case = 'relate'. Но так же выяснил, что НЕ срабатывает блок if (isset($widgetDef['link']) && $this->seedBean->load_relationship($widgetDef['link'])) {} просто по причине отсутствия 'link' в описании нашего поля. Решил добавить 'link', и ВСЕ ПОЛУЧИЛОСЬ!!! За основу для добавления link взял поле assigned_user_id, так как эти поля получились очень похожими: эти поля находятся в таблице `accounts` и ссылаются на таблицу и модуль с Пользователями (`users`) Таким образом в файл, в котором я описал мое поле, я добавил еще следующие блоки: $dictionary['Account']['fields']['fixed_accountant_c_link'] = array ( 'name' => 'fixed_accountant_c_link', 'type' => 'link', 'relationship' => 'fixed_accountant_c', 'vname' => 'LBL_FIXED_ACCOUNTANT_C_LINK', 'link_type' => 'one', 'module' => 'Users', 'bean_name' => 'User', 'source' => 'non-db', 'duplicate_merge' => 'enabled', 'rname' => 'fixed_accountant_c', 'id_name' => 'user_id2_c', 'table' => 'users', ); $dictionary['Account']['relationships']['fixed_accountant_c'] = array ( 'lhs_module' => 'Users', 'lhs_table' => 'users', 'lhs_key' => 'id', 'rhs_module' => 'Accounts', 'rhs_table' => 'accounts', 'rhs_key' => 'user_id2_c', 'relationship_type' => 'one-to-many' ); А в описание нашего поля вставляю link: $dictionary['Account']['fields']['fixed_accountant_c'] = array ( 'required' => false, 'source' => 'non-db', 'name' => 'fixed_accountant_c', 'vname' => 'LBL_FIXED_ACCOUNTANT_C', 'type' => 'relate', 'link'=>'fixed_accountant_c_link' , 'massupdate' => 0, 'no_default' => false, 'comments' => '', 'help' => '', 'importable' => 'true', 'duplicate_merge' => 'disabled', 'duplicate_merge_dom_value' => '0', 'audited' => false, 'inline_edit' => true, 'reportable' => true, 'unified_search' => false, 'merge_filter' => 'disabled', 'len' => '255', 'size' => '20', 'id_name' => 'user_id2_c', 'ext2' => 'Users', 'module' => 'Users', 'rname' => 'name', 'quicksearch' => 'enabled', 'studio' => 'visible', ); После этих манипуляций делаю быстрое восстановление и смотрим что получилось с генерацией SQL-запроса: SELECT accounts.id , accounts.name , accounts.renewal_date_c , accounts.time_zone_c , accounts.tariff_c , accounts.assigned_user_id FROM accounts where ((accounts.id IN ('d924c25c-7ab0-25c8-f5cd-5a58c168620a'))) AND accounts.deleted=0 ORDER BY accounts.date_entered DESC Что уже то, что нам нужно...
  16. Всем привет! Давайте разберем ситуацию, когда нам необходимо выполнить единоразово какую то задачу в CRM-системе, и мы хотим, чтобы эту задачу выполнил планировщик. Подобное может потребоваться, например когда: Пользователь должен запустить "тяжелый" алгоритм, результаты которого не надо сразу получить на экране. Например: синхронизация CRM-системы с какой-либо другой системой (если честно, я этот механизм "подсмотрел" в библиотеки связки Mautic и SuiteCRM). То есть пользователь где то в системе запускает синхронизацию и продолжает далее работать в CRM-системе, а CRM-система в этот момент производит долгую ресурсоёмкую процедуру синхронизации. В теории можно настроить запуск планировщик SuiteCRM на запуск из под root. Таким образом в задачах, выполняемых в планировщике, появится полный доступ к всему серверу. Это может пригодиться для каких то специфических задач (все же обычно запускать crontab из под root не рекомендуется). В этом случае запуск задачи из планировщика позволит сделать что то такое, что выполнить простым запросом не получится (в частности в данный момент при написании статьи мне было необходимо менять chmod у файла с логами php, которые создавались из под root). Нужна некая отсрочка выполнения некой задачи. Наверное можно еще много чего напридумывать, но статья про запуск задач в планировщике, а не про зачем это надо ))) Итак. Общий посыл к тому, что у нас должно происходить: Есть некая задача в планировщике Эту задачу не видно в списке задач, она не предназначена для цикличного воспроизведения, только разовый запуск Мы должны иметь некий механизм, который указывает ЕДИНОРАЗОВО запустить нашу задачу в планировщике Планировщик, используя его встроенные механизмы, видит необходимость запуска задачи и выполняет это действие Теперь давайте перейдем к практике. Давайте начнем с механизма создания задачи в планировщике: require_once('modules/SchedulersJobs/SchedulersJob.php'); require_once("include/SugarQueue/SugarJobQueue.php"); // Создаем задачу в планировщике $job = new SchedulersJob(); $job->name = "postInstall"; $job->target = "class::postInstallCRMHosting"; $job->assigned_user_id = '1'; $job->execute_time =$GLOBALS['db']->convert($GLOBALS['timedate']->getNow()->modify("+10 seconds")->asDb(), 'datetime'); $jq = new SugarJobQueue(); $jq->submitJob($job); Этот блок создаст запись в таблице `job_queue` (да да, именно в той, куда валятся все логи выполняемых задач в планировщике). Мы создаем запись с названием "postInstall" и указываем, что должен выполниться класс postInstallCRMHosting. Время выполнения - через 10 секунд. На самом деле задачи в планировщике выполняются по тикам crontab, который запускается ежеминутно в начале минуты. По этому если надо "прям сейчас", то все равно будет в ближайшую минуту. Но пусть будет 10 секунд. Также, при необходимости, можно указать через час/день/год. Таким образом в ближайшие минуту система попытается запустить выполнение задачи в классе postInstallCRMHosting. Давайте добавим файл с таким классом. Проще всего (и думаю правильнее всего) добавлять задачи в папку /custom/Extension/modules/Schedulers/Ext/ScheduledTasks. Например, файл с моей задачей будет выглядеть примерно так: /custom/Extension/modules/Schedulers/Ext/ScheduledTasks/CRMHosting.postInstall.php <?php /** * Created by PhpStorm. * User: crmhosting * Date: 06.05.2018 * Time: 9:39 */ class postInstallCRMHosting implements RunnableSchedulerJob { public function setJob(SchedulersJob $job) { $this->job = $job; } public function run($job_data) { /************************************ * Назначаем права на файл с логами */ $log_file = "/var/log/php-fpm/www-error.log"; exec("chmod 0777 " . $log_file); /************************************/ return true; } } Таким образом SuiteCRM выполнит наш скрипт единоразово в планировщике. В списке задач планировщика ничего не появится лишнего. Можно подобным образом запускать задачу необходимое количество раз.
  17. Всем привет! Просто небольшая заметка, но может кому пригодиться... Иногда бывает, что в карточке модуля на панели необходимо разместить некую информацию, но Label у этой информации будет лишним. Например: список ссылок, относящихся к тому или иному контрагенту: Как вы видите, здесь имеет место быть отдельная панель, и в этой панели единственная переменная, которая содержит кучу ссылок. В DetailView это выглядит примерно так: 'lbl_editview_panel12' => array ( 0 => array ( 0 => array ( 'name' => 'custom_all_link', 'label' => '', ), ), ), Видите две точки? Это наш пустой Label. Выглядит не красиво, не правда ли? Можно конечно было бы добавить туда какой-нибудь LBL_, но он тут избыточен! Панель и так называется "Ссылки". И внутрь вставлять еще раз "Ссылки" или что то такое уже было бы лишним! Давайте просто уберем вообще блок, в котором находится Label: 'lbl_editview_panel12' => array ( 0 => array ( 0 => array ( 'name' => 'custom_all_link', 'label' => '', 'hideLabel' => true, ), ), ), Теперь наш блок примет вид: Как вы видите Label для нашего поля перестал вообще отображаться!
  18. Добрый день. В модуль сделки необходимо добавить дополнительное поле - Сумма оплаты по сделке, с выбором валюты, отличной от базовой валюты данной сделки. Возможна ли такая реализация в одном документе сделки? Спасибо.
  19. Так же можно сделать отдельный отчет по сотруднику , если их не 100 500 и вывести каждый из них в отдельном дашлете на главной или добавить на главной отдельную вкладку под это...
  20. там надо код править... когда нибудь руки дойдут... а может пофиксят....
  21. Была задача в CRM системе настроить быструю привязку контрагента к Обращению вводя в поле контрагент либо ИНН или Логин или название организации. Поиск по ресурсам вывел на статью http://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_6.5/Module_Framework/Metadata/Examples/Adding_QuickSearch_to_a_custom_field/index.html для тех кто будет организовать подобный поиск для кастомного поля будет очень полезна. Поискав сам механизм поиска контрагентов пришел к файлу: include/QuickSearchDefaults.php в котором описывается формат пориска function getQSAccount($nameKey, $idKey, $billingKey = null, $shippingKey = null, $additionalFields = null) { global $app_strings; $field_list = array('name', 'id'); //описывает поля передаваемые в браузер найденных записей $populate_list = array($nameKey, $idKey); if($billingKey != null) { $field_list = array_merge($field_list, array('billing_address_street', 'billing_address_city', 'billing_address_state', 'billing_address_postalcode', 'billing_address_country')); $populate_list = array_merge($populate_list, array($billingKey . "_address_street", $billingKey . "_address_city", $billingKey . "_address_state", $billingKey . "_address_postalcode", $billingKey . "_address_country")); } //if if($shippingKey != null) { $field_list = array_merge($field_list, array('shipping_address_street', 'shipping_address_city', 'shipping_address_state', 'shipping_address_postalcode', 'shipping_address_country')); $populate_list = array_merge($populate_list, array($shippingKey . "_address_street", $shippingKey . "_address_city", $shippingKey . "_address_state", $shippingKey . "_address_postalcode", $shippingKey . "_address_country")); } if(!empty($additionalFields) && is_array($additionalFields)) { $field_list = array_merge($field_list, array_keys($additionalFields)); $populate_list = array_merge($populate_list, array_values($additionalFields)); } $qsParent = array( 'form' => $this->form_name, 'method' => 'query', 'modules' => array('Accounts'), 'group' => 'or', 'field_list' => $field_list, 'populate_list' => $populate_list, 'conditions' => array(array('name'=>'name','op'=>'like_custom','end'=>'%','value'=>'')), // условия выборки нужной записи в нашем случае это выглядело бы array(array('name'=>'name','op'=>'like_custom','end'=>'%','value'=>'') or array('name'=>'inn_c','op'=>'like_custom','end'=>'%','value'=>'') or array('name'=>'login_c','op'=>'like_custom','end'=>'%','value'=>'')) 'required_list' => array($idKey), 'order' => 'name', 'limit' => '30', 'no_match_text' => $app_strings['ERR_SQS_NO_MATCH'] ); return $qsParent; } но изменив таким образом код мы поменяем условия поиска во всех полях Контрагент, а перед нами стоит задача поменять условие поиска только в обращениях... дальнейший поиска привел к тому что формат поиска указывается джаваскриптом на каждой странице для всех полей отдельно </script><script language="javascript">if(typeof sqs_objects == 'undefined'){var sqs_objects = new Array;}sqs_objects['EditView_account_name']={"form":"EditView","method":"query","modules":["Accounts"],"group":"or","field_list":["name","id"],"populate_list":["EditView_account_name","account_id"],"conditions":[{"name":"name","op":"like_custom","end":"%","value":""}],"required_list":["account_id"],"order":"name","limit":"30","no_match_text":"\u041d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043e"};sqs_objects['EditView_assigned_user_name']={"form":"EditView","method":"get_user_array","field_list":["user_name","id"],"populate_list":["assigned_user_name","assigned_user_id"],"required_list":["assigned_user_id"],"conditions":[{"name":"user_name","op":"like_custom","end":"%","value":""}],"limit":"30","no_match_text":"\u041d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043e"};</script> таким образом нам необходимо просто переписать данный код либо добавить ниже необходимый нам , что я и сделал: создал хук для вывод в конце страницы custom/Extension/modules/Cases/Ext/LogicHooks/quickSearch.php: /** * Created by PhpStorm. * User: SeedTeam * Date: 14.03.2018 * Time: 19:14 */ $hook_array['after_ui_frame'][] = Array( 99, 'быстрый поиск по ИНН, Логину, Названию', 'custom/modules/Cases/quicksearchaccount.php', 'QuickSearchCasesAccount', 'QuickSearchCasesAccountUpdateScript' ); далле сам файл обработчик custom/modules/Cases/quicksearchaccount.php который подключает tpl ку в конце страницы: /** * Created by PhpStorm. * User: SeedTeam * Date: 14.03.2018 * Time: 19:19 */ class QuickSearchCasesAccount { public function QuickSearchCasesAccountUpdateScript($event, $arguments) { $smarty = new Sugar_Smarty(); $smarty->display("custom/modules/Cases/tpl/QuickSearchCasesAcc.tpl"); } } и наконец сама tpl custom/modules/Cases/tpl/QuickSearchCasesAcc.tpl с джаваскриптом: <script type="text/javascript"> {literal} if(typeof sqs_objects == 'undefined'){var sqs_objects = new Array;}sqs_objects['EditView_account_name']={"form":"EditView","method":"query","modules":["Accounts"],"group":"or","field_list":["name","id","login_c","inn_c"],"populate_list":["EditView_account_name","account_id"],"conditions":[{"name":"name","op":"like_custom","end":"%","value":""}, {"name":"id","op":"like_custom","end":"%","value":""}, {"name":"login_c","op":"like_custom","end":"%","value":""}, {"name":"inn_c","op":"like_custom","end":"%","value":""}],"required_list":["account_id"],"order":["id"," ","id"],"limit":"30","no_match_text":"\u041d\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043e"}; {/literal} </script> теперь при вводе инн или логина происходит искомого контрагента, но у нас выводится только его название... а это не всегда удоблно, хотелось бы видеть и сам логин и ИНН для этого идем в файл modules/Home/QuickSearch.php который отвечает за выдачу json обекта в браузер и чуть чуть корректируем формат построения массива: $modifide = false; //создаем логическую переменную if(in_array('inn_c',$args['field_list'])) { $modifide = true; // присваиваем ей true если в формате полей присутствует требуемые нам поля в нашем случае inn_c, $args['field_list'] данный массив содержит переменные которые необходимы для передачи в браузер и которые мы указали в джаваскрипте } и изменяем значение name которое выводится в полое, следующим образом : if(($field == 'name') and ($modifide)) {$data['fields'][$i][$field]= "<p><b>Название:</b> ".$results[$i]->name."</p><p><b>Логин: </b> ".$results[$i]->login_c."</p><p><b>ИНН: </b> ".$results[$i]->inn_c."</p>";} получаем что в modules/Home/QuickSearch.php функция formatResults выглядит следующим образом protected function formatResults($results, $args) { global $sugar_config; $app_list_strings = null; $data['totalCount'] = count($results); $data['fields'] = array(); for ($i = 0; $i < count($results); $i++) { $data['fields'][$i] = array(); $data['fields'][$i]['module'] = $results[$i]->object_name; //C.L.: Bug 43395 - For Quicksearch, do not return values with salutation and title formatting if($results[$i] instanceof Person) { $results[$i]->createLocaleFormattedName = false; } $listData = $results[$i]->get_list_view_data(); //print_array($listData); //print_array($args['field_list']); $modifide = false; if(in_array('inn_c',$args['field_list'])) { $modifide = true; } //print_array($modifide); foreach ($args['field_list'] as $field) { if ($field == "user_hash") { continue; } // handle enums if ((isset($results[$i]->field_name_map[$field]['type']) && $results[$i]->field_name_map[$field]['type'] == 'enum') || (isset($results[$i]->field_name_map[$field]['custom_type']) && $results[$i]->field_name_map[$field]['custom_type'] == 'enum')) { // get fields to match enum vals if(empty($app_list_strings)) { if(isset($_SESSION['authenticated_user_language']) && $_SESSION['authenticated_user_language'] != '') $current_language = $_SESSION['authenticated_user_language']; else $current_language = $sugar_config['default_language']; $app_list_strings = return_app_list_strings_language($current_language); } // match enum vals to text vals in language pack for return if(!empty($app_list_strings[$results[$i]->field_name_map[$field]['options']])) { $results[$i]->$field = $app_list_strings[$results[$i]->field_name_map[$field]['options']][$results[$i]->$field]; } } if (isset($listData[$field])) { $data['fields'][$i][$field] = $listData[$field]; } else if (isset($results[$i]->$field)) { $data['fields'][$i][$field] = $results[$i]->$field; if(($field == 'name') and ($modifide)) {$data['fields'][$i][$field]= "<p><b>Название:</b> ".$results[$i]->name."</p><p><b>Логин: </b> ".$results[$i]->login_c."</p><p><b>ИНН: </b> ".$results[$i]->inn_c."</p>";} } else { $data['fields'][$i][$field] = ''; } } } if (is_array($data['fields'])) { foreach ($data['fields'] as $i => $recordIn) { if (!is_array($recordIn)) { continue; } foreach ($recordIn as $col => $dataIn) { if (!is_scalar($dataIn)) { continue; } $data['fields'][$i][$col] = html_entity_decode($dataIn, ENT_QUOTES, 'UTF-8'); } } } return $data; } теперь у нас выводися в необходимом формате, но и в поле добавляется значение с тегами... что ни есть гуд... теперь идем в файл и для автозаполнения поля добавим фильтр на js что бы удалять ненужные теги находим следующие строки и меняем на YAHOO.widget.AutoComplete.prototype._updateValue = function(elListItem) { if(!this.suppressInputUpdate) { var elTextbox = this._elTextbox; var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null; var sResultMatch = elListItem._sResultMatch; // Calculate the new value var sNewValue = ""; if(sDelimChar) { // Preserve selections from past queries sNewValue = this._sPastSelections; // Add new selection plus delimiter sNewValue += sResultMatch + sDelimChar; if(sDelimChar != " ") { sNewValue += " "; } } else { sNewValue = sResultMatch; } sNewValue = sNewValue.replace("<p><b>Название:</b> ",""); // удаляем заголовки вместе с тегами sNewValue = sNewValue.replace("</p><p><b>Логин: </b>","");// удаляем заголовки вместе с тегами sNewValue = sNewValue.replace("</p><p><b>ИНН: </b>","");// удаляем заголовки вместе с тегами sNewValue = sNewValue.replace("</p>","");// удаляем заголовки вместе с тегами // Update input field elTextbox.value = sNewValue; // Scroll to bottom of textarea if necessary if(elTextbox.type == "textarea") { elTextbox.scrollTop = elTextbox.scrollHeight; } // Move cursor to end var end = elTextbox.value.length; this._selectText(elTextbox,end,end); this._elCurListItem = elListItem; } }; в итоге получаем следующий вид авто заполнения ячейки с контрагентом :
  22. Всем привет! В последних на текущий момент (апрель 2018 года) версиях SuiteCRM (версии SuiteCRM 7.10.2 и 7.10.3) столкнулись с небольшой проблемой: куда то пропали боковые меню в модулях, доступных админу (которые находятся в админке). Например, когда смотрим на свой профиль: Начал искать что куда делось. Нашел не очень старые версии, в которых все работало. Состав меню определяется в том числе в файле Menu.php, лежащем в модуле. Там есть строки типа: $module_menu = Array(); if ($GLOBALS['current_user']->isAdminForModule('Users') ) { $module_menu = Array( Array("index.php?module=Users&action=EditView&return_module=Users&return_action=DetailView", $mod_strings['LNK_NEW_USER'], "Create"), Array("index.php?module=Users&action=EditView&usertype=group&return_module=Users&return_action=DetailView", $mod_strings['LNK_NEW_GROUP_USER'], "Create_Group_User") ); $module_menu[] = Array("index.php?module=Users&action=ListView&return_module=Users&return_action=DetailView", $mod_strings['LNK_USER_LIST'], "List"); $module_menu[] = Array("index.php?module=Import&action=Step1&import_module=Users&return_module=Users&return_action=index", $mod_strings['LNK_IMPORT_USERS'], "Import", 'Contacts'); } Ищем в /includes/ файлы, где активно используется переменная $module_menu. Внимание привлекла папка /include/MVC/ Сравниваем текущую версию этой папки с версией, где все работало: Смутило использование unset. Оказалось верно, что смутило. В общем убираем этот блок из файла SugarView.php, и вуа-ля:
  23. Стояла задача организовать поиск(фильтрацию на карточке списка) обращений по email связанных c ним Контактов , так же необходимо , что бы искались записи по частичному вхождению адреса, например почта pupok@mail.ru должна находиться при запросе: "pup" добавили поле mail_contact_init типа non-db выводим его в карточку расширенного поиска custom/modules/Cases/metadata/searchdefs.php или через студию, добавляем запрос по которому будит производиться поиск записи Обращения (в запросе нужно получить Id записи модуля по необходимым нам условиям) в файле custom/modules/Cases/metadata/SearchFields.php в моем случае это: 'mail_contact_init' => //Наименование поля array ( 'query_type' => 'format', // Тип запроса, важно указать format 'operator' => 'subquery', 'subquery' => 'SELECT cases.id FROM cases INNER JOIN contacts_cases ON cases.id IN (SELECT contacts_cases.case_id FROM contacts_cases WHERE contacts_cases.deleted = 0 AND contacts_cases.contact_id IN (SELECT email_addr_bean_rel.bean_id FROM email_addr_bean_rel WHERE email_addr_bean_rel.deleted = 0 AND email_addr_bean_rel.bean_module = \'Contacts\' AND email_addr_bean_rel.email_address_id IN (SELECT email_addresses.id FROM email_addresses WHERE email_addresses.deleted = 0 AND email_addresses.email_address LIKE "%{0}%" ) ) )', // вместо {0} suitecrm подставляет введенный запрос в данном поле 'db_field' => // Выборка записей основного модуля (обращения) идет по id array ( 0 => 'id', ), 'vname' => 'LBL_MAIL_CONTACT_INIT', ),
  24. Решил по одному проекту сделать сортировку записей в сабпанели. Добавил кнопки, повесил ajax-запросы при нажатии на ссылки. После сортировки надо перезагрузить сабпанель желательно без перезагрузки всей страницы: после успешной смены сортировки через ajax вызываем showSubPanel('themes',null,true); примерно так: function sortThemes(theme_id, order) { console.log('sortThemes - start'); console.log('theme_id = ', theme_id); console.log('order = ', order); var record = $('#formDetailView input[name=record]').val(); console.log('record = ', record); //Pass the properties to the controller function via ajax $.ajax({ type: "GET", url: "index.php?module=AOS_Quotes&action=setOrderThemes&record="+record+"&theme_id="+theme_id+"&sort_order="+order+"&to_pdf=true", dataType: 'json', success: function(data) { // On success generate the tasks for the chart console.log('data = ', data); showSubPanel('themes',null,true); } }); } Панель, которую перезагружаю, соответственно описывается как theme: $layout_defs["AOS_Quotes"]["subpanel_setup"]['themes'] = array ( 'order' => 100, 'module' => 'Themes', 'subpanel_name' => 'default', 'sort_order' => 'asc', 'sort_by' => 'id', 'title_key' => 'LBL_THEMES_SUBPANEL_TITLE', 'get_subpanel_data' => 'themes', 'top_buttons' => array ( 0 => array ( 'widget_class' => 'SubPanelTopButtonQuickCreate', ), 1 => array ( 'widget_class' => 'SubPanelTopSelectButton', 'mode' => 'MultiSelect', ), ), );
  25. Еще одно хорошее бесплатное решение отчетов и графиков - выгрузка данных в CSV. Вы эту "простынку" добавляете в виде первой вкладки в Excel-файл. А на остальных вкладках на основании данных из первой вкладки у вас уже строятся графики, диаграммы, формулы разные и прочее. Подобная схема неплохо сработала в одной компании, когда очень много логики в большом количестве отчетов нужно было. SuiteCRM просто не справлялся с логикой этой. А вот Excel вполне себе справлялся. Делаете заготовку, которая должна брать за основу данные в первой вкладке. А потом туда просто выгружаете нужные вам данные. Ну или как еще один вариант - интеграция с Google Sheets. Это из отчета данные сразу выгружать в таблицу на Google Drive. Есть такая доработка, наверное надо будет ее вытащить в виде модуля....
  26. Добрый день! Модуль с отчетами иногда глючит. Из версии в версию они там иногда фиксят, но иногда приходится подправлять и переделывать. Именно графики лично в моей практике заказывают и используют не очень часто. Как правило просто цифровых данных в таблицах вроде хватает. Сам пробовал для своих нужд использовать графики, но тоже что то не получалось, уже правда не помню что. Богатого опыта получения стандартных графиков к сожалению нет. Мне иногда проще запилить график подключив Google Charts, нежели пытаться разобраться что там в этих отчетах понапридумано. Функционал достаточно сложный, и не всегда сходу разобраться получается. Что там в конкретно вашем случае и вашей версии SuiteCRM - хз. Наверное, если вы по всякому попробовали и не получилось, значит или и правда глючит или не предусмотрено изначально.
  27. Добрый день! Указанное вами поле может содержать ссылку внешний документ, ассоциированный с записью в модуле "Документы". В SugarCRM Pro была (и думаю есть) возможность не только загружать файлы в саму CRM, но и создавать записи по документам, находящимся на других сервисах (Google Drive и так далее). Так вот это поле содержит id документа на внешнем сервисе, до которого можно добраться по API этого сервиса. В интерфейсе SugarCRM CE а далее в SuiteCRM я не встречал возможности указать расположение файла во внешнем сервисе (в прошке была возможность прямо в CRM-системе указать что файл лежит в Google Drive и можно было загрузить туда файл или указать уже ранее загруженный). Но, однако, в SuiteCRM часть этого функционала осталась. Во всяком случае поле есть (doc_id) и даже есть функции, которые при беглом осмотре вроде как ссылаются на некую возможность использования API внешних систем. Хз как с этим раборать, может на досуге разберусь... Попробуйте в "Источник" указать "Sugar", а в "Источник документа (ID)" указать ссылку на файл в гугль драйве. Как стандартными средствами загрузить кучу файлов в систему хз. У каждого документа (записи в модуле Documents) должна быть связанная запись в модуле DOcumentRevisions. и ID этой записи = название файла в папке upload То есть заходите в документ, смотрите ID записи в панели "История" (или как там она может называться в вашей локализации), и вот нужно чтобы в папке upload лежал файл, название которого = этому ID (без расширений и прочего) как автоматизировать этот процесс? наверное только непостредственно через программирование
  1. Load more activity
×