先日の記事で、LINEのグループトークでシフト希望の回答をするGoogleフォームを自動送信する方法をまとめました。
【LINE】GASでシフト希望の募集を自動化する(無料)
接客業で働き始めて早3年。3年経ったということで、最近はバイトの方のシフト管理を任せられることになりました。シフト管理をすることで見えてきた改善できそうなことに対して今回は取り組んでいきたいと思います。 こんな方におすすめの記事 お金をかけずにシフト募集を自動化したい方 GASを勉強したい方 現在の実態と目標 現在はバイトの方とのグループLINEで週一回シフト希望を聞いて、回答をもとにシフトを考え共有しています。しかし、これは時間の無駄でかつめんどくさいんです。なので、これらの作業を自動化していきたいと思 ...
これで晴れて、シフト希望の募集は自動化されるようになりました。そして、回答はスプレッドシートに記録されるのですが、


これ、見にくい…
ということで、今回はフォームの回答を自動で見やすく整理する機能を実装していきたいと思います。
既存機能のおさらい

今回の仕様を詳しく説明する前に、これまでの機能などをおさらいします。
プロジェクト構成
既存機能では、main.gasにLINEのグループトークにGoogleフォームを送信する処理を記述しています 。
ホーム
├ config/
│ └ Config ← 設定値を管理するスプレッドシート
├ form/ ← GAS が自動生成する Google フォームを格納
├ sheet/
│ └ shit ← フォーム回答が書き込まれるスプレッドシートを格納
└ src/
└ main.gs ← 全ロジック(GAS スクリプト本体)
既存機能
- 毎週水曜日にLINEのグループトークにシフト希望の回答をするGoogleフォームを送信する
- Googleフォームの回答を受けるとファイル名"shift”のスプレッドシートに「可能」「不可」のどちらかで回答を記録する
今回の仕様
①回答を記録するシートの自動生成

- 回答を記録するシートは、年(例:2025)をそのままシート名として作成する
- A列の2行目以降に、その年1年分の日付を記載する
②ヘッダーに名前を自動記載

- 設定ファイルから名前を取得し、B列以降に記載する
- 既にシートに名前がある場合は、新たに記述はしない
- 設定ファイルに記述のない名前がシートにある場合、その名前のある列を削除する
③出勤可否を〇×で記載

- 出勤可否を可能なら"〇"、不可なら"×"と記載する
実装
コードを書く場所
スプレッドシート"shift"を開き、「Extensions」→「App Script」の順にクリックして、開いた場所にコードを記述してください。

