Question

I cannot delete a participant from an activity. The error message informs that I canot delete the Owner, but the participant in fact IS NOT the owner.

Answer

-- downgrading to a participant
SELECT ac.Id, Title, OwnerId, ap.ParticipantId, ap.Id as ApId FROM Activity ac
LEFT JOIN ActivityParticipant ap on ap.RoleId = '53fc4a92-b0ea-e111-96c4-00165d094c12'
AND ac.Id = ap.ActivityId
WHERE OwnerId <> ap.ParticipantId
--
UPDATE ActivityParticipant SET RoleId = '1a8324e8-a6e1-df11-971b-001d60e938c6'
WHERE Id In (
SELECT ap.Id FROM Activity ac
LEFT JOIN ActivityParticipant ap on ap.RoleId = '53fc4a92-b0ea-e111-96c4-00165d094c12'
AND ac.Id = ap.ActivityId
WHERE OwnerId <> ap.ParticipantId
)
-- upgrading to an owner
SELECT ac.Id, Title, OwnerId, ap.ParticipantId, ap.Id as AppId, ap.RoleId FROM Activity ac
LEFT JOIN ActivityParticipant ap on ac.Id = ap.ActivityId
WHERE ac.OwnerId = ap.ParticipantId AND
ap.RoleId <> '53fc4a92-b0ea-e111-96c4-00165d094c12'
--
UPDATE ActivityParticipant Set RoleId = '53fc4a92-b0ea-e111-96c4-00165d094c12'
WHERE Id In (
SELECT ap.Id FROM Activity ac
LEFT JOIN ActivityParticipant ap on ac.Id = ap.ActivityId
WHERE ac.OwnerId = ap.ParticipantId AND
ap.RoleId <> '53fc4a92-b0ea-e111-96c4-00165d094c12'
)

 

Like 0

Like

Share

0 comments
Show all comments

Case description:

We want to change color of activity on ActivitySection.

