kintone
投稿日:

「点呼記録、まだ紙に書いていませんか?」── kintoneで運輸業のアルコールチェック・点呼記録を電子化する

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

先日、運送会社のお客様から「アルコールチェックと点呼記録の管理が限界です」と相談がありました。

ドライバー80名を抱える中規模の運送会社で、点呼時のアルコール検知器使用は2011年から義務付けられていますが、記録管理は紙ベースのまま溜まり続けている状況でした。2023年12月には白ナンバー車両への義務も拡大され、業界全体で記録管理への関心が高まっています。運行管理者3名が手分けして記録を管理していますが、監査前になると過去の記録を探し出す作業だけで丸2日かかるとのこと。さらに記録の抜け漏れがあると、行政処分のリスクにもつながるため、心理的な負担も大きかったそうです。

今回は、kintoneでアルコールチェック・点呼記録を電子化し、法令で求められる1年間の保存義務にも対応した事例を紹介します。ドライバー30〜200名規模の運送会社で、点呼記録を紙やExcelで管理している運行管理者・総務担当の方に参考になる内容です。

紙・Excelでの点呼管理で起きていた問題

道路運送法および貨物自動車運送事業法では、運転者に対する乗務前・乗務後の点呼が義務付けられています。点呼記録には、ドライバーの氏名、点呼日時、アルコール検知器の結果、健康状態、指示事項などを記載し、1年間保存する必要があります。

この記録を紙やExcelで管理していると、以下の問題が発生します。

  • 紙の点呼簿が月ごとにファイルされ、特定のドライバーの記録を探すのに時間がかかる
  • 記入漏れや記入ミスに気づけず、監査時に指摘を受ける
  • アルコール検知器の結果(数値)と点呼記録が別々に管理されている
  • 遠隔地の営業所ではIT点呼の記録と対面点呼の記録が混在し、整理できない
  • 運行管理者が複数いると、誰がどの点呼を実施したかの把握が難しい

特に「記入漏れ」は深刻でした。ドライバーが出発を急ぐ早朝の乗務前点呼で、記録が不十分なまま出発してしまうケースが月に数件発生していたんですよね。

構築した2アプリの構成

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

アプリ名役割主なフィールド
ドライバーマスタアプリドライバーの基本情報・資格情報を管理氏名、社員番号、所属営業所、免許種別、免許有効期限、健康診断日
点呼記録アプリ日々の点呼・アルコールチェック結果を記録ドライバー(ルックアップ)、点呼種別、点呼日時、アルコール値、健康状態、点呼実施者、指示事項

シンプルな2アプリ構成ですが、点呼記録アプリの入力を極力少なくするために、必須フィールドの自動セットと入力チェックに力を入れました。

点呼記録アプリのフィールド構成

フィールド名フィールドタイプフィールドコード備考
ドライバー名ルックアップlookupDriverドライバーマスタアプリから取得
社員番号文字列(1行)employeeNoルックアップでコピー
所属営業所文字列(1行)branchルックアップでコピー
点呼種別ドロップダウンtenkoType乗務前、乗務後、中間
点呼方法ドロップダウンtenkoMethod対面、電話、IT点呼
点呼日時日時tenkoDatetime必須
アルコール検知結果数値alcoholValue単位: mg/L
アルコール判定文字列(1行)alcoholResult自動判定(適正/要確認)
健康状態ドロップダウンhealthStatus良好、やや不良、不良
疲労度ドロップダウンfatigueLevel通常、やや疲労、疲労
睡眠状況ドロップダウンsleepStatus十分、やや不足、不足
指示事項文字列(複数行)instruction運行管理者からの指示内容
点呼実施者ユーザー選択inspector運行管理者
備考文字列(複数行)remarks

アルコール値自動判定と入力必須チェックのカスタマイズ

点呼記録アプリのメインカスタマイズです。アルコール検知器の数値入力に連動して判定結果を自動表示し、必須フィールドの入力漏れを防止します。

