プログラム

【LINE】シフト希望の募集を自動化する(無料)

接客業で働き始めて早3年。3年経ったということで、最近はバイトの方のシフト管理を任せられることになりました。
シフト管理をすることで見えてきた改善できそうなことに対して今回は取り組んでいきたいと思います。

こんな方におすすめの記事

  • お金をかけずにシフト募集を自動化したい方
  • GASを勉強したい方

現在の実態と目標

現在はバイトの方とのグループLINEで週一回シフト希望を聞いて、回答をもとにシフトを考え共有しています。しかし、これは時間の無駄でかつめんどくさいんです。なので、これらの作業を自動化していきたいと思います。

まずはシフト募集を自動で

まずはシフト募集を自動化していきたいと思います。
その際、今回はGoogleフォームで回答してもらうようにしていきます。定型にすることで、回答を自動でスプレッドシートにまとめられるようになり、後のシフトを考える作業も楽になるのです。
なにより、人によって回答の仕方が異なることは本当に嫌なのです。

使用するツール

Googleフォーム

シフト希望を調査するためのツール。スクリプトで自動生成します。

Googleスプレッドシート

シフト希望の回答を集計するためのツール。

GAS(Google Apps Script)

Googleドライブ上のツールを扱ったり、LINEでメッセージを送信したりすることができます。

LINE公式アカウント

LINEでメッセージを自動送信するためのアカウント。これがないとメッセージが自動送信できません。

Messaging API

LINEでメッセージを自動送信するためのツール。

LINE関連の設定はこちらを参考にしてください。

シフト募集の仕様

シフト募集は毎週行っています。バイトの方は学生さんがメインで、1週間後の予定までしか決まっていない場合が多いためです。
そこで今回は以下のような仕様にします。

毎週水曜日の朝9:00に

  • シフト希望を回答するGoogleフォームを作成
  • 作成したGoogleフォームをグループトークに送信

フォルダ構成

作業に入る前に、フォルダ構成を整理していきます。


ホーム
├ config/                      ← 設定用スプレッドシートを置く共有ドライブ
│   └ Config                   ← 設定値を管理するスプレッドシート
│       └ シート「Config」      ← A列:キー、B列:値
├ form/                        ← GAS が自動生成する Google フォームを格納
├ sheet/                       ← フォーム回答が書き込まれるスプレッドシートを格納
└ src/
    └ main.gs        ← 全ロジック(GAS スクリプト本体)

【準備】フォルダ/ファイルの作成

今回はGoogleフォームとスプレッドシートを使用するため、Googleドライブの準備が必要です。

フォルダを作成する

▼フォルダの作り方

①Googleドライブに"ShiftManagement"フォルダを作る
②"ShiftManagement"フォルダの中に以下の4つのフォルダを作成する

  • src
  • sheet
  • form
  • config

設定値を記録するスプレッドシートを作成する

①configフォルダ内にスプレッドシート(ファイル名:config)を作成する

スプレッドシートの作成方法

スプレッドシートの作成場所

My Drive > ShifManagement > config

A."config"フォルダの中に新しくスプレッドシートを作成する

B.ファイル名を「config」にする

②シート名をconfigにする

③以下をコピーしてA1セルに張り付ける

Key	Value
formFolderId
formName
spreadsheetId
lineGroupId
accessToken
names

回答を記録するスプレッドシートを作成する

①sheetフォルダ内にスプレッドシート(ファイル名:shift)を作成する

スプレッドシートの作成方法

スプレッドシートの作成場所

My Drive > ShifManagement > sheet

A."sheet"フォルダの中に新しくスプレッドシートを作成する

B.ファイル名を「shift」にする

ソースコードファイルを作成する

①srcフォルダ内にGASファイル(ファイル名:main)を作成する

GASファイルの作成方法

スプレッドシートの作成場所

My Drive > ShifManagement > src

A."src"フォルダの中に新しくGASファイルを作成する

B.「Create scripit」を押す

C.ファイル名を「src」にする

【実装】コードを記述する

コードを記述する

さきほど作成したGASファイルに、以下のコードを張り付けてください。
'設定用スプレッドシートのファイルID'には、スプレッドシート名"config"のIDを記述してください。

スプレッドシートのIDは、スプレッドシートのURLの'd/'と'/edit'の間にある文字列です

// 設定を記述したスプレッドシートのファイルID
const CONFIG_SPREADSHEET_ID = '設定用スプレッドシートのファイルID';
// 設定を管理するシート名
const CONFIG_SHEET_NAME = 'config';

// フォルダ検索フラグ
const ROOT_DIR = 0;
const SPECIFIC_DIR = 1;

// 翌週の日付をためる配列
var weekDates = [];

/**
 * メイン関数
 */
