【WordPress財務データアプリ】データ検索ダウンロード

ブログ

財務データを検索

今回は以下のような財務データ全体を条件検索できるページを作成したいと思います。

機能としては、

  • 企業名やコードによる検索
  • 提出日による検索
  • 財務項目データ条件による検索
  • CSVのダウンロード

になります。

財務項目データ条件による検索は複数条件(ANDかOR)による検索にも対応できるようにしました。

今回もこれまで同様に固定ページのテンプレートを使って実装します。

このアプリの作り方のはじめのページはこちら

全体のコード

固定ページテンプレートには以下を記述します。
financial-data-db.php

<?php
/* Template Name: Financial Data */

// 財務項目の配列(キーがデータベースのカラム名、値が表示名)
$financial_metrics = [
    'sales' => '売上高',
    'gross_profit_loss' => '売上総利益',
    'operating_income_loss' => '営業利益',
    'ordinary_income_loss' => '経常利益',
    'net_income_loss' => '当期純利益',
    'parent_net_income_loss' => '親会社株主に帰属する当期純利益',
    'operating_cf' => '営業活動によるCF',
    'investing_cf' => '投資活動によるCF',
    'financing_cf' => '財務活動によるCF',
    'assets' => '資産',
    'liabilities' => '負債',
    'current_assets' => '流動資産',
    'current_liabilities' => '流動負債',
    'net_assets' => '純資産',
    'shareholders_equity' => '株主資本',
    'retained_earnings' => '利益剰余金',
    'short_term_debt' => '短期借入金',
    'long_term_debt' => '長期借入金',
    'corporate_taxes' => '法人税等',
    'sg_and_a' => '販管費',
    'depreciation' => '減価償却費',
    'interest_income_dividends' => '受取利息及び配当金',
    'interest_expenses' => '支払利息',
    'per' => '株価収益率',
    'shares_outstanding' => '発行済株式総数',
    'dividend_per_share' => '1株当たり配当額',
];

get_header(); ?>
<div class="financial-container">
    <h1>財務データ検索</h1>

    <form id="search-form" class="search-form">
        <div class="form-group">
            <label for="company-name">企業名:</label>
            <input type="text" id="company-name" name="company_name">
        </div>

        <div class="form-group">
            <label for="sec-code">証券コード:</label>
            <input type="text" id="sec-code" name="sec_code">
        </div>

        <div class="form-group">
            <label for="date-range">日付範囲:</label>
            <input type="date" id="start-date" name="start_date">
            <input type="date" id="end-date" name="end_date">
        </div>
        <div class="form-group">
        <label for="financial-metrics">財務項目:</label>
        <div id="financial-conditions">
            <div class="financial-condition">

                <select name="financial_metrics[]">
                    <option value="">選択してください</option>
                    <?php foreach ($financial_metrics as $field => $label): ?>
                        <option value="<?php echo esc_attr($field); ?>">
                            <?php echo esc_html($label); ?>
                        </option>
                    <?php endforeach; ?>
                </select>

                <select name="operator[]">
                    <option value="=">=</option>
                    <option value=">">></option>
                    <option value="<"><</option>
                    <option value=">=">>=</option>
                    <option value="<="><=</option>
                    <option value="!=">!=</option>
                </select>

                <input type="text" name="metric_value[]">
                <!-- 削除ボタン -->
                <button type="button" class="btn btn-danger remove-condition">削除</button>
            </div>

        </div>
        <button type="button" id="add-condition" class="btn btn-secondary">条件を追加</button>
        </div>


        <div class="form-group">
            <label for="condition-type">条件の組み合わせ:</label>
            <select id="condition-type" name="condition_type">
                <option value="AND">AND</option>
                <option value="OR">OR</option>
            </select>
        </div>


        <button type="submit" class="btn btn-primary">検索</button>
    </form>



    <table id="financial-table" class="display">
        <thead>
            <tr>

            </tr>
        </thead>
        <tbody>
            <!-- データがここに挿入されます -->
        </tbody>
    </table>
    <button id="downloadCsvBtn" class="btn btn-primary">Download CSV</button> <!-- CSVダウンロードボタンを追加 -->
</div>

<?php get_footer(); ?>

<!-- CSSの読み込み -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.24/css/jquery.dataTables.min.css">

