diff --git a/app/Console/Commands/ShjThreeCommand.php b/app/Console/Commands/ShjThreeCommand.php new file mode 100644 index 0000000..1af4430 --- /dev/null +++ b/app/Console/Commands/ShjThreeCommand.php @@ -0,0 +1,108 @@ +shjThreeService = $shjThreeService; + } + + /** + * コンソールコマンドを実行 + * + * 処理フロー: + * 1. 駐輪場マスタ情報取得 + * 2. 各駐輪場の実行タイミングチェック + * 3. 定期更新対象者取得とリマインダー送信 + * 4. バッチログ作成 + * 5. 処理結果返却 + * + * @return int + */ + public function handle() + { + try { + // 開始ログ出力 + $startTime = now(); + $this->info('SHJ-3 定期更新リマインダー処理を開始します。'); + + Log::info('SHJ-3 定期更新リマインダー処理開始', [ + 'start_time' => $startTime + ]); + + // SHJ-3メイン処理実行 + $result = $this->shjThreeService->executeReminderProcess(); + + $endTime = now(); + $this->info('SHJ-3 定期更新リマインダー処理が完了しました。'); + $this->info("処理時間: {$startTime->diffInSeconds($endTime)}秒"); + + // 処理結果表示 + $this->line('=== 処理結果 ==='); + $this->line("対象駐輪場数: {$result['processed_parks_count']}"); + $this->line("対象者総数: {$result['total_target_users']}"); + $this->line("メール送信成功: {$result['mail_success_count']}件"); + $this->line("メール送信失敗: {$result['mail_error_count']}件"); + $this->line("オペレーターキュー追加: {$result['operator_queue_count']}件"); + + Log::info('SHJ-3 定期更新リマインダー処理完了', [ + 'end_time' => $endTime, + 'duration_seconds' => $startTime->diffInSeconds($endTime), + 'result' => $result + ]); + + return $result['success'] ? self::SUCCESS : self::FAILURE; + + } catch (\Exception $e) { + $this->error('SHJ-3 定期更新リマインダー処理で予期しないエラーが発生しました: ' . $e->getMessage()); + + Log::error('SHJ-3 定期更新リマインダー処理例外エラー', [ + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return self::FAILURE; + } + } +} diff --git a/app/Services/ShjThreeService.php b/app/Services/ShjThreeService.php new file mode 100644 index 0000000..53cb893 --- /dev/null +++ b/app/Services/ShjThreeService.php @@ -0,0 +1,796 @@ +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でエラーが発生してもメイン処理は継続 + // エラーログのみ出力 + } + } +}