メンバーの勤務スケジュール＆勤務実績一括管理を毎日リマインド！

## はじめに
こんにちは。普段は愛猫:cat:×2と、
わちゃわちゃ戯れている
社会人１２年目のまろみいです。:dizzy:

今回は、普段の業務で何気に不便だと感じていることについて、記事にしてみました。

皆さんの会社でも、こんなことってありませんか？:woman_tone1:

## チームメンバーの正確な勤務状況が分からない！

私は、現在総勢約３０名の部内メンバーと共に仕事をしています💻
その中の拠点が３カ所あるグループに所属をしているのです:family_mmb:

現状として私の部署は、勤務予定を
エクセル管理表での入力管理と社内の
ホワイトボードで勤務場所を一元化しています。

(図表左下のＣ、Ｄのグループに所属しています)
![コメント 2025-01-26 161627.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/a015d6a0-f85a-cd3b-d809-48e975e6a631.png)



勤務予定のエクセル管理表、、、、
一元管理が出来て便利！ではあるのですが、googleカレンダーへの入力と二重
入力をしているグループもいるのです。

そしてホワイトボード転記用に月初に
一度出力されてしまうと、エクセルシート
の変更は誰も見ていないという現状があるのです。:zipper_mouth:
はたまた、遠方の人は出力シートに反映ができない。。。

そこで、大変！〇〇さんの予定〇〇に
なっているけど、**〇〇さん今日どこだっけ？** が
発生してしまいます。:fearful:

:::note warn
問題点
・グループによってスケジュールの変更が随時反映されていない。
・勤務状況を二重入力しているグループがいる。
・誰がどこで何をしているのかが詳細(時間単位)には分からないメンバーがいる。
・確認することに時間(手間)が発生している。


:::

タイムリーにその日の状況が分かれば、
誰がどこで何をしているのかが一目瞭然！

お互いのスケジュール管理も出来て、
必要なタイミングで連絡も取りやすいのではないか。
そして、不要な連絡(電話)が減るのではないかと考えたわけです。

そこで、デジタルで勤務計画＆勤務実績を
一括管理できないかと思いました。

## 実現したいイメージ:telescope:
![コメント 2025-01-26 162741.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/561e526b-8d0e-bf6a-00c0-9e3f0f0626a2.png)

## 使用したツールはこちら
-Gmail
-Googleカレンダー
-google Spread Sheet(GAS)

### 実行すること：①全員がGoogleカレンダーに予定を入力
![コメント 2025-01-26 152148.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/8db12e7a-9390-c996-9304-9964c7785818.png)

### ②googleカレンダーのスケジュールリストを毎朝リマインド
朝の予定をチームメンバーと自分のリマインドを兼ねてメール配信したい。
内容をChat GPTに聞いてみました。:dancer_tone2:

:information_desk_person:![コメント 2025-01-26 153456.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/7970279b-f06c-8170-e160-4162bcd92a35.png)

<details>
  <summary>出来たコードがこちら！</summary>

 ```ruby:qiita.rb
    function sendDailyScheduleEmails() {
      const spreadsheetId = "スプレッドシートIDをここに"; // スプレッドシートIDを入力
      const sheetName = "シート名をここに"; // シート名を入力
      const timeZone = "Asia/Tokyo";
      const today = new Date();
      const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
      const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
      
      // スプレッドシートから対象者リストを取得
      const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
      const data = sheet.getDataRange().getValues();
      const headers = data.shift(); // ヘッダー行を除外
      
      // 各対象者のカレンダー予定を取得してメール送信
      data.forEach(row => {
        const [name, email, calendarId] = row;
        if (name && email && calendarId) {
          const calendar = CalendarApp.getCalendarById(calendarId);
          if (calendar) {
            const events = calendar.getEvents(startTime, endTime);
            let eventDetails = events.map(event => {
              return `・${event.getTitle()} (${formatDate(event.getStartTime(), timeZone)} - ${formatDate(event.getEndTime(), timeZone)})`;
            }).join("\n");

            // メール本文を作成
            const subject = `本日の予定 (${formatDate(today, timeZone)})`;
            const body = `${name}さん、\n\n以下は本日の予定です:\n\n${eventDetails || "本日の予定はありません。"}\n\nよろしくお願いいたします。`;

            // メール送信
            GmailApp.sendEmail(email, subject, body);
          }
        }
      });
    }

    // 日付フォーマット関数
    function formatDate(date, timeZone) {
      return Utilities.formatDate(date, timeZone, "yyyy/MM/dd HH:mm");
    }

    // トリガーを設定 (毎日 8:30 に実行)
    function createTrigger() {
      ScriptApp.newTrigger("sendDailyScheduleEmails")
        .timeBased()
        .atHour(8)
        .nearMinute(30)
        .everyDays(1)
        .create();
    }
 ```
