kintone
投稿日:

「シフト表、毎月作るのに3日かかっていませんか?」── kintoneで介護事業所のシフト管理を効率化する

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

先日、介護事業所を3拠点運営するお客様から「シフト表作成に毎月3日かかっている」と相談がありました。

介護職員35名、看護師8名、計43名のスタッフを抱える事業所で、資格の種類、希望休、夜勤回数の上限、法定の人員配置基準など、考慮すべき条件が多く、Excelでの作成が限界に達していました。管理者がひとりで作成しているため、その人が休むとシフト表が完成しない、、、、という属人化の問題もありました。

今回は、kintoneでスタッフの資格情報・希望休を管理し、法定配置基準のチェックをカスタマイズで実装した事例を紹介します。介護職員20〜100名規模の事業所で、シフト管理をExcelで行っている管理者・サービス提供責任者の方に参考になる内容です。

Excelでのシフト管理で起きていた問題

介護事業所のシフト管理は、一般的な小売や飲食のシフトよりも制約条件が多いのが特徴です。

  • 介護福祉士、介護職員初任者研修修了者、看護師など、資格の種類によって配置できるポジションが異なる
  • 夜勤は月4回まで、連続勤務は5日まで、といった労務上の制約がある
  • 各時間帯に「介護福祉士1名以上」「看護師1名以上」などの法定配置基準を満たす必要がある
  • スタッフの希望休は紙やLINEで集めており、反映漏れが発生する
  • シフト確定後の変更が多く、変更のたびにExcelの整合性を取り直す必要がある
  • 複数拠点のシフトを1人の管理者が作成しており、作成に月3日かかる

ここで一番厄介なのが「配置基準のチェック」なんです。日中の時間帯に介護福祉士が最低何名必要か、夜間帯に看護師の配置が必要か、といった条件をExcelの目視で確認するのは見落としのリスクが非常に高い。実際に行政監査で「この日の夜間帯に配置基準を満たしていない」と指摘を受けたこともあったそうです。

構築した3アプリの構成

今回は以下の3つのアプリで構築しました。

アプリ名役割主なフィールド
スタッフマスタアプリ職員の基本情報・資格・勤務条件を管理氏名、所属拠点、保有資格、雇用形態、夜勤上限回数、週あたり勤務日数
希望休アプリ月ごとの希望休・勤務希望を収集スタッフ(ルックアップ)、対象年月、希望休日テーブル、備考
シフトアプリ日別・時間帯別のシフトを管理対象日、時間帯、担当スタッフ、ポジション、配置基準チェック結果

ポイントは、シフトアプリにレコード保存時の「配置基準チェック」を組み込んだことです。基準を満たさない場合は保存時に警告が表示されるようにしました。

スタッフマスタアプリのフィールド構成

フィールド名フィールドタイプフィールドコード備考
スタッフ名文字列(1行)staffName必須
所属拠点ドロップダウンfacility拠点A、拠点B、拠点C
保有資格チェックボックスqualifications介護福祉士、介護職員初任者研修、実務者研修、看護師、准看護師
雇用形態ドロップダウンemploymentType常勤、非常勤、パート
夜勤可否ドロップダウンnightShiftAvailable可、不可
月あたり夜勤上限数値nightShiftLimit
週あたり勤務日数数値weeklyWorkDays
連続勤務上限数値consecutiveLimitデフォルト5

シフトアプリのフィールド構成

フィールド名フィールドタイプフィールドコード備考
対象日日付shiftDate必須
所属拠点ドロップダウンfacility
時間帯ドロップダウンtimeSlot早番(7:00-16:00)、日勤(9:00-18:00)、遅番(11:00-20:00)、夜勤(16:00-翌10:00)
担当スタッフユーザー選択assignedStaff
スタッフ名文字列(1行)staffNameDisplayルックアップ等で表示用
ポジションドロップダウンposition介護、看護、生活相談員、管理者
配置基準チェック文字列(1行)complianceCheck自動判定(適合/不適合)
チェック詳細文字列(複数行)complianceDetail不適合時の理由を表示

配置基準チェックのカスタマイズ

シフトアプリ保存時に、同じ日・同じ拠点・同じ時間帯の他のレコードを取得し、配置基準を満たしているかチェックします。

