Articles tagged with: スケジュール

事例:某弁護士法人様


事務所の立ち上げにあたって、多忙な先生とスタッフの連携に備える

  Topへ↓

利益相反(コンフリクト)の点で具体的な名前を出せないのですが、某弁護士法人様は、高度な専門知識を駆使した法律事務所としてご活躍しておられます。
弊社が初めてお話をいただいた時、事務所開設に向けた準備を行っている最中でした。
代表社員弁護士の先生は、事務所の立ち上げ前から弁護士として幅広く活動されており、そのスケジュールは多忙の一言に尽きました。
スタッフを雇用し、事務所として立ち上げる中、スタッフと先生のコミュニケーションをいかに円滑に回していくかは、喫緊の課題でもありました。
弊社がkintoneを提案した結果、最初の立ち上げ時からスタッフと先生のコミュニケーションロスを抑えながらの運営ができました。

いかにして先生の予定や業務を共有するかのヒアリング

  Topへ↑

弁護士とは多忙な職業という印象を持っていましたが、代表の先生がこなすスケジュールの多忙さは私の想像を超えていました。
まさに分刻みという言葉がふさわしい先生のご予定をスタッフとどのように共有するか。予定の共有をせずに事務所の運用は回りません。
先生がご相談したのが、鎌倉に事務所を構えておられるヒトノコト(https://hitonokoto.com/)の渡辺みさきさんでした。
渡辺みさきさんが、なぜ事務所のサポートをしようと思ったかは、代表の先生やオフィスメンバーが働き易い職場を作りたいと思い、そのためには情報共有が欠かせないと判断されたからだそうです。
そこで渡辺みさきさんは、弊社代表の私と知己であったことから、システム構築のご相談を弊社に持ち掛けていただきました。

弊社代表がお話を伺ったところ、代表の先生の多忙なスケジュール管理を行うには工夫しなければ難しいと感じました。
当初、ヒトノコトの渡辺さんはCybozu Officeをご提案しようとされていました。ですが、お話を伺ってみたところ、他システムとの連動を行わずにスケジュール共有を行うのは難しいと判断しました。そこでkintoneをご提案しました。

kintoneについては、弊社は諸アプリの作成には携わっていません。こちらはヒトノコトの渡辺さんから別の方にご依頼していただきました。
その方が事務所の運営に必要な各種アプリを構築し、事務所の方が使えるように作業を行っていただきました。
弊社は標準機能では難しい他システムとの連動に注力し、並列で開発する体制を組んでいただきました。

kintoneとTwillioとGoogle Calendarとカレンダーの連動を提案

  Topへ↑

先生はiPhoneを駆使して連絡をとっておられます。スケジュール管理もiPhoneの中に入っているカレンダーが中心でした。
ところが、kintoneはカレンダーとの連動ができません。なぜならiPhoneのカレンダーは外部との連動をそもそも許可していないからです。ただ、唯一連携ができたのがGoogle Calendarです。そのiPhone内の連携オプションを利用し、Google Celanderとkintoneの連動を開発することによって、kintoneとカレンダーの連動を実現しました。
これによって、先生がiPhoneのカレンダーに入れた予定をすぐにスタッフはkintoneでGoogle Calendarからの予定を取得する事で共有でき、スタッフが入れた予定も先生が取得できるようになりました。
なお、事務所が立ち上ってすぐ、もう一人弁護士の先生が入所されたのですが、その先生にも同じような仕組みで予定の共有を可能にしました。

もう一つ、考慮しなければならなかったのが、事務所から先生に連絡を行う場合です。例えば事務所に先生宛の電話があった場合、その要件をすぐに先生に伝える必要があります。
先生からは、kintoneの通知ではなくiPhoneのSMSで連絡をもらったほうがありがたい、というご希望がありました。
そこで、kintoneのアプリ上にボタンを設置しました。そのボタンをスタッフが押すことで、アプリに格納した情報を先生のiPhoneにSMSとして飛ばすように設定しました。その連携にあたってはJavaScriptでTwillioにAPIリクエストを投げることで実現させました。

カレンダーの表示を見やすくするための工夫

  Topへ↑

苦労したのは、多忙な先生と予定を合わせることです。というのも、先生の端末をお借りしての操作が必要となったからです。カレンダーとGoogle Celandarの連動設定、Google Calendarの設定、さらに内部のGoogle Cloud Platform上での認証設定などを行う必要があったからです。ガソリンスタンドの休憩スペースで落ち合って作業したのは良い思い出です。
先生の激務のさなか、分で刻まれるスケジュールの合間を縫っての作業でしたが、それ以外にお手を煩わせることがなかったのは幸いでした。
もう一人の先生の端末も後日事務所にお伺いして設定することができました。

もう一つ苦労した点は、カレンダーの見え方です。スタッフの方が日・週・月で先生の予定を把握するためのデザインや色の設定などを工夫しました。
特に二人の先生が抱えたそれぞれの予定に加え、数名のスタッフが入れた予定など、多くの予定が混在する情報をどのように整理するか、色分けの部分は何度か打ち合わせを行わせていただきました。

それと、Google Calendarへのアクセスの際にGoogleから認証を求められますが、このやり方については、動画でマニュアルを作成し、スタッフの皆さんにご理解をいただけるように努めました。
当初はスタッフからの連携方法や先生の端末操作などの操作に慣れていただく必要があり、その間は先生のお手間を煩わせたこともあったかと思います。が、無事に弊社の手を離れて今に至ります。

導入してから

  Topへ↑

弊社には時折Twillioからの通知が来ますが、今のところ弊社の手を離れ、自立して動いてくれているようです。
多忙な先生のスケジュール管理にもkintoneが活躍できる場を見いだせたことは、弊社にとっても貴重な知見となりました。

渡辺様より

今の状況について渡辺様はこう語ってくださいました。

「新入社員としてジョインしたメンバーも、最初は使うのをためらっていたけど、使って見たら便利だという事がわかったので使っている、との話も聞いています。
自分で使い勝手を考えて、ある機能を使っているようなので、いいサイクルになってきているのではないかと想像しています。」

まとめ

弊社にとって某弁護士法人様は、iPhoneのカレンダーをどうkintoneにつなげるのか、という点で工夫したことで印象に残っています。

また、多忙な皆さんのスケジュールの合間に伺ったタイミングで的確に作業を終わらせなければならず、とてもメリハリの利いた開発になったことでも思い出に残ります。
今も順調に動いているということで、安心しています。

某弁護士法人様のご紹介

情報については、非公開です。

Cybozu OfficeのスケジュールをCalendar Plusで(だいぶ)再現!


カレンダーPlus Advent Calendar 2020の5日目の記事です。

  Topへ↓

先日のCybozu Days 2020 Osakaの後、Calendar PlusのAdvent Calendarに空きがあると聞き、やってみましょうか?と言ってしまいまして。
言った以上はエントリーせねば。というわけで翌日、神戸で商談する前に中華街で飯を食べながら記事にエントリー宣言。

さて、エントリーしたのはいいが、何を書けばええんやろ?しかもエントリーしたタイミングは前日やし(^^;)。これはひょっとして勇み足をやらかしてしまったのではなかろうか。

迷いを振り切った私が商談に臨むと、なんとお客様がCybozu Officeのスケジュールを愛用しており、それをkintoneに載せ替えたいとのこと。しかもkintone標準の標準のカレンダー機能は表現に乏しく、Calendar Plusを検討してみたけど、プラグイン設定の色設定の種類が少なくてなぁ。と悩みを吐露されまして。商談からネタが転がってきた瞬間です。
これはCybozu Officeのスケジュールアプリの画面です。

私は今までも何回かCalendar Plusは提案してきました。けれど、本格的なユーザーではないし、弊社のkintone納入実績からするとCalendar Plusの納品実績は一割もありません。熟練のユーザーではないのです。

が、Calendar Plus JavaScript APIを使ったカスタマイズの経験はあります。API使えば、ある程度ならCybozu Officeの体裁にCalendar Plusを合わせられるんちゃうの?という見切り発車でした。

Calendar Plusのプラグイン設定画面

  Topへ↑

うーむ、確かにCalendar Plusの色設定は12種類か。

そして、お客様のCybozu Officeのスケジュールの色設定で設けた種別は25種類。つまり倍。

確かにお客様がkintoneへの移行をためらわれるのもわかるなあ。

アプリを作る

  Topへ↑

じゃあ、ちょっと試しにアプリを作ってみよう。こんな感じで。

あらよっと、Cybozu Officeと同じようにレコードを登録してみました。さて、Calendar Plusで見るとどうなる・・・?

あかん・・・Calendar Plus側の色設定をしていないのですべて同じ緑地になってる。
しかも予定の状況がカレンダー上に表示されておらず、直行なのか直帰なのかが一目で区別できない。
やはり見にくいなあ・・・・これは、Calendar Plus JavaScript APIの出番かな。

コーダーモード発動

  Topへ↑

ということで、ガリガリとコーディングすること2時間。うん、こんな感じになりました。


というわけで最低限、記事にできるところまでこぎつけたのでいったん寝ます。アップ当日になってしまいましたが、AM2時前なのにもう眠い。年や。

プログラム開示

  Topへ↑
さて、アップ当日の朝、旅に出かける前に少しだけ記事を書きまして。
ここにコードを開示しておきます。すみません。洗練されたコードとは程遠くて・・・・。

(function () {
  "use strict"

  // 関数本体
  const calenderformat = function (event) {
    // Calendar Plusのイベントリスナー定義
    const calendareventslist = ["cp.event.show"];
    // kintoneのイベントの書き方と似ててわかりみ
    calendarplus.events.on(calendareventslist, function (calendarevent) {
      // kintoneのイベントのjson(上記イベントで対象データループしてくれます)
      const record = calendarevent.record;
      // kintoneのイベントの要素
      const element = calendarevent.element;
      // テーブルだけkintoneのイベントの要素
      const tdelement = document.getElementsByClassName("fc-event-container");
      // Cybozu Officeの時刻表示に合わせるため(ださいかも)
      const starttime =
        ("00" + new Date(record.開始時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.開始時刻.value).getMinutes()).slice(-2);
      const endtime =
        ("00" + new Date(record.終了時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.終了時刻.value).getMinutes()).slice(-2);
      // 左端の行高がCalendar Plusでは縮まるため、Cybozu Officeの行高っぽくする
      tdelement[0].style.minHeight = "90px";
      tdelement[1].style.minHeight = "90px";
      tdelement[2].style.minHeight = "90px";
      tdelement[3].style.minHeight = "90px";
      tdelement[4].style.minHeight = "90px";
      // リンクするためのurl
      const baseurl =
        kintone.api.url("/k/").replace(".json", "") +
        kintone.app.getId() +
        "/show#record=" +
        record.$id.value;
      // 予定の状況にあわせてSwitch(ださい)
      switch (record.予定の状況.value) {
        case "ーーー":
          // 枠の背景色
          element[0].style.backgroundColor = "#FFFFFF";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";

          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつける
            element[0].innerText = "・" + element[0].innerText;
          } else {
            // 頭に時刻をつけて改行
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              record.予定.value +
              "</a>";
          }
          break;
        case "直行":
          // 枠の背景色
          element[0].style.backgroundColor = "#ebf7ef";
          // 枠の線色
          element[0].style.borderColor = "#d8f2e0";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";
          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつけ、予定の状況を枠で囲む
            element[0].innerHTML =
              "・<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">" +
              record.予定の状況.value +
              "</span>" +
              record.予定.value;
          } else {
            // 頭に時刻をつけ、予定の状況を枠で囲む(以下コメントは省略)
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              '<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">' +
              record.予定の状況.value +
              "</span>" +
              record.予定.value +
              "</a>";
          }
          break;
        case "直帰":
        case "直行/直帰":
          element[0].style.backgroundColor = "#e8f2fc";
          element[0].style.borderColor = "#dae8f7";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#3182dc;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "転送":
        case "納品":
        case "設定":
        case "設置":
        case "移設":
          element[0].style.backgroundColor = "#f4fce8";
          element[0].style.borderColor = "#e5f2d3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#83cb26;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "点検":
          element[0].style.backgroundColor = "#fff4e3";
          element[0].style.borderColor = "#fae8cd";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#ef9201;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "デモ":
        case "営業":
        case "商談":
        case "打合せ":
        case "来客":
          element[0].style.backgroundColor = "#faf6f2";
          element[0].style.borderColor = "#f2e9df";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#c3a88b;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "当番":
          element[0].style.backgroundColor = "#fcfae8";
          element[0].style.borderColor = "#f2efd3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#dfc506;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "休み":
        case "有休":
        case "リフ":
        case "代休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "午前半休":
        case "午後半休":
          element[0].style.backgroundColor = "#fff2f5";
          element[0].style.borderColor = "#f7e9d2";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f3a4b4;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜当番出勤":
        case "出張":
        case "会議":
          element[0].style.backgroundColor = "#f5edfc";
          element[0].style.borderColor = "#ebe1f5";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#b592d8;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜振休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
      }
      return calendarevent;
    });
  };
  // kintone自体のイベント
  const eventslist = ["app.record.index.show"];
  kintone.events.on(eventslist, (event) => {
    // ビューが一致したら本体の関数呼び出し
    if (event.viewId === 5543487) {
      calenderformat(event);
    }
    return event;
  });
})(jQuery);

