Tutorial - How to add electronic signature capability to espoCRM

Collapse
X
 
  • Time
  • Show
Clear All
new posts

  • item
    replied
    Hello,
    you must add eSignature field to your entity. it's a new type of field like varchar, integer, float, image,....
    go admin section, entity manager, contact, add field "eSignature".
    same for opportuinity.

    Leave a comment:


  • alexisc
    replied
    2) Make sure that you are using the "print" icon at the top of the document to print and not the Print to Pdf Espo option
    Thanks for your reply!
    this is my mistake, it looks like eSignature only works for "Work Order". I am trying to use it on an existing Entity - "Contact" and "Opportunity". I do not have "Display eSignature Document" in the menu, only "Print to PDF"

    if there is a solution to how to connect "eSignature", I will be very grateful for the hint




    Leave a comment:


  • telecastg
    replied
    Originally posted by alexisc
    Espo Version 6.0.8
    eSignature version 2.0.0

    everything works perfectly, until the moment the signature is printed

    place holder @@sig[{{eSignature}}]/sig@@ displays SVG code, not the image itself
    We have the extension (same version of Espo and eSignature) running without a problem.

    Suggestions:

    1) Try: @@sig[eSignature]/sig@@ (no double bracket)
    2) Make sure that you are using the "print" icon at the top of the document to print and not the Print to Pdf Espo option


    Originally posted by alexisc
    code "<img src="{{field-name}}">" - does not work
    That is Espo's handlebars notation, will not display SVG images at present. The eSignature extension uses the custom placeholder to render SVG
    Last edited by telecastg; 01-12-2021, 07:55 PM.

    Leave a comment:


  • alexisc
    replied
    Espo Version 6.0.8
    eSignature version 2.0.0

    everything works perfectly, until the moment the signature is printed

    place holder @@sig[{{eSignature}}]/sig@@ displays SVG code, not the image itself

    code "<img src="{{field-name}}">" - does not work

    Leave a comment:


  • telecastg
    replied
    New version (2.0.0) released, compatible with Espo 6.0.6 https://github.com/telecastg/esignat...ts-for-espocrm

    Leave a comment:


  • telecastg
    replied
    New version released, addresses the deprecation of isPortalUser attribute in User entity.

    GitHub is where people build software. More than 100 million people use GitHub to discover, fork, and contribute to over 420 million projects.

    Leave a comment:


  • Fehu
    replied
    Off-topic but there are some great coders in here. Do you know what would need to be done to embed a WordPress page in an iframe dashlet? If anybody knows that would be great!

    Leave a comment:


  • esforim
    commented on 's reply
    Welcome new member! Please enjoy EspoCRM

  • Entony
    replied
    Good tutorial, very helpful thanks a lot.

    Leave a comment:


  • telecastg
    replied
    I would use a generic "start date" field and create different templates for each type of agreement, since each document should have difference verbiage and all you want to do is to fill in placeholders with field information.

    Leave a comment:


  • esforim
    replied
    I'm quite interested in this new post of yours too telecastg! We have multiple agreement (of which is one Lease one type of agreement we also do).

    Food for thought though.

    How do should field be created? For example with Lease you have have a "move in date", so a field you would want a field for that. But if we do something like a "Employment agreement", then there is no such thing as a "Move in date", maybe a "start working date".

    Would you create a Generic field such as "Start date" which would be applicable in general. Or would it be more wise to create Multiple Layout, and depend on Agreement type field and or layout get show and hidden.

    Leave a comment:


  • telecastg
    commented on 's reply
    You're very welcome :-), I posted some last minute lines (not sure if you saw them already) regarding the possibility of sending notifications once a document has been signed.

  • Fehu
    replied
    This is fantastic!! I really appreciate your post and knowledge. I'm going to use this and create this tomorrow. I can't thank you enough!

    -Marcus

    Leave a comment:


  • telecastg
    replied
    ​5) Create the custom entry point invoked by the controller in Step 4 above, to retrieve the Lease template id from the database

    custom/Espo/Custom/EntryPoints/Lease.php
    PHP Code:
    namespace Espo\Custom\EntryPoints;
    
    use \Espo\Core\Exceptions\NotFound;
    use \Espo\Core\Exceptions\Forbidden;
    use \Espo\Core\Exceptions\BadRequest;
    
    class Lease extends \Espo\Core\EntryPoints\Base
    {
        public static $authRequired = true;
    
        // default action
        public function run() {
            // excecute actions invoked, if none specified throw error
            if(empty($_GET['action'])) {
                throw new BadRequest();
            } else {
                $action = $_GET['action'];
            }
    
            switch ($action) {
    
                case 'getTemplateIdByTenancy':
                    if (empty($_GET['tenancyId'])) {
                        throw new BadRequest();
                    } else {
                        $tenancyId = $_GET['tenancyId'];
                    }
                    $tenancyObject = $this->getEntityManager()->getRepository('Tenancy')->where(['id'=>$tenancyId,'status'=>'Active'])->findOne();
                    $templateObject = $this->getEntityManager()->getRepository('Template')->where(['id'=>$tenancyObject->get("templateId")])->findOne();
                    $templateData['templateId'] = $templateObject->get('id');
                    echo(json_encode($templateData));
                break;
    
                default:
                    //code to be executed if $action is different from all labels;
            }
        }
    } 
    
    Note that in our application the template id is part of the Tenancy entity, so you don't have to select one every time that you want to render the Lease, so in your case you might want to include the template id also in your Agreement entity.

    6) Create a "Tenant" role and give that role access to "My Lease" (the human friendly name for "Lease") like this:
    Click image for larger version  Name:	Tenant My Lease Permissions.PNG Views:	0 Size:	3.0 KB ID:	61221

    7) Include the Tab "My Lease" in the Tenant Portal:
    Click image for larger version  Name:	Tenant Portal Tab List.PNG Views:	0 Size:	4.4 KB ID:	61222

    8) Clear cache and Rebuild.

    Now, when a Tenant clicks on the My Lease menu item, the lease document displays like this, with the esignature panel at the bottom. Click image for larger version  Name:	Full Page Lease Display.png Views:	0 Size:	41.6 KB ID:	61220

    Bonus benefit: if a user is accessing the portal from a mobile phone, the document will span the full screen and the esignature panel will have a decent size so it can be easily signed, so this feature is already "mobile" friendly and you can adapt these techniques to implement other mobile centric projects with Espo.

    Also, is it possible to get notified once a person does e-sign a document?
    Yes, you could create an afterSave hook for "Agreement" or your entity name to send an email or trigger a notification when the esignature field (your field name) is NOT empty.

    We don't use that functionality so I can't provide any code samples but you can check the documentation if you are not familiar with hooks and notifications.
    Last edited by telecastg; 08-05-2020, 08:21 PM.

    Leave a comment:


  • telecastg
    replied
    Hi Marcus,

    I am thinking of using this as an onboarding tool. As an example, when new agents are looking to get hired we currently send them a document that requires a custom signature. What I'm thinking of doing is creating a document/role-based where I give them access to the CRM via login credentials. Once they login they only see a document where they can agree and sign. Once this is completed I would change their role (in essence, unlock other parts of the CRM), etc.

    Thoughts and do you think that would be a good use of this technology?
    Yes I think that it is possible. In our application we prepare leases for new tenants (a document based on a "Tenancy" entity) and let the new tenants know that the lease is ready for signature, so they can go to the "Tenant" portal which has a custom "My Lease" menu option that when clicked displays the Lease in full page view and the tenant can see and esign this document.

    Click image for larger version  Name:	My Lease menu option.PNG Views:	0 Size:	5.3 KB ID:	61218

    I think that you could adapt this to your application. This is how we did it:

    1) Create a custom "Lease" scope (NOT an entity) so we are able to create a menu option and add it to the navbar:
    custom/Espo/Custom/Resources/metadata/scopes/Lease.json
    Code:
    {
        "entity": false,
        "tab": true,
        "acl": "true",
        "aclPortal": true,
        "aclPortalLevelList": [
            "all",
            "account",
            "contact",
            "own",
            "no"
        ],
        "disabled": false,
        "module": "Custom",
        "isCustom": true
    }
    In your case, this could be an "OnboardingAgreement" scope

    2) Create or update the language file to make the scope name "human friendly"
    custom/Espo/Custom/Resources/i18n/en_US/Global.json
    Code:
    {
        "scopeNames": {
            "Lease": "My Lease"
        },
        "scopeNamesPlural": {
            "Lease": "My Lease"
        }
    }
    3) Create a clientDefs metadata file for the "Lease" scope that will tell Espo what front end controller to call when the menu option in the navbar is clicked
    custom/Espo/Custom/Resources/metadata/clientDefs/Lease.json
    Code:
    {
        "controller": "custom:controllers/lease",
        "color": "#00ff66",
        "iconClass": "fas fa-file-contract"
    }
    4) Create the front end controller that will execute the necessary code to display the lease
    client/custom/src/controllers/lease.js
    Code:
    Espo.define('custom:controllers/lease', 'controllers/base', function (Dep) {
    
        return Dep.extend({
    
            leaseScope: "Tenancy",
    
            // default action
            actionIndex: function () {
                var options = {};
                var isPortal = '0';
                if(this.getUser().attributes.isPortalUser) {
                    isPortal = '1';
                }
                var self = this;
    
                this.viewFactory.create('views/modals/select-records', {
                    scope: 'Tenancy',
                    multiple: false,
                    createButton: false,
                    triggerCreateEvent: false,
                    filters: {},
                    massRelateEnabled: false,
                    primaryFilterName: '',
                    boolFilterList: []
                    }, function(dialog) {
                        if(dialog.collection.length &gt; 1) {
                            // this will apply only if the tenant is linked to 2 or more tenancies
                            dialog.render();
                            Espo.Ui.notify(false);
                            dialog.listenToOnce(dialog, 'select', function (selectObj) {
                                var data = {};
                                if (Object.prototype.toString.call(selectObj) === '[object Array]') {
                                    var ids = [];
                                    selectObj.forEach(function (model) {
                                        ids.push(model.id);
                                    });
                                    data.ids = ids;
                                } else {
                                    if (selectObj.massRelate) {
                                        data.massRelate = true;
                                        data.where = selectObj.where;
                                    } else {
                                        data.id = selectObj.id;
                                    }
                                }
                                options = {
                                    entityType: 'Tenancy',
                                    entityId: data.id,
                                    isPortal: isPortal,
                                    templateId: ''
                                };
                                self.actionRenderLease(options);
                            }, this);
                        } else {
                            options = {
                                entityType: 'Tenancy',
                                entityId: dialog.collection.models[0].attributes.id,
                                isPortal: isPortal,
                                templateId: ''
                            };
                            self.actionRenderLease(options);
                        }
                }, this);
            },
    
            actionRenderLease: function (options) {
                var entityType = options.entityType;
                var entityId = options.entityId;
                var isPortal = options.isPortal;
                var templateId = '';
                var templateName = '';
                var self = this;
                // get the complete lease object
                this.collectionFactory.create(options.entityType, function (scopeList) {
                    scopeList.fetch().then(function(){
                        var leaseObject = scopeList.get(entityId);
                        // get the template data
                        var url = '?entryPoint=lease&amp;tenancyId='+entityId+'&amp;action=getTemplateIdByTenancy';
                        var xmlhttp = new XMLHttpRequest();
                        xmlhttp.onreadystatechange = function() {
                            if (xmlhttp.readyState === XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4
                                // if the ajax call is successful load the template values into the options object
                                if (xmlhttp.status === 200) {
                                    var templateObject = JSON.parse(xmlhttp.responseText);
                                    templateId = templateObject.templateId;
                                    // build the options object to pass to the view which will render the lease
                                    var options = {
                                        entityType: entityType,
                                        entityId: entityId,
                                        templateId: templateId,
                                        model: leaseObject,
                                        isPortal: isPortal
                                    };
                                    // invoke the esignature front end controller esignature-document function "showDocument" that will render the document in full page view
                                    self.getRouter().dispatch("EsignatureDocument", 'showDocument', options);
                                } else if (xmlhttp.status === 400) {
                                    alert('There was an error 400');
                                } else {
                                    alert('something else other than 200 was returned');
                                }
                            }
                        };
                        xmlhttp.open("POST",url , true);
                        xmlhttp.send();
                    });
                });
            }
    
        });
    });
    In our case, we have tenants that are linked to more than one Tenancy, thus they might have more than one lease, but I think that in your case you would only have one agreement per salesperson, so you might want to simplify the actionIndex() function above.

    This post is going to exceed the maximum number of characters so it will continue below.
    Last edited by telecastg; 08-05-2020, 07:47 PM.

    Leave a comment:

Working...