Questetra BPM Suite の外からケースを開始する(Google Apps Script 編)

在庫の一覧、取引先の一覧、対応待ちの一覧。Google スプレッドシートで管理している表の中から、いま処理が必要な行だけをワークフローに乗せたい、という場面があります。行の内容をケースの起票フォームに手で入力し直すのは手間ですし、写し間違いも起きます。

この記事では、Google Apps Script から[メッセージ開始イベント (HTTP)]を呼び出し、スプレッドシートに追加した独自メニューのクリックで、選んだ行の内容を持ったケースを開始します。表のすべての行を機械的に処理するのではなく、どの行をケースにするかを人が選べることが、この方法の特徴です。在庫の一覧から発注のケースを開始する例で、独自メニューの追加から、開始されたケースの ID を行に書き戻すところまでを作ります。

この記事は連載「Questetra BPM Suite の外からケースを開始する」の 1 本です。[メッセージ開始イベント (HTTP)]の設定は準備編で説明しています。

シートのメニューからケースを開始する

準備:スクリプトを書く前に控えておくもの

ワークフローアプリに[メッセージ開始イベント (HTTP)]を配置し、シートから受け取るデータ項目を用意します。設定の手順は準備編のとおりです。スクリプトを書く前に、次の 3 つを控えておきます。

  • リクエスト URL(https://~/System/Event/MessageStart/~/start の形式)
  • API キー
  • データを受け取るデータ項目のフィールド名(q_ で始まる名前)

この記事では、次のデータ項目を用意したものとして進めます。

データ項目名データ型フィールド名
品目名文字文字
型番文字q_model
発注数数値q_quantity
希望納期日付 (年月日)q_dueDate
仕入先文字q_supplier

データ編集許可は、「件名」を含めて「編集可」に設定してください。件名は title というパラメータ名で送ります。データ型ごとのパラメータの書式は、次を参照してください。

IP アドレス制限を、Google Apps Script から通す

準備編で説明している IP アドレス制限は、Google Apps Script が相手の場合に注意が必要です。Google Apps Script がリクエストを送る際の送信元 IP アドレスは Google 側で管理されており、利用者が固定することはできません。そのため、許可するネットワークとして特定の IP アドレスを指定することは実質的にできず、0.0.0.0/0(すべて許可)を指定することになります。全体の設定を緩める必要はありません。IP アドレス制限の[メッセージ開始イベント/受信タスク設定]で、対象をこの開始イベントに絞って 0.0.0.0/0 を指定すれば、他の URL への制限はそのまま保てます。

この場合、エンドポイントを保護するのは API キーだけになります。API キーは自分で値を決めず、モデラの自動生成([キーを自動生成]ボタン)に任せてください。また、スクリプト内に直接書かず、次に説明するスクリプトプロパティに保管してください。

なお、この設定を行えるのは、システム管理権限を持つアカウントです。アプリを作成できる権限とは別ですので、権限を持っていない場合はシステム管理者に依頼してください。

シートの列と、書き戻し用の列

1 行目を見出し行とし、2 行目以降を 1 件 1 行のデータとします。この記事では、次の列があるものとして進めます。

  • 品目名
  • 型番
  • 発注数
  • 希望納期
  • 仕入先
  • ケース ID

「ケース ID」は、開始されたケースの ID を書き戻すための列です。この列に値が入っている行は起票済みとみなし、もう一度メニューをクリックしても開始しないようにします。同じ行から二重にケースが開始されるのを防ぐためのものです。

API キーの保管

API キーはスクリプト本体には書かず、スクリプトプロパティに保管します。Apps Script エディタの[プロジェクトの設定]にある「スクリプト プロパティ」で、プロパティ名 QUESTETRA_API_KEY として値を登録してください。スクリプト本体をコピーして渡したときに、API キーが一緒に渡ってしまうことを避けられます。

ただし、スプレッドシートを編集できる権限で共有した相手は、Apps Script エディタを開いてスクリプトプロパティの値を読むことができます。API キーを見せたくない相手とは、閲覧権限で共有してください。

スクリプトの記述

スプレッドシートのメニューから[拡張機能]>[Apps Script]を開き、次のスクリプトを貼り付けます。ENDPOINT は、準備編で確認したリクエスト URL に置き換えてください。リクエスト URL に ?key= 以降が付いている場合は、? より前の部分だけを使います。

準備編では「この URL をそのまま使えば API キーも一緒に送られる」と説明していますが、この記事のスクリプトは、API キーをスクリプトプロパティから読んでリクエストの本文に入れて送ります。URL 側にも ?key= が残っていると、API キーを二重に指定したことになり、値が正しくてもエラーになります。

Script (click to open)

const ENDPOINT = 'https://example.questetra.net/System/Event/MessageStart/1234/0/start';

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Questetra BPM Suite')
    .addItem('選択行からケースを開始', 'startCaseFromSelectedRow')
    .addToUi();
}