(function() {
  'use strict';

  // アルコール基準値(mg/L)
  var ALCOHOL_THRESHOLD = 0.15;

  // 判定結果を自動セットする関数
  function setAlcoholResult(record) {
    var alcoholValue = parseFloat(record.alcoholValue.value);
    if (isNaN(alcoholValue)) {
      record.alcoholResult.value = '';
      return;
    }
    if (alcoholValue >= ALCOHOL_THRESHOLD) {
      record.alcoholResult.value = '要確認';
    } else {
      record.alcoholResult.value = '適正';
    }
  }

  // アルコール値変更時の自動判定
  kintone.events.on(
    ['app.record.create.change.alcoholValue',
     'app.record.edit.change.alcoholValue'],
    function(event) {
      setAlcoholResult(event.record);
      return event;
    }
  );

  // 保存前バリデーション
  kintone.events.on(
    ['app.record.create.submit',
     'app.record.edit.submit'],
    function(event) {
      var record = event.record;
      var errors = [];

      // 必須チェック
      if (!record.tenkoDatetime.value) {
        errors.push('点呼日時を入力してください');
        record.tenkoDatetime.error = '必須項目です';
      }

      if (!record.tenkoType.value) {
        errors.push('点呼種別を選択してください');
        record.tenkoType.error = '必須項目です';
      }

      if (record.alcoholValue.value === '' ||
          record.alcoholValue.value === null ||
          record.alcoholValue.value === undefined) {
        errors.push('アルコール検知結果を入力してください');
        record.alcoholValue.error = '必須項目です';
      }

      if (!record.healthStatus.value) {
        errors.push('健康状態を選択してください');
        record.healthStatus.error = '必須項目です';
      }

      if (!record.inspector.value ||
          record.inspector.value.length === 0) {
        errors.push('点呼実施者を選択してください');
        record.inspector.error = '必須項目です';
      }

      if (errors.length > 0) {
        event.error = '入力漏れがあります:\n'
          + errors.join('\n');
        return event;
      }

      // アルコール値の最終判定
      setAlcoholResult(record);

      // 要確認の場合は指示事項を必須にする
      var alcoholValue = parseFloat(
        record.alcoholValue.value
      );
      if (alcoholValue >= ALCOHOL_THRESHOLD
          && !record.instruction.value) {
        event.error = 'アルコール値が基準値以上です。'
          + '指示事項を入力してください。';
        record.instruction.error =
          'アルコール値が基準以上のため必須です';
        return event;
      }

      return event;
    }
  );

  // 新規作成画面を開いた際、点呼日時にデフォルト値をセット
  kintone.events.on('app.record.create.show', function(event) {
    var now = new Date();
    var yyyy = now.getFullYear();
    var mm = ('0' + (now.getMonth() + 1)).slice(-2);
    var dd = ('0' + now.getDate()).slice(-2);
    var hh = ('0' + now.getHours()).slice(-2);
    var mi = ('0' + now.getMinutes()).slice(-2);
    event.record.tenkoDatetime.value =
      yyyy + '-' + mm + '-' + dd + 'T' + hh + ':' + mi
      + ':00+09:00';
    return event;
  });
})();

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

  • アルコール検知値が0.15mg/L以上の場合、自動で「要確認」と判定します。この閾値は道路交通法の酒気帯び運転の基準(呼気1リットルあたり0.15mg)に基づいています
  • 要確認判定の場合、運行管理者の指示事項を必須にしています。監査時に「検知されたがどう対応したか」を記録として残すためです
  • 新規作成時に点呼日時をデフォルトで現在日時にセットしています。早朝の点呼時に日時入力の手間を減らす工夫です

ハマりどころ

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

日時フィールドのタイムゾーン指定

kintoneの日時フィールドにAPIから値をセットする際、、、、タイムゾーン指定を省略するとUTCとして解釈されます。日本時間の朝6時に点呼した記録が前日の21時として登録されてしまう問題が発生しました。+09:00のタイムゾーン指定を忘れないようにする必要があります。

数値フィールドの空文字チェック

アルコール値の未入力チェックで、record.alcoholValue.valuenullではなく空文字""で返ってくるケースがあります。=== nullだけでは漏れてしまい、、、、未入力のまま保存できてしまう問題が発生しました。=== ''=== null=== undefinedの3パターンでチェックするのが確実です。

アルコール検知器メーカーごとの数値単位の違い

お客様が使用しているアルコール検知器のメーカーによって、検知結果の数値単位がmg/LとBAC(%)で異なるケースがありました。入力時の単位を統一するルールを決めておかないと、、、、基準値との比較が正しく機能しません。今回はmg/Lに統一し、フィールドの説明文にも単位を明記しました。

導入結果

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

  • 記入漏れが月平均12件からゼロになった(必須チェックの効果)
  • 監査対応が2日から半日に短縮された(検索・フィルタ機能の活用)
  • 運行管理者3名の点呼関連業務が1日あたり約40分短縮された
  • アルコール検知で「要確認」が出た際の対応フローが明確化され、記録が残るようになった
  • 遠隔地の営業所のIT点呼記録と本社の対面点呼記録が同一アプリで管理できるようになった

特に監査対応の負担軽減は大きかったですね。「この期間のこのドライバーの記録を出してください」と言われたときに、一覧画面のフィルタで即座に該当レコードを抽出できるようになりました。

まとめ

今回は運輸業のアルコールチェック・点呼記録をkintoneで電子化した事例を紹介しました。

アルコール検知器による点呼確認は運送事業者に2011年から義務付けられており、2023年12月には白ナンバー車両にも義務が拡大されました。記録の保存・管理体制が厳しく見られるようになっています。紙やExcelでの管理は記入漏れや検索性の面でリスクが高く、監査のたびに対応工数が膨れ上がる原因にもなります。

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

  • アルコール検知値の自動判定で、基準超過時の対応フローを仕組み化する
  • 必須チェックで記入漏れを防止し、監査で指摘されるリスクを排除する
  • 日時フィールドのタイムゾーン指定を忘れず、正確な点呼時刻を記録する

結果として、運行管理者3名の業務負担が大幅に軽減され、法令遵守の面でも安心して運用できる体制が整いました。

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