Algorithm of realization:

  1. You should create client replacing module for "ActivitySectionV2" from NUI package.
  2. Add to this module three methods:
    1.  getGridDataColumns - this method configures which fields we will get from DB for each activity;

      getGridDataColumns: function() {
          var baseGridDataColumns = this.callParent(arguments);
          var gridDataColumns = {
              "Account": {path: "Account"},
              "StartDate": {path: "StartDate"},
              "DueDate": {path: "DueDate"},
              "ShowInScheduler": {path: "ShowInScheduler"},
              "Status": {path: "Status"},
              //Added by me, because in base implementation this method doesn't get this field
              "Result": {path: "Result"},
              "Status.Finish": {path: "Status.Finish"},
              "ProcessElementId": {
                  path: "ProcessElementId",
                  dataValueType: 0
              }
          };
          return Ext.apply(baseGridDataColumns, gridDataColumns);
      },

       

    2. prepareResponseCollection and setColor - this methods set background color for activities;

      prepareResponseCollection: function(collection) {
          var notStartedId = ConfigurationConstants.Activity.Status.NotStarted;
          var inProgressId = ConfigurationConstants.Activity.Status.InProgress;
          var finishedId   = ConfigurationConstants.Activity.Status.Done;
          var canceledId   = ConfigurationConstants.Activity.Status.Cancel;
       
          this.callParent(arguments);
       
          collection.each(function(item) {
              var status = item.get("Status").value;
              if (status === notStartedId.toLowerCase()) {
                  this.setColor(item, "#42f4dc");
              } else if (status === inProgressId.toLowerCase()) {
                  this.setColor(item, "#4168f4");
              } else if (status === finishedId.toLowerCase()) {
                  this.setColor(item, "#88f441");
              } else if (status === canceledId.toLowerCase()) {
                  this.setColor(item, "#f441af");
              }
          }, this);
      },
      setColor: function(item, color) {
          //Items in register
          item.customStyle = {
              background: color,
          };
          //Items on calendar view
          item.set("Background", color);
      }

       

  3. In this case we change color depend on activity status, but you can use another field. For example - Result. You can get Result id of item by call item.get("Result").value. IMPORTANT! You must add this field to getGridDataColumns method. Identificators of default activity results you can get by next query

    SELECT Name, Id FROM ActivityResult

    to your bpm'online DB.

  4. This code can work for ActivityDetail too. Algorithm of realization is same.
  5. If you want you can store your color settings in Lookup.
    1. Create lookup which will looks like this:

      Figure 0.

    2. Add method which will get them from DB

      init: function() {
          this.callParent(arguments);
          this.getColorsFromLookup();
      },
      getColorsFromLookup: function() {
          var colors = {};
          var esq = Ext.create("Terrasoft.EntitySchemaQuery", {
              rootSchemaName: "GlbActivityColor"
          });
          esq.addColumn("Name");
          esq.addColumn("GlbCode");
          esq.getEntityCollection(function (result) {
              if (!result.success) {
                  return;
              }
              result.collection.each(function (item) {
                  colors[item.get("Name")] = item.get("GlbCode");
              });
              this.set("Colors", colors);
              debugger;
          }, this);
      }

       

    3. Add attribute which will store colors at front-end

      attributes: {
          Colors: {
              dataValueType: this.Terrasoft.DataValueType.CUSTOM_OBJECT
          },
      },

       

    4. Fix prepareResponseCollection method for using colors from our attribute

      prepareResponseCollection: function(collection) {
          var notStartedId = ConfigurationConstants.Activity.Status.NotStarted;
          var inProgressId = ConfigurationConstants.Activity.Status.InProgress;
          var finishedId   = ConfigurationConstants.Activity.Status.Done;
          var canceledId   = ConfigurationConstants.Activity.Status.Cancel;
       
          this.callParent(arguments);
       
          collection.each(function(item) {
              var status = item.get("Status").value;
              if (status === notStartedId.toLowerCase()) {
                  this.setColor(item, this.get("Colors")["notStarted"]);
              } else if (status === inProgressId.toLowerCase()) {
                  this.setColor(item, this.get("Colors")["inProgress"]);
              } else if (status === finishedId.toLowerCase()) {
                  this.setColor(item, this.get("Colors")["finished"]);
              } else if (status === canceledId.toLowerCase()) {
                  this.setColor(item, this.get("Colors")["canceled"]);
              }
          }, this);
      },

       

Like 0

Like

Share

1 comments

Color is not getting updated once activity edit page is saved and closed when activity edit page is opened from onScheduleItemDoubleClick method

Show all comments

Case:

How to send email from script task from portal? I am using 

Script task code

var emailClientFactory = ClassFactory.Get<EmailClientFactory>(new ConstructorArgument("userConnection", UserConnection));
var activityEmailSender = new ActivityEmailSender(emailClientFactory, UserConnection);
activityEmailSender.Send(AddDataUserTask1.RecordId);

And i get errors on sending. I've already added Terrasoft.Core.Factories, Terrasoft.Mail.Sender, Terrasoft.Mail namespaces to my process.

Solutuion:

1. Create a shared mailbox and allow access to it for portal users

2. Open access to following objects for portal users:

  • Activity
  • ActivityStatus
  • ActivityParticipant
  • ActivityType
  • ActivityCategory
  • ActivityParticipantRole
  • MailboxSyncSettings
  • MailServerType
  • MailServer
  • EmailTemplate
  • EmailMessageData
  • EmailSendStatus
  • EmailType

3. Allow access for portal users for SysSetting with code IgnoreExchangeSSLWarnings and make sure it is NOT cached

Like 0

Like

Share

0 comments
Show all comments

Symptoms

A customer has the "Payment status", "Delivery status" fields together with currency dashboards displayed on all tabs of the order page. These fields cannot be edited via the section wizard, since they are displayed the same way there.

Cause

The OrderPageV2 page was developed by a partner and contained the following code for the specified elements:

