Quantcast
Channel: SalesLogix – Customer FX
Viewing all 169 articles
Browse latest View live

Changing the Background Color of Composite Controls in the Infor CRM (Saleslogix) Web Client

$
0
0

Many of the controls in Infor CRM (Saleslogix) are composite controls. This means, that a single control you add to a quickform, such as a picklist, address control, date picker, etc, are rendered on the web as multiple controls that make up that single control. On top of that, many of these controls are transformed at runtime as they are converted by the Dojo libraries into Dijit widget based controls. This can present a problem if you want to do things to the control, such as change it’s background color, since the ASP.NET control that you modify in a C# action on the quickform isn’t what the user ends up seeing in the UI.

You could add a CSS file master page, but that means another thing to modify. A solution to this (when changing the background color of the control) is to inject some Javascript/CSS onto the page at runtime, which also allows you to conditionally change the color of the control as well.

When injecting this Javascript/CSS to the page, if you add these to the controls themselves, by adding style to the control elements, many of these changes will be lost when the controls are transformed into Dijits. However, by adding the style to the page HEAD will ensure the style sticks.

The trick is to use the Chrome dev tools inspector to find the elements you need to add the background-color style to. This makes it easy since you can add the background-color style to the elements there as well to see if that is what to need. Once you get that all figured out you can take the CSS selectors to add at runtime in a LoadAction.

This is an example of changing a Currency control named “numShipping” (the “Shipping Costs” control on the insert sales order screen) in a LoadAction. The CSS selector that we want to apply the background-color to is:

  • #MainContent_InsertSalesOrder_numShipping_InputCurrency_CurrencyTextBox
ScriptManager.RegisterClientScriptBlock(this, GetType(), "salesOrderStyle1_Script", "$('head').append('<style type=\"text/css\">#MainContent_InsertSalesOrder_numShipping_InputCurrency_CurrencyTextBox { background-color: #9CFF9C !important; }</style>');", true);

What is happening there is we get the HEAD element and append the style to it, using one of the ID of the numShipping control’s composite elements. Note we have to include !important to override the style added by the dijit transformation. We found that this was the right control by digging around in the inspector. In the end, we’ll have this:

currency-control-color

Now, let’s do the same for a picklist. This is a picklist named “pklStatus” (which is the status picklist on the insert sales order screen). A picklist has more parts so we’ll need to color a couple of things. For this, the CSS selectors that we need to apply the background-color to are:

  • #widget_MainContent_InsertSalesOrder_pklStatus-SingleSelectPickList-Combo
  • #MainContent_InsertSalesOrder_pklStatus-SingleSelectPickList-Combo
ScriptManager.RegisterClientScriptBlock(this, GetType(), "salesOrderStyle2_Script", "$('head').append('<style type=\"text/css\">#widget_MainContent_InsertSalesOrder_pklStatus-SingleSelectPickList-Combo, #MainContent_InsertSalesOrder_pklStatus-SingleSelectPickList-Combo { background-color: #F9F988 !important; }</style>');", true);

and we end up with this:

picklist-control-color

Checkboxes are a bit different. A checkbox renders as two separate controls. The actual checkbox nested inside a div and a separate label that contains the text for the checkbox. The problem is that the label itself does not have an ID, nor does the parent div it is inside of. This makes it a bit trickier to select in CSS, but luckily the label is associated with the checkbox with a “for” attribute. We can use that to select the label and add our color to it. Using the “Do Not Solicit” checkbox on the Contact Detail, you would be selecting the following in your CSS:

  • label[for=MainContent_ContactDetails_chkDoNotSolicit]
ScriptManager.RegisterClientScriptBlock(this, GetType(), "chkDoNotSolicit_Script", "$('head').append('<style> label[for=MainContent_ContactDetails_chkDoNotSolicit] { color: #FD6767; }</style>');", true);

We’ll end up with this:

checkbox-color

It’s more work than I’d like to do to color a control, but it’s a working, viable solution. One thing to note, for all of the code samples you can replace the hardcoded name for the controls with the ClientID value for the control.

Alternative Approach

The code samples above show how to change the control colors at runtime, for a given single control. However, there are several different approaches to this. You can easily combine the CSS you are appending to the head in a single statement, like this:

ScriptManager.RegisterClientScriptBlock(this, GetType(), "salesOrderStyle2_Script", 
    @"$('head').append('<style type=\"text/css\">
        #widget_MainContent_InsertSalesOrder_pklStatus-SingleSelectPickList-Combo, 
        #MainContent_InsertSalesOrder_pklStatus-SingleSelectPickList-Combo { 
            background-color: #F9F988 !important; 
        }
        #MainContent_InsertSalesOrder_numShipping_InputCurrency_CurrencyTextBox { 
            background-color: #9CFF9C !important; 
        }
    </style>');", true);

Now you’re adding the CSS for the Currency control and the Picklist to the head all at once and the code is more readable as well.

Lastly, one more alternative approach is to simply add the style changes for the controls to a separate CSS file and add that CSS to the master page. This way might work best if you’re changing a lot of different controls throughout the system and the changing of the controls isn’t conditional in any way. A possible downside to adding the stylesheet is that you need to modify the base.master master page. You’ll need to either merge any future changes to that master in upgrades or add back in the change anytime you add in an upgrade bundle.


Editable Grid modifications/deployments with new entity properties in InforCRM (Saleslogix)

$
0
0

Just a quick post regarding something that I was banging my head against today…