</details>

:information_desk_person:![コメント 2025-01-26 155207.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/92544b6a-80df-329a-1d28-a3afd226bb49.png)

<details>
<summary>出来たコードがこちら！</summary>

 ```ruby:qiita.rb
function sendDailyScheduleEmailsToAll() {
  const spreadsheetId = "スプレッドシートIDをここに"; // スプレッドシートIDを入力
  const sheetName = "シート名をここに"; // シート名を入力
  const timeZone = "Asia/Tokyo";
  const today = new Date();
  const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
  const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);

  // スプレッドシートから対象者リストを取得
  const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
  const data = sheet.getDataRange().getValues();
  const headers = data.shift(); // ヘッダー行を除外

  let allEvents = []; // 全員の予定を格納するリスト

  data.forEach(row => {
    const [name, email, calendarId] = row;
    if (name && calendarId) {
      const calendar = CalendarApp.getCalendarById(calendarId);
      if (calendar) {
        const events = calendar.getEvents(startTime, endTime);
        if (events.length > 0) {
          // 個人の予定をタイトル付きリストで表示
          let eventDetails = `\n【${name}さんの予定】\n` + events.map(event => {
            return `    - ${event.getTitle()} (${formatDate(event.getStartTime(), timeZone)} - ${formatDate(event.getEndTime(), timeZone)})`;
          }).join("\n");
          allEvents.push(eventDetails);
        }
      }
    }
  });

  // メール本文を作成
  const subject = `本日の全員の予定 (${formatDate(today, timeZone)})`;
  const body = allEvents.length > 0
    ? `以下は本日の全員の予定です:\n${allEvents.join("\n")}\n\nよろしくお願いいたします。`
    : `本日は全員の予定がありません。\n\nよろしくお願いいたします。`;

  // 全員にメールを送信
  const recipients = data.map(row => row[1]).filter(email => email); // メールアドレスのリストを作成
  if (recipients.length > 0) {
    GmailApp.sendEmail(recipients.join(","), subject, body);
  }
}

// 日付フォーマット関数
function formatDate(date, timeZone) {
  return Utilities.formatDate(date, timeZone, "yyyy/MM/dd HH:mm");
}

// トリガーを設定 (毎日 8:30 に実行)
function createTrigger() {
  ScriptApp.newTrigger("sendDailyScheduleEmailsToAll")
    .timeBased()
    .atHour(8)
    .nearMinute(30)
    .everyDays(1)
    .create();
}
 ```
</details>

そして、受信時のイメージを聞いてみるとこのように返事がありました！
![コメント 2025-01-30 081604.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/1652a137-4a39-97db-e51a-9c3777911572.png)

:::note warn
ここで発生した問題点
・日時の表記をシンプルに分かりやすくしたい
・もし１人の予定が多い場合は、５件以内にしたい
・場所(会議室)も記載したい

:::

上記３点を再度、chat GPTに聞いてコードを作成してもらいました！:writing_hand:

