FlexプラグインTips（後処理中に他の着信を受ける）

:::note alert
2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
:::

# はじめに

みなさん、こんにちは。
KDDIウェブコミュニケーションズ、Twilioエバンジェリストの高橋です。

今回は、Twilio Flex の後処理中に、別の着信を受ける方法について解説します。

## 後処理について

多くのコールセンターでは、お客様との電話対応が終了した時点で、どのような内容であったかを記録する必要があります。
そのため、Twilio Flex では後処理中は新しい着信は受けないようになっています。

しかし、一部のお客様では後処理中にも別の着信を受けたいというニーズがあります。もちろん、通話中には別の着信は受けられませんので、通話が終了している後処理に限っての話です。

## オペレーターの着信可能数

そもそもオペレーターが同時にさばけるタスクの制限については、オペレーター（Flex では　Worker と呼びます）ごとにどのチャネルを同時にどのくらい着信するかを決めることができるようになっています。
具体的には、TaskRouter の中の Workers で、各自のプロパティページを開くと、その人のキャパシティが見られます。

![スクリーンショット 2022-07-29 10.44.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/4be9422b-27ff-0323-c56a-f11a7fd12b55.png)

上のスクリーンショットはデフォルトのキャパシティです。
音声通話とビデオについては、同時に処理することができないために、どちらもキャパシティは「１」に設定されていることがわかります。

では、ここの数字を上げるとどうなるでしょうか？
実は、後処理中にも着信が受けられるようになります。ただし、通話中にも次の着信が入ってきてしまいますし、それを受けようとすると Flex がクラッシュします。

よって、単純にここの数値を上げるだけではうまくいきません。

そこで今回のプラグインの登場です。

## シナリオ

待受中に着信を受けるためには、このキャパシティを上げるのが良さそうです。ただし、待受中になった時点であげないと上記のようにうまくありませんので、まずは待受状態になったことを検知して、キャパシティを１つあげます。
そしてタスクが終了（待受中が解除されたら）、キャパシティを１つ減らします。

このロジックでうまくいきそうです。

## タスクイベントを検知する

待受中の検出には　Flex Events が使えます。

https://assets.flex.twilio.com/docs/releases/flex-ui/2.0.0-beta.1/ui-actions/FlexEvent

:::note warn
待受になったことを検知するイベントは、Flex UI 2.x からの実装になります。
:::

コードはこんな感じになります。

```javascript:待受中の検知
  manager.events.addListener('taskWrapup', async (task: ITask) => {
    if (task.channelType === 'voice') {
      console.log(`🐸 taskWrapup`);
    }
  });
```

同様に、タスクが終了したときのコードは以下のようになります。

```javascript:タスク終了時の検知
  manager.events.addListener('taskCompleted', async (task: ITask) => {
    if (task.channelType === 'voice') {
      console.log(`🐸 taskCompleted`);
    }
  });
```

## キャパシティを更新する

Worker のキャパシティは、以下のAPIで可能です。

https://www.twilio.com/docs/taskrouter/api/worker-channel?code-sample=code-update-worker-channel

ただし、Flex から直接APIを叩くしくみは用意されていないので、ここは Twilio Functions を用意することにします。

コードは以下の通りです。

```javascript:キャパシティを更新するFunction
const Twilio = require('twilio');
exports.handler = async function (context, event, callback) {
  const response = new Twilio.Response();
  response.appendHeader('Access-Control-Allow-Origin', '*');
  response.appendHeader('Access-Control-Allow-Methods', 'OPTIONS, POST, GET');
  response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
  try {
    // Get parameters
    const { workerSid, action } = event;
    if (!workerSid || !action) throw new Error('Parameter(s) error.');
    if (!action.match(/inc|dec/)) throw new Error('Parameter(s) error.');

    // Get environment variables
    const { WORKSPACE_SID } = context;

    // Create twilio client
    const client = context.getTwilioClient();

    // Search the voice channel sid
    const workerChannels = await client.taskrouter.v1
      .workspaces(WORKSPACE_SID)
      .workers(workerSid)
      .workerChannels.list();
    console.dir(workerChannels);
    const voiceChannels = workerChannels.filter(
      (channel) => channel.taskChannelUniqueName === 'voice',
    );
    if (voiceChannels.length === 0)
      throw new Error(`This worker haven't a voice channel`);

    // Get current capacity
    const voiceChannel = await client.taskrouter.v1
      .workspaces(WORKSPACE_SID)
      .workers(workerSid)
      .workerChannels(voiceChannels[0].sid)
      .fetch();

    // Update worker's task channel capacity
    await client.taskrouter.v1
      .workspaces(WORKSPACE_SID)
      .workers(workerSid)
      .workerChannels(voiceChannel.sid)
      .update({
        capacity:
          action === 'inc'
            ? voiceChannel.configuredCapacity + 1
            : voiceChannel.configuredCapacity > 1
            ? voiceChannel.configuredCapacity - 1
            : voiceChannel.configuredCapacity,
      });

    response.appendHeader('Content-Type', 'text/plain');
    response.setBody('OK');
    callback(null, response);
  } catch (err) {
    console.error(err.message ? err.message : err);
    response.appendHeader('Content-Type', 'text/plain');
    response.setBody(`Error: ${err.message}`);
    callback(null, response);
  }
};
```

ちょっと長いですが、まずは Voice のタスクチャンネルを検索して、その値を増やす、もしくは減らすしくみが実装されています。
この Function を呼ぶためのパラメータとして、WorkerSid と、増やすか減らすかの action が必要です。
WorkerSid は先程のイベントで渡される task オブジェクト内に記載されています。

## Flex から Function を呼び出す

ここまでできれば、あとは Flex から Function を呼び出すだけです。
先程の待受中を検知したイベントはこんな感じになります。

```javascript:待受中になったらFunctionを呼び出す
import * as Flex from '@twilio/flex-ui';
import { ITask, Manager } from '@twilio/flex-ui';
import axios from 'axios';

