Merge remote-tracking branch 'origin/main' into main_watanabe

This commit is contained in:
Yu Watanabe 2025-09-26 17:28:18 +09:00
commit 0e67f5a11b
10 changed files with 1763 additions and 83 deletions

View File

@ -0,0 +1,207 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Services\ShjFiveService;
use App\Models\Batch\BatchLog;
/**
* SHJ-5 空き待ち通知処理コマンド
*
* 駐輪場の空き状況を確認し、空き待ち予約者への通知処理を実行する
* バックグラウンドで実行される定期バッチ処理
*/
class ShjFiveCommand extends Command
{
/**
* コンソールコマンドの名前とシグネチャ
*
* 引数なし - 全ての駐輪場を対象に処理を実行
*
* @var string
*/
protected $signature = 'shj:5';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-5 空き待ち通知処理 - 駐輪場の空き状況確認と空き待ち者への通知を実行';
/**
* SHJ-5サービスクラス
*
* @var ShjFiveService
*/
protected $shjFiveService;
/**
* コンストラクタ
*
* @param ShjFiveService $shjFiveService
*/
public function __construct(ShjFiveService $shjFiveService)
{
parent::__construct();
$this->shjFiveService = $shjFiveService;
}
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. バッチログ開始記録
* 2. 駐輪場の空き状況を取得する
* 3. 空き状況判定
* 4. 空き待ち者の情報を取得する
* 5. 取得件数判定
* 6. 空き待ち者への通知、またはオペレーターキュー追加処理
* 7. バッチ処理ログを作成する
* 8. 処理結果返却
*
* @return int
*/
public function handle()
{
$batchLog = null;
try {
// 開始ログ出力
$startTime = now();
$this->info('SHJ-5 空き待ち通知処理を開始します。');
Log::info('SHJ-5 空き待ち通知処理開始', [
'start_time' => $startTime
]);
// SHJ-8共通処理呼び出し - 仕様書準拠
$batchLog = BatchLog::createBatchLog(
'SHJ-5空き待ち通知処理',
BatchLog::STATUS_START,
[
'device_id' => 1, // 仕様書device_id=1
'process_code' => 1, // 仕様書process_code=1
'status_comment' => '処理開始' // 仕様書:内部変数.ステータスコメント
],
'SHJ-5処理開始: 駐輪場空き状況確認と空き待ち通知処理'
);
// SHJ-5メイン処理実行
$result = $this->shjFiveService->executeParkVacancyNotification();
$endTime = now();
$this->info('SHJ-5 空き待ち通知処理が完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
// 処理結果表示
$this->displayProcessResult($result);
// SHJ-8共通処理 - バッチログ完了記録(仕様書準拠)
$logStatus = $result['success'] ? BatchLog::STATUS_SUCCESS : BatchLog::STATUS_ERROR;
// 仕様書準拠:サービス側で作成した完全なステータスコメントを使用
$statusComment = $result['success'] ?
($result['status_comment'] ?? 'ステータスコメント生成エラー') :
sprintf('処理失敗: %s', $result['message'] ?? 'エラー');
$batchLog->update([
'status' => $logStatus,
'end_time' => $endTime,
'message' => $result['message'],
'parameters' => [
'device_id' => 1, // 仕様書device_id=1
'process_code' => 1, // 仕様書process_code=1
'status_comment' => $statusComment, // 仕様書:内部変数.ステータスコメント(完全版)
'processed_parks_count' => $result['processed_parks_count'] ?? 0,
'vacant_parks_count' => $result['vacant_parks_count'] ?? 0,
'total_waiting_users' => $result['total_waiting_users'] ?? 0,
'notification_success_count' => $result['notification_success_count'] ?? 0,
'operator_queue_count' => $result['operator_queue_count'] ?? 0,
'error_count' => $result['error_count'] ?? 0,
'duration_seconds' => $result['duration_seconds'] ?? 0
],
'execution_count' => 1,
'success_count' => $result['notification_success_count'] ?? 0,
'error_count' => $result['error_count'] ?? 0
]);
Log::info('SHJ-5 空き待ち通知処理完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime),
'result' => $result
]);
return $result['success'] ? self::SUCCESS : self::FAILURE;
} catch (\Exception $e) {
$this->error('SHJ-5 空き待ち通知処理で予期しないエラーが発生しました: ' . $e->getMessage());
// SHJ-8共通処理 - エラーログ記録(仕様書準拠)
if ($batchLog) {
$statusComment = sprintf(
'メール正常終了件数0メール異常終了件数1キュー登録正常終了件数0キュー登録異常終了件数1 - 処理エラー: %s',
$e->getMessage()
);
$batchLog->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => 'SHJ-5処理中にエラーが発生: ' . $e->getMessage(),
'parameters' => [
'device_id' => 1, // 仕様書device_id=1
'process_code' => 1, // 仕様書process_code=1
'status_comment' => $statusComment // 仕様書:内部変数.ステータスコメント(完全版)
],
'error_details' => $e->getMessage(),
'error_count' => 1
]);
}
Log::error('SHJ-5 空き待ち通知処理例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return self::FAILURE;
}
}
/**
* 処理結果を表示
*
* @param array $result 処理結果
* @return void
*/
private function displayProcessResult(array $result): void
{
$this->line('');
$this->info('=== 処理結果 ===');
if ($result['success']) {
$this->info("✓ 処理実行成功: " . $result['message']);
} else {
$this->error("✗ 処理実行失敗: " . $result['message']);
}
$this->line('');
$this->info('=== 処理統計 ===');
$this->line(" 処理対象駐輪場数: " . ($result['processed_parks_count'] ?? 0));
$this->line(" 空きあり駐輪場数: " . ($result['vacant_parks_count'] ?? 0));
$this->line(" 空き待ち者総数: " . ($result['total_waiting_users'] ?? 0));
$this->line(" 通知送信成功: " . ($result['notification_success_count'] ?? 0) . "");
$this->line(" オペレーターキュー追加: " . ($result['operator_queue_count'] ?? 0) . "");
$this->line(" エラー件数: " . ($result['error_count'] ?? 0) . "");
$this->line(" 処理時間: " . ($result['duration_seconds'] ?? 0) . "");
// エラー詳細があれば表示
if (!empty($result['errors'])) {
$this->line('');
$this->warn('=== エラー詳細 ===');
foreach ($result['errors'] as $index => $error) {
$this->line(" " . ($index + 1) . ". " . $error);
}
}
}
}

View File

