kintone
投稿日:

製造業で設備管理を効率化!kintone×QRコードで“現場点検をスマート化”

こんにちは。kintoneアプリエンジニアのtomiokaです。以前からブログ書いてよって言われ続けていたんですが、ネタ集めに時間がかかり、、、、
今回デビュー作となります!
少しだけ自己紹介ですが、元々はフロントエンドエンジニアとしてTypeScriptをガリガリ書いていたのですが、kintoneのカスタマイズの楽しさにハマって、いまはkintoneアプリの構築やプラグイン開発をメインにしています。
平日はアプリ開発やプラグインづくりに勤しみ、休日は昼からビールを飲むのが最高のリフレッシュ。美味しい料理とお酒があれば、だいたいご機嫌というシンプルな人間です。

そんな私がブログデビューの今回、ご紹介するのは、「QRコードで現場点検をスマート化するkintoneカスタマイズ」
実際にお客様より依頼いただいた内容でして、ちょっとした仕組みですが、現場の動きを大きく変えられます。

はじめに ― 現場の“探す時間”を減らすだけで変わる

製造業の現場では、点検結果を紙やExcelに書き留め、後からパソコンに入力する――そんな手順がまだまだ当たり前。
でも、記録を取るたびに「どの設備のレコードだったっけ?」と機器管理台帳を検索する時間、もったいないですよね。

「QRを貼っておいて、スマホで読み取るだけで開けたらいいのに」
という現場の声を形にしたのが、今回ご紹介するQRコードのカスタマイズです。
標準のkintoneでは機器毎に詳細ページのURLが作られますので、これをQRコードにしてしまいます。

1. QRコードで点検が変わる理由

QRコードのいいところは、とにかく“迷わない”こと。
URLや検索条件をいちいち入力する必要もなく、スマホでかざせば一発で目的のレコードにアクセスできます。

たとえば、設備の側面や管理ラベルにQRを貼っておけば、
担当者は現場で立ったまま入力。作業スピードがぐっと上がります。
しかも紙の点検表も不要。スマホ1台で完結します。

2. 方式A ― 機器別にQRを印刷して貼る

最も簡単なのがこの方式。
kintoneの各レコード(=設備)ごとにQRコードを生成し、設備に貼ってしまうやり方です。

方式Aの実装用コード

kintoneアプリにJavaScriptとして読み込ませてください。

/*
 * レコード詳細画面のヘッダーメニューに
 * QR表示ボタンを追加し、モーダルでQRコードを表示するカスタマイズ。
 *
 * QRCode.js をCDNから動的ロードします。
 * https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js
 */
