/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}時間以内"); // バッチログ作成 $batch = BatchLog::createBatchLog( 'shj4b_check', BatchLog::STATUS_START, [ 'command' => 'shj4b:check', 'options' => [ 'dry_run' => $isDryRun, 'limit' => $limit, 'hours' => $hours, ], 'start_time' => $startTime, ], 'SHJ-4B チェックコマンド開始' ); try { // 未処理の決済トランザクション取得 $unprocessedSettlements = $this->getUnprocessedSettlements($hours, $limit); $this->info("未処理決済トランザクション: " . $unprocessedSettlements->count() . "件"); if ($unprocessedSettlements->isEmpty()) { $this->info("処理対象なし"); $batch->update([ 'status' => BatchLog::STATUS_SUCCESS, 'end_time' => now(), 'message' => 'SHJ-4B チェック完了 - 処理対象なし', 'success_count' => 0, ]); return 0; } // 対象一覧表示 $this->displayTargets($unprocessedSettlements); if ($isDryRun) { $this->info("ドライランモードのため、実際の処理はスキップします"); $batch->update([ 'status' => BatchLog::STATUS_SUCCESS, 'end_time' => now(), 'message' => 'SHJ-4B チェック完了 - ドライラン', 'success_count' => 0, 'parameters' => json_encode(['targets' => $unprocessedSettlements->pluck('settlement_transaction_id')->toArray()]), ]); return 0; } // 実際の処理実行 $processed = $this->processSettlements($unprocessedSettlements); $this->info("処理完了: {$processed['success']}件成功, {$processed['failed']}件失敗"); $batch->update([ 'status' => $processed['failed'] > 0 ? BatchLog::STATUS_ERROR : BatchLog::STATUS_SUCCESS, 'end_time' => now(), 'message' => "SHJ-4B チェック完了 - 成功:{$processed['success']}件, 失敗:{$processed['failed']}件", 'success_count' => $processed['success'], 'error_count' => $processed['failed'], 'parameters' => json_encode($processed), ]); 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(), ]); $batch->update([ 'status' => BatchLog::STATUS_ERROR, 'end_time' => now(), 'message' => 'SHJ-4B チェック失敗: ' . $e->getMessage(), 'error_details' => $e->getTraceAsString(), 'error_count' => 1, ]); 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. batch_logで処理完了記録があるかチェック $processedInBatch = BatchLog::where('process_name', 'shj4b') ->where('status', BatchLog::STATUS_SUCCESS) ->where('parameters', 'like', '%"settlement_transaction_id":' . $settlement->settlement_transaction_id . '%') ->exists(); if ($processedInBatch) { return true; } // 3. 現在キューに入っているかチェック(簡易版) // 注: より正確にはRedis/DBキューの内容を確認する必要がある $recentJobDispatched = BatchLog::where('process_name', 'shj4b') ->where('parameters', '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(), ]; } }