Tutorial - How to add electronic signature capability to espoCRM

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • llsatellite
    Junior Member
    • May 2020
    • 12

    #16
    Thank you for all your help,
    One good addition in the future versions will be to be able to add a button next to the print button that let you send the work order to the inbox in order to send it to the customer by email with out printing and saving the file manually, if it does exist i would like to know were in the forum are the instructions on how to do it

    Comment

    • telecastg
      Active Community Member
      • Jun 2018
      • 907

      #17
      You are very welcome,

      One good addition in the future versions will be to be able to add a button next to the print button that let you send the work order to the inbox in order to send it to the customer by email with out printing and saving the file manually, if it does exist i would like to know were in the forum are the instructions on how to do it
      There is no such capability at present, if we make any improvements and release a new version in the future, I will look into incorporating your suggestions.

      Comment

      • llsatellite
        Junior Member
        • May 2020
        • 12

        #18
        I see that espo have the option to send in email in the "Knowledge Base" entity in detail in the drop down menu and it attach the attached file and create the email automatic after clicking on it. may be we can use that same option below "Display eSignature Document" option is the same function but i just don't know how to clone that option from "Knowledge Base"

        Comment

        • esforim
          Active Community Member
          • Jan 2020
          • 2204

          #19
          How can I make both Print2PDF button appear (your eSginature version and the EspoCRM version)? Sometime I don't want to use the WebBrowser PDF renderer and just want a render PDF documents using default engine (look like Firefox PDF is not as good or I'm doing something wrong).

          Currently the guide/tutorial "remove" the old button/menu and replaced it with eSignature version.

          Thank you.

          Comment

          • esforim
            Active Community Member
            • Jan 2020
            • 2204

            #20
            Originally posted by llsatellite
            I see that espo have the option to send in email in the "Knowledge Base" entity in detail in the drop down menu and it attach the attached file and create the email automatic after clicking on it. may be we can use that same option below "Display eSignature Document" option is the same function but i just don't know how to clone that option from "Knowledge Base"
            Wow I never noticed this "Send in Email" button. I'm curious to implement this button in other entity to, but not sure if this thread is where we should discuss about it.

            Comment

            • telecastg
              Active Community Member
              • Jun 2018
              • 907

              #21
              How can I make both Print2PDF button appear (your eSginature version and the EspoCRM version)?
              1) Open script client/modules/esignature/src/views/record/detail.js
              2) Comment line this.removeButton('printPdf');
              3) Save the script and Rebuild

              Both buttons should appear in your esignature capable entities.

              (look like Firefox PDF is not as good or I'm doing something wrong).
              I use Chrome and Edge without any problems, after clicking on the "Print" icon at the top of the document, choose "Save as PDF" in the printer dialog and your document will be rendered exactly as it appears in the screen.

              Last edited by telecastg; 06-06-2020, 05:48 PM.

              Comment


              • esforim
                esforim commented
                Editing a comment
                Manage to get around to doing it, bought back the menu by doing adding comments. Thank you.

                It could be because of Plugins I use, or I'm doing something wrong, need to explore. Firefox PDF don't have option such as Remove Footer/Header like Chrome does for example.
                Last edited by esforim; 06-30-2020, 07:39 AM.
            • Fehu
              Member
              • Jan 2019
              • 64

              #22
              I have a quick question.

              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?

              Also, is it possible to get notified once a person does e-sign a document?

              Thanks,
              Marcus

              Comment

              • telecastg
                Active Community Member
                • Jun 2018
                • 907

                #23
                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 > 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&tenancyId='+entityId+'&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.

                Comment

                • telecastg
                  Active Community Member
                  • Jun 2018
                  • 907

                  #24
                  ​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.

                  Comment

                  • Fehu
                    Member
                    • Jan 2019
                    • 64

                    #25
                    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

                    Comment


                    • telecastg
                      telecastg commented
                      Editing a comment
                      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.
                  • esforim
                    Active Community Member
                    • Jan 2020
                    • 2204

                    #26
                    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.

                    Comment

                    • telecastg
                      Active Community Member
                      • Jun 2018
                      • 907

                      #27
                      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.

                      Comment

                      • Entony
                        Banned
                        • Aug 2020
                        • 1

                        #28
                        Good tutorial, very helpful thanks a lot.

                        Comment


                        • esforim
                          esforim commented
                          Editing a comment
                          Welcome new member! Please enjoy EspoCRM
                      • Fehu
                        Member
                        • Jan 2019
                        • 64

                        #29
                        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!

                        Comment

                        • telecastg
                          Active Community Member
                          • Jun 2018
                          • 907

                          #30
                          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.

                          Comment

                          Working...