(function () {
  'use strict';

  // QRCodeライブラリを動的ロード(重複ロード防止)
  function loadQRCodeLib() {
    return new Promise((resolve, reject) => {
      if (window.QRCode) return resolve();
      const s = document.createElement('script');
      s.src = 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js';
      s.onload = () => resolve();
      s.onerror = () => reject(new Error('QRCode.jsの読み込みに失敗しました'));
      document.head.appendChild(s);
    });
  }

  // CSSを注入(1度だけ)
  function injectModalCSS() {
    if (document.getElementById('qr-modal-style')) return;
    const style = document.createElement('style');
    style.id = 'qr-modal-style';
    style.textContent = `
      /* QR表示ボタンのスタイル */
      .qr-show-btn {
        appearance: none; border: 1px solid #0066cc; border-radius: 6px;
        padding: 6px 12px; background: #0066cc; color: white; cursor: pointer;
        font-size: 12px; font-weight: bold;
      }
      .qr-show-btn:hover { background: #0052a3; }

      /* モーダル風ポップアップのスタイル */
      .qr-modal {
        position: fixed; top: 0; left: 0; width: 100%; height: 100%;
        background: rgba(0,0,0,0.5); z-index: 10000;
        display: none; align-items: center; justify-content: center;
      }
      .qr-modal-content {
        background: white; padding: 20px; border-radius: 8px;
        box-shadow: 0 4px 20px rgba(0,0,0,0.3);
        max-width: 400px; width: 90%;
        text-align: center;
      }
      .qr-modal-header {
        display: flex; justify-content: space-between; align-items: center;
        margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee;
      }
      .qr-modal-title { font-size: 16px; font-weight: bold; margin: 0; }
      .qr-modal-close {
        background: none; border: none; font-size: 20px; cursor: pointer;
        color: #666; padding: 0; width: 24px; height: 24px;
      }
      .qr-modal-close:hover { color: #333; }
      .qr-modal-body { margin-bottom: 15px; }
      .qr-modal-footer { display: flex; gap: 10px; justify-content: center; }
      #qr-canvas { width:200px; margin:0 auto; }
      .qr-meta { font-size: 12px; color: #555; }
      .qr-print-btn {
        appearance: none; border: 1px solid #ddd; border-radius: 6px;
        padding: 6px 10px; background: #f7f7f7; cursor: pointer;
      }
      .qr-print-btn:hover { background: #efefef; }
    `;
    document.head.appendChild(style);
  }

  // モーダル表示・非表示の制御
  function showQRModal(url, recordId) {
    // 既存のモーダルを削除
    const existingModal = document.getElementById('qr-modal');
    if (existingModal) existingModal.remove();

    // モーダル生成
    const modal = document.createElement('div');
    modal.id = 'qr-modal';
    modal.className = 'qr-modal';
    modal.style.display = 'flex';

    modal.innerHTML = `
      <div class="qr-modal-content">
        <div class="qr-modal-header">
          <h3 class="qr-modal-title">QRコード</h3>
          <button type="button" class="qr-modal-close" id="qr-modal-close">×</button>
        </div>
        <div class="qr-modal-body">
          <div id="qr-print-wrap">
            <div id="qr-canvas" title="このレコードのURLをQR化"></div>
            <div class="qr-meta">
              レコードID: ${recordId}<br>
              <a href="${url}" target="_blank" rel="noopener">このレコードを開く</a>
            </div>
          </div>
        </div>
        <div class="qr-modal-footer">
          <button type="button" class="qr-print-btn" id="qr-modal-print-btn">このQRだけ印刷</button>
        </div>
      </div>
    `;

    document.body.appendChild(modal);

    // QR生成
    const qrTarget = modal.querySelector('#qr-canvas');
    new QRCode(qrTarget, { text: url, width: 200, height: 200 });

    // イベントリスナー
    modal.querySelector('#qr-modal-close').addEventListener('click', () => {
      modal.remove();
    });

    modal.querySelector('#qr-modal-print-btn').addEventListener('click', () => {
      // 印刷前にモーダルを確実に表示状態にする
      modal.style.display = 'flex';
      modal.style.visibility = 'visible';

      // 少し遅延してから印刷を実行(レンダリング完了を待つ)
      setTimeout(() => {
        window.print();
      }, 100);
    });

    // モーダル外クリックで閉じる
    modal.addEventListener('click', (e) => {
      if (e.target === modal) {
        modal.remove();
      }
    });
  }

  // レコード詳細表示時にQR表示ボタンを描画
  kintone.events.on('app.record.detail.show', async (event) => {
    try {
      await loadQRCodeLib();
      injectModalCSS();

      const menu = kintone.app.record.getHeaderMenuSpaceElement();
      if (!menu) return event;

      // 既存をクリア(再描画対策)
      let host = menu.querySelector('#qr-show-btn-host');
      if (host) host.remove();

      const recordId = kintone.app.record.getId();
      const appId = kintone.app.getId();
      const url = `${location.origin}/k/${appId}/show#record=${recordId}`;

      // QR表示ボタンのコンテナ生成
      host = document.createElement('div');
      host.id = 'qr-show-btn-host';
      host.style.marginLeft = '12px';

      host.innerHTML = `
        <button type="button" class="qr-show-btn" id="qr-show-btn">
          QR表示
        </button>
      `;

      menu.appendChild(host);

      // QR表示ボタンのイベントリスナー
      host.querySelector('#qr-show-btn').addEventListener('click', () => {
        showQRModal(url, recordId);
      });

      return event;
    } catch (e) {
      console.error('QRコード生成エラー:', e);
      return event;
    }
  });
})();

このコードを登録すると、レコード詳細画面にQRコード表示ボタンが表示され、ボタンをクリックすると自動的にQRが表示されます。
印刷して設備に貼っておけば、スマホでスキャンするだけでその設備のレコードを即表示。
現場の担当者は「探す」手間がゼロになります。

ただし、設備台数が数百台を超えるような大規模な工場では印刷が手間です。
その場合は、次の方式B(カテゴリ別QR)がオススメです。

3. 方式B ― カテゴリや部署ごとにQRを発行

もっと柔軟に、かつ印刷枚数を減らしたいなら、カテゴリ/部署ごとに1枚のQRを用意します。
この方式では、kintoneの一覧画面(検索結果)のヘッダーメニュー領域に、
「現在表示中のビューにアクセスできるQR」と「そのQRだけ印刷するボタン」を自動生成します。

