Appendix

The Aras.Server.Core.Configurator.IStringNotationConverter interface requires its implementors to support the ConvertExpressionToStringNotation method. This method provides a String Notation value to a given Effectivity Expression object representation.

The Aras.Server.Core.Configurator.IStringNotationConverter interface definition is:

namespace Aras.Server.Core.Configurator
{
 public interface IStringNotationConverter
 {
 string ConvertExpressionToStringNotation(ExpressionBase expression);
 }
}

This section provides examples of customizing the Effectivity String Notation. These simple examples supplement the concepts detailed in sections 7.10.1 Customizing Effectivity String Notation on Effectivity Expression and 7.10.2 Customizing Unified Effectivity String Notation.

Custom Effectivity String Notation on Effectivity Expression

The default Effectivity String Notations Converter uses a conjunction word for each logical operator:

  • AND for logical conjunction
  • OR for logical disjunction
  • NOT for logical complement
  • For example:

    ((Factory = Munich AND Production Date >= 01/01/2020) OR(Factory = Detroit AND Production Date >= 06/01/2020)) AND

    NOT(Body Type = Hatchback)

    The customized String Notation will use the following characters instead of the conjunction words:

  • && for logical conjunction
  • || for logical disjunction
  • ! for logical complement

For example:

((Factory = Munich && Production Date >= 01/01/2020) ||(Factory = Detroit && Production Date >= 06/01/2020)) &&

!(Body Type = Hatchback)

ExpressionToShortStringNotationConverter Class Definition

To achieve this customization goal, a new ExpressionToShortStringNotationConverter class is defined representing the new Converter:

