見出し画像

「契約が締結されたら、あとは自動でいい」クラウドサイン×Googleドライブ×スプレッドシートで契約書台帳をつくった話


はじめに

契約書まわりの仕事って、地味だけど積み重なると結構つらいですよね。

  • クラウドサインで契約が締結される

  • 締結済みPDFをダウンロードする

  • 部署ごとのフォルダにアップロードし直す

  • 契約先・契約日・金額・契約期間などをExcelやスプレッドシートに手入力する

  • 「あの契約書どこ?」と聞かれて探し回る

うちの会社にも契約書台帳自体はもともとありました。ただし中身は完全に手作業です。契約が締結されるたびに担当者がPDFを開き、契約先・契約日・金額・契約期間などをスプレッドシートに転記する、という運用でした。

今回、この「台帳への転記作業」をクラウドサインのAPIで自動化し、全社の運用ルールとして定着させるところまでやったので、その過程をまとめておきます。同じような課題を抱えている情シス・コーポレート部門の方の参考になれば嬉しいです。

課題感:台帳はあるのに、運用が手作業のままだった

具体的に困っていたのはこんな点です。

  1. 転記が後回しにされる:契約締結時にすぐ台帳へ反映されればいいのですが、日々の業務に追われて後回しにされ、気づけば1ヶ月分の記入が溜まっている、ということが珍しくありませんでした

  2. 反映までのタイムラグ:締結から台帳反映までに数日〜数週間かかることがあり、「台帳を見ても最新の状況が分からない」状態になっていました

  3. 表記ゆれ:日付の書式や契約種別の表記が担当者ごとに微妙に違い、フィルタや検索がうまく機能しない

  4. 転記ミス:手入力である以上、契約金額や契約期間の入力ミスが一定数発生していました

  5. 属人化:台帳の入力を特定の担当者に任せがちで、その人が忙しい・不在だと更新が滞る

台帳という仕組み自体は間違っていなかったので、「入力作業だけを自動化する」ことに狙いを絞りました。

全体の設計

やりたいことをシンプルに図式化するとこうなります。

GASの時間主導型トリガー(例:15分おきに実行)
        ↓
① 前回チェック時刻(最終更新日時)以降に更新された契約を、
   クラウドサインAPIで一覧取得
        ↓
② ステータスが「締結完了」の契約のみ抽出
        ↓
③ 締結済みPDFをクラウドサインAPIから取得
        ↓
④ Googleドライブ APIで指定フォルダに格納
        ↓
⑤ Google Sheets APIで契約書台帳に1行追記
        ↓
⑥ 今回チェックした日時を保存し、次回はそこから続きを見る
        ↓
⑦ Slack通知で担当者に完了報告

本当は「クラウドサインのWebhookで締結イベントを検知して即座に動く」設計を最初に検討していました。ただ、うちのGoogle Workspaceは外部に公開していない(GASをWebアプリとして外部から叩けるURLで公開すること自体がセキュリティポリシー上NG)という制約があり、クラウドサイン側から社内へWebhookを飛ばしてもらう形は取れませんでした。

そこで方針転換し、「GAS側から能動的に、定期的にクラウドサインを見に行く」ポーリング方式にしました。具体的には、最終更新日時をどこかに保持しておき、そこから前進した契約だけをクラウドサインAPIで拾い上げてくる、という形です。Webhookのような即時性はありませんが、1時間に一度程度のポーリングでも業務上は十分実用的でした。

使った技術要素

  • クラウドサインWeb API:契約書の一覧取得(更新日時での絞り込み)、締結済み文書・添付情報の取得

  • Google Drive API:フォルダへのファイルアップロード、権限設定

  • Google Sheets API:台帳シートへの行追記

  • 実行環境:Google Apps Script(GAS)のみ。時間主導型トリガーで定期実行し、最終更新日時はスクリプトプロパティに保持