{
    "operation": "merge",
    "name": "PaymentStatus",
    "values": {
        "layout": {
            "column": 0,
            "row": 1,
            "colSpan": 12,
            "rowSpan": 1
        },
        "caption": {
            "bindTo": "Resources.Strings.PaymentStatusCaption"
        },
        "enabled": true
    }
},
{
    "operation": "remove",
    "name": "PaymentStatus",
    "properties": [
        "contentType",
        "parentName",
        "propertyName"
    ]
},
{
    "operation": "move",
    "name": "PaymentStatus",
    "index": 1
}

Solution

Change the page code for the specified elements by eliminating partner logic, for example, for:

{
    "operation": "insert",
    "name": "PaymentStatus",
    "values": {
        "bindTo": "PaymentStatus",
        "layout": {
            "column": 0,
            "row": 6,
            "colSpan": 12,
            "rowSpan": 1
 },
        "contentType": Terrasoft.ContentType.ENUM,
        "caption": {
            "bindTo": "Resources.Strings.PaymentStatusCaption"
 },
        "enabled": true
 },
    "parentName": "Header",
    "propertyName": "items",
    "index": 9
}

Necessary conditions and possible restrictions:

Particular case, individual solution

Like 0

Like

Share

0 comments
Show all comments

Question

Terrasoft.Geolocation.getCurrentCoordinates is used to determine GPS coordinates on an Android mobile device, which for some reason does not launch the GPS module (the GPS start indicator does not appear in the UI of the device). 

Is it possible to collect the coordinate information without enabling this module (maybe a coordinate cache?)

Answer

Yes, the GPS service can be disabled. The data can be collected based on mobile device settings (from Wi-Fi points), or it can be cached. 

In fact, the way bpm'online collects the coordinates can be controlled. Below is an example of the current implementation of the mentioned method:

getCurrentCoordinates: function(config) {
   var enableHighAccuracy = !Terrasoft.Connection.isOnline() ||
      Terrasoft.Connection.getType() !== Terrasoft.ConnectionTypes.WiFi;
   var geo = Ext.create("Ext.util.Geolocation", {
      autoUpdate: false,
      allowHighAccuracy: enableHighAccuracy,
      timeout: 60000,
      listeners: {
         scope: this,
         locationupdate: function(geo) {
            Ext.callback(config.success, config.scope, [geo.getLatitude(), geo.getLongitude()]);
         },
         locationerror: function(geo, timeout, permissionDenied, locationUnavailable, message) {}
      }
   });
   geo.updateLocation();
}

