so-manager-dev.com/app/Services/ShjThreeService.php
2025-09-19 19:01:21 +09:00

797 lines
29 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use App\Models\Park;
use App\Models\User;
use App\Models\RegularContract;
use App\Models\Batch\BatchLog;
use App\Services\ShjMailSendService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
/**
* SHJ-3 定期更新リマインダー処理サービス
*
* 駐輪場の定期契約更新対象者に対するリマインダー処理を実行するビジネスロジック
* バッチ処理「SHJ-3定期更新リマインダー」の核となる処理を担当
*/
class ShjThreeService
{
/**
* Park モデル
*
* @var Park
*/
protected $parkModel;
/**
* User モデル
*
* @var User
*/
protected $userModel;
/**
* RegularContract モデル
*
* @var RegularContract
*/
protected $contractModel;
/**
* BatchLog モデル
*
* @var BatchLog
*/
protected $batchLogModel;
/**
* ShjMailSendService
*
* @var ShjMailSendService
*/
protected $mailSendService;
/**
* コンストラクタ
*
* @param Park $parkModel
* @param User $userModel
* @param RegularContract $contractModel
* @param BatchLog $batchLogModel
* @param ShjMailSendService $mailSendService
*/
public function __construct(
Park $parkModel,
User $userModel,
RegularContract $contractModel,
BatchLog $batchLogModel,
ShjMailSendService $mailSendService
) {
$this->parkModel = $parkModel;
$this->userModel = $userModel;
$this->contractModel = $contractModel;
$this->batchLogModel = $batchLogModel;
$this->mailSendService = $mailSendService;
}
/**
* SHJ-3 定期更新リマインダー処理メイン実行
*
* 処理フロー:
* 【処理0】駐輪場マスタの情報を取得する
* 【判断0】当該駐輪場実行タイミングチェック
* 【処理2】定期更新対象者を取得する
* 【判断2】利用者有無をチェック
* 【処理3】対象者向けにメール送信、またはオペレーターキュー追加処理
* 【処理4】バッチ処理ログを作成する
*
* @return array 処理結果
*/
public function executeReminderProcess(): array
{
$batchLogId = null;
$processedParksCount = 0;
$totalTargetUsers = 0;
$mailSuccessCount = 0;
$mailErrorCount = 0;
$operatorQueueCount = 0;
try {
// バッチ処理開始ログ作成
$batchLog = BatchLog::createBatchLog(
'shj3',
BatchLog::STATUS_START,
[],
'SHJ-3 定期更新リマインダー処理開始'
);
$batchLogId = $batchLog->id;
Log::info('SHJ-3 定期更新リマインダー処理開始', [
'batch_log_id' => $batchLogId
]);
// 【処理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
]);
return [
'success' => false,
'message' => $message,
'processed_parks_count' => 0,
'total_target_users' => 0,
'mail_success_count' => 0,
'mail_error_count' => 0,
'operator_queue_count' => 0,
'batch_log_id' => $batchLogId
];
}
// 各駐輪場に対する処理ループ
foreach ($parkList as $park) {
Log::info('駐輪場処理開始', [
'park_id' => $park->park_id,
'park_name' => $park->park_name
]);
// 【判断0】当該駐輪場実行タイミングチェック
$timingCheckResult = $this->checkExecutionTiming($park);
if (!$timingCheckResult['should_execute']) {
Log::info('実行タイミング対象外', [
'park_id' => $park->park_id,
'reason' => $timingCheckResult['reason']
]);
continue;
}
$processedParksCount++;
// 【処理2】定期更新対象者を取得する
$targetUsers = $this->getRegularUpdateTargetUsers($park->park_id);
// 【判断2】利用者有無をチェック
if (empty($targetUsers)) {
Log::info('利用者なし', [
'park_id' => $park->park_id
]);
continue;
}
$totalTargetUsers += count($targetUsers);
// 【処理3】対象者向けにメール送信、またはオペレーターキュー追加処理
foreach ($targetUsers as $targetUser) {
$processResult = $this->processTargetUser($targetUser);
if ($processResult['type'] === 'mail_success') {
$mailSuccessCount++;
} elseif ($processResult['type'] === 'mail_error') {
$mailErrorCount++;
} elseif ($processResult['type'] === 'operator_queue') {
$operatorQueueCount++;
}
}
Log::info('駐輪場処理完了', [
'park_id' => $park->park_id,
'target_users_count' => count($targetUsers)
]);
}
// 【処理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
]);
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
];
} 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()
]);
return [
'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
];
}
}
/**
* 【処理0】駐輪場マスタの情報を取得する
*
* 仕様書に基づくSQLクエリ:
* SELECT 駐輪場ID, 駐輪場名, 更新期間開始日, 更新期間開始時,
* 更新期間終了日, 更新期間終了時, リマインダー種別, リマインダー時間
* FROM 駐輪場マスタ
* WHERE 閉設フラグ = 0
* ORDER BY 駐輪場ふりがな asc
*
* @return array 駐輪場マスタ情報
*/
private function getParkMasterInfo(): array
{
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'
])
->where('park_close_flag', 0)
->orderBy('park_ruby', 'asc')
->get()
->toArray();
Log::info('駐輪場マスタ情報取得完了', [
'park_count' => count($parkInfo)
]);
return $parkInfo;
} catch (\Exception $e) {
Log::error('駐輪場マスタ情報取得エラー', [
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 【判断0】当該駐輪場実行タイミングチェック
*
* 仕様書に基づく複雑なリマインダー時期判定:
* - パターンA: 月を跨らない場合(更新期間開始日 <= 更新期間終了日)
* - パターンB: 月を跨る場合(更新期間開始日 > 更新期間終了日)
* - リマインダー種別による実行判定
*
* @param object $park 駐輪場情報
* @return array 実行タイミング判定結果
*/
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);
Log::info('実行タイミングチェック開始', [
'park_id' => $park->park_id,
'current_date' => $currentDate,
'start_date' => $startDate->format('Y-m-d'),
'end_date' => $endDate->format('Y-m-d'),
'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) {
return [
'should_execute' => false,
'reason' => '更新期間外のため実行対象外',
'pattern' => $isPatternA ? 'A' : 'B'
];
}
// リマインダー種別による実行判定
$reminderExecutionResult = $this->checkReminderTiming($park, $today, $startDate, $endDate, $isPatternA);
Log::info('実行タイミングチェック完了', [
'park_id' => $park->park_id,
'should_execute' => $reminderExecutionResult['should_execute'],
'pattern' => $isPatternA ? 'A' : 'B',
'reminder_result' => $reminderExecutionResult
]);
return $reminderExecutionResult;
} catch (\Exception $e) {
Log::error('実行タイミングチェックエラー', [
'park_id' => $park->park_id,
'error' => $e->getMessage()
]);
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 を結合して
* 更新対象者の情報を取得する
*
* @param int $parkId 駐輪場ID
* @return array 定期更新対象者情報
*/
private function getRegularUpdateTargetUsers(int $parkId): array
{
try {
$currentDate = Carbon::now()->format('Y-m-d');
// 仕様書に記載されたSQLクエリに基づく対象者取得
$targetUsers = DB::table('regular_contract as T1')
->select([
'T1.contract_id as 定期契約内ID',
'T1.park_id as 駐輪場ID',
'T2.user_id as 利用者ID',
'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();
Log::info('定期更新対象者取得完了', [
'park_id' => $parkId,
'target_users_count' => count($targetUsers)
]);
return $targetUsers;
} catch (\Exception $e) {
Log::error('定期更新対象者取得エラー', [
'park_id' => $parkId,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 【処理3】対象者の処理実行
*
* 手動登録フラグによって処理を分岐:
* - = 0 (ウェブ申込み): SHJ-7メール送信を呼び出し
* - その他: 内部変数のカウントアップ(オペレーターキュー処理)
*
* @param object $targetUser 対象者情報
* @return array 処理結果
*/
private function processTargetUser($targetUser): array
{
try {
$manualRegistFlag = $targetUser->手動登録フラグ ?? 1;
if ($manualRegistFlag == 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' => 'オペレーターキュー対象として処理'
];
}
} catch (\Exception $e) {
Log::error('対象者処理エラー', [
'user_id' => $targetUser->利用者ID ?? 'unknown',
'error' => $e->getMessage()
]);
return [
'type' => 'error',
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* リマインダーメール送信処理
*
* SHJ-7メール送信処理を呼び出し、使用プログラムID=200を使用
*
* @param object $targetUser 対象者情報
* @return array 送信結果
*/
private function sendReminderMail($targetUser): array
{
try {
$mailAddress = $targetUser->メールアドレス ?? '';
$backupMailAddress = $targetUser->予備メールアドレス ?? '';
$mailTemplateId = 200; // 使用プログラムID
Log::info('SHJ-7メール送信処理呼び出し', [
'user_id' => $targetUser->利用者ID,
'contract_id' => $targetUser->定期契約内ID,
'mail_address' => $mailAddress,
'backup_mail_address' => $backupMailAddress,
'mail_template_id' => $mailTemplateId
]);
// SHJ-7メール送信処理実行
$mailResult = $this->mailSendService->executeMailSend(
$mailAddress,
$backupMailAddress,
$mailTemplateId
);
if ($mailResult['success']) {
return [
'type' => 'mail_success',
'success' => true,
'message' => 'メール送信成功',
'mail_result' => $mailResult
];
} else {
return [
'type' => 'mail_error',
'success' => false,
'message' => 'メール送信失敗: ' . $mailResult['message'],
'mail_result' => $mailResult
];
}
} catch (\Exception $e) {
Log::error('リマインダーメール送信エラー', [
'user_id' => $targetUser->利用者ID ?? 'unknown',
'error' => $e->getMessage()
]);
return [
'type' => 'mail_error',
'success' => false,
'message' => 'メール送信エラー: ' . $e->getMessage()
];
}
}
/**
* 【処理4】SHJ-8バッチ処理ログ作成
*
* 仕様書に基づくSHJ-8共通処理呼び出し
*
* @param array $statistics 処理統計情報
* @return void
*/
private function createShjBatchLog(array $statistics): void
{
try {
// 仕様書に基づくSHJ-8パラメータ設定
$deviceId = 9999; // テスト用デバイスID規格書では"-"だが、既存実装に合わせて9999使用
$processName = 'SHJ-3定期更新リマインダー';
$jobName = 'success';
$status = 'success';
// ステータスコメント生成
$statusComment = "メール正常終了件数: {$statistics['mail_success_count']}" .
" + メール異常終了件数: {$statistics['mail_error_count']}" .
" + キュー登録正常終了件数: {$statistics['operator_queue_count']}";
$createdDate = now()->format('Y/m/d');
$updatedDate = now()->format('Y/m/d');
Log::info('SHJ-8バッチ処理ログ作成', [
'device_id' => $deviceId,
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'status_comment' => $statusComment,
'created_date' => $createdDate,
'updated_date' => $updatedDate
]);
// 共通処理SHJ-8バッチ処理ログ作成を呼び出し
// 注意: 実際の運用では外部コマンド呼び出しまたは専用サービス経由で実行
BatchLog::createBatchLog(
$processName,
$status,
[
'device_id' => $deviceId,
'job_name' => $jobName,
'status_comment' => $statusComment,
'statistics' => $statistics,
'shj8_params' => [
'device_id' => $deviceId,
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'created_date' => $createdDate,
'updated_date' => $updatedDate
]
],
$statusComment
);
} catch (\Exception $e) {
Log::error('SHJ-8バッチ処理ログ作成エラー', [
'error' => $e->getMessage(),
'statistics' => $statistics
]);
// SHJ-8でエラーが発生してもメイン処理は継続
// エラーログのみ出力
}
}
}