スプレッドシートで業務システムを構築する方法【GAS活用ガイド】

この記事でわかること

  • スプレッドシートを業務システム化するメリット
  • 効果的なシート設計のパターン
  • カスタムメニューの作り方
  • 入力規則・データ検証の設定方法
  • GASによる自動化の実装方法

この記事の対象読者

  • Excelやスプレッドシートで業務管理している方
  • 専用システムを導入するほどではないが効率化したい方
  • GAS(Google Apps Script)で業務自動化を検討している方
  • 低コストで業務システムを構築したい方

なぜスプレッドシートで業務システムなのか

専用システム vs スプレッドシート

項目専用システムスプレッドシート
初期コスト高い(数十万〜数百万円)無料
月額費用あり(数千〜数万円/月)なし
導入期間長い(数週間〜数ヶ月)即日
カスタマイズ制限あり or 追加費用自由自在
学習コスト新しいUIを覚える必要使い慣れた操作
データ出力制限あり自由に加工可能

スプレッドシートが向いているケース

  • ✅ 数人〜数十人で使う小規模な業務
  • ✅ データ量が数万件以下
  • ✅ 頻繁に仕様変更が発生する
  • ✅ とりあえず試してみたい
  • ✅ 予算が限られている

スプレッドシートが向かないケース

  • ❌ 同時編集者が100人以上
  • ❌ データ量が数十万件以上
  • ❌ 厳密な権限管理が必要
  • ❌ 複雑なワークフローが必要

システム設計の基本

3層構造で考える

スプレッドシートで業務システムを作る際は、以下の3層構造を意識します。

┌─────────────────────────────────────┐
│  UI層:メニュー・ボタン・入力規則    │
├─────────────────────────────────────┤
│  ロジック層:GAS(処理・自動化)     │
├─────────────────────────────────────┤
│  データ層:シート(データ保存)      │
└─────────────────────────────────────┘
役割使用技術
UI層ユーザーが操作する画面メニュー、ボタン、入力規則、条件付き書式
ロジック層データ処理・自動化Google Apps Script(GAS)
データ層データの保存・管理スプレッドシートのシート

シート設計パターン

基本の4シート構成

多くの業務システムは、以下の4シート構成で実現できます。

シート名役割
マスタメインデータの一覧顧客一覧、商品一覧、案件一覧
履歴トランザクション・ログ注文履歴、対応履歴、変更履歴
設定システム設定値APIキー、オプション、フラグ
ログ実行ログ・エラーログデバッグ・監査用

シート設計の例:顧客管理システム

マスタシート(顧客一覧)

ABCDEFG
選択顧客ID会社名担当者名メールステータス登録日
C001株式会社ABC田中太郎tanaka@abc.co.jpアクティブ2024/01/15
C002株式会社XYZ鈴木花子suzuki@xyz.co.jpアクティブ2024/02/20

履歴シート(対応履歴)

ABCDE
日時顧客ID対応種別内容担当者
2024/06/01 10:30C001電話新製品の問い合わせ山田
2024/06/02 14:00C002メール見積もり送付佐藤

設定シート

AB
項目
通知メールアドレスadmin@example.com
自動バックアップTRUE
処理状況待機中

シート設計のベストプラクティス

1. 1行目はヘッダー固定

データは必ず2行目から開始する
→ GASでの処理が簡単になる

2. A列は選択用チェックボックス

一括処理の対象を選択できるようにする
→ 「選択した行だけ処理」が実現できる

3. IDは自動採番

javascript

// 最大ID + 1 で自動採番
function getNextId(sheet, idColumn) {
  const data = sheet.getDataRange().getValues();
  let maxId = 0;
  for (let i = 1; i < data.length; i++) {
    const id = parseInt(data[i][idColumn - 1].toString().replace(/\D/g, '')) || 0;
    if (id > maxId) maxId = id;
  }
  return maxId + 1;
}

4. 日時は自動記録

javascript

// 登録日時を自動記録
sheet.getRange(newRow, 7).setValue(new Date());

5. ステータスはプルダウン

入力規則でプルダウンを設定
→ 入力ミス防止、集計しやすい

入力規則の設定

プルダウンリストの作成

javascript

/**
 * プルダウンリストを設定
 */