I was working on an editable datagrid, adding two new columns from a couple of field properties that I had just added to the entity model.  After I made my changes, built the web platform and deployed, my new columns were not showing up in the datagrid.  “Oh well”, I thought, “Sometimes you have to rebuild everything for a change to come across”.  So, I deleted everything from my build folders to force a full rebuild.  I also deleted everything from my SLXClient folder in IIS to make sure I was getting current versions of everything.  No luck.  Changes still not coming across.

The heck?!

For fun, I made a couple superficial changes to the form itself, and tried to build/deploy again.  No new columns, but the other changes I made on the form were there.

That’s when it hit me…

Editable grids utilize SData to display information.  Since these fields were newly added to the database, I did have to update my entity properties.  However, I never re-deployed the Sdata portal.  5 minutes later, Sdata has been re-deployed and sure enough, there are my new columns.

So, In summary; if you are adding newly created fields to an editable datagrid, make sure you deploy Sdata in addition to SLXClient.  Your forehead and desk will thank you.

Infor CRM 8.2.1 Web Client issues with Milliseconds

$
0
0

We recently had a client who ran into issues trying to edit some of their activities in the Infor CRM web client. The same activities could be edited in the Windows client successfully.
When attempting to edit the record in the web client the system throws an exception with event log stack trace shown below.

The issue is that the Infor CRM web client is not handling dates that include milliseconds.

In the underlying SQL queries the entity model attempts to find records based on where conditions containing dates. There where condition looks something like “where datefield = ‘20151014 17:37:11.000′” If your date field is actually something like “20151014 17:37:11.783” then the SQL query does not return the record because the times are not equivalent.

Steps to reproduce:

  • Find an existing or create a new activity.
  • Get the activity ID
  • Run this sql script against the activity ID you get:
