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

SpravkaCRM.ru

Administrators
  • Content count

    290
  • Joined

  • Last visited

  • Days Won

    7

SpravkaCRM.ru last won the day on February 21

SpravkaCRM.ru had the most liked content!

Community Reputation

16 Good

About SpravkaCRM.ru

  • Rank
    Advanced Member

Recent Profile Visitors

1932 profile views
  1. Добрый день! На скриншете не доработанная версия отчета по плановой загрузке сотрудника. Это не какой то отдельный модуль. Это CRM-система для разработчика: то, что я сам себе для своей компании пилю. Фактически сейчас там отображается плановое количество рабочих часов по дням для текущего сотрудника. Уже не помню что хотел вложить в этот отчет. Сейчас с точки зрения контроля работы сотрудников более удобным образом можно наблюдать следующую информацию: Слева распределение по сотрудникам сколько фактически отработано часов в день. Справа распределение фиктического относительно планового кол-ва часов. Тут календарь рабочих нагрузок для определения объема занятости сотрудника. Собирает в себе плановые нагрузки и фактически назначенные. Тут сублимированная информация по сотруднику из разных областей его деятельности. А тут у нас расчетная ведомость, где список фактически выполненных работ и суммарная стоимость к выплате сотрудникам в зависимости от выполненных работ. Как видите информацию в можно очень по разному представить к выводу. Я отталкивался от наиболее удобного мне формата для решения той или иной задачи. Вряд ли подобные отчеты можно найти где то уже готовыми, уж больно это все индивидуально. Потратив немного бюджета можно запилить именно то, что Вам будет удобно. Это не очень дорого, но очень удобно использовать.
  2. Если говорить про каждого отдельного сотрудника, то тогда уж можно лучше сделать графики прямо в карточке сотрудника. Открыл сотрудника - там панель с графиком. Типа такого: И не нужно плодить кучу дашлетов на главной.
  3. На сколько я помню логика работы SuiteCRM подразумевает возможность работы с несколькими полями с типом "валюта", но валюта будет одна. То есть все значения будут в рублях, долларах, или любой другой присутствующей в CRM валюте. Но хранение нескольких полей в разной валюте в рамках одной карточки не предусмотрено. Обусловлено тем, что все добавленные поля ссылаются на едиснтвенное поле, которое будет обозначать код валюты. То есть добавляем первое поле с валютой в карточку: формируется спец.поле, хранящее код валюты. Добавляем второе поле - оно будет получать код валюты из первого поля. И так далее. Сделать мультивалютность внутри одной карты можно, но это уже программировать.
  4. Всем привет! Столкнулся только что с ситуацией, когда в карточку Контрагента добавили поле с пользователем (связанная запись из 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 Что уже то, что нам нужно...
  5. Всем привет! Давайте разберем ситуацию, когда нам необходимо выполнить единоразово какую то задачу в 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 выполнит наш скрипт единоразово в планировщике. В списке задач планировщика ничего не появится лишнего. Можно подобным образом запускать задачу необходимое количество раз.
  6. Всем привет! Просто небольшая заметка, но может кому пригодиться... Иногда бывает, что в карточке модуля на панели необходимо разместить некую информацию, но 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 для нашего поля перестал вообще отображаться!
  7. Всем привет! В последних на текущий момент (апрель 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, и вуа-ля:
  8. Решил по одному проекту сделать сортировку записей в сабпанели. Добавил кнопки, повесил 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', ), ), );
  9. Еще одно хорошее бесплатное решение отчетов и графиков - выгрузка данных в CSV. Вы эту "простынку" добавляете в виде первой вкладки в Excel-файл. А на остальных вкладках на основании данных из первой вкладки у вас уже строятся графики, диаграммы, формулы разные и прочее. Подобная схема неплохо сработала в одной компании, когда очень много логики в большом количестве отчетов нужно было. SuiteCRM просто не справлялся с логикой этой. А вот Excel вполне себе справлялся. Делаете заготовку, которая должна брать за основу данные в первой вкладке. А потом туда просто выгружаете нужные вам данные. Ну или как еще один вариант - интеграция с Google Sheets. Это из отчета данные сразу выгружать в таблицу на Google Drive. Есть такая доработка, наверное надо будет ее вытащить в виде модуля....
  10. Добрый день! Модуль с отчетами иногда глючит. Из версии в версию они там иногда фиксят, но иногда приходится подправлять и переделывать. Именно графики лично в моей практике заказывают и используют не очень часто. Как правило просто цифровых данных в таблицах вроде хватает. Сам пробовал для своих нужд использовать графики, но тоже что то не получалось, уже правда не помню что. Богатого опыта получения стандартных графиков к сожалению нет. Мне иногда проще запилить график подключив Google Charts, нежели пытаться разобраться что там в этих отчетах понапридумано. Функционал достаточно сложный, и не всегда сходу разобраться получается. Что там в конкретно вашем случае и вашей версии SuiteCRM - хз. Наверное, если вы по всякому попробовали и не получилось, значит или и правда глючит или не предусмотрено изначально.
  11. Добрый день! Указанное вами поле может содержать ссылку внешний документ, ассоциированный с записью в модуле "Документы". В 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 (без расширений и прочего) как автоматизировать этот процесс? наверное только непостредственно через программирование
  12. Добрый день! Попробуйте сделать восстановление ролей в админке в пункте Восстановление. Эта штука добавляет новые модули в кеш доступных действий. Без этого восстановления новый модуль будет доступен (скорее всего) только админам. Если не получится будем дальше думать.
  13. Всем привет! Хочу поделиться методом, который позволяет добавлять фильтры в список записей модуля, когда надо найти все записи, у которых есть связи с определенной записью из другого модуля. Сразу оговорюсь, что SuiteCRM позволяет выполнять подобный поиск стандартным способом, когда ищем в связке один-ко-многим. Например, должны найти все Контакты, принадлежащие определенному Контрагенту. Подобные связи можно видеть в настройках шаблона поиска в студии, и настраиваются они без вмешательства программиста путем работы с интерфейсом. Мы же с вами поговорим о ситуациях, когда надо найти все записи модуля, который связан с другим модулем произвольным образом (возможно связью многие-ко-многим, а возможно вообще какими то сложными логическими схемами, главное чтобы эту связь можно было уместить в одном SQL-запросе). Чтобы было понятней давайте рассмотрим пример: Допустим, нам надо в модуль "Документы" в списке записей добавить фильтр, в котором мы сможем найти все документы, у которых за контрагента отвечает определенный пользователь. Таким образом: Мы находимся в модуле Документы Нам надо отфильтровать Документы по пользователю (модуль Users) У каждого Документа есть связь с Контрагентами многие-ко-многим (в карточке Документа сабпанель Контрагенты) В Контрагенте есть связь с Пользователем через поле assigned_user_id Нам надо получить список Документов по фильтру Стандартным способом без программирования мы сможем реализовать подобную задачу только используя модуль "Отчёты". Да. Так тоже можно. Но не удобно. Хочется находиться в модуле Документы, а не "шарится" по другим модулям/отчётам. Далее я тезисно опишу что нам понадобиться сделать, а потом покажу, как это будет выглядеть в файлах. Итак: Хотя механизм формирования фильтров и позволяет в них добавлять разную самописную отсебятину, но наиболее удобным средством, по результатам многочисленных опытов, стало добавление фильтра в виде поля модуля. То есть мы добавляем поле в модуль Documents, которое будет собой представлять как бы ссылку на модуль Users. А чтобы это поле не вносило изменения в структуру таблицы с документами, ему (этому полю) необходимо указать характеристику 'source' => 'non-db'. После выполнения быстрого восстановления с вновь добавленным полем модуля можно работать как с обычным полем: заходим в студию, и в модуле Документы помещаем его в макет поиска туда, куда удобно (имеет смысл работать только с макетом поиска, в других макетах это поле не будет работать, потому что не хранит данные в базе). В настройках поиска вручную задаем SQL-запрос, который должен будет выполниться, если мы введем какое то значение в фильтре в нашем поле. Ну а теперь по конкретике: Создаем файл с описанием добавляемого поля: custom/Extension/modules/Documents/Ext/Vardefs/account_assigned_user.php: <?php $dictionary['Document']['fields']['account_assigned_user'] = array ( 'required' => false, 'source' => 'non-db', 'name' => 'account_assigned_user', 'vname' => 'LBL_ACCOUNT_ASSIGNED_USER', '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' => 'id', 'ext2' => 'Users', 'module' => 'Users', 'rname' => 'name', 'quicksearch' => 'enabled', 'studio' => 'visible', ); Хотел бы обратить ваше внимание на следующу строку: 'id_name' => 'id', Вообще подобное поле положено создавать в паре с полем, которое будет хранить непосредственно id связанной записи в базе данных. Это когда мы хотим добавить связь один-ко-многим. И в параметре 'id_name' нужно было бы указать название подобного поля. Но в нашем случае в таблице с документами нам не надо ничего хранить. Все необходимые данные мы будем получать из других таблиц. По этому в параметре 'id_name' мы указываем значение 'id' (если не вдаваться сильно в детали, то неуказать ничего мы тоже не можем, потому что этот параметр потом будет учавствовать в SQL-запросе в виде `documents`.`<id_name>`, и если ничего не указать, то будет SQL-ошибка). Создаем файл с локализацией создаваемого поля: custom/Extension/modules/Documents/Ext/Language/ru_RU.account_assigned_user.php <?php $mod_strings['LBL_ACCOUNT_ASSIGNED_USER'] = 'Ответственный за Контрагента'; Выполняем быстрое восстановление. После этого можно идти в Студию и настраивать внешний вид поиска: Переходим в модуль "Документы, и видим, что поле появилось в списке фильтров (верстка кривая, но сейчас не о этом): Нам необходимо немного модернизировать отображение этого поля. Дело в том, что если мы выбор пользователя оставим в текущем виде, то в наш SQL-запрос будет попадать имя пользователя. А это немного не то, что надо. Нам надо, чтобы в SQL-запрос попадал ID выбранного поля. Для этого мы поле переделываем в выпадающий список: открываем файл custom/modules/Documents/metadata/searchdefs.php, находим в нем секцию account_assigned_user, и меняем ее на 'account_assigned_user' => array ( 'type' => 'enum', 'studio' => 'visible', 'label' => 'LBL_ACCOUNT_ASSIGNED_USER', 'id' => 'ID', 'link' => true, 'width' => '10%', 'default' => true, 'name' => 'account_assigned_user', 'function' => array ( 'name' => 'get_user_array', 'params' => array ( 0 => false, ), ), ), После данной манипуляции фильтр по пользователю должен выглядеть как список пользователей (там опять кривая верстка, но мы опять не про нее): Если сейчас выбрать в этом поле пользователя, и нажать кнопку "Найти" в поиске, то мы ничего не получим, потому что еще не настроили механизм поиска нужных нам записей. А для этого переходим к файлу custom/modules/Documents/metadata/SearchFields.php (он как раз должен был создастся после манипуляций в студии), и вставляем в него блок такого содержания: 'account_assigned_user' => array ( 'query_type' => 'format', 'operator' => 'subquery', 'subquery' => ' SELECT `documents`.`id` FROM `documents` INNER JOIN `documents_accounts` ON `documents_accounts`.`document_id` = `documents`.`id` AND `documents_accounts`.`deleted` = 0 INNER JOIN `accounts` ON `accounts`.`id` = `documents_accounts`.`account_id` AND `accounts`.`deleted` = 0 WHERE `documents`.`deleted` = 0 AND `accounts`.`assigned_user_id` = "{0}" ', 'db_field' => array ( 0 => 'documents.id', ), ), Как видно (надеюсь), данный код ищет все id в таблице documents, которые связаны с контрагентом, у которого в assigned_user_id указано выбранное нами значение. А вот такой SQL-запрос соберется в итоге при попытке воспользоваться данным фильтром (указаю юзера с айди = 1 - админ): SELECT documents.id , documents.assigned_user_id , documents.id , documents.document_name , documents.document_revision_id , documents.doc_id , documents.doc_type , documents.doc_url , documents.category_id , documents.subcategory_id , documents.exp_date , jt0.user_name assigned_user_name , jt0.created_by assigned_user_name_owner , 'Users' assigned_user_name_mod, documents.date_entered , LTRIM(RTRIM(CONCAT(IFNULL(jt1.first_name,''),' ',IFNULL(jt1.last_name,'')))) account_assigned_user , documents.created_by FROM documents LEFT JOIN users jt0 ON documents.assigned_user_id=jt0.id AND jt0.deleted=0 AND jt0.deleted=0 LEFT JOIN users jt1 ON documents.id = jt1.id AND jt1.deleted=0 where ((documents.id IN ( SELECT `documents`.`id` FROM `documents` INNER JOIN `documents_accounts` ON `documents_accounts`.`document_id` = `documents`.`id` AND `documents_accounts`.`deleted` = 0 INNER JOIN `accounts` ON `accounts`.`id` = `documents_accounts`.`account_id` AND `accounts`.`deleted` = 0 WHERE `documents`.`deleted` = 0 AND `accounts`.`assigned_user_id` = "1" ))) AND documents.deleted=0 ORDER BY documents.document_name ASC Подобным образом можно заложить любую логику поиска нужной записи. Пользуйтесь )))
  14. А не пробовал завести учетку в Исходящих в админке? Или в настройках в модуле Email указать учетную запись... при отправке письма на сколько я помню можно указать через какую исходящую отправлять...
×