Participant functionality in a custom entity.

Collapse
X
 
  • Time
  • Show
Clear All
new posts

  • esforim
    commented on 's reply
    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.
    Last edited by esforim; 03-18-2020, 07:31 AM.

  • esforim
    commented on 's reply
    Nevermind: Speak of the devil: https://github.com/espocrm/espocrm/issues/1642

    I think if I update to 5.8.4 it should work.

  • esforim
    replied
    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?

    Leave a comment:


  • Maximus
    commented on 's reply
    You can send an invitation to the users (any type), contacts and leads as well.

  • esforim
    replied
    Originally posted by Maximus
    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.
    So they must be a portal user? They can't just be a Contact/Account?

    Leave a comment:


  • telecastg
    replied
    Thanks Maximus can you tell me which scripts I should explore ?

    Leave a comment:


  • Maximus
    replied
    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:


  • esforim
    commented on 's reply
    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.

  • esforim
    replied
    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:
    Click image for larger version

Name:	Meeting Invitation 2 - email.png
Views:	580
Size:	6.6 KB
ID:	56716

    Meeting layout (top right for Send Invitation)
    Click image for larger version

Name:	Meeting Invitation.png
Views:	723
Size:	67.2 KB
ID:	56715
    Last edited by esforim; 03-12-2020, 02:17 AM.

    Leave a comment:


  • telecastg
    replied
    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)"
    
    } 
    
    Thanks in advance.

    Leave a comment:


  • Maximus
    replied
    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"
            }
        }
    }
    6. Add the next code into the file /custom/Espo/Custom/Controllers/Event.php:

    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);
        }
    }
    7. Add the next code into the file /custom/Espo/Custom/Services/Event.php:

    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;
        }
    }
    8. Add the next code into the file /custom/Espo/Custom/Repositories/Event.php:

    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');
                }
            }
        }
    
    }
    9. Administration -> Rebuild.

    10. F5 to refresh a web page.

    Leave a comment:


  • Maximus
    replied
    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 .....
    }
    Note: be sure that your tables names and fields are corresponded to DB.

    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 .....
    }
    3. Add to the file /custom/Espo/Custom/Resources/metadata/entityDefs/Lead.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": "leads",
                "layoutRelationshipsDisabled": true,
                "audited": true
            }
        }
    }
    4. Add to the file /custom/Espo/Custom/Resources/metadata/entityDefs/Contact.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": "contacts",
                "layoutRelationshipsDisabled": true,
                "audited": true
            }
        }
    }

    Leave a comment:


  • fcm.moura
    replied
    Okay. I'll try the suggestions mentioned. Then I post the results here on the forum.
    Thanks

    Leave a comment:


  • esforim
    replied
    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-espocrm
    Last edited by esforim; 03-11-2020, 08:03 AM.

    Leave a comment:


  • telecastg
    replied
    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 wishes
    Last edited by telecastg; 03-11-2020, 05:59 AM.

    Leave a comment:

Working...