function main() {
  const cfg = loadConfig();

  // 翌週分の日付を生成
  getWeekDates();

  // 旧フォームを削除
  deleteForm(cfg);

  // 新フォームを作成
  makeForm(cfg);

  // フォーム回答先をリンク
  setFormDestination(cfg);

  // フォームを指定フォルダへ移動&公開URL取得
  const formUrl = moveForm(cfg);

  // LINEグループへ通知
  sendViaLine(formUrl, cfg);
}


/**
 * config シートから A列(Key)/B列(Value) を読み込み、
 * オブジェクトを返す
 */
function loadConfig() {
  Logger.log("START FUNCTION: loadConfig()");
  const ss    = SpreadsheetApp.openById(CONFIG_SPREADSHEET_ID);
  const sheet = ss.getSheetByName(CONFIG_SHEET_NAME);
  const rows  = sheet.getRange('A2:B' + sheet.getLastRow()).getValues();
  const cfg   = {};
  rows.forEach(function(r) {
    const key = r[0], val = r[1];
    if (key) cfg[key] = val;
  });
  // names はコンマ区切り文字列 → 配列に
  cfg.names = cfg.names ? cfg.names.split(',').map(function(n){ return n.trim(); }) : [];
  Logger.log("cfg: %s", JSON.stringify(cfg));
  return cfg;
}

/**
 * Googleフォームを作成
 */
function makeForm(cfg) {
  Logger.log("START FUNCTION: makeForm()");
  const form = FormApp.create(cfg.formName).setTitle(cfg.formName);
  const section = form.addSectionHeaderItem();
  section.setHelpText(getNextFridayDate() + "17:00までに回答してください。");

  // 名前プルダウン(config シートの names)
  form.addMultipleChoiceItem()
      .setTitle("名前")
      .setChoiceValues(cfg.names)
      .setRequired(true);

  // 出勤可能日グリッド
  form.addGridItem()
      .setTitle("出勤可能日")
      .setRows(weekDates)
      .setColumns(["可能","不可"])
      .setRequired(true);
}


function getWeekDates() {
  Logger.log("START FUNCTION: getWeekDates()");

  // 既存のデータをクリア
  weekDates = [];

  const today = new Date();
  const wday  = today.getDay();  // 日曜=0,月曜=1…土曜=6

  // 「次の月曜日」までの日数を計算
  // (1 - wday + 7) % 7 が 0 なら、offset = 7(日付が月曜の場合は次の週の月曜を取得)
  let offset = (1 - wday + 7) % 7;
  if (offset === 0) offset = 7;

  // 翌週の月曜日の日付オブジェクト
  const nextMon = new Date(
    today.getFullYear(),
    today.getMonth(),
    today.getDate() + offset
  );

  // 表示フォーマット
  const opts = { weekday: 'short', month: 'numeric', day: 'numeric' };

  // 月曜から日曜まで7日分を配列に追加
  for (let i = 0; i < 7; i++) {
    const d = new Date(
      nextMon.getFullYear(),
      nextMon.getMonth(),
      nextMon.getDate() + i
    );
    weekDates.push(d.toLocaleDateString('ja-JP', opts));
  }
}


/**
 * 金曜日の日付文字列を返す
 */
function getNextFridayDate() {
  Logger.log("START FUNCTION: getNextFridayDate()");
  const today = new Date();
  const wday  = today.getDay();
  const nextF = new Date(
    today.getFullYear(),
    today.getMonth(),
    today.getDate() + ((5 - wday) % 7)
  );
  const opts = { weekday:'short', month:'numeric', day:'numeric' };
  return nextF.toLocaleDateString('ja-JP', opts);
}


/**
 * 旧フォームをゴミ箱へ移動
 */
function deleteForm(cfg) {
  Logger.log("START FUNCTION: deleteForm()");
  const formId = getFormId(cfg.formFolderId, SPECIFIC_DIR, cfg.formName);
  if (!formId) return;
  unsetDestination(formId);
  DriveApp.getFileById(formId).setTrashed(true);
}


/**
 * フォームの回答先リンク解除&回答削除
 */
function unsetDestination(formId) {
  Logger.log("START FUNCTION: unsetDestination()");
  const form = FormApp.openById(formId);
  form.removeDestination();
  form.deleteAllResponses();
}


/**
 * フォーム回答先スプレッドシートをリンク
 */
function setFormDestination(cfg) {
  Logger.log("START FUNCTION: setFormDestination()");
  const formId = getFormId(cfg.formFolderId, ROOT_DIR, cfg.formName);
  const form   = FormApp.openById(formId);

  // 既存の「フォームの回答」シートを削除
  deleteSheet(cfg.spreadsheetId);

  form.setDestination(
    FormApp.DestinationType.SPREADSHEET,
    cfg.spreadsheetId
  );
}


