kintone
投稿日:

「出席管理をまだ紙で取っていますか?」─ kintoneで学校の出欠管理と保護者連絡を効率化する

皆さんこんにちは。kintoneアプリエンジニアのtomiokaです。

先日、公立中学校の教務主任の方から「出欠管理をなんとかしたい」と相談がありました。

生徒数400名ほどの中学校で、出欠管理はすべて紙の出席簿。毎朝、担任が名前を一人ずつ読み上げて出欠を確認し、紙に記入する。それを月末に事務員がExcelに転記して集計する。さらに、欠席連絡は朝の電話対応で、事務員が8時から8時半の間に30本近い電話を取るそうです。「新学期の初日なんて、電話が鳴り止まなくて他の業務が完全に止まります」と。

この記事は、生徒数300〜500名程度の公立中学校で、出欠管理が紙ベースのまま残っている学校の教務主任やICT推進担当の方に向けて書いています。

紙の出席簿で何が起きていたか

相談を受けて現状を詳しく聞いてみると、問題は「紙が面倒」だけではありませんでした。

まず、集計のタイムラグです。担任が紙に記入した出欠データが集計されるのは月末。つまり、ある生徒が今月に入って何日休んでいるかを、月の途中でリアルタイムに把握する手段がない。「気づいたら10日連続で休んでいた」ということが実際に起きていたそうです。

次に、欠席連絡のボトルネックです。保護者からの欠席連絡は電話のみ。朝の30分に集中するため、事務員が電話対応にかかりきりになる。しかも、電話の内容を担任に伝えるのはメモを職員室の机に置く方式で、担任が教室に行った後にメモが届くこともある。「保護者が電話したのに、担任が知らなかった」というトラブルも起きていました。

そして、長期欠席者の早期発見です。文部科学省の調査では、年間30日以上の欠席が不登校の目安の一つとされていますが、月ごとの紙集計では日数の積み上がりを見落としやすい。学期末に振り返ってようやく気づく、というケースがあったそうです。

kintoneで構築した3つのアプリ

今回の仕組みは、3つのアプリで構成しています。

生徒マスタアプリ

生徒の基本情報を管理するアプリです。

フィールド名フィールドタイプ用途
生徒番号文字列(1行)一意の識別子
氏名文字列(1行)生徒のフルネーム
学年ドロップダウン1年/2年/3年
クラスドロップダウン1組〜6組
担任名文字列(1行)クラス担任の氏名
保護者メール文字列(1行)欠席連絡通知の送信先
保護者電話番号文字列(1行)緊急連絡先

ポイントは、このアプリを他の2つのアプリからルックアップで参照する点です。生徒情報を一元管理することで、クラス替えや担任変更があっても、マスタを更新するだけで全アプリに反映されます。

出欠記録アプリ

担任が毎日の出欠を入力するアプリです。

フィールド名フィールドタイプ用途
日付日付出欠の対象日
生徒番号ルックアップ生徒マスタから取得
氏名文字列(1行)ルックアップで自動コピー
学年・クラス文字列(1行)ルックアップで自動コピー
出欠区分ドロップダウン出席/欠席/遅刻/早退/出席停止
欠席理由ドロップダウン体調不良/家庭事情/忌引/出席停止/その他
備考文字列(複数行)自由記入
保護者連絡済チェックボックス連絡の有無

担任はスマホやタブレットからkintoneにアクセスして、朝のHR中に出欠を入力します。紙の出席簿と違って、入力した瞬間にデータが集計に反映されるのがポイントです。

欠席連絡アプリ

保護者からの欠席連絡を受け付けるアプリです。

フィールド名フィールドタイプ用途
受付日時日時連絡を受けた日時
生徒番号ルックアップ生徒マスタから取得
氏名文字列(1行)ルックアップで自動コピー
学年・クラス文字列(1行)ルックアップで自動コピー
担任名文字列(1行)ルックアップで自動コピー
欠席予定日日付欠席する日
理由ドロップダウン体調不良/家庭事情/忌引/その他
詳細文字列(複数行)症状の詳細など
対応ステータスドロップダウン未確認/担任確認済/対応完了