新たなインフラを立てずにGASだけで完結させたことで、情シスへの申請や環境構築のリードタイムをかけずに、スモールスタートで動くものを作れたのが良かった点です。

実装のポイント

1. 最終更新日時を基準に、定期的にクラウドサインを見に行く

GASの時間主導型トリガーで数分〜十数分おきにスクリプトを実行し、「前回チェックした日時以降に更新された契約」だけをクラウドサインAPIから取得します。前回チェック日時はスクリプトプロパティに保存しておき、次の実行時にはそこから続きを見に行きます。

function checkCloudSignUpdates() {
  const props = PropertiesService.getScriptProperties();
  const lastCheckedAt = props.getProperty('LAST_CHECKED_AT') || '2023-01-01T00:00:00+09:00';

  // 前回チェック日時以降に更新された契約書一覧を取得
  const documents = fetchUpdatedDocuments(lastCheckedAt);

  documents
    .filter(doc => doc.status === 'completed') // 締結完了のみ処理
    .forEach(doc => processCompletedContract(doc.id));

  // 次回はここから続きを見るので、チェック日時を更新しておく
  props.setProperty('LAST_CHECKED_AT', new Date().toISOString());
}

function setupTrigger() {
  ScriptApp.newTrigger('checkCloudSignUpdates')
    .timeBased()
    .everyMinutes(15)
    .create();
}

ポーリング方式にする上で気をつけたのは以下の2点です。

  • 取りこぼしを防ぐための余裕:トリガーが何らかの理由で1回スキップされても更新分を取りこぼさないよう、チェック範囲には少し前後の余裕(バッファ)を持たせています

  • 重複処理の防止:バッファを持たせた結果、同じ契約書を複数回取得してしまうことがあるため、後述の台帳側で契約書IDによる重複チェックを行い、二重登録を防いでいます

2. 締結済みPDFの取得とドライブへの格納

クラウドサインAPIから締結済み文書のバイナリを取得し、Googleドライブの所定フォルダに保存します。フォルダ構成は「年度/部署/取引先名」を基本にし、ファイル名は「締結日_取引先名_契約種別」で統一しました。命名ルールを決め打ちにしたことで、後から人が探すときの検索性が大きく上がりました。

function saveToGoogleDrive(pdfBlob, folderPath, fileName) {
  const folder = getOrCreateFolderByPath(folderPath);
  const file = folder.createFile(pdfBlob).setName(fileName);
  return file.getUrl();
}

getOrCreateFolderByPath は、指定パスのフォルダが存在しなければ自動作成する自前のユーティリティ関数です。担当者が手でフォルダを掘る必要をなくしました。

3. スプレッドシート(契約書台帳)への自動追記

台帳には以下の項目を自動入力するようにしました。

  • 契約書ID(クラウドサイン側のID、重複チェック用のキー)

  • 契約先名

  • 契約種別(業務委託/秘密保持契約/販売代理店契約 など)

  • 締結日

  • 契約期間(開始日・終了日・自動更新の有無)

  • 担当部署/担当者

  • ドライブ格納先URL

  • ステータス(有効/更新待ち/解約済み)

function appendToLedger(sheet, contractData) {
  // 契約書IDで重複チェック
  const existingIds = sheet.getRange('A2:A').getValues().flat();
  if (existingIds.includes(contractData.documentId)) {
    return; // 既に登録済みならスキップ
  }

  sheet.appendRow([
    contractData.documentId,
    contractData.partnerName,
    contractData.contractType,
    contractData.signedDate,
    contractData.startDate,
    contractData.endDate,
    contractData.department,
    contractData.driveUrl,
    '有効'
  ]);
}

契約期間の終了日を入力しておくことで、後から「更新期限が近い契約」をフィルタや条件付き書式で可視化できるようにしたのも地味に効きました。

4. 通知でひと安心

処理が完了したら、担当部署のSlackチャンネルに「〇〇社との契約書を格納し、台帳に登録しました」と自動通知するようにしました。「ちゃんと処理されたか不安」という心理的な負担をなくすためです。