/**
 * 「フォームの回答」シートをすべて削除
 */
function deleteSheet(ssId) {
  Logger.log("START FUNCTION: deleteSheet()");
  const ss     = SpreadsheetApp.openById(ssId);
  ss.getSheets().forEach(function(s) {
    if (s.getName().indexOf("Form Responses") !== -1) {
      ss.deleteSheet(s);
    }
  });
}


/**
 * フォームを指定フォルダに移動し、公開URLを返す
 */
function moveForm(cfg) {
  Logger.log("START FUNCTION: moveForm()");
  const formId = getFormId(cfg.formFolderId, ROOT_DIR, cfg.formName);
  const file   = DriveApp.getFileById(formId);
  file.moveTo(DriveApp.getFolderById(cfg.formFolderId));
  return FormApp.openById(formId).getPublishedUrl();
}


/**
 * 指定フォルダからフォームIDを検索
 */
function getFormId(formFolderId, dirFlag, formName) {
  Logger.log("START FUNCTION: getFormId()");
  const folder = dirFlag===ROOT_DIR
    ? DriveApp.getRootFolder()
    : DriveApp.getFolderById(formFolderId);
  const files = folder.getFilesByName(formName);
  return files.hasNext() ? files.next().getId() : null;
}


/**
 * LINEグループにフォームURLを送信
 */
function sendViaLine(formUrl, cfg) {
  Logger.log("START FUNCTION: sendViaLine()");
  const payload = {
    to: cfg.lineGroupId,
    messages: [{
      type: "text",
      text: getNextFridayDate() + "17:00までに回答してください。\n" + formUrl
    }]
  };
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    headers: { Authorization: 'Bearer ' + cfg.accessToken },
    muteHttpExceptions: true
  };
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', options);
}

値を設定する

スプレッドシート名"config"を開いて、B列のValueに値を入れていきます。

formFolderId(フォームを格納するフォルダのID)

formフォルダのIDを記述します。

formフォルダのIDは、formフォルダのURLの'folders'以降にある文字列です

formName(作成するフォームの名前)

作成するフォームの名前を任意で入力します。今回は「シフト希望回答フォーム」とします。

spreadsheetId(回答を記録するスプレッドシートのID)

スプレッドシート名"shift"のIDを記述します。

スプレッドシートのIDは、スプレッドシートのURLの'd/'と'/edit'の間にある文字列です

lineGroupId(LINEのグループトークID)

LINEのグループトークIDを入力します。グループトークIDの取得方法は、以下の記事を参照してください。

accessToken(LINEの長期チャネルアクセストークン)

LINEの長期チャネルアクセストークンを設定します。

▼トークンの取得方法はこちら

LINE Developersにアクセスする
②作成した公式LINEアカウント名の個所を選択する


③"Messaging API設定"を押す
④"チャネルアクセストークン(長期)"の「実行」を押す

⑥チャネルアクセストークンをコピーする

names(シフト管理しているスタッフの名前)

シフト管理しているスタッフの名前を入力します。

実行する

①「Run」を押す

②「Review permissions」を押す

③アクセス権を設定する

▼詳細

A.自分のアカウントを選択

B.「Advanced」を押す

C.「Go to Unlitled project(unsafe)」を押す

D. "Select All"にチェックを入れ、「Continue」を押す

④もう一度、「Run」を押す

実行する関数が「main」となっていることを確認してください

⑤以下のように、「Execution completed」とログが出たらOKです!

結果を確認する

LINEを開くと、グループトークにメッセージが送られてきていました!

リンクをタップし、フォームを開くと「名前」と「出勤できる日」が入力できるようになりました。

フォームを回答してスプレッドシートを確認してみます。

うまく記録できていました!

GASコードが定期的に実行されるようにする

①GASファイルで、「Triggers」を押す

②「Add Trigger」を押す

③以下のように設定し、「Save」を押す

  • Select type of time based trigger:「Week timer(毎週)」にする
  • Select day of week:「Every Wednesday(毎週水曜日)」にする
  • Select time of day:「9 am to 10am(9時~10時の間)」にする

設定間隔はご自身の都合に合わせて調整してください

【まとめ】シフト募集は自動化できたが、先は長い

シフト募集は自動化できましたが、回答をまとめるという工程が残っています。これができないと、完全に効率化できたとはとても言えません。先はまだまだ長いです。

  • この記事を書いた人

なんグラマ

福岡県在住の23歳。高校を卒業後2年くらいシステムエンジニアをして、接客業に転職。プログラムを書くことが今でも好きで、ChatGPTなどを活用して仕事やプライベートを楽する方法を探しています。ガジェット、甘い食べ物が大好き。

-プログラム
-, , ,