At the moment, if there is no Internet connection or if the device is not connected to Wi-Fi (for example, you're using EDGE or 3G), then the “accurate” positioning will be used (GPS), but otherwise the data will be obtained using Wi-Fi, caching, etc., providing less accurate location information, but sufficient for given business tasks.

Nevertheless, if you need to receive data using GPS in all cases, you can create your own implementation of the action, similar to how it is implemented in bpm'online:

getCurrentCoordinates: function(config) {
   var geo = Ext.create("Ext.util.Geolocation", {
      autoUpdate: false,
      allowHighAccuracy: true,
      timeout: 60000,
      listeners: {
         scope: this,
         locationupdate: function(geo) {
            Ext.callback(config.success, config.scope, [geo.getLatitude(), geo.getLongitude()]);
         },
         locationerror: function(geo, timeout, permissionDenied, locationUnavailable, message) {}
      }
   });
   geo.updateLocation();
}

This method has a number of problems:

- despite the fact that the GPS method is more accurate, the time for receiving such data can be quite long (up to 10-15 minutes).

- the waiting time for receiving the response should be increased, i.e. the timeout parameter in the code above will need to be set to more than 1 minute (60,000 ms).

- this method may decrease your Android device's battery life.

Like 0

Like

Share

1 comments

hi Kirill Potapkin,



Could you please assist with where to add this schema?

1. When does this schema gets triggered?

2. what is geo.updatelocation method performs?

3. How to capture the GPS co-ordinates and store it in a field in custom section.

 

 

BR,

Bhoobalan Palanivelu.

Show all comments

Symptoms

The "Lookups" menu option in the system designer gets duplcated upon upgrading to version 7.6.0

Cause

A "Lookup" package that has been deleted from version 7.6.0 and whose contents have been transferred to other packages, remained in the system.

Solution

If no client logic is bound to the schemas of this package, delete the "Lookup" package dependencies to other configuration packages and afterwards delete the package itself.

Necessary conditions and possible resitrictions

Prior to getting down to the solution implementation, make a database backup. After you implement the solution, perform the "Compile all items" action. If there exists any client logic bound to the package, additional analysis is needed.

Like 0

Like

Share

0 comments
Show all comments

Case description:

We need to edit field of some record using custom action in the section. We will add 2 extra  fields (Warranty Period, Delivery Period) in the section. When we search product, We would like to realize the following function:

Section-> Actions-> Select multiple records > Change Warranty Period or Change Delivery Period. You can see it in the Figure 0.

Figure 0. Custom action using for data update.

In this case, there should be a small popup and I we change the value there.

Algorithm of realization.

  1. Create replacing page of section .
  2. Add localizable string like "Resources.Strings.MultiplyChangeAction" and bind this string to caption.



    Figure 1. Localizable string properties.



     
  3. Add function “isCustomActionEnabled” that determines conditions of the clickability of the action. This function determines whether the action is active. In our example, the action is active if at least an entry is selected.

     

    isCustomActionEnabled: function() {
                        var selectedRows = this.get("SelectedRows");
                        return selectedRows ? (selectedRows.length > 0) : false;
                    },  

     

  4. Add function "getSectionActions" is an owerriden virtual method in which to bind the handler method to an action. 

    getSectionActions: function() {
                        var actionMenuItems = this.callParent(arguments);
                        actionMenuItems.addItem(this.getButtonMenuItem({
                            "Caption": {bindTo: "Resources.Strings.MultiplyChangeAction"},
                            "Click": {bindTo: "showLoanInfo"},
                            "Enabled": {bindTo: "isCustomActionEnabled"}
                        }));
                        return actionMenuItems;
                    }

     

  5.  Add function "showLoanInfo" - action handler method which will show the user a window for data entry using "Terrasoft.utils.inputBox". After user clicks "OK", we need to update the record. 

    showLoanInfo: function() {
                    Terrasoft.utils.inputBox("Set fields for update", function(result, arg) {
                            if (result === Terrasoft.MessageBoxButtons.YES.returnCode) {
                                var warrantyPeriod = arg.warranty.value;
                                var deliveryPeriod= arg.delivery.value;
                                var autoIds = this.getSelectedItems();
                                this.updateProduct(function(context, result) {
                                }, autoIds, deliveryPeriod, warrantyPeriod);
     
                            }
                        }, [{
                            className: "Terrasoft.Button",
                            caption: "OK",
                            returnCode: "yes"
                        }, "cancel"], this,
                        {
                            warranty: {
                                dataValueType: Terrasoft.DataValueType.INTEGER,
                                caption: "Warranty",
                                customConfig: {
                                    className: "Terrasoft.MemoEdit",
                                    height: "17px"
                                }
                            },
                            delivery: {
                                dataValueType: Terrasoft.DataValueType.INTEGER,
                                caption: "Term",
                                customConfig: {
                                    className: "Terrasoft.MemoEdit",
                                    height: "17px"
                                }
                            }
                        },
                        {
                            defaultButton: 0,
                            style:  {
                                borderStyle: "ts-messagebox-border-style-blue ts-messagebox-border-no-header",
                                buttonStyle: "blue"
                            }
                        }
                    );
                },

    You can see generated Pop-Up in the next Figure.

    Figure 2. Pop-Up using for multiply update.

  6.  Add update function using "Terrasoft.UpdateQuery". We choose a filter of type "OR" to choise selected records. 

updateProduct: function(callback, autoIds, valueWarranty, valueDelivery) {
                        var update = Ext.create("Terrasoft.UpdateQuery", {
                            rootSchemaName: "Product"
                        });
                        var filters = Terrasoft.createFilterGroup();
                        filters.logicalOperation = Terrasoft.LogicalOperatorType.OR;
                        autoIds.forEach(function(item) {
                            var productIdFilter = update.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL,
                            "Id", item);
                            filters.add("ProductIdFilter" + item, productIdFilter);
                        }, this);
                        update.filters.add(filters);
                        if (valueWarranty) {
                            update.setParameterValue("UsrWaranty", valueWarranty, Terrasoft.DataValueType.TEXT);
                        }
                        if (valueDelivery) {
                            update.setParameterValue("UsrDelivery", valueDelivery, Terrasoft.DataValueType.TEXT);
                        }
                        update.execute(function(result) {
                            callback.call(this, result);
                        }, this);
                    },

 