現在は事務員が電話を受けてこのアプリにレコードを作成していますが、将来的にはGoogleフォーム等と連携して、保護者が直接入力できる導線も検討しています。

出欠記録を一覧画面でクラスごとに色分け表示する

出欠記録アプリの一覧画面で、出欠区分ごとにセルの色を変えるカスタマイズを入れました。担任がクラスの状況をひと目で把握するための仕組みです。

色分けの基準をまとめると次のとおりです。

  • 欠席:赤の背景(#ffe0e0)
  • 遅刻:黄色の背景(#fff8e0)
  • 早退:オレンジの背景(#ffefe0)
  • 出席停止:紫の背景(#f0e0ff)
  • 出席:色なし(デフォルト)
(function() {
  'use strict';

  // 一覧画面表示イベント
  kintone.events.on('app.record.index.show', function(event) {
    var records = event.records;

    // 色分けの定義
    var colorMap = {
      '欠席': '#ffe0e0',
      '遅刻': '#fff8e0',
      '早退': '#ffefe0',
      '出席停止': '#f0e0ff'
    };

    // 一覧のテーブル行を取得
    var rows = document.querySelectorAll('.recordlist-row-gaia');
    if (!rows || rows.length === 0) {
      return event;
    }

    for (var i = 0; i < records.length; i++) {
      var status = records[i]['出欠区分'].value;
      if (colorMap[status] && rows[i]) {
        rows[i].style.backgroundColor = colorMap[status];
      }
    }

    return event;
  });
})();

処理の流れを整理すると、以下のようになります。

  • 一覧画面の表示イベント(app.record.index.show)で全レコードを取得する
  • 各レコードの「出欠区分」フィールドの値に応じて、対応する行の背景色を変更する
  • 「出席」の場合は色を変えない(デフォルトのまま)

月間出席率を自動集計する

出欠記録アプリのレコードをREST APIで取得して、クラスごと・月ごとの出席率を計算するカスタマイズです。担任や教務主任が、月の途中でもリアルタイムに出席状況を確認できます。

(function() {
  'use strict';

  // 出欠記録アプリのID(実環境に合わせて変更)
  var ATTENDANCE_APP_ID = 86;

  kintone.events.on('app.record.index.show', function(event) {
    // 集計ボタンが既にあれば何もしない
    if (document.getElementById('attendance-summary-btn')) {
      return event;
    }

    var headerSpace = kintone.app.getHeaderMenuSpaceElement();
    if (!headerSpace) {
      return event;
    }

    var btn = document.createElement('button');
    btn.id = 'attendance-summary-btn';
    btn.textContent = '月間出席率を集計';
    btn.style.cssText = 'padding: 8px 16px; background: #3498db; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;';
    headerSpace.appendChild(btn);

    btn.addEventListener('click', function() {
      var now = new Date();
      var year = now.getFullYear();
      var month = now.getMonth() + 1;
      var startDate = year + '-' + ('0' + month).slice(-2) + '-01';
      var endYear = month === 12 ? year + 1 : year;
      var endMonth = month === 12 ? 1 : month + 1;
      var endDate = endYear + '-' + ('0' + endMonth).slice(-2) + '-01';

      // 今月のレコードを取得
      var query = '日付 >= "' + startDate + '" and 日付 < "' + endDate + '"';
      var body = {
        app: ATTENDANCE_APP_ID,
        query: query,
        fields: ['学年・クラス', '出欠区分']
      };

      kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', body)
        .then(function(resp) {
          var records = resp.records;
          var summary = {};

          for (var i = 0; i < records.length; i++) {
            var cls = records[i]['学年・クラス'].value;
            if (!summary[cls]) {
              summary[cls] = { total: 0, present: 0 };
            }
            summary[cls].total++;
            if (records[i]['出欠区分'].value === '出席') {
              summary[cls].present++;
            }
          }

          var message = '【' + month + '月の出席率】\n';
          var keys = Object.keys(summary).sort();
          for (var j = 0; j < keys.length; j++) {
            var cls = keys[j];
            var rate = Math.round(summary[cls].present / summary[cls].total * 1000) / 10;
            message += cls + ': ' + rate + '% (' + summary[cls].present + '/' + summary[cls].total + ')\n';
          }

          alert(message);
        })
        .catch(function(error) {
          console.error(error);
          alert('集計中にエラーが発生しました。');
        });
    });

    return event;
  });
})();

ヘッダーメニューに「月間出席率を集計」ボタンを配置して、クリックすると今月のレコードをREST APIで取得し、クラスごとの出席率を計算してアラートで表示します。なお、このサンプルコードはレコード数が500件を超えると正しく集計できません。実運用では後述の fetchAllRecords 関数のような全件取得処理を組み込んでください。また、表示もアラートではなくカスタムのモーダルやダッシュボードにするのが望ましいですが、まずはシンプルに動く形を優先しました。

長期欠席者を早期発見するアラート

この仕組みで特に喜ばれたのが、長期欠席者の自動検知です。

出欠記録アプリのレコードを直近30日間で集計し、欠席日数が一定のしきい値を超えた生徒を一覧表示するカスタマイズを入れました。

(function() {
  'use strict';

  var ATTENDANCE_APP_ID = 86;
  var ABSENCE_THRESHOLD = 5; // 直近30日でこの日数以上欠席した生徒を表示

  kintone.events.on('app.record.index.show', function(event) {
    if (document.getElementById('absence-alert-btn')) {
      return event;
    }

    var headerSpace = kintone.app.getHeaderMenuSpaceElement();
    if (!headerSpace) {
      return event;
    }

    var btn = document.createElement('button');
    btn.id = 'absence-alert-btn';
    btn.textContent = '長期欠席チェック';
    btn.style.cssText = 'padding: 8px 16px; background: #e74c3c; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin-left: 8px;';
    headerSpace.appendChild(btn);

    btn.addEventListener('click', function() {
      var now = new Date();
      var thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
      var startDate = thirtyDaysAgo.getFullYear() + '-' +
        ('0' + (thirtyDaysAgo.getMonth() + 1)).slice(-2) + '-' +
        ('0' + thirtyDaysAgo.getDate()).slice(-2);

      var query = '日付 >= "' + startDate + '" and 出欠区分 in ("欠席") order by 生徒番号 asc';
      var body = {
        app: ATTENDANCE_APP_ID,
        query: query,
        fields: ['生徒番号', '氏名', '学年・クラス', '日付']
      };

      kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', body)
        .then(function(resp) {
          var records = resp.records;
          var absenceCount = {};

          for (var i = 0; i < records.length; i++) {
            var studentId = records[i]['生徒番号'].value;
            if (!absenceCount[studentId]) {
              absenceCount[studentId] = {
                name: records[i]['氏名'].value,
                cls: records[i]['学年・クラス'].value,
                count: 0
              };
            }
            absenceCount[studentId].count++;
          }

          var alerts = [];
          var ids = Object.keys(absenceCount);
          for (var j = 0; j < ids.length; j++) {
            if (absenceCount[ids[j]].count >= ABSENCE_THRESHOLD) {
              var s = absenceCount[ids[j]];
              alerts.push(s.cls + ' ' + s.name + ': ' + s.count + '日欠席');
            }
          }

          if (alerts.length === 0) {
            alert('直近30日間で' + ABSENCE_THRESHOLD + '日以上欠席した生徒はいません。');
          } else {
            alert('【要注意】直近30日間の長期欠席者\n\n' + alerts.join('\n'));
          }
        })
        .catch(function(error) {
          console.error(error);
          alert('チェック中にエラーが発生しました。');
        });
    });

    return event;
  });
})();

しきい値は ABSENCE_THRESHOLD で変更できます。この学校では「直近30日で5日以上」をアラートの基準にしていますが、学校ごとの方針に合わせて調整してもらっています。

アクセス権限の設計

教育現場で特に重要なのが、個人情報の取り扱いとアクセス権限です。

kintoneのアクセス権限は、アプリ単位とレコード単位の2段階で設定できます。今回は以下のように設計しました。

役割生徒マスタ出欠記録欠席連絡
管理職(校長・教頭)閲覧・編集閲覧閲覧
教務主任閲覧・編集閲覧・編集閲覧・編集
担任閲覧自クラスのみ閲覧・編集自クラスのみ閲覧
事務員閲覧閲覧閲覧・編集

担任は自分のクラスの生徒の出欠記録だけを見られるようにしています。他クラスの情報にはアクセスできません。これはkintoneのレコード単位のアクセス権限で、「担任名」フィールドとログインユーザーの一致を条件にして実現しています。

ポイントは、保護者のメールアドレスや電話番号が入っている生徒マスタは、担任には「閲覧のみ」にしていることです。編集は教務主任と管理職に限定しています。

ハマりどころ

一覧画面のDOM操作タイミング

出欠区分の色分け表示で、app.record.index.show イベントの発火時点ではDOMが完全に描画されていないことがあります、、、、特にレコード数が多い場合に発生しやすいです。

対策として、setTimeout で少し待ってからDOM操作を行うか、MutationObserver で描画完了を検知する方法があります。今回はシンプルに setTimeout(function() { ... }, 500) で対応しました。

REST APIの500件制限

月間の出席率を集計するとき、400名×20日=8,000件のレコードを取得する可能性があります。kintone REST APIは1回のリクエストで最大500件までしか返さないため、全件取得するにはオフセットを使ったループ処理が必要です。

今回のサンプルコードでは簡略化していますが、実運用では以下のような再帰的な取得処理を入れています。

function fetchAllRecords(appId, query, opt_offset, opt_records) {
  var offset = opt_offset || 0;
  var records = opt_records || [];
  var body = {
    app: appId,
    query: query + ' limit 500 offset ' + offset
  };

  return kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', body)
    .then(function(resp) {
      records = records.concat(resp.records);
      if (resp.records.length === 500) {
        return fetchAllRecords(appId, query, offset + 500, records);
      }
      return records;
    });
}

校務支援システムとの使い分け

学校には既に「校務支援システム」が導入されているケースが多いです。出欠管理機能を持っているシステムもあります。

今回の学校では、校務支援システムの出欠機能が「月末に一括入力する前提の設計」で、毎日のリアルタイム入力に向いていなかったため、kintoneを日次の入力・集計用として使い、月末に校務支援システムへ転記する運用にしました。理想的にはAPIで連携して二重入力をなくしたいですが、校務支援システム側がAPIを公開していないため、現時点では手動転記にしています。

導入結果

導入から2か月時点での変化をまとめます。

  • 欠席連絡の電話対応: 朝30分の電話ラッシュは変わらないが、事務員がメモを書く代わりにkintoneに直接入力するようになり、担任への伝達漏れがゼロになった
  • 出欠集計: 月末のExcel転記作業(約4時間)がなくなった。月の途中でもリアルタイムに出席率を確認できるようになった
  • 長期欠席者の早期発見: 教務主任が週1回「長期欠席チェック」ボタンを押す運用を始め、3日以上連続で休んだ生徒を翌週の職員会議で共有するフローができた

事務員の方からは「電話を受けながら入力するのは最初は大変だったけど、メモを書いてあとで担任の机に届けるよりはるかにラク」という声をもらいました。

まとめ

今回は、kintoneを使った学校の出欠管理について紹介しました。

学校の出欠管理は、一見シンプルに見えますが、「リアルタイムの集計」「保護者連絡の伝達」「長期欠席者の早期発見」「個人情報の権限管理」と、実は考えるべきことが多い業務です。紙やExcelでもなんとか回っているように見えても、情報の鮮度と共有の速さでは仕組み化に大きなメリットがあります。

押さえておきたいポイントは以下の3つです。

  • 生徒マスタを中心にルックアップで連携させ、クラス替えや担任変更に強い構造にする
  • 出欠区分の色分けやワンクリック集計で、担任や教務主任が「見る習慣」を作れるUIにする
  • アクセス権限は最初から厳密に設計し、個人情報保護の方針を運用に組み込む

校務のDX化やkintoneの導入を検討されている学校・教育関連の方は、株式会社ファストコーディングでkintoneのカスタマイズや業務アプリの構築を承っていますので、お問い合わせフォームからお気軽にご連絡ください。