update activity set startdate='2015-10-14 17:37:11.783', alarmtime='2015-10-14 17:37:11.783' where activityid='VCZV0A00NY2Z'
  • Now try to edit the activity. You will get the error.
  • Now run this SQL script against the activity ID you get:
  • update activity set startdate='2015-10-14 17:37:11.000', ALARMTIME='2015-10-14 17:37:11.000' where activityid='VCZV0A00NY2Z'
  • The activity can now be edited.
  • To fix this you can run a SQL script like this

    update sysdba.activity set alarmtime = DATEADD(ms, -DATEPART(ms, alarmtime), alarmtime) where datepart(ms, alarmtime)>0
    update sysdba.activity set StartDate = DATEADD(ms, -DATEPART(ms, StartDate), StartDate) where datepart(ms, StartDate)>0

    Using the SQL function getdate() or getutcdate() includes milliseconds so if you are populating dates directly in SQL you can get this behavior. You can instead use a different SQL function to get the date with no milliseconds specified:

    select CONVERT(DATETIME2(0),SYSDATETIME()) --gets local date time
    select CONVERT(DATETIME2(0),SYSUTCDATETIME()) --gets UTC current date time

    Stack Trace:

    "slxErrorId": "SLXCF3F7BA36D6FB743",
    "mitigation": "AjaxMessagingServiceError (500)",
    "date": "2016-03-16T08:07:45",
    "utc": "2016-03-16T15:07:45",
    "message": "Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Sage.SalesLogix.Activity.Activity#VCZV0A00NY2Z].",
    "source": "NHibernate.Persister.Entity.AbstractEntityPersister, NHibernate, Version=3.3.1.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4",
    "type": "NHibernate.StaleObjectStateException",
    "stackTrace": " at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)\r\n at NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)\r\n at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Int32[] dirtyFields, Boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object obj, Object rowId, ISessionImplementor session)\r\n at NHibernate.Action.EntityUpdateAction.Execute()\r\n at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)\r\n at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)\r\n at NHibernate.Engine.ActionQueue.ExecuteActions()\r\n at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)\r\n at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)\r\n at NHibernate.Impl.SessionImpl.Flush()\r\n at Sage.SalesLogix.Activity.Entities.Activity.Save() in c:\\Users\\Administrator\\AppData\\Roaming\\Sage\\Platform\\Output\\implementation\\Activity.cs:line 1505\r\n at Sage.SalesLogix.Activity.Activity.Save()\r\n at Sage.SalesLogix.Activity.Entities.Activity.Sage.Platform.Orm.Interfaces.IPersistentEntity.Save() in c:\\Users\\Administrator\\AppData\\Roaming\\Sage\\Platform\\Output\\implementation\\Activity.cs:line 1621\r\n at Sage.Platform.NHibernateRepository.NHibernateRepository.SaveImpl(Object instance)\r\n at Sage.Platform.NHibernateRepository.NHibernateRepository.Sage.Platform.Repository.IRepository.Save(Object instance)\r\n at Sage.Platform.EntityFactory.Save(Object instance)\r\n at Sage.SalesLogix.SystemAdapter.Activities.ActivityRequestHandler.SaveEntity(IActivity entity)\r\n at Sage.Platform.SData.RequestHandlerBase`3.InternalPut(SDataUri uri, String ifMatch, TFeedEntry entry)\r\n at Sage.Platform.SData.RequestHandlerBase`3.Put(TFeedEntry entry)\r\n at Invoke3cd06cf009c44daabf3b80c0c55a4460.Invoke(Object , IRequest )\r\n at Sage.Integration.Messaging.RequestTargetRegistration.RequestTargetInvoker.Invoke(IRequest request)\r\n at Sage.Integration.Messaging.Request.Process(RequestTargetInvoker invoker)\r\n at Sage.Integration.Adapter.AdapterController.RealAdapterController.Process(IRequest request)\r\n at Sage.Integration.Adapter.AdapterController.RealAdapterController.ProcessWorker(IProtocolRequest protocolRequest)\r\n at Sage.Integration.Adapter.AdapterController.Process(IProtocolRequest request)\r\n at Sage.Integration.Messaging.MessagingService.Process(IProtocolRequest protocolRequest)",
    "targetSite": "Boolean Update(System.Object, System.Object[], System.Object[], System.Object, Boolean[], Int32, System.Object, System.Object, NHibernate.SqlCommand.SqlCommandInfo, NHibernate.Engine.ISessionImplementor)",
    "fullException": "Sage.Common.Syndication.DiagnosesException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Sage.SalesLogix.Activity.Activity#VCZV0A00NY2Z] ---&gt; NHibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Sage.SalesLogix.Activity.Activity#VCZV0A00NY2Z]\r\n at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)\r\n at NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)\r\n at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Int32[] dirtyFields, Boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object obj, Object rowId, ISessionImplementor session)\r\n at NHibernate.Action.EntityUpdateAction.Execute()\r\n at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)\r\n at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)\r\n at NHibernate.Engine.ActionQueue.ExecuteActions()\r\n at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)\r\n at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)\r\n at NHibernate.Impl.SessionImpl.Flush()\r\n at Sage.SalesLogix.Activity.Entities.Activity.Save() in c:\\Users\\Administrator\\AppData\\Roaming\\Sage\\Platform\\Output\\implementation\\Activity.cs:line 1505\r\n at Sage.SalesLogix.Activity.Activity.Save()\r\n at Sage.SalesLogix.Activity.Entities.Activity.Sage.Platform.Orm.Interfaces.IPersistentEntity.Save() in c:\\Users\\Administrator\\AppData\\Roaming\\Sage\\Platform\\Output\\implementation\\Activity.cs:line 1621\r\n at Sage.Platform.NHibernateRepository.NHibernateRepository.SaveImpl(Object instance)\r\n at Sage.Platform.NHibernateRepository.NHibernateRepository.Sage.Platform.Repository.IRepository.Save(Object instance)\r\n at Sage.Platform.EntityFactory.Save(Object instance)\r\n at Sage.SalesLogix.SystemAdapter.Activities.ActivityRequestHandler.SaveEntity(IActivity entity)\r\n at Sage.Platform.SData.RequestHandlerBase`3.InternalPut(SDataUri uri, String ifMatch, TFeedEntry entry)\r\n at Sage.Platform.SData.RequestHandlerBase`3.Put(TFeedEntry entry)\r\n at Invoke3cd06cf009c44daabf3b80c0c55a4460.Invoke(Object , IRequest )\r\n at Sage.Integration.Messaging.RequestTargetRegistration.RequestTargetInvoker.Invoke(IRequest request)\r\n at Sage.Integration.Messaging.Request.Process(RequestTargetInvoker invoker)\r\n at Sage.Integration.Adapter.AdapterController.RealAdapterController.Process(IRequest request)\r\n at Sage.Integration.Adapter.AdapterController.RealAdapterController.ProcessWorker(IProtocolRequest protocolRequest)\r\n at Sage.Integration.Adapter.AdapterController.Process(IProtocolRequest request)\r\n at Sage.Integration.Messaging.MessagingService.Process(IProtocolRequest protocolRequest)\r\n --- End of inner exception stack trace ---",
    "hashCode": "FA95BF4E-5A43D045-FC7846A0",
    "pid": 3716,
    "identity": {
    "name": "0017596",
    "isAuthenticated": true,
    "authenticationType": "Forms"
    },
    "version": "8.2.0.1211",
    "logger": {
    "level": "ERROR",
    "location": "Sage.Platform.Diagnostics.ErrorHelper.LogException(:0)",
    "name": "Global",
    "message": "Integration Messaging MessagingService unhandled exception [Saleslogix Error Id=SLXCF3F7BA36D6FB743]"
    },
    "request": {
    "looksLikeAjax": true,
    "isLocal": true,
    "method": "PUT",
    "url": "http://localhost/SlxClient/slxdata.ashx/slx/system/-/activities(\"VCZV0A00NY2Z\")?_compact=true&amp;include=%24descriptors&amp;format=json&amp;_t=1458140865357",
    "referrer": "http://localhost/SlxClient/Account.aspx?entityid=ACZV0A00A8KP&amp;modeid=Detail",
    "ipAddress": "::1",
    "userAgent": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36",
    "userLanguages": "en-US; en;q=0.8"
    },
    "extendedExceptionInfo": [
    {
    "type": "NHibernate.StaleObjectStateException",
    "message": "Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Sage.SalesLogix.Activity.Activity#VCZV0A00NY2Z]",
    "source": "NHibernate",
    "targetSite": "Boolean Update(System.Object, System.Object[], System.Object[], System.Object, Boolean[], Int32, System.Object, System.Object, NHibernate.SqlCommand.SqlCommandInfo, NHibernate.Engine.ISessionImplementor)",
    "stackPosition": 1,
    "entityName": "\"Sage.SalesLogix.Activity.Activity\"",
    "identifier": "\"VCZV0A00NY2Z\""
    }
    ],
    "browser": {
    "type": "Chrome49",
    "name": "Chrome",
    "version": "49.0",
    "majorVersion": 49,
    "minorVersion": 0.0,
    "platform": "WinNT"
    },
    "server": {
    "machineName": "SERVER",
    "timeZone": "Pacific Standard Time",
    "commandLine": "C:\\Windows\\SysWOW64\\inetsrv\\w3wp.exe -ap \"Saleslogix\" -v \"v4.0\" -l \"webengine4.dll\" -a \\\\.\\pipe\\iisipmf4ae07a0-527e-4a72-b137-6a46c798241c -h \"C:\\inetpub\\temp\\apppools\\Saleslogix\\Saleslogix.config\" -w \"\" -m 0",
    "versionString": "Microsoft Windows NT 6.2.9200.0",
    "is64BitOperatingSystem": true,
    "host": {
    "siteName": "Saleslogix",
    "applicationId": "/LM/W3SVC/2/ROOT/SlxClient",
    "applicationPhysicalPath": "c:\\inetpub\\wwwroot\\SlxClient\\",
    "applicationVirtualPath": "/SlxClient",
    "isDebuggingEnabled": false,
    "isHosted": true,
    "maxConcurrentRequestsPerCPU": 5000,
    "maxConcurrentThreadsPerCPU": 0
    },
    "logonUser": {
    "name": "SERVER\\WebDLL",
    "authenticationType": "Forms",
    "impersonationLevel": "Impersonation",
    "isAnonymous": false,
    "isGuest": false,
    "isSystem": false
    }
    }
    }
    

    Determining if a User is a Member of a Team in Infor CRM Web Part 2

    $
    0
    0

    Back in 2009, Ryan wrote about how to determine if a user is in a team. His last code snippet example is no longer valid as those methods now return different object types. His old code was this:

    Sage.SalesLogix.Security.SLXUserService usersvc = (Sage.SalesLogix.Security.SLXUserService)Sage.Platform.Application.ApplicationContext.Current.Services.Get<Sage.Platform.Security.IUserService>();
    Sage.SalesLogix.Security.User user = usersvc.GetUser();
     
    System.Collections.Generic.IList<Sage.SalesLogix.Security.Owner> teamlist = Sage.SalesLogix.Security.Owner.GetTeamsByUser(user);
     
    string list = string.Empty;
    foreach (Sage.SalesLogix.Security.Owner team in teamlist)
    {
        list += team.OwnerDescription + "rn";
    }
    QFTextBox.Text = list;
    

    The new code should be this:

        System.Collections.Generic.IList<Sage.Entity.Interfaces.IOwner> teamlist = Sage.SalesLogix.Security.Owner.GetTeamsByUser(Sage.SalesLogix.API.MySlx.Security.CurrentSalesLogixUser);
     
        string list = string.Empty;
        foreach (Sage.Entity.Interfaces.IOwner team in teamlist)
        {
            list += team.OwnerDescription + "rn";
        }
        QFTextBox.Text = list;
    

    Inforum 2016 Early, Early Bird Rate Extended to April 7th

    $
    0
    0

    Big savings just extended, don’t wait.

    Infor just announced that the deep discount offered to those customers that register super early (or early, early as Infor is calling it) has been extended until Thursday, April 7th.  This represents a $600 savings versus the regular early bird registration, and a $1000 savings versus registering on site in July.  Take a look at the pricing and ask yourself if it’s prudent to wait any longer to register.

    Early, Early Bird December 17, 2015–April 7, 2016 $1,095
    Early Bird April 8–May 19, 2016 $1,695
    Regular May 20–July 8, 2016 $1,895
    On-site July 9–13, 2016 $2,095

    Customer FX will be attending and we hope many of our Infor CRM customers can join us!

    -Brianna

    Infor CRM (Saleslogix) v8.3 Outlook Integration: An In-Depth Look

    $
    0
    0

    Webinar on Wednesday, April 20th at 2pm CDT.

    The release of Infor CRM (Saleslogix) v8.3 may be behind schedule, but that doesn’t mean you can’t take the time to learn more about the features that will be included.  This month we are going to get into the nitty-gritty of a feature that is very important to the majority of Infor CRM users: Outlook Integration.  Join us as we discuss enhancements to desktop integration for syncing Calendar items and Contacts, as well as what’s new with Xbar 1.3, including:

    • Single file install for Outlook desktop integration and Xbar (now just called Xbar).
    • Associate CRM Contact, Account, Lead, Ticket, and Opportunity to an Outlook activity.
    • Area/Category/Issue added cascading picklist for Tickets.
    • Completing an appointment in Outlook completes it in CRM.
    • Access CRM record details, related entities, and user fields from linked calendar items.
    • New and updated Outlook calendar items push to CRM in real time.

    What: Infor CRM (Saleslogix) v8.3 Outlook Integration: An In-Depth Look Presented by Scott Weber of Customer FX
    When: Wednesday, April 20th at 2pm CDT
    Where: Online via GoToWebinar

    Register now!

    Free Training Workshop: Creating and Running Mail Merge in Infor CRM (Saleslogix) v8.x

    $
    0
    0

    Monday, April 11th at 2pm CDT.

    Take a little break during your day to learn something new!  Our free training workshops help you to better use Infor CRM by breaking up the myriad of functionality into manageable chunks.  No training workshop is longer than a half hour, meaning you won’t get information overload and can easily fit it into your day.

    New to Customer FX Workshops?  Why not start with Monday’s Mail Merge training?  Knowing the proper way to create and run Mail Merge in Infor CRM is a simple and straightforward way to communicate valuable information to your contacts and leads.

    Register now!

    -Brianna

    Building a new Job Service Job in Infor CRM Web- Step by Step

    $
    0
    0

    Using the power of the job service can open up a lot of tasks within the web client. By executing out of the web context they are a perfect place to run long running processes and because they can be scheduled easily, can allow a lot of cool things to happen.

    There is not a lot of documentation on exactly how to create a new job service job however which is where this post comes in. The first thing to know are the jobs are compiled assemblies. That means we are going to use Visual Studio to build this. I am not going to go into the ins and outs of using VS. What I will do is to define what you need minimally to put a job together.

    Step 1
    In Visual Studio, create an class library project.

    Step 2
    Add the required assemblies. All of these files can be found in a deployed Infro CRM client web site Bin folder. The assemblies required are:
    NHibernate
    Quartz
    Sage.Entity.Interfaces (to work with the entity model)
    Sage.Platform
    Sage.Scheduling
    Sage.SalesLogix.BusinessRules (if relying on that for existing methods)

    Step 3
    Add usings

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Globalization; 
    
    using Quartz;
    using Sage.Entity.Interfaces;
    using Sage.Platform.Orm;
    using Sage.Platform.Scheduling;
    using Sage.SalesLogix.BusinessRules;
    using System.ComponentModel;
    

    Step 4
    Create your public class name. This should inherit from SystemJobBase if you want the job to be tagged as a system job in the web client. The signature would look like:

    public class MyJobIsCool : SystemJobBase
    {
    }
    

    The class name will be used to select it as a job available to be used within the Application Architect, so name it something meaningful.

    In addition, you should add attributes to your class as follows:

    [DisallowConcurrentExecution]
    [Description("My Description")]
    [DisplayName("My Name")]
    public class MyJobIsCool : SystemJobBase
    {
    }
    

    These attributes are exposed by the job service and are what shows in the web client as the job name and description.

    Step 5
    Create the OnExecute method within your class. Only one method is required for your class. An overriden OnExecute. That signature look like this:

    protected override void OnExecute()
    {
    }
    

    Within that method there are a couple of built in outputs you can use:

  • Phase (string)- corresponds to the output of the Phase column in the Job Manager within the web client. Used as a main level status field.
  • PhaseDetail (string)- corresponds to the Phase Detail column in the Job Manager within the web client. Used as a more granular status within the main Phase.
  • Progress (decimal?)- corresponds to the progress bar percent complete (0-100). You can decide how this is set, either for the overall task or for within the Phases you are working in. Setting it to 100 does not mean anything, it is just informational.
  • The following is a completed sample file that runs a query retrieving all contacts where the last name starts with A. It then loops through the records and updates all of their phone numbers to be a specific value. (not a real valuable example but hey)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Globalization; 
    
    using Quartz;
    using Sage.Entity.Interfaces;
    using Sage.Platform.Orm;
    using Sage.Platform.Scheduling;
    using Sage.SalesLogix.BusinessRules;
    using System.ComponentModel;
    
    
    namespace FX.JobService
    {    
        [DisallowConcurrentExecution]
        [Description("My Description")]
        [DisplayName("My Name")]
        public class MyJobIsCool : SystemJobBase
        {
            private static void Log(string Message)
            {
                using (System.IO.TextWriter writer = new System.IO.StreamWriter(@"C:\Temp\Test_debug_log.txt", true))
                {
                    writer.WriteLine(string.Format("{0} - {1}", DateTime.Now, Message));
                }
            }
    
            private static readonly string _entityDisplayName = typeof(IContact).GetDisplayName();
            protected override void OnExecute()
            {
                //Set the main phase status
                Phase = "Running";
                //Set the phase detail status
                PhaseDetail = "Starting";            
                using (var session = new SessionScopeWrapper())
                {
                    //Set the phase detail status
                    PhaseDetail = string.Format("Composing Query {0}", _entityDisplayName);
                    
                    // Now we build our list of records
                    // I chose contacts starting with A for a lastname                
                    var contacts = session.QueryOver<IContact>()
                        .WhereRestrictionOn(c => c.LastName).IsLike("a%")
                        .List<IContact>();
    
                    //now work with my list of contacts
                    if (contacts != null)
                    {
                        //Set the main phase status
                        Phase = "Doing the work";
                        //Set the phase detail status
                        PhaseDetail = string.Format("Processing {1} {0} records", _entityDisplayName, contacts.Count);
                        //Write to my text file
                        Log(PhaseDetail);
    
                        //Initialize a counter
                        var counter = 0;
                        foreach (var contact in contacts)
                        {
                            //Here we can do a sample update like this:
                            contact.WorkPhone = "2128675309";
                            contact.Save();
    
                            // halt processing if interrupt requested by job server
                            if (Interrupted)
                            {
                                PhaseDetail = "Job was stopped by a user";
                                Log(PhaseDetail);
                                return;
                            }
                            // update job progress percentage
                            Progress = 100M * ++counter / contacts.Count;                        
                        }
                    }
                    else
                    {
                        // no records to process
                        //Set the phase detail status
                        PhaseDetail = string.Format("No qualifying records for {0}", _entityDisplayName);
                        //Write to my text file
                        Log(PhaseDetail);
                    }
                }
                //Set the main phase status
                Phase = "Complete";
                //Set the phase detail status
                PhaseDetail = "Complete";
                //Write to my text file
                Log("We are done!");            
            }
        }
    }
     
    
    

    Step 6
    Compile the assembly. Now take the compiled dll file and copy it into the Saleslogix Job Service portal, under the Support Files/bin folder. If you do this in Application Architect at this point you will need to close the AA and re-open it to ensure the job is available for the next step.

    Step 7
    Add the job to the list of jobs available in the job service portal.

  • In the AA, under portal manager, double click on the Saleslogix Job Service portal.
  • Under the Jobs tab, right click on the Jobs folder and choose Add.
  • To the right in the type name, do the drop down. The list will compile. You should now see your assembly name space. Expand it out
  • to find your public class name (i.e. MyJobIsCool).
  • Save your changes to the portal.
  • Step 8
    Deploy the SLXJobService portal. It is possible you might need to restart the SLX Job Server service. Not sure on that.

    You should now be able to log in to the web client, and under administration/job manager and be able to see your new job listed under the Definitions tab.


    Free Training: Sales Forecasting in Infor CRM (Saleslogix) Web

    $
    0
    0

    Monday, April 18th at 2pm CDT.

    If you’re interested in better management of, and insight to, your opportunities and sales pipeline our Infor CRM Sales Forecasting workshop is a good place to start.  Join us as we review the options available for forecasting sales, pipeline options, and how to create a pipeline.

    Did we mention the training is free of charge?

    Register now!

    -Brianna

    Free Training: Using Global Replace to Update Records in Infor CRM (Saleslogix)

    $
    0
    0

    Join us Monday, April 25th at 2pm CDT.

    Are you familiar with the Global Replace Wizard?  No, you’re not?  Then Monday’s workshop is perfect for you.  We’ll review how Global Replace can take the hassle out of updating multiple records and how to use the Global Replace Wizard properly.  All in under 30  minutes.

    Register now!

    -Brianna

    inforCRM (formerly SalesLogix) Reducing Event Viewer SData errors

    $
    0
    0

    If this is a constant issue in the Event viewer “ERROR Sage.SalesLogix.Web.SLXMembershipProvider – No error message available, result code: E_FAIL(0x80004005).”  It may be due to the way the Webdll user is running the website.  We changed the Webdll user to a local admin ( in this case Webdll was a domain admin) and we gave Webdll read rights to the following folder: C:\Windows\SysWOW64\config\systemprofile.  This simple change increased the speed and stability of the SLXClient significantly!

     

    Recording: Infor CRM (Saleslogix) v8.3 Outlook Integration

    $
    0
    0

    Did you miss last week’s webinar?

    If you did, not to worry, we have a recording available that we hope will get you excited for the improvements to Outlook integration, which Infor is now strictly calling Xbar.

    View the recording here.

    And, if you are interested in learning about the new Infor CRM Mobile 3.4, stay tuned.  May’s webinar is right around the corner and it just might have something to do with Mobile. (hint hint)

    -Brianna

    Training Workshop: Creating Groups in Infor CRM Web

    $
    0
    0

    There’s still time to register!

    If you’re using Infor CRM there will come a time when you need to create a group, and whether you’re creating groups on a regular basis, or your group creation is sporadic, it’s important to understand how to setup the criteria you need.  Join us Monday, May 9th at 2pm CDT for our free training workshop.

    Register now!

    -Brianna

    Infor CRM 8.3 – New feature for linking Contacts to Accounts

    $
    0
    0

    Infor CRM 8.3 adds a useful new feature for linking Contacts to Accounts where the Account is not the primary Account for the Contact.  This new tab is called “Contact Associations”.

    This is in addition to the legacy Associations tab where you could only link records horizontally, Contacts to Contacts and Accounts to Accounts.  Now you can link Contacts to multiple Accounts to represent some association they have to that secondary Account.  An example of this might be to link a consultant Contact to an Account he is working with or an IT contractor or supplier/vendor.  The only thing I wish they would have added is the ability to designate a “Role” for that associated Contact like they have with the legacy Associations tab.  A new field could easily be added to the editable grid if you wanted it to do this.

    Once you navigate to a Contact record, there is a new tab there as well called “Account Associations” which of course shows the links to the Accounts you linked him to.  It’s puzzling to me that you cannot create a new linked Account from this tab, you can only do this from an Account, oh well.Contact Associations

    Training Workshop: Sales Forecasting in Infor CRM Web

    $
    0
    0

    Set yourself up to win.

    Make sure your sales pipeline is setup in a way that meets your unique needs, ensuring that your sales forecast is accurate and useful.  How?  Join us on Monday, May 23rd at 2pm CDT as we walk through different options available when setting up your sales pipeline, and how to use them.  As always, our end user focused workshops are free and a great way to spend 30 minutes of your Monday afternoon.

     Register now!

    -Brianna


    Update 01 for Infor CRM v8.3 Has Been Released

    $
    0
    0

    Is there a fix for what ails you?

    Last Thursday Infor released Update 01 for Infor CRM v8.3 and while there are a handful of new features – Reporting performance improvements, added logging and exception handling for Price and Availability, enhanced Account detail view Sales Order tab to include additional information – there are many more fixes.  As an example, here are the fixed issues included in SNC Update 01 (for the Windows client):

    SNC Update

    If you would like to see the list of fixed issues for Web Core Update 01 and Web Model Update 01, visit the Infor Xtreme Portal >Downloads >Patches.  Choose your product and click Search. Voilà.

    -Brianna

     

     

    Infor CRM Xbar for Outlook version 1.3.1 brings backwards compatibility

    $
    0
    0

    Infor CRM Xbar for Outlook 1.3 has been updated to 1.3.1 which adds backwards compatibility for Infor CRM version 8.1.0.8, 8.2.0.2 and 8.3 or later.

    Ensure your version of Microsoft Outlook is the desktop version of 2010, 2013, 2016, or Office 365.

    You can download the software on the Infor Xtreme Portal right now.

     

    Customizing the Account Associations SData Grid in Infor CRM (Saleslogix)

    $
    0
    0

    The Account Associations tab in Infor CRM is made of a custom SmartPart that contains an SData DataGrid. Usually, when working with an SData Grid in Application Architect, you have editors that help you set up the columns and elements of the grid. However, since this is a custom SmartPart, you’ll need to edit the Javascript directly.

    To locate the Account Associations tab, you’ll need to open the SlxClient portal in AA under the Portal Manager, then on the Support Files tab, expand, SmartParts and then the Association folder. Double-click to open the “AccountAssociations.js” file.

    In this example, we’ll be adding the City, State, & Zip to the grid layout.

    Adding Address Fields to the Associations Grid

    First, we’ll need to add the fields we’d like to add to the list of fields that are being retrieved via SData. You’ll find that on line 83, where it shows “select” under the “storeOptions”. These are the fields being retrieved via SData. For our example, we’ll be adding the Address.CityStatePostalCode calculated field to be retrieved, and since there are two sides to the association, we’ll need to retrieve that for the ToAccount and also the FromAccount. The code for that section will look like this:

    storeOptions: {
        resourceKind: 'associations',
        include: [],
        select: ["FromId", "ToId", "ToAccount.AccountName", "BackNotes", "BackRelation", "IsAccountAssociation", "ToAccount.Address.CityStatePostal", "FromAccount.Address.CityStatePostal"],
        dataCarrierId: 'AccountAssociationsgrdAssociations_DataCarrier',
        sort: []
    },

    Notice the “ToAccount.Address.CityStatePostal”, “FromAccount.Address.CityStatePostal” at the end of the select line.

    Now, we need to add the field to the layout. That is done just above, in the “columns” section (which starts at line 68). We’ll add a new line and specify the field the column is bound to, the name (heading/caption), and other special rules for our column. It will look like this:

    columns: [
        { 
            name: ' ', field: 'Id', width: 5, type: slxEdit, cellValue: AccountAssociationsResource.AccountAssociationsGrid_Edit_Text,
            entityType: 'Sage.Entity.Interfaces.IAssociation, Sage.Entity.Interfaces', smartPart: 'AddEditAccountAssociation',
            isCentered: true, dialogHeight: 320, dialogWidth: 600, formObjectName: 'Sage.UI.Forms.AccountAssociations'
        },
        { field: "FromAccount.AccountName", name: AccountAssociationsResource.AccountAssociationsGrid_Name_HeaderText, width: 10, type: accountCell },
        { field: "FromAccount.Address.CityStatePostal", name: 'City/State/Postal', width: 20, type: addressCell },
        { field: "ForwardRelation", name: AccountAssociationsResource.AccountAssociationsGrid_Relation_HeaderText, width: 8, type: relationshipCell },
        { field: "ForwardNotes", name: AccountAssociationsResource.AccountAssociationsGrid_Notes_HeaderText, width: 20, type: descriptionCell, formatter: Sage.Format.abbreviationFormatter(128) },
        { field: "CreateUser", name: AccountAssociationsResource.AccountAssociationsGrid_CreatedBy_HeaderText, width: 10, type: slxUser },
        { field: "CreateDate", name: AccountAssociationsResource.AccountAssociationsGrid_Date_HeaderText, width: 10, type: dateTime, dateOnly: true }
    ],

    Our new column is the following row:

    { field: "FromAccount.Address.CityStatePostal", name: 'City/State/Postal', width: 20, type: addressCell },

    The “field” value is the field the column is bound to, the “name” is the column heading/caption, we’re specifying a wider width to allow for the value to show, and the “type” is the crucial part here. We need to define a custom column type to translate between the “From” account’s address and the “To” account address, depending on which side of the association we’re currently looking it. If we’re looking at the “From” account record, we want to see the “To” account’s address listed since the “To” account is what will be showing in the grid, and vice-versa. We’re telling the grid that the column’s type is “addressCell”, so now we need to define what an “addressCell” is.

    Just above in the code, you’ll see some other cell types defined, for things like “descriptionCell”, etc. We’ll be creating one of those. Just above the “var options” line (on line 65), add the following code:

    var addressCell = dojo.declare(dojox.grid.cells.Cell, {
        format: function (inRowIndex, inItem) {
            return utility.getValue(inItem, "FromId") == parentId
                   ? utility.getValue(inItem, "ToAccount.Address.CityStatePostal") 
                   : utility.getValue(inItem, "FromAccount.Address.CityStatePostal");
        }
    });

    That code get’s the “FromId” from the row’s data (which is passed in as “inItem” and check’s to see if it matches the “parentId” (parentId is declared at the top of the code, which is the ID value of the account record we’re looking at, the “current ID”). If the “FromId” matches the parentId, we’re looking at the “From” account’s record, so we need to return the “To” account’s address. Otherwise we return the “From” account’s address.

    The end result will look like this:

    Associations Tab Address

    After deploying, keep in mind that you might not see changes right away since the js file is likely cached by your browser. You can force to see the changes by clearing your browser cache and refreshing.

    Business Process Management with TaskCentre and Infor CRM

    $
    0
    0

    Webinar Wednesday, June 22nd at 2pm CDT.

    taskcentre

    How does the ability to automate virtually any process, cut down on errors and inefficiencies, and integrate your ERP with Infor CRM, eCommerce, marketing automation, and website – all without writing a line of code – sound? With the TaskCentre Business Process Management suite the dream can become a reality!  TaskCentre integrates seamlessly with Infor CRM to bring cohesion to the people, systems, and information that make your business unique.  Join us for this month’s live demonstration and overview of TaskCentre and the positive impact it can have on your bottom line.

    Learn more and register!

    -Brianna

    Using Computed columns to create an Email link on a standard ICRM web datagrid

    $
    0
    0

    Recently, I had a request to build functionality that would allow a user to send an email based on information in a datagrid row.  Here is an outline of how I put that functionality together.

    The form contains a standard datasource/datagrid setup (Note, this does not work with an editable datagrid).  In addition to any other functionality on the form, we also need to add two objects:  A Hidden Text control, and a button on the form.  The Hidden Text field will contain the record ID for the selected record.  The button is what actually triggers the send email process, however we “click” this button via code when using the Custom Format Column.

    grid

    On Load Action of the Form

    Load Action 0 (C# Snippet Action Item)

    On Repaint Event = False

    The code here builds and registers the javascript both to populate the hidden field, and to actually send the email, via a click event on the tbrButton.

    string script;
    script = @"function fxsendEmail(email, subject, body) {
    dojo.require('Sage.Utility.Email');
    Sage.Utility.Email.writeEmail(email, subject, body);
    };
    function FXSendMail (id) {
    document.getElementById('" + QFHidden.ClientID + @"').value=id;
    document.getElementById('" + tbrButton.ClientID + @"').click();
    };
    ";
    
    ScriptManager.RegisterStartupScript(this, GetType(), "FXSendEmail", script, true);
    

    Load Action 1 (C# Snippet Action Item)

    ON Repaint Event = True

    This action is simply used to hide the button we added.  On Repaint event has to be set to true so the button will be hidden on every record.

    tbrButton.Style["Display"]="none";
    

    On Click event of Hidden Button

    C# Snippet Action Item:

    This code reads the hidden field value (which should be the currently selected record).  This is where the email, subject and Body of the email is created, using a new entity object to get data from selected row and related records.

    if (QFHidden.Value != "")
    {
    	string recid = QFHidden.Value;
    	Sage.Entity.Interfaces.IOpportunityTest ot = Sage.Platform.EntityFactory.GetById<Sage.Entity.Interfaces.IOpportunityTest>(recid);
    	
    	string email = ot.StringField2;
    	string subject = recid;
    	string body = "This is a test.";
     	string emailbody = Sage.Platform.WebPortal.PortalUtil.JsonEncode(body);
    
    	string script = string.Format("sendEmail(\"{0}\", \"{1}\", \"{2}\");", email, subject, emailbody);
    
    	ScriptManager.RegisterStartupScript(this, GetType(), "FXSendEmail2", script, true);
    }
    

    DataGrid computed column

    I’ve added the custom format column, with the Datafield set as Id.  I’ve also defined a Method name, and will create a method with that name in the Format code.

    ColumnsCollection

    The Code here simply builds a link that displays the value “Email”, and will call the SendMail function for the current ID.

    protected string EmailTest(object Id)
    {	
    	string recid = Id.ToString();
    	string script = string.Format("<a href='javascript:FXSendMail(\"{0}\")'>Email</a>", recid);
    	return script;
    }
    

    Setting Up Email Body:

    The required email fields (Email, Subject and body) are defined on the hidden button created for the form.  Since we are creating an entity object for the selected item in the grid, we can access other information related to that entity.  When building the email body, there are certain formatting standards you will need to follow.  The value “\x0D\x0A” should be used in-string for any CRLF needed in the email body.  For readability in code, it wouldn’t be a bad idea to create each line and then append them together into a single string.  Once the initial string is built, it needs to be Encoded to be used in the email.

    Example:

    if (QFHidden.Value != "")
    {
    	string recid = QFHidden.Value; //Get the selected record's ID from the hidden text control
    	
    	//Create enity object for selected record, using the ID from the previous line.
    	Sage.Entity.Interfaces.IOpportunityTest ot = Sage.Platform.EntityFactory.GetById<Sage.Entity.Interfaces.IOpportunityTest>(recid);
    	
    	string email = ot.StringField2; //This is where I am storing an email address.  Note that I am using the entity object to return value.
    	string subject = recid; //I am returning the recordID as the subject line.
    		
    	string body = string.Format("{0}{1} \x0D\x0A", "This is a test for record: ", recid); //Initial string
    	
    	//Appending items to the initial string.  Note "\x0D\x0A" for CRLF
    	
    	body += String.Format("\x0D\x0A");
    	body += String.Format("Here is the Opportunity Description: {0} \x0D\x0A", ot.Opportunity.Description);
    	body += String.Format("\x0D\x0A");
    	body += String.Format("End Of Email");
    	body += "\x0D\x0AMore Text Here";
    	
    	//This line encodes the value for use in the email.  Skipping this step will not allow the email to be generated.
    	string emailBody = Sage.Platform.WebPortal.PortalUtil.JsonEncode(body);
    	
    	//Finally, we call the fxsendEmail function that was registered on the load of the form, then we register the function call.
    	string script = string.Format("fxsendEmail(\"{0}\", \"{1}\", \"{2}\");", email, subject, emailBody);
    	ScriptManager.RegisterStartupScript(this, GetType(), "FXSendEmail2", script, true);
    }
    
    

    And that’s it!  With this modification, you can add an email link to a datagrid row, and then pull information based on the selected row.

    Thanks for reading!

    Viewing all 169 articles
    Browse latest View live