Codeunit 1501 Workflow Management, source in 29
Source29
src/Layers/W1/BaseApp/System/Workflow/WorkflowManagement.Codeunit.al903 lines, Copyright (c) Microsoft Corporation. MIT
namespace System.Automation;
using System;
using System.Telemetry;
using System.Utilities;
codeunit 1501 "Workflow Management"
{
Permissions = TableData Workflow = R,
TableData "Workflow Step" = R,
TableData "Workflow Step Instance" = Rimd,
TableData "Workflow Table Relation Value" = rimd,
tabledata "Workflow Event" = r,
TableData "Workflow Event Queue" = rimd,
TableData "Workflow Step Argument" = rd,
TableData "Workflow - Record Change" = rimd,
TableData "Workflow Record Change Archive" = rimd,
TableData "Workflow Step Instance Archive" = rimd,
TableData "Workflow Step Argument Archive" = rimd,
tabledata "Workflow - Table Relation" = r;
trigger OnRun()
begin
end;
var
WorkflowRecordManagement: Codeunit "Workflow Record Management";
AlwaysTxt: Label '<Always>';
CombinedConditionTxt: Label '%1; %2', Locked = true;
// Telemetry strings
WorkflowTelemetryCategoryTxt: Label 'Workflow', Locked = true;
WorkflowEventStartTelemetryTxt: Label 'Workflow event: Start Scope', Locked = true;
WorkflowUpgradeTxt: Label 'Skip executing workflow during upgrade.', Locked = true;
WorkflowEventEndTelemetryTxt: Label 'Workflow event: End Scope', Locked = true;
WorkflowStepFoundTelemetryTxt: Label 'Active workflow step instance was found for the provided function and the conditions were met', Locked = true;
WorkflowStepConditionsNotMetTelemetryTxt: Label 'Active workflow step instances were found for the provided function, but the conditions were not met', Locked = true;
WorkflowMatchingStepFoundTelemetryTxt: Label 'Matching workflow step instance found for the provided function', Locked = true;
WorkflowInstanceCreatedTelemetryTxt: Label 'Workflow instance created', Locked = true;
WorkflowNotFoundTelemetryTxt: Label 'No active workflow or step instance found', Locked = true;
WorkflowStepNotFoundTelemetryTxt: Label 'No actionable workflow step was found for the provided function or the conditions were not met', Locked = true;
WorkflowArchiveTelemetryTxt: Label 'Workflow is being archived', Locked = true;
procedure TrackWorkflow(Variant: Variant; var WorkflowStepInstance: Record "Workflow Step Instance")
var
RecRef: RecordRef;
begin
WorkflowStepInstance.MoveForward(Variant);
RecRef.GetTable(Variant);
UpdateRelatedTableValues(RecRef);
if IsWorkflowCompleted(WorkflowStepInstance) then
ArchiveWorkflowInstance(WorkflowStepInstance);
end;
procedure FindWorkflowStepInstance(Variant: Variant; xVariant: Variant; var WorkflowStepInstance: Record "Workflow Step Instance"; FunctionName: Code[128]): Boolean
begin
exit(FindWorkflowStepInstanceWithOptionalWorkflowStart(Variant, xVariant, WorkflowStepInstance, FunctionName, true));
end;
procedure CanExecuteWorkflow(Variant: Variant; FunctionName: Code[128]): Boolean
var
WorkflowStepInstance: Record "Workflow Step Instance";
begin
exit(FindWorkflowStepInstanceWithOptionalWorkflowStart(Variant, Variant, WorkflowStepInstance, FunctionName, false));
end;
local procedure FindWorkflowStepInstanceWithOptionalWorkflowStart(Variant: Variant; xVariant: Variant; var WorkflowStepInstance: Record "Workflow Step Instance"; FunctionName: Code[128]; StartWorkflow: Boolean): Boolean
var
Workflow: Record Workflow;
WorkflowRule: Record "Workflow Rule";
WorkflowStepInstanceLoop: Record "Workflow Step Instance";
WorkflowStepInstance2: Record "Workflow Step Instance";
RecRef: RecordRef;
xRecRef: RecordRef;
ActiveStepInstanceFound: Boolean;
TelemetryDimensions: Dictionary of [Text, Text];
begin
RecRef.GetTable(Variant);
xRecRef.GetTable(xVariant);
if not StartWorkflow then begin
Workflow.SetRange(Enabled, true);
if Workflow.IsEmpty() then begin
// Example Scenario:
// 1. Approval Workflow is enabled for Sales Order
// 2. An Approval is sent for a Sales Order -> Step Instance is created
// 3. The Workflow from (step 1) is disabled before the Approval (from step 2) is processed
//
// Disabled workflow can still have active workflow step instances (from before it was disabled, e.g. step 2)
WorkflowStepInstanceLoop.ReadIsolation := WorkflowStepInstanceLoop.ReadIsolation::ReadUncommitted;
WorkflowStepInstanceLoop.SetRange(Type, WorkflowStepInstanceLoop.Type::"Event");
WorkflowStepInstanceLoop.SetRange(Status, WorkflowStepInstanceLoop.Status::Active);
WorkflowStepInstanceLoop.SetRange("Function Name", FunctionName);
OnFindWorkflowStepInstanceWithOptionalWorkflowStartOnAfterSetWorkflowStepInstanceLoopFi1tersWhenNotStartWorkFlow(RecRef, WorkflowStepInstanceLoop, FunctionName, StartWorkflow);
if WorkflowStepInstanceLoop.IsEmpty() then
exit(false);
end;
end;
WorkflowStepInstanceLoop.Reset(); // Reset will clear ReadIsolation and SetLoadFields
WorkflowStepInstanceLoop.ReadIsolation := WorkflowStepInstanceLoop.ReadIsolation::ReadUncommitted;
WorkflowStepInstanceLoop.SetLoadFields("Sequence No.", "Previous Workflow Step ID", Argument);
WorkflowStepInstanceLoop.SetRange(Type, WorkflowStepInstanceLoop.Type::"Event");
WorkflowStepInstanceLoop.SetRange(Status, WorkflowStepInstanceLoop.Status::Active);
WorkflowStepInstanceLoop.SetRange("Function Name", FunctionName);
WorkflowStepInstanceLoop.SetFilter("Previous Workflow Step ID", '<>%1', 0); // "Previous Workflow Step ID" is used as primary key in `WorkflowStepInstance2.Get` inside the loop below
OnFindWorkflowStepInstanceWithOptionalWorkflowStartOnAfterSetWorkflowStepInstanceLoopFi1ters(RecRef, WorkflowStepInstanceLoop, FunctionName, StartWorkflow);
WorkflowStepInstanceLoop.SetCurrentKey("Sequence No.");
WorkflowStepInstance2.SetLoadFields(Status, "Record ID");
WorkflowStepInstance2.ReadIsolation := WorkflowStepInstance2.ReadIsolation::ReadUncommitted;
WorkflowStepInstance.ReadIsolation := WorkflowStepInstance.ReadIsolation::ReadUncommitted;
// Example Scenario: There exists a sequence of workflow step instances like below
// Step1 -> Step2.1 -> Step3
// \
// -> Step2.2
//
// Each of the step instances have properties:
// Step1 - "Previous Workflow Step ID" = 0 - Status = Complete|Processing
// Step2.1 - "Previous Workflow Step ID" = Step1 - Status = Active - Condition = Met
// Step2.2 - "Previous Workflow Step ID" = Step1 - Status = Active - Condition = Not Met
// Step3 - "Previous Workflow Step ID" = Step2.1
//
// Below Loop will find Step2.1 and return true
if not WorkflowStepInstanceLoop.IsEmpty() then
if WorkflowStepInstanceLoop.FindSet() then
repeat
// below IF statements separated for performance reasons, no short-circuit evaluation in AL
if WorkflowStepInstance2.Get(WorkflowStepInstanceLoop.ID, WorkflowStepInstanceLoop."Workflow Code", WorkflowStepInstanceLoop."Previous Workflow Step ID") then
if WorkflowStepInstance2.Status in [WorkflowStepInstance2.Status::Completed, WorkflowStepInstance2.Status::Processing] then
if Format(WorkflowStepInstance2."Record ID") = Format(RecRef.RecordId) then begin
ActiveStepInstanceFound := true;
WorkflowStepInstanceLoop.FindWorkflowRules(WorkflowRule);
if EvaluateCondition(RecRef, xRecRef, WorkflowStepInstanceLoop.Argument, WorkflowRule) then begin // Step2.1 in above example
WorkflowStepInstance.Get(WorkflowStepInstanceLoop.ID, WorkflowStepInstanceLoop."Workflow Code", WorkflowStepInstanceLoop."Workflow Step ID");
GetTelemetryDimensions(FunctionName, WorkflowStepInstance.ToString(), TelemetryDimensions);
Session.LogMessage('0000DZB', WorkflowStepFoundTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
exit(true);
end;
end;
until WorkflowStepInstanceLoop.Next() = 0;
// This occurs when there are active steps found, but the conditions were not met
// e.g. If Step2.1 from above scenario doesn't exist, we will find Step2.2 and return false instead
if ActiveStepInstanceFound then begin
GetTelemetryDimensions(FunctionName, '', TelemetryDimensions);
Session.LogMessage('0000DZC', WorkflowStepConditionsNotMetTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
exit(false);
end;
WorkflowStepInstance.Reset();
WorkflowStepInstance.ReadIsolation := WorkflowStepInstance.ReadIsolation::ReadUncommitted;
// Example Scenario:
// 1. Post Purchase Invoice (No. = 10804)
// 2. Workflow is set up to automatically create a General Journal Line for posted Purchase Invoice
// 3. RecRef is (about to be inserted) General Journal Line (table = 81, field 36 "Applies-to Doc. No." = 10804)
// 4. "Workflow Table Relation Value" has one entry matching
// - Table ID = 122 (Purchase Invoice Header)
// - Field ID = 3 (No.)
// - Related Table ID = 81 (General Journal Line)
// - Related Field ID = 36 (Applies-to Doc. No.)
// - Value = 10804
//
// This scenario is not caught in the previous loop, because we can't know RecRef.RecordId in advance.
// The corresponding Workflow Step Instance's "Record ID" field is, in fact, empty.
if FindMatchingWorkflowStepInstance(RecRef, xRecRef, WorkflowStepInstance, FunctionName) then begin
GetTelemetryDimensions(FunctionName, WorkflowStepInstance.ToString(), TelemetryDimensions);
Session.LogMessage('0000DZD', WorkflowMatchingStepFoundTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
exit(true);
end;
WorkflowStepInstance.Reset();
WorkflowStepInstance.ReadIsolation := WorkflowStepInstance.ReadIsolation::ReadUncommitted;
// When no workflow step instance is found, we check if a workflow is enabled for the provided function name and ready to start
if FindWorkflow(RecRef, xRecRef, FunctionName, Workflow) then begin
if StartWorkflow then begin
InstantiateWorkflow(Workflow, FunctionName, WorkflowStepInstance);
GetTelemetryDimensions(FunctionName, WorkflowStepInstance.ToString(), TelemetryDimensions);
Session.LogMessage('0000DYR', WorkflowInstanceCreatedTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
end;
exit(true);
end;
exit(false);
end;
procedure MarkChildrenStatus(WorkflowStepInstance: Record "Workflow Step Instance"; NewStatus: Option)
var
ChildWorkflowStepInstance: Record "Workflow Step Instance";
begin
ChildWorkflowStepInstance.SetRange("Workflow Code", WorkflowStepInstance."Workflow Code");
ChildWorkflowStepInstance.SetRange("Previous Workflow Step ID", WorkflowStepInstance."Workflow Step ID");
ChildWorkflowStepInstance.SetRange(ID, WorkflowStepInstance.ID);
ChildWorkflowStepInstance.SetFilter(Status, '<>%1', WorkflowStepInstance.Status::Processing);
ChildWorkflowStepInstance.ModifyAll(Status, NewStatus, true);
end;
procedure MarkSiblingStatus(WorkflowStepInstance: Record "Workflow Step Instance"; NewStatus: Option)
var
SiblingWorkflowStepInstance: Record "Workflow Step Instance";
begin
SiblingWorkflowStepInstance.SetRange("Workflow Code", WorkflowStepInstance."Workflow Code");
SiblingWorkflowStepInstance.SetRange(ID, WorkflowStepInstance.ID);
SiblingWorkflowStepInstance.SetRange("Previous Workflow Step ID", WorkflowStepInstance."Previous Workflow Step ID");
SiblingWorkflowStepInstance.SetFilter("Workflow Step ID", '<>%1', WorkflowStepInstance."Workflow Step ID");
SiblingWorkflowStepInstance.SetFilter(Status, '<>%1', WorkflowStepInstance.Status::Processing);
SiblingWorkflowStepInstance.ModifyAll(Status, NewStatus, true);
end;
procedure UpdateStatus(var WorkflowStepInstance: Record "Workflow Step Instance"; NewStatus: Option; ChildrenStatus: Option; SiblingsStatus: Option)
begin
WorkflowStepInstance.Status := NewStatus;
WorkflowStepInstance.Modify(true);
MarkChildrenStatus(WorkflowStepInstance, ChildrenStatus);
MarkSiblingStatus(WorkflowStepInstance, SiblingsStatus);
end;
procedure FindWorkflow(RecRef: RecordRef; xRecRef: RecordRef; FunctionName: Code[128]; var Workflow: Record Workflow): Boolean
var
WorkflowStep: Record "Workflow Step";
WorkflowRule: Record "Workflow Rule";
WorkflowEvent: Record "Workflow Event";
begin
Workflow.SetRange(Enabled, true);
if Workflow.IsEmpty() then
exit(false);
WorkflowEvent.SetRange("Table ID", RecRef.Number);
WorkflowEvent.SetRange("Function Name", FunctionName);
if WorkflowEvent.IsEmpty() then
exit(false);
WorkflowStep.SetLoadFields(WorkflowStep."Workflow Code", WorkflowStep.Argument);
// Fields filtered with SetRange and primary key will be loaded automatically
WorkflowStep.SetRange("Function Name", FunctionName);
WorkflowStep.SetRange("Entry Point", true);
WorkflowStep.SetRange(Type, WorkflowStep.Type::"Event");
OnAfterSetFilterWorkflowStep(WorkflowStep);
if WorkflowStep.FindSet() then
repeat
if Workflow.Get(WorkflowStep."Workflow Code") then
if Workflow.Enabled then begin
WorkflowStep.SetFilters(WorkflowRule);
if EvaluateCondition(RecRef, xRecRef, WorkflowStep.Argument, WorkflowRule) then begin
Workflow.Get(Workflow.Code);
exit(true);
end;
end;
until WorkflowStep.Next() = 0;
Clear(Workflow);
exit(false);
end;
procedure WorkflowExists(Variant: Variant; xVariant: Variant; FunctionName: Code[128]): Boolean
var
Workflow: Record Workflow;
RecordRef: RecordRef;
xRecordRef: RecordRef;
begin
RecordRef.GetTable(Variant);
xRecordRef.GetTable(xVariant);
exit(FindWorkflow(RecordRef, xRecordRef, FunctionName, Workflow));
end;
local procedure InstantiateWorkflow(Workflow: Record Workflow; FunctionName: Text; var WorkflowStepInstance: Record "Workflow Step Instance")
begin
Workflow.CreateInstance(WorkflowStepInstance);
WorkflowStepInstance.SetRange("Function Name", FunctionName);
WorkflowStepInstance.FindFirst();
end;
local procedure UpdateRelatedTableValues(RecRef: RecordRef)
var
WorkflowTableRelationValue: Record "Workflow Table Relation Value";
begin
WorkflowTableRelationValue.SetRange("Record ID", RecRef.RecordId);
if WorkflowTableRelationValue.FindSet(true) then
repeat
WorkflowTableRelationValue.UpdateRelationValue(RecRef);
until WorkflowTableRelationValue.Next() = 0;
end;
local procedure FindMatchingWorkflowStepInstance(RecRef: RecordRef; xRecRef: RecordRef; var WorkflowStepInstance: Record "Workflow Step Instance"; FunctionName: Code[128]): Boolean
var
WorkflowRule: Record "Workflow Rule";
begin
WorkflowStepInstance.ReadIsolation := WorkflowStepInstance.ReadIsolation::ReadUncommitted;
WorkflowStepInstance.SetLoadFields(Argument, "Original Workflow Code", "Original Workflow Step ID");
WorkflowStepInstance.SetRange("Function Name", FunctionName);
WorkflowStepInstance.SetRange(Type, WorkflowStepInstance.Type::"Event");
WorkflowStepInstance.SetFilter(Status, '%1|%2', WorkflowStepInstance.Status::Active, WorkflowStepInstance.Status::Processing);
if WorkflowStepInstance.FindSet() then
repeat
WorkflowStepInstance.FindWorkflowRules(WorkflowRule);
if WorkflowStepInstance.MatchesRecordValues(RecRef) then
if EvaluateCondition(RecRef, xRecRef, WorkflowStepInstance.Argument, WorkflowRule) then
exit(true);
until WorkflowStepInstance.Next() = 0;
exit(false);
end;
local procedure EvaluateCondition(RecRef: RecordRef; xRecRef: RecordRef; ArgumentID: Guid; var WorkflowRule: Record "Workflow Rule"): Boolean
var
WorkflowStepArgument: Record "Workflow Step Argument";
TempBlob: Codeunit "Temp Blob";
Result: Boolean;
begin
if WorkflowStepArgument.Get(ArgumentID) and WorkflowStepArgument."Event Conditions".HasValue() then begin
TempBlob.FromRecord(WorkflowStepArgument, WorkflowStepArgument.FieldNo("Event Conditions"));
Result := EvaluateConditionOnTable(RecRef.RecordId, RecRef.Number, TempBlob) and
EvaluateConditionsOnRelatedTables(RecRef, TempBlob);
end else
Result := true;
Result := Result and EvaluateRules(RecRef, xRecRef, WorkflowRule);
OnAfterEvaluateCondition(RecRef, xRecRef, ArgumentID, WorkflowRule, Result);
exit(Result);
end;
local procedure EvaluateConditionOnTable(SourceRecordId: RecordID; TableId: Integer; TempBlob: Codeunit "Temp Blob"): Boolean
var
RequestPageParametersHelper: Codeunit "Request Page Parameters Helper";
RecRef: RecordRef;
LookupRecRef: RecordRef;
begin
RecRef.Open(TableId);
if not RequestPageParametersHelper.ConvertParametersToFilters(RecRef, TempBlob, TextEncoding::UTF8) then
exit(true);
if not LookupRecRef.Get(SourceRecordId) then
exit(false);
LookupRecRef.SetView(RecRef.GetView(false));
exit(LookupRecRef.Find());
end;
local procedure EvaluateRules(RecRef: RecordRef; xRecRef: RecordRef; var WorkflowRule: Record "Workflow Rule") Result: Boolean
begin
if RecRef.Number <> xRecRef.Number then
exit(false);
Result := true;
if WorkflowRule.FindSet() then
repeat
Result := Result and WorkflowRule.EvaluateRule(RecRef, xRecRef);
until (WorkflowRule.Next() = 0) or (not Result);
exit(Result);
end;
local procedure IsWorkflowCompleted(WorkflowStepInstance: Record "Workflow Step Instance"): Boolean
var
CompletedWorkflowStepInstance: Record "Workflow Step Instance";
begin
CompletedWorkflowStepInstance.SetRange("Workflow Code", WorkflowStepInstance."Workflow Code");
CompletedWorkflowStepInstance.SetRange(ID, WorkflowStepInstance.ID);
CompletedWorkflowStepInstance.SetRange(Status, WorkflowStepInstance.Status::Active);
exit(CompletedWorkflowStepInstance.IsEmpty);
end;
procedure ArchiveWorkflowInstance(WorkflowStepInstance: Record "Workflow Step Instance")
var
ToArchiveWorkflowStepInstance: Record "Workflow Step Instance";
WorkflowStepInstanceArchive: Record "Workflow Step Instance Archive";
ToArchiveWorkflowRecordChange: Record "Workflow - Record Change";
WorkflowRecordChangeArchive: Record "Workflow Record Change Archive";
ToArchiveWorkflowStepArgument: Record "Workflow Step Argument";
TelemetryDimensions: Dictionary of [Text, Text];
begin
GetTelemetryDimensions(WorkflowStepInstance."Function Name", WorkflowStepInstance.ToString(), TelemetryDimensions);
Session.LogMessage('0000DZF', WorkflowArchiveTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
ToArchiveWorkflowStepInstance.SetRange("Workflow Code", WorkflowStepInstance."Workflow Code");
ToArchiveWorkflowStepInstance.SetRange(ID, WorkflowStepInstance.ID);
if ToArchiveWorkflowStepInstance.FindSet() then begin
repeat
WorkflowStepInstanceArchive.Init();
WorkflowStepInstanceArchive.TransferFields(ToArchiveWorkflowStepInstance);
if ToArchiveWorkflowStepArgument.Get(ToArchiveWorkflowStepInstance.Argument) then begin
WorkflowStepInstanceArchive.Argument := CreateWorkflowStepArgumentArchive(ToArchiveWorkflowStepArgument);
ToArchiveWorkflowStepArgument.Delete(true);
end;
WorkflowStepInstanceArchive.Insert();
until ToArchiveWorkflowStepInstance.Next() = 0;
ToArchiveWorkflowStepInstance.DeleteAll(true);
end;
ToArchiveWorkflowRecordChange.SetRange("Workflow Step Instance ID", WorkflowStepInstance.ID);
if ToArchiveWorkflowRecordChange.FindSet() then begin
repeat
WorkflowRecordChangeArchive.Init();
WorkflowRecordChangeArchive.TransferFields(ToArchiveWorkflowRecordChange);
if not WorkflowRecordChangeArchive.Insert() then
WorkflowRecordChangeArchive.Modify();
until ToArchiveWorkflowRecordChange.Next() = 0;
ToArchiveWorkflowRecordChange.DeleteAll(true);
end;
end;
local procedure EvaluateConditionsOnRelatedTables(RecRef: RecordRef; TempBlob: Codeunit "Temp Blob"): Boolean
var
WorkflowTableRelation: Record "Workflow - Table Relation";
RequestPageParametersHelper: Codeunit "Request Page Parameters Helper";
RecRefRelated: RecordRef;
List: DotNet ArrayList;
begin
WorkflowTableRelation.SetRange("Table ID", RecRef.Number);
if WorkflowTableRelation.FindSet() then begin
List := List.ArrayList();
repeat
if not List.Contains(WorkflowTableRelation."Related Table ID") then begin
List.Add(WorkflowTableRelation."Related Table ID");
RecRefRelated.Open(WorkflowTableRelation."Related Table ID");
if RequestPageParametersHelper.ConvertParametersToFilters(RecRefRelated, TempBlob, TextEncoding::UTF8) then begin
ApplyRelationshipFilters(RecRef, RecRefRelated);
if RecRefRelated.IsEmpty() then
exit(false);
end;
RecRefRelated.Close();
end;
until WorkflowTableRelation.Next() = 0;
end;
exit(true);
end;
local procedure ApplyRelationshipFilters(RecRef: RecordRef; RecRefRelated: RecordRef)
var
WorkflowTableRelation: Record "Workflow - Table Relation";
FieldRefSrc: FieldRef;
FieldRefRelated: FieldRef;
begin
WorkflowTableRelation.SetRange("Table ID", RecRef.Number);
WorkflowTableRelation.SetRange("Related Table ID", RecRefRelated.Number);
if WorkflowTableRelation.FindSet() then
repeat
FieldRefRelated := RecRefRelated.Field(WorkflowTableRelation."Related Field ID");
FieldRefSrc := RecRef.Field(WorkflowTableRelation."Field ID");
FieldRefRelated.SetRange(FieldRefSrc.Value);
until WorkflowTableRelation.Next() = 0
end;
procedure FindResponse(var ResponseWorkflowStepInstance: Record "Workflow Step Instance"; PreviousWorkflowStepInstance: Record "Workflow Step Instance"): Boolean
begin
ResponseWorkflowStepInstance.SetRange("Workflow Code", PreviousWorkflowStepInstance."Workflow Code");
ResponseWorkflowStepInstance.SetRange(ID, PreviousWorkflowStepInstance.ID);
ResponseWorkflowStepInstance.SetRange(Type, ResponseWorkflowStepInstance.Type::Response);
ResponseWorkflowStepInstance.SetRange("Previous Workflow Step ID", PreviousWorkflowStepInstance."Workflow Step ID");
exit(ResponseWorkflowStepInstance.FindFirst());
end;
procedure FindEventWorkflowStepInstance(var WorkflowStepInstance: Record "Workflow Step Instance"; FunctionName: Code[128]; Variant: Variant; xVariant: Variant): Boolean
var
Workflow: Record Workflow;
WorkflowEvent: Record "Workflow Event";
TelemetryDimensions: Dictionary of [Text, Text];
begin
Workflow.SetRange(Enabled, true);
WorkflowStepInstance.ReadIsolation(WorkflowStepInstance.ReadIsolation::ReadUncommitted);
if WorkflowStepInstance.IsEmpty() and Workflow.IsEmpty() then begin
GetTelemetryDimensions(FunctionName, '', TelemetryDimensions);
Session.LogMessage('0000DZE', WorkflowNotFoundTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
exit(false);
end;
WorkflowEvent.Get(FunctionName);
exit(FindWorkflowStepInstance(Variant, xVariant, WorkflowStepInstance, WorkflowEvent."Function Name"));
end;
procedure HandleEvent(FunctionName: Code[128]; Variant: Variant)
begin
HandleEventWithxRec(FunctionName, Variant, Variant);
end;
procedure HandleEventWithxRec(FunctionName: Code[128]; Variant: Variant; xVariant: Variant)
var
ActionableWorkflowStepInstance: Record "Workflow Step Instance";
FeatureTelemetry: Codeunit "Feature Telemetry";
RecRef: RecordRef;
IsHandled: Boolean;
TelemetryDimensions: Dictionary of [Text, Text];
begin
GetTelemetryDimensions(FunctionName, '', TelemetryDimensions);
if GetExecutionContext() = ExecutionContext::Upgrade then begin
Session.LogMessage('0000GG5', WorkflowUpgradeTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
exit;
end;
Session.LogMessage('0000DYS', WorkflowEventStartTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
IsHandled := false;
OnBeforeHandleEventWithxRec(FunctionName, Variant, xVariant, IsHandled);
if IsHandled then
exit;
RecRef.GetTable(Variant);
if RecRef.IsTemporary() then
exit;
if FindEventWorkflowStepInstance(ActionableWorkflowStepInstance, FunctionName, Variant, xVariant) then begin
ExecuteResponses(Variant, xVariant, ActionableWorkflowStepInstance);
FeatureTelemetry.LogUsage('0000GDQ', 'Workflows', 'Event processed');
end;
Session.LogMessage('0000DYV', WorkflowEventEndTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
end;
procedure HandleEventOnKnownWorkflowInstance(FunctionName: Code[128]; Variant: Variant; WorkflowStepInstanceID: Guid)
begin
HandleEventWithxRecOnKnownWorkflowInstance(FunctionName, Variant, Variant, WorkflowStepInstanceID)
end;
procedure HandleEventWithxRecOnKnownWorkflowInstance(FunctionName: Code[128]; Variant: Variant; xVariant: Variant; WorkflowStepInstanceID: Guid)
var
WorkflowStepInstance: Record "Workflow Step Instance";
ActionableWorkflowStepInstance: Record "Workflow Step Instance";
WorkflowRule: Record "Workflow Rule";
RecRef: RecordRef;
xRecRef: RecordRef;
ActionableStepFound: Boolean;
TelemetryDimensions: Dictionary of [Text, Text];
begin
GetTelemetryDimensions(FunctionName, '', TelemetryDimensions);
Session.LogMessage('0000DYW', WorkflowEventStartTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
RecRef.GetTable(Variant);
xRecRef.GetTable(xVariant);
WorkflowStepInstance.SetRange(ID, WorkflowStepInstanceID);
WorkflowStepInstance.SetRange(Status, ActionableWorkflowStepInstance.Status::Active);
WorkflowStepInstance.SetRange("Function Name", FunctionName);
if WorkflowStepInstance.FindSet() then
repeat
WorkflowStepInstance.FindWorkflowRules(WorkflowRule);
if EvaluateCondition(RecRef, xRecRef, WorkflowStepInstance.Argument, WorkflowRule) then begin
ActionableWorkflowStepInstance := WorkflowStepInstance;
ActionableStepFound := true;
end;
until (WorkflowStepInstance.Next() = 0) or ActionableStepFound;
if ActionableStepFound then begin
GetTelemetryDimensions(FunctionName, ActionableWorkflowStepInstance.ToString(), TelemetryDimensions);
Session.LogMessage('0000DYX', WorkflowStepFoundTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
ExecuteResponses(Variant, xVariant, ActionableWorkflowStepInstance);
end else begin
GetTelemetryDimensions(FunctionName, '', TelemetryDimensions);
Session.LogMessage('0000DYY', WorkflowStepNotFoundTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
end;
GetTelemetryDimensions(FunctionName, '', TelemetryDimensions);
Session.LogMessage('0000DYZ', WorkflowEventEndTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryDimensions);
end;
procedure ExecuteResponses(Variant: Variant; xVariant: Variant; ActionableWorkflowStepInstance: Record "Workflow Step Instance")
var
ResponseWorkflowStepInstance: Record "Workflow Step Instance";
WorkflowResponseHandling: Codeunit "Workflow Response Handling";
RecRef: RecordRef;
Variant1: Variant;
begin
if not CanExecuteEvent(ActionableWorkflowStepInstance) then begin
StoreEventForLaterProcessing(Variant, xVariant, ActionableWorkflowStepInstance);
if ActionableWorkflowStepInstance.Status <> ActionableWorkflowStepInstance.Status::Completed then begin
ActionableWorkflowStepInstance.Status := ActionableWorkflowStepInstance.Status::Processing;
ActionableWorkflowStepInstance.Modify(true);
end;
exit;
end;
RecRef.GetTable(Variant);
UpdateStatus(ActionableWorkflowStepInstance, ActionableWorkflowStepInstance.Status::Completed,
ActionableWorkflowStepInstance.Status::Active, ActionableWorkflowStepInstance.Status::Ignored);
UpdateStepAndRelatedTableData(ActionableWorkflowStepInstance, RecRef);
ChangeStatusForResponsesAndEvents(ActionableWorkflowStepInstance);
Variant1 := Variant;
while FindResponse(ResponseWorkflowStepInstance, ActionableWorkflowStepInstance) do begin
UpdateStepAndRelatedTableData(ResponseWorkflowStepInstance, RecRef);
WorkflowResponseHandling.ExecuteResponse(Variant1, ResponseWorkflowStepInstance, xVariant);
UpdateStatusForResponse(ResponseWorkflowStepInstance);
ActionableWorkflowStepInstance := ResponseWorkflowStepInstance;
Clear(ResponseWorkflowStepInstance);
end;
ExecuteQueuedEvents();
if IsWorkflowCompleted(ActionableWorkflowStepInstance) then
ArchiveWorkflowInstance(ActionableWorkflowStepInstance);
end;
internal procedure GetTelemetryDimensions(FunctionName: Text; SerializedWorkflowStepInstance: Text; var Dimensions: Dictionary of [Text, Text])
begin
Clear(Dimensions);
Dimensions.Add('Category', WorkflowTelemetryCategoryTxt);
if FunctionName <> '' then
Dimensions.Add('FunctionName', FunctionName);
if SerializedWorkflowStepInstance <> '' then
Dimensions.Add('WorkflowStepInstance', SerializedWorkflowStepInstance);
end;
local procedure CanExecuteEvent(WorkflowStepInstance: Record "Workflow Step Instance"): Boolean
var
ProcessingWorkflowStepInstance: Record "Workflow Step Instance";
begin
ProcessingWorkflowStepInstance.SetRange(ID, WorkflowStepInstance.ID);
ProcessingWorkflowStepInstance.SetRange(Status, WorkflowStepInstance.Status::Processing);
ProcessingWorkflowStepInstance.SetFilter("Workflow Step ID", '<>%1', WorkflowStepInstance."Workflow Step ID");
exit(ProcessingWorkflowStepInstance.IsEmpty);
end;
local procedure StoreEventForLaterProcessing(Variant: Variant; xVariant: Variant; WorkflowStepInstance: Record "Workflow Step Instance")
var
WorkflowEventQueue: Record "Workflow Event Queue";
RecRef: RecordRef;
begin
RecRef.GetTable(Variant);
WorkflowEventQueue.Init();
WorkflowEventQueue."Session ID" := SessionId();
WorkflowEventQueue."Step Record ID" := WorkflowStepInstance.RecordId;
WorkflowEventQueue."Record ID" := RecRef.RecordId;
WorkflowEventQueue."Record Index" := WorkflowRecordManagement.BackupRecord(Variant);
WorkflowEventQueue."xRecord Index" := WorkflowRecordManagement.BackupRecord(xVariant);
WorkflowEventQueue.Insert(true);
end;
local procedure ExecuteQueuedEvents()
var
WorkflowEventQueue: Record "Workflow Event Queue";
WorkflowStepInstance: Record "Workflow Step Instance";
WorkflowRule: Record "Workflow Rule";
RecRef: RecordRef;
xRecRef: RecordRef;
Variant: Variant;
xVariant: Variant;
begin
WorkflowEventQueue.SetRange("Session ID", SessionId());
if WorkflowEventQueue.FindSet() then
repeat
WorkflowStepInstance.Get(WorkflowEventQueue."Step Record ID");
if WorkflowStepInstance.Status = WorkflowStepInstance.Status::Processing then begin
WorkflowRecordManagement.RestoreRecord(WorkflowEventQueue."Record Index", Variant);
WorkflowRecordManagement.RestoreRecord(WorkflowEventQueue."xRecord Index", xVariant);
if Variant.IsRecord and xVariant.IsRecord then begin
RecRef.GetTable(Variant);
xRecRef.GetTable(xVariant);
WorkflowStepInstance.FindWorkflowRules(WorkflowRule);
if EvaluateCondition(RecRef, xRecRef, WorkflowStepInstance.Argument, WorkflowRule) then begin
ExecuteResponses(RecRef, xRecRef, WorkflowStepInstance);
WorkflowEventQueue.Delete();
end;
end;
end;
until WorkflowEventQueue.Next() = 0;
end;
procedure ChangeStatusForResponsesAndEvents(WorkflowStepInstance: Record "Workflow Step Instance")
var
MarkWorkflowStepInstance: Record "Workflow Step Instance";
WorkflowEventQueue: Record "Workflow Event Queue";
begin
MarkWorkflowStepInstance.SetRange(ID, WorkflowStepInstance.ID);
MarkWorkflowStepInstance.SetRange("Workflow Code", WorkflowStepInstance."Workflow Code");
MarkWorkflowStepInstance.SetRange("Previous Workflow Step ID", WorkflowStepInstance."Workflow Step ID");
if MarkWorkflowStepInstance.FindSet() then
repeat
if MarkWorkflowStepInstance.Type = MarkWorkflowStepInstance.Type::Response then begin
MarkWorkflowStepInstance.Status := MarkWorkflowStepInstance.Status::Processing;
MarkWorkflowStepInstance.Modify(true);
ChangeStatusForResponsesAndEvents(MarkWorkflowStepInstance);
end else begin
// check if queued event
WorkflowEventQueue.SetRange("Session ID", SessionId());
WorkflowEventQueue.SetRange("Step Record ID", MarkWorkflowStepInstance."Record ID");
if WorkflowEventQueue.IsEmpty() then begin
MarkWorkflowStepInstance.Status := MarkWorkflowStepInstance.Status::Active;
MarkWorkflowStepInstance.Modify(true);
end;
end;
until MarkWorkflowStepInstance.Next() = 0;
end;
local procedure UpdateStepAndRelatedTableData(var WorkflowStepInstance: Record "Workflow Step Instance"; RecRef: RecordRef)
begin
WorkflowStepInstance."Record ID" := RecRef.RecordId;
WorkflowStepInstance.Modify(true);
UpdateRelatedTableValues(RecRef);
if not WorkflowStepInstance.TableRelationValuesExist(RecRef) then
WorkflowStepInstance.SetTableRelationValues(RecRef);
end;
local procedure UpdateStatusForResponse(var WorkflowStepInstance: Record "Workflow Step Instance")
var
NextWorkflowStepInstance: Record "Workflow Step Instance";
begin
if WorkflowStepInstance."Next Workflow Step ID" <> 0 then begin
NextWorkflowStepInstance.Get(WorkflowStepInstance.ID,
WorkflowStepInstance."Workflow Code", WorkflowStepInstance."Next Workflow Step ID");
case NextWorkflowStepInstance.Type of
NextWorkflowStepInstance.Type::"Event":
UpdateStatus(NextWorkflowStepInstance, NextWorkflowStepInstance.Status::Active,
NextWorkflowStepInstance.Status::Inactive, NextWorkflowStepInstance.Status::Ignored);
NextWorkflowStepInstance.Type::Response:
UpdateStatus(NextWorkflowStepInstance, NextWorkflowStepInstance.Status::Completed,
NextWorkflowStepInstance.Status::Active, NextWorkflowStepInstance.Status::Ignored);
end;
end;
UpdateStatus(WorkflowStepInstance, WorkflowStepInstance.Status::Completed,
WorkflowStepInstance.Status::Active, WorkflowStepInstance.Status::Ignored);
end;
procedure EnabledWorkflowExist(TableNo: Integer; EventFilter: Text): Boolean
var
WorkflowDefinition: Query "Workflow Definition";
begin
WorkflowDefinition.SetRange(Table_ID, TableNo);
WorkflowDefinition.SetRange(Entry_Point, true);
WorkflowDefinition.SetRange(Enabled, true);
WorkflowDefinition.SetRange(Template, false);
WorkflowDefinition.SetRange(Type, WorkflowDefinition.Type::"Event");
WorkflowDefinition.SetFilter(Function_Name, EventFilter);
WorkflowDefinition.Open();
exit(WorkflowDefinition.Read());
end;
procedure NavigateToWorkflows(TableNo: Integer; EventFilter: Text)
var
Workflow: Record Workflow;
TempWorkflowBuffer: Record "Workflow Buffer" temporary;
Workflows: Page Workflows;
WorkflowDefinition: Query "Workflow Definition";
"Count": Integer;
WorkflowFilter: Text;
CategoryFilter: Text;
begin
WorkflowDefinition.SetRange(Table_ID, TableNo);
WorkflowDefinition.SetRange(Entry_Point, true);
WorkflowDefinition.SetRange(Enabled, true);
WorkflowDefinition.SetRange(Template, false);
WorkflowDefinition.SetRange(Type, WorkflowDefinition.Type::"Event");
WorkflowDefinition.SetFilter(Function_Name, EventFilter);
WorkflowDefinition.Open();
while WorkflowDefinition.Read() do begin
Workflow.Get(WorkflowDefinition.Code);
BuildFilter(WorkflowFilter, Workflow.Code);
BuildFilter(CategoryFilter, Workflow.Category);
Count += 1;
end;
if Count = 1 then begin
Workflow.Get(WorkflowFilter);
PAGE.RunModal(PAGE::Workflow, Workflow);
end else begin
TempWorkflowBuffer.InitBufferForWorkflows(TempWorkflowBuffer);
TempWorkflowBuffer.SetFilter("Workflow Code", WorkflowFilter);
TempWorkflowBuffer.SetFilter("Category Code", CategoryFilter);
Workflows.SetWorkflowBufferRec(TempWorkflowBuffer);
Workflows.RunModal();
end;
end;
local procedure BuildFilter(var InitialFilter: Text; NewValue: Text)
begin
if StrPos(InitialFilter, NewValue) = 0 then begin
if StrLen(InitialFilter) > 0 then
InitialFilter += '|';
InitialFilter += NewValue;
end;
end;
local procedure CreateWorkflowStepArgumentArchive(ToArchiveWorkflowStepArgument: Record "Workflow Step Argument"): Guid
var
WorkflowStepArgumentArchive: Record "Workflow Step Argument Archive";
begin
WorkflowStepArgumentArchive.Init();
WorkflowStepArgumentArchive.TransferFields(ToArchiveWorkflowStepArgument);
WorkflowStepArgumentArchive."Original Record ID" := ToArchiveWorkflowStepArgument.RecordId;
WorkflowStepArgumentArchive.Insert(true);
exit(WorkflowStepArgumentArchive.ID);
end;
procedure BuildConditionDisplay(WorkflowStep: Record "Workflow Step") Condition: Text[100]
var
RuleAsText: Text;
begin
Condition := CopyStr(WorkflowStep.GetConditionAsDisplayText(), 1, MaxStrLen(Condition));
RuleAsText := WorkflowStep.GetRuleAsDisplayText();
if RuleAsText <> '' then
if Condition = '' then
Condition := CopyStr(RuleAsText, 1, MaxStrLen(Condition))
else
Condition := CopyStr(StrSubstNo(CombinedConditionTxt, Condition, RuleAsText), 1, MaxStrLen(Condition));
if Condition = '' then
Condition := AlwaysTxt;
end;
procedure ClearSupportedCombinations(FunctionName: Code[128]; WFEventResponseCombinationType: Option "Event",Response)
var
WFEventResponseCombination: Record "WF Event/Response Combination";
begin
WFEventResponseCombination.SetRange(Type, WFEventResponseCombinationType);
WFEventResponseCombination.SetRange("Function Name", FunctionName);
if not WFEventResponseCombination.IsEmpty() then
WFEventResponseCombination.DeleteAll(true);
WFEventResponseCombination.Reset();
WFEventResponseCombination.SetRange("Predecessor Type", WFEventResponseCombinationType);
WFEventResponseCombination.SetRange("Predecessor Function Name", FunctionName);
if not WFEventResponseCombination.IsEmpty() then
WFEventResponseCombination.DeleteAll(true);
end;
procedure GetWebhookClientLink(ClientId: Guid; ClientType: Text): Text
var
FlowServiceMgt: Codeunit "Flow Service Management";
begin
if not IsNullGuid(ClientId) then
case ClientType of
'Flow':
exit(FlowServiceMgt.GetFlowDetailsUrl(ClientId));
end;
exit('');
end;
procedure AnyWorkflowExists(): Boolean
var
Workflow: Record Workflow;
ApprovalEntry: Record "Approval Entry";
begin
if Workflow.IsEmpty() then
exit(false);
if ApprovalEntry.IsEmpty() then
exit(false);
exit(true);
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeHandleEventWithxRec(FunctionName: Code[128]; Variant: Variant; xVariant: Variant; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterEvaluateCondition(RecRef: RecordRef; xRecRef: RecordRef; ArgumentID: Guid; var WorkflowRule: Record "Workflow Rule"; var Result: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnFindWorkflowStepInstanceWithOptionalWorkflowStartOnAfterSetWorkflowStepInstanceLoopFi1tersWhenNotStartWorkFlow(RecordRef: RecordRef; var WorkflowStepInstanceLoop: Record "Workflow Step Instance"; FunctionName: Code[128]; StartWorkflow: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnFindWorkflowStepInstanceWithOptionalWorkflowStartOnAfterSetWorkflowStepInstanceLoopFi1ters(RecordRef: RecordRef; var WorkflowStepInstanceLoop: Record "Workflow Step Instance"; FunctionName: Code[128]; StartWorkflow: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterSetFilterWorkflowStep(var WorkflowStep: Record "Workflow Step")
begin
end;
}