Compare commits

...

2 Commits

Author SHA1 Message Date
8cf1d96eeb Merge pull request '空き待ち状況確認追加' (#24) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #24
2025-09-19 16:44:25 +09:00
ded82e0b8e 空き待ち状況確認追加 2025-09-19 16:42:32 +09:00
8 changed files with 418 additions and 75 deletions

View File

@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
use App\Mail\ReservationCancelledMail;
class ParkWaitlistController extends Controller
{
// 空き待ち状況一覧画面
public function index(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
// 空き待ち予約状況データ取得
$waitlists = DB::table('reserve')
->where('reserve.user_id', $user_id)
->where('reserve.valid_flag', 1)
->leftJoin('park', 'reserve.park_id', '=', 'park.park_id')
->leftJoin('station', 'park.park_id', '=', 'station.park_id')
->leftJoin('ptype', 'reserve.ptype_id', '=', 'ptype.ptype_id')
->leftJoin('psection', 'reserve.psection_id', '=', 'psection.psection_id')
->select(
'reserve.reserve_id',
'reserve.reserve_date',
'park.park_name',
'station.station_neighbor_station',
'ptype.ptype_subject',
'psection.psection_subject'
)
->orderBy('reserve.reserve_date', 'desc')
->get();
\Log::info('空き待ち状況確認画面にアクセス', [
'user_id' => $user_id,
]);
return view('park_waitlist.index', [
'active_menu' => 'SWC-11-1', // この画面のID
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
'waitlists' => $waitlists,
]);
}
public function check(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$park_id = $request->input('park_id');
$psection_id = $request->input('psection_id');
$ptype_id = $request->input('ptype_id');
$existingReservation = DB::table('reserve')
->where('user_id', $user_id)
->where('park_id', $park_id)
->where('psection_id', $psection_id)
->where('ptype_id', $ptype_id)
->where('valid_flag', 1)
->first();
if ($existingReservation) {
return response()->json(['status' => 'exists']);
} else {
return response()->json(['status' => 'ok']);
}
}
public function create(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
$park_id = $request->input('park_id');
$psection_id = $request->input('psection_id');
$ptype_id = $request->input('ptype_id');
// 予約順を決定
$reserve_order = DB::table('reserve')
->where('park_id', $park_id)
->where('psection_id', $psection_id)
->where('ptype_id', $ptype_id)
->where('valid_flag', 1)
->count() + 1;
// 予約情報を追加
DB::table('reserve')->insert([
'created_at' => now(),
'updated_at' => now(),
'user_categoryid' => $user->user_categoryid,
'user_id' => $user_id,
'park_id' => $park_id,
'psection_id' => $psection_id,
'reserve_date' => now(),
'valid_flag' => 1,
'ptype_id' => $ptype_id,
'reserve_order' => $reserve_order
]);
return redirect()->route('park_waitlist.index');
}
public function cancelConfirm($reserve_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
\Log::info('空き待ち状況確認 - キャンセル確認画面にアクセス', [
'user_id' => $user_id,
]);
return view('park_waitlist.cancel', [
'active_menu' => 'SWC-11-1', // この画面のID
'user_name' => $user_name, // ユーザー名(ヘッダー用)
'reserve_id' => $reserve_id,
]);
}
public function cancel($reserve_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
// 定期予約をキャンセル
DB::table('reserve')->where('reserve_id', $reserve_id)->update([
'valid_flag' => 0,
'updated_at' => now(),
'reserve_cancelday' => date('Y-m-d'),
]);
// メール送信用データ取得
$reserve = DB::table('reserve')
->leftJoin('park', 'reserve.park_id', '=', 'park.park_id')
->leftJoin('psection', 'reserve.psection_id', '=', 'psection.psection_id')
->leftJoin('ptype', 'reserve.ptype_id', '=', 'ptype.ptype_id')
->select(
'park.park_name',
'psection.psection_subject',
'ptype.ptype_subject'
)
->where('reserve.reserve_id', $reserve_id)
->first();
// メール送信処理
try {
Mail::to($user->user_primemail)->send(
new ReservationCancelledMail(
$user->user_name,
$reserve
)
);
} catch (\Exception $e) {
Log::error('予約キャンセルメール送信エラー: ' . $e->getMessage());
}
\Log::info('空き待ち状況確認 - キャンセル完了画面にアクセス', [
'user_id' => $user_id,
]);
return view('park_waitlist.cancel_complete', [
'reserve' => $reserve,
'active_menu' => 'SWC-11-1', // この画面のID
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
]);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class ReservationCancelledMail extends Mailable
{
use Queueable, SerializesModels;
public $user_name;
public $reserve;
public function __construct($user_name, $reserve)
{
$this->user_name = $user_name;
$this->reserve = $reserve;
}
public function build()
{
return $this->subject('So-Manager空き待ちのキャンセルが完了しました')
->view('emails.reservation_cancelled')
->with([
'user_name' => $this->user_name,
'reserve' => $this->reserve,
]);
}
}

View File

@ -0,0 +1,22 @@
{!! nl2br(e(
$user_name . '
So-Manager自動応答システムです。
下記駐輪場の空き待ちのキャンセルを受け付けました。
空き駐輪場: ' . $reserve->park_name . '
車種区分 : ' . $reserve->psection_subject . '
駐輪分類 : ' . $reserve->ptype_subject . '
So-Manager.comのユーザー登録は解約されておりません。別の駐輪場でのご契約や空き待ちをご希望の際には、マイページをご活用ください。
■お問合せ先■
So-Managerコールセンターソーマネージャーコールセンター
●電話03-5856-4720
●メールでのお問合せ(専用フォームよりお問合わせください)
※本メールアドレスは送信専用です。ご返信には回答致しかねますのでご了承ください。
※本メールにお心あたりのない場合には、コールセンターまでご連絡くださいますようお願い致します。'
)) !!}

View File

@ -0,0 +1,29 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">空き待ち状況確認 > キャンセル</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="row">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-success">キャンセルしますか?</h3>
<ul>
<li>キャンセル確定を押されますと確認ページは表示されません。即キャンセルになりますのでご注意ください。</li>
<li>一度キャンセルされると再度予約しても順番は最後になります。予めご了承ください。</li>
<li>再度予約を希望の場合は、空き待ち申込からお手続きください。</li>
</ul>
</div>
<div class="col-12 col-md-4 offset-md-2 mt10">
<a href="{{ route('park_waitlist.index') }}" class="btn btn-lg btn-block btn-outline-success">戻る</a>
</div>
<div class="col-12 col-md-4 offset-0 mt10">
<form action="{{ route('park_waitlist.cancel.post', ['reserve_id' => $reserve_id]) }}" method="POST">
@csrf
<button type="submit" class="btn btn-lg btn-block btn-success">キャンセル確定</button>
</form>
</div>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,19 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">空き待ち状況確認 > キャンセル完了</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="row">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-success">駐輪場名:{{ $reserve->park_name }}</h3>
<p class="mt30 mb30">上記の空き待ちキャンセルが完了しました。確認のメールを会員情報のメールアドレスに送信しました。</p>
</div>
<div class="col-12 col-md-8 offset-0 offset-md-2 mt30">
<a href="{{ route('park_waitlist.index') }}" class="btn btn-lg btn-block btn-outline-success">予約状況一覧へ戻る</a>
</div>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,55 @@
@extends('layouts.app')
@section('content')
<main>
<style>
/* テーブルヘッダーの色を #d4edda に固定し、クリックやフォーカス時も変化しない */
#searchTable th,
#searchTable th:active,
#searchTable th:focus {
background-color: #d4edda !important;
color: #212529 !important;
outline: none !important;
}
#searchTable th {
cursor: default !important;
}
</style>
<header class="alert alert-success">
<h4 class="container">空き待ち状況確認 > 予約状況</h4>
</header>
<section id="" class="container mt30 mb50">
@if($waitlists->count())
<div class="table-responsive">
<table id="searchTable" class="tablesorter table table-striped">
<thead>
<tr>
<th>予約日</th>
<th>駐輪場名</th>
<th>駅名</th>
<th>駐輪分類</th>
<th>車種区分</th>
</tr>
</thead>
<tbody>
@foreach($waitlists as $row)
<tr>
<td>{{ \Carbon\Carbon::parse($row->reserve_date)->format('Y/m/d') }}</td>
<td><a href="javascript:void(0);" class="btn-reserve-popup text-primary" data-reserve-id="{{ $row->reserve_id }}">
{{ $row->park_name }}
</a></td>
<td>{{ $row->station_neighbor_station }}</td>
<td>{{ $row->ptype_subject }}</td>
<td>{{ $row->psection_subject }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div class="text-center mt-5">予約している駐輪場はありません。</div>
@endif
</section>
</main>
<div id="modalArea"></div>
@endsection

View File

@ -0,0 +1,47 @@
<!-- Bootstrapモーダル -->
<div class="modal fade" id="parkDetailWaitModal" tabindex="-1" aria-labelledby="parkDetailWaitLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="parkDetailWaitLabel">{{ $park->park_name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<!-- Googleマップ埋め込み -->
<div style="width:100%; height:350px;">
<iframe
width="100%"
height="350"
frameborder="0"
style="border:0"
src="https://www.google.com/maps?q={{ $park->park_latitude }},{{ $park->park_longitude }}&hl=ja&z=16&output=embed"
allowfullscreen>
</iframe>
</div>
<!-- 駐輪場情報 -->
<div class="mt-3 small">
{{ $park->park_adrs }}
<span class="d-inline-block" style="margin-top:0.5em;"></span>
<span>
【標準収容台数】
@foreach($zoneStandardSum as $type => $count)
{{ $type }}{{ $count }}台 
@endforeach
</span>
</div>
</div>
<div class="modal-footer">
<a href="{{ route('park_waitlist.cancel', ['reserve_id' => $reserve->reserve_id]) }}"
class="btn btn-outline-success btn_103">
キャンセルする
</a>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">閉じる</button>
</div>
</div>
</div>
</div>
<!-- jQuery Confirm用CSS/JSjQuery本体は既に読み込まれていれば不要 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.4/jquery-confirm.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.4/jquery-confirm.min.js"></script>

View File

@ -83,102 +83,58 @@ Route::get('/mypage', function () {
'; ';
})->name('mypage'); })->name('mypage');
// ユーザー情報確認画面 // ユーザー情報確認・編集
Route::get('/user/info', [UserInfoController::class, 'show']) Route::get('/user/info', [UserInfoController::class, 'show'])->name('user.info');
->name('user.info'); Route::get('/user/edit', [UserEditController::class, 'show'])->name('user.edit');
Route::post('/user/edit', [UserEditController::class, 'update'])->name('user.edit.post');
Route::get('/user/edit/confirm', [UserEditConfirmController::class, 'show'])->name('user.confirm');
Route::post('/user/edit/submit', [UserEditConfirmController::class, 'submit'])->name('user.edit.submit');
Route::get('/user/edit/verify', [UserEditConfirmController::class, 'verify'])->name('user.edit.verify');
// ユーザー情報編集画面GET: 編集フォーム表示) // 退会
Route::get('/user/edit', [UserEditController::class, 'show']) Route::get('/user/withdraw', [UserWithdrawController::class, 'showConfirm'])->name('user.withdraw');
->name('user.edit'); Route::post('/user/withdraw/confirm', [UserWithdrawController::class, 'withdraw'])->name('user.withdraw.confirm');
// ユーザー情報編集POST: 編集内容保存)
Route::post('/user/edit', [UserEditController::class, 'update'])
->name('user.edit.post');
// ユーザー情報編集確認
Route::get('/user/edit/confirm', [UserEditConfirmController::class, 'show'])
->name('user.confirm');
// 入力内容確認画面から「変更を確定する」ボタン押下時(認証メール送信)
Route::post('/user/edit/submit', [UserEditConfirmController::class, 'submit'])
->name('user.edit.submit');
// 認証メール内URLクリック時変更確定処理
Route::get('/user/edit/verify', [UserEditConfirmController::class, 'verify'])
->name('user.edit.verify');
// 退会画面GET: 退会確認)
Route::get('/user/withdraw', [UserWithdrawController::class, 'showConfirm'])
->name('user.withdraw');
// 退会処理POST: 退会確定)
Route::post('/user/withdraw/confirm', [UserWithdrawController::class, 'withdraw'])
->name('user.withdraw.confirm');
// 定期契約情報確認 // 定期契約情報確認
Route::get('regular_contract/info', [RegularContractController::class, 'showInfo']) Route::get('regular_contract/info', [RegularContractController::class, 'showInfo'])->name('regular_contract.info');
->name('regular_contract.info');
// 領収書宛名入力画面 // 領収書発行
Route::get('receipt/input/{contract_id}', [ReceiptController::class, 'input']) Route::get('receipt/input/{contract_id}', [ReceiptController::class, 'input'])->name('receipt.input');
->name('receipt.input'); Route::get('receipt/download/{contract_id}', [ReceiptController::class, 'download'])->name('receipt.download');
Route::get('receipt/download/{contract_id}', [ReceiptController::class, 'download'])
->name('receipt.download');
Route::post('receipt/issue/{contract_id}', [ReceiptController::class, 'issue']); Route::post('receipt/issue/{contract_id}', [ReceiptController::class, 'issue']);
// 新規定期契約画面 // 新規定期契約
Route::get('regular_contract/create', [RegularContractCreateController::class, 'show']) Route::get('regular_contract/create', [RegularContractCreateController::class, 'show'])->name('regular_contract.create');
->name('regular_contract.create');
Route::get('/api/park-detail/{park_id}', [ParkDetailController::class, 'show']); Route::get('/api/park-detail/{park_id}', [ParkDetailController::class, 'show']);
Route::get('/regular-contract/regulationCheck', [RegularContractCreateController::class, 'regulationCheck']); Route::get('/regular-contract/regulationCheck', [RegularContractCreateController::class, 'regulationCheck']);
Route::get('/regular-contract/regulation', [RegularContractCreateController::class, 'showRegulation']) Route::get('/regular-contract/regulation', [RegularContractCreateController::class, 'showRegulation'])->name('regular_contract.regulation');
->name('regular_contract.regulation');
Route::post('/regular-contract/insertRegulation', [RegularContractCreateController::class, 'insertRegulation']); Route::post('/regular-contract/insertRegulation', [RegularContractCreateController::class, 'insertRegulation']);
Route::get('/regular-contract/input', [RegularContractCreateController::class, 'showContractForm'])->name('regular_contract.input'); Route::get('/regular-contract/input', [RegularContractCreateController::class, 'showContractForm'])->name('regular_contract.input');
Route::post('/regular_contract/input/check', [RegularContractCreateController::class, 'inputCheck'])->name('regular_contract.input.check'); Route::post('/regular_contract/input/check', [RegularContractCreateController::class, 'inputCheck'])->name('regular_contract.input.check');
Route::get('/regular-contract/upload_identity_create', [RegularContractCreateController::class, 'showUploadIdentityCreate'])->name('regular_contract.upload_identity_create'); Route::get('/regular-contract/upload_identity_create', [RegularContractCreateController::class, 'showUploadIdentityCreate'])->name('regular_contract.upload_identity_create');
Route::post('regular_contract/confirm_upload_identity/{contract_id}', [RegularContractCreateController::class, 'confirmUploadIdentity'])->name('regular_contract.confirm_upload_identity'); Route::post('regular_contract/confirm_upload_identity/{contract_id}', [RegularContractCreateController::class, 'confirmUploadIdentity'])->name('regular_contract.confirm_upload_identity');
Route::get('regular_contract/create_confirm', [RegularContractCreateController::class, 'createConfirm'])->name('regular_contract.create_confirm'); Route::get('regular_contract/create_confirm', [RegularContractCreateController::class, 'createConfirm'])->name('regular_contract.create_confirm');
Route::post('/regular_contract/create_confirm_next/{contract_id}', [RegularContractCreateController::class, 'createConfirmNext'])->name('regular_contract.create_confirm_next'); Route::post('/regular_contract/create_confirm_next/{contract_id}', [RegularContractCreateController::class, 'createConfirmNext'])->name('regular_contract.create_confirm_next');
Route::post('regular_contract/create_select_period', [RegularContractCreateController::class, 'selectPeriod']) Route::post('regular_contract/create_select_period', [RegularContractCreateController::class, 'selectPeriod'])->name('regular_contract.create_select_period');
->name('regular_contract.create_select_period');
// 定期契約更新 // 定期契約更新
Route::get('regular_contract/update', [RegularContractController::class, 'showInfo']) Route::get('regular_contract/update', [RegularContractController::class, 'showInfo'])->name('regular_contract.update');
->name('regular_contract.update');
Route::get('regular_contract/update/{contract_id}', [RegularContractController::class, 'update']); Route::get('regular_contract/update/{contract_id}', [RegularContractController::class, 'update']);
// 契約区分確認 Route::get('regular_contract/confirm_category/{contract_id}', [RegularContractController::class, 'confirmCategory'])->name('regular_contract.confirm_category');
Route::get('regular_contract/confirm_category/{contract_id}', [RegularContractController::class, 'confirmCategory']) Route::get('regular_contract/confirm_category_next/{contract_id}', [RegularContractController::class, 'confirmCategoryNext'])->name('regular_contract.confirm_category_next');
->name('regular_contract.confirm_category'); Route::get('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentity'])->name('regular_contract.upload_identity');
Route::get('regular_contract/confirm_category_next/{contract_id}', [RegularContractController::class, 'confirmCategoryNext']) Route::post('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentitySubmit'])->name('regular_contract.upload_identity_submit');
->name('regular_contract.confirm_category_next'); Route::get('regular_contract/select_period/{contract_id}', [RegularContractController::class, 'selectPeriod'])->name('regular_contract.select_period');
// 本人確認書類アップロード Route::post('regular_contract/update_period', [RegularContractController::class, 'updatePeriod'])->name('regular_contract.update_period');
Route::get('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentity'])
->name('regular_contract.upload_identity');
// 本人確認書類確認中
Route::post('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentitySubmit'])
->name('regular_contract.upload_identity_submit');
// 利用期間選択
Route::get('regular_contract/select_period/{contract_id}', [RegularContractController::class, 'selectPeriod'])
->name('regular_contract.select_period');
Route::post('regular_contract/update_period', [RegularContractController::class, 'updatePeriod'])
->name('regular_contract.update_period');
// 定期契約履歴 // 定期契約履歴
Route::get('regular_contract/history', [RegularContractController::class, 'showHistory']) Route::get('regular_contract/history', [RegularContractController::class, 'showHistory'])->name('regular_contract.history');
->name('regular_contract.history');
// 駐輪場検索 // 駐輪場検索
Route::get('park_search', [RegularContractCreateController::class, 'show']) Route::get('park_search', [RegularContractCreateController::class, 'show'])->name('park_search');
->name('park_search');
// 空き待ち状況確認画面 // 空き待ち状況確認
Route::get('park_waitlist', [ParkWaitlistController::class, 'index']) Route::get('park_waitlist', [ParkWaitlistController::class, 'index'])->name('park_waitlist.index');
->name('park_waitlist.index');
Route::post('/login', function (Request $request) {
$user_id = $request->input('user_id');
Session::put('user_id', $user_id); // 入力されたIDをそのまま保存
return redirect('/user/info'); // 認証なしでリダイレクト
});
// ウェルネット決済画面(仮) // ウェルネット決済画面(仮)
Route::get('/wellnet/payment', function (): mixed { Route::get('/wellnet/payment', function (): mixed {