<summary>完成したコードがこちら！！！</summary>

 ```ruby:qiita.rb
    function sendDailyScheduleEmailsToAll() {
      const spreadsheetId = "1zTHZS7AbCPHFrwzAsR9beqp-bl1BSNsU7zhJB3tJeJs"; // スプレッドシートID
      const sheetName = "対象者一覧"; // シート名
      const timeZone = "Asia/Tokyo";
      const maxEventsPerPerson = 5; // 各メンバーの最大表示件数
      const today = new Date();
      const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
      const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);

      // スプレッドシートから対象者リストを取得
      const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
      if (!sheet) {
        Logger.log(`エラー: シート「${sheetName}」が見つかりません。`);
        return; // シートが見つからない場合は処理を中止
      }

      const data = sheet.getDataRange().getValues();
      const headers = data.shift(); // ヘッダー行を除外

      let allEvents = []; // 全員の予定を格納するリスト

      data.forEach(row => {
        const [name, email, calendarId] = row;
        if (name && calendarId) {
          const calendar = CalendarApp.getCalendarById(calendarId);
          if (calendar) {
            const events = calendar.getEvents(startTime, endTime);
            if (events.length > 0) {
              // イベントを最大表示件数に制限
              const limitedEvents = events.slice(0, maxEventsPerPerson);
              const remainingEvents = events.length - limitedEvents.length;

              let eventDetails = `\n【${name}さんの予定】\n` + limitedEvents.map(event => {
                const start = formatTime(event.getStartTime(), timeZone);
                const end = formatTime(event.getEndTime(), timeZone);
                const timeText = event.isAllDayEvent() ? "終日" : `${start}-${end}`;
                const location = event.getLocation(); // 場所を取得
                const locationText = location ? ` (場所: ${location})` : ""; // 場所が設定されている場合のみ表示
                return `    - ${event.getTitle()} (${timeText})${locationText}`;
              }).join("\n");

              // 他の予定がある場合に簡略化メッセージを追加
              if (remainingEvents > 0) {
                eventDetails += `\n    他 ${remainingEvents} 件の予定があります。`;
              }

              allEvents.push(eventDetails);
            }
          }
        }
      });

      // メール本文を作成
      const subject = `〇〇部 主要メンバー本日の予定一覧 (${formatDate(today, timeZone)})`;
      const body = allEvents.length > 0
        ? `以下は本日の予定です:\n${allEvents.join("\n")}\n\nよろしくお願いいたします。`
        : `本日はメンバーの予定がありません。\n\nよろしくお願いいたします。`;

      // 全員にメールを送信
      const recipients = data.map(row => row[1]).filter(email => email); // メールアドレスのリストを作成
      if (recipients.length > 0) {
        GmailApp.sendEmail(recipients.join(","), subject, body);
      }
    }

    // 時間フォーマット関数 (短い時刻形式)
    function formatTime(date, timeZone) {
      return Utilities.formatDate(date, timeZone, "HH:mm");
    }

    // 日付フォーマット関数
    function formatDate(date, timeZone) {
      return Utilities.formatDate(date, timeZone, "yyyy/MM/dd");
    }

    // トリガーを設定 (毎日 8:30 に実行)
    function createTrigger() {
      ScriptApp.newTrigger("sendDailyScheduleEmailsToAll")
        .timeBased()
        .atHour(8)
        .nearMinute(30)
        .everyDays(1)
        .create();
    }
 ```

### ③トリガーの設定
![コメント 2025-01-29 135605.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/3a00248e-a7ee-0595-7638-3b0007b7502b.png)


GASのコード入力画面を開き、左側の🕔トリガーをクリックし、
上記図表の赤枠の仕様に設定をします！　※ここでは午前８時～9時の設定をしています。


### ④主要メンバーの予定を毎朝8:30:alarm_clock:に勤務状況のリマインド配信！

![コメント 2025-01-26 120621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/48a2e2bc-5cd5-4b76-9796-347c6006716a.png)

ポイントは、毎朝のリマインド機能をつけることで、
主要メンバーの予定がメール配信され、最新情報を知ることが出来ることです💡

自動メール通知により、予定をチェックする手間が省け、管理がしやすくなります:relaxed:

## :point_up: 退勤時は、勤怠入力も忘れずにリマインド！で業務時間管理
私たちの会社は、生産性向上に向けて、勤務時間の管理を徹底しています。
本社は、スーパーフレックスタイム制を導入し、
毎日勤怠状況を各自で上司と共有、把握することが求められています。

退勤時、毎日の勤務状態をリマインド機能で対象者全員にメール！
入力忘れによる記入漏れを防ぎます！

![コメント 2025-01-29 111859.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961203/8e23bd13-2d00-d1d4-ef48-a3b8d9a024ad.png)

メールの本文に目的をしっかり伝えることで、
メンバーの皆さんへの意識付けにも繋がります。

初めの一歩👣は現状を認識することが大切です💡
全員が目的を認識し、意識付けができると行動にも繋がりやすくなります:dizzy:

## まとめ 今後の改善にむけて
今回苦労したことは、トリガー⏰の設定です。
毎日同じ時刻に配信することでリマインドセットを試みましたが、
１時間単位の〇時～〇時の設定しか選択ができませんでした。
そこで、GASのコードにトリガーを埋め込んでみたら上手くいくのではないかと思い
Chat GPTに確認しながらコードへの入力を実行しました。

結果的には反映されず、設定時間の１時間のどこかのタイミングでメールが配信される
設定にしかできませんでした。２日～３日色んなパターンで試みましたが解決ができず、
まだ若干もやっとしています。引き続き、試行錯誤を繰り返します。

最終的には、定時設定でのメール配信ができるようになります！:woman_tone1:
また報告させてください:point_up:

そして、上記内容について一部メンバーに話をしたところ、
勤務時間管理のリマインドメールには、目的が書かれていたほうが意識づけになるとの
フィードバックをもらい、早速、記載することにしました！✍

フィードバック＆コーチャビリティ(素直に聞き入れること)を大切にします！:thumbsup:

### まろみいの紹介・記事についてはこちら！
Xはこちら☞ https://x.com/maromii_y
noteの記事はこちら！☞ https://note.com/yuri_kataoka35/all

Qiitaの 前回1回目の記事はこちら

https://qiita.com/yuri_kataoka35/items/e0cea4aa521b7c340e9b