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