<!-- JavaScriptの読み込み -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
<script>
    jQuery(document).ready(function($) {
        function initializeDataTable(data) {

            // DataTableの初期化
            $('#financial-table').DataTable({
                "data": data,
                "columns": [
                    { title: 'docID', data: 'docID' },
                    { title: '提出日', data: 'date' },
                    { title: '証券コード', data: 'secCode' },
                    { title: '企業名', data: 'filerName' },
                    { title: '会計期間終了日', data: 'EndDate' },
                    { title: '売上高(百万)', data: 'sales' },
                    { title: '売上総利益(百万)', data: 'gross_profit_loss' },
                    { title: '営業利益(百万)', data: 'operating_income_loss' },
                    { title: '経常利益(百万)', data: 'ordinary_income_loss' },
                    { title: '当期純利益(百万)', data: 'net_income_loss' },
                    { title: '親会社株主に帰属する当期純利益(百万)', data: 'parent_net_income_loss' },
                    { title: '営業活動によるCF(百万)', data: 'operating_cf' },
                    { title: '投資活動によるCF(百万)', data: 'investing_cf' },
                    { title: '財務活動によるCF(百万)', data: 'financing_cf' },
                    { title: '資産(百万)', data: 'assets' },
                    { title: '負債(百万)', data: 'liabilities' },
                    { title: '流動資産(百万)', data: 'current_assets' },
                    { title: '流動負債(百万)', data: 'current_liabilities' },
                    { title: '純資産(百万)', data: 'net_assets' },
                    { title: '株主資本(百万)', data: 'shareholders_equity' },
                    { title: '利益剰余金(百万)', data: 'retained_earnings' },
                    { title: '短期借入金(百万)', data: 'short_term_debt' },
                    { title: '長期借入金(百万)', data: 'long_term_debt' },
                    { title: '法人税等(百万)', data: 'corporate_taxes' },
                    { title: '販管費(百万)', data: 'sg_and_a' },
                    { title: '減価償却費(百万)', data: 'depreciation' },
                    { title: '受取利息及び配当金(百万)', data: 'interest_income_dividends' },
                    { title: '支払利息(百万)', data: 'interest_expenses' },
                    { title: '株価収益率', data: 'per' },
                    { title: '発行済株式総数(百万)', data: 'shares_outstanding' },
                    { title: '1株当たり配当額', data: 'dividend_per_share' }
                ],
                "scrollY": "400px",
                "scrollX":true,
                "scrollCollapse": true,
                "paging": true,

                "language": {
                    "emptyTable": "データがありません",
                    "info": "全 _TOTAL_ 件中 _START_ 件から _END_ 件を表示",
                    "infoEmpty": "データがありません",
                    "infoFiltered": "(全 _MAX_ 件からフィルタリングされています)",
                    "lengthMenu": "_MENU_ 件表示",
                    "loadingRecords": "読み込み中...",
                    "processing": "処理中...",
                    "search": "検索:",
                    "zeroRecords": "一致するレコードがありません",
                    "paginate": {
                        "first": "最初",
                        "last": "最後",
                        "next": "次",
                        "previous": "前"
                    }
                }
            });
        }

         // 現在の日付と1か月前の日付を計算
        function getLastMonthDateRange() {
            let today = new Date();
            let lastMonth = new Date();
            lastMonth.setMonth(today.getMonth() - 1);

            let startDate = lastMonth.toISOString().split('T')[0]; // YYYY-MM-DD
            let endDate = today.toISOString().split('T')[0]; // YYYY-MM-DD

            return { startDate, endDate };
        }

        // 日付範囲をフォームに設定
        function setFormDateRange() {
            let { startDate, endDate } = getLastMonthDateRange();
            $('#start-date').val(startDate);
            $('#end-date').val(endDate);
        }

        // フォームに日付範囲を設定
        setFormDateRange();

        // 初回データを取得してDataTableを初期化
        let { startDate, endDate } = getLastMonthDateRange();
        $('body').append('<div id="loading-screen" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); color: #fff; display: flex; justify-content: center; align-items: center; z-index: 1000;">Loading...</div>');

        $.ajax({
            url: "<?php echo admin_url('admin-ajax.php'); ?>",
            type: "POST",
            data: {
                action: 'fetch_financial_data', // AJAXアクションの名前
                company_name: '',
                sec_code: '',
                start_date: startDate,
                end_date: endDate,
                financial_metrics: '',
                operator: '', // 演算子
                metric_value: '' // 値
            },
            success: function(response) {
                var data = response.data;
                initializeDataTable(data); // 初期化
            },
            complete: function() {
                // ローディング画面を非表示
                $('#loading-screen').remove();
            }
        });

    // 条件を追加するイベントリスナー
    $('#add-condition').click(function() {
        $('#financial-conditions').append(`
            <div class="financial-condition">
                <select name="financial_metrics[]">
                    <option value="">選択してください</option>
                    <?php foreach ($financial_metrics as $field => $label): ?>
                        <option value="<?php echo esc_attr($field); ?>"><?php echo esc_html($label); ?></option>
                    <?php endforeach; ?>
                </select>

                <select name="operator[]">
                    <option value="=">=</option>
                    <option value=">">></option>
                    <option value="<"><</option>
                    <option value=">=">>=</option>
                    <option value="<="><=</option>
                    <option value="!=">!=</option>
                </select>

                <input type="text" name="metric_value[]">
                <!-- 削除ボタン -->
                <button type="button" class="btn btn-danger remove-condition">削除</button>
            </div>
        `);
    });

    // 条件の削除
    $('#financial-conditions').on('click', '.remove-condition', function() {
        $(this).closest('.financial-condition').remove();
    });



    $('#search-form').on('submit', function(e) {
        $('body').append('<div id="loading-screen" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); color: #fff; display: flex; justify-content: center; align-items: center; z-index: 1000;">Loading...</div>');

        e.preventDefault();
        var formData = $(this).serialize(); // フォームデータをすべて取得

        // DataTableの再初期化
        var table = $('#financial-table').DataTable();


        $.ajax({
            url: "<?php echo admin_url('admin-ajax.php'); ?>",
            type: "POST",
            data: formData + '&action=fetch_financial_data',
            success: function(response) {
                var data = response.data;
                table.clear().destroy();
                initializeDataTable(data);
            },
            complete: function() {
                // ローディング画面を非表示
                $('#loading-screen').remove();
            }
        });
    });

    $('#downloadCsvBtn').on('click', function(e) {
    e.preventDefault();
    let table = $('#financial-table').DataTable();
    let csv = [];

    // テーブルヘッダーの取得
    let headers = table.columns().header().toArray().map(header => '"' + $(header).text().replace(/"/g, '""') + '"');
    csv.push(headers.join(','));

    // 全データの取得
    let data = table.rows().data().toArray(); // 修正: rows().data() メソッドを使用
    data.forEach(row => {
        // row がオブジェクトである場合、各プロパティを取得して処理
        if (typeof row === 'object') {
            let rowData = Object.values(row).map(cell => '"' + String(cell).replace(/"/g, '""') + '"');
            csv.push(rowData.join(','));
        } else {
            console.error('Unexpected data format:', row);
        }
    });

    // Blobオブジェクトの作成とダウンロード
    let csvContent = csv.join('\n');
    let blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
    let link = document.createElement('a');
    if (link.download !== undefined) { // feature detection
        let url = URL.createObjectURL(blob);
        link.setAttribute('href', url);
        link.setAttribute('download', 'data.csv');
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
    }
});



});
</script>
<style>
    #financial-table {
    width: 100%;
}

#financial-table th, #financial-table td {
    white-space: nowrap; 
    overflow: hidden;
    text-overflow: ellipsis;
}

#financial-table th {
    width: 300px; /* 各カラムの幅を指定 */
}

#financial-table td {
    min-width: 100px; /* 各カラムの最小幅を指定 */
}
.financial-container {
    width: 80%;
    margin: 0 auto;
    padding: 20px;
    background-color: #f9f9f9;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

h1 {
    text-align: center;
    margin-bottom: 20px;
}

.search-form {
    margin-bottom: 30px;
}

.form-group {
    margin-bottom: 15px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
}

.form-group input[type="text"],
.form-group input[type="date"],
.form-group select {
    width: 100%;
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
    margin-top: 5px;
}

#financial-conditions .financial-condition {
    margin-bottom: 10px;
    display: flex;
    gap: 10px;
}

