/dev/null 2>&1 */ class ShjFourBCheckCommand extends Command { /** * コマンド名と説明 * * @var string */ protected $signature = 'shj4b:check {--dry-run : 実際の処理を行わず対象のみ表示} {--limit=100 : 処理する最大件数} {--hours=24 : 指定時間以内の決済のみ対象}'; /** * コマンドの説明 * * @var string */ protected $description = 'SHJ-4B 兜底チェック - 未処理の決済トランザクションを検索してProcessSettlementJobをディスパッチ'; /** * SHJ-4B サービス * * @var ShjFourBService */ protected $shjFourBService; /** * コンストラクタ */ public function __construct(ShjFourBService $shjFourBService) { parent::__construct(); $this->shjFourBService = $shjFourBService; } /** * コマンド実行 * * @return int */ public function handle() { $startTime = now(); $isDryRun = $this->option('dry-run'); $limit = (int) $this->option('limit'); $hours = (int) $this->option('hours'); $this->info("SHJ-4B チェックコマンド開始"); $this->info("実行モード: " . ($isDryRun ? "ドライラン(実際の処理なし)" : "本実行")); $this->info("処理制限: {$limit}件"); $this->info("対象期間: {$hours}時間以内"); try { // 未処理の決済トランザクション取得 $unprocessedSettlements = $this->getUnprocessedSettlements($hours, $limit); $this->info("未処理決済トランザクション: " . $unprocessedSettlements->count() . "件"); if ($unprocessedSettlements->isEmpty()) { $this->info("処理対象なし"); // バッチログ作成 - 処理対象なし $this->createBatchLog('success', 0, 0, 'SHJ-4B チェック完了 - 処理対象なし'); return 0; } // 対象一覧表示 $this->displayTargets($unprocessedSettlements); if ($isDryRun) { $this->info("ドライランモードのため、実際の処理はスキップします"); // バッチログ作成 - ドライラン $this->createBatchLog('success', 0, 0, 'SHJ-4B チェック完了 - ドライラン(' . $unprocessedSettlements->count() . '件検出)'); return 0; } // 実際の処理実行 $processed = $this->processSettlements($unprocessedSettlements); $this->info("処理完了: {$processed['success']}件成功, {$processed['failed']}件失敗"); // バッチログ作成 - 処理完了 $status = $processed['failed'] > 0 ? 'error' : 'success'; $message = "SHJ-4B チェック完了 - 成功:{$processed['success']}件, 失敗:{$processed['failed']}件"; $this->createBatchLog($status, $processed['success'], $processed['failed'], $message); return $processed['failed'] > 0 ? 1 : 0; } catch (\Throwable $e) { $this->error("SHJ-4B チェック処理でエラーが発生しました: " . $e->getMessage()); Log::error('SHJ-4B チェックコマンドエラー', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); // バッチログ作成 - エラー $this->createBatchLog('error', 0, 1, 'SHJ-4B チェック失敗: ' . $e->getMessage()); return 1; } } /** * 未処理の決済トランザクション取得 * * @param int $hours * @param int $limit * @return \Illuminate\Database\Eloquent\Collection */ private function getUnprocessedSettlements(int $hours, int $limit) { $cutoffTime = Carbon::now()->subHours($hours); // 条件: // 1. 指定時間以内に作成された // 2. contract_payment_numberがnullでない // 3. まだregular_contractのsettlement_transaction_idに関連付けられていない // 4. ProcessSettlementJobが実行されていない(batch_logで確認) $query = SettlementTransaction::where('created_at', '>=', $cutoffTime) ->whereNotNull('contract_payment_number') ->whereNotNull('pay_date') ->whereNotNull('settlement_amount') ->orderBy('created_at', 'asc'); $settlements = $query->limit($limit)->get(); // 既に処理済みのものを除外 $unprocessed = $settlements->filter(function ($settlement) { return !$this->isAlreadyProcessed($settlement); }); return $unprocessed; } /** * 既に処理済みかチェック * * @param SettlementTransaction $settlement * @return bool */ private function isAlreadyProcessed(SettlementTransaction $settlement): bool { // 1. regular_contractの同一contract_payment_numberが既に処理済みかチェック $linkedContract = DB::table('regular_contract') ->where('contract_payment_number', $settlement->contract_payment_number) ->whereNotNull('contract_payment_day') ->exists(); if ($linkedContract) { return true; } // 2. bat_job_logで処理完了記録があるかチェック $processedInBatch = BatJobLog::where('process_name', 'SHJ-4B') ->where('status', 'success') ->where('status_comment', 'like', '%settlement_transaction_id:' . $settlement->settlement_transaction_id . '%') ->exists(); if ($processedInBatch) { return true; } // 3. 現在キューに入っているかチェック(簡易版) // 注: より正確にはRedis/DBキューの内容を確認する必要がある $recentJobDispatched = BatJobLog::where('process_name', 'SHJ-4B') ->where('status_comment', 'like', '%settlement_transaction_id:' . $settlement->settlement_transaction_id . '%') ->where('created_at', '>=', Carbon::now()->subHours(1)) ->exists(); return $recentJobDispatched; } /** * 対象一覧表示 * * @param \Illuminate\Database\Eloquent\Collection $settlements */ private function displayTargets($settlements) { $this->info("対象の決済トランザクション:"); $this->table( ['ID', '契約支払番号', '決済金額', '支払日', '作成日時'], $settlements->map(function ($settlement) { return [ $settlement->settlement_transaction_id, $settlement->contract_payment_number, number_format($settlement->settlement_amount) . '円', Carbon::parse($settlement->pay_date)->format('Y-m-d H:i:s'), $settlement->created_at->format('Y-m-d H:i:s'), ]; })->toArray() ); } /** * 決済処理実行 * * @param \Illuminate\Database\Eloquent\Collection $settlements * @return array */ private function processSettlements($settlements): array { $success = 0; $failed = 0; $results = []; foreach ($settlements as $settlement) { try { $this->info("処理中: 決済トランザクションID {$settlement->settlement_transaction_id}"); // ProcessSettlementJobをディスパッチ ProcessSettlementJob::dispatch( $settlement->settlement_transaction_id, [ 'source' => 'shj4b_check_command', 'triggered_at' => now()->toISOString(), ] ); $success++; $results[] = [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'status' => 'dispatched', 'message' => 'ProcessSettlementJobディスパッチ成功', ]; $this->info("✓ 成功: {$settlement->settlement_transaction_id}"); } catch (\Throwable $e) { $failed++; $results[] = [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'status' => 'failed', 'error' => $e->getMessage(), ]; $this->error("✗ 失敗: {$settlement->settlement_transaction_id} - {$e->getMessage()}"); Log::error('SHJ-4B チェック 個別処理失敗', [ 'settlement_transaction_id' => $settlement->settlement_transaction_id, 'error' => $e->getMessage(), ]); } } return [ 'success' => $success, 'failed' => $failed, 'results' => $results, 'total' => $settlements->count(), ]; } /** * バッチログ作成 - 直接bat_job_log書き込み * * @param string $status * @param int $successCount * @param int $errorCount * @param string $message */ private function createBatchLog(string $status, int $successCount, int $errorCount, string $message): void { try { $device = Device::orderBy('device_id')->first(); $deviceId = $device ? $device->device_id : 1; BatJobLog::create([ 'device_id' => $deviceId, 'process_name' => 'SHJ-4B-CHECK', 'job_name' => 'SHJ-4Bチェックコマンド', 'status' => $status, 'status_comment' => $message . " (成功:{$successCount}/失敗:{$errorCount})", 'created_at' => now()->startOfDay(), 'updated_at' => now()->startOfDay() ]); } catch (\Exception $e) { Log::error('SHJ-4B-CHECK バッチログ作成エラー', [ 'error' => $e->getMessage(), 'status' => $status, 'message' => $message ]); } } }