input('period', 'month'); // month | all $type = $request->input('type', 'all'); // task(<99) | hard(>99) | all $status = $request->input('status', 'untreated'); // untreated(=1) | inprogress(=2) | done(=3) | all $q = DB::table('operator_que as oq') ->leftJoin('user as u', 'oq.user_id', '=', 'u.user_id') ->leftJoin('park as p', 'oq.park_id', '=', 'p.park_id') // オペレータマスタ(テーブル・カラム名は環境に合わせて調整) ->leftJoin('ope as o', 'oq.operator_id', '=', 'o.ope_id') ->select( 'oq.que_id','oq.que_class','oq.user_id', DB::raw('u.user_name as user_name'), 'oq.contract_id','oq.park_id', DB::raw('p.park_name as park_name'), 'oq.que_comment','oq.que_status','oq.que_status_comment', 'oq.work_instructions','oq.created_at','oq.updated_at','oq.operator_id', DB::raw('o.ope_name as operator_name') ); // 期間: 登録日ベース(最新1ヵ月 or 全期間) if ($period === 'month') { $q->where('oq.created_at', '>=', now()->subMonth()); } // 種別: que_class if ($type === 'task') { $q->where('oq.que_class', '<', 99); } elseif ($type === 'hard') { $q->where('oq.que_class', '>', 99); } // all は絞り込みなし // ステータス: que_status if ($status === 'untreated') { $q->where('oq.que_status', 1); } elseif ($status === 'inprogress') { $q->where('oq.que_status', 2); } elseif ($status === 'done') { $q->where('oq.que_status', 3); } // all は絞り込みなし $jobs = $q->orderBy('oq.que_id')->paginate(20)->appends($request->query()); return view('admin.information.list', compact('jobs','period','type','status')); } // ダッシュボード表示 public function dashboard(Request $request) { // ダッシュボード統計情報を集計 // 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(); $totalCapacityValue = ($totalCapacity->std_sum ?? 0) + ($totalCapacity->num_sum ?? 0) + ($totalCapacity->limit_sum ?? 0); // 予約待ち人数(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(), 'total_contracts' => DB::table('regular_contract')->count(), 'total_users' => DB::table('user')->count(), 'total_devices' => DB::table('device')->count(), 'today_queues' => DB::table('operator_que') ->whereDate('created_at', today()) ->count(), 'total_waiting' => $totalWaiting, 'total_capacity' => $totalCapacityValue, 'total_utilization_rate' => $utilizationRate, 'total_vacant_number' => $totalVacant, 'total_waiting_rate' => $totalWaitingRate, ]; // 自治体別統計情報を作成 $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, ]; } // グラフ用データ: city_name と utilization_rate, waiting_count のみ $cityStatsChart = []; foreach ($cities as $city) { $parkIds = DB::table('park') ->where('city_id', $city->city_id) ->pluck('park_id') ->toArray(); // park_standard と park_number の合計 $parkNumberStats = DB::table('park_number') ->whereIn('park_id', $parkIds) ->selectRaw('COALESCE(SUM(park_standard), 0) as total_standard, COALESCE(SUM(park_number), 0) as total_number') ->first(); $parkStandard = $parkNumberStats->total_standard ?? 0; $parkNumber = $parkNumberStats->total_number ?? 0; // 利用率計算:floor((park_number / park_standard) * 100) $utilizationRate = $parkStandard > 0 ? (int) floor(($parkNumber / $parkStandard) * 100) : 0; // 予約待ち人数 $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(); } $cityStatsChart[] = [ 'city_id' => $city->city_id, 'city_name' => $city->city_name, 'utilization_rate' => $utilizationRate, 'waiting_count' => $waitingCount, ]; } return view('admin.information.dashboard', compact('totalStats', 'cityStats', 'cityStatsChart')); } // ステータス一括更新(着手=2 / 対応完了=3) public function updateStatus(Request $request) { $request->validate([ 'ids' => 'required|array', 'action' => 'required|in:inprogress,done', ]); $new = $request->action === 'inprogress' ? 2 : 3; DB::table('operator_que') ->whereIn('que_id', $request->ids) ->update([ 'que_status' => $new, 'updated_at' => now(), ]); return back()->with('success', '選択したキューのステータスを更新しました。'); } }