フォームの報告をシートに自動で集め、週次レポートにしてメールで届けられます

現場の担当者がGoogle フォームで出した報告を、Google Apps Script(GAS)でスプレッドシートに自動で入れ、週ごとに集計してGoogle ドキュメントのレポートにまとめ、PDFにして責任者へメールで送る仕組みの見本です。題材は宿泊施設の清掃報告にしました。転記や集計の手作業がなくなり、気になる不具合は週の頭に一覧で届きます。

画面は動きを再現したもので、データはすべて架空です

約3.5年、GASの仕組みを作って運用してきました。

  1. 1フォームの回答をシートに自動で入れる
  2. 2週ごとに施設別の回数・作業時間・不具合を集計
  3. 3ドキュメントのレポートをPDFにしてメールで送る
フォームの回答を取り込んだあとの清掃報告の一覧。新しく入った行に色がついている(再現画面)再現画面

※再現画面です(実際のGoogleの画面ではありません)。

清掃報告のシート(見本)

10/19〜10/25の週です。最初は3件だけ入っています。「フォームの回答を取り込む」で、届いた5件がシートに入ります。

この一覧は、フォームの回答8件を取り込んだあとの例です。JavaScriptが有効なら、取り込みとレポート作成を順に試せます。

受付番号報告日時施設担当作業時間補充した備品不具合
R-30110/19 11:20施設A山田95分タオル・洗剤なし
R-30210/19 14:05施設B佐々木80分なしシャワーの水圧が弱い
R-30310/20 10:40施設C田村110分トイレットペーパーなし
R-30410/21 12:15施設A中川90分なしなし
R-30510/22 10:30施設B山田85分タオルなし
R-30610/23 13:50施設C佐々木120分洗剤エアコンのリモコン電池切れ
R-30710/24 11:00施設A田村100分タオル・トイレットペーパー玄関の電球切れ
R-30810/25 10:10施設B中川75分なしなし
  • シートの件数8件
  • 作業時間の合計755分
  • 不具合の報告4件

「週次レポートを作る」を押すと、レポートと送るメールがここに出ます。

清掃報告 週次レポート(10/19〜10/25)

ドキュメントの雛形に差し込み、PDFで保存した内容の例

施設清掃回数作業時間不具合
施設A3回285分1件
施設B3回240分1件
施設C2回230分1件

対応が必要な不具合

  • 10/19 14:05 施設B:シャワーの水圧が弱い
  • 10/23 13:50 施設C:エアコンのリモコン電池切れ
  • 10/24 11:00 施設A:玄関の電球切れ

宛先:運営責任者 件名:【週次】清掃報告レポート 10/19〜10/25 添付:清掃報告_1019-1025.pdf 今週の清掃は8回、作業時間は合計755分でした。不具合の報告が4件あります。詳しくは添付をご確認ください。

GASの関数(抜粋)

フォームの送信時に1つ目の関数、毎週月曜の朝に2つ目の関数をトリガーで動かす想定です。項目や集計の単位、レポートの雛形、送り先は、今の業務の流れを伺って決めます。

// フォーム送信時のトリガー:回答をシートに入れ、受付番号を振る
function onReportSubmit(e) {
  const sheet = SpreadsheetApp.getActive().getSheetByName('清掃報告');
  const v = e.namedValues;
  const no = 'R-' + (300 + sheet.getLastRow());
  sheet.appendRow([no, new Date(), v['施設'][0], v['担当'][0],
    Number(v['作業時間(分)'][0]), v['補充した備品'][0] || 'なし', v['不具合'][0] || 'なし']);
}

// 毎週月曜8時のトリガー:先週分を集計し、PDFにしてメールで送る
function sendWeeklyReport() {
  const end = new Date(); end.setHours(0, 0, 0, 0);
  const start = new Date(end.getTime() - 7 * 24 * 3600 * 1000);
  const rows = SpreadsheetApp.getActive().getSheetByName('清掃報告')
    .getDataRange().getValues().slice(1)
    .filter(r => r[1] >= start && r[1] < end);

  const byPlace = {};
  const issues = [];
  rows.forEach(([no, at, place, staff, min, items, issue]) => {
    const p = byPlace[place] = byPlace[place] || { count: 0, min: 0, issue: 0 };
    p.count++; p.min += min;
    if (issue !== 'なし') { p.issue++; issues.push(`${fmt_(at)} ${place}:${issue}`); }
  });

  const copy = DriveApp.getFileById(TEMPLATE_ID).makeCopy(`清掃報告_${fmt_(start)}`);
  const doc = DocumentApp.openById(copy.getId());
  const body = doc.getBody();
  body.replaceText('{{期間}}', `${fmt_(start)}〜${fmt_(end)}`);
  const table = Object.keys(byPlace).map(k =>
    [k, byPlace[k].count + '回', byPlace[k].min + '分', byPlace[k].issue + '件']);
  body.appendTable([['施設', '清掃回数', '作業時間', '不具合'], ...table]);
  body.appendParagraph(issues.length ? issues.join('\n') : '不具合の報告はありません');
  doc.saveAndClose();

  const total = rows.reduce((s, r) => s + r[4], 0);
  GmailApp.sendEmail(MANAGER_ADDRESS, `【週次】清掃報告レポート ${fmt_(start)}〜`,
    `今週の清掃は${rows.length}回、作業時間は合計${total}分でした。不具合の報告が${issues.length}件あります。`,
    { attachments: [copy.getAs(MimeType.PDF)] });
}

const fmt_ = d => Utilities.formatDate(d, 'Asia/Tokyo', 'M/d');

作業時間や不具合の件数は、施設ごとの比較や外注先との精算にも使えます。最初に今の報告の流れを見て、どこで手が止まっているかを洗い出してから組みます。

範囲・金額・日数の目安

最終的な金額は、内容を伺ってからお見積もりします。

ほかの見本