これにより、保存済みビューを開くだけでQRが表示され、印刷して掲示することができます。

方式Bの実装用コード

kintoneアプリにJavaScriptとして読み込ませてください。

/*
 * 一覧ビュー右上(ヘッダーメニュー)に
 * - 現在表示中ビューへのQRコード
 * - そのQRだけ印刷するボタン
 * を追加するカスタマイズ。
 *
 * QRCode.js をCDNから動的ロードします。
 * https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js
 */
(function () {
  'use strict';

  // 1) QRCodeライブラリを動的ロード(重複ロード防止)
  function loadQRCodeLib() {
    return new Promise((resolve, reject) => {
      if (window.QRCode) return resolve();
      const s = document.createElement('script');
      s.src = 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js';
      s.onload = () => resolve();
      s.onerror = () => reject(new Error('QRCode.jsの読み込みに失敗しました'));
      document.head.appendChild(s);
    });
  }

  // 2) 印刷時にQR枠だけ出すCSSを注入(1度だけ)
  function injectPrintOnlyCSS() {
    if (document.getElementById('qr-print-style')) return;
    const style = document.createElement('style');
    style.id = 'qr-print-style';
    style.textContent = `


      .qr-box {
        display: inline-flex; align-items: center; gap: 12px;
        padding: 8px 12px; border: 1px solid #e2e2e2; border-radius: 8px;
        background: #fff;
      }
      .qr-meta { font-size: 12px; color: #555; }
      .qr-print-btn {
        appearance: none; border: 1px solid #ddd; border-radius: 6px;
        padding: 6px 10px; background: #f7f7f7; cursor: pointer;
      }
      .qr-print-btn:hover { background: #efefef; }

      /* QR表示ボタンのスタイル */
      .qr-show-btn {
        appearance: none; border: 1px solid #0066cc; border-radius: 6px;
        padding: 6px 12px; background: #0066cc; color: white; cursor: pointer;
        font-size: 12px; font-weight: bold;
      }
      .qr-show-btn:hover { background: #0052a3; }

      /* モーダル風ポップアップのスタイル */
      .qr-modal {
        position: fixed; top: 0; left: 0; width: 100%; height: 100%;
        background: rgba(0,0,0,0.5); z-index: 10000;
        display: none; align-items: center; justify-content: center;
      }
      .qr-modal-content {
        background: white; padding: 20px; border-radius: 8px;
        box-shadow: 0 4px 20px rgba(0,0,0,0.3);
        max-width: 400px; width: 90%;
        text-align: center;
      }
      .qr-modal-header {
        display: flex; justify-content: space-between; align-items: center;
        margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee;
      }
      .qr-modal-title { font-size: 16px; font-weight: bold; margin: 0; }
      .qr-modal-close {
        background: none; border: none; font-size: 20px; cursor: pointer;
        color: #666; padding: 0; width: 24px; height: 24px;
      }
      .qr-modal-close:hover { color: #333; }
      .qr-modal-body { margin-bottom: 15px; }
      .qr-modal-footer { display: flex; gap: 10px; justify-content: center; }
      #qr-canvas { width:200px; margin:0 auto; }
    `;
    document.head.appendChild(style);
  }

  // 3) モーダル表示・非表示の制御
  function showQRModal(url, viewId) {
    // 既存のモーダルを削除
    const existingModal = document.getElementById('qr-modal');
    if (existingModal) existingModal.remove();

    // モーダル生成
    const modal = document.createElement('div');
    modal.id = 'qr-modal';
    modal.className = 'qr-modal';
    modal.style.display = 'flex';

    modal.innerHTML = `
      <div class="qr-modal-content">
        <div class="qr-modal-header">
          <h3 class="qr-modal-title">QRコード</h3>
          <button type="button" class="qr-modal-close" id="qr-modal-close">×</button>
        </div>
        <div class="qr-modal-body">
          <div id="qr-print-wrap">
            <div id="qr-canvas" title="この一覧ビューのURLをQR化"</div>
            <div class="qr-meta">
              一覧ビューID: ${viewId}<br>
              <a href="${url}" target="_blank" rel="noopener">このビューを開く</a>
            </div>
          </div>
        </div>
        <div class="qr-modal-footer">
          <button type="button" class="qr-print-btn" id="qr-modal-print-btn">このQRだけ印刷</button>
        </div>
      </div>
    `;

    document.body.appendChild(modal);

    // QR生成
    const qrTarget = modal.querySelector('#qr-canvas');
    new QRCode(qrTarget, { text: url, width: 200, height: 200 });

    // イベントリスナー
    modal.querySelector('#qr-modal-close').addEventListener('click', () => {
      modal.remove();
    });

    modal.querySelector('#qr-modal-print-btn').addEventListener('click', () => {
      // 印刷前にモーダルを確実に表示状態にする
      modal.style.display = 'flex';
      modal.style.visibility = 'visible';

      // 少し遅延してから印刷を実行(レンダリング完了を待つ)
      setTimeout(() => {
        window.print();
      }, 100);
    });

    // モーダル外クリックで閉じる
    modal.addEventListener('click', (e) => {
      if (e.target === modal) {
        modal.remove();
      }
    });
  }

  // 4) 一覧表示時にQR表示ボタンを描画
  kintone.events.on('app.record.index.show', async (event) => {
    try {
      await loadQRCodeLib();
      injectPrintOnlyCSS();

      const menu = kintone.app.getHeaderMenuSpaceElement();
      if (!menu) return event;

      // 既存をクリア(再描画対策)
      let host = menu.querySelector('#qr-show-btn-host');
      if (host) host.remove();

      // 現在表示中ビューのURLを生成(= 検索結果にダイレクト)
      const appId = kintone.app.getId();
      const viewId = event.viewId; // ← 今の一覧ビューID
      const url = location.origin + `/k/${appId}/?view=${viewId}`;

      // QR表示ボタンのコンテナ生成
      host = document.createElement('div');
      host.id = 'qr-show-btn-host';
      host.style.marginLeft = '12px';

      host.innerHTML = `
        <button type="button" class="qr-show-btn" id="qr-show-btn">
          QR表示
        </button>
      `;

      menu.appendChild(host);

      // QR表示ボタンのイベントリスナー
      host.querySelector('#qr-show-btn').addEventListener('click', () => {
        showQRModal(url, viewId);
      });

      return event;
    } catch (e) {
      console.error(e);
      return event;
    }
  });
})();