(function() {
  'use strict';

  // 配置基準の定義(時間帯ごとの最低人数)
  var STAFFING_REQUIREMENTS = {
    '早番(7:00-16:00)': {
      total: 3,
      qualifiedCareWorker: 1,
      nurse: 0
    },
    '日勤(9:00-18:00)': {
      total: 5,
      qualifiedCareWorker: 2,
      nurse: 1
    },
    '遅番(11:00-20:00)': {
      total: 3,
      qualifiedCareWorker: 1,
      nurse: 0
    },
    '夜勤(16:00-翌10:00)': {
      total: 2,
      qualifiedCareWorker: 1,
      nurse: 0
    }
  };

  // スタッフマスタアプリのID
  var STAFF_APP_ID = 789;

  kintone.events.on(
    ['app.record.create.submit',
     'app.record.edit.submit'],
    function(event) {
      var record = event.record;
      var shiftDate = record.shiftDate.value;
      var facility = record.facility.value;
      var timeSlot = record.timeSlot.value;

      if (!shiftDate || !facility || !timeSlot) {
        return event;
      }

      // 同日・同拠点・同時間帯のシフトレコードを取得
      var query = 'shiftDate = "' + shiftDate + '"'
        + ' and facility = "' + facility + '"'
        + ' and timeSlot = "' + timeSlot + '"'
        + ' and $id != "' + (record.$id ? record.$id.value : '0') + '"';

      return kintone.api(
        kintone.api.url('/k/v1/records.json', true),
        'GET',
        { app: kintone.app.getId(), query: query }
      ).then(function(resp) {
        var staffNames = resp.records.map(function(r) {
          return r.staffNameDisplay.value;
        });
        // 現在のレコードも追加
        staffNames.push(record.staffNameDisplay.value);

        var totalStaff = staffNames.length;
        var requirement = STAFFING_REQUIREMENTS[timeSlot];

        if (!requirement) {
          record.complianceCheck.value = '適合';
          record.complianceDetail.value = '';
          return event;
        }

        // スタッフの資格情報を取得
        var staffQuery = 'staffName in ("'
          + staffNames.join('","') + '")';

        return kintone.api(
          kintone.api.url('/k/v1/records.json', true),
          'GET',
          { app: STAFF_APP_ID, query: staffQuery }
        ).then(function(staffResp) {
          var qualifiedCount = 0;
          var nurseCount = 0;

          staffResp.records.forEach(function(s) {
            var quals = s.qualifications.value;
            quals.forEach(function(q) {
              if (q === '介護福祉士') qualifiedCount++;
              if (q === '看護師' || q === '准看護師') {
                nurseCount++;
              }
            });
          });

          var issues = [];
          if (totalStaff < requirement.total) {
            issues.push(
              '配置人数不足:現在' + totalStaff + '名'
              + '(基準' + requirement.total + '名)'
            );
          }
          if (qualifiedCount < requirement.qualifiedCareWorker) {
            issues.push(
              '介護福祉士不足:現在' + qualifiedCount + '名'
              + '(基準' + requirement.qualifiedCareWorker + '名)'
            );
          }
          if (nurseCount < requirement.nurse) {
            issues.push(
              '看護師不足:現在' + nurseCount + '名'
              + '(基準' + requirement.nurse + '名)'
            );
          }

          if (issues.length > 0) {
            record.complianceCheck.value = '不適合';
            record.complianceDetail.value = issues.join('\n');
          } else {
            record.complianceCheck.value = '適合';
            record.complianceDetail.value = '';
          }

          return event;
        });
      });
    }
  );
})();

処理のポイントを整理します。

  • 時間帯ごとに「合計人数」「介護福祉士の最低人数」「看護師の最低人数」を定義しています。この基準は事業所の種別(特養、老健、デイサービスなど)によって異なるため、定数として切り出して変更しやすくしています
  • 保存時に同条件のレコードをREST APIで検索し、現在のレコードと合算して基準チェックを行います
  • 不適合の場合でも保存はブロックしません。シフト作成途中の段階では基準を満たさないのが当然なので、警告として表示するにとどめています

ハマりどころ

実装中にいくつかハマったポイントがあります。

submitイベント内でのREST APIコール

submitイベント内でkintone REST APIを呼ぶ場合、Promiseを返す必要があります。最初は同期的に処理しようとして、、、、チェック結果が反映されないまま保存されてしまう問題が発生しました。return kintone.api(...).then(...) の形でPromiseチェーンを正しく返すことが重要です。

自分自身のレコードを除外するクエリ

配置基準チェックで同条件のレコードを検索する際、編集中の自分自身のレコードを除外する必要があります。新規作成時は$idがまだ存在しないため、、、、$id != ""のようなクエリが使えません。新規作成時は$id != "0"のようなダミー値で回避する必要がありました。

資格情報のチェックボックスフィールドの扱い

チェックボックスフィールドの値は配列で返ってきます。「介護福祉士」と「看護師」の両方を持つスタッフの場合、、、、両方のカウントに加算する必要があります。forEachで全資格をチェックする形にしないと、最初にヒットした資格だけでカウントが止まってしまいます。

導入結果

導入から4か月後、以下の成果が出ました。

  • シフト表作成にかかる時間が月3日から1日に短縮された
  • 配置基準の不適合をシフト確定前に検知できるようになり、行政監査での指摘がゼロになった
  • 希望休の反映漏れがゼロになった(希望休アプリから一覧で確認できるため)
  • シフト作成業務が管理者1名から副管理者を含む2名で分担できるようになった
  • 月の夜勤回数が上限を超えるスタッフが出なくなった

特に管理者からは「配置基準チェックが自動で走るのが安心」という声をいただきました。Excel時代は最終確認を目視で行っていたため、毎月のシフト確定時に大きなストレスを感じていたそうです。

まとめ

今回は介護事業所のシフト管理をkintoneで効率化し、法定配置基準のチェックを組み込んだ事例を紹介しました。

介護業界のシフト管理は、単純な「誰がいつ出勤するか」だけでなく、資格、労務条件、法定基準という複数の制約を同時に満たす必要があります。Excelでこれらを管理し続けるのは、事業規模が拡大するほど負担が増大します。

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

  • スタッフマスタに資格・勤務条件を持たせ、シフト作成時の判断材料を一元化する
  • 配置基準チェックはsubmitイベントで自動実行し、不適合を見落とさない仕組みにする
  • 希望休を専用アプリで収集し、紙やLINEでの申請を廃止する

結果として、3拠点43名のシフト作成が月3日から1日に短縮され、管理者の精神的な負担も大きく軽減されました。

株式会社ファストコーディングでは、kintoneのカスタマイズや業務アプリの構築を承っています。お問い合わせフォームからお気軽にご連絡ください。