読者です 読者をやめる 読者になる 読者になる

atskimura-memo

あまり仕事とは関係なく適当に書きます。開発ネタが多いかもしれません。

空き会議室が10%増える会議室予約サービス「AITA」をハックチャレンジに出しました

この記事はSalesforce1 Advent Calendar 2014 - 15日目の記事です。


AITA - 空き会議室が10%増える企業向け会議室予約サービス - YouTube

上の動画のようなAITAという会議室予約サービスを作りまして、Salesforceハックチャレンジ2014に応募しました。

Salesforceハックチャレンジ2014は12月4日のSalesforce World Tour Tokyoで申し込みが行われた「12月4日から14日の10日間で開発」、「賞金総額200万円」というSalesforce主催のアプリ開発コンテストです。

何の気なしに12月15日でAdvent Calendarに参加してみたら、締め切りが重なって、さらにはプライベートでもこの土日忙しくて、なかなか大変なことになりました。
というわけで今はハックチャレンジの提出を終えてひと安心して、Advent Calendar用の記事を書いています。
これはもうハックチャレンジのことを書いていいですよね。

AITAとは?

要は会議室使うときはチェックインさせて、チェックインしなかったら自動キャンセルして、会議室の利用効率あげようぜというサービスで、メールや電話で通知してチェックイン・キャンセルを徹底させるので、導入したら本気で効果はあると思います。

上のURLにも書いてありますが、まずAITAの概要を転載しておきます。

概要

AITA(あいた)は空き会議室が10%増える会議室予約システムです。

「誰も使っていないのに会議室が空いていない。」
「会議室が足りない。」
「新たな会議室を作るコストを抑えたい」

そんなお悩みはありませんか?AITAはそんなお悩みを解決します。

主な特徴

  • 会議をするときは会議室にチェックイン
  • チェックインしなかったら予約を自動キャンセル、自動チェックアウトも
  • きめ細かい通知でキャンセルを簡単に
  • 利用状況を分析し、会議室運用を最適化

今後の開発予定機能

  • 会議室設置型端末への対応
  • センサーによる自動チェックイン・チェックアウト
  • Chatterや取引先・取引先責任者との連携

技術的な特徴

  • AITAはForce.comネイティブアプリケーションです。
  • しかし、アプリケーションのほとんどの部分をAngularJSアプリで作り、Force.com APIを呼び出すというシステム構成を取っています。
  • AngularJSアプリの構成もYeomanのgenerator-angularで生成した構成ほぼそのままで動作するようにしており、開発時にはローカルでも動作し、効率的な開発を可能にしています。
  • ユーザへの通知部分のみApexで実装しており、Apexジョブにより監視をし、メール通知に加え、Twilioを使用しての電話通知を実現しています。

技術的なお話

しかし、Advent Calendar読者としては技術的な内容に興味があるかと思うので、この記事ではそちらを主に書きたいと思います。
AITAの内容について詳しくはこちらをご覧ください。→AITA - 空き会議室が10%増える企業向け会議室予約サービス | 株式会社co-meeting

Single Page Application

AITAは、AngularJSを使ったSingle Page Applicationで、1枚だけVisualforceページがあって、それをブラウザにロードしたら以降はForce.com REST APIを呼び出すという構成になっています。
基本的にはApexクラスは使っていません。(ちょっとだけ使っているのでそれは後ほど。)

f:id:a_kimura:20141215013941p:plain

この構成にしておくとForce.comでカスタムオブジェクトを作ってさえおけば、ローカルでも動かしやすいです。
「apex:pageタグをつけない」、「認証をOAuthにする」、「プロキシサーバーを立てる」とかいろいろ小細工はしていますが、基本的にそのまま動きます。
grunt serveっ打つとローカルでWebサーバーとプロキシサーバーが動くようにしています。

f:id:a_kimura:20141215014345p:plain

うちで作っている以下のアプリたちは全部この構成で作っています。

Force.comへのデプロイ

yeoman/generator-angularをほぼそのまま使っていますので、CoffeeScriptにSassを使っています。つまりコンパイルする必要があります。それだけじゃなく、JavaScriptとCSSは圧縮して結合するのが普通ですよね。

Gruntとgrunt-ant-sfdcを使って、grunt deployと打つとAngularJSアプリをビルドしてForce.comにデプロイできるようにしています。
元々generator-angularさんがビルドはしてくれるので、私が付け加えたのはForce.comで動くように変換する処理とForce.comへのデプロイ処理だけです。

やっていることは以下のような処理です。

  1. JS/CSS/画像/AngularJSのViewなどはコンパイル→圧縮→zipして1つの静的リソースとして保存
  2. AngularJSのindex.htmlはapexタグを付加・置換して唯一のVisualforceページとして変換
  3. OAuth使用から$API.Session_Idを利用するよう置換
  4. タブ/アプリケーションなどのForce.comメタ情報は事前に用意
  5. Force.com Migration Toolでアップロード

f:id:a_kimura:20141215021806j:plain

※AITAは次に説明するTwilioとかでちょっと複雑なので上図はSalesFollowUpでの例です。

基本的にローカルで開発できるので、そういう開発に慣れている身としては非常に快適に開発できます。

Twilio

基本的にApexコードは書かないようにしているんですが、今回はユーザへのメール通知と電話通知のところでApexコードを少し書いています。
Apexジョブで監視して、利用30分前になったらメール通知、利用開始時間5分後になってもチェックインしなかったら電話通知をしています。

電話通知はTwilioを使っていて、TwilioのApexクライアントを使用している日本語記事を見かけなかったので、ご紹介します。
Twilioは電話やSMSを扱える簡単に扱えるクラウドサービスです。日本ではKDDIが代理店になって提供しています

Twilioを使った電話通知の実装は非常に簡単です。

  1. Twilio公式のApexクライアントtwilio/twilio-salesforceを読み込む。
  2. 以下のように呼び出す。
    @future(callout=true)
    private static void callTwilio(String phone){
        try {
            TwilioRestClient client = new TwilioRestClient(TWILIO_ACCOUNT, TWILIO_TOKEN);
            Map<String,String> params = new Map<String,String> {
                'To'   => phone,
                'From' => TWILIO_ACCOUNT_PHONE,
                'Url' => 'https://dl.dropboxusercontent.com/u/8969548/call.xml'
            };
            TwilioCall call = client.getAccount().getCalls().create(params);
         } catch (TwilioRestException e) {
            System.debug(e);
         }   
    }

上のDropboxのPublicフォルダに置いているXMLファイルの中身は以下のようになっていて、自動音声のメッセージ内容が設定されています。
自動音声のメッセージ内容はインターネットから取れるところに置いておく必要があります。

<Response>
  <Say language="ja-jp" voice="woman">既に利用開始時刻が過ぎている会議室予約があります。今すぐチェックインするか予約を取り消してください。</Say>
</Response>

基本これだけです。簡単ですね。

終わりに

AITAは本当に導入効果出ると思うのでそのうち有償アプリで出そうかなと思っています。
ご興味あればぜひこちらからご連絡ください。

本当はこのAngularJS+Force.comの仕組みをもうちょっと整理してYeomanジェネレータにしてGithubに上げたよって記事にしようかなあと思っていたんですが、ハックチャレンジが入ってしまったので手が回りませんでした。
また機会があったらその辺整理したいと思います。