#financial-conditions .financial-condition select,
#financial-conditions .financial-condition input[type="text"] {
    flex: 1;
}

#condition-type {
    width: 100%;
    padding: 8px;
    margin-top: 5px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

.btn {
    padding: 10px 15px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.btn-primary {
    background-color: #007bff;
    color: #fff;
}

.btn-secondary {
    background-color: #6c757d;
    color: #fff;
    margin-right: 10px;
}

.btn:hover {
    opacity: 0.9;
}


</style>

functions.phpには以下を追記します。


function fetch_financial_data() {
    global $wpdb;

    // フォームデータの取得
    $company_name = sanitize_text_field($_POST['company_name']);
    $sec_code = sanitize_text_field($_POST['sec_code']);
    $start_date = sanitize_text_field($_POST['start_date']);
    $end_date = sanitize_text_field($_POST['end_date']);
    $financial_metrics = $_POST['financial_metrics'];
    $operators = $_POST['operator'];
    $metric_values = $_POST['metric_value'];
    $condition_type = sanitize_text_field($_POST['condition_type']); // AND または OR

    // 基本のクエリを準備
    $query = "SELECT * FROM {$wpdb->prefix}financial_data WHERE 1=1";

    // 企業名での絞り込み
    if (!empty($company_name)) {
        $query .= $wpdb->prepare(" AND filerName LIKE %s", '%' . $wpdb->esc_like($company_name) . '%');
    }

    // 証券コードでの絞り込み
    if (!empty($sec_code)) {
        $query .= $wpdb->prepare(" AND secCode = %s", $sec_code);
    }

    // 日付範囲での絞り込み
    if (!empty($start_date) && !empty($end_date)) {
        $query .= $wpdb->prepare(" AND date BETWEEN %s AND %s", $start_date, $end_date);
    }

    // 財務項目の条件での絞り込み
    if (!empty($financial_metrics) && is_array($financial_metrics)) {
        $conditions = [];

        for ($i = 0; $i < count($financial_metrics); $i++) {
            $field = sanitize_text_field($financial_metrics[$i]);
            $operator = sanitize_text_field($operators[$i]);
            $value = sanitize_text_field($metric_values[$i]);

            // 演算子チェック
            $allowed_operators = ['=', '>', '<', '>=', '<=', '!='];
            if (!empty($field) && in_array($operator, $allowed_operators) && is_numeric($value)) {
                $conditions[] = $wpdb->prepare("{$field} {$operator} %f", $value);
            }
        }

        if (!empty($conditions)) {
            // ユーザーが選択した条件で結合 ("AND" または "OR")
            $query .= " AND (" . implode(" {$condition_type} ", $conditions) . ")";
        }
    }

    // クエリの実行
    $results = $wpdb->get_results($query, ARRAY_A);

    // 単位を100万にする項目
    $million_fields = array(
        'sales', 'gross_profit_loss', 'operating_income_loss', 'ordinary_income_loss', 'net_income_loss',
        'parent_net_income_loss', 'operating_cf', 'investing_cf', 'financing_cf', 'assets', 'liabilities',
        'current_assets', 'current_liabilities', 'net_assets', 'shareholders_equity', 'retained_earnings',
        'short_term_debt', 'long_term_debt', 'corporate_taxes', 'sg_and_a', 'depreciation',
        'interest_income_dividends', 'interest_expenses', 'shares_outstanding'
    );

    // 結果をループして、該当するフィールドの値を変換
    foreach ($results as &$row) {
        foreach ($million_fields as $field) {
            if (isset($row[$field]) && is_numeric($row[$field])) {
                $row[$field] = $row[$field] / 1000000; // 100万で割る
            }
        }
    }
    // 結果から'id'列を削除
    foreach ($results as &$row) {
        if (isset($row['id'])) {
            unset($row['id']);
        }
    }   

    wp_send_json_success($results);
}

add_action('wp_ajax_fetch_financial_data', 'fetch_financial_data');
add_action('wp_ajax_nopriv_fetch_financial_data', 'fetch_financial_data');

これのテンプレートを固定ページに適用することで、検索、ダウンロードできるページを作成できます。

コメント

タイトルとURLをコピーしました