0
0

More than 3 years have passed since last update.

KintoneとOutlookカレンダーを連携して工数を集計する

Last updated at Posted at 2020-06-10

やりたいこと

スケジュールをOutlookカレンダーで行っているので、そのデータを元に工数を集計できるようにしたい。

仕様

一覧画面に集計開始日と終了日を日付形式で設定し、その期間のOutlookカレンダーのデータを取得して一覧にする。
既に登録済みのデータは更新する。

ソースはこちら

公式にサンプルがあるのでこれをベースにする

Outlook連携 - kintoneからOutlookスケジュールを登録しよう!!

公式サンプルでは予定の登録だけなので、これを改造して予定を取得をできるようにする

JS/CSS

サンプルからの変更としては dateformat.js を追加しています。

フォーム設定

項目 フィールド形式 フィールドコード
イベントID 文字列(1行) EventId
更新キー 文字列(1行) UpdateKey
件名 文字列(1行) To_Do
担当者 ユーザー選択 Assignees
開始日 日時 From
終了日 日時 To
工数(時) 数値 WorkTime
終日 チェックボックス AllDay
詳細内容 文字列(複数行) Details
添付ファイル 添付ファイル Attachments

ヘッダー部をカスタマイズ

一覧画面のヘッダー部に「取得開始日」、「取得終了日」、「実行ボタン」配置の設定をする

言語情報にボタン表記名を追加

