parkModel = $parkModel; $this->userModel = $userModel; $this->contractModel = $contractModel; $this->batchLogModel = $batchLogModel; $this->mailSendService = $mailSendService; } /** * SHJ-3 定期更新リマインダー処理メイン実行 * * 処理フロー: * 【処理0】駐輪場マスタの情報を取得する * 【判断0】当該駐輪場実行タイミングチェック * 【処理2】定期更新対象者を取得する * 【判断2】利用者有無をチェック * 【処理3】対象者向けにメール送信、またはオペレーターキュー追加処理 * 【処理4】バッチ処理ログを作成する * * @return array 処理結果 */ public function executeReminderProcess(): array { $batchLogId = null; $processedParksCount = 0; $totalTargetUsers = 0; $mailSuccessCount = 0; $mailErrorCount = 0; $operatorQueueCount = 0; try { // バッチ処理開始ログ作成 $batchLog = BatchLog::createBatchLog( 'shj3', BatchLog::STATUS_START, [], 'SHJ-3 定期更新リマインダー処理開始' ); $batchLogId = $batchLog->id; Log::info('SHJ-3 定期更新リマインダー処理開始', [ 'batch_log_id' => $batchLogId ]); // 【処理0】駐輪場マスタの情報を取得する $parkList = $this->getParkMasterInfo(); if (empty($parkList)) { $message = '対象の駐輪場マスタが見つかりません'; $batchLog->update([ 'status' => BatchLog::STATUS_ERROR, 'end_time' => now(), 'message' => $message, 'error_details' => $message, 'error_count' => 1 ]); return [ 'success' => false, 'message' => $message, 'processed_parks_count' => 0, 'total_target_users' => 0, 'mail_success_count' => 0, 'mail_error_count' => 0, 'operator_queue_count' => 0, 'batch_log_id' => $batchLogId ]; } // 各駐輪場に対する処理ループ foreach ($parkList as $park) { Log::info('駐輪場処理開始', [ 'park_id' => $park->park_id, 'park_name' => $park->park_name ]); // 【判断0】当該駐輪場実行タイミングチェック $timingCheckResult = $this->checkExecutionTiming($park); if (!$timingCheckResult['should_execute']) { Log::info('実行タイミング対象外', [ 'park_id' => $park->park_id, 'reason' => $timingCheckResult['reason'] ]); continue; } $processedParksCount++; // 【処理2】定期更新対象者を取得する $targetUsers = $this->getRegularUpdateTargetUsers($park->park_id); // 【判断2】利用者有無をチェック if (empty($targetUsers)) { Log::info('利用者なし', [ 'park_id' => $park->park_id ]); continue; } $totalTargetUsers += count($targetUsers); // 【処理3】対象者向けにメール送信、またはオペレーターキュー追加処理 foreach ($targetUsers as $targetUser) { $processResult = $this->processTargetUser($targetUser); if ($processResult['type'] === 'mail_success') { $mailSuccessCount++; } elseif ($processResult['type'] === 'mail_error') { $mailErrorCount++; } elseif ($processResult['type'] === 'operator_queue') { $operatorQueueCount++; } } Log::info('駐輪場処理完了', [ 'park_id' => $park->park_id, 'target_users_count' => count($targetUsers) ]); } // 【処理4】バッチ処理ログを作成する(SHJ-8呼び出し) $this->createShjBatchLog([ 'processed_parks_count' => $processedParksCount, 'total_target_users' => $totalTargetUsers, 'mail_success_count' => $mailSuccessCount, 'mail_error_count' => $mailErrorCount, 'operator_queue_count' => $operatorQueueCount ]); // バッチ処理完了ログ更新 $batchLog->update([ 'status' => BatchLog::STATUS_SUCCESS, 'end_time' => now(), 'message' => 'SHJ-3 定期更新リマインダー処理正常完了', 'success_count' => 1, 'parameters' => [ 'processed_parks_count' => $processedParksCount, 'total_target_users' => $totalTargetUsers, 'mail_success_count' => $mailSuccessCount, 'mail_error_count' => $mailErrorCount, 'operator_queue_count' => $operatorQueueCount, 'executed_at' => now()->toISOString() ] ]); Log::info('SHJ-3 定期更新リマインダー処理完了', [ 'batch_log_id' => $batchLogId, 'processed_parks_count' => $processedParksCount, 'total_target_users' => $totalTargetUsers ]); return [ 'success' => true, 'message' => 'SHJ-3 定期更新リマインダー処理が正常に完了しました', 'processed_parks_count' => $processedParksCount, 'total_target_users' => $totalTargetUsers, 'mail_success_count' => $mailSuccessCount, 'mail_error_count' => $mailErrorCount, 'operator_queue_count' => $operatorQueueCount, 'batch_log_id' => $batchLogId ]; } catch (\Exception $e) { $errorMessage = 'SHJ-3 定期更新リマインダー処理でエラーが発生: ' . $e->getMessage(); if (isset($batchLog) && $batchLog) { $batchLog->update([ 'status' => BatchLog::STATUS_ERROR, 'end_time' => now(), 'message' => $errorMessage, 'error_details' => $e->getMessage(), 'error_count' => 1 ]); } Log::error('SHJ-3 定期更新リマインダー処理エラー', [ 'batch_log_id' => $batchLogId, 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return [ 'success' => false, 'message' => $errorMessage, 'details' => $e->getMessage(), 'processed_parks_count' => $processedParksCount, 'total_target_users' => $totalTargetUsers, 'mail_success_count' => $mailSuccessCount, 'mail_error_count' => $mailErrorCount, 'operator_queue_count' => $operatorQueueCount, 'batch_log_id' => $batchLogId ]; } } /** * 【処理0】駐輪場マスタの情報を取得する * * 仕様書に基づくSQLクエリ: * SELECT 駐輪場ID, 駐輪場名, 更新期間開始日, 更新期間開始時, * 更新期間終了日, 更新期間終了時, リマインダー種別, リマインダー時間 * FROM 駐輪場マスタ * WHERE 閉設フラグ = 0 * ORDER BY 駐輪場ふりがな asc * * @return array 駐輪場マスタ情報 */ private function getParkMasterInfo(): array { try { $parkInfo = DB::table('park') ->select([ 'park_id', 'park_name', 'renew_start_date', 'renew_start_time', 'renew_end_date', 'renew_end_time', 'reminder_type', 'reminder_time' ]) ->where('park_close_flag', 0) ->orderBy('park_ruby', 'asc') ->get() ->toArray(); Log::info('駐輪場マスタ情報取得完了', [ 'park_count' => count($parkInfo) ]); return $parkInfo; } catch (\Exception $e) { Log::error('駐輪場マスタ情報取得エラー', [ 'error' => $e->getMessage() ]); throw $e; } } /** * 【判断0】当該駐輪場実行タイミングチェック * * 仕様書に基づく複雑なリマインダー時期判定: * - パターンA: 月を跨らない場合(更新期間開始日 <= 更新期間終了日) * - パターンB: 月を跨る場合(更新期間開始日 > 更新期間終了日) * - リマインダー種別による実行判定 * * @param object $park 駐輪場情報 * @return array 実行タイミング判定結果 */ private function checkExecutionTiming($park): array { try { $today = Carbon::now(); $currentDate = $today->format('Y-m-d'); // 更新期間の日付を取得 $startDate = Carbon::parse($park->renew_start_date); $endDate = Carbon::parse($park->renew_end_date); Log::info('実行タイミングチェック開始', [ 'park_id' => $park->park_id, 'current_date' => $currentDate, 'start_date' => $startDate->format('Y-m-d'), 'end_date' => $endDate->format('Y-m-d'), 'reminder_type' => $park->reminder_type ]); // パターン判定: 月を跨るかどうか $isPatternA = $startDate->lte($endDate); // パターンA: 月を跨らない $isPatternB = !$isPatternA; // パターンB: 月を跨る // 現在日付が更新期間内かチェック $isWithinUpdatePeriod = false; if ($isPatternA) { // パターンA: 更新期間開始日 <= 現在日 <= 更新期間終了日 $isWithinUpdatePeriod = $today->between($startDate, $endDate); } else { // パターンB: 月を跨る場合の判定 // 現在日 >= 更新期間開始日 OR 現在日 <= 更新期間終了日 $isWithinUpdatePeriod = $today->gte($startDate) || $today->lte($endDate); } if (!$isWithinUpdatePeriod) { return [ 'should_execute' => false, 'reason' => '更新期間外のため実行対象外', 'pattern' => $isPatternA ? 'A' : 'B' ]; } // リマインダー種別による実行判定 $reminderExecutionResult = $this->checkReminderTiming($park, $today, $startDate, $endDate, $isPatternA); Log::info('実行タイミングチェック完了', [ 'park_id' => $park->park_id, 'should_execute' => $reminderExecutionResult['should_execute'], 'pattern' => $isPatternA ? 'A' : 'B', 'reminder_result' => $reminderExecutionResult ]); return $reminderExecutionResult; } catch (\Exception $e) { Log::error('実行タイミングチェックエラー', [ 'park_id' => $park->park_id, 'error' => $e->getMessage() ]); throw $e; } } /** * リマインダー時期の詳細判定 * * 仕様書の複雑なリマインダー種別判定を実装 * パターンA/Bに基づく詳細な日付計算ロジック * * @param object $park 駐輪場情報 * @param Carbon $today 現在日 * @param Carbon $startDate 更新期間開始日 * @param Carbon $endDate 更新期間終了日 * @param bool $isPatternA パターンAかどうか * @return array リマインダー実行判定結果 */ private function checkReminderTiming($park, Carbon $today, Carbon $startDate, Carbon $endDate, bool $isPatternA): array { $reminderType = $park->reminder_type ?? 0; // リマインダー種別 = 0 の場合は実行しない if ($reminderType == 0) { return [ 'should_execute' => false, 'reason' => 'リマインダー種別=0のため実行対象外' ]; } // 仕様書に基づく詳細なリマインダー時期判定 $executionCheck = $this->performDetailedReminderCheck($park, $today, $startDate, $endDate, $isPatternA); return [ 'should_execute' => $executionCheck['should_execute'], 'reason' => $executionCheck['reason'], 'reminder_type' => $reminderType, 'pattern' => $isPatternA ? 'A' : 'B', 'execution_details' => $executionCheck ]; } /** * 仕様書に基づく詳細なリマインダー実行判定 * * 複雑な条件分岐を含む実行フラグ判定処理 * - パターンA/Bによる分岐 * - リマインダー種別による日数計算 * - 月跨ぎ処理の考慮 * * @param object $park 駐輪場情報 * @param Carbon $today 現在日 * @param Carbon $startDate 更新期間開始日 * @param Carbon $endDate 更新期間終了日 * @param bool $isPatternA パターンAかどうか * @return array 実行判定詳細結果 */ private function performDetailedReminderCheck($park, Carbon $today, Carbon $startDate, Carbon $endDate, bool $isPatternA): array { $reminderType = $park->reminder_type ?? 0; // 内部変数 更新パターン判定 $updatePattern = ''; if ($isPatternA) { // パターンA: 月を跨らない場合 if ($startDate->lte($endDate)) { $updatePattern = 'A'; } else { $updatePattern = 'B'; // 実際はパターンBになる } } else { // パターンB: 月を跨る場合 $updatePattern = 'B'; } // 実行フラグ判定処理 $executionFlag = $this->calculateExecutionFlag($today, $startDate, $endDate, $reminderType, $updatePattern); Log::info('詳細リマインダー判定完了', [ 'park_id' => $park->park_id, 'reminder_type' => $reminderType, 'update_pattern' => $updatePattern, 'execution_flag' => $executionFlag, 'today' => $today->format('Y-m-d'), 'start_date' => $startDate->format('Y-m-d'), 'end_date' => $endDate->format('Y-m-d') ]); return [ 'should_execute' => $executionFlag['should_execute'], 'reason' => $executionFlag['reason'], 'update_pattern' => $updatePattern, 'execution_details' => $executionFlag ]; } /** * 実行フラグ計算処理 * * 仕様書の複雑な分岐条件に基づく実行判定 * * @param Carbon $today 現在日 * @param Carbon $startDate 更新期間開始日 * @param Carbon $endDate 更新期間終了日 * @param int $reminderType リマインダー種別 * @param string $updatePattern 更新パターン(A/B) * @return array 実行フラグ判定結果 */ private function calculateExecutionFlag(Carbon $today, Carbon $startDate, Carbon $endDate, int $reminderType, string $updatePattern): array { // リマインダー種別による実行判定 switch ($reminderType) { case 1: // -1日前 return $this->checkReminderType1($today, $startDate, $endDate, $updatePattern); case 2: // -2日前 return $this->checkReminderType2($today, $startDate, $endDate, $updatePattern); default: return [ 'should_execute' => false, 'reason' => "未対応のリマインダー種別: {$reminderType}" ]; } } /** * リマインダー種別=1(-1日前)の判定 * * @param Carbon $today 現在日 * @param Carbon $startDate 更新期間開始日 * @param Carbon $endDate 更新期間終了日 * @param string $updatePattern 更新パターン * @return array 判定結果 */ private function checkReminderType1(Carbon $today, Carbon $startDate, Carbon $endDate, string $updatePattern): array { // 更新期間終了日の1日前が実行日 $executionDate = $endDate->copy()->subDay(); if ($today->isSameDay($executionDate)) { return [ 'should_execute' => true, 'reason' => '-1日前のリマインダー実行日', 'execution_date' => $executionDate->format('Y-m-d') ]; } return [ 'should_execute' => false, 'reason' => "リマインダー実行日({$executionDate->format('Y-m-d')})ではない(現在: {$today->format('Y-m-d')})", 'execution_date' => $executionDate->format('Y-m-d') ]; } /** * リマインダー種別=2(-2日前)の判定 * * @param Carbon $today 現在日 * @param Carbon $startDate 更新期間開始日 * @param Carbon $endDate 更新期間終了日 * @param string $updatePattern 更新パターン * @return array 判定結果 */ private function checkReminderType2(Carbon $today, Carbon $startDate, Carbon $endDate, string $updatePattern): array { // 更新期間終了日の2日前が実行日 $executionDate = $endDate->copy()->subDays(2); if ($today->isSameDay($executionDate)) { return [ 'should_execute' => true, 'reason' => '-2日前のリマインダー実行日', 'execution_date' => $executionDate->format('Y-m-d') ]; } return [ 'should_execute' => false, 'reason' => "リマインダー実行日({$executionDate->format('Y-m-d')})ではない(現在: {$today->format('Y-m-d')})", 'execution_date' => $executionDate->format('Y-m-d') ]; } /** * 【処理2】定期更新対象者を取得する * * 仕様書に基づく複雑なSQLクエリ: * 定期契約マスタ T1 と 利用者マスタ T2 を結合して * 更新対象者の情報を取得する * * @param int $parkId 駐輪場ID * @return array 定期更新対象者情報 */ private function getRegularUpdateTargetUsers(int $parkId): array { try { $currentDate = Carbon::now()->format('Y-m-d'); // 仕様書に記載されたSQLクエリに基づく対象者取得 $targetUsers = DB::table('regular_contract as T1') ->select([ 'T1.contract_id as 定期契約内ID', 'T1.park_id as 駐輪場ID', 'T2.user_id as 利用者ID', 'T2.user_manual_regist_flag as 手動登録フラグ', 'T2.user_primemail as メールアドレス', 'T2.user_submail as 予備メールアドレス', 'T2.user_name as 氏名', 'T1.contract_periode as 有効期間E' ]) ->join('user as T2', 'T1.user_id', '=', 'T2.user_id') ->where('T1.park_id', $parkId) ->where('T1.contract_periode', '<=', $currentDate) // 更新可能日チェック ->where('T1.contract_cancel_flag', 0) // 解約フラグ = 0 ->where('T2.user_quit_flag', 0) // 退会フラグ = 0 ->where('T1.contract_flag', 1) // 承認フラグ = 1 ->whereNull('T1.contract_permission') // 更新済フラグ is null ->get() ->toArray(); Log::info('定期更新対象者取得完了', [ 'park_id' => $parkId, 'target_users_count' => count($targetUsers) ]); return $targetUsers; } catch (\Exception $e) { Log::error('定期更新対象者取得エラー', [ 'park_id' => $parkId, 'error' => $e->getMessage() ]); throw $e; } } /** * 【処理3】対象者の処理実行 * * 手動登録フラグによって処理を分岐: * - = 0 (ウェブ申込み): SHJ-7メール送信を呼び出し * - その他: 内部変数のカウントアップ(オペレーターキュー処理) * * @param object $targetUser 対象者情報 * @return array 処理結果 */ private function processTargetUser($targetUser): array { try { $manualRegistFlag = $targetUser->手動登録フラグ ?? 1; if ($manualRegistFlag == 0) { // ウェブ申込み: SHJ-7メール送信処理 return $this->sendReminderMail($targetUser); } else { // その他: オペレーターキュー追加処理(内部変数カウントアップ) Log::info('オペレーターキュー対象者', [ 'user_id' => $targetUser->利用者ID, 'contract_id' => $targetUser->定期契約内ID, 'manual_regist_flag' => $manualRegistFlag ]); return [ 'type' => 'operator_queue', 'success' => true, 'message' => 'オペレーターキュー対象として処理' ]; } } catch (\Exception $e) { Log::error('対象者処理エラー', [ 'user_id' => $targetUser->利用者ID ?? 'unknown', 'error' => $e->getMessage() ]); return [ 'type' => 'error', 'success' => false, 'message' => $e->getMessage() ]; } } /** * リマインダーメール送信処理 * * SHJ-7メール送信処理を呼び出し、使用プログラムID=200を使用 * * @param object $targetUser 対象者情報 * @return array 送信結果 */ private function sendReminderMail($targetUser): array { try { $mailAddress = $targetUser->メールアドレス ?? ''; $backupMailAddress = $targetUser->予備メールアドレス ?? ''; $mailTemplateId = 200; // 使用プログラムID Log::info('SHJ-7メール送信処理呼び出し', [ 'user_id' => $targetUser->利用者ID, 'contract_id' => $targetUser->定期契約内ID, 'mail_address' => $mailAddress, 'backup_mail_address' => $backupMailAddress, 'mail_template_id' => $mailTemplateId ]); // SHJ-7メール送信処理実行 $mailResult = $this->mailSendService->executeMailSend( $mailAddress, $backupMailAddress, $mailTemplateId ); if ($mailResult['success']) { return [ 'type' => 'mail_success', 'success' => true, 'message' => 'メール送信成功', 'mail_result' => $mailResult ]; } else { return [ 'type' => 'mail_error', 'success' => false, 'message' => 'メール送信失敗: ' . $mailResult['message'], 'mail_result' => $mailResult ]; } } catch (\Exception $e) { Log::error('リマインダーメール送信エラー', [ 'user_id' => $targetUser->利用者ID ?? 'unknown', 'error' => $e->getMessage() ]); return [ 'type' => 'mail_error', 'success' => false, 'message' => 'メール送信エラー: ' . $e->getMessage() ]; } } /** * 【処理4】SHJ-8バッチ処理ログ作成 * * 仕様書に基づくSHJ-8共通処理呼び出し * * @param array $statistics 処理統計情報 * @return void */ private function createShjBatchLog(array $statistics): void { try { // 仕様書に基づくSHJ-8パラメータ設定 $deviceId = 9999; // テスト用デバイスID(規格書では"-"だが、既存実装に合わせて9999使用) $processName = 'SHJ-3定期更新リマインダー'; $jobName = 'success'; $status = 'success'; // ステータスコメント生成 $statusComment = "メール正常終了件数: {$statistics['mail_success_count']}" . " + メール異常終了件数: {$statistics['mail_error_count']}" . " + キュー登録正常終了件数: {$statistics['operator_queue_count']}"; $createdDate = now()->format('Y/m/d'); $updatedDate = now()->format('Y/m/d'); Log::info('SHJ-8バッチ処理ログ作成', [ 'device_id' => $deviceId, 'process_name' => $processName, 'job_name' => $jobName, 'status' => $status, 'status_comment' => $statusComment, 'created_date' => $createdDate, 'updated_date' => $updatedDate ]); // 共通処理SHJ-8バッチ処理ログ作成を呼び出し // 注意: 実際の運用では外部コマンド呼び出しまたは専用サービス経由で実行 BatchLog::createBatchLog( $processName, $status, [ 'device_id' => $deviceId, 'job_name' => $jobName, 'status_comment' => $statusComment, 'statistics' => $statistics, 'shj8_params' => [ 'device_id' => $deviceId, 'process_name' => $processName, 'job_name' => $jobName, 'status' => $status, 'created_date' => $createdDate, 'updated_date' => $updatedDate ] ], $statusComment ); } catch (\Exception $e) { Log::error('SHJ-8バッチ処理ログ作成エラー', [ 'error' => $e->getMessage(), 'statistics' => $statistics ]); // SHJ-8でエラーが発生してもメイン処理は継続 // エラーログのみ出力 } } }