shjMailSendService = $shjMailSendService; $this->shjEightService = $shjEightService; } /** * 【処理1】定期契約マスタより未払い者を取得する * * SQL条件: * - 解約フラグ = 0 (未解約) * - 授受フラグ = 0 (授受フラグOFF) * - 請求金額 > 0 (請求金額あり) * * @return array 未払い者リスト */ public function getUnpaidUsers(): array { try { $query = DB::table('regular_contract as T1') ->select([ 'T1.contract_id', // 定期契約ID 'T2.user_seq', // 利用者ID 'T2.user_name', // 利用者名 'T2.user_manual_regist_flag', // 手動登録フラグ 'T2.user_primemail', // メールアドレス 'T2.user_submail', // 予備メールアドレス 'T1.park_id', // 駐輪場ID (regular_contractテーブルから) 'T3.park_name', // 駐輪場名 'T1.billing_amount' // 請求金額 ]) ->join('user as T2', 'T1.user_id', '=', 'T2.user_seq') ->join('park as T3', 'T1.park_id', '=', 'T3.park_id') ->where([ ['T1.contract_cancel_flag', '=', 0], // 解約フラグ = 0 ['T1.contract_flag', '=', 0], // 授受フラグ = 0 ]) ->where('T1.billing_amount', '>', 0) // 請求金額 > 0 ->get(); Log::info('SHJ-12 未払い者取得完了', [ 'count' => $query->count(), 'sql_conditions' => [ 'contract_cancel_flag' => 0, 'contract_flag' => 0, 'billing_amount' => '> 0' ] ]); return $query->toArray(); } catch (\Exception $e) { Log::error('SHJ-12 未払い者取得エラー', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); throw $e; } } /** * 【処理2】未払い者への通知、またはオペレーターキュー追加処理 * * 仕様: * - 手動登録フラグ = 0 (Web申込) → SHJ-7メール送信のみ * - 手動登録フラグ ≠ 0 (その他) → オペレーターキュー追加のみ * * @param array $unpaidUsers 未払い者リスト * @return array 処理結果 */ public function processUnpaidUserNotifications(array $unpaidUsers): array { // 内部変数(カウンタ)初期化 $mailSuccessCount = 0; $mailErrorCount = 0; $queueSuccessCount = 0; $queueErrorCount = 0; $batchComments = []; // バッチコメント(エラー情報)累積用 $processParameters = []; try { DB::beginTransaction(); // 処理1の取得レコード数分繰り返し foreach ($unpaidUsers as $user) { try { // 【判定】手動登録フラグによる分岐 if ($user->user_manual_regist_flag == 0) { // Web申込 → SHJ-7メール送信 $mailResult = $this->sendNotificationMailViaShj7($user); // 処理結果判定: result === 0 → 正常 if (($mailResult['result'] ?? 1) === 0) { $mailSuccessCount++; } else { $mailErrorCount++; // バッチコメントにSHJ-7異常情報を設定(後ろに足す) $batchComments[] = sprintf( 'SHJ-7メール送信: 契約ID:%s メール送信失敗: %s', $user->contract_id, $mailResult['error_info'] ?? 'Unknown error' ); } // 処理パラメータ記録 $processParameters[] = [ 'contract_id' => $user->contract_id, 'user_seq' => $user->user_seq, 'billing_amount' => $user->billing_amount, 'process_type' => 'mail', 'result' => ($mailResult['result'] ?? 1) === 0 ? 'success' : 'error' ]; } else { // その他(手動登録)→ オペレーターキュー追加 $queueResult = $this->addToOperatorQueue($user); if ($queueResult['success']) { $queueSuccessCount++; } else { $queueErrorCount++; // バッチコメントに異常情報を設定(後ろに足す) $batchComments[] = sprintf( 'キュー登録: 契約ID:%s キュー登録失敗: %s', $user->contract_id, $queueResult['message'] ?? 'Unknown error' ); } // 処理パラメータ記録 $processParameters[] = [ 'contract_id' => $user->contract_id, 'user_seq' => $user->user_seq, 'billing_amount' => $user->billing_amount, 'process_type' => 'queue', 'result' => $queueResult['success'] ? 'success' : 'error' ]; } } catch (\Exception $e) { // 個別エラーを記録して次の繰り返し処理へ $batchComments[] = sprintf( '契約ID:%s 処理エラー: %s', $user->contract_id, $e->getMessage() ); Log::warning('SHJ-12 個別処理エラー', [ 'contract_id' => $user->contract_id, 'user_seq' => $user->user_seq, 'error' => $e->getMessage() ]); } } DB::commit(); // ステータスコメント構築 $statusComment = $this->buildStatusComment( $mailSuccessCount, $mailErrorCount, $queueSuccessCount, $queueErrorCount ); // バッチコメント整形(最大100件、超過分は省略) $formattedBatchComments = $this->formatBatchComments($batchComments); return [ 'success' => true, 'mail_success_count' => $mailSuccessCount, 'mail_error_count' => $mailErrorCount, 'queue_success_count' => $queueSuccessCount, 'queue_error_count' => $queueErrorCount, 'status_comment' => $statusComment, 'batch_comments' => $formattedBatchComments, 'parameters' => $processParameters, 'message' => '未払い者通知処理完了' ]; } catch (\Exception $e) { DB::rollBack(); Log::error('SHJ-12 通知処理全体エラー', [ 'error' => $e->getMessage(), 'processed_count' => count($processParameters) ]); return [ 'success' => false, 'mail_success_count' => $mailSuccessCount, 'mail_error_count' => $mailErrorCount, 'queue_success_count' => $queueSuccessCount, 'queue_error_count' => $queueErrorCount, 'status_comment' => 'システムエラー', 'batch_comments' => $batchComments, 'parameters' => $processParameters, 'message' => '通知処理エラー: ' . $e->getMessage(), 'details' => $e->getTraceAsString() ]; } } /** * SHJ-7 メール送信サービス経由でメール通知送信 * * 仕様書パラメータ: * - メールアドレス: 処理1.メールアドレス * - 予備メールアドレス: 処理1.予備メールアドレス * - 使用プログラムID: 204 * * @param object $user 未払い者情報 * @return array SHJ-7の処理結果 (result_code: 0=正常, 1=異常) */ private function sendNotificationMailViaShj7($user): array { try { // SHJ-7 executeMailSend 呼び出し $result = $this->shjMailSendService->executeMailSend( (string)($user->user_primemail ?? ''), // メールアドレス (string)($user->user_submail ?? ''), // 予備メールアドレス 204 // 使用プログラムID ); Log::info('SHJ-12 SHJ-7メール送信結果', [ 'contract_id' => $user->contract_id, 'user_seq' => $user->user_seq, 'result' => $result['result'] ?? 1, 'error_info' => $result['error_info'] ?? '' ]); return $result; } catch (\Exception $e) { Log::error('SHJ-12 SHJ-7メール送信エラー', [ 'contract_id' => $user->contract_id, 'error' => $e->getMessage() ]); return [ 'result' => 1, // 異常終了 'error_info' => 'SHJ-7メール送信エラー: ' . $e->getMessage() ]; } } /** * オペレーターキューへの追加 * * 仕様書SQL定義に基づき operator_que テーブルに登録 * - キュー種別ID: 8 (支払い催促) * - キューステータスID: 1 (キュー発生) * - que_id: MAX+1方式で生成(db_now.sqlは非AUTO_INCREMENT) * - 主キー衝突時に1回リトライ * * @param object $user 未払い者情報 * @return array 追加結果 */ private function addToOperatorQueue($user): array { $maxRetries = 2; // 最大2回試行(初回 + リトライ1回) $lastException = null; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { // オペレーターキューデータ構築 // 注: que_idはAUTO_INCREMENTのため、DBに委任(手動採番しない) $queueData = [ // 'que_id' は削除(AUTO_INCREMENTに委任) 'que_class' => 8, // キュー種別ID: 8=支払い催促 'user_id' => $user->user_seq, // 利用者ID 'contract_id' => $user->contract_id, // 定期契約ID 'park_id' => $user->park_id, // 駐輪場ID 'que_comment' => '', // キューコメント (空文字) 'que_status' => 1, // キューステータスID: 1=キュー発生 'que_status_comment' => '', // キューステータスコメント (空文字) 'work_instructions' => '', // 業務指示コメント (空文字) 'created_at' => now(), // 登録日時 'updated_at' => now(), // 更新日時 'operator_id' => null // 更新オペレータID (NULL: システム自動) ]; // operator_queテーブルに挿入(que_idはDBが自動採番) $newQueId = DB::table('operator_que')->insertGetId($queueData); Log::info('SHJ-12 オペレーターキュー追加完了', [ 'que_id' => $newQueId, 'contract_id' => $user->contract_id, 'user_seq' => $user->user_seq, 'park_id' => $user->park_id, 'que_class' => 8, 'que_status' => 1, 'attempt' => $attempt ]); return [ 'success' => true, 'message' => 'オペレーターキュー追加成功', 'que_id' => $newQueId ]; } catch (\Illuminate\Database\QueryException $e) { $lastException = $e; // 主キー重複エラー(Duplicate entry)の場合のみリトライ // SQLSTATEコード 23000 は整合性制約違反を示す if ($e->getCode() == 23000 && $attempt < $maxRetries) { Log::warning('SHJ-12 que_id重複検出、リトライします', [ 'attempt' => $attempt, 'max_retries' => $maxRetries, 'que_id' => $newQueId ?? null, 'contract_id' => $user->contract_id, 'error_code' => $e->getCode() ]); // 短時間待機後に再試行(100ms) usleep(100000); continue; } // その他のエラー、または最終試行での失敗時は例外をスロー throw $e; } catch (\Exception $e) { // QueryException以外の例外は即座にスロー $lastException = $e; throw $e; } } // ループを抜けた場合(通常は到達しない) Log::error('SHJ-12 オペレーターキュー追加エラー(最大リトライ回数超過)', [ 'contract_id' => $user->contract_id, 'user_seq' => $user->user_seq, 'max_retries' => $maxRetries, 'error' => $lastException ? $lastException->getMessage() : 'Unknown error', 'trace' => $lastException ? $lastException->getTraceAsString() : '' ]); return [ 'success' => false, 'message' => 'オペレーターキュー追加エラー(リトライ失敗): ' . ($lastException ? $lastException->getMessage() : 'Unknown error') ]; } /** * ステータスコメント構築 * * 仕様書に基づくステータスコメント形式: * - エラーなし: "メール送信成功:X件 / キュー登録成功:Y件" * - エラーあり: "メール送信成功:X件 / メール送信失敗:A件 / キュー登録成功:Y件 / キュー登録失敗:B件" * * @param int $mailSuccessCount メール正常終了件数 * @param int $mailErrorCount メール異常終了件数 * @param int $queueSuccessCount キュー登録正常終了件数 * @param int $queueErrorCount キュー登録異常終了件数 * @return string ステータスコメント */ private function buildStatusComment( int $mailSuccessCount, int $mailErrorCount, int $queueSuccessCount, int $queueErrorCount ): string { $parts = []; // メール送信結果 if ($mailSuccessCount > 0 || $mailErrorCount > 0) { if ($mailErrorCount > 0) { $parts[] = "メール送信成功:{$mailSuccessCount}件"; $parts[] = "メール送信失敗:{$mailErrorCount}件"; } else { $parts[] = "メール送信成功:{$mailSuccessCount}件"; } } // キュー登録結果 if ($queueSuccessCount > 0 || $queueErrorCount > 0) { if ($queueErrorCount > 0) { $parts[] = "キュー登録成功:{$queueSuccessCount}件"; $parts[] = "キュー登録失敗:{$queueErrorCount}件"; } else { $parts[] = "キュー登録成功:{$queueSuccessCount}件"; } } return implode(' / ', $parts); } /** * バッチコメント整形 * * エラー情報を最大100件に制限し、超過分は省略表記 * * @param array $batchComments エラー情報配列 * @return string 整形済みバッチコメント */ private function formatBatchComments(array $batchComments): string { if (empty($batchComments)) { return ''; } // 最大100件に制限 $limitedComments = array_slice($batchComments, 0, 100); // 超過分の件数を追加 if (count($batchComments) > 100) { $limitedComments[] = sprintf( '...他%d件省略', count($batchComments) - 100 ); } return implode("\n", $limitedComments); } /** * 【処理3】バッチ処理ログを作成する * * SHJ-8サービスを呼び出してbat_job_logテーブルに登録(業務固有のstatus_comment記録) * * @param string $status ステータス * @param array $parameters パラメータ * @param string $message メッセージ * @param int $executionCount 実行回数 * @param int $successCount 成功回数 * @param int $errorCount エラー回数 * @return void */ public function createBatchLog( string $status, array $parameters, string $message, int $executionCount = 0, int $successCount = 0, int $errorCount = 0 ): void { try { // デバイスIDを取得 $device = Device::orderBy('device_id')->first(); $deviceId = $device ? $device->device_id : 1; // status_commentを構築(業務情報を含む) $statusComment = sprintf( '%s (実行:%d/成功:%d/エラー:%d)', $message, $executionCount, $successCount, $errorCount ); $today = now()->format('Y/m/d'); Log::info('SHJ-8 バッチ処理ログ作成', [ 'device_id' => $deviceId, 'status' => $status, 'status_comment' => $statusComment ]); // SHJ-8サービスを呼び出し $this->shjEightService->execute( $deviceId, 'SHJ-12', 'SHJ-12未払い者通知', $status, $statusComment, $today, $today ); } catch (\Exception $e) { Log::error('SHJ-8 バッチログ作成エラー', [ 'error' => $e->getMessage(), 'status' => $status, 'message' => $message ]); } } }