function setDropdown() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  
  // ステータス列(F列)にプルダウンを設定
  const statusRange = sheet.getRange('F2:F1000');
  const statusRule = SpreadsheetApp.newDataValidation()
    .requireValueInList(['アクティブ', '休眠', '解約'], true)
    .setAllowInvalid(false)
    .build();
  statusRange.setDataValidation(statusRule);
}

チェックボックスの設定

javascript

/**
 * チェックボックスを設定
 */
function setCheckboxes() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  
  // A列にチェックボックスを設定
  const checkRange = sheet.getRange('A2:A1000');
  checkRange.insertCheckboxes();
}

メールアドレスの検証

javascript

/**
 * メールアドレス形式を検証
 */
function setEmailValidation() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  
  // E列にメール形式の検証を設定
  const emailRange = sheet.getRange('E2:E1000');
  const emailRule = SpreadsheetApp.newDataValidation()
    .requireTextContains('@')
    .setAllowInvalid(true)  // 警告のみ
    .setHelpText('有効なメールアドレスを入力してください')
    .build();
  emailRange.setDataValidation(emailRule);
}

カスタムメニューの作成

基本的なメニュー

javascript

/**
 * スプレッドシートを開いた時にメニューを作成
 */
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  
  ui.createMenu('📊 顧客管理')
    .addItem('➕ 新規顧客を追加', 'addNewCustomer')
    .addItem('📧 選択した顧客にメール送信', 'sendEmailToSelected')
    .addSeparator()
    .addItem('📤 CSVエクスポート', 'exportToCsv')
    .addItem('📥 CSVインポート', 'importFromCsv')
    .addSeparator()
    .addSubMenu(ui.createMenu('⚙️ 設定')
      .addItem('🔧 初期セットアップ', 'initialSetup')
      .addItem('🔄 入力規則を再設定', 'resetValidation'))
    .addToUi();
}

メニューアイコンの例

アイコン用途
追加
✏️編集
🗑️削除
📧メール
📤エクスポート
📥インポート
🔄更新・同期
⚙️設定
🔧セットアップ
▶️開始
⏹️停止
完了・選択

よく使う機能の実装

選択した行を取得

javascript

/**
 * チェックボックスで選択された行を取得
 */
function getSelectedRows() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  const data = sheet.getDataRange().getValues();
  
  const selectedRows = [];
  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === true) {  // A列がチェックされている
      selectedRows.push({
        rowIndex: i + 1,
        data: data[i]
      });
    }
  }
  
  return selectedRows;
}

/**
 * 選択した顧客にメール送信
 */
function sendEmailToSelected() {
  const selected = getSelectedRows();
  
  if (selected.length === 0) {
    SpreadsheetApp.getUi().alert('顧客を選択してください');
    return;
  }
  
  selected.forEach(row => {
    const email = row.data[4];  // E列:メールアドレス
    const name = row.data[3];   // D列:担当者名
    
    MailApp.sendEmail({
      to: email,
      subject: 'お知らせ',
      body: `${name}様\n\nいつもお世話になっております。`
    });
  });
  
  SpreadsheetApp.getUi().alert(`${selected.length}件のメールを送信しました`);
}

新規データの追加(ダイアログ)

javascript

/**
 * 新規顧客追加ダイアログを表示
 */
function addNewCustomer() {
  const html = HtmlService.createHtmlOutput(`
    <style>
      body { font-family: Arial, sans-serif; padding: 20px; }
      label { display: block; margin-top: 10px; font-weight: bold; }
      input, select { width: 100%; padding: 8px; margin-top: 5px; box-sizing: border-box; }
      button { margin-top: 20px; padding: 10px 20px; background: #4285f4; color: white; border: none; cursor: pointer; }
      button:hover { background: #3367d6; }
    </style>
    
    <label>会社名</label>
    <input type="text" id="company" required>
    
    <label>担当者名</label>
    <input type="text" id="name" required>
    
    <label>メールアドレス</label>
    <input type="email" id="email" required>
    
    <label>ステータス</label>
    <select id="status">
      <option value="アクティブ">アクティブ</option>
      <option value="休眠">休眠</option>
    </select>
    
    <button onclick="submitForm()">登録</button>
    
    <script>
      function submitForm() {
        const data = {
          company: document.getElementById('company').value,
          name: document.getElementById('name').value,
          email: document.getElementById('email').value,
          status: document.getElementById('status').value
        };
        
        if (!data.company || !data.name || !data.email) {
          alert('必須項目を入力してください');
          return;
        }
        
        google.script.run
          .withSuccessHandler(() => {
            alert('登録しました');
            google.script.host.close();
          })
          .withFailureHandler(err => alert('エラー: ' + err))
          .saveNewCustomer(data);
      }
    </script>
  `)
  .setWidth(400)
  .setHeight(350);
  
  SpreadsheetApp.getUi().showModalDialog(html, '新規顧客登録');
}

