Jump to content

SpravkaCRM.ru

Administrators
  • Posts

    337
  • Joined

  • Last visited

  • Days Won

    7

SpravkaCRM.ru last won the day on February 21 2018

SpravkaCRM.ru had the most liked content!

Recent Profile Visitors

10255 profile views

SpravkaCRM.ru's Achievements

Explorer

Explorer (4/14)

  • Dedicated Rare
  • First Post Rare
  • Collaborator Rare
  • Conversation Starter Rare
  • Week One Done Rare

Recent Badges

19

Reputation

  1. Всем привет! делюсь находками... может конечно это уже не ново, но мне вот прям "зашло"... Тестирование. То самое, многострадальное, которое мы пока так и не затащим толком. А именно интеграционное тестирование. Postman наверное уже все юзают для отладки API. А вот что в постман есть тесты - узнал только сегодня. Вернее что то такое слышал. Но реально попробовал сегодня. И мне это очень понравилось. Делюсь. В общем, есть некий сервис, который умеет принимать API читая JSON, переданный в запросе. Что то типа такого: Отдаем содержимое в JSON, получаем в JSON. Так вот там в Postman есть вкладочка tests: В ней можно написать простенький скриптик, который будет выполнять анализ ответа и что то с этим делать: // Проверка ответа pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); // Проверка длительности открытия pm.test("Response time is less than 1.5s", function () { pm.expect(pm.response.responseTime).to.be.below(1500); }); // Проверка структуры var schema = { "type": "object", "properties": { "response": { "type": "object", "properties": { "text": {"type":"string"}, "tts": {"type":"string"}, "buttons": { "type":"array", "items": { "type":"object", "required":["title"], "properties": { "title": {"type":"string"} } } }, "end_session": {"type":"boolean"} }, "required":["text","tts","buttons","end_session"] }, "version": {"type": "string"} }, "required":["response","version"] }; pm.test('Schema is valid', function() { pm.response.to.have.jsonSchema(schema); }); // Проверка содержимого pm.test('Check answer content', function() { var jsonData = pm.response.json(); // Проверка кнопок pm.expect(jsonData.response.buttons.length).to.eql(4); pm.expect(jsonData.response.buttons[0].title).to.eql("Русский"); pm.expect(jsonData.response.buttons[1].title).to.eql("Помощь"); pm.expect(jsonData.response.buttons[2].title).to.eql("English"); pm.expect(jsonData.response.buttons[3].title).to.eql("Help"); // Проверка наличия текста pm.expect(jsonData.response.text).not.eql(null); pm.expect(jsonData.response.text).not.eql(""); pm.expect(jsonData.response.tts).not.eql(null); pm.expect(jsonData.response.tts).not.eql(""); // Проверка end_session pm.expect(jsonData.response.end_session).to.eql(false); // Проверка version pm.expect(jsonData.version).to.eql("1.0"); }); Данный скрипт обрабатывает именно текущий запрос. Можно к каждому запросу написать по тестовому скрипту, который будет проверять код ответа сервера, время, за которое выполнился скрипт, валидировать схему JSON-ответа, определять, чтобы в ответе были или наоборот не были те или иные значения. В общем по полной управлять тем, что отдает сервер, и что бы вы хотели проверить в этом ответе. Под тестирование сервиса я создал новую коллекцию. Всю коллекцию можно запустить на тестирование. Тогда все папки последовательно и все запросы в папках последовательно запустятся и запустятся тесты, если они написаны. Запускаем тест всей коллекции из меню коллекции нажав пункт "Run collection": Можно почекать какие то отдельные запросы, или оставить как есть, чтобы проверило все запросы. Нажимаем кнопку и понеслась ... Через несколько секунд результаты: 75 тестов успешно и 1 не успешный. Можно глянуть на неуспешные: В частности здесь ошибка в тестировании времени отклика в запросе "Неизвестный интент [intents: hello]": я поставил, что должно выполняться не более полутора секунд (1500мс), а по факту сервер отвечал 5 секунд. Вот и среагировало... В общем то тестов получается достаточно много. Нужно немного вспомогательных функций на backend, чтобы формировало и подтирало тестовые данные. Чтобы прям без заглушек "по живому" можно было проверять. Но результатами я очень доволен! Если нужно, чтобы ваш сервис имел низкий уровень забагованности, то вот пожалуйста бесплатный классный иснтруемент, как можно одной кнопкой проверять самые разные ситуации. Один раз написали, потом всю жизнь пользуемся. Сразу видим где что мы починили, что поломало нам в другом месте. P.S. Ну и небольшой "минус" - судя по всему эта штука работает в ручном режиме. Как ее запускать автоматически при поставке кода на прод и как включить в цикл проверок в CI/CD - я пока не понял. Возможно как то можно, но надо разбираться еще с этим. Так что данная возможность пока наверное для именно разработки. А не для полноценной стадии при поставке кода на прод-сервера. Но мне сейчас, на данном этапе жизни проекта, этого более чем "за глаза".
  2. Зависит от того, как же все же было добавлено поле со связью. Если мы добавили связь многие-ко-многим, то поля не появляются. появляются сабпанели у обоих модулей Если мы добавили связь один-ко-многим, то у одного модуля появится поле, а у другого - сабпанель Если мы добавили связь один-к-одному, то в обоих модулях появятся поля и во всех этих случаях связь будет организована через промежуточную таблицу, которая будет создана Но есть еще случаи, когда мы добавляем поле "Относится к" или как то так называется. В этом случае в модуль добавляется поле c ID записи, которую мы хотим добавить.
  3. Код не смотрел, но попробуй сопоставить список с полями, по которым есть индексы. В механизме импорта точно по этому признакую появлялись поля. Возможно здесь тоже так же. Причем индексы не в ручную в БД проставленные, а описанные через vardefs.php если не в этом дело, то пиши, посмотрю по коду ...
  4. Всем привет! Жили - не тужили в Trello выстроив в нашей CRM-системе интеграцию с ней. Канбан-доска с картами и обсуждение задач - это Trello. Клиенты, Проекты, Бухгалтерия, фиксация времени - это все в CRM. Наколхожено было много всего в этой обвязке: Конвертация карт Trello в Проектные задачи в CRM; Конвертация Проектных задач в CRM в карты в Trello; Реакция на смену Labels в Trello; реакция на смену Листов у карточки в Trello; всякие Webhooks и прочая автоматизация в виде интеграции с Camunda BPM и выстраивании на ней рабочего сценария. Но в один прекрасный момент Trello поменяли политику предоставления доступа сделав обязательным платные услуги при превышении некоторых лимитов. Эти лимиты нам конечно "не зашли" и уж коль все равно платить, мы начали смотреть по предложениям "на рынки" и на что же перейти или остаться в Trello с оплатой. У ряда наших партнеров и клиентов в работе используется Jira, и мы подумали "а чё бы не". По деньгам получается вроде нормально: до 10 человек (а это пока наш случай) - это 10 баксов в месяц за всех, команда более 10 человек - по 7 баксов за человека в месяц. В целом норм. И вот идем мы в процедуру интеграции нашей CRM-системы с Jira полным ходом. Про что и хотелось бы написать. Итак, по пунктам: 1. Да, есть мнение, что Jira вполне самодостаточный сервис для команд разработки и интегрировать ее с CRM в целом не обязательно. Но я пока не согласен с этим мнением. Помимо стандартных чисто CRM-ных фичей типа Договора/Счета/Акты не совсем понятно как фиксировать время, получаемое из приложения учета времени. Да, я видел, что в картах можно сделать учет времени. Но 1 - по карте может заниматься несколько человек, а время надо считать по каждому отдельно, т.к. на этом времени базируется зарплата сотрудников 2 - а кто же будет эти подсчеты делать? сколько зарплата, сколько прибыль ... возможно это где то есть в Jira, но я пока не понял где. В общем от CMR пока не отказываемся полностью оставляя ее неким Backend. 2. Что или не понравилось или вызывает вопросы (но возможно просто еще не разобрались как это сделать): Если кастомное поле добавлено только в режим просмотра задачи, но не добавлено в режим редактирования задачи, то это поле не доступно для изменения через API. Пишет, что поле не найдено. Делается это для того, чтобы в карте отобразить некие поля, которые должен менять только Bacckend (CRM), но не должен менять сотрудник. Например: время, затраченное на работу по задаче. Не понятно как это обойти при помощи Jira Если мы через API что то поменяли в карточке, то в браузере это обновляется как то не уверенно: или не видно пока не обновили страницу, или обновляется с задержкой, или обновляется частями и не все. Вот чем очень нравилось Trello, так это своей молниеносностью обновления данных, будь то браузер или мобильное приложение. Кто бы где бы как бы что не поменял - изменения сразу на всех клиентах применяются. Jira - сделай так же! Если создаем проект новый, то для него создается куча всякого хлама, типа бизнес-процессов, схем отображения, схем полей и прочего. Все равно я же этот новый проект назначу на какой то свой заранее добавленный шаблон. А эти созданные дефолтные записи приходится долго и нудно удалять. 3. Что понравилось: А вот если честно пока не понятно что прям лучше. Да, много разных фичей для разработчиков. Какие то предустановленные отчеты, фильтры, инструменты. Это однозначно круче, чем голый Trello. Но вот относительно связки Trello+CRM, при учете что мы команда, которая пилит ту самую CRM внедряя ее у своих клиентов, то есть в CRM - любой каприз, вот тут нет такого "Вау-эффекта". Ну по крайней мере я его пока не испытал. Опять же возможно пока еще не понял всех особенностей и возможностей Jira. Наверное этот пункт не в плюс Jira как продукту, а в плюс переезда на Jira. Связка Trello + CRM сущствовала много лет, постепенно обрастала всевозможными фичами, кастылями, доработками. И, как следствие, стала эпизодически глючить. И достаточно сложно стало этот зоопарк обслуживать и допиливать. Для Jira же начали практически "с чистого листа" поменяв концепцию: если в связке Trello+CRM и Trello и CRM были равноправными участниками процесса, и была необходимость двухсторонней синхронизации карт с задачами + приложение учета времени опиралось именно на Проектные задачи в CRM для построения списка задач тому или иному сотруднику, то при интеграции с Jira мы пошли по пути, что Jira является источником задач и актуализации статусов задач. Это сразу упростило синхронизацию, ведь по сути она стала однонаправленной. Процесс интеграции Jira и CRM пока все еще идет, и мы обязательно отпишемся по факту что же у нас получилось. Ну а в качестве затравки вот пример BPMN-схемы обработки Webhooks из Jira:
  5. Добрый день! Общий принцип: исключаем файлы, которые являются автогенерируемыми (папка cache, папки Ext в /custom/modules) и так далее. Так же исключаем загружаемые файлы, историю изменений metadata-файлов, индексы поиска, служебные файлы разных GUI и прочее. Я сталкивался с тем, что некоторые системные администраторы пытаются засунуть в .gitignore core-файлы типа того, что находится в папках Zend, XTemplate и так далее обосновывая тем, что не надо эти файлы править, значит и в гите им делать нечего. Я против такого подхода. Считаю, что в git должны быть файлы, достаточные для корректного запуска SuiteCRM в том месте, где его развернули из git-репозитория. Вот файл .gitignore одного из проектов: *.log .DS_Store Thumbs.db /cache/* /.idea/* /config.php /config_override.php /tmp/ /upload/ /custom/screenshots/ /test_*.php /custom/blowfish/* /custom/history/* /custom/modulebuilder/* /custom/working/* /custom/application/Ext/ /custom/modules/*/Ext/ /custom/modules/unified_search_modules_display.php /modules/AOD_Index/Index/* /.htaccess /custom/client_secret.json /custom/appsactivity-php-quickstart.json Такой подход комфортно работает если изменения структуры модулей делать не в студии, а вручную. Поля, добавляемые в студии, вносят meta-данные (vardef) в таблицу в БД, а она через git не обновляется. А если будете расширять список полей вручную, то вполне сможете работать через git запуская быстрое восстановление. Не претендую на супер-универсальность и что это решение самое лучшее, но вот одна из реализаций, которой пользуемся наиболее часто: Ветки + сервера: В git-репозитории создаем ветку master. Эта ветка содержит состояние файлов на PROD-сервере В git-репозитории создаем ветку test. Эта ветка содержит состояние файлов на TEST-сервере В git-репозитории создаем произвольное кол-во веток с конкретными задачами, находящимися в работе (по 1-ой ветке на каждую задачу) Процесс работы: Берем очередную задачу в работу: из master делаем ветку с задачей. работаем в ней. Как правило это удаленная ветка, так как в процессе работы над задачей часто работают несколько человек. После выполнения задачи она окончательно пушится на сервер. Делается мерж ветки test и ветки с задачей. На тестовом сервере делаем pull ветки test и проверяем работу. Если все норм, то ветка с задачей мержится с веткой master Если что то не норм, то ветка с задачей просто уходит в доработку. Ветки test и master никогда не мержатся. Все происходит именно путем мержа ветки с задачей. P.S. надо видимо уведомления где то тут найти чтобы на почту приходили... а то задача то наверное уже не актуальна ... больше для тех, кто по поиску потом придет...
  6. Тут поднакопилось условных "ошибок" в написании кода. Это не синтаксические или логические ошибки, а именно внешний вид, читабельность и вообще ... Итак, продолжим! Всегда добавлять локализацию для добавляемых полей в модуле! Речь идет про ситуации, когда вы для реализации своих задач добавляете поле в тот или иной модуль и в параметре vname указываете лейбл добавляемого поля. Например так: $dictionary['Account']['fields']['custom_all_link'] = array ( 'name' => 'custom_all_link', 'vname' => 'LBL_CUSTOM_ALL_LINK', 'type' => 'varchar', 'inline_edit' => '', 'source' => 'non-db', 'studio' => 'visible', ); В данном примере мы в модуль Accounts добавляем некое поле custom_all_link, которое не будет хранить значение в базе данных, а скорее всего будет неким калькулируемым "на лету" ('source' => 'non-db'). Так вот если нигде в языковых файлах не описать связь $mod_strings['LBL_CUSTOM_ALL_LINK'] = 'Все ссылки'; то мало того, что мы увидим LBL_CUSTOM_ALL_LINK в месте отображения этого поля, но мы также увидим этот лейбл и в технических местах типа в списке полей в Отчетах или в Студии: И да! Это касается не только полей, но и связей: $dictionary['Account']['fields']['accounts_audit_link'] = array ( 'name' => 'accounts_audit_link', 'type' => 'link', 'relationship' => 'accounts_audit', 'vname' => 'LBL_ACCOUNTS_AUDIT', 'link_type' => 'one', 'module' => 'AccountsAudit', 'bean_name' => 'AccountsAudit', 'source' => 'non-db', ); Нам обязательно надо в файле локализации определить ключ LBL_ACCOUNTS_AUDIT! Использование функции strtotime(): Допустим вам надо получить дату меньше текущей на 3 дня. Вот такой подход "кривой": $old_date = time() - 259200; Сходу не понятно сколько это в днях/часах/минутах. А если еще и комментариев нет - считай совсем фигня фигней. Не удобно и не понятно. Вот так чуть лучше: $old_date = time() - 60*60*24*3; Ну хотя бы наглядно видно что тут происходит. А моя рекомендация: функция strtotime(): $old_date = strtotime("-3 day"); Или, если надо относительно какой то даты, а не текущего времени: $old_time = strtotime("-3 day", strtotime($date)); Любые переменные, перед их использованием, необходимо инициализировать! Если в коде где то планируется использовать переменные, которые будут дополняться данными в процессе работы текущего скрипта (наполнение списков, конкатенация строк), то в обязательном порядке необходимо вначале работы скрипта инициализировать эти переменные. Например: if(!empty($massiv_report[$i]['our_offer_percent_total'])) { $predlogenie_ot_totala[] = $massiv_report[$i]['our_offer_percent_total']; } Перед тем, как выполнить этот блок, в скрипте обязательно необходимо как то создать или получить переменную $predlogenie_ot_totala: или она в параметре была передана в функцию, или что то типа: $predlogenie_ot_totala = []; и ровным счетом наоборот должно происходить с объявлением новых LBL_xxx в модулях (в следующем пункте) Объявление LBL_xxx: $mod_strings = array ( 'LBL_SITE_ID' => 'id на сайте', 'LBL_DESCRIPTION' => 'Текущие договоренности', 'LBL_TYPE_OF_OWNERSHIP' => 'Форма собственности', 'LBL_URIST_NAME' => 'Полное наименование', 'LBL_INN' => 'ИНН', 'LBL_KPP' => 'КПП', 'LBL_BANK' => 'Банк', 'LBL_BIK' => 'БИК', 'LBL_KS' => 'Кор. Счет', 'LBL_RS' => 'Р. Счет', 'LBL_POSITION_DIR' => 'Должность руководителя', 'LBL_DIR' => 'ФИО Руководителя', 'LBL_CONTAKT_FIO_REAL' => 'ФИО контактного лица', 'LBL_POSITION' => 'Должность', 'LBL_PHONE_OFFICE' => 'Телефон', 'LBL_EMAIL' => 'E-mail:', ); Так нельзя делать в кастомных разделах добавления новых лейблов! Оно собой затрет все что там остальное будет! Надо добавлять примерно так: $mod_strings['LBL_SITE_ID'] = 'id на сайте'; $mod_strings['LBL_DESCRIPTION'] = 'Текущие договоренности'; $mod_strings['LBL_TYPE_OF_OWNERSHIP'] = 'Форма собственности'; $mod_strings['LBL_URIST_NAME'] = 'Полное наименование'; $mod_strings['LBL_INN'] = 'ИНН'; $mod_strings['LBL_KPP'] = 'КПП'; $mod_strings['LBL_BANK'] = 'Банк'; $mod_strings['LBL_BIK'] = 'БИК'; $mod_strings['LBL_KS'] = 'Кор. Счет'; $mod_strings['LBL_RS'] = 'Р. Счет'; $mod_strings['LBL_POSITION_DIR'] = 'Должность руководителя'; $mod_strings['LBL_DIR'] = 'ФИО Руководителя'; $mod_strings['LBL_CONTAKT_FIO_REAL'] = 'ФИО контактного лица'; $mod_strings['LBL_POSITION'] = 'Должность'; $mod_strings['LBL_PHONE_OFFICE'] = 'Телефон'; $mod_strings['LBL_EMAIL'] = 'E-mail'; Переменные внутри SQL-запросов: SQL-запрос изначально просто строка. И тут действуют все те правила, что можно отнести к программированию строковых переменных. Я про ситуации типа такой: $sql="SELECT `id`, `user_id7_c` FROM `accounts` WHERE `avance_date` = '$day_control' AND `deleted`='0' "; Здесь переменная $day_control вставлена не корректно. Да, возможно это будет работать. Возможно даже чаше будет работать, чем не работать. Но следует избегать подобных вставок переменных! Вместо подобного необходимо пользоваться конструкциями типа такой: $sql="SELECT `id`, `user_id7_c` FROM `accounts` WHERE `avance_date` = '".$day_control."' AND `deleted`='0' "; Или такой: $sql="SELECT `id`, `user_id7_c` FROM `accounts` WHERE `avance_date` = '{$day_control}' AND `deleted`='0' "; Подобный подход позволяет однозначно отделить код пхп от текста, чтобы интерпретатор абсолютно четко понимал где заканчивается название переменной и начинается текст. В случае с '$var' может это еще не так критично, но например привет$username_маленькаяжирнаяжопа уже будет не понятно где текст а где переменная. Передавать второй параметр true в запросах к БД через ->query: $result = $db->query($sql); Так НЕ ПРАВИЛЬНО $result = $db->query($sql, true); А так ПРАВИЛЬНО! Второй параметр указывает на то, что если во время выполнения SQL-запроса произойдет синтаксическая ошибка, то сценарий остановится (сработает функция die()). По-умолчанию второй параметр равен false, то есть если не указать второй параметр, то в случае кривого SQL-запроса сценарий продолжит свою работу. Это плохо тем, что усложняет отладку подобного кода: то вы сразу увидели проблему (страница не прогрузилась). А то не увидили, не все ситуации предусмотрели или в принципе SQL-запрос получился "кривым" - все это ушло на боевую систему и там будет работать криво. В общем о возможных проблемах надо знать сразу на этапе отладки, и чем ярче будет уведомление, тем лучше. Просто добавляйте везде , true "return true;" в любых задачах планировщика: Если вы пишите новую задачу для планировщика, то в конце функции необходимо обязательно поставить return true; Если функция планировщика не вернула true, то планировщик считает, что задача не была выполнена, и если стоит в настройках задачи галка "Выполнить, если пропущено":то планировщик будет запускать задачу не в 5 утра каждый день, а каждую минут, потому что предыдущий запуск задачи не был корректно выполнен (true то не вернула функция). Проверять наличие индексов в массиве перед их использованием: Если наличие индекса не однозначно и массив набивается из SQL-запроса или еще из каких пришедших данных, и структура массива не является жестко заданной константой - необходимо проверять наличие используемых индексов перед тем, как их использовать. Иначе будут вот такие ошибки валиться в лог PHP: Nov-2018 13:26:30 Europe/Moscow] PHP Notice: Undefined index: description in /home/euspenskiy/suitecrm/custom/modules/Accounts/auto_update_site_sunch.php on line 27 [08-Nov-2018 13:26:30 Europe/Moscow] PHP Notice: Undefined index: deleted in /home/euspenskiy/suitecrm/custom/modules/Accounts/auto_update_site_sunch.php on line 27 [08-Nov-2018 13:26:30 Europe/Moscow] PHP Notice: Undefined index: assigned_user_id in /home/euspenskiy/suitecrm/custom/modules/Accounts/auto_update_site_sunch.php on line 27 [08-Nov-2018 13:26:30 Europe/Moscow] PHP Notice: Undefined index: account_type in /home/euspenskiy/suitecrm/custom/modules/Accounts/auto_update_site_sunch.php on line 27 [08-Nov-2018 13:26:30 Europe/Moscow] PHP Notice: Undefined index: industry in /home/euspenskiy/suitecrm/custom/modules/Accounts/auto_update_site_sunch.php on line 27 [08-Nov-2018 13:26:30 Europe/Moscow] PHP Notice: Undefined index: annual_revenue in /home/euspenskiy/suitecrm/custom/modules/Accounts/auto_update_site_sunch.php on line 27 А иногда таких логов на пару мегабайт в минуту прилетает ... Наименование переменных. Даже не так: НАИМЕНОВАНИЕ ПЕРЕМЕННЫХ: Необходимо внимательно относится к тому, как вы называете свои переменные. Они должны быть понятными. Это самое главное! Чтобы одно название переменной в себе уже содержало информацию что находится внутри этой переменной: какой тип и примерно какое значение. И второе: старайтесь называть переменные всеже английскими словами без ошибок. Очень часто не обязательно лезть в словарь, а достаточно просто внимательнее приглядеться к коду. Ну например: $seedParticpaint= new s7_event_participants(); То, что тут используется "seed" - отлично! сразу понятно что речь пойдет про экземпляр класса. Но вот "Particpaint" - видно, что есть модуль "s7_event_participants" и в нем есть слово "participants", не "particpaint". Или: $dell_volue['module_parrent'] == 'Agend_Break' Нет с SuiteCRM слова "parrent", есть "parent". Да, согласен, это мелкие придирки, и в целом то код рабочий. Но мы ведь писали выше, что код должен быть не только рабочим ))) наверное "to be continue ..."
  7. Всем добрый день! Хочу рассказать об одной интересной работе, которую делали последнюю неделю и которую закрыли вот прям буквально сегодня. К нам обратилась некая компания, которая попросила показать демо-стенд реализации интеграции Camunda с CRM-системой на базе SuiteCRM. В SuiteCRM есть свой собственный модуль "Процессы". Но его наглядность - не самое его сильное качество. Да и функциональность конечно весьма не плоха, но очень далеко до "отличная". По этому компания приняла решение в качестве BPM-платформы выбрать стороннее решение в виде Camunda. Вот что пишут про Camunda: Погуглив по Camunda ничего толком не нашел в русском сегменте интернета. Какие то общие описания. В английском секторе чуть больше, и есть более менее не плохое описание и REST API. Так же очень помогла наработка от ребят из hardsoft321: при помощи этого класса получилось достаточно быстро "поднять протокол" - использовать API Camunda. Постановка задачи Итак, далее опишу что нужно было сделать, что было сделано и как это все работает. По задумке надо было организовать: Прием заявки с сайта (сработала форма) Заявка назначается на ответственного менеджера Менеджер обрабатывает новую заявку проверяя хватает ли данных для оказания услуги Если данных достаточно, то менеджер передает заявку в другой отдел (в службу безопасности) В службе безопасности заявка назначается на сотрудника, он проверяет заявку Если проверка выявила проблемы, то сотрудник службы безопасности возвращает заявку обратно менеджеру Если проверка проблем не выявила, то сотрудник службы безопасности отправляет заявку на следующий этап - в юридический отдел для предоставления непосредственно самой услуги В юридическом отделе заявка назначается на сотрудника этого отдела Сотрудник оказывает услугу по заявке Если по заявке были выявлены проблемы - сотрудник юридического отдела должен иметь возможность вернуть заявку обратно менеджеру После того, как услуга по заявка была оказана, сотрудник юридического отдела возвращает заявку обратно менеджеру, чтобы тот уведомил клиента о оказании ему услуги. После уведомления клиента заявка уходит в архив Если графически, то бизнес-процесс должен выглядеть примерно так: В связке SuiteCRM + Camunda предполагалось, что все логические действия по движению заявки по отделам и статусам будет выполнять Camunda, а SuiteCRM будет выступать в виде некоего UI, в котором будут работать сотрудники: видеть список своих заявок в работе, иметь возможность отправлять заявки дальше по цепочке. Чтобы реализовать подобное взаимодействие потребуется двухсторонняя синхронизация: В момент создания заявки, а также в моменты, когда сотрудники готовы отдать заявку дальше по цепочке, SuiteCRM должна сообщать о этом Camunda, чтобы та уже определяла дальнейшее действие. В момент, когда Camunda определяет что же дальше должно происходить с заявкой, она должна свое решение передавать обратно в SuiteCRM, чтобы та соответствующим образом меня статус заявки, ответственного по заявке и прочие действия, характерные для прохождения той или иной стадии. С первым пунктом интеграции было все более или менее понятно: Необходимо в Camunda разработать и загрузить процесс, который будет описывать всю логику. При помощи Camunda Rest API мы будем должны стартовать этот разработанный процесс в момент прихода новой заявки из формы на сайте В процессе работы над заявкой, когда сотрудники отмечают свою работу выполненной, мы должны передавать в процесс информацию о этом, чтобы процесс двигал заявку дальше С вторым пунктом было больше вопросов: Как передать из Camunda в SuiteCRM информацию о очередном этапе? Умеет ли Camunda отправлять Webhooks, или используется какая то другая технология? Есть ли в Camunda встроенные возможности такого взаимодействия, или необходимо привлекать разработчиков чтобы реализовать подобные возможности? Поверхностное гугление не давало четкого ответа на эти вопросы. Решение задачи Со стороны SuiteCRM: За основу была взята CRM-система для салонов красоты и парикмахерских. В ней мы переделали модуль "Посещения" на "Заявки": Для списка предоставляемых услуг подошел модуль "Прейскурант": Клиенты так и остались в карточках модуля "Клиенты": Для тестирования получения данных из формы при помощи модуля "Компании" была сгенерирована форма тестовой заявки: Данные на нее попадали прямиком в CRM-систему. Первоначально весь бизнес-процесс мы настроили силами самой CRM при помощи модуля "Процессы": Таким образом Заявка, при попадании в CRM-систему, автоматически создавала как запись с Клиентом, так и с Заявкой. В заявке назначался ответственный менеджер из списка сотрудников, находящихся в роли "Менеджер". Кстати были созданы 3 роли: Менеджер, СБ и Юрист: Сотрудники, имеющие ту или иную роль, назначаются Заявке в соответствующий момент времени. Движение заявки по статусам осуществляется при помощи кнопок, находящихся вверху карточки Заявки. Передача заявки в службу безопасности: Заявка назначается в работу сотруднику из службы безопасности. По результатам работы он может или вернуть заявку обратно менеджеру с своими комментариями, или отправить заявку дальше по цепочке: Если сотрудник службы безопасности подтвердил заявку, то она уходит сотруднику с ролью Юрист. Юрист также может вернуть обратно в работу заявку менеджеру (если в ней чего-либо не хватает), или выполнить свою работу и отметить заявку выполненной: После того, как Юрист выполнит свою работу и отметит заявку выполненной, она возвращается Менеджеру в статусе "Уведомление клиента" и менеджеру необходимо уведомить клиента о выполнении его заявки, после чего менеджер может закрыть заявку: Со стороны Camunda: Сначала мы развернули демо-стенд с Camunda. Самый быстрый и простой способ это сделать оказалось загрузить на сервер Docker-образ с официального репозитория: # docker pull camunda/camunda-bpm-platform После этого появляется возможность развернуть уже настроенную систему с предустановленной Camunda на борту. Я воспользовался системой разворачивания виртуальных хостов, которая работает на нашем сайте crmhosting.ru, но вы можете самостоятельно посмотреть про работу с docker. Если будет совсем не понятно, то пишите под статьей, дополню подобной информацией. Оставлю тут ссылку на демо-стенд: http://camunda.crmhosting.ru/camunda/ логин/пароль = demo/demo Путем изучения Camunda пришло понимание, что данное ПО умеет обрабатывать диаграммы BPMN, но не умеет их создавать. Для создания диаграмм используется распространяемый Modeler от Camunda: В изучении возможностей моделера и Camunda в том числе очень помог процесс, присутствующий в Camunda по умолчанию: обработка счетов: Настройка взаимодействия: Задачей нашего демо-стенда было показать принципиальную возможность взаимодействия между SuiteCRM и Camunda. Для этого мы решили не реализовывать весь процесс на связке Camunda+SuiteCRM, а добавить 1-2 звена для отображения того, как SuiteCRM отправляет данные в Camunda и как Camunda уведомляет SuiteCRM. В Modeler от Camunda мы разработали простенький процесс, задачей которого было стартовать при срабатывании формы и назначить заявке ответственного менеджера по заданному алгоритму, а затем вернуть назначенного менеджера в SuiteCRM, чтобы та, в свою очередь, назначила этого ответственного за Заявкой. Таким образом мы убрали из наших ранее настроенных бизнес-процессов определение ответственного силами SuiteCRM и перевели этот блок на плечи Camunda. Вот сам получившийся процесс в Camunda: Для формирования логики блока "Определение ответственного менеджера" существует множество разных способов. Мы выбрали указание пользователя при помощи Модели принятия решений (DMN). Настройки блока "Определение ответственного менеджера": Сама модель идет в виде отдельного файла *.dmn В ней мы явно указываем, что не зависимо не от чего результат будет равен строке "manager1": Таким образом при старте процесса Camunda назначает ответственным сотрудником юзера "manager1" и уходит в блок "Утверждение введенных данных менеджером" (этот блок никак не был автоматизирован). Чтобы стартовать процесс Camunda из SuiteCRM мы создали в SuiteCRM Hook для события before_save, который через API стартует наш процесс. В настройках LogicHooks: $hook_array['before_save'][] = Array( 10, 'Стартуем процесс', 'modules/Opportunities/Hooks.php', 'OpportunitiesLogicHooks', 'camundaStartProcess' ); Сам хук: public function camundaStartProcess($bean, $event, $arguments) { global $sugar_config; if(isset($GLOBALS['OpportunityCamundaStartProcess'][$bean->id])) return; print_array("*** camundaStartProcess - start ***",0,1); require_once 'custom/include/PestJSON.php'; require_once 'modules/CamundaProcesses/SugarCamunda.php'; print_array('$bean->id = ' . $bean->id,0,1); print_array('$bean->new_with_id = ' . var_export($bean->new_with_id,1),0,1); print_array('$bean->fetched_row = ' . var_export($bean->fetched_row,1),0,1); if(empty($bean->camunda_id)) { // Создание новой Заявки print_array("* Создание новой Заявки",0,1); if(empty($bean->id)) { $bean->id = create_guid(); $bean->new_with_id = true; } if($bean->lead_source == 'Form') { // Если источник Заявки = Форма print_array("* источник Заявки = Форма",0,1); // Подключаемся к Camunda $camunda = SugarCamunda::getJsonClient(); // Получаем список процессов $processes = $camunda->get("/process-definition"); if(isset($sugar_config['camunda_new_opportunity_process_key'])) { // Если в конфигурации указан процесс для входящего запроса с формы print_array("* в конфигурации указан процесс для входящего запроса с формы = " . $sugar_config['camunda_new_opportunity_process_key'],0,1); foreach ($processes as $processInfo) { if($processInfo['key'] == $sugar_config['camunda_new_opportunity_process_key']) { // Нашли процесс, указанный в конфиге print_array("* Нашли процесс, указанный в конфиге",0,1); // Стартуем процесс print_array("* Стартуем процесс",0,1); $GLOBALS['OpportunityCamundaStartProcess'][$bean->id] = true; $resultStart = $camunda->post("/process-definition/{$processInfo['id']}/start",[ 'opportunity_id' => $bean->id, ]); if(isset($resultStart['id'])) { // Записываем ID процесса в Заявку print_array("* Записываем ID процесса в Заявку: " . $resultStart['id'],0,1); print_array("** resultStart: " . var_export($resultStart,1),0,1); $bean->camunda_id = $resultStart['id']; } } } } } } } В Camunda может быть описано множество разных процессов. В конфигурационной переменной SuiteCRM мы указали название нашего нужного процесса. $sugar_config['camunda_new_opportunity_process_key'] = 'testSuite'; Обращаясь через API Camunda к списку всех доступных процессов мы их перебираем, и как только находим процесс с нужным названием, то сразу запускаем его через вызов метода "/process-definition/{$processInfo['id']}/start" В результате вызова мы получаем ID запущенного процесса (именно не ID схемы процесса, а ID именно текущего старта), и записываем его в Заявку в поле camunda_id: $bean->camunda_id = $resultStart['id']; Так как данное действие выполняется во время before_save, то данное поле естественным образом запишется в Заявку без каких-либо дополнительных действий с нашей стороны. Дальше начинается самая интересная и сложная часть, на которую ушло больше всего времени - мы заставляем Camunda отдать в SuiteCRM логин выбранного менеджера. После долгого гугления и попыток осознать предоставляемые Camunda возможности пришло понимание, что встроенных нативных средств в Camunda для передачи данных в внешнюю среду нет. Вроде "пичаль", но не совсем. У Camunda зато есть ряд других возможностей, подразумевающих программирование. И реализуются они при помощи Listeners в Modeler при редактировании свойств блока "Определение ответственного менеджера". Listeners - это возможность вставить свою логику в тот или иной блок диаграммы используя или специально разработанные Java-классы, или другие языки программирования. Существующего опыта программирования на Java нам не хватало для анализа существующих возможностей и разворачивания действующего демо-стенда в заданные сроки. Вроде как есть поддержка JavaScript, но попытки использовать XMLHttpRequest закончились неудачей: Camunda отказалась работать с этими функциями. Но на выручку пришел язык groovy: В этом языке нашлись конструкции, которые позволяют обращаться к сторонним ресурсам по HTTP и получать результаты. А в нашем случае на текущем этапе нам надо было всего лишь обратиться к SuiteCRM с ID текущего процесса и назначенным ответственным, чтобы передать в SuiteCRM эту информацию. И вот что в результате получилось: Таким образом скрипт на groove выполнялся ПОСЛЕ того, как ответственный менеджер был выбран, и отправлял эту информацию в SuiteCRM. SuiteCRM, в свою очередь, принимала этот запрос и обрабатывала его. Для приема был добавлен новый entryPoint = camunda В настройках entryPoint: $entry_point_registry['camunda'] = array( 'file' => 'modules/CamundaProcesses/inboundRequests.php', 'auth' => false ); Файл modules/CamundaProcesses/inboundRequests.php: <?php /** * Created by PhpStorm. * User: crmhosting * Date: 06.11.2018 * Time: 19:26 */ include_once "modules/Opportunities/Opportunity.php"; global $db; $json = new JSON(); if(isset($_REQUEST['action'])) { // Пришел запрос с действием print_array('*** inboundRequests - start ***',0,1); print_array($_REQUEST,0,1); // Формируем массив с подробностями $content = []; switch ($_REQUEST['action']) { case 'setAssignedUserInOpportunity': // Указан ответственный пользователь у Заявки if(isset($_REQUEST['user']) AND $_REQUEST['user'] != '') { $content['user'] = $_REQUEST['user']; } break; } $camundaID = isset($_REQUEST['camundaID']) ? $_REQUEST['camundaID'] : ''; $camundaIDArray =explode(":", $camundaID); if(isset($camundaIDArray[2])) $camundaID = $camundaIDArray[2]; // Записываем входящее действие в таблицу print_array('* Записываем входящее действие в таблицу',0,1); $sql = " INSERT INTO `camunda_inbound` SET `action` = '".$_REQUEST['action']."', `camundaID` = '{$camundaID}', `content` = '".base64_encode($json->encode($content))."' "; $db->query($sql, true); } Таким образом мы в этом entryPoint можем принимать из Camunda запросы и подготовили тем самым платформу для реагирования на самые разные запросы. Кстати, в процессе тестирования выявилась интересная особенность: изначально мы думал, что процесс взаимодействия и взаимных вызовов будет строиться следующим образом: Пришли данные из формы -> Стартовали процесс в Camunda -> Получили ID процесса из Camunda -> Сохранили заявку -> Пришел запрос из Camunda с указанием ответственного -> определили заявку -> указали ответственного в SuiteCRM -> Сохранили заявку Но все оказалось немного иначе. Camunda не хотела отдавать ID процесса, пока не пройдется по всем своим блокам. А так как блок с определением ответственного стартует обратный вызов SuiteCRM, то фактическая цепочка получалась такой: Пришли данные из формы -> Стартовали процесс в Camunda -> Пришел запрос из Camunda с указанием ответственного -> пытаемся определить заявку (ее еще нет) -> некуда указывать ответственного -> вызов из Camunda закончился в пустую -> Получили ID процесса из Camunda -> Сохранили заявку То есть из Camunda приходит обратный запрос с ответственным еще до того, как Camunda отдала ID процесса при создании заявки. И так, как ID заявки в SuiteCRM пока нет, то ничего нормально мы не сохранили и ничего не работало. Но выход нашелся в работе через вспомогательную таблицу `camunda_inbound`: в нее мы промежуточно сохраняем информацию когда приходит ответ от Camunda - в каком процессе какой ответственный был указан. Таким образом мы корректно завершаем Hook в заявке, получаем ID процесса и сохраняем его в Заявке. А хуком after_save мы лезем в таблицу `camunda_inbound` и обрабатываем накопившиеся там отбойки от Camunda: public function camundaCheckInboundProcess($bean, $event, $arguments) { include_once "modules/Opportunities/OpportunityCamundaHelper.php"; if(isset($GLOBALS['OpportunityCamundaStartProcess'][$bean->id])) { // Запускаем анализ всех пришедших активностей из Camunda OpportunityCamundaHelper::processAllCamundaInbound(); unset($GLOBALS['OpportunityCamundaStartProcess'][$bean->id]); } } class OpportunityCamundaHelper extends Opportunity { static function processAllCamundaInbound() { global $db; print_array("*** processAllCamundaInbound - start ***",0,1); // Получаем все записи $sql = "SELECT * FROM `camunda_inbound` ORDER BY `id` ASC"; $result = $db->query($sql, true); $json = new JSON(); while ($row = $db->fetchByAssoc($result)) { $content = $json->decode(base64_decode($row['content'])); switch ($row['action']) { case 'setAssignedUserInOpportunity': // Указан ответственный пользователь у Заявки print_array("* Пришел запрос на указание ответственного у Заявки",0,1); if(isset($row['camundaID']) AND $row['camundaID'] != '') { // Пришла информация о процессе // Ищем Заявки с таким ID процесса print_array("* camundaID указано ({$row['camundaID']})",0,1); $sql = "SELECT `id` FROM `opportunities` WHERE `deleted` = 0 AND `camunda_id` = '{$row['camundaID']}'"; print_array('$sql: ' . $sql,0,1); $opportunity_id = $db->getOne($sql, true); if($opportunity_id) { // Если ID Заявки найдено print_array("* ID Заявки найдено: " . $opportunity_id,0,1); $seedOpportunity = new Opportunity(); $seedOpportunity->retrieve($opportunity_id); if(!empty($seedOpportunity->id) AND empty($seedOpportunity->assigned_user_id)) { // Заявка корректно найдена print_array("* Заявка найдена: " . $seedOpportunity->name . ' (ID = '.$seedOpportunity->id.')',0,1); // Далее ищем ответсственного пользователя if(isset($content['user']) AND $content['user'] != '') { // Пользователь указан print_array("* Пользователь указан: " . $content['user'],0,1); $sql = "SELECT `id` FROM `users` WHERE `deleted` = 0 AND `user_name` = '{$content['user']}'"; $user_id = $db->getOne($sql, true); if($user_id) { // Если Пользователь найден print_array("* Пользователь найден: " . $user_id,0,1); $seedOpportunity->assigned_user_id = $user_id; $seedOpportunity->user_assigned_manager_id = $user_id; $seedOpportunity->save(); print_array("** Сохранили: " . $seedOpportunity->assigned_user_name,0,1); } } } } } break; } // Удаляем запись из таблицы $sql = "DELETE FROM `camunda_inbound` WHERE `id` = '{$row['id']}'"; print_array("Удаляем запись из таблицы: " . $sql,0,1); $db->query($sql, true); } } } Вот такое получилось решение. Могу предположить, что это не самое оптимальное решение. Если у кого то есть мысли как можно сделать лучше, или опыт чего то подобного, то буду рад если вы прокомментируете данную статью. Демо-стенд SuiteCRM: http://u1200.crmhosting.ru admin/admin manager1/Manager1 SB1/SB1 Yur1 / Yur1 Возможно эту сборку добавим в список сборок для автоматического разворачивания на нашем сервисе CRMHosting.ru (но это не точно )))) ) Презентация Ну и под конец размещаю видео с презентацией получившегося решения:
  8. Всем привет! Столкнулся с небольшой проблемой: SuiteCRM автоматически при отображении поля типа Text преобразует все URL в ссылки. То есть берет URL и "пихает" его в тег <A>. Классная штука на самом деле, удобно. Но мне надо было отключить её, так как я создал поле "source" => "non-db", в котором хочу сам формировать список ссылок, чтобы в карточке записи был набор ссылок типа такого: Ссылка 1 Ссылка 2 Ссылка 3 Именно так, нормальное название. А не: http://site.ru/page1.html http://site.ru/page2.html http://site.ru/page3.html Ведь удобнее видеть нормальное человеческое описание, кликнуть на него и попасть на желаемую страницу. По реализации: Добавил новое поле в модуль, в котором будет находится текст с списком ссылок: 'different_links' => array ( 'required' => false, 'name' => 'different_links', 'vname' => 'LBL_DIFFERENT_LINKS', 'type' => 'text', 'massupdate' => 0, 'no_default' => false, 'comments' => '', 'help' => '', 'importable' => 'true', 'duplicate_merge' => 'disabled', 'duplicate_merge_dom_value' => '0', 'audited' => true, 'reportable' => true, 'unified_search' => false, 'merge_filter' => 'disabled', 'source' => 'non-db', ), Добавил хук after_retrieve в custom/Extension/modules/lm_CRMSystems/Ext/LogicHooks/calculateFields.php, чтобы можно было наполнять поле текстом: if (!isset($hook_array) || !is_array($hook_array)) { $hook_array = array(); } if (!isset($hook_array['after_retrieve']) || !is_array($hook_array['after_retrieve'])) { $hook_array['after_retrieve'] = array(); } $hook_array['after_retrieve'][] = Array(10, 'Ссылка на карточку на сервере', 'custom/modules/lm_CRMSystems/hooks.php', 'lm_CRMSystemsHooks', 'calculateFields'); Добавляем функцию для хука в файле custom/modules/lm_CRMSystems/hooks.php: /** * Получение ссылки на карточку CRM-системы на ее Сервере * @param $bean * @param $event * @param $arguments */ public function calculateFields($bean, $event, $arguments) { global $db; global $sugar_config; // Ссылки $bean->different_links = ""; // Ссылка на карточку на другом сервере switch ($sugar_config['CRMSystems']['systemType']) { case 'base': // Мы находимся на Базовом сервере // Пытаемся сформировать ссылка на карточку на сервере с проектами // Получаем URL сервера if(!empty($bean->lm_crmservers_lm_crmsystemslm_crmservers_ida)) { $sql = "SELECT `url` FROM `lm_crmservers` WHERE `deleted` = 0 AND `id` = '{$bean->lm_crmservers_lm_crmsystemslm_crmservers_ida}'"; $url = $db->getOne($sql, true); $bean->different_links .= '<li><A href="'.$url . '/index.php?module=lm_CRMSystems&action=DetailView&record=' . $bean->id.'" target="_blank">Карта на сервере с проектом</A></li>'; } break; case 'projects': // Мы находимся на Сервере с проектами // Добавляем ссылку на карточку на Базовом сервере // Ищем базовый сервер $sql = "SELECT `url` FROM `lm_crmservers` WHERE `deleted` = 0 AND `type` = 'base'"; $url = $db->getOne($sql, true); $bean->different_links .= '<li><A href="'.$url . '/index.php?module=lm_CRMSystems&action=DetailView&record=' . $bean->id.'" target="_blank">Карта на базовом сервере</A></li>'; break; } if($bean->different_links != '') { $bean->different_links = '<ul>'.$bean->different_links.'</ul>'; } } И вот что у меня получилось: Получается, что я сформировал корректную HTML-ссылку, а SuiteCRM внутри нее нашла URL, и обрамила ее своей ссылкой. Вышло не очень. Полез в функцию-обработчик полей типа Text include/SugarFields/Fields/Text/SugarFieldText.php, и видим там следующее: class SugarFieldText extends SugarFieldBase { function getDetailViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex) { if(!isset($displayParams['nl2br'])){ $displayParams['nl2br'] = true; } if(!isset($displayParams['htmlescape']) && $vardef['editor'] != "html") { $displayParams['htmlescape'] = true; } if(!isset($displayParams['url2html'])) { $displayParams['url2html'] = true; } return parent::getDetailViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex); } Тут особо интересный блок: if(!isset($displayParams['url2html'])) { $displayParams['url2html'] = true; } То есть если при отображении поля использовать 'url2html' => false, то в теории оно не должно преобразовывать ссылки. Пробуем: в файле custom/modules/lm_CRMSystems/metadata/detailviewdefs.php описываем поле different_links: 11 => array ( 0 => 'lm_crmservers_lm_crmsystems_name', 1 => array( 'name' => 'different_links', 'label' => 'LBL_DIFFERENT_LINKS', 'displayParams' => array( 'url2html' => false, ), ), ), И вот что получилось после быстрого восстановления: То, что нужно!
  9. Добрый день! при помощи этой настройки можно регулировать нагрузку как на собственный сервер, так и на почтовый сервер. Большое количество писем, если сервер не очень мощный, может привести к нехватке памяти и все встанет. Так же некоторые почтовые сервера имеют ограничения на кол-во отправляемых через них писем в течении какого то кол-ва времени (в рамках борьбы со спамом или предлагая бизнес-тарифы). Для регулирования подобных вещей и применяется этот параметр
  10. Всем привет! Хочу показать мой вариант решения следующей проблемы: Есть бизнес-процесс, который настроен на модуль "Контрагенты". В результате срабатывания бизнес-процесса должны создаться две записи: Запись в модуле "Обращения" Запись в модуле "Документы" Но вся сложность заключается в том, что и Обращение и Документ должны не просто добавиться в Контрагент, но и между собой сформировать связь. То есть зайдя потом в карточку созданного Обращения мы должны увидеть в сабпанели созданный Документ! Если очень упрощенно, то мой бизнес-процесс выглядит так: Как вы видите, у меня в действиях отмечен чекбокс "Связать с записью в контролируемом модуле". Этот чекбокс позволяет созданным записям находится в соответствующих сабпанелях Контрагента. Но вот Обращение с Документом между собой никак не хотели связываться! Как я не тестировал, что не пробовал, но без доработки CRM-системы чисто имеющимися средствами эту задачу решить не получилось. Я пожалуй не буду утомлять рассуждениями на тему "что же тут делать и как я пришел к конечному результату", а просто опишу то, что в итоге было сделано в CRM-системе, что позволило решить эту задачу: 1. Первое: Добавляем в модуль Контрагенты два новых поля: custom/Extension/modules/Accounts/Ext/Vardefs/custom_fields.tmp.php: <?php $dictionary['Account']['fields']['tmp_document_id'] = array ( 'required' => false, 'name' => 'tmp_document_id', 'vname' => 'LBL_TMP_DOCUMENT_ID', 'type' => 'id', 'massupdate' => 0, 'no_default' => false, 'comments' => '', 'help' => '', 'importable' => 'true', 'duplicate_merge' => 'disabled', 'duplicate_merge_dom_value' => 0, 'audited' => false, 'inline_edit' => true, 'reportable' => false, 'unified_search' => false, 'merge_filter' => 'disabled', 'len' => 36, 'size' => '20', 'source' => 'non-db', ); $dictionary['Account']['fields']['tmp_document'] = array ( 'required' => false, 'source' => 'non-db', 'name' => 'tmp_document', 'vname' => 'LBL_TMP_DOCUMENT', 'type' => 'relate', 'massupdate' => 1, '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' => 'tmp_document_id', 'ext2' => 'Documents', 'module' => 'Documents', 'rname' => 'name', 'quicksearch' => 'enabled', 'studio' => 'visible', ); $dictionary['Account']['fields']['tmp_case_id'] = array ( 'required' => false, 'name' => 'tmp_case_id', 'vname' => 'LBL_TMP_CASE_ID', 'type' => 'id', 'massupdate' => 0, 'no_default' => false, 'comments' => '', 'help' => '', 'importable' => 'true', 'duplicate_merge' => 'disabled', 'duplicate_merge_dom_value' => 0, 'audited' => false, 'inline_edit' => true, 'reportable' => false, 'unified_search' => false, 'merge_filter' => 'disabled', 'len' => 36, 'size' => '20', 'source' => 'non-db', ); $dictionary['Account']['fields']['tmp_case'] = array ( 'required' => false, 'source' => 'non-db', 'name' => 'tmp_case', 'vname' => 'LBL_TMP_CASE', 'type' => 'relate', 'massupdate' => 1, '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' => 'tmp_case_id', 'ext2' => 'Cases', 'module' => 'Cases', 'rname' => 'name', 'quicksearch' => 'enabled', 'studio' => 'visible', ); custom/Extension/modules/Accounts/Ext/Language/ru_RU.TMP.php: <?php $mod_strings['LBL_TMP_DOCUMENT'] = 'TMP.DOCUMENT'; $mod_strings['LBL_TMP_DOCUMENT_ID'] = 'TMP.DOCUMENT_ID'; $mod_strings['LBL_TMP_CASE'] = 'TMP.CASE'; $mod_strings['LBL_TMP_CASE_ID'] = 'TMP.CASE_ID'; Обращаю ваше внимание на то, что данные поля имеют параметр 'source' => 'non-db', что говорит о том, что эти поля исключительно расчетные, и не хранят значения в базе данных. 2. Второе. Добавляю Hook на добавление связи Контрагента с чем-либо: custom/Extension/modules/Accounts/Ext/LogicHooks/tmp.hooks.php: <?php $hook_array['after_relationship_add'][] = Array( 10, 'TMP Fields', 'custom/modules/Accounts/TMPLogicHooks.php', 'TMPLogicHooks', 'setTMPFields' ); custom/modules/Accounts/TMPLogicHooks.php: <?php /** * Created by PhpStorm. * User: crmhosting * Date: 01.06.2018 * Time: 15:52 */ class TMPLogicHooks { /** * Заполняем временные поля значениями * @param $bean * @param $event * @param $arguments */ function setTMPFields($bean, $event, $arguments) { if($arguments['module'] == 'Accounts' AND $arguments['related_module'] == 'Cases') { // Если сейчас связь Контрагента и Обращения $bean->tmp_case_id = $arguments['related_bean']->id; } elseif ($arguments['module'] == 'Accounts' AND $arguments['related_module'] == 'Documents') { // Если сейчас связь Контрагента и Документа $bean->tmp_document_id = $arguments['related_bean']->id; } } } Таким образом мы во время связи Контрагента с Обращением или Документом заполняем наши вспомогательные поля. 3. Третье. Делаем быстрое восстановление. Чтобы в системе применились все добавленные поля и хуки. 4. Четвертое. В Бизнес-процессе в действии добавления Документа указываем связь с Обращением через нашу переменную tmp_case_id: Вот как теперь будет выглядеть бизнес-процесс: После срабатывания такого бизнес-процесса у создаваемого обращения появляется прямая связь с создаваемым документом. Что нам и было нужно. Давайте теперь немного объясню что же у нас получилось: Мы в Контрагенте создали пару полей. Но не простых, а типа relate - это когда в текущем модуле есть связь с записью в другом модул Мы в Контрагенты добавили хук after_relationship_add, который будет срабатывать каждый раз, когда к Контрагенту будет добавляться связь с записью другого модуля В момент, когда срабатывает бизнес-процесс, в первую очередь создается Обращение, а затем добавляется связь этого обращения с Контрагентом, что приводит к срабатыванию нашего хука Внутри хука мы видим, что добавляется связь Контрагента и Обращения. Мы добавляем в поле Контрагента tmp_case_id айди созданного и добавляемого Обращения Все, Обращение создано и привязано к Контрагенту Далее по бизнес-процессу создается Документ Документ мало того, что привязывается к Контрагенту, так мы в бизнес-процессе еще прописали ему некую связь с Обращениями, и указали, что связать он должен по полю TMP.CASE (а это как раз наше поле tmp_case_id, которое мы заполнили в прошлый раз, когда добавляли связь с Обращением) Таким образом создаваемый документ связывается с ранее созданным Обращением. Ну и связь двухсторонняя: если привязали Документ к Обращению, значит и Обращение привязано к Документу. Profit! Если что не понятно, или есть предложения по реализации данной задачи другими способами, то буду рад пообщаться в комментариях.
  11. Всем привет! Хочу поделиться одной доработкой, которая, на мой взгляд, немного полнее позволит понять роль модуля "Процессы" в работе с CRM-системой, и что не обязательно все на свете дописывать: можно еще и активно пользоваться встроенными средствами. Итак! Задача: в Обращениях выводить время последнего сообщения от Клиента. При этом выводить не только в карточке, но и в списках, отчетах и вообще везде, где заблагорассудится. Вот если задача была вывести только в карточке обращения, я наверное бы особо не заморачивался, и добавил расчётное поле source="non-db", в котором "на лету" определял последнее сообщение и подставлял его дату. Но тут задача получить универсальное поле. По этому решено было добавить полноценное поле в модуль "Обращения" (с хранением значения в базе данных) и заполнять его соответствующим значением. Добавляем поле (создаем файл custom/Extension/modules/Cases/Ext/Vardefs/last_reply_date.php): <?php $dictionary['Case']['fields']['last_reply_date'] = array ( 'name' => 'last_reply_date', 'vname' => 'LBL_LAST_REPLY_DATE', 'type' => 'datetimecombo', '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', 'size' => '20', 'enable_range_search' => false, 'dbType' => 'datetime', ); Добавляем локализацию для поля (custom/Extension/modules/Cases/Ext/Language/ru_RU.last_reply_date.php): <?php $mod_strings['LBL_LAST_REPLY_DATE'] = 'Последний ответ'; После выполнения быстрого восстановления в админке это поле появляется в списке полей модуля Обращения, и вы можете его использовать при формировании макетов. Далее наша задача это поле корректно заполнить. По задаче нам надо туда добавлять дату последного ответа клиента. Модуль с перепиской по Обращениям называется AOP_Case_Updates и в моей локализации по русски он звучит как "Обновления обращения". Чтобы выполнить нашу задачу нам надо при создании новой записи в этом модуле обновлять поле last_reply_date в записи родительского Обращения. Но так как в модуль "Обновления обращения" добавляются ответы как Клиента, так и сотрудника, ведущего переписку по Обращению, то нам надо определять дату только тогда, когда ответил клиент. Лично я определяю записи от клиентов отсутствием значения в поле `assigned_user_id` (если вы определяете это как то по другому, то пишите в комментариях, интересно будет узнать). Таким образом нам достаточно создать Бизнес-процесс, который будет привязан к модулю "Обновления обращения". В момент, когда будет создаваться новая запись, и в ней будет пусто поле `assigned_user_id` - это будет как раз наш случай, и мы должны будем обновить поле в родительском Обращении. Вот скрин этого Бизнес-процесса: Испытания показали, что данный механизм успешно работает. И небольшой бонус: То, что мы выше создали, будет исправно фиксировать дату последнего ответа для всех обращений, по которым клиенты только ответят с момента привнесения этого функционала. Далее я приведу листинг скрипта, который можно единоразово запустить в CRM-системе, и он найдет все Обращения, найдет все последние ответы клиентов по ним, и запишет дату: <?php if(!defined('sugarEntry'))define('sugarEntry', true); /** * Created by PhpStorm. * User: crmhosting * Date: 25.04.2018 * Time: 9:32 */ require_once('include/entryPoint.php'); global $db; // Получаем список Обращений $sql = " SELECT `cases`.`id` FROM `cases` WHERE `cases`.`deleted` = 0 "; $result = $db->query($sql, true); $counter = 0; while ($row = $db->fetchByAssoc($result)) { $counter++; // Получаем последнее сообщение, полученное от клиента $sql = " SELECT `date_entered` FROM `aop_case_updates` WHERE `deleted` = 0 AND `case_id` = '".$row['id']."' AND (`assigned_user_id` IS NULL OR `assigned_user_id` = '') ORDER BY `date_entered` DESC LIMIT 1 "; $date = $db->getOne($sql, true); if(!empty($date)) { // Если есть дата $sql = "UPDATE `cases` SET `last_reply_date` = '".$date."' WHERE `id` = '".$row['id']."'"; $db->query($sql, true); } } print_array("Всего записей звонков: " . $counter);
  12. Всем привет! В одном из проектов потребовалось формировать отчеты из данных, которые находятся в аудите. Напомню, что аудит - это табличка с информацией кто что поменял в карточке, вызываемая нажатием кнопки "Просмотр журнала изменений": Классная штука, конечно, этот аудит! Знай не забывай новые поля в него добавлять в студии... Но вот беда: работать с ним из модуля Отчеты нет возможности. Совсем. Если в кратце про аудит, то: Должен ли быть в модуле аудит полей или нет определяется в vardefs.php в параметре 'audited' для всего модуля: $dictionary['Account'] = array( 'table' => 'accounts', 'audited' => true, 'unified_search' => true, 'full_text_search' => true, 'unified_search_default_enabled' => true, 'duplicate_merge' => true, 'comment' => 'Accounts are organizations or entities that are the target of selling, support, and marketing activities, or have already purchased products or services', 'fields' => array( ................ Еще раз повторюсь, что это настройка, говорящая о принципиальной возможности работы с аудитом в текущем модуле. Вести аудит того или иного поля в модуле определяется также параметром 'audited' => true, в настройках того или иного поля: 'parent_id' => array( 'name' => 'parent_id', 'vname' => 'LBL_PARENT_ACCOUNT_ID', 'type' => 'id', 'required' => false, 'reportable' => false, 'audited' => true, 'comment' => 'Account ID of the parent of this account', ), Для хранения данных в базе данных используются таблицы с названием `модуль_audit` везде одинаковой структуры: Структура от модуля к модулю неизменна, потому что названия аудируемых полей хранятся в поле `field_name`, а значения поля в `before_value_string` и `after_value_string`. Нажимая "Быстрое восстановление" в админке мы восстанавливаем таблицы аудита в том числе там, где это необходимо (например, если добавили в модуль возможность аудировать записи). Так вот данные таблицы и все эти данные не являются объектами в понимании SuiteCRM, и к ним нет доступа из модуля AOR_Reports. По этому смотреть таблички - можем, строить аналитику по ним - нет. Решение задачи анализа изменения тех или иных полей в модуле на самом деле можно решить разными способами: Сделать аудит модулем (наш способ); Добавить в модуль для каждого поля, которое мы хотим наблюдать, еще одно поле-дублер, которое будет содержать предыдущее значение анализируемого поля. Например, нам надо видеть как поменялось поле "Статус" в модуле. Для этого мы можем добавить поле `status_before`, и в него при помощи тех же Hooks при смене поля "Статус" указывать предыдущее значение. В этом случае мы будем иметь прямой удобный доступ к `status_before` из отчетов, в студии (карточка записи, поиск по полю и т.д.). Но есть в этом подходе существенный минус: мы в дополнительном поле храним лишь предыдущее значение, историю изменений не получится хранить + для каждого нового поля, которое мы хотим анализировать, придется создавать доп.поля и настраивать их запись, что несколько не удобно. Можно добавить еще один модуль, в который мы будем записывать изменения значений в записях основного модуля. Например, при срабатывании Hooks. Кстати таким образом разработчики сделали хранение смены значений в модуле Обращения: AOP_Case_Events. Если грамотно реализовать этот метод, то в целом такой подход тоже может быть не плох. Но, например в AOP_Case_Events, явно указано какие поля необходимо фиксировать при смене в модуле Обращений. Это не удобно. Нужно постоянно привлекать разработчика, если захотите добавить новое поле в аудит. Плюс по сути это дубляж уже имеющихся в аудите данных. В общем не плохо, но аудит всеже лучше ))) Итак, давайте вернемся от теории к практике. На входе у нас есть таблица аудита, и нам надо из нее сделать модуль, при этом сохранив и не изменяя весь функционал аудирования, который уже есть. Я покажу на примере создания модуля аудирования для модуля Контрагенты. Модуль назовем AccountsAudit и по русски это будет "Аудит Контрагентов". Почти все файлы модуля будут лежать в /modules/AccountsAudit/, где структура папок и файлов будет следующей: Далее я приведу листинг всех этих файлов. modules/AccountsAudit/Dashlets/AccountsAuditDashlet/AccountsAuditDashlet.meta.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ if (!defined('sugarEntry') || !sugarEntry) { die('Not A Valid Entry Point'); } global $app_strings; $dashletMeta['AccountsAuditDashlet'] = array( 'module' => 'AccountsAudit', 'title' => translate('LBL_HOMEPAGE_TITLE', 'AccountsAudit'), 'description' => 'A customizable view into AccountsAudit', 'category' => 'Module Views' ); modules/AccountsAudit/Dashlets/AccountsAuditDashlet/AccountsAuditDashlet.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ if (!defined('sugarEntry') || !sugarEntry) { die('Not A Valid Entry Point'); } require_once('include/Dashlets/DashletGeneric.php'); require_once('modules/AccountsAudit/AccountsAudit.php'); class AccountsAuditDashlet extends DashletGeneric { function __construct($id, $def = null) { global $current_user, $app_strings; require('modules/AccountsAudit/metadata/dashletviewdefs.php'); parent::__construct($id, $def); if (empty($def['title'])) { $this->title = translate('LBL_HOMEPAGE_TITLE', 'AccountsAudit'); } $this->searchFields = $dashletData['AccountsAuditDashlet']['searchFields']; $this->columns = $dashletData['AccountsAuditDashlet']['columns']; $this->seedBean = new AccountsAudit(); } } modules/AccountsAudit/language/en_us.lang.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $mod_strings = array ( 'LBL_ASSIGNED_TO_ID' => 'Assigned User Id', 'LBL_ASSIGNED_TO_NAME' => 'Assigned to', 'LBL_SECURITYGROUPS' => 'Security Groups', 'LBL_SECURITYGROUPS_SUBPANEL_TITLE' => 'Security Groups', 'LBL_ID' => 'ID', 'LBL_DATE_ENTERED' => 'Date Created', 'LBL_DATE_MODIFIED' => 'Date Modified', 'LBL_MODIFIED' => 'Modified By', 'LBL_MODIFIED_ID' => 'Modified By Id', 'LBL_MODIFIED_NAME' => 'Modified By Name', 'LBL_CREATED' => 'Created By', 'LBL_CREATED_ID' => 'Created By Id', 'LBL_DESCRIPTION' => 'Description', 'LBL_DELETED' => 'Deleted', 'LBL_NAME' => 'Name', 'LBL_CREATED_USER' => 'Created by User', 'LBL_MODIFIED_USER' => 'Modified by User', 'LBL_LIST_NAME' => 'Name', 'LBL_EDIT_BUTTON' => 'Edit', 'LBL_REMOVE' => 'Remove', 'LBL_LIST_FORM_TITLE' => 'Аудит Контрагентов List', 'LBL_MODULE_NAME' => 'Аудит Контрагентов', 'LBL_MODULE_TITLE' => 'Аудит Контрагентов', 'LBL_HOMEPAGE_TITLE' => 'My Аудит Контрагентов', 'LNK_NEW_RECORD' => 'Create Аудит Контрагентов', 'LNK_LIST' => 'View Аудит Контрагентов', 'LNK_IMPORT_ACCOUNTSAUDIT' => 'Импорт Аудит Контрагентов', 'LBL_SEARCH_FORM_TITLE' => ' Аудит Контрагентов', 'LBL_HISTORY_SUBPANEL_TITLE' => 'View History', 'LBL_ACTIVITIES_SUBPANEL_TITLE' => 'Activities', 'LBL_ACCOUNTSAUDIT_SUBPANEL_TITLE' => 'Аудит Контрагентов', 'LBL_NEW_FORM_TITLE' => 'New Аудит Контрагентов', ); modules/AccountsAudit/language/ru_RU.lang.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $mod_strings = array ( 'LBL_ASSIGNED_TO_ID' => 'Ответственный(ая)', 'LBL_ASSIGNED_TO_NAME' => 'Ответственный(ая)', 'LBL_SECURITYGROUPS' => 'Группы пользователей', 'LBL_SECURITYGROUPS_SUBPANEL_TITLE' => 'Группы пользователей', 'LBL_ID' => 'ID', 'LBL_DATE_ENTERED' => 'Дата создания', 'LBL_DATE_MODIFIED' => 'Дата изменения', 'LBL_MODIFIED' => 'Изменено', 'LBL_MODIFIED_ID' => 'Изменено(ID)', 'LBL_MODIFIED_NAME' => 'Изменено', 'LBL_CREATED' => 'Создано', 'LBL_CREATED_BY' => 'Создано(ID)', 'LBL_DESCRIPTION' => 'Описание', 'LBL_DELETED' => 'Удалено', 'LBL_NAME' => 'Название', 'LBL_CREATED_USER' => 'Создано', 'LBL_MODIFIED_USER' => 'Изменено', 'LBL_LIST_NAME' => 'Название', 'LBL_EDIT_BUTTON' => 'Править', 'LBL_REMOVE' => 'Удалить', 'LBL_PARENT' => 'Контрагент', 'LBL_PARENT_ID' => 'Контрагент (ID)', 'LBL_DATE_CREATED' => 'Дата создания', 'LBL_FIELD_NAME' => 'Поле', 'LBL_DATA_TYPE' => 'Тип поля', 'LBL_BEFORE_VALUE_STRING' => 'Значение ДО', 'LBL_AFTER_VALUE_STRING' => 'Значение ПОСЛЕ', 'LBL_BEFORE_VALUE_TEXT' => 'Значение ДО (TEXT)', 'LBL_AFTER_VALUE_TEXT' => 'Значение ПОСЛЕ (TEXT)', 'LBL_LIST_FORM_TITLE' => 'Список аудита', 'LBL_MODULE_NAME' => 'Аудит Контрагентов', 'LBL_MODULE_TITLE' => 'Аудит Контрагентов', 'LBL_HOMEPAGE_TITLE' => 'Мой Аудит Контрагентов', 'LNK_NEW_RECORD' => 'Добавить строку аудита', 'LNK_LIST' => 'Просмотр Аудит Контрагентов', 'LNK_IMPORT_ACCOUNTSAUDIT' => 'Импорт Аудит Контрагентов', 'LBL_SEARCH_FORM_TITLE' => 'Фильтр Аудит Контрагентов', 'LBL_HISTORY_SUBPANEL_TITLE' => 'Просмотр истории', 'LBL_ACTIVITIES_SUBPANEL_TITLE' => 'Мероприятия', 'LBL_ACCOUNTSAUDIT_SUBPANEL_TITLE' => 'Аудит Контрагентов', 'LBL_NEW_FORM_TITLE' => 'Новый Аудит Контрагентов', 'LBL_ACCOUNTS_AUDIT' => 'Аудит', ); modules/AccountsAudit/metadata/subpanels/default.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ if (!defined('sugarEntry') || !sugarEntry) { die('Not A Valid Entry Point'); } $module_name = 'AccountsAudit'; $subpanel_layout = array( 'top_buttons' => array( array('widget_class' => 'SubPanelTopCreateButton'), array('widget_class' => 'SubPanelTopSelectButton', 'popup_module' => $module_name), ), 'where' => '', 'list_fields' => array( 'name' => array( 'vname' => 'LBL_NAME', 'widget_class' => 'SubPanelDetailViewLink', 'width' => '45%', ), 'date_modified' => array( 'vname' => 'LBL_DATE_MODIFIED', 'width' => '45%', ), 'edit_button' => array( 'vname' => 'LBL_EDIT_BUTTON', 'widget_class' => 'SubPanelEditButton', 'module' => $module_name, 'width' => '4%', ), 'remove_button' => array( 'vname' => 'LBL_REMOVE', 'widget_class' => 'SubPanelRemoveButton', 'module' => $module_name, 'width' => '5%', ), ), ); modules/AccountsAudit/metadata/dashletviewdefs.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ if (!defined('sugarEntry') || !sugarEntry) { die('Not A Valid Entry Point'); } global $current_user; $dashletData['AccountsAuditDashlet']['searchFields'] = array( 'date_entered' => array('default' => ''), 'date_modified' => array('default' => ''), 'assigned_user_id' => array( 'type' => 'assigned_user_name', 'default' => $current_user->name ) ); $dashletData['AccountsAuditDashlet']['columns'] = array( 'name' => array( 'width' => '40', 'label' => 'LBL_LIST_NAME', 'link' => true, 'default' => true ), 'date_entered' => array( 'width' => '15', 'label' => 'LBL_DATE_ENTERED', 'default' => true ), 'date_modified' => array( 'width' => '15', 'label' => 'LBL_DATE_MODIFIED' ), 'created_by' => array( 'width' => '8', 'label' => 'LBL_CREATED' ), 'assigned_user_name' => array( 'width' => '8', 'label' => 'LBL_LIST_ASSIGNED_USER' ), ); modules/AccountsAudit/metadata/detailviewdefs.php: <?php $module_name = 'AccountsAudit'; $viewdefs [$module_name] = array ( 'DetailView' => array ( 'templateMeta' => array ( 'form' => array ( 'buttons' => array ( 0 => 'EDIT', 1 => 'DUPLICATE', 2 => 'DELETE', 3 => 'FIND_DUPLICATES', ), ), 'maxColumns' => '2', 'widths' => array ( 0 => array ( 'label' => '10', 'field' => '30', ), 1 => array ( 'label' => '10', 'field' => '30', ), ), 'useTabs' => false, 'tabDefs' => array ( 'DEFAULT' => array ( 'newTab' => false, 'panelDefault' => 'expanded', ), ), ), 'panels' => array ( 'default' => array ( 0 => array ( 0 => 'id', ), 1 => array ( 0 => array ( 'name' => 'parent', 'studio' => 'visible', 'label' => 'LBL_PARENT', ), ), 2 => array ( 0 => 'date_created', ), 3 => array ( 0 => 'created_by_name', ), 4 => array ( 0 => array ( 'name' => 'field_name', 'label' => 'LBL_FIELD_NAME', ), ), 5 => array ( 0 => 'data_type', ), 6 => array ( 0 => 'before_value_string', ), 7 => array ( 0 => 'after_value_string', ), 8 => array ( 0 => 'before_value_text', ), 9 => array ( 0 => 'after_value_text', ), ), ), ), ); ?> modules/AccountsAudit/metadata/editviewdefs.php: <?php $module_name = 'AccountsAudit'; $viewdefs [$module_name] = array ( 'EditView' => array ( 'templateMeta' => array ( 'maxColumns' => '2', 'widths' => array ( 0 => array ( 'label' => '10', 'field' => '30', ), 1 => array ( 'label' => '10', 'field' => '30', ), ), 'useTabs' => false, 'tabDefs' => array ( 'DEFAULT' => array ( 'newTab' => false, 'panelDefault' => 'expanded', ), ), ), 'panels' => array ( 'default' => array ( 0 => array ( 0 => 'id', ), 1 => array ( 0 => array ( 'name' => 'parent', 'studio' => 'visible', 'label' => 'LBL_PARENT', ), ), 2 => array ( 0 => 'date_created', ), 3 => array ( 0 => 'created_by_name', ), 4 => array ( 0 => array ( 'name' => 'field_name', 'label' => 'LBL_FIELD_NAME', ), ), 5 => array ( 0 => 'data_type', ), 6 => array ( 0 => 'before_value_string', ), 7 => array ( 0 => 'after_value_string', ), 8 => array ( 0 => 'before_value_text', ), 9 => array ( 0 => 'after_value_text', ), ), ), ), ); ?> modules/AccountsAudit/metadata/listviewdefs.php: <?php $module_name = 'AccountsAudit'; $listViewDefs [$module_name] = array ( 'ID' => array ( 'width' => '32%', 'label' => 'LBL_ID', 'default' => true, 'link' => true, ), 'PARENT' => array ( 'type' => 'relate', 'studio' => 'visible', 'label' => 'LBL_PARENT', 'id' => 'PARENT_ID', 'link' => true, 'width' => '10%', 'default' => true, ), 'DATE_CREATED' => array ( 'width' => '20%', 'label' => 'LBL_DATE_CREATED', 'default' => true, 'link' => false, ), 'CREATED_BY_NAME' => array ( 'width' => '20%', 'label' => 'LBL_CREATED', 'default' => true, ), 'FIELD_NAME' => array ( 'width' => '20%', 'label' => 'LBL_FIELD_NAME', 'default' => true, 'link' => false, ), 'DATA_TYPE' => array ( 'width' => '20%', 'label' => 'LBL_DATA_TYPE', 'default' => true, 'link' => false, ), 'BEFORE_VALUE_STRING' => array ( 'width' => '20%', 'label' => 'LBL_BEFORE_VALUE_STRING', 'default' => true, 'link' => false, ), 'AFTER_VALUE_STRING' => array ( 'width' => '20%', 'label' => 'LBL_AFTER_VALUE_STRING', 'default' => true, 'link' => false, ), 'BEFORE_VALUE_TEXT' => array ( 'width' => '20%', 'label' => 'LBL_BEFORE_VALUE_TEXT', 'default' => true, 'link' => false, ), 'AFTER_VALUE_TEXT' => array ( 'width' => '20%', 'label' => 'LBL_AFTER_VALUE_TEXT', 'default' => true, 'link' => false, ), 'PARENT_ID' => array ( 'width' => '20%', 'label' => 'LBL_PARENT_ID', 'default' => false, 'link' => false, ), ); ?> modules/AccountsAudit/metadata/metafiles.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $module_name = 'AccountsAudit'; $metafiles[$module_name] = array( 'detailviewdefs' => 'modules/' . $module_name . '/metadata/detailviewdefs.php', 'editviewdefs' => 'modules/' . $module_name . '/metadata/editviewdefs.php', 'listviewdefs' => 'modules/' . $module_name . '/metadata/listviewdefs.php', 'searchdefs' => 'modules/' . $module_name . '/metadata/searchdefs.php', 'popupdefs' => 'modules/' . $module_name . '/metadata/popupdefs.php', 'searchfields' => 'modules/' . $module_name . '/metadata/SearchFields.php', ); modules/AccountsAudit/metadata/popupdefs.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ if (!defined('sugarEntry') || !sugarEntry) { die('Not A Valid Entry Point'); } $module_name = 'AccountsAudit'; $object_name = 'AccountsAudit'; $_module_name = 'accountsaudit'; $popupMeta = array( 'moduleMain' => $module_name, 'varName' => $object_name, 'orderBy' => $_module_name . '.name', 'whereClauses' => array( 'name' => $_module_name . '.name', ), 'searchInputs' => array($_module_name . '_number', 'name', 'priority', 'status'), ); modules/AccountsAudit/metadata/quickcreatedefs.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $module_name = 'AccountsAudit'; $viewdefs[$module_name]['QuickCreate'] = array( 'templateMeta' => array( 'maxColumns' => '2', 'widths' => array( array('label' => '10', 'field' => '30'), array('label' => '10', 'field' => '30') ), ), 'panels' => array( 'default' => array( array( 'name', 'assigned_user_name', ), ), ), ); modules/AccountsAudit/metadata/searchdefs.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $module_name = 'AccountsAudit'; $searchdefs[$module_name] = array( 'templateMeta' => array( 'maxColumns' => '3', 'maxColumnsBasic' => '4', 'widths' => array('label' => '10', 'field' => '30'), ), 'layout' => array( 'basic_search' => array( 'name', array('name' => 'current_user_only', 'label' => 'LBL_CURRENT_USER_FILTER', 'type' => 'bool'), ), 'advanced_search' => array( 'name', array( 'name' => 'assigned_user_id', 'label' => 'LBL_ASSIGNED_TO', 'type' => 'enum', 'function' => array('name' => 'get_user_array', 'params' => array(false)) ), ), ), ); modules/AccountsAudit/metadata/SearchFields.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ if (!defined('sugarEntry') || !sugarEntry) { die('Not A Valid Entry Point'); } $module_name = 'AccountsAudit'; $searchFields[$module_name] = array( 'name' => array('query_type' => 'default'), 'current_user_only' => array( 'query_type' => 'default', 'db_field' => array('assigned_user_id'), 'my_items' => true, 'vname' => 'LBL_CURRENT_USER_FILTER', 'type' => 'bool' ), 'assigned_user_id' => array('query_type' => 'default'), //Range Search Support 'range_date_entered' => array('query_type' => 'default', 'enable_range_search' => true, 'is_date_field' => true), 'start_range_date_entered' => array( 'query_type' => 'default', 'enable_range_search' => true, 'is_date_field' => true ), 'end_range_date_entered' => array( 'query_type' => 'default', 'enable_range_search' => true, 'is_date_field' => true ), 'range_date_modified' => array('query_type' => 'default', 'enable_range_search' => true, 'is_date_field' => true), 'start_range_date_modified' => array( 'query_type' => 'default', 'enable_range_search' => true, 'is_date_field' => true ), 'end_range_date_modified' => array( 'query_type' => 'default', 'enable_range_search' => true, 'is_date_field' => true ), //Range Search Support ); modules/AccountsAudit/metadata/studio.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ /** * This file adds support for studio */ modules/AccountsAudit/AccountsAudit.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ require_once('include/SugarObjects/templates/audit/Audit.php'); class AccountsAudit extends Audit { public $new_schema = true; public $module_dir = 'AccountsAudit'; public $object_name = 'AccountsAudit'; public $table_name = 'accounts_audit'; public $importable = false; public $id; public $name; public $date_entered; public $date_modified; public $modified_user_id; public $modified_by_name; public $created_by; public $created_by_name; public $description; public $deleted; public $created_by_link; public $modified_user_link; public $assigned_user_id; public $assigned_user_name; public $assigned_user_link; public $SecurityGroups; public $parent; public $parent_id; public $date_created; public $field_name; public $data_type; public $before_value_string; public $after_value_string; public $before_value_text; public $after_value_text; public function bean_implements($interface) { switch($interface) { case 'ACL': return true; } return false; } } modules/AccountsAudit/Menu.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ if (!defined('sugarEntry') || !sugarEntry) { die('Not A Valid Entry Point'); } global $mod_strings, $app_strings, $sugar_config; if(ACLController::checkAccess('AccountsAudit', 'edit', true)){ $module_menu[]=array('index.php?module=AccountsAudit&action=EditView&return_module=AccountsAudit&return_action=DetailView', $mod_strings['LNK_NEW_RECORD'], 'Add', 'AccountsAudit'); } if(ACLController::checkAccess('AccountsAudit', 'list', true)){ $module_menu[]=array('index.php?module=AccountsAudit&action=index&return_module=AccountsAudit&return_action=DetailView', $mod_strings['LNK_LIST'],'View', 'AccountsAudit'); } modules/AccountsAudit/vardefs.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $dictionary['AccountsAudit'] = array( 'table' => 'accounts_audit', 'audited' => false, 'inline_edit' => true, 'duplicate_merge' => false, 'fields' => array ( 'parent' => array ( 'required' => false, 'source' => 'non-db', 'name' => 'parent', 'vname' => 'LBL_PARENT', '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' => 'parent_id', 'ext2' => 'Accounts', 'module' => 'Accounts', 'rname' => 'name', 'quicksearch' => 'enabled', 'studio' => 'visible', ), 'accounts_audit_link' => array ( 'name' => 'accounts_audit_link', 'type' => 'link', 'relationship' => 'accounts_audit', 'vname' => 'LBL_ACCOUNTS_AUDIT', 'link_type' => 'one', 'module' => 'Accounts', 'bean_name' => 'Account', 'source' => 'non-db', ), 'created_by_link' => array( 'name' => 'created_by_link', 'type' => 'link', 'relationship' => 'accounts_audit_created_by', 'vname' => 'LBL_CREATED_BY_USER', 'link_type' => 'one', 'module' => 'Users', 'bean_name' => 'User', 'source' => 'non-db', ), ), 'relationships' => array ( 'accounts_audit' => array ( 'lhs_module' => 'AccountsAudit', 'lhs_table' => 'accounts_audit', 'lhs_key' => 'parent_id', 'rhs_module' => 'Accounts', 'rhs_table' => 'accounts', 'rhs_key' => 'id', 'relationship_type' => 'one-to-one', 'readonly' => false, 'deleted' => true, 'relationship_only' => false, 'for_activities' => false, 'is_custom' => true, 'from_studio' => true, 'relationship_name' => 'accounts_audit', ), 'accounts_audit_created_by' => array( 'lhs_module' => 'Users', 'lhs_table' => 'users', 'lhs_key' => 'id', 'rhs_module' => 'AccountsAudit', 'rhs_table' => 'accounts_audit', 'rhs_key' => 'created_by', 'relationship_type' => 'one-to-many' ), ), 'optimistic_locking' => true, 'unified_search' => false, ); if (!class_exists('VardefManager')) { require_once('include/SugarObjects/VardefManager.php'); } VardefManager::createVardef('AccountsAudit', 'AccountsAudit', array('audit')); Это были основные базовые (почти все) файлы модуля, без которых это все просто не будет работать. Файлы в себе не содержат по сути особо никакой логики, и просто должны быть, чтобы обеспечить необходимый функционал нового модуля. Обращаю ваше внимание на файлы AccountsAudit.php и vardefs.php: мы в них указали, что таблица нашего модуля будет называться "accounts_audit", то есть наша уже существующая таблица с аудированием. Таким образом мы просто вокруг уже существующей таблицы развернули модуль, который будет использовать ее для своей работы. Далее нам необходимо заявить в CRM-системе, что у нас появился новый модуль и объявить как он называется. Для этого в папке custom/Extension/application/Ext/Include/ добавляем файл AccountsAudit.php: <?php //WARNING: The contents of this file are auto-generated $beanList['AccountsAudit'] = 'AccountsAudit'; $beanFiles['AccountsAudit'] = 'modules/AccountsAudit/AccountsAudit.php'; $moduleList[] = 'AccountsAudit'; ?> А в папке custom/Extension/application/Ext/Language/ добавляем файл en_us.AccountsAudit.php <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $app_list_strings['moduleList']['AccountsAudit'] = 'Аудит Контрагентов'; и файл ru_RU.AccountsAudit.php: <?php /** * * SugarCRM Community Edition is a customer relationship management program developed by * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc. * * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd. * Copyright (C) 2011 - 2017 SalesAgility Ltd. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by the * Free Software Foundation with the addition of the following permission added * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License along with * this program; if not, see http://www.gnu.org/licenses or write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road, * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected] * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "Powered by * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not * reasonably feasible for technical reasons, the Appropriate Legal Notices must * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM". */ $app_list_strings['moduleList']['AccountsAudit'] = 'Аудит Контрагентов'; Но это пока не все. Как выяснилось в процессе работы над заданием, в модуле отчетов мало указать связь модуля AccountsAudit -> Accounts! Необходимо еще "прокинуть" и обратную связь: Accounts -> AccountsAudit. Для этого добавляем файл /custom/Extension/modules/Accounts/Ext/Vardefs/auditFieds.php с следующим содержанием: <?php $dictionary['Account']['fields']['accounts_audit_link'] = array ( 'name' => 'accounts_audit_link', 'type' => 'link', 'relationship' => 'accounts_audit', 'vname' => 'LBL_ACCOUNTS_AUDIT', 'link_type' => 'one', 'module' => 'AccountsAudit', 'bean_name' => 'AccountsAudit', 'source' => 'non-db', ); $dictionary['Account']['relationships']['accounts_audit'] = array ( 'lhs_module' => 'AccountsAudit', 'lhs_table' => 'accounts_audit', 'lhs_key' => 'parent_id', 'rhs_module' => 'Accounts', 'rhs_table' => 'accounts', 'rhs_key' => 'id', 'relationship_type' => 'one-to-one', 'readonly' => false, 'deleted' => true, 'relationship_only' => false, 'for_activities' => false, 'is_custom' => true, 'from_studio' => true, 'relationship_name' => 'accounts_audit', ); Здесь мы добавили поле с линком на модуль AccountsAudit и добавили связь с AccountsAudit в модуле Контрагенты. Ну вот и все! Выполняем быстрое восстановление (там система предложит что то поменять в структуре таблицы accounts_audit, помоему добавить поле `deleted`, но это не страшно и никак не повлияет на функциональность аудирования), добавляем новый модуль "Аудит Контрагентов" в админке в "Настройка отображения закладок и субпанелей" и теперь у нас есть такой модуль, котором мы можем теперь полноценно пользоваться и в меню, и в Студии, и в Отчетах, и в Процессах и вообще:
  13. Добрый день! Попробуйте зайти в модуль с шаблонами и зайдите в любой первый попавшийся шаблон. Затем в строке браузера поменяйте его айдишник на 9f0d0646-1bf4-3da9-500a-5ad6dcec5796 и попробуйте загрузить страницу. Что будет? Пустая страница или загрузится шаблон?
  14. Добрый день! У вас не корректно настроены права доступа к файлам. SuiteCRM не может редактировать/добавлять их. Для начала посмотрите от имени кого запускается ваш сервер: echo exec("id"); Посмотрите, все ли файлы в проекте от этого пользователя и от этой группы? Выправить ситуацию с настройками доступа можно примерно так: chown -R 48:48 /var/www/vhosts/crmhosting.ru/crm/public_html/ find /var/www/vhosts/crmhosting.ru/crm/public_html -type d | xargs chmod 0755 find /var/www/vhosts/crmhosting.ru/crm/public_html -type f | xargs chmod 0644 подставляем только свои значения и пути. Если не помогло, то есть еще одна особенность в SuiteCRM по настройке доступа к файлам и папкам. В config.php: 'default_permissions' => array ( 'dir_mode' => 1533, 'file_mode' => 436, 'user' => 'apache', 'group' => 'apache', ), попробуйте тут явно задать юзера и группу (обычно они пустые в этом конфиге). Или "поиграйтесь" с цифрами. Например такие попробовать: 'default_permissions' => array ( 'dir_mode' => 1528, 'file_mode' => 432, 'user' => 'crmhosting', 'group' => 'staff', ), Попробуйте, потом расскажите что получилось )) В восстановлении оно лишь таблицу для аудита анализирует (есть или надо создать). Находится ли отдельно взятое поле в аудите или нет - это задача совсем других механизмов, а быстрому восстановлению отношения не имеющих.
×
×
  • Create New...