Narsing1
Mega Sage

I have gone through many articles & community queries on how to copy the activities/journal entries between parent & child tables [Bi-Directional].  Either it can be RITM & SCTASK (Or) CHANGE REQUEST & CHANGE TASK (Or) PROBLEM & PROBLEM TASK etc.,

 

But I didn't find any comprehensive solution.  So thought of coming up with a permanent solution for it.  The approach that I followed in order to complete this is from the OOTB Asset-CI Synchronization.  Here you go.

 

Step#1

Create a field called "Skip Sync" under "Task" table as below.

capture.png

Step#2

Create a Script Include called "activitySyncUtils" and copy the below code.  Make sure it is accessible from all applications.

 

 

var activitySyncUtils = Class.create();
activitySyncUtils.prototype = {
    initialize: function(crec, prec) {
        this.current = crec;
        this.previous = prec;

        this.parentChilds = [{
            "parent": "sc_req_item",
            "child": "sc_task",
            "enablesync": true,
            "syncdirection": "childparent",
            "encodedQuery": {
                "parent": "request_item=" + this.current.getUniqueValue(),
                "child": "sys_id=" + this.current.request_item.toString()
            },
            "updatetype": "comments_and_worknotes"
        }, {
            "parent": "change_request",
            "child": "change_task",
            "enablesync": true,
            "syncdirection": "both",
            "encodedQuery": {
                "parent": "change_request=" + this.current.getUniqueValue(),
                "child": "sys_id=" + this.current.change_request.toString()
            },
            "updatetype": "worknotes"
        }, {
            "parent": "problem",
            "child": "problem_task",
            "enablesync": true,
            "syncdirection": "parentchild",
            "encodedQuery": {
                "parent": "problem_id=" + this.current.getUniqueValue(),
                "child": ""
            },
            "updatetype": "comments"
        }];

        this.updateWithComments = "";
        if (this.current.work_notes.changes()) {
            this.updateWithComments = this.current.work_notes.getJournalEntry(1).toString().split("(Work notes)")[1];
        } else {
            this.updateWithComments = this.current.comments.getJournalEntry(1).toString().split("(Additional comments)")[1];
        }
        this.updateWithComments = this.updateWithComments.replaceAll("\n", "");
    },
    process: function() {
        var canSync = false;
        var parentTable = "";
        var childTable = "";
        var encQuery = "";
        var syncDirection = "";
        var updateType = "";
        for (var p = 0; p < this.parentChilds.length; p++) {
            if (this.parentChilds[p].parent == this.current.sys_class_name.toString() || this.parentChilds[p].child == this.current.sys_class_name.toString()) {
                parentTable = this.parentChilds[p].parent;
                childTable = this.parentChilds[p].child;
                canSync = this.parentChilds[p].enablesync;
                if (this.current.sys_class_name == parentTable) {
                    encQuery = this.parentChilds[p].encodedQuery.parent;
                } else {
                    encQuery = this.parentChilds[p].encodedQuery.child;
                }
                syncDirection = this.parentChilds[p].syncdirection;
                updateType = this.parentChilds[p].updatetype;
                break;
            }
        }
        var isUpdateTypeCanUpdate = false;
        if (this.current.work_notes.changes() && updateType == "worknotes" || this.current.work_notes.changes() && updateType == "comments_and_worknotes") {
            isUpdateTypeCanUpdate = true;
        } else if (this.current.comments.changes() && updateType == "comments" || this.current.comments.changes() && updateType == "comments_and_worknotes") {
            isUpdateTypeCanUpdate = true;
        }
        if (isUpdateTypeCanUpdate) {
            if (JSUtil.notNil(parentTable) && JSUtil.notNil(childTable)) {
                if (canSync && JSUtil.notNil(encQuery)) {
                    if (syncDirection == "both") {
                        this.syncBiDirectional(childTable, parentTable, encQuery);
                    } else if (syncDirection == "parentchild" && this.current.sys_class_name == parentTable) {
                        this.syncParentToChild(childTable, encQuery);
                    } else if (syncDirection == "childparent" && this.current.sys_class_name == childTable) {
                        this.syncChildToParent(parentTable, encQuery);
                    }
                }
            }
        }
    },
    syncChildToParent: function(parentTable, encQuery) {
        try {
            var pRecord = new GlideRecord(parentTable);
            pRecord.addEncodedQuery(encQuery);
            pRecord.query();
            if (pRecord.next()) {
                pRecord.u_skip_sync = true;
                if (this.current.work_notes.changes()) {
                    pRecord.work_notes = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                } else {
                    pRecord.comments = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                }
                pRecord.update();
            }
        } catch (e) {

        }
    },
    syncParentToChild: function(childTable, encQuery) {
        try {
            var cRecord = new GlideRecord(childTable);
            cRecord.addEncodedQuery(encQuery);
            cRecord.query();
            while (cRecord.next()) {
                cRecord.u_skip_sync = true;
                if (this.current.work_notes.changes()) {
                    cRecord.work_notes = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                } else {
                    cRecord.comments = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                }
                cRecord.update();
            }
        } catch (e) {

        }

    },
    syncBiDirectional: function(childTable, parentTable, encQuery) {
        try {
            if (this.current.sys_class_name == childTable && !this.current.u_skip_sync) {
                var pRecord = new GlideRecord(parentTable);
                pRecord.addEncodedQuery(encQuery);
                pRecord.query();
                if (pRecord.next()) {
                    pRecord.u_skip_sync = true;
                    if (this.current.work_notes.changes()) {
                        pRecord.work_notes = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                    } else {
                        pRecord.comments = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                    }
                    pRecord.update();
                }
            } else if (this.current.sys_class_name == parentTable && !this.current.u_skip_sync) {
                var cRecord = new GlideRecord(childTable);
                cRecord.addEncodedQuery(encQuery);
                cRecord.query();
                while (cRecord.next()) {
                    cRecord.u_skip_sync = true;
                    if (this.current.work_notes.changes()) {
                        cRecord.work_notes = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                    } else {
                        cRecord.comments = "Update from " + this.current.number.toString() + " - " + this.updateWithComments;
                    }
                    cRecord.update();
                }
            }
        } catch (e) {

        }
    },

    type: 'activitySyncUtils'
};

 

 

