handleSessionExpired('駐輪場詳細', false)) { return $redirect; } $userId = session('user_id'); $management = session('management'); if (!$management) { \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 運営元情報取得失敗 user_id=" . $userId); return response()->json(['error' => 'Management not found'], 404); } // マルチテナント対応:運営元に紐づく駐輪場のみ取得 $park = $this->getParkInfo($park_id, $management->management_id); if (!$park) { \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場情報取得失敗 park_id=" . $park_id . ", management_id=" . $management->management_id . ", user_id=" . $userId); return response()->json(['error' => 'Park not found'], 404); } // ゾーン情報取得 $zones = $this->getZones($park_id); // 予約情報取得 $reserves = $this->getReserves($park_id); // 車種別の基準台数集計 $zoneStandardSum = $this->calculateZoneStandardSum($zones); // 車種別の空き台数計算 $vacancyData = $this->calculateVacancy($zones, $reserves); // 駐輪場ごとの更新可能期間取得 $parkGracePeriod = $this->getParkGracePeriod($park_id); \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " 駐輪場詳細取得成功 park_id=" . $park_id . ", management_id=" . $management->management_id . ", user_id=" . $userId); try { $html = view('regular_contract.park_detail', compact( 'park', 'zones', 'reserves', 'zoneStandardSum', 'vacancyData', 'parkGracePeriod' ))->render(); \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " ビュー生成成功 park_id=" . $park_id . ", HTML長=" . strlen($html)); return response()->json(['html' => $html]); } catch (\Exception $e) { \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " ビュー生成失敗 park_id=" . $park_id . ", error=" . $e->getMessage()); return response()->json(['error' => 'View rendering failed'], 500); } } /** * 駐輪場情報取得(マルチテナントフィルタ適用) * * 運営元に紐づく駐輪場のみを取得することで、他の運営元のデータが * 誤って表示されることを防ぐ。 * * @param int $parkId * @param int $managementId * @return object|null */ private function getParkInfo(int $parkId, int $managementId) { try { return DB::table('park') ->where('park_id', $parkId) ->where('management_id', $managementId) ->first(); } catch (\Exception $e) { \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場情報取得エラー park_id=" . $parkId . ", error=" . $e->getMessage()); return null; } } /** * ゾーン情報取得 * * @param int $parkId * @return \Illuminate\Support\Collection */ private function getZones(int $parkId) { try { return DB::table('zone') ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id') ->leftJoin('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id') ->select( 'zone.*', 'psection.psection_subject', 'ptype.ptype_subject' ) ->where('zone.park_id', $parkId) ->get(); } catch (\Exception $e) { \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " zone情報取得失敗 park_id=" . $parkId . ", error=" . $e->getMessage()); return collect(); } } /** * 予約情報取得 * * @param int $parkId * @return \Illuminate\Support\Collection */ private function getReserves(int $parkId) { try { return DB::table('reserve') ->join('zone', function ($join) { $join->on('reserve.psection_id', '=', 'zone.psection_id') ->on('reserve.ptype_id', '=', 'zone.ptype_id'); }) ->join('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id') ->where('reserve.park_id', $parkId) ->where('reserve.valid_flag', 1) ->select('reserve.*', 'ptype.ptype_subject') ->get(); } catch (\Exception $e) { \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " reserve情報取得失敗 park_id=" . $parkId . ", error=" . $e->getMessage()); return collect(); } } /** * 車種別の基準台数集計 * * 駐輪場の車種別定員(zone_standard の合計)を算出する。 * * @param \Illuminate\Support\Collection $zones * @return array */ private function calculateZoneStandardSum($zones) { $zoneStandardSum = []; foreach ($zones as $zone) { $key = $zone->psection_subject; // 「自転車」「原付」など if (!isset($zoneStandardSum[$key])) { $zoneStandardSum[$key] = 0; } $zoneStandardSum[$key] += $zone->zone_standard; } return $zoneStandardSum; } /** * 車種別の空き台数計算 * * 予約を考慮した実際の空き台数を算出する。 * 許容台数から現在台数を引き、さらに有効な予約件数分を減算することで、正確な空き状況を把握する。 * * @param \Illuminate\Support\Collection $zones * @param \Illuminate\Support\Collection $reserves * @return array */ private function calculateVacancy($zones, $reserves) { $vacancyData = []; // zone_tolerance - zone_number を集計 foreach ($zones as $zone) { $key = $zone->psection_id . '_' . $zone->ptype_subject; if (!isset($vacancyData[$key])) { $vacancyData[$key] = 0; } $vacancyData[$key] += ($zone->zone_tolerance - $zone->zone_number); } // reserve件数分を減算 foreach ($reserves as $reserve) { $key = $reserve->psection_id . '_' . $reserve->ptype_subject; if (isset($vacancyData[$key])) { $vacancyData[$key] -= 1; } } return $vacancyData; } /** * 駐輪場の更新可能期間取得 * * @param int $parkId * @return object|null */ private function getParkGracePeriod(int $parkId) { try { return DB::table('park') ->select( 'park_id', 'update_grace_period_start_date', 'update_grace_period_start_time', 'update_grace_period_end_date', 'update_grace_period_end_time' ) ->where('park_id', $parkId) ->first(); } catch (\Exception $e) { \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場更新可能期間取得失敗 park_id=" . $parkId . ", error=" . $e->getMessage()); return null; } } public function showWait($reserve_id) { $reserve = DB::table('reserve')->where('reserve_id', $reserve_id)->first(); $park = DB::table('park')->where('park_id', $reserve->park_id)->first(); $zones = DB::table('zone') ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id') ->leftJoin('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id') ->select('zone.*', 'psection.psection_subject', 'ptype.ptype_subject') ->where('zone.park_id', $park->park_id) ->get(); $zoneStandardSum = []; foreach ($zones as $zone) { $key = $zone->psection_subject; // 「自転車」「原付」など if (!isset($zoneStandardSum[$key])) { $zoneStandardSum[$key] = 0; } $zoneStandardSum[$key] += $zone->zone_standard; } // 必要なら他テーブルJOINや追加情報も取得 return response()->json([ 'html' => view('park_waitlist.park_detail', compact('park', 'zones', 'reserve', 'zoneStandardSum'))->render() ]); } }