internal class ExpressionToShortStringNotationConverter : Aras.Server.Core.Configurator.IStringNotationConverter
{
 private readonly IEnumerable<Aras.Server.Core.Configurator.Variable> _variablesContainer;
 private static readonly Dictionary<Type, Aras.Server.Core.Configurator.DataType> typeTranslator = new Dictionary<Type, Aras.Server.Core.Configurator.DataType>
 {
 { typeof(string), Aras.Server.Core.Configurator.DataType.String },
 { typeof(int), Aras.Server.Core.Configurator.DataType.Int },
 { typeof(DateTime), Aras.Server.Core.Configurator.DataType.DateTime }
 };
 /// <summary>
 /// Initializes a new instance of the ExpressionToShortStringNotationConverter class with the defined variables container
 /// </summary>
 /// <param name="variablesContainer">Represents a list of “Aras.Server.Core.Configurator.Variable” objects, which will be used to get human-readable identifiers of Variables and/or Named-constants</param>
 public CustomExpressionToStringNotationConverter(IEnumerable<Aras.Server.Core.Configurator.Variable> variablesContainer)
 {
 if (variablesContainer == null)
 {
 throw new ArgumentNullException(“variablesContainer”);
 }
 this._variablesContainer = variablesContainer;
 }
 /// <summary>
 /// Converts an incoming “Aras.Server.Core.Configurator.ExpressionBase” Effectivity Expression object representation to a string notation and returns it.
 /// </summary>
 /// <param name="expression">An Effectivity Expression object representation</param>
 /// <returns>A effectivity string notation value</returns>
 public string ConvertExpressionToStringNotation(Aras.Server.Core.Configurator.ExpressionBase expression)
 {
 if (expression == null)
 {
 throw new ArgumentNullException(“expression”);
 }
 return TranslateToStringNotationImplementation(expression, null);
 }
 private string TranslateToStringNotationImplementation(Aras.Server.Core.Configurator.ExpressionBase expression, Aras.Server.Core.Configurator.ExpressionBase parentExpression)
 {
 if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.Term)
 {
 Aras.Server.Core.Configurator.ExpressionTerm expressionTerm = expression as Aras.Server.Core.Configurator.ExpressionTerm;
 string variableId = expressionTerm.OperandLeft.GetId();
 Aras.Server.Core.Configurator.Variable variable = _variablesContainer.FirstOrDefault(var => var.Id == variableId);
 string variableName = variable?.Name ?? variableId;
 string assignedVariableValue;
 Aras.Server.Core.Configurator.ITermOperand expressionTermOperandRight = expressionTerm.OperandRight;
 if (expressionTermOperandRight.OperandType == Aras.Server.Core.Configurator.TermOperandType.NamedConstant)
 {
 string namedConstantId = expressionTermOperandRight.GetId();
 assignedVariableValue = variable?.Enum?.FindNamedConstantById(namedConstantId)?.Name ?? namedConstantId;
 }
 else if (expressionTermOperandRight.OperandType == Aras.Server.Core.Configurator.TermOperandType.Constant)
 {
 object constantValue = expressionTermOperandRight.GetValue();
 Type constantType = constantValue.GetType();
 Aras.Server.Core.Configurator.DataType constantDataType;
 if (!typeTranslator.TryGetValue(constantType, out constantDataType))
 {
 throw new NotSupportedException(FormattableString.Invariant($"'{constantType.FullName}' constant data type is not supported as the right operand type for string notation conversion”));
 }
 //'null’ as the first parameter of the ‘ToLocalString()’ method call indicates the incoming DateTime object shouldn’t be adjusted to any time zone, because it’s assumed ‘datetime’ always is returned in neutral format with UTC time zone
 assignedVariableValue = constantDataType != Aras.Server.Core.Configurator.DataType.DateTime ? Convert.ToString(constantValue, CultureInfo.InvariantCulture) : Aras.I18NUtils.DateTimeConverter.ToLocalString(null, (DateTime)constantValue);
 }
 else
 {
 throw new NotSupportedException(FormattableString.Invariant($"{expressionTerm.OperandRight.OperandType} is not supported as the right operand type for string notation conversion”));
 }
 return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", AddSquareBracketsIfNeed(variableName), GetAlgebraicTermOperatorNotation(expressionTerm.Operator), AddSquareBracketsIfNeed(assignedVariableValue));
 }
 string expressionStringNotation = string.Empty;
 string expressionOperatorStrNotation = GetAlgebraicNotationByOperator(expression.ExpressionType);
 //we assume at-least-one, at-most-one, exactly-one expressions consists of terms(<EQ><variable id="" /><named-constant id="" /></EQ>) only here
 if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.AtLeastOne ||
 expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.AtMostOne ||
 expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.ExactlyOne)
 {
 IEnumerable<string> innerTermsStringNotation = expression.InnerExpressions.Select(expr => TranslateToStringNotationImplementation(expr, expression));
 return string.Format(CultureInfo.InvariantCulture, expressionOperatorStrNotation, string.Join(" | ", innerTermsStringNotation));
 }
 foreach (Aras.Server.Core.Configurator.ExpressionBase innerExpression in expression.InnerExpressions)
 {
 string innerExpressionStrNotation = TranslateToStringNotationImplementation(innerExpression, expression);
 if (!string.IsNullOrEmpty(innerExpressionStrNotation))
 {
 if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.NOT)
 {
 expressionStringNotation += string.Format(CultureInfo.InvariantCulture, expressionOperatorStrNotation, innerExpressionStrNotation);
 }
 else if (expression.ExpressionType == Aras.Server.Core.Configurator.ExpressionType.IMPLICATION)
 {
 string[] ifThen = expressionOperatorStrNotation.Split(' ');
 bool isStringNotationEmpty = string.IsNullOrEmpty(expressionStringNotation);
 // implicationTemplate describes “IF {expr}" or “THEN {expr}". In case “IF”, whitespace after "{expr}" is required -"IF {expr} “
 string implicationTemplate = isStringNotationEmpty ? "{0} {1} " : "{0} {1}";
 expressionStringNotation += string.Format(CultureInfo.InvariantCulture, implicationTemplate, ifThen[isStringNotationEmpty ? 0 : 1], innerExpressionStrNotation);
 }
 else
 {
 expressionStringNotation += !string.IsNullOrEmpty(expressionStringNotation) ? expressionOperatorStrNotation + innerExpressionStrNotation : innerExpressionStrNotation;
 }
 }
 }
 return parentExpression != null && parentExpression.InnerExpressions.Count > 1 &&
 parentExpression.ExpressionType != Aras.Server.Core.Configurator.ExpressionType.IMPLICATION && expression.ExpressionType != Aras.Server.Core.Configurator.ExpressionType.NOT ?
 "(" + expressionStringNotation + ")” : expressionStringNotation;
 }
 private static string AddSquareBracketsIfNeed(string name)
 {
 return !string.IsNullOrEmpty(name) && !System.Text.RegularExpressions.Regex.IsMatch(name, @"^[a-zA-Z0-9]+$") ?
 string.Format(CultureInfo.InvariantCulture, "[{0}]”, name) : name;
 }
 private static string GetAlgebraicNotationByOperator(Aras.Server.Core.Configurator.ExpressionType boolOperator)
 {
 string notation = string.Empty;
 switch (boolOperator)
 {
 case Aras.Server.Core.Configurator.ExpressionType.AND:
 notation = " && ";
 break;
 case Aras.Server.Core.Configurator.ExpressionType.OR:
 notation = " || ";
 break;
 case Aras.Server.Core.Configurator.ExpressionType.NOT:
 notation = "!({0})";
 break;
 case Aras.Server.Core.Configurator.ExpressionType.IMPLICATION:
 notation = “IF THEN";
 break;
 case Aras.Server.Core.Configurator.ExpressionType.AtLeastOne:
 notation = “AT-LEAST-ONE({0})";
 break;
 case Aras.Server.Core.Configurator.ExpressionType.AtMostOne:
 notation = “AT-MOST-ONE({0})";
 break;
 case Aras.Server.Core.Configurator.ExpressionType.ExactlyOne:
 notation = “EXACTLY-ONE({0})";
 break;
 default:
 throw new NotSupportedException(boolOperator + " type is not supported for string notation conversion”);
 }
 return notation;
 }
 private static string GetAlgebraicTermOperatorNotation(Aras.Server.Core.Configurator.TermOperator termOperator)
 {
 string algebraicTermOperatorNotation;
 switch (termOperator)
 {
 case Aras.Server.Core.Configurator.TermOperator.Equal:
 algebraicTermOperatorNotation = "=";
 break;
 case Aras.Server.Core.Configurator.TermOperator.GreaterThanOrEqual:
 algebraicTermOperatorNotation = ">=";
 break;
 case Aras.Server.Core.Configurator.TermOperator.LessThanOrEqual:
 algebraicTermOperatorNotation = "<=";
 break;
 default:
 throw new NotSupportedException(FormattableString.Invariant($"'{termOperator}' term operator is not supported for string notation conversion”));
 }
 return algebraicTermOperatorNotation;
 }
}

Custom Unified Effectivity String Notation

By default, a comma (,) is used to combine multiple Effectivities on a single Relationship. A customized String Notation uses the OR conjunction word instead of the comma.

A customized String Notation example

Effective Item

Single Effectivity String Notations

Unified Effectivity String Notation

Unified Effectivity String Notation

Effective Item Single Effectivity String Notations Default Customized
Engine Factory = Detroit (Factory = Detroit), (Factory = Munich) (Factory = Detroit) OR (Factory = Munich)
Engine Factory = Munich (Factory = Detroit), (Factory = Munich) (Factory = Detroit) OR (Factory = Munich)

JoinExpressionStringNotation Method Definition

To achieve this customization goal, change the definition of the JoinExpressionStringNotation internal method of the effs_getExpressionStringNotation Method as follows:

internal virtual string JoinExpressionStringNotation(IEnumerable<string> expressionStringNotations)
{
if (!string.IsNullOrEmpty(expressionStringNotations.ElementAtOrDefault(1)))
{
 expressionStringNotations = expressionStringNotations.Select(
expressionStringNotation => "(" + expressionStringNotation + ")”);
}
return string.Join(" OR ", expressionStringNotations);
}

This section provides examples of customizing BOM Reports. These simple examples supplement the concept described in section 8 Configuring BOM Reports.

Adding a New Column Using an Item Property

In this example, the Multilevel BOM Report is customized. Using the following procedure, the Unit column is added between the Quantity and AML Status columns to display the Unit property of Part Items:

  1. Search for the PE_multilevelReport Query Definition (QD) and open its item view for editing.
  2. Click Show Editor on the PE_multilevelReport QD sidebar.
  3. Click the Properties button on the top RootPart row. The Properties dialog box appears.
  4. Add the Unit property to the Properties dialog box and click Save.
  5. Repeat steps 3-4 on the Part row representing the Part Query Item.
  6. Note
    Both Root and Part Query Items should have the same Properties in a QD. Otherwise, Aras Innovator will return an error. This requirement does not apply to other Query Items of the QD.

  7. Click Save, Unlock & Close on the PE_multilevelReport QD toolbar.
  8. Search for the Multilevel BOM Report and open its Item view for editing.
  9. Go to the Stylesheet editor tab.
  10. Add the <th>Unit</th> table header cell element after <th>Quantity</th>.
  11. Add a table data cell element for the unitproperty values after the one for quantity.
  12. <td rowspan="{$rowCount}" width="120px"> 
     <xsl:value-of select="unit"/>
    </td>

  13. Click Apply on the Stylesheet editor tab.
  14. Click Done on the Multilevel BOM Report toolbar. The Unit column is added to the Multilevel BOM Report.

Adding a New Column Using a Relationship Property

Note
This example can be used as a guide for adding any relationship property. For the Sequence relationship property, prefer the configuration as described in the next section.

In this example, the BOM Costing Report is customized. Using the following procedure, the Reference Designator column is added as the last column to display the Reference Designator property of the Part BOM Relationship ItemType:

  1. Search for the PE_costingReport Query Definition (QD) and open its item view for editing.
  2. Click Show Editor on the PE_costingReport QD sidebar.
  3. Click the Properties button on the Part BOM row. The Properties dialog box appears.
  4. Add the Reference Designator property to the Properties dialog box and click Save.
  5. Click Save, Unlock & Close on the PE_costingReport QD toolbar.
  6. Search for the BOM Costing Report and open its Item view for editing.
  7. Go to the Stylesheet editor tab.
  8. Add the <th>Reference Designator</th> table header cell element after <th>Cost Basis</th>.
  9. Add a table data cell element for the reference_designator property values after the one for cost_basis.
  10. <td>
    <xsl:value-of select="Relationships/Item[@alias='Part BOM’ and @info='parent’]/reference_designator"/>
    </td>
    Note
    When customizing the Multilevel BOM Report with a Relationship ItemType property, this property table data tag should have the rowspan attribute as follows:<td rowspan="{$rowCount}" width="120px”><xsl:value-of select="Relationships/Item[@alias='Part BOM’ and@info='parent’]/property_name"/></td>

  11. Click Apply on the Stylesheet editor tab.
  12. Click Done on the BOM Costing Report toolbar. The Reference Designator column is added to the BOM Costing Report.

Adding the Sequence Column

In this example, the BOM Costing Report is customized. Using the following procedure, the Sequence column is added as the last column to show the Sequence property of the Part BOM Relationship ItemType:

  1. Search for the BOM Costing Report and open its Item view for editing.
  2. Go to the Stylesheet editor tab.
  3. Add the <th>Sequence</th> table header cell element after <th>Cost Basis</th>.
  4. Add a table data cell element for the sort_order property values after the one for cost_basis.
  5. <td>
    <xsl:value-of select="sort_order"/>
    </td>
    Note
    When customizing the Multilevel BOM Report with the Sequence property, this property table data tag should have the rowspan attribute as follows:<td rowspan="{$rowCount}" width="120px”><xsl:value-of select="sort_oder"/></td>

  6. Click Apply on the Stylesheet editor tab.
  7. Click Done on the BOM Costing Report toolbar. The Sequence column is added to the BOM Costing Report.