見出し画像

レコード一覧を顧客名寄せグループ集計して表示する

Kintoneのカスタムビュー機能の活用例です。
今回は、案件管理一覧を、顧客ごとに名寄せ表示する方法をご紹介します。

やりたいこと

Kintoneの案件管理の一覧表を顧客名の名寄せグループで表示したい。
kintoneの一覧表には、条件絞り込みやクロス集計の機能はありますが、名寄せ表示の機能はありませんので、カスタムビューとJavascriptカスタマイズの組み合わせで、名寄せ集計付きの一覧表を実現します。
名寄せ顧客名に案件合計金額を表示しその下に案件明細を表示します。

カスタムビュー(顧客名寄せ)のイメージ

Kintoneの案件管理一覧表を顧客名ごとにグループ化し、受注金額の多い順に表示。顧客別の受注金額を集計して受注金額の大きい順番にソート表示します。案件名をクリックするだけで詳細画面にアクセスできます。
※名寄せ効果が分かり易い様にサンプルデータを追加しています。

顧客名寄せ集計の一覧表

サンプルとして、Kintoneのアプリストアから「営業支援パック」の案件管理アプリを作成してカスタムビューを設定してみます。

営業支援パック

デモ画面

一覧表からカスタムビューに切り替えると顧客名で名寄せ表示されます。
案件名のクリックでレコード詳細画面を表示します。

デモ画面

カスタムビューの設定

アプリの設定>一覧から、+ボタンで一覧を追加画面を開きます。

一覧表設定画面

一覧追加画面で各設定を以下の通り行います。
一覧名は、自由に好きな名前を付けてもかまいません。
一覧ID(自動採番)は、Javascriptの設定で使用します。

