財務データを検索
今回は以下のような財務データ全体を条件検索できるページを作成したいと思います。
機能としては、
- 企業名やコードによる検索
- 提出日による検索
- 財務項目データ条件による検索
- 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');
これのテンプレートを固定ページに適用することで、検索、ダウンロードできるページを作成できます。