318 lines
11 KiB
PHP
318 lines
11 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use Illuminate\Console\Command;
|
||
use App\Models\SettlementTransaction;
|
||
use App\Models\BatJobLog;
|
||
use App\Models\Device;
|
||
use App\Jobs\ProcessSettlementJob;
|
||
use App\Services\ShjFourBService;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Queue;
|
||
use Carbon\Carbon;
|
||
|
||
/**
|
||
* SHJ-4B チェックコマンド
|
||
*
|
||
* 未処理の決済トランザクションを検索し、ProcessSettlementJobをディスパッチする兜底処理
|
||
*
|
||
* 実行方法:
|
||
* php artisan shj4b:check
|
||
* php artisan shj4b:check --dry-run # 実際の処理は行わず、対象のみ表示
|
||
* php artisan shj4b:check --limit=50 # 処理件数制限
|
||
*
|
||
* Cron設定例(10分毎実行):
|
||
* 0,10,20,30,40,50 * * * * cd /path/to/project && php artisan shj4b:check > /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
|
||
]);
|
||
}
|
||
}
|
||
}
|