diff --git a/app/Services/ShjThreeService.php b/app/Services/ShjThreeService.php index 53cb893..e4306b7 100644 --- a/app/Services/ShjThreeService.php +++ b/app/Services/ShjThreeService.php @@ -5,6 +5,7 @@ namespace App\Services; use App\Models\Park; use App\Models\User; use App\Models\RegularContract; +use App\Models\OperatorQue; use App\Models\Batch\BatchLog; use App\Services\ShjMailSendService; use Illuminate\Support\Facades\Log; @@ -40,6 +41,13 @@ class ShjThreeService */ protected $contractModel; + /** + * OperatorQue モデル + * + * @var OperatorQue + */ + protected $operatorQueModel; + /** * BatchLog モデル * @@ -60,6 +68,7 @@ class ShjThreeService * @param Park $parkModel * @param User $userModel * @param RegularContract $contractModel + * @param OperatorQue $operatorQueModel * @param BatchLog $batchLogModel * @param ShjMailSendService $mailSendService */ @@ -67,12 +76,14 @@ class ShjThreeService Park $parkModel, User $userModel, RegularContract $contractModel, + OperatorQue $operatorQueModel, BatchLog $batchLogModel, ShjMailSendService $mailSendService ) { $this->parkModel = $parkModel; $this->userModel = $userModel; $this->contractModel = $contractModel; + $this->operatorQueModel = $operatorQueModel; $this->batchLogModel = $batchLogModel; $this->mailSendService = $mailSendService; } @@ -80,52 +91,34 @@ class ShjThreeService /** * SHJ-3 定期更新リマインダー処理メイン実行 * - * 処理フロー: + * 処理フロー(仕様書準拠): * 【処理0】駐輪場マスタの情報を取得する * 【判断0】当該駐輪場実行タイミングチェック * 【処理2】定期更新対象者を取得する * 【判断2】利用者有無をチェック * 【処理3】対象者向けにメール送信、またはオペレーターキュー追加処理 - * 【処理4】バッチ処理ログを作成する + * 【処理4】バッチ処理ログを作成する(各駐輪場ごとに実行) * * @return array 処理結果 */ public function executeReminderProcess(): array { - $batchLogId = null; - $processedParksCount = 0; - $totalTargetUsers = 0; - $mailSuccessCount = 0; - $mailErrorCount = 0; - $operatorQueueCount = 0; + $overallProcessedParksCount = 0; + $overallTotalTargetUsers = 0; + $overallMailSuccessCount = 0; + $overallMailErrorCount = 0; + $overallQueueSuccessCount = 0; + $overallQueueErrorCount = 0; try { - // バッチ処理開始ログ作成 - $batchLog = BatchLog::createBatchLog( - 'shj3', - BatchLog::STATUS_START, - [], - 'SHJ-3 定期更新リマインダー処理開始' - ); - $batchLogId = $batchLog->id; - - Log::info('SHJ-3 定期更新リマインダー処理開始', [ - 'batch_log_id' => $batchLogId - ]); + Log::info('SHJ-3 定期更新リマインダー処理開始'); // 【処理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 - ]); + Log::warning($message); return [ 'success' => false, @@ -134,13 +127,19 @@ class ShjThreeService 'total_target_users' => 0, 'mail_success_count' => 0, 'mail_error_count' => 0, - 'operator_queue_count' => 0, - 'batch_log_id' => $batchLogId + 'operator_queue_count' => 0 ]; } - // 各駐輪場に対する処理ループ + // 取得レコード数分【判断0】を繰り返す foreach ($parkList as $park) { + // 各駐輪場ごとの内部変数(仕様書:場景A) + $mailSuccessCount = 0; + $mailErrorCount = 0; + $queueSuccessCount = 0; + $queueErrorCount = 0; + $batchComment = ''; + Log::info('駐輪場処理開始', [ 'park_id' => $park->park_id, 'park_name' => $park->park_name @@ -154,100 +153,112 @@ class ShjThreeService 'park_id' => $park->park_id, 'reason' => $timingCheckResult['reason'] ]); + // 次の駐輪場マスタへ continue; } - $processedParksCount++; + $overallProcessedParksCount++; // 【処理2】定期更新対象者を取得する - $targetUsers = $this->getRegularUpdateTargetUsers($park->park_id); + $targetUsers = $this->getRegularUpdateTargetUsers( + $park, + $timingCheckResult['update_pattern'] + ); // 【判断2】利用者有無をチェック if (empty($targetUsers)) { + // 仕様書:利用者なしの結果を設定する + $batchComment = "定期更新リマインダー:今月の定期更新対象者は無しです / {$park->park_name}"; + Log::info('利用者なし', [ - 'park_id' => $park->park_id + 'park_id' => $park->park_id, + 'batch_comment' => $batchComment ]); + + // 【処理4】バッチ処理ログを作成する + $this->createShjBatchLog( + $park, + $batchComment, + $mailSuccessCount, + $mailErrorCount, + $queueSuccessCount, + $queueErrorCount + ); + + // 次の駐輪場マスタへ continue; } - $totalTargetUsers += count($targetUsers); + $overallTotalTargetUsers += count($targetUsers); - // 【処理3】対象者向けにメール送信、またはオペレーターキュー追加処理 + // 【処理3】処理2の対象レコード数分繰り返す foreach ($targetUsers as $targetUser) { - $processResult = $this->processTargetUser($targetUser); + $processResult = $this->processTargetUser($targetUser, $park); if ($processResult['type'] === 'mail_success') { $mailSuccessCount++; } elseif ($processResult['type'] === 'mail_error') { $mailErrorCount++; - } elseif ($processResult['type'] === 'operator_queue') { - $operatorQueueCount++; + // バッチコメントに異常情報を追加 + if (!empty($processResult['error_info'])) { + $batchComment .= ($batchComment ? ' / ' : '') . $processResult['error_info']; + } + } elseif ($processResult['type'] === 'queue_success') { + $queueSuccessCount++; + } elseif ($processResult['type'] === 'queue_error') { + $queueErrorCount++; } } + // 全体集計用に加算 + $overallMailSuccessCount += $mailSuccessCount; + $overallMailErrorCount += $mailErrorCount; + $overallQueueSuccessCount += $queueSuccessCount; + $overallQueueErrorCount += $queueErrorCount; + Log::info('駐輪場処理完了', [ 'park_id' => $park->park_id, - 'target_users_count' => count($targetUsers) + 'target_users_count' => count($targetUsers), + 'mail_success' => $mailSuccessCount, + 'mail_error' => $mailErrorCount, + 'queue_success' => $queueSuccessCount, + 'queue_error' => $queueErrorCount ]); + + // 【処理4】バッチ処理ログを作成する(各駐輪場ごと) + $this->createShjBatchLog( + $park, + $batchComment, + $mailSuccessCount, + $mailErrorCount, + $queueSuccessCount, + $queueErrorCount + ); } - // 【処理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 + 'processed_parks_count' => $overallProcessedParksCount, + 'total_target_users' => $overallTotalTargetUsers, + 'mail_success_count' => $overallMailSuccessCount, + 'mail_error_count' => $overallMailErrorCount, + 'queue_success_count' => $overallQueueSuccessCount, + 'queue_error_count' => $overallQueueErrorCount ]); 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 + 'processed_parks_count' => $overallProcessedParksCount, + 'total_target_users' => $overallTotalTargetUsers, + 'mail_success_count' => $overallMailSuccessCount, + 'mail_error_count' => $overallMailErrorCount, + 'operator_queue_count' => $overallQueueSuccessCount + $overallQueueErrorCount ]; } 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() ]); @@ -256,12 +267,11 @@ class ShjThreeService '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 + 'processed_parks_count' => $overallProcessedParksCount, + 'total_target_users' => $overallTotalTargetUsers, + 'mail_success_count' => $overallMailSuccessCount, + 'mail_error_count' => $overallMailErrorCount, + 'operator_queue_count' => $overallQueueSuccessCount + $overallQueueErrorCount ]; } } @@ -283,17 +293,17 @@ class ShjThreeService 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' + 'park_id', // 駐輪場ID + 'park_name', // 駐輪場名 + 'update_grace_period_start_date', // 更新期間開始日(例:"20") + 'update_grace_period_start_time', // 更新期間開始時(例:"09:00") + 'update_grace_period_end_date', // 更新期間終了日(例:"6") + 'update_grace_period_end_time', // 更新期間終了時(例:"23:59") + 'reminder_type', // リマインダー種別(0=毎日,1=1日おき,2=2日おき) + 'reminder_time' // リマインダー時間(例:"09:00") ]) - ->where('park_close_flag', 0) - ->orderBy('park_ruby', 'asc') + ->where('park_close_flag', 0) // 閉設フラグ = 0 + ->orderBy('park_ruby', 'asc') // 駐輪場ふりがな 昇順 ->get() ->toArray(); @@ -315,10 +325,11 @@ class ShjThreeService /** * 【判断0】当該駐輪場実行タイミングチェック * - * 仕様書に基づく複雑なリマインダー時期判定: - * - パターンA: 月を跨らない場合(更新期間開始日 <= 更新期間終了日) - * - パターンB: 月を跨る場合(更新期間開始日 > 更新期間終了日) - * - リマインダー種別による実行判定 + * 仕様書に基づく実行タイミング判定: + * 1. リマインダー時間 = 現在の時間 のチェック + * 2. 内部変数.更新パターン の設定(A or B) + * 3. 内部変数.更新期間開始日からの経過日数 の算出 + * 4. 内部変数.実行フラグ の判定 * * @param object $park 駐輪場情報 * @return array 実行タイミング判定結果 @@ -326,284 +337,207 @@ class ShjThreeService 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); + $now = Carbon::now(); + $currentTime = $now->format('H:i'); + $todayDay = (int)$now->format('d'); // 本日の日(1-31) Log::info('実行タイミングチェック開始', [ 'park_id' => $park->park_id, - 'current_date' => $currentDate, - 'start_date' => $startDate->format('Y-m-d'), - 'end_date' => $endDate->format('Y-m-d'), + 'current_time' => $currentTime, + 'today_day' => $todayDay, + 'reminder_time' => $park->reminder_time, '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) { + // 仕様書:駐輪場マスタ.リマインダー時間 = [現在の時間] の場合 + if ($park->reminder_time !== $currentTime) { return [ 'should_execute' => false, - 'reason' => '更新期間外のため実行対象外', - 'pattern' => $isPatternA ? 'A' : 'B' + 'reason' => "リマインダー時間不一致(設定:{$park->reminder_time} vs 現在:{$currentTime})" ]; } - // リマインダー種別による実行判定 - $reminderExecutionResult = $this->checkReminderTiming($park, $today, $startDate, $endDate, $isPatternA); + // 内部変数.更新パターン を設定 + // DBから返る値は文字列なので、型変換して使用 + $startDay = (int)$park->update_grace_period_start_date; // 例:"20" → 20 + $endDay = (int)$park->update_grace_period_end_date; // 例:"6" → 6 + + $updatePattern = ''; + if ($startDay <= $endDay) { + // パターンA: 月を跨らない場合 + $updatePattern = 'A'; + } else { + // パターンB: 月を跨る場合 + $updatePattern = 'B'; + } + + // 内部変数.更新期間開始日からの経過日数 を設定 + $elapsedDays = 99; // デフォルト: 対象外 + + if ($updatePattern === 'A') { + // パターンA の場合 + if ($endDay < $todayDay) { + // 駐輪場マスタ.更新期間終了日 > [本日の日付]の日 の場合 + $elapsedDays = 99; // 対象外 + } elseif ($startDay <= $todayDay) { + // 駐輪場マスタ.更新期間開始日 <= [本日の日付]の日 の場合 + $elapsedDays = $todayDay - $startDay; + } else { + // その他の場合 + $elapsedDays = 99; // 対象外 + } + } else { + // パターンB の場合 + if ($startDay <= $todayDay) { + // 駐輪場マスタ.更新期間開始日 <= [本日の日付]の日 の場合 + $elapsedDays = $todayDay - $startDay; + } elseif ($endDay >= $todayDay) { + // 駐輪場マスタ.更新期間終了日 >= [本日の日付]の日 の場合 + // 仕様書の計算式: ([先月の月末日]の日 − 駐輪場マスタ.更新期間開始日) + [本日の日付]の日 + $lastMonthEnd = $now->copy()->subMonth()->endOfMonth()->day; + $elapsedDays = ($lastMonthEnd - $startDay) + $todayDay; + } else { + // その他の場合 + $elapsedDays = 99; // 対象外 + } + } + + Log::info('経過日数算出完了', [ + 'park_id' => $park->park_id, + 'update_pattern' => $updatePattern, + 'start_day' => $startDay, + 'end_day' => $endDay, + 'today_day' => $todayDay, + 'elapsed_days' => $elapsedDays + ]); + + // 内部変数.実行フラグ を設定 + $executionFlag = 0; + + if ($elapsedDays !== 99) { + // DBから返る値は文字列なので、型変換して比較 + $reminderType = (int)($park->reminder_type ?? 0); + + if ($reminderType === 0) { + // 仕様書:毎日 + $executionFlag = 1; + } elseif ($reminderType === 1) { + // 仕様書:1日おき(経過日数が偶数の場合) + $executionFlag = ($elapsedDays % 2 === 0) ? 1 : 0; + } elseif ($reminderType === 2) { + // 仕様書:2日おき(経過日数を3で割った余りが0の場合) + $executionFlag = ($elapsedDays % 3 === 0) ? 1 : 0; + } else { + // あり得ない + $executionFlag = 0; + } + } + + $shouldExecute = ($executionFlag === 1); Log::info('実行タイミングチェック完了', [ 'park_id' => $park->park_id, - 'should_execute' => $reminderExecutionResult['should_execute'], - 'pattern' => $isPatternA ? 'A' : 'B', - 'reminder_result' => $reminderExecutionResult + 'update_pattern' => $updatePattern, + 'elapsed_days' => $elapsedDays, + 'reminder_type' => $park->reminder_type, + 'execution_flag' => $executionFlag, + 'should_execute' => $shouldExecute ]); - return $reminderExecutionResult; + return [ + 'should_execute' => $shouldExecute, + 'reason' => $shouldExecute ? '実行対象' : "実行フラグ=0(経過日数:{$elapsedDays}, リマインダー種別:{$park->reminder_type})", + 'update_pattern' => $updatePattern, + 'elapsed_days' => $elapsedDays, + 'execution_flag' => $executionFlag + ]; } catch (\Exception $e) { Log::error('実行タイミングチェックエラー', [ - 'park_id' => $park->park_id, - 'error' => $e->getMessage() + 'park_id' => $park->park_id ?? 'unknown', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() ]); 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 を結合して - * 更新対象者の情報を取得する + * 仕様書に基づくSQLクエリ: + * 定期契約マスタ T1 と 利用者マスタ T2 を結合して更新対象者の情報を取得する + * + * WHERE条件: + * - T1.駐輪場ID = 処理0.駐輪場ID + * - T1.更新可能日 <= 本日の日付 + * - T1.解約フラグ = 0 + * - T2.退会フラグ = 0 + * - T1.授受フラグ = 1 + * - T1.更新済フラグ is null + * - 更新パターンによる有効期間Eの判定 * - * @param int $parkId 駐輪場ID + * @param object $park 駐輪場情報 + * @param string $updatePattern 更新パターン("A" or "B") * @return array 定期更新対象者情報 */ - private function getRegularUpdateTargetUsers(int $parkId): array + private function getRegularUpdateTargetUsers($park, string $updatePattern): array { try { - $currentDate = Carbon::now()->format('Y-m-d'); - - // 仕様書に記載されたSQLクエリに基づく対象者取得 - $targetUsers = DB::table('regular_contract as T1') + $now = Carbon::now(); + $currentDate = $now->format('Y-m-d'); + $todayDay = (int)$now->format('d'); // 本日の日(1-31) + // DBから返る値は文字列なので、型変換して使用 + $startDay = (int)$park->update_grace_period_start_date; // 例:"20" → 20 + $endDay = (int)$park->update_grace_period_end_date; // 例:"6" → 6 + + // 有効期間E(契約終了日)の判定 + $thisMonthEnd = $now->copy()->endOfMonth()->format('Y-m-d'); + $lastMonthEnd = $now->copy()->subMonth()->endOfMonth()->format('Y-m-d'); + + $query = DB::table('regular_contract as T1') ->select([ - 'T1.contract_id as 定期契約内ID', + 'T1.contract_id as 定期契約ID', 'T1.park_id as 駐輪場ID', - 'T2.user_id as 利用者ID', + 'T2.user_seq as 利用者ID', // user_seqが主キー '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(); + ->join('user as T2', 'T1.user_id', '=', 'T2.user_seq') // user_seqに結合 + ->where('T1.park_id', $park->park_id) // 駐輪場ID + ->where('T1.contract_updated_at', '<=', $currentDate) // 更新可能日 + ->where('T1.contract_cancel_flag', 0) // 解約フラグ = 0 + ->where('T2.user_quit_flag', 0) // 退会フラグ = 0 + ->where('T1.contract_flag', 1) // 授受フラグ = 1 + ->whereNull('T1.contract_renewal'); // 更新済フラグ is null + + // 仕様書:更新パターンによる有効期間Eの判定 + if ($updatePattern === 'A') { + // パターンA の場合: 有効期間E = 今月末 + $query->where('T1.contract_periode', '=', $thisMonthEnd); + } else { + // パターンB の場合 + if ($startDay <= $todayDay) { + // 処理0.更新期間開始日 <= [本日の日付]の日 の場合 + $query->where('T1.contract_periode', '=', $thisMonthEnd); + } elseif ($endDay >= $todayDay) { + // 処理0.更新期間終了日 >= [本日の日付]の日 の場合 + $query->where('T1.contract_periode', '=', $lastMonthEnd); + } + } + + $targetUsers = $query->get()->toArray(); Log::info('定期更新対象者取得完了', [ - 'park_id' => $parkId, + 'park_id' => $park->park_id, + 'update_pattern' => $updatePattern, + 'this_month_end' => $thisMonthEnd, + 'last_month_end' => $lastMonthEnd, 'target_users_count' => count($targetUsers) ]); @@ -611,8 +545,10 @@ class ShjThreeService } catch (\Exception $e) { Log::error('定期更新対象者取得エラー', [ - 'park_id' => $parkId, - 'error' => $e->getMessage() + 'park_id' => $park->park_id ?? 'unknown', + 'update_pattern' => $updatePattern ?? 'unknown', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() ]); throw $e; @@ -624,38 +560,34 @@ class ShjThreeService * * 手動登録フラグによって処理を分岐: * - = 0 (ウェブ申込み): SHJ-7メール送信を呼び出し - * - その他: 内部変数のカウントアップ(オペレーターキュー処理) + * - その他: オペレーターキュー追加処理 * * @param object $targetUser 対象者情報 + * @param object $park 駐輪場情報 * @return array 処理結果 */ - private function processTargetUser($targetUser): array + private function processTargetUser($targetUser, $park): array { try { $manualRegistFlag = $targetUser->手動登録フラグ ?? 1; if ($manualRegistFlag == 0) { - // ウェブ申込み: SHJ-7メール送信処理 + // 仕様書:手動登録フラグ = 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' => 'オペレーターキュー対象として処理' - ]; + // 仕様書:手動登録フラグ <> 0 の場合 + // オペレーターキュー追加処理 + // ※文書には詳細仕様なし。他のService実装を参考に実装 + return $this->addToOperatorQueue($targetUser, $park); } } catch (\Exception $e) { Log::error('対象者処理エラー', [ 'user_id' => $targetUser->利用者ID ?? 'unknown', - 'error' => $e->getMessage() + 'contract_id' => $targetUser->定期契約ID ?? 'unknown', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() ]); return [ @@ -669,7 +601,7 @@ class ShjThreeService /** * リマインダーメール送信処理 * - * SHJ-7メール送信処理を呼び出し、使用プログラムID=200を使用 + * 仕様書:SHJ-7メール送信を呼び出し、使用プログラムID=200を使用 * * @param object $targetUser 対象者情報 * @return array 送信結果 @@ -679,17 +611,17 @@ class ShjThreeService try { $mailAddress = $targetUser->メールアドレス ?? ''; $backupMailAddress = $targetUser->予備メールアドレス ?? ''; - $mailTemplateId = 200; // 使用プログラムID + $mailTemplateId = 200; // 仕様書:使用プログラムID = 200 Log::info('SHJ-7メール送信処理呼び出し', [ 'user_id' => $targetUser->利用者ID, - 'contract_id' => $targetUser->定期契約内ID, + 'contract_id' => $targetUser->定期契約ID, 'mail_address' => $mailAddress, 'backup_mail_address' => $backupMailAddress, 'mail_template_id' => $mailTemplateId ]); - // SHJ-7メール送信処理実行 + // 仕様書:共通処理「SHJ-7 メール送信」を呼び出し $mailResult = $this->mailSendService->executeMailSend( $mailAddress, $backupMailAddress, @@ -697,31 +629,118 @@ class ShjThreeService ); if ($mailResult['success']) { + // 仕様書:処理結果 = 0(正常)の場合 + Log::info('メール送信成功', [ + 'user_id' => $targetUser->利用者ID, + 'contract_id' => $targetUser->定期契約ID + ]); + return [ 'type' => 'mail_success', 'success' => true, 'message' => 'メール送信成功', - 'mail_result' => $mailResult + 'error_info' => null ]; } else { + // 仕様書:その他の場合(異常) + // バッチコメントに「処理2.定期契約ID」+「SHJ-7 メール送信.異常情報」を設定する(後ろに足す) + $errorInfo = "定期契約ID:{$targetUser->定期契約ID} / SHJ-7 メール送信.異常情報:{$mailResult['message']}"; + + Log::warning('メール送信失敗', [ + 'user_id' => $targetUser->利用者ID, + 'contract_id' => $targetUser->定期契約ID, + 'error' => $mailResult['message'] + ]); + return [ 'type' => 'mail_error', 'success' => false, - 'message' => 'メール送信失敗: ' . $mailResult['message'], - 'mail_result' => $mailResult + 'message' => 'メール送信失敗', + 'error_info' => $errorInfo ]; } } catch (\Exception $e) { Log::error('リマインダーメール送信エラー', [ 'user_id' => $targetUser->利用者ID ?? 'unknown', - 'error' => $e->getMessage() + 'contract_id' => $targetUser->定期契約ID ?? 'unknown', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() ]); + // 仕様書準拠のエラー情報フォーマット + $errorInfo = "定期契約ID:{$targetUser->定期契約ID} / SHJ-7 メール送信.異常情報:例外エラー - {$e->getMessage()}"; + return [ 'type' => 'mail_error', 'success' => false, - 'message' => 'メール送信エラー: ' . $e->getMessage() + 'message' => 'メール送信例外エラー', + 'error_info' => $errorInfo + ]; + } + } + + /** + * オペレーターキュー追加処理 + * + * 仕様書には詳細記載なし。 + * 他のService(ShjOneService、ShjSixService)の実装を参考に実装。 + * + * @param object $targetUser 対象者情報 + * @param object $park 駐輪場情報 + * @return array 追加結果 + */ + private function addToOperatorQueue($targetUser, $park): array + { + try { + // operator_queテーブルに登録 + $operatorQue = OperatorQue::create([ + 'que_class' => 5, // 定期更新通知(OperatorQueモデルの定数参照) + 'user_id' => $targetUser->利用者ID, + 'contract_id' => $targetUser->定期契約ID, + 'park_id' => $targetUser->駐輪場ID, + 'que_comment' => sprintf( + '定期更新通知 / 契約ID:%s / 利用者:%s / 駐輪場:%s', + $targetUser->定期契約ID, + $targetUser->氏名 ?? '', + $park->park_name ?? '' + ), + 'que_status' => 1, // キュー発生 + 'que_status_comment' => '', + 'work_instructions' => '', + 'operator_id' => 9999999, // 仕様書準拠:固定値(他Serviceと同様) + 'created_at' => now(), + 'updated_at' => now() + ]); + + Log::info('オペレーターキュー追加成功', [ + 'que_id' => $operatorQue->que_id, + 'que_class' => 5, + 'user_id' => $targetUser->利用者ID, + 'contract_id' => $targetUser->定期契約ID, + 'park_id' => $targetUser->駐輪場ID + ]); + + return [ + 'type' => 'queue_success', + 'success' => true, + 'message' => 'オペレーターキュー追加成功', + 'que_id' => $operatorQue->que_id + ]; + + } catch (\Exception $e) { + Log::error('オペレーターキュー追加エラー', [ + 'user_id' => $targetUser->利用者ID ?? 'unknown', + 'contract_id' => $targetUser->定期契約ID ?? 'unknown', + 'park_id' => $targetUser->駐輪場ID ?? 'unknown', + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'type' => 'queue_error', + 'success' => false, + 'message' => 'オペレーターキュー追加エラー: ' . $e->getMessage() ]; } } @@ -730,47 +749,76 @@ class ShjThreeService * 【処理4】SHJ-8バッチ処理ログ作成 * * 仕様書に基づくSHJ-8共通処理呼び出し + * BatchLog統一システムを使用してバッチ処理の実行ログを記録 + * ※各駐輪場ごとに1回実行される * - * @param array $statistics 処理統計情報 + * @param object $park 駐輪場情報 + * @param string $batchComment バッチコメント + * @param int $mailSuccessCount メール正常終了件数 + * @param int $mailErrorCount メール異常終了件数 + * @param int $queueSuccessCount キュー登録正常終了件数 + * @param int $queueErrorCount キュー登録異常終了件数 * @return void */ - private function createShjBatchLog(array $statistics): void - { + private function createShjBatchLog( + $park, + string $batchComment, + int $mailSuccessCount, + int $mailErrorCount, + int $queueSuccessCount, + int $queueErrorCount + ): void { try { - // 仕様書に基づくSHJ-8パラメータ設定 - $deviceId = 9999; // テスト用デバイスID(規格書では"-"だが、既存実装に合わせて9999使用) - $processName = 'SHJ-3定期更新リマインダー'; - $jobName = 'success'; - $status = 'success'; + // 仕様書:SHJ-8パラメータ設定 + $deviceId = 9999999; // バッチ処理用固定デバイスID(他Serviceと同様) + $processName = 'SHJ-3'; + $jobName = 'SHJ-3定期更新リマインダー'; + $status = BatchLog::STATUS_SUCCESS; - // ステータスコメント生成 - $statusComment = "メール正常終了件数: {$statistics['mail_success_count']}" . - " + メール異常終了件数: {$statistics['mail_error_count']}" . - " + キュー登録正常終了件数: {$statistics['operator_queue_count']}"; + // 仕様書:ステータスコメント生成 + // 「内部変数.バッチコメント」+ "/" + 「処理1.駐輪場名」 + // + ":メール正常終了件数" + 「内部変数.メール正常終了件数」 + // + "、メール異常終了件数" + 「内部変数.メール異常終了件数」 + // + "、キュー登録正常終了件数" + 「内部変数.キュー登録正常終了件数」 + // + "、キュー登録異常終了件数" + 「内部変数.キュー登録異常終了件数」 + $statusComment = ($batchComment ? $batchComment . ' / ' : '') . + "{$park->park_name} : " . + "メール正常終了件数={$mailSuccessCount}、" . + "メール異常終了件数={$mailErrorCount}、" . + "キュー登録正常終了件数={$queueSuccessCount}、" . + "キュー登録異常終了件数={$queueErrorCount}"; $createdDate = now()->format('Y/m/d'); $updatedDate = now()->format('Y/m/d'); Log::info('SHJ-8バッチ処理ログ作成', [ + 'park_id' => $park->park_id, + 'park_name' => $park->park_name, 'device_id' => $deviceId, 'process_name' => $processName, 'job_name' => $jobName, 'status' => $status, - 'status_comment' => $statusComment, - 'created_date' => $createdDate, - 'updated_date' => $updatedDate + 'status_comment' => $statusComment ]); - // 共通処理SHJ-8バッチ処理ログ作成を呼び出し - // 注意: 実際の運用では外部コマンド呼び出しまたは専用サービス経由で実行 + // 仕様書:共通処理「SHJ-8 バッチ処理ログ作成」を呼び出す + // BatchLog::createBatchLog を使用して統一的にログを記録 BatchLog::createBatchLog( $processName, $status, [ 'device_id' => $deviceId, 'job_name' => $jobName, + 'park_id' => $park->park_id, + 'park_name' => $park->park_name, 'status_comment' => $statusComment, - 'statistics' => $statistics, + 'statistics' => [ + 'mail_success_count' => $mailSuccessCount, + 'mail_error_count' => $mailErrorCount, + 'queue_success_count' => $queueSuccessCount, + 'queue_error_count' => $queueErrorCount, + 'batch_comment' => $batchComment + ], 'shj8_params' => [ 'device_id' => $deviceId, 'process_name' => $processName, @@ -785,11 +833,12 @@ class ShjThreeService } catch (\Exception $e) { Log::error('SHJ-8バッチ処理ログ作成エラー', [ + 'park_id' => $park->park_id ?? 'unknown', 'error' => $e->getMessage(), - 'statistics' => $statistics + 'trace' => $e->getTraceAsString() ]); - // SHJ-8でエラーが発生してもメイン処理は継続 + // 仕様書:SHJ-8でエラーが発生してもメイン処理は継続 // エラーログのみ出力 } }