How do I fix a corrupt Picklist in Infor CRM version 8.1?
Feb 23, 2015 - Infor CRM (Saleslogix) Web - Navigating Reports
Attend the Workshop - Infor CRM (Saleslogix) Web - Navigating Reports!
Event details:
- Monday, February 23, 2015
- From 2:00 PM Central to 2:30 PM Central
(8:00 PM GMT to 8:30 PM GMT)
Register Today!
Mar 09, 2015 - Infor CRM v8: Creating and Running Mail Merge for Contacts or Leads
Attend the Workshop - Infor CRM v8: Creating and Running Mail Merge for Contacts or Leads!
Event details:
- Monday, March 09, 2015
- From 2:00 PM Central to 2:30 PM Central
(8:00 PM GMT to 8:30 PM GMT)
Register Today!
Mar 16, 2015 - Creating a New Sales Process in Infor CRM (Saleslogix)
Attend the Workshop - Creating a New Sales Process in Infor CRM (Saleslogix)!
Event details:
- Monday, March 16, 2015
- From 2:00 PM Central to 2:30 PM Central
(8:00 PM GMT to 8:30 PM GMT)
Register Today!
Adding Missing DB Columns to the SECTABLEDEFS Table in Infor CRM (Formerly Saleslogix)
Here is a SQL script I came up with to add any fields that exist in a Infor CRM enabled table (it exists in the REYNCTABLEDEFS table) but that are not correctly defined in the SECTABLEDEFS table. This type of thing usually happens if someone adds a new database field outside of the Infor CRM tools, like using the SQL Management studio wizards, or a SQL script directly.
Boiler plate warning: Running SQL on your database without knowing what you are doing can cause all kinds of bad things. Run these scripts only if you understand what they are doing. ALWAYS RUN THIS TYPE OF THING IN A TEST SYSTEM FIRST!
--Finds columns in tables that exist in RESYNCTABLEDEFS but are not
--properly defined in the SECTABLEDEFS.
select
db.tablename tablename,
db.fieldname fieldname
from
(select a.name tablename, b.name fieldname, b.xusertype from sysobjects a inner join syscolumns b on a.id=b.id where a.xtype='U') db
left join
sysdba.sectabledefs b on db.tablename=b.tablename and db.fieldname=b.fieldname
inner join
sysdba.RESYNCTABLEDEFS c on db.tablename=c.TABLENAME
where
b.FIELDNAME is null
--This script actually fixes the SECTABLEDEFS so any real DB columns not in the SECTABLEDEF
--are created. This assumes any DateTime data type should be set to store in UTC format.
declare @maxoffset int
declare @maxindex int
set @maxindex = (select MAX(fieldindex) from sysdba.SECTABLEDEFS)
set @maxoffset = (select MAX(fieldoffset) from sysdba.SECTABLEDEFS)
insert into sysdba.SECTABLEDEFS
(TABLENAME, FIELDNAME, FIELDOFFSET, FIELDINDEX, SEQCODE, KEYFLAG, SYNC, USERDEF, DISPLAYNAME, ISHIDDEN,
DATETIMETYPE, DESCRIPTION, AUTOINCREMENT, MODIFYDATE, MODIFYUSER, CREATEDATE, CREATEUSER)
select
upper(db.tablename) tablename,
upper(db.fieldname) fieldname,
case when c.SECURE='T' then @maxoffset + ROW_NUMBER() over(order by db.tablename, db.fieldname)
else 0 end fieldoffset,
@maxindex + ROW_NUMBER() over(order by db.tablename, db.fieldname) fieldindex,
'2' SEQCODE,
'0' keyflag,
'Y' sync,
'T' userdef,
db.fieldname Displayname,
'F' isHidden,
case when t.name='datetime' then 'U'
else null end datetimetype,
null description,
'F' autoincrement,
GETUTCDATE() modifydate,
'ADMIN' modifyuser,
GETUTCDATE() createdate,
'ADMIN' createuser
from
(select a.name tablename, b.name fieldname, b.xusertype from sysobjects a inner join syscolumns b on a.id=b.id where a.xtype='U') db
left join
sysdba.sectabledefs b on db.tablename=b.tablename and db.fieldname=b.fieldname
inner join
sysdba.RESYNCTABLEDEFS c on db.tablename=c.TABLENAME
left JOIN
sys.types AS t ON db.xusertype=t.user_type_id
where
b.FIELDNAME is null
Don't Miss Out: Infor CRM (Saleslogix) Update 05 and Your Customizations
Infor CRM (Formerly Saleslogix) Web Client- Error in the Event Logs "userOption entity not found"
We recently had a client upgrade from a previous version to v8.1 of Infor CRM. Their web client was running however frequent error logs were being written to the event logs on the web server such as the following:
2015-01-06 15:02:11,665 [36] ERROR Global - Integration Messaging MessagingService unhandled exception [Saleslogix Error Id=SLXDA19EBA812E1B6AA]
{
"slxErrorId": "SLXDA19EBA812E1B6AA",
"mitigation": "AjaxMessagingServiceError (404)",
"date": "2015-01-06T15:02:11",
"utc": "2015-01-06T23:02:11",
"message": "userOption entity not found.",
"source": "Sage.Platform.SData.RequestHandlerBase`3, Sage.Platform, Version=8.1.0.1228, Culture=neutral, PublicKeyToken=null",
"type": "Sage.Common.Syndication.DiagnosesException",
"stackTrace": " at Sage.Platform.SData.RequestHandlerBase`3.GetEntity(ICriteria criteria, Nullable`1 missingStatus)\r\n at Sage.Platform.SData.RequestHandlerBase`3.GetEntity(UriFormatter uri, Boolean isRead)\r\n at Sage.Platform.SData.RequestHandlerBase`3.InternalGet()\r\n at Invokeb0c53120ce6a4149875595b333d08f02.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": "TEntity GetEntity(NHibernate.ICriteria, System.Nullable`1[System.Net.HttpStatusCode])",
"hashCode": "FA95BF4E-38B437F8-523DCDAA",
"pid": 5820,
"identity": {
"name": "admin",
"isAuthenticated": true,
"authenticationType": "Forms"
},
"version": "8.1.0.1179",
"logger": {
"level": "ERROR",
"location": "Sage.Platform.Diagnostics.ErrorHelper.LogException(:0)",
"name": "Global",
"message": "Integration Messaging MessagingService unhandled exception [Saleslogix Error Id=SLXDA19EBA812E1B6AA]"
},
"request": {
"looksLikeAjax": true,
"isLocal": false,
"method": "GET",
"url": "http://localhost:3333/SlxClient/slxdata.ashx/slx/system/-/userOptions(category eq 'GroupLookup' and name eq 'StayInDetailView')?_includeContent=false&format=json&_t=1420585330779",
"referrer": "http://localhost:3333/SlxClient/Home.aspx",
"ipAddress": "localhost",
"userAgent": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
"userLanguages": "en-US; en;q=0.8"
},
It turns out there really is not anything wrong with the userOptions entity. This type of error is produced when a specific record is attempted to be located through the slxdata.ashx handler and when no results are returned. The key is this line here:
http://localhost:3333/SlxClient/slxdata.ashx/slx/system/-/userOptions(category eq 'GroupLookup' and name eq 'StayInDetailView')?_includeContent=false&format=json&_t=1420585330779
This line shows what was being queried. The userOptions system endpoint is actually pointing to the table USEROPTIONDEF (there is a USEROPTIONS table but that is not the right one). Turns out during the upgrade 4 standard items were missing. These include:
Adding these default values to the table fixed the issues and the errors no longer occurred in the event logs.
I have seen similar errors when looking for other records, like groups. The Welcome page has pre-defined dashboard widgets pointing to groups. If you don't have these groups in your system You will get similar errors.
These have errors like this.
"message": "group entity not found.",
"url": "http://localhost:3333/SlxClient/slxdata.ashx/slx/system/-/groups(name eq 'My Notes' and upper(family) eq 'HISTORY')?_includeContent=false&include=layout&format=json&_t=1420585332770",
Recording: Infor CRM Update 05 and The Affect on Your Customizations
InforCRM Xbar1.2 - Adds 2 New Features
Accessing the UserOptions for a User in the Infor CRM (formerly Saleslogix) Web Client
The entity model in the web client does not contain an entity which exposes the UserOptions table. SO how can you access the data here?
Well there are 2 ways really. Lets take a look.
#1 Add the USEROPTIONS table to the Saleslogix Application Entities area. Then you can query against it and do finds using the IRepositoryHelper. The following shows how to get the default team for a user:
var userExCode = "SYST00000001";
Sage.Platform.RepositoryHelper<IUserOptions> repository =
Sage.Platform.EntityFactory.GetRepositoryHelper<IUserOptions>();
Sage.Platform.Repository.ICriteria criteria = repository.CreateCriteria();
criteria.Add(repository.EF.Eq("USERID", "XYZ"));
criteria.Add(repository.EF.Eq("CATEGORY", "General"));
criteria.Add(repository.EF.Eq("NAME", "InsertSecCodeID"));
System.Collections.Generic.IList<Sage.Entity.Interfaces.IUserOptions> options = criteria.List<Sage.Entity.Interfaces.IUserOptions>();
foreach(Sage.Entity.Interfaces.IUserOptions option in options)
{
userExCode = option.OPTIONVALUE;
break;
}
#2 Use something already built in! The UserOptionsService. Here is a sample C# snippet showing how to use it to also find the users default owner:
var userOption = ApplicationContext.Current.Services.Get<IUserOptionsService>();
var userExCode = !string.IsNullOrEmpty(userOption.GetCommonOption("InsertSecCodeID", "General"))
? userOption.GetCommonOption("InsertSecCodeID", "General")
: "SYST00000001";
Note that users may or may not always have a specific option defined. That is why you should always check that like shown in the code snippet above.
Infor CRM (Formerly Saleslogix) v8.1 Update 05 Issues
We have recently discovered 3 issues that have developed for customers after upgrading to update 05 for version 8.1 web. These have all been reported to Infor support but details on fixes have not been released. I wanted to make people aware in case it would adversely affect them. I am not sure if the Core or Model update is the cause or if both can result in these problems.
Pie Charts
The pie chart widgets on the dashboard no longer function. This seems to be the only charting object with problems.
Modules Loading
The implementation of modules on a page can allow things like tabs, detail views, menus, and toolbars to show or hide based on conditions on the page. After update 05 the modules still operate when the page first loads but the system no longer reruns the modules on switching records client side using the group navigator or VCR buttons at the top of the screen.
Cross Entity Data Problems
By far the biggest issue is some kind of problematic undocumented implementation of automatic field level security. What I mean by this is in the Windows client, if you add an Account level field to a Contact screen that field is automatically read only because it is a field at a different level. Apparently someone had the bright idea to try this in the web client in update 05. Before you could add an Account level field to the Contact screen and decide if you wanted it editable or not. This gave better flexibility for design and allowed the system to not require navigating away from one record to update something at another level. Now, if you add an account level field to a Contact screen the control is automatically set as Disabled (a problem in itself- it should be read-only if anything). However the bigger problem is the data doesn't even show in the field. It is just an empty disabled control!
You can change the control on the form load event to make it enabled, however the auto binding still does not work. You need to programmatically populate the control with the data via code on the form load. When a user makes changes to the field, they get the "Unsaved Changes" warning, but clicking the standard Save button doesn't update the other entity level. So the change at the Account level is discarded. You need to also programmatically update the entity as well. Ugh.
Worse yet, this cross entity implementation seems inconsistent. Data from the Account at the Contact level doesn't display and controls are automatically disabled. Data from the Contact level on Tickets are also automatically disabled but the data does display. So depending on what entity you are on and what entity you are going to seems to impact the behavior. Ugh Ugh.
This behavior only happens for user. T he admin user does not exhibit any problems with cross entity data binding.
First Look: The Infor CRM (Saleslogix) Xbar v1.2
Infor CRM (Formerly Saleslogix) - New Triggers To Be Aware Of in v8.1
Just a heads up, Infor CRM has added several triggers into the system as part of the upgrade process. These triggers are used for their ERP integrations. For most customers who don't use this integration, these are unnecessary objects that will only result in extraneous data being written to tables that are never used (tables start with ERP). Worse, they can impact your ability to manipulate your database with t-sql statements. I recently ran into an issue trying to run an update on the CONTACT table. The update statement would not run- it hung indefinitely. Turns out it was the triggers preventing the update.
I would strongly recommend identifying if you need these triggers in your system. If not it would not be a bad idea to disable them.
Here is a list of the triggers and the table they are in:
ACCOUNT_INTEGRATION_INSERT | ACCOUNT |
ACCOUNT_INTEGRATION_CHANGE | ACCOUNT |
ACCOUNT_TOMBSTONE | ACCOUNT |
ACTIVITY_INTEGRATION_INSERT | ACTIVITY |
ACTIVITY_INTEGRATION_CHANGE | ACTIVITY |
ACTIVITY_INT_INSTEAD_INS | ACTIVITY |
ACTIVITYATTENDEE_INT_INSTEAD_INS | ACTIVITYATTENDEE |
ACTIVITYATTENDEE_INTEGRATION_CHANGE | ACTIVITYATTENDEE |
ACTIVITYATTENDEE_COUNT | ACTIVITYATTENDEE |
ADDRESS_INTEGRATION_CHANGE | ADDRESS |
ADHOCGROUP_INTEGRATION_TOMBSTONE | ADHOCGROUP |
ADHOCGROUP_INTEGRATION_INSERT | ADHOCGROUP |
CONTACT_TOMBSTONE | CONTACT |
CONTACT_INTEGRATION_CHANGE | CONTACT |
CONTACT_INTEGRATION_INSERT | CONTACT |
USERACTIVITY_TOMBSTONE | USER_ACTIVITY |
USERACTIVITY_INTEGRATION_CHANGE | USER_ACTIVITY |
USERACTIVITY_INT_INSTEAD_INS | USER_ACTIVITY |
How do you set the Base Directory when Deploying your Web Client on Infor CRM 8.1?
Another Issue Discovered in Infor CRM v8.1 Update 5- Sales Process Activity Dates Are Off
We had a client who discovered when clicking to create a timeless Activity from a Opportunity Sales Process step, the dates were one day off (behind). In the Sales Process you can define the number of days in the future the Activity is to be created for. So if you have an Activity that is to get created 4 days in the future, when you click to create the Activity from a Sales Process, the date would actually default to 3 days in the future. I dug around and never really found the problem, but did find a fix to resolve the issue.
The creation of activities from the Sales Process tab is handled by a custom smart part in the OpportunitySalesProcess folder called SalesProcessService.aspx. Inside the SalesProcessService.aspx.cs code file there is a method used called "ScheduleActivity" which returns a string of a new Activity ID. Inside this method there is a call to get the SalesProcessAudit entity which is passed into the method as a parametrized array.
ISalesProcessAudit spAudit = EntityFactory.GetById<ISalesProcessAudit>(spaId);
if (spAudit != null)
{
IContact contact = EntityFactory.GetById<IContact>(contactId);
IOpportunity opp = EntityFactory.GetById<IOpportunity>(opportunityId);
IUser leader = EntityFactory.GetById<IUser>(leaderId);
Activity activity = (Activity)spAudit.SalesProcess.ScheduleActivity(spaId, opp, leader, contact, true);
activityId = (string) activity.Id;
}
The spAudit.SalesProcess.ScheduleActivity is a business rule in the SalesProcess entity. Unfortunately, this is contained in the compiled Sage.Saleslogix.BusinessRules.dll and can't be modified. What it does is to parse out the details of the step that was clicked and then create an activity with these parameters. The result of that is an Activity record. Luckily with this Activity object being returned by the compiled method we can edit the results to correct the problem. The only issue remaining is to do our own parsing of the step to get the step's details. If we add our own code between these two lines:
Activity activity = (Activity)spAudit.SalesProcess.ScheduleActivity(spaId, opp, leader, contact, true);
activityId = (string) activity.Id;
We can do just that:
//CFX Added to fix sales process being one day off
DateTime today = DateTime.Today;
string xmlData = Encoding.UTF8.GetString(spAudit.Data);
System.Xml.XmlDocument document = new System.Xml.XmlDocument();
document.LoadXml(xmlData);
System.Xml.XmlNode node = document.DocumentElement.SelectSingleNode("//Action/ActivityAction");
string innerText = node.SelectSingleNode("Type").InnerText;
string daysOut = node.SelectSingleNode("ScheduleDaysFromToday").InnerText;
string tml = node.SelectSingleNode("Timeless").InnerText;
bool timeless = tml == "T";
int num = (daysOut != string.Empty) ? int.Parse(daysOut) : 0;
if (timeless)
{
activity.Notes = "Days: " + num.ToString() + "Currently: " + activity.StartDate.ToString("MMddyyyy hh:mm:ss") + " | " + today.Date.ToString("MMddyyyy hh:mm:ss") + " | " + today.AddDays(4).Date.AddSeconds(5).ToString("MMddyyyy hh:mm:ss");
activity.LongNotes = activity.Notes;
activity.StartDate = today.AddDays(num).Date.AddSeconds(5);
activity.Timeless = true;
activity.Save();
}
//End CFX Add to fix sales process being one day off
So the final code is:
private string ScheduleActivity(string scheduleContext)
{
string activityId = string.Empty;
try
{
string[] args = scheduleContext.Split(new Char[] { ',' });
string spaId = args[0];
string contactId = args[1];
string opportunityId = args[2];
string leaderId = args[3];
ISalesProcessAudit spAudit = EntityFactory.GetById<ISalesProcessAudit>(spaId);
if (spAudit != null)
{
IContact contact = EntityFactory.GetById<IContact>(contactId);
IOpportunity opp = EntityFactory.GetById<IOpportunity>(opportunityId);
IUser leader = EntityFactory.GetById<IUser>(leaderId);
Activity activity = (Activity)spAudit.SalesProcess.ScheduleActivity(spaId, opp, leader, contact, true);
//CFX Added to fix sales process being one day off
DateTime today = DateTime.Today;
string xmlData = Encoding.UTF8.GetString(spAudit.Data);
System.Xml.XmlDocument document = new System.Xml.XmlDocument();
document.LoadXml(xmlData);
System.Xml.XmlNode node = document.DocumentElement.SelectSingleNode("//Action/ActivityAction");
string innerText = node.SelectSingleNode("Type").InnerText;
string daysOut = node.SelectSingleNode("ScheduleDaysFromToday").InnerText;
string tml = node.SelectSingleNode("Timeless").InnerText;
bool timeless = tml == "T";
int num = (daysOut != string.Empty) ? int.Parse(daysOut) : 0;
if (timeless)
{
activity.Notes = "Days: " + num.ToString() + "Currently: " + activity.StartDate.ToString("MMddyyyy hh:mm:ss") + " | " + today.Date.ToString("MMddyyyy hh:mm:ss") + " | " + today.AddDays(4).Date.AddSeconds(5).ToString("MMddyyyy hh:mm:ss");
activity.LongNotes = activity.Notes;
activity.StartDate = today.AddDays(num).Date.AddSeconds(5);
activity.Timeless = true;
activity.Save();
}
//End CFX Add to fix sales process being one day off
activityId = (string) activity.Id;
}
}
catch
{
activityId = string.Empty;
}
return activityId;
}
Infor ION - The next generation integration solution
Infor CRM (Formerly Saleslogix) v8.1.3- Strange Behavior with the Account Hierarchy Add-In
We recently had a customer on version 8.1 update 03 that was having problems with the Account Hierarchy module Customer FX created.
The Account Hierarchy screen displays the Parent/Child relationships that Infor CRM never recreated in the web client. This is a custom smart part that is launched as a dialog from a button. We have always been able to simply add the button that launches the custom view using a ShowDialog action.
This used to correctly bind the Account Hierarchy popup view to the current Account entity.
What I found with this particular customer, is even though the screen seemed bound to the current Account entity (this.BindingSource.Current did not return null) that Entity was in fact empty of had no Account.Id. Very strange.
Looking at how the dialog action is rendered onto the deployed user control form I found the code looks like this:
if (DialogService != null)
{
// DialogActionItem
DialogService.SetSpecs(300, 700, "ViewAccountHierarchy", string.Empty );
DialogService.EntityType = typeof(Sage.Entity.Interfaces.IAccount);
DialogService.ShowDialog();
}
What seemed to fix the problem was explicitly passing in the EntityID to the Dialog using DialogService.EntityID=. UNfortunately since the Dialog action auto-creates the code deployed to the website, that is not something I could add from there.
The solution? Change the button click action to a C# Snippet Action Item. Then in the code snippet add the following equivalent code with the Id correctly passed in.
Sage.Entity.Interfaces.IAccount acc = this.BindingSource.Current as Sage.Entity.Interfaces.IAccount;
if (DialogService != null)
{
// DialogActionItem
DialogService.SetSpecs(300, 700, "ViewAccountHierarchy", string.Empty );
DialogService.EntityType = typeof(Sage.Entity.Interfaces.IAccount);
DialogService.EntityID = acc.Id.ToString();
DialogService.ShowDialog();
}
Strange but at least there is a work-around. Onto the next thing....
Mar 30, 2015 - Infor CRM Web - Creating a New Opportunity
Attend the Workshop - Infor CRM Web - Creating a New Opportunity!
Event details:
- Monday, March 30, 2015
- From 2:00 PM Central to 2:30 PM Central
(7:00 PM GMT to 7:30 PM GMT)
Register Today!
Infor CRM (Formerly Saleslogix) Rolling Your Own Resource Files
In my last post I talked about international localization in the Infor CRM web client. It comes with the 5 standard, but what if you want to roll a new one out? Well it is as simple as adding new resource files for the pages you want to convert. The resource files are stored in a subfolder that houses the user control. They are named in the format of [User Control Name and Extension].[Culture Name].resx. So for instance the Italian version of the Account Details user control is AccountDetails.ascx.it.resx. The list of culture codes to use can be found all over. Here is a listing.
For custom smart parts you are responsible for creating your own separate resource files if you want to. For QuickForms you can do the following to add custom ones.
Open the Quick Form you want to apply new cultural localization to.
In the upper right you will see "Click to modify form resources" Click It.
Click the link in the upper right "Click to add a new resource file".
Select the new Culture you want to add a localization file for. Optionally, you can specify an existing resource file in the "Copy From" so you don't need to start from scratch.
Click OK. You will be taken back to the previous window to now edit your localization resource file.
That is it! The next time you build and deploy, you will see a new resource file in the App_LocalResources sub folder of the SmartPart folder containing whatever quick form you did this against.
A word of warning, if you do make a custom resource file you cant put only some of the keys into it. When the user control opens it will use the matching culture's resource file. If the user control expects a key to exist in that resource file and it doesn't you will get a run time error and your smart part will not load.