あと、cssも全体のフォントに関する設定と予定の状況の枠に対して設定しています。

div#calendarPlus {
    font-family:"ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", ヒラギノ角ゴシック, "Hiragino Sans", メイリオ, Meiryo, "MS Pゴシック", "MS PGothic", sans-serif;
    font-size:14.4px;
    font-weight:400;
}
    
div#calendarPlus span.scheduleMarkEventMenu{
    display: inline-block;
    padding: 1px 2px;
    color: #fff;
    margin: 0 1px 2px 1px;
    line-height: 1.2em;
    border-radius: 2px;
    font-size: 12px;
    vertical-align: middle;
}
    

まとめ

  Topへ↑

てなわけで、日中は記事のことは一切忘れて徳島のあちこちをめぐっておりました。で、21時前に実家に帰ってきました。
では、早速記事の続きを。うん、あらためて確認しても表示はこんな感じで大丈夫かと。

本当は実装すべきなのは週だけでなく、他の日や月でも同じように表示できるか試さねばならないですね。また、Cybozu Officeのスケジュールアプリの日時と曜日の行見出しの部分はCalendar Plusでは再現していません。
まあこんな感じですが、記事の主旨は満たせたと判断し、今宵はここまでにしてアップしたいと思います。

あとは、金曜日に商談したお客様がこの実装でご満足いただけるかどうかでしょうか。なにしろ、お客様に商談の御礼のメールを送るより先に、こちらの記事をアップしてしまったので(^^;)。おいおい。

本稿がCybozu Officeからkintoneへの移行をお考えの皆様にとって少しでも手助けになれば幸せです。

もしお困りの際は弊社までご連絡くださいませ。

当エントリーの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリー作成にあたり、以下のサイトからの情報を参考にさせていただきました。
ラジカルブリッジの斎藤さんにはいつも感謝です。内容にやばい内容が含まれていたら直しますね。
ありがとうございました。

 Calendar Plus JavaScript APIのリファレンス