Init
Überführung von github
This commit is contained in:
parent
4f4eabb53c
commit
34ab7a7176
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Graphical_Application_Designer_Project>
|
||||
<Version>2.1</Version>
|
||||
<Files>
|
||||
<File path="Main.flow" type="callflow" />
|
||||
</Files>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Variable>
|
||||
<Name>ExtensionNr</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>AuswahlOpt</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>Zielstatus</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
</ArrayOfVariable>
|
||||
</Variables>
|
||||
<DebugBuildSuccessful>False</DebugBuildSuccessful>
|
||||
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
|
||||
<DebugBuildNumber>0</DebugBuildNumber>
|
||||
<ReleaseBuildNumber>51</ReleaseBuildNumber>
|
||||
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
|
||||
<DoNotAskForExtension>False</DoNotAskForExtension>
|
||||
<Extension>*39</Extension>
|
||||
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
|
||||
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
|
||||
<AmazonClientID>
|
||||
</AmazonClientID>
|
||||
<AmazonClientSecret>
|
||||
</AmazonClientSecret>
|
||||
<AmazonRegion>us-east-2</AmazonRegion>
|
||||
<AmazonLexicons>
|
||||
</AmazonLexicons>
|
||||
<GoogleCloudServiceAccountKeyFileName>
|
||||
</GoogleCloudServiceAccountKeyFileName>
|
||||
<GoogleCloudServiceAccountKeyJSON>
|
||||
</GoogleCloudServiceAccountKeyJSON>
|
||||
</Graphical_Application_Designer_Project>
|
||||
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/FehlerBeiDerEingabe-vicky.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/FehlerBeiDerEingabe-vicky.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/Status0.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/Status0.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/Status1.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/Status1.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/Status3.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/Status3.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/Status4.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/Status4.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/Sttatus2.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/Sttatus2.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/leer50ms.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/leer50ms.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/welcheNSt.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/welcheNSt.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Audio/welcher_Status.wav
Normal file
BIN
39_Profilstatus_mitExtension/Audio/welcher_Status.wav
Normal file
Binary file not shown.
51
39_Profilstatus_mitExtension/Main.flow
Normal file
51
39_Profilstatus_mitExtension/Main.flow
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<File>
|
||||
<Version>2.1</Version>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
|
||||
</Variables>
|
||||
<Flows>
|
||||
<MainFlow>
|
||||
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
|
||||
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>FehlerBeiDerEingabe-vicky.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>leer50ms.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>welcheNSt.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="InputExtension" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav</AudioFileName></Prompt></ArrayOfPrompt>">
|
||||
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch1" />
|
||||
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch2" />
|
||||
</ns0:UserInputComponent>
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="InputExtension.Buffer" x:Name="variableAssignmentExtensionNr" />
|
||||
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>FehlerBeiDerEingabe-vicky.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_7="False" IsValidDigit_4="True" IsValidDigit_5="False" SubsequentPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>leer50ms.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="False" MinDigits="1" InitialPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>welcher_Status.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="InputStatus" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="1" Tag="" IsValidDigit_8="False" IsValidDigit_9="False" IsValidDigit_Star="False" TimeoutPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav</AudioFileName></Prompt></ArrayOfPrompt>">
|
||||
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch3" />
|
||||
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch4" />
|
||||
</ns0:UserInputComponent>
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.Zielstatus" Tag="" DebugModeActive="False" Expression="InputStatus.Buffer" x:Name="variableAssignmentZielStatus" />
|
||||
<ns0:ConditionalComponent Tag="" DebugModeActive="False" x:Name="CreateCondition1">
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,0)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch1">
|
||||
<ns0:TcxSetExtensionStatusComponent Status="Available" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Available" />
|
||||
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>Status0.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="PromptPlayback1" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,1)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch2">
|
||||
<ns0:TcxSetExtensionStatusComponent Status="Away" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Away" />
|
||||
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>Status1.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="promptPlaybackComponent1" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,2)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch3">
|
||||
<ns0:TcxSetExtensionStatusComponent Status="DoNotDisturb" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_DND" />
|
||||
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>Sttatus2.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="promptPlaybackComponent2" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,3)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch4">
|
||||
<ns0:TcxSetExtensionStatusComponent Status="Lunch" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Custom1" />
|
||||
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>Status3.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="promptPlaybackComponent3" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,4)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch5">
|
||||
<ns0:TcxSetExtensionStatusComponent Status="BusinessTrip" Description="Custom State 2" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Custom2" />
|
||||
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>Status4.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="promptPlaybackComponent4" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
</ns0:ConditionalComponent>
|
||||
</ns0:MainFlow>
|
||||
</MainFlow>
|
||||
<ErrorHandlerFlow>
|
||||
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</ErrorHandlerFlow>
|
||||
<DisconnectHandlerFlow>
|
||||
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</DisconnectHandlerFlow>
|
||||
</Flows>
|
||||
</File>
|
||||
Binary file not shown.
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status0.wav
Normal file
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status0.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status1.wav
Normal file
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status1.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status3.wav
Normal file
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status3.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status4.wav
Normal file
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Status4.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Sttatus2.wav
Normal file
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/Sttatus2.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/leer50ms.wav
Normal file
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/leer50ms.wav
Normal file
Binary file not shown.
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/welcheNSt.wav
Normal file
BIN
39_Profilstatus_mitExtension/Output/Release/Audio/welcheNSt.wav
Normal file
Binary file not shown.
Binary file not shown.
570
39_Profilstatus_mitExtension/Output/Release/Script/Main.cs
Normal file
570
39_Profilstatus_mitExtension/Output/Release/Script/Main.cs
Normal file
@ -0,0 +1,570 @@
|
||||
using CallFlow.CFD;
|
||||
using CallFlow;
|
||||
using MimeKit;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using TCX.Configuration;
|
||||
|
||||
namespace _39_Profilstatus_mitExtension
|
||||
{
|
||||
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
|
||||
{
|
||||
private bool executionStarted;
|
||||
private bool executionFinished;
|
||||
private bool disconnectFlowPending;
|
||||
|
||||
private BufferBlock<AbsEvent> eventBuffer;
|
||||
|
||||
private int currentComponentIndex;
|
||||
private List<AbsComponent> mainFlowComponentList;
|
||||
private List<AbsComponent> disconnectFlowComponentList;
|
||||
private List<AbsComponent> errorFlowComponentList;
|
||||
private List<AbsComponent> currentFlowComponentList;
|
||||
|
||||
private LogFormatter logFormatter;
|
||||
private TimerManager timerManager;
|
||||
private Dictionary<string, Variable> variableMap;
|
||||
private TempWavFileManager tempWavFileManager;
|
||||
private PromptQueue promptQueue;
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
|
||||
private CfdAppScope scope;
|
||||
|
||||
private void DisconnectCallAndExitCallflow()
|
||||
{
|
||||
if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
logFormatter.Trace("Callflow finished...");
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Callflow finished, disconnecting call...");
|
||||
MyCall.Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteErrorFlow()
|
||||
{
|
||||
if (currentFlowComponentList == errorFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during error handler flow, exiting callflow...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
else if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
|
||||
executionFinished = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFlowComponentList = errorFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
if (errorFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing error handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Error handler flow is empty...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteDisconnectFlow()
|
||||
{
|
||||
currentFlowComponentList = disconnectFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
disconnectFlowPending = false;
|
||||
if (disconnectFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing disconnect handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Disconnect handler flow is empty...");
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EventResults CheckEventResult(EventResults eventResult)
|
||||
{
|
||||
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
|
||||
{
|
||||
DisconnectCallAndExitCallflow();
|
||||
return EventResults.Exit;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
DisconnectCallAndExitCallflow();
|
||||
|
||||
return eventResult;
|
||||
}
|
||||
|
||||
private void InitializeVariables(string callID)
|
||||
{
|
||||
// Call variables
|
||||
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
|
||||
variableMap["session.callid"] = new Variable(callID);
|
||||
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
|
||||
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
|
||||
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
|
||||
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
|
||||
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
|
||||
|
||||
// Standard variables
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
// User variables
|
||||
variableMap["project$.ExtensionNr"] = new Variable("");
|
||||
variableMap["project$.AuswahlOpt"] = new Variable("");
|
||||
variableMap["project$.Zielstatus"] = new Variable("");
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
|
||||
{
|
||||
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
|
||||
|
||||
{
|
||||
UserInputComponent InputExtension = scope.CreateComponent<UserInputComponent>("InputExtension");
|
||||
InputExtension.AllowDtmfInput = true;
|
||||
InputExtension.MaxRetryCount = 2;
|
||||
InputExtension.FirstDigitTimeout = 5000;
|
||||
InputExtension.InterDigitTimeout = 3000;
|
||||
InputExtension.FinalDigitTimeout = 2000;
|
||||
InputExtension.MinDigits = 1;
|
||||
InputExtension.MaxDigits = 14;
|
||||
InputExtension.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
|
||||
InputExtension.StopDigitList.AddRange(new char[] { '#' });
|
||||
InputExtension.InitialPrompts.Add(new AudioFilePrompt(() => { return "welcheNSt.wav"; }));
|
||||
InputExtension.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
|
||||
InputExtension.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
|
||||
InputExtension.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
|
||||
mainFlowComponentList.Add(InputExtension);
|
||||
ConditionalComponent InputExtension_Conditional = scope.CreateComponent<ConditionalComponent>("InputExtension_Conditional");
|
||||
mainFlowComponentList.Add(InputExtension_Conditional);
|
||||
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.ValidDigits; });
|
||||
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_ValidInput"));
|
||||
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.InvalidDigits || InputExtension.Result == UserInputComponent.UserInputResults.Timeout; });
|
||||
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_InvalidInput"));
|
||||
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
|
||||
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
|
||||
variableAssignmentExtensionNr.VariableValueHandler = () => { return InputExtension.Buffer; };
|
||||
mainFlowComponentList.Add(variableAssignmentExtensionNr);
|
||||
UserInputComponent InputStatus = scope.CreateComponent<UserInputComponent>("InputStatus");
|
||||
InputStatus.AllowDtmfInput = true;
|
||||
InputStatus.MaxRetryCount = 2;
|
||||
InputStatus.FirstDigitTimeout = 5000;
|
||||
InputStatus.InterDigitTimeout = 3000;
|
||||
InputStatus.FinalDigitTimeout = 2000;
|
||||
InputStatus.MinDigits = 1;
|
||||
InputStatus.MaxDigits = 1;
|
||||
InputStatus.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4' });
|
||||
InputStatus.StopDigitList.AddRange(new char[] { '#' });
|
||||
InputStatus.InitialPrompts.Add(new AudioFilePrompt(() => { return "welcher_Status.wav"; }));
|
||||
InputStatus.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
|
||||
InputStatus.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
|
||||
InputStatus.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
|
||||
mainFlowComponentList.Add(InputStatus);
|
||||
ConditionalComponent InputStatus_Conditional = scope.CreateComponent<ConditionalComponent>("InputStatus_Conditional");
|
||||
mainFlowComponentList.Add(InputStatus_Conditional);
|
||||
InputStatus_Conditional.ConditionList.Add(() => { return InputStatus.Result == UserInputComponent.UserInputResults.ValidDigits; });
|
||||
InputStatus_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputStatus_Conditional_ValidInput"));
|
||||
InputStatus_Conditional.ConditionList.Add(() => { return InputStatus.Result == UserInputComponent.UserInputResults.InvalidDigits || InputStatus.Result == UserInputComponent.UserInputResults.Timeout; });
|
||||
InputStatus_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputStatus_Conditional_InvalidInput"));
|
||||
VariableAssignmentComponent variableAssignmentZielStatus = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielStatus");
|
||||
variableAssignmentZielStatus.VariableName = "project$.Zielstatus";
|
||||
variableAssignmentZielStatus.VariableValueHandler = () => { return InputStatus.Buffer; };
|
||||
mainFlowComponentList.Add(variableAssignmentZielStatus);
|
||||
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
|
||||
mainFlowComponentList.Add(CreateCondition1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,0)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
|
||||
TcxSetExtensionStatusComponent SetExtensionStatus_Available = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Available");
|
||||
SetExtensionStatus_Available.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
|
||||
SetExtensionStatus_Available.ProfileNameHandler = () => { return "Available"; };
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(SetExtensionStatus_Available);
|
||||
PromptPlaybackComponent PromptPlayback1 = scope.CreateComponent<PromptPlaybackComponent>("PromptPlayback1");
|
||||
PromptPlayback1.AllowDtmfInput = true;
|
||||
PromptPlayback1.Prompts.Add(new AudioFilePrompt(() => { return "Status0.wav"; }));
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(PromptPlayback1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,1)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
|
||||
TcxSetExtensionStatusComponent SetExtensionStatus_Away = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Away");
|
||||
SetExtensionStatus_Away.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
|
||||
SetExtensionStatus_Away.ProfileNameHandler = () => { return "Away"; };
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtensionStatus_Away);
|
||||
PromptPlaybackComponent promptPlaybackComponent1 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent1");
|
||||
promptPlaybackComponent1.AllowDtmfInput = true;
|
||||
promptPlaybackComponent1.Prompts.Add(new AudioFilePrompt(() => { return "Status1.wav"; }));
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(promptPlaybackComponent1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,2)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch3"));
|
||||
TcxSetExtensionStatusComponent SetExtensionStatus_DND = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_DND");
|
||||
SetExtensionStatus_DND.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
|
||||
SetExtensionStatus_DND.ProfileNameHandler = () => { return "Out of office"; };
|
||||
CreateCondition1.ContainerList[2].ComponentList.Add(SetExtensionStatus_DND);
|
||||
PromptPlaybackComponent promptPlaybackComponent2 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent2");
|
||||
promptPlaybackComponent2.AllowDtmfInput = true;
|
||||
promptPlaybackComponent2.Prompts.Add(new AudioFilePrompt(() => { return "Sttatus2.wav"; }));
|
||||
CreateCondition1.ContainerList[2].ComponentList.Add(promptPlaybackComponent2);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,3)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch4"));
|
||||
TcxSetExtensionStatusComponent SetExtensionStatus_Custom1 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Custom1");
|
||||
SetExtensionStatus_Custom1.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
|
||||
SetExtensionStatus_Custom1.ProfileNameHandler = () => { return "Custom 1"; };
|
||||
CreateCondition1.ContainerList[3].ComponentList.Add(SetExtensionStatus_Custom1);
|
||||
PromptPlaybackComponent promptPlaybackComponent3 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent3");
|
||||
promptPlaybackComponent3.AllowDtmfInput = true;
|
||||
promptPlaybackComponent3.Prompts.Add(new AudioFilePrompt(() => { return "Status3.wav"; }));
|
||||
CreateCondition1.ContainerList[3].ComponentList.Add(promptPlaybackComponent3);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,4)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch5"));
|
||||
TcxSetExtensionStatusComponent SetExtensionStatus_Custom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Custom2");
|
||||
SetExtensionStatus_Custom2.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
|
||||
SetExtensionStatus_Custom2.ProfileNameHandler = () => { return "Custom 2"; };
|
||||
CreateCondition1.ContainerList[4].ComponentList.Add(SetExtensionStatus_Custom2);
|
||||
PromptPlaybackComponent promptPlaybackComponent4 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent4");
|
||||
promptPlaybackComponent4.AllowDtmfInput = true;
|
||||
promptPlaybackComponent4.Prompts.Add(new AudioFilePrompt(() => { return "Status4.wav"; }));
|
||||
CreateCondition1.ContainerList[4].ComponentList.Add(promptPlaybackComponent4);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
|
||||
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
|
||||
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
|
||||
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
|
||||
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
|
||||
}
|
||||
|
||||
public Main()
|
||||
{
|
||||
this.executionStarted = false;
|
||||
this.executionFinished = false;
|
||||
this.disconnectFlowPending = false;
|
||||
|
||||
this.eventBuffer = new BufferBlock<AbsEvent>();
|
||||
|
||||
this.currentComponentIndex = 0;
|
||||
this.mainFlowComponentList = new List<AbsComponent>();
|
||||
this.disconnectFlowComponentList = new List<AbsComponent>();
|
||||
this.errorFlowComponentList = new List<AbsComponent>();
|
||||
this.currentFlowComponentList = mainFlowComponentList;
|
||||
|
||||
this.timerManager = new TimerManager();
|
||||
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
|
||||
this.variableMap = new Dictionary<string, Variable>();
|
||||
|
||||
AbsTextToSpeechEngine textToSpeechEngine = null;
|
||||
AbsSpeechToTextEngine speechToTextEngine = null;
|
||||
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
string callID = MyCall?.Caller["chid"] ?? "Unknown";
|
||||
string logHeader = $"_39_Profilstatus_mitExtension - CallID {callID}";
|
||||
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
|
||||
this.promptQueue = new PromptQueue(this, MyCall, "_39_Profilstatus_mitExtension", logHeader);
|
||||
this.tempWavFileManager = new TempWavFileManager(logFormatter);
|
||||
this.timerManager.CallStarted();
|
||||
this.officeHoursManager = new OfficeHoursManager(MyCall);
|
||||
|
||||
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
|
||||
|
||||
if (MyCall.Status == ConnectionStatus.Ringing)
|
||||
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
|
||||
else
|
||||
StartInternal(logHeader, callID);
|
||||
}
|
||||
|
||||
private void StartInternal(string logHeader, string callID)
|
||||
{
|
||||
logFormatter.Trace("SetBackgroundAudio to false");
|
||||
MyCall.SetBackgroundAudio(false, new string[] { });
|
||||
|
||||
logFormatter.Trace("Initialize components");
|
||||
InitializeComponents(this, MyCall, logHeader);
|
||||
logFormatter.Trace("Initialize variables");
|
||||
InitializeVariables(callID);
|
||||
|
||||
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
|
||||
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
|
||||
|
||||
logFormatter.Trace("Start executing main flow...");
|
||||
eventBuffer.Post(new StartEvent());
|
||||
Task.Run(() => EventProcessingLoop());
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void PostStartEvent()
|
||||
{
|
||||
eventBuffer.Post(new StartEvent());
|
||||
}
|
||||
|
||||
public void PostDTMFReceivedEvent(char digit)
|
||||
{
|
||||
eventBuffer.Post(new DTMFReceivedEvent(digit));
|
||||
}
|
||||
|
||||
public void PostPromptPlayedEvent()
|
||||
{
|
||||
eventBuffer.Post(new PromptPlayedEvent());
|
||||
}
|
||||
|
||||
public void PostTransferFailedEvent()
|
||||
{
|
||||
eventBuffer.Post(new TransferFailedEvent());
|
||||
}
|
||||
|
||||
public void PostMakeCallResultEvent(bool result)
|
||||
{
|
||||
eventBuffer.Post(new MakeCallResultEvent(result));
|
||||
}
|
||||
|
||||
public void PostCallTerminatedEvent()
|
||||
{
|
||||
eventBuffer.Post(new CallTerminatedEvent());
|
||||
}
|
||||
|
||||
public void PostTimeoutEvent(object state)
|
||||
{
|
||||
eventBuffer.Post(new TimeoutEvent(state));
|
||||
}
|
||||
|
||||
private async Task EventProcessingLoop()
|
||||
{
|
||||
executionStarted = true;
|
||||
while (!executionFinished)
|
||||
{
|
||||
AbsEvent evt = await eventBuffer.ReceiveAsync();
|
||||
await evt?.ProcessEvent(this);
|
||||
}
|
||||
|
||||
if (scope != null) scope.Dispose();
|
||||
}
|
||||
|
||||
public async Task ProcessStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
EventResults eventResult;
|
||||
do
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
|
||||
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
}
|
||||
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
|
||||
|
||||
if (eventResult == EventResults.Exit) executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessDTMFReceived(char digit)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessPromptPlayed()
|
||||
{
|
||||
try
|
||||
{
|
||||
promptQueue.NotifyPlayFinished();
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTransferFailed()
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessMakeCallResult(bool result)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessCallTerminated()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (executionStarted)
|
||||
{
|
||||
// First notify the call termination to the current component
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
|
||||
|
||||
// Don't wrap around CheckEventResult, because the call has been already disconnected,
|
||||
// and the following action to execute depends on the returned value.
|
||||
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
// Next, if the current component has completed its job, execute the disconnect flow
|
||||
await ExecuteDisconnectFlow();
|
||||
}
|
||||
else if (eventResult == EventResults.Wait)
|
||||
{
|
||||
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
|
||||
// the disconnect handler flow of the callflow next...
|
||||
disconnectFlowPending = true;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Finally, delete temporary files
|
||||
tempWavFileManager.DeleteFilesAndFolders();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTimeout(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
28
39_Profilstatus_mitExtension/README.md
Normal file
28
39_Profilstatus_mitExtension/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Profilstatus wechseln
|
||||
### 39_Profilstatus_mitExtension ###
|
||||
Das Skript *39 ermöglicht das wechseln des Profilstatus einer beliebigen Nebenstelle
|
||||
Mittels DTMF wird zuerst die Nebenstelle abgefragt und dann die ID des Status (0-4, wobei 0 = Verfügbar).
|
||||
Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
|
||||
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
|
||||
|
||||
Nutzungsbeispiel: *39 anrufen, "40#2#" eingeben um für die Nebenstelle 40 auf "Bitte nicht stören umzuschalten.
|
||||
|
||||
|
||||
|
||||
## Funktion auf BLF Tasten von Tischtelefonen legen ##
|
||||
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
|
||||
|
||||
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
|
||||
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72;dtmf=10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Graphical_Application_Designer_Project>
|
||||
<Version>2.1</Version>
|
||||
<Files>
|
||||
<File path="Main.flow" type="callflow" />
|
||||
<File path="setStateAndFWDestination.comp" type="component" />
|
||||
</Files>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Variable>
|
||||
<Name>ExtensionNr</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>AuswahlOpt</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>ZielNr</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
</ArrayOfVariable>
|
||||
</Variables>
|
||||
<DebugBuildSuccessful>False</DebugBuildSuccessful>
|
||||
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
|
||||
<DebugBuildNumber>0</DebugBuildNumber>
|
||||
<ReleaseBuildNumber>56</ReleaseBuildNumber>
|
||||
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
|
||||
<DoNotAskForExtension>False</DoNotAskForExtension>
|
||||
<Extension>*721</Extension>
|
||||
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
|
||||
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
|
||||
<AmazonClientID>
|
||||
</AmazonClientID>
|
||||
<AmazonClientSecret>
|
||||
</AmazonClientSecret>
|
||||
<AmazonRegion>us-east-2</AmazonRegion>
|
||||
<AmazonLexicons>
|
||||
</AmazonLexicons>
|
||||
<GoogleCloudServiceAccountKeyFileName>
|
||||
</GoogleCloudServiceAccountKeyFileName>
|
||||
<GoogleCloudServiceAccountKeyJSON>
|
||||
</GoogleCloudServiceAccountKeyJSON>
|
||||
</Graphical_Application_Designer_Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
721_dynWeiterleitung_mitExtension_an/Audio/leer50ms.wav
Normal file
BIN
721_dynWeiterleitung_mitExtension_an/Audio/leer50ms.wav
Normal file
Binary file not shown.
31
721_dynWeiterleitung_mitExtension_an/Main.flow
Normal file
31
721_dynWeiterleitung_mitExtension_an/Main.flow
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<File>
|
||||
<Version>2.1</Version>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
|
||||
</Variables>
|
||||
<Flows>
|
||||
<MainFlow>
|
||||
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
|
||||
<ns0:LoggerComponent Tag="" DebugModeActive="False" Text=""huhu"" Level="Info" x:Name="Logger1" />
|
||||
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>FehlerBeiDerEingabe-vicky.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>leer50ms.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>WecheNebenstelleSollWeitergeitetwerden.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="InputExtension" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav</AudioFileName></Prompt></ArrayOfPrompt>">
|
||||
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch3" />
|
||||
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch4" />
|
||||
</ns0:UserInputComponent>
|
||||
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>FehlerBeiDerEingabe-vicky.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>leer50ms.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="InputDestination" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav</AudioFileName></Prompt></ArrayOfPrompt>">
|
||||
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch1" />
|
||||
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch2" />
|
||||
</ns0:UserInputComponent>
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="InputExtension.Buffer" x:Name="variableAssignmentExtensionNr" />
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.ZielNr" Tag="" DebugModeActive="False" Expression="InputDestination.Buffer" x:Name="variableAssignmentZielNr" />
|
||||
<ns0:UserComponent PublicProperties="<?xml version="1.0" encoding="utf-16"?><ArrayOfUserProperty xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><UserProperty><Name>strDestNo</Name><Value xsi:type="xsd:string">project$.ZielNr</Value></UserProperty><UserProperty><Name>strExtensionNo</Name><Value xsi:type="xsd:string">project$.ExtensionNr</Value></UserProperty></ArrayOfUserProperty>" RelativeFilePath="setStateAndFWDestination.comp" Tag="" DebugModeActive="False" x:Name="setStateAndFWDestination1" />
|
||||
</ns0:MainFlow>
|
||||
</MainFlow>
|
||||
<ErrorHandlerFlow>
|
||||
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</ErrorHandlerFlow>
|
||||
<DisconnectHandlerFlow>
|
||||
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</DisconnectHandlerFlow>
|
||||
</Flows>
|
||||
</File>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,773 @@
|
||||
using CallFlow.CFD;
|
||||
using CallFlow;
|
||||
using MimeKit;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using TCX.Configuration;
|
||||
|
||||
namespace _721_dynWeiterleitung_mitExtension_an
|
||||
{
|
||||
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
|
||||
{
|
||||
private bool executionStarted;
|
||||
private bool executionFinished;
|
||||
private bool disconnectFlowPending;
|
||||
|
||||
private BufferBlock<AbsEvent> eventBuffer;
|
||||
|
||||
private int currentComponentIndex;
|
||||
private List<AbsComponent> mainFlowComponentList;
|
||||
private List<AbsComponent> disconnectFlowComponentList;
|
||||
private List<AbsComponent> errorFlowComponentList;
|
||||
private List<AbsComponent> currentFlowComponentList;
|
||||
|
||||
private LogFormatter logFormatter;
|
||||
private TimerManager timerManager;
|
||||
private Dictionary<string, Variable> variableMap;
|
||||
private TempWavFileManager tempWavFileManager;
|
||||
private PromptQueue promptQueue;
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
|
||||
private CfdAppScope scope;
|
||||
|
||||
private void DisconnectCallAndExitCallflow()
|
||||
{
|
||||
if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
logFormatter.Trace("Callflow finished...");
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Callflow finished, disconnecting call...");
|
||||
MyCall.Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteErrorFlow()
|
||||
{
|
||||
if (currentFlowComponentList == errorFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during error handler flow, exiting callflow...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
else if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
|
||||
executionFinished = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFlowComponentList = errorFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
if (errorFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing error handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Error handler flow is empty...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteDisconnectFlow()
|
||||
{
|
||||
currentFlowComponentList = disconnectFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
disconnectFlowPending = false;
|
||||
if (disconnectFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing disconnect handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Disconnect handler flow is empty...");
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EventResults CheckEventResult(EventResults eventResult)
|
||||
{
|
||||
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
|
||||
{
|
||||
DisconnectCallAndExitCallflow();
|
||||
return EventResults.Exit;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
DisconnectCallAndExitCallflow();
|
||||
|
||||
return eventResult;
|
||||
}
|
||||
|
||||
private void InitializeVariables(string callID)
|
||||
{
|
||||
// Call variables
|
||||
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
|
||||
variableMap["session.callid"] = new Variable(callID);
|
||||
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
|
||||
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
|
||||
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
|
||||
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
|
||||
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
|
||||
|
||||
// Standard variables
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
// User variables
|
||||
variableMap["project$.ExtensionNr"] = new Variable("");
|
||||
variableMap["project$.AuswahlOpt"] = new Variable("");
|
||||
variableMap["project$.ZielNr"] = new Variable("");
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
|
||||
{
|
||||
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
|
||||
|
||||
{
|
||||
LoggerComponent Logger1 = scope.CreateComponent<LoggerComponent>("Logger1");
|
||||
Logger1.Level = LoggerComponent.LogLevels.Info;
|
||||
Logger1.TextHandler = () => { return Convert.ToString("huhu"); };
|
||||
mainFlowComponentList.Add(Logger1);
|
||||
UserInputComponent InputExtension = scope.CreateComponent<UserInputComponent>("InputExtension");
|
||||
InputExtension.AllowDtmfInput = true;
|
||||
InputExtension.MaxRetryCount = 2;
|
||||
InputExtension.FirstDigitTimeout = 5000;
|
||||
InputExtension.InterDigitTimeout = 3000;
|
||||
InputExtension.FinalDigitTimeout = 2000;
|
||||
InputExtension.MinDigits = 1;
|
||||
InputExtension.MaxDigits = 14;
|
||||
InputExtension.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
|
||||
InputExtension.StopDigitList.AddRange(new char[] { '#' });
|
||||
InputExtension.InitialPrompts.Add(new AudioFilePrompt(() => { return "WecheNebenstelleSollWeitergeitetwerden.wav"; }));
|
||||
InputExtension.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
|
||||
InputExtension.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
|
||||
InputExtension.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
|
||||
mainFlowComponentList.Add(InputExtension);
|
||||
ConditionalComponent InputExtension_Conditional = scope.CreateComponent<ConditionalComponent>("InputExtension_Conditional");
|
||||
mainFlowComponentList.Add(InputExtension_Conditional);
|
||||
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.ValidDigits; });
|
||||
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_ValidInput"));
|
||||
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.InvalidDigits || InputExtension.Result == UserInputComponent.UserInputResults.Timeout; });
|
||||
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_InvalidInput"));
|
||||
UserInputComponent InputDestination = scope.CreateComponent<UserInputComponent>("InputDestination");
|
||||
InputDestination.AllowDtmfInput = true;
|
||||
InputDestination.MaxRetryCount = 2;
|
||||
InputDestination.FirstDigitTimeout = 5000;
|
||||
InputDestination.InterDigitTimeout = 3000;
|
||||
InputDestination.FinalDigitTimeout = 2000;
|
||||
InputDestination.MinDigits = 1;
|
||||
InputDestination.MaxDigits = 14;
|
||||
InputDestination.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
|
||||
InputDestination.StopDigitList.AddRange(new char[] { '#' });
|
||||
InputDestination.InitialPrompts.Add(new AudioFilePrompt(() => { return "Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav"; }));
|
||||
InputDestination.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
|
||||
InputDestination.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
|
||||
InputDestination.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
|
||||
mainFlowComponentList.Add(InputDestination);
|
||||
ConditionalComponent InputDestination_Conditional = scope.CreateComponent<ConditionalComponent>("InputDestination_Conditional");
|
||||
mainFlowComponentList.Add(InputDestination_Conditional);
|
||||
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.ValidDigits; });
|
||||
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_ValidInput"));
|
||||
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.InvalidDigits || InputDestination.Result == UserInputComponent.UserInputResults.Timeout; });
|
||||
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_InvalidInput"));
|
||||
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
|
||||
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
|
||||
variableAssignmentExtensionNr.VariableValueHandler = () => { return InputExtension.Buffer; };
|
||||
mainFlowComponentList.Add(variableAssignmentExtensionNr);
|
||||
VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielNr");
|
||||
variableAssignmentZielNr.VariableName = "project$.ZielNr";
|
||||
variableAssignmentZielNr.VariableValueHandler = () => { return InputDestination.Buffer; };
|
||||
mainFlowComponentList.Add(variableAssignmentZielNr);
|
||||
setStateAndFWDestination setStateAndFWDestination1 = new setStateAndFWDestination(onlineServices, officeHoursManager, scope, "setStateAndFWDestination1", callflow, myCall, logHeader);
|
||||
setStateAndFWDestination1.strDestNoSetter = () => { return variableMap["project$.ZielNr"].Value; };
|
||||
setStateAndFWDestination1.strExtensionNoSetter = () => { return variableMap["project$.ExtensionNr"].Value; };
|
||||
mainFlowComponentList.Add(setStateAndFWDestination1);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
|
||||
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
|
||||
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
|
||||
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
|
||||
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
|
||||
}
|
||||
|
||||
public Main()
|
||||
{
|
||||
this.executionStarted = false;
|
||||
this.executionFinished = false;
|
||||
this.disconnectFlowPending = false;
|
||||
|
||||
this.eventBuffer = new BufferBlock<AbsEvent>();
|
||||
|
||||
this.currentComponentIndex = 0;
|
||||
this.mainFlowComponentList = new List<AbsComponent>();
|
||||
this.disconnectFlowComponentList = new List<AbsComponent>();
|
||||
this.errorFlowComponentList = new List<AbsComponent>();
|
||||
this.currentFlowComponentList = mainFlowComponentList;
|
||||
|
||||
this.timerManager = new TimerManager();
|
||||
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
|
||||
this.variableMap = new Dictionary<string, Variable>();
|
||||
|
||||
AbsTextToSpeechEngine textToSpeechEngine = null;
|
||||
AbsSpeechToTextEngine speechToTextEngine = null;
|
||||
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
string callID = MyCall?.Caller["chid"] ?? "Unknown";
|
||||
string logHeader = $"_721_dynWeiterleitung_mitExtension_an - CallID {callID}";
|
||||
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
|
||||
this.promptQueue = new PromptQueue(this, MyCall, "_721_dynWeiterleitung_mitExtension_an", logHeader);
|
||||
this.tempWavFileManager = new TempWavFileManager(logFormatter);
|
||||
this.timerManager.CallStarted();
|
||||
this.officeHoursManager = new OfficeHoursManager(MyCall);
|
||||
|
||||
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
|
||||
|
||||
if (MyCall.Status == ConnectionStatus.Ringing)
|
||||
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
|
||||
else
|
||||
StartInternal(logHeader, callID);
|
||||
}
|
||||
|
||||
private void StartInternal(string logHeader, string callID)
|
||||
{
|
||||
logFormatter.Trace("SetBackgroundAudio to false");
|
||||
MyCall.SetBackgroundAudio(false, new string[] { });
|
||||
|
||||
logFormatter.Trace("Initialize components");
|
||||
InitializeComponents(this, MyCall, logHeader);
|
||||
logFormatter.Trace("Initialize variables");
|
||||
InitializeVariables(callID);
|
||||
|
||||
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
|
||||
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
|
||||
|
||||
logFormatter.Trace("Start executing main flow...");
|
||||
eventBuffer.Post(new StartEvent());
|
||||
Task.Run(() => EventProcessingLoop());
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void PostStartEvent()
|
||||
{
|
||||
eventBuffer.Post(new StartEvent());
|
||||
}
|
||||
|
||||
public void PostDTMFReceivedEvent(char digit)
|
||||
{
|
||||
eventBuffer.Post(new DTMFReceivedEvent(digit));
|
||||
}
|
||||
|
||||
public void PostPromptPlayedEvent()
|
||||
{
|
||||
eventBuffer.Post(new PromptPlayedEvent());
|
||||
}
|
||||
|
||||
public void PostTransferFailedEvent()
|
||||
{
|
||||
eventBuffer.Post(new TransferFailedEvent());
|
||||
}
|
||||
|
||||
public void PostMakeCallResultEvent(bool result)
|
||||
{
|
||||
eventBuffer.Post(new MakeCallResultEvent(result));
|
||||
}
|
||||
|
||||
public void PostCallTerminatedEvent()
|
||||
{
|
||||
eventBuffer.Post(new CallTerminatedEvent());
|
||||
}
|
||||
|
||||
public void PostTimeoutEvent(object state)
|
||||
{
|
||||
eventBuffer.Post(new TimeoutEvent(state));
|
||||
}
|
||||
|
||||
private async Task EventProcessingLoop()
|
||||
{
|
||||
executionStarted = true;
|
||||
while (!executionFinished)
|
||||
{
|
||||
AbsEvent evt = await eventBuffer.ReceiveAsync();
|
||||
await evt?.ProcessEvent(this);
|
||||
}
|
||||
|
||||
if (scope != null) scope.Dispose();
|
||||
}
|
||||
|
||||
public async Task ProcessStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
EventResults eventResult;
|
||||
do
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
|
||||
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
}
|
||||
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
|
||||
|
||||
if (eventResult == EventResults.Exit) executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessDTMFReceived(char digit)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessPromptPlayed()
|
||||
{
|
||||
try
|
||||
{
|
||||
promptQueue.NotifyPlayFinished();
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTransferFailed()
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessMakeCallResult(bool result)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessCallTerminated()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (executionStarted)
|
||||
{
|
||||
// First notify the call termination to the current component
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
|
||||
|
||||
// Don't wrap around CheckEventResult, because the call has been already disconnected,
|
||||
// and the following action to execute depends on the returned value.
|
||||
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
// Next, if the current component has completed its job, execute the disconnect flow
|
||||
await ExecuteDisconnectFlow();
|
||||
}
|
||||
else if (eventResult == EventResults.Wait)
|
||||
{
|
||||
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
|
||||
// the disconnect handler flow of the callflow next...
|
||||
disconnectFlowPending = true;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Finally, delete temporary files
|
||||
tempWavFileManager.DeleteFilesAndFolders();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTimeout(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// User Defined component
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
public class setStateAndFWDestination : AbsUserComponent
|
||||
{
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
private CfdAppScope scope;
|
||||
|
||||
private ObjectExpressionHandler _strDestNoHandler = null;
|
||||
private ObjectExpressionHandler _strExtensionNoHandler = null;
|
||||
|
||||
|
||||
protected override void InitializeVariables()
|
||||
{
|
||||
componentVariableMap["callflow$.strDestNo"] = new Variable("");
|
||||
componentVariableMap["callflow$.strExtensionNo"] = new Variable("");
|
||||
|
||||
}
|
||||
|
||||
protected override void InitializeComponents()
|
||||
{
|
||||
Dictionary<string, Variable> variableMap = componentVariableMap;
|
||||
{
|
||||
SetExtDNDDest514741345ECCComponent SetExtDNDDest = new SetExtDNDDest514741345ECCComponent("SetExtDNDDest", callflow, myCall, logHeader);
|
||||
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strExtNr", () => { return variableMap["callflow$.strExtensionNo"].Value; }));
|
||||
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strDestNr", () => { return variableMap["callflow$.strDestNo"].Value; }));
|
||||
mainFlowComponentList.Add(SetExtDNDDest);
|
||||
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
|
||||
mainFlowComponentList.Add(CreateCondition1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
|
||||
TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetAvailable");
|
||||
SetAvailable.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
|
||||
SetAvailable.ProfileNameHandler = () => { return "Available"; };
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(SetAvailable);
|
||||
PromptPlaybackComponent Weiterleitung_entfernt = scope.CreateComponent<PromptPlaybackComponent>("Weiterleitung_entfernt");
|
||||
Weiterleitung_entfernt.AllowDtmfInput = true;
|
||||
Weiterleitung_entfernt.Prompts.Add(new AudioFilePrompt(() => { return "Weiterleitung wurde entfernt.wav"; }));
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(Weiterleitung_entfernt);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,4)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
|
||||
TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtCustom2");
|
||||
SetExtCustom2.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
|
||||
SetExtCustom2.ProfileNameHandler = () => { return "Custom 2"; };
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtCustom2);
|
||||
PromptPlaybackComponent PlayDigits = scope.CreateComponent<PromptPlaybackComponent>("PlayDigits");
|
||||
PlayDigits.AllowDtmfInput = true;
|
||||
PlayDigits.Prompts.Add(new AudioFilePrompt(() => { return "IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav"; }));
|
||||
PlayDigits.Prompts.Add(new NumberPrompt(NumberPrompt.NumberFormats.OneByOne, () => { return Convert.ToString(variableMap["callflow$.strDestNo"].Value); }));
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(PlayDigits);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public setStateAndFWDestination(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
|
||||
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
|
||||
{
|
||||
this.onlineServices = onlineServices;
|
||||
this.officeHoursManager = officeHoursManager;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
protected override void GetVariableValues()
|
||||
{
|
||||
if (_strDestNoHandler != null) componentVariableMap["callflow$.strDestNo"].Set(_strDestNoHandler());
|
||||
if (_strExtensionNoHandler != null) componentVariableMap["callflow$.strExtensionNo"].Set(_strExtensionNoHandler());
|
||||
|
||||
}
|
||||
|
||||
public ObjectExpressionHandler strDestNoSetter { set { _strDestNoHandler = value; } }
|
||||
public object strDestNo { get { return componentVariableMap["callflow$.strDestNo"].Value; } }
|
||||
public ObjectExpressionHandler strExtensionNoSetter { set { _strExtensionNoHandler = value; } }
|
||||
public object strExtensionNo { get { return componentVariableMap["callflow$.strExtensionNo"].Value; } }
|
||||
|
||||
|
||||
private bool IsServerInHoliday(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
|
||||
}
|
||||
|
||||
private bool IsServerOfficeHourActive(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
if (tenant == null) return false;
|
||||
|
||||
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
|
||||
if (!String.IsNullOrEmpty(overrideOfficeTime))
|
||||
{
|
||||
if (overrideOfficeTime == "1") // Forced to in office hours
|
||||
return true;
|
||||
else if (overrideOfficeTime == "2") // Forced to out of office hours
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime nowDt = DateTime.Now;
|
||||
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
|
||||
|
||||
Schedule officeHours = tenant.Hours;
|
||||
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
|
||||
return result.GetValueOrDefault(false);
|
||||
}
|
||||
}
|
||||
public class SetExtDNDDest514741345ECCComponent : ExternalCodeExecutionComponent
|
||||
{
|
||||
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
|
||||
public SetExtDNDDest514741345ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
|
||||
protected override object ExecuteCode()
|
||||
{
|
||||
return SetExtDNDDest(Convert.ToString(Parameters[0].Value), Convert.ToString(Parameters[1].Value));
|
||||
}
|
||||
|
||||
private object SetExtDNDDest(string strExtNr, string strDestNr)
|
||||
{
|
||||
var ext = PhoneSystem.Root.GetDNByNumber(strExtNr) as Extension;
|
||||
var profile=ext.FwdProfiles.Where( x => x.Name == "Custom 2").First(); // 'Available', 'Away', 'Out of office', 'Custom 1', 'Custom 2', maybe parameter?
|
||||
|
||||
var NewStatus=-1;
|
||||
if( profile != null ) {
|
||||
// DestinationStruct need 3 parameter
|
||||
// 1 DestinationType: 'None', 'VoiceMail', 'Extension', 'Queue', 'RingGroup', 'IVR', 'External', 'Fax', 'Boomerang' (external number),
|
||||
// 'Deflect', 'VoiceMailOfDestination', 'Callback' (reserved), 'RoutePoint'
|
||||
// 2 internal DN, maybe select by parameter - OR -
|
||||
// 3 external number as string, maybe select by parameter
|
||||
|
||||
// DestinationType anhand der Zielnummer (strDestNr) bestimmen
|
||||
bool isInternal = false;
|
||||
bool isExtension = false;
|
||||
bool isRG = false;
|
||||
bool isIVR = false;
|
||||
bool isQUEUE = false;
|
||||
|
||||
// check if strDestNr is a internal extension
|
||||
var extension = PhoneSystem.Root.GetDNByNumber(strDestNr) as Extension;
|
||||
if( extension != null ) {
|
||||
isInternal = true;
|
||||
isExtension = true;
|
||||
}
|
||||
|
||||
// check if strDestNr is a ringgroup
|
||||
var allRGs = PhoneSystem.Root.GetRingGroups();
|
||||
foreach (var rg in allRGs)
|
||||
{
|
||||
if (rg.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isRG = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if strDestNr is a IVR/receptionist
|
||||
var allIVRs = PhoneSystem.Root.GetIVRs();
|
||||
foreach (var ivr in allIVRs)
|
||||
{
|
||||
if (ivr.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isIVR = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if strDestNr is a Queue
|
||||
var allQUEUQs = PhoneSystem.Root.GetQueues();
|
||||
foreach (var queue in allQUEUQs)
|
||||
{
|
||||
if (queue.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isQUEUE = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// define the DestinationStruct depending on the type of the destination target....
|
||||
|
||||
var dest = new DestinationStruct();
|
||||
if (strExtNr.Equals(strDestNr)) {
|
||||
// set state to available if destination number is the number of the extension
|
||||
NewStatus = 0;
|
||||
}
|
||||
else {
|
||||
if( isInternal) {
|
||||
if( isExtension ) {
|
||||
dest.To=DestinationType.Extension;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isRG ) {
|
||||
|
||||
dest.To=DestinationType.RingGroup;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isIVR ) {
|
||||
|
||||
dest.To=DestinationType.IVR;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isQUEUE ) {
|
||||
|
||||
dest.To=DestinationType.Queue;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
dest.To=DestinationType.External;
|
||||
dest.External=strDestNr; // needed if external destination
|
||||
|
||||
}
|
||||
|
||||
|
||||
NewStatus = 4;
|
||||
}
|
||||
|
||||
|
||||
if( NewStatus >0)
|
||||
{
|
||||
// Depending on the type of status (present or absent), the forwarding must be entered in other target fields
|
||||
if( profile.TypeOfRouting == RoutingType.Available) {
|
||||
var route=profile.AvailableRoute; // maybe select by parameter?
|
||||
route.NoAnswer.AllCalls = dest;
|
||||
route.NoAnswer.Internal = dest;
|
||||
route.Busy.AllCalls = route.NotRegistered.AllCalls = dest;
|
||||
route.Busy.Internal = route.NotRegistered.Internal = dest;
|
||||
}
|
||||
if( profile.TypeOfRouting == RoutingType.Away) {
|
||||
var route=profile.AwayRoute;
|
||||
var external = profile.AwayRoute.External;
|
||||
route.Internal.AllHours = dest; // maybe select by parameter?
|
||||
route.Internal.OutOfOfficeHours = dest;
|
||||
route.External.AllHours = dest;
|
||||
route.External.OutOfOfficeHours = dest;
|
||||
}
|
||||
ext.Save();
|
||||
}
|
||||
|
||||
}
|
||||
return( NewStatus); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
38
721_dynWeiterleitung_mitExtension_an/README.md
Normal file
38
721_dynWeiterleitung_mitExtension_an/README.md
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
## Dynamische Weiterleitungen ##
|
||||
|
||||
### 721_dynWeiterleitung_mitExtension_an ###
|
||||
Das Skript *721 arbeitet wie 72_dynWeiterleitung_an, allerdings wird zuerst die zu ändernde Nebenstelle mittels DTMF abgefragt.
|
||||
Anschließend wird mittels DTFM die Zielrufnummer der Weiterleitung abgefragt, diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" eingetragen
|
||||
und der Status der angegebenen Nebenstelle auf diesen umgeschaltet.
|
||||
Die Nebenstelle und das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
|
||||
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
|
||||
|
||||
Nutzungsbeispiel: *721 anrufen, "80#83#" eingeben um für die Nebenstelle 80 eine Weiterleitung auf die Nummer IVR 83 einzurichten
|
||||
Dies kann z.B. verwendet werden um die Dummy Nebenstelle 80 zwischen dem Anrufbeantworter "81 TAG", dem Anrufbeantworter "82 Nacht" oder "83 Urlaub" umzuschalten.
|
||||
|
||||
|
||||
|
||||
|
||||
## Funktion auf BLF Tasten von Tischtelefonen legen ##
|
||||
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
|
||||
|
||||
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
|
||||
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72;dtmf=10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
|
||||
|
||||
*Quellen / Nützliche Tools*
|
||||
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
|
||||
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
|
||||
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
|
||||
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)
|
||||
File diff suppressed because one or more lines are too long
50
72_dynWeiterleitung_an/72_dynWeiterleitung_an.cfdproj
Normal file
50
72_dynWeiterleitung_an/72_dynWeiterleitung_an.cfdproj
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Graphical_Application_Designer_Project>
|
||||
<Version>2.1</Version>
|
||||
<Files>
|
||||
<File path="Main.flow" type="callflow" />
|
||||
<File path="setStateAndFWDestination.comp" type="component" />
|
||||
</Files>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Variable>
|
||||
<Name>ExtensionNr</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>AuswahlOpt</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>ZielNr</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
</ArrayOfVariable>
|
||||
</Variables>
|
||||
<DebugBuildSuccessful>False</DebugBuildSuccessful>
|
||||
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
|
||||
<DebugBuildNumber>0</DebugBuildNumber>
|
||||
<ReleaseBuildNumber>49</ReleaseBuildNumber>
|
||||
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
|
||||
<DoNotAskForExtension>False</DoNotAskForExtension>
|
||||
<Extension>*72</Extension>
|
||||
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
|
||||
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
|
||||
<AmazonClientID>
|
||||
</AmazonClientID>
|
||||
<AmazonClientSecret>
|
||||
</AmazonClientSecret>
|
||||
<AmazonRegion>us-east-2</AmazonRegion>
|
||||
<AmazonLexicons>
|
||||
</AmazonLexicons>
|
||||
<GoogleCloudServiceAccountKeyFileName>
|
||||
</GoogleCloudServiceAccountKeyFileName>
|
||||
<GoogleCloudServiceAccountKeyJSON>
|
||||
</GoogleCloudServiceAccountKeyJSON>
|
||||
</Graphical_Application_Designer_Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
72_dynWeiterleitung_an/Audio/FehlerBeiDerEingabe-vicky.wav
Normal file
BIN
72_dynWeiterleitung_an/Audio/FehlerBeiDerEingabe-vicky.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
72_dynWeiterleitung_an/Audio/Weiterleitung wurde entfernt.wav
Normal file
BIN
72_dynWeiterleitung_an/Audio/Weiterleitung wurde entfernt.wav
Normal file
Binary file not shown.
BIN
72_dynWeiterleitung_an/Audio/leer50ms.wav
Normal file
BIN
72_dynWeiterleitung_an/Audio/leer50ms.wav
Normal file
Binary file not shown.
26
72_dynWeiterleitung_an/Main.flow
Normal file
26
72_dynWeiterleitung_an/Main.flow
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<File>
|
||||
<Version>2.1</Version>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
|
||||
</Variables>
|
||||
<Flows>
|
||||
<MainFlow>
|
||||
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
|
||||
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>FehlerBeiDerEingabe-vicky.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>leer50ms.wav</AudioFileName></Prompt></ArrayOfPrompt>" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="InputDestination" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="AudioFilePrompt"><AudioFileName>DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav</AudioFileName></Prompt></ArrayOfPrompt>">
|
||||
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch1" />
|
||||
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch2" />
|
||||
</ns0:UserInputComponent>
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="session.ani" x:Name="variableAssignmentExtensionNr" />
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.ZielNr" Tag="" DebugModeActive="False" Expression="InputDestination.Buffer" x:Name="variableAssignmentZielNr" />
|
||||
<ns0:UserComponent PublicProperties="<?xml version="1.0" encoding="utf-16"?><ArrayOfUserProperty xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><UserProperty><Name>strDestNo</Name><Value xsi:type="xsd:string">project$.ZielNr</Value></UserProperty><UserProperty><Name>strExtensionNo</Name><Value xsi:type="xsd:string">project$.ExtensionNr</Value></UserProperty></ArrayOfUserProperty>" RelativeFilePath="setStateAndFWDestination.comp" Tag="" DebugModeActive="False" x:Name="setStateAndFWDestination1" />
|
||||
</ns0:MainFlow>
|
||||
</MainFlow>
|
||||
<ErrorHandlerFlow>
|
||||
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</ErrorHandlerFlow>
|
||||
<DisconnectHandlerFlow>
|
||||
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</DisconnectHandlerFlow>
|
||||
</Flows>
|
||||
</File>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
72_dynWeiterleitung_an/Output/Release/Audio/leer50ms.wav
Normal file
BIN
72_dynWeiterleitung_an/Output/Release/Audio/leer50ms.wav
Normal file
Binary file not shown.
748
72_dynWeiterleitung_an/Output/Release/Script/Main.cs
Normal file
748
72_dynWeiterleitung_an/Output/Release/Script/Main.cs
Normal file
@ -0,0 +1,748 @@
|
||||
using CallFlow.CFD;
|
||||
using CallFlow;
|
||||
using MimeKit;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using TCX.Configuration;
|
||||
|
||||
namespace _72_dynWeiterleitung_an
|
||||
{
|
||||
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
|
||||
{
|
||||
private bool executionStarted;
|
||||
private bool executionFinished;
|
||||
private bool disconnectFlowPending;
|
||||
|
||||
private BufferBlock<AbsEvent> eventBuffer;
|
||||
|
||||
private int currentComponentIndex;
|
||||
private List<AbsComponent> mainFlowComponentList;
|
||||
private List<AbsComponent> disconnectFlowComponentList;
|
||||
private List<AbsComponent> errorFlowComponentList;
|
||||
private List<AbsComponent> currentFlowComponentList;
|
||||
|
||||
private LogFormatter logFormatter;
|
||||
private TimerManager timerManager;
|
||||
private Dictionary<string, Variable> variableMap;
|
||||
private TempWavFileManager tempWavFileManager;
|
||||
private PromptQueue promptQueue;
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
|
||||
private CfdAppScope scope;
|
||||
|
||||
private void DisconnectCallAndExitCallflow()
|
||||
{
|
||||
if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
logFormatter.Trace("Callflow finished...");
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Callflow finished, disconnecting call...");
|
||||
MyCall.Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteErrorFlow()
|
||||
{
|
||||
if (currentFlowComponentList == errorFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during error handler flow, exiting callflow...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
else if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
|
||||
executionFinished = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFlowComponentList = errorFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
if (errorFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing error handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Error handler flow is empty...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteDisconnectFlow()
|
||||
{
|
||||
currentFlowComponentList = disconnectFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
disconnectFlowPending = false;
|
||||
if (disconnectFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing disconnect handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Disconnect handler flow is empty...");
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EventResults CheckEventResult(EventResults eventResult)
|
||||
{
|
||||
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
|
||||
{
|
||||
DisconnectCallAndExitCallflow();
|
||||
return EventResults.Exit;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
DisconnectCallAndExitCallflow();
|
||||
|
||||
return eventResult;
|
||||
}
|
||||
|
||||
private void InitializeVariables(string callID)
|
||||
{
|
||||
// Call variables
|
||||
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
|
||||
variableMap["session.callid"] = new Variable(callID);
|
||||
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
|
||||
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
|
||||
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
|
||||
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
|
||||
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
|
||||
|
||||
// Standard variables
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
// User variables
|
||||
variableMap["project$.ExtensionNr"] = new Variable("");
|
||||
variableMap["project$.AuswahlOpt"] = new Variable("");
|
||||
variableMap["project$.ZielNr"] = new Variable("");
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
|
||||
{
|
||||
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
|
||||
|
||||
{
|
||||
UserInputComponent InputDestination = scope.CreateComponent<UserInputComponent>("InputDestination");
|
||||
InputDestination.AllowDtmfInput = true;
|
||||
InputDestination.MaxRetryCount = 2;
|
||||
InputDestination.FirstDigitTimeout = 5000;
|
||||
InputDestination.InterDigitTimeout = 3000;
|
||||
InputDestination.FinalDigitTimeout = 2000;
|
||||
InputDestination.MinDigits = 1;
|
||||
InputDestination.MaxDigits = 14;
|
||||
InputDestination.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
|
||||
InputDestination.StopDigitList.AddRange(new char[] { '#' });
|
||||
InputDestination.InitialPrompts.Add(new AudioFilePrompt(() => { return "Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav"; }));
|
||||
InputDestination.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
|
||||
InputDestination.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
|
||||
InputDestination.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
|
||||
mainFlowComponentList.Add(InputDestination);
|
||||
ConditionalComponent InputDestination_Conditional = scope.CreateComponent<ConditionalComponent>("InputDestination_Conditional");
|
||||
mainFlowComponentList.Add(InputDestination_Conditional);
|
||||
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.ValidDigits; });
|
||||
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_ValidInput"));
|
||||
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.InvalidDigits || InputDestination.Result == UserInputComponent.UserInputResults.Timeout; });
|
||||
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_InvalidInput"));
|
||||
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
|
||||
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
|
||||
variableAssignmentExtensionNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
|
||||
mainFlowComponentList.Add(variableAssignmentExtensionNr);
|
||||
VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielNr");
|
||||
variableAssignmentZielNr.VariableName = "project$.ZielNr";
|
||||
variableAssignmentZielNr.VariableValueHandler = () => { return InputDestination.Buffer; };
|
||||
mainFlowComponentList.Add(variableAssignmentZielNr);
|
||||
setStateAndFWDestination setStateAndFWDestination1 = new setStateAndFWDestination(onlineServices, officeHoursManager, scope, "setStateAndFWDestination1", callflow, myCall, logHeader);
|
||||
setStateAndFWDestination1.strDestNoSetter = () => { return variableMap["project$.ZielNr"].Value; };
|
||||
setStateAndFWDestination1.strExtensionNoSetter = () => { return variableMap["project$.ExtensionNr"].Value; };
|
||||
mainFlowComponentList.Add(setStateAndFWDestination1);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
|
||||
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
|
||||
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
|
||||
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
|
||||
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
|
||||
}
|
||||
|
||||
public Main()
|
||||
{
|
||||
this.executionStarted = false;
|
||||
this.executionFinished = false;
|
||||
this.disconnectFlowPending = false;
|
||||
|
||||
this.eventBuffer = new BufferBlock<AbsEvent>();
|
||||
|
||||
this.currentComponentIndex = 0;
|
||||
this.mainFlowComponentList = new List<AbsComponent>();
|
||||
this.disconnectFlowComponentList = new List<AbsComponent>();
|
||||
this.errorFlowComponentList = new List<AbsComponent>();
|
||||
this.currentFlowComponentList = mainFlowComponentList;
|
||||
|
||||
this.timerManager = new TimerManager();
|
||||
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
|
||||
this.variableMap = new Dictionary<string, Variable>();
|
||||
|
||||
AbsTextToSpeechEngine textToSpeechEngine = null;
|
||||
AbsSpeechToTextEngine speechToTextEngine = null;
|
||||
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
string callID = MyCall?.Caller["chid"] ?? "Unknown";
|
||||
string logHeader = $"_72_dynWeiterleitung_an - CallID {callID}";
|
||||
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
|
||||
this.promptQueue = new PromptQueue(this, MyCall, "_72_dynWeiterleitung_an", logHeader);
|
||||
this.tempWavFileManager = new TempWavFileManager(logFormatter);
|
||||
this.timerManager.CallStarted();
|
||||
this.officeHoursManager = new OfficeHoursManager(MyCall);
|
||||
|
||||
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
|
||||
|
||||
if (MyCall.Status == ConnectionStatus.Ringing)
|
||||
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
|
||||
else
|
||||
StartInternal(logHeader, callID);
|
||||
}
|
||||
|
||||
private void StartInternal(string logHeader, string callID)
|
||||
{
|
||||
logFormatter.Trace("SetBackgroundAudio to false");
|
||||
MyCall.SetBackgroundAudio(false, new string[] { });
|
||||
|
||||
logFormatter.Trace("Initialize components");
|
||||
InitializeComponents(this, MyCall, logHeader);
|
||||
logFormatter.Trace("Initialize variables");
|
||||
InitializeVariables(callID);
|
||||
|
||||
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
|
||||
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
|
||||
|
||||
logFormatter.Trace("Start executing main flow...");
|
||||
eventBuffer.Post(new StartEvent());
|
||||
Task.Run(() => EventProcessingLoop());
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void PostStartEvent()
|
||||
{
|
||||
eventBuffer.Post(new StartEvent());
|
||||
}
|
||||
|
||||
public void PostDTMFReceivedEvent(char digit)
|
||||
{
|
||||
eventBuffer.Post(new DTMFReceivedEvent(digit));
|
||||
}
|
||||
|
||||
public void PostPromptPlayedEvent()
|
||||
{
|
||||
eventBuffer.Post(new PromptPlayedEvent());
|
||||
}
|
||||
|
||||
public void PostTransferFailedEvent()
|
||||
{
|
||||
eventBuffer.Post(new TransferFailedEvent());
|
||||
}
|
||||
|
||||
public void PostMakeCallResultEvent(bool result)
|
||||
{
|
||||
eventBuffer.Post(new MakeCallResultEvent(result));
|
||||
}
|
||||
|
||||
public void PostCallTerminatedEvent()
|
||||
{
|
||||
eventBuffer.Post(new CallTerminatedEvent());
|
||||
}
|
||||
|
||||
public void PostTimeoutEvent(object state)
|
||||
{
|
||||
eventBuffer.Post(new TimeoutEvent(state));
|
||||
}
|
||||
|
||||
private async Task EventProcessingLoop()
|
||||
{
|
||||
executionStarted = true;
|
||||
while (!executionFinished)
|
||||
{
|
||||
AbsEvent evt = await eventBuffer.ReceiveAsync();
|
||||
await evt?.ProcessEvent(this);
|
||||
}
|
||||
|
||||
if (scope != null) scope.Dispose();
|
||||
}
|
||||
|
||||
public async Task ProcessStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
EventResults eventResult;
|
||||
do
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
|
||||
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
}
|
||||
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
|
||||
|
||||
if (eventResult == EventResults.Exit) executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessDTMFReceived(char digit)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessPromptPlayed()
|
||||
{
|
||||
try
|
||||
{
|
||||
promptQueue.NotifyPlayFinished();
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTransferFailed()
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessMakeCallResult(bool result)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessCallTerminated()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (executionStarted)
|
||||
{
|
||||
// First notify the call termination to the current component
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
|
||||
|
||||
// Don't wrap around CheckEventResult, because the call has been already disconnected,
|
||||
// and the following action to execute depends on the returned value.
|
||||
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
// Next, if the current component has completed its job, execute the disconnect flow
|
||||
await ExecuteDisconnectFlow();
|
||||
}
|
||||
else if (eventResult == EventResults.Wait)
|
||||
{
|
||||
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
|
||||
// the disconnect handler flow of the callflow next...
|
||||
disconnectFlowPending = true;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Finally, delete temporary files
|
||||
tempWavFileManager.DeleteFilesAndFolders();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTimeout(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// User Defined component
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
public class setStateAndFWDestination : AbsUserComponent
|
||||
{
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
private CfdAppScope scope;
|
||||
|
||||
private ObjectExpressionHandler _strDestNoHandler = null;
|
||||
private ObjectExpressionHandler _strExtensionNoHandler = null;
|
||||
|
||||
|
||||
protected override void InitializeVariables()
|
||||
{
|
||||
componentVariableMap["callflow$.strDestNo"] = new Variable("");
|
||||
componentVariableMap["callflow$.strExtensionNo"] = new Variable("");
|
||||
|
||||
}
|
||||
|
||||
protected override void InitializeComponents()
|
||||
{
|
||||
Dictionary<string, Variable> variableMap = componentVariableMap;
|
||||
{
|
||||
SetExtDNDDest1287229307ECCComponent SetExtDNDDest = new SetExtDNDDest1287229307ECCComponent("SetExtDNDDest", callflow, myCall, logHeader);
|
||||
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strExtNr", () => { return variableMap["callflow$.strExtensionNo"].Value; }));
|
||||
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strDestNr", () => { return variableMap["callflow$.strDestNo"].Value; }));
|
||||
mainFlowComponentList.Add(SetExtDNDDest);
|
||||
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
|
||||
mainFlowComponentList.Add(CreateCondition1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
|
||||
TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetAvailable");
|
||||
SetAvailable.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
|
||||
SetAvailable.ProfileNameHandler = () => { return "Available"; };
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(SetAvailable);
|
||||
PromptPlaybackComponent Weiterleitung_entfernt = scope.CreateComponent<PromptPlaybackComponent>("Weiterleitung_entfernt");
|
||||
Weiterleitung_entfernt.AllowDtmfInput = true;
|
||||
Weiterleitung_entfernt.Prompts.Add(new AudioFilePrompt(() => { return "Weiterleitung wurde entfernt.wav"; }));
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(Weiterleitung_entfernt);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,4)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
|
||||
TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtCustom2");
|
||||
SetExtCustom2.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
|
||||
SetExtCustom2.ProfileNameHandler = () => { return "Custom 2"; };
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtCustom2);
|
||||
PromptPlaybackComponent PlayDigits = scope.CreateComponent<PromptPlaybackComponent>("PlayDigits");
|
||||
PlayDigits.AllowDtmfInput = true;
|
||||
PlayDigits.Prompts.Add(new AudioFilePrompt(() => { return "IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav"; }));
|
||||
PlayDigits.Prompts.Add(new NumberPrompt(NumberPrompt.NumberFormats.OneByOne, () => { return Convert.ToString(variableMap["callflow$.strDestNo"].Value); }));
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(PlayDigits);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public setStateAndFWDestination(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
|
||||
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
|
||||
{
|
||||
this.onlineServices = onlineServices;
|
||||
this.officeHoursManager = officeHoursManager;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
protected override void GetVariableValues()
|
||||
{
|
||||
if (_strDestNoHandler != null) componentVariableMap["callflow$.strDestNo"].Set(_strDestNoHandler());
|
||||
if (_strExtensionNoHandler != null) componentVariableMap["callflow$.strExtensionNo"].Set(_strExtensionNoHandler());
|
||||
|
||||
}
|
||||
|
||||
public ObjectExpressionHandler strDestNoSetter { set { _strDestNoHandler = value; } }
|
||||
public object strDestNo { get { return componentVariableMap["callflow$.strDestNo"].Value; } }
|
||||
public ObjectExpressionHandler strExtensionNoSetter { set { _strExtensionNoHandler = value; } }
|
||||
public object strExtensionNo { get { return componentVariableMap["callflow$.strExtensionNo"].Value; } }
|
||||
|
||||
|
||||
private bool IsServerInHoliday(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
|
||||
}
|
||||
|
||||
private bool IsServerOfficeHourActive(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
if (tenant == null) return false;
|
||||
|
||||
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
|
||||
if (!String.IsNullOrEmpty(overrideOfficeTime))
|
||||
{
|
||||
if (overrideOfficeTime == "1") // Forced to in office hours
|
||||
return true;
|
||||
else if (overrideOfficeTime == "2") // Forced to out of office hours
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime nowDt = DateTime.Now;
|
||||
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
|
||||
|
||||
Schedule officeHours = tenant.Hours;
|
||||
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
|
||||
return result.GetValueOrDefault(false);
|
||||
}
|
||||
}
|
||||
public class SetExtDNDDest1287229307ECCComponent : ExternalCodeExecutionComponent
|
||||
{
|
||||
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
|
||||
public SetExtDNDDest1287229307ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
|
||||
protected override object ExecuteCode()
|
||||
{
|
||||
return SetExtDNDDest(Convert.ToString(Parameters[0].Value), Convert.ToString(Parameters[1].Value));
|
||||
}
|
||||
|
||||
private object SetExtDNDDest(string strExtNr, string strDestNr)
|
||||
{
|
||||
var ext = PhoneSystem.Root.GetDNByNumber(strExtNr) as Extension;
|
||||
var profile=ext.FwdProfiles.Where( x => x.Name == "Custom 2").First(); // 'Available', 'Away', 'Out of office', 'Custom 1', 'Custom 2', maybe parameter?
|
||||
|
||||
var NewStatus=-1;
|
||||
if( profile != null ) {
|
||||
// DestinationStruct need 3 parameter
|
||||
// 1 DestinationType: 'None', 'VoiceMail', 'Extension', 'Queue', 'RingGroup', 'IVR', 'External', 'Fax', 'Boomerang' (external number),
|
||||
// 'Deflect', 'VoiceMailOfDestination', 'Callback' (reserved), 'RoutePoint'
|
||||
// 2 internal DN, maybe select by parameter - OR -
|
||||
// 3 external number as string, maybe select by parameter
|
||||
|
||||
// DestinationType anhand der Zielnummer (strDestNr) bestimmen
|
||||
bool isInternal = false;
|
||||
bool isExtension = false;
|
||||
bool isRG = false;
|
||||
bool isIVR = false;
|
||||
bool isQUEUE = false;
|
||||
|
||||
// check if strDestNr is a internal extension
|
||||
var extension = PhoneSystem.Root.GetDNByNumber(strDestNr) as Extension;
|
||||
if( extension != null ) {
|
||||
isInternal = true;
|
||||
isExtension = true;
|
||||
}
|
||||
|
||||
// check if strDestNr is a ringgroup
|
||||
var allRGs = PhoneSystem.Root.GetRingGroups();
|
||||
foreach (var rg in allRGs)
|
||||
{
|
||||
if (rg.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isRG = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if strDestNr is a IVR/receptionist
|
||||
var allIVRs = PhoneSystem.Root.GetIVRs();
|
||||
foreach (var ivr in allIVRs)
|
||||
{
|
||||
if (ivr.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isIVR = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if strDestNr is a Queue
|
||||
var allQUEUQs = PhoneSystem.Root.GetQueues();
|
||||
foreach (var queue in allQUEUQs)
|
||||
{
|
||||
if (queue.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isQUEUE = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// define the DestinationStruct depending on the type of the destination target....
|
||||
|
||||
var dest = new DestinationStruct();
|
||||
if (strExtNr.Equals(strDestNr)) {
|
||||
// set state to available if destination number is the number of the extension
|
||||
NewStatus = 0;
|
||||
}
|
||||
else {
|
||||
if( isInternal) {
|
||||
if( isExtension ) {
|
||||
dest.To=DestinationType.Extension;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isRG ) {
|
||||
|
||||
dest.To=DestinationType.RingGroup;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isIVR ) {
|
||||
|
||||
dest.To=DestinationType.IVR;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isQUEUE ) {
|
||||
|
||||
dest.To=DestinationType.Queue;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
dest.To=DestinationType.External;
|
||||
dest.External=strDestNr; // needed if external destination
|
||||
|
||||
}
|
||||
|
||||
|
||||
NewStatus = 4;
|
||||
}
|
||||
|
||||
|
||||
if( NewStatus >0)
|
||||
{
|
||||
// Depending on the type of status (present or absent), the forwarding must be entered in other target fields
|
||||
if( profile.TypeOfRouting == RoutingType.Available) {
|
||||
var route=profile.AvailableRoute; // maybe select by parameter?
|
||||
route.NoAnswer.AllCalls = dest;
|
||||
route.NoAnswer.Internal = dest;
|
||||
route.Busy.AllCalls = route.NotRegistered.AllCalls = dest;
|
||||
route.Busy.Internal = route.NotRegistered.Internal = dest;
|
||||
}
|
||||
if( profile.TypeOfRouting == RoutingType.Away) {
|
||||
var route=profile.AwayRoute;
|
||||
var external = profile.AwayRoute.External;
|
||||
route.Internal.AllHours = dest; // maybe select by parameter?
|
||||
route.Internal.OutOfOfficeHours = dest;
|
||||
route.External.AllHours = dest;
|
||||
route.External.OutOfOfficeHours = dest;
|
||||
}
|
||||
ext.Save();
|
||||
}
|
||||
|
||||
}
|
||||
return( NewStatus); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
32
72_dynWeiterleitung_an/README.md
Normal file
32
72_dynWeiterleitung_an/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
## Dynamische Weiterleitungen ##
|
||||
### 72_dynWeiterleitung_an ###
|
||||
Das Skript *72 fragt mittels DTFM die Zielrufnummer der Weiterleitung ab, trägt diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" und schaltet den Status der anrufenden Nebenstelle auf diesen um.
|
||||
Das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
|
||||
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
|
||||
|
||||
Nutzungsbeispiel: *72 anrufen, "0172123456#" eingeben um eine Weiterleitung auf die Nummer 0172123456 einzurichten
|
||||
|
||||
|
||||
|
||||
## Funktion auf BLF Tasten von Tischtelefonen legen ##
|
||||
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
|
||||
|
||||
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
|
||||
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72;dtmf=10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
|
||||
|
||||
*Quellen / Nützliche Tools*
|
||||
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
|
||||
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
|
||||
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
|
||||
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)
|
||||
43
72_dynWeiterleitung_an/setStateAndFWDestination.comp
Normal file
43
72_dynWeiterleitung_an/setStateAndFWDestination.comp
Normal file
File diff suppressed because one or more lines are too long
50
73_dynWeiterleitung_aus/73_dynWeiterleitung_aus.cfdproj
Normal file
50
73_dynWeiterleitung_aus/73_dynWeiterleitung_aus.cfdproj
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Graphical_Application_Designer_Project>
|
||||
<Version>2.1</Version>
|
||||
<Files>
|
||||
<File path="Main.flow" type="callflow" />
|
||||
<File path="setStateAndFWDestination.comp" type="component" />
|
||||
</Files>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Variable>
|
||||
<Name>ExtensionNr</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>AuswahlOpt</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>ZielNr</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
</ArrayOfVariable>
|
||||
</Variables>
|
||||
<DebugBuildSuccessful>False</DebugBuildSuccessful>
|
||||
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
|
||||
<DebugBuildNumber>0</DebugBuildNumber>
|
||||
<ReleaseBuildNumber>48</ReleaseBuildNumber>
|
||||
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
|
||||
<DoNotAskForExtension>False</DoNotAskForExtension>
|
||||
<Extension>*73</Extension>
|
||||
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
|
||||
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
|
||||
<AmazonClientID>
|
||||
</AmazonClientID>
|
||||
<AmazonClientSecret>
|
||||
</AmazonClientSecret>
|
||||
<AmazonRegion>us-east-2</AmazonRegion>
|
||||
<AmazonLexicons>
|
||||
</AmazonLexicons>
|
||||
<GoogleCloudServiceAccountKeyFileName>
|
||||
</GoogleCloudServiceAccountKeyFileName>
|
||||
<GoogleCloudServiceAccountKeyJSON>
|
||||
</GoogleCloudServiceAccountKeyJSON>
|
||||
</Graphical_Application_Designer_Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
73_dynWeiterleitung_aus/Audio/FehlerBeiDerEingabe-vicky.wav
Normal file
BIN
73_dynWeiterleitung_aus/Audio/FehlerBeiDerEingabe-vicky.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
73_dynWeiterleitung_aus/Audio/Weiterleitung wurde entfernt.wav
Normal file
BIN
73_dynWeiterleitung_aus/Audio/Weiterleitung wurde entfernt.wav
Normal file
Binary file not shown.
BIN
73_dynWeiterleitung_aus/Audio/leer50ms.wav
Normal file
BIN
73_dynWeiterleitung_aus/Audio/leer50ms.wav
Normal file
Binary file not shown.
22
73_dynWeiterleitung_aus/Main.flow
Normal file
22
73_dynWeiterleitung_aus/Main.flow
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<File>
|
||||
<Version>2.1</Version>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
|
||||
</Variables>
|
||||
<Flows>
|
||||
<MainFlow>
|
||||
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="session.ani" x:Name="variableAssignmentExtensionNr" />
|
||||
<ns0:VariableAssignmentComponent VariableName="project$.ZielNr" Tag="" DebugModeActive="False" Expression="session.ani" x:Name="variableAssignmentZielNr" />
|
||||
<ns0:UserComponent PublicProperties="<?xml version="1.0" encoding="utf-16"?><ArrayOfUserProperty xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><UserProperty><Name>strDestNo</Name><Value xsi:type="xsd:string">project$.ZielNr</Value></UserProperty><UserProperty><Name>strExtensionNo</Name><Value xsi:type="xsd:string">project$.ExtensionNr</Value></UserProperty></ArrayOfUserProperty>" RelativeFilePath="setStateAndFWDestination.comp" Tag="" DebugModeActive="False" x:Name="setStateAndFWDestination1" />
|
||||
</ns0:MainFlow>
|
||||
</MainFlow>
|
||||
<ErrorHandlerFlow>
|
||||
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</ErrorHandlerFlow>
|
||||
<DisconnectHandlerFlow>
|
||||
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</DisconnectHandlerFlow>
|
||||
</Flows>
|
||||
</File>
|
||||
Binary file not shown.
Binary file not shown.
727
73_dynWeiterleitung_aus/Output/Release/Script/Main.cs
Normal file
727
73_dynWeiterleitung_aus/Output/Release/Script/Main.cs
Normal file
@ -0,0 +1,727 @@
|
||||
using CallFlow.CFD;
|
||||
using CallFlow;
|
||||
using MimeKit;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using TCX.Configuration;
|
||||
|
||||
namespace _73_dynWeiterleitung_aus
|
||||
{
|
||||
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
|
||||
{
|
||||
private bool executionStarted;
|
||||
private bool executionFinished;
|
||||
private bool disconnectFlowPending;
|
||||
|
||||
private BufferBlock<AbsEvent> eventBuffer;
|
||||
|
||||
private int currentComponentIndex;
|
||||
private List<AbsComponent> mainFlowComponentList;
|
||||
private List<AbsComponent> disconnectFlowComponentList;
|
||||
private List<AbsComponent> errorFlowComponentList;
|
||||
private List<AbsComponent> currentFlowComponentList;
|
||||
|
||||
private LogFormatter logFormatter;
|
||||
private TimerManager timerManager;
|
||||
private Dictionary<string, Variable> variableMap;
|
||||
private TempWavFileManager tempWavFileManager;
|
||||
private PromptQueue promptQueue;
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
|
||||
private CfdAppScope scope;
|
||||
|
||||
private void DisconnectCallAndExitCallflow()
|
||||
{
|
||||
if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
logFormatter.Trace("Callflow finished...");
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Callflow finished, disconnecting call...");
|
||||
MyCall.Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteErrorFlow()
|
||||
{
|
||||
if (currentFlowComponentList == errorFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during error handler flow, exiting callflow...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
else if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
|
||||
executionFinished = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFlowComponentList = errorFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
if (errorFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing error handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Error handler flow is empty...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteDisconnectFlow()
|
||||
{
|
||||
currentFlowComponentList = disconnectFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
disconnectFlowPending = false;
|
||||
if (disconnectFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing disconnect handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Disconnect handler flow is empty...");
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EventResults CheckEventResult(EventResults eventResult)
|
||||
{
|
||||
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
|
||||
{
|
||||
DisconnectCallAndExitCallflow();
|
||||
return EventResults.Exit;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
DisconnectCallAndExitCallflow();
|
||||
|
||||
return eventResult;
|
||||
}
|
||||
|
||||
private void InitializeVariables(string callID)
|
||||
{
|
||||
// Call variables
|
||||
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
|
||||
variableMap["session.callid"] = new Variable(callID);
|
||||
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
|
||||
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
|
||||
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
|
||||
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
|
||||
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
|
||||
|
||||
// Standard variables
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
// User variables
|
||||
variableMap["project$.ExtensionNr"] = new Variable("");
|
||||
variableMap["project$.AuswahlOpt"] = new Variable("");
|
||||
variableMap["project$.ZielNr"] = new Variable("");
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
|
||||
{
|
||||
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
|
||||
|
||||
{
|
||||
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
|
||||
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
|
||||
variableAssignmentExtensionNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
|
||||
mainFlowComponentList.Add(variableAssignmentExtensionNr);
|
||||
VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielNr");
|
||||
variableAssignmentZielNr.VariableName = "project$.ZielNr";
|
||||
variableAssignmentZielNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
|
||||
mainFlowComponentList.Add(variableAssignmentZielNr);
|
||||
setStateAndFWDestination setStateAndFWDestination1 = new setStateAndFWDestination(onlineServices, officeHoursManager, scope, "setStateAndFWDestination1", callflow, myCall, logHeader);
|
||||
setStateAndFWDestination1.strDestNoSetter = () => { return variableMap["project$.ZielNr"].Value; };
|
||||
setStateAndFWDestination1.strExtensionNoSetter = () => { return variableMap["project$.ExtensionNr"].Value; };
|
||||
mainFlowComponentList.Add(setStateAndFWDestination1);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
|
||||
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
|
||||
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
|
||||
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
|
||||
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
|
||||
}
|
||||
|
||||
public Main()
|
||||
{
|
||||
this.executionStarted = false;
|
||||
this.executionFinished = false;
|
||||
this.disconnectFlowPending = false;
|
||||
|
||||
this.eventBuffer = new BufferBlock<AbsEvent>();
|
||||
|
||||
this.currentComponentIndex = 0;
|
||||
this.mainFlowComponentList = new List<AbsComponent>();
|
||||
this.disconnectFlowComponentList = new List<AbsComponent>();
|
||||
this.errorFlowComponentList = new List<AbsComponent>();
|
||||
this.currentFlowComponentList = mainFlowComponentList;
|
||||
|
||||
this.timerManager = new TimerManager();
|
||||
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
|
||||
this.variableMap = new Dictionary<string, Variable>();
|
||||
|
||||
AbsTextToSpeechEngine textToSpeechEngine = null;
|
||||
AbsSpeechToTextEngine speechToTextEngine = null;
|
||||
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
string callID = MyCall?.Caller["chid"] ?? "Unknown";
|
||||
string logHeader = $"_73_dynWeiterleitung_aus - CallID {callID}";
|
||||
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
|
||||
this.promptQueue = new PromptQueue(this, MyCall, "_73_dynWeiterleitung_aus", logHeader);
|
||||
this.tempWavFileManager = new TempWavFileManager(logFormatter);
|
||||
this.timerManager.CallStarted();
|
||||
this.officeHoursManager = new OfficeHoursManager(MyCall);
|
||||
|
||||
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
|
||||
|
||||
if (MyCall.Status == ConnectionStatus.Ringing)
|
||||
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
|
||||
else
|
||||
StartInternal(logHeader, callID);
|
||||
}
|
||||
|
||||
private void StartInternal(string logHeader, string callID)
|
||||
{
|
||||
logFormatter.Trace("SetBackgroundAudio to false");
|
||||
MyCall.SetBackgroundAudio(false, new string[] { });
|
||||
|
||||
logFormatter.Trace("Initialize components");
|
||||
InitializeComponents(this, MyCall, logHeader);
|
||||
logFormatter.Trace("Initialize variables");
|
||||
InitializeVariables(callID);
|
||||
|
||||
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
|
||||
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
|
||||
|
||||
logFormatter.Trace("Start executing main flow...");
|
||||
eventBuffer.Post(new StartEvent());
|
||||
Task.Run(() => EventProcessingLoop());
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void PostStartEvent()
|
||||
{
|
||||
eventBuffer.Post(new StartEvent());
|
||||
}
|
||||
|
||||
public void PostDTMFReceivedEvent(char digit)
|
||||
{
|
||||
eventBuffer.Post(new DTMFReceivedEvent(digit));
|
||||
}
|
||||
|
||||
public void PostPromptPlayedEvent()
|
||||
{
|
||||
eventBuffer.Post(new PromptPlayedEvent());
|
||||
}
|
||||
|
||||
public void PostTransferFailedEvent()
|
||||
{
|
||||
eventBuffer.Post(new TransferFailedEvent());
|
||||
}
|
||||
|
||||
public void PostMakeCallResultEvent(bool result)
|
||||
{
|
||||
eventBuffer.Post(new MakeCallResultEvent(result));
|
||||
}
|
||||
|
||||
public void PostCallTerminatedEvent()
|
||||
{
|
||||
eventBuffer.Post(new CallTerminatedEvent());
|
||||
}
|
||||
|
||||
public void PostTimeoutEvent(object state)
|
||||
{
|
||||
eventBuffer.Post(new TimeoutEvent(state));
|
||||
}
|
||||
|
||||
private async Task EventProcessingLoop()
|
||||
{
|
||||
executionStarted = true;
|
||||
while (!executionFinished)
|
||||
{
|
||||
AbsEvent evt = await eventBuffer.ReceiveAsync();
|
||||
await evt?.ProcessEvent(this);
|
||||
}
|
||||
|
||||
if (scope != null) scope.Dispose();
|
||||
}
|
||||
|
||||
public async Task ProcessStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
EventResults eventResult;
|
||||
do
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
|
||||
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
}
|
||||
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
|
||||
|
||||
if (eventResult == EventResults.Exit) executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessDTMFReceived(char digit)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessPromptPlayed()
|
||||
{
|
||||
try
|
||||
{
|
||||
promptQueue.NotifyPlayFinished();
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTransferFailed()
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessMakeCallResult(bool result)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessCallTerminated()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (executionStarted)
|
||||
{
|
||||
// First notify the call termination to the current component
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
|
||||
|
||||
// Don't wrap around CheckEventResult, because the call has been already disconnected,
|
||||
// and the following action to execute depends on the returned value.
|
||||
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
// Next, if the current component has completed its job, execute the disconnect flow
|
||||
await ExecuteDisconnectFlow();
|
||||
}
|
||||
else if (eventResult == EventResults.Wait)
|
||||
{
|
||||
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
|
||||
// the disconnect handler flow of the callflow next...
|
||||
disconnectFlowPending = true;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Finally, delete temporary files
|
||||
tempWavFileManager.DeleteFilesAndFolders();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTimeout(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// User Defined component
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
public class setStateAndFWDestination : AbsUserComponent
|
||||
{
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
private CfdAppScope scope;
|
||||
|
||||
private ObjectExpressionHandler _strDestNoHandler = null;
|
||||
private ObjectExpressionHandler _strExtensionNoHandler = null;
|
||||
|
||||
|
||||
protected override void InitializeVariables()
|
||||
{
|
||||
componentVariableMap["callflow$.strDestNo"] = new Variable("");
|
||||
componentVariableMap["callflow$.strExtensionNo"] = new Variable("");
|
||||
|
||||
}
|
||||
|
||||
protected override void InitializeComponents()
|
||||
{
|
||||
Dictionary<string, Variable> variableMap = componentVariableMap;
|
||||
{
|
||||
SetExtDNDDest1962110077ECCComponent SetExtDNDDest = new SetExtDNDDest1962110077ECCComponent("SetExtDNDDest", callflow, myCall, logHeader);
|
||||
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strExtNr", () => { return variableMap["callflow$.strExtensionNo"].Value; }));
|
||||
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strDestNr", () => { return variableMap["callflow$.strDestNo"].Value; }));
|
||||
mainFlowComponentList.Add(SetExtDNDDest);
|
||||
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
|
||||
mainFlowComponentList.Add(CreateCondition1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
|
||||
TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetAvailable");
|
||||
SetAvailable.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
|
||||
SetAvailable.ProfileNameHandler = () => { return "Available"; };
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(SetAvailable);
|
||||
PromptPlaybackComponent Weiterleitung_entfernt = scope.CreateComponent<PromptPlaybackComponent>("Weiterleitung_entfernt");
|
||||
Weiterleitung_entfernt.AllowDtmfInput = true;
|
||||
Weiterleitung_entfernt.Prompts.Add(new AudioFilePrompt(() => { return "Weiterleitung wurde entfernt.wav"; }));
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(Weiterleitung_entfernt);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,4)); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
|
||||
TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtCustom2");
|
||||
SetExtCustom2.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
|
||||
SetExtCustom2.ProfileNameHandler = () => { return "Custom 2"; };
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtCustom2);
|
||||
PromptPlaybackComponent PlayDigits = scope.CreateComponent<PromptPlaybackComponent>("PlayDigits");
|
||||
PlayDigits.AllowDtmfInput = true;
|
||||
PlayDigits.Prompts.Add(new AudioFilePrompt(() => { return "IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav"; }));
|
||||
PlayDigits.Prompts.Add(new NumberPrompt(NumberPrompt.NumberFormats.OneByOne, () => { return Convert.ToString(variableMap["callflow$.strDestNo"].Value); }));
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(PlayDigits);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public setStateAndFWDestination(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
|
||||
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
|
||||
{
|
||||
this.onlineServices = onlineServices;
|
||||
this.officeHoursManager = officeHoursManager;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
protected override void GetVariableValues()
|
||||
{
|
||||
if (_strDestNoHandler != null) componentVariableMap["callflow$.strDestNo"].Set(_strDestNoHandler());
|
||||
if (_strExtensionNoHandler != null) componentVariableMap["callflow$.strExtensionNo"].Set(_strExtensionNoHandler());
|
||||
|
||||
}
|
||||
|
||||
public ObjectExpressionHandler strDestNoSetter { set { _strDestNoHandler = value; } }
|
||||
public object strDestNo { get { return componentVariableMap["callflow$.strDestNo"].Value; } }
|
||||
public ObjectExpressionHandler strExtensionNoSetter { set { _strExtensionNoHandler = value; } }
|
||||
public object strExtensionNo { get { return componentVariableMap["callflow$.strExtensionNo"].Value; } }
|
||||
|
||||
|
||||
private bool IsServerInHoliday(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
|
||||
}
|
||||
|
||||
private bool IsServerOfficeHourActive(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
if (tenant == null) return false;
|
||||
|
||||
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
|
||||
if (!String.IsNullOrEmpty(overrideOfficeTime))
|
||||
{
|
||||
if (overrideOfficeTime == "1") // Forced to in office hours
|
||||
return true;
|
||||
else if (overrideOfficeTime == "2") // Forced to out of office hours
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime nowDt = DateTime.Now;
|
||||
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
|
||||
|
||||
Schedule officeHours = tenant.Hours;
|
||||
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
|
||||
return result.GetValueOrDefault(false);
|
||||
}
|
||||
}
|
||||
public class SetExtDNDDest1962110077ECCComponent : ExternalCodeExecutionComponent
|
||||
{
|
||||
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
|
||||
public SetExtDNDDest1962110077ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
|
||||
protected override object ExecuteCode()
|
||||
{
|
||||
return SetExtDNDDest(Convert.ToString(Parameters[0].Value), Convert.ToString(Parameters[1].Value));
|
||||
}
|
||||
|
||||
private object SetExtDNDDest(string strExtNr, string strDestNr)
|
||||
{
|
||||
var ext = PhoneSystem.Root.GetDNByNumber(strExtNr) as Extension;
|
||||
var profile=ext.FwdProfiles.Where( x => x.Name == "Custom 2").First(); // 'Available', 'Away', 'Out of office', 'Custom 1', 'Custom 2', maybe parameter?
|
||||
|
||||
var NewStatus=-1;
|
||||
if( profile != null ) {
|
||||
// DestinationStruct need 3 parameter
|
||||
// 1 DestinationType: 'None', 'VoiceMail', 'Extension', 'Queue', 'RingGroup', 'IVR', 'External', 'Fax', 'Boomerang' (external number),
|
||||
// 'Deflect', 'VoiceMailOfDestination', 'Callback' (reserved), 'RoutePoint'
|
||||
// 2 internal DN, maybe select by parameter - OR -
|
||||
// 3 external number as string, maybe select by parameter
|
||||
|
||||
// DestinationType anhand der Zielnummer (strDestNr) bestimmen
|
||||
bool isInternal = false;
|
||||
bool isExtension = false;
|
||||
bool isRG = false;
|
||||
bool isIVR = false;
|
||||
bool isQUEUE = false;
|
||||
|
||||
// check if strDestNr is a internal extension
|
||||
var extension = PhoneSystem.Root.GetDNByNumber(strDestNr) as Extension;
|
||||
if( extension != null ) {
|
||||
isInternal = true;
|
||||
isExtension = true;
|
||||
}
|
||||
|
||||
// check if strDestNr is a ringgroup
|
||||
var allRGs = PhoneSystem.Root.GetRingGroups();
|
||||
foreach (var rg in allRGs)
|
||||
{
|
||||
if (rg.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isRG = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if strDestNr is a IVR/receptionist
|
||||
var allIVRs = PhoneSystem.Root.GetIVRs();
|
||||
foreach (var ivr in allIVRs)
|
||||
{
|
||||
if (ivr.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isIVR = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if strDestNr is a Queue
|
||||
var allQUEUQs = PhoneSystem.Root.GetQueues();
|
||||
foreach (var queue in allQUEUQs)
|
||||
{
|
||||
if (queue.Number == strDestNr)
|
||||
{
|
||||
isInternal = true;
|
||||
isQUEUE = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// define the DestinationStruct depending on the type of the destination target....
|
||||
|
||||
var dest = new DestinationStruct();
|
||||
if (strExtNr.Equals(strDestNr)) {
|
||||
// set state to available if destination number is the number of the extension
|
||||
NewStatus = 0;
|
||||
}
|
||||
else {
|
||||
if( isInternal) {
|
||||
if( isExtension ) {
|
||||
dest.To=DestinationType.Extension;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isRG ) {
|
||||
|
||||
dest.To=DestinationType.RingGroup;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isIVR ) {
|
||||
|
||||
dest.To=DestinationType.IVR;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
else if( isQUEUE ) {
|
||||
|
||||
dest.To=DestinationType.Queue;
|
||||
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
dest.To=DestinationType.External;
|
||||
dest.External=strDestNr; // needed if external destination
|
||||
|
||||
}
|
||||
|
||||
|
||||
NewStatus = 4;
|
||||
}
|
||||
|
||||
|
||||
if( NewStatus >0)
|
||||
{
|
||||
// Depending on the type of status (present or absent), the forwarding must be entered in other target fields
|
||||
if( profile.TypeOfRouting == RoutingType.Available) {
|
||||
var route=profile.AvailableRoute; // maybe select by parameter?
|
||||
route.NoAnswer.AllCalls = dest;
|
||||
route.NoAnswer.Internal = dest;
|
||||
route.Busy.AllCalls = route.NotRegistered.AllCalls = dest;
|
||||
route.Busy.Internal = route.NotRegistered.Internal = dest;
|
||||
}
|
||||
if( profile.TypeOfRouting == RoutingType.Away) {
|
||||
var route=profile.AwayRoute;
|
||||
var external = profile.AwayRoute.External;
|
||||
route.Internal.AllHours = dest; // maybe select by parameter?
|
||||
route.Internal.OutOfOfficeHours = dest;
|
||||
route.External.AllHours = dest;
|
||||
route.External.OutOfOfficeHours = dest;
|
||||
}
|
||||
ext.Save();
|
||||
}
|
||||
|
||||
}
|
||||
return( NewStatus); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
31
73_dynWeiterleitung_aus/README.md
Normal file
31
73_dynWeiterleitung_aus/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
## Dynamische Weiterleitungen ##
|
||||
|
||||
### 73_dynWeiterleitung_aus ###
|
||||
Das Skript *73 schaltet die Nebenstelle auf den Status "Verfügbar" um um die Weiterleitung zu deaktivieren.
|
||||
|
||||
Nutzungsbeispiel: *73 anrufen um die aktuelle Nebenstelle in den Status Verfügbar umzuschalten (die Weiterleitung zu deaktivieren)
|
||||
|
||||
|
||||
## Funktion auf BLF Tasten von Tischtelefonen legen ##
|
||||
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
|
||||
|
||||
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
|
||||
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72;dtmf=10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
|
||||
|
||||
*Quellen / Nützliche Tools*
|
||||
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
|
||||
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
|
||||
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
|
||||
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)
|
||||
43
73_dynWeiterleitung_aus/setStateAndFWDestination.comp
Normal file
43
73_dynWeiterleitung_aus/setStateAndFWDestination.comp
Normal file
File diff suppressed because one or more lines are too long
51
99_Mailbox_NurAnsage/Main.flow
Normal file
51
99_Mailbox_NurAnsage/Main.flow
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<File>
|
||||
<Version>2.1</Version>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Variable>
|
||||
<Name>ExtensionNo</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>ExtensionIVRPath</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavfile</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavfullpath</Name>
|
||||
<ShowScopeProperty>false</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
</ArrayOfVariable>
|
||||
</Variables>
|
||||
<Flows>
|
||||
<MainFlow>
|
||||
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
|
||||
<ns0:UserComponent PublicProperties="<?xml version="1.0" encoding="utf-16"?><ArrayOfUserProperty xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><UserProperty><Name>DialedExtensionNo</Name><Value xsi:type="xsd:string"></Value></UserProperty></ArrayOfUserProperty>" RelativeFilePath="cGetDialedExtension.comp" Tag="" DebugModeActive="False" x:Name="cGetDialedExtension1" />
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.ExtensionNo" Tag="ExtensionNo" DebugModeActive="False" Expression="cGetDialedExtension1.DialedExtensionNo" x:Name="AssignVariable1" />
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.ExtensionIVRPath" Tag="ExtensionIVRPath" DebugModeActive="False" Expression="CONCATENATE("/var/lib/3cxpbx/Instance1/Data/Ivr/Voicemail/Data/",callflow$.ExtensionNo)" x:Name="AssignVariable2" />
|
||||
<ns0:TcxGetExtensionStatusComponent Tag="" DebugModeActive="False" Extension="callflow$.ExtensionNo" x:Name="GetExtensionStatus1" />
|
||||
<ns0:UserComponent PublicProperties="<?xml version="1.0" encoding="utf-16"?><ArrayOfUserProperty xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><UserProperty><Name>Filename</Name></UserProperty><UserProperty><Name>IVRPath</Name><Value xsi:type="xsd:string">callflow$.ExtensionIVRPath</Value></UserProperty><UserProperty><Name>Profilename</Name><Value xsi:type="xsd:string">GetExtensionStatus1.CurrentProfileName</Value></UserProperty><UserProperty><Name>result_Fullfilename</Name></UserProperty></ArrayOfUserProperty>" RelativeFilePath="cGetVMWavfile.comp" Tag="" DebugModeActive="False" x:Name="cGetVMWavfile1" />
|
||||
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="<?xml version="1.0" encoding="utf-16"?><ArrayOfPrompt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Prompt xsi:type="DynamicAudioFilePrompt"><AudioFileName>cGetVMWavfile1.result_Fullfilename</AudioFileName></Prompt></ArrayOfPrompt>" x:Name="PromptPlayback2" />
|
||||
<ns0:DisconnectCallComponent Tag="" DebugModeActive="False" x:Name="DisconnectCall1" />
|
||||
</ns0:MainFlow>
|
||||
</MainFlow>
|
||||
<ErrorHandlerFlow>
|
||||
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</ErrorHandlerFlow>
|
||||
<DisconnectHandlerFlow>
|
||||
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</DisconnectHandlerFlow>
|
||||
</Flows>
|
||||
</File>
|
||||
810
99_Mailbox_NurAnsage/Output/Release/Script/Main.cs
Normal file
810
99_Mailbox_NurAnsage/Output/Release/Script/Main.cs
Normal file
@ -0,0 +1,810 @@
|
||||
using CallFlow.CFD;
|
||||
using CallFlow;
|
||||
using MimeKit;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System;
|
||||
using TCX.Configuration;
|
||||
|
||||
namespace VMNurAnsage
|
||||
{
|
||||
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
|
||||
{
|
||||
private bool executionStarted;
|
||||
private bool executionFinished;
|
||||
private bool disconnectFlowPending;
|
||||
|
||||
private BufferBlock<AbsEvent> eventBuffer;
|
||||
|
||||
private int currentComponentIndex;
|
||||
private List<AbsComponent> mainFlowComponentList;
|
||||
private List<AbsComponent> disconnectFlowComponentList;
|
||||
private List<AbsComponent> errorFlowComponentList;
|
||||
private List<AbsComponent> currentFlowComponentList;
|
||||
|
||||
private LogFormatter logFormatter;
|
||||
private TimerManager timerManager;
|
||||
private Dictionary<string, Variable> variableMap;
|
||||
private TempWavFileManager tempWavFileManager;
|
||||
private PromptQueue promptQueue;
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
|
||||
private CfdAppScope scope;
|
||||
|
||||
private void DisconnectCallAndExitCallflow()
|
||||
{
|
||||
if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
logFormatter.Trace("Callflow finished...");
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Callflow finished, disconnecting call...");
|
||||
MyCall.Terminate();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteErrorFlow()
|
||||
{
|
||||
if (currentFlowComponentList == errorFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during error handler flow, exiting callflow...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
else if (currentFlowComponentList == disconnectFlowComponentList)
|
||||
{
|
||||
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
|
||||
executionFinished = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentFlowComponentList = errorFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
if (errorFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing error handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Error handler flow is empty...");
|
||||
DisconnectCallAndExitCallflow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteDisconnectFlow()
|
||||
{
|
||||
currentFlowComponentList = disconnectFlowComponentList;
|
||||
currentComponentIndex = 0;
|
||||
disconnectFlowPending = false;
|
||||
if (disconnectFlowComponentList.Count > 0)
|
||||
{
|
||||
logFormatter.Trace("Start executing disconnect handler flow...");
|
||||
await ProcessStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
logFormatter.Trace("Disconnect handler flow is empty...");
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private EventResults CheckEventResult(EventResults eventResult)
|
||||
{
|
||||
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
|
||||
{
|
||||
DisconnectCallAndExitCallflow();
|
||||
return EventResults.Exit;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
DisconnectCallAndExitCallflow();
|
||||
|
||||
return eventResult;
|
||||
}
|
||||
|
||||
private void InitializeVariables(string callID)
|
||||
{
|
||||
// Call variables
|
||||
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
|
||||
variableMap["session.callid"] = new Variable(callID);
|
||||
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
|
||||
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
|
||||
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
|
||||
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
|
||||
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
|
||||
|
||||
// Standard variables
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
// User variables
|
||||
variableMap["callflow$.ExtensionNo"] = new Variable("");
|
||||
variableMap["callflow$.ExtensionIVRPath"] = new Variable("");
|
||||
variableMap["callflow$.wavfile"] = new Variable("");
|
||||
variableMap["callflow$.wavfullpath"] = new Variable("");
|
||||
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
|
||||
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
|
||||
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
|
||||
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
|
||||
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
|
||||
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
|
||||
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
|
||||
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
|
||||
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
|
||||
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
|
||||
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
|
||||
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
|
||||
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
|
||||
|
||||
}
|
||||
|
||||
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
|
||||
{
|
||||
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
|
||||
|
||||
{
|
||||
cGetDialedExtension cGetDialedExtension1 = new cGetDialedExtension(onlineServices, officeHoursManager, scope, "cGetDialedExtension1", callflow, myCall, logHeader);
|
||||
mainFlowComponentList.Add(cGetDialedExtension1);
|
||||
VariableAssignmentComponent AssignVariable1 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable1");
|
||||
AssignVariable1.VariableName = "callflow$.ExtensionNo";
|
||||
AssignVariable1.VariableValueHandler = () => { return cGetDialedExtension1.DialedExtensionNo; };
|
||||
mainFlowComponentList.Add(AssignVariable1);
|
||||
VariableAssignmentComponent AssignVariable2 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable2");
|
||||
AssignVariable2.VariableName = "callflow$.ExtensionIVRPath";
|
||||
AssignVariable2.VariableValueHandler = () => { return CFDFunctions.CONCATENATE(Convert.ToString("/var/lib/3cxpbx/Instance1/Data/Ivr/Voicemail/Data/"),Convert.ToString(variableMap["callflow$.ExtensionNo"].Value)); };
|
||||
mainFlowComponentList.Add(AssignVariable2);
|
||||
TcxGetExtensionStatusComponent GetExtensionStatus1 = scope.CreateComponent<TcxGetExtensionStatusComponent>("GetExtensionStatus1");
|
||||
GetExtensionStatus1.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.ExtensionNo"].Value); };
|
||||
mainFlowComponentList.Add(GetExtensionStatus1);
|
||||
cGetVMWavfile cGetVMWavfile1 = new cGetVMWavfile(onlineServices, officeHoursManager, scope, "cGetVMWavfile1", callflow, myCall, logHeader);
|
||||
cGetVMWavfile1.IVRPathSetter = () => { return variableMap["callflow$.ExtensionIVRPath"].Value; };
|
||||
cGetVMWavfile1.ProfilenameSetter = () => { return GetExtensionStatus1.CurrentProfileName; };
|
||||
mainFlowComponentList.Add(cGetVMWavfile1);
|
||||
PromptPlaybackComponent PromptPlayback2 = scope.CreateComponent<PromptPlaybackComponent>("PromptPlayback2");
|
||||
PromptPlayback2.AllowDtmfInput = true;
|
||||
PromptPlayback2.Prompts.Add(new AudioFilePrompt(() => { return Convert.ToString(cGetVMWavfile1.result_Fullfilename); }));
|
||||
mainFlowComponentList.Add(PromptPlayback2);
|
||||
DisconnectCallComponent DisconnectCall1 = scope.CreateComponent<DisconnectCallComponent>("DisconnectCall1");
|
||||
mainFlowComponentList.Add(DisconnectCall1);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
|
||||
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
|
||||
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
|
||||
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
|
||||
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
|
||||
}
|
||||
|
||||
public Main()
|
||||
{
|
||||
this.executionStarted = false;
|
||||
this.executionFinished = false;
|
||||
this.disconnectFlowPending = false;
|
||||
|
||||
this.eventBuffer = new BufferBlock<AbsEvent>();
|
||||
|
||||
this.currentComponentIndex = 0;
|
||||
this.mainFlowComponentList = new List<AbsComponent>();
|
||||
this.disconnectFlowComponentList = new List<AbsComponent>();
|
||||
this.errorFlowComponentList = new List<AbsComponent>();
|
||||
this.currentFlowComponentList = mainFlowComponentList;
|
||||
|
||||
this.timerManager = new TimerManager();
|
||||
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
|
||||
this.variableMap = new Dictionary<string, Variable>();
|
||||
|
||||
AbsTextToSpeechEngine textToSpeechEngine = null;
|
||||
AbsSpeechToTextEngine speechToTextEngine = null;
|
||||
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
string callID = MyCall?.Caller["chid"] ?? "Unknown";
|
||||
string logHeader = $"VMNurAnsage - CallID {callID}";
|
||||
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
|
||||
this.promptQueue = new PromptQueue(this, MyCall, "VMNurAnsage", logHeader);
|
||||
this.tempWavFileManager = new TempWavFileManager(logFormatter);
|
||||
this.timerManager.CallStarted();
|
||||
this.officeHoursManager = new OfficeHoursManager(MyCall);
|
||||
|
||||
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
|
||||
|
||||
if (MyCall.Status == ConnectionStatus.Ringing)
|
||||
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
|
||||
else
|
||||
StartInternal(logHeader, callID);
|
||||
}
|
||||
|
||||
private void StartInternal(string logHeader, string callID)
|
||||
{
|
||||
logFormatter.Trace("SetBackgroundAudio to false");
|
||||
MyCall.SetBackgroundAudio(false, new string[] { });
|
||||
|
||||
logFormatter.Trace("Initialize components");
|
||||
InitializeComponents(this, MyCall, logHeader);
|
||||
logFormatter.Trace("Initialize variables");
|
||||
InitializeVariables(callID);
|
||||
|
||||
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
|
||||
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
|
||||
|
||||
logFormatter.Trace("Start executing main flow...");
|
||||
eventBuffer.Post(new StartEvent());
|
||||
Task.Run(() => EventProcessingLoop());
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void PostStartEvent()
|
||||
{
|
||||
eventBuffer.Post(new StartEvent());
|
||||
}
|
||||
|
||||
public void PostDTMFReceivedEvent(char digit)
|
||||
{
|
||||
eventBuffer.Post(new DTMFReceivedEvent(digit));
|
||||
}
|
||||
|
||||
public void PostPromptPlayedEvent()
|
||||
{
|
||||
eventBuffer.Post(new PromptPlayedEvent());
|
||||
}
|
||||
|
||||
public void PostTransferFailedEvent()
|
||||
{
|
||||
eventBuffer.Post(new TransferFailedEvent());
|
||||
}
|
||||
|
||||
public void PostMakeCallResultEvent(bool result)
|
||||
{
|
||||
eventBuffer.Post(new MakeCallResultEvent(result));
|
||||
}
|
||||
|
||||
public void PostCallTerminatedEvent()
|
||||
{
|
||||
eventBuffer.Post(new CallTerminatedEvent());
|
||||
}
|
||||
|
||||
public void PostTimeoutEvent(object state)
|
||||
{
|
||||
eventBuffer.Post(new TimeoutEvent(state));
|
||||
}
|
||||
|
||||
private async Task EventProcessingLoop()
|
||||
{
|
||||
executionStarted = true;
|
||||
while (!executionFinished)
|
||||
{
|
||||
AbsEvent evt = await eventBuffer.ReceiveAsync();
|
||||
await evt?.ProcessEvent(this);
|
||||
}
|
||||
|
||||
if (scope != null) scope.Dispose();
|
||||
}
|
||||
|
||||
public async Task ProcessStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
EventResults eventResult;
|
||||
do
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
|
||||
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
}
|
||||
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
|
||||
|
||||
if (eventResult == EventResults.Exit) executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessDTMFReceived(char digit)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessPromptPlayed()
|
||||
{
|
||||
try
|
||||
{
|
||||
promptQueue.NotifyPlayFinished();
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTransferFailed()
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessMakeCallResult(bool result)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessCallTerminated()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (executionStarted)
|
||||
{
|
||||
// First notify the call termination to the current component
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
|
||||
|
||||
// Don't wrap around CheckEventResult, because the call has been already disconnected,
|
||||
// and the following action to execute depends on the returned value.
|
||||
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
// Next, if the current component has completed its job, execute the disconnect flow
|
||||
await ExecuteDisconnectFlow();
|
||||
}
|
||||
else if (eventResult == EventResults.Wait)
|
||||
{
|
||||
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
|
||||
// the disconnect handler flow of the callflow next...
|
||||
disconnectFlowPending = true;
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Finally, delete temporary files
|
||||
tempWavFileManager.DeleteFilesAndFolders();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessTimeout(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
|
||||
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
|
||||
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
|
||||
if (eventResult == EventResults.MoveToNextComponent)
|
||||
{
|
||||
if (disconnectFlowPending)
|
||||
await ExecuteDisconnectFlow();
|
||||
else
|
||||
await ProcessStart();
|
||||
}
|
||||
else if (eventResult == EventResults.Exit)
|
||||
executionFinished = true;
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logFormatter.Error("Error executing last component: " + exc.ToString());
|
||||
await ExecuteErrorFlow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// User Defined component
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
public class cGetDialedExtension : AbsUserComponent
|
||||
{
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
private CfdAppScope scope;
|
||||
|
||||
private ObjectExpressionHandler _DialedExtensionNoHandler = null;
|
||||
|
||||
|
||||
protected override void InitializeVariables()
|
||||
{
|
||||
componentVariableMap["callflow$.DialedExtensionNo"] = new Variable("");
|
||||
|
||||
}
|
||||
|
||||
protected override void InitializeComponents()
|
||||
{
|
||||
Dictionary<string, Variable> variableMap = componentVariableMap;
|
||||
{
|
||||
ExecuteCSharpCode1864467505ECCComponent ExecuteCSharpCode1 = new ExecuteCSharpCode1864467505ECCComponent("ExecuteCSharpCode1", callflow, myCall, logHeader);
|
||||
mainFlowComponentList.Add(ExecuteCSharpCode1);
|
||||
VariableAssignmentComponent AssignVariable1 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable1");
|
||||
AssignVariable1.VariableName = "callflow$.DialedExtensionNo";
|
||||
AssignVariable1.VariableValueHandler = () => { return ExecuteCSharpCode1.ReturnValue; };
|
||||
mainFlowComponentList.Add(AssignVariable1);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public cGetDialedExtension(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
|
||||
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
|
||||
{
|
||||
this.onlineServices = onlineServices;
|
||||
this.officeHoursManager = officeHoursManager;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
protected override void GetVariableValues()
|
||||
{
|
||||
if (_DialedExtensionNoHandler != null) componentVariableMap["callflow$.DialedExtensionNo"].Set(_DialedExtensionNoHandler());
|
||||
|
||||
}
|
||||
|
||||
public ObjectExpressionHandler DialedExtensionNoSetter { set { _DialedExtensionNoHandler = value; } }
|
||||
public object DialedExtensionNo { get { return componentVariableMap["callflow$.DialedExtensionNo"].Value; } }
|
||||
|
||||
|
||||
private bool IsServerInHoliday(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
|
||||
}
|
||||
|
||||
private bool IsServerOfficeHourActive(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
if (tenant == null) return false;
|
||||
|
||||
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
|
||||
if (!String.IsNullOrEmpty(overrideOfficeTime))
|
||||
{
|
||||
if (overrideOfficeTime == "1") // Forced to in office hours
|
||||
return true;
|
||||
else if (overrideOfficeTime == "2") // Forced to out of office hours
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime nowDt = DateTime.Now;
|
||||
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
|
||||
|
||||
Schedule officeHours = tenant.Hours;
|
||||
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
|
||||
return result.GetValueOrDefault(false);
|
||||
}
|
||||
}
|
||||
public class ExecuteCSharpCode1864467505ECCComponent : ExternalCodeExecutionComponent
|
||||
{
|
||||
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
|
||||
public ExecuteCSharpCode1864467505ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
|
||||
protected override object ExecuteCode()
|
||||
{
|
||||
return test();
|
||||
}
|
||||
|
||||
private object test()
|
||||
{
|
||||
// https://www.3cx.com/community/threads/get-extension-by-called-number.132441/post-630907
|
||||
string retval="0";
|
||||
if(myCall.Caller.DN is ExternalLine externalLine && myCall.IsInbound)
|
||||
{
|
||||
string[] range;
|
||||
foreach (var a in externalLine.RoutingRules)
|
||||
{
|
||||
bool match = (a.Conditions.Condition.Type == RuleConditionType.BasedOnDID &&
|
||||
(
|
||||
a.Data == myCall.Caller.CalledNumber
|
||||
|| (a.Data.StartsWith('*') && myCall.Caller.CalledNumber.EndsWith(a.Data[1..]))
|
||||
))
|
||||
||
|
||||
(
|
||||
a.Conditions.Condition.Type == RuleConditionType.BasedOnCallerID &&
|
||||
a.Data.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Any
|
||||
(x =>
|
||||
x == "*"
|
||||
|| x == myCall.Caller.CallerID
|
||||
|| x.StartsWith('*') && myCall.Caller.CallerID.EndsWith(x[1..])
|
||||
|| x.EndsWith('*') && myCall.Caller.CallerID.StartsWith(x[..^1])
|
||||
|| x.StartsWith('*') && x.EndsWith('*') && myCall.Caller.CallerID.Contains(x[1..^1])
|
||||
|| ((range = x.Split("-", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).Length == 2 ?
|
||||
(range[0].Length == myCall.Caller.CallerID.Length && range[1].Length == myCall.Caller.CallerID.Length) && (range[0].CompareTo(myCall.Caller.CallerID) <= 0 && range[1].CompareTo(myCall.Caller.CallerID) >= 0) : false)
|
||||
)
|
||||
)
|
||||
||
|
||||
(a.Conditions.Condition.Type == RuleConditionType.ForwardAll);
|
||||
|
||||
if (match)
|
||||
{
|
||||
retval=(a.ForwardDestinations.OfficeHoursDestination).Internal?.Number?? "0";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
retval=myCall.Caller.AttachedData["extnumber"]?? "0";
|
||||
}
|
||||
return retval; }
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
// User Defined component
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
public class cGetVMWavfile : AbsUserComponent
|
||||
{
|
||||
private OnlineServices onlineServices;
|
||||
private OfficeHoursManager officeHoursManager;
|
||||
private CfdAppScope scope;
|
||||
|
||||
private ObjectExpressionHandler _IVRPathHandler = null;
|
||||
private ObjectExpressionHandler _ProfilenameHandler = null;
|
||||
private ObjectExpressionHandler _result_FullfilenameHandler = null;
|
||||
private ObjectExpressionHandler _wavDefaultHandler = null;
|
||||
private ObjectExpressionHandler _wavAvailableHandler = null;
|
||||
private ObjectExpressionHandler _wavAwayHandler = null;
|
||||
private ObjectExpressionHandler _wavOoOHandler = null;
|
||||
private ObjectExpressionHandler _wavCustom1Handler = null;
|
||||
private ObjectExpressionHandler _wavCustom2Handler = null;
|
||||
private ObjectExpressionHandler _FilenameHandler = null;
|
||||
|
||||
|
||||
protected override void InitializeVariables()
|
||||
{
|
||||
componentVariableMap["callflow$.IVRPath"] = new Variable("");
|
||||
componentVariableMap["callflow$.Profilename"] = new Variable("");
|
||||
componentVariableMap["callflow$.result_Fullfilename"] = new Variable("");
|
||||
componentVariableMap["callflow$.wavDefault"] = new Variable("");
|
||||
componentVariableMap["callflow$.wavAvailable"] = new Variable("");
|
||||
componentVariableMap["callflow$.wavAway"] = new Variable("");
|
||||
componentVariableMap["callflow$.wavOoO"] = new Variable("");
|
||||
componentVariableMap["callflow$.wavCustom1"] = new Variable("");
|
||||
componentVariableMap["callflow$.wavCustom2"] = new Variable("");
|
||||
componentVariableMap["callflow$.Filename"] = new Variable("");
|
||||
|
||||
}
|
||||
|
||||
protected override void InitializeComponents()
|
||||
{
|
||||
Dictionary<string, Variable> variableMap = componentVariableMap;
|
||||
{
|
||||
FileManagementComponent ReadWriteFile1 = scope.CreateComponent<FileManagementComponent>("ReadWriteFile1");
|
||||
ReadWriteFile1.Action = FileManagementComponent.Actions.Read;
|
||||
ReadWriteFile1.FileMode = System.IO.FileMode.Open;
|
||||
ReadWriteFile1.FileNameHandler = () => { return Convert.ToString(CFDFunctions.CONCATENATE(Convert.ToString(variableMap["callflow$.IVRPath"].Value),Convert.ToString("/greetings.xml"))); };
|
||||
ReadWriteFile1.FirstLineToReadHandler = () => { return Convert.ToInt32(0); };
|
||||
ReadWriteFile1.ReadToEndHandler = () => { return Convert.ToBoolean(true); };
|
||||
mainFlowComponentList.Add(ReadWriteFile1);
|
||||
TextAnalyzerComponent JsonXmlParser1 = scope.CreateComponent<TextAnalyzerComponent>("JsonXmlParser1");
|
||||
JsonXmlParser1.TextType = TextAnalyzerComponent.TextTypes.XML;
|
||||
JsonXmlParser1.TextHandler = () => { return Convert.ToString(ReadWriteFile1.Result); };
|
||||
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='default']/@file)", "callflow$.wavDefault");
|
||||
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Available']/@file)", "callflow$.wavAvailable");
|
||||
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Away']/@file)", "callflow$.wavAway");
|
||||
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Out of office']/@file)", "callflow$.wavOoO");
|
||||
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Custom 1']/@file)", "callflow$.wavCustom1");
|
||||
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Custom 2']/@file)", "callflow$.wavCustom2");
|
||||
mainFlowComponentList.Add(JsonXmlParser1);
|
||||
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
|
||||
mainFlowComponentList.Add(CreateCondition1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Available")); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("cond1"));
|
||||
VariableAssignmentComponent AssignVariable1 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable1");
|
||||
AssignVariable1.VariableName = "callflow$.Filename";
|
||||
AssignVariable1.VariableValueHandler = () => { return variableMap["callflow$.wavAvailable"].Value; };
|
||||
CreateCondition1.ContainerList[0].ComponentList.Add(AssignVariable1);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Away")); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
|
||||
VariableAssignmentComponent AssignVariable2 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable2");
|
||||
AssignVariable2.VariableName = "callflow$.Filename";
|
||||
AssignVariable2.VariableValueHandler = () => { return variableMap["callflow$.wavAway"].Value; };
|
||||
CreateCondition1.ContainerList[1].ComponentList.Add(AssignVariable2);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Out of office")); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
|
||||
VariableAssignmentComponent AssignVariable3 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable3");
|
||||
AssignVariable3.VariableName = "callflow$.Filename";
|
||||
AssignVariable3.VariableValueHandler = () => { return variableMap["callflow$.wavOoO"].Value; };
|
||||
CreateCondition1.ContainerList[2].ComponentList.Add(AssignVariable3);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Custom 1")); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch3"));
|
||||
VariableAssignmentComponent AssignVariable4 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable4");
|
||||
AssignVariable4.VariableName = "callflow$.Filename";
|
||||
AssignVariable4.VariableValueHandler = () => { return variableMap["callflow$.wavCustom1"].Value; };
|
||||
CreateCondition1.ContainerList[3].ComponentList.Add(AssignVariable4);
|
||||
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Custom 2")); });
|
||||
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch4"));
|
||||
VariableAssignmentComponent AssignVariable5 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable5");
|
||||
AssignVariable5.VariableName = "callflow$.Filename";
|
||||
AssignVariable5.VariableValueHandler = () => { return variableMap["callflow$.wavCustom2"].Value; };
|
||||
CreateCondition1.ContainerList[4].ComponentList.Add(AssignVariable5);
|
||||
ConditionalComponent CreateCondition2 = scope.CreateComponent<ConditionalComponent>("CreateCondition2");
|
||||
mainFlowComponentList.Add(CreateCondition2);
|
||||
CreateCondition2.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Filename"].Value,"")); });
|
||||
CreateCondition2.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch5"));
|
||||
VariableAssignmentComponent variableAssignmentComponent1 = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentComponent1");
|
||||
variableAssignmentComponent1.VariableName = "callflow$.Filename";
|
||||
variableAssignmentComponent1.VariableValueHandler = () => { return variableMap["callflow$.wavDefault"].Value; };
|
||||
CreateCondition2.ContainerList[0].ComponentList.Add(variableAssignmentComponent1);
|
||||
VariableAssignmentComponent AssignVariable6 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable6");
|
||||
AssignVariable6.VariableName = "callflow$.result_Fullfilename";
|
||||
AssignVariable6.VariableValueHandler = () => { return CFDFunctions.CONCATENATE(Convert.ToString(variableMap["callflow$.IVRPath"].Value),Convert.ToString("/"),Convert.ToString(variableMap["callflow$.Filename"].Value)); };
|
||||
mainFlowComponentList.Add(AssignVariable6);
|
||||
}
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public cGetVMWavfile(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
|
||||
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
|
||||
{
|
||||
this.onlineServices = onlineServices;
|
||||
this.officeHoursManager = officeHoursManager;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
protected override void GetVariableValues()
|
||||
{
|
||||
if (_IVRPathHandler != null) componentVariableMap["callflow$.IVRPath"].Set(_IVRPathHandler());
|
||||
if (_ProfilenameHandler != null) componentVariableMap["callflow$.Profilename"].Set(_ProfilenameHandler());
|
||||
if (_result_FullfilenameHandler != null) componentVariableMap["callflow$.result_Fullfilename"].Set(_result_FullfilenameHandler());
|
||||
if (_wavDefaultHandler != null) componentVariableMap["callflow$.wavDefault"].Set(_wavDefaultHandler());
|
||||
if (_wavAvailableHandler != null) componentVariableMap["callflow$.wavAvailable"].Set(_wavAvailableHandler());
|
||||
if (_wavAwayHandler != null) componentVariableMap["callflow$.wavAway"].Set(_wavAwayHandler());
|
||||
if (_wavOoOHandler != null) componentVariableMap["callflow$.wavOoO"].Set(_wavOoOHandler());
|
||||
if (_wavCustom1Handler != null) componentVariableMap["callflow$.wavCustom1"].Set(_wavCustom1Handler());
|
||||
if (_wavCustom2Handler != null) componentVariableMap["callflow$.wavCustom2"].Set(_wavCustom2Handler());
|
||||
if (_FilenameHandler != null) componentVariableMap["callflow$.Filename"].Set(_FilenameHandler());
|
||||
|
||||
}
|
||||
|
||||
public ObjectExpressionHandler IVRPathSetter { set { _IVRPathHandler = value; } }
|
||||
public object IVRPath { get { return componentVariableMap["callflow$.IVRPath"].Value; } }
|
||||
public ObjectExpressionHandler ProfilenameSetter { set { _ProfilenameHandler = value; } }
|
||||
public object Profilename { get { return componentVariableMap["callflow$.Profilename"].Value; } }
|
||||
public ObjectExpressionHandler result_FullfilenameSetter { set { _result_FullfilenameHandler = value; } }
|
||||
public object result_Fullfilename { get { return componentVariableMap["callflow$.result_Fullfilename"].Value; } }
|
||||
public ObjectExpressionHandler wavDefaultSetter { set { _wavDefaultHandler = value; } }
|
||||
public object wavDefault { get { return componentVariableMap["callflow$.wavDefault"].Value; } }
|
||||
public ObjectExpressionHandler wavAvailableSetter { set { _wavAvailableHandler = value; } }
|
||||
public object wavAvailable { get { return componentVariableMap["callflow$.wavAvailable"].Value; } }
|
||||
public ObjectExpressionHandler wavAwaySetter { set { _wavAwayHandler = value; } }
|
||||
public object wavAway { get { return componentVariableMap["callflow$.wavAway"].Value; } }
|
||||
public ObjectExpressionHandler wavOoOSetter { set { _wavOoOHandler = value; } }
|
||||
public object wavOoO { get { return componentVariableMap["callflow$.wavOoO"].Value; } }
|
||||
public ObjectExpressionHandler wavCustom1Setter { set { _wavCustom1Handler = value; } }
|
||||
public object wavCustom1 { get { return componentVariableMap["callflow$.wavCustom1"].Value; } }
|
||||
public ObjectExpressionHandler wavCustom2Setter { set { _wavCustom2Handler = value; } }
|
||||
public object wavCustom2 { get { return componentVariableMap["callflow$.wavCustom2"].Value; } }
|
||||
public ObjectExpressionHandler FilenameSetter { set { _FilenameHandler = value; } }
|
||||
public object Filename { get { return componentVariableMap["callflow$.Filename"].Value; } }
|
||||
|
||||
|
||||
private bool IsServerInHoliday(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
|
||||
}
|
||||
|
||||
private bool IsServerOfficeHourActive(ICall myCall)
|
||||
{
|
||||
Tenant tenant = myCall.PS.GetTenant();
|
||||
if (tenant == null) return false;
|
||||
|
||||
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
|
||||
if (!String.IsNullOrEmpty(overrideOfficeTime))
|
||||
{
|
||||
if (overrideOfficeTime == "1") // Forced to in office hours
|
||||
return true;
|
||||
else if (overrideOfficeTime == "2") // Forced to out of office hours
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime nowDt = DateTime.Now;
|
||||
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
|
||||
|
||||
Schedule officeHours = tenant.Hours;
|
||||
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
|
||||
return result.GetValueOrDefault(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
BIN
99_Mailbox_NurAnsage/Output/Release/VMNurAnsage.zip
Normal file
BIN
99_Mailbox_NurAnsage/Output/Release/VMNurAnsage.zip
Normal file
Binary file not shown.
10
99_Mailbox_NurAnsage/README.md
Normal file
10
99_Mailbox_NurAnsage/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# 3CX
|
||||
|
||||
|
||||
### 99_Mailbox_NurAnsage ###
|
||||
Das Skript *99 stellt eine Ansage ohne Aufnahmemöglichkeit zur Verfügung, die der Nutzer (im Gegensatz zur IVR Lösung) selbst besprechen/verändern kann.
|
||||
Der Aufruf erfolgt als Ziel über die Anrufweiterleitung in der Nebenstelle. Die Ansagen werden aus den im Status des Benutzers hinterlegten Ansagen bezogen.
|
||||
Ist dort keine gefüllt, so erfolgt ein Fallback auf die Standardansage (Register Mailbox).
|
||||
|
||||
Da in v20 die ursprünglich angerufene Nebenstelle als Quelle der Ansagen nur indirekt mit schwarzer Magie ermittelt werden kann,
|
||||
kann dieses CFA nur bei direkter Weiterleitung aus der Nebenstelle (10 -> *99) verwendet werden. Also kein Nebenstellenhopping (10->11->*99), nicht als IVR Ziel, ...)
|
||||
32
99_Mailbox_NurAnsage/VMNurAnsage.cfdproj
Normal file
32
99_Mailbox_NurAnsage/VMNurAnsage.cfdproj
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Graphical_Application_Designer_Project>
|
||||
<Version>2.1</Version>
|
||||
<Files>
|
||||
<File path="cGetDialedExtension.comp" type="component" />
|
||||
<File path="cGetVMWavfile.comp" type="component" />
|
||||
<File path="Main.flow" type="callflow" />
|
||||
</Files>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
|
||||
</Variables>
|
||||
<DebugBuildSuccessful>False</DebugBuildSuccessful>
|
||||
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
|
||||
<DebugBuildNumber>0</DebugBuildNumber>
|
||||
<ReleaseBuildNumber>43</ReleaseBuildNumber>
|
||||
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
|
||||
<DoNotAskForExtension>True</DoNotAskForExtension>
|
||||
<Extension>*99</Extension>
|
||||
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
|
||||
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
|
||||
<AmazonClientID>
|
||||
</AmazonClientID>
|
||||
<AmazonClientSecret>
|
||||
</AmazonClientSecret>
|
||||
<AmazonRegion>us-east-2</AmazonRegion>
|
||||
<AmazonLexicons>
|
||||
</AmazonLexicons>
|
||||
<GoogleCloudServiceAccountKeyFileName>
|
||||
</GoogleCloudServiceAccountKeyFileName>
|
||||
<GoogleCloudServiceAccountKeyJSON>
|
||||
</GoogleCloudServiceAccountKeyJSON>
|
||||
</Graphical_Application_Designer_Project>
|
||||
28
99_Mailbox_NurAnsage/cGetDialedExtension.comp
Normal file
28
99_Mailbox_NurAnsage/cGetDialedExtension.comp
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<File>
|
||||
<Version>2.1</Version>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Variable>
|
||||
<Name>DialedExtensionNo</Name>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
</ArrayOfVariable>
|
||||
</Variables>
|
||||
<Flows>
|
||||
<MainFlow>
|
||||
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="cGetDialedExtension" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
|
||||
<ns0:ExecuteCSharpCodeComponent ReturnsValue="True" Code="// https://www.3cx.com/community/threads/get-extension-by-called-number.132441/post-630907
string retval="0";
if(myCall.Caller.DN is ExternalLine externalLine && myCall.IsInbound)
{
 string[] range;
 foreach (var a in externalLine.RoutingRules)
 {
 bool match = (a.Conditions.Condition.Type == RuleConditionType.BasedOnDID &&
 (
 a.Data == myCall.Caller.CalledNumber
 || (a.Data.StartsWith('*') && myCall.Caller.CalledNumber.EndsWith(a.Data[1..]))
 ))
 ||
 (
 a.Conditions.Condition.Type == RuleConditionType.BasedOnCallerID &&
 a.Data.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
 .Any
 (x =>
 x == "*"
 || x == myCall.Caller.CallerID
 || x.StartsWith('*') && myCall.Caller.CallerID.EndsWith(x[1..])
 || x.EndsWith('*') && myCall.Caller.CallerID.StartsWith(x[..^1])
 || x.StartsWith('*') && x.EndsWith('*') && myCall.Caller.CallerID.Contains(x[1..^1])
 || ((range = x.Split("-", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).Length == 2 ?
 (range[0].Length == myCall.Caller.CallerID.Length && range[1].Length == myCall.Caller.CallerID.Length) && (range[0].CompareTo(myCall.Caller.CallerID) <= 0 && range[1].CompareTo(myCall.Caller.CallerID) >= 0) : false)
 )
 )
 ||
 (a.Conditions.Condition.Type == RuleConditionType.ForwardAll);
 
 if (match)
 {
 retval=(a.ForwardDestinations.OfficeHoursDestination).Internal?.Number?? "0";
 break;
 }
 }
}
else
{
 retval=myCall.Caller.AttachedData["extnumber"]?? "0";
}
return retval;" ParameterList="<?xml version="1.0" encoding="utf-16"?><ArrayOfScriptParameter xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />" MethodName="test" Tag="" DebugModeActive="False" x:Name="ExecuteCSharpCode1" />
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.DialedExtensionNo" Tag="" DebugModeActive="False" Expression="ExecuteCSharpCode1.ReturnValue" x:Name="AssignVariable1" />
|
||||
</ns0:MainFlow>
|
||||
</MainFlow>
|
||||
<ErrorHandlerFlow>
|
||||
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="cGetDialedExtension" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</ErrorHandlerFlow>
|
||||
<DisconnectHandlerFlow>
|
||||
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="cGetDialedExtension" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</DisconnectHandlerFlow>
|
||||
</Flows>
|
||||
</File>
|
||||
112
99_Mailbox_NurAnsage/cGetVMWavfile.comp
Normal file
112
99_Mailbox_NurAnsage/cGetVMWavfile.comp
Normal file
@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<File>
|
||||
<Version>2.1</Version>
|
||||
<Variables>
|
||||
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Variable>
|
||||
<Name>IVRPath</Name>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>Profilename</Name>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>result_Fullfilename</Name>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavDefault</Name>
|
||||
<Scope>Private</Scope>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavAvailable</Name>
|
||||
<Scope>Private</Scope>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavAway</Name>
|
||||
<Scope>Private</Scope>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavOoO</Name>
|
||||
<Scope>Private</Scope>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavCustom1</Name>
|
||||
<Scope>Private</Scope>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>wavCustom2</Name>
|
||||
<Scope>Private</Scope>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
<Variable>
|
||||
<Name>Filename</Name>
|
||||
<Scope>Private</Scope>
|
||||
<ShowScopeProperty>true</ShowScopeProperty>
|
||||
<DebuggerVisible>true</DebuggerVisible>
|
||||
<HelpText />
|
||||
</Variable>
|
||||
</ArrayOfVariable>
|
||||
</Variables>
|
||||
<Flows>
|
||||
<MainFlow>
|
||||
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="cGetVMWavfile" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
|
||||
<ns0:FileManagementComponent AppendFinalCrLf="True" Action="Read" x:Name="ReadWriteFile1" FileName="CONCATENATE(callflow$.IVRPath,"/greetings.xml")" ReadToEnd="true" Content="" Tag="Greetings.xml" FirstLineToRead="0" OpenMode="Open" DebugModeActive="False" LinesToRead="" />
|
||||
<ns0:JsonXmlParserComponent ResponseMappingsList="<?xml version="1.0" encoding="utf-16"?><ArrayOfResponseMapping xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><ResponseMapping><Path>string(/overrides/greeting[@profile='default']/@file)</Path><Variable>callflow$.wavDefault</Variable></ResponseMapping><ResponseMapping><Path>string(/overrides/greeting[@profile='Available']/@file)</Path><Variable>callflow$.wavAvailable</Variable></ResponseMapping><ResponseMapping><Path>string(/overrides/greeting[@profile='Away']/@file)</Path><Variable>callflow$.wavAway</Variable></ResponseMapping><ResponseMapping><Path>string(/overrides/greeting[@profile='Out of office']/@file)</Path><Variable>callflow$.wavOoO</Variable></ResponseMapping><ResponseMapping><Path>string(/overrides/greeting[@profile='Custom 1']/@file)</Path><Variable>callflow$.wavCustom1</Variable></ResponseMapping><ResponseMapping><Path>string(/overrides/greeting[@profile='Custom 2']/@file)</Path><Variable>callflow$.wavCustom2</Variable></ResponseMapping></ArrayOfResponseMapping>" Input="ReadWriteFile1.Result" Tag="VMdefaultWav" DebugModeActive="False" TextType="XML" x:Name="JsonXmlParser1" />
|
||||
<ns0:ConditionalComponent Tag="" DebugModeActive="False" x:Name="CreateCondition1">
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,"Available")" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="cond1">
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavAvailable" x:Name="AssignVariable1" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,"Away")" Description="Execution path when the specified condition is met." Tag="Away" DebugModeActive="False" x:Name="conditionalComponentBranch1">
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavAway" x:Name="AssignVariable2" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,"Out of office")" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="conditionalComponentBranch2">
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavOoO" x:Name="AssignVariable3" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,"Custom 1")" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="conditionalComponentBranch3">
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavCustom1" x:Name="AssignVariable4" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,"Custom 2")" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="conditionalComponentBranch4">
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavCustom2" x:Name="AssignVariable5" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
</ns0:ConditionalComponent>
|
||||
<ns0:ConditionalComponent Tag="" DebugModeActive="False" x:Name="CreateCondition2">
|
||||
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Filename,"")" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch5">
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavDefault" x:Name="variableAssignmentComponent1" />
|
||||
</ns0:ConditionalComponentBranch>
|
||||
</ns0:ConditionalComponent>
|
||||
<ns0:VariableAssignmentComponent VariableName="callflow$.result_Fullfilename" Tag="result_FullFilename" DebugModeActive="False" Expression="CONCATENATE(callflow$.IVRPath,"/",callflow$.Filename)" x:Name="AssignVariable6" />
|
||||
</ns0:MainFlow>
|
||||
</MainFlow>
|
||||
<ErrorHandlerFlow>
|
||||
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="cGetVMWavfile" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</ErrorHandlerFlow>
|
||||
<DisconnectHandlerFlow>
|
||||
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="cGetVMWavfile" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
|
||||
</DisconnectHandlerFlow>
|
||||
</Flows>
|
||||
</File>
|
||||
71
README.md
71
README.md
@ -1,2 +1,71 @@
|
||||
# 3CX_CFD
|
||||
|
||||
Die importierebaren ZIP Dateien liegen im Ordner /RELEASE
|
||||
|
||||
## Profilstatus wechseln ##
|
||||
### 39_Profilstatus_mitExtension ###
|
||||
Das Skript *39 ermöglicht das wechseln des Profilstatus einer beliebigen Nebenstelle
|
||||
Mittels DTMF wird zuerst die Nebenstelle abgefragt und dann die ID des Status (0-4, wobei 0 = Verfügbar).
|
||||
Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
|
||||
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
|
||||
|
||||
Nutzungsbeispiel: *39 anrufen, "40#2#" eingeben um für die Nebenstelle 40 auf "Bitte nicht stören umzuschalten.
|
||||
|
||||
|
||||
## 99_Mailbox_NurAnsage ##
|
||||
Das Skript *99 stellt eine Ansage ohne Aufnahmemöglichkeit zur Verfügung, die der Nutzer (im Gegensatz zur IVR Lösung) selbst besprechen/verändern kann.
|
||||
Der Aufruf erfolgt als Ziel über die Anrufweiterleitung in der Nebenstelle.
|
||||
Die Ansagen werden aus den im Status des Benutzers hinterlegten Ansagen bezogen.
|
||||
Ist dort keine gefüllt, so erfolgt ein Fallback auf die Standardansage (Register Mailbox).
|
||||
|
||||
Da in v20 die ursprünglich angerufene Nebenstelle als Quelle der Ansagen nur indirekt mit schwarzer Magie ermittelt werden kann,
|
||||
kann dieses CFA nur bei direkter Weiterleitung aus der Nebenstelle (10 -> *99) verwendet werden. Also kein Nebenstellenhopping (10->11->*99), nicht als IVR Ziel, ...)
|
||||
|
||||
## Dynamische Weiterleitungen ##
|
||||
### 72_dynWeiterleitung_an ###
|
||||
Das Skript *72 fragt mittels DTFM die Zielrufnummer der Weiterleitung ab, trägt diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" und schaltet den Status der anrufenden Nebenstelle auf diesen um.
|
||||
Das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
|
||||
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
|
||||
|
||||
Nutzungsbeispiel: *72 anrufen, "0172123456#" eingeben um eine Weiterleitung auf die Nummer 0172123456 einzurichten
|
||||
|
||||
### 73_dynWeiterleitung_aus ###
|
||||
Das Skript *73 schaltet die Nebenstelle auf den Status "Verfügbar" um um die Weiterleitung zu deaktivieren.
|
||||
|
||||
Nutzungsbeispiel: *73 anrufen um die aktuelle Nebenstelle in den Status Verfügbar umzuschalten (die Weiterleitung zu deaktivieren)
|
||||
|
||||
|
||||
### 721_dynWeiterleitung_mitExtension_an ###
|
||||
Das Skript *721 arbeitet wie 72_dynWeiterleitung_an, allerdings wird zuerst die zu ändernde Nebenstelle mittels DTMF abgefragt.
|
||||
Anschließend wird mittels DTFM die Zielrufnummer der Weiterleitung abgefragt, diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" eingetragen
|
||||
und der Status der angegebenen Nebenstelle auf diesen umgeschaltet.
|
||||
Die Nebenstelle und das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
|
||||
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
|
||||
|
||||
Nutzungsbeispiel: *721 anrufen, "80#83#" eingeben um für die Nebenstelle 80 eine Weiterleitung auf die Nummer IVR 83 einzurichten
|
||||
Dies kann z.B. verwendet werden um die Dummy Nebenstelle 80 zwischen dem Anrufbeantworter "81 TAG", dem Anrufbeantworter "82 Nacht" oder "83 Urlaub" umzuschalten.
|
||||
|
||||
|
||||
|
||||
|
||||
## Funktion auf BLF Tasten von Tischtelefonen legen ##
|
||||
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
|
||||
|
||||
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
|
||||
z.B. Wtlg Handy: *72,0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72,10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
|
||||
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
|
||||
z.B. Wtlg Nst10: *72;dtmf=10#<br>
|
||||
z.B. Wtlg aus: *73<br>
|
||||
|
||||
|
||||
*Quellen / Nützliche Tools*
|
||||
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
|
||||
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
|
||||
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
|
||||
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)
|
||||
|
||||
BIN
RELEASE/VMNurAnsage.zip
Normal file
BIN
RELEASE/VMNurAnsage.zip
Normal file
Binary file not shown.
BIN
RELEASE/_39_Profilstatus_mitExtension.zip
Normal file
BIN
RELEASE/_39_Profilstatus_mitExtension.zip
Normal file
Binary file not shown.
BIN
RELEASE/_721_dynWeiterleitung_mitExtension_an.zip
Normal file
BIN
RELEASE/_721_dynWeiterleitung_mitExtension_an.zip
Normal file
Binary file not shown.
BIN
RELEASE/_72_dynWeiterleitung_an.zip
Normal file
BIN
RELEASE/_72_dynWeiterleitung_an.zip
Normal file
Binary file not shown.
BIN
RELEASE/_73_dynWeiterleitung_aus.zip
Normal file
BIN
RELEASE/_73_dynWeiterleitung_aus.zip
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user