Like 2

Like

Share

5 comments

Hello, I think isCustomActionEnabled function not working when I click Select All button.

 

Do you have any suggestion? I made some tracing and this function is calling and returning true. but custom action button is not enabled.

 

But if I manually select one/some/all rows it is enabled. Also, when I manually select all rows and then click Select All button, it is somehow setting disabled state.



For test, I returned true always from bind function.

Also, I removed Enable binding and got same result

 

I found one property which is necessary to enable this menu item on select all.

"IsEnabledForSelectedAll": true

Hi Luka,

 

The custom action is not active when using Select All option on purpose. The majority of default actions are not active as well. It is designed to prevent changing all data in the section, for example from deleting data or merging it. That's why majority of actions are greyed out in the drop down list.

 

Regards,

Dean

Hello,

Is callback function not required here?

This not working without it. Please provide the definition for it.

Show all comments

1. Client side

Add next code to your page in methods section:

methods: {
    init: function() {
        this.callParent(arguments);
        Terrasoft.ServerChannel.on(Terrasoft.EventName.ON_MESSAGE, this.onMessageReceived, this);
    },
    onMessageReceived: function(sender, message) {
        if (message && message.Header && message.Header.Sender === "MySenderName") {
            var result = this.Ext.decode(message.Body);
            /// TODO: your code
        }
    },
    destroy: function() {
        this.Terrasoft.ServerChannel.un(Terrasoft.EventName.ON_MESSAGE, this.onMessageReceived, this);
        this.callParent(arguments);
    }
}

2. Server side

Add usings

using Terrasoft.Configuration;
using Terrasoft.Messaging.Common;
using Newtonsoft.Json;

Send message

string senderName = "MySenderName";
// Example for message
string message = JsonConvert.SerializeObject(new {
    RecordId = Guid.NewGuid(), // your record Id
    Name = "Some name"
    // some other parameters
});
 
// For all users
MsgChannelUtilities.PostMessageToAll(senderName, message);
 
// For current user
MsgChannelUtilities.PostMessage(UserConnection, senderName, message);
 
// For specific user with sysAdminUnitId
IMsgChannel channel = MsgChannelManager.Instance.FindItemByUId(sysAdminUnitId);
if (channel != null) {
    var simpleMessage = new SimpleMessage() {
        Id = sysAdminUnitId,
        Body = message,
        Header = {
            Sender = senderName
        }
    };
    channel.PostMessage(simpleMessage);
}

 

Like 4

Like

Share

5 comments

Hi Tatiana. I know this is an old article. Follow up question - 



Can we use the in-built WebSocket to communicate back from the client to the server??

M Shrikanth,

 

Please check the response here 

https://community.creatio.com/questions/send-message-client-server-webs…

 

Best regards,

Oscar

Dear community,

 

Is it possible that a server code called from a pre-configured page is able to send message to client in another section page? Curious to understand how the client is able to recieve the messages from server. What happens if there are 2 section edit pages where the JS code is written? will the message be received in both these pages?

