diff --git a/app/Http/Controllers/ParkDetailController.php b/app/Http/Controllers/ParkDetailController.php
index 371802e..197f14f 100644
--- a/app/Http/Controllers/ParkDetailController.php
+++ b/app/Http/Controllers/ParkDetailController.php
@@ -2,35 +2,165 @@
namespace App\Http\Controllers;
+use App\Http\Traits\AuthenticatesUser;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class ParkDetailController extends Controller
{
- public function show($park_id)
+ use AuthenticatesUser;
+
+ /**
+ * 駐輪場詳細情報を取得(Ajax用)
+ *
+ * @param string $management_code
+ * @param int $park_id
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function show($management_code, $park_id)
{
+ if ($redirect = $this->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);
+ }
+
$park = DB::table('park')->where('park_id', $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_id)
- ->get();
+ // ゾーン情報取得
+ $zones = $this->getZones($park_id);
- $reserves = 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', $park_id)
- ->where('reserve.valid_flag', 1)
- ->select('reserve.*', 'ptype.ptype_subject')
- ->get();
+ // 予約情報取得
+ $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;
+ }
+ }
+
+ /**
+ * zone情報取得
+ *
+ * @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();
+ }
+ }
+
+ /**
+ * reserve情報取得
+ *
+ * @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();
+ }
+ }
+
+ /**
+ * 車種別の基準台数集計
+ *
+ * なぜ: 駐輪場の車種別定員を把握するため
+ *
+ * @param \Illuminate\Support\Collection $zones
+ * @return array
+ */
+ private function calculateZoneStandardSum($zones)
+ {
$zoneStandardSum = [];
+
foreach ($zones as $zone) {
$key = $zone->psection_subject; // 「自転車」「原付」など
if (!isset($zoneStandardSum[$key])) {
@@ -39,14 +169,28 @@ class ParkDetailController extends Controller
$zoneStandardSum[$key] += $zone->zone_standard;
}
- // 空き台数集計用配列
+ return $zoneStandardSum;
+ }
+
+ /**
+ * 車種別の空き台数計算
+ *
+ * なぜ: reserve(予約)を考慮した実際の空き台数を算出するため
+ *
+ * @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;
}
- // zone_tolerance - zone_number を合計
$vacancyData[$key] += ($zone->zone_tolerance - $zone->zone_number);
}
@@ -54,23 +198,38 @@ class ParkDetailController extends Controller
foreach ($reserves as $reserve) {
$key = $reserve->psection_id . '_' . $reserve->ptype_subject;
if (isset($vacancyData[$key])) {
- $vacancyData[$key] -= 1; // 1件分減算
+ $vacancyData[$key] -= 1;
}
}
- // 更新期間取得
- $city_grace_periods = DB::table('city')
- ->select('city_id', 'update_grace_period_start_date', 'update_grace_period_start_time', 'update_grace_period_end_date', 'update_grace_period_end_time')
- ->whereIn('city_id', function ($query) {
- $query->select('city_id')->from('park');
- })
- ->get()
- ->keyBy('city_id');
+ return $vacancyData;
+ }
- // 必要なら他テーブルJOINや追加情報も取得
- return response()->json([
- 'html' => view('regular_contract.park_detail', compact('park', 'zones', 'reserves', 'zoneStandardSum', 'vacancyData', 'city_grace_periods'))->render()
- ]);
+ /**
+ * 駐輪場の更新可能期間取得
+ *
+ * なぜ: 更新可能期間がcityテーブルからparkテーブルに変更されたため
+ *
+ * @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)
diff --git a/app/Http/Controllers/RegularContractCreateController.php b/app/Http/Controllers/RegularContractCreateController.php
index a28b05b..24ca40f 100644
--- a/app/Http/Controllers/RegularContractCreateController.php
+++ b/app/Http/Controllers/RegularContractCreateController.php
@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
+use App\Http\Traits\AuthenticatesUser;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
@@ -16,150 +17,440 @@ use Carbon\Carbon;
class RegularContractCreateController extends Controller
{
- // 新規作成画面表示
+ use AuthenticatesUser;
+
+ /**
+ * 駐輪場選択画面を表示
+ *
+ * @param Request $request
+ * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
+ */
public function show(Request $request)
{
- $user_id = session('user_id');
- if (!$user_id) {
- return redirect('/login');
+ if ($redirect = $this->handleSessionExpired('駐輪場選択')) {
+ return $redirect;
}
- $user = DB::table('user')->where('user_id', $user_id)->first();
- // 市町村名(park→city JOINで重複排除)
- $cities = DB::table('park')
- ->join('city', 'park.city_id', '=', 'city.city_id')
- ->select('city.city_id', 'city.city_name')
- ->distinct()
- ->get();
+ $userId = session('user_id');
+ $user = DB::table('user')->where('user_id', $userId)->first();
- // city_idごとの更新可能期間情報を取得
- $city_grace_periods = DB::table('city')
- ->select('city_id', 'update_grace_period_start_date', 'update_grace_period_start_time', 'update_grace_period_end_date', 'update_grace_period_end_time')
- ->get()
- ->keyBy('city_id');
+ // マルチテナント対応のため、運営元情報をセッションから取得
+ $management = session('management');
+ if (!$management) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 運営元情報が見つからない user_id=" . $userId);
+ abort(404);
+ }
- // 駅名(stationテーブルのstation_neighbor_station全件)
- $stations = DB::table('station')
- ->select('station_neighbor_station')
- ->distinct()
- ->get();
-
- // 駐輪場名(parkテーブルのpark_name全件)
- $parks = DB::table('park')
- ->select('park_id', 'park_name')
- ->distinct()
- ->get();
-
- // テーブル表示用データ(park/city/station JOIN, park_id昇順, 10件ずつページング)
- $page = request()->input('page', 1);
+ // 検索条件・ページング取得
+ $cityId = $request->input('city_id');
+ $stationName = $request->input('station_neighbor_station');
+ $parkId = $request->input('park_id');
+ $page = (int)$request->input('page', 1);
$perPage = 10;
- $city_id = request()->input('city_id');
- $station_name = request()->input('station_neighbor_station');
- $park_id = request()->input('park_id');
- $query = DB::table('park')
- ->join('city', 'park.city_id', '=', 'city.city_id')
- ->leftJoin('station', 'park.park_id', '=', 'station.park_id')
- ->select(
- 'park.park_id',
- 'park.park_name',
- 'park.park_ruby',
- 'city.city_name',
- 'city.city_id',
- 'station.station_neighbor_station',
- 'station.station_name_ruby'
- );
-
- if ($city_id) {
- $query->where('city.city_id', $city_id);
- }
- if ($station_name) {
- $query->where('station.station_neighbor_station', $station_name);
- }
- if ($park_id) {
- $query->where('park.park_id', $park_id);
- }
-
- // 並び替えパラメータ取得
- $sort = request()->input('sort', 'park_id');
- $order = request()->input('order', 'asc');
- $sortable = [
- 'park_ruby' => 'park.park_ruby',
- 'city_id' => 'city.city_id',
- 'station_name_ruby' => 'station.station_name_ruby',
- 'park_id' => 'park.park_id',
- ];
- $query->orderBy('park.park_id', 'asc');
-
- $total = $query->count();
- $parks_table = $query->get();
-
- if ($sort === 'park_ruby' || $sort === 'station_name_ruby') {
- $collator = new \Collator('ja');
- $parks_table = $parks_table->sort(function ($a, $b) use ($order, $sort, $collator) {
- $a_val = $a->$sort ?? '';
- $b_val = $b->$sort ?? '';
- return $order === 'asc'
- ? $collator->compare($a_val, $b_val)
- : $collator->compare($b_val, $a_val);
- })->values();
- } else {
- // park_id, city_idなどはSQLのorderByで十分なので、ここでPHPソートは不要
- if (isset($sortable[$sort])) {
- $parks_table = $parks_table->sortBy($sort, SORT_REGULAR, $order === 'desc')->values();
- }
- }
- $page = request()->input('page', 1);
- $perPage = 10;
- $parks_table = $parks_table->slice(($page - 1) * $perPage, $perPage)->values();
-
- // zoneテーブルデータを取得(psectionテーブルとJOINしてpsection_subjectも取得)
- $zones = DB::table('zone')
- ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id')
- ->select('zone.zone_id', 'zone.park_id', 'zone.ptype_id', 'zone.psection_id', 'zone.zone_number', 'zone.zone_tolerance', 'psection.psection_subject')
- ->get()
- ->groupBy('park_id');
-
- // 空き予約マスタデータを取得
- $reserve = DB::table('reserve')
- ->select('reserve_id', 'park_id', 'ptype_id', 'psection_id')
- ->where('valid_flag', 1)
- ->get()
- ->groupBy('park_id');
-
- // ルート名で画面表示を切り替え(新規定期契約 or 駐輪場検索)
+ // ルート名で画面種別判定(新規定期契約 or 駐輪場検索)
$isRegularContract = $request->route()->getName() === 'regular_contract.create';
+ $activeMenu = $isRegularContract ? 'SWC-8-1' : 'SWC-10-1';
- // ヘッダーの選択状態を分岐
- $active_menu = $isRegularContract ? 'SWC-8-1' : 'SWC-10-1';
+ // マスタデータ取得(運営元フィルタ適用)
+ $cities = $this->getCitiesList($management->management_id);
+ $stations = $this->getStationsList($management->management_id);
+ $parks = $this->getParksList($management->management_id);
- if ($isRegularContract) {
- \Log::info('新規定期契約-駐輪場選択画面にアクセス', [
- 'user_id' => $user_id,
- ]);
- } else {
- \Log::info('駐輪場検索-駐輪場選択画面にアクセス', [
- 'user_id' => $user_id,
- ]);
- }
+ // 運営元が取り扱う車種一覧を取得
+ $availableVehicles = $this->getAvailableVehicles($management->management_id);
+
+ // 駐輪場テーブルデータ取得(検索・ページング、運営元フィルタ適用)
+ $parksTableData = $this->getParksTableData($management->management_id, $cityId, $stationName, $parkId, $page, $perPage);
+
+ // zone・reserve マスタ取得(運営元フィルタ適用)
+ $zones = $this->getZonesByParkId($management->management_id);
+ $reserve = $this->getReserveByParkId($management->management_id);
+
+ // 駐輪場ごとの更新可能期間取得(運営元フィルタ適用)
+ $parkGracePeriods = $this->getParkGracePeriods($management->management_id);
+
+ // 駐輪場ごと・車種ごとの状態を事前計算
+ $parksTableData['data'] = $this->calculateVehicleStatus(
+ $parksTableData['data'],
+ $zones,
+ $reserve,
+ $parkGracePeriods,
+ $availableVehicles
+ );
+
+ // アクセスログ記録
+ $screenName = $isRegularContract ? '新規定期契約登録' : '駐輪場検索';
+ \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " {$screenName}画面アクセス user_id=" . $userId . ", management_id=" . $management->management_id . ", city_id=" . ($cityId ?? 'null') . ", park_id=" . ($parkId ?? 'null'));
return view('regular_contract.create', [
- 'active_menu' => $active_menu, // この画面のID
- 'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
+ 'active_menu' => $activeMenu,
+ 'user_name' => $user->user_name ?? '',
'cities' => $cities,
'stations' => $stations,
'parks' => $parks,
- 'parks_table' => $parks_table,
- 'parks_table_total' => $total,
+ 'parks_table' => $parksTableData['data'],
+ 'parks_table_total' => $parksTableData['total'],
'parks_table_page' => $page,
'parks_table_perPage' => $perPage,
- 'zones' => $zones,
- 'city_grace_periods' => $city_grace_periods,
+ 'available_vehicles' => $availableVehicles,
'reserve' => $reserve,
- 'isRegularContract' => $isRegularContract
+ 'isRegularContract' => $isRegularContract,
]);
}
+ /**
+ * 市町村一覧取得(運営元フィルタ適用)
+ * マルチテナント対応のため、運営元に紐づく駐輪場の市町村のみ表示
+ *
+ * @param int $managementId
+ * @return \Illuminate\Support\Collection
+ */
+ private function getCitiesList(int $managementId)
+ {
+ try {
+ return DB::table('park')
+ ->join('city', 'park.city_id', '=', 'city.city_id')
+ ->where('park.management_id', $managementId)
+ ->select('city.city_id', 'city.city_name')
+ ->distinct()
+ ->get();
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 市町村一覧取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return collect();
+ }
+ }
+
+ /**
+ * 駅名一覧取得(運営元フィルタ適用)
+ *
+ * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場の駅のみ表示
+ *
+ * @param int $managementId
+ * @return \Illuminate\Support\Collection
+ */
+ private function getStationsList(int $managementId)
+ {
+ try {
+ return DB::table('station')
+ ->join('park', 'station.park_id', '=', 'park.park_id')
+ ->where('park.management_id', $managementId)
+ ->select('station.station_neighbor_station')
+ ->distinct()
+ ->get();
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駅名一覧取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return collect();
+ }
+ }
+
+ /**
+ * 駐輪場名一覧取得(運営元フィルタ適用)
+ *
+ * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のみ表示
+ *
+ * @param int $managementId
+ * @return \Illuminate\Support\Collection
+ */
+ private function getParksList(int $managementId)
+ {
+ try {
+ return DB::table('park')
+ ->where('management_id', $managementId)
+ ->select('park_id', 'park_name')
+ ->distinct()
+ ->get();
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場名一覧取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return collect();
+ }
+ }
+
+ /**
+ * 駐輪場テーブルデータ取得(検索・ページング適用、運営元フィルタ適用)
+ *
+ * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のみ表示
+ *
+ * @param int $managementId
+ * @param int|null $cityId
+ * @param string|null $stationName
+ * @param int|null $parkId
+ * @param int $page
+ * @param int $perPage
+ * @return array ['data' => Collection, 'total' => int]
+ */
+ private function getParksTableData(int $managementId, $cityId, $stationName, $parkId, int $page, int $perPage)
+ {
+ try {
+ $query = DB::table('park')
+ ->join('city', 'park.city_id', '=', 'city.city_id')
+ ->leftJoin('station', 'park.park_id', '=', 'station.park_id')
+ ->where('park.management_id', $managementId)
+ ->select(
+ 'park.park_id',
+ 'park.park_name',
+ 'park.park_ruby',
+ 'city.city_name',
+ 'city.city_id',
+ 'station.station_neighbor_station',
+ 'station.station_name_ruby'
+ );
+
+ // 検索条件適用
+ if ($cityId) {
+ $query->where('city.city_id', $cityId);
+ }
+ if ($stationName) {
+ $query->where('station.station_neighbor_station', $stationName);
+ }
+ if ($parkId) {
+ $query->where('park.park_id', $parkId);
+ }
+
+ $query->orderBy('park.park_id', 'asc');
+ $total = $query->count();
+
+ // ページング適用
+ $data = $query
+ ->skip(($page - 1) * $perPage)
+ ->take($perPage)
+ ->get();
+
+ \Log::info("[INFO] " . now()->format('Y-m-d H:i:s') . " 駐輪場データ取得成功 management_id=" . $managementId . ", 件数=" . $total . ", page=" . $page);
+
+ return [
+ 'data' => $data,
+ 'total' => $total,
+ ];
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場テーブルデータ取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return [
+ 'data' => collect(),
+ 'total' => 0,
+ ];
+ }
+ }
+
+ /**
+ * zone データを park_id でグループ化して取得(運営元フィルタ適用)
+ *
+ * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のzoneのみ取得
+ *
+ * @param int $managementId
+ * @return \Illuminate\Support\Collection
+ */
+ private function getZonesByParkId(int $managementId)
+ {
+ try {
+ return DB::table('zone')
+ ->join('park', 'zone.park_id', '=', 'park.park_id')
+ ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id')
+ ->where('park.management_id', $managementId)
+ ->select(
+ 'zone.zone_id',
+ 'zone.park_id',
+ 'zone.ptype_id',
+ 'zone.psection_id',
+ 'zone.zone_number',
+ 'zone.zone_tolerance',
+ 'psection.psection_subject'
+ )
+ ->get()
+ ->groupBy('park_id');
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " zone データ取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return collect();
+ }
+ }
+
+ /**
+ * reserve データを park_id でグループ化して取得(運営元フィルタ適用)
+ *
+ * なぜ: マルチテナント対応のため、運営元に紐づく駐輪場のreserveのみ取得
+ *
+ * @param int $managementId
+ * @return \Illuminate\Support\Collection
+ */
+ private function getReserveByParkId(int $managementId)
+ {
+ try {
+ return DB::table('reserve')
+ ->join('park', 'reserve.park_id', '=', 'park.park_id')
+ ->where('park.management_id', $managementId)
+ ->where('reserve.valid_flag', 1)
+ ->select('reserve.reserve_id', 'reserve.park_id', 'reserve.ptype_id', 'reserve.psection_id')
+ ->get()
+ ->groupBy('park_id');
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " reserve データ取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return collect();
+ }
+ }
+
+ /**
+ * 駐輪場ごとの更新可能期間を取得(運営元フィルタ適用)
+ *
+ * なぜ: 更新可能期間がcityテーブルからparkテーブルに変更されたため
+ *
+ * @param int $managementId
+ * @return \Illuminate\Support\Collection
+ */
+ private function getParkGracePeriods(int $managementId)
+ {
+ try {
+ return DB::table('park')
+ ->where('management_id', $managementId)
+ ->select(
+ 'park_id',
+ 'update_grace_period_start_date',
+ 'update_grace_period_start_time',
+ 'update_grace_period_end_date',
+ 'update_grace_period_end_time'
+ )
+ ->get()
+ ->keyBy('park_id');
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 駐輪場更新可能期間取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return collect();
+ }
+ }
+
+ /**
+ * 運営元が取り扱う車種一覧を取得
+ *
+ * なぜ: マルチテナント対応のため、運営元ごとに表示する車種を可変にする
+ *
+ * @param int $managementId
+ * @return array
+ */
+ private function getAvailableVehicles(int $managementId)
+ {
+ try {
+ return DB::table('zone')
+ ->join('park', 'zone.park_id', '=', 'park.park_id')
+ ->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id')
+ ->where('park.management_id', $managementId)
+ ->whereNotNull('psection.psection_subject')
+ ->select('psection.psection_subject')
+ ->distinct()
+ ->orderByRaw("FIELD(psection.psection_subject, '自転車', '原付', '自動二輪', '自動車')")
+ ->pluck('psection_subject')
+ ->toArray();
+ } catch (\Exception $e) {
+ \Log::error("[ERROR] " . now()->format('Y-m-d H:i:s') . " 取り扱い車種取得失敗 management_id=" . $managementId . ", error=" . $e->getMessage());
+ return [];
+ }
+ }
+
+ /**
+ * 駐輪場ごと・車種ごとの状態を計算
+ *
+ * なぜ: Bladeでのビジネスロジック実行を避け、Fat Controller回避のため事前計算
+ *
+ * @param \Illuminate\Support\Collection $parks
+ * @param \Illuminate\Support\Collection $zones
+ * @param \Illuminate\Support\Collection $reserve
+ * @param \Illuminate\Support\Collection $parkGracePeriods
+ * @param array $availableVehicles
+ * @return \Illuminate\Support\Collection
+ */
+ private function calculateVehicleStatus($parks, $zones, $reserve, $parkGracePeriods, $availableVehicles)
+ {
+ $now = \Carbon\Carbon::now();
+
+ foreach ($parks as $park) {
+ $park->vehicle_status = [];
+
+ foreach ($availableVehicles as $vehicle) {
+ $zonesForType = ($zones[$park->park_id] ?? collect())->where('psection_subject', $vehicle);
+
+ // 空き台数計算
+ $hasVacancy = false;
+ foreach ($zonesForType as $zone) {
+ $reserveCount = ($reserve[$park->park_id] ?? collect())
+ ->where('psection_id', $zone->psection_id)
+ ->where('ptype_id', $zone->ptype_id)
+ ->count();
+ $vacancy = $zone->zone_tolerance - $zone->zone_number - $reserveCount;
+ if ($vacancy > 0) {
+ $hasVacancy = true;
+ break;
+ }
+ }
+
+ // 猶予期間判定
+ $grace = $parkGracePeriods[$park->park_id] ?? null;
+ $inGrace = $this->isInGracePeriod($grace, $now);
+
+ // 状態判定
+ if ($zonesForType->isEmpty() || !$grace) {
+ $park->vehicle_status[$vehicle] = 'none';
+ } elseif ($inGrace && $hasVacancy) {
+ $park->vehicle_status[$vehicle] = 'available';
+ } elseif (!$inGrace) {
+ $park->vehicle_status[$vehicle] = 'out_of_period';
+ } elseif (!$hasVacancy) {
+ $park->vehicle_status[$vehicle] = 'waiting';
+ } else {
+ $park->vehicle_status[$vehicle] = 'none';
+ }
+ }
+ }
+
+ return $parks;
+ }
+
+ /**
+ * 猶予期間内かどうかを判定
+ *
+ * なぜ: 複雑な日時判定ロジックを分離し、可読性向上
+ *
+ * @param object|null $grace
+ * @param \Carbon\Carbon $now
+ * @return bool
+ */
+ private function isInGracePeriod($grace, $now)
+ {
+ if (!$grace) {
+ return false;
+ }
+
+ // バリデーション
+ if (
+ !is_numeric($grace->update_grace_period_start_date) ||
+ !preg_match('/^\d{1,2}:\d{2}$/', $grace->update_grace_period_start_time) ||
+ !is_numeric($grace->update_grace_period_end_date) ||
+ !preg_match('/^\d{1,2}:\d{2}$/', $grace->update_grace_period_end_time)
+ ) {
+ return false;
+ }
+
+ $year = $now->year;
+ $month = $now->month;
+ $startDay = (int)$grace->update_grace_period_start_date;
+ $endDay = (int)$grace->update_grace_period_end_date;
+
+ if ($startDay > $endDay) {
+ // 月またぎ(例: 25日~5日)
+ $prevMonth = $now->copy()->subMonth();
+ $startPrev = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $prevMonth->year, $prevMonth->month, $startDay, $grace->update_grace_period_start_time));
+ $endCurr = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $endDay, $grace->update_grace_period_end_time));
+
+ $startCurr = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $grace->update_grace_period_start_time));
+ $nextMonth = $month == 12 ? 1 : $month + 1;
+ $nextYear = $month == 12 ? $year + 1 : $year;
+ $endNext = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $nextYear, $nextMonth, $endDay, $grace->update_grace_period_end_time));
+
+ return $now->between($startPrev, $endCurr) || $now->between($startCurr, $endNext);
+ } else {
+ // 同月(例: 5日~25日)
+ $start = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $grace->update_grace_period_start_time));
+ $end = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $endDay, $grace->update_grace_period_end_time));
+
+ return $now->between($start, $end);
+ }
+ }
+
public function regulationCheck(Request $request)
{
// GETパラメータを取得
diff --git a/public/assets/css/mypage/tablesorter-blue.css b/public/assets/css/mypage/tablesorter-blue.css
index 1290f8d..9b41940 100644
--- a/public/assets/css/mypage/tablesorter-blue.css
+++ b/public/assets/css/mypage/tablesorter-blue.css
@@ -17,7 +17,6 @@ table.tablesorter tfoot tr th {
}
table.tablesorter thead tr .header {
- background-image: url(bg.gif);
background-repeat: no-repeat;
background-position: center right;
cursor: pointer;
@@ -34,14 +33,6 @@ table.tablesorter tbody tr.odd td {
background-color: #F0F0F6;
}
-table.tablesorter thead tr .headerSortUp {
- background-image: url(asc.gif);
-}
-
-table.tablesorter thead tr .headerSortDown {
- background-image: url(desc.gif);
-}
-
table.tablesorter thead tr .headerSortDown,
table.tablesorter thead tr .headerSortUp {
background-color: #8dbdd8;
diff --git a/public/assets/js/commons.js b/public/assets/js/commons.js
index 97c3d3e..8e98edb 100644
--- a/public/assets/js/commons.js
+++ b/public/assets/js/commons.js
@@ -115,17 +115,26 @@ $(function () {
});
// 駐輪場詳細ポップアップ
-$(document).on('click', '.btn-popup', function () {
- var parkId = $(this).data('park-id');
+$(document).on('click', '.btn-popup', function (e) {
+ e.preventDefault();
+ const url = $(this).data('url');
+
+ // data-url が存在しない場合はエラー
+ if (!url) {
+ console.error('data-url属性が設定されていません');
+ alert('URLが取得できませんでした。');
+ return;
+ }
+
$.ajax({
- url: '/api/park-detail/' + parkId,
+ url: url,
method: 'GET',
- success: function (data) {
- $('#modalArea').html(data.html); // モーダル全体を挿入
- $('#parkDetailModal').modal('show'); // モーダルを表示
+ success: function (response) {
+ $('#modalArea').html(response.html);
+ $('#popup-modal').modal('show');
},
error: function () {
- alert('詳細情報の取得に失敗しました');
+ alert('詳細情報の取得に失敗しました。');
}
});
});
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
index de412e7..9c6e1cd 100644
--- a/resources/views/layouts/app.blade.php
+++ b/resources/views/layouts/app.blade.php
@@ -10,7 +10,7 @@
-
+
@@ -18,6 +18,7 @@
+
@yield('head')