@ -0,0 +1,154 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Services\ShjThirteenService;
/**
* SHJ-13 契約台数追加処理コマンド
*
* 指定されたパラメータで契約台数をpark_number・zoneテーブルに反映する
* 主にSHJ-4Bから呼び出されるが、独立実行も可能
*/
class ShjThirteenCommand extends Command
{
/**
* コンソールコマンドの名前とシグネチャ
*
* 引数:
* - park_id: 駐輪場ID (必須)
* - psection_id: 車種区分ID (必須)
* - ptype_id: 駐輪分類ID (必須)
* - zone_id: ゾーンID (必須)
*
* オプション:
* - contract_id: 契約ID (任意、ログ用)
*
* @var string
*/
protected $signature = 'shj:13
{park_id : 駐輪場ID}
{psection_id : 車種区分ID}
{ptype_id : 駐輪分類ID}
{zone_id : ゾーンID}
{--contract_id= : 契約IDログ用、任意}';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-13 契約台数追加処理 - park_number・zoneテーブルの契約台数を+1更新しログ記録';
/**
* SHJ-13サービスクラス
*
* @var ShjThirteenService
*/
protected $shjThirteenService;
/**
* コンストラクタ
*
* @param ShjThirteenService $shjThirteenService
*/
public function __construct(ShjThirteenService $shjThirteenService)
{
parent::__construct();
$this->shjThirteenService = $shjThirteenService;
}
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. 引数取得・検証
* 2. ShjThirteenService実行
* 3. 結果表示
*
* @return int
*/
public function handle()
{
try {
// 開始ログ出力
$startTime = now();
$this->info('SHJ-13 契約台数追加処理を開始します。');
// 引数取得
$contractData = [
'park_id' => (int) $this->argument('park_id'),
'psection_id' => (int) $this->argument('psection_id'),
'ptype_id' => (int) $this->argument('ptype_id'),
'zone_id' => (int) $this->argument('zone_id'),
'contract_id' => $this->option('contract_id'),
];
Log::info('SHJ-13 契約台数追加処理開始', [
'start_time' => $startTime,
'contract_data' => $contractData,
]);
$this->info("駐輪場ID: {$contractData['park_id']}");
$this->info("車種区分ID: {$contractData['psection_id']}");
$this->info("駐輪分類ID: {$contractData['ptype_id']}");
$this->info("ゾーンID: {$contractData['zone_id']}");
if ($contractData['contract_id']) {
$this->info("契約ID: {$contractData['contract_id']}");
}
// SHJ-13処理実行
$this->info('【処理実行】契約台数追加処理を実行しています...');
$result = $this->shjThirteenService->execute($contractData);
// 処理結果確認・表示
if ($result['result'] === 0) {
$endTime = now();
$this->info('SHJ-13 契約台数追加処理が正常に完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
Log::info('SHJ-13 契約台数追加処理完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime),
'contract_data' => $contractData,
]);
// 成功時の結果出力
$this->line('処理結果: 0'); // 0 = 正常終了
$this->line('異常情報: '); // 正常時は空文字
return self::SUCCESS;
} else {
$this->error('SHJ-13 契約台数追加処理でエラーが発生しました。');
$this->error("エラーコード: {$result['error_code']}");
$this->error("エラーメッセージ: {$result['error_message']}");
Log::error('SHJ-13 契約台数追加処理エラー', [
'contract_data' => $contractData,
'error_result' => $result,
]);
// エラー時の結果出力
$this->line('処理結果: 1'); // 1 = 異常終了
$this->line('異常情報: ' . $result['error_message']);
return self::FAILURE;
}
} catch (\Exception $e) {
$this->error('SHJ-13 契約台数追加処理で予期しないエラーが発生しました: ' . $e->getMessage());
Log::error('SHJ-13 契約台数追加処理例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
// 例外時の結果出力
$this->line('処理結果: 1'); // 1 = 異常終了
$this->line('異常情報: システムエラー: ' . $e->getMessage());
return self::FAILURE;
}
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use Carbon\Carbon;
class MypageController extends Controller
{
public function index()
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
$today = date('Y-m-d');
// 定期契約情報を取得park/usertype/psection/ptypeテーブルもJOIN
$contracts = DB::table('regular_contract')
->join('park', 'regular_contract.park_id', '=', 'park.park_id')
->join('usertype', 'regular_contract.user_categoryid', '=', 'usertype.user_categoryid')
->leftJoin('city', 'park.city_id', '=', 'city.city_id')
->leftJoin('psection', 'regular_contract.psection_id', '=', 'psection.psection_id')
->leftJoin('ptype', 'regular_contract.ptype_id', '=', 'ptype.ptype_id')
->where('regular_contract.user_id', $user_id)
->where('regular_contract.contract_flag', 1)
->where('regular_contract.contract_cancel_flag', 0)
->where(function ($query) use ($today) {
$query->where('regular_contract.contract_periode', '>', $today)
->orWhere(function ($q) use ($today) {
$q->where('regular_contract.contract_periode', '<=', $today)
->whereRaw('DATEDIFF(?, regular_contract.contract_periode) <= 5', [$today]);
});
})
->select(
'regular_contract.contract_id',
'park.park_name',
'usertype.usertype_subject1',
'regular_contract.contract_periods',
'regular_contract.contract_periode',
'regular_contract.enable_months',
'regular_contract.contract_renewal',
'regular_contract.park_id',
'city.update_grace_period_start_date',
'city.update_grace_period_start_time',
'city.update_grace_period_end_date',
'city.update_grace_period_end_time',
'psection.psection_subject',
'ptype.ptype_subject',
'regular_contract.pplace_no'
)
->get();
// シール情報を取得
$seals = DB::table('regular_contract')
->join('usertype', 'regular_contract.user_categoryid', '=', 'usertype.user_categoryid')
->leftJoin('psection', 'regular_contract.psection_id', '=', 'psection.psection_id')
->leftJoin('ptype', 'regular_contract.ptype_id', '=', 'ptype.ptype_id')
->where('regular_contract.user_id', $user_id)
->where('regular_contract.contract_flag', 1)
->where('regular_contract.contract_cancel_flag', 0)
->where('regular_contract.contract_periode', '>=', $today)
->where('regular_contract.contract_permission', 1)
->select(
'regular_contract.contract_id',
'regular_contract.contract_qr_id',
'usertype.usertype_subject1',
'regular_contract.contract_periods',
'regular_contract.contract_periode',
'regular_contract.enable_months',
'regular_contract.contract_renewal',
'regular_contract.park_id',
'psection.psection_subject',
'ptype.ptype_subject',
'regular_contract.pplace_no'
)
->get();
// お知らせ情報を取得
$information = DB::table('user_information_history')
->where('user_id', $user_id)
->orderBy('user_information_history_id', 'desc')
->select('entry_date', 'user_information_history')
->first();
\Log::info('マイページにアクセス', [
'user_id' => $user_id,
]);
return view('mypage.index', [
'active_menu' => 'SWO-4-1', // マイページメニューの選択状態用
'user_name' => $user->user_name, // ユーザー名(ヘッダー用)
'contracts' => $contracts,
'seals' => $seals,
'information' => $information
]);
}
}

View File

@ -0,0 +1,650 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\OperatorQue;
use Exception;
/**
* SHJ-5 空き待ち通知処理サービス
*
* 駐輪場の空き状況を確認し、空き待ち予約者への通知処理を実行する
* 仕様書に基づくバックグラウンド定期バッチ処理
*/
class ShjFiveService
{
/**
* SHJ-5 メイン処理を実行
*
* 処理フロー:
* 1. 駐輪場の空き状況を取得する
* 2. 空き状況判定
* 3. 空き待ち者の情報を取得する
* 4. 取得件数判定
* 5. 空き待ち者への通知、またはオペレーターキュー追加処理
* 6. バッチ処理ログを作成する
*
* @return array 処理結果
*/
public function executeParkVacancyNotification(): array
{
try {
$startTime = now();
Log::info('SHJ-5 空き待ち通知処理開始');
// 処理統計
$processedParksCount = 0;
$vacantParksCount = 0;
$totalWaitingUsers = 0;
$notificationSuccessCount = 0;
$operatorQueueCount = 0;
$mailErrors = []; // メール異常終了件数専用
$errors = []; // 全体エラー収集用
$allQueueItems = []; // 全オペレーターキュー作成用データ
// 【処理1】駐輪場の空き状況を取得する
$parkVacancyList = $this->getParkVacancyStatus();
Log::info('駐輪場空き状況取得完了', [
'total_parks' => count($parkVacancyList)
]);
// 各駐輪場に対する処理
foreach ($parkVacancyList as $parkVacancyData) {
// 配列をオブジェクトに変換
$parkVacancy = (object) $parkVacancyData;
$processedParksCount++;
Log::info('駐輪場処理開始', [
'park_id' => $parkVacancy->park_id,
'park_name' => $parkVacancy->park_name,
'psection_id' => $parkVacancy->psection_id,
'ptype_id' => $parkVacancy->ptype_id,
'vacant_count' => $parkVacancy->vacant_count
]);
// 【判断1】空き状況判定
if ($parkVacancy->vacant_count < 1) {
Log::info('空きなし - 処理スキップ', [
'park_id' => $parkVacancy->park_id,
'vacant_count' => $parkVacancy->vacant_count
]);
continue;
}
$vacantParksCount++;
// 【処理2】空き待ち者の情報を取得する
$waitingUsers = $this->getWaitingUsersInfo(
$parkVacancy->park_id,
$parkVacancy->psection_id,
$parkVacancy->ptype_id
);
// 【判断2】取得件数判定
if (empty($waitingUsers)) {
Log::info('空き待ち者なし', [
'park_id' => $parkVacancy->park_id
]);
continue;
}
$totalWaitingUsers += count($waitingUsers);
Log::info('空き待ち者情報取得完了', [
'park_id' => $parkVacancy->park_id,
'waiting_users_count' => count($waitingUsers)
]);
// 【処理3】空き待ち者への通知、またはオペレーターキュー追加処理
$notificationResult = $this->processWaitingUsersNotification(
$waitingUsers,
$parkVacancy
);
$notificationSuccessCount += $notificationResult['notification_success_count'];
$operatorQueueCount += $notificationResult['operator_queue_count'];
if (!empty($notificationResult['errors'])) {
$mailErrors = array_merge($mailErrors, $notificationResult['errors']);
$errors = array_merge($errors, $notificationResult['errors']);
}
// オペレーターキュー作成用データを収集
if (!empty($notificationResult['queue_items'])) {
$allQueueItems = array_merge($allQueueItems ?? [], $notificationResult['queue_items']);
}
}
// 【処理4】仕様書準拠先に呼び出し、成功時のみ内部変数を更新
$queueErrorCount = 0; // キュー登録異常終了件数(累計)
$queueSuccessCount = 0; // キュー登録正常終了件数(累計)
foreach ($allQueueItems as $queueItem) {
// 仕様書準拠:在呼叫前先計算"如果這次成功會是第幾件",確保記錄反映最新件數
$predictedSuccessCount = $queueSuccessCount + 1;
$predictedErrorCount = $queueErrorCount; // 暂时保持当前错误计数
$queueResult = $this->addToOperatorQueue(
$queueItem['waiting_user'],
$queueItem['park_vacancy'],
$queueItem['batch_comment'],
$notificationSuccessCount, // 最終メール正常終了件数
$predictedSuccessCount, // 預測成功時的件數(包含本次)
count($mailErrors), // 現在のメール異常終了件数(動態計算)
$predictedErrorCount // 現在のキュー登録異常終了件数
);
// 仕様書:根据实际结果决定是否采用预测值
if ($queueResult['success']) {
$queueSuccessCount = $predictedSuccessCount; // 采用预测的成功计数
} else {
$queueErrorCount++; // 失败时递增错误计数
// 仕様書:包含具体错误消息,满足"エラーメッセージ/スタックトレースを保持"要求
$errorDetail = $queueResult['error'] ?? 'Unknown error';
$queueErrorInfo = sprintf('キュー登録失敗:予約ID:%d - %s',
$queueItem['waiting_user']->reserve_id ?? 0,
$errorDetail
);
$errors[] = $queueErrorInfo; // 加入总错误统计(包含具体原因)
Log::error('オペレーターキュー作成失敗', [
'user_id' => $queueItem['waiting_user']->user_id,
'reserve_id' => $queueItem['waiting_user']->reserve_id ?? 0,
'error' => $errorDetail
]);
}
}
$endTime = now();
$duration = $startTime->diffInSeconds($endTime);
Log::info('SHJ-5 空き待ち通知処理完了', [
'duration_seconds' => $duration,
'processed_parks_count' => $processedParksCount,
'vacant_parks_count' => $vacantParksCount,
'total_waiting_users' => $totalWaitingUsers,
'notification_success_count' => $notificationSuccessCount,
'operator_queue_success_count' => $queueSuccessCount, // 仕様書:正常完了件数
'queue_error_count' => $queueErrorCount,
'mail_error_count' => count($mailErrors), // メール異常終了件数(分離)
'total_error_count' => count($errors) // 全体エラー件数
]);
// 仕様書に基づく内部変数.ステータスコメント生成
$statusComment = sprintf(
'メール正常終了件数:%dメール異常終了件数%dキュー登録正常終了件数%dキュー登録異常終了件数%d',
$notificationSuccessCount,
count($mailErrors), // メール異常終了件数(キュー失敗を除外)
$queueSuccessCount ?? 0, // 実際のキュー登録成功件数
$queueErrorCount ?? 0 // 実際のキュー登録失敗件数
);
return [
'success' => true,
'message' => 'SHJ-5 空き待ち通知処理が正常に完了しました',
'processed_parks_count' => $processedParksCount,
'vacant_parks_count' => $vacantParksCount,
'total_waiting_users' => $totalWaitingUsers,
'notification_success_count' => $notificationSuccessCount,
'operator_queue_count' => $queueSuccessCount ?? 0, // 仕様書:正常完了件数を使用
'error_count' => count($errors),
'errors' => $errors,
'duration_seconds' => $duration,
'status_comment' => $statusComment // SHJ-8用の完全なステータスコメント
];
} catch (Exception $e) {
Log::error('SHJ-5 空き待ち通知処理でエラーが発生', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'message' => 'SHJ-5 空き待ち通知処理でエラーが発生: ' . $e->getMessage(),
'error_details' => $e->getMessage()
];
}
}
/**
* 【処理1】駐輪場の空き状況を取得する
*
* 仕様書に基づくSQL:
* - zone表から標準台数と現在の契約台数を比較
* - 空きがある駐輪場の情報を取得
*
* @return array 駐輪場空き状況リスト
*/
private function getParkVacancyStatus(): array
{
try {
// ゾーン毎の契約台数を取得
$contractCounts = DB::table('regular_contract as T1')
->select([
'T1.park_id',
'T1.psection_id',
'T5.ptype_id',
DB::raw('count(T1.contract_id) as contract_count')
])
->join('park as T2', 'T1.park_id', '=', 'T2.park_id')
->join('price_a as T5', function($join) {
$join->on('T1.park_id', '=', 'T5.park_id')
->on('T1.price_parkplaceid', '=', 'T5.price_parkplaceid')
->on('T1.psection_id', '=', 'T5.psection_id');
})
->where([
['T1.contract_flag', '=', 1], // 有効契約
['T2.park_close_flag', '=', 0], // 駐輪場未閉鎖
])
// 契約有効期間内の条件
->whereRaw("date_format(now(), '%Y%m%d') BETWEEN T1.contract_periods AND T1.contract_periode")
->groupBy(['T1.park_id', 'T1.psection_id', 'T5.ptype_id'])
->get()
->keyBy(function($item) {
return $item->park_id . '_' . $item->psection_id . '_' . $item->ptype_id;
});
// ゾーン情報と照合して空き状況を算出
$vacancyList = DB::table('zone as T1')
->select([
'T1.park_id',
'T2.park_name',
'T1.psection_id',
'T1.ptype_id',
'T1.zone_standard',
'T3.psection_subject',
'T4.ptype_subject'
])
->join('park as T2', 'T1.park_id', '=', 'T2.park_id')
->join('psection as T3', 'T1.psection_id', '=', 'T3.psection_id')
->join('ptype as T4', 'T1.ptype_id', '=', 'T4.ptype_id')
->where([
['T1.delete_flag', '=', 0], // ゾーン有効
['T2.park_close_flag', '=', 0], // 駐輪場開設
])
->get()
->map(function($zone) use ($contractCounts) {
$key = $zone->park_id . '_' . $zone->psection_id . '_' . $zone->ptype_id;
$contractCount = isset($contractCounts[$key]) ? $contractCounts[$key]->contract_count : 0;
$zone->contract_count = $contractCount;
$zone->vacant_count = max(0, $zone->zone_standard - $contractCount);
return $zone;
})
->filter(function($zone) {
return $zone->vacant_count > 0; // 空きがあるもののみ
})
->values();
Log::info('駐輪場空き状況算出完了', [
'total_zones' => count($vacancyList),
'vacant_zones' => $vacancyList->filter(function($v) {
return $v->vacant_count > 0;
})->count()
]);
return $vacancyList->toArray();
} catch (Exception $e) {
Log::error('駐輪場空き状況取得エラー', [
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 【処理2】空き待ち者の情報を取得する
*
* 仕様書に基づく取得条件:
* - 契約未紐付contract_id IS NULL
* - 退会でないuser_quit_flag <> 1
* - 有効な予約valid_flag = 1
* - 予約日時順で取得reserve_date昇順
*
* @param int $parkId 駐輪場ID
* @param int $psectionId 車種区分ID
* @param int $ptypeId 駐輪分類ID
* @return array 空き待ち者情報リスト
*/
private function getWaitingUsersInfo(int $parkId, int $psectionId, int $ptypeId): array
{
try {
$waitingUsers = DB::table('reserve as T1')
->select([
'T1.reserve_id',
'T1.user_id',
'T1.park_id',
'T1.psection_id',
'T1.ptype_id',
'T1.reserve_order',
'T1.reserve_date',
'T1.reserve_manual', // 手動通知フラグ
'T1.contract_id', // 契約紐付確認用
'T2.user_name',
'T2.user_primemail',
'T2.user_submail', // 副メールアドレス
'T2.user_manual_regist_flag', // 手動登録フラグ
'T2.user_quit_flag', // 退会フラグ
'T3.park_name',
'T4.psection_subject',
'T5.ptype_subject'
])
->join('user as T2', 'T1.user_id', '=', 'T2.user_id')
->join('park as T3', 'T1.park_id', '=', 'T3.park_id')
->join('psection as T4', 'T1.psection_id', '=', 'T4.psection_id')
->join('ptype as T5', 'T1.ptype_id', '=', 'T5.ptype_id')
->where([
['T1.park_id', '=', $parkId],
['T1.psection_id', '=', $psectionId],
['T1.ptype_id', '=', $ptypeId],
['T1.valid_flag', '=', 1], // 有効な予約
['T2.user_quit_flag', '<>', 1] // 退会でない
])
->whereNull('T1.contract_id') // 契約未紐付
->orderBy('T1.reserve_date', 'asc') // 仕様書に基づく予約日時順
->get()
->toArray();
Log::info('空き待ち者情報取得完了', [
'park_id' => $parkId,
'psection_id' => $psectionId,
'ptype_id' => $ptypeId,
'waiting_users_count' => count($waitingUsers)
]);
return $waitingUsers;
} catch (Exception $e) {
Log::error('空き待ち者情報取得エラー', [
'park_id' => $parkId,
'psection_id' => $psectionId,
'ptype_id' => $ptypeId,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 【処理3】空き待ち者への通知、またはオペレーターキュー追加処理
*
* 仕様書に基づく分岐処理:
* - 手動通知フラグ判定reserve_manual
* - メール送信成功時のreserve.sent_date更新
* - 失敗時のオペレーターキュー追加(最終統計で処理)
*
* @param array $waitingUsers 空き待ち者リスト
* @param object $parkVacancy 駐輪場空き情報
* @return array 通知処理結果
*/
private function processWaitingUsersNotification(array $waitingUsers, object $parkVacancy): array
{
$notificationSuccessCount = 0;
$operatorQueueCount = 0;
$errors = [];
$queueItems = []; // オペレーターキュー作成用データ収集
try {
// 空きがある分だけ処理(先着順)
$availableSpots = min($parkVacancy->vacant_count, count($waitingUsers));
for ($i = 0; $i < $availableSpots; $i++) {
$waitingUserData = $waitingUsers[$i];
// 配列をオブジェクトに変換
$waitingUser = (object) $waitingUserData;
try {
// 【仕様判断】手動通知フラグチェック
if ($waitingUser->reserve_manual == 1) {
// 手動通知 → オペレーターキュー作成データ収集
$batchComment = '手動通知フラグ設定のため予約ID:' . $waitingUser->reserve_id;
$queueItems[] = [
'waiting_user' => $waitingUser,
'park_vacancy' => $parkVacancy,
'batch_comment' => $batchComment
];
$operatorQueueCount++;
Log::info('手動通知フラグによりオペレーターキュー登録予定', [
'user_id' => $waitingUser->user_id,
'reserve_id' => $waitingUser->reserve_id
]);
} else {
// 自動通知 → メール送信を試行
$mailResult = $this->sendVacancyNotificationMail($waitingUser, $parkVacancy);
if ($mailResult['success']) {
// メール送信成功 → reserve.sent_date更新
$this->updateReserveSentDate($waitingUser->reserve_id);
$notificationSuccessCount++;
Log::info('空き待ち通知メール送信成功', [
'user_id' => $waitingUser->user_id,
'reserve_id' => $waitingUser->reserve_id,
'park_id' => $parkVacancy->park_id
]);
} else {
// メール送信失敗 → オペレーターキュー作成データ収集
$shjSevenError = $mailResult['error'] ?? $mailResult['message'] ?? 'SHJ-7メール送信エラー';
$batchComment = $shjSevenError . '予約ID:' . $waitingUser->reserve_id;
$queueItems[] = [
'waiting_user' => $waitingUser,
'park_vacancy' => $parkVacancy,
'batch_comment' => $batchComment
];
$operatorQueueCount++;
$errors[] = $shjSevenError;
}
}
} catch (Exception $e) {
Log::error('空き待ち者通知処理エラー', [
'user_id' => $waitingUser->user_id,
'reserve_id' => $waitingUser->reserve_id,
'error' => $e->getMessage()
]);
// エラー発生時もオペレーターキュー作成データ収集
$batchComment = 'システムエラー:' . $e->getMessage() . '予約ID:' . $waitingUser->reserve_id;
$queueItems[] = [
'waiting_user' => $waitingUser,
'park_vacancy' => $parkVacancy,
'batch_comment' => $batchComment
];
$operatorQueueCount++;
$errors[] = $e->getMessage();
}
}
return [
'notification_success_count' => $notificationSuccessCount,
'operator_queue_count' => $operatorQueueCount,
'errors' => $errors,
'queue_items' => $queueItems // 後でキュー作成用
];
} catch (Exception $e) {
Log::error('空き待ち者通知処理全体エラー', [
'park_id' => $parkVacancy->park_id,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 空き待ち通知メールを送信
*
* 仕様書に基づくSHJ-7呼び出し:
* - 主メールアドレス・副メールアドレスを正しく渡す
* - 必要なパラメータを全て設定
*
* @param object $waitingUser 空き待ち者情報
* @param object $parkVacancy 駐輪場空き情報
* @return array 送信結果
*/
private function sendVacancyNotificationMail(object $waitingUser, object $parkVacancy): array
{
try {
// ShjMailSendServiceを利用してメール送信
$mailService = app(ShjMailSendService::class);
// 空き待ち通知用のメールテンプレートID予約告知通知
// OperatorQueの定数と合わせて4番を使用
$mailTemplateId = 4; // 予約告知通知のテンプレートID
// 仕様書No1/No2に基づく主メール・副メール設定
$mainEmail = $waitingUser->user_primemail ?? '';
$subEmail = $waitingUser->user_submail ?? '';
// メール送信実行(仕様書準拠)
$mailResult = $mailService->executeMailSend(
$mainEmail,
$subEmail,
$mailTemplateId
);
Log::info('空き待ち通知メール送信試行完了', [
'user_id' => $waitingUser->user_id,
'main_email' => $mainEmail,
'sub_email' => $subEmail,
'mail_template_id' => $mailTemplateId,
'result_success' => $mailResult['success'] ?? false
]);
return $mailResult;
} catch (Exception $e) {
Log::error('空き待ち通知メール送信エラー', [
'user_id' => $waitingUser->user_id,
'main_email' => $waitingUser->user_primemail ?? '',
'sub_email' => $waitingUser->user_submail ?? '',
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* reserve.sent_date及びvalid_flag更新
*
* 仕様書準拠メール送信成功時にreserve.sent_dateとvalid_flag=0を同時更新
* 重複通知を防ぎ、処理済みマークを設定
*
* @param int $reserveId 予約ID
* @return void
*/
private function updateReserveSentDate(int $reserveId): void
{
try {
DB::table('reserve')
->where('reserve_id', $reserveId)
->update([
'sent_date' => now()->format('Y-m-d H:i:s'),
'valid_flag' => 0, // 仕様書メール送信成功時に0に更新
'updated_at' => now()
]);
Log::info('reserve.sent_date及びvalid_flag更新完了', [
'reserve_id' => $reserveId,
'sent_date' => now()->format('Y-m-d H:i:s'),
'valid_flag' => 0
]);
} catch (Exception $e) {
Log::error('reserve.sent_date及びvalid_flag更新エラー', [
'reserve_id' => $reserveId,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* オペレーターキューに追加
*
* 仕様書に基づくキュー登録:
* - que_comment: 空文字列
* - que_status_comment: 仕様書完全準拠形式(統計情報含む)
* - operator_id: 9999999固定
*
* @param object $waitingUser 空き待ち者情報
* @param object $parkVacancy 駐輪場空き情報
* @param string $batchComment 内部変数.バッチコメント
* @param int $mailSuccessCount メール正常終了件数
* @param int $queueSuccessCount キュー登録正常終了件数
* @param int $mailErrorCount メール異常終了件数
* @param int $queueErrorCount キュー登録異常終了件数
* @return array 追加結果
*/
private function addToOperatorQueue(object $waitingUser, object $parkVacancy, string $batchComment, int $mailSuccessCount, int $queueSuccessCount, int $mailErrorCount, int $queueErrorCount): array
{
try {
// 仕様書完全準拠駐輪場名駐輪分類名車種区分名空き台数…対象予約ID…内部変数.バッチコメント/内部変数.メール正常終了件数…メール異常終了件数…キュー登録正常終了件数…キュー登録異常終了件数…
$statusComment = sprintf(
'%s%s%s空き台数%d台対象予約ID%d%sメール正常終了件数%dメール異常終了件数%dキュー登録正常終了件数%dキュー登録異常終了件数%d',
$waitingUser->park_name ?? '',
$waitingUser->ptype_subject ?? '', // 駐輪分類名
$waitingUser->psection_subject ?? '', // 車種区分名
$parkVacancy->vacant_count ?? 0,
$waitingUser->reserve_id ?? 0,
$batchComment, // 内部変数.バッチコメント
$mailSuccessCount, // 内部変数.メール正常終了件数
$mailErrorCount,
$queueSuccessCount,
$queueErrorCount
);
OperatorQue::create([
'que_class' => 4, // 予約告知通知
'user_id' => $waitingUser->user_id,
'contract_id' => null,
'park_id' => $waitingUser->park_id,
'que_comment' => '', // 仕様書:空文字列
'que_status' => 1, // キュー発生
'que_status_comment' => $statusComment, // 仕様書:完全準拠形式
'work_instructions' => '空き待ち者への連絡をお願いします。',
'operator_id' => 9999999, // 仕様書固定値9999999
]);
Log::info('オペレーターキュー追加成功', [
'user_id' => $waitingUser->user_id,
'park_id' => $waitingUser->park_id,
'reserve_id' => $waitingUser->reserve_id,
'que_class' => 4,
'operator_id' => 9999999,
'batch_comment' => $batchComment,
'mail_success_count' => $mailSuccessCount,
'mail_error_count' => $mailErrorCount,
'queue_success_count' => $queueSuccessCount,
'queue_error_count' => $queueErrorCount,
'status_comment' => $statusComment
]);
return ['success' => true];
} catch (Exception $e) {
Log::error('オペレーターキュー追加エラー', [
'user_id' => $waitingUser->user_id,
'park_id' => $waitingUser->park_id,
'reserve_id' => $waitingUser->reserve_id ?? null,
'batch_comment' => $batchComment,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
}

View File

@ -7,6 +7,7 @@ use App\Models\RegularContract;
use App\Models\Park; use App\Models\Park;
use App\Models\PriceA; use App\Models\PriceA;
use App\Models\Batch\BatchLog; use App\Models\Batch\BatchLog;
use App\Services\ShjThirteenService;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Carbon\Carbon; use Carbon\Carbon;
@ -170,6 +171,7 @@ class ShjFourBService
'T1.billing_amount', 'T1.billing_amount',
'T4.price_ptypeid as ptype_id', 'T4.price_ptypeid as ptype_id',
'T1.psection_id', 'T1.psection_id',
'T1.zone_id',
'T1.update_flag', 'T1.update_flag',
'T1.reserve_id', 'T1.reserve_id',
'T1.contract_payment_number', 'T1.contract_payment_number',
@ -765,27 +767,57 @@ class ShjFourBService
/** /**
* SHJ-13実行処理(新規のみ) * SHJ-13実行処理(新規のみ)
* *
* ShjThirteenServiceを使用した契約台数追加処理
*
* @param object $contract * @param object $contract
* @return array * @return array
*/ */
private function triggerShjThirteen($contract): array private function triggerShjThirteen($contract): array
{ {
// TODO: SHJ-13の具体的な処理を実装
// 現在はプレースホルダー
Log::info('SHJ-4B SHJ-13実行処理', [ Log::info('SHJ-4B SHJ-13実行処理', [
'contract_id' => $contract->contract_id, 'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'park_id' => $contract->park_id, 'park_id' => $contract->park_id,
'psection_id' => $contract->psection_id,
'ptype_id' => $contract->ptype_id,
'zone_id' => $contract->zone_id,
]);
try {
// 契約データ準備
$contractData = [
'contract_id' => $contract->contract_id,
'park_id' => $contract->park_id,
'psection_id' => $contract->psection_id,
'ptype_id' => $contract->ptype_id,
'zone_id' => $contract->zone_id,
];
// ShjThirteenService実行
$shjThirteenService = app(ShjThirteenService::class);
$result = $shjThirteenService->execute($contractData);
Log::info('SHJ-4B SHJ-13実行完了', [
'contract_id' => $contract->contract_id,
'result' => $result,
]);
return $result;
} catch (\Throwable $e) {
Log::error('SHJ-4B SHJ-13実行エラー', [
'contract_id' => $contract->contract_id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]); ]);
return [ return [
'triggered' => true, 'result' => 1,
'method' => 'placeholder', 'error_code' => $e->getCode() ?: 1999,
'message' => 'SHJ-13処理は実装予定です', 'error_message' => $e->getMessage(),
'contract_id' => $contract->contract_id, 'stack_trace' => $e->getTraceAsString(),
]; ];
} }
}
/** /**
* 利用者メール送信処理 * 利用者メール送信処理

View File

@ -0,0 +1,346 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Batch\BatchLog;
use Carbon\Carbon;
/**
* SHJ-13 契約台数追加処理サービス
*
* 新規契約時の契約台数を park_number・zone テーブルに反映する処理
* SHJ-4B の副作用処理として実行される
*/
class ShjThirteenService
{
/**
* SHJ-13 契約台数追加処理実行
*
* 【処理1】契約台数を反映する - park_number・zone テーブルの契約台数を+1更新
* 【処理2】バッチ処理ログを作成する - SHJ-8共通仕様でログ登録
*
* @param array $contractData 契約データ
* @return array 処理結果
*/
public function execute(array $contractData): array
{
$startTime = now();
Log::info('SHJ-13 契約台数追加処理開始', [
'contract_id' => $contractData['contract_id'] ?? null,
'park_id' => $contractData['park_id'] ?? null,
'psection_id' => $contractData['psection_id'] ?? null,
'ptype_id' => $contractData['ptype_id'] ?? null,
'zone_id' => $contractData['zone_id'] ?? null,
]);
try {
// パラメータ検証
$validationResult = $this->validateParameters($contractData);
if (!$validationResult['valid']) {
return $this->createErrorResult(
1001,
'パラメータエラー: ' . $validationResult['message']
);
}
// 【処理1・2】契約台数反映とバッチログ作成を一体で実行
$processResult = $this->executeProcessWithLogging($contractData);
if (!$processResult['success']) {
return $this->createErrorResult(
$processResult['error_code'],
$processResult['error_message'],
$processResult['stack_trace'] ?? ''
);
}
$statusComment = $processResult['status_comment'];
$endTime = now();
Log::info('SHJ-13 契約台数追加処理完了', [
'contract_id' => $contractData['contract_id'],
'execution_time' => $startTime->diffInSeconds($endTime),
'updated_count' => $processResult['updated_count'],
'status_comment' => $statusComment,
]);
return ['result' => 0];
} catch (\Throwable $e) {
Log::error('SHJ-13 契約台数追加処理例外エラー', [
'contract_data' => $contractData,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return $this->createErrorResult(
$e->getCode() ?: 1999,
$e->getMessage(),
$e->getTraceAsString()
);
}
}
/**
* パラメータ検証
*
* @param array $contractData
* @return array
*/
private function validateParameters(array $contractData): array
{
$errors = [];
if (empty($contractData['park_id'])) {
$errors[] = '駐輪場IDが設定されていません';
}
if (empty($contractData['psection_id'])) {
$errors[] = '車種区分IDが設定されていません';
}
if (empty($contractData['ptype_id'])) {
$errors[] = '駐輪分類IDが設定されていません';
}
if (empty($contractData['zone_id'])) {
$errors[] = 'ゾーンIDが設定されていません';
}
if (!empty($errors)) {
return [
'valid' => false,
'message' => implode(', ', $errors),
'details' => $errors,
];
}
return ['valid' => true];
}
/**
* 契約台数反映とバッチログ作成の一体処理
*
* park_number・zone テーブルの契約台数を+1更新し、バッチログを作成
* 全てを1つのトランザクション内で実行
*
* @param array $contractData
* @return array
*/
private function executeProcessWithLogging(array $contractData): array
{
try {
return DB::transaction(function() use ($contractData) {
// 各テーブルの名称取得
$names = $this->getTableNames($contractData);
if (!$names['success']) {
throw new \Exception('名称取得エラー: ' . $names['message'], 1002);
}
// park_number テーブル更新
$parkNumberUpdated = DB::table('park_number')
->where('park_id', $contractData['park_id'])
->where('psection_id', $contractData['psection_id'])
->where('ptype_id', $contractData['ptype_id'])
->increment('park_number', 1, [
'updated_at' => now(),
'operator_id' => 'SHJ-13',
]);
if ($parkNumberUpdated === 0) {
throw new \Exception('park_numberテーブルの対象レコードが存在しません', 1011);
}
// zone テーブル更新
$zoneUpdated = DB::table('zone')
->where('zone_id', $contractData['zone_id'])
->increment('zone_number', 1, [
'updated_at' => now(),
'ope_id' => 'SHJ-13',
]);
if ($zoneUpdated === 0) {
throw new \Exception('zoneテーブルの対象レコードが存在しません', 1012);
}
// 更新後の契約台数取得
$updatedZone = DB::table('zone')
->where('zone_id', $contractData['zone_id'])
->first(['zone_number']);
// ステータスコメント構築
$statusComment = $this->buildStatusComment($names['names'], $updatedZone->zone_number);
// バッチ処理ログ作成(同一トランザクション内)
$currentDate = now()->format('Y/m/d');
$batchLog = BatchLog::createBatchLog(
'SHJ-13', // process_name
BatchLog::STATUS_SUCCESS,
[
'device_id' => 1,
'job_name' => 'SHJ-13',
'status' => 'success',
'status_comment' => $statusComment,
'created_date' => $currentDate,
'updated_date' => $currentDate,
'contract_id' => $contractData['contract_id'] ?? null,
'park_id' => $contractData['park_id'],
'psection_id' => $contractData['psection_id'],
'ptype_id' => $contractData['ptype_id'],
'zone_id' => $contractData['zone_id'],
],
$statusComment
);
Log::info('SHJ-13 契約台数更新・ログ作成完了', [
'contract_id' => $contractData['contract_id'],
'park_number_updated' => $parkNumberUpdated,
'zone_updated' => $zoneUpdated,
'updated_count' => $updatedZone->zone_number,
'batch_log_id' => $batchLog->id,
'status_comment' => $statusComment,
]);
return [
'success' => true,
'updated_count' => $updatedZone->zone_number,
'status_comment' => $statusComment,
];
});
} catch (\Throwable $e) {
Log::error('SHJ-13 契約台数更新・ログ作成エラー', [
'contract_data' => $contractData,
'error_code' => $e->getCode(),
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return [
'success' => false,
'error_code' => $e->getCode() ?: 1010,
'error_message' => $e->getMessage(),
'stack_trace' => $e->getTraceAsString(),
];
}
}
/**
* テーブル名称取得
*
* @param array $contractData
* @return array
*/
private function getTableNames(array $contractData): array
{
try {
// 駐輪場名取得
$park = DB::table('park')
->where('park_id', $contractData['park_id'])
->first(['park_name']);
if (!$park) {
return [
'success' => false,
'message' => "駐輪場が見つかりません: park_id={$contractData['park_id']}",
];
}
// 駐輪分類名取得
$ptype = DB::table('ptype')
->where('ptype_id', $contractData['ptype_id'])
->first(['ptype_subject']);
if (!$ptype) {
return [
'success' => false,
'message' => "駐輪分類が見つかりません: ptype_id={$contractData['ptype_id']}",
];
}
// 車種区分名取得
$psection = DB::table('psection')
->where('psection_id', $contractData['psection_id'])
->first(['psection_subject']);
if (!$psection) {
return [
'success' => false,
'message' => "車種区分が見つかりません: psection_id={$contractData['psection_id']}",
];
}
// ゾーン名取得
$zone = DB::table('zone')
->where('zone_id', $contractData['zone_id'])
->first(['zone_name']);
if (!$zone) {
return [
'success' => false,
'message' => "ゾーンが見つかりません: zone_id={$contractData['zone_id']}",
];
}
return [
'success' => true,
'names' => [
'park_name' => $park->park_name,
'ptype_subject' => $ptype->ptype_subject,
'psection_subject' => $psection->psection_subject,
'zone_name' => $zone->zone_name,
],
];
} catch (\Throwable $e) {
return [
'success' => false,
'message' => 'テーブル名称取得エラー: ' . $e->getMessage(),
'details' => $e->getTraceAsString(),
];
}
}
/**
* ステータスコメント構築
*
* 形式:駐輪場名/駐輪分類名/車種区分名/ゾーン名/現在契約台数(更新後):{数値}
*
* @param array $names
* @param int $updatedCount
* @return string
*/
private function buildStatusComment(array $names, int $updatedCount): string
{
return sprintf(
'%s%s%s%s現在契約台数更新後%d',
$names['park_name'],
$names['ptype_subject'],
$names['psection_subject'],
$names['zone_name'],
$updatedCount
);
}
/**
* エラー結果作成
*
* @param int $errorCode
* @param string $errorMessage
* @param string $stackTrace
* @return array
*/
private function createErrorResult(int $errorCode, string $errorMessage, string $stackTrace = ''): array
{
return [
'result' => 1,
'error_code' => $errorCode,
'error_message' => $errorMessage,
'stack_trace' => $stackTrace,
];
}
}

View File

@ -1,61 +0,0 @@
/* iCheck plugin skins
----------------------------------- */
@import url("minimal/_all.css");
/*
@import url("minimal/minimal.css");
@import url("minimal/red.css");
@import url("minimal/green.css");
@import url("minimal/blue.css");
@import url("minimal/aero.css");
@import url("minimal/grey.css");
@import url("minimal/orange.css");
@import url("minimal/yellow.css");
@import url("minimal/pink.css");
@import url("minimal/purple.css");
*/
@import url("square/_all.css");
/*
@import url("square/square.css");
@import url("square/red.css");
@import url("square/green.css");
@import url("square/blue.css");
@import url("square/aero.css");
@import url("square/grey.css");
@import url("square/orange.css");
@import url("square/yellow.css");
@import url("square/pink.css");
@import url("square/purple.css");
*/
@import url("flat/_all.css");
/*
@import url("flat/flat.css");
@import url("flat/red.css");
@import url("flat/green.css");
@import url("flat/blue.css");
@import url("flat/aero.css");
@import url("flat/grey.css");
@import url("flat/orange.css");
@import url("flat/yellow.css");
@import url("flat/pink.css");
@import url("flat/purple.css");
*/
@import url("line/_all.css");
/*
@import url("line/line.css");
@import url("line/red.css");
@import url("line/green.css");
@import url("line/blue.css");
@import url("line/aero.css");
@import url("line/grey.css");
@import url("line/orange.css");
@import url("line/yellow.css");
@import url("line/pink.css");
@import url("line/purple.css");
*/
@import url("polaris/polaris.css");
@import url("futurico/futurico.css");

View File

@ -12,7 +12,6 @@
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css"/> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css"/>
<link rel="stylesheet" href="{{ asset('assets/css/mypage/font-awesome.min.css') }}"> <link rel="stylesheet" href="{{ asset('assets/css/mypage/font-awesome.min.css') }}">
<link href="{{ asset('assets/css/mypage/bootstrap.min.css') }}" rel="stylesheet"> <link href="{{ asset('assets/css/mypage/bootstrap.min.css') }}" rel="stylesheet">
<link rel="stylesheet" href="{{ asset('assets/css/mypage/all.css') }}">
<link rel="stylesheet" href="{{ asset('assets/css/mypage/picker.min.css') }}"> <link rel="stylesheet" href="{{ asset('assets/css/mypage/picker.min.css') }}">
<link rel="stylesheet" href="{{ asset('assets/css/mypage/tablesorter-blue.css') }}" type="text/css" media="print, projection, screen"> <link rel="stylesheet" href="{{ asset('assets/css/mypage/tablesorter-blue.css') }}" type="text/css" media="print, projection, screen">
<link href="{{ asset('assets/css/mypage/style.css') }}" rel="stylesheet"> <link href="{{ asset('assets/css/mypage/style.css') }}" rel="stylesheet">

View File

@ -0,0 +1,260 @@
@extends('layouts.app')
@section('content')
<main>
<section id="" class="container mt20 mb20">
<div class="row">
<div class="col-12 col-lg-6 mb20">
<div class="card border-success">
<div class="card-header border-success">
<h5 class="card-title text-success">定期契約情報</h5>
</div>
<div class="card-body">
<div class="slider_2-1">
@forelse($contracts as $contract)
@php
$now = \Carbon\Carbon::now();
$update_flag = $contract->contract_renewal;
$start_dd = $contract->update_grace_period_start_date;
$start_hm = $contract->update_grace_period_start_time;
$end_dd = $contract->update_grace_period_end_date;
$end_hm = $contract->update_grace_period_end_time;
$contract_end_dt = $contract->contract_periode ? \Carbon\Carbon::parse($contract->contract_periode) : null;
$periode_month = $contract_end_dt ? $contract_end_dt->month : null;
$periode_year = $contract_end_dt ? $contract_end_dt->year : null;
$bg = 'alert-warning';
$btn_text = '更新する';
$btn_active = true;
// 契約終了月より前は「ご契約中」
if ($now->lt($contract_end_dt)) {
$bg = 'bg-white';
$btn_text = 'ご契約中';
$btn_active = false;
} else {
// 契約終了月より後は猶予期間判定
if (is_numeric($start_dd) && is_numeric($end_dd)) {
// 開始日
$start_date = $contract_end_dt->format('Y-m-') . str_pad($start_dd, 2, '0', STR_PAD_LEFT);
$start_time = ($start_hm && preg_match('/^\d{2}:\d{2}$/', $start_hm)) ? $start_hm : '00:00';
$start_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $start_date . ' ' . $start_time . ':00');
// 終了日
if ($start_dd < $end_dd) {
$end_date=$contract_end_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT);
$end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59';
$end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00');
} else {
$next_month_dt = $contract_end_dt->copy()->addMonth();
$end_date = $next_month_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT);
$end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59';
$end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00');
}
} else {
$start_dt = null;
$end_dt = null;
}
// 猶予期間判定
if ($update_flag === 0) {
$bg = 'bg-white';
$btn_text = '手続き中';
$btn_active = false;
} elseif ($update_flag === 1) {
$bg = 'bg-white';
$btn_text = '更新済';
$btn_active = false;
} elseif ($start_dt && $end_dt && $now->between($start_dt, $end_dt)) {
// 猶予期間内
if ($contract_end_dt && $now->gt($contract_end_dt)) {
$bg = 'alert-danger';
$btn_text = '更新する';
$btn_active = true;
} else {
$bg = 'alert-warning';
$btn_text = '更新する';
$btn_active = true;
}
} else {
$bg = 'bg-white';
$btn_text = 'ご契約中';
$btn_active = false;
}
}
// 契約終了月の場合(既存ロジック)
if (is_numeric($start_dd) && is_numeric($end_dd)) {
// 開始日
$start_date = $contract_end_dt->format('Y-m-') . str_pad($start_dd, 2, '0', STR_PAD_LEFT);
$start_time = ($start_hm && preg_match('/^\d{2}:\d{2}$/', $start_hm)) ? $start_hm : '00:00';
$start_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $start_date . ' ' . $start_time . ':00');
// 終了日
if ($start_dd < $end_dd) {
$end_date=$contract_end_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT);
$end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59';
$end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00');
} else {
$next_month_dt = $contract_end_dt->copy()->addMonth();
$end_date = $next_month_dt->format('Y-m-') . str_pad($end_dd, 2, '0', STR_PAD_LEFT);
$end_time = ($end_hm && preg_match('/^\d{2}:\d{2}$/', $end_hm)) ? $end_hm : '23:59';
$end_dt = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $end_date . ' ' . $end_time . ':00');
}
} else {
$start_dt = null;
$end_dt = null;
}
// 以降は既存のボタン・背景色判定ロジック
if ($update_flag===0) {
$bg='bg-white';
$btn_text='手続き中';
$btn_active=false;
}
elseif ($update_flag===1) {
$bg='bg-white';
$btn_text='更新済';
$btn_active=false;
}
elseif (!is_null($end_dt) && $end_dt->gt($start_dt)) {
if ($start_dt && $now->lt($start_dt)) {
$bg = 'bg-white';
$btn_text = 'ご契約中';
$btn_active = false;
} else {
// 契約終了日を過ぎていて、更新可能期間内は赤背景
if ($contract_end_dt && $now->gt($contract_end_dt) && $start_dt && $end_dt && $now->between($start_dt, $end_dt)) {
$bg = 'alert-danger';
$btn_text = '更新する';
$btn_active = true;
} else {
$bg = 'alert-warning';
$btn_text = '更新する';
$btn_active = true;
}
}
}
elseif ($start_dt && $start_dt->gt($end_dt)) {
if ($now->lt($start_dt)) {
$bg = 'bg-white';
$btn_text = 'ご契約中';
$btn_active = false;
} elseif ($now->gte($start_dt) && $now->lte($contract_end_dt->copy()->endOfMonth())) {
$bg = 'alert-warning';
$btn_text = '更新する';
$btn_active = true;
} else {
$bg = 'alert-danger';
$btn_text = '更新する';
$btn_active = true;
}
}
@endphp
<div>
<div class="card {{ $bg }}">
<h6 class="mt10 ml10 font-weight-bold">{{ $contract->park_name }}</h6>
<table class="table table-sm">
<tr>
<th>{{ $contract->psection_subject ?? '' }}</th>
<td>{{ $contract->usertype_subject1 ?? '' }}</td>
</tr>
<tr>
<th>{{ $contract->ptype_subject ?? '' }}</th>
<td>{{ $contract->pplace_no ?? '' }}</td>
</tr>
<tr>
<th>定期契約ID</th>
<td>{{ $contract->contract_id }}</td>
</tr>
<tr>
<th>期間</th>
<td>{{ \Carbon\Carbon::parse($contract->contract_periods)->format('Y-m-d') }}から</td>
</tr>
<tr>
<th class="text-center" colspan="2"><span class="h2">{{ $contract->enable_months }}</span>ヶ月</th>
</tr>
<tr>
<td class="text-center" colspan="2">
@if($btn_active)
<a href="{{ url('regular_contract/update/' . $contract->contract_id) }}"
class="btn {{ $bg == 'alert-warning' ? 'btn-warning' : ($bg == 'alert-danger' ? 'btn-danger' : 'btn-outline-secondary disabled') }} badge-pill">
{{ $btn_text }}
</a>
@else
<button class="btn btn-outline-secondary badge-pill disabled">{{ $btn_text }}</button>
@endif
</td>
</tr>
</table>
</div>
</div>
@empty
<p class="text-center">定期契約情報はありません<br>
<a href="{{ url('regular_contract/create') }}" class="btn btn-block btn-lg btn-success">新規定期契約</a>
</p>
@endforelse
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 mb20">
<div class="card border-success mb20">
<div class="card-header border-success text-success">
<h5 class="card-title">シール発行</h5>
</div>
<div class="container slider_1-1">
@forelse($seals as $seal)
<article class="row">
<figure class="col-12 col-md-4 float-right mt50">
<div class="ml30 mt10 text-danger text-center">
@if(!empty($seal->contract_qr_id))
{!! QrCode::size(120)->generate($seal->contract_qr_id) !!}
@else
<div class="text-danger">QRコード<br>未発行</div>
@endif
</div>
</figure>
<div class="col-12 col-md-8">
<h6 class="mt30 ml20 font-weight-bold"></h6>
<table class="table table-sm ml20">
<tr>
<th style="padding-right:8px;padding-left:8px;">{{ $seal->psection_subject ?? '' }}</th>
<td style="padding-left:8px;">{{ $seal->usertype_subject1 ?? '' }}</td>
</tr>
<tr>
<th style="padding-right:8px;padding-left:8px;">{{ $seal->ptype_subject ?? '' }}</th>
<td style="padding-left:8px;">{{ $seal->pplace_no ?? '' }}</td>
</tr>
<tr>
<th style="padding-right:8px;padding-left:8px;">定期契約ID</th>
<td style="padding-left:8px;">{{ $seal->contract_id }}</td>
</tr>
<tr>
<th class="text-center" colspan="2">
<span class="h2">{{ $seal->enable_months }}</span>ヶ月
</th>
</tr>
</table>
</div>
</article>
@empty
<div class="text-center p-4">シール発行対象の契約はありません。</div>
@endforelse
</div>
</div>
<div id="my-information" class="card border-success">
<div class="card-header border-success text-success">
<h5 class="card-title">{{ $user_name }}さんへのお知らせ
<a href="{{ url('/user_information') }}" class="badge badge-secondary badge-pill float-right">お知らせ一覧を見る</a>
</h5>
</div>
<ul class="info-slider_1-1">
@if($information)
<li>
<span class="small" style="margin-right: 1em;">{{ $information->entry_date }}</span>
{{ $information->user_information_history }}
</li>
@else
<li class="text-center">お知らせはありません。</li>
@endif
</ul>
</div>
</div>
</div>
</section>
</main>
@endsection

View File

@ -1,4 +1,3 @@
<?php <?php
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -12,6 +11,7 @@ use App\Http\Controllers\InquiryConfirmController;
use App\Http\Controllers\LoginController; use App\Http\Controllers\LoginController;
use App\Http\Controllers\MemberRegistrationController; use App\Http\Controllers\MemberRegistrationController;
use App\Http\Controllers\PasswordReminderController; use App\Http\Controllers\PasswordReminderController;
use App\Http\Controllers\MypageController;
use App\Http\Controllers\UserInfoController; use App\Http\Controllers\UserInfoController;
use App\Http\Controllers\UserEditController; use App\Http\Controllers\UserEditController;
use App\Http\Controllers\UserEditConfirmController; use App\Http\Controllers\UserEditConfirmController;
@ -73,15 +73,8 @@ Route::get('/login', function () {
return redirect()->route('swo8_1'); return redirect()->route('swo8_1');
})->name('login'); })->name('login');
// マイページ画面へのリダイレクト // マイページ
Route::get('/mypage', function () { Route::get('/mypage', [MypageController::class, 'index'])->name('mypage');
return '
<div style="padding:2em;">
<h2>マイページ(仮)</h2>
<a href="' . route('user.info') . '" class="btn btn-primary">ユーザー情報を確認する</a>
</div>
';
})->name('mypage');
// ユーザー情報確認・編集 // ユーザー情報確認・編集
Route::get('/user/info', [UserInfoController::class, 'show'])->name('user.info'); Route::get('/user/info', [UserInfoController::class, 'show'])->name('user.info');