function startCaseFromSelectedRow() {
  const ui = SpreadsheetApp.getUi();
  const sheet = SpreadsheetApp.getActiveSheet();
  const row = sheet.getActiveRange().getRow();
  if (row === 1) {
    ui.alert('見出し行が選択されています。データの行を選択してください。');
    return;
  }

  const header = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
  const values = sheet.getRange(row, 1, 1, sheet.getLastColumn()).getValues()[0];
  const record = {};
  header.forEach(function (name, i) { record[name] = values[i]; });

  if (record['ケース ID']) {
    ui.alert('この行はすでに起票済みです(ケース ID: ' + record['ケース ID'] + ')。');
    return;
  }

  const apiKey = PropertiesService.getScriptProperties().getProperty('QUESTETRA_API_KEY');
  if (!apiKey) {
    ui.alert('スクリプトプロパティ QUESTETRA_API_KEY が登録されていません。');
    return;
  }

  const payload = {
    key: apiKey,
    title: record['品目名'] + ' の発注',
    q_item: record['品目名'],
    q_model: record['型番'],
    q_quantity: String(record['発注数']),
    q_dueDate: Utilities.formatDate(record['希望納期'], 'Asia/Tokyo', 'yyyy-MM-dd'),
    q_supplier: record['仕入先']
  };

  const response = UrlFetchApp.fetch(ENDPOINT, {
    method: 'post',
    payload: payload,
    muteHttpExceptions: true
  });

  if (response.getResponseCode() !== 200) {
    ui.alert('ケースを開始できませんでした。\n応答コード: ' + response.getResponseCode()
      + '\n応答内容: ' + response.getContentText());
    return;
  }

  const caseId = response.getContentText().trim();
  sheet.getRange(row, header.indexOf('ケース ID') + 1).setValue(caseId);
  ui.alert('ケースを開始しました(ケース ID: ' + caseId + ')。');
}

列名でデータを取り出しています。 見出し行の文字列でパラメータを組み立てているため、列の順番が入れ替わってもスクリプトを直す必要がありません。列名そのものを変更したときは、スクリプト側の record['…'] も合わせて変更します。

見出しの「ケース ID」は、スクリプトの文字列と 1 字も違わないようにします。 「ケースID」のように間の半角スペースが無かったり、全角スペースだったりすると、起票済みの判定も、ケース ID の書き戻しも働きません。

起票されるのは、選択範囲の先頭行の 1 件だけです。 複数の行を選んでメニューをクリックしても、2 行目以降は無視されます。

発注数は String() で文字列にしてから送っています。 数値のまま payload に入れると 20 が 20.0 として送信され、小数点以下 0 桁の数値型データ項目に入りません。理由は後述します。

payload にオブジェクトを渡すと、application/x-www-form-urlencoded 形式で送信されます。 [メッセージ開始イベント (HTTP)]が受け取れる形式です。

muteHttpExceptions: true を付けています。 これを付けないと、エラーの応答が返ったときに Google Apps Script 側で例外となり、Questetra BPM Suite が返したエラー内容を読めません。

メニューの表示と、初回の承認

onOpen はスプレッドシートを開いたときに実行される関数です。スクリプトを保存したあと、スプレッドシートを再読み込みすると、メニューバーに[Questetra BPM Suite]が追加されます。保存するだけで動きます。ウェブアプリのようなデプロイの操作は必要ありません。

なお、onOpen の中では UrlFetchApp のように承認が必要なサービスを呼び出せません。onOpen はメニューを組み立てるだけにして、通信はメニューから呼ばれる関数の側で行います。

初めて[選択行からケースを開始]をクリックしたときは、Google アカウントの承認画面が表示されます。このスクリプトは、スプレッドシートの読み書きと、外部サービスへの接続を行うため、その 2 つの権限を承認することになります。承認は、スクリプトを実行する人ごとに 1 回必要です。承認画面にはプロジェクト名が表示されるので、「無題のプロジェクト」のままにせず、エディタ上部で名前を付けておいてください。

開始されたケースの ID が行に書き戻される

送信する値の形式

セルから読み取った値をそのまま送ると、意図した形式にならないことがあります。

日付・日時 — 日付が入ったセルの値は、Date オブジェクトとして読み取られます。上のスクリプトの「希望納期」のように、Utilities.formatDate(value, 'Asia/Tokyo', 'yyyy-MM-dd') で文字列へ変換してから送ります。空のセルは Date ではなく空文字列として読み取られ、Utilities.formatDate はエラーになります。空欄がありうる列では、record['希望納期'] ? Utilities.formatDate(record['希望納期'], 'Asia/Tokyo', 'yyyy-MM-dd') : '' のように、値があるときだけ変換してください。

選択肢 — 選択型のデータ項目には、画面に表示されるラベルではなく、選択肢 ID を送ります。シートに選択肢 ID の列を持たせるか、スクリプト内でラベルから ID へ変換します。

数値 — 数値が入ったセルの値は、数値として読み取られます。これを payload にそのまま入れると 20 が 20.0 として送信され、小数点以下 0 桁の数値型データ項目では「小数以下の桁数は 0 桁でなければいけません。」というエラーになります。JSON.stringify でログに出しても 20 としか見えないため、気づきにくいところです。上のスクリプトの「発注数」のように、String(record['発注数']) で文字列へ変換してから送ってください。小数点はピリオドで送ります。セルに 1,234 のような文字列で入っている場合は、String(value).replace(/,/g, '') でカンマを取り除きます。対象の列にデータの入力規則(数値)を設定しておくと、文字列の混入自体を防げます。

うまくいかないときの確認

応答コードが 200 以外のときは、上のスクリプトが応答内容をそのまま表示します。まずはその内容を確認してください。

  • 応答コードが 403 の場合は、IP アドレス制限の設定を確認します
  • 応答コードが 400 で XML が返る場合は、リクエストの内容が受け付けられていません

400 のときに返る XML は <detail>不正です。</detail> だけで、どこが原因かは示されません。次の順に確認してください。

  1. API キーが、モデラに表示されている値と一致しているか(スクリプトプロパティの値を見直します)
  2. API キーを二重に送っていないか(ENDPOINT に ?key= が残っていないか)
  3. 必須に設定したデータ項目に、パラメータを送っているか
  4. パラメータ名(q_ で始まる名前)と値の書式が、データ項目の定義と合っているか

原因の切り分け方は、次の記事で説明しています。

記事末尾

連載「Questetra BPM Suite の外からケースを開始する」

参考

Questetra Supportをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む