コード
// 設定を記述したスプレッドシートのファイルID
const CONFIG_SPREADSHEET_ID = 'スプレッドシートのファイルID';
// 設定を管理するシート名
const CONFIG_SHEET_NAME = 'config';
// 翌週の日付をためる配列
let weekDates = [];
let weekDateObjects = [];
/**
* フォーム回答トリガーで実行されるメイン関数
* e.values: [タイムスタンプ, 回答者名, 日付1の回答, 日付2の回答, …]
*/
function updateShifts(e) {
// 翌週の日付(月曜~日曜)を取得
getWeekDates();
// 名前を取得
const validNames = loadConfigNames();
const respondentName = e.values[1];
const nameIndex = validNames.indexOf(respondentName);
if (nameIndex < 0) {
throw new Error(`config シートに未登録の名前: ${respondentName}`);
}
const targetColumn = nameIndex + 2; // B列=2, C列=3,…
// 回答を記録するスプレッドシートを開く
const ss = SpreadsheetApp.getActiveSpreadsheet();
const processedYears = {};
let firstSheet, firstRow;
weekDates.forEach((label, i) => {
const dateObj = weekDateObjects[i];
const year = dateObj.getFullYear();
// シートがない場合
if (!processedYears[year]) {
// その年でシートを作成
ensureYearSheet(year);
const sheet = ss.getSheetByName(String(year));
// GAS実行時に config ファイルにある名前をすべて記述する
sheet.getRange(1, 2, 1, validNames.length).setValues([validNames]);
// 設定ファイルにない名前の列を削除する
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
for (let col = headers.length; col >= 2; col--) {
const h = headers[col - 1];
if (h && validNames.indexOf(h) === -1) {
sheet.deleteColumn(col);
}
}
// ヘッダーに名前がない場合は追加する
const existing = sheet.getRange(1, targetColumn).getValue();
if (existing !== respondentName) {
sheet.getRange(1, targetColumn).setValue(respondentName);
}
// 日付ラベルの配列を取得(A2~最終行)
const dateLabels = sheet
.getRange(2, 1, sheet.getLastRow() - 1, 1)
.getValues()
.map(r => r[0]);
processedYears[year] = { sheet, dateLabels };
}
// 出勤可否(○・×)を書き込む
const { sheet, dateLabels } = processedYears[year];
const idxInSheet = dateLabels.indexOf(label);
if (idxInSheet < 0) {
throw new Error(`シート ${year} に日付ラベル「${label}」が見つかりません`);
}
const row = idxInSheet + 2;
const response = e.values[i + 2]; // e.values[2] が日付1の回答
const mark = (response === '可能') ? '○' : '×';
sheet.getRange(row, targetColumn).setValue(mark);
// 最初の書き込みではアクティブセル用に保持
if (i === 0) {
firstSheet = sheet;
firstRow = row;
}
});
// 月曜行をアクティブに設定
if (firstSheet && firstRow) {
firstSheet.setActiveRange(firstSheet.getRange(firstRow, 1));
}
}
/**
* 翌週月曜~日曜の日付ラベル('M/D(曜)')と Date オブジェクトを取得
*/
function getWeekDates() {
weekDates = [];
weekDateObjects = [];
const today = new Date();
const weekdayIndex = today.getDay();
const offsetToMon = (8 - weekdayIndex) % 7;
const nextMonday = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + offsetToMon
);
const options = { weekday:'short', month:'numeric', day:'numeric' };
for (let i = 0; i < 7; i++) {
const d = new Date(
nextMonday.getFullYear(),
nextMonday.getMonth(),
nextMonday.getDate() + i
);
weekDateObjects.push(new Date(d)); // Date オブジェクト保持
weekDates.push(d.toLocaleDateString('ja-JP', options));
}
}
/**
* config シートから名前を取得して返す
* @returns {string[]} ['佐藤','鈴木',…]
*/
function loadConfigNames() {
const cfgSs = SpreadsheetApp.openById(CONFIG_SPREADSHEET_ID);
const cfgSheet = cfgSs.getSheetByName(CONFIG_SHEET_NAME);
const data = cfgSheet.getDataRange().getValues();
for (let i = 0; i < data.length; i++) {
if (data[i][0] === 'names') {
return String(data[i][1])
.split(',')
.map(v => v.trim())
.filter(v => v);
}
}
throw new Error('config シートに "names" の行が見つかりません');
}
/**
* 年ごとのシートを確保し、存在しなければ新規作成してA1 に年、A2~ にその年の全日付(曜付き)を投入する
*/
function ensureYearSheet(year) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const name = String(year);
let sheet = ss.getSheetByName(name);
if (sheet) return; // 既存なら何もしない
sheet = ss.insertSheet(name);
// A1:年を表示
sheet.getRange(1, 1).setValue(year);
// A2以降:その年の全日付
const start = new Date(year, 0, 1);
const end = new Date(year + 1, 0, 1);
const options = { weekday:'short', month:'numeric', day:'numeric' };
let row = 2;
for (let d = new Date(start); d < end; d.setDate(d.getDate() + 1)) {
sheet.getRange(row++, 1)
.setValue(d.toLocaleDateString('ja-JP', options));
}
// シート作成時に、すべてのセルを中央揃え
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).setHorizontalAlignment('center');
// シート作成時に、1行目を固定
sheet.setFrozenRows(1);
}
/**
* テスト関数: testUpdateShiftsYearEnd
*
* 概要:
* 年末年始をまたぐ週(例:2025-12-29(月)~2026-01-04(日))のシフト反映ロジックを検証します。
* weekDates と weekDateObjects をスタブし、updateShifts() を実行することで、
* 2025 年タブおよび 2026 年タブに
* 各日付行の「佐藤」列へ正しく “○” が書き込まれるかをチェックします。
*
* 実行方法:
* 1. Google Apps Script エディタを開きます。
* 2. エディタ上部の関数一覧ドロップダウンから
* “testUpdateShiftsYearEnd” を選択します。
* 3. ▶︎(実行)ボタンをクリックして関数を実行します。
* 4. スプレッドシートに戻り、"2025" シートと "2026" シートの
* 12/29~1/4 の各行・「佐藤」列に “○” が入っていることを確認してください。
*/
function testUpdateShiftsYearEnd() {
// —————— step1: getWeekDatesをコピー ——————
const originalGetWeekDates = getWeekDates;
// —————— step2: テスト用に weekDates を固定する stub を差し替え ——————
getWeekDates = function() {
weekDates = [
'12/29(月)',
'12/30(火)',
'12/31(水)',
'1/1(木)',
'1/2(金)',
'1/3(土)',
'1/4(日)'
];
weekDateObjects = [
new Date(2025, 11, 29),
new Date(2025, 11, 30),
new Date(2025, 11, 31),
new Date(2026, 0, 1),
new Date(2026, 0, 2),
new Date(2026, 0, 3),
new Date(2026, 0, 4)
];
};
// —————— step3: ダミー回答イベントを作成 ——————
const fakeEvent = {
values: [
new Date().toISOString(), // タイムスタンプ
'佐藤', // 回答者名
'可能','可能','可能','可能','可能','可能','可能'
]
};
// —————— step4: メイン処理を呼び出し ——————
updateShifts(fakeEvent);
// —————— step5: getWeekDates を元に戻す ——————
getWeekDates = originalGetWeekDates;
Logger.log('テスト完了:2025年タブ・2026年タブに○が入っているか確認してください');
}
動作確認
トリガーを設定する
GASエディタ画面から「Triggers」をクリックする。

"Select event type"の「On form submit」をクリックし、「Save」をクリックする。

動作確認
①LINEで送られているフォームをクリックし、回答する。


②"shift"スプレッドシートを開いて回答の内容が正しく反映されているか確認する。
