Nevermind: Speak of the devil: https://github.com/espocrm/espocrm/issues/1642
I think if I update to 5.8.4 it should work.
UPDATE: Yes worked, it showed for me now. Can send it to Contacts.
Announcement
Collapse
No announcement yet.
Participant functionality in a custom entity.
Collapse
X
-
Nevermind: Speak of the devil: https://github.com/espocrm/espocrm/issues/1642
I think if I update to 5.8.4 it should work.
-
Maximus Weird, I don't have that option to send Invitation.
I tested the demo and does show it on there and the Send Invitation did work.
Is there a setting we need to enable because it not showing for me. Also I noticed that the "send invitation" don't create an email History/Archive. Is that normal?
- Likes 1
-
So they must be a portal user? They can't just be a Contact/Account?
Leave a comment:
-
Hello,
esforim
> Can you send an invite if they are an portal user?
Yes, you can. Just change a filter from 'Active' to 'All' upon adding a new user to the event to be able to select a portal user.
telecastg
We do not have documentation of how it is translated into SQL. This conversion logic is buried deep in the code. I can only suggest you use grep to explore it.
Leave a comment:
-
Tested the Email button and it work good. Don't need to log in to Accept/Decline/Tentative. As long as you click the button the meeting Status will change.
EDIT: Found out where you can email the email template:
#Admin/templateManager/Last edited by esforim; 03-12-2020, 02:22 AM.
-
Wow, I didn't even notice there is a "Send Invitations" button until I recently noticed it and tested it.
But this is only usable for "Users?" ie. Staff members? You can't seem to send invite to Contact(s)? Can you send an invite if they are an portal user? The Demo I think disable email system so bit hard to test.
The Email Template also look rather ugly and plain, need way to customize it too, anyway we can?
Email looks:
Meeting layout (top right for Send Invitation)
Last edited by esforim; 03-12-2020, 02:17 AM.
Leave a comment:
-
Thank you so much for the detailed instructions !
I have been trying to find documentation on the use of the "where" syntax applied above, in order to understand how is exactly this JSON structure translated into an actual sql statement. Can you point out where I could find more information ?
PHP Code:
"where": {
"=": {
"leftJoins": ["users", "contacts", "leads"],
"sql": "contactsMiddle.status = {value} OR leadsMiddle.status = {value} OR usersMiddle.status = {value}",
"distinct": true
},
"<>": "event.id NOT IN (SELECT event_id FROM contact_event WHERE deleted = 0 AND status = {value}) AND event.id NOT IN (SELECT event_id FROM event_user WHERE deleted = 0 AND status = {value}) AND event.id NOT IN (SELECT event_id FROM event_lead WHERE deleted = 0 AND status = {value})",
"IN": {
"leftJoins": ["users", "leads", "contacts"],
"sql": "contactsMiddle.status IN {value} OR leadsMiddle.status IN {value} OR usersMiddle.status IN {value}",
"distinct": true
},
"NOT IN": {
"event.id NOT IN (SELECT event_id FROM contact_event WHERE deleted = 0 AND status IN {value}) AND event.id NOT IN (SELECT event_id FROM event_user WHERE deleted = 0 AND status IN {value}) AND event.id NOT IN (SELECT event_id FROM event_lead WHERE deleted = 0 AND status IN {value})",
},
"IS NULL": {
"leftJoins": ["users", "contacts", "leads"],
"sql": "contactsMiddle.status IS NULL AND leadsMiddle.status IS NULL AND usersMiddle.status IS NULL",
"distinct": true
},
"IS NOT NULL": "event.id NOT IN (SELECT event_id FROM contact_event WHERE deleted = 0 AND status IS NULL) OR event.id NOT IN (SELECT event_id FROM event_user WHERE deleted = 0 AND status IS NULL) OR event.id NOT IN (SELECT event_id FROM event_lead WHERE deleted = 0 AND status IS NULL)"
}
Leave a comment:
-
Part 2
5. Add to the file /custom/Espo/Custom/Resources/metadata/entityDefs/User.json these fields and link:
Code:{ "fields": { "acceptanceStatus": { "type": "varchar", "notStorable": true, "exportDisabled": true, "disabled": true }, "acceptanceStatusEvents": { "type": "enum", "notStorable": true, "directUpdateDisabled": true, "layoutAvailabilityList": ["filters"], "importDisabled": true, "exportDisabled": true, "view": "crm:views/lead/fields/acceptance-status", "link": "events", "column": "status" } }, "links": { "events": { "type": "hasMany", "entity": "Event", "foreign": "users" } } }
PHP Code:<?php
namespace Espo\Custom\Controllers;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
use \Espo\Core\Exceptions\NotFound;
class Event extends \Espo\Core\Templates\Controllers\Event
{
public function postActionSendInvitations($params, $data)
{
if (empty($data->id)) {
throw new BadRequest();
}
$entity = $this->getRecordService()->getEntity($data->id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
if (!$this->getAcl()->checkScope('Email', 'create')) {
throw new Forbidden();
}
return $this->getRecordService()->sendInvitations($entity);
}
public function postActionSetAcceptanceStatus($params, $data)
{
if (empty($data->id) || empty($data->status)) {
throw new BadRequest();
}
return $this->getRecordService()->setAcceptanceStatus($data->id, $data->status);
}
}
PHP Code:<?php
namespace Espo\Custom\Services;
use \Espo\ORM\Entity;
use \Espo\Modules\Crm\Business\Event\Invitations;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class Event extends \Espo\Core\Templates\Services\Event
{
protected function init()
{
$this->addDependencyList([
'preferences',
'language',
'dateTime',
'container',
'fileManager',
'number'
]);
}
protected $duplicateIgnoreAttributeList = ['usersColumns', 'contactsColumns', 'leadsColumns'];
protected function getMailSender()
{
return $this->getInjection('container')->get('mailSender');
}
protected function getPreferences()
{
return $this->getInjection('preferences');
}
protected function getLanguage()
{
return $this->getInjection('language');
}
protected function getDateTime()
{
return $this->getInjection('dateTime');
}
public function checkAssignment(Entity $entity)
{
$result = parent::checkAssignment($entity);
if (!$result) return false;
$userIdList = $entity->get('usersIds');
if (!is_array($userIdList)) {
$userIdList = [];
}
$newIdList = [];
if (!$entity->isNew()) {
$existingIdList = [];
foreach ($entity->get('users') as $user) {
$existingIdList[] = $user->id;
}
foreach ($userIdList as $id) {
if (!in_array($id, $existingIdList)) {
$newIdList[] = $id;
}
}
} else {
$newIdList = $userIdList;
}
foreach ($newIdList as $userId) {
if (!$this->getAcl()->checkAssignmentPermission($userId)) {
return false;
}
}
return true;
}
protected function getInvitationManager($useUserSmtp = true)
{
$smtpParams = null;
if ($useUserSmtp) {
$smtpParams = $this->getServiceFactory()->create('Email')->getUserSmtpParams($this->getUser()->id);
}
$templateFileManager = $this->getInjection('container')->get('templateFileManager');
return new Invitations(
$this->getEntityManager(),
$smtpParams,
$this->getMailSender(),
$this->getConfig(),
$this->getInjection('fileManager'),
$this->getDateTime(),
$this->getInjection('number'),
$this->getLanguage(),
$templateFileManager
);
}
public function sendInvitations(Entity $entity, $useUserSmtp = true)
{
$invitationManager = $this->getInvitationManager($useUserSmtp);
$emailHash = array();
$sentCount = 0;
$users = $entity->get('users');
foreach ($users as $user) {
if ($user->id === $this->getUser()->id) {
if ($entity->getLinkMultipleColumn('users', 'status', $user->id) === 'Accepted') {
continue;
}
}
if ($user->get('emailAddress') && !array_key_exists($user->get('emailAddress'), $emailHash)) {
$invitationManager->sendInvitation($entity, $user, 'users');
$emailHash[$user->get('emailAddress')] = true;
$sentCount ++;
}
}
$contacts = $entity->get('contacts');
foreach ($contacts as $contact) {
if ($contact->get('emailAddress') && !array_key_exists($contact->get('emailAddress'), $emailHash)) {
$invitationManager->sendInvitation($entity, $contact, 'contacts');
$emailHash[$user->get('emailAddress')] = true;
$sentCount ++;
}
}
$leads = $entity->get('leads');
foreach ($leads as $lead) {
if ($lead->get('emailAddress') && !array_key_exists($lead->get('emailAddress'), $emailHash)) {
$invitationManager->sendInvitation($entity, $lead, 'leads');
$emailHash[$user->get('emailAddress')] = true;
$sentCount ++;
}
}
if (!$sentCount) return false;
return true;
}
public function setAcceptanceStatus(string $id, string $status, ?string $userId = null)
{
$userId = $userId ?? $this->getUser()->id;
$statusList = $this->getMetadata()->get(['entityDefs', $this->entityType, 'fields', 'acceptanceStatus', 'options'], []);
if (!in_array($status, $statusList)) throw new BadRequest();
$entity = $this->getEntityManager()->getEntity($this->entityType, $id);
if (!$entity) throw new NotFound();
if (!$entity->hasLinkMultipleId('users', $userId));
$this->getEntityManager()->getRepository($this->entityType)->updateRelation(
$entity, 'users', $userId, (object) ['status' => $status]
);
$actionData = [
'eventName' => $entity->get('name'),
'eventType' => $entity->getEntityType(),
'eventId' => $entity->id,
'dateStart' => $entity->get('dateStart'),
'status' => $status,
'link' => 'users',
'inviteeType' => 'User',
'inviteeId' => $userId,
];
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterConfirmation', $entity, [], $actionData);
return true;
}
}
PHP Code:<?php
namespace Espo\Custom\Repositories;
use Espo\ORM\Entity;
use Espo\Core\Utils\Util;
class Event extends \Espo\Core\Templates\Repositories\Event
{
protected function beforeSave(Entity $entity, array $options = [])
{
if (!$this->getConfig()->get('eventAssignedUserIsAttendeeDisabled')) {
if ($entity->hasLinkMultipleField('assignedUsers')) {
$assignedUserIdList = $entity->getLinkMultipleIdList('assignedUsers');
foreach ($assignedUserIdList as $assignedUserId) {
$entity->addLinkMultipleId('users', $assignedUserId);
$entity->setLinkMultipleName('users', $assignedUserId, $entity->getLinkMultipleName('assignedUsers', $assignedUserId));
}
} else {
$assignedUserId = $entity->get('assignedUserId');
if ($assignedUserId) {
$entity->addLinkMultipleId('users', $assignedUserId);
$entity->setLinkMultipleName('users', $assignedUserId, $entity->get('assignedUserName'));
}
}
}
if ($entity->isNew()) {
$currentUserId = $this->getEntityManager()->getUser()->id;
if (
$entity->hasLinkMultipleId('users', $currentUserId)
&&
(
!$entity->getLinkMultipleColumn('users', 'status', $currentUserId)
||
$entity->getLinkMultipleColumn('users', 'status', $currentUserId) === 'None'
)
) {
$entity->setLinkMultipleColumn('users', 'status', $currentUserId, 'Accepted');
}
}
}
}
10. F5 to refresh a web page.
- Likes 1
Leave a comment:
-
Hi everyone. Here is an example of how you can build attendees field and Send Invitation logic for the custom entity. In my case, I used the custom Event type entity called "Event".
Note: Due to max post size (10000 characters) on forum my solution is divided into 2 part.
Part 1
1. Add into the file /custom/Espo/Custom/Resources/metadata/entityDefs/Event.json:
Code:} "fields": { "acceptanceStatus": { "type": "enum", "notStorable": true, "options": ["None", "Accepted", "Tentative", "Declined"], "style": { "Accepted": "success", "Declined": "danger", "Tentative": "warning" }, "layoutDetailDisabled": true, "layoutMassUpdateDisabled": true, "importDisabled": true, "exportDisabled": true, "where": { "=": { "leftJoins": ["users", "contacts", "leads"], "sql": "contactsMiddle.status = {value} OR leadsMiddle.status = {value} OR usersMiddle.status = {value}", "distinct": true }, "<>": "[COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]contact_event[/COLOR] WHERE deleted = 0 AND status = {value}) AND [COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]event_user[/COLOR] WHERE deleted = 0 AND status = {value}) AND [COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]event_lead[/COLOR] WHERE deleted = 0 AND status = {value})", "IN": { "leftJoins": ["users", "leads", "contacts"], "sql": "contactsMiddle.status IN {value} OR leadsMiddle.status IN {value} OR usersMiddle.status IN {value}", "distinct": true }, "NOT IN": "[COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]contact_event[/COLOR] WHERE deleted = 0 AND status IN {value}) AND [COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]event_user[/COLOR] WHERE deleted = 0 AND status IN {value}) AND [COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]event_lead[/COLOR] WHERE deleted = 0 AND status IN {value})", "IS NULL": { "leftJoins": ["users", "contacts", "leads"], "sql": "contactsMiddle.status IS NULL AND leadsMiddle.status IS NULL AND usersMiddle.status IS NULL", "distinct": true }, "IS NOT NULL": "[COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]contact_event[/COLOR] WHERE deleted = 0 AND status IS NULL) OR [COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]event_user [/COLOR]WHERE deleted = 0 AND status IS NULL) OR [COLOR=#FF0000]event.id[/COLOR] NOT IN (SELECT [COLOR=#FF0000]event_id[/COLOR] FROM [COLOR=#FF0000]event_lead[/COLOR] WHERE deleted = 0 AND status IS NULL)" }, "view": "crm:views/meeting/fields/acceptance-status" }, "users": { "type": "linkMultiple", "view": "crm:views/meeting/fields/users", "layoutDetailDisabled": true, "layoutListDisabled": true, "columns": { "status": "acceptanceStatus" }, "additionalAttributeList": ["columns"], "orderBy": "name" }, "contacts": { "type": "linkMultiple", "layoutDetailDisabled": true, "layoutListDisabled": true, "view": "crm:views/meeting/fields/contacts", "columns": { "status": "acceptanceStatus" }, "additionalAttributeList": ["columns"], "orderBy": "name" }, "leads": { "type": "linkMultiple", "view": "crm:views/meeting/fields/attendees", "layoutDetailDisabled": true, "layoutListDisabled": true, "columns": { "status": "acceptanceStatus" }, "additionalAttributeList": ["columns"], "orderBy": "name" }, .... other fields ..... "links": { "users": { "type": "hasMany", "entity": "User", "foreign": "events", "additionalColumns": { "status": { "type": "varchar", "len": "36", "default": "None" } } }, "contacts": { "type": "hasMany", "entity": "Contact", "foreign": "events", "additionalColumns": { "status": { "type": "varchar", "len": "36", "default": "None" } } }, "leads": { "type": "hasMany", "entity": "Lead", "foreign": "events", "additionalColumns": { "status": { "type": "varchar", "len": "36", "default": "None" } } }, .... other links ..... }
2. In the file /custom/Espo/Custom/Resources/metadata/clientDefs/Event.json you need to have this links to view:
Code:{ "controller": "controllers/record", "acl": "crm:acl/meeting", "views":{ "detail":"crm:views/meeting/detail" }, "recordViews": { "list":"crm:views/meeting/record/list", "detail":"crm:views/meeting/record/detail" }, "modalViews": { "detail":"crm:views/meeting/modals/detail" }, "activityDefs": { "activitiesCreate": true, "historyCreate": true }, "sidePanels":{ "detail":[ { "name":"attendees", "label":"Attendees", "view":"crm:views/meeting/record/panels/attendees", "options":{ "fieldList":[ "users", "contacts", "leads" ] }, "sticked": true, "isForm": true, "notRefreshable": true } ], "detailSmall":[ { "name":"attendees", "label":"Attendees", "view":"crm:views/meeting/record/panels/attendees", "sticked": true, "isForm": true, "notRefreshable": true } ], "edit":[ { "name":"attendees", "label":"Attendees", "view":"crm:views/meeting/record/panels/attendees", "sticked": true, "isForm": true, "notRefreshable": true } ], "editSmall":[ { "name":"attendees", "label":"Attendees", "view":"crm:views/meeting/record/panels/attendees", "sticked": true, "isForm": true, "notRefreshable": true } ] }, ..... other your parameters ..... }
Code:{ "fields": { "acceptanceStatus": { "type": "varchar", "notStorable": true, "exportDisabled": true, "disabled": true }, "acceptanceStatusEvents": { "type": "enum", "notStorable": true, "directUpdateDisabled": true, "layoutAvailabilityList": ["filters"], "importDisabled": true, "exportDisabled": true, "view": "crm:views/lead/fields/acceptance-status", "link": "events", "column": "status" } }, "links": { "events": { "type": "hasMany", "entity": "Event", "foreign": "leads", "layoutRelationshipsDisabled": true, "audited": true } } }
Code:{ "fields": { "acceptanceStatus": { "type": "varchar", "notStorable": true, "exportDisabled": true, "disabled": true }, "acceptanceStatusEvents": { "type": "enum", "notStorable": true, "directUpdateDisabled": true, "layoutAvailabilityList": ["filters"], "importDisabled": true, "exportDisabled": true, "view": "crm:views/lead/fields/acceptance-status", "link": "events", "column": "status" } }, "links": { "events": { "type": "hasMany", "entity": "Event", "foreign": "contacts", "layoutRelationshipsDisabled": true, "audited": true } } }
Leave a comment:
-
Okay. I'll try the suggestions mentioned. Then I post the results here on the forum.
Thanks
Leave a comment:
-
Not sure if this is what you want but you can do these via GUI rather than coding. Currently this is good enough for me, in future I want to separate the History side panel into another area.
There was one more screenshot I wanted create but I can't find where it is.
I think this post might answer your question: https://www.eblasoft.com.tr/post/clo...ity-in-espocrm1 PhotoLast edited by esforim; 03-11-2020, 08:03 AM.
Leave a comment:
-
I'm sorry, I am not familiar with the internal mechanics of "attendees" which is translated as "Participant" in the Espo language files but I believe that a good source to learn about it would be to study the "Meeting" entity and see how "attendees" are handled there.
In a "Meeting" entity, "attendees" are actually "Leads"
"Meeting" and "Lead" are linked as a One-to-Many relationship
The relevant scripts that you might want to check are:
application\Espo\Modules\Crm\Resources\metadata\cl ientDefs\Meeting.json
application\Espo\Modules\Crm\Resources\metadata\en tityDefs\Meeting.json
client\modules\crm\views\meeting\fields\attendees. js
client\modules\crm\views\meeting\record\panels\att endees.js
Once you are able to implement your customization, it would be greatly appreciated if you can post the solution here.
Best wishesLast edited by telecastg; 03-11-2020, 05:59 AM.
Leave a comment:
Leave a comment: