From 3337d6abac97dedc39ae9d9e3f9743ec7b11cb97 Mon Sep 17 00:00:00 2001 From: Yuka Higashide Date: Wed, 24 Sep 2025 16:52:21 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E3=83=9E=E3=82=A4=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/MypageController.php | 100 +++++++++ resources/views/layouts/app.blade.php | 1 - resources/views/mypage/index.blade.php | 255 ++++++++++++++++++++++ routes/web.php | 11 +- 4 files changed, 357 insertions(+), 10 deletions(-) create mode 100644 app/Http/Controllers/MypageController.php create mode 100644 resources/views/mypage/index.blade.php diff --git a/app/Http/Controllers/MypageController.php b/app/Http/Controllers/MypageController.php new file mode 100644 index 0000000..2fb59c6 --- /dev/null +++ b/app/Http/Controllers/MypageController.php @@ -0,0 +1,100 @@ +where('user_id', $user_id)->first(); + + $today = date('Y-m-d'); + // 定期契約情報を取得(park/usertype/psection/ptypeテーブルもJOIN) + $contracts = DB::table('regular_contract') + ->join('park', 'regular_contract.park_id', '=', 'park.park_id') + ->join('usertype', 'regular_contract.user_categoryid', '=', 'usertype.user_categoryid') + ->leftJoin('city', 'park.city_id', '=', 'city.city_id') + ->leftJoin('psection', 'regular_contract.psection_id', '=', 'psection.psection_id') + ->leftJoin('ptype', 'regular_contract.ptype_id', '=', 'ptype.ptype_id') + ->where('regular_contract.user_id', $user_id) + ->where('regular_contract.contract_flag', 1) + ->where('regular_contract.contract_cancel_flag', 0) + ->where(function ($query) use ($today) { + $query->where('regular_contract.contract_periode', '>', $today) + ->orWhere(function ($q) use ($today) { + $q->where('regular_contract.contract_periode', '<=', $today) + ->whereRaw('DATEDIFF(?, regular_contract.contract_periode) <= 5', [$today]); + }); + }) + ->select( + 'regular_contract.contract_id', + 'park.park_name', + 'usertype.usertype_subject1', + 'regular_contract.contract_periods', + 'regular_contract.contract_periode', + 'regular_contract.enable_months', + 'regular_contract.contract_renewal', + 'regular_contract.park_id', + 'city.update_grace_period_start_date', + 'city.update_grace_period_start_time', + 'city.update_grace_period_end_date', + 'city.update_grace_period_end_time', + 'psection.psection_subject', + 'ptype.ptype_subject', + 'regular_contract.pplace_no' + ) + ->get(); + + // シール情報を取得 + $seals = DB::table('regular_contract') + ->join('usertype', 'regular_contract.user_categoryid', '=', 'usertype.user_categoryid') + ->leftJoin('psection', 'regular_contract.psection_id', '=', 'psection.psection_id') + ->leftJoin('ptype', 'regular_contract.ptype_id', '=', 'ptype.ptype_id') + ->where('regular_contract.user_id', $user_id) + ->where('regular_contract.contract_flag', 1) + ->where('regular_contract.contract_cancel_flag', 0) + ->where('regular_contract.contract_periode', '>=', $today) + ->where('regular_contract.contract_permission', 1) + ->select( + 'regular_contract.contract_id', + 'regular_contract.contract_qr_id', + 'usertype.usertype_subject1', + 'regular_contract.contract_periods', + 'regular_contract.contract_periode', + 'regular_contract.enable_months', + 'regular_contract.contract_renewal', + 'regular_contract.park_id', + 'psection.psection_subject', + 'ptype.ptype_subject', + 'regular_contract.pplace_no' + ) + ->get(); + + // お知らせ情報を取得 + $informations= DB::table('user_information_history') + ->where('user_id', $user_id) + ->orderBy('user_information_history_id', 'desc') + ->limit(3) + ->get(); + + \Log::info('マイページにアクセス', [ + 'user_id' => $user_id, + ]); + + return view('mypage.index', [ + 'active_menu' => 'SWO-4-1', // マイページメニューの選択状態用 + 'user_name' => $user->user_name, // ユーザー名(ヘッダー用) + 'contracts' => $contracts, + 'seals' => $seals, + 'informations' => $informations + ]); + } +} diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 7a4c2a0..8d2fd2c 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -12,7 +12,6 @@ - diff --git a/resources/views/mypage/index.blade.php b/resources/views/mypage/index.blade.php new file mode 100644 index 0000000..2269f75 --- /dev/null +++ b/resources/views/mypage/index.blade.php @@ -0,0 +1,255 @@ +@extends('layouts.app') +@section('content') +
+
+
+
+
+
+
定期契約情報
+
+
+
+ @forelse($contracts as $contract) + @php + $now = \Carbon\Carbon::now(); + $update_flag = $contract->contract_renewal; + $start_dd = $contract->update_grace_period_start_date; + $start_hm = $contract->update_grace_period_start_time; + $end_dd = $contract->update_grace_period_end_date; + $end_hm = $contract->update_grace_period_end_time; + $contract_end_dt = $contract->contract_periode ? \Carbon\Carbon::parse($contract->contract_periode) : null; + $periode_month = $contract_end_dt ? $contract_end_dt->month : null; + $periode_year = $contract_end_dt ? $contract_end_dt->year : null; + $bg = 'alert-warning'; + $btn_text = '更新する'; + $btn_active = true; + + // 契約終了月より前は「ご契約中」 + if ($now->lt($contract_end_dt)) { + $bg = 'bg-white'; + $btn_text = 'ご契約中'; + $btn_active = false; + } else { + // 契約終了月より後は猶予期間判定 + if (is_numeric($start_dd) && is_numeric($end_dd)) { + // 開始日 + $start_date = $contract_end_dt->format('Y-m-') . str_pad($start_dd, 2, '0', STR_PAD_LEFT); + $start_time = ($start_hm && preg_match('/^\d{2}:\d{2}$/', $start_hm)) ? $start_hm : '00:00'; + $start_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $start_date . ' ' . $start_time . ':00'); + // 終了日 + if ($start_dd < $end_dd) { + $end_date=$contract_end_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT); + $end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59'; + $end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00'); + } else { + $next_month_dt = $contract_end_dt->copy()->addMonth(); + $end_date = $next_month_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT); + $end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59'; + $end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00'); + } + } else { + $start_dt = null; + $end_dt = null; + } + // 猶予期間判定 + if ($update_flag === 0) { + $bg = 'bg-white'; + $btn_text = '手続き中'; + $btn_active = false; + } elseif ($update_flag === 1) { + $bg = 'bg-white'; + $btn_text = '更新済'; + $btn_active = false; + } elseif ($start_dt && $end_dt && $now->between($start_dt, $end_dt)) { + // 猶予期間内 + if ($contract_end_dt && $now->gt($contract_end_dt)) { + $bg = 'alert-danger'; + $btn_text = '更新する'; + $btn_active = true; + } else { + $bg = 'alert-warning'; + $btn_text = '更新する'; + $btn_active = true; + } + } else { + $bg = 'bg-white'; + $btn_text = 'ご契約中'; + $btn_active = false; + } + } + // 契約終了月の場合(既存ロジック) + if (is_numeric($start_dd) && is_numeric($end_dd)) { + // 開始日 + $start_date = $contract_end_dt->format('Y-m-') . str_pad($start_dd, 2, '0', STR_PAD_LEFT); + $start_time = ($start_hm && preg_match('/^\d{2}:\d{2}$/', $start_hm)) ? $start_hm : '00:00'; + $start_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $start_date . ' ' . $start_time . ':00'); + // 終了日 + if ($start_dd < $end_dd) { + $end_date=$contract_end_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT); + $end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59'; + $end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00'); + } else { + $next_month_dt = $contract_end_dt->copy()->addMonth(); + $end_date = $next_month_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT); + $end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59'; + $end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00'); + } + } else { + $start_dt = null; + $end_dt = null; + } + // 以降は既存のボタン・背景色判定ロジック + if ($update_flag===0) { + $bg='bg-white'; + $btn_text='手続き中'; + $btn_active=false; + } + elseif ($update_flag===1) { + $bg='bg-white'; + $btn_text='更新済'; + $btn_active=false; + } + elseif (!is_null($end_dt) && $end_dt->gt($start_dt)) { + if ($start_dt && $now->lt($start_dt)) { + $bg = 'bg-white'; + $btn_text = 'ご契約中'; + $btn_active = false; + } else { + // 契約終了日を過ぎていて、更新可能期間内は赤背景 + if ($contract_end_dt && $now->gt($contract_end_dt) && $start_dt && $end_dt && $now->between($start_dt, $end_dt)) { + $bg = 'alert-danger'; + $btn_text = '更新する'; + $btn_active = true; + } else { + $bg = 'alert-warning'; + $btn_text = '更新する'; + $btn_active = true; + } + } + } + elseif ($start_dt && $start_dt->gt($end_dt)) { + if ($now->lt($start_dt)) { + $bg = 'bg-white'; + $btn_text = 'ご契約中'; + $btn_active = false; + } elseif ($now->gte($start_dt) && $now->lte($contract_end_dt->copy()->endOfMonth())) { + $bg = 'alert-warning'; + $btn_text = '更新する'; + $btn_active = true; + } else { + $bg = 'alert-danger'; + $btn_text = '更新する'; + $btn_active = true; + } + } + @endphp +
+
+
{{ $contract->park_name }}
+ + + + + + + + + + + + + + + + + + + + + + + +
{{ $contract->psection_subject ?? '' }}{{ $contract->usertype_subject1 ?? '' }}
{{ $contract->ptype_subject ?? '' }}{{ $contract->pplace_no ?? '' }}
定期契約ID{{ $contract->contract_id }}
期間{{ \Carbon\Carbon::parse($contract->contract_periods)->format('Y-m-d') }}から
{{ $contract->enable_months }}ヶ月
+ @if($btn_active) + + {{ $btn_text }} + + @else + + @endif +
+
+
+ @empty +

定期契約情報はありません
+ 新規定期契約 +

+ @endforelse +
+
+
+
+
+
+
+
シール発行
+
+
+ @forelse($seals as $seal) +
+
+
+ @if(!empty($seal->contract_qr_id)) + {!! QrCode::size(120)->generate($seal->contract_qr_id) !!} + @else +
QRコード
未発行
+ @endif +
+
+
+
+ + + + + + + + + + + + + + + + +
{{ $seal->psection_subject ?? '' }}{{ $seal->usertype_subject1 ?? '' }}
{{ $seal->ptype_subject ?? '' }}{{ $seal->pplace_no ?? '' }}
定期契約ID{{ $seal->contract_id }}
+ {{ $seal->enable_months }}ヶ月 +
+
+
+ @empty +
シール発行対象の契約はありません。
+ @endforelse +
+
+
+
+
{{ $user_name }}さんへのお知らせ + お知らせ一覧を見る +
+
+
    + @foreach($informations as $information) +
  • {{ $information->entry_date }}{{ $information->user_information_history }}
  • + @endforeach +
+
+
+
+
+
+@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index d069fb9..9db15c6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -73,15 +73,8 @@ Route::get('/login', function () { return redirect()->route('swo8_1'); })->name('login'); -// マイページ画面へのリダイレクト -Route::get('/mypage', function () { - return ' -
-

マイページ(仮)

- ユーザー情報を確認する -
- '; -})->name('mypage'); +// マイページ +Route::get('/mypage', [MypageController::class, 'index'])->name('mypage.index'); // ユーザー情報確認・編集 Route::get('/user/info', [UserInfoController::class, 'show'])->name('user.info'); From 814b85cdbbe8842b0e8b3f88bcb9514b90f87c60 Mon Sep 17 00:00:00 2001 From: "y.higashide" Date: Wed, 24 Sep 2025 16:53:22 +0900 Subject: [PATCH 2/6] =?UTF-8?q?public/assets/css/mypage/all.css=20?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/assets/css/mypage/all.css | 61 -------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 public/assets/css/mypage/all.css diff --git a/public/assets/css/mypage/all.css b/public/assets/css/mypage/all.css deleted file mode 100644 index 6439b74..0000000 --- a/public/assets/css/mypage/all.css +++ /dev/null @@ -1,61 +0,0 @@ -/* iCheck plugin skins ------------------------------------ */ -@import url("minimal/_all.css"); -/* -@import url("minimal/minimal.css"); -@import url("minimal/red.css"); -@import url("minimal/green.css"); -@import url("minimal/blue.css"); -@import url("minimal/aero.css"); -@import url("minimal/grey.css"); -@import url("minimal/orange.css"); -@import url("minimal/yellow.css"); -@import url("minimal/pink.css"); -@import url("minimal/purple.css"); -*/ - -@import url("square/_all.css"); -/* -@import url("square/square.css"); -@import url("square/red.css"); -@import url("square/green.css"); -@import url("square/blue.css"); -@import url("square/aero.css"); -@import url("square/grey.css"); -@import url("square/orange.css"); -@import url("square/yellow.css"); -@import url("square/pink.css"); -@import url("square/purple.css"); -*/ - -@import url("flat/_all.css"); -/* -@import url("flat/flat.css"); -@import url("flat/red.css"); -@import url("flat/green.css"); -@import url("flat/blue.css"); -@import url("flat/aero.css"); -@import url("flat/grey.css"); -@import url("flat/orange.css"); -@import url("flat/yellow.css"); -@import url("flat/pink.css"); -@import url("flat/purple.css"); -*/ - -@import url("line/_all.css"); -/* -@import url("line/line.css"); -@import url("line/red.css"); -@import url("line/green.css"); -@import url("line/blue.css"); -@import url("line/aero.css"); -@import url("line/grey.css"); -@import url("line/orange.css"); -@import url("line/yellow.css"); -@import url("line/pink.css"); -@import url("line/purple.css"); -*/ - -@import url("polaris/polaris.css"); - -@import url("futurico/futurico.css"); \ No newline at end of file From 01c1f74d09cd45a770632da049b5d228de8a2f7b Mon Sep 17 00:00:00 2001 From: "y.higashide" Date: Wed, 24 Sep 2025 16:55:46 +0900 Subject: [PATCH 3/6] =?UTF-8?q?routes/web.php=20=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/web.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/routes/web.php b/routes/web.php index 9db15c6..6651f5f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,4 +1,3 @@ - name('login'); // マイページ -Route::get('/mypage', [MypageController::class, 'index'])->name('mypage.index'); +Route::get('/mypage', [MypageController::class, 'index'])->name('mypage'); // ユーザー情報確認・編集 Route::get('/user/info', [UserInfoController::class, 'show'])->name('user.info'); From 41cd07db41606c4d25853bd0072d106e57f976ec Mon Sep 17 00:00:00 2001 From: "y.higashide" Date: Wed, 24 Sep 2025 16:57:27 +0900 Subject: [PATCH 4/6] =?UTF-8?q?routes/web.php=20=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/web.php | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/web.php b/routes/web.php index 6651f5f..9d94e6f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -11,6 +11,7 @@ use App\Http\Controllers\InquiryConfirmController; use App\Http\Controllers\LoginController; use App\Http\Controllers\MemberRegistrationController; use App\Http\Controllers\PasswordReminderController; +use App\Http\Controllers\MypageController; use App\Http\Controllers\UserInfoController; use App\Http\Controllers\UserEditController; use App\Http\Controllers\UserEditConfirmController; From dce7e1daadcfa4215ac99c5dcc063f6b15baffa8 Mon Sep 17 00:00:00 2001 From: Yuka Higashide Date: Thu, 25 Sep 2025 14:11:35 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E3=83=9E=E3=82=A4=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=8A=E7=9F=A5=E3=82=89=E3=81=9B=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/MypageController.php | 8 ++++---- resources/views/mypage/index.blade.php | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/MypageController.php b/app/Http/Controllers/MypageController.php index 2fb59c6..a64f0a6 100644 --- a/app/Http/Controllers/MypageController.php +++ b/app/Http/Controllers/MypageController.php @@ -79,11 +79,11 @@ class MypageController extends Controller ->get(); // お知らせ情報を取得 - $informations= DB::table('user_information_history') + $information = DB::table('user_information_history') ->where('user_id', $user_id) ->orderBy('user_information_history_id', 'desc') - ->limit(3) - ->get(); + ->select('entry_date', 'user_information_history') + ->first(); \Log::info('マイページにアクセス', [ 'user_id' => $user_id, @@ -94,7 +94,7 @@ class MypageController extends Controller 'user_name' => $user->user_name, // ユーザー名(ヘッダー用) 'contracts' => $contracts, 'seals' => $seals, - 'informations' => $informations + 'information' => $information ]); } } diff --git a/resources/views/mypage/index.blade.php b/resources/views/mypage/index.blade.php index 2269f75..f31a0b1 100644 --- a/resources/views/mypage/index.blade.php +++ b/resources/views/mypage/index.blade.php @@ -243,9 +243,14 @@
    - @foreach($informations as $information) -
  • {{ $information->entry_date }}{{ $information->user_information_history }}
  • - @endforeach + @if($information) +
  • + {{ $information->entry_date }} + {{ $information->user_information_history }} +
  • + @else +
  • お知らせはありません。
  • + @endif
From 3960e062b96991d9e055d81eddda6e807bceecdf Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 26 Sep 2025 17:03:01 +0900 Subject: [PATCH 6/6] =?UTF-8?q?SHJ-5=E9=A7=90=E8=BC=AA=E5=A0=B4=E7=A9=BA?= =?UTF-8?q?=E3=81=8D=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=20SHJ-13=E5=A5=91?= =?UTF-8?q?=E7=B4=84=E5=8F=B0=E6=95=B0=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ShjFiveCommand.php | 207 +++++++ app/Console/Commands/ShjThirteenCommand.php | 154 +++++ app/Services/ShjFiveService.php | 650 ++++++++++++++++++++ app/Services/ShjFourBService.php | 54 +- app/Services/ShjThirteenService.php | 346 +++++++++++ 5 files changed, 1400 insertions(+), 11 deletions(-) create mode 100644 app/Console/Commands/ShjFiveCommand.php create mode 100644 app/Console/Commands/ShjThirteenCommand.php create mode 100644 app/Services/ShjFiveService.php create mode 100644 app/Services/ShjThirteenService.php diff --git a/app/Console/Commands/ShjFiveCommand.php b/app/Console/Commands/ShjFiveCommand.php new file mode 100644 index 0000000..4fd5930 --- /dev/null +++ b/app/Console/Commands/ShjFiveCommand.php @@ -0,0 +1,207 @@ +shjFiveService = $shjFiveService; + } + + /** + * コンソールコマンドを実行 + * + * 処理フロー: + * 1. バッチログ開始記録 + * 2. 駐輪場の空き状況を取得する + * 3. 空き状況判定 + * 4. 空き待ち者の情報を取得する + * 5. 取得件数判定 + * 6. 空き待ち者への通知、またはオペレーターキュー追加処理 + * 7. バッチ処理ログを作成する + * 8. 処理結果返却 + * + * @return int + */ + public function handle() + { + $batchLog = null; + + try { + // 開始ログ出力 + $startTime = now(); + $this->info('SHJ-5 空き待ち通知処理を開始します。'); + + Log::info('SHJ-5 空き待ち通知処理開始', [ + 'start_time' => $startTime + ]); + + // SHJ-8共通処理呼び出し - 仕様書準拠 + $batchLog = BatchLog::createBatchLog( + 'SHJ-5空き待ち通知処理', + BatchLog::STATUS_START, + [ + 'device_id' => 1, // 仕様書:device_id=1 + 'process_code' => 1, // 仕様書:process_code=1 + 'status_comment' => '処理開始' // 仕様書:内部変数.ステータスコメント + ], + 'SHJ-5処理開始: 駐輪場空き状況確認と空き待ち通知処理' + ); + + // SHJ-5メイン処理実行 + $result = $this->shjFiveService->executeParkVacancyNotification(); + + $endTime = now(); + $this->info('SHJ-5 空き待ち通知処理が完了しました。'); + $this->info("処理時間: {$startTime->diffInSeconds($endTime)}秒"); + + // 処理結果表示 + $this->displayProcessResult($result); + + // SHJ-8共通処理 - バッチログ完了記録(仕様書準拠) + $logStatus = $result['success'] ? BatchLog::STATUS_SUCCESS : BatchLog::STATUS_ERROR; + + // 仕様書準拠:サービス側で作成した完全なステータスコメントを使用 + $statusComment = $result['success'] ? + ($result['status_comment'] ?? 'ステータスコメント生成エラー') : + sprintf('処理失敗: %s', $result['message'] ?? 'エラー'); + + $batchLog->update([ + 'status' => $logStatus, + 'end_time' => $endTime, + 'message' => $result['message'], + 'parameters' => [ + 'device_id' => 1, // 仕様書:device_id=1 + 'process_code' => 1, // 仕様書:process_code=1 + 'status_comment' => $statusComment, // 仕様書:内部変数.ステータスコメント(完全版) + 'processed_parks_count' => $result['processed_parks_count'] ?? 0, + 'vacant_parks_count' => $result['vacant_parks_count'] ?? 0, + 'total_waiting_users' => $result['total_waiting_users'] ?? 0, + 'notification_success_count' => $result['notification_success_count'] ?? 0, + 'operator_queue_count' => $result['operator_queue_count'] ?? 0, + 'error_count' => $result['error_count'] ?? 0, + 'duration_seconds' => $result['duration_seconds'] ?? 0 + ], + 'execution_count' => 1, + 'success_count' => $result['notification_success_count'] ?? 0, + 'error_count' => $result['error_count'] ?? 0 + ]); + + Log::info('SHJ-5 空き待ち通知処理完了', [ + 'end_time' => $endTime, + 'duration_seconds' => $startTime->diffInSeconds($endTime), + 'result' => $result + ]); + + return $result['success'] ? self::SUCCESS : self::FAILURE; + + } catch (\Exception $e) { + $this->error('SHJ-5 空き待ち通知処理で予期しないエラーが発生しました: ' . $e->getMessage()); + + // SHJ-8共通処理 - エラーログ記録(仕様書準拠) + if ($batchLog) { + $statusComment = sprintf( + 'メール正常終了件数:0/メール異常終了件数:1/キュー登録正常終了件数:0/キュー登録異常終了件数:1 - 処理エラー: %s', + $e->getMessage() + ); + $batchLog->update([ + 'status' => BatchLog::STATUS_ERROR, + 'end_time' => now(), + 'message' => 'SHJ-5処理中にエラーが発生: ' . $e->getMessage(), + 'parameters' => [ + 'device_id' => 1, // 仕様書:device_id=1 + 'process_code' => 1, // 仕様書:process_code=1 + 'status_comment' => $statusComment // 仕様書:内部変数.ステータスコメント(完全版) + ], + 'error_details' => $e->getMessage(), + 'error_count' => 1 + ]); + } + + Log::error('SHJ-5 空き待ち通知処理例外エラー', [ + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return self::FAILURE; + } + } + + /** + * 処理結果を表示 + * + * @param array $result 処理結果 + * @return void + */ + private function displayProcessResult(array $result): void + { + $this->line(''); + $this->info('=== 処理結果 ==='); + + if ($result['success']) { + $this->info("✓ 処理実行成功: " . $result['message']); + } else { + $this->error("✗ 処理実行失敗: " . $result['message']); + } + + $this->line(''); + $this->info('=== 処理統計 ==='); + $this->line(" 処理対象駐輪場数: " . ($result['processed_parks_count'] ?? 0)); + $this->line(" 空きあり駐輪場数: " . ($result['vacant_parks_count'] ?? 0)); + $this->line(" 空き待ち者総数: " . ($result['total_waiting_users'] ?? 0)); + $this->line(" 通知送信成功: " . ($result['notification_success_count'] ?? 0) . "件"); + $this->line(" オペレーターキュー追加: " . ($result['operator_queue_count'] ?? 0) . "件"); + $this->line(" エラー件数: " . ($result['error_count'] ?? 0) . "件"); + $this->line(" 処理時間: " . ($result['duration_seconds'] ?? 0) . "秒"); + + // エラー詳細があれば表示 + if (!empty($result['errors'])) { + $this->line(''); + $this->warn('=== エラー詳細 ==='); + foreach ($result['errors'] as $index => $error) { + $this->line(" " . ($index + 1) . ". " . $error); + } + } + } +} diff --git a/app/Console/Commands/ShjThirteenCommand.php b/app/Console/Commands/ShjThirteenCommand.php new file mode 100644 index 0000000..dc0eb95 --- /dev/null +++ b/app/Console/Commands/ShjThirteenCommand.php @@ -0,0 +1,154 @@ +shjThirteenService = $shjThirteenService; + } + + /** + * コンソールコマンドを実行 + * + * 処理フロー: + * 1. 引数取得・検証 + * 2. ShjThirteenService実行 + * 3. 結果表示 + * + * @return int + */ + public function handle() + { + try { + // 開始ログ出力 + $startTime = now(); + $this->info('SHJ-13 契約台数追加処理を開始します。'); + + // 引数取得 + $contractData = [ + 'park_id' => (int) $this->argument('park_id'), + 'psection_id' => (int) $this->argument('psection_id'), + 'ptype_id' => (int) $this->argument('ptype_id'), + 'zone_id' => (int) $this->argument('zone_id'), + 'contract_id' => $this->option('contract_id'), + ]; + + Log::info('SHJ-13 契約台数追加処理開始', [ + 'start_time' => $startTime, + 'contract_data' => $contractData, + ]); + + $this->info("駐輪場ID: {$contractData['park_id']}"); + $this->info("車種区分ID: {$contractData['psection_id']}"); + $this->info("駐輪分類ID: {$contractData['ptype_id']}"); + $this->info("ゾーンID: {$contractData['zone_id']}"); + if ($contractData['contract_id']) { + $this->info("契約ID: {$contractData['contract_id']}"); + } + + // SHJ-13処理実行 + $this->info('【処理実行】契約台数追加処理を実行しています...'); + $result = $this->shjThirteenService->execute($contractData); + + // 処理結果確認・表示 + if ($result['result'] === 0) { + $endTime = now(); + $this->info('SHJ-13 契約台数追加処理が正常に完了しました。'); + $this->info("処理時間: {$startTime->diffInSeconds($endTime)}秒"); + + Log::info('SHJ-13 契約台数追加処理完了', [ + 'end_time' => $endTime, + 'duration_seconds' => $startTime->diffInSeconds($endTime), + 'contract_data' => $contractData, + ]); + + // 成功時の結果出力 + $this->line('処理結果: 0'); // 0 = 正常終了 + $this->line('異常情報: '); // 正常時は空文字 + + return self::SUCCESS; + } else { + $this->error('SHJ-13 契約台数追加処理でエラーが発生しました。'); + $this->error("エラーコード: {$result['error_code']}"); + $this->error("エラーメッセージ: {$result['error_message']}"); + + Log::error('SHJ-13 契約台数追加処理エラー', [ + 'contract_data' => $contractData, + 'error_result' => $result, + ]); + + // エラー時の結果出力 + $this->line('処理結果: 1'); // 1 = 異常終了 + $this->line('異常情報: ' . $result['error_message']); + + return self::FAILURE; + } + + } catch (\Exception $e) { + $this->error('SHJ-13 契約台数追加処理で予期しないエラーが発生しました: ' . $e->getMessage()); + Log::error('SHJ-13 契約台数追加処理例外エラー', [ + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + // 例外時の結果出力 + $this->line('処理結果: 1'); // 1 = 異常終了 + $this->line('異常情報: システムエラー: ' . $e->getMessage()); + + return self::FAILURE; + } + } +} diff --git a/app/Services/ShjFiveService.php b/app/Services/ShjFiveService.php new file mode 100644 index 0000000..4b4b57a --- /dev/null +++ b/app/Services/ShjFiveService.php @@ -0,0 +1,650 @@ +getParkVacancyStatus(); + Log::info('駐輪場空き状況取得完了', [ + 'total_parks' => count($parkVacancyList) + ]); + + // 各駐輪場に対する処理 + foreach ($parkVacancyList as $parkVacancyData) { + // 配列をオブジェクトに変換 + $parkVacancy = (object) $parkVacancyData; + $processedParksCount++; + + Log::info('駐輪場処理開始', [ + 'park_id' => $parkVacancy->park_id, + 'park_name' => $parkVacancy->park_name, + 'psection_id' => $parkVacancy->psection_id, + 'ptype_id' => $parkVacancy->ptype_id, + 'vacant_count' => $parkVacancy->vacant_count + ]); + + // 【判断1】空き状況判定 + if ($parkVacancy->vacant_count < 1) { + Log::info('空きなし - 処理スキップ', [ + 'park_id' => $parkVacancy->park_id, + 'vacant_count' => $parkVacancy->vacant_count + ]); + continue; + } + + $vacantParksCount++; + + // 【処理2】空き待ち者の情報を取得する + $waitingUsers = $this->getWaitingUsersInfo( + $parkVacancy->park_id, + $parkVacancy->psection_id, + $parkVacancy->ptype_id + ); + + // 【判断2】取得件数判定 + if (empty($waitingUsers)) { + Log::info('空き待ち者なし', [ + 'park_id' => $parkVacancy->park_id + ]); + continue; + } + + $totalWaitingUsers += count($waitingUsers); + Log::info('空き待ち者情報取得完了', [ + 'park_id' => $parkVacancy->park_id, + 'waiting_users_count' => count($waitingUsers) + ]); + + // 【処理3】空き待ち者への通知、またはオペレーターキュー追加処理 + $notificationResult = $this->processWaitingUsersNotification( + $waitingUsers, + $parkVacancy + ); + + $notificationSuccessCount += $notificationResult['notification_success_count']; + $operatorQueueCount += $notificationResult['operator_queue_count']; + + if (!empty($notificationResult['errors'])) { + $mailErrors = array_merge($mailErrors, $notificationResult['errors']); + $errors = array_merge($errors, $notificationResult['errors']); + } + + // オペレーターキュー作成用データを収集 + if (!empty($notificationResult['queue_items'])) { + $allQueueItems = array_merge($allQueueItems ?? [], $notificationResult['queue_items']); + } + } + + // 【処理4】仕様書準拠:先に呼び出し、成功時のみ内部変数を更新 + $queueErrorCount = 0; // キュー登録異常終了件数(累計) + $queueSuccessCount = 0; // キュー登録正常終了件数(累計) + + foreach ($allQueueItems as $queueItem) { + // 仕様書準拠:在呼叫前先計算"如果這次成功會是第幾件",確保記錄反映最新件數 + $predictedSuccessCount = $queueSuccessCount + 1; + $predictedErrorCount = $queueErrorCount; // 暂时保持当前错误计数 + + $queueResult = $this->addToOperatorQueue( + $queueItem['waiting_user'], + $queueItem['park_vacancy'], + $queueItem['batch_comment'], + $notificationSuccessCount, // 最終メール正常終了件数 + $predictedSuccessCount, // 預測成功時的件數(包含本次) + count($mailErrors), // 現在のメール異常終了件数(動態計算) + $predictedErrorCount // 現在のキュー登録異常終了件数 + ); + + // 仕様書:根据实际结果决定是否采用预测值 + if ($queueResult['success']) { + $queueSuccessCount = $predictedSuccessCount; // 采用预测的成功计数 + } else { + $queueErrorCount++; // 失败时递增错误计数 + + // 仕様書:包含具体错误消息,满足"エラーメッセージ/スタックトレースを保持"要求 + $errorDetail = $queueResult['error'] ?? 'Unknown error'; + $queueErrorInfo = sprintf('キュー登録失敗:予約ID:%d - %s', + $queueItem['waiting_user']->reserve_id ?? 0, + $errorDetail + ); + $errors[] = $queueErrorInfo; // 加入总错误统计(包含具体原因) + + Log::error('オペレーターキュー作成失敗', [ + 'user_id' => $queueItem['waiting_user']->user_id, + 'reserve_id' => $queueItem['waiting_user']->reserve_id ?? 0, + 'error' => $errorDetail + ]); + } + } + + $endTime = now(); + $duration = $startTime->diffInSeconds($endTime); + + Log::info('SHJ-5 空き待ち通知処理完了', [ + 'duration_seconds' => $duration, + 'processed_parks_count' => $processedParksCount, + 'vacant_parks_count' => $vacantParksCount, + 'total_waiting_users' => $totalWaitingUsers, + 'notification_success_count' => $notificationSuccessCount, + 'operator_queue_success_count' => $queueSuccessCount, // 仕様書:正常完了件数 + 'queue_error_count' => $queueErrorCount, + 'mail_error_count' => count($mailErrors), // メール異常終了件数(分離) + 'total_error_count' => count($errors) // 全体エラー件数 + ]); + + // 仕様書に基づく内部変数.ステータスコメント生成 + $statusComment = sprintf( + 'メール正常終了件数:%d/メール異常終了件数:%d/キュー登録正常終了件数:%d/キュー登録異常終了件数:%d', + $notificationSuccessCount, + count($mailErrors), // メール異常終了件数(キュー失敗を除外) + $queueSuccessCount ?? 0, // 実際のキュー登録成功件数 + $queueErrorCount ?? 0 // 実際のキュー登録失敗件数 + ); + + return [ + 'success' => true, + 'message' => 'SHJ-5 空き待ち通知処理が正常に完了しました', + 'processed_parks_count' => $processedParksCount, + 'vacant_parks_count' => $vacantParksCount, + 'total_waiting_users' => $totalWaitingUsers, + 'notification_success_count' => $notificationSuccessCount, + 'operator_queue_count' => $queueSuccessCount ?? 0, // 仕様書:正常完了件数を使用 + 'error_count' => count($errors), + 'errors' => $errors, + 'duration_seconds' => $duration, + 'status_comment' => $statusComment // SHJ-8用の完全なステータスコメント + ]; + + } catch (Exception $e) { + Log::error('SHJ-5 空き待ち通知処理でエラーが発生', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => 'SHJ-5 空き待ち通知処理でエラーが発生: ' . $e->getMessage(), + 'error_details' => $e->getMessage() + ]; + } + } + + /** + * 【処理1】駐輪場の空き状況を取得する + * + * 仕様書に基づくSQL: + * - zone表から標準台数と現在の契約台数を比較 + * - 空きがある駐輪場の情報を取得 + * + * @return array 駐輪場空き状況リスト + */ + private function getParkVacancyStatus(): array + { + try { + // ゾーン毎の契約台数を取得 + $contractCounts = DB::table('regular_contract as T1') + ->select([ + 'T1.park_id', + 'T1.psection_id', + 'T5.ptype_id', + DB::raw('count(T1.contract_id) as contract_count') + ]) + ->join('park as T2', 'T1.park_id', '=', 'T2.park_id') + ->join('price_a as T5', function($join) { + $join->on('T1.park_id', '=', 'T5.park_id') + ->on('T1.price_parkplaceid', '=', 'T5.price_parkplaceid') + ->on('T1.psection_id', '=', 'T5.psection_id'); + }) + ->where([ + ['T1.contract_flag', '=', 1], // 有効契約 + ['T2.park_close_flag', '=', 0], // 駐輪場未閉鎖 + ]) + // 契約有効期間内の条件 + ->whereRaw("date_format(now(), '%Y%m%d') BETWEEN T1.contract_periods AND T1.contract_periode") + ->groupBy(['T1.park_id', 'T1.psection_id', 'T5.ptype_id']) + ->get() + ->keyBy(function($item) { + return $item->park_id . '_' . $item->psection_id . '_' . $item->ptype_id; + }); + + // ゾーン情報と照合して空き状況を算出 + $vacancyList = DB::table('zone as T1') + ->select([ + 'T1.park_id', + 'T2.park_name', + 'T1.psection_id', + 'T1.ptype_id', + 'T1.zone_standard', + 'T3.psection_subject', + 'T4.ptype_subject' + ]) + ->join('park as T2', 'T1.park_id', '=', 'T2.park_id') + ->join('psection as T3', 'T1.psection_id', '=', 'T3.psection_id') + ->join('ptype as T4', 'T1.ptype_id', '=', 'T4.ptype_id') + ->where([ + ['T1.delete_flag', '=', 0], // ゾーン有効 + ['T2.park_close_flag', '=', 0], // 駐輪場開設 + ]) + ->get() + ->map(function($zone) use ($contractCounts) { + $key = $zone->park_id . '_' . $zone->psection_id . '_' . $zone->ptype_id; + $contractCount = isset($contractCounts[$key]) ? $contractCounts[$key]->contract_count : 0; + + $zone->contract_count = $contractCount; + $zone->vacant_count = max(0, $zone->zone_standard - $contractCount); + + return $zone; + }) + ->filter(function($zone) { + return $zone->vacant_count > 0; // 空きがあるもののみ + }) + ->values(); + + Log::info('駐輪場空き状況算出完了', [ + 'total_zones' => count($vacancyList), + 'vacant_zones' => $vacancyList->filter(function($v) { + return $v->vacant_count > 0; + })->count() + ]); + + return $vacancyList->toArray(); + + } catch (Exception $e) { + Log::error('駐輪場空き状況取得エラー', [ + 'error' => $e->getMessage() + ]); + throw $e; + } + } + + /** + * 【処理2】空き待ち者の情報を取得する + * + * 仕様書に基づく取得条件: + * - 契約未紐付(contract_id IS NULL) + * - 退会でない(user_quit_flag <> 1) + * - 有効な予約(valid_flag = 1) + * - 予約日時順で取得(reserve_date昇順) + * + * @param int $parkId 駐輪場ID + * @param int $psectionId 車種区分ID + * @param int $ptypeId 駐輪分類ID + * @return array 空き待ち者情報リスト + */ + private function getWaitingUsersInfo(int $parkId, int $psectionId, int $ptypeId): array + { + try { + $waitingUsers = DB::table('reserve as T1') + ->select([ + 'T1.reserve_id', + 'T1.user_id', + 'T1.park_id', + 'T1.psection_id', + 'T1.ptype_id', + 'T1.reserve_order', + 'T1.reserve_date', + 'T1.reserve_manual', // 手動通知フラグ + 'T1.contract_id', // 契約紐付確認用 + 'T2.user_name', + 'T2.user_primemail', + 'T2.user_submail', // 副メールアドレス + 'T2.user_manual_regist_flag', // 手動登録フラグ + 'T2.user_quit_flag', // 退会フラグ + 'T3.park_name', + 'T4.psection_subject', + 'T5.ptype_subject' + ]) + ->join('user as T2', 'T1.user_id', '=', 'T2.user_id') + ->join('park as T3', 'T1.park_id', '=', 'T3.park_id') + ->join('psection as T4', 'T1.psection_id', '=', 'T4.psection_id') + ->join('ptype as T5', 'T1.ptype_id', '=', 'T5.ptype_id') + ->where([ + ['T1.park_id', '=', $parkId], + ['T1.psection_id', '=', $psectionId], + ['T1.ptype_id', '=', $ptypeId], + ['T1.valid_flag', '=', 1], // 有効な予約 + ['T2.user_quit_flag', '<>', 1] // 退会でない + ]) + ->whereNull('T1.contract_id') // 契約未紐付 + ->orderBy('T1.reserve_date', 'asc') // 仕様書に基づく予約日時順 + ->get() + ->toArray(); + + Log::info('空き待ち者情報取得完了', [ + 'park_id' => $parkId, + 'psection_id' => $psectionId, + 'ptype_id' => $ptypeId, + 'waiting_users_count' => count($waitingUsers) + ]); + + return $waitingUsers; + + } catch (Exception $e) { + Log::error('空き待ち者情報取得エラー', [ + 'park_id' => $parkId, + 'psection_id' => $psectionId, + 'ptype_id' => $ptypeId, + 'error' => $e->getMessage() + ]); + throw $e; + } + } + + /** + * 【処理3】空き待ち者への通知、またはオペレーターキュー追加処理 + * + * 仕様書に基づく分岐処理: + * - 手動通知フラグ判定(reserve_manual) + * - メール送信成功時のreserve.sent_date更新 + * - 失敗時のオペレーターキュー追加(最終統計で処理) + * + * @param array $waitingUsers 空き待ち者リスト + * @param object $parkVacancy 駐輪場空き情報 + * @return array 通知処理結果 + */ + private function processWaitingUsersNotification(array $waitingUsers, object $parkVacancy): array + { + $notificationSuccessCount = 0; + $operatorQueueCount = 0; + $errors = []; + $queueItems = []; // オペレーターキュー作成用データ収集 + + try { + // 空きがある分だけ処理(先着順) + $availableSpots = min($parkVacancy->vacant_count, count($waitingUsers)); + + for ($i = 0; $i < $availableSpots; $i++) { + $waitingUserData = $waitingUsers[$i]; + // 配列をオブジェクトに変換 + $waitingUser = (object) $waitingUserData; + + try { + // 【仕様判断】手動通知フラグチェック + if ($waitingUser->reserve_manual == 1) { + // 手動通知 → オペレーターキュー作成データ収集 + $batchComment = '手動通知フラグ設定のため予約ID:' . $waitingUser->reserve_id; + $queueItems[] = [ + 'waiting_user' => $waitingUser, + 'park_vacancy' => $parkVacancy, + 'batch_comment' => $batchComment + ]; + $operatorQueueCount++; + + Log::info('手動通知フラグによりオペレーターキュー登録予定', [ + 'user_id' => $waitingUser->user_id, + 'reserve_id' => $waitingUser->reserve_id + ]); + } else { + // 自動通知 → メール送信を試行 + $mailResult = $this->sendVacancyNotificationMail($waitingUser, $parkVacancy); + + if ($mailResult['success']) { + // メール送信成功 → reserve.sent_date更新 + $this->updateReserveSentDate($waitingUser->reserve_id); + $notificationSuccessCount++; + + Log::info('空き待ち通知メール送信成功', [ + 'user_id' => $waitingUser->user_id, + 'reserve_id' => $waitingUser->reserve_id, + 'park_id' => $parkVacancy->park_id + ]); + } else { + // メール送信失敗 → オペレーターキュー作成データ収集 + $shjSevenError = $mailResult['error'] ?? $mailResult['message'] ?? 'SHJ-7メール送信エラー'; + $batchComment = $shjSevenError . '予約ID:' . $waitingUser->reserve_id; + $queueItems[] = [ + 'waiting_user' => $waitingUser, + 'park_vacancy' => $parkVacancy, + 'batch_comment' => $batchComment + ]; + $operatorQueueCount++; + $errors[] = $shjSevenError; + } + } + + } catch (Exception $e) { + Log::error('空き待ち者通知処理エラー', [ + 'user_id' => $waitingUser->user_id, + 'reserve_id' => $waitingUser->reserve_id, + 'error' => $e->getMessage() + ]); + + // エラー発生時もオペレーターキュー作成データ収集 + $batchComment = 'システムエラー:' . $e->getMessage() . '予約ID:' . $waitingUser->reserve_id; + $queueItems[] = [ + 'waiting_user' => $waitingUser, + 'park_vacancy' => $parkVacancy, + 'batch_comment' => $batchComment + ]; + $operatorQueueCount++; + $errors[] = $e->getMessage(); + } + } + + return [ + 'notification_success_count' => $notificationSuccessCount, + 'operator_queue_count' => $operatorQueueCount, + 'errors' => $errors, + 'queue_items' => $queueItems // 後でキュー作成用 + ]; + + } catch (Exception $e) { + Log::error('空き待ち者通知処理全体エラー', [ + 'park_id' => $parkVacancy->park_id, + 'error' => $e->getMessage() + ]); + throw $e; + } + } + + /** + * 空き待ち通知メールを送信 + * + * 仕様書に基づくSHJ-7呼び出し: + * - 主メールアドレス・副メールアドレスを正しく渡す + * - 必要なパラメータを全て設定 + * + * @param object $waitingUser 空き待ち者情報 + * @param object $parkVacancy 駐輪場空き情報 + * @return array 送信結果 + */ + private function sendVacancyNotificationMail(object $waitingUser, object $parkVacancy): array + { + try { + // ShjMailSendServiceを利用してメール送信 + $mailService = app(ShjMailSendService::class); + + // 空き待ち通知用のメールテンプレートID(予約告知通知) + // OperatorQueの定数と合わせて4番を使用 + $mailTemplateId = 4; // 予約告知通知のテンプレートID + + // 仕様書No1/No2に基づく主メール・副メール設定 + $mainEmail = $waitingUser->user_primemail ?? ''; + $subEmail = $waitingUser->user_submail ?? ''; + + // メール送信実行(仕様書準拠) + $mailResult = $mailService->executeMailSend( + $mainEmail, + $subEmail, + $mailTemplateId + ); + + Log::info('空き待ち通知メール送信試行完了', [ + 'user_id' => $waitingUser->user_id, + 'main_email' => $mainEmail, + 'sub_email' => $subEmail, + 'mail_template_id' => $mailTemplateId, + 'result_success' => $mailResult['success'] ?? false + ]); + + return $mailResult; + + } catch (Exception $e) { + Log::error('空き待ち通知メール送信エラー', [ + 'user_id' => $waitingUser->user_id, + 'main_email' => $waitingUser->user_primemail ?? '', + 'sub_email' => $waitingUser->user_submail ?? '', + 'error' => $e->getMessage() + ]); + + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } + + /** + * reserve.sent_date及びvalid_flag更新 + * + * 仕様書準拠:メール送信成功時にreserve.sent_dateとvalid_flag=0を同時更新 + * 重複通知を防ぎ、処理済みマークを設定 + * + * @param int $reserveId 予約ID + * @return void + */ + private function updateReserveSentDate(int $reserveId): void + { + try { + DB::table('reserve') + ->where('reserve_id', $reserveId) + ->update([ + 'sent_date' => now()->format('Y-m-d H:i:s'), + 'valid_flag' => 0, // 仕様書:メール送信成功時に0に更新 + 'updated_at' => now() + ]); + + Log::info('reserve.sent_date及びvalid_flag更新完了', [ + 'reserve_id' => $reserveId, + 'sent_date' => now()->format('Y-m-d H:i:s'), + 'valid_flag' => 0 + ]); + + } catch (Exception $e) { + Log::error('reserve.sent_date及びvalid_flag更新エラー', [ + 'reserve_id' => $reserveId, + 'error' => $e->getMessage() + ]); + throw $e; + } + } + + /** + * オペレーターキューに追加 + * + * 仕様書に基づくキュー登録: + * - que_comment: 空文字列 + * - que_status_comment: 仕様書完全準拠形式(統計情報含む) + * - operator_id: 9999999固定 + * + * @param object $waitingUser 空き待ち者情報 + * @param object $parkVacancy 駐輪場空き情報 + * @param string $batchComment 内部変数.バッチコメント + * @param int $mailSuccessCount メール正常終了件数 + * @param int $queueSuccessCount キュー登録正常終了件数 + * @param int $mailErrorCount メール異常終了件数 + * @param int $queueErrorCount キュー登録異常終了件数 + * @return array 追加結果 + */ + private function addToOperatorQueue(object $waitingUser, object $parkVacancy, string $batchComment, int $mailSuccessCount, int $queueSuccessCount, int $mailErrorCount, int $queueErrorCount): array + { + try { + // 仕様書完全準拠:駐輪場名/駐輪分類名/車種区分名/空き台数…/対象予約ID…/内部変数.バッチコメント/内部変数.メール正常終了件数…メール異常終了件数…キュー登録正常終了件数…キュー登録異常終了件数… + $statusComment = sprintf( + '%s/%s/%s/空き台数:%d台/対象予約ID:%d/%s/メール正常終了件数:%d/メール異常終了件数:%d/キュー登録正常終了件数:%d/キュー登録異常終了件数:%d', + $waitingUser->park_name ?? '', + $waitingUser->ptype_subject ?? '', // 駐輪分類名 + $waitingUser->psection_subject ?? '', // 車種区分名 + $parkVacancy->vacant_count ?? 0, + $waitingUser->reserve_id ?? 0, + $batchComment, // 内部変数.バッチコメント + $mailSuccessCount, // 内部変数.メール正常終了件数 + $mailErrorCount, + $queueSuccessCount, + $queueErrorCount + ); + + OperatorQue::create([ + 'que_class' => 4, // 予約告知通知 + 'user_id' => $waitingUser->user_id, + 'contract_id' => null, + 'park_id' => $waitingUser->park_id, + 'que_comment' => '', // 仕様書:空文字列 + 'que_status' => 1, // キュー発生 + 'que_status_comment' => $statusComment, // 仕様書:完全準拠形式 + 'work_instructions' => '空き待ち者への連絡をお願いします。', + 'operator_id' => 9999999, // 仕様書:固定値9999999 + ]); + + Log::info('オペレーターキュー追加成功', [ + 'user_id' => $waitingUser->user_id, + 'park_id' => $waitingUser->park_id, + 'reserve_id' => $waitingUser->reserve_id, + 'que_class' => 4, + 'operator_id' => 9999999, + 'batch_comment' => $batchComment, + 'mail_success_count' => $mailSuccessCount, + 'mail_error_count' => $mailErrorCount, + 'queue_success_count' => $queueSuccessCount, + 'queue_error_count' => $queueErrorCount, + 'status_comment' => $statusComment + ]); + + return ['success' => true]; + + } catch (Exception $e) { + Log::error('オペレーターキュー追加エラー', [ + 'user_id' => $waitingUser->user_id, + 'park_id' => $waitingUser->park_id, + 'reserve_id' => $waitingUser->reserve_id ?? null, + 'batch_comment' => $batchComment, + 'error' => $e->getMessage() + ]); + + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } +} diff --git a/app/Services/ShjFourBService.php b/app/Services/ShjFourBService.php index 491cb42..ebb5a7b 100644 --- a/app/Services/ShjFourBService.php +++ b/app/Services/ShjFourBService.php @@ -7,6 +7,7 @@ use App\Models\RegularContract; use App\Models\Park; use App\Models\PriceA; use App\Models\Batch\BatchLog; +use App\Services\ShjThirteenService; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\DB; use Carbon\Carbon; @@ -170,6 +171,7 @@ class ShjFourBService 'T1.billing_amount', 'T4.price_ptypeid as ptype_id', 'T1.psection_id', + 'T1.zone_id', 'T1.update_flag', 'T1.reserve_id', 'T1.contract_payment_number', @@ -764,27 +766,57 @@ class ShjFourBService /** * SHJ-13実行処理(新規のみ) + * + * ShjThirteenServiceを使用した契約台数追加処理 * * @param object $contract * @return array */ private function triggerShjThirteen($contract): array { - // TODO: SHJ-13の具体的な処理を実装 - // 現在はプレースホルダー - Log::info('SHJ-4B SHJ-13実行処理', [ 'contract_id' => $contract->contract_id, - 'user_id' => $contract->user_id, 'park_id' => $contract->park_id, + 'psection_id' => $contract->psection_id, + 'ptype_id' => $contract->ptype_id, + 'zone_id' => $contract->zone_id, ]); - - return [ - 'triggered' => true, - 'method' => 'placeholder', - 'message' => 'SHJ-13処理は実装予定です', - 'contract_id' => $contract->contract_id, - ]; + + try { + // 契約データ準備 + $contractData = [ + 'contract_id' => $contract->contract_id, + 'park_id' => $contract->park_id, + 'psection_id' => $contract->psection_id, + 'ptype_id' => $contract->ptype_id, + 'zone_id' => $contract->zone_id, + ]; + + // ShjThirteenService実行 + $shjThirteenService = app(ShjThirteenService::class); + $result = $shjThirteenService->execute($contractData); + + Log::info('SHJ-4B SHJ-13実行完了', [ + 'contract_id' => $contract->contract_id, + 'result' => $result, + ]); + + return $result; + + } catch (\Throwable $e) { + Log::error('SHJ-4B SHJ-13実行エラー', [ + 'contract_id' => $contract->contract_id, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + return [ + 'result' => 1, + 'error_code' => $e->getCode() ?: 1999, + 'error_message' => $e->getMessage(), + 'stack_trace' => $e->getTraceAsString(), + ]; + } } /** diff --git a/app/Services/ShjThirteenService.php b/app/Services/ShjThirteenService.php new file mode 100644 index 0000000..cc3f276 --- /dev/null +++ b/app/Services/ShjThirteenService.php @@ -0,0 +1,346 @@ + $contractData['contract_id'] ?? null, + 'park_id' => $contractData['park_id'] ?? null, + 'psection_id' => $contractData['psection_id'] ?? null, + 'ptype_id' => $contractData['ptype_id'] ?? null, + 'zone_id' => $contractData['zone_id'] ?? null, + ]); + + try { + // パラメータ検証 + $validationResult = $this->validateParameters($contractData); + if (!$validationResult['valid']) { + return $this->createErrorResult( + 1001, + 'パラメータエラー: ' . $validationResult['message'] + ); + } + + // 【処理1・2】契約台数反映とバッチログ作成を一体で実行 + $processResult = $this->executeProcessWithLogging($contractData); + if (!$processResult['success']) { + return $this->createErrorResult( + $processResult['error_code'], + $processResult['error_message'], + $processResult['stack_trace'] ?? '' + ); + } + + $statusComment = $processResult['status_comment']; + + $endTime = now(); + Log::info('SHJ-13 契約台数追加処理完了', [ + 'contract_id' => $contractData['contract_id'], + 'execution_time' => $startTime->diffInSeconds($endTime), + 'updated_count' => $processResult['updated_count'], + 'status_comment' => $statusComment, + ]); + + return ['result' => 0]; + + } catch (\Throwable $e) { + Log::error('SHJ-13 契約台数追加処理例外エラー', [ + 'contract_data' => $contractData, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + return $this->createErrorResult( + $e->getCode() ?: 1999, + $e->getMessage(), + $e->getTraceAsString() + ); + } + } + + /** + * パラメータ検証 + * + * @param array $contractData + * @return array + */ + private function validateParameters(array $contractData): array + { + $errors = []; + + if (empty($contractData['park_id'])) { + $errors[] = '駐輪場IDが設定されていません'; + } + + if (empty($contractData['psection_id'])) { + $errors[] = '車種区分IDが設定されていません'; + } + + if (empty($contractData['ptype_id'])) { + $errors[] = '駐輪分類IDが設定されていません'; + } + + if (empty($contractData['zone_id'])) { + $errors[] = 'ゾーンIDが設定されていません'; + } + + if (!empty($errors)) { + return [ + 'valid' => false, + 'message' => implode(', ', $errors), + 'details' => $errors, + ]; + } + + return ['valid' => true]; + } + + /** + * 契約台数反映とバッチログ作成の一体処理 + * + * park_number・zone テーブルの契約台数を+1更新し、バッチログを作成 + * 全てを1つのトランザクション内で実行 + * + * @param array $contractData + * @return array + */ + private function executeProcessWithLogging(array $contractData): array + { + try { + return DB::transaction(function() use ($contractData) { + // 各テーブルの名称取得 + $names = $this->getTableNames($contractData); + if (!$names['success']) { + throw new \Exception('名称取得エラー: ' . $names['message'], 1002); + } + + // park_number テーブル更新 + $parkNumberUpdated = DB::table('park_number') + ->where('park_id', $contractData['park_id']) + ->where('psection_id', $contractData['psection_id']) + ->where('ptype_id', $contractData['ptype_id']) + ->increment('park_number', 1, [ + 'updated_at' => now(), + 'operator_id' => 'SHJ-13', + ]); + + if ($parkNumberUpdated === 0) { + throw new \Exception('park_numberテーブルの対象レコードが存在しません', 1011); + } + + // zone テーブル更新 + $zoneUpdated = DB::table('zone') + ->where('zone_id', $contractData['zone_id']) + ->increment('zone_number', 1, [ + 'updated_at' => now(), + 'ope_id' => 'SHJ-13', + ]); + + if ($zoneUpdated === 0) { + throw new \Exception('zoneテーブルの対象レコードが存在しません', 1012); + } + + // 更新後の契約台数取得 + $updatedZone = DB::table('zone') + ->where('zone_id', $contractData['zone_id']) + ->first(['zone_number']); + + // ステータスコメント構築 + $statusComment = $this->buildStatusComment($names['names'], $updatedZone->zone_number); + + // バッチ処理ログ作成(同一トランザクション内) + $currentDate = now()->format('Y/m/d'); + + $batchLog = BatchLog::createBatchLog( + 'SHJ-13', // process_name + BatchLog::STATUS_SUCCESS, + [ + 'device_id' => 1, + 'job_name' => 'SHJ-13', + 'status' => 'success', + 'status_comment' => $statusComment, + 'created_date' => $currentDate, + 'updated_date' => $currentDate, + 'contract_id' => $contractData['contract_id'] ?? null, + 'park_id' => $contractData['park_id'], + 'psection_id' => $contractData['psection_id'], + 'ptype_id' => $contractData['ptype_id'], + 'zone_id' => $contractData['zone_id'], + ], + $statusComment + ); + + Log::info('SHJ-13 契約台数更新・ログ作成完了', [ + 'contract_id' => $contractData['contract_id'], + 'park_number_updated' => $parkNumberUpdated, + 'zone_updated' => $zoneUpdated, + 'updated_count' => $updatedZone->zone_number, + 'batch_log_id' => $batchLog->id, + 'status_comment' => $statusComment, + ]); + + return [ + 'success' => true, + 'updated_count' => $updatedZone->zone_number, + 'status_comment' => $statusComment, + ]; + }); + + } catch (\Throwable $e) { + Log::error('SHJ-13 契約台数更新・ログ作成エラー', [ + 'contract_data' => $contractData, + 'error_code' => $e->getCode(), + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ]); + + return [ + 'success' => false, + 'error_code' => $e->getCode() ?: 1010, + 'error_message' => $e->getMessage(), + 'stack_trace' => $e->getTraceAsString(), + ]; + } + } + + /** + * テーブル名称取得 + * + * @param array $contractData + * @return array + */ + private function getTableNames(array $contractData): array + { + try { + // 駐輪場名取得 + $park = DB::table('park') + ->where('park_id', $contractData['park_id']) + ->first(['park_name']); + + if (!$park) { + return [ + 'success' => false, + 'message' => "駐輪場が見つかりません: park_id={$contractData['park_id']}", + ]; + } + + // 駐輪分類名取得 + $ptype = DB::table('ptype') + ->where('ptype_id', $contractData['ptype_id']) + ->first(['ptype_subject']); + + if (!$ptype) { + return [ + 'success' => false, + 'message' => "駐輪分類が見つかりません: ptype_id={$contractData['ptype_id']}", + ]; + } + + // 車種区分名取得 + $psection = DB::table('psection') + ->where('psection_id', $contractData['psection_id']) + ->first(['psection_subject']); + + if (!$psection) { + return [ + 'success' => false, + 'message' => "車種区分が見つかりません: psection_id={$contractData['psection_id']}", + ]; + } + + // ゾーン名取得 + $zone = DB::table('zone') + ->where('zone_id', $contractData['zone_id']) + ->first(['zone_name']); + + if (!$zone) { + return [ + 'success' => false, + 'message' => "ゾーンが見つかりません: zone_id={$contractData['zone_id']}", + ]; + } + + return [ + 'success' => true, + 'names' => [ + 'park_name' => $park->park_name, + 'ptype_subject' => $ptype->ptype_subject, + 'psection_subject' => $psection->psection_subject, + 'zone_name' => $zone->zone_name, + ], + ]; + + } catch (\Throwable $e) { + return [ + 'success' => false, + 'message' => 'テーブル名称取得エラー: ' . $e->getMessage(), + 'details' => $e->getTraceAsString(), + ]; + } + } + + /** + * ステータスコメント構築 + * + * 形式:駐輪場名/駐輪分類名/車種区分名/ゾーン名/現在契約台数(更新後):{数値}/ + * + * @param array $names + * @param int $updatedCount + * @return string + */ + private function buildStatusComment(array $names, int $updatedCount): string + { + return sprintf( + '%s/%s/%s/%s/現在契約台数(更新後):%d/', + $names['park_name'], + $names['ptype_subject'], + $names['psection_subject'], + $names['zone_name'], + $updatedCount + ); + } + + + /** + * エラー結果作成 + * + * @param int $errorCode + * @param string $errorMessage + * @param string $stackTrace + * @return array + */ + private function createErrorResult(int $errorCode, string $errorMessage, string $stackTrace = ''): array + { + return [ + 'result' => 1, + 'error_code' => $errorCode, + 'error_message' => $errorMessage, + 'stack_trace' => $stackTrace, + ]; + } +}