田中力磨 › 一人塾のGAS自動化 実録 › 学年の自動進級

毎年4月1日に生徒の学年を自動で進級させる

年に1回しかやらない学年更新は、手作業だとやり方を忘れて事故りやすい。GASで4月1日に自動実行し、判断が要る幼児・高3だけは人に確認を求める設計にした。

2026-09-14 公開 / 滋賀県彦根市の一人塾「総合学習教室ブリッジ」で実際に動いている仕組みの記録 / Google Apps Script(GAS)

何を自動化したか

一人塾の生徒管理では、4月1日になると生徒シートの「学年」列を一括で書き換える作業が毎年発生する。小1は小2に、中3は高1に、といった具合に全員分を手で直していくと、行を1つ飛ばして学年がずれる・去年の入力し忘れに気づかない、といった事故が起きやすい。年に1回しかやらない作業ほど手順を覚えていないので、毎年やり方を思い出すところから始まっていた。

この作業をGoogle Apps Script(GAS)の関数1つに置き換え、4月1日の朝に自動実行するようにした。実測で「何分減った」という比較はしていない(年1回の作業のため計測しづらい)ので、ここは体感になるが、当日に何も操作しなくても学年が揃っている状態になり、進級忘れ・入力ミスの心配自体がなくなった。

仕組み(データの流れ)

生徒シートは「bridge生徒管理」という名前のGoogleスプレッドシートで管理している。学年の自動進級はこのシートを直接読み書きするだけのシンプルな仕組みで、外部サービスとの連携は無い。

ステップやること
1. 起動毎月1日の朝に走る既存のディスパッチャー関数から、進級処理を呼び出す(4月以外の月は関数の内部で即リターンする)
2. 二重実行ガードスクリプトプロパティに「今年度は実行済み」の印が付いていないか確認する
3. シート特定「生徒」「生徒一覧」などいくつかの想定シート名を順に探し、見つからなければ先頭シートを使う
4. 列特定ヘッダー行の文言(日本語・英語どちらでも)から「学年」「氏名」「退会日」の列位置を割り出す
5. 一括更新退会済みでない行だけ、学年を1つ先に進める。幼児・高3は自動対象外とし、名前を集めておく
6. 通知進級した人数と、幼児・高3で手動確認が必要な人の名前をメールで通知する

「塾 GAS 自動化」の文脈でよくある悩みが、生徒管理シートの列構成をあとから変えると自動化スクリプトが壊れることだが、ここでは列番号を直接指定せず、ヘッダー文言から探す方式にしている。列を1つ足しても並び替えても、ヘッダー名さえ変えなければ動き続ける。

コード(要点)

進級テーブルはオブジェクト1個で済む。キーが現在の学年、値が進級後の学年で、対応が無い学年(幼児・高3)は自動では動かさない。

var GRADE_NEXT = {
  '小1': '小2', '小2': '小3', '小3': '小4', '小4': '小5', '小5': '小6', '小6': '中1',
  '中1': '中2', '中2': '中3', '中3': '高1',
  '高1': '高2', '高2': '高3',
};

function gradePromoteAnnual(force) {
  var now = new Date();
  if (!force && (now.getMonth() + 1) !== 4) return '';

  var props = PropertiesService.getScriptProperties();
  var fiscalYear = String(now.getFullYear());
  if (props.getProperty('GRADE_PROMOTED_YEAR') === fiscalYear) {
    return '学年進級: ' + fiscalYear + '年度分は実行済みです';
  }

  var srcId = props.getProperty('BRIDGE_DATA_SHEET_ID');
  var src = SpreadsheetApp.openById(srcId);
  var sheet = null;
  ['生徒', '生徒一覧', 'students', 'Students'].some(function(name) {
    sheet = src.getSheetByName(name);
    return !!sheet;
  });
  if (!sheet) sheet = src.getSheets()[0];
  // ヘッダー文言から列位置を割り出す(列の並びが変わっても壊れない)
  var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
    .map(function(h) { return String(h || '').trim().toLowerCase(); });
  // ...gradeCol / nameCol / leaveCol を探す処理は省略(全文はnoteへ)...
}

本体は、退会済みでない行だけを対象に GRADE_NEXT を引いて書き換え、該当が無い「幼児」「高3」は名前を配列にためて最後にまとめてメール本文へ入れる、という流れだけになっている。長くなるのは列探索まわりのガードで、進級のロジック自体は10行に満たない。

ハマった点と回避策

最初に決めておかないと事故につながった点が2つあった。

導入手順

  1. スプレッドシートのIDをスクリプトプロパティ BRIDGE_DATA_SHEET_ID に保存する(コードに直接書かない)。
  2. 通知先メールを使いたい場合はスクリプトプロパティ NOTIFY_EMAIL を設定する(未設定なら実行アカウントのメールが使われる)。
  3. 生徒シートに「学年」「氏名」(できれば「退会日」)の見出しがある列を用意する。列の並び順は問わない。
  4. GRADE_NEXT の対応表を、自分の教室の学年区分(学年制・級制など)に合わせて書き換える。
  5. 4月1日の朝に自動実行したい場合は、月初に走る既存のトリガー(無ければonMonthDay(1)の時間主導型トリガー)から gradePromoteAnnual() を呼ぶ1行を足す。新しいトリガーをむやみに増やさず、既存のトリガーに相乗りするのがGASのトリガー上限(20本)対策になる。
  6. 年度の途中で強制的に再実行したい場合は gradePromoteAnnual(true) のように引数へ真値を渡す(月チェックを飛ばして即実行される。二重実行ガードは効いたままなので、同じ年度で連発しても安全)。

よくある質問

Q. 学年ではなく「級」や「コース」で管理している場合も使える?

使える。GRADE_NEXT はただの文字列対応表なので、キーと値を自分の教室の呼び方に置き換えるだけでよい。対応が無い区分(自動で進められない区分)は自動的にスキップされ、通知に名前が挙がる。

Q. シートの列を後から並び替えても大丈夫?

大丈夫。列番号ではなくヘッダーの文言で列を探しているため、列を追加・並び替えても、見出しの文言さえ変わらなければ動き続ける。

Q. 実行に失敗したことに気づけますか?

この処理自体はメール通知を送るだけなので単体では失敗検知は無いが、トリガー集約の仕組み(6本のディスパッチャーに相乗りする方式)に載せている場合は、失敗を握りつぶさず記録して毎晩まとめて通知する共通のしくみに乗る。詳しくはシリーズの「GASトリガー上限20本の壁」を参照。

この仕組みの「全体像」と「コード全文」