Shivani Lakshman,

Hi Shivani. Here is my understanding. I will let Oscar or Tatiana confirm. 



The Server to Client socket messaging is independent of which section page a user is on. Whichever client side schemas has code to receive those messages, It will be received in all those pages.



The way it works under the hood is that - There is a web socket connection which is established from the Creatio client to the server when a user logs in. This is persistent across all client pages that a user might be in. Any message triggered from the server using the code in this article will flow through that web socket. I also believe that Creatio's inbuilt notifications, Email Sync, telephony related messages are routed to the Client through this socket. 



And by 'server code called from a pre-configured page' - I presume you are referring to (for eg) a script task attached to that business process which has code to trigger the message from server to client. If it is some other way, let me know. 

Shivani Lakshman and M Shrikanth,

 

Yes, M Shrikanth, is correct in the explanation of how a web socket works. If you have two pages that should receive a message from a server, both of them can receive it through a web-socket. 

 

Regards,

Anastasiia

Show all comments

Symptoms

Reproducing the case:

  1. open the section (e.g., [Contacts]);
  2. select the [Show folders] action in the [Filters/folders] menu;
  3. select a dynamic folder in the folder tree;
  4. in the folder setup menu, select the [Set up filter] action;
  5. close the advanced filtering module by clicking the cross icon;
  6. select any record from the section list and start editing it by clicking the [Open] button;
  7. click the [New contact] button;
  8. click [Cancel].

As a result, instead of the [Contacts] section we only see the section caption, the following message is displayed in the console:

1) message: Uncaught TypeError: Cannot read property 'modules' of undefined

2) user: Supervisor/7f3b869f-34f3-4f20-ab4d-7480a5fdf647

file: undefined

line: undefined

message: Cannot read property 'components' of undefined 

date: Mon Nov 16 2015 12:35:00 GMT+0200 (FLE Standard Time)

moduleId: SectionModuleV2_ContactSectionV2_ExtendedFilterEditModule

moduleName: SectionModuleV2

3) message: Uncaught Terrasoft.UnsupportedTypeException: Message GetSectionFiltersInfo is not defined in undefined module 

Cause

The case is reproduced in versions higher han 7.6.0.1500. It is connected with changing the logic of advanced filtering module operation (ExtendedFilterEditModuleV2): it has been inherited from the base schema. It is anyway saved in the browser history and nuder certain circumstances is loaded instead of the expected section module.

Solution

To fix the issue locally on the customer side by replacing the base module, replace the ExtendedFilterEditModuleV2 module having completely copied the code and styles and add the following property before declaring the module methods:

/** * Indicates that the history is used when loading the module. * @public  * @type {Boolean} */
useHistoryState: false,

 

Like 0

Like

Share

2 comments

please explain how to replace this kind of module (ExtendedFilterEditModuleV2)

Hello,



You can do it using the Ext.define() method described in the article by the link below: 

https://academy.creatio.com/docs/developer/front_end_development/module…

 

Best regards,

Bogdan

Show all comments

Symptoms

BPM 7.6.0.838 onSite mobile application does not work

Solution

Most likely you have not updated the database structure.

To update the database structure in the browser, you must:

- Close all instances of Chrome;

- Go to the folder in which Chrome stores local data: C:\Users\[USERNAME]\AppData\Local\Google\Chrome\User Data\Default;

- Delete the contents of the following folders:

C:\Users\[USERNAME]\AppData\Local\Google\Chrome\User Data\Default\File System

C:\Users\[USERNAME]\AppData\Local\Google\Chrome\User Data\Default\databases

- Restart Chrome using startchrome.bat (in the Mobile folder with the source code of the mobile application);

- Run the mobile application in the browser.

As a result, Chrome will create a local database with all the necessary tables from scratch (including new columns).

Additionally, please ensure that the latest Framework is installed on the server.

Like 0

Like

Share

0 comments
Show all comments