kintone-connect-outlook-schedule.js
lang: (
  en: {
    getEvent: 'Get Event',
  },
  ja: {
    getEvent: '予定を取得',
  }

settings: (
  ui: {
    buttons: {
      getEvent: {
        text: 'getEvent',
        type: 'normal'
      }
    }
  }
}

日付項目とボタンのスタイルを設定

kintone-connect-outlook-schedule.js
uiCreateForIndex: function(kintoneHeaderSpace) {
~
  // ラベルを設定
  this.data.ui.btnGetEvent = this.createButton(this.setting.ui.buttons.getEvent, this.setting.i18n.button);
  this.data.ui.labelFromDate = new kintoneUIComponent.Label({text: '取得開始日'});
  this.data.ui.labelToDate = new kintoneUIComponent.Label({text: '取得終了日'});

  // スタイルを設定
  this.data.ui.btnGetEvent.element.style.display = 'inline-block';
  this.data.ui.btnGetEvent.element.style.margin = '0 15px 15px 15px';
  this.data.ui.labelFromDate.element.style.display = 'inline-block';
  this.data.ui.labelFromDate.element.style.margin = '0 15px 15px 15px';
  this.data.ui.labelToDate.element.style.display = 'inline-block';
  this.data.ui.labelToDate.element.style.margin = '0 15px 15px 15px';

  // 日付の初期値を入力
  var iFrom = storage.inputFromDate ? new Date(storage.inputFromDate) : new Date();
  var iTo = storage.inputToDate ? new Date(storage.inputToDate) : new Date();
  this.data.ui.inputFromDate = new kintoneUIComponent.DateTime({value: iFrom, type: 'date', locale: 'ja', dateFormat: 'YYYY/MM/dd'});
  this.data.ui.inputToDate = new kintoneUIComponent.DateTime({value: iTo, type: 'date', locale: 'ja', dateFormat: 'YYYY/MM/dd'});

  // 各要素を配置
  this.data.ui.kintoneCustomizeOutlookHeaderSigned.appendChild(this.data.ui.labelFromDate.render());
  this.data.ui.kintoneCustomizeOutlookHeaderSigned.appendChild(this.data.ui.inputFromDate.render());
  this.data.ui.kintoneCustomizeOutlookHeaderSigned.appendChild(this.data.ui.labelToDate.render());
  this.data.ui.kintoneCustomizeOutlookHeaderSigned.appendChild(this.data.ui.inputToDate.render());
  this.data.ui.kintoneCustomizeOutlookHeaderSigned.appendChild(this.data.ui.btnGetEvent.render());
}

ボタン押下時のイベント登録する

kintone-connect-outlook-schedule.js
kintone.events.on('app.record.index.show', function(event) {
~
  // 予定取得ボタン押下時
  kintoneScheduleService.data.ui.btnGetEvent.on('click', function() {
    outlookAPI.getEvent();
  });
}

APIリクエストを設定する

calendarviewを使うので追加しましょう。
こんな感じで変更しました。

kintone-connect-outlook-schedule-common.js
  graphApiScorp: {
    scopes: [
      'calendars.readwrite',
      'calendars.read'
    ],
  },

  event: {
    eventUrl: 'https://graph.microsoft.com/v1.0/me/events',
    eventGetUrl: 'https://graph.microsoft.com/v1.0/me/calendarview'
  },

  kintone: {
    fieldCode: {
      // 件名
      subject: 'To_Do',
      // 詳細内容
      body: 'Details',
      // 開始日時
      startDate: 'From',
      // 終了日時
      endDate: 'To',
      // イベントID
      eventId: 'EventId',
      // 添付ファイル
      attachFile: 'Attachments',
      // 更新キー
      updateKey: 'UpdateKey',
      // 終日
      isAllDay: 'AllDay',
      // ユーザー指定
      assignees: 'Assignees',
      // 工数
      worktime: 'WorkTime',
    }
  }

ボタン押下時の処理を追加する

宣言部で必要な要素を取得する

kintone-connect-outlook-schedule.js
  var SUBJECT_FIELD_CODE = KAC.kintone.fieldCode.subject;
  var BODY_FIELD_CODE = KAC.kintone.fieldCode.body;
  var START_DATE_FIELD_CODE = KAC.kintone.fieldCode.startDate;
  var END_DATE_FIELD_CODE = KAC.kintone.fieldCode.endDate;
  var ATTACH_FILE_FIELD_CODE = KAC.kintone.fieldCode.attachFile;
  var EVENT_ID_FIELD_CODE = KAC.kintone.fieldCode.eventId;
  var EVENT_URL = KAC.event.eventUrl;
  var UPDATE_KEY_FIELD_CODE = KAC.kintone.fieldCode.updateKey;
  var ALLDAY_FIELD_CODE = KAC.kintone.fieldCode.isAllDay;
  var ASSIGNEES_FIELD_CODE = KAC.kintone.fieldCode.assignees;
  var WORKTIME_FIELD_CODE = KAC.kintone.fieldCode.worktime;
  var EVENT_GET_URL = KAC.event.eventGetUrl;

予定表からデータを取得する

kintone-connect-outlook-schedule.js
var outlookAPI = {
~
// outlook予定表取得
getEvent: function() {
  var self = this;
  var accessToken;
  var header;
  KC.ui.loading.show();

  // アクセストークンを取得
  if (kintoneScheduleService.isExpireAccessToken()) {
    accessToken = storage.getItem('SESSION_KEY_TO_ACCESS_TOKEN');
  } else {
    KC.ui.loading.hide();
    return;
  }

  // タイムゾーンを日本設定
  header = {
    'Authorization': 'Bearer ' + accessToken,
    'Content-Type': 'application/json',
    'Prefer': 'outlook.timezone=\"Tokyo Standard Time\"',
    'outlook.body-content-type': 'text'
  };

  // ヘッダーの日付情報を取得してリクエストURLを作成
  var dateFormat = new DateFormat("yyyy/MM/dd");
  var fromDate = dateFormat.format(new Date(kintoneScheduleService.data.ui.inputFromDate.getValue()));
  var toDate = dateFormat.format(new Date(kintoneScheduleService.data.ui.inputToDate.getValue()));
  var eventGetUrl = EVENT_GET_URL + "?StartDatetime=" + fromDate + "T00:00:00" + "&EndDatetime=" + toDate + "T23:59:59";

  // 入力内容を保持(再表示用)
  storage.inputFromDate = kintoneScheduleService.data.ui.inputFromDate.getValue();
  storage.inputToDate = kintoneScheduleService.data.ui.inputToDate.getValue();

  // Outlookの予定表取得
  kintone.proxy(eventGetUrl, 'GET', header, {}).then(function(res) {
    var data = JSON.parse(res[0]).value;
    if (data === undefined) {
      Swal.fire({
        title: 'ERROR!',
        type: 'error',
        text: kintoneScheduleService.setting.i18n.message.error.accessOutlookFailure,
        allowOutsideClick: false
      });
      KC.ui.loading.hide();
      return;
    }

    // データがない場合
    if (data.length === 0) {
      Swal.fire({
        title: 'WARN!',
        type: 'warning',
        text: kintoneScheduleService.setting.i18n.message.warning.noMail,
        allowOutsideClick: false
      });
      KC.ui.loading.hide();
      return;
    }

    // 取得したスケジュールを登録
    self.putScheduleToKintoneApp(0, data, accessToken).catch(function(err) {
      Swal.fire({
        title: 'ERROR!',
        type: 'error',
        text: kintoneScheduleService.setting.i18n.message.error.addKintoneRecordFailure,
        allowOutsideClick: false
      });
      KC.ui.loading.hide();
    });
  }, function(err) {
    Swal.fire({
      title: 'ERROR!',
      type: 'error',
      text: kintoneScheduleService.setting.i18n.message.error.getOutlookMailFailure,
      allowOutsideClick: false
    });
    KC.ui.loading.hide();
  });
},
~
};

取得したデータのチェック

新規なら登録処理、登録済みなら更新処理に振り分ける

kintone-connect-outlook-schedule.js
var outlookAPI = {
~
// 取得した予定をkintoneへ登録
putScheduleToKintoneApp: function(index, data, accessToken) {
  var self = this;
  // キントーンに登録済みのデータかをチェックする
  return this.getScheduleIDIndexIsNotRetrived(index, data).then(function(indexSchedule) {
    // 登録済の予定なら更新
    if (indexSchedule === null) {
      return self.updateScheduleIntoKintone(data[index], accessToken).then(function(result) {
        // 次のデータ
        if (index + 1 < data.length) {
          return self.putScheduleToKintoneApp(index + 1, data, accessToken);
        }
        return null;
      });
    }

    // 未登録の予定なら登録
    return self.addScheduleIntoKintone(data[indexSchedule], accessToken).then(function(result) {
      // 次のデータ
      if (indexSchedule + 1 < data.length) {
        return self.putScheduleToKintoneApp(indexSchedule + 1, data, accessToken);
      }
      return null;
    });
  }).then(function(resp) {
    window.location.reload();
    KC.ui.loading.hide();
  });
},

getScheduleIDIndexIsNotRetrived: function(index, data) {
  var self = this;
  // 取得済み予定かチェック
  return self.checkSchedulesIsNotExistsOnkintone(data[index].id.substr(data[index].id.length-64)).then(function(resp) {
    if (resp === false) {
      if (data.length <= index + 1) {
        return null;
      }
      return self.getScheduleIDIndexIsNotRetrived(index + 1, data);
    }
    return index;
  });
},

// 取得済み予定かチェック
checkSchedulesIsNotExistsOnkintone: function(scheduleID) {
  var dataRequestkintoneApp = {
    app: kintone.app.getId(),
    fields: ['$id'],
    query: UPDATE_KEY_FIELD_CODE + ' like "' + scheduleID + '"',
    totalCount: true
  };

  return kintoneSDKRecord.getRecords(dataRequestkintoneApp).then(function(response) {
    if (!response.records || response.records.length === 0) {
      return true;
    }
    return false;
  });
},

// 登録処理呼び出し
addScheduleIntoKintone: function(data) {
  var kintoneRecord = outlookAPI.setParam(data);
  return outlookAPI.addKintone(kintoneRecord);
},

// 更新処理呼び出し
updateScheduleIntoKintone: function(data) {
  var kintoneRecord = outlookAPI.setParam(data);
  return outlookAPI.updateKintone(kintoneRecord, data.id);
},
~
};

取得したデータを設定する

kintone-connect-outlook-schedule.js
var outlookAPI = {
~
setParam: function(data) {
  var kintoneRecord = {};
  // Subject
  kintoneRecord[SUBJECT_FIELD_CODE] = {
    value: data.subject
  };
  // From
  kintoneRecord[START_DATE_FIELD_CODE] = {
    value: data.start.dateTime.substr(0, 19)
  };
  // To
  kintoneRecord[END_DATE_FIELD_CODE] = {
    value: data.end.dateTime.substr(0, 19)
  };
  // Details
  kintoneRecord[BODY_FIELD_CODE] = {
    value: data.body.content
  };
  // WorkTime (工数を算出する)
  var sDate = new Date(data.start.dateTime.substr(0, 19));
  var eDate = new Date(data.end.dateTime.substr(0, 19));
  var sTime = sDate.getHours() * 60 + sDate.getMinutes();
  var eTime = eDate.getHours() * 60 + eDate.getMinutes();
  var workTime = (eTime - sTime) / 60;
  kintoneRecord[WORKTIME_FIELD_CODE] = {
    value: workTime
  };
  // Assignees(ログインユーザを固定で設定)
  kintoneRecord[ASSIGNEES_FIELD_CODE] = {
    value: [{code: storage.getItem('SIGN_USER_DISPINFO')}]
  };
  // eventId(OutlookのイベントID)
  kintoneRecord[EVENT_ID_FIELD_CODE] = {
    value: data.id
  };
  // updateKey(OutlookのイベントIDの末尾64桁)
  kintoneRecord[UPDATE_KEY_FIELD_CODE] = {
    value: data.id.substr(data.id.length-64)
  };
  // isAllDay
  var allday = [];
  if (data.isAllDay) {
    allday.push("終日");
  }
  kintoneRecord[ALLDAY_FIELD_CODE] = {
    value: allday
  };
  return kintoneRecord;
},
~
};

登録処理

kintone-connect-outlook-schedule.js
var outlookAPI = {
~
// kintoneへ登録
addKintone: function(postParam) {
  var param = {
    app: kintone.app.getId(),
    record: postParam
  };

  return kintoneSDKRecord.addRecord(param);
},
~
};

更新処理

kintone-connect-outlook-schedule.js
var outlookAPI = {
~
// kintoneへ更新
updateKintone: function(putParam, eventId) {
  // 更新キーはレコード情報から削除する
  delete putParam[UPDATE_KEY_FIELD_CODE];

  // 更新キーを設定
  var updateKey = {
    field: UPDATE_KEY_FIELD_CODE,
    value: eventId.substr(eventId.length-64)
  }

  var param = {
    app: kintone.app.getId(),
    updateKey: updateKey,
    record: putParam
  };

  return kintoneSDKRecord.updateRecordByUpdateKey(param);
},
~
};

サンプルの機能ではまだ微妙なのでさらに改修します

▼改修内容はこちら
- KintoneとOutlookカレンダーを連携して工数を集計する #2

トラブルシューティング

エラー
アクセストークンが期限切れと怒られる
→ログアウトしてもっかいログインする

{
  "error": {
    "code": "InvalidAuthenticationToken",
    "message": "Access token has expired.",
    "innerError": {
      "request-id": "********-****-****-****-************",
      "date": "2020-06-04T06:52:27"
    }
  }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0