【ダッシュボード】初版作成
All checks were successful
Deploy main / deploy (push) Successful in 22s

This commit is contained in:
OU.ZAIKOU 2026-02-03 01:00:23 +09:00
parent 3188276fe5
commit 592c12c152
4 changed files with 241 additions and 23 deletions

View File

@ -217,13 +217,34 @@ class CityController extends Controller
->toArray();
$usersCount = count(array_filter($userIds));
// 予約待ち人数を取得valid_flag = 1 かつ reserve_order が設定されているもの)
$waitingCount = DB::table('reserve')
// 予約待ち人数を取得
// 条件:有効(valid_flag=1) かつ契約化されていない(contract_id IS NULL)
// キャンセル除外reserve_cancel_flag が NULL または 0、かつ reserve_cancelday が NULL
$waitingQuery = DB::table('reserve')
->whereIn('park_id', $parkIds)
->where('valid_flag', 1)
->whereNotNull('reserve_order')
->where('reserve_order', '>', 0)
->count();
->whereNull('contract_id');
// キャンセルフラグの有無をチェック(列が存在するかどうか)
try {
DB::table('reserve')
->select(DB::raw('1'))
->whereNotNull('reserve_cancel_flag')
->limit(1)
->first();
// 列が存在する場合、キャンセル除外条件を追加
$waitingQuery = $waitingQuery
->where(function ($q) {
$q->whereNull('reserve_cancel_flag')
->orWhere('reserve_cancel_flag', 0);
})
->whereNull('reserve_cancelday');
} catch (\Exception $e) {
// キャンセルフラグが未運用の場合は基本条件のみで計算
}
$waitingCount = $waitingQuery->count();
}
$stats = [

View File

@ -61,22 +61,68 @@ class InformationController extends Controller
{
// ダッシュボード統計情報を集計
// 駐輪場の総収容台数
$totalCapacity = DB::table('park')
->sum('park_capacity') ?? 0;
// park_number テーブルから総容量を計算
// park_standard標準 + park_number割当+ park_limit制限値の合算
$totalCapacity = DB::table('park_number')
->selectRaw('
COALESCE(SUM(park_standard), 0) as std_sum,
COALESCE(SUM(park_number), 0) as num_sum,
COALESCE(SUM(park_limit), 0) as limit_sum
')
->first();
// 予約待ち人数regular_contractで状態チェック
$totalWaiting = DB::table('regular_contract')
->whereIn('rc_status', [1]) // 待機中など
->count();
$totalCapacityValue = ($totalCapacity->std_sum ?? 0) +
($totalCapacity->num_sum ?? 0) +
($totalCapacity->limit_sum ?? 0);
// 利用率計算(使用中台数 / 総容量)
$utilizationRate = $totalCapacity > 0
? round((DB::table('park')
->where('park_status', 1)
->sum('park_capacity') ?? 0) / $totalCapacity * 100)
// 予約待ち人数reserve テーブルから集計)
// 条件:有効(valid_flag=1) かつ契約化されていない(contract_id IS NULL)
// キャンセル除外reserve_cancel_flag が NULL または 0、かつ reserve_cancelday が NULL
$reserveQuery = DB::table('reserve')
->where('valid_flag', 1)
->whereNull('contract_id');
// キャンセルフラグの有無をチェック(列が存在するかどうか)
try {
$testResult = DB::table('reserve')
->select(DB::raw('1'))
->whereNotNull('reserve_cancel_flag')
->limit(1)
->first();
// 列が存在する場合、キャンセル除外条件を追加
$reserveQuery = $reserveQuery
->where(function ($q) {
$q->whereNull('reserve_cancel_flag')
->orWhere('reserve_cancel_flag', 0);
})
->whereNull('reserve_cancelday');
} catch (\Exception $e) {
// キャンセルフラグが未運用の場合は基本条件のみで計算
}
$totalWaiting = $reserveQuery->count();
// 使用中台数park_number の park_number が使用台数)
$totalUsed = DB::table('park_number')
->sum('park_number') ?? 0;
// 空き台数 = 総容量 - 使用中台数
$totalVacant = max(0, $totalCapacityValue - $totalUsed);
// 利用率計算(小数点以下切捨て)
$utilizationRate = $totalCapacityValue > 0
? (int) floor(($totalUsed / $totalCapacityValue) * 100)
: 0;
// 予約待ち率超過時のみ、超過なしは0%
// 超過判定:待機人数 > 空き台数
$totalWaitingRate = 0;
if ($totalCapacityValue > 0 && $totalWaiting > 0 && $totalWaiting > $totalVacant) {
// 超過分 / 総容量 * 100分母チェック付き
$totalWaitingRate = (int) floor((($totalWaiting - $totalVacant) / $totalCapacityValue) * 100);
}
$totalStats = [
'total_cities' => DB::table('city')->count(),
'total_parks' => DB::table('park')->count(),
@ -87,11 +133,110 @@ class InformationController extends Controller
->whereDate('created_at', today())
->count(),
'total_waiting' => $totalWaiting,
'total_capacity' => $totalCapacity,
'total_capacity' => $totalCapacityValue,
'total_utilization_rate' => $utilizationRate,
'total_vacant_number' => $totalVacant,
'total_waiting_rate' => $totalWaitingRate,
];
return view('admin.information.dashboard', compact('totalStats'));
// 自治体別統計情報を作成
$cityStats = [];
$cities = DB::table('city')->get();
foreach ($cities as $city) {
// その自治体に属する駐輪場 ID を取得
$parkIds = DB::table('park')
->where('city_id', $city->city_id)
->pluck('park_id')
->toArray();
// ① 駐輪場数
$parksCount = count($parkIds);
// ② 総収容台数park_number テーブルの park_standard を合算)
$capacity = 0;
if (!empty($parkIds)) {
$capacityResult = DB::table('park_number')
->whereIn('park_id', $parkIds)
->sum('park_standard');
$capacity = $capacityResult ?? 0;
}
// ③ 契約台数contract_cancel_flag = 0 かつ有効期間内)
$contractsCount = 0;
if (!empty($parkIds)) {
$contractsCount = DB::table('regular_contract')
->whereIn('park_id', $parkIds)
->where('contract_cancel_flag', 0)
->where(function ($q) {
// 有効期間内:開始日 <= 今日 かつ 終了日 >= 今日
$q->where('contract_periods', '<=', now())
->where('contract_periode', '>=', now());
})
->count();
}
// ④ 利用率計算(小数点以下切捨て)
$utilizationRate = $capacity > 0
? (int) floor(($contractsCount / $capacity) * 100)
: 0;
// ⑤ 空き台数
$availableSpaces = max(0, $capacity - $contractsCount);
// ⑥ 予約待ち人数reserve テーブルで contract_id IS NULL かつ valid_flag = 1
$waitingCount = 0;
if (!empty($parkIds)) {
$waitingQuery = DB::table('reserve')
->whereIn('park_id', $parkIds)
->where('valid_flag', 1)
->whereNull('contract_id');
// キャンセルフラグの有無をチェック
try {
DB::table('reserve')
->select(DB::raw('1'))
->whereNotNull('reserve_cancel_flag')
->limit(1)
->first();
// 列が存在する場合、キャンセル除外条件を追加
$waitingQuery = $waitingQuery
->where(function ($q) {
$q->whereNull('reserve_cancel_flag')
->orWhere('reserve_cancel_flag', 0);
})
->whereNull('reserve_cancelday');
} catch (\Exception $e) {
// キャンセルフラグが未運用の場合は基本条件のみで計算
}
$waitingCount = $waitingQuery->count();
}
// ⑦ 利用者数(ユニークユーザー数)
$usersCount = 0;
if (!empty($parkIds)) {
$usersCount = DB::table('regular_contract')
->whereIn('park_id', $parkIds)
->distinct()
->count('user_id');
}
// 配列に追加
$cityStats[] = [
'city' => $city,
'parks_count' => $parksCount,
'contracts_count' => $contractsCount,
'users_count' => $usersCount,
'waiting_count' => $waitingCount,
'capacity' => $capacity,
'utilization_rate' => $utilizationRate,
'available_spaces' => $availableSpaces,
];
}
return view('admin.information.dashboard', compact('totalStats', 'cityStats'));
}
// ステータス一括更新(着手=2 / 対応完了=3

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckCityAccess
{
/**
* 自治体へのアクセス権限を確認するミドルウェア
*
* 将来的に以下の権限判定を追加予定:
* - ユーザーが指定自治体にアクセス権があるか確認
* - 権限がない場合は 403 Forbidden を返す
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, Closure $next): Response
{
// 現在の処理:権限判定なしで通す
// TODO: 将来的に以下の権限判定ロジックを追加
// $city_id = $request->route('city_id');
// $user = auth()->user();
// if (!$user->canAccessCity($city_id)) {
// return abort(403, '指定された自治体へのアクセス権がありません。');
// }
return $next($request);
}
}

View File

@ -89,7 +89,19 @@
<div class="col-lg-3 col-6">
<div class="small-box bg-purple">
<div class="inner">
<h3>{{ number_format($parks->sum('park_capacity') ?? 0) }}</h3>
@php
// park_number テーブルから指定駐輪場群の容量を集計
$parkIds = $parks->pluck('park_id')->toArray();
$parkNumberCapacity = 0;
if (!empty($parkIds)) {
$parkNumberData = \Illuminate\Support\Facades\DB::table('park_number')
->whereIn('park_id', $parkIds)
->selectRaw('COALESCE(SUM(park_standard), 0) + COALESCE(SUM(park_number), 0) + COALESCE(SUM(park_limit), 0) as total')
->first();
$parkNumberCapacity = $parkNumberData->total ?? 0;
}
@endphp
<h3>{{ number_format($parkNumberCapacity) }}</h3>
<p>総収容台数</p>
</div>
<div class="icon">
@ -105,7 +117,7 @@
<div class="small-box bg-teal">
<div class="inner">
@php
$totalCapacity = $parks->sum('park_capacity') ?? 0;
$totalCapacity = $parkNumberCapacity;
$utilizationRate = $totalCapacity > 0 ? round(($stats['contracts_count'] / $totalCapacity) * 100, 1) : 0;
@endphp
<h3>{{ $utilizationRate }}%</h3>
@ -123,7 +135,7 @@
<div class="col-lg-3 col-6">
<div class="small-box bg-secondary">
<div class="inner">
<h3>{{ number_format($totalCapacity - $stats['contracts_count']) }}</h3>
<h3>{{ number_format(max(0, $totalCapacity - $stats['contracts_count'])) }}</h3>
<p>空き台数</p>
</div>
<div class="icon">
@ -139,7 +151,13 @@
<div class="small-box bg-navy">
<div class="inner">
@php
$waitingRate = $stats['contracts_count'] > 0 ? round(($stats['waiting_count'] / $stats['contracts_count']) * 100, 1) : 0;
// 待機超過分 / 総容量で予約待ち率を計算超過なしは0%
// 分母チェック付き:総容量 > 0 の場合のみ計算
$totalVacant = max(0, $totalCapacity - $stats['contracts_count']);
$waitingRate = 0;
if ($totalCapacity > 0 && $stats['waiting_count'] > 0 && $stats['waiting_count'] > $totalVacant) {
$waitingRate = (int) floor((($stats['waiting_count'] - $totalVacant) / $totalCapacity) * 100);
}
@endphp
<h3>{{ $waitingRate }}%</h3>
<p>予約待ち率</p>