Features of the above Class

  • Properties of the JSON
    • enablesync - Whether to enable the journal entry updates on a specific parent & child. Available values are true/false.
    • syncdirection - Whether to update only from parent to child (or) only from child to parent (or) both. Available values are "parentchild"/"childparent"/"both"
    • updatetype - Whether to update only comments (or) only work notes (or) both comments & work notes. Available values are "comments"/"worknotes"/"comments_and_worknotes"

Step#3

Create a Business Rule like this

Name : Synchronize Activities - After

Table: task

when: after

Screenshot 2024-03-31 141805.png

Copy the below Script to "Advanced"

 

 

(function executeRule(current, previous /*null when async*/ ) {

    // Add your code here
    var task = new GlideRecord(current.sys_class_name);
    task.addQuery("sys_id", current.getUniqueValue());
    task.query();
    if (task.next()) {
        task.setWorkflow(false);
        task.u_skip_sync = false;
        task.update();
    }
})(current, previous);

 

 

Step#4

Create another Business Rule like this

Name: Synchronize Activities - Before

Table: task

when: before

capture.png

Copy the below Script to "Advanced"

 

 

(function executeRule(current, previous /*null when async*/ ) {
    // Add your code here
    new activitySyncUtils(current, previous).process();
})(current, previous);

 

 

 

That's all, you are done and now the bi-directional journal entry update will start updating the comments/work notes between parent & child tables.  As I mentioned earlier, this is enabled on 

  • RITM <==> SCTASK
  • CHANGE REQUEST <==> CHANGE TASK
  • PROBLEM <==> PROBLEM TASK