運用の流れ

方式Bでは以下の流れでQRコードを管理を利用することができます。

  1. 各部署・カテゴリごとに保存済みビューを作成。
    例:「ラインA一覧」「検査部門一覧」など。
  2. 閲覧対象のビューを開くと、自動的にそのビュー専用QR+印刷ボタンが右上に表示。
  3. 「このQRだけ印刷」をクリックすれば、QRのみをきれいに印刷可能。
  4. 現場に掲示しておけば、QRスキャン → その部署一覧が即開く → 設備をタップして点検。

ポイント:ビューのURLは固定なので、カテゴリが増減してもQRを貼り替える必要がありません。

5. 現場での導入ステップ

どちらの方式でも、運用の流れはシンプルです。

機器別QRの例

  • 各レコードにQRが自動生成される
  • 管理者が印刷して設備に貼る
  • 現場でスマホをかざしてレコードを即表示
  • その場で点検結果を入力して保存

「設備1台=QR1枚」のため、対象が少ない現場では非常にわかりやすく運用できます。

カテゴリ別QRの例

  • 部署やカテゴリ単位でQRを1枚作成
  • 現場の掲示板などに貼る
  • 担当者がスキャン → 一覧を開いて目的の設備を選択
  • 点検結果をその場で入力

こちらは多人数・多設備の現場に最適です。
たとえば「検査ラインA」「生産2課」といったチーム単位でQRを分けておくと、
担当者は迷うことなく自分の範囲のデータにアクセスできます。

6. 導入後のリアルな効果

こういったQRコードによる機器台帳管理の仕組みを導入した工場では、
機器の確認に「点検1件あたり30秒~1分短縮」という結果が出ました。
数字だけ見ると小さく見えますが、1日100件の点検を行う現場なら、
毎月50分以上の削減効果になります。

それだけでなく、「記録漏れが減った」「現場メンバーが自発的に入力してくれるようになった」 という声も多くいただきました。
現場で使う仕組みこそ、“操作がシンプルで迷わないこと”が最重要ですね。

おわりに ― 現場も開発者も“気持ちよく”

私はいつも、「現場の人が使って気持ちいいkintoneアプリ」を作ることを大事にしています。
QRコード連携はその中でも、一番シンプルで一番効果が出やすい工夫です。

弊社では、こういったちょっとしたkintoneの活用方法をご提案、実装するサービスをご提供しております。
ご相談はもちろん無料ですので、kintoneをすでに導入していてお困りごとがあれば、ぜひご連絡ください。

※記事内に記載のコードはサンプルコードとなります。弊社はサンプルコード使用に起因する損害、第三者の権利を侵害に関し、いかなる責任も負いませんことご理解の上ご利用ください。