// Get environment variables
const { FLEX_APP_FUNCTIONS_DOMAIN } = process.env;

export default function wrapupAction(manager: Flex.Manager) {
  manager.events.addListener('taskWrapup', async (task: ITask) => {
    if (task.channelType === 'voice') {
      console.log(`🐸 taskWrapup`);
      console.dir(task);
      const url = `https://${FLEX_APP_FUNCTIONS_DOMAIN}/change-capacity?workerSid=${task.workerSid}&action=inc`;
      const res = await axios.post(url);
      console.log(`🐸 taskWrapup capacity incremented.`);
    }
  });
}
```

axios を使って Function を呼び出しています。その際に、workerSidとactionパラメータを付与しています。

同様に、タスクが完了したときのコードは以下のようになります。

```javascript:タスクが完了したときにFunctionを呼び出す
import * as Flex from '@twilio/flex-ui';
import { ITask, Manager } from '@twilio/flex-ui';
import axios from 'axios';

// Get environment variables
const { FLEX_APP_FUNCTIONS_DOMAIN } = process.env;

export default function completedAction(manager: Flex.Manager) {
  manager.events.addListener('taskCompleted', async (task: ITask) => {
    if (task.channelType === 'voice') {
      console.log(`🐸 taskCompleted`);
      console.dir(task);
      const url = `https://${FLEX_APP_FUNCTIONS_DOMAIN}/change-capacity?workerSid=${task.workerSid}&action=dec`;
      const res = await axios.post(url);
      console.log(`🐸 taskWrapup capacity decremented.`);
    }
  });
}
```

## 完成したプラグイン

以上を実装したプラグインを公開してありますので、ぜひご利用ください。

https://github.com/mobilebiz/plugin-wrapup-multitask

# まとめ

最初は、Worker のステータスを使ってできないかと考えたのですが、色々と調べると、着信中も待受中も Worker のステータス自体は変わらないことがわかり、今回の方法を考えました。
とりあえず、こちらでテストしたところはうまく行っていますが、転送とか途中で強制切断されたときとかの動作については完全にテストされているわけではないので、その点がご容赦ください。
何か不具合などがでましたら、コメントいただけると助かります。

---
### Twilio（トゥイリオ）とは

https://cloudapi.kddi-web.com
Twilio は音声通話、メッセージング（SMS /チャット）、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウド API サービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。

Twilioに関するご相談などがございましたら、ぜひ毎週水曜日の午後に開催しております相談会をご利用ください。
[Twilio相談会（毎週水曜日）](https://cloudapi.kddi-web.com/contact/consultation?hsLang=ja-jp)

自己紹介|　
:--|:--
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/b51a8b88-abd5-ce5e-195a-de3564ba2ae6.png" height='200' width='150'/>|高橋克己（Katsumi Takahashi） 自称「<font color='red'>赤い芸人</font>」<br />グローバル・インターネット・ジャパン株式会社　代表取締役<br />株式会社KDDIウェブコミュニケーションズ　Twilio事業部エバンジェリスト<br /><br />2001年より大手通信事業者の法人サービスの教育に携わり、企業における電話のしくみや重要性を研究。2016年よりTwilio事業部にジョインし、Twilioを使ったスマートコミュニケーションの普及活動を精力的に行っている。<br />2015 Hall of Doers<br />2019 Twilio Champions