/**
 * 新規顧客を保存
 */
function saveNewCustomer(data) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  
  // 新しいIDを生成
  const newId = 'C' + String(getNextId(sheet, 2)).padStart(3, '0');
  
  // 新しい行を追加
  sheet.appendRow([
    false,           // 選択チェックボックス
    newId,           // 顧客ID
    data.company,    // 会社名
    data.name,       // 担当者名
    data.email,      // メールアドレス
    data.status,     // ステータス
    new Date()       // 登録日
  ]);
}

CSVエクスポート

javascript

/**
 * CSVファイルとしてエクスポート
 */
function exportToCsv() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  const data = sheet.getDataRange().getValues();
  
  // CSVデータを作成
  let csv = '';
  data.forEach((row, index) => {
    if (index === 0) {
      // ヘッダー行(A列の「選択」は除く)
      csv += row.slice(1).join(',') + '\n';
    } else {
      // データ行(A列のチェックボックスは除く)
      csv += row.slice(1).map(cell => {
        // カンマや改行を含む場合はダブルクォートで囲む
        if (String(cell).includes(',') || String(cell).includes('\n')) {
          return `"${cell}"`;
        }
        return cell;
      }).join(',') + '\n';
    }
  });
  
  // ダウンロードダイアログを表示
  const html = HtmlService.createHtmlOutput(`
    <style>
      body { font-family: Arial, sans-serif; padding: 20px; }
      textarea { width: 100%; height: 200px; font-family: monospace; }
      button { margin-top: 10px; padding: 10px 20px; background: #4285f4; color: white; border: none; cursor: pointer; }
    </style>
    <p>以下のデータをコピーしてCSVファイルとして保存してください:</p>
    <textarea id="csv" readonly>${csv}</textarea>
    <button onclick="copyToClipboard()">📋 コピー</button>
    <script>
      function copyToClipboard() {
        document.getElementById('csv').select();
        document.execCommand('copy');
        alert('コピーしました');
      }
    </script>
  `)
  .setWidth(500)
  .setHeight(350);
  
  SpreadsheetApp.getUi().showModalDialog(html, 'CSVエクスポート');
}

条件付き書式の活用

ステータスによる色分け

javascript

/**
 * ステータスに応じて行の背景色を変更
 */
function setConditionalFormatting() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  const range = sheet.getRange('A2:G1000');
  
  // 既存のルールをクリア
  sheet.clearConditionalFormatRules();
  
  const rules = [];
  
  // アクティブ:緑
  rules.push(SpreadsheetApp.newConditionalFormatRule()
    .whenFormulaSatisfied('=$F2="アクティブ"')
    .setBackground('#E8F5E9')
    .setRanges([range])
    .build());
  
  // 休眠:黄色
  rules.push(SpreadsheetApp.newConditionalFormatRule()
    .whenFormulaSatisfied('=$F2="休眠"')
    .setBackground('#FFF8E1')
    .setRanges([range])
    .build());
  
  // 解約:赤
  rules.push(SpreadsheetApp.newConditionalFormatRule()
    .whenFormulaSatisfied('=$F2="解約"')
    .setBackground('#FFEBEE')
    .setRanges([range])
    .build());
  
  sheet.setConditionalFormatRules(rules);
}

自動化の実装

定期実行(トリガー)

javascript

/**
 * 毎日の定期処理を設定
 */
function setupDailyTrigger() {
  // 既存のトリガーを削除
  ScriptApp.getProjectTriggers().forEach(trigger => {
    if (trigger.getHandlerFunction() === 'dailyReport') {
      ScriptApp.deleteTrigger(trigger);
    }
  });
  
  // 毎日9時に実行
  ScriptApp.newTrigger('dailyReport')
    .timeBased()
    .everyDays(1)
    .atHour(9)
    .create();
}