苦労した点

  • クラウドサインAPI側の仕様変更への追従:APIのレスポンス形式が変わるタイミングがあり、パース処理でエラーが出たことがあった。エラー時にSlackへアラートを飛ばす仕組みを後から追加した

  • フォルダ権限の設計:全社展開する以上、誰でも中身が見えていい契約と、経理・法務しか見てはいけない契約が混在する。フォルダ単位で閲覧権限を分け、機密性の高い契約は自動的に限定共有フォルダへ振り分けるロジックを追加した

  • GASの実行時間制限:一度のポーリングで更新件数が多いとGASの実行時間上限(6分)に引っかかることがあり、1回の実行で処理する件数に上限を設け、処理しきれなかった分は最終更新日時を更新せずに次回のトリガーで続きから処理する形にした

  • 外部公開できない制約への対応:当初はクラウドサインのWebhookを使う想定だったが、GASを外部公開のWebアプリとして立てられないという社内のセキュリティポリシーがあり、急遽ポーリング方式に設計変更した。即時性は落ちるが、15分程度の遅延は業務上問題にならなかった

  • 例外系の契約:契約書が分割されている、当事者が3社以上いる、などのイレギュラーな契約は自動処理からいったん外し、担当者に手動対応を促す仕分けロジックを入れた

「完全に自動化」を目指しすぎると例外処理で疲弊するので、8〜9割の定型契約を自動化し、残りは人の目に委ねる、という割り切りをしたのが結果的に良かったと思っています。

全社展開のプロセス

仕組みができても、使ってもらえなければ意味がありません。展開にあたっては以下を意識しました。

  1. まずは法務・経理部門で試験運用:もっとも契約書を扱う頻度の高い部門で1〜2ヶ月試し、フィードバックをもらう

  2. 既存台帳のフォーマットを棚卸しする:これまで手入力で使っていた台帳の列を見直し、担当者ごとに表記がブレていた項目(日付形式や契約種別の表記など)を自動入力にあわせて統一した

  3. 説明会と簡単なマニュアル作成:全社向けに「クラウドサインで契約を締結すると、こう処理されます」という3分程度の説明動画と1枚もののマニュアルを用意

  4. 問い合わせ窓口の明確化:エラー時や例外契約の相談先をSlackの専用チャンネルに一本化

トップダウンで「使ってください」と言うより、「これまで手作業でやっていた台帳の記入作業がなくなります」というメリットを具体的に伝えたことで、現場の抵抗感なくスムーズに浸透しました。

得られた効果

  • 契約書を探す時間がほぼゼロに(フォルダ構成とファイル名が統一されたため)

  • 台帳への転記漏れがなくなり、監査対応の工数が大幅に削減

  • 契約更新期限のうっかり失効がなくなった(台帳から自動リマインドの仕組みも追加)

  • 特定の担当者に依存していた契約書管理業務が、仕組みとして誰でも回せる状態になった

数値化しづらい部分もありますが、体感として「契約書まわりの問い合わせ対応時間が半分以下になった」という声が現場から上がっています。

今後の展望

現状は「締結」イベントのみをトリガーにしていますが、今後は以下も検討しています。

  • 契約更新期限が近づいた契約を自動でリストアップし、担当者にリマインドする仕組みの強化

  • 解約・失効イベントも同様にポーリングで検知し、台帳のステータスを自動更新

  • 契約書の内容自体をテキスト化し、検索性をさらに高める

おわりに

「締結されたら、あとは自動でいい」という状態をつくることで、契約書管理という地味だけど止められない業務の負担を大きく減らすことができました。クラウドサインのAPIとGoogle Workspaceの組み合わせは、そこまで大掛かりな開発をしなくても実現できるので、同じような課題を抱えている方にはぜひ検討してみてほしいです。

何かご質問やフィードバックがあれば、コメントで気軽に聞いてください。

いいなと思ったら応援しよう!