HTMLの欄に以下のHTMLコードをコピーして貼り付けます。
先頭の<META>タグは、レスポンシブデザインを実現する基本設定です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>顧客名寄せ一覧表示</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            margin: 0;
            padding: 20px;
        }
        #customer-list {
            max-width: 800px;
            margin: 0 auto;
        }
        .customer-group {
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 15px;
            background-color: #f9f9f9;
        }
        .customer-name {
            font-size: 20px;
            font-weight: bold;
            margin-bottom: 10px;
            color: #2c3e50;
            border-bottom: 2px solid #3498db;
            padding-bottom: 5px;
        }
        .total-amount {
            font-size: 0.8em;
            color: #7f8c8d;
            margin-left: 10px;
            font-weight: normal;
        }
        .project-card {
            background-color: #ffffff;
            border: 1px solid #eee;
            border-radius: 3px;
            padding: 10px;
            margin-bottom: 8px;
            transition: box-shadow 0.3s ease;
        }
        .project-card:hover {
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        .project-link {
            font-weight: bold;
            color: #3498db;
            text-decoration: none;
        }
        .project-link:hover {
            text-decoration: underline;
        }
        .project-details {
            margin-left: 10px;
            color: #7f8c8d;
            font-size: 0.9em;
        }
    </style>
</head>
<body>
    <div id="customer-list"></div>
</body>
</html>

サンプルJavascriptコード

// 初期設定の部分をアプリの設定に合わせて変更します。
CUSTOM_VIEW_IDには、一覧IDを設定します。
FIELDS
の各要素には、フォームのフィールドコードを設定します
初期設定のフィールド名を調整して工夫すれば、他のアプリでも応用が利くと思います。

/* カスタムビューで顧客名名寄せ一覧表 */
(() => {
    'use strict';

    // 初期設定
    const CONFIG = {
        CUSTOM_VIEW_ID: 123456, // 実際のカスタムビューの一覧IDに変更してください
        FIELDS: {
            CUSTOMER_NAME: '顧客名',
            PROJECT_NAME: '案件名',
            ORDER_AMOUNT: '合計費用',
            DELIVERY_DATE: '受注予定日',
            PROBABILITY: '確度'
        }
    };

    // レコードをグループ化する関数
    const groupRecords = (records, groupField) => {
        const grouped = {};
        records.forEach(record => {
            const key = record[groupField].value;
            if (!grouped[key]) {
                grouped[key] = [];
            }
            grouped[key].push(record);
        });
        return grouped;
    };

    // レコードをソートする関数
    const sortRecords = (records, sortField, ascending = true) => {
        return records.sort((a, b) => {
            const valueA = a[sortField].value;
            const valueB = b[sortField].value;
            return ascending ? valueA.localeCompare(valueB) : valueB - valueA;
        });
    };

    // HTMLエレメントを作成する関数
    const createElement = (tag, className, textContent = '') => {
        const element = document.createElement(tag);
        element.className = className;
        element.textContent = textContent;
        return element;
    };

    // 数値を3桁カンマ区切りにフォーマットする関数
    const formatNumber = (number) => {
        return number.toLocaleString();
    };

    // プロジェクトカードを作成する関数
    const createProjectCard = (project, appId) => {
        const card = createElement('div', 'project-card');
        const recordId = project.$id.value;
        const projectName = project[CONFIG.FIELDS.PROJECT_NAME].value;
        const orderAmount = formatNumber(Number(project[CONFIG.FIELDS.ORDER_AMOUNT].value));
        const deliveryDate = project[CONFIG.FIELDS.DELIVERY_DATE].value;
        const probability = project[CONFIG.FIELDS.PROBABILITY].value;

        card.innerHTML = `
            <a href="/k/${appId}/show#record=${recordId}" target="_blank" class="project-link">${projectName}</a>
            <span class="project-details">
                受注金額: ${orderAmount}円 | 納期: ${deliveryDate} | 案件確度: ${probability}
            </span>
        `;
        return card;
    };

    kintone.events.on('app.record.index.show', (event) => {
        // 現在の一覧IDがカスタムビューの一覧IDと一致しない場合は何もしない
        if (event.viewId !== CONFIG.CUSTOM_VIEW_ID) return;

        const customerList = document.getElementById('customer-list');
        customerList.innerHTML = '';

        // アプリIDを取得
        const appId = kintone.app.getId();

        // レコードを顧客名でグループ化
        const groupedRecords = groupRecords(event.records, CONFIG.FIELDS.CUSTOMER_NAME);

        // 顧客ごとの合計受注金額を計算し、ソート用の配列を作成
        const sortedCustomers = Object.keys(groupedRecords).map(customerName => {
            const totalOrderAmount = groupedRecords[customerName].reduce((sum, project) => {
                return sum + Number(project[CONFIG.FIELDS.ORDER_AMOUNT].value);
            }, 0);
            return { customerName, totalOrderAmount };
        });

        // 合計受注金額の降順でソート
        sortedCustomers.sort((a, b) => b.totalOrderAmount - a.totalOrderAmount);

        sortedCustomers.forEach(({ customerName, totalOrderAmount }) => {
            const customerGroup = createElement('div', 'customer-group');
            
            const customerNameElement = createElement('div', 'customer-name');
            customerNameElement.innerHTML = `${customerName} <span class="total-amount">合計受注金額: ${formatNumber(totalOrderAmount)}円</span>`;
            customerGroup.appendChild(customerNameElement);

            // 顧客内のプロジェクトを金額の降順でソート
            const sortedProjects = sortRecords(groupedRecords[customerName], CONFIG.FIELDS.ORDER_AMOUNT, false);

            sortedProjects.forEach(project => {
                customerGroup.appendChild(createProjectCard(project, appId));
            });

            customerList.appendChild(customerGroup);
        });
    });
})();

注意点

kintoneにはレコード一括取得の制限値(上限500件)があります。
上記のサンプルコードでは、レコード数を500件以上取得できませんので、レコード数が500件超の場合は、名寄せ合計の集計結果が正しく得られない点に注意して下さい。
もちろん工夫すれば、500件超のレコード取得と集計も可能です。

500件超のレコードを取得する方法は、以下の2つの方法があります。
(1)リクエストパラメーターoffsetを指定(レコード数1万以下)
(2)カーソルAPIを使用(レコード数1万超)
詳細は、以下のページを参照して下さい。


カスタマイズした感想

レコード一覧での集計処理では、レコード一括取得の制限値(上限500件)も意識してカスタマイズ対応する必要があります。
今回のサンプルコードでは、レコード数500件以下を前提としていますが、500件以上のレコードを読み込んで集計も出来る様にoffsetパラメータを用いた改良バージョンにも、そのうち挑戦してみたいと思います。

2024/08/19 offsetパラメータを用いた事例を公開しました。


今回のカスタマイズでは、新しい設定方法が沢山出てきたので、個人的な備忘録として以下に残しておきます。

(1)HTMLファイル

カスタムビューのHTMLファイルの先頭のMETAタグについて
<meta name="viewport" content="width=device-width, initial-scale=1.0">
上記のコードは、ウェブページを様々なデバイスで快適に閲覧できるようにするための設定です。

各属性の意味

  • name="viewport": この属性値が"viewport"であることで、ブラウザにこのメタタグがviewportの設定であることを伝えます。

    • width=device-width: 表示領域の幅を、デバイスの画面幅に合わせることを指定します。これにより、ページがデバイスの画面いっぱいに表示され、ユーザーは横スクロールすることなくページ全体を見ることができます。

    • initial-scale=1.0: ページの初期表示倍率を1.0倍に設定します。つまり、ページがピンチインやピンチアウトされることなく、実際のサイズで表示されます。

このコードを記述することで、異なる画面サイズのデバイス(スマートフォン、タブレット、PCなど)で、それぞれに最適な表示サイズでページが表示される「レスポンシブデザイン」を実現するための基本的な設定です。

(2)Javascriptファイル

全体の処理は以下の通りです。

  1. 取得したレコードを顧客名ごとにグループ化

  2. 顧客ごとの合計受注金額を計算し、ソート用の配列を作成

  3. 合計受注金額の降順でソートし顧客名と合計額のelementを出力

  4. 顧客内のプロジェクトを金額の降順でソート

  5. プロジェクト明細のelementを出力

今回工夫したのは、レコードをグループ化して並び替えする処理です。
レコードをグループ化する関数は、event.recordとグループ化するキー項目を指定し、処理対象レコードをループ処理してグループキーの値別にオブジェクト(レコードの集まり)を作成しています。
つまり オブジェクトgroupedkeyに顧客名の値を指定すれば、顧客名が同じレコードの集まりを操作することが出来ます。
例:grouped['金都運総研']

    // レコードをグループ化する関数
    const groupRecords = (records, groupField) => {
        const grouped = {};
        records.forEach(record => {
            const key = record[groupField].value;
            if (!grouped[key]) {
                grouped[key] = [];
            }
            grouped[key].push(record);
        });
        return grouped;
    };

一覧表のカスタマイズは、なかなか奥が深いですね。
今回も最後まで読んで頂き、ありがとうございました。

いいなと思ったら応援しよう!

アプリ活用研究会
よろしければサポートお願いします! いただいたサポートは、note記事制作の活動費に使わせていただきます!