Remove redundant methods and IVR handling logic from Bird voice call webhook route, focusing on streamlined call flow and improved maintainability.
This commit is contained in:
@@ -28,7 +28,6 @@ class birdVoiceWebhooksRoute
|
||||
$this->post('/bird/voice/calls/webhook/inbound', function () {
|
||||
global $response;
|
||||
|
||||
//self::requirePermission('modules_bird_voice_call_webhooks_trigger');
|
||||
$client = new bird();
|
||||
|
||||
$ws = $this->normalizeOptionalString($this->fromRequest('workspaceId') ?? $this->fromQuery('workspaceId'));
|
||||
@@ -47,204 +46,11 @@ class birdVoiceWebhooksRoute
|
||||
$client->sayMessage($ws, $ch, $ci, [
|
||||
'text' => 'Hej! Tak for dit opkald. Vi vil nu guide dig gennem en menu for at aabne en port. Afslut hvert valg med firkantstasten.',
|
||||
]);
|
||||
// Tell options to the caller
|
||||
}, [
|
||||
'modules_bird_voice_call_webhooks_trigger' => 'Trigger a voice call via Bird webhooks',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function listDepartmentOptions(): array
|
||||
{
|
||||
$department_gates = array_unique((new department_gates_o())->getFields(['department']));
|
||||
$departments = array_map(function ($row) {
|
||||
return (new departments_o())->select((int)($row['department'] ?? 0))['name'] ?? 'Afdeling ' . (string)($row['department'] ?? '');
|
||||
}, $department_gates);
|
||||
$options = [];
|
||||
foreach ($departments as $department) {
|
||||
$options[] = [
|
||||
'input' => 'text',
|
||||
'value' => $department,
|
||||
'label' => $department,
|
||||
];
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
protected function handleGatherResponse(
|
||||
bird $client,
|
||||
string $ws,
|
||||
string $ch,
|
||||
string $callId,
|
||||
string $digits,
|
||||
int $countryCode,
|
||||
int $phone,
|
||||
array $departments
|
||||
): void {
|
||||
$state = $this->readIvrState($callId);
|
||||
if ($state === null || !$this->isStateForCaller($state, $countryCode, $phone)) {
|
||||
$this->promptForDepartmentSelection($client, $ws, $ch, $callId, $departments, $countryCode, $phone);
|
||||
return;
|
||||
}
|
||||
|
||||
$stage = (string)($state['stage'] ?? '');
|
||||
if ($stage === self::IVR_STAGE_DEPARTMENT_SELECT) {
|
||||
$this->handleDepartmentSelectionDigit($client, $ws, $ch, $callId, $digits, $state, $countryCode, $phone);
|
||||
return;
|
||||
}
|
||||
if ($stage === self::IVR_STAGE_GATE_TYPE_SELECT) {
|
||||
$this->handleGateTypeSelectionDigit($client, $ws, $ch, $callId, $digits, $state);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clearIvrState($callId);
|
||||
$this->promptForDepartmentSelection($client, $ws, $ch, $callId, $departments, $countryCode, $phone);
|
||||
}
|
||||
|
||||
protected function handleDepartmentSelectionDigit(
|
||||
bird $client,
|
||||
string $ws,
|
||||
string $ch,
|
||||
string $callId,
|
||||
string $digits,
|
||||
array $state,
|
||||
int $countryCode,
|
||||
int $phone
|
||||
): void {
|
||||
$optionMap = is_array($state['department_options'] ?? null) ? $state['department_options'] : [];
|
||||
$selected = $this->resolveDepartmentIdByDigit($optionMap, $digits);
|
||||
if ($selected === null) {
|
||||
$this->promptForDepartmentRetry($client, $ws, $ch, $callId, $state);
|
||||
return;
|
||||
}
|
||||
|
||||
$state['stage'] = self::IVR_STAGE_GATE_TYPE_SELECT;
|
||||
$state['selected_department'] = $selected;
|
||||
$state['country_code'] = $countryCode;
|
||||
$state['phone'] = $phone;
|
||||
$state['selected_department_name'] = $this->getDepartmentNameById($selected);
|
||||
$this->saveIvrState($callId, $state);
|
||||
|
||||
$this->promptForGateTypeSelection($client, $ws, $ch, $callId, $selected, (string)$state['selected_department_name']);
|
||||
}
|
||||
|
||||
protected function handleGateTypeSelectionDigit(
|
||||
bird $client,
|
||||
string $ws,
|
||||
string $ch,
|
||||
string $callId,
|
||||
string $digits,
|
||||
array $state
|
||||
): void {
|
||||
$departmentId = (int)($state['selected_department'] ?? 0);
|
||||
if ($departmentId <= 0) {
|
||||
$this->clearIvrState($callId);
|
||||
$this->say($client, $ws, $ch, $callId, 'Sessionen er udloeebet. Ring venligst op igen.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$gateType = $this->resolveGateTypeByDigit($digits);
|
||||
if ($gateType === null) {
|
||||
$departmentName = (string)($state['selected_department_name'] ?? $this->getDepartmentNameById($departmentId));
|
||||
$this->promptForGateTypeRetry($client, $ws, $ch, $callId, $departmentId, $departmentName);
|
||||
return;
|
||||
}
|
||||
|
||||
$gate = $gateType === self::IVR_GATE_TYPE_ENTRANCE
|
||||
? (new department_gates_o())->getEntranceGate($departmentId)
|
||||
: (new department_gates_o())->getExitGate($departmentId);
|
||||
|
||||
$this->clearIvrState($callId);
|
||||
|
||||
if ($gate === null || !$gate->exists()) {
|
||||
$directionLabel = $gateType === self::IVR_GATE_TYPE_ENTRANCE ? 'indgang' : 'udgang';
|
||||
$this->say($client, $ws, $ch, $callId, 'Vi kunne ikke finde en ' . $directionLabel . 'sport for den valgte afdeling.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$gate->openGate();
|
||||
$this->say($client, $ws, $ch, $callId, 'Aabner port: ' . $gate->name->value(), true);
|
||||
return;
|
||||
} catch (\Throwable) {
|
||||
$this->say($client, $ws, $ch, $callId, 'Kunne ikke aktivere relaeet for ' . $gate->name->value() . '. Kontakt venligst support.', true);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCallerDepartments(int $countryCode, int $phone): array
|
||||
{
|
||||
// Return all visible departments.
|
||||
$tmp = (new departments_o())->getFieldsWhere([
|
||||
'visible' => 1,
|
||||
], ['id']);
|
||||
return array_map(function ($row) {
|
||||
return (int)$row['id'];
|
||||
}, $tmp);
|
||||
}
|
||||
|
||||
protected function promptForDepartmentSelection(
|
||||
bird $client,
|
||||
string $ws,
|
||||
string $ch,
|
||||
string $callId,
|
||||
array $departments,
|
||||
int $countryCode,
|
||||
int $phone
|
||||
): void {
|
||||
$optionMap = $this->buildDepartmentOptionMap($departments);
|
||||
if (empty($optionMap)) {
|
||||
$this->say($client, $ws, $ch, $callId, 'Der er ingen valgbare afdelinger lige nu. Kontakt venligst support.', true);
|
||||
return;
|
||||
}
|
||||
|
||||
$state = [
|
||||
'stage' => self::IVR_STAGE_DEPARTMENT_SELECT,
|
||||
'department_options' => $optionMap,
|
||||
'selected_department' => null,
|
||||
'country_code' => $countryCode,
|
||||
'phone' => $phone,
|
||||
];
|
||||
$this->saveIvrState($callId, $state);
|
||||
|
||||
$text = $this->buildDepartmentPromptText($optionMap);
|
||||
$this->gather($client, $ws, $ch, $callId, $text);
|
||||
}
|
||||
|
||||
protected function promptForDepartmentRetry(bird $client, string $ws, string $ch, string $callId, array $state): void
|
||||
{
|
||||
$optionMap = is_array($state['department_options'] ?? null) ? $state['department_options'] : [];
|
||||
if (empty($optionMap)) {
|
||||
$this->clearIvrState($callId);
|
||||
$this->say($client, $ws, $ch, $callId, 'Sessionen er udloeebet. Ring venligst op igen.', true);
|
||||
return;
|
||||
}
|
||||
$text = 'Ugyldigt valg. ' . $this->buildDepartmentPromptText($optionMap);
|
||||
$this->gather($client, $ws, $ch, $callId, $text);
|
||||
}
|
||||
|
||||
protected function promptForGateTypeSelection(
|
||||
bird $client,
|
||||
string $ws,
|
||||
string $ch,
|
||||
string $callId,
|
||||
int $departmentId,
|
||||
string $departmentName
|
||||
): void {
|
||||
$text = $this->buildGateTypePromptText($departmentName);
|
||||
$this->gather($client, $ws, $ch, $callId, $text);
|
||||
}
|
||||
|
||||
protected function promptForGateTypeRetry(
|
||||
bird $client,
|
||||
string $ws,
|
||||
string $ch,
|
||||
string $callId,
|
||||
int $departmentId,
|
||||
string $departmentName
|
||||
): void {
|
||||
$text = 'Ugyldigt valg. ' . $this->buildGateTypePromptText($departmentName);
|
||||
$this->gather($client, $ws, $ch, $callId, $text);
|
||||
}
|
||||
|
||||
protected function buildDepartmentOptionMap(array $departmentIds): array
|
||||
{
|
||||
$map = [];
|
||||
@@ -425,37 +231,6 @@ class birdVoiceWebhooksRoute
|
||||
}
|
||||
}
|
||||
|
||||
protected function extractCallerPhone(array $payload): ?array
|
||||
{
|
||||
$candidates = [
|
||||
$payload['phone_number'] ?? null,
|
||||
$payload['source'] ?? null,
|
||||
$payload['from'] ?? null,
|
||||
];
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
$normalized = department_gates_o::normalizePhoneCandidate($candidate);
|
||||
if ($normalized !== null) {
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function isRegisteredCaller(int $countryCode, int $phone): bool
|
||||
{
|
||||
$userRows = (new users_o())->getFieldsWhere([
|
||||
'phone_country_code' => $countryCode,
|
||||
'phone' => $phone,
|
||||
], ['id']);
|
||||
if (!empty($userRows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (new subusers_o())->getSubuserByPhone($countryCode, $phone) !== null;
|
||||
}
|
||||
|
||||
protected function say(bird $client, string $ws, string $ch, string $callId, string $text, bool $hangup = false): void
|
||||
{
|
||||
if ($callId !== '') {
|
||||
|
||||
Reference in New Issue
Block a user