/**
 * 日次レポートを送信
 */
function dailyReport() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('顧客一覧');
  const data = sheet.getDataRange().getValues();
  
  // 集計
  let activeCount = 0;
  let dormantCount = 0;
  
  for (let i = 1; i < data.length; i++) {
    if (data[i][5] === 'アクティブ') activeCount++;
    if (data[i][5] === '休眠') dormantCount++;
  }
  
  // メール送信
  const adminEmail = getSettingValue(ss, '通知メールアドレス');
  MailApp.sendEmail({
    to: adminEmail,
    subject: '【日次レポート】顧客管理システム',
    body: `
顧客管理システム 日次レポート

■ 顧客数サマリー
・アクティブ: ${activeCount}件
・休眠: ${dormantCount}件
・合計: ${data.length - 1}件

詳細はスプレッドシートをご確認ください。
${ss.getUrl()}
    `
  });
}

編集時の自動処理

javascript

/**
 * 編集時に自動実行
 */
function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  const range = e.range;
  
  // 顧客一覧シート以外は無視
  if (sheet.getName() !== '顧客一覧') return;
  
  // ステータス列(F列)が変更された場合
  if (range.getColumn() === 6) {
    // 履歴に記録
    const historySheet = e.source.getSheetByName('履歴');
    const customerId = sheet.getRange(range.getRow(), 2).getValue();
    
    historySheet.appendRow([
      new Date(),
      customerId,
      'ステータス変更',
      `${e.oldValue} → ${e.value}`,
      Session.getActiveUser().getEmail()
    ]);
  }
}

完成イメージ

メニュー構成

📊 顧客管理
├── ➕ 新規顧客を追加
├── 📧 選択した顧客にメール送信
├── ──────────────
├── 📤 CSVエクスポート
├── 📥 CSVインポート
├── ──────────────
└── ⚙️ 設定
    ├── 🔧 初期セットアップ
    └── 🔄 入力規則を再設定

シート構成

顧客管理システム
├── 📋 顧客一覧(マスタ)
├── 📜 履歴(トランザクション)
├── ⚙️ 設定
└── 📝 ログ

実装のコツ

1. まず小さく作る

❌ 最初から全機能を作り込む
⭕ 必要最小限で動くものを作り、徐々に拡張

2. 設定値はシートで管理

❌ コード内に直接値を書く
⭕ 設定シートで管理し、変更しやすく

3. ログを残す

❌ 何が起きたかわからない
⭕ ログシートに記録し、トラブル時に追跡可能に

4. エラーハンドリング

javascript

// ユーザーに優しいエラー表示
try {
  // 処理
} catch (e) {
  SpreadsheetApp.getUi().alert('エラーが発生しました: ' + e.message);
  addLog(ss, e.message, 'ERROR');
}

まとめ

スプレッドシート業務システムのメリット

メリット説明
低コスト無料で始められる
導入が早い即日で運用開始可能
カスタマイズ自由自分で機能追加できる
学習コスト低使い慣れた操作感
データ活用しやすいそのまま分析・加工可能

設計のポイント

ポイント内容
3層構造UI層・ロジック層・データ層を意識
4シート構成マスタ・履歴・設定・ログ
入力規則プルダウン・チェックボックスで入力ミス防止
カスタムメニュー誰でも使いやすいUI
自動化GASで定期実行・自動処理

関連記事

もっと詳しく知りたい方へ

「スプレッドシートで業務システムを作りたい」 「既存のExcel業務をGoogleに移行したい」 「GASで自動化を実現したい」

そのようなご要望があれば、お気軽にご相談ください。

AIDXの仕組みを社内で内製化したい方へ

スパルタAIDX研修で、現場で使えるAI実践スキルを短期集中で習得できます。
ベテランの技術継承や業務自動化を自社で実現したい方におすすめです。

AIDXの仕組みを構築を依頼したい方へ

貴社の課題に合わせた最適なソリューションをご提案いたします。
導入事例の詳細や、具体的な実装方法についてもお気軽にご相談ください。

CONTACT

資料請求・お問い合わせはこちらから

お気軽にご連絡ください