Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. In the AdminPortal load the Setup tab

  2. Click on Custom Code

  3. Click Add

  4. Enter a Name and then paste in the code below[cce lang="C#"]
    //Reference: Castle.MonoRail.Framework
    //Reference: Newtonsoft.Json
    //Reference: Logisense.Utility
    //Reference: System.Data
    using Castle.MonoRail.Framework;
    using Logisense.Boss.Logic;
    using Logisense.Boss.Logic.DomainModel;
    using Logisense.Boss.Utility;
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using Newtonsoft.Json.Linq;

    Code Block
    //Reference: Castle.MonoRail.Framework
    //Reference: Newtonsoft.Json
    //Reference: Logisense.Utility
    //Reference: System.Data
    using Castle.MonoRail.Framework;
    using Logisense.Boss.Logic;
    using Logisense.Boss.Logic.DomainModel;
    using Logisense.Boss.Utility;
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using Newtonsoft.Json.Linq;
    
    namespace LogisenseKB.EventHandlers
    {
        public class CreatePackageQuestionAction : BaseTicketType, IAction
        {
            public string Name
            {
                get { return "CreatePackageQuestionAction"; }
            }
    
            public string Description
            {
                get { return "Triggered by a new package being added to a system, which will add 2 package profile questions"; }
            }
    
            public string EventCode
            {
                get { return "Package.Create"; }
            }
    
            public int OwnerID { get; set; }
    
            public void Run(ScriptContext context)
            {
                if (!ProfileQuestion.SearchByQuestion(TicketTypeForCreateUserPackage).Any())
                {
                    Logisense.Boss.Logic.Core.PackageProfileQuestion.GetNewQuestion(TicketTypeForCreateUserPackage, Logisense.Boss.Logic.Core.DataType.TEXT_NAME, OwnerID);
                }
                if (!ProfileQuestion.SearchByQuestion(TicketTypeForCancelUserPackage).Any())
                {
                    Logisense.Boss.Logic.Core.PackageProfileQuestion.GetNewQuestion(TicketTypeForCancelUserPackage, Logisense.Boss.Logic.Core.DataType.TEXT_NAME, OwnerID);
                }
            }
        }
    
        public class InitializeTicketWhenCreateAction : BaseTicketType, IAction
        {
            public string Name
            {
                get { return "InitializeTicketWhenCreateAction"; }
            }
    
            public string Description
            {
                get { return "Triggered by a new user package being added to a system"; }
            }
    
            public string EventCode
            {
                get { return "UserPackage.Create"; }
            }
    
            public int OwnerID { get; set; }
    
            public void Run(ScriptContext context)
            {
                var userPackage = UserPackage.GetByID(Convert.ToInt32(context["UserPackageID"]));
                var user = userPackage.GetUser();
                var owner = user.GetActingOwner();
    
                var ticketType = GetTicketType(TicketTypeForCreateUserPackage, GetPackageAttributeProfileAnswers(userPackage), owner);
                if (ticketType != default(TicketType))
                {
                    var ticketName = string.Format("Auto Generated Ticket for User Package: '{0}'", userPackage.Name);
                    InitializeTicket(userPackage, user, owner, ticketName, ticketType);
                }
            }
        }
    
        public class InitializeTicketWhenCancelAction : BaseTicketType, IAction
        {
            public string Name
            {
                get { return "InitializeTicketWhenCancelAction"; }
            }
    
            public string Description
            {
                get { return "Triggered by a user package was cancelled"; }
            }
    
            public string EventCode
            {
                get { return "UserPackage.Update"; }
            }
    
            public int OwnerID { get; set; }
    
            public void Run(ScriptContext context)
            {
                var userPackage = UserPackage.GetByID(Convert.ToInt32(context["UserPackageID"]));
                var oldUserPackage = UserPackage.Deserialize((string)context["OldRow"]);
                var user = userPackage.GetUser();
                var owner = user.GetActingOwner();
                var newStatusType = userPackage.GetStatusType().Name;
    
                if (newStatusType == Logisense.Boss.Logic.Core.StatusType.CanceledName && oldUserPackage.GetStatusType().Name != newStatusType)
                {
                    var packageAttributeProfileAnswers = GetPackageAttributeProfileAnswers(userPackage);
                    var ticketTypeForCancelUserPackage = GetTicketType(TicketTypeForCancelUserPackage, packageAttributeProfileAnswers, owner);
    
                    if (ticketTypeForCancelUserPackage != default(TicketType))
                    {
                        var ticketTypeForCreateUserPackage = GetTicketType(TicketTypeForCreateUserPackage, packageAttributeProfileAnswers, owner);
                        var ticketName = string.Format("Auto Generated Ticket for Cancelling the User Package: '{0}'", userPackage.Name);
                        if (ticketTypeForCreateUserPackage != default(TicketType))
                        {
                            var ticketQuery = new TicketQuery()
                            {
                                UserPackageID = userPackage.ID,
                                TicketTypeID = ticketTypeForCreateUserPackage.ID,
                            };
                            var existingTicket = Ticket.GetCollection(ref ticketQuery).FirstOrDefault();
                            if (existingTicket != default(Ticket))
                            {
                                //if the user package has existing tickets with the ticketTypeForCreateUserPackage
                                if (existingTicket.GetTicketStatus().GetTicketStatusType().Name == Logisense.Boss.Logic.Core.TicketStatusType.OpenName)
                                {
                                    //if there is an existing open ticket, update ticket type to it
                                    EditExistingTicket((Logisense.Boss.Logic.Core.Ticket)existingTicket, ticketTypeForCancelUserPackage, owner);
                                }
                                else
                                {
                                    //else the ticket has been closed, create a new one
                                    InitializeTicket(userPackage, user, owner, ticketName, ticketTypeForCancelUserPackage);
                                }
                            }
                        }
                        else
                        {
                            //else initialize a new ticket
                            InitializeTicket(userPackage, user, owner, ticketName, ticketTypeForCancelUserPackage);
                        }
    
                    }
                }
            }
    
            private static void EditExistingTicket(Logisense.Boss.Logic.Core.Ticket ticket, TicketType ticketType, Owner owner)
            {
                ticket.TicketTypeID = ticketType.ID;
    
                if (!ticket.ValidateTicketTypeAndTransitions())
                {
                    Logisense.Boss.Logic.Core.Alert.Create(Logisense.Boss.Logic.Core.TicketTypeTransition.invalidTicketTypeMsg, owner.ID);
                }
                else
                {
                    ticket.RawUpdate();
                }
            }
        }
    
        public class BaseTicketType
        {
            protected static string TicketType
            {
                get { return "TicketType"; }
            }
    
            public static string TicketTypeForCreateUserPackage
            {
                get { return "Ticket Type for Creating a User Package"; }
            }
    
            public static string TicketTypeForCancelUserPackage
            {
                get { return "Ticket Type for Cancelling a User Package"; }
            }
    
            protected static void InitializeTicket(UserPackage userPackage, User user, Owner owner, string ticketName, TicketType ticketType)
            {
                var ticketCategory = owner.GetTicketCategoryCollection().FirstOrDefault();
                var ticketGroup = owner.GetTicketGroupCollection().FirstOrDefault();
                var ticketPriority = owner.GetTicketPriorityCollection().FirstOrDefault();
                var ticketStatus = owner.GetTicketStatusCollection().FirstOrDefault();
    
                var ticketCategoryId = ticketCategory != null ? ticketCategory.ID : int.MinValue;
                var ticketGroupId = ticketGroup != null ? ticketGroup.ID : int.MinValue;
                var ticketPriorityId = ticketPriority != null ? ticketPriority.ID : int.MinValue;
                var ticketStatusId = ticketStatus != null ? ticketStatus.ID : int.MinValue;
    
                var ticket = Ticket.GetNew();
                ticket.Name = ticketName;
                ticket.Title = ticketName;
                ticket.TicketTypeID = ticketType.ID;
                ticket.OpenedBy_UserID = user.ID;
                ticket.RelatedTo_UserID = ticket.OpenedBy_UserID;
                ticket.TicketCategoryID = ticketCategoryId;
                ticket.TicketGroupID = ticketGroupId;
                ticket.TicketPriorityID = ticketPriorityId;
                ticket.TicketStatusID = ticketStatusId;
                ticket.UserID = user.ID;
                ticket.CreatedDate = DateTime.Now;
                ticket.UserPackageID = userPackage.ID;
    
                var coreTicket = (Logisense.Boss.Logic.Core.Ticket)ticket;
                if (coreTicket.ValidateTicketTypeAndTransitions())
                {
                    coreTicket.Create(ticketName, string.Empty, null, false);
                }
                else
                {
                    Logisense.Boss.Logic.Core.Alert.Create(Logisense.Boss.Logic.Core.TicketTypeTransition.invalidTicketTypeMsg, owner.ID);
                }
    
            }
    
            protected static PackageAttributeProfileAnswer[] GetPackageAttributeProfileAnswers(UserPackage userPackage)
            {
                var packageAttributeProfileAnswers = userPackage.GetPackage().GetPackageAttributeProfileAnswerCollection();
                return packageAttributeProfileAnswers;
            }
    
            protected static TicketType GetTicketType(string profileQuestion, IEnumerable<PackageAttributeProfileAnswer> packageAttributeProfileAnswers, Owner owner)
            {
                var answer = packageAttributeProfileAnswers.FirstOrDefault(x => x.GetPackageProfileQuestion().GetProfileQuestion().Name == profileQuestion);
                return answer != null
                    ? owner.GetTicketTypeCollection().FirstOrDefault(x => x.Name == answer.GetProfileAnswer().Value)
                    : default(TicketType);
            }
        }
    
        public class TicketTypeTransitionInstaller : Logisense.Boss.Logic.Core.ICustomCodeInstaller
        {
            public void Install(int ownerId)
            {
                var owner = (Logisense.Boss.Logic.Core.Owner)Owner.GetByID(ownerId);
                SetupReversedTicketTypeTransitions();
                SetupPageExtensions(owner);
            }
    
            public static void SetupReversedTicketTypeTransitions()
            {
                //This SQL query will only create reversed TicketTypeTransitions that do not already exist (based on Current_TicketStatusID and Allowed_TicketStatusID
                //appearing in alternate order in a second row for each TicketTypeID).
                //NOTE: This CustomCode relies on reversed transitions being named [TransitionName] + "_Reversed", reversed transitions missing
                //"_Reversed" from their names will not be handled correctly and incorrect behaviour of TicketStatuses will be observed.
                var createdTransitions = SQLHelper.ExecuteDataTable(@"
    INSERT INTO TicketTypeTransition (Name, Current_TicketStatusID, Allowed_TicketStatusID, TicketGroupID, AssignedTo_UserID, TicketTypeID)
        OUTPUT INSERTED.ID AS TransitionID, INSERTED.Name AS TransitionName
        SELECT
            ttt1.Name + '_Reversed', ttt1.Allowed_TicketStatusID, ttt1.Current_TicketStatusID, ttt3.TicketGroupID, ttt3.AssignedTo_UserID, ttt1.TicketTypeID
        FROM TicketTypeTransition AS ttt1
        LEFT JOIN TicketTypeTransition AS ttt2 ON --Searching for missing reversed transitions
            ttt2.Allowed_TicketStatusID = ttt1.Current_TicketStatusID AND
            ttt2.Current_TicketStatusID = ttt1.Allowed_TicketStatusID AND
            ttt2.TicketTypeID = ttt1.TicketTypeID
        LEFT JOIN TicketTypeTransition AS ttt3 ON --Searching for transitions previous to the current one, the reversed transitions will use their TicketGroupID/AssignTo_UserID
            ttt3.Allowed_TicketStatusID = ttt1.Current_TicketStatusID AND
            ttt3.TicketTypeID = ttt1.TicketTypeID
        WHERE
            ttt2.ID IS NULL
        GROUP BY
            ttt1.Name, ttt1.Allowed_TicketStatusID, ttt1.Current_TicketStatusID, ttt3.TicketGroupID, ttt3.AssignedTo_UserID, ttt1.TicketTypeID;");
    
                foreach (DataRow transition in createdTransitions.Rows)
                {
                    Log.Information("Created TicketTypeTransition '{0}' (ID: {1})", transition["TransitionName"], transition["TransitionID"]);
                }
            }
    
            private static void SetupPageExtensions(Owner owner)
            {
                var script = string.Format(@"
    
    var ticketId = document.getElementById('Ticket.id').value;
    setLoadSession();
    ByPassIfTicketTypeHasNoTransition();
    
    // Description:     if the ticket has ticket type with transition configured, update input values and the logic behind the select button
    function ByPassIfTicketTypeHasNoTransition(){{
       
        var query = 'AJAXBypassIfTicketTypeHasNoTransition.rails?ticketId=' + ticketId;
        new Ajax.Request(query, {{
                asynchronous: false, evalScripts: true, onSuccess: function (response) {{
                 
                    if(response.responseJSON.None == false){{
                       
                        //action starts as Load when the page loads and changes to Previous or Next based on what button was clicked
                        var action = '{2}';
                       
                        if (sessionStorage.previousTicketTypeTransition == 'true') {{
                            action = '{0}';
                        }} else if (sessionStorage.nextTicketTypeTransition == 'true') {{
                            action = '{1}';
                        }}
    
                        //TS-1697 Editing the ticket without clicking the next or previous button shouldn't populate inputs
                        var ajaxUpdateInput = action != '{2}';
    
                        updateTicketField(action, 'TicketStatus', ajaxUpdateInput);
                        updateTicketField(action, 'User', ajaxUpdateInput);
                        updateTicketField(action, 'TicketGroup', ajaxUpdateInput);
                        UpdateButton(ajaxUpdateInput);
    
                        new Form.Element.Observer('Ticket.TicketStatus', 0.100, function (element, value) {{
                            //These need a separate action since they're changed by the TicketStatus dropdown
                            updateTicketField('{3}', 'User', true);
                            updateTicketField('{3}', 'TicketGroup', true);
                            UpdateButton(false);
                        }});
    
                    }}
                }}
        }})
    }}
    
    function updateTicketField(action, field, updateInput) {{
        var ticketField = field != 'TicketGroup'
            ? 'Ticket.' + field
            : 'TicketGroup.TicketGroup';
       
        if (updateInput === true){{
            ajaxUpdateInput(action, ticketField, ticketField);
        }}
        if (ticketField == 'Ticket.TicketStatus'){{
            jQuery('#TicketStatus_select').attr('href', ""javascript: ajaxLaunchCustomTicketStatusSelectWindow();"");
        }}
    }}
    
    function UpdateButton(multipleTransitionsDisableButtons) {{
        var ticketStatusName = document.getElementById('Ticket.TicketStatus').value;
    
        var query = 'AJAXUpdateButton.rails?ticketId=' + ticketId + '&ticketStatusName=' + ticketStatusName;
        new Ajax.Request(query, {{
            asynchronous: false, evalScripts: true, onSuccess: function (response) {{
    
                if ($('nextTicketTypeTransition') != null){{
                    $('nextTicketTypeTransition').remove();
                }}
                if ($('previousTicketTypeTransition') != null){{
                    $('previousTicketTypeTransition').remove();
                }}
                if (response){{
                    clearSession();
                    if (response.responseJSON.Previous || response.responseJSON.MultiplePrevious && !multipleTransitionsDisableButtons){{
                        jQuery(""#SaveButton"").before(""<input name='previousTicketTypeTransition' id='previousTicketTypeTransition' type='submit' value='{0}' onclick='setPreviousSession(); return thisCheckvalidation(); '>     "");
                    }}                
                    if (response.responseJSON.Next || response.responseJSON.MultipleNext && !multipleTransitionsDisableButtons){{
                        jQuery(""#SaveButton"").before(""<input name='nextTicketTypeTransition' id='nextTicketTypeTransition' type='submit' value='{1}' onclick='setNextSession(); return thisCheckvalidation(); '>     "");
                    }}  
                }}
             
            }}
        }});
    }}
    
    function showInvalidError(){{
        if ($('errorMsgDiv') != null){{
            $('errorMsgDiv').remove();
        }}
        jQuery(""#EditForm"").before(""<div class='error' id='errorMsgDiv'>Multiple valid transitions were found, please choose one status from the drop down list.</div>  "");
    }}
    
    function thisCheckvalidation(){{
        return checkvalidation(document.getElementById('EditForm'), 0);
    }}
    
    function setPreviousSession(){{
        sessionStorage.previousTicketTypeTransition = true;
        sessionStorage.removeItem(""nextTicketTypeTransition"");
        sessionStorage.removeItem(""load"");
    }}
    
    function setNextSession(){{
        sessionStorage.nextTicketTypeTransition = true;
        sessionStorage.removeItem(""previousTicketTypeTransition"");
        sessionStorage.removeItem(""load"");
    }}
    
    function setLoadSession(){{
        if (sessionStorage.previousTicketTypeTransition != 'true' && sessionStorage.nextTicketTypeTransition != 'true') {{
            sessionStorage.load = true;
        }}
    }}
       
    function clearSession(){{
        sessionStorage.removeItem(""nextTicketTypeTransition"");
        sessionStorage.removeItem(""previousTicketTypeTransition"");
        sessionStorage.removeItem(""load"");
    }}
    
    function ajaxUpdateInput(action, target, selector) {{
        var ticketStatusName = document.getElementById('Ticket.TicketStatus').value;
        var ticketStatusId = document.getElementById('Ticket.TicketStatusID').value;
       
        var query = 'AJAXUpdateInput.rails?ticketId=' + ticketId + '&currentTicketStatusId=' + ticketStatusId + '&newTicketStatusName=' + ticketStatusName + '&action=' + action + '&target=' + target;
        new Ajax.Request(query, {{
            asynchronous: true, evalScripts: true, onSuccess: function (response) {{
                if (response){{
                    if (response.responseJSON.Message == '{4}') {{
                        //if restictive
                        document.getElementById(selector).value = response.responseJSON.Value;
                    }} else if (response.responseJSON.Message == '{5}') {{
                        //if invalid
                        document.getElementById(selector).value = '';
                        showInvalidError();
                    }}
                         
                }}
    
            }}
        }});
    }}
    
    //  Description:    restrict selection lists
    function ajaxLaunchCustomTicketStatusSelectWindow() {{
        //We only create a custom select list for TicketStatus, everything else should use the standard select list
        var field = 'TicketStatus';
        var target = 'Ticket.' + field;
    
        var d = document.getElementById(target + 'selectdiv');
        var element = document.getElementById(target);
        d.innerHTML = 'Loading' + '...';
        d.style.marginLeft = jQuery('#Ticket\\.TicketStatus').css('margin-left');
        d.style.width = element.offsetWidth + 'px';
        d.style.display = 'block';
        window.currentlyVisiblePopup = d;
        window.currentlyVisiblePopup.style.visibility = 'visible';
    
        var query = 'AJAXCustomSelect.rails?ticketId=' + ticketId + '&target=' + target;
        new Ajax.Request(query, {{
            asynchronous: true, evalScripts: true, onSuccess: function (response) {{
                if (response.responseText == 'no match') {{
                    // empty the selection
                    d.innerHTML = '';
                }} else if (response.responseText == '') {{
                    // non restricted selection
                    LaunchSelectWindow(field, 'Name', target, '', 0);
                }}
                else {{
                    // restricted selection
                    d.innerHTML = response.responseText;
                }}
            }}
        }});
    }}", TransitionAction.Previous, TransitionAction.Next, TransitionAction.Load, TransitionAction.ManualChange, GetFieldMessage.Restrictive, GetFieldMessage.Invalid);
    
                const string PAGE_EXTENSION_NAME = "TicketTypeTransitionPageExtension";
    
                try
                {
                    var pageExtensionQuery = new PageExtensionQuery
                    {
                        OwnerID = owner.ID,
                        Name = PAGE_EXTENSION_NAME
                    };
                    if (!PageExtension.GetCollection(ref pageExtensionQuery).Any())
                    {
                        var pageExtension = new Logisense.Boss.Logic.Core.PageExtension()
                        {
                            OwnerID = owner.ID,
                            Name = PAGE_EXTENSION_NAME,
                            Page = "ticket/edit",
                            Script = script
                        };
                        pageExtension.Create();
                    }
                }
                catch (Exception ex)
                {
                    throw new ApplicationException(string.Format("Error while creating/updating page extension: '{0}'", PAGE_EXTENSION_NAME), ex);
                }
            }
        }
    
        public enum TransitionAction { Previous, Next, Load, ManualChange }
        public enum GetFieldMessage { NonRestrictive, Restrictive, Invalid }
    
        public class AJAXUpdateButton : IDynamicAction
        {
            public void Execute(Controller controller)
            {
                var ticketId = controller.Params["ticketId"];
                var ticketStatusName = controller.Params["ticketStatusName"];
    
                var next = false;
                var multipleNext = false;
                var previous = false;
                var multiplePrevious = false;
    
                if (!string.IsNullOrWhiteSpace(ticketId) && ticketId != int.MinValue.ToString())
                {
                    var ticket = (Logisense.Boss.Logic.Core.Ticket)Ticket.GetByID(Convert.ToInt32(ticketId));
                    var ticketTypeTransitions = ticket.GetTicketType().GetTicketTypeTransitionCollection();
                    var countNextTransitions = ticketTypeTransitions.Count(x => x.GetCurrent_TicketStatus().Name == ticketStatusName && !x.Name.Contains("_Reversed"));
                    var countPreviousTransitions = ticketTypeTransitions.Count(x => x.GetCurrent_TicketStatus().Name == ticketStatusName && x.Name.Contains("_Reversed"));
    
                        if (countNextTransitions == 1)
                        {
                            next = true;
                        }
                        else if (countNextTransitions > 1)
                        {
                            multipleNext = true;
                        }
    
                        if (countPreviousTransitions == 1)
                        {
                            previous = true;
                        }
                        else if (countPreviousTransitions > 1)
                        {
                            multiplePrevious = true;
                        }
                    }
    
                var json = new JObject
                {
                    {"Next", next},
                    {"MultipleNext", multipleNext},
                    {"Previous", previous},
                    {"MultiplePrevious", multiplePrevious},
                    {"None", true}
                };
    
                controller.Response.ContentType = "application/json";
                controller.RenderText(json.ToString());
            }
        }
    
        public class AJAXCustomSelect : BaseTicketType, IDynamicAction
        {
            public void Execute(Controller controller)
            {
                var ticketId = controller.Params["ticketId"];
    
                var noRestriction = false;
                var results = GetFieldSelection(ticketId, ref noRestriction);
    
                if (!results.Any())
                {
                    // render "no match":   empty the ticket status' selection if no previous or next status has been found
                    // render empty string: load the non restricted selections
                    var noMatch = !noRestriction
                        ? "no match"
                        : string.Empty;
                    controller.RenderText(noMatch);
                }
                else
                {
                    //Target will always be Ticket.TicketStatus
                    var target = controller.Params["target"];
                    var listResults = new ListResults
                    {
                        objects = results.Values.ToArray(),
                        RowCount = results.Count
                    };
                    controller.PropertyBag["results"] = listResults;
                    controller.PropertyBag["target"] = target;
                    //Select.vm requires that $table be set, it should be last portion of the target
                    controller.PropertyBag["table"] = target.Split('.').Last();
                    controller.RenderSharedView("select");
                }
            }
            public class ListResults
            {
                private object[] _objects;
                public object[] objects
                {
                    get
                    {
                        return _objects;
                    }
                    set
                    {
                        _objects = value;
                    }
                }
                private int _RowCount;
                public int RowCount
                {
                    get
                    {
                        return _RowCount;
                    }
                    set
                    {
                        _RowCount = value;
                    }
                }
            }
    
            /// <summary>
            /// Get a dictionary of restricted value for ticket field
            /// </summary>
            /// <param name="ticketId">string: ID of the ticket to act on</param>
            /// <param name="noRestriction">ref bool: output true means the ticket type doesn't restrict ticket status</param>
            /// <returns></returns>
            private static Dictionary<string, object> GetFieldSelection(string ticketId, ref bool noRestriction)
            {
                var ticket = (Logisense.Boss.Logic.Core.Ticket)Ticket.GetByID(Convert.ToInt32(ticketId));
                var results = new Dictionary<string, object>();
    
                if (ticket.TicketTypeID != int.MinValue)
                {
                    var ticketType = ticket.GetTicketType();
                    var transitionCollection = ticketType.GetTicketTypeTransitionCollection();
    
                    if (!transitionCollection.Any())
                    {
                        noRestriction = true;
                    }
                    else
                    {
                        //Get all transitions that start on the current status to populate the TicketStatus dropdown
                        var possibleTransitions = transitionCollection.Where(x => x.Current_TicketStatusID == ticket.TicketStatusID).ToList();
    
                        foreach (var transition in possibleTransitions)
                                {
                                if (transition.Allowed_TicketStatusID != int.MinValue)
                                    {
                                    var item = transition.GetAllowed_TicketStatus();
                                        AddToDictionary(ref results, item, item.Name);
                                    }
                            if (transition.Current_TicketStatusID != int.MinValue)
                                {
                                var item = transition.GetCurrent_TicketStatus();
                                    AddToDictionary(ref results, item, item.Name);
                                }
                            }
                        }
                    }
                return results;
            }
    
            private static void AddToDictionary(ref Dictionary<string, object> dictionary, object item, string name)
            {
                if (item != null & !dictionary.ContainsKey(name))
                {
                    dictionary.Add(name, item);
                }
            }
        }
    
        public class AJAXUpdateInput : BaseTicketType, IDynamicAction
        {
            public void Execute(Controller controller)
            {
                var ticketId = controller.Params["ticketId"];
                var newTicketStatusName = controller.Params["newTicketStatusName"];
                var currentTicketStatusId = controller.Params["currentTicketStatusId"];
                var action = controller.Params["action"];
                var target = controller.Params["target"];
    
                var fieldValue = GetFieldValue(action, target, ticketId, GetCorrectTicketStatusId(target, currentTicketStatusId, newTicketStatusName));
                controller.Response.ContentType = "application/json";
                controller.RenderText(fieldValue);
            }
    
            /// <summary>
            /// Get the value of a ticket field which is restricted by the ticket type transition
            /// </summary>
            /// <param name="action">string: "previous" or "next"</param>
            /// <param name="target">string: the id of the ticket field that needs to be restricted</param>
            /// <param name="ticketId">string: ID of the ticket to act on</param>
            /// <param name="ticketStatusId">string: ticketStatusId from the form</param>
            /// <returns>string: the value of restricted ticket field</returns>
            private static string GetFieldValue(string action, string target, string ticketId, int ticketStatusId)
            {
                var ticket = (Logisense.Boss.Logic.Core.Ticket)Ticket.GetByID(Convert.ToInt32(ticketId));
                var returnString = string.Empty;    // if returnString is empty, no restriction on this field
                var jsonMessage = GetFieldMessage.NonRestrictive;
               
                if (ticket.TicketTypeID != int.MinValue)
                {
                    var ticketType = ticket.GetTicketType();
                    var transitionCollection = ticketType.GetTicketTypeTransitionCollection();
    
                    if (transitionCollection.Any())
                    {
                        var allowedTransitions = ticket.GetNextTicketTypeTransitions(ticket.TicketStatusID, transitionCollection);
    
                        //ticket.GetNextTicketTypeTransitions() will return both a forward and return transition for the Allowed_TicketStatusID, differentiate using "_Reversed"
                        if (action == TransitionAction.Next.ToString())
                        {
                            allowedTransitions = allowedTransitions.Where(x => !x.Name.Contains("_Reversed")).ToList();
                        }
                        else if (action == TransitionAction.Previous.ToString())
                        {
                            allowedTransitions = allowedTransitions.Where(x => x.Name.Contains("_Reversed")).ToList();
                        }
                        else if (action == TransitionAction.ManualChange.ToString())
                        {
                            //This will only happen when the form observer on TicketStatus triggers an update for User and TicketGroup
                            allowedTransitions = allowedTransitions.Where(x => x.Allowed_TicketStatusID == ticketStatusId).ToList();
    
                            if (ticketStatusId == ticket.TicketStatusID)
                        {
                                //TicketStatus was moved back to the original value, since TicketGroup and User are
                                //associated to the allowed TicketStatus just grab the first transition that matches
                                allowedTransitions = transitionCollection.Where(x => x.Allowed_TicketStatusID == ticketStatusId).Take(1).ToList();
                            }
                        }
    
                        var firstPageLoad = action == TransitionAction.Load.ToString();
    
                        if (allowedTransitions.Count == 1 || firstPageLoad)
                        {
                            //If the page is being loaded for the first time we don't care what transitions exist just yet, we're going to load in ticket details instead
                            var ticketTransition = !firstPageLoad
                                ? allowedTransitions.Single()
                                : default(TicketTypeTransition);
    
                            // get field value
                            switch (target)
                            {
                                case "Ticket.User":
                                    if (firstPageLoad)
                                    {
                                        returnString = ticket.GetUser().Name;
                            }
                                    else if (ticketTransition != null && ticketTransition.AssignedTo_UserID != int.MinValue)
                            {
                                        returnString = ticketTransition.GetAssignedTo_User().Name;
                                    }
                                    break;
    
                                case "Ticket.TicketStatus":
                                    if (firstPageLoad)
                                    {
                                        returnString = ticket.GetTicketStatus().Name;
                                    }
                                    else if (ticketTransition != null)
                                    {
                                        //If the TicketStatusID from the form doesn't match the ticket it means the form has already transitioned forward,
                                        //don't update the TicketStatus
                                        returnString = ticket.TicketStatusID != ticketStatusId
                                            ? ticketTransition.GetCurrent_TicketStatus().Name
                                            : ticketTransition.GetAllowed_TicketStatus().Name;
                                    }
                                    break;
    
                                case "TicketGroup.TicketGroup":
                                    if (firstPageLoad)
                                    {
                                        returnString = ViewTicketGroupWithOwner.GetByID(ticket.TicketGroupID).Name;
                                    }
                                    else if (ticketTransition != null && ticketTransition.TicketGroupID != int.MinValue)
                                    {
                                        returnString = ViewTicketGroupWithOwner.GetByID(ticketTransition.TicketGroupID).Name;
                                    }
                                    break;
    
                                default:
                                    returnString = string.Empty;
                                    break;
                            }
    
                            if (returnString != string.Empty)
                            {
                                jsonMessage = GetFieldMessage.Restrictive;
                            }
                        }
                        else if (allowedTransitions.Count > 1)
                        {
                            jsonMessage = GetFieldMessage.Invalid;
                        }
                    }
                }
                var json = new JObject { { "Message", jsonMessage.ToString() }, { "Value", returnString } };
                return json.ToString();
            }
    
            private static int GetCorrectTicketStatusId(string target, string currentTicketStatusId, string newTicketStatusName)
            {
                var ticketStatusId = Convert.ToInt32(currentTicketStatusId);
    
                if (ticketStatusId > 0 && target != "Ticket.TicketStatus")
                {
                    //TicketStatus requires currentTicketStatusId (related to ticket), but TicketGroup and User require newTicketStatusName (current value in form)
                    TicketStatus ticketStatus;
                    try
                    {
                        ticketStatus = TicketStatus.GetByID(ticketStatusId);
                    }
                    catch (RecordNotFoundException)
                    {
                        //Couldn't find a TicketStatus for the passed in ID, all we can really do is pass back the original ID
                        return ticketStatusId;
        }
                    var ticketStatusQuery = new TicketStatusQuery { OwnerID = ticketStatus.OwnerID, Name = newTicketStatusName };
                    ticketStatus = TicketStatus.GetCollection(ref ticketStatusQuery).SingleOrDefault();
    
                    if (ticketStatus != null)
                    {
                        ticketStatusId = ticketStatus.ID;
                    }
                }
    
                return ticketStatusId;
            }
        }
    
        public class AJAXBypassIfTicketTypeHasNoTransition : IDynamicAction
        {
            public void Execute(Controller controller)
            {
                var ticketId = controller.Params["ticketId"];
                var none = true;
    
                if (!string.IsNullOrWhiteSpace(ticketId) && ticketId != int.MinValue.ToString())
                {
                    var ticket = Ticket.GetByID(Convert.ToInt32(ticketId));
                    if (ticket.TicketTypeID != int.MinValue)
                    {
                        var ticketTypeTransitions = ticket.GetTicketType().GetTicketTypeTransitionCollection();
    
                        if (ticketTypeTransitions.Any())
                        {
                            none = false;
                        }
                    }
                }
    
                var json = new JObject { { "None", none } };
                controller.Response.ContentType = "application/json";
                controller.RenderText(json.ToString());
            }
        }
    }    
  5. Click Save

...