Sample updates

Change Request & Change Task

capture.png

 

capture.png

 

capture.png

 

capture.png

 

 

YOU CAN DOWNLOAD THE UPDATE SET FROM HERE

 

FAQ:

1. Can I disable the bi-directional update after committing this update set?

Yes, you can disable completely / partially as well.

  • Go to Script include -activitySyncUtils
  • Update the JSON Payload element called "enablesync" to "false" wherever is needed

2. How can I add other Parent & Child Tables apart from change/request item/problem to enable the bi-directional updates?

You can add those tables as long as it extends to the Task table via the JSON from the Business rule.

3. I am not using the OOTB Labels (Additional comments/Work notes) for Activities, but these are also journal entries.  How can I handle this kind of scenario?

From the "activitySyncUtils" Script include, you can modify the script logic within the "initialize()" like this.  But before doing this, make sure how you are getting the journal entry when you use getJournalEntry(1) so that you can split that accordingly.

 

 

var updateWithComments = "";
            if (current.work_notes.changes()) {
                updateWithComments = current.work_notes.getJournalEntry(1).toString().split("(<your activity label>)")[1];
            } else {
                updateWithComments = current.comments.getJournalEntry(1).toString().split("(<your activity label>)")[1];
            }
            updateWithComments = updateWithComments.replaceAll("\n", "");

 

 

 

Feel free to provide your feedback /queries here and Like/Share if you think its useful.

 

Comments
sailesh1
Tera Contributor

Article is great! Thanks
It's my thinking if there is a way if we could eliminate the custom field creation by using a system property for enablesync (true/false) and use in BR's therefore removing code depedency?

Additionally, I think the table names can also be saved in as JSON property & be used to refer in the BR

Narsing1
Mega Sage

Thank you.  I tried with the system property first, but it doesn't gave me good result as the complete sync is dependent on record to record.  That is true that system property would be a good option for JSON.  But I am also thinking to create a separate table for it like the Asset-CI synchronization so that people don't need to change within the code, rather deal with that table.

 

Thanks,

Narsing

Nick Simonelli
Tera Contributor

Hey thanks for this @Narsing . I am a bit curious about step 3 though. It looks like you're just doing an extra GlideRecord lookup for the current record that the BR already has context of. Couldn't the BR in step 3 literally just be:

current.setWorkflow(false);
current.setValue('u_skip_sync', false);

 

Narsing1
Mega Sage

Hi @Nick Simonelli  - Its an "after" BR and without using the current.update(), it won't update.  But just to make sure it's not doing any recursive call, used the gliderecord here.

 

Thanks,

Narsing

Nick Simonelli
Tera Contributor

Ahh I did not pay enough attention @Narsing1 , thanks for pointing that out!

Justin Scheich
Tera Guru

This is a great write-up and should be how requests function out of the box. My fulfillers no longer need to jump between tasks and the ritm.
Works perfectly, with the only change for my field labels.
Thank you

mngreen
Tera Contributor

Thanks for sharing Narsing

This is a great and works fine, but i have an issue with the labels changing according to the users selected language.

 

Does anyone have a solution to how the script include can be modified to handle "localized labels" ?

 

Best regards

Martin

Narsing1
Mega Sage

Hi,

Thank you.  Can you try with System Localization -> Messages and create entries for all the languages.

Then use "gs.getMessage("<name>").  Here instead of Work notes/Additional comments, use gs.getMessage

 if (this.current.work_notes.changes()) {
            this.updateWithComments = this.current.work_notes.getJournalEntry(1).toString().split("(Work notes)")[1];
        } else {
            this.updateWithComments = this.current.comments.getJournalEntry(1).toString().split("(Additional comments)")[1];
        }

 

Thanks,

Narsing

Version history
Last update:
‎04-22-2024 07:13 PM
Updated by:
Contributors