何を自動化したか
一人塾の生徒管理では、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回しか動かない処理は「二重実行」に一番弱い。 トリガーの再実行、依頼キューからの手動実行、翌年のテストなど、同じ年度に2回呼ばれる経路が複数あるため、実行のたびにスクリプトプロパティへ「今年度は実行済み」を記録して、2回目以降は即座に何もせず終わるようにした。これが無いと、同じ年に2回動いた場合に小1が一気に小3まで進むという事故になる。
- 「進級できない学年」を無視せず、必ず人に確認を求める。 幼児は年少・年中・年長の区別がシート上に無いため、どのタイミングで「小1」に上げるべきか機械的に判断できない。高3も、進級ではなく卒業(退会処理)が必要なので自動で進めてはいけない。ここを黙って何もしないままにすると「あの子の学年、直し忘れてないか」と毎年不安になるので、対象者の名前を通知メールに列挙し、人が見て手で直す運用にした。自動化を「全部自動でやる」ことと勘違いすると、判断が要る境界を握りつぶしてしまう。
導入手順
- スプレッドシートのIDをスクリプトプロパティ
BRIDGE_DATA_SHEET_IDに保存する(コードに直接書かない)。 - 通知先メールを使いたい場合はスクリプトプロパティ
NOTIFY_EMAILを設定する(未設定なら実行アカウントのメールが使われる)。 - 生徒シートに「学年」「氏名」(できれば「退会日」)の見出しがある列を用意する。列の並び順は問わない。
GRADE_NEXTの対応表を、自分の教室の学年区分(学年制・級制など)に合わせて書き換える。- 4月1日の朝に自動実行したい場合は、月初に走る既存のトリガー(無ければ
onMonthDay(1)の時間主導型トリガー)からgradePromoteAnnual()を呼ぶ1行を足す。新しいトリガーをむやみに増やさず、既存のトリガーに相乗りするのがGASのトリガー上限(20本)対策になる。 - 年度の途中で強制的に再実行したい場合は
gradePromoteAnnual(true)のように引数へ真値を渡す(月チェックを飛ばして即実行される。二重実行ガードは効いたままなので、同じ年度で連発しても安全)。
よくある質問
Q. 学年ではなく「級」や「コース」で管理している場合も使える?
使える。GRADE_NEXT はただの文字列対応表なので、キーと値を自分の教室の呼び方に置き換えるだけでよい。対応が無い区分(自動で進められない区分)は自動的にスキップされ、通知に名前が挙がる。
Q. シートの列を後から並び替えても大丈夫?
大丈夫。列番号ではなくヘッダーの文言で列を探しているため、列を追加・並び替えても、見出しの文言さえ変わらなければ動き続ける。
Q. 実行に失敗したことに気づけますか?
この処理自体はメール通知を送るだけなので単体では失敗検知は無いが、トリガー集約の仕組み(6本のディスパッチャーに相乗りする方式)に載せている場合は、失敗を握りつぶさず記録して毎晩まとめて通知する共通のしくみに乗る。詳しくはシリーズの「GASトリガー上限20本の壁」を参照。
この仕組みの「全体像」と「コード全文」
- Kindle『個人塾長、AIを雇う。』(500円・Kindle Unlimited対応)2年間の実録を1冊に。何をどの順で自動化したか、失敗も含めて
- note『個人塾をGASで自動化した実録(実コード付き)』(1,800円)この記事で省略したコード全文と、貼り付け手順
- English edition: How I Automated My One-Man Tutoring School with AI ($6.99)