A set of gists for re-use throughout various Epicor customizations.
Code snippets intended to be reused throughout the Epicor 10 implementation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Add this function to the beginning of any BPM directives and utilize whenever logging is needed. | |
This function can use predefined UDCodes to parse the logEnabled flag and the log path. | |
Updating either of these user codes will change the logging behavior for all debug logs written in the current environment that utilize this code. This gives developers a one-stop shop for turning logging on/off and configuring the folder path for the log specific to each environment. | |
I set a string called timeinticks to the current time measured in ticks (10,000 ticks per millisecond) to act as a unique identifier for each iteration of the BPM. This has helped in the past when the same BPM was being fired by different processes, and each process was writing to the same log file. | |
I set a local boolean called debugMode in each BPM so a developer can turn on logging for a single BPM for troubleshooting purposes without turning on logging for all BPMs using this snippet. This boolean will not only override the global logEnabled flag, but will also append the timeinticks variable to the log to ensure that each iteration of the BPM is writing to a unique log file instead of sharing the same log file. | |
This code can be modified to work in a form customization as well as a BPM. Just make it into a private method instead of a delegate. | |
Required variables: | |
--logActive: If set to false,the function does nothing. Can be set globlly using UserCodes. | |
--logPath: UDCode with "LongDescription" set to the network path for writing logs to the server. Must be a path the end user can access. | |
--timeinticks: A unique identifier specific to this instance of the BPM | |
--debugMode: A local boolean that can override the global logActive variable (used for troubleshooting a single BPM in production) | |
*/ | |
//Set the global logging variables | |
bool logActive = true; // Set logging to true by default | |
var writeLog = Db.UDCodes.FirstOrDefault(u=>u.CodeTypeID.ToUpper()=="{CodeTypeID}" && u.CodeID.ToLower()=="writelogs"); // Look up the global variable to enable or disable logging | |
bool.TryParse(writeLog.CodeDesc, out logActive); // Parse the UDCode's description field as a boolean (If unable to parse, the initial value of logActive is used) | |
var logPath = Db.UDCodes.FirstOrDefault(u=>u.CodeTypeID.ToUpper()=="{CodeTypeID}" && u.CodeID.ToLower()=="logpath"); | |
//Set the local variables | |
string timeinticks = DateTime.Now.Ticks.ToString(); // Unique identifier for this iteration of the BPM | |
bool debugMode=false; // Local variable to override logActive and to force each iteration of the BPM to write to a unique file | |
Func<string, string, bool> WriteLog = delegate(string message, string level) | |
{ | |
if (logActive || DebugMode) | |
{ | |
try | |
{ | |
string lvl; | |
switch(level.ToLower()) | |
{ | |
case "debug": | |
lvl="00-DEBUG"; | |
break; | |
case "info": | |
lvl="01-INFORMATION"; | |
break; | |
case "information": | |
lvl="01-INFORMATION"; | |
break; | |
case "warning": | |
lvl="02-WARNING"; | |
break; | |
case "error": | |
lvl="03-ERROR"; | |
break; | |
default: | |
lvl="04-" + level; | |
break; | |
} | |
string logFile = logPath.LongDesc.Trim('/') | |
+ "/" + this.Name + "_Log-" | |
+ DateTime.Now.ToString("yyyy-MM-dd") | |
+ (DebugMode ? "_Iteration-" + timeinticks : "") // Separate log files for each run of BPM | |
+ ".csv"; | |
if (!System.IO.File.Exists(logFile)) | |
{ | |
System.IO.File.AppendAllText(logFile, | |
"\"Timestamp\",\"Method\",\"Iteration\",\"User\",\"Level\",\"Message\"" | |
+Environment.NewLine | |
); | |
} | |
System.IO.File.AppendAllText(logFile, | |
"\"" + DateTime.Now.ToString() + "\"," | |
+ "\"" + this.Name + "\"," | |
+ "\"I-" + timeinticks + "\"," | |
+ "\"" + Session.UserID + "\"," | |
+ "\"" + lvl + "\"," | |
+ "\"" + message.Replace("\"","''") + "\"" | |
+ Environment.NewLine | |
); | |
} | |
catch (Exception ex) | |
{ | |
// Fail silently so we can continue on if the log is being accessed by another process or the log path can't be found | |
} | |
} | |
return true; | |
}; | |
Func<string, string> Debug = delegate(string msg) | |
{ | |
if (DebugMode) | |
{ | |
WriteLog(msg, "Debug"); | |
} | |
return ""; | |
}; | |
WriteLog("Staring BPM...","Info"); // This message will be logged if debug=true or logActive=true | |
Debug("This message will only show if debug=true"); // This message will be logged if debug=true, regardless of the logActive value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This fuction seialzes any object to XML. Very useful when debugging Epicor business object method call that take Tablesets as parameters. | |
*/ | |
Func<Object,string> SerializeObj = delegate(Object o) | |
{ | |
try | |
{ | |
System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(o.GetType()); | |
TextWriter WriteFileStream = new StreamWriter(@"\\Server\Share\Logs\" + o.ToString() + ".xml"); | |
x.Serialize(WriteFileStream, o); | |
WriteFileStream.Close(); | |
WriteLog("Serializing " + o.ToString() + " succeeded."); | |
} | |
catch (Exception ex) | |
{ | |
// I cath and ignore errors on my troubleshooting functions | |
} | |
return ""; | |
}; | |
var OrderAllocListTS = new Erp.Tablesets.OrderAllocListTableset(); | |
SerializeObj(OrderAllocListTS); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Had a thought tht we could easily create "featue flags" in Epicor that old enable us to turn on/off whole sets of customizations (Primarily BPMs) by using a secial UD Code Type ID to control whether features were turned on or off. | |
In any BPM you crate, add a variable named FeatureXEnabled (Wheere "X" is the name of the feature you want to check). | |
Next, add a custom code block with the following code: | |
*/ | |
var featurexflag = Db.UDCodes.FirstOrDefault(u=>u.CodeTypeID=="FeatureFlg" && u.CodeID.ToLower()=="featurex"); | |
FeatureXEnabled = (featurexflag!=null ? featurexflag.IsActive : false); | |
/* | |
Finally, add a Condition block to check whether FeatureXEnabled is true or false. If true, continue executing your BPM code. If false, stop the process. | |
This way, BPMs can be moved into production enviroments, but will remain inactive until someone adds th correct FeatureFlg and sets the "Active" column to true. | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Add this function to the beginning of any BPM under development and utilize whenever you need to monitor the data being transmitted back and forth. | |
This function will use reflection to find the name and value of every property associated with the object passed in. Use this to see what data is being passed with each row in a temporary table, etc. | |
This function is for use on single objects. Use with a foreach clause to iterate through multiple instances of an object (Such as rows in a temporary table, etc.). | |
*/ | |
using System.ComponentModel; | |
Func<object, string> GetProperties = delegate(object obj) | |
{ | |
var msg = ""; | |
try | |
{ | |
for (var i=0; i<TypeDescriptor.GetProperties(obj).Count; i++) | |
{ | |
var descriptor = TypeDescriptor.GetProperties(obj)[i]; | |
var curVal = ""; | |
try | |
{ | |
curVal = (descriptor.GetValue(obj) ?? "").ToString(); | |
} | |
catch (Exception ex) | |
{ | |
curVal=ex.Message.ToString(); | |
} | |
msg += descriptor.Name + "=" + curVal + "\r\n"; | |
} | |
} | |
catch (Exception ex) | |
{ | |
msg += "\r\nAN ERROR HAS OCCURRED:\r\n" + ex.ToString(); | |
} | |
msg += "\r\n"; | |
return msg; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Title: Check to see if we need to show Developer Message | |
Type: Standard Code Snippet | |
Requirements: (Values should be set in a previous process) | |
string DeveloperMessage | |
bool ShowDeveloperMessage | |
Author: Doug Lockwood | |
Date: 12/13/2017 | |
Usage: This snippet is intended for use by any Epicor developer. Add this as a custom executable code block at the end of any BPM to display any DeveloperMessage strings created as an information message at the end of the BPM process. This snippet can be customized by adding UserIds to the (callContextClient.CurrentUserId.ToLower()=="dlockwood") condition. For questions, contact Doug Lockwood. | |
Modifications: | |
Date: Summary: | |
12/14/17 Modified to check for blank DeveloperMessage before anything else (If there is no message, there is no need to decide whether or not to display it). | |
*/ | |
if (!String.IsNullOrEmpty(DeveloperMessage)) | |
{ | |
// Provide information about the current BPM | |
DeveloperMessage = "--------------------\r\n" | |
+ "{Current BPM Information}" | |
+ "\r\n--------------------\r\n" | |
+ DeveloperMessage + "\r\n"; | |
// Show developer messages to Doug Lockwood always | |
if (callContextClient.CurrentUserId.ToLower()=="dlockwood") | |
{ | |
ShowDeveloperMessage=true; | |
DeveloperMessage += "\r\n----------\r\n* Showing Developer Messages because current user is " + callContextClient.CurrentUserId + ".\r\n"; | |
} | |
// Show developer messages to anyone in the DEV environment only | |
var curCompany = Db.Company.FirstOrDefault(x=>x.Company1==callContextClient.CurrentCompany && x.Name.ToLower().Contains("dev")); | |
if (curCompany!=null) | |
{ | |
ShowDeveloperMessage=true; | |
DeveloperMessage += "\r\n----------\r\n* Showing Developer Messages because company \"" + curCompany.Name + "\" is a development company.\r\n"; | |
} | |
// Queue up a Developer Message to display at the end of the process | |
if (ShowDeveloperMessage) | |
{ | |
DeveloperMessage = "DEVELOPER MESSAGE*:\r\n====================\r\n" | |
+ DeveloperMessage | |
+ "\r\n\r\n====================\r\n" | |
+ "Assembly: " + callContextClient.AssemblyName + "\r\n" | |
+ "CGC: " + callContextClient.CGCCode + "\r\n" | |
+ "Client Type: " + callContextClient.ClientType + "\r\n" | |
+ "Current Company: " + callContextClient.CurrentCompany + "\r\n" | |
+ "Current Plant: " + callContextClient.CurrentPlant + "\r\n" | |
+ "Current User ID: " + callContextClient.CurrentUserId + "\r\n" | |
+ "Customization: " + callContextClient.CustomizationId + "\r\n" | |
+ "Process: " + callContextClient.ProcessId + "\r\n" | |
+ "====================\r\n"; | |
this.PublishInfoMessage(DeveloperMessage, | |
Ice.Common.BusinessObjectMessageType.Information, | |
Ice.Bpm.InfoMessageDisplayMode.Individual, | |
"Developer Message", | |
"Information From the Developer"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Use these comments for the main script section | |
/*********************************************** | |
Customization: {Current Customization Name} | |
Date: {Date Created} | |
Author: {Primary Developer} | |
Purpose: {Brief description of purpose} | |
***********************************************/ | |
//Use these comments for in-line comments or method comments | |
/*********************************************** | |
Summary: | |
{High-level overview of code block} | |
Inputs: | |
Outputs: | |
Created by: | |
Created on: | |
Change Log: | |
Initials Date Description | |
***********************************************/ | |
By default, all customizations should be marked with "All Companies" (so that if we happen to add new companies in the future, our customizations will be available to them).
When saving a new customization, add the following header block: /*********************************************** Based on: {Previous Customization Name}
Date: {Date Created}
Author: {Primary Developer}
Purpose: {Brief description of purpose}
***********************************************/
Example App.PartEntry.PartForm customization "02_Phase_II_Enhancements":
/***********************************************
Based on: 26774_Part_Master_Customization
Date: 10/19/2017
Author: Doug Lockwood
Purpose: Modifications to Part Master required for Phase II Rollout
***********************************************/
Examples:�02_Phase_II_Enhancements Number="02" Customization Name="Phase_II_Enhancements"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Place these comments in the first Custom Code block of a BPM | |
/*********************************************** | |
BPM: {Current BPM Path & Name} | |
Date: {Date Created} | |
Author: {Primary Developer} | |
Purpose: {Brief description of purpose} | |
Requirements: {Brief overview of requirements} | |
***********************************************/ | |
//Use these comments for in-line comments or method comments | |
/*********************************************** | |
Summary: | |
{High-level overview of code block} | |
Inputs: | |
Outputs: | |
Created by: | |
Created on: | |
Change Log: | |
Initials Date Description | |
***********************************************/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment