Compare commits

..

99 Commits

Author SHA1 Message Date
781e665d7e Merge pull request 'ユーザー情報確認画面修正' (#52) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 27s
Reviewed-on: #52
2025-10-15 16:38:05 +09:00
a266cdca57 ユーザー情報確認画面修正 2025-10-15 16:37:13 +09:00
Your Name
10a917b556 【更新】SHJ関連の修正
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
2025-10-10 19:55:46 +09:00
d22bafbc93 メール情報を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
Deploy preview (main_watanabe) / deploy (push) Successful in 15s
2025-10-10 13:06:41 +09:00
0c076466a9 resources/views/regular_contract/update.blade.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
2025-10-09 16:23:43 +09:00
2fdb3ce74a app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 25s
2025-10-09 16:03:04 +09:00
778b3ac9cc Merge pull request '新規契約減免区分修正' (#51) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 25s
Reviewed-on: #51
2025-10-09 15:55:53 +09:00
ae8315ba89 新規契約減免区分修正 2025-10-09 15:55:21 +09:00
fcf07cecc4 Merge pull request '契約履歴 表示条件修正' (#50) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
Reviewed-on: #50
2025-10-09 10:23:37 +09:00
c602b214e4 契約履歴 表示条件修正 2025-10-09 10:22:51 +09:00
a085a47e04 app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-08 16:59:54 +09:00
195f130a46 Merge pull request 'ルート追加' (#49) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
Reviewed-on: #49
2025-10-08 16:56:23 +09:00
3c40565377 ルート追加 2025-10-08 16:55:48 +09:00
0a102e2a47 Merge pull request '契約期間選択修正' (#48) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
Reviewed-on: #48
2025-10-08 16:50:02 +09:00
a6637c9f72 契約期間選択修正 2025-10-08 16:49:40 +09:00
61b3283b58 Merge pull request '契約期間選択エラー処理修正' (#47) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
Reviewed-on: #47
2025-10-08 16:35:52 +09:00
c1e95ce881 契約期間選択エラー処理修正 2025-10-08 16:35:28 +09:00
7e2a25d37b routes/web.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 25s
2025-10-08 16:18:06 +09:00
184860862b resources/views/regular_contract/create_select_period.blade.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 25s
2025-10-08 16:10:37 +09:00
57bae5bcf4 resources/views/regular_contract/create_select_period.blade.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-08 15:55:57 +09:00
63efbb9481 Merge pull request '新規定期契約 定期契約期間選択修正' (#46) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
Reviewed-on: #46
2025-10-08 15:38:13 +09:00
6aebe63251 新規定期契約 定期契約期間選択修正 2025-10-08 15:37:42 +09:00
f29ba66745 routes/web.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-07 18:28:48 +09:00
ebd3a65096 Merge pull request '駐輪場検索修正' (#45) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
Reviewed-on: #45
2025-10-07 18:07:19 +09:00
89465021e4 駐輪場検索修正 2025-10-07 18:06:27 +09:00
5dff9d627a app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-07 17:36:50 +09:00
e13302475e Merge pull request '駐輪場検索 並び替え修正' (#44) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
Reviewed-on: #44
2025-10-07 17:32:06 +09:00
8a9bd9d569 駐輪場検索 並び替え修正 2025-10-07 17:31:46 +09:00
ca283fc9a9 app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-07 17:07:42 +09:00
d84b0fb9e1 app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-07 16:54:59 +09:00
e80b0639ca resources/views/regular_contract/create.blade.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 26s
2025-10-07 16:46:29 +09:00
3cad3b3d0f Merge pull request '駐輪場検索画面 並び替え条件修正' (#43) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 25s
Reviewed-on: #43
2025-10-07 16:17:22 +09:00
a139353a2f 駐輪場検索画面 並び替え条件修正 2025-10-07 16:16:38 +09:00
29f40c37bb app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-07 15:56:39 +09:00
8d33e2bf78 app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-07 15:54:00 +09:00
57efa7f63e app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
2025-10-07 15:49:14 +09:00
9d9717863e app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
2025-10-07 15:32:25 +09:00
f22f447a86 Merge pull request '駐輪場検索画面 並び替え条件修正' (#42) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 27s
Reviewed-on: #42
2025-10-07 15:27:46 +09:00
0afe05606e 駐輪場検索画面 並び替え条件修正 2025-10-07 15:27:12 +09:00
dfa786ca6f Merge pull request '駐輪場ポップアップ ボタンサイズ調整' (#41) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 25s
Reviewed-on: #41
2025-10-06 16:30:01 +09:00
e25df2dd27 駐輪場ポップアップ ボタンサイズ調整 2025-10-06 16:29:21 +09:00
Your Name
253e388058 shj3 修正
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
2025-10-05 01:50:06 +09:00
Your Name
ff21ee5796 Merge branch 'main' of https://git.so-manager-dev.com/so-manager/so-manager-dev.com
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
2025-10-03 20:32:33 +09:00
Your Name
9441a34f6f SHJ-9/SHJ-10: 修复定期契約集計処理の統合ロジック
- 定期契約データを psectionusertypemonths で統合
- 新規/更新  減免/通常 を1レコードに集約
- Operator Queue に park_id と operator_id を正確に設定
- SQL に contract_money の SUM を追加
2025-10-03 20:32:09 +09:00
56c7ae8778 Merge pull request 'main_watanabe' (#40) from main_watanabe into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
Deploy preview (main_watanabe) / deploy (push) Successful in 19s
Reviewed-on: #40
2025-10-03 17:47:10 +09:00
b4532dd76c 10/3 マージ
All checks were successful
Deploy preview (main_watanabe) / deploy (push) Successful in 12s
2025-10-03 17:46:11 +09:00
7b2d8c4416 Merge pull request '契約更新画面修正' (#39) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 24s
Reviewed-on: #39
2025-10-03 15:30:09 +09:00
1ecaf6d46a 契約更新画面修正 2025-10-03 15:29:41 +09:00
23327a4ca3 resources/views/regular_contract/update.blade.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
2025-10-02 17:21:01 +09:00
a34046b72a Merge pull request 'ファビコン設定' (#38) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
Reviewed-on: #38
2025-10-02 16:44:33 +09:00
467ae8d055 ファビコン設定 2025-10-02 16:44:09 +09:00
d2b631bbab Merge pull request '駐輪場空き台数計算処理修正' (#37) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
Reviewed-on: #37
2025-10-02 16:29:09 +09:00
8dc41b211c 駐輪場空き台数計算処理修正 2025-10-02 16:28:17 +09:00
56ea54ab15 Merge pull request 'mpdfインストール' (#36) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 27s
Reviewed-on: #36
2025-10-01 18:35:04 +09:00
609faf58a4 mpdfインストール 2025-10-01 18:34:26 +09:00
b18f4762bb Merge pull request 'シール再発行修正' (#35) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #35
2025-10-01 16:47:41 +09:00
f389f1482a シール再発行修正 2025-10-01 16:47:13 +09:00
4f4a914b7d Merge pull request 'シール再発行、タグ再発行機能追加' (#34) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #34
2025-10-01 13:04:26 +09:00
d677aa232d シール再発行、タグ再発行機能追加 2025-09-30 13:45:36 +09:00
70a0919746 Merge pull request 'お知らせ画面修正' (#33) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 20s
Reviewed-on: #33
2025-09-29 15:50:53 +09:00
ecc7095818 お知らせ画面修正 2025-09-29 15:50:17 +09:00
d1306131cf Merge remote-tracking branch 'origin/main' into main_watanabe
All checks were successful
Deploy preview (main_watanabe) / deploy (push) Successful in 14s
2025-09-29 11:44:02 +09:00
5a7721b217 Merge pull request 'フッターメニュー修正' (#32) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
Reviewed-on: #32
2025-09-26 20:12:42 +09:00
facc01dca6 フッターメニュー修正 2025-09-26 20:12:19 +09:00
3c0406727b Merge pull request 'composer関連更新' (#31) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #31
2025-09-26 20:09:30 +09:00
926562d135 composer関連更新 2025-09-26 20:08:22 +09:00
bf7e6c901a composer.lock を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
2025-09-26 19:46:33 +09:00
b2b359028b composer.json を更新
Some checks failed
Deploy so-manager (auto) / deploy (push) Failing after 3s
2025-09-26 19:18:58 +09:00
86af1df345 composer.json を更新
Some checks failed
Deploy so-manager (auto) / deploy (push) Failing after 4s
2025-09-26 19:10:28 +09:00
831b25ae8b Merge pull request '会員へのお知らせ追加' (#30) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
Reviewed-on: #30
2025-09-26 18:33:47 +09:00
b6bbb605dd 会員へのお知らせ追加 2025-09-26 18:33:19 +09:00
79fd7f2ca3 Merge remote-tracking branch 'origin/main' into main_watanabe
All checks were successful
Deploy preview (main_watanabe) / deploy (push) Successful in 11s
2025-09-26 17:28:37 +09:00
0e67f5a11b Merge remote-tracking branch 'origin/main' into main_watanabe 2025-09-26 17:28:18 +09:00
e70c834bb0 Merge pull request '9/26 マージ' (#29) from main_watanabe into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 20s
Reviewed-on: #29
2025-09-26 17:27:54 +09:00
eed5d85741 9/26 マージ
All checks were successful
Deploy preview (main_watanabe) / deploy (push) Successful in 13s
2025-09-26 17:27:15 +09:00
Your Name
3960e062b9 SHJ-5駐輪場空きチェック
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
SHJ-13契約台数追加
2025-09-26 17:03:01 +09:00
bbc7ae7e83 Merge pull request 'マイページお知らせ修正' (#28) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #28
2025-09-25 14:12:03 +09:00
dce7e1daad マイページお知らせ修正 2025-09-25 14:11:35 +09:00
41cd07db41 routes/web.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
2025-09-24 16:57:27 +09:00
01c1f74d09 routes/web.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
2025-09-24 16:55:46 +09:00
ef056d1255 Merge pull request 'マイページ追加' (#27) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #27
2025-09-24 16:53:54 +09:00
814b85cdbb public/assets/css/mypage/all.css を削除 2025-09-24 16:53:22 +09:00
3337d6abac マイページ追加 2025-09-24 16:52:21 +09:00
52f568c5f4 app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 20s
Deploy preview (main_watanabe) / deploy (push) Successful in 14s
2025-09-22 12:21:59 +09:00
3964a85578 Merge pull request '新規定期契約 先祖返り部分修正' (#26) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 20s
Reviewed-on: #26
2025-09-22 12:10:50 +09:00
a85f96cc01 新規定期契約 先祖返り部分修正 2025-09-22 12:10:03 +09:00
00ae084aad app/Http/Controllers/RegularContractCreateController.php を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
自動本人確認テストコード削除
2025-09-22 11:54:41 +09:00
5ba63ace77 .env を更新
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
2025-09-19 19:07:34 +09:00
641375c7b4 Merge pull request 'feat: SHJ-1本人確認実装 and other batch' (#25) from main_go into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
Reviewed-on: #25
2025-09-19 19:05:21 +09:00
8cf1d96eeb Merge pull request '空き待ち状況確認追加' (#24) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #24
2025-09-19 16:44:25 +09:00
ded82e0b8e 空き待ち状況確認追加 2025-09-19 16:42:32 +09:00
8cae7b6c47 Merge pull request '9/19 マージ' (#23) from main_watanabe into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 21s
Reviewed-on: #23
2025-09-19 14:34:32 +09:00
de257704ab 9/19 マージ
All checks were successful
Deploy preview (main_watanabe) / deploy (push) Successful in 14s
2025-09-19 14:32:44 +09:00
ada3971259 Merge pull request '駐輪場検索画面レイアウト調整' (#22) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 22s
Reviewed-on: #22
2025-09-18 14:13:49 +09:00
62e0fe7f5c 駐輪場検索画面レイアウト調整 2025-09-18 14:12:50 +09:00
5682ea1614 Merge pull request 'ログアウト処理追加' (#21) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 25s
Reviewed-on: #21
2025-09-18 14:04:02 +09:00
ada43efd75 ログアウト処理追加 2025-09-18 14:03:41 +09:00
c8aba53248 Merge pull request '駐輪場検索追加' (#20) from main_higashide into main
All checks were successful
Deploy so-manager (auto) / deploy (push) Successful in 23s
Reviewed-on: #20
2025-09-18 14:00:21 +09:00
d5a4d8bd9c 駐輪場検索追加 2025-09-18 13:59:26 +09:00
79 changed files with 9201 additions and 3909 deletions

25
.env
View File

@ -2,7 +2,7 @@ APP_NAME=so-manager
APP_ENV=local
APP_KEY=base64:ejLwJbt2bEXY9emPUmsurG+X1hzkjTxQQvq2/FO14RY=
APP_DEBUG=true
APP_URL=http://www-somanager.localhost:81/
APP_URL=https://so-manager-dev.com/public/
APP_LOCALE=ja
APP_FALLBACK_LOCALE=ja
APP_FAKER_LOCALE=ja_JP
@ -22,9 +22,9 @@ LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=somanager_admin
DB_USERNAME=root
DB_PASSWORD=
DB_DATABASE=krgm
DB_USERNAME=krgm_user
DB_PASSWORD=StrongDbP@ss2
SESSION_DRIVER=database
SESSION_LIFETIME=120
@ -46,15 +46,16 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hp@so-manager-dev.com"
MAIL_MAILER=smtp
#MAIL_SCHEME=null
MAIL_HOST=tomatofox9.sakura.ne.jp
MAIL_PORT=587
MAIL_USERNAME=demo@so-rin.jp
MAIL_PASSWORD=rokuchou4665
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=demo@so-rin.jp
MAIL_FROM_NAME="${APP_NAME}"
MAIL_ADMIN=null
MAIL_ADMIN=demo@so-rin.jp
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

View File

@ -4,54 +4,70 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Models\Batch\BatchLog;
use App\Models\Device;
use App\Services\ShjEightService;
/**
* SHJ-8 バッチ処理ログ登録コマンド
*
* 統一BatchLogを使用してバッチ処理の実行ログをbatch_logテーブルに登録する
* 仕様書に基づくSHJ-8の要求パラメータを受け取り、通用のログシステムで記録
* bat_job_logテーブルにバッチ処理の実行ログを登録する
* ShjEightServiceを使用して実装
*/
class ShjBatchLogCommand extends Command
{
/**
* ShjEightService インスタンス
*
* @var ShjEightService
*/
protected $shjEightService;
/**
* コンソールコマンドの名前とシグネチャ
*
* 引数:
* 修正版7項目status_comment追加
* - device_id: デバイスID (必須)
* - process_name: プロセス名 (必須)
* - job_name: ジョブ名 (必須)
* - status: ステータス (必須)
* - status_comment: ステータスコメント (必須)
* - created_date: 登録日時 (必須、yyyy/mm/dd形式)
* - updated_date: 更新日時 (必須、yyyy/mm/dd形式)
* - process_name: プロセス名 (オプション)
* - job_name: ジョブ名 (オプション)
*
* @var string
*/
protected $signature = 'shj:batch-log {device_id : デバイスID} {process_name : プロセス名} {job_name : ジョブ名} {status : ステータス} {created_date : 登録日時} {updated_date : 更新日時}';
protected $signature = 'shj:batch-log
{device_id : デバイスID}
{status : ステータス}
{status_comment : ステータスコメント}
{created_date : 登録日時}
{updated_date : 更新日時}
{process_name? : プロセス名}
{job_name? : ジョブ名}';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-8 バッチ処理ログ登録 - バッチ処理の実行ログを登録';
protected $description = 'SHJ-8 バッチ処理ログ登録 - bat_job_logテーブルにバッチ処理の実行ログを登録';
/**
* コンストラクタ
*
* @param ShjEightService $shjEightService
*/
public function __construct()
public function __construct(ShjEightService $shjEightService)
{
parent::__construct();
$this->shjEightService = $shjEightService;
}
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. 入力パラメーターをチェックする
* 2. 統一BatchLogを使用してbatch_logテーブルに記録
* 3. 仕様書準拠の処理結果を返却する
* 1. ShjEightServiceを呼び出して処理を実行
* 2. 処理結果を返却する
*
* @return int
*/
@ -67,6 +83,7 @@ class ShjBatchLogCommand extends Command
$processName = $this->argument('process_name');
$jobName = $this->argument('job_name');
$status = $this->argument('status');
$statusComment = $this->argument('status_comment');
$createdDate = $this->argument('created_date');
$updatedDate = $this->argument('updated_date');
@ -76,163 +93,71 @@ class ShjBatchLogCommand extends Command
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'status_comment' => $statusComment,
'created_date' => $createdDate,
'updated_date' => $updatedDate
]);
// 【処理1】入力パラメーターをチェックする
$paramCheckResult = $this->validateParameters($deviceId, $processName, $jobName, $status, $createdDate, $updatedDate);
if (!$paramCheckResult['valid']) {
$this->error('パラメータエラー: ' . $paramCheckResult['message']);
// ShjEightServiceを呼び出して処理を実行
$result = $this->shjEightService->execute(
$deviceId,
$processName,
$jobName,
$status,
$statusComment,
$createdDate,
$updatedDate
);
// 仕様書【判断1】パラメーターNG時の結果出力
$this->line('処理結果: 1'); // 1 = 異常終了
$this->line('異常情報: ' . $paramCheckResult['message']);
$endTime = now();
// 処理結果に応じた出力
if ($result['result'] === 0) {
// 正常終了
$this->info('SHJ-8 バッチ処理ログ登録が正常に完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
// 標準出力形式 (SHJ-8仕様書準拠)
$this->line('処理結果: 0');
$this->line('異常情報: ');
Log::info('SHJ-8 バッチ処理ログ登録完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime)
]);
return self::SUCCESS;
} else {
// 異常終了
$errorMessage = $result['error_message'] ?? '不明なエラー';
$this->error('SHJ-8 バッチ処理ログ登録エラー: ' . $errorMessage);
// 標準出力形式 (SHJ-8仕様書準拠)
$this->line('処理結果: 1');
$this->line('異常情報: ' . $errorMessage);
Log::error('SHJ-8 バッチ処理ログ登録エラー', [
'error_message' => $errorMessage,
'error_code' => $result['error_code'] ?? null
]);
return self::FAILURE;
}
// 【処理2】統一BatchLogを使用してログ登録
$batchLog = BatchLog::createBatchLog(
$processName, // 実際のプロセス名を使用
$status,
[
'device_id' => $deviceId,
'job_name' => $jobName,
'status_comment' => BatchLog::getSuccessComment(),
'input_created_date' => $createdDate,
'input_updated_date' => $updatedDate,
'shj8_params' => [
'device_id' => $deviceId,
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'created_date' => $createdDate,
'updated_date' => $updatedDate
]
],
$jobName . '' . BatchLog::getSuccessComment()
);
$endTime = now();
$this->info('SHJ-8 バッチ処理ログ登録が正常に完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
Log::info('SHJ-8 バッチ処理ログ登録完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime),
'batch_log_id' => $batchLog->id
]);
// 仕様書【処理3】正常終了時の結果出力
$this->line('処理結果: 0'); // 0 = 正常終了
$this->line('異常情報: '); // 正常時は空文字
return self::SUCCESS;
} catch (\Exception $e) {
$this->error('SHJ-8 バッチ処理ログ登録で予期しないエラーが発生しました: ' . $e->getMessage());
// 標準出力形式 (SHJ-8仕様書準拠 - 例外時)
$this->line('処理結果: 1');
$this->line('異常情報: ' . $e->getMessage());
Log::error('SHJ-8 バッチ処理ログ登録例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// 仕様書【処理3】異常終了時の結果出力
$this->line('処理結果: 1'); // 1 = 異常終了
$this->line('異常情報: エラー: ' . $e->getMessage());
return self::FAILURE;
}
}
/**
* 【処理1】パラメータの妥当性を検証
*
* 仕様書に基づく検証内容:
* - デバイスID: 必須、数値、device表に存在するか
* - プロセス名: 「プロセス名」「ジョブ名」いずれか必須
* - ジョブ名: 「プロセス名」「ジョブ名」いずれか必須
* - ステータス: 必須
* - 登録日時: 必須、yyyy/mm/dd形式
* - 更新日時: 必須、yyyy/mm/dd形式
*
* @param int $deviceId デバイスID
* @param string $processName プロセス名
* @param string $jobName ジョブ名
* @param string $status ステータス
* @param string $createdDate 登録日時
* @param string $updatedDate 更新日時
* @return array 検証結果 ['valid' => bool, 'message' => string]
*/
private function validateParameters(int $deviceId, string $processName, string $jobName, string $status, string $createdDate, string $updatedDate): array
{
// デバイスID存在チェック
if ($deviceId <= 0) {
return [
'valid' => false,
'message' => 'パラメーターNG: デバイスIDは正の整数である必要があります'
];
}
if (!Device::exists($deviceId)) {
return [
'valid' => false,
'message' => "パラメーターNG: デバイスID {$deviceId} が存在しません"
];
}
// プロセス名とジョブ名のいずれか必須チェック
if (empty($processName) && empty($jobName)) {
return [
'valid' => false,
'message' => 'パラメーターNG: プロセス名またはジョブ名のいずれかは必須です'
];
}
// ステータス必須チェック
if (empty($status)) {
return [
'valid' => false,
'message' => 'パラメーターNG: ステータスは必須です'
];
}
// 日付形式チェック
if (!$this->isValidDateFormat($createdDate)) {
return [
'valid' => false,
'message' => 'パラメーターNG: 登録日時の形式が正しくありませんyyyy/mm/dd'
];
}
if (!$this->isValidDateFormat($updatedDate)) {
return [
'valid' => false,
'message' => 'パラメーターNG: 更新日時の形式が正しくありませんyyyy/mm/dd'
];
}
return [
'valid' => true,
'message' => 'パラメーターチェックOK'
];
}
/**
* 日付形式の検証
*
* @param string $date 日付文字列
* @return bool 有効な日付形式かどうか
*/
private function isValidDateFormat(string $date): bool
{
// yyyy/mm/dd形式の正規表現チェック
if (!preg_match('/^\d{4}\/\d{2}\/\d{2}$/', $date)) {
return false;
}
// 実際の日付として有効かチェック
$dateParts = explode('/', $date);
return checkdate((int)$dateParts[1], (int)$dateParts[2], (int)$dateParts[0]);
}
}

View File

@ -52,14 +52,13 @@ class ShjElevenCommand extends Command
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. 集計単位每个の契約台数を算出する
* 2. 取得件数判定
* 3. ゾーンマスタを取得する
* 4. 取得判定とゾーンマスタ登録
* 5. 契約台数チェック(限界台数超過判定)
* 6. 契約台数を反映する
* 7. バッチ処理ログを作成する
* 処理フロー(仕様書準拠):
* 【処理1】集計単位每个の契約台数を算出する
* 【判断1】取得件数判定
* - 取得件数 = 0 【処理4】バッチログ作成「全駐輪場契約なし」 終了
* - 取得件数 1 【処理2】ゾーンマスタ処理循環 終了
*
* ※【処理4】は各レコードの処理ごとにService層で呼び出される
*
* @return int
*/
@ -82,29 +81,37 @@ class ShjElevenCommand extends Command
$this->info("取得件数: {$countResults}");
if ($countResults === 0) {
// 対象なしの結果を設定する
$this->info('契約台数算出対象なしのため処理を終了します。');
// 【判断1】取得件数 = 0 の場合
$this->info('対象なしのため処理を終了します。');
// バッチ処理ログを作成
$this->shjElevenService->createBatchLog(
'success',
[],
'契約台数算出対象なし',
0,
0,
0
);
// 【処理4】bat_job_logに直接書き込み取得件数=0時
$batchLogResult = $this->shjElevenService->writeBatJobLogForNoContracts();
// bat_job_log書き込み結果をチェック
if (!$batchLogResult['success']) {
$this->warn('bat_job_log書き込みで異常が発生しました');
if (isset($batchLogResult['error_message'])) {
$this->warn('error_message: ' . $batchLogResult['error_message']);
}
Log::warning('bat_job_log書き込み失敗対象なし', [
'error_message' => $batchLogResult['error_message'] ?? null
]);
}
Log::info('SHJ-11 現在契約台数集計完了(対象なし)', [
'end_time' => now(),
'duration_seconds' => $startTime->diffInSeconds(now())
'duration_seconds' => $startTime->diffInSeconds(now()),
'bat_job_log_success' => $batchLogResult['success']
]);
return self::SUCCESS;
}
// 【処理2・3】ゾーンマスタ処理取得・登録・更新
// 【判断1】取得件数 ≥ 1 の場合
// 【処理2・3・4】ゾーンマスタ処理各レコードごとに処理4を実行
$this->info('【処理2】ゾーンマスタ処理を実行しています...');
$this->info('※各レコードごとに【処理4】バッチログを作成します');
$processResult = $this->shjElevenService->processZoneManagement($contractCounts);
// 処理結果確認
@ -117,15 +124,9 @@ class ShjElevenCommand extends Command
$this->info("ゾーン更新件数: {$processResult['updated_zones']}");
$this->info("限界台数超過件数: {$processResult['over_capacity_count']}");
// 【処理4】バッチ処理ログを作成する
$this->shjElevenService->createBatchLog(
'success',
$processResult['parameters'],
'現在契約台数集計処理完了',
$countResults,
$processResult['created_zones'] + $processResult['updated_zones'],
0
);
if (!empty($processResult['batch_log_errors'])) {
$this->warn("バッチログエラー件数: " . count($processResult['batch_log_errors']) . "");
}
Log::info('SHJ-11 現在契約台数集計完了', [
'end_time' => $endTime,
@ -133,23 +134,14 @@ class ShjElevenCommand extends Command
'processed_count' => $countResults,
'created_zones' => $processResult['created_zones'],
'updated_zones' => $processResult['updated_zones'],
'over_capacity_count' => $processResult['over_capacity_count']
'over_capacity_count' => $processResult['over_capacity_count'],
'batch_log_errors' => count($processResult['batch_log_errors'])
]);
return self::SUCCESS;
} else {
$this->error('SHJ-11 現在契約台数集計でエラーが発生しました: ' . $processResult['message']);
// エラー時のバッチログ作成
$this->shjElevenService->createBatchLog(
'error',
$processResult['parameters'] ?? [],
$processResult['message'],
$countResults,
$processResult['created_zones'] ?? 0,
1
);
Log::error('SHJ-11 現在契約台数集計エラー', [
'error' => $processResult['message'],
'details' => $processResult['details'] ?? null
@ -161,16 +153,6 @@ class ShjElevenCommand extends Command
} catch (\Exception $e) {
$this->error('SHJ-11 現在契約台数集計で予期しないエラーが発生しました: ' . $e->getMessage());
// 例外時のバッチログ作成
$this->shjElevenService->createBatchLog(
'error',
[],
'システムエラー: ' . $e->getMessage(),
0,
0,
1
);
Log::error('SHJ-11 現在契約台数集計例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()

View File

@ -0,0 +1,148 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Services\ShjFiveService;
/**
* 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()
{
try {
// 開始ログ出力
$startTime = now();
$this->info('SHJ-5 空き待ち通知処理を開始します。');
Log::info('SHJ-5 空き待ち通知処理開始', [
'start_time' => $startTime
]);
// SHJ-5メイン処理実行
$result = $this->shjFiveService->executeParkVacancyNotification();
$endTime = now();
$this->info('SHJ-5 空き待ち通知処理が完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
// 処理結果表示
$this->displayProcessResult($result);
// バッチログはShjFiveServiceが自動的にSHJ-8経由で作成
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());
// エラー時もShjFiveServiceが自動的にバッチログを作成
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

@ -4,7 +4,8 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\SettlementTransaction;
use App\Models\Batch\BatchLog;
use App\Models\BatJobLog;
use App\Models\Device;
use App\Jobs\ProcessSettlementJob;
use App\Services\ShjFourBService;
use Illuminate\Support\Facades\Log;
@ -77,22 +78,6 @@ class ShjFourBCheckCommand extends Command
$this->info("処理制限: {$limit}");
$this->info("対象期間: {$hours}時間以内");
// バッチログ作成
$batch = BatchLog::createBatchLog(
'shj4b_check',
BatchLog::STATUS_START,
[
'command' => 'shj4b:check',
'options' => [
'dry_run' => $isDryRun,
'limit' => $limit,
'hours' => $hours,
],
'start_time' => $startTime,
],
'SHJ-4B チェックコマンド開始'
);
try {
// 未処理の決済トランザクション取得
$unprocessedSettlements = $this->getUnprocessedSettlements($hours, $limit);
@ -102,12 +87,8 @@ class ShjFourBCheckCommand extends Command
if ($unprocessedSettlements->isEmpty()) {
$this->info("処理対象なし");
$batch->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => 'SHJ-4B チェック完了 - 処理対象なし',
'success_count' => 0,
]);
// バッチログ作成 - 処理対象なし
$this->createBatchLog('success', 0, 0, 'SHJ-4B チェック完了 - 処理対象なし');
return 0;
}
@ -118,13 +99,8 @@ class ShjFourBCheckCommand extends Command
if ($isDryRun) {
$this->info("ドライランモードのため、実際の処理はスキップします");
$batch->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => 'SHJ-4B チェック完了 - ドライラン',
'success_count' => 0,
'parameters' => json_encode(['targets' => $unprocessedSettlements->pluck('settlement_transaction_id')->toArray()]),
]);
// バッチログ作成 - ドライラン
$this->createBatchLog('success', 0, 0, 'SHJ-4B チェック完了 - ドライラン(' . $unprocessedSettlements->count() . '件検出)');
return 0;
}
@ -134,14 +110,10 @@ class ShjFourBCheckCommand extends Command
$this->info("処理完了: {$processed['success']}件成功, {$processed['failed']}件失敗");
$batch->update([
'status' => $processed['failed'] > 0 ? BatchLog::STATUS_ERROR : BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => "SHJ-4B チェック完了 - 成功:{$processed['success']}件, 失敗:{$processed['failed']}",
'success_count' => $processed['success'],
'error_count' => $processed['failed'],
'parameters' => json_encode($processed),
]);
// バッチログ作成 - 処理完了
$status = $processed['failed'] > 0 ? 'error' : 'success';
$message = "SHJ-4B チェック完了 - 成功:{$processed['success']}件, 失敗:{$processed['failed']}";
$this->createBatchLog($status, $processed['success'], $processed['failed'], $message);
return $processed['failed'] > 0 ? 1 : 0;
@ -152,13 +124,8 @@ class ShjFourBCheckCommand extends Command
'trace' => $e->getTraceAsString(),
]);
$batch->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => 'SHJ-4B チェック失敗: ' . $e->getMessage(),
'error_details' => $e->getTraceAsString(),
'error_count' => 1,
]);
// バッチログ作成 - エラー
$this->createBatchLog('error', 0, 1, 'SHJ-4B チェック失敗: ' . $e->getMessage());
return 1;
}
@ -214,10 +181,10 @@ class ShjFourBCheckCommand extends Command
return true;
}
// 2. batch_logで処理完了記録があるかチェック
$processedInBatch = BatchLog::where('process_name', 'shj4b')
->where('status', BatchLog::STATUS_SUCCESS)
->where('parameters', 'like', '%"settlement_transaction_id":' . $settlement->settlement_transaction_id . '%')
// 2. bat_job_logで処理完了記録があるかチェック
$processedInBatch = BatJobLog::where('process_name', 'SHJ-4B')
->where('status', 'success')
->where('status_comment', 'like', '%settlement_transaction_id:' . $settlement->settlement_transaction_id . '%')
->exists();
if ($processedInBatch) {
@ -226,8 +193,8 @@ class ShjFourBCheckCommand extends Command
// 3. 現在キューに入っているかチェック(簡易版)
// 注: より正確にはRedis/DBキューの内容を確認する必要がある
$recentJobDispatched = BatchLog::where('process_name', 'shj4b')
->where('parameters', 'like', '%"settlement_transaction_id":' . $settlement->settlement_transaction_id . '%')
$recentJobDispatched = BatJobLog::where('process_name', 'SHJ-4B')
->where('status_comment', 'like', '%settlement_transaction_id:' . $settlement->settlement_transaction_id . '%')
->where('created_at', '>=', Carbon::now()->subHours(1))
->exists();
@ -314,4 +281,37 @@ class ShjFourBCheckCommand extends Command
'total' => $settlements->count(),
];
}
/**
* バッチログ作成 - 直接bat_job_log書き込み
*
* @param string $status
* @param int $successCount
* @param int $errorCount
* @param string $message
*/
private function createBatchLog(string $status, int $successCount, int $errorCount, string $message): void
{
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
BatJobLog::create([
'device_id' => $deviceId,
'process_name' => 'SHJ-4B-CHECK',
'job_name' => 'SHJ-4Bチェックコマンド',
'status' => $status,
'status_comment' => $message . " (成功:{$successCount}/失敗:{$errorCount})",
'created_at' => now()->startOfDay(),
'updated_at' => now()->startOfDay()
]);
} catch (\Exception $e) {
Log::error('SHJ-4B-CHECK バッチログ作成エラー', [
'error' => $e->getMessage(),
'status' => $status,
'message' => $message
]);
}
}
}

View File

@ -54,11 +54,13 @@ class ShjMailSendCommand extends Command
/**
* コンソールコマンドを実行
*
* 処理フロー:
* 1. 入力パラメーターをチェックする
* 2. メール送信テンプレート情報を取得する
* 3. メールを送信する
* 4. 処理結果を返却する
* SHJ-7 メール送信処理フロー:
* 【処理1】入力パラメーターをチェックする
* 【判断1】チェック結果
* 【処理2】メール送信テンプレート情報を取得する
* 【判断2】取得結果
* 【処理3】メールを送信する
* 【処理4】処理結果を返却する
*
* @return int
*/
@ -67,8 +69,8 @@ class ShjMailSendCommand extends Command
try {
// 開始ログ出力
$startTime = now();
$this->info('SHJ メール送信処理を開始します。');
Log::info('SHJ メール送信処理開始', [
$this->info('SHJ-7 メール送信処理を開始します。');
Log::info('SHJ-7 メール送信処理開始', [
'start_time' => $startTime,
'mail_address' => $this->argument('mail_address'),
'backup_mail_address' => $this->argument('backup_mail_address'),
@ -80,41 +82,36 @@ class ShjMailSendCommand extends Command
$backupMailAddress = $this->argument('backup_mail_address');
$mailTemplateId = $this->argument('mail_template_id');
// 【処理1】パラメータ検証
if (!$this->validateParameters($mailAddress, $backupMailAddress, $mailTemplateId)) {
$this->error('パラメータが不正です。');
return self::FAILURE;
}
// SHJメール送信処理実行
// SHJ-7 メール送信処理実行
$result = $this->shjMailSendService->executeMailSend($mailAddress, $backupMailAddress, $mailTemplateId);
// 処理結果確認
if ($result['success']) {
// 【処理4】処理結果を返却する
if ($result['result'] === 0) {
$endTime = now();
$this->info('SHJ メール送信処理が正常に完了しました。');
$this->info('SHJ-7 メール送信処理が正常に完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
Log::info('SHJ メール送信処理完了', [
Log::info('SHJ-7 メール送信処理完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime),
'result' => $result
'result' => $result['result'],
'error_info' => $result['error_info']
]);
return self::SUCCESS;
} else {
$this->error('SHJ メール送信処理でエラーが発生しました: ' . $result['message']);
Log::error('SHJ メール送信処理エラー', [
'error' => $result['message'],
'details' => $result['details'] ?? null
$this->error('SHJ-7 メール送信処理でエラーが発生しました: ' . $result['error_info']);
Log::error('SHJ-7 メール送信処理エラー', [
'result' => $result['result'],
'error_info' => $result['error_info']
]);
return self::FAILURE;
}
} catch (\Exception $e) {
$this->error('SHJ メール送信処理で予期しないエラーが発生しました: ' . $e->getMessage());
Log::error('SHJ メール送信処理例外エラー', [
$this->error('SHJ-7 メール送信処理で予期しないエラーが発生しました: ' . $e->getMessage());
Log::error('SHJ-7 メール送信処理例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
@ -123,55 +120,4 @@ class ShjMailSendCommand extends Command
}
}
/**
* 【処理1】パラメータの妥当性を検証
*
* 仕様書に基づく検証内容:
* - メールアドレス: 「メールアドレス」「予備メールアドレス」いずれか必須
* - メールテンプレートID: 必須
*
* @param mixed $mailAddress メールアドレス
* @param mixed $backupMailAddress 予備メールアドレス
* @param mixed $mailTemplateId メールテンプレートID
* @return bool 検証結果
*/
private function validateParameters($mailAddress, $backupMailAddress, $mailTemplateId): bool
{
// メールテンプレートIDチェック
if (empty($mailTemplateId)) {
$this->error('メールテンプレートIDは必須です。');
return false;
}
// 数値形式チェックメールテンプレートID
if (!is_numeric($mailTemplateId)) {
$this->error('メールテンプレートIDは数値である必要があります。');
return false;
}
// 正の整数チェックメールテンプレートID
if ($mailTemplateId <= 0) {
$this->error('メールテンプレートIDは正の整数である必要があります。');
return false;
}
// メールアドレスチェック(いずれか必須)
if (empty($mailAddress) && empty($backupMailAddress)) {
$this->error('メールアドレスまたは予備メールアドレスのいずれかは必須です。');
return false;
}
// メールアドレス形式チェック
if (!empty($mailAddress) && !filter_var($mailAddress, FILTER_VALIDATE_EMAIL)) {
$this->error('メールアドレスの形式が正しくありません。');
return false;
}
if (!empty($backupMailAddress) && !filter_var($backupMailAddress, FILTER_VALIDATE_EMAIL)) {
$this->error('予備メールアドレスの形式が正しくありません。');
return false;
}
return true;
}
}

View File

@ -9,7 +9,7 @@ use App\Services\ShjNineService;
/**
* SHJ-9 売上集計処理コマンド
*
* 駐輪場の売上データを日次・月次・年次で集計する処理を実行する
* 駐輪場の売上データを日次で集計する処理を実行する
* バックグラウンドで実行される定期バッチ処理
*/
class ShjNineCommand extends Command
@ -18,19 +18,19 @@ class ShjNineCommand extends Command
* コンソールコマンドの名前とシグネチャ
*
* 引数:
* - type: 集計種別 (daily/monthly/yearly) (必須)
* - type: 集計種別 (daily のみ) (必須)
* - target_date: 集計対象日 (オプション、YYYY-MM-DD形式)
*
* @var string
*/
protected $signature = 'shj:9 {type : 集計種別(daily/monthly/yearly)} {target_date? : 集計対象日(YYYY-MM-DD)}';
protected $signature = 'shj:9 {type : 集計種別(daily)} {target_date? : 集計対象日(YYYY-MM-DD)}';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-9 売上集計処理 - 日次/月次/年次売上データ集計を実行';
protected $description = 'SHJ-9 売上集計処理 - 日次売上データ集計を実行';
/**
* SHJ-9サービスクラス
@ -138,10 +138,9 @@ class ShjNineCommand extends Command
*/
private function validateParameters(string $type, ?string $targetDate): bool
{
// 集計種別チェック
$allowedTypes = ['daily', 'monthly', 'yearly'];
if (!in_array($type, $allowedTypes)) {
$this->error('集計種別は daily, monthly, yearly のいずれかを指定してください。');
// 集計種別チェックSHJ-9 は日次のみ対応)
if ($type !== 'daily') {
$this->error('SHJ-9 は日次集計dailyのみ対応しています。月次/年次は SHJ-10 を使用してください。');
return false;
}
@ -157,7 +156,7 @@ class ShjNineCommand extends Command
/**
* 集計対象日を決定
*
* @param string $type 集計種別
* @param string $type 集計種別daily 固定)
* @param string|null $targetDate 指定日
* @return string 集計対象日
*/
@ -167,23 +166,8 @@ class ShjNineCommand extends Command
return $targetDate;
}
// パラメータ指定がない場合のデフォルト設定
switch ($type) {
case 'daily':
// 日次昨日本日の1日前
// パラメータ指定がない場合は昨日本日の1日前
return now()->subDay()->format('Y-m-d');
case 'monthly':
// 月次:前月の最終日
return now()->subMonth()->endOfMonth()->format('Y-m-d');
case 'yearly':
// 年次:前年の最終日
return now()->subYear()->endOfYear()->format('Y-m-d');
default:
return now()->subDay()->format('Y-m-d');
}
}
/**

View File

@ -3,8 +3,8 @@
namespace App\Console\Commands;
use App\Services\ShjOneService;
use App\Models\Batch\BatchLog;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Exception;
/**
@ -58,30 +58,13 @@ class ShjOneCommand extends Command
$this->info("駐輪場ID: {$parkId}");
try {
// バッチログ開始 - SHJ-8共通処理
$batchLog = BatchLog::createBatchLog(
'SHJ-1本人確認自動処理',
BatchLog::STATUS_START,
$parameters,
'SHJ-1処理開始: 利用者ID=' . $userId . ', 駐輪場ID=' . $parkId
);
// SHJ-1 メイン処理を実行
$result = $this->shjOneService->execute($userId, $parkId);
// 処理結果の表示
$this->displayResult($result);
// バッチログ完了 - 実際の処理結果に基づく
$logStatus = $result['log_status'] ?? ($result['system_success'] ? BatchLog::STATUS_SUCCESS : BatchLog::STATUS_ERROR);
$batchLog->update([
'status' => $logStatus,
'end_time' => now(),
'message' => $result['message'],
'success_count' => $result['stats']['success_count'] ?? 0,
'error_count' => $result['stats']['error_count'] ?? 0,
'execution_count' => $result['stats']['processed_count'] ?? 1
]);
// バッチログはShjOneServiceが自動的にSHJ-8経由で作成
$this->info("=== SHJ-1 本人確認自動処理 完了 ===");
@ -92,16 +75,14 @@ class ShjOneCommand extends Command
} catch (Exception $e) {
$this->error("SHJ-1処理中にエラーが発生しました: " . $e->getMessage());
// エラーログ記録
if (isset($batchLog)) {
$batchLog->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => $e->getMessage(),
'error_details' => $e->getMessage(),
'error_count' => 1
// エラー時もShjOneServiceが自動的にバッチログを作成
Log::error('SHJ-1処理例外エラー', [
'user_id' => $userId,
'park_id' => $parkId,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
}
return 1;
}
@ -139,4 +120,5 @@ class ShjOneCommand extends Command
}
}
}
}

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

@ -79,14 +79,17 @@ class ShjTwelveCommand extends Command
$this->info("取得件数: {$unpaidCount}");
if ($unpaidCount === 0) {
// 未払い者対象なしの結果を設定する
$this->info('未払い者対象なしのため処理を終了します。');
// 未払い者なしの結果を設定する
$this->info('未払い者なしのため処理を終了します。');
// バッチ処理ログを作成
// バッチ処理ログを作成SHJ-8仕様に合わせ job_name と status_comment を追加)
$this->shjTwelveService->createBatchLog(
'success',
[],
'未払い者対象なし',
[
'job_name' => 'SHJ-12未払い者アラート',
'status_comment' => '未払い者なし'
],
'未払い者なし',
0,
0,
0
@ -110,25 +113,46 @@ class ShjTwelveCommand extends Command
$this->info('SHJ-12 未払い者通知処理が正常に完了しました。');
$this->info("処理時間: {$startTime->diffInSeconds($endTime)}");
$this->info("処理対象件数: {$unpaidCount}");
$this->info("通知送信件数: {$processResult['notification_count']}");
$this->info("オペレーターキュー追加件数: {$processResult['queue_count']}");
$this->info("メール送信成功: {$processResult['mail_success_count']}");
$this->info("メール送信失敗: {$processResult['mail_error_count']}");
$this->info("キュー登録成功: {$processResult['queue_success_count']}");
$this->info("キュー登録失敗: {$processResult['queue_error_count']}");
// 【処理3】バッチ処理ログを作成する
$statusComment = $processResult['status_comment'];
$batchComments = $processResult['batch_comments'];
$totalSuccessCount = $processResult['mail_success_count'] + $processResult['queue_success_count'];
$totalErrorCount = $processResult['mail_error_count'] + $processResult['queue_error_count'];
// status_commentに詳細エラー情報を付加
$finalMessage = $statusComment;
if (!empty($batchComments)) {
$finalMessage .= "\n" . $batchComments;
}
// SHJ-8仕様に合わせ、job_name と status_comment を parameters に追加
$batchLogParameters = array_merge($processResult['parameters'], [
'job_name' => 'SHJ-12未払い者アラート',
'status_comment' => $statusComment
]);
$this->shjTwelveService->createBatchLog(
'success',
$processResult['parameters'],
'未払い者通知処理完了',
$batchLogParameters,
$finalMessage,
$unpaidCount,
$processResult['notification_count'] + $processResult['queue_count'],
0
$totalSuccessCount,
$totalErrorCount
);
Log::info('SHJ-12 未払い者通知処理完了', [
'end_time' => $endTime,
'duration_seconds' => $startTime->diffInSeconds($endTime),
'processed_count' => $unpaidCount,
'notification_count' => $processResult['notification_count'],
'queue_count' => $processResult['queue_count']
'mail_success_count' => $processResult['mail_success_count'],
'mail_error_count' => $processResult['mail_error_count'],
'queue_success_count' => $processResult['queue_success_count'],
'queue_error_count' => $processResult['queue_error_count']
]);
return self::SUCCESS;
@ -136,13 +160,26 @@ class ShjTwelveCommand extends Command
$this->error('SHJ-12 未払い者通知処理でエラーが発生しました: ' . $processResult['message']);
// エラー時のバッチログ作成
$statusComment = $processResult['status_comment'] ?? 'システムエラー';
$batchComments = $processResult['batch_comments'] ?? '';
$finalMessage = $statusComment;
if (!empty($batchComments)) {
$finalMessage .= "\n" . $batchComments;
}
// SHJ-8仕様に合わせ、job_name と status_comment を parameters に追加
$batchLogParameters = array_merge($processResult['parameters'] ?? [], [
'job_name' => 'SHJ-12未払い者アラート',
'status_comment' => $statusComment
]);
$this->shjTwelveService->createBatchLog(
'error',
$processResult['parameters'] ?? [],
$processResult['message'],
$batchLogParameters,
$finalMessage,
$unpaidCount,
$processResult['notification_count'] ?? 0,
1
($processResult['mail_success_count'] ?? 0) + ($processResult['queue_success_count'] ?? 0),
($processResult['mail_error_count'] ?? 0) + ($processResult['queue_error_count'] ?? 0)
);
Log::error('SHJ-12 未払い者通知処理エラー', [
@ -156,10 +193,13 @@ class ShjTwelveCommand extends Command
} catch (\Exception $e) {
$this->error('SHJ-12 未払い者通知処理で予期しないエラーが発生しました: ' . $e->getMessage());
// 例外時のバッチログ作成
// 例外時のバッチログ作成SHJ-8仕様に合わせ job_name と status_comment を追加)
$this->shjTwelveService->createBatchLog(
'error',
[],
[
'job_name' => 'SHJ-12未払い者アラート',
'status_comment' => 'システムエラー'
],
'システムエラー: ' . $e->getMessage(),
0,
0,

View File

@ -0,0 +1,146 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use App\Services\ShjMailSendService;
/**
* メール送信テストコマンド
*
* SHJ-7メール送信機能をテストするための便利なコマンド
* 開発・検証環境で使用することを想定
*/
class TestMailCommand extends Command
{
/**
* コンソールコマンドの名前とシグネチャ
*
* @var string
*/
protected $signature = 'test:mail
{email? : 送信先メールアドレス(省略時: wyf_0506@hotmail.com}
{--template=205 : テンプレートIDデフォルト: 205=SHJ-4B用}
{--backup= : 予備メールアドレス}';
/**
* コンソールコマンドの説明
*
* @var string
*/
protected $description = 'SHJ-7メール送信機能のテストコマンド - 指定したメールアドレスにテストメールを送信';
/**
* SHJメール送信サービス
*
* @var ShjMailSendService
*/
protected $mailSendService;
/**
* コンストラクタ
*
* @param ShjMailSendService $mailSendService
*/
public function __construct(ShjMailSendService $mailSendService)
{
parent::__construct();
$this->mailSendService = $mailSendService;
}
/**
* コマンド実行
*
* @return int
*/
public function handle()
{
// パラメータ取得
$email = $this->argument('email') ?? 'wyf_0506@hotmail.com';
$backupEmail = $this->option('backup') ?? '';
$templateId = (int) $this->option('template');
// 確認画面
$this->info('=== SHJ-7 メール送信テスト ===');
$this->line('');
$this->line("送信先メールアドレス: {$email}");
$this->line("予備メールアドレス : " . ($backupEmail ?: '(なし)'));
$this->line("テンプレートID : {$templateId}");
$this->line('');
// 環境チェック
$mailDriver = config('mail.default');
$this->warn("現在のメール設定: MAIL_MAILER={$mailDriver}");
if ($mailDriver === 'log') {
$this->warn('⚠ メールはログファイルにのみ記録され、実際には送信されません');
$this->warn('⚠ 実際に送信するには .env で MAIL_MAILER=smtp 等に変更してください');
} else {
$this->info("✓ メールは実際に送信されます({$mailDriver}");
}
$this->line('');
// 実行確認
if (!$this->confirm('メール送信を実行しますか?', true)) {
$this->info('キャンセルしました。');
return self::SUCCESS;
}
// メール送信実行
$this->line('');
$this->info('メール送信を開始します...');
try {
$startTime = now();
// SHJ-7サービス呼び出し
$result = $this->mailSendService->executeMailSend(
$email,
$backupEmail,
$templateId
);
$endTime = now();
$duration = $startTime->diffInSeconds($endTime);
$this->line('');
$this->info('=== 実行結果 ===');
if ($result['result'] === 0) {
$this->info('✓ メール送信成功');
$this->line("処理時間: {$duration}");
if ($mailDriver === 'log') {
$this->line('');
$this->comment('※ ログファイルを確認してください:');
$this->comment(' storage/logs/laravel.log');
} else {
$this->line('');
$this->comment("{$email} のメールボックスを確認してください");
}
return self::SUCCESS;
} else {
$this->error('✗ メール送信失敗');
$this->error("エラー情報: {$result['error_info']}");
$this->line('');
$this->comment('ログファイルで詳細を確認してください: storage/logs/laravel.log');
return self::FAILURE;
}
} catch (\Exception $e) {
$this->error('✗ 予期しないエラーが発生しました');
$this->error("エラー: {$e->getMessage()}");
Log::error('test:mail コマンドエラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return self::FAILURE;
}
}
}

View File

@ -11,9 +11,9 @@ class InquiryConfirmController extends Controller
{
// 初期表示内容
public $form_data = [
["name", "text", "氏名*", ""],
["email", "email", "メールアドレス*", ""],
["tel", "text", "電話番号*", ""],
["name", "text", "氏名*", "山田 太郎"],
["email", "email", "メールアドレス*", "info@so-manager.com"],
["tel", "text", "電話番号*", "000-0000-0000"],
["subject", "checkbox", "お問い合わせ概要*", ["定期契約について", "操作方法について", "支払方法について", "その他お問合せ"]],
["parking", "text", "お問い合わせ駐輪場名", ""],
["detail", "textarea", "お問い合わせ詳細*", ""],
@ -40,11 +40,11 @@ class InquiryConfirmController extends Controller
// エラーメッセージ
$message = [
'name.required' => '名前を入力してください',
'email.required' => 'メールアドレスを入力してください',
'tel.required' => '電話番号を入力してください',
'subject.required' => 'お問い合わせ概要を選択してください',
'detail.required' => 'お問い合わせ詳細を入力してください',
'name.required' => '氏名は必ず入力してください。',
'email.required' => 'メールアドレスは必ず入力してください。',
'tel.required' => '電話番号は必ず入力してください。',
'subject.required' => 'お問い合わせ概要が選択されていません。',
'detail.required' => 'お問い合わせの詳細が入力されていません。',
];
// バリデーションチェック

View File

@ -15,7 +15,7 @@ class LoginController extends Controller
public function login(Request $request)
{
// ID・パスワードチェック
$existingMember = User::where('user_primemail', $request->input('login_id'))->first();
$existingMember = User::where('user_primemail', $request->input('login_id'))->where('user_quit_flag', 0)->first();
if (!$existingMember || !CommonFunction::verifyPassword($existingMember->user_seq, $request->input('password'), $existingMember->user_pass)) {
return redirect('swo8_1')
->withErrors(['login' => 'ID/パスワードが間違っています'])

View File

@ -51,7 +51,7 @@ class MemberRegistrationController extends Controller
return view('general.swo2_2');
}
// 入力画面表示
// メールのURLからアクセス
public function index()
{
// 署名付きURLの有効期限チェック
@ -59,9 +59,6 @@ class MemberRegistrationController extends Controller
return redirect('error')->withErrors(['error' => '署名の有効期限が切れています']);
}
// 初回遷移(GETアクセス)時のリクエストパラメータチェック
if (!session()->has('email')) {
// パラメータ存在チェック
$encryptedEmail = request()->query('email');
if (!$encryptedEmail) {
@ -85,6 +82,16 @@ class MemberRegistrationController extends Controller
// メールアドレスをセッションに保存
session(['email' => $email]);
// 入力画面に遷移
return view('general.swo2_3');
}
// リダイレクト用
public function indexBack(Request $request)
{
if (!session('email')) {
return redirect('error')->withErrors(['error' => '不正なアクセスです']);
}
// 入力画面に遷移
@ -119,9 +126,7 @@ class MemberRegistrationController extends Controller
// バリデーションチェック
$validator = Validator::make($request->all(), $rules, $message);
if ($validator->fails()) {
return redirect('swo2_3')
->withErrors($validator)
->withInput();
return redirect('swo2_3B')->withErrors($validator)->withInput();
}
// 画面返却値
@ -142,7 +147,7 @@ class MemberRegistrationController extends Controller
{
// 前の画面に戻る
if($request->input('back') == 'back'){
return redirect('swo2_3')->withInput();
return redirect('swo2_3B')->withInput();
}
// 登録完了後のブラウザバックによる二重登録対策

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

@ -11,14 +11,14 @@ class ParkDetailController extends Controller
public function show($park_id)
{
$park = DB::table('park')->where('park_id', $park_id)->first();
Log::debug('park:', (array)$park);
$zones = DB::table('zone')
->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id')
->leftJoin('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id')
->select('zone.*', 'psection.psection_subject', 'ptype.ptype_subject')
->where('zone.park_id', $park_id)
->get();
Log::debug('zones:', $zones->toArray());
$reserves = DB::table('reserve')
->join('zone', function ($join) {
$join->on('reserve.psection_id', '=', 'zone.psection_id')
@ -29,16 +29,7 @@ class ParkDetailController extends Controller
->where('reserve.valid_flag', 1)
->select('reserve.*', 'ptype.ptype_subject')
->get();
Log::debug('reserves:', $reserves->toArray());
$zoneStandardSum = [];
/*foreach ($zones as $zone) {
$psectionId = $zone->psection_id;
if (!isset($zoneStandardSum[$psectionId])) {
$zoneStandardSum[$psectionId] = 0;
}
$zoneStandardSum[$psectionId] += $zone->zone_standard;
}*/
$zoneStandardSum = [];
foreach ($zones as $zone) {
$key = $zone->psection_subject; // 「自転車」「原付」など
@ -47,7 +38,6 @@ class ParkDetailController extends Controller
}
$zoneStandardSum[$key] += $zone->zone_standard;
}
Log::debug('zoneStandardSum:', $zoneStandardSum);
// 空き台数集計用配列
$vacancyData = [];
@ -59,7 +49,6 @@ class ParkDetailController extends Controller
// zone_tolerance - zone_number を合計
$vacancyData[$key] += ($zone->zone_tolerance - $zone->zone_number);
}
Log::debug('vacancyData:', $vacancyData);
// reserve件数分を減算
foreach ($reserves as $reserve) {
@ -77,11 +66,37 @@ class ParkDetailController extends Controller
})
->get()
->keyBy('city_id');
Log::debug('city_grace_periods:', $city_grace_periods->toArray());
// 必要なら他テーブルJOINや追加情報も取得
return response()->json([
'html' => view('regular_contract.park_detail', compact('park', 'zones', 'reserves', 'zoneStandardSum', 'vacancyData', 'city_grace_periods'))->render()
]);
}
public function showWait($reserve_id)
{
$reserve = DB::table('reserve')->where('reserve_id', $reserve_id)->first();
$park = DB::table('park')->where('park_id', $reserve->park_id)->first();
$zones = DB::table('zone')
->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id')
->leftJoin('ptype', 'zone.ptype_id', '=', 'ptype.ptype_id')
->select('zone.*', 'psection.psection_subject', 'ptype.ptype_subject')
->where('zone.park_id', $park->park_id)
->get();
$zoneStandardSum = [];
foreach ($zones as $zone) {
$key = $zone->psection_subject; // 「自転車」「原付」など
if (!isset($zoneStandardSum[$key])) {
$zoneStandardSum[$key] = 0;
}
$zoneStandardSum[$key] += $zone->zone_standard;
}
// 必要なら他テーブルJOINや追加情報も取得
return response()->json([
'html' => view('park_waitlist.park_detail', compact('park', 'zones', 'reserve', 'zoneStandardSum'))->render()
]);
}
}

View File

@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
use App\Mail\ReservationCancelledMail;
class ParkWaitlistController extends Controller
{
// 空き待ち状況一覧画面
public function index(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
// 空き待ち予約状況データ取得
$waitlists = DB::table('reserve')
->where('reserve.user_id', $user_id)
->where('reserve.valid_flag', 1)
->leftJoin('park', 'reserve.park_id', '=', 'park.park_id')
->leftJoin('station', 'park.park_id', '=', 'station.park_id')
->leftJoin('ptype', 'reserve.ptype_id', '=', 'ptype.ptype_id')
->leftJoin('psection', 'reserve.psection_id', '=', 'psection.psection_id')
->select(
'reserve.reserve_id',
'reserve.reserve_date',
'park.park_name',
'station.station_neighbor_station',
'ptype.ptype_subject',
'psection.psection_subject'
)
->orderBy('reserve.reserve_date', 'desc')
->get();
\Log::info('空き待ち状況確認画面にアクセス', [
'user_id' => $user_id,
]);
return view('park_waitlist.index', [
'active_menu' => 'SWC-11-1', // この画面のID
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
'waitlists' => $waitlists,
]);
}
public function check(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$park_id = $request->input('park_id');
$psection_id = $request->input('psection_id');
$ptype_id = $request->input('ptype_id');
$existingReservation = DB::table('reserve')
->where('user_id', $user_id)
->where('park_id', $park_id)
->where('psection_id', $psection_id)
->where('ptype_id', $ptype_id)
->where('valid_flag', 1)
->first();
if ($existingReservation) {
return response()->json(['status' => 'exists']);
} else {
return response()->json(['status' => 'ok']);
}
}
public function create(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
$park_id = $request->input('park_id');
$psection_id = $request->input('psection_id');
$ptype_id = $request->input('ptype_id');
// 予約順を決定
$reserve_order = DB::table('reserve')
->where('park_id', $park_id)
->where('psection_id', $psection_id)
->where('ptype_id', $ptype_id)
->where('valid_flag', 1)
->count() + 1;
// 予約情報を追加
DB::table('reserve')->insert([
'created_at' => now(),
'updated_at' => now(),
'user_categoryid' => $user->user_categoryid,
'user_id' => $user_id,
'park_id' => $park_id,
'psection_id' => $psection_id,
'reserve_date' => now(),
'valid_flag' => 1,
'ptype_id' => $ptype_id,
'reserve_order' => $reserve_order
]);
return redirect()->route('park_waitlist.index');
}
public function cancelConfirm($reserve_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
\Log::info('空き待ち状況確認 - キャンセル確認画面にアクセス', [
'user_id' => $user_id,
]);
return view('park_waitlist.cancel', [
'active_menu' => 'SWC-11-1', // この画面のID
'user_name' => $user_name, // ユーザー名(ヘッダー用)
'reserve_id' => $reserve_id,
]);
}
public function cancel($reserve_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
// 定期予約をキャンセル
DB::table('reserve')->where('reserve_id', $reserve_id)->update([
'valid_flag' => 0,
'updated_at' => now(),
'reserve_cancelday' => date('Y-m-d'),
]);
// メール送信用データ取得
$reserve = DB::table('reserve')
->leftJoin('park', 'reserve.park_id', '=', 'park.park_id')
->leftJoin('psection', 'reserve.psection_id', '=', 'psection.psection_id')
->leftJoin('ptype', 'reserve.ptype_id', '=', 'ptype.ptype_id')
->select(
'park.park_name',
'psection.psection_subject',
'ptype.ptype_subject'
)
->where('reserve.reserve_id', $reserve_id)
->first();
// メール送信処理
try {
Mail::to($user->user_primemail)->send(
new ReservationCancelledMail(
$user->user_name,
$reserve
)
);
} catch (\Exception $e) {
Log::error('予約キャンセルメール送信エラー: ' . $e->getMessage());
}
\Log::info('空き待ち状況確認 - キャンセル完了画面にアクセス', [
'user_id' => $user_id,
]);
return view('park_waitlist.cancel_complete', [
'reserve' => $reserve,
'active_menu' => 'SWC-11-1', // この画面のID
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
]);
}
}

View File

@ -12,86 +12,184 @@ class ParkingSearchController extends Controller
// 初期表示
public function index()
{
// 検索仕様
// 駐輪場マスタの全件(条件を絞った場合はその条件に一致するもの)を取得。
// 併せて各マスタから追加情報を取得するが、その際のレコードは全て1対1で結びつく想定で暫定実装する
// ※設計書に詳細な記載なし。DBの定義上は1対多の可能性もあるが、その場合現在の画面イメージと矛盾するため、実態として無い想定で進める
$result = $this->getParkData('', '', '');
return view('general.swo5_1', $result);
}
// プルダウン選択
public function search(Request $request)
{
$result = $this->getParkData(
$request->input('conditions_city'),
$request->input('conditions_station'),
$request->input('conditions_park')
);
return view('general.swo5_1', $result);
}
// 検索処理
public function getParkData($city_name, $station_neighbor_station, $park_name)
{
// 駐輪場情報検索
$park = \DB::table('park as p')
->select(
'p.park_id',
'p.park_name',
'p.park_adrs',
'p.price_memo',
'p.park_latitude',
'p.park_longitude',
'p.update_grace_period_start_date',
'p.update_grace_period_start_time',
'p.update_grace_period_end_date',
'p.update_grace_period_end_time',
'c.city_name',
's.station_neighbor_station',
'z.psection_id'
's.station_neighbor_station'
)
->leftJoin('city as c', 'p.city_id', '=', 'c.city_id')
->leftJoin(\DB::raw(
'(SELECT park_id, station_neighbor_station FROM station WHERE station_id IN (SELECT MAX(station_id) FROM station GROUP BY park_id)) as s'
),'p.park_id','=','s.park_id')
->leftJoin('zone as z', 'p.park_id', '=', 'z.park_id')
//->where('p.park_name', 'a')
//->where('c.city_name', 'b')
//->where('s.station_neighbor_station', 'c')
->orderBy('p.park_ruby')
->get();
),'p.park_id','=','s.park_id');
// プルダウン指定の条件でwhere句を付与
if (!empty($city_name)) {
$park = $park->where('c.city_name', $city_name);
}
if (!empty($station_neighbor_station)) {
$park = $park->where('s.station_neighbor_station', $station_neighbor_station);
}
if (!empty($park_name)) {
$park = $park->whereRaw("LEFT(p.park_ruby, 1) REGEXP '^[".$park_name."]'");
}
$park = $park->orderBy('p.park_ruby')->get();
// 各マスタから追加情報を取得し、各ボタンの表示有無を判定する
$result = [];
$form_data = [];
$now = date('Y-m-d H:i:s');
foreach ($park as $row) {
// ゾーンマスタの情報から空き台数を取得する
$vacantInfo = \DB::table('zone')
->selectRaw('SUM(zone_tolerance) - SUM(zone_number) as vacant')
->where('psection_id', $row->psection_id)
->groupBy('psection_id')
->first();
// ゾーンマスタの情報を取得する
$zoneInfo = \DB::table('zone as z')
->select(
'z.psection_id',
'z.ptype_id',
\DB::raw('SUM(z.zone_standard) as zone_standard'),
\DB::raw('SUM(z.zone_number) as zone_number'),
\DB::raw('SUM(z.zone_tolerance) as zone_tolerance'),
'ps.psection_subject',
'pt.ptype_subject'
)
->join('ptype as pt', 'z.ptype_id', '=', 'pt.ptype_id')
->leftJoin('psection as ps', 'z.psection_id', '=', 'ps.psection_id')
->where('z.park_id', $row->park_id)
->groupBy('z.park_id', 'z.ptype_id', 'z.psection_id', 'ps.psection_subject', 'pt.ptype_subject')
->get();
// 定期予約マスタから予約中の台数を取得する
$reservedCount = \DB::table('reserve')
->where('psection_id', $row->psection_id)
->where('valid_flag', 1)
->count();
// 空き台数を計算
$vacant = ($vacantInfo ? $vacantInfo->vacant : 0) - $reservedCount;
// ゾーンマスタが0件の場合、次のデータへ
if ($zoneInfo->isEmpty()) {
$form_data[] = [
'park_name' => $row->park_name,
'park_adrs' => $row->park_adrs,
'price_memo' => $row->price_memo,
'park_latitude' => $row->park_latitude,
'park_longitude' => $row->park_longitude,
'city_name' => $row->city_name,
'station_neighbor_station' => $row->station_neighbor_station,
'zone_data' => []
];
continue;
}
// 更新期間内判定
$update_start = $row->update_grace_period_start_date . ' ' . $row->update_grace_period_start_time;
$update_end = $row->update_grace_period_end_date . ' ' . $row->update_grace_period_end_time;
$is_update_period = ($now >= $update_start && $now <= $update_end);
// ボタン表示有無判定
if ($vacant > 0 && $is_update_period) { // 定期契約ボタン (空き台数が1台以上かつ更新期間内)
// ゾーンマスタの件数分だけループする
$zone_data = [];
foreach ($zoneInfo as $zone) {
// 予約中件数取得
$reservedCount = \DB::table('reserve')
->where('park_id', $row->park_id)
->where('psection_id', $zone->psection_id)
->where('ptype_id', $zone->ptype_id)
->where('valid_flag', 1)
->count();
// ステータス(表示ボタン)判定
$status = 0; // 0:非表示, 1:定期契約, 2:空き待ち予約, 3:販売期間外
$zone_vacant = $zone->zone_tolerance - $zone->zone_number - $reservedCount;
if ($zone_vacant > 0 && $is_update_period) { // 定期契約ボタン (空き台数が1台以上かつ更新期間内)
$status = 1;
} elseif ($vacant <= 0 && $is_update_period) { // 当日予約ボタン (空き台数が0台以下かつ更新期間内)
} elseif ($zone_vacant <= 0 && $is_update_period) { // 空き待ち予約ボタン (空き台数が0台以下かつ更新期間内)
$status = 2;
} elseif ($vacant <= 0 && !$is_update_period) { // 販売期間外ボタン (空き台数が0台以下かつ更新期間外)
} elseif ($zone_vacant <= 0 && !$is_update_period) { // 販売期間外ボタン (空き台数が0台以下かつ更新期間外)
$status = 3;
} else {
$status = null;
}
// 画面返却用データに追加
$result[] = [
'park_name' => $row->park_name,
'city_name' => $row->city_name,
'station_neighbor_station' => $row->station_neighbor_station,
// 返却用データに追加
$zone_data[] = [
'psection_subject' => $zone->psection_subject,
'ptype_subject' => $zone->ptype_subject,
'zone_standard' => $zone->zone_standard,
'zone_vacant' => $zone_vacant,
'status' => $status
];
}
// 検索用プルダウン取得
$cities = \DB::table('city')->select('city_name')->orderBy('city_ruby')->get();
$stations = \DB::table('station')->select('station_neighbor_station')->distinct()->orderBy('station_neighbor_station')->get();
$parkings = ['全て', 'あ行', 'か行', 'さ行', 'た行', 'な行', 'は行', 'ま行', 'や行', 'ら行', 'わ行'];
// $zone_dataを並び替え
usort($zone_data, function($a, $b) {
// 第一優先: ptype_subject昇順
$ptypeCmp = strcmp($a['ptype_subject'], $b['ptype_subject']);
if ($ptypeCmp !== 0) {
return $ptypeCmp;
}
// 第二優先: psection_subject昇順
$psectionCmp = strcmp($a['psection_subject'], $b['psection_subject']);
if ($psectionCmp !== 0) {
return $psectionCmp;
}
// 第三優先: status昇順
return $a['status'] <=> $b['status'];
});
// 検索結果返却
return view('general.swo5_1',['form_data' => $result, 'cities' => $cities, 'stations' => $stations, 'parkings' => $parkings]);
// 画面返却用データに追加
$form_data[] = [
'park_name' => $row->park_name,
'park_adrs' => $row->park_adrs,
'price_memo' => $row->price_memo,
'park_latitude' => $row->park_latitude,
'park_longitude' => $row->park_longitude,
'city_name' => $row->city_name,
'station_neighbor_station' => $row->station_neighbor_station,
'zone_data' => $zone_data
];
}
// プルダウン用データ取得
$conditions = [$city_name, $station_neighbor_station, $park_name];
$cities = \DB::table('city')->select('city_name')->orderBy('city_id')->get();
$stations = \DB::table('station')->select('station_neighbor_station')->distinct()->orderBy('station_neighbor_station')->get();
$parks = [
'全て'=>'',
'あ行'=>'あいうえおアイウエオ',
'か行'=>'かきくけこがぎぐげごカキクケコガギグゲゴ',
'さ行'=>'さしすせそざじずぜぞサシスセソザジズゼゾ',
'た行'=>'たちつてとだぢづでどタチツテトダヂヅデド',
'な行'=>'なにぬねのナニヌネノ',
'は行'=>'はひふへほばびぶべぼぱぴぷぺぽハヒフヘホバビブベボパピプペポ',
'ま行'=>'まみむめもマミムメモ',
'や行'=>'やゆよヤユヨ',
'ら行'=>'らりるれろラリルレロ',
'わ行'=>'わをんワヲン '
];
// 車種区分リスト取得
$psections = \DB::table('psection')->select('psection_subject')->orderBy('psection_id', 'asc')->limit(4)->get();
// 情報返却
return ['form_data' => $form_data, 'conditions' => $conditions, 'cities' => $cities, 'stations' => $stations, 'parks' => $parks, 'psections' => $psections];
}
}

View File

@ -90,7 +90,7 @@ class RegularContractController extends Controller
->join('usertype', 'regular_contract.user_categoryid', '=', 'usertype.user_categoryid')
->leftJoin('city', 'park.city_id', '=', 'city.city_id')
->where('regular_contract.user_id', $user_id)
->whereNotNull('regular_contract.contract_periods')
->whereNotNull('regular_contract.contract_money')
->select(
'regular_contract.contract_id',
'park.park_name',

View File

@ -12,11 +12,12 @@ use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Artisan;
use Exception;
use function redirect;
use Carbon\Carbon;
class RegularContractCreateController extends Controller
{
// 新規作成画面表示
public function show()
public function show(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
@ -88,35 +89,62 @@ class RegularContractCreateController extends Controller
'station_name_ruby' => 'station.station_name_ruby',
'park_id' => 'park.park_id',
];
if (isset($sortable[$sort])) {
$query->orderBy($sortable[$sort], $order);
} else {
$query->orderBy('park.park_id', 'asc');
}
$total = $query->count();
$parks_table = $query->skip(($page - 1) * $perPage)->take($perPage)->get();
$parks_table = $query->get();
if ($sort === 'park_ruby' || $sort === 'station_name_ruby') {
$collator = new \Collator('ja');
$parks_table = $parks_table->sort(function ($a, $b) use ($order, $sort, $collator) {
$a_val = $a->$sort ?? '';
$b_val = $b->$sort ?? '';
return $order === 'asc'
? $collator->compare($a_val, $b_val)
: $collator->compare($b_val, $a_val);
})->values();
} else {
// park_id, city_idなどはSQLのorderByで十分なので、ここでPHPソートは不要
if (isset($sortable[$sort])) {
$parks_table = $parks_table->sortBy($sort, SORT_REGULAR, $order === 'desc')->values();
}
}
$page = request()->input('page', 1);
$perPage = 10;
$parks_table = $parks_table->slice(($page - 1) * $perPage, $perPage)->values();
// zoneテーブルデータを取得psectionテーブルとJOINしてpsection_subjectも取得
$zones = DB::table('zone')
->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id')
->select('zone.zone_id', 'zone.park_id', 'zone.psection_id', 'zone.zone_number', 'zone.zone_tolerance', 'psection.psection_subject')
->select('zone.zone_id', 'zone.park_id', 'zone.ptype_id', 'zone.psection_id', 'zone.zone_number', 'zone.zone_tolerance', 'psection.psection_subject')
->get()
->groupBy('park_id');
// 空き予約マスタデータを取得
$reserve = DB::table('reserve')
->select('reserve_id', 'park_id', 'psection_id')
->select('reserve_id', 'park_id', 'ptype_id', 'psection_id')
->where('valid_flag', 1)
->get()
->groupBy('park_id');
// ルート名で画面表示を切り替え(新規定期契約 or 駐輪場検索)
$isRegularContract = $request->route()->getName() === 'regular_contract.create';
// ヘッダーの選択状態を分岐
$active_menu = $isRegularContract ? 'SWC-8-1' : 'SWC-10-1';
if ($isRegularContract) {
\Log::info('新規定期契約-駐輪場選択画面にアクセス', [
'user_id' => $user_id,
]);
} else {
\Log::info('駐輪場検索-駐輪場選択画面にアクセス', [
'user_id' => $user_id,
]);
}
return view('regular_contract.create', [
'active_menu' => 'SWC-8-1', // この画面のID
'active_menu' => $active_menu, // この画面のID
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
'cities' => $cities,
'stations' => $stations,
@ -128,6 +156,7 @@ class RegularContractCreateController extends Controller
'zones' => $zones,
'city_grace_periods' => $city_grace_periods,
'reserve' => $reserve,
'isRegularContract' => $isRegularContract
]);
}
@ -563,7 +592,7 @@ class RegularContractCreateController extends Controller
'user_categoryid' => $user_categoryid,
'park_id' => $park->park_id,
'contract_created_at' => now(),
'contract_reduction' => $ward_residents,
'contract_reduction' => $request->contract_reduction === 'はい' ? 1 : 0,
'update_flag' => 2,
'contract_cancel_flag' => 0,
'psection_id' => $request->psection_id,
@ -685,45 +714,24 @@ class RegularContractCreateController extends Controller
'user_seq' => $user_seq,
'park_id' => $park_id
]);
// 処理結果に基づいて遷移先を決定
if ($exitCode === 0) {
// 成功の場合
return redirect("/regular-contract/upload_identity_success?contract_id={$contract_id}");
// return view('regular_contract.create_confirm', [
// 'contract_id' => $request->contract_id,
// 'user' => $user,
// 'park' => $park,
// 'psection' => $psection,
// 'usertype' => $usertype,
// 'user_name' => $user->user_name,
// 'active_menu' => 'SWC-8-1'
// ]);
} else {
// 失敗の場合 または、学生証 と その他 の場合
return redirect("/regular-contract/upload_identity_fail?contract_id={$contract_id}");
// return view('regular_contract.create_confirm', [
// 'contract_id' => $request->contract_id,
// 'user' => $user,
// 'park' => $park,
// 'psection' => $psection,
// 'usertype' => $usertype,
// 'user_name' => $user->user_name,
// 'active_menu' => 'SWC-8-1'
// ]);
}
} catch (\Exception $e) {
\Log::error('SHJ-1バッチ処理でエラー発生', [
'error' => $e->getMessage(),
'user_seq' => $user_seq,
'park_id' => $park_id
]);
return redirect("/regular-contract/upload_identity_fail?contract_id={$contract_id}");
}
// 処理結果に基づいて遷移
return view('regular_contract.create_confirm', [
'contract_id' => $request->contract_id,
'user' => $user,
'park' => $park,
'psection' => $psection,
'usertype' => $usertype,
'user_name' => $user->user_name,
'active_menu' => 'SWC-8-1'
]);
}
public function createConfirmNext($contract_id)
@ -784,7 +792,7 @@ class RegularContractCreateController extends Controller
// 利用期間選択画面へ遷移
return view('regular_contract.create_select_period', [
'active_menu' => 'SWC-4-1', // マイページメニューの選択状態用
'active_menu' => 'SWC-8-1', // マイページメニューの選択状態用
'user_name' => $user->user_name, // ユーザー名(ヘッダー用)
'contract_id' => $contract_id,
'regular_type' => $regular_type,
@ -809,14 +817,20 @@ class RegularContractCreateController extends Controller
return redirect('/login');
}
// 期間選択チェック
$request->validate([
$contract_id = $request->input('contract_id');
$validator = Validator::make($request->all(), [
'month' => 'required',
], [
'month.required' => '契約期間が選択されていません。',
]);
$contract_id = $request->input('contract_id');
if ($validator->fails()) {
return redirect()->route('regular_contract.create_confirm_error', ['contract_id' => $contract_id])
->withErrors($validator)
->withInput();
}
$month = $request->input('month');
$price = $request->input('price_' . $month);
@ -842,554 +856,4 @@ class RegularContractCreateController extends Controller
// 完了後はウェルネット決済画面(仮)へリダイレクト
return redirect()->route('wellnet.payment');
}
/**
* SHJ-1本人確認処理成功ページ表示テスト後に削除してviewも
*/
public function showUploadIdentitySuccess(Request $request)
{
$contractId = $request->get('contract_id');
$userId = Session::get('user_id');
// 詳細情報を取得
$debugInfo = $this->getShjDebugInfo($userId, $contractId);
return view('regular_contract.upload_identity_success', compact('debugInfo', 'contractId'));
}
/**
* SHJ-1本人確認処理失敗ページ表示テスト後に削除してviewも
*/
public function showUploadIdentityFail(Request $request)
{
$contractId = $request->get('contract_id');
$userId = Session::get('user_id');
// 詳細情報を取得
$debugInfo = $this->getShjDebugInfo($userId, $contractId);
return view('regular_contract.upload_identity_fail', compact('debugInfo', 'contractId'));
}
/**
* SHJ-1デバッグ情報取得!!!!テスト後に削除して!!!!
*/
private function getShjDebugInfo($userId, $contractId)
{
try {
// ユーザー情報取得
$user = DB::table('user')->where('user_id', $userId)->first();
// 契約情報取得
$contract = DB::table('regular_contract')->where('user_id', $user->user_id ?? 0)->first();
// 駐輪場情報取得
$park = null;
if ($contract) {
$park = DB::table('park')->where('park_id', $contract->park_id)->first();
}
// 最新のSHJ-1バッチログ取得
$batchLog = DB::table('batch_log')
->where('process_name', 'SHJ-1本人確認自動処理')
->orderBy('created_at', 'desc')
->first();
// 最新のログファイルから詳細ログを取得
$logContent = $this->getRecentShjLogs();
// ログからAPI結果情報を解析
$apiResults = $this->parseApiResultsFromLogs($logContent);
return [
'user' => $user,
'contract' => $contract,
'park' => $park,
'batch_log' => $batchLog,
'detailed_logs' => $logContent,
'recent_logs' => $logContent, // 为调试页面提供日志内容访问
'timestamp' => now()->format('Y-m-d H:i:s'),
// API結果情報
'ocr_text_length' => $apiResults['ocr_text_length'],
'ocr_text_preview' => $apiResults['ocr_text_preview'],
'ocr_full_text' => $apiResults['ocr_full_text'],
'ocr_threshold' => $apiResults['ocr_threshold'],
'name_similarity' => $apiResults['name_similarity'],
'address_similarity' => $apiResults['address_similarity'],
'name_passed' => $apiResults['name_passed'],
'address_passed' => $apiResults['address_passed'],
'matched_address_type' => $apiResults['matched_address_type'] ?? null, // 新增:匹配成功的地址类型
'name_match_attempts' => $apiResults['name_match_attempts'],
'address_match_attempts' => $apiResults['address_match_attempts'],
'best_name_match' => $apiResults['best_name_match'],
'best_address_match' => $apiResults['best_address_match'],
'name_match_details' => $apiResults['name_match_details'],
'address_match_details' => $apiResults['address_match_details'],
'ocr_debug_info' => $apiResults['ocr_debug_info'] ?? null,
// 移除重复分析逻辑只显示SHJ-1的结果
'calculated_distance' => $apiResults['calculated_distance'],
'distance_text' => $apiResults['distance_text'] ?? null,
'distance_limit' => $apiResults['distance_limit'] ?? null,
'distance_start_address' => $apiResults['distance_start_address'] ?? null,
'distance_end_address' => $apiResults['distance_end_address'] ?? null,
'distance_passed' => $apiResults['distance_passed'],
'maps_api_status' => $apiResults['maps_api_status'],
'maps_api_error' => $apiResults['maps_api_error']
];
} catch (Exception $e) {
\Log::error('Debug info error: ' . $e->getMessage());
return [
'error' => 'デバッグ情報の取得に失敗しました: ' . $e->getMessage(),
'timestamp' => now()->format('Y-m-d H:i:s')
];
}
}
/**
* 最新のSHJ-1関連ログを取得!!!!テスト後に削除して!!!!
*/
private function getRecentShjLogs()
{
try {
$logPath = storage_path('logs/laravel.log');
if (!file_exists($logPath)) {
return '日志文件未找到';
}
$logs = file_get_contents($logPath);
$lines = explode("\n", $logs);
// 最新の1000行からSHJ-1関連ログを抽出さらに範囲拡大
$recentLines = array_slice($lines, -1000);
$shjLogs = [];
foreach ($recentLines as $line) {
if (strpos($line, 'SHJ-1') !== false ||
strpos($line, 'GoogleVision') !== false ||
strpos($line, 'Google Maps') !== false ||
strpos($line, 'バッチ処理') !== false) {
$shjLogs[] = $line;
}
}
return implode("\n", array_slice($shjLogs, -200)); // 最新200行さらに拡大
} catch (Exception $e) {
return 'ログ取得エラー: ' . $e->getMessage();
}
}
/**
* ログからAPI結果情報を解析テスト後に削除して
*/
private function parseApiResultsFromLogs($logContent)
{
$results = [
'ocr_text_length' => 'N/A',
'ocr_text_preview' => 'N/A',
'ocr_threshold' => 'N/A',
'name_similarity' => 'N/A',
'address_similarity' => 'N/A',
'name_passed' => false,
'address_passed' => false,
'calculated_distance' => 'N/A',
'distance_passed' => false,
'maps_api_status' => '成功',
'maps_api_error' => 'なし',
// 新增详细OCR信息
'ocr_full_text' => 'N/A',
'name_match_attempts' => [],
'address_match_attempts' => [],
'best_name_match' => 'N/A',
'best_address_match' => 'N/A',
'match_details' => 'N/A'
];
try {
// OCR処理完了ログから文字数とプレビューを抽取清理格式
if (preg_match('/GoogleVision OCR処理完了.*"text_length":(\d+).*"text_preview":"([^"]*)"/', $logContent, $matches)) {
$results['ocr_text_length'] = $matches[1];
// 清理OCR文本移除可能的日志污染
$cleanText = $matches[2];
// 移除可能混入的日志时间戳和标记
$cleanText = preg_replace('/\[\d{4}-\d{2}-\d{2}.*?\].*?local\.INFO.*?$/', '', $cleanText);
// 移除末尾的不完整JSON片段
$cleanText = preg_replace('/\s*\{?\s*$/', '', $cleanText);
// 清理换行和多余空格
$cleanText = trim($cleanText);
$results['ocr_text_preview'] = $cleanText;
}
// 尝试从GoogleVision日志提取完整的OCR文本
if (preg_match('/GoogleVision OCR処理完了.*"text_full":"([^"]*)"/', $logContent, $matches)) {
$fullText = $matches[1];
// 解码JSON转义字符
$fullText = str_replace('\n', "\n", $fullText);
$fullText = str_replace('\r', "\r", $fullText);
$fullText = str_replace('\"', '"', $fullText);
$fullText = str_replace('\\\\', '\\', $fullText);
$results['ocr_full_text'] = $fullText;
}
// 尝试从SHJ-1日志提取完整的OCR文本多种模式匹配
$patterns = [
'/SHJ-1 完全OCR認識結果.*"ocr_full_text":"([^"]*)"/', // 原始日文版本
'/螳悟・OCR隱崎ュ倡オ先棡.*"ocr_full_text":"([^"]*)"/', // 日文编码版本
'/OCR認識結果.*"ocr_full_text":"([^"]*)"/', // 简化匹配
'/"ocr_full_text":"([^"]*)"/', // 最宽泛匹配
];
foreach ($patterns as $pattern) {
if (empty($results['ocr_full_text']) && preg_match($pattern, $logContent, $matches)) {
$encodedText = $matches[1];
// Base64解码
$fullText = base64_decode($encodedText);
if ($fullText !== false && strlen($fullText) > 10) {
$results['ocr_full_text'] = $fullText;
// 简化OCR信息显示 - 只显示基本信息
$results['ocr_debug_info'] = [
'decoded_length' => strlen($fullText),
'preview' => substr($fullText, 0, 200)
];
break; // 找到后停止尝试其他模式
}
}
}
// 尝试从OCR认识内容详细日志提取备选方案适配日文编码
if (empty($results['ocr_full_text']) && preg_match('/OCR隱崎ュ伜・螳ケ隧ウ邏ー.*"raw_text":"OCR_TEXT_START>([^<]*)<OCR_TEXT_END"/', $logContent, $matches)) {
$results['ocr_full_text'] = $matches[1];
}
// 提取氏名匹配详细信息(适配日文编码)
$nameMatchDetails = [];
if (preg_match_all('/豌丞錐繝槭ャ繝√Φ繧ー隧ウ邏ー.*"target_name":"([^"]*)".*"similarity_score":([^,}]*).*"ocr_contains_name":"([^"]*)"/', $logContent, $nameMatches, PREG_SET_ORDER)) {
foreach ($nameMatches as $match) {
$nameMatchDetails[] = [
'target' => $match[1],
'score' => round(floatval($match[2]), 2),
'found_in_ocr' => $match[3]
];
}
}
$results['name_match_details'] = $nameMatchDetails;
// 提取住所匹配详细信息(适配日文编码)
$addressMatchDetails = [];
if (preg_match_all('/菴乗園繝槭ャ繝√Φ繧ー隧ウ邏ー.*"target_address":"([^"]*)".*"address_type":"([^"]*)".*"similarity_score":([^,}]*).*"ocr_contains_address":"([^"]*)"/', $logContent, $addressMatches, PREG_SET_ORDER)) {
foreach ($addressMatches as $match) {
$addressMatchDetails[] = [
'target' => $match[1],
'type' => $match[2],
'score' => round(floatval($match[3]), 2),
'found_in_ocr' => $match[4]
];
}
}
$results['address_match_details'] = $addressMatchDetails;
// 从新的SHJ-1日志格式中提取信息
// 提取OCR抽出结果
if (preg_match('/SHJ-1 OCR抽出成功.*"extracted_name":"([^"]*)".*"extracted_address":"([^"]*)".*"ocr_value":"([^"]*)"/', $logContent, $matches)) {
$results['extracted_name'] = $matches[1];
$results['extracted_address'] = $matches[2];
$results['extracted_ocr_value'] = $matches[3];
}
// 提取居住住所照合结果
if (preg_match('/SHJ-1 居住住所照合.*"resident_address":"([^"]*)".*"similarity":([^,}]*)/', $logContent, $matches)) {
$results['resident_address'] = $matches[1];
$results['resident_similarity'] = round(floatval($matches[2]), 2);
}
// 提取関連住所照合结果
if (preg_match('/SHJ-1 関連住所照合.*"related_address":"([^"]*)".*"similarity":([^,}]*)/', $logContent, $matches)) {
$results['related_address'] = $matches[1];
$results['related_similarity'] = round(floatval($matches[2]), 2);
}
// 提取最終OCR結果
if (preg_match('/SHJ-1 OCR照合(成功|失敗)/', $logContent, $matches)) {
$results['final_ocr_result'] = $matches[1];
$results['address_passed'] = ($matches[1] === '成功');
$results['name_passed'] = ($matches[1] === '成功'); // 新SHJ-1逻辑中成功表示整体成功
}
// 如果OCR文本为空或太短提供说明
if (empty($results['ocr_text_preview']) || strlen($results['ocr_text_preview']) < 5) {
$results['ocr_text_preview'] = '(OCR認識内容が短いか、表示できない文字が含まれています)';
}
// 表面画像処理完了の詳細結果を抽取
if (preg_match('/SHJ-1 表面画像処理完了.*"front_result":\{"name_matches":\[([^\]]*)\],"address_matches":\[([^\]]*)\]\}/', $logContent, $matches)) {
$nameMatches = explode(',', $matches[1]);
$addressMatches = explode(',', $matches[2]);
$results['name_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $nameMatches);
$results['address_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $addressMatches);
}
// OCR類似度計算結果の詳細情報を抽取
if (preg_match('/SHJ-1 OCR類似度計算結果.*"best_name_match":([^,}]*).*"best_address_match":([^,}]*).*"all_name_matches":\[([^\]]*)\].*"all_address_matches":\[([^\]]*)\]/', $logContent, $matches)) {
$results['best_name_match'] = round(floatval($matches[1]), 2);
$results['best_address_match'] = round(floatval($matches[2]), 2);
$allNameMatches = explode(',', $matches[3]);
$allAddressMatches = explode(',', $matches[4]);
$results['name_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $allNameMatches);
$results['address_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $allAddressMatches);
}
// OCR閾値チェックログから類似度情報を抽出新しい順序匹配対応
if (preg_match('/SHJ-1 OCR閾値チェック.*"threshold":"?([^",}]*)"?.*"name_match":([^,}]*).*"address_match":([^,}]*).*"name_passed":([^,}]*).*"address_passed":([^,}]*).*"matched_address_type":"?([^",}]*)"?/', $logContent, $matches)) {
$results['ocr_threshold'] = $matches[1];
$results['name_similarity'] = round(floatval($matches[2]), 2);
$results['address_similarity'] = round(floatval($matches[3]), 2);
$results['name_passed'] = $matches[4] === 'true';
$results['address_passed'] = $matches[5] === 'true';
$results['matched_address_type'] = $matches[6] ?: null;
}
// Google Maps API エラーチェック
if (strpos($logContent, 'Google Maps distance calculation error') !== false) {
$results['maps_api_status'] = 'エラー';
if (preg_match('/Google Maps distance calculation error.*"error":"([^"]*)"/', $logContent, $matches)) {
$results['maps_api_error'] = $matches[1];
}
} else if (strpos($logContent, 'Distance calculation failed') !== false) {
$results['maps_api_status'] = 'アドレス未発見';
$results['maps_api_error'] = 'NOT_FOUND';
}
// 距離計算結果を抽取(最新の詳細ログから)
if (preg_match_all('/SHJ-1 距離計算完了.*"calculated_distance_meters":(\d+).*"distance_text":"([^"]*)".*"limit_meters":"?([^",}]*)"?.*"within_limit":([^,}]*)/', $logContent, $allMatches, PREG_SET_ORDER)) {
// 最後の(最新の)マッチを使用
$matches = end($allMatches);
$results['calculated_distance'] = $matches[1]; // 距離メートル
$results['distance_text'] = $matches[2]; // Google Mapsテキスト
$results['distance_limit'] = $matches[3]; // 制限値
$results['distance_passed'] = $matches[4] === 'true';
} else {
// ログから具体的な結果が取得できない場合はデフォルト値を設定
// ※重要API成功≠距離制限内ではないため、明示的にfalseにする
$results['distance_passed'] = false;
if (strpos($logContent, 'Distance check error') === false &&
strpos($logContent, 'Google Maps distance calculation error') === false) {
$results['calculated_distance'] = '計算成功(制限値詳細はログで確認)';
$results['maps_api_status'] = '成功(但制限確認要)';
} else {
$results['calculated_distance'] = '計算失敗';
$results['maps_api_status'] = 'エラー';
}
}
// 距離計算開始ログから起点・終点住所を抽取
if (preg_match('/SHJ-1 距離計算開始.*"user_address":"([^"]*)".*"park_address":"([^"]*)"/', $logContent, $matches)) {
$results['distance_start_address'] = $matches[1];
$results['distance_end_address'] = $matches[2];
}
} catch (Exception $e) {
\Log::error('API結果解析エラー: ' . $e->getMessage());
}
return $results;
}
// 不再需要的分析方法已移除 - 只显示SHJ-1的结果
/**
* 废弃的方法(已不再使用)
*/
private function manualOcrAnalysis($logContent, $user = null)
{
$analysis = [
'found_base64' => false,
'decoded_success' => false,
'decoded_text' => '',
'contains_yamada' => false,
'contains_taro' => false,
'contains_tokyo' => false,
'contains_osaka' => false,
'full_analysis' => 'Analysis failed',
'corrected_matching' => null
];
try {
// 分割日志内容为行数组按时间倒序搜索最新的OCR结果
$logLines = explode("\n", $logContent);
$logLines = array_reverse($logLines); // 从最新的开始搜索
// 查找最新的Base64编码OCR结果
$patterns = [
'/SHJ-1 完全OCR認識結果.*"ocr_full_text":"([^"]*)"/',
'/SHJ-1.*OCR.*結果.*"ocr_full_text":"([^"]*)"/',
'/"ocr_full_text":"([^"]*)"/'
];
foreach ($logLines as $line) {
foreach ($patterns as $pattern) {
if (preg_match($pattern, $line, $matches)) {
$analysis['found_base64'] = true;
$base64Text = $matches[1];
// Base64解码
$decodedText = base64_decode($base64Text);
if ($decodedText !== false && strlen($decodedText) > 10) {
$analysis['decoded_success'] = true;
$analysis['decoded_text'] = $decodedText;
// 内容分析
$analysis['contains_yamada'] = strpos($decodedText, '山田') !== false;
$analysis['contains_taro'] = strpos($decodedText, '太郎') !== false;
$analysis['contains_tokyo'] = strpos($decodedText, '東京') !== false;
$analysis['contains_osaka'] = strpos($decodedText, '大阪') !== false;
// ユーザー提案:空白と改行を除去して比較
$analysis['corrected_matching'] = $this->performCorrectedMatching($decodedText, $user);
// 詳細分析(実際のユーザーデータから期待値を取得)
if ($user) {
$expectedName = $user->user_name ?? "";
$expectedAddress = ($user->user_regident_pre ?? '') .
($user->user_regident_city ?? '') .
($user->user_regident_add ?? '') ?: "";
} else {
$expectedName = "";
$expectedAddress = "";
}
$analysis['full_analysis'] =
"OCR認識テキスト長: " . strlen($decodedText) . "文字\n" .
"期待氏名: '$expectedName'\n" .
"期待住所: '$expectedAddress'\n" .
"山田を含む: " . ($analysis['contains_yamada'] ? 'YES' : 'NO') . "\n" .
"太郎を含む: " . ($analysis['contains_taro'] ? 'YES' : 'NO') . "\n" .
"東京を含む: " . ($analysis['contains_tokyo'] ? 'YES' : 'NO') . "\n" .
"大阪を含む: " . ($analysis['contains_osaka'] ? 'YES' : 'NO') . "\n" .
"認識完全内容: " . substr($decodedText, 0, 200) . "...";
// 成功解码最新OCR结果后立即返回
return $analysis;
}
// 解码失败时,继续搜索其他条目
}
}
}
} catch (Exception $e) {
$analysis['full_analysis'] = 'OCR分析エラー: ' . $e->getMessage();
}
return $analysis;
}
/**
* 修正マッチングアルゴリズム実行(空白・改行除去)
*/
private function performCorrectedMatching($ocrText, $user = null)
{
// ユーザー情報から期待値を取得、デフォルトはテスト用
if ($user) {
$expectedName = $user->user_name ?? "";
$expectedAddress = ($user->user_regident_pre ?? '') .
($user->user_regident_city ?? '') .
($user->user_regident_add ?? '') ?: "";
} else {
$expectedName = "";
$expectedAddress = "";
}
// 統一的テキスト正規化関数
$normalize = function($text) {
// 全空白文字と改行を除去(全角スペース含む)
$text = preg_replace('/[\s\x{3000}]+/u', '', $text); // \x{3000}は全角スペース
// 一般的な空白文字を明示的に除去
$text = str_replace([' ', ' ', "\t", "\n", "\r"], '', $text);
// 全角→半角変換
$text = mb_convert_kana($text, 'rnask', 'UTF-8');
// 住所統一
$text = str_replace(['東京市', '東京府'], '東京都', $text);
$text = str_replace(['の'], '', $text);
// 数字統一
$text = str_replace(['','','','','','','','','',''],
['1','2','3','4','5','6','7','8','9','0'], $text);
return $text;
};
// 正規化処理
$normalizedOcr = $normalize($ocrText);
$normalizedExpectedName = $normalize($expectedName);
$normalizedExpectedAddr = $normalize($expectedAddress);
// 使用"住所"分割OCR文本
$addressKeyword = '住所';
$ocrParts = explode($addressKeyword, $normalizedOcr, 2);
$personalInfoSection = $ocrParts[0] ?? ''; // "住所"前の個人情報欄
$addressSection = isset($ocrParts[1]) ? $addressKeyword . $ocrParts[1] : ''; // "住所"後の住所欄
// 分区マッチング計算
$nameMatch = $this->calculateSimpleMatch($normalizedExpectedName, $personalInfoSection);
$addrMatch = $this->calculateSimpleMatch($normalizedExpectedAddr, $addressSection);
return [
'original_ocr' => substr($ocrText, 0, 100) . '...',
'normalized_ocr' => substr($normalizedOcr, 0, 100) . '...',
'normalized_expected_name' => $normalizedExpectedName,
'normalized_expected_addr' => $normalizedExpectedAddr,
'personal_info_section' => substr($personalInfoSection, 0, 80) . '...',
'address_section' => substr($addressSection, 0, 80) . '...',
'name_match_score' => $nameMatch,
'addr_match_score' => $addrMatch,
'name_passed' => $nameMatch >= 70,
'addr_passed' => $addrMatch >= 70,
'overall_result' => ($nameMatch >= 70 && $addrMatch >= 70) ? 'PASS' : 'FAIL'
];
}
/**
* シンプルマッチング計算
*/
private function calculateSimpleMatch($expected, $haystack)
{
if (empty($expected)) return 0;
// 1. 完全包含チェック
if (strpos($haystack, $expected) !== false) {
return 100;
}
// 2. 文字包含率
$expectedChars = mb_str_split($expected, 1, 'UTF-8');
$foundChars = 0;
foreach ($expectedChars as $char) {
if (mb_strpos($haystack, $char, 0, 'UTF-8') !== false) {
$foundChars++;
}
}
$charRate = ($foundChars / count($expectedChars)) * 100;
// 3. 類似度計算
similar_text($expected, $haystack, $similarRate);
// 最高スコアを返す
return max($charRate, $similarRate);
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
class SealReissueController extends Controller
{
public function index($contract_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
$contract = DB::table('regular_contract')
->join('park', 'regular_contract.park_id', '=', 'park.park_id')
->where('contract_id', $contract_id)
->select('regular_contract.contract_id', 'park.park_name')
->first();
\Log::info('シール再発行確認画面にアクセス', [
'user_id' => $user_id,
]);
return view('regular_contract.seal_reissue', [
'contract' => $contract,
'active_menu' => 'SWC-3-1', // マイページメニューの選択状態用
'user_name' => $user_name ? $user_name : '', // ユーザー名(ヘッダー用)
]);
}
public function reason($contract_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
\Log::info('シール再発行理由入力画面にアクセス', [
'user_id' => $user_id,
]);
return view('regular_contract.seal_reissue_reason', [
'contract_id' => $contract_id,
'active_menu' => 'SWC-3-1', // マイページメニューの選択状態用
'user_name' => $user_name ? $user_name : '', // ユーザー名(ヘッダー用)
]);
}
public function complete(Request $request, $contract_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
$validated = $request->validate([
'reason' => ['required'],
'other_reason' => [
'nullable',
'string',
'max:255',
'regex:/^[\x20-\x7Eぁ-んァ-ヶ一-龠々ーa-zA-Z0-9---Z、。・「」『』()【】[]{}〈〉《》!?:;…ー~\s\r\n]+$/u',
'required_if:reason,その他'
],
], [
'reason.required' => '理由を選択してください。',
'other_reason.max' => 'その他の理由は255文字以内で入力してください。',
'other_reason.regex' => 'その他の理由に使用できない文字が含まれています。',
'other_reason.required_if' => 'その他を選択した場合は理由を入力してください。'
]);
$contract = DB::table('regular_contract')
->join('park', 'regular_contract.park_id', '=', 'park.park_id')
->where('contract_id', $contract_id)
->select('regular_contract.contract_id', 'park.park_name')
->first();
$reason = $request->input('reason');
$other_reason = $request->input('other_reason');
\Log::info('シール再発行申請完了画面にアクセス', [
'user_id' => $user_id,
]);
return view('regular_contract.seal_reissue_complete', [
'active_menu' => 'SWC-3-1', // マイページメニューの選択状態用
'user_name' => $user_name ? $user_name : '', // ユーザー名(ヘッダー用)
'contract' => $contract
]);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
class UserInformationController extends Controller
{
public function index()
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
// お知らせデータ取得
$informations = DB::table('user_information_history')
->where('user_id', $user_id)
->orderByDesc('user_information_history_id')
->select('entry_date', 'user_information_history')
->limit(10)
->get();
\Log::info('お知らせ画面にアクセス', [
'user_id' => $user_id,
]);
return view('user_information.index', [
'user_name' => $user_name, // ユーザー名(ヘッダー用)
'informations' => $informations
]);
}
public function history()
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
// お知らせデータ取得(全件)
$informations = DB::table('user_information_history')
->where('user_id', $user_id)
->orderByDesc('user_information_history_id')
->select('entry_date', 'user_information_history')
->paginate(10);
\Log::info('過去のお知らせ画面にアクセス', [
'user_id' => $user_id,
]);
return view('user_information.history', [
'user_name' => $user_name, // ユーザー名(ヘッダー用)
'informations' => $informations
]);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
class UserTagReissueController 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();
\Log::info('タグ再発行申請画面にアクセス', [
'user_id' => $user_id,
]);
return view('user.tag_reissue', [
'user' => $user,
'active_menu' => 'SWC-1-1', // マイページメニューの選択状態用
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
]);
}
public function complete()
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
\Log::info('タグ再発行申請完了画面にアクセス', [
'user_id' => $user_id,
]);
return view('user.tag_reissue_complete', [
'active_menu' => 'SWC-1-1', // マイページメニューの選択状態用
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
]);
}
}

View File

@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Auth;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
use App\Mail\WithdrawCompleteMail;
class UserWithdrawController extends Controller

View File

@ -8,8 +8,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use App\Models\Batch\BatchLog;
use App\Models\SettlementTransaction;
use App\Services\ShjFourBService;
/**
@ -79,18 +77,6 @@ class ProcessSettlementJob implements ShouldQueue
{
$startTime = now();
// バッチログの開始記録
$batch = BatchLog::createBatchLog(
'shj4b',
BatchLog::STATUS_START,
[
'settlement_transaction_id' => $this->settlementTransactionId,
'context' => $this->context,
'job_id' => $this->job->getJobId(),
],
'SHJ-4B ProcessSettlementJob start'
);
try {
Log::info('SHJ-4B ProcessSettlementJob開始', [
'settlement_transaction_id' => $this->settlementTransactionId,
@ -99,24 +85,15 @@ class ProcessSettlementJob implements ShouldQueue
]);
// SHJ-4Bサービスを使用して決済トランザクション処理を実行
// バッチログはShjFourBServiceが自動的にSHJ-8経由で作成
$shjFourBService = app(ShjFourBService::class);
$result = $shjFourBService->processSettlementTransaction(
$this->settlementTransactionId,
$this->context
);
// 処理結果に基づいてバッチログを更新
// 処理結果のログ記録
if ($result['success']) {
$batch->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => 'SHJ-4B ProcessSettlementJob completed successfully',
'success_count' => 1,
'parameters' => json_encode([
'result' => $result,
]),
]);
Log::info('SHJ-4B ProcessSettlementJob完了', [
'settlement_transaction_id' => $this->settlementTransactionId,
'execution_time' => now()->diffInSeconds($startTime),
@ -124,17 +101,6 @@ class ProcessSettlementJob implements ShouldQueue
]);
} else {
// ビジネスロジック上の問題(エラーではない)
$batch->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => 'SHJ-4B ProcessSettlementJob completed with issues: ' . $result['reason'],
'success_count' => 0,
'parameters' => json_encode([
'result' => $result,
'requires_manual_action' => true,
]),
]);
Log::warning('SHJ-4B ProcessSettlementJob要手動対応', [
'settlement_transaction_id' => $this->settlementTransactionId,
'result' => $result,
@ -148,15 +114,7 @@ class ProcessSettlementJob implements ShouldQueue
'trace' => $e->getTraceAsString(),
]);
// バッチログのエラー記録
$batch->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => 'SHJ-4B ProcessSettlementJob failed: ' . $e->getMessage(),
'error_details' => $e->getTraceAsString(),
'error_count' => 1,
]);
// エラー時もShjFourBServiceが自動的にバッチログを作成
// ジョブを失敗させて再試行を促す
throw $e;
}
@ -177,6 +135,7 @@ class ProcessSettlementJob implements ShouldQueue
'attempts' => $this->attempts(),
]);
// バッチログはShjFourBServiceが既に作成済み
// 最終失敗時の追加処理があればここに記述
// 例:管理者への通知、障害キューへの登録など
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class ReservationCancelledMail extends Mailable
{
use Queueable, SerializesModels;
public $user_name;
public $reserve;
public function __construct($user_name, $reserve)
{
$this->user_name = $user_name;
$this->reserve = $reserve;
}
public function build()
{
return $this->subject('So-Manager空き待ちのキャンセルが完了しました')
->view('emails.reservation_cancelled')
->with([
'user_name' => $this->user_name,
'reserve' => $this->reserve,
]);
}
}

70
app/Models/BatJobLog.php Normal file
View File

@ -0,0 +1,70 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* バッチジョブログモデル - bat_job_logテーブル
*
* SHJ-8で使用する旧バッチログテーブル
* 各バッチ処理の実行ログを記録する
*/
class BatJobLog extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'bat_job_log';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'job_log_id';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'device_id', // デバイスID
'process_name', // プロセス名
'job_name', // ジョブ名
'status', // ステータス
'status_comment', // ステータスコメント
'created_at', // 登録日時
'updated_at' // 更新日時
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'job_log_id' => 'integer',
'device_id' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* タイムスタンプを使用
*
* @var bool
*/
public $timestamps = true;
/**
* deviceとのリレーション
*/
public function device()
{
return $this->belongsTo(Device::class, 'device_id', 'device_id');
}
}

View File

@ -1,159 +0,0 @@
<?php
namespace App\Models\Batch;
use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;
/**
* 共通バッチログモデル - batch_logテーブル
*
* 全てのSHJ系バッチ処理の実行ログを管理する統一モデル
*/
class BatchLog extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'batch_log';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'id';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'process_name', // プロセス名
'status', // ステータス
'start_time', // 開始時刻
'end_time', // 終了時刻
'parameters', // パラメータJSON形式
'message', // メッセージ
'error_details', // エラー詳細
'execution_count', // 実行回数
'success_count', // 成功回数
'error_count', // エラー回数
'created_at', // 作成日時
'updated_at' // 更新日時
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'start_time' => 'datetime',
'end_time' => 'datetime',
'parameters' => 'array',
'execution_count' => 'integer',
'success_count' => 'integer',
'error_count' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* ステータスの定数
*/
const STATUS_START = 'start'; // 開始
const STATUS_RUNNING = 'running'; // 実行中
const STATUS_SUCCESS = 'success'; // 成功
const STATUS_ERROR = 'error'; // エラー
const STATUS_WARNING = 'warning'; // 警告
const STATUS_CANCELLED = 'cancelled'; // キャンセル
/**
* 通用バッチログ作成メソッド
*
* 任意のバッチ処理で使用可能な統一ログ記録機能
* 実際の実行コマンド名をそのまま記録
*
* @param string $processName プロセス名shj1, shj9:daily等
* @param string $status ステータス
* @param array $parameters パラメーター配列
* @param string $message メッセージ
* @param array $additionalData 追加データdevice_id等
* @return BatchLog 作成されたバッチログ
*/
public static function createBatchLog(
string $processName,
string $status,
array $parameters = [],
string $message = '',
array $additionalData = []
): BatchLog {
// パラメーターに追加データをマージ
$allParameters = array_merge($parameters, $additionalData, [
'executed_at' => now()->toISOString()
]);
return self::create([
'process_name' => $processName,
'status' => $status,
'start_time' => now(),
'end_time' => ($status === self::STATUS_SUCCESS || $status === self::STATUS_ERROR) ? now() : null,
'parameters' => $allParameters,
'message' => $message,
'error_details' => ($status === self::STATUS_ERROR) ? $message : null,
'execution_count' => 1,
'success_count' => $status === self::STATUS_SUCCESS ? 1 : 0,
'error_count' => $status === self::STATUS_ERROR ? 1 : 0
]);
}
/**
* 成功時のステータスコメント生成
*
* @return string ステータスコメント
*/
public static function getSuccessComment(): string
{
return '処理成功';
}
/**
* エラー時のステータスコメント生成
*
* @param string $errorMessage エラーメッセージ
* @return string ステータスコメント
*/
public static function getErrorComment(string $errorMessage): string
{
return 'エラー: ' . $errorMessage;
}
/**
* バッチログの文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'BatchLog[ID:%d, Process:%s, Status:%s, Time:%s]',
$this->id,
$this->process_name,
$this->status,
$this->start_time ? $this->start_time->format('Y-m-d H:i:s') : 'N/A'
);
}
}

View File

@ -48,8 +48,8 @@ class EarningsSummary extends Model
'regular_update_amount', // 更新金額
'regular_update_reduction_count', // 更新成免件数
'regular_update_reduction_amount', // 更新成免金額
'turnsum_count', // 残金件数
'turnsum', // 残
'lumpsum_count', // 一時金件数
'lumpsum', // 一時
'refunds', // 解時返戻金
'other_income', // 分別収入
'other_spending', // 分別支出
@ -79,8 +79,8 @@ class EarningsSummary extends Model
'regular_update_amount' => 'decimal:2',
'regular_update_reduction_count' => 'integer',
'regular_update_reduction_amount' => 'decimal:2',
'turnsum_count' => 'integer',
'turnsum' => 'decimal:2',
'lumpsum_count' => 'integer',
'lumpsum' => 'decimal:2',
'refunds' => 'decimal:2',
'other_income' => 'decimal:2',
'other_spending' => 'decimal:2',
@ -175,8 +175,8 @@ class EarningsSummary extends Model
SUM(regular_new_amount) as total_new_amount,
SUM(regular_update_count) as total_update_count,
SUM(regular_update_amount) as total_update_amount,
SUM(turnsum_count) as total_turnsum_count,
SUM(turnsum) as total_turnsum,
SUM(lumpsum_count) as total_lumpsum_count,
SUM(lumpsum) as total_lumpsum,
SUM(refunds) as total_refunds,
SUM(reissue_count) as total_reissue_count,
SUM(reissue_amount) as total_reissue_amount
@ -249,8 +249,8 @@ class EarningsSummary extends Model
'regular_update_amount' => 0.00,
'regular_update_reduction_count' => 0,
'regular_update_reduction_amount' => 0.00,
'turnsum_count' => 0,
'turnsum' => 0.00,
'lumpsum_count' => 0,
'lumpsum' => 0.00,
'refunds' => 0.00,
'other_income' => 0.00,
'other_spending' => 0.00,
@ -273,7 +273,7 @@ class EarningsSummary extends Model
{
return $this->regular_new_amount +
$this->regular_update_amount +
$this->turnsum +
$this->lumpsum +
$this->reissue_amount +
$this->other_income -
$this->other_spending -

View File

@ -24,7 +24,7 @@ class HardwareCheckLog extends Model
*
* @var string
*/
protected $primaryKey = 'log_id';
protected $primaryKey = 'hardware_check_log_id';
/**
* 一括代入可能な属性
@ -46,7 +46,7 @@ class HardwareCheckLog extends Model
* @var array
*/
protected $casts = [
'log_id' => 'integer',
'hardware_check_log_id' => 'integer',
'device_id' => 'integer',
'status' => 'integer',
'operator_id' => 'integer',
@ -220,7 +220,7 @@ class HardwareCheckLog extends Model
{
return sprintf(
'HardwareCheckLog[ID:%d, Device:%d, Status:%s, Time:%s]',
$this->log_id,
$this->hardware_check_log_id,
$this->device_id,
$this->getStatusNameAttribute(),
$this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A'

View File

@ -0,0 +1,97 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 管轄駐輪場モデル - jurisdiction_parkingテーブル
*
* オペレータが管轄する駐輪場の情報を管理
*/
class JurisdictionParking extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'jurisdiction_parking';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'jurisdiction_parking_id';
/**
* タイムスタンプ使用
*
* @var bool
*/
public $timestamps = true;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'jurisdiction_parking_name',
'ope_id',
'park_id',
'operator_id'
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'jurisdiction_parking_id' => 'integer',
'ope_id' => 'integer',
'park_id' => 'integer',
'operator_id' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* オペレータとの関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function ope()
{
return $this->belongsTo(Ope::class, 'ope_id', 'ope_id');
}
/**
* 駐輪場との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function park()
{
return $this->belongsTo(Park::class, 'park_id', 'park_id');
}
/**
* 指定駐輪場を管轄するオペレータIDリストを取得
*
* @param int $parkId 駐輪場ID
* @return array オペレータIDの配列
*/
public static function getOperatorIdsByPark(int $parkId): array
{
return self::where('park_id', $parkId)
->pluck('ope_id')
->toArray();
}
}

228
app/Models/Manager.php Normal file
View File

@ -0,0 +1,228 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 駐輪場管理者モデル - managerテーブル
*
* 駐輪場管理者マスタ情報を管理するモデル
* 各駐輪場に紐づく管理者の情報、警報送信設定などを保持
*/
class Manager extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'manager';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'manager_id';
/**
* タイムスタンプ使用
*
* @var bool
*/
public $timestamps = true;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'manager_name', // 管理者名
'manager_type', // 管理者種別
'manager_parkid', // 所属駐輪場ID
'manager_device1', // デバイス1
'manager_device2', // デバイス2
'manager_mail', // メールアドレス
'manager_tel', // 電話番号
'manager_alert1', // アラート送信フラグ1
'manager_alert2', // アラート送信フラグ2
'manager_quit_flag', // 退職フラグ
'manager_quitday', // 退職日
'operator_id' // 登録オペレータID
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'manager_id' => 'integer',
'manager_parkid' => 'integer',
'manager_device1' => 'integer',
'manager_device2' => 'integer',
'manager_alert1' => 'boolean',
'manager_alert2' => 'boolean',
'manager_quit_flag' => 'boolean',
'operator_id' => 'integer',
'manager_quitday' => 'date',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* 所属駐輪場との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function park()
{
return $this->belongsTo(Park::class, 'manager_parkid', 'park_id');
}
/**
* 登録オペレータとの関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function operator()
{
return $this->belongsTo(Ope::class, 'operator_id', 'ope_id');
}
/**
* アクティブな管理者のみを取得するスコープ
* (退職フラグ = 0
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('manager_quit_flag', 0);
}
/**
* メールアドレスが設定されている管理者のみを取得するスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeHasEmail($query)
{
return $query->whereNotNull('manager_mail')
->where('manager_mail', '!=', '');
}
/**
* 指定駐輪場の管理者を取得するスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $parkId 駐輪場ID
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeByPark($query, int $parkId)
{
return $query->where('manager_parkid', $parkId);
}
/**
* メール送信対象の駐輪場管理者を取得
*
* 条件:
* - 退職フラグ = 0(在職中)
* - メールアドレスが設定されている
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getMailTargetManagers()
{
return self::active()
->hasEmail()
->select(['manager_id', 'manager_name', 'manager_mail', 'manager_parkid'])
->get()
->map(function ($manager) {
return [
'manager_id' => $manager->manager_id,
'name' => $manager->manager_name,
'email' => $manager->manager_mail,
'park_id' => $manager->manager_parkid
];
});
}
/**
* 指定駐輪場のメール送信対象管理者を取得
*
* @param int $parkId 駐輪場ID
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getMailTargetManagersByPark(int $parkId)
{
return self::active()
->hasEmail()
->byPark($parkId)
->select(['manager_id', 'manager_name', 'manager_mail', 'manager_parkid'])
->get()
->map(function ($manager) {
return [
'manager_id' => $manager->manager_id,
'name' => $manager->manager_name,
'email' => $manager->manager_mail,
'park_id' => $manager->manager_parkid
];
});
}
/**
* 退職しているかどうかを判定
*
* @return bool 退職しているかどうか
*/
public function isQuit(): bool
{
return (bool) $this->manager_quit_flag;
}
/**
* アクティブ(在職中)かどうかを判定
*
* @return bool 在職中かどうか
*/
public function isActive(): bool
{
return !$this->isQuit();
}
/**
* メールアドレスが設定されているかどうかを判定
*
* @return bool メールアドレスが設定されているかどうか
*/
public function hasEmail(): bool
{
return !empty($this->manager_mail);
}
/**
* 文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'Manager[ID:%d, Name:%s, Park:%d, Email:%s]',
$this->manager_id,
$this->manager_name,
$this->manager_parkid,
$this->manager_mail ?? 'N/A'
);
}
}

View File

@ -24,7 +24,7 @@ class PrintJobLog extends Model
*
* @var string
*/
protected $primaryKey = 'log_id';
protected $primaryKey = 'job_log_id';
/**
* 一括代入可能な属性
@ -49,7 +49,7 @@ class PrintJobLog extends Model
* @var array
*/
protected $casts = [
'log_id' => 'integer',
'job_log_id' => 'integer',
'park_id' => 'integer',
'user_id' => 'integer',
'contract_id' => 'integer',
@ -242,7 +242,7 @@ class PrintJobLog extends Model
{
return sprintf(
'PrintJobLog[ID:%d, Process:%s, ErrorCode:%d, Time:%s]',
$this->log_id,
$this->job_log_id,
$this->process_name,
$this->error_code,
$this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A'

85
app/Models/Setting.php Normal file
View File

@ -0,0 +1,85 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 設定マスタモデル - settingテーブル
*
* システム全体の設定情報を管理するモデル
*/
class Setting extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'setting';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'setting_id';
/**
* タイムスタンプ使用
*
* @var bool
*/
public $timestamps = true;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'edit_master',
'web_master',
'auto_change_date',
'auto_chage_master',
're-issue_alert_number',
'image_base_url1',
'image_base_url2',
'printable_alert_flag',
'printable_number',
'printable_alert_number',
'printer_keep_alive',
'operator_id'
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'setting_id' => 'integer',
'printable_alert_flag' => 'boolean',
'printable_number' => 'integer',
'printable_alert_number' => 'integer',
'printer_keep_alive' => 'integer',
'operator_id' => 'integer',
'auto_change_date' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* 設定情報を取得通常はID=1の単一レコード)
*
* @return Setting|null
*/
public static function getSettings(): ?Setting
{
return self::first();
}
}

View File

@ -0,0 +1,270 @@
<?php
namespace App\Services;
use App\Models\BatJobLog;
use App\Models\Device;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
/**
* SHJ-8 バッチ処理ログ作成サービス
*
* 概要: 入力パラメーターの情報を元にバッチ処理ログ情報を作成する
*
* 処理フロー:
* 【処理1】入力パラメーターをチェックする
* 【判断1】チェック結果
* NG パラメーターNGの結果を設定する 【処理3】
* OK 【処理2】
* 【処理2】バッチ処理ログを登録する
* 【処理3】処理結果を返却する
*/
class ShjEightService
{
/**
* SHJ-8 メイン処理実行
*
* 修正版7項目入力status_comment追加
* @param int $deviceId デバイスID (必須)
* @param string|null $processName プロセス名
* @param string|null $jobName ジョブ名
* @param string $status ステータス
* @param string $statusComment ステータスコメント (必須, ≤255文字)
* @param string $createdDate 登録日時 (yyyy/mm/dd形式)
* @param string $updatedDate 更新日時 (yyyy/mm/dd形式)
* @return array 処理結果 ['result' => 0|1, 'error_message' => string|null]
*/
public function execute(
int $deviceId,
?string $processName,
?string $jobName,
string $status,
string $statusComment,
string $createdDate,
string $updatedDate
): array {
try {
Log::info('SHJ-8 バッチ処理ログ作成開始', [
'device_id' => $deviceId,
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'status_comment' => $statusComment,
'created_date' => $createdDate,
'updated_date' => $updatedDate
]);
// 【処理1】入力パラメーターをチェックする
$validationResult = $this->validateParameters(
$deviceId,
$processName,
$jobName,
$status,
$statusComment,
$createdDate,
$updatedDate
);
// 【判断1】チェック結果
if (!$validationResult['valid']) {
// パラメーターNG
$errorMessage = $validationResult['error_message'];
Log::warning('SHJ-8 パラメーターチェックNG', [
'error_message' => $errorMessage
]);
// 【処理3】異常終了の結果を返却
return [
'result' => 1,
'error_message' => $errorMessage
];
}
// 【処理2】バッチ処理ログを登録する
$batJobLog = $this->createBatchJobLog(
$deviceId,
$processName,
$jobName,
$status,
$statusComment,
$createdDate,
$updatedDate
);
Log::info('SHJ-8 バッチ処理ログ作成完了', [
'job_log_id' => $batJobLog->job_log_id,
'device_id' => $batJobLog->device_id,
'process_name' => $batJobLog->process_name,
'job_name' => $batJobLog->job_name
]);
// 【処理3】正常終了の結果を返却
return [
'result' => 0
];
} catch (\Exception $e) {
Log::error('SHJ-8 バッチ処理ログ作成エラー', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// 例外発生時の処理結果を返却
return [
'result' => 1,
'error_code' => $e->getCode(),
'error_message' => $e->getMessage(),
'stack_trace' => $e->getTraceAsString()
];
}
}
/**
* 【処理1】入力パラメーターをチェックする
*
* 修正版7項目チェックstatus_comment追加
* 1. デバイスID: 必須、device表に存在チェック
* 2. プロセス名: 「プロセス名」「ジョブ名」いずれか必須
* 3. ジョブ名: 「プロセス名」「ジョブ名」いずれか必須
* 4. ステータス: 必須
* 5. ステータスコメント: 必須、≤255文字
* 6. 登録日時: 日付型(yyyy/mm/dd形式)
* 7. 更新日時: 日付型(yyyy/mm/dd形式)
*
* @param int $deviceId デバイスID
* @param string|null $processName プロセス名
* @param string|null $jobName ジョブ名
* @param string $status ステータス
* @param string $statusComment ステータスコメント
* @param string $createdDate 登録日時
* @param string $updatedDate 更新日時
* @return array 検証結果 ['valid' => bool, 'error_message' => string|null]
*/
private function validateParameters(
int $deviceId,
?string $processName,
?string $jobName,
string $status,
string $statusComment,
string $createdDate,
string $updatedDate
): array {
$errors = [];
// 1. デバイスIDチェック (必須、存在チェック)
if ($deviceId <= 0) {
$errors[] = "パラメーターNGデバイスID/{$deviceId}";
} elseif (!Device::where('device_id', $deviceId)->exists()) {
$errors[] = "パラメーターNGデバイスID/{$deviceId}";
}
// 2. プロセス名とジョブ名のいずれか必須チェック
if (empty($processName) && empty($jobName)) {
$errors[] = "パラメーターNGプロセス名/<空>";
$errors[] = "パラメーターNGジョブ名/<空>";
}
// 3. ステータス必須チェック
if (empty($status)) {
$errors[] = "パラメーターNGステータス/<空>";
}
// 4. ステータスコメント必須チェック、255文字以内
if (empty($statusComment)) {
$errors[] = "パラメーターNGステータスコメント/<空>";
} elseif (mb_strlen($statusComment) > 255) {
$errors[] = "パラメーターNGステータスコメント/長さ超過(" . mb_strlen($statusComment) . "文字)";
}
// 5. 登録日時の日付型チェック
if (!$this->isValidDateFormat($createdDate)) {
$errors[] = "パラメーターNG登録日時/{$createdDate}";
}
// 6. 更新日時の日付型チェック
if (!$this->isValidDateFormat($updatedDate)) {
$errors[] = "パラメーターNG更新日時/{$updatedDate}";
}
// エラーがある場合
if (!empty($errors)) {
// 複数のエラーがある場合は全角カンマ(、)で連結
$errorMessage = implode('、', $errors);
return [
'valid' => false,
'error_message' => $errorMessage
];
}
// 正常終了
return [
'valid' => true,
'error_message' => null
];
}
/**
* 日付形式の検証
*
* yyyy/mm/dd形式かチェック
*
* @param string $date 日付文字列
* @return bool 有効な日付形式かどうか
*/
private function isValidDateFormat(string $date): bool
{
// yyyy/mm/dd形式の正規表現チェック
if (!preg_match('/^\d{4}\/\d{2}\/\d{2}$/', $date)) {
return false;
}
// 実際の日付として有効かチェック
$dateParts = explode('/', $date);
return checkdate((int)$dateParts[1], (int)$dateParts[2], (int)$dateParts[0]);
}
/**
* 【処理2】バッチ処理ログを登録する
*
* bat_job_logテーブルにINSERT
* 修正版:ステータスコメントは呼び出し元から受け取る(固定値廃止)
*
* @param int $deviceId デバイスID
* @param string|null $processName プロセス名
* @param string|null $jobName ジョブ名
* @param string $status ステータス
* @param string $statusComment ステータスコメント(業務固有)
* @param string $createdDate 登録日時 (yyyy/mm/dd形式)
* @param string $updatedDate 更新日時 (yyyy/mm/dd形式)
* @return BatJobLog 作成されたバッチジョブログ
*/
private function createBatchJobLog(
int $deviceId,
?string $processName,
?string $jobName,
string $status,
string $statusComment,
string $createdDate,
string $updatedDate
): BatJobLog {
// 日付文字列をdatetime型に変換現在時刻を使用
$createdDatetime = Carbon::createFromFormat('Y/m/d', $createdDate);
$updatedDatetime = Carbon::createFromFormat('Y/m/d', $updatedDate);
// bat_job_logテーブルに登録status_commentは呼び出し元から受け取った値を使用
$batJobLog = BatJobLog::create([
'device_id' => $deviceId,
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'status_comment' => $statusComment,
'created_at' => $createdDatetime,
'updated_at' => $updatedDatetime
]);
return $batJobLog;
}
}

View File

@ -4,20 +4,102 @@ namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Batch\BatchLog;
use App\Models\RegularContract;
use App\Models\Park;
use App\Models\Psection;
use App\Models\Ptype;
use App\Models\Setting;
use App\Models\Device;
use Carbon\Carbon;
/**
* SHJ-11 現在契約台数集計サービス
*
* 集計単位每个の契約台数を算出し、ゾーンマスタとの管理処理を実行
* bat_job_logへの書き込みはSHJ-8を呼び出す
*/
class ShjElevenService
{
/**
* ShjEightService
*
* @var ShjEightService
*/
protected $shjEightService;
/**
* コンストラクタ
*
* @param ShjEightService $shjEightService
*/
public function __construct(ShjEightService $shjEightService)
{
$this->shjEightService = $shjEightService;
}
/**
* バッチ処理用の有効なデバイスIDを取得
*
* deviceテーブルから最初の有効なdevice_idを取得する。
* データが存在しない場合は例外をスローする。
*
* @return int 有効なdevice_id
* @throws \Exception deviceテーブルにレコードが存在しない場合
*/
private function getBatchDeviceId(): int
{
$device = Device::orderBy('device_id')->first();
if (!$device) {
throw new \Exception('deviceテーブルにレコードが存在しません。SHJ-8バッチログ作成に必要なデバイスIDを取得できません。');
}
Log::debug('SHJ-11 バッチ用デバイスID取得', [
'device_id' => $device->device_id
]);
return $device->device_id;
}
/**
* 現在使用中のprice主表名を取得する
*
* setting.web_master から価格マスタテーブル名を決定
* web_master の値('_a' または '_b')から 'price_a' または 'price_b' を返す
*
* @return string price主表名'price_a' または 'price_b'
* @throws \Exception setting取得失敗時
*/
private function getPriceTableName(): string
{
try {
$setting = Setting::getSettings();
if (!$setting || empty($setting->web_master)) {
throw new \Exception('setting.web_masterの取得に失敗しました');
}
// web_master の値:'_a' または '_b'
$webMaster = $setting->web_master;
// 'price_' + ('_a' → 'a' / '_b' → 'b')
$priceTable = 'price_' . ltrim($webMaster, '_');
Log::info('SHJ-11 price主表名決定', [
'web_master' => $webMaster,
'price_table' => $priceTable
]);
return $priceTable;
} catch (\Exception $e) {
Log::error('SHJ-11 price主表名取得エラー', [
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 【処理1】集計単位每个の契約台数を算出する
*
@ -27,7 +109,7 @@ class ShjElevenService
* - regular_contract (T1)
* - park (T2)
* - psection (T4)
* - price_a (T5)
* - price_a/price_b (T5) ※web_master設定により動的に決定
* - ptype (T3)
*
* @return array 契約台数集計結果
@ -35,6 +117,9 @@ class ShjElevenService
public function calculateContractCounts(): array
{
try {
// setting.web_master から使用するprice主表を決定
$priceTable = $this->getPriceTableName();
$query = DB::table('regular_contract as T1')
->select([
'T1.park_id', // 駐輪場ID
@ -50,8 +135,8 @@ class ShjElevenService
->join('park as T2', 'T1.park_id', '=', 'T2.park_id')
// psection テーブルとの JOIN
->join('psection as T4', 'T1.psection_id', '=', 'T4.psection_id')
// price_a テーブルとの複合条件 JOIN
->join('price_a as T5', function($join) {
// price_a/price_b テーブルとの複合条件 JOIN(動的テーブル名)
->join(DB::raw($priceTable . ' 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');
@ -59,11 +144,13 @@ class ShjElevenService
// ptype テーブルとの JOIN
->join('ptype as T3', 'T5.ptype_id', '=', 'T3.ptype_id')
->where([
['T1.contract_flag', '=', 1], // 有効契約
['T2.park_close_flag', '=', 0], // 駐輪場未閉鎖
['T1.contract_flag', '=', 1], // 授受フラグ = 1
['T2.park_close_flag', '=', 0], // 閉設フラグ = 0
])
// 契約有効期間内の条件
->whereRaw("date_format(now(), '%y %m %d') BETWEEN T1.contract_periods AND T1.contract_periode")
// 契約有効期間内の条件(仕様書指定:'%y.%m.%d'形式)
->whereRaw("date_format(now(), '%y.%m.%d') BETWEEN T1.contract_periods AND T1.contract_periode")
// zone_idがNULLのレコードは除外ゾーンマスタ更新不可のため
->whereNotNull('T1.zone_id')
->groupBy([
'T1.park_id',
'T2.park_name',
@ -77,9 +164,11 @@ class ShjElevenService
Log::info('SHJ-11 契約台数算出完了', [
'count' => $query->count(),
'price_table' => $priceTable,
'sql_conditions' => [
'contract_flag' => 1,
'park_close_flag' => 0,
'date_format' => '%y.%m.%d',
'contract_period_check' => 'BETWEEN contract_periods AND contract_periode'
]
]);
@ -98,11 +187,14 @@ class ShjElevenService
/**
* 【処理2・3】ゾーンマスタ管理処理
*
* 処理フロー:
* 1. ゾーンマスタを取得する
* 2. 取得判定 存在しない場合は新規登録
* 3. 契約台数チェック(限界台数超過判定)
* 4. 契約台数を反映する(ゾーンマスタ更新)
* 処理フロー(仕様書準拠):
* 処理1の取得レコード数分繰り返し:
* 【処理2】ゾーンマスタを取得する
* 【判断2】取得判定
* - ゾーンマスタなし INSERTトランザクション内 commit 【処理4】SHJ-8(トランザクション外) 次へ
* - ゾーンマスタあり 【判断3】契約台数チェック 【処理3】UPDATEトランザクション内 commit 【処理4】SHJ-8(トランザクション外) 次へ
*
* ※SHJ-8ログは各レコード処理後に確実に記録するため、トランザクション外で実行
*
* @param array $contractCounts 契約台数集計結果
* @return array 処理結果
@ -112,27 +204,104 @@ class ShjElevenService
$createdZones = 0;
$updatedZones = 0;
$overCapacityCount = 0;
$errors = [];
$processParameters = [];
$batchLogErrors = [];
try {
DB::beginTransaction();
// 処理1の取得レコード数分繰り返し
foreach ($contractCounts as $contractData) {
try {
// 【防御的チェック】必須キーがNULLの場合はスキップ
if (empty($contractData->zone_id) || empty($contractData->psection_id) || empty($contractData->ptype_id)) {
Log::warning('SHJ-11 必須キー欠落のためスキップ', [
'park_id' => $contractData->park_id ?? null,
'zone_id' => $contractData->zone_id ?? null,
'psection_id' => $contractData->psection_id ?? null,
'ptype_id' => $contractData->ptype_id ?? null
]);
$batchLogErrors[] = [
'park_id' => $contractData->park_id ?? null,
'zone_id' => $contractData->zone_id ?? null,
'error' => '必須キー欠落zone_id/psection_id/ptype_id'
];
continue;
}
// 各レコードごとに独立したトランザクションを開始
DB::beginTransaction();
// 【処理2】ゾーンマスタを取得する
$zoneData = $this->getZoneData($contractData);
// 【判断2】取得判定
if (!$zoneData) {
// 【判断2】ゾーンマスタが存在しない場合 → 新規登録
// ゾーンマスタなし → INSERT
$createResult = $this->createZoneData($contractData);
if ($createResult['success']) {
$createdZones++;
}
$zoneData = $createResult['zone_data'];
if (!$createResult['success']) {
// INSERT失敗時のエラー処理
DB::rollBack();
Log::error('SHJ-11 ゾーンマスタINSERT失敗', [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id,
'error' => $createResult['message']
]);
$batchLogErrors[] = [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id,
'error' => 'INSERT失敗: ' . $createResult['message']
];
// INSERT失敗時も次の繰り返しへ
continue;
}
// 【判断3】契約台数チェック限界台数超過判定
// INSERTトランザクションをcommit
DB::commit();
$createdZones++;
// status_comment作成INSERT分岐
$statusComment = $this->formatStatusComment(
$contractData->park_name,
$contractData->ptype_subject,
$contractData->psection_subject,
$contractData->zone_id,
'' // INSERT分岐では台数アラートなし
);
// 【処理4】bat_job_logに直接書き込みトランザクション外・INSERT分岐
$batchLogResult = $this->writeBatJobLog(
$contractData,
$statusComment,
false // 限界台数超過なし
);
// bat_job_log書き込み結果チェック
if (!$batchLogResult['success']) {
Log::warning('bat_job_log書き込み失敗INSERT分岐', [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id,
'error_message' => $batchLogResult['error_message'] ?? null
]);
// bat_job_log書き込み失敗をbatch_log_errorsに記録
$batchLogErrors[] = [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id,
'error' => 'bat_job_log書き込み失敗INSERT分岐: ' . ($batchLogResult['error_message'] ?? 'unknown error')
];
}
// INSERT分岐は【処理4】後に次の繰り返しへ
continue;
}
// ゾーンマスタあり → 【判断3】契約台数チェック
$isOverCapacity = $this->checkCapacityLimit($contractData, $zoneData);
if ($isOverCapacity) {
$overCapacityCount++;
@ -144,57 +313,112 @@ class ShjElevenService
]);
}
// 【処理3】契約台数を反映するゾーンマスタ更新
// 【処理3】契約台数を反映するUPDATE
$updateResult = $this->updateZoneContractCount($contractData);
if ($updateResult['success']) {
$updatedZones++;
}
// 処理パラメータ記録
$processParameters[] = [
if (!$updateResult['success']) {
// UPDATE失敗時のエラー処理
DB::rollBack();
Log::error('SHJ-11 ゾーンマスタUPDATE失敗', [
'park_id' => $contractData->park_id,
'psection_id' => $contractData->psection_id,
'ptype_id' => $contractData->ptype_id,
'zone_id' => $contractData->zone_id,
'contract_count' => $contractData->cnt,
'is_over_capacity' => $isOverCapacity,
'zone_created' => !$zoneData && $createResult['success'] ?? false,
'zone_updated' => $updateResult['success'] ?? false
'error' => $updateResult['message']
]);
$batchLogErrors[] = [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id,
'error' => 'UPDATE失敗: ' . $updateResult['message']
];
// UPDATE失敗時も次の繰り返しへ
continue;
}
// UPDATEトランザクションをcommit
DB::commit();
$updatedZones++;
// status_comment作成UPDATE分岐
$capacityAlert = $isOverCapacity ? '限界収容台数を超えています。' : '';
$statusComment = $this->formatStatusComment(
$contractData->park_name,
$contractData->ptype_subject,
$contractData->psection_subject,
$contractData->zone_id,
$capacityAlert
);
// 【処理4】bat_job_logに直接書き込みトランザクション外・UPDATE分岐
$batchLogResult = $this->writeBatJobLog(
$contractData,
$statusComment,
$isOverCapacity
);
// bat_job_log書き込み結果チェック
if (!$batchLogResult['success']) {
Log::warning('bat_job_log書き込み失敗UPDATE分岐', [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id,
'error_message' => $batchLogResult['error_message'] ?? null
]);
// bat_job_log書き込み失敗をbatch_log_errorsに記録
$batchLogErrors[] = [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id,
'error' => 'bat_job_log書き込み失敗UPDATE分岐: ' . ($batchLogResult['error_message'] ?? 'unknown error')
];
}
} catch (\Exception $e) {
$errors[] = [
// 例外時はロールバック(アクティブなトランザクションがある場合のみ)
if (DB::transactionLevel() > 0) {
DB::rollBack();
}
// 個別レコードエラーもログ記録して次へ進む
$batchLogErrors[] = [
'park_id' => $contractData->park_id ?? null,
'zone_id' => $contractData->zone_id ?? null,
'error' => $e->getMessage()
];
Log::warning('SHJ-11 個別処理エラー', [
Log::warning('SHJ-11 個別レコード処理エラー', [
'park_id' => $contractData->park_id ?? null,
'zone_id' => $contractData->zone_id ?? null,
'error' => $e->getMessage()
]);
}
}
DB::commit();
// エラーでも次の繰り返しへ続行
continue;
}
}
return [
'success' => true,
'created_zones' => $createdZones,
'updated_zones' => $updatedZones,
'over_capacity_count' => $overCapacityCount,
'parameters' => $processParameters,
'errors' => $errors,
'batch_log_errors' => $batchLogErrors,
'message' => '現在契約台数集計処理完了'
];
} catch (\Exception $e) {
// 外層エラー時のロールバック
// ※各レコード処理は独立トランザクションのため、
// ここでのrollBackは不要だが安全のため実行
if (DB::transactionLevel() > 0) {
DB::rollBack();
}
Log::error('SHJ-11 ゾーンマスタ管理処理全体エラー', [
'error' => $e->getMessage(),
'processed_count' => count($processParameters)
'created_zones' => $createdZones,
'updated_zones' => $updatedZones
]);
return [
@ -202,8 +426,7 @@ class ShjElevenService
'created_zones' => $createdZones,
'updated_zones' => $updatedZones,
'over_capacity_count' => $overCapacityCount,
'parameters' => $processParameters,
'errors' => $errors,
'batch_log_errors' => $batchLogErrors,
'message' => 'ゾーンマスタ管理処理エラー: ' . $e->getMessage(),
'details' => $e->getTraceAsString()
];
@ -341,7 +564,7 @@ class ShjElevenService
$updateData = [
'zone_number' => $contractData->cnt, // 現在契約台数を更新
'updated_at' => now(), // 更新日時
'ope_id' => 'SHJ-11' // 更新オペレータID
'ope_id' => 9999999 // 更新オペレータIDINSERT時と同様、DB型int unsignedに対応
];
$updated = DB::table('zone')
@ -365,6 +588,11 @@ class ShjElevenService
'message' => 'ゾーンマスタ契約台数を更新しました'
];
} else {
Log::warning('SHJ-11 ゾーンマスタ更新対象なし', [
'zone_id' => $contractData->zone_id,
'park_id' => $contractData->park_id
]);
return [
'success' => false,
'message' => 'ゾーンマスタ更新対象が見つかりません'
@ -386,47 +614,176 @@ class ShjElevenService
}
/**
* 【処理4】バッチ処理ログを作成する
* status_commentを仕様書指定フォーマットで作成
*
* 統一BatchLogシステムを使用してSHJ-11の実行ログを記録
* フォーマット: 駐輪場名駐輪分類名車種区分名ゾーンID[台数アラート]
*
* @param string $status ステータス
* @param array $parameters パラメータ
* @param string $message メッセージ
* @param int $executionCount 実行回数
* @param int $successCount 成功回数
* @param int $errorCount エラー回数
* @return void
* @param string $parkName 駐輪場名
* @param string $ptypeSubject 駐輪分類名
* @param string $psectionSubject 車種区分名
* @param int $zoneId ゾーンID
* @param string $capacityAlert 台数アラート(「限界収容台数を超えています。」または空文字)
* @return string フォーマット済みstatus_comment
*/
public function createBatchLog(
string $status,
array $parameters,
string $message,
int $executionCount = 0,
int $successCount = 0,
int $errorCount = 0
): void {
private function formatStatusComment(
string $parkName,
string $ptypeSubject,
string $psectionSubject,
int $zoneId,
string $capacityAlert
): string {
$baseComment = $parkName . '' . $ptypeSubject . '' . $psectionSubject . '' . $zoneId;
if (!empty($capacityAlert)) {
return $baseComment . ' ' . $capacityAlert;
}
return $baseComment;
}
/**
* 【処理4】個別レコードのバッチ処理ログを作成する
*
* SHJ-11は業務固有のstatus_commentを記録するため、SHJ-8を使わずbat_job_logに直接書き込む
*
* bat_job_log登録内容:
* 1. デバイスID
* 2. プロセス名: SHJ-11
* 3. ジョブ名: SHJ-11現在契約台数集計
* 4. ステータス: success
* 5. ステータスコメント: 業務固有駐輪場名駐輪分類名車種区分名ゾーンID[台数アラート]
* 6. 登録日時: 現在の日付
* 7. 更新日時: 現在の日付
*
* @param object $contractData 契約台数集計データ
* @param string $statusComment status_comment台数アラート含む
* @param bool $isOverCapacity 限界台数超過フラグ
* @return array 処理結果 ['success' => bool, 'error_message' => string|null]
*/
private function writeBatJobLog(
$contractData,
string $statusComment,
bool $isOverCapacity
): array {
try {
BatchLog::createBatchLog(
'SHJ-11',
// SHJ-8バッチ処理ログ作成パラメータ設定
$deviceId = $this->getBatchDeviceId(); // deviceテーブルから有効なIDを取得
$processName = 'SHJ-11';
$jobName = 'SHJ-11現在契約台数集計';
$status = 'success';
$today = now()->format('Y/m/d');
Log::info('SHJ-8バッチ処理ログ作成', [
'device_id' => $deviceId,
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'status_comment' => $statusComment,
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id
]);
// SHJ-8サービスを呼び出し
$this->shjEightService->execute(
$deviceId,
$processName,
$jobName,
$status,
$parameters,
$message,
[
'execution_count' => $executionCount,
'success_count' => $successCount,
'error_count' => $errorCount,
'process_type' => '現在契約台数集計',
'executed_at' => now()->toISOString()
]
$statusComment,
$today,
$today
);
} catch (\Exception $e) {
Log::error('SHJ-11 バッチログ作成エラー', [
'error' => $e->getMessage(),
'status' => $status,
'message' => $message
Log::info('SHJ-8バッチ処理ログ作成完了', [
'park_id' => $contractData->park_id,
'zone_id' => $contractData->zone_id
]);
return [
'success' => true,
'error_message' => null
];
} catch (\Exception $e) {
Log::error('bat_job_log書き込みエラー', [
'park_id' => $contractData->park_id ?? null,
'zone_id' => $contractData->zone_id ?? null,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// エラーが発生してもメイン処理は継続
return [
'success' => false,
'error_message' => $e->getMessage()
];
}
}
/**
* 【判断1】取得件数=0時のバッチログ作成
*
* 取得件数が0件の場合に呼び出される特別なバッチログ作成
* bat_job_logに直接書き込み
*
* bat_job_log登録内容:
* 1. デバイスID
* 2. プロセス名: SHJ-11
* 3. ジョブ名: SHJ-11現在契約台数集計
* 4. ステータス: success
* 5. ステータスコメント: 全駐輪場契約なし
* 6. 登録日時: 現在の日付
* 7. 更新日時: 現在の日付
*
* @return array 処理結果 ['success' => bool, 'error_message' => string|null]
*/
public function writeBatJobLogForNoContracts(): array
{
try {
// SHJ-8バッチ処理ログ作成パラメータ設定
$deviceId = $this->getBatchDeviceId(); // deviceテーブルから有効なIDを取得
$processName = 'SHJ-11';
$jobName = 'SHJ-11現在契約台数集計';
$status = 'success';
$statusComment = '全駐輪場契約なし';
$today = now()->format('Y/m/d');
Log::info('SHJ-8バッチ処理ログ作成対象なし', [
'device_id' => $deviceId,
'process_name' => $processName,
'job_name' => $jobName,
'status' => $status,
'status_comment' => $statusComment
]);
// SHJ-8サービスを呼び出し
$this->shjEightService->execute(
$deviceId,
$processName,
$jobName,
$status,
$statusComment,
$today,
$today
);
Log::info('SHJ-8バッチ処理ログ作成完了対象なし');
return [
'success' => true,
'error_message' => null
];
} catch (\Exception $e) {
Log::error('bat_job_log書き込みエラー対象なし', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'error_message' => $e->getMessage()
];
}
}
}

View File

@ -0,0 +1,699 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\OperatorQue;
use App\Models\Device;
use Exception;
/**
* SHJ-5 空き待ち通知処理サービス
*
* 駐輪場の空き状況を確認し、空き待ち予約者への通知処理を実行する
* 仕様書に基づくバックグラウンド定期バッチ処理
*/
class ShjFiveService
{
/**
* ShjEightService
*
* @var ShjEightService
*/
protected $shjEightService;
/**
* コンストラクタ
*
* @param ShjEightService $shjEightService
*/
public function __construct(ShjEightService $shjEightService)
{
$this->shjEightService = $shjEightService;
}
/**
* 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 // 実際のキュー登録失敗件数
);
// SHJ-8 バッチ処理ログ作成
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
$this->shjEightService->execute(
$deviceId,
'SHJ-5',
'SHJ-5空き待ち通知',
'success',
$statusComment,
$today,
$today
);
Log::info('SHJ-8 バッチ処理ログ作成完了');
} catch (Exception $e) {
Log::error('SHJ-8 バッチ処理ログ作成エラー', [
'error' => $e->getMessage()
]);
}
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
);
// SHJ-7の結果を標準形式に変換result: 0=成功, 1=失敗)
$success = ($mailResult['result'] ?? 1) === 0;
Log::info('空き待ち通知メール送信試行完了', [
'user_id' => $waitingUser->user_id,
'main_email' => $mainEmail,
'sub_email' => $subEmail,
'mail_template_id' => $mailTemplateId,
'result' => $mailResult['result'] ?? 1,
'success' => $success
]);
return [
'success' => $success,
'result' => $mailResult['result'] ?? 1,
'error' => $mailResult['error_info'] ?? null,
'message' => $success ? 'メール送信成功' : ($mailResult['error_info'] ?? 'メール送信失敗')
];
} 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

@ -6,7 +6,12 @@ use App\Models\SettlementTransaction;
use App\Models\RegularContract;
use App\Models\Park;
use App\Models\PriceA;
use App\Models\Batch\BatchLog;
use App\Models\BatJobLog;
use App\Models\Device;
use App\Models\User;
use App\Services\ShjThirteenService;
use App\Services\ShjEightService;
use App\Services\ShjMailSendService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
@ -33,6 +38,34 @@ class ShjFourBService
const CONTRACT_FLAG_UPDATED = 1; // 更新済
const CONTRACT_FLAG_ERROR = 2; // エラー状態
/**
* SHJ-8 バッチ処理ログ作成サービス
*
* @var ShjEightService
*/
protected $shjEightService;
/**
* SHJ-7 メール送信サービス
*
* @var ShjMailSendService
*/
protected $mailSendService;
/**
* コンストラクタ
*
* @param ShjEightService $shjEightService
* @param ShjMailSendService $mailSendService
*/
public function __construct(
ShjEightService $shjEightService,
ShjMailSendService $mailSendService
) {
$this->shjEightService = $shjEightService;
$this->mailSendService = $mailSendService;
}
/**
* 決済トランザクション処理メイン実行
*
@ -65,12 +98,17 @@ class ShjFourBService
if (!$contractResult['found']) {
// 対象レコードなしの場合
return $this->handleNoTargetRecord($settlement, $contractResult);
$result = $this->handleNoTargetRecord($settlement, $contractResult);
$this->createBatchLog($settlement, null, null, true, $result['message']);
return $result;
}
if ($contractResult['already_processed']) {
// 登録済みの場合
return $this->handleAlreadyProcessed($settlement, $contractResult);
$result = $this->handleAlreadyProcessed($settlement, $contractResult);
$contract = $contractResult['contract'];
$this->createBatchLog($settlement, $contract, null, true, $result['message']);
return $result;
}
$contract = $contractResult['contract'];
@ -80,7 +118,9 @@ class ShjFourBService
if (!$statusResult['valid']) {
// 授受状態が異常な場合
return $this->handleInvalidStatus($settlement, $contract, $statusResult);
$result = $this->handleInvalidStatus($settlement, $contract, $statusResult);
$this->createBatchLog($settlement, $contract, null, true, $result['message']);
return $result;
}
// 【判断2】金額チェック
@ -105,6 +145,10 @@ class ShjFourBService
Log::info('SHJ-4B 決済トランザクション処理完了', $result);
// 【処理】バッチ処理ログ作成SHJ-8呼び出し
$mailCommentSuffix = $sideEffectResult['user_mail']['batch_comment_suffix'] ?? null;
$this->createBatchLog($settlement, $contract, $amountResult, true, null, $mailCommentSuffix);
return $result;
} catch (\Throwable $e) {
@ -115,6 +159,16 @@ class ShjFourBService
'trace' => $e->getTraceAsString(),
]);
// エラー時のバッチログ作成
try {
$settlement = SettlementTransaction::find($settlementTransactionId);
if ($settlement) {
$this->createBatchLog($settlement, null, null, false, $e->getMessage());
}
} catch (\Throwable $logError) {
Log::error('SHJ-4B バッチログ作成エラー', ['error' => $logError->getMessage()]);
}
throw $e;
}
}
@ -170,6 +224,7 @@ class ShjFourBService
'T1.billing_amount',
'T4.price_ptypeid as ptype_id',
'T1.psection_id',
'T1.zone_id',
'T1.update_flag',
'T1.reserve_id',
'T1.contract_payment_number',
@ -282,16 +337,17 @@ class ShjFourBService
];
}
// 条件3: batch_logで同一決済の処理完了記録があるか
$existingBatchLog = BatchLog::where('process_name', 'shj4b')
->where('status', BatchLog::STATUS_SUCCESS)
->where('parameters', 'like', '%"settlement_transaction_id":' . $settlement->settlement_transaction_id . '%')
// 条件3: bat_job_logで同一決済の処理完了記録があるか
// status_commentに決済トランザクションIDが含まれているかチェック
$existingBatchLog = BatJobLog::where('process_name', 'SHJ-4B')
->where('status', 'success')
->where('status_comment', 'like', '%settlement_transaction_id:' . $settlement->settlement_transaction_id . '%')
->exists();
if ($existingBatchLog) {
return [
'processed' => true,
'reason' => "batch_logに処理完了記録が存在",
'reason' => "bat_job_logに処理完了記録が存在",
];
}
@ -765,59 +821,162 @@ class ShjFourBService
/**
* SHJ-13実行処理(新規のみ)
*
* ShjThirteenServiceを使用した契約台数追加処理
*
* @param object $contract
* @return array
*/
private function triggerShjThirteen($contract): array
{
// TODO: SHJ-13の具体的な処理を実装
// 現在はプレースホルダー
Log::info('SHJ-4B SHJ-13実行処理', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_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 [
'triggered' => true,
'method' => 'placeholder',
'message' => 'SHJ-13処理は実装予定です',
'contract_id' => $contract->contract_id,
'result' => 1,
'error_code' => $e->getCode() ?: 1999,
'error_message' => $e->getMessage(),
'stack_trace' => $e->getTraceAsString(),
];
}
}
/**
* 利用者メール送信処理
* 【処理5】利用者メール送信処理
*
* SHJ-4B仕様準拠:
* 1. 利用者マスタよりメールアドレス、予備メールアドレスを取得
* 2. SHJ-7メール送信を呼び出し使用プログラムID: 205
* 3. 処理結果判定:
* - result = 0 (正常): バッチコメントに "メール正常終了件数1" を追加
* - その他: バッチコメントに "メール異常終了件数1、" + error_info を追加
*
* @param SettlementTransaction $settlement
* @param object $contract
* @param array $amountResult
* @return array
* @return array 処理結果 ['success' => bool, 'mail_status' => string, 'batch_comment_suffix' => string]
*/
private function sendUserNotificationMail(SettlementTransaction $settlement, $contract, array $amountResult): array
{
// TODO: 実際のメール送信処理を実装
// 現在はプレースホルダー
try {
// 【処理5】利用者マスタよりメールアドレス、予備メールアドレスを取得する
$user = User::select('user_name', 'user_primemail', 'user_submail')
->where('user_seq', $contract->user_id)
->first();
$mailType = ($amountResult['comparison'] === self::AMOUNT_MATCH) ? 'success' : 'error';
Log::info('SHJ-4B 利用者メール送信処理', [
'contract_id' => $contract->contract_id,
if (!$user) {
Log::error('SHJ-4B 利用者メール送信処理: 利用者情報取得失敗', [
'user_id' => $contract->user_id,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'mail_type' => $mailType,
'amount_comparison' => $amountResult['comparison'],
'contract_id' => $contract->contract_id,
]);
return [
'sent' => true,
'method' => 'placeholder',
'mail_type' => $mailType,
'message' => '利用者メール送信処理は実装予定です',
'success' => false,
'mail_status' => 'user_not_found',
'batch_comment_suffix' => 'メール異常終了件数1、利用者情報取得失敗'
];
}
$mailAddress = $user->user_primemail ?? '';
$backupMailAddress = $user->user_submail ?? '';
Log::info('SHJ-4B 利用者メール送信処理開始', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'user_name' => $user->user_name,
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'mail_address' => $mailAddress,
'amount_comparison' => $amountResult['comparison'],
]);
// 共通処理「SHJ-7メール送信」を呼び出し
// 使用プログラムID: 205仕様書準拠
$mailResult = $this->mailSendService->executeMailSend(
$mailAddress,
$backupMailAddress,
205 // SHJ-4B仕様: 使用プログラムID = 205
);
// SHJ-7仕様準拠: result === 0 が正常、それ以外は異常
if (($mailResult['result'] ?? 1) === 0) {
// 【正常終了】バッチコメントに "メール正常終了件数1" を設定
Log::info('SHJ-4B 利用者メール送信成功', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'mail_address' => $mailAddress,
]);
return [
'success' => true,
'mail_status' => 'sent',
'batch_comment_suffix' => 'メール正常終了件数1'
];
} else {
// 【異常終了】バッチコメントに "メール異常終了件数1、" + error_info を設定
$errorInfo = $mailResult['error_info'] ?? 'メール送信失敗';
Log::error('SHJ-4B 利用者メール送信失敗', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'mail_address' => $mailAddress,
'error_info' => $errorInfo,
]);
return [
'success' => false,
'mail_status' => 'failed',
'batch_comment_suffix' => "メール異常終了件数1、{$errorInfo}"
];
}
} catch (\Exception $e) {
Log::error('SHJ-4B 利用者メール送信処理例外エラー', [
'contract_id' => $contract->contract_id,
'user_id' => $contract->user_id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return [
'success' => false,
'mail_status' => 'exception',
'batch_comment_suffix' => 'メール異常終了件数1、システムエラー: ' . $e->getMessage()
];
}
}
/**
* オペレーターキューへの登録
*
@ -846,4 +1005,86 @@ class ShjFourBService
'message' => 'オペレーターキュー登録処理は実装予定です',
];
}
/**
* 【処理6】バッチ処理ログ作成
*
* SHJ-8サービスを呼び出してbat_job_logに記録
*
* @param SettlementTransaction $settlement
* @param object|null $contract
* @param array|null $amountResult
* @param bool $isSuccess
* @param string|null $errorMessage
* @param string|null $mailCommentSuffix メール送信結果コメント(例: "メール正常終了件数1" or "メール異常終了件数1、{error_info}"
* @return void
*/
private function createBatchLog(
SettlementTransaction $settlement,
$contract = null,
?array $amountResult = null,
bool $isSuccess = true,
?string $errorMessage = null,
?string $mailCommentSuffix = null
): void {
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
// ステータスコメント生成(内部変数.バッチコメント)
if ($errorMessage) {
// エラー時
$statusComment = "支払いステータスチェックエラー決済トランザクションID{$settlement->settlement_transaction_id} - {$errorMessage}";
} elseif ($amountResult) {
// 正常処理時
$contractId = $contract ? $contract->contract_id : 'N/A';
switch ($amountResult['comparison']) {
case self::AMOUNT_MATCH:
$statusComment = "支払いステータスチェックOK決済トランザクションID{$settlement->settlement_transaction_id}、契約ID{$contractId}、金額一致)";
break;
case self::AMOUNT_SHORTAGE:
$statusComment = "支払いステータスチェック請求金額より授受金額が少ないです決済トランザクションID{$settlement->settlement_transaction_id}、契約ID{$contractId}、差額:{$amountResult['difference']}円)";
break;
case self::AMOUNT_EXCESS:
$statusComment = "支払いステータスチェック請求金額より授受金額が多いです決済トランザクションID{$settlement->settlement_transaction_id}、契約ID{$contractId}、差額:{$amountResult['difference']}円)";
break;
default:
$statusComment = "支払いステータスチェック処理完了決済トランザクションID{$settlement->settlement_transaction_id}、契約ID{$contractId}";
}
} else {
// その他のケース(対象なし、登録済み等)
$contractId = $contract ? $contract->contract_id : 'N/A';
$statusComment = "支払いステータスチェック処理完了決済トランザクションID{$settlement->settlement_transaction_id}、契約ID{$contractId}";
}
// メール送信結果をバッチコメントに追加
if ($mailCommentSuffix) {
$statusComment .= $mailCommentSuffix;
}
// SHJ-8サービス呼び出し
$this->shjEightService->execute(
$deviceId,
'SHJ-4B',
'SHJ-4支払いステータスチェック',
'success',
$statusComment,
$today,
$today
);
Log::info('SHJ-4B バッチ処理ログ作成完了', [
'settlement_transaction_id' => $settlement->settlement_transaction_id,
'status_comment' => $statusComment,
]);
} catch (\Exception $e) {
Log::error('SHJ-4B バッチ処理ログ作成エラー', [
'error' => $e->getMessage(),
'settlement_transaction_id' => $settlement->settlement_transaction_id,
]);
}
}
}

View File

@ -4,7 +4,7 @@ namespace App\Services;
use App\Models\Park;
use App\Models\RegularContract;
use App\Models\Batch\BatchLog;
use App\Models\Device;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
@ -32,27 +32,27 @@ class ShjFourCService
protected $contractModel;
/**
* BatchLog モデル
* ShjEightService
*
* @var BatchLog
* @var ShjEightService
*/
protected $batchLogModel;
protected $shjEightService;
/**
* コンストラクタ
*
* @param Park $parkModel
* @param RegularContract $contractModel
* @param BatchLog $batchLogModel
* @param ShjEightService $shjEightService
*/
public function __construct(
Park $parkModel,
RegularContract $contractModel,
BatchLog $batchLogModel
ShjEightService $shjEightService
) {
$this->parkModel = $parkModel;
$this->contractModel = $contractModel;
$this->batchLogModel = $batchLogModel;
$this->shjEightService = $shjEightService;
}
/**
@ -71,24 +71,11 @@ class ShjFourCService
*/
public function executeRoomAllocation(int $parkId, int $ptypeId, int $psectionId): array
{
$batchLogId = null;
$statusComment = '';
$status = 'success';
try {
// バッチ処理開始ログ作成(実際のコマンド名を記録)
$batchLog = BatchLog::createBatchLog(
'shj4c',
BatchLog::STATUS_START,
[
'park_id' => $parkId,
'ptype_id' => $ptypeId,
'psection_id' => $psectionId
],
'SHJ-4C 室割当処理開始'
);
$batchLogId = $batchLog->id;
Log::info('SHJ-4C 室割当処理開始', [
'batch_log_id' => $batchLogId,
'park_id' => $parkId,
'ptype_id' => $ptypeId,
'psection_id' => $psectionId
@ -99,20 +86,16 @@ class ShjFourCService
if (empty($zoneInfo)) {
$message = '対象のゾーン情報が見つかりません';
$status = 'error';
$statusComment = sprintf('エラー: %s (park_id:%d, ptype_id:%d, psection_id:%d)',
$message, $parkId, $ptypeId, $psectionId);
// バッチログ更新(通用方法使用)
$batchLog->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => $message,
'error_details' => $message,
'error_count' => 1
]);
// バッチログ作成
$this->createBatchLog($status, $statusComment);
return [
'success' => false,
'message' => $message,
'batch_log_id' => $batchLogId
'message' => $message
];
}
@ -123,35 +106,30 @@ class ShjFourCService
// 割当NGの場合、対象事室番号を設定
$this->setTargetRoomNumber($allocationResult['target_room_number']);
// バッチログ更新(警告)
$batchLog->update([
'status' => BatchLog::STATUS_WARNING,
'end_time' => now(),
'message' => '割当処理NG: ' . $allocationResult['reason'],
'success_count' => 1 // 処理は成功したが割当NGのため
]);
$status = 'warning';
$statusComment = sprintf('割当NG: %s (park_id:%d, ptype_id:%d, psection_id:%d)',
$allocationResult['reason'], $parkId, $ptypeId, $psectionId);
// バッチログ作成
$this->createBatchLog($status, $statusComment);
return [
'success' => true,
'message' => '割当判定完了割当NG',
'allocation_result' => $allocationResult,
'batch_log_id' => $batchLogId
'allocation_result' => $allocationResult
];
}
// 【処理2】割当実行割当OKの場合
$executionResult = $this->executeAllocation($zoneInfo, $allocationResult);
// バッチ処理完了ログ更新
$batchLog->update([
'status' => BatchLog::STATUS_SUCCESS,
'end_time' => now(),
'message' => 'SHJ-4C 室割当処理正常完了',
'success_count' => 1
]);
$statusComment = sprintf('室割当処理完了 (park_id:%d, ptype_id:%d, psection_id:%d, zone_id:%s)',
$parkId, $ptypeId, $psectionId, $allocationResult['target_room_number'] ?? 'N/A');
// バッチログ作成
$this->createBatchLog($status, $statusComment);
Log::info('SHJ-4C 室割当処理完了', [
'batch_log_id' => $batchLogId,
'execution_result' => $executionResult
]);
@ -161,25 +139,19 @@ class ShjFourCService
'message' => 'SHJ-4C 室割当処理が正常に完了しました',
'zone_info' => $zoneInfo,
'allocation_result' => $allocationResult,
'execution_result' => $executionResult,
'batch_log_id' => $batchLogId
'execution_result' => $executionResult
];
} catch (\Exception $e) {
$errorMessage = 'SHJ-4C 室割当処理でエラーが発生: ' . $e->getMessage();
$status = 'error';
$statusComment = sprintf('例外エラー: %s (park_id:%d, ptype_id:%d, psection_id:%d)',
$e->getMessage(), $parkId, $ptypeId, $psectionId);
if (isset($batchLog) && $batchLog) {
$batchLog->update([
'status' => BatchLog::STATUS_ERROR,
'end_time' => now(),
'message' => $errorMessage,
'error_details' => $e->getMessage(),
'error_count' => 1
]);
}
// バッチログ作成
$this->createBatchLog($status, $statusComment);
Log::error('SHJ-4C 室割当処理エラー', [
'batch_log_id' => $batchLogId,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
@ -187,12 +159,52 @@ class ShjFourCService
return [
'success' => false,
'message' => $errorMessage,
'details' => $e->getMessage(),
'batch_log_id' => $batchLogId
'details' => $e->getMessage()
];
}
}
/**
* SHJ-8バッチ処理ログ作成
*
* @param string $status ステータス
* @param string $statusComment ステータスコメント
* @return void
*/
private function createBatchLog(string $status, string $statusComment): void
{
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
Log::info('SHJ-8バッチ処理ログ作成', [
'device_id' => $deviceId,
'process_name' => 'SHJ-4C',
'job_name' => 'SHJ-4C室割当',
'status' => $status,
'status_comment' => $statusComment
]);
// SHJ-8サービスを呼び出し
$this->shjEightService->execute(
$deviceId,
'SHJ-4C',
'SHJ-4C室割当',
$status,
$statusComment,
$today,
$today
);
} catch (\Exception $e) {
Log::error('SHJ-8 バッチログ作成エラー', [
'error' => $e->getMessage()
]);
}
}
/**
* 【処理1】ゾーン情報取得
*

View File

@ -3,7 +3,6 @@
namespace App\Services;
use App\Models\MailTemplate;
use App\Models\Batch\BatchLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
@ -23,32 +22,23 @@ class ShjMailSendService
*/
protected $mailTemplateModel;
/**
* BatchLog モデル
*
* @var BatchLog
*/
protected $batchLogModel;
/**
* コンストラクタ
*
* @param MailTemplate $mailTemplateModel
* @param BatchLog $batchLogModel
*/
public function __construct(
MailTemplate $mailTemplateModel,
BatchLog $batchLogModel
MailTemplate $mailTemplateModel
) {
$this->mailTemplateModel = $mailTemplateModel;
$this->batchLogModel = $batchLogModel;
}
/**
* SHJ メール送信処理メイン実行
* SHJ-7 メール送信処理メイン実行
*
* 処理フロー:
* 【処理1】入力パラメーターをチェックする
* 【判断1】チェック結果
* 【処理2】メール送信テンプレート情報を取得する
* 【判断2】取得結果判定
* 【処理3】メールを送信する
@ -56,29 +46,13 @@ class ShjMailSendService
*
* @param string $mailAddress メールアドレス
* @param string $backupMailAddress 予備メールアドレス
* @param int $mailTemplateId メールテンプレートID
* @return array 処理結果
* @param int $mailTemplateId メールテンプレートID使用プログラムID
* @return array 処理結果 ['result' => 0|1, 'error_info' => string]
*/
public function executeMailSend(string $mailAddress, string $backupMailAddress, int $mailTemplateId): array
{
$batchLogId = null;
try {
// バッチ処理開始ログ作成(実際のコマンド名を記録)
$batchLog = BatchLog::createBatchLog(
'shj-mail-send',
BatchLog::STATUS_START,
[
'mail_address' => $mailAddress,
'backup_mail_address' => $backupMailAddress,
'mail_template_id' => $mailTemplateId
],
'SHJ メール送信処理開始'
);
$batchLogId = $batchLog->id;
Log::info('SHJ メール送信処理開始', [
'batch_log_id' => $batchLogId,
Log::info('SHJ-7 メール送信処理開始', [
'mail_address' => $mailAddress,
'backup_mail_address' => $backupMailAddress,
'mail_template_id' => $mailTemplateId
@ -86,14 +60,19 @@ class ShjMailSendService
// 【処理1】入力パラメーターをチェックする
$paramCheckResult = $this->checkInputParameters($mailAddress, $backupMailAddress, $mailTemplateId);
if (!$paramCheckResult['valid']) {
$this->updateBatchLog($batchLogId, 'error', $paramCheckResult['message']);
// 【判断1】チェック結果
if (!$paramCheckResult['valid']) {
$errorInfo = $paramCheckResult['error_info'];
Log::warning('SHJ-7 パラメーターチェックNG', [
'error_info' => $errorInfo
]);
// 【処理4】処理結果を返却する異常終了
return [
'success' => false,
'result_code' => 1, // 異常終了
'message' => $paramCheckResult['message'],
'batch_log_id' => $batchLogId
'result' => 1,
'error_info' => $errorInfo
];
}
@ -102,14 +81,18 @@ class ShjMailSendService
// 【判断2】取得結果判定
if (empty($templateInfo)) {
$message = "メールテンプレートが存在しません。テンプレートID: {$mailTemplateId}";
$this->updateBatchLog($batchLogId, 'error', $message);
// 0件の場合取得NGの結果を設定する
$errorInfo = "メール送信NGメール送信テンプレートが存在しません。/{$mailTemplateId}";
Log::warning('SHJ-7 メールテンプレート取得NG', [
'mail_template_id' => $mailTemplateId,
'error_info' => $errorInfo
]);
// 【処理4】処理結果を返却する異常終了
return [
'success' => false,
'result_code' => 1, // 異常終了
'message' => $message,
'batch_log_id' => $batchLogId
'result' => 1,
'error_info' => $errorInfo
];
}
@ -117,52 +100,41 @@ class ShjMailSendService
$mailSendResult = $this->sendMail($mailAddress, $backupMailAddress, $templateInfo);
if (!$mailSendResult['success']) {
$this->updateBatchLog($batchLogId, 'error', $mailSendResult['message']);
$errorInfo = $mailSendResult['error_info'];
Log::error('SHJ-7 メール送信失敗', [
'error_info' => $errorInfo
]);
// 【処理4】処理結果を返却する異常終了
return [
'success' => false,
'result_code' => 1, // 異常終了
'message' => $mailSendResult['message'],
'batch_log_id' => $batchLogId
'result' => 1,
'error_info' => $errorInfo
];
}
// バッチ処理完了ログ更新
$this->updateBatchLog($batchLogId, 'success', 'SHJ メール送信処理正常完了');
Log::info('SHJ メール送信処理完了', [
'batch_log_id' => $batchLogId,
'mail_send_result' => $mailSendResult
Log::info('SHJ-7 メール送信処理完了', [
'to_address' => $mailSendResult['to_address']
]);
// 【処理4】処理結果を返却する
// 【処理4】処理結果を返却する正常終了
return [
'success' => true,
'result_code' => 0, // 正常終了
'message' => 'SHJ メール送信処理が正常に完了しました',
'mail_send_result' => $mailSendResult,
'batch_log_id' => $batchLogId
'result' => 0,
'error_info' => ''
];
} catch (\Exception $e) {
$errorMessage = 'SHJ メール送信処理でエラーが発生: ' . $e->getMessage();
$errorInfo = 'メール送信NG' . $e->getMessage();
if ($batchLogId) {
$this->updateBatchLog($batchLogId, 'error', $errorMessage);
}
Log::error('SHJ メール送信処理エラー', [
'batch_log_id' => $batchLogId,
Log::error('SHJ-7 メール送信処理例外エラー', [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
// 【処理4】処理結果を返却する異常終了
return [
'success' => false,
'result_code' => 1, // 異常終了
'message' => $errorMessage,
'details' => $e->getMessage(),
'batch_log_id' => $batchLogId
'result' => 1,
'error_info' => $errorInfo
];
}
}
@ -170,50 +142,67 @@ class ShjMailSendService
/**
* 【処理1】入力パラメーターをチェックする
*
* 仕様書に基づく詳細チェック:
* - メールアドレス形式チェック
* - テンプレートID存在性チェック
* SHJ-7仕様書に基づくチェック内容:
* 1. メールアドレス: 「メールアドレス」「予備メールアドレス」いずれか必須
* 2. 予備メールアドレス: 「メールアドレス」「予備メールアドレス」いずれか必須
* 3. 使用プログラムID: 必須
*
* エラーメッセージ形式: "パラメーターNG: 項目名/入力値"
* 複数エラーの場合は全角カンマ(、)で連結
*
* @param string $mailAddress メールアドレス
* @param string $backupMailAddress 予備メールアドレス
* @param int $mailTemplateId メールテンプレートID
* @return array チェック結果
* @param int $mailTemplateId メールテンプレートID使用プログラムID
* @return array チェック結果 ['valid' => bool, 'error_info' => string]
*/
private function checkInputParameters(string $mailAddress, string $backupMailAddress, int $mailTemplateId): array
{
$errors = [];
try {
// メールアドレス存在チェック(いずれか必須)
// 1. メールアドレスと予備メールアドレスのいずれか必須チェック
// 両方とも空の場合は、それぞれ別エラーとして追加
if (empty($mailAddress) && empty($backupMailAddress)) {
return [
'valid' => false,
'message' => 'パラメーターNG: メールアドレスまたは予備メールアドレスのいずれかは必須です'
];
}
// メールアドレス形式チェック
$errors[] = "パラメーターNGメールアドレス/<空>";
$errors[] = "パラメーターNG予備メールアドレス/<空>";
} else {
// いずれかに値がある場合は、形式チェックを実施
// 2. メールアドレス形式チェック(値がある場合のみ)
if (!empty($mailAddress) && !filter_var($mailAddress, FILTER_VALIDATE_EMAIL)) {
return [
'valid' => false,
'message' => 'パラメーターNG: メールアドレスの形式が正しくありません'
];
$errors[] = "パラメーターNGメールアドレス/{$mailAddress}";
}
// 3. 予備メールアドレス形式チェック(値がある場合のみ)
if (!empty($backupMailAddress) && !filter_var($backupMailAddress, FILTER_VALIDATE_EMAIL)) {
$errors[] = "パラメーターNG予備メールアドレス/{$backupMailAddress}";
}
}
// 4. 使用プログラムIDメールテンプレートID必須チェック
if (empty($mailTemplateId) || $mailTemplateId <= 0) {
$errors[] = "パラメーターNG使用プログラムID/{$mailTemplateId}";
}
// エラーがある場合
if (!empty($errors)) {
// 複数のエラーがある場合は全角カンマ(、)で連結
$errorInfo = implode('、', $errors);
Log::warning('SHJ-7 入力パラメーターチェックNG', [
'mail_address' => $mailAddress,
'backup_mail_address' => $backupMailAddress,
'mail_template_id' => $mailTemplateId,
'error_info' => $errorInfo
]);
return [
'valid' => false,
'message' => 'パラメーターNG: 予備メールアドレスの形式が正しくありません'
'error_info' => $errorInfo
];
}
// メールテンプレートID形式チェック
if ($mailTemplateId <= 0) {
return [
'valid' => false,
'message' => 'パラメーターNG: メールテンプレートIDは正の整数である必要があります'
];
}
Log::info('入力パラメーターチェック完了', [
// 正常終了
Log::info('SHJ-7 入力パラメーターチェックOK', [
'mail_address' => $mailAddress,
'backup_mail_address' => $backupMailAddress,
'mail_template_id' => $mailTemplateId
@ -221,17 +210,20 @@ class ShjMailSendService
return [
'valid' => true,
'message' => 'パラメーターチェックOK'
'error_info' => ''
];
} catch (\Exception $e) {
Log::error('入力パラメーターチェックエラー', [
'error' => $e->getMessage()
$errorInfo = 'パラメーターチェック中にエラーが発生:' . $e->getMessage();
Log::error('SHJ-7 入力パラメーターチェック例外エラー', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'valid' => false,
'message' => 'パラメーターチェック中にエラーが発生しました: ' . $e->getMessage()
'error_info' => $errorInfo
];
}
}
@ -239,45 +231,45 @@ class ShjMailSendService
/**
* 【処理2】メール送信テンプレート情報を取得する
*
* 仕様書に基づくSQLクエリ:
* SHJ-7仕様書に基づくSQLクエリ:
* SELECT エリアマネージャー同報, bccアドレス, 件名, 本文
* FROM メール送信テンプレート
* WHERE 使用プログラムID = 入力パラメーター使用プログラムID
* WHERE 使用プログラムID = 入力パラメーター.使用プログラムID
* AND 使用フラグ = 1
*
* @param int $mailTemplateId メールテンプレートID使用プログラムIDとして扱う
* @param int $mailTemplateId メールテンプレートID使用プログラムID
* @return MailTemplate|null メールテンプレート情報
*/
private function getMailTemplateInfo(int $mailTemplateId): ?MailTemplate
{
try {
// 仕様書に記載されたSQLクエリに基づくメールテンプレート情報取得
// 注意: 仕様書では「使用プログラムID」を条件にしているが、
// 入力パラメーターは「メールテンプレートID」なので、pg_idで検索
// SHJ-7仕様: 使用プログラムIDpg_idで検索、使用フラグ=1
$templateInfo = $this->mailTemplateModel::where('pg_id', $mailTemplateId)
->where('use_flag', 1)
->first();
if ($templateInfo) {
Log::info('メールテンプレート情報取得完了', [
Log::info('SHJ-7 メールテンプレート情報取得完了', [
'mail_template_id' => $mailTemplateId,
'template_found' => true,
'subject' => $templateInfo->getSubject()
'subject' => $templateInfo->getSubject(),
'mgr_cc_flag' => $templateInfo->isManagerCcEnabled(),
'has_bcc' => !empty($templateInfo->getBccAddress())
]);
} else {
Log::warning('メールテンプレート情報取得結果', [
Log::warning('SHJ-7 メールテンプレート情報取得結果0件', [
'mail_template_id' => $mailTemplateId,
'template_found' => false,
'message' => 'テンプレートが見つかりません'
'template_found' => false
]);
}
return $templateInfo;
} catch (\Exception $e) {
Log::error('メールテンプレート情報取得エラー', [
Log::error('SHJ-7 メールテンプレート情報取得エラー', [
'mail_template_id' => $mailTemplateId,
'error' => $e->getMessage()
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
@ -287,16 +279,18 @@ class ShjMailSendService
/**
* 【処理3】メールを送信する
*
* 仕様書に基づくmb_send_mail関数使用:
* - 送信者: 処理2で取得したメールアドレス処理1で予備メールアドレス
* - タイトル: 処理2で取得したタイトル
* - 本文: 処理2で取得した本文※現在の文字列は「So-Manager一般的なWebサイト内部処理」参照
* - 追加ヘッダ: 処理2で取得したbccアドレス※値が設定されている場合のみ
* SHJ-7仕様書に基づくmb_send_mail関数使用:
* - 送信先: 入力パラメーター.メールアドレス(空なら予備メールアドレス)
* - タイトル: 処理2.件名
* - 本文: 処理2.本文
* - 追加ヘッダ: 処理2.bccアドレス※値が設定されている場合のみ
*
* ※待ち時間: 仕様では「一定の待ち時間を設ける」とあるが、現時点では実装しない0秒
*
* @param string $mailAddress メールアドレス
* @param string $backupMailAddress 予備メールアドレス
* @param MailTemplate $templateInfo テンプレート情報
* @return array 送信結果
* @return array 送信結果 ['success' => bool, 'error_info' => string, 'to_address' => string]
*/
private function sendMail(string $mailAddress, string $backupMailAddress, MailTemplate $templateInfo): array
{
@ -304,64 +298,64 @@ class ShjMailSendService
// 送信先アドレス決定(優先: メールアドレス、代替: 予備メールアドレス)
$toAddress = !empty($mailAddress) ? $mailAddress : $backupMailAddress;
// メール内容取得
// 処理2で取得したメール内容取得
$subject = $templateInfo->getSubject() ?? '';
$message = $templateInfo->getText() ?? '';
// 追加ヘッダ設定
// 追加ヘッダ設定BCC、From等
$headers = $this->buildMailHeaders($templateInfo);
Log::info('メール送信準備完了', [
Log::info('SHJ-7 メール送信準備完了', [
'to_address' => $toAddress,
'subject' => $subject,
'has_bcc' => !empty($templateInfo->getBccAddress()),
'manager_cc_enabled' => $templateInfo->isManagerCcEnabled()
'mgr_cc_flag' => $templateInfo->isManagerCcEnabled()
]);
// mb_send_mail関数を使用してメール送信
$sendResult = mb_send_mail(
$toAddress, // 送信先
$subject, // 件名
$subject, // 件名(タイトル)
$message, // 本文
$headers // 追加ヘッダ
);
if ($sendResult) {
Log::info('メール送信成功', [
Log::info('SHJ-7 メール送信成功', [
'to_address' => $toAddress,
'subject' => $subject
]);
return [
'success' => true,
'message' => 'メール送信が正常に完了しました',
'to_address' => $toAddress,
'subject' => $subject
'error_info' => '',
'to_address' => $toAddress
];
} else {
$errorMessage = 'mb_send_mail関数でメール送信に失敗しました';
Log::error('メール送信失敗', [
// mb_send_mail がfalseを返した場合
$errorInfo = 'メール送信NGmb_send_mail関数がfalseを返しました';
Log::error('SHJ-7 メール送信失敗', [
'to_address' => $toAddress,
'subject' => $subject,
'error' => $errorMessage
'error_info' => $errorInfo
]);
return [
'success' => false,
'message' => $errorMessage
'error_info' => $errorInfo
];
}
} catch (\Exception $e) {
$errorMessage = 'メール送信中にエラーが発生: ' . $e->getMessage();
Log::error('メール送信エラー', [
$errorInfo = 'メール送信NG' . $e->getMessage();
Log::error('SHJ-7 メール送信例外エラー', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'success' => false,
'message' => $errorMessage
'error_info' => $errorInfo
];
}
}
@ -369,9 +363,10 @@ class ShjMailSendService
/**
* メールヘッダを構築
*
* 仕様書に基づく設定:
* - BCCアドレス: 値が設定されている場合のみ追加
* - エリアマネージャー同報: フラグが有効な場合の処理
* SHJ-7仕様書に基づく追加ヘッダ設定:
* - 追加ヘッダ: 処理2.bccアドレス※値が設定されている場合のみ
* - From: システム設定から取得
* - エリアマネージャー同報: フラグが有効な場合はログ出力のみ(実装保留)
*
* @param MailTemplate $templateInfo テンプレート情報
* @return string メールヘッダ
@ -380,19 +375,19 @@ class ShjMailSendService
{
$headers = [];
// BCCアドレス設定値が設定されている場合のみ
// BCCアドレス設定値が設定されている場合のみ追加
$bccAddress = $templateInfo->getBccAddress();
if (!empty($bccAddress)) {
$headers[] = "Bcc: {$bccAddress}";
}
// エリアマネージャー同報フラグが有効な場合
// ※具体的な処理内容は仕様書に詳細がないため、ログ出力のみ
// ※SHJ-7では実装しない仕様詳細不明。ログ出力のみ。
if ($templateInfo->isManagerCcEnabled()) {
Log::info('エリアマネージャー同報フラグが有効です', [
Log::info('SHJ-7 エリアマネージャー同報フラグが有効', [
'mail_template_id' => $templateInfo->mail_template_id
]);
// 実際のエリアマネージャーアドレス取得・設定処理は追加仕様が必要
// 実際のエリアマネージャーアドレス取得・設定処理は将来実装
}
// From設定システム設定から取得
@ -405,41 +400,6 @@ class ShjMailSendService
return implode("\r\n", $headers);
}
/**
* バッチログ作成
*
* @param string $processName プロセス名
* @param string $status ステータス
* @param array $params パラメータ
* @return int バッチログID
*/
private function createBatchLog(string $processName, string $status, array $params = []): int
{
return $this->batchLogModel->create([
'process_name' => $processName,
'status' => $status,
'start_time' => now(),
'parameters' => json_encode($params),
'message' => 'バッチ処理開始'
])->id;
}
/**
* バッチログ更新
*
* @param int $batchLogId バッチログID
* @param string $status ステータス
* @param string $message メッセージ
* @return void
*/
private function updateBatchLog(int $batchLogId, string $status, string $message): void
{
$this->batchLogModel->where('id', $batchLogId)->update([
'status' => $status,
'end_time' => now(),
'message' => $message
]);
}
/**
* SHJ-12 未払い者通知メール送信

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,11 @@ use App\Models\Usertype;
use App\Models\Park;
use App\Models\RegularContract;
use App\Models\OperatorQue;
use App\Models\Device;
use App\Services\GoogleVisionService;
use App\Services\GoogleMapsService;
use App\Services\ShjMailSendService;
use App\Services\ShjEightService;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
@ -22,15 +24,18 @@ class ShjOneService
protected $googleVisionService;
protected $googleMapsService;
protected $mailSendService;
protected $shjEightService;
public function __construct(
GoogleVisionService $googleVisionService,
GoogleMapsService $googleMapsService,
ShjMailSendService $mailSendService
ShjMailSendService $mailSendService,
ShjEightService $shjEightService
) {
$this->googleVisionService = $googleVisionService;
$this->googleMapsService = $googleMapsService;
$this->mailSendService = $mailSendService;
$this->shjEightService = $shjEightService;
}
/**
@ -55,11 +60,13 @@ class ShjOneService
'user_id' => $userId,
'park_id' => $parkId
]);
return [
$result = [
'system_success' => false,
'message' => '利用者が見つかりません',
'stats' => ['error_count' => 1, 'processed_count' => 0]
];
$this->createBatchLog($userId, null, null, $result);
return $result;
}
Log::info('SHJ-1 【処理1】成功: 利用者データ取得', [
@ -79,11 +86,13 @@ class ShjOneService
'user_idcard_chk_flag' => $user->user_idcard_chk_flag,
'reason' => '免許証以外または写真なしまたは既に処理済み'
]);
return [
$result = [
'system_success' => false,
'message' => '処理対象外の利用者です(免許証以外または写真なし)',
'stats' => ['error_count' => 1, 'processed_count' => 0]
];
$this->createBatchLog($userId, $user->user_name, null, $result);
return $result;
}
$park = $this->getParkRecord($parkId);
@ -91,11 +100,13 @@ class ShjOneService
Log::error('SHJ-1 駐輪場データ取得失敗', [
'park_id' => $parkId
]);
return [
$result = [
'system_success' => false,
'message' => '駐輪場が見つかりません',
'stats' => ['error_count' => 1, 'processed_count' => 0]
];
$this->createBatchLog($userId, $user->user_name, null, $result);
return $result;
}
Log::info('SHJ-1 駐輪場データ取得成功', [
@ -119,6 +130,7 @@ class ShjOneService
]);
$result = $this->processNonTargetUser($user, $categoryCheck['category_name']);
DB::commit(); // 対象外処理後はコミット
$this->createBatchLog($userId, $user->user_name, null, $result);
return $result;
}
@ -133,6 +145,9 @@ class ShjOneService
DB::commit();
// バッチ処理ログ作成
$this->createBatchLog($userId, $user->user_name, $identityResult['similarity_rate'] ?? null, $identityResult);
return $identityResult;
} catch (Exception $e) {
@ -143,11 +158,16 @@ class ShjOneService
'error' => $e->getMessage()
]);
return [
$result = [
'system_success' => false,
'message' => 'システムエラーが発生しました: ' . $e->getMessage(),
'stats' => ['error_count' => 1, 'processed_count' => 0]
];
// エラー時もバッチログ作成
$this->createBatchLog($userId, null, null, $result);
return $result;
}
}
@ -803,6 +823,7 @@ class ShjOneService
'system_success' => true,
'identity_result' => 'OK',
'message' => '本人確認自動処理が成功しました',
'similarity_rate' => $ocrResult['similarity'] ?? 0,
'details' => [
'user_id' => $user->user_seq,
'park_id' => $park->park_id,
@ -856,6 +877,7 @@ class ShjOneService
'system_success' => true,
'identity_result' => 'NG',
'message' => '本人確認自動処理NGのため手動処理キューを作成しました',
'similarity_rate' => $ocrResult['similarity'] ?? 0,
'details' => [
'user_id' => $user->user_seq,
'park_id' => $park->park_id,
@ -914,13 +936,29 @@ class ShjOneService
private function sendSuccessEmail(User $user, Park $park): void
{
try {
$this->mailSendService->executeMailSend(
// SHJ-7 メール送信処理
$mailResult = $this->mailSendService->executeMailSend(
$user->user_primemail,
$user->user_submail,
config('shj1.mail.program_id_success')
);
// SHJ-7仕様準拠: result === 0 が正常、それ以外は異常
if (($mailResult['result'] ?? 1) === 0) {
Log::info('SHJ-1 成功メール送信成功', [
'user_id' => $user->user_seq,
'email' => $user->user_primemail
]);
} else {
// 仕様準拠: error_info をログに記録
Log::error('SHJ-1 成功メール送信失敗', [
'user_id' => $user->user_seq,
'email' => $user->user_primemail,
'error_info' => $mailResult['error_info'] ?? 'メール送信失敗'
]);
}
} catch (Exception $e) {
Log::error('Success email sending failed', [
Log::error('SHJ-1 成功メール送信例外エラー', [
'user_id' => $user->user_seq,
'error' => $e->getMessage()
]);
@ -933,13 +971,29 @@ class ShjOneService
private function sendFailureEmail(User $user, Park $park): void
{
try {
$this->mailSendService->executeMailSend(
// SHJ-7 メール送信処理
$mailResult = $this->mailSendService->executeMailSend(
$user->user_primemail,
$user->user_submail,
config('shj1.mail.program_id_failure')
);
// SHJ-7仕様準拠: result === 0 が正常、それ以外は異常
if (($mailResult['result'] ?? 1) === 0) {
Log::info('SHJ-1 失敗メール送信成功', [
'user_id' => $user->user_seq,
'email' => $user->user_primemail
]);
} else {
// 仕様準拠: error_info をログに記録
Log::error('SHJ-1 失敗メール送信失敗', [
'user_id' => $user->user_seq,
'email' => $user->user_primemail,
'error_info' => $mailResult['error_info'] ?? 'メール送信失敗'
]);
}
} catch (Exception $e) {
Log::error('Failure email sending failed', [
Log::error('SHJ-1 失敗メール送信例外エラー', [
'user_id' => $user->user_seq,
'error' => $e->getMessage()
]);
@ -992,4 +1046,57 @@ class ShjOneService
return $analysis;
}
/**
* バッチ処理ログ作成
*
* SHJ-8サービスを呼び出してbat_job_logテーブルに登録
*
* @param int $userId 利用者連番
* @param string|null $userName 利用者名
* @param float|null $similarityRate 類似度
* @param array $result 処理結果
* @return void
*/
private function createBatchLog(int $userId, ?string $userName, ?float $similarityRate, array $result): void
{
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
// ステータス判定
$status = $result['system_success'] ? 'success' : 'error';
// ステータスコメント生成: {user_id}/{user_name}/{similarity_rate}%
$displayUserId = $userId;
$displayUserName = $userName ?? '不明';
$displaySimilarity = $similarityRate !== null ? number_format($similarityRate, 1) : '0.0';
$statusComment = "{$displayUserId}/{$displayUserName}/{$displaySimilarity}%";
// SHJ-8サービス呼び出し
$this->shjEightService->execute(
$deviceId,
'SHJ-1',
'SHJ-1本人確認自動処理',
$status,
$statusComment,
$today,
$today
);
Log::info('SHJ-1 バッチ処理ログ作成完了', [
'user_id' => $userId,
'status' => $status,
'status_comment' => $statusComment
]);
} catch (Exception $e) {
Log::error('SHJ-1 バッチ処理ログ作成エラー', [
'error' => $e->getMessage(),
'user_id' => $userId
]);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,385 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Device;
use Carbon\Carbon;
/**
* SHJ-13 契約台数追加処理サービス
*
* 新規契約時の契約台数を park_number・zone テーブルに反映する処理
* SHJ-4B の副作用処理として実行される
*/
class ShjThirteenService
{
/**
* ShjEightService
*
* @var ShjEightService
*/
protected $shjEightService;
/**
* コンストラクタ
*
* @param ShjEightService $shjEightService
*/
public function __construct(ShjEightService $shjEightService)
{
$this->shjEightService = $shjEightService;
}
/**
* 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(),
]);
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(),
]);
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);
// バッチ処理ログ作成(同一トランザクション内)
// SHJ-8サービスを呼び出し
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
$this->shjEightService->execute(
$deviceId,
'SHJ-13',
'SHJ-13契約台数追加',
'success',
$statusComment,
$today,
$today
);
Log::info('SHJ-13 契約台数更新・ログ作成完了', [
'contract_id' => $contractData['contract_id'],
'park_number_updated' => $parkNumberUpdated,
'zone_updated' => $zoneUpdated,
'updated_count' => $updatedZone->zone_number,
'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
);
}
/**
* エラー結果作成SHJ-8ログも作成)
*
* @param int $errorCode
* @param string $errorMessage
* @param string $stackTrace
* @return array
*/
private function createErrorResult(int $errorCode, string $errorMessage, string $stackTrace = ''): array
{
// エラー時もSHJ-8でバッチログを作成
try {
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
$today = now()->format('Y/m/d');
$statusComment = sprintf(
'エラーコード:%dエラーメッセージ%s',
$errorCode,
$errorMessage
);
$this->shjEightService->execute(
$deviceId,
'SHJ-13',
'SHJ-13契約台数追加',
'success', // 仕様書:エラー時も"success"で記録
$statusComment,
$today,
$today
);
Log::info('SHJ-13 エラー時バッチログ作成完了', [
'error_code' => $errorCode,
'status_comment' => $statusComment
]);
} catch (\Exception $e) {
Log::error('SHJ-13 エラー時バッチログ作成失敗', [
'error' => $e->getMessage()
]);
}
return [
'result' => 1,
'error_code' => $errorCode,
'error_message' => $errorMessage,
'stack_trace' => $stackTrace,
];
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Models\Batch\BatchLog;
use App\Models\Device;
use App\Models\RegularContract;
use App\Models\User;
use App\Models\Park;
@ -25,14 +25,25 @@ class ShjTwelveService
*/
protected $shjMailSendService;
/**
* ShjEightService
*
* @var ShjEightService
*/
protected $shjEightService;
/**
* コンストラクタ
*
* @param ShjMailSendService $shjMailSendService
* @param ShjEightService $shjEightService
*/
public function __construct(ShjMailSendService $shjMailSendService)
{
public function __construct(
ShjMailSendService $shjMailSendService,
ShjEightService $shjEightService
) {
$this->shjMailSendService = $shjMailSendService;
$this->shjEightService = $shjEightService;
}
/**
@ -53,16 +64,14 @@ class ShjTwelveService
'T1.contract_id', // 定期契約ID
'T2.user_seq', // 利用者ID
'T2.user_name', // 利用者名
'T2.user_manual_flag', // 手動登録フラグ
'T2.user_mail', // メールアドレス
'T2.user_mail_sub', // 予備メールアドレス
'T2.park_id', // 駐輪場ID (userテーブルから)
'T2.user_manual_regist_flag', // 手動登録フラグ
'T2.user_primemail', // メールアドレス
'T2.user_submail', // 予備メールアドレス
'T1.park_id', // 駐輪場ID (regular_contractテーブルから)
'T3.park_name', // 駐輪場名
'T1.billing_amount' // 請求金額
])
->join('user as T2', function($join) {
$join->on('T1.user_seq', '=', 'T2.user_seq');
})
->join('user as T2', 'T1.user_id', '=', 'T2.user_seq')
->join('park as T3', 'T1.park_id', '=', 'T3.park_id')
->where([
['T1.contract_cancel_flag', '=', 0], // 解約フラグ = 0
@ -94,35 +103,45 @@ class ShjTwelveService
/**
* 【処理2】未払い者への通知、またはオペレーターキュー追加処理
*
* 各未払い者に対して以下の処理を実行:
* 1. メール通知の実行 (SHJ-7連携)
* 2. オペレーターキューへの追加 (opeテーブル)
* 仕様:
* - 手動登録フラグ = 0 (Web申込) SHJ-7メール送信のみ
* - 手動登録フラグ 0 (その他) オペレーターキュー追加のみ
*
* @param array $unpaidUsers 未払い者リスト
* @return array 処理結果
*/
public function processUnpaidUserNotifications(array $unpaidUsers): array
{
$notificationCount = 0;
$queueCount = 0;
$errors = [];
// 内部変数(カウンタ)初期化
$mailSuccessCount = 0;
$mailErrorCount = 0;
$queueSuccessCount = 0;
$queueErrorCount = 0;
$batchComments = []; // バッチコメント(エラー情報)累積用
$processParameters = [];
try {
DB::beginTransaction();
// 処理1の取得レコード数分繰り返し
foreach ($unpaidUsers as $user) {
try {
// メール通知処理
$mailResult = $this->sendNotificationMail($user);
if ($mailResult['success']) {
$notificationCount++;
}
// 【判定】手動登録フラグによる分岐
if ($user->user_manual_regist_flag == 0) {
// Web申込 → SHJ-7メール送信
$mailResult = $this->sendNotificationMailViaShj7($user);
// オペレーターキュー追加処理
$queueResult = $this->addToOperatorQueue($user);
if ($queueResult['success']) {
$queueCount++;
// 処理結果判定: result === 0 → 正常
if (($mailResult['result'] ?? 1) === 0) {
$mailSuccessCount++;
} else {
$mailErrorCount++;
// バッチコメントにSHJ-7異常情報を設定後ろに足す
$batchComments[] = sprintf(
'SHJ-7メール送信: 契約ID:%s メール送信失敗: %s',
$user->contract_id,
$mailResult['error_info'] ?? 'Unknown error'
);
}
// 処理パラメータ記録
@ -130,15 +149,43 @@ class ShjTwelveService
'contract_id' => $user->contract_id,
'user_seq' => $user->user_seq,
'billing_amount' => $user->billing_amount,
'mail_sent' => $mailResult['success'],
'queue_added' => $queueResult['success']
'process_type' => 'mail',
'result' => ($mailResult['result'] ?? 1) === 0 ? 'success' : 'error'
];
} catch (\Exception $e) {
$errors[] = [
} else {
// その他(手動登録)→ オペレーターキュー追加
$queueResult = $this->addToOperatorQueue($user);
if ($queueResult['success']) {
$queueSuccessCount++;
} else {
$queueErrorCount++;
// バッチコメントに異常情報を設定(後ろに足す)
$batchComments[] = sprintf(
'キュー登録: 契約ID:%s キュー登録失敗: %s',
$user->contract_id,
$queueResult['message'] ?? 'Unknown error'
);
}
// 処理パラメータ記録
$processParameters[] = [
'contract_id' => $user->contract_id,
'error' => $e->getMessage()
'user_seq' => $user->user_seq,
'billing_amount' => $user->billing_amount,
'process_type' => 'queue',
'result' => $queueResult['success'] ? 'success' : 'error'
];
}
} catch (\Exception $e) {
// 個別エラーを記録して次の繰り返し処理へ
$batchComments[] = sprintf(
'契約ID:%s 処理エラー: %s',
$user->contract_id,
$e->getMessage()
);
Log::warning('SHJ-12 個別処理エラー', [
'contract_id' => $user->contract_id,
@ -150,12 +197,26 @@ class ShjTwelveService
DB::commit();
// ステータスコメント構築
$statusComment = $this->buildStatusComment(
$mailSuccessCount,
$mailErrorCount,
$queueSuccessCount,
$queueErrorCount
);
// バッチコメント整形最大100件、超過分は省略
$formattedBatchComments = $this->formatBatchComments($batchComments);
return [
'success' => true,
'notification_count' => $notificationCount,
'queue_count' => $queueCount,
'mail_success_count' => $mailSuccessCount,
'mail_error_count' => $mailErrorCount,
'queue_success_count' => $queueSuccessCount,
'queue_error_count' => $queueErrorCount,
'status_comment' => $statusComment,
'batch_comments' => $formattedBatchComments,
'parameters' => $processParameters,
'errors' => $errors,
'message' => '未払い者通知処理完了'
];
@ -169,10 +230,13 @@ class ShjTwelveService
return [
'success' => false,
'notification_count' => $notificationCount,
'queue_count' => $queueCount,
'mail_success_count' => $mailSuccessCount,
'mail_error_count' => $mailErrorCount,
'queue_success_count' => $queueSuccessCount,
'queue_error_count' => $queueErrorCount,
'status_comment' => 'システムエラー',
'batch_comments' => $batchComments,
'parameters' => $processParameters,
'errors' => $errors,
'message' => '通知処理エラー: ' . $e->getMessage(),
'details' => $e->getTraceAsString()
];
@ -180,54 +244,44 @@ class ShjTwelveService
}
/**
* 未払い者へのメール通知送信
* SHJ-7 メール送信サービス経由でメール通知送信
*
* SHJ-7 メール送信サービスを使用してメール通知を実行
* 仕様書パラメータ:
* - メールアドレス: 処理1.メールアドレス
* - 予備メールアドレス: 処理1.予備メールアドレス
* - 使用プログラムID: 204
*
* @param object $user 未払い者情報
* @return array 送信結果
* @return array SHJ-7の処理結果 (result_code: 0=正常, 1=異常)
*/
private function sendNotificationMail($user): array
private function sendNotificationMailViaShj7($user): array
{
try {
// メールアドレスの確認
$emailAddress = $user->user_mail ?: $user->user_mail_sub;
// SHJ-7 executeMailSend 呼び出し
$result = $this->shjMailSendService->executeMailSend(
(string)($user->user_primemail ?? ''), // メールアドレス
(string)($user->user_submail ?? ''), // 予備メールアドレス
204 // 使用プログラムID
);
if (empty($emailAddress)) {
return [
'success' => false,
'message' => 'メールアドレスが設定されていません'
];
}
// SHJ-7 メール送信サービス呼び出し
$mailParams = [
'to_email' => $emailAddress,
'user_name' => $user->user_name,
'park_name' => $user->park_name,
'billing_amount' => $user->billing_amount,
'contract_id' => $user->contract_id
];
$result = $this->shjMailSendService->sendUnpaidNotificationMail($mailParams);
Log::info('SHJ-12 メール送信結果', [
Log::info('SHJ-12 SHJ-7メール送信結果', [
'contract_id' => $user->contract_id,
'email' => $emailAddress,
'success' => $result['success']
'user_seq' => $user->user_seq,
'result' => $result['result'] ?? 1,
'error_info' => $result['error_info'] ?? ''
]);
return $result;
} catch (\Exception $e) {
Log::error('SHJ-12 メール送信エラー', [
Log::error('SHJ-12 SHJ-7メール送信エラー', [
'contract_id' => $user->contract_id,
'error' => $e->getMessage()
]);
return [
'success' => false,
'message' => 'メール送信エラー: ' . $e->getMessage()
'result' => 1, // 異常終了
'error_info' => 'SHJ-7メール送信エラー: ' . $e->getMessage()
];
}
}
@ -235,63 +289,179 @@ class ShjTwelveService
/**
* オペレーターキューへの追加
*
* opeテーブルにオペレーター処理キューとして登録
* 仕様書SQL定義に基づき operator_que テーブルに登録
* - キュー種別ID: 8 (支払い催促)
* - キューステータスID: 1 (キュー発生)
* - que_id: MAX+1方式で生成db_now.sqlは非AUTO_INCREMENT
* - 主キー衝突時に1回リトライ
*
* @param object $user 未払い者情報
* @return array 追加結果
*/
private function addToOperatorQueue($user): array
{
$maxRetries = 2; // 最大2回試行初回 + リトライ1回
$lastException = null;
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
// オペレーターキューデータ構築
// 注: que_idはAUTO_INCREMENTのため、DBに委任手動採番しない
$queueData = [
'ope_device_id' => null, // デバイスID (未設定)
'ope_process_name' => 'SHJ-12', // プロセス名
'ope_job_name' => '未払い者通知', // ジョブ名
'ope_status' => 'pending', // ステータス
'ope_comment' => sprintf(
'契約ID:%s ユーザー:%s 金額:%s円',
$user->contract_id,
$user->user_name,
number_format($user->billing_amount)
),
'ope_target_user_id' => $user->user_seq, // 対象ユーザーID
'ope_target_contract_id' => $user->contract_id, // 対象契約ID
'ope_billing_amount' => $user->billing_amount, // 請求金額
'created_at' => now(),
'updated_at' => now()
// 'que_id' は削除AUTO_INCREMENTに委任
'que_class' => 8, // キュー種別ID: 8=支払い催促
'user_id' => $user->user_seq, // 利用者ID
'contract_id' => $user->contract_id, // 定期契約ID
'park_id' => $user->park_id, // 駐輪場ID
'que_comment' => '', // キューコメント (空文字)
'que_status' => 1, // キューステータスID: 1=キュー発生
'que_status_comment' => '', // キューステータスコメント (空文字)
'work_instructions' => '', // 業務指示コメント (空文字)
'created_at' => now(), // 登録日時
'updated_at' => now(), // 更新日時
'operator_id' => null // 更新オペレータID (NULL: システム自動)
];
// opeテーブルに挿入
DB::table('ope')->insert($queueData);
// operator_queテーブルに挿入que_idはDBが自動採番
$newQueId = DB::table('operator_que')->insertGetId($queueData);
Log::info('SHJ-12 オペレーターキュー追加完了', [
'que_id' => $newQueId,
'contract_id' => $user->contract_id,
'user_seq' => $user->user_seq,
'billing_amount' => $user->billing_amount
'park_id' => $user->park_id,
'que_class' => 8,
'que_status' => 1,
'attempt' => $attempt
]);
return [
'success' => true,
'message' => 'オペレーターキューに追加しました'
'message' => 'オペレーターキュー追加成功',
'que_id' => $newQueId
];
} catch (\Exception $e) {
Log::error('SHJ-12 オペレーターキュー追加エラー', [
} catch (\Illuminate\Database\QueryException $e) {
$lastException = $e;
// 主キー重複エラーDuplicate entryの場合のみリトライ
// SQLSTATEコード 23000 は整合性制約違反を示す
if ($e->getCode() == 23000 && $attempt < $maxRetries) {
Log::warning('SHJ-12 que_id重複検出、リトライします', [
'attempt' => $attempt,
'max_retries' => $maxRetries,
'que_id' => $newQueId ?? null,
'contract_id' => $user->contract_id,
'error' => $e->getMessage()
'error_code' => $e->getCode()
]);
// 短時間待機後に再試行100ms
usleep(100000);
continue;
}
// その他のエラー、または最終試行での失敗時は例外をスロー
throw $e;
} catch (\Exception $e) {
// QueryException以外の例外は即座にスロー
$lastException = $e;
throw $e;
}
}
// ループを抜けた場合(通常は到達しない)
Log::error('SHJ-12 オペレーターキュー追加エラー(最大リトライ回数超過)', [
'contract_id' => $user->contract_id,
'user_seq' => $user->user_seq,
'max_retries' => $maxRetries,
'error' => $lastException ? $lastException->getMessage() : 'Unknown error',
'trace' => $lastException ? $lastException->getTraceAsString() : ''
]);
return [
'success' => false,
'message' => 'オペレーターキュー追加エラー: ' . $e->getMessage()
'message' => 'オペレーターキュー追加エラー(リトライ失敗): ' .
($lastException ? $lastException->getMessage() : 'Unknown error')
];
}
/**
* ステータスコメント構築
*
* 仕様書に基づくステータスコメント形式:
* - エラーなし: "メール送信成功:X件 / キュー登録成功:Y件"
* - エラーあり: "メール送信成功:X件 / メール送信失敗:A件 / キュー登録成功:Y件 / キュー登録失敗:B件"
*
* @param int $mailSuccessCount メール正常終了件数
* @param int $mailErrorCount メール異常終了件数
* @param int $queueSuccessCount キュー登録正常終了件数
* @param int $queueErrorCount キュー登録異常終了件数
* @return string ステータスコメント
*/
private function buildStatusComment(
int $mailSuccessCount,
int $mailErrorCount,
int $queueSuccessCount,
int $queueErrorCount
): string {
$parts = [];
// メール送信結果
if ($mailSuccessCount > 0 || $mailErrorCount > 0) {
if ($mailErrorCount > 0) {
$parts[] = "メール送信成功:{$mailSuccessCount}";
$parts[] = "メール送信失敗:{$mailErrorCount}";
} else {
$parts[] = "メール送信成功:{$mailSuccessCount}";
}
}
// キュー登録結果
if ($queueSuccessCount > 0 || $queueErrorCount > 0) {
if ($queueErrorCount > 0) {
$parts[] = "キュー登録成功:{$queueSuccessCount}";
$parts[] = "キュー登録失敗:{$queueErrorCount}";
} else {
$parts[] = "キュー登録成功:{$queueSuccessCount}";
}
}
return implode(' / ', $parts);
}
/**
* バッチコメント整形
*
* エラー情報を最大100件に制限し、超過分は省略表記
*
* @param array $batchComments エラー情報配列
* @return string 整形済みバッチコメント
*/
private function formatBatchComments(array $batchComments): string
{
if (empty($batchComments)) {
return '';
}
// 最大100件に制限
$limitedComments = array_slice($batchComments, 0, 100);
// 超過分の件数を追加
if (count($batchComments) > 100) {
$limitedComments[] = sprintf(
'...他%d件省略',
count($batchComments) - 100
);
}
return implode("\n", $limitedComments);
}
/**
* 【処理3】バッチ処理ログを作成する
*
* 統一BatchLogシステムを使用してSHJ-12の実行ログを記録
* SHJ-8サービスを呼び出してbat_job_logテーブルに登録業務固有のstatus_comment記録
*
* @param string $status ステータス
* @param array $parameters パラメータ
@ -310,22 +480,40 @@ class ShjTwelveService
int $errorCount = 0
): void {
try {
BatchLog::createBatchLog(
'SHJ-12',
$status,
$parameters,
// デバイスIDを取得
$device = Device::orderBy('device_id')->first();
$deviceId = $device ? $device->device_id : 1;
// status_commentを構築業務情報を含む
$statusComment = sprintf(
'%s (実行:%d/成功:%d/エラー:%d)',
$message,
[
'execution_count' => $executionCount,
'success_count' => $successCount,
'error_count' => $errorCount,
'process_type' => '未払い者通知処理',
'executed_at' => now()->toISOString()
]
$executionCount,
$successCount,
$errorCount
);
$today = now()->format('Y/m/d');
Log::info('SHJ-8 バッチ処理ログ作成', [
'device_id' => $deviceId,
'status' => $status,
'status_comment' => $statusComment
]);
// SHJ-8サービスを呼び出し
$this->shjEightService->execute(
$deviceId,
'SHJ-12',
'SHJ-12未払い者通知',
$status,
$statusComment,
$today,
$today
);
} catch (\Exception $e) {
Log::error('SHJ-12 バッチログ作成エラー', [
Log::error('SHJ-8 バッチログ作成エラー', [
'error' => $e->getMessage(),
'status' => $status,
'message' => $message

View File

@ -8,7 +8,9 @@
"require": {
"php": "^8.2",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"
"laravel/tinker": "^2.10.1",
"mpdf/mpdf": "^8.2",
"simplesoftwareio/simple-qrcode": "^4.2"
},
"require-dev": {
"fakerphp/faker": "^1.23",

589
composer.lock generated
View File

@ -4,8 +4,62 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d8f67b824a1bf31c8999ead558d9b485",
"content-hash": "571741bd63db78c990a3a07320a20496",
"packages": [
{
"name": "bacon/bacon-qr-code",
"version": "2.0.8",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22",
"shasum": ""
},
"require": {
"dasprid/enum": "^1.0.3",
"ext-iconv": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phly/keep-a-changelog": "^2.1",
"phpunit/phpunit": "^7 | ^8 | ^9",
"spatie/phpunit-snapshot-assertions": "^4.2.9",
"squizlabs/php_codesniffer": "^3.4"
},
"suggest": {
"ext-imagick": "to generate QR code images"
},
"type": "library",
"autoload": {
"psr-4": {
"BaconQrCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "BaconQrCode is a QR code generator for PHP.",
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8"
},
"time": "2022-12-07T17:46:57+00:00"
},
{
"name": "brick/math",
"version": "0.13.1",
@ -135,6 +189,56 @@
],
"time": "2024-02-09T16:56:22+00:00"
},
{
"name": "dasprid/enum",
"version": "1.0.7",
"source": {
"type": "git",
"url": "https://github.com/DASPRiD/Enum.git",
"reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
"reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce",
"shasum": ""
},
"require": {
"php": ">=7.1 <9.0"
},
"require-dev": {
"phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "*"
},
"type": "library",
"autoload": {
"psr-4": {
"DASPRiD\\Enum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Ben Scholzen 'DASPRiD'",
"email": "mail@dasprids.de",
"homepage": "https://dasprids.de/",
"role": "Developer"
}
],
"description": "PHP 7.1 enum implementation",
"keywords": [
"enum",
"map"
],
"support": {
"issues": "https://github.com/DASPRiD/Enum/issues",
"source": "https://github.com/DASPRiD/Enum/tree/1.0.7"
},
"time": "2025-09-16T12:23:56+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@ -2109,6 +2213,239 @@
],
"time": "2025-03-24T10:02:05+00:00"
},
{
"name": "mpdf/mpdf",
"version": "v8.2.6",
"source": {
"type": "git",
"url": "https://github.com/mpdf/mpdf.git",
"reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mpdf/mpdf/zipball/dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44",
"reference": "dd30e3b01061cf8dfe65e7041ab4cc46d8ebdd44",
"shasum": ""
},
"require": {
"ext-gd": "*",
"ext-mbstring": "*",
"mpdf/psr-http-message-shim": "^1.0 || ^2.0",
"mpdf/psr-log-aware-trait": "^2.0 || ^3.0",
"myclabs/deep-copy": "^1.7",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": "^5.6 || ^7.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"psr/http-message": "^1.0 || ^2.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"setasign/fpdi": "^2.1"
},
"require-dev": {
"mockery/mockery": "^1.3.0",
"mpdf/qrcode": "^1.1.0",
"squizlabs/php_codesniffer": "^3.5.0",
"tracy/tracy": "~2.5",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"ext-bcmath": "Needed for generation of some types of barcodes",
"ext-xml": "Needed mainly for SVG manipulation",
"ext-zlib": "Needed for compression of embedded resources, such as fonts"
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Mpdf\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-only"
],
"authors": [
{
"name": "Matěj Humpál",
"role": "Developer, maintainer"
},
{
"name": "Ian Back",
"role": "Developer (retired)"
}
],
"description": "PHP library generating PDF files from UTF-8 encoded HTML",
"homepage": "https://mpdf.github.io",
"keywords": [
"pdf",
"php",
"utf-8"
],
"support": {
"docs": "https://mpdf.github.io",
"issues": "https://github.com/mpdf/mpdf/issues",
"source": "https://github.com/mpdf/mpdf"
},
"funding": [
{
"url": "https://www.paypal.me/mpdf",
"type": "custom"
}
],
"time": "2025-08-18T08:51:51+00:00"
},
{
"name": "mpdf/psr-http-message-shim",
"version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/mpdf/psr-http-message-shim.git",
"reference": "f25a0153d645e234f9db42e5433b16d9b113920f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mpdf/psr-http-message-shim/zipball/f25a0153d645e234f9db42e5433b16d9b113920f",
"reference": "f25a0153d645e234f9db42e5433b16d9b113920f",
"shasum": ""
},
"require": {
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Mpdf\\PsrHttpMessageShim\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Dorison",
"email": "mark@chromatichq.com"
},
{
"name": "Kristofer Widholm",
"email": "kristofer@chromatichq.com"
},
{
"name": "Nigel Cunningham",
"email": "nigel.cunningham@technocrat.com.au"
}
],
"description": "Shim to allow support of different psr/message versions.",
"support": {
"issues": "https://github.com/mpdf/psr-http-message-shim/issues",
"source": "https://github.com/mpdf/psr-http-message-shim/tree/v2.0.1"
},
"time": "2023-10-02T14:34:03+00:00"
},
{
"name": "mpdf/psr-log-aware-trait",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/mpdf/psr-log-aware-trait.git",
"reference": "a633da6065e946cc491e1c962850344bb0bf3e78"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mpdf/psr-log-aware-trait/zipball/a633da6065e946cc491e1c962850344bb0bf3e78",
"reference": "a633da6065e946cc491e1c962850344bb0bf3e78",
"shasum": ""
},
"require": {
"psr/log": "^3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Mpdf\\PsrLogAwareTrait\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Dorison",
"email": "mark@chromatichq.com"
},
{
"name": "Kristofer Widholm",
"email": "kristofer@chromatichq.com"
}
],
"description": "Trait to allow support of different psr/log versions.",
"support": {
"issues": "https://github.com/mpdf/psr-log-aware-trait/issues",
"source": "https://github.com/mpdf/psr-log-aware-trait/tree/v3.0.0"
},
"time": "2023-05-03T06:19:36+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.13.3",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
"phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
"autoload": {
"files": [
"src/DeepCopy/deep_copy.php"
],
"psr-4": {
"DeepCopy\\": "src/DeepCopy/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Create deep copies (clones) of your objects",
"keywords": [
"clone",
"copy",
"duplicate",
"object",
"object graph"
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
},
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2025-07-05T12:25:42+00:00"
},
{
"name": "nesbot/carbon",
"version": "3.10.1",
@ -2507,6 +2844,56 @@
],
"time": "2025-05-08T08:14:37+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.3",
@ -3271,6 +3658,146 @@
},
"time": "2025-06-25T14:20:11+00:00"
},
{
"name": "setasign/fpdi",
"version": "v2.6.4",
"source": {
"type": "git",
"url": "https://github.com/Setasign/FPDI.git",
"reference": "4b53852fde2734ec6a07e458a085db627c60eada"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/4b53852fde2734ec6a07e458a085db627c60eada",
"reference": "4b53852fde2734ec6a07e458a085db627c60eada",
"shasum": ""
},
"require": {
"ext-zlib": "*",
"php": "^7.1 || ^8.0"
},
"conflict": {
"setasign/tfpdf": "<1.31"
},
"require-dev": {
"phpunit/phpunit": "^7",
"setasign/fpdf": "~1.8.6",
"setasign/tfpdf": "~1.33",
"squizlabs/php_codesniffer": "^3.5",
"tecnickcom/tcpdf": "^6.8"
},
"suggest": {
"setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured."
},
"type": "library",
"autoload": {
"psr-4": {
"setasign\\Fpdi\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Slabon",
"email": "jan.slabon@setasign.com",
"homepage": "https://www.setasign.com"
},
{
"name": "Maximilian Kresse",
"email": "maximilian.kresse@setasign.com",
"homepage": "https://www.setasign.com"
}
],
"description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.",
"homepage": "https://www.setasign.com/fpdi",
"keywords": [
"fpdf",
"fpdi",
"pdf"
],
"support": {
"issues": "https://github.com/Setasign/FPDI/issues",
"source": "https://github.com/Setasign/FPDI/tree/v2.6.4"
},
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/setasign/fpdi",
"type": "tidelift"
}
],
"time": "2025-08-05T09:57:14+00:00"
},
{
"name": "simplesoftwareio/simple-qrcode",
"version": "4.2.0",
"source": {
"type": "git",
"url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git",
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/916db7948ca6772d54bb617259c768c9cdc8d537",
"reference": "916db7948ca6772d54bb617259c768c9cdc8d537",
"shasum": ""
},
"require": {
"bacon/bacon-qr-code": "^2.0",
"ext-gd": "*",
"php": ">=7.2|^8.0"
},
"require-dev": {
"mockery/mockery": "~1",
"phpunit/phpunit": "~9"
},
"suggest": {
"ext-imagick": "Allows the generation of PNG QrCodes.",
"illuminate/support": "Allows for use within Laravel."
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"QrCode": "SimpleSoftwareIO\\QrCode\\Facades\\QrCode"
},
"providers": [
"SimpleSoftwareIO\\QrCode\\QrCodeServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"SimpleSoftwareIO\\QrCode\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Simple Software LLC",
"email": "support@simplesoftware.io"
}
],
"description": "Simple QrCode is a QR code generator made for Laravel.",
"homepage": "https://www.simplesoftware.io/#/docs/simple-qrcode",
"keywords": [
"Simple",
"generator",
"laravel",
"qrcode",
"wrapper"
],
"support": {
"issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues",
"source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/4.2.0"
},
"time": "2021-02-08T20:43:55+00:00"
},
{
"name": "symfony/clock",
"version": "v7.3.0",
@ -6258,66 +6785,6 @@
},
"time": "2024-05-16T03:13:13+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.13.3",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36",
"reference": "faed855a7b5f4d4637717c2b3863e277116beb36",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
"phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
"autoload": {
"files": [
"src/DeepCopy/deep_copy.php"
],
"psr-4": {
"DeepCopy\\": "src/DeepCopy/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Create deep copies (clones) of your objects",
"keywords": [
"clone",
"copy",
"duplicate",
"object",
"object graph"
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.3"
},
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2025-07-05T12:25:42+00:00"
},
{
"name": "nunomaduro/collision",
"version": "v8.8.2",

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

@ -130,6 +130,21 @@ $(document).on('click', '.btn-popup', function () {
});
});
$(document).on('click', '.btn-reserve-popup', function () {
var reserveId = $(this).data('reserve-id');
$.ajax({
url: '/api/park-detail-wait/' + reserveId,
method: 'GET',
success: function (data) {
$('#modalArea').html(data.html); // モーダル全体を挿入
$('#parkDetailWaitModal').modal('show'); // モーダルを表示
},
error: function () {
alert('詳細情報の取得に失敗しました');
}
});
});
$(document).on('click', '.btn-reserve', function (e) {
e.preventDefault();
var parkId = $(this).data('park-id');
@ -141,8 +156,19 @@ $(document).on('click', '.btn-reserve', function (e) {
content: 'こちらの駐輪場を予約しますか?<br>お申込みいただく各自治体の条例等によって、駐輪場までの距離制限等によりご契約いただけない場合がございます。<br>予めご了承くださいますようお願いいたします。',
buttons: {
OK: function () {
// GETパラメータでregulationメソッドに遷移
// Ajaxで予約済みかチェック
$.get('/park-waitlist/check', {
park_id: parkId,
psection_id: psectionId,
ptype_id: ptypeId
}, function (response) {
if (response.status === 'exists') {
$.alert('こちらの駐輪場は予約済みです。');
} else {
// 予約画面へ遷移
location.href = '/park-waitlist/create?park_id=' + parkId + '&psection_id=' + psectionId + '&ptype_id=' + ptypeId;
}
});
},
キャンセル: function () { }
}

View File

@ -0,0 +1,22 @@
{!! nl2br(e(
$user_name . '
So-Manager自動応答システムです。
下記駐輪場の空き待ちのキャンセルを受け付けました。
空き駐輪場: ' . $reserve->park_name . '
車種区分 : ' . $reserve->psection_subject . '
駐輪分類 : ' . $reserve->ptype_subject . '
So-Manager.comのユーザー登録は解約されておりません。別の駐輪場でのご契約や空き待ちをご希望の際には、マイページをご活用ください。
■お問合せ先■
So-Managerコールセンターソーマネージャーコールセンター
●電話03-5856-4720
●メールでのお問合せ(専用フォームよりお問合わせください)
※本メールアドレスは送信専用です。ご返信には回答致しかねますのでご了承ください。
※本メールにお心あたりのない場合には、コールセンターまでご連絡くださいますようお願い致します。'
)) !!}

View File

@ -31,7 +31,7 @@
<h3 class="other mt50">開示等の依頼の手続き、使用する様式</h3>
<p class="p1">開示等の依頼は、以下の手続き及び様式に則って実施致します。</p>
<p class="p1">利用目的の通知:本書面の“開示対象個人情報の利用目的”をご覧下さい。</p>
<p class="p1">開示、訂正・削除、利用停止:当社の定める様式にて実施致します。該当の受付け窓口にご連絡頂き、所定の様式『<a href="{{ asset('assets/page-img/privacy_disclosure.pdf') }}" target="_blank">個人情報開示等依頼書(PDF:9KB)</a>』を入手のうえ、手続きをお願い致します。</p>
<p class="p1">開示、訂正・削除、利用停止:当社の定める様式にて実施致します。該当の受付け窓口にご連絡頂き、所定の様式『<a href="{{ asset('assets/privacy_disclosure.pdf') }}" target="_blank">個人情報開示等依頼書(PDF:9KB)</a>』を入手のうえ、手続きをお願い致します。</p>
<p class="p1">回答に関しては、記入済み個人情報開示等依頼書を、ご自宅への郵送のみとさせて頂きます。</p>
<h3 class="other mt50">開示対象個人情報の取扱いに関する苦情受付け窓口</h3>
<p class="p1">開示対象個人情報の取扱いに関する苦情、相談に関しましては、以下の窓口宛てにご連絡ください。</p>

View File

@ -9,140 +9,140 @@
<h5 class="card-title text-success">駐輪場をお選びください。</h5>
</div>
<div class="card-body">
<form class="row form">
<form class="row form" method="post" action="{{ route('swo5_2') }}">
<div class="w-100 alert alert-success">
<h6><a class="text-success" data-toggle="collapse" href="#search-option" role="button" aria-expanded="false" aria-controls="search-option">絞込み条件を追加する</a></h6>
<div class="collapse row" id="search-option">
<div class="col-3">市町村名</div>
<div class="col-9 mb10">
<select class="form-control form-control-lg">
<option>市町村を選択してください</option>
<option>〇〇市</option>
<option>〇〇市</option>
<option>〇〇市</option>
<option>〇〇市</option>
<option>〇〇市</option>
<select class="form-control form-control-lg" name="conditions_city">
<option value="">市町村を選択してください</option>
@foreach($cities as $city)
@if(isset($conditions) && $conditions[0] == $city->city_name) {
<option value="{{ $city->city_name }}" selected>{{ $city->city_name }}</option>
@else
<option value="{{ $city->city_name }}">{{ $city->city_name }}</option>
@endif
@endforeach
</select>
</div>
<div class="col-3">駅名</div>
<div class="col-9 mb10">
<select class="form-control form-control-lg">
<option>駅名を選択してください</option>
<option>〇〇駅</option>
<option>〇〇駅</option>
<option>〇〇駅</option>
<option>〇〇駅</option>
<option>〇〇駅</option>
<select class="form-control form-control-lg" name="conditions_station">
<option value="">駅名を選択してください</option>
@foreach($stations as $station)
@if(isset($conditions) && $conditions[1] == $station->station_neighbor_station) {
<option value="{{ $station->station_neighbor_station }}" selected>{{ $station->station_neighbor_station }}</option>
@else
<option value="{{ $station->station_neighbor_station }}">{{ $station->station_neighbor_station }}</option>
@endif
@endforeach
</select>
</div>
<div class="col-3">駐車場名</div>
<div class="col-9 mb10">
<select class="form-control form-control-lg">
<option>全て</option>
<option>あ行</option>
<option>か行</option>
<option>さ行</option>
<option>た行</option>
<option>な行</option>
<option>は行</option>
<option>ま行</option>
<option>や行</option>
<option>ら行</option>
<option>わ行</option>
<select class="form-control form-control-lg" name="conditions_park">
@foreach($parks as $key => $value)
@if(isset($conditions) && $conditions[2] == $value) {
<option value="{{ $value }}" selected>{{ $key }}</option>
@else
<option value="{{ $value }}">{{ $key }}</option>
@endif
@endforeach
</select>
</div>
</div>
</div>
<script>
$(function() {
$('select[name="conditions_city"], select[name="conditions_station"], select[name="conditions_park"]').on('change', function() {
$(this).closest('form').submit();
});
});
</script>
@csrf
</form>
<table id="searchTable" class="tablesorter table table-striped">
<thead>
<tr>
<th>駐輪場名</th>
<th>市町村名</th>
<th>駅名</th>
<th></th>
<th width="20%">駐輪場名</th>
<th width="30%">市町村名</th>
<th width="20%">駅名</th>
@foreach($psections as $psection)
<th width="10%">{{ $psection->psection_subject }}</th>
@endforeach
</tr>
</thead>
<tbody>
@php
$perPage = 10;
$page = request()->get('page', 1);
$total = count($form_data);
$start = ($page - 1) * $perPage;
$pagedData = array_slice($form_data, $start, $perPage);
$lastPage = ceil($total / $perPage);
@endphp
@foreach($pagedData as $data)
<tr>
<td><a href="#placeModal01" data-toggle="modal" data-target="#placeModal01">あマルバツ駐輪場A</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="./SWC-08-02.html" class="btn btn-block btn-sm btn-outline-success">定期契約</a></td>
</tr>
<tr>
<td><a href="#placeModal02" data-toggle="modal" data-target="#placeModal02">かマルバツ駐輪場B</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="#" class="btn btn-block btn-sm btn-outline-danger">空き待ち申込</a></td>
</tr>
<tr>
<td><a href="#placeModal01" data-toggle="modal" data-target="#placeModal01">あマルバツ駐輪場A</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="./SWC-08-02.html" class="btn btn-block btn-sm btn-outline-success">定期契約</a></td>
</tr>
<tr>
<td><a href="#placeModal02" data-toggle="modal" data-target="#placeModal02">かマルバツ駐輪場B</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="#" class="btn btn-block btn-sm btn-outline-danger">空き待ち申込</a></td>
</tr>
<tr>
<td><a href="#placeModal01" data-toggle="modal" data-target="#placeModal01">あマルバツ駐輪場A</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="./SWC-08-02.html" class="btn btn-block btn-sm btn-outline-success">定期契約</a></td>
</tr>
<tr>
<td><a href="#placeModal02" data-toggle="modal" data-target="#placeModal02">かマルバツ駐輪場B</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="#" class="btn btn-block btn-sm btn-outline-danger">空き待ち申込</a></td>
</tr>
<tr>
<td><a href="#placeModal01" data-toggle="modal" data-target="#placeModal01">あマルバツ駐輪場A</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="./SWC-08-02.html" class="btn btn-block btn-sm btn-outline-success">定期契約</a></td>
</tr>
<tr>
<td><a href="#placeModal02" data-toggle="modal" data-target="#placeModal02">かマルバツ駐輪場B</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="#" class="btn btn-block btn-sm btn-outline-danger">空き待ち申込</a></td>
</tr>
<tr>
<td><a href="#placeModal01" data-toggle="modal" data-target="#placeModal01">あマルバツ駐輪場A</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="./SWC-08-02.html" class="btn btn-block btn-sm btn-outline-success">定期契約</a></td>
</tr>
<tr>
<td><a href="#placeModal02" data-toggle="modal" data-target="#placeModal02">かマルバツ駐輪場B</a></td>
<td>〇〇市</td>
<td>〇〇駅</td>
<td><a href="#" class="btn btn-block btn-sm btn-outline-danger">空き待ち申込</a></td>
<td>
<a href="#placeModal"
data-toggle="modal"
data-target="#placeModal"
data-park_name="{{ $data['park_name'] }}"
data-park_adrs="{{ $data['park_adrs'] ?? '' }}"
data-price_memo="{{ $data['price_memo'] ?? '' }}"
data-park_latitude="{{ $data['park_latitude'] ?? '' }}"
data-park_longitude="{{ $data['park_longitude'] ?? '' }}"
data-city_name="{{ $data['city_name'] }}"
data-station="{{ $data['station_neighbor_station'] }}"
data-zone_data='@json($data["zone_data"])'>
{{ $data['park_name'] }}
</a>
</td>
<td>{{ $data['city_name'] }}</td>
<td>{{ $data['station_neighbor_station'] }}</td>
@foreach($psections as $psection)
<td>
@foreach($data['zone_data'] as $zone)
@if($zone['psection_subject'] == $psection->psection_subject)
@if($zone['status'] == 1)
<a href="{{route('user.info')}}" class="btn btn-block btn-sm btn-outline-success">定期契約</a>
@elseif($zone['status'] == 2)
<a href="{{route('park_waitlist.index')}}" class="btn btn-block btn-sm btn-outline-primary">空き待ち予約</a>
@elseif($zone['status'] == 3)
<a href="{{route('park_waitlist.index')}}" class="btn btn-block btn-sm btn-secondary">販売期間外</a>
@endif
@break;
@endif
@endforeach
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
<nav aria-label="searchTable-pager">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link text-success" href="#" aria-label="">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only"></span>
@if($lastPage > 1)
{{-- 前へ --}}
<li class="page-item {{ $page == 1 ? 'disabled' : '' }}">
<a class="page-link text-success" href="?page={{ $page - 1 }}" aria-label="">
<span aria-hidden="true">&laquo;</span><span class="sr-only"></span>
</a>
</li>
<li class="page-item"><a class="page-link text-success" href="#">1</a></li>
<li class="page-item"><a class="page-link text-success" href="#">2</a></li>
<li class="page-item"><a class="page-link text-success" href="#">3</a></li>
<li class="page-item">
<a class="page-link text-success" href="#" aria-label="">
<span aria-hidden="true">&raquo;</span>
<span class="sr-only"></span>
{{-- ページ番号 --}}
@for($i = 1; $i <= $lastPage; $i++)
<li class="page-item {{ $page == $i ? 'active' : '' }}">
<a class="page-link text-success" href="?page={{ $i }}">{{ $i }}</a>
</li>
@endfor
{{-- 次へ --}}
<li class="page-item {{ $page == $lastPage ? 'disabled' : '' }}">
<a class="page-link text-success" href="?page={{ $page + 1 }}" aria-label="">
<span aria-hidden="true">&raquo;</span><span class="sr-only"></span>
</a>
</li>
@endif
</ul>
</nav>
</div>
@ -151,64 +151,91 @@
</div>
</section>
</main>
<div class="modal fade" id="placeModal01" tabindex="-1" role="dialog" aria-hidden="true">
<!-- Modal -->
<div class="modal fade" id="placeModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<script>
$('#placeModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var parkName = button.data('park_name') || '';
var parkAdrs = button.data('park_adrs') || '';
var parkMemo = button.data('price_memo') || '';
var lat = button.data('park_latitude') || '';
var lng = button.data('park_longitude') || '';
$('#parkName').text(parkName).attr('data-park_name', parkName);
$('#parkAdrs').text('住所:' + parkAdrs).attr('data-park_adrs', parkAdrs);
$('#parkmemo').text(parkMemo).attr('data-price_memo', parkMemo);
$('#parkMap').attr('src', 'https://www.google.com/maps?q=' + lat + ',' + lng + '&z=15&output=embed');
// zone_dataはJSON文字列として渡されているのでパース
var zoneData = button.data('zone_data') || [];
// psection_subjectごとの標準収容台数を集計
var standardMap = {};
if (zoneData && Array.isArray(zoneData)) {
zoneData.forEach(function(zone) {
if (!standardMap[zone.psection_subject]) {
standardMap[zone.psection_subject] = 0;
}
standardMap[zone.psection_subject] += parseInt(zone.zone_standard, 10) || 0;
});
}
// 表示用文字列を生成
var standardText = '';
var keys = Object.keys(standardMap);
if (keys.length > 0) {
standardText = '【標準収容台数】';
standardText += keys.map(function(key) {
return key + '' + standardMap[key] + '台';
}).join(' / ');
}
$('#parkStandard').text(standardText);
// 各ゾーンの空き台数・ボタンを表示
var html = '';
if (zoneData && Array.isArray(zoneData)) {
var grouped = {};
zoneData.forEach(function(zone) {
if (!grouped[zone.ptype_subject]) grouped[zone.ptype_subject] = [];
grouped[zone.ptype_subject].push(zone);
});
Object.keys(grouped).forEach(function(ptype) {
html += '<h4 class="mt-3">' + ptype + '</h4>';
grouped[ptype].forEach(function(zone) {
html += '<div class="d-flex align-items-center mb-2">';
html += '<span>' + zone.psection_subject + ':空き' + zone.zone_vacant + '台</span>';
if (zone.status == 1) {
html += '<a href="{{route('regular_contract.create')}}" class="btn btn-sm btn-outline-success ml-2">定期契約</a>';
} else if (zone.status == 2) {
html += '<a href="{{route('park_waitlist.index')}}" class="btn btn-sm btn-outline-primary ml-2">空き待ち予約</a>';
} else if (zone.status == 3) {
html += '<a href="{{route('park_waitlist.index')}}" class="btn btn-sm btn-outline-secondary ml-2">販売期間外</a>';
}
html += '</div>';
});
});
}
$('#zoneData').html(html);
});
</script>
<div class="modal-header">
<h5 class="modal-title">あマルバツ駐輪場A</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title" id="parkName" data-park_name=""></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3240.722943699139!2d139.75162621525894!3d35.68382338019366!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x60188c0b185b3b75%3A0x3282e79fbc91959c!2z44CSMTAwLTAwMDEg5p2x5Lqs6YO95Y2D5Luj55Sw5Yy65Y2D5Luj55Sw77yR4oiS77yR!5e0!3m2!1sja!2sjp!4v1536695351221" width="100%" height="450" frameborder="0" style="border:0" allowfullscreen></iframe>
<iframe id="parkMap" src="" width="100%" height="450" frameborder="0" style="border:0" allowfullscreen></iframe>
<p class="small">
〒000-0000 東京都千代田区1-1 <br class="sp">
標準収容台数XXX台
<span id="parkAdrs"> </span>
<span id="parkStandard"></span><br />
<span id="parkmemo"></span>
</p>
<p class="text-danger">
空き台数XXX台
</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success" onClick="location.href='./SWC-08-02.html'">定期契約</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">閉じる</button>
<span id="zoneData"></span>
<div class="text-right"><button type="button" class="btn btn-outline-secondary" data-dismiss="modal">閉じる</button></div>
</div>
</div>
<div class="modal-footer"></div>
</div>
</div>
<div class="modal fade" id="placeModal02" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">かマルバツ駐輪場B</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3240.722943699139!2d139.75162621525894!3d35.68382338019366!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x60188c0b185b3b75%3A0x3282e79fbc91959c!2z44CSMTAwLTAwMDEg5p2x5Lqs6YO95Y2D5Luj55Sw5Yy65Y2D5Luj55Sw77yR4oiS77yR!5e0!3m2!1sja!2sjp!4v1536695351221" width="100%" height="450" frameborder="0" style="border:0" allowfullscreen></iframe>
<p class="small">
〒000-0000 東京都千代田区1-1 <br class="sp">
標準収容台数XXX台
</p>
<p class="text-danger">
空き待ち
</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger">空き待ち申込</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">閉じる</button>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script>window.jQuery || document.write('<script src="./assets/js/vendor/jquery.min.js"><\/script>')</script>
<script src="./assets/js/vendor/popper.min.js"></script>
<script src="./bootstrap/js/bootstrap.min.js"></script>
<script src="./assets/js/ie10-viewport-bug-workaround.js"></script>
<script src="./assets/js/commons.js"></script>
<script src="./assets/js/vendor/jquery.tablesorter/jquery.tablesorter.min.js" type="text/javascript"></script>
<script type="text/javascript">$(document).ready(function() { $("#sampleTable").tablesorter();} ); </script>
@endsection

View File

@ -5,13 +5,18 @@
<div class="col-12 col-lg-10 offset-0 offset-lg-1 mt20 mb20">
<h2 class="text-success text-center">お問い合わせ</h2>
<p>株式会社ソーリンへのご訪問ありがとうございます。
お問い合わせいただくお客さまは、当社のホームページにおける<a href="{{ route('swo14_1') }}">個人情報の取り扱い</a>について、あらかじめご確認いただき、ご同意いただいた上でお問い合わせください。個人情報の開示、訂正、削除、利用停止については、<a href="{{ asset('assets/privacy_disclosure.pdf') }}">こちら</a>をご覧下さい。<br>
お問い合わせいただくお客さまは、当社のホームページにおける<a href="{{ route('swo14_1') }}">個人情報の取り扱い</a>について、あらかじめご確認いただき、ご同意いただいた上でお問い合わせください。個人情報の開示、訂正、削除、利用停止については、<a href="{{ route('swo15_1') }}">こちら</a>をご覧下さい。<br>
また、メールから送信ができないお客様はお手数ですが、下記電話までご連絡をお願い致します。
携帯・スマートフォンからメールでのお問い合わせの際に、確実にご返信をさせていただくために、ドメイン (so-manager.com) の受信許可設定をお願い致します。</p>
<form class="form-contact form mt50" method="post" action="{{ route('swo7_2') }}">
@if($errors->any())
<div class="alert alert-danger error-message" role="alert">
@foreach ($errors->all() as $error){{ $error }}<br /> @endforeach
</div>
@endif
<div class="row">
@foreach($form_data as $value)
<div class="col-lg-3">{{ $value[2] }}</div>
<div class="col-lg-3">{!! preg_replace('/\*$/', '<span style="color:red;">*</span>', e($value[2])) !!}</div>
@if($value[1] == 'textarea')
<div class="col-lg-9"><textarea name="{{ $value[0] }}" cols="6" class="form-control">{{ old($value[0]) }}</textarea></div>
@elseif($value[1] == 'checkbox')
@ -25,18 +30,10 @@
@endforeach
</div>
@else
<div class="col-lg-9"><input name="{{ $value[0] }}" type="{{ $value[1] }}" value="{{ old($value[0]) }}" class="form-control mb10"></div>
<div class="col-lg-9"><input name="{{ $value[0] }}" type="{{ $value[1] }}" value="{{ old($value[0]) }}" class="form-control mb10" placeholder="{{ $value[3] }}"></div>
@endif
@endforeach
</div>
<p style="color: red;">
@foreach($form_data as $value)
@if($errors->has($value[0]))
{{ $errors->first($value[0]) }}
@break;
@endif
@endforeach
</p>
<div class="col-lg-12 mt50 mb50 text-center"><button type="submit" class="btn btn-lg btn-success mb10">入力内容を確認する</button></div>
@csrf
</form>

View File

@ -16,7 +16,7 @@
</div>
<div class="col-12 col-lg-3 text-lg-center offset-0 offset-lg-1"><label>パスワード</label></div>
<div class="col-12 col-lg-7 mb10">
<input type="text" name="password" class="form-control form-control-lg" value="" />
<input type="password" name="password" class="form-control form-control-lg" value="" />
</div>
<div class="col-12 col-lg-6 text-lg-center offset-0 offset-lg-3 mt30 mb50">
<div class="text-danger">@if ($errors->any()) @foreach ($errors->all() as $error) {{ $error }} @endforeach @endif</div><br />

View File

@ -5,14 +5,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>@yield('title', 'So-Manager')</title>
<link rel="icon" href="{{ asset('assets/img/favicon.ico') }}">
<link href="{{ asset('assets/css/mypage/app.css') }}" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/typicons/2.0.9/typicons.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/earlyaccess/roundedmplus1c.css" rel="stylesheet">
<link href="{{ asset('assets/css/mypage/slick.css') }}" rel="stylesheet">
<link href="{{ asset('assets/css/mypage/slick-theme.css') }}" rel="stylesheet">
<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 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/tablesorter-blue.css') }}" type="text/css" media="print, projection, screen">
<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

@ -0,0 +1,29 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">空き待ち状況確認 > キャンセル</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="row">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-success">キャンセルしますか?</h3>
<ul>
<li>キャンセル確定を押されますと確認ページは表示されません。即キャンセルになりますのでご注意ください。</li>
<li>一度キャンセルされると再度予約しても順番は最後になります。予めご了承ください。</li>
<li>再度予約を希望の場合は、空き待ち申込からお手続きください。</li>
</ul>
</div>
<div class="col-12 col-md-4 offset-md-2 mt10">
<a href="{{ route('park_waitlist.index') }}" class="btn btn-lg btn-block btn-outline-success">戻る</a>
</div>
<div class="col-12 col-md-4 offset-0 mt10">
<form action="{{ route('park_waitlist.cancel.post', ['reserve_id' => $reserve_id]) }}" method="POST">
@csrf
<button type="submit" class="btn btn-lg btn-block btn-success">キャンセル確定</button>
</form>
</div>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,19 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">空き待ち状況確認 > キャンセル完了</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="row">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-success">駐輪場名:{{ $reserve->park_name }}</h3>
<p class="mt30 mb30">上記の空き待ちキャンセルが完了しました。確認のメールを会員情報のメールアドレスに送信しました。</p>
</div>
<div class="col-12 col-md-8 offset-0 offset-md-2 mt30">
<a href="{{ route('park_waitlist.index') }}" class="btn btn-lg btn-block btn-outline-success">予約状況一覧へ戻る</a>
</div>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,55 @@
@extends('layouts.app')
@section('content')
<main>
<style>
/* テーブルヘッダーの色を #d4edda に固定し、クリックやフォーカス時も変化しない */
#searchTable th,
#searchTable th:active,
#searchTable th:focus {
background-color: #d4edda !important;
color: #212529 !important;
outline: none !important;
}
#searchTable th {
cursor: default !important;
}
</style>
<header class="alert alert-success">
<h4 class="container">空き待ち状況確認 > 予約状況</h4>
</header>
<section id="" class="container mt30 mb50">
@if($waitlists->count())
<div class="table-responsive">
<table id="searchTable" class="tablesorter table table-striped">
<thead>
<tr>
<th>予約日</th>
<th>駐輪場名</th>
<th>駅名</th>
<th>駐輪分類</th>
<th>車種区分</th>
</tr>
</thead>
<tbody>
@foreach($waitlists as $row)
<tr>
<td>{{ \Carbon\Carbon::parse($row->reserve_date)->format('Y/m/d') }}</td>
<td><a href="javascript:void(0);" class="btn-reserve-popup text-primary" data-reserve-id="{{ $row->reserve_id }}">
{{ $row->park_name }}
</a></td>
<td>{{ $row->station_neighbor_station }}</td>
<td>{{ $row->ptype_subject }}</td>
<td>{{ $row->psection_subject }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div class="text-center mt-5">予約している駐輪場はありません。</div>
@endif
</section>
</main>
<div id="modalArea"></div>
@endsection

View File

@ -0,0 +1,47 @@
<!-- Bootstrapモーダル -->
<div class="modal fade" id="parkDetailWaitModal" tabindex="-1" aria-labelledby="parkDetailWaitLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="parkDetailWaitLabel">{{ $park->park_name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<!-- Googleマップ埋め込み -->
<div style="width:100%; height:350px;">
<iframe
width="100%"
height="350"
frameborder="0"
style="border:0"
src="https://www.google.com/maps?q={{ $park->park_latitude }},{{ $park->park_longitude }}&hl=ja&z=16&output=embed"
allowfullscreen>
</iframe>
</div>
<!-- 駐輪場情報 -->
<div class="mt-3 small">
{{ $park->park_adrs }}
<span class="d-inline-block" style="margin-top:0.5em;"></span>
<span>
【標準収容台数】
@foreach($zoneStandardSum as $type => $count)
{{ $type }}{{ $count }}台 
@endforeach
</span>
</div>
</div>
<div class="modal-footer">
<a href="{{ route('park_waitlist.cancel', ['reserve_id' => $reserve->reserve_id]) }}"
class="btn btn-outline-success btn_103">
キャンセルする
</a>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">閉じる</button>
</div>
</div>
</div>
</div>
<!-- jQuery Confirm用CSS/JSjQuery本体は既に読み込まれていれば不要 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.4/jquery-confirm.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.4/jquery-confirm.min.js"></script>

View File

@ -1,12 +1,11 @@
@php
// コントローラーから $user_name を渡してください
if (!isset($user_name)) $user_name = '';
@endphp
<header id="top" class="bg-light">
<div class="container pt10">
<div class="row mt10 mb10">
<div class="col-5 col-lg-4">
<a id="site-logo" class="navbar-brand" href="{{ url('../index.html') }}">
<a id="site-logo" class="navbar-brand" href="{{ url('/mypage') }}">
<img src="{{ asset('assets/img/so-rin_logo.png') }}" alt="logo" />
<h1>
<span class="small pc">駐車場・駐輪場総合サポートの株式会社ソーリン<br></span>
@ -15,12 +14,11 @@ if (!isset($user_name)) $user_name = '';
</a>
</div>
<div class="col-6 col-lg-4 ml-auto tl-btn-area">
<a href="{{ url('SWO-4-1.html') }}" class="" id="login-btn">
<a href="{{ url('/mypage') }}" class="" id="login-btn">
<span class="small d-none d-xl-inline">ようこそ</span>{{ $user_name }}さん
</a>
<a href="{{ url('../index.html') }}" class="btn btn-outline-secondary badge-pill pc" id="sub-btn">ログアウト</a>
<a href="{{ url('/logout') }}" class="btn btn-outline-secondary badge-pill pc" id="sub-btn">ログアウト</a>
<a class="d-lg-none h2" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample" href="#collapseExample" id="nav-menu-btn">
<span class="typcn typcn-th-menu"></span>
</a>
</div>
</div>

View File

@ -15,15 +15,15 @@ if (!isset($active_menu)) $active_menu = '';
<a id="my-menu-btn" class="navbar-toggler text-center" href="#navbarSupportedContent" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> マイメニューを開く </a>
<div class="collapse navbar-collapse justify-content-around" id="navbarSupportedContent">
<ul class="navbar-nav">
<li class="nav-item {{ $active_menu == 'SWO-4-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('SWO-4-1.html') }}">マイページトップ<span class="sr-only">(current)</span></a></li>
<li class="nav-item {{ $active_menu == 'SWO-4-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/mypage') }}">マイページトップ<span class="sr-only">(current)</span></a></li>
<li class="nav-item {{ $active_menu == 'SWC-1-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/user/info') }}">ユーザー情報確認</a></li>
<li class="nav-item {{ $active_menu == 'SWC-3-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/regular_contract/info') }}">定期契約情報</a></li>
<li class="nav-item {{ $active_menu == 'SWC-4-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/regular_contract/update') }}">契約更新</a></li>
<li class="nav-item {{ $active_menu == 'SWC-6-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/regular_contract/history') }}">契約履歴</a></li>
<li class="nav-item {{ $active_menu == 'SWC-8-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/regular_contract/create') }}">新規定期契約</a></li>
<li class="nav-item {{ $active_menu == 'SWC-11-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/park_waitlist') }}">空き待ち状況確認</a></li>
<li class="nav-item {{ $active_menu == 'SWC-10-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('SWC-10-1.html') }}">駐輪場検索</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url('../howto.html') }}" target="_blank">このページの使い方</a></li>
<li class="nav-item {{ $active_menu == 'SWC-10-1' ? 'active' : '' }}"><a class="nav-link" href="{{ url('/park_search') }}">駐輪場検索</a></li>
<li class="nav-item"><a class="nav-link" href="{{ route('swo16_1') }}" target="_blank">このページの使い方</a></li>
</ul>
</div>
</nav>

View File

@ -42,7 +42,7 @@
</div>
<div class="col-6 col-md-2 mb20">
<div class="card text-center border-success">
<a href="{{ url('SWC-10-1.html') }}" target="_top">
<a href="{{ url('/park_search') }}" target="_top">
<figure><img src="{{ asset('assets/img/menu-aki.png') }}" alt="" class="w-75 mt10"></figure>
<p class="text-success">駐輪場検索</p><br>
</a>
@ -50,7 +50,7 @@
</div>
<div class="col-6 col-md-2 mb20">
<div class="card text-center border-success">
<a href="{{ url('SWC-15-1.html') }}" target="_top">
<a href="{{ url('/user_information') }}" target="_top">
<figure><img src="{{ asset('assets/img/menu-tuuchi.png') }}" alt="" class="w-75 mt10"></figure>
<p class="text-success">お知らせ一覧</p>
</a>
@ -66,7 +66,7 @@
</div>
<div class="col-6 col-md-2 mb20">
<div class="card text-center border-success">
<a href="{{ url('../index.html') }}" target="_top">
<a href="{{ route('logout') }}" target="_top">
<figure><img src="{{ asset('assets/img/menu-logout.png') }}" alt="" class="w-75 mt10"></figure>
<p class="text-success">ログアウト</p>
</a>
@ -74,7 +74,7 @@
</div>
<div class="col-6 col-md-2 mb20">
<div class="card text-center border-success">
<a href="{{ url('../howto.html') }}" target="_blank">
<a href="{{ route('swo16_1') }}" target="_blank">
<figure><img src="{{ asset('assets/img/menu-help.png') }}" alt="" class="w-75 mt10"></figure>
<p class="text-success">このページの使い方</p>
</a>

View File

@ -2,7 +2,11 @@
@section('content')
<main>
<header class="alert alert-success">
@if($isRegularContract)
<h4 class="container">新規定期契約 > 空き駐輪場を確認する</h4>
@else
<h4 class="container">駐輪場検索 > 駐輪場選択</h4>
@endif
</header>
<section id="" class="container mt20 mb20">
<div class="row">
@ -47,28 +51,17 @@
</div>
</div>
</form>
<div style="overflow-x: auto; width: 100%;">
<table id="searchTable" class="tablesorter table table-striped">
<thead>
<tr>
<th class="header">
駐輪場名
<a href="{{ request()->fullUrlWithQuery(['sort' => 'park_ruby', 'order' => 'asc', 'page' => 1]) }}" class="text-success"><i class="bi bi-caret-up-fill"></i></a>
<a href="{{ request()->fullUrlWithQuery(['sort' => 'park_ruby', 'order' => 'desc', 'page' => 1]) }}" class="text-success"><i class="bi bi-caret-down-fill"></i></a>
</th>
<th class="header">
市町村名
<a href="{{ request()->fullUrlWithQuery(['sort' => 'city_id', 'order' => 'asc', 'page' => 1]) }}" class="text-success"><i class="bi bi-caret-up-fill"></i></a>
<a href="{{ request()->fullUrlWithQuery(['sort' => 'city_id', 'order' => 'desc', 'page' => 1]) }}" class="text-success"><i class="bi bi-caret-down-fill"></i></a>
</th>
<th class="header">
駅名
<a href="{{ request()->fullUrlWithQuery(['sort' => 'station_name_ruby', 'order' => 'asc', 'page' => 1]) }}" class="text-success"><i class="bi bi-caret-up-fill"></i></a>
<a href="{{ request()->fullUrlWithQuery(['sort' => 'station_name_ruby', 'order' => 'desc', 'page' => 1]) }}" class="text-success"><i class="bi bi-caret-down-fill"></i></a>
</th>
<th class="header">自転車</th>
<th class="header">原付</th>
<th class="header">自動二輪</th>
<th class="header">自動車</th>
<th class="header" style="cursor:default; pointer-events: none; background-color: #d4edda !important;">駐輪場名</th>
<th class="header" style="cursor:default; pointer-events: none;">市町村名</th>
<th class="header" style="cursor:default; pointer-events: none;">駅名</th>
<th class="header" style="cursor:default; pointer-events: none;">自転車</th>
<th class="header" style="cursor:default; pointer-events: none;">原付</th>
<th class="header" style="cursor:default; pointer-events: none;">自動二輪</th>
<th class="header" style="cursor:default; pointer-events: none;">自動車</th>
</tr>
</thead>
<tbody>
@ -84,18 +77,31 @@
<td>
@php
$zonesForType = ($zones[$row->park_id] ?? collect())->where('psection_subject', $vehicle);
@endphp
@forelse ($zonesForType as $zone)
@php
// 空き台数計算
$hasVacancy = false;
foreach ($zonesForType as $zone) {
$reserveCount = ($reserve[$row->park_id] ?? collect())
->where('psection_id', $zone->psection_id)
->where('ptype_id', $zone->ptype_id)
->count();
$vacancy = $zone->zone_tolerance - $zone->zone_number - $reserveCount;
if ($vacancy > 0) {
$hasVacancy = true;
break;
}
}
// 猶予期間判定
$grace = $city_grace_periods[$row->city_id] ?? null;
$now = \Carbon\Carbon::now();
$gracePeriodValid =
$grace &&
is_numeric($grace->update_grace_period_start_date) &&
preg_match('/^\d{1,2}:\d{2}$/', $grace->update_grace_period_start_time) &&
is_numeric($grace->update_grace_period_end_date) &&
preg_match('/^\d{1,2}:\d{2}$/', $grace->update_grace_period_end_time);
$inGrace = false;
if ($grace && $grace->update_grace_period_start_date && $grace->update_grace_period_start_time && $grace->update_grace_period_end_date && $grace->update_grace_period_end_time) {
if ($gracePeriodValid) {
$now = \Carbon\Carbon::now();
$year = $now->year;
$month = $now->month;
$startDay = (int)$grace->update_grace_period_start_date;
@ -120,16 +126,17 @@
}
}
@endphp
@if ($vacancy > 0 && $inGrace)
@if ($zonesForType->isNotEmpty() && $gracePeriodValid)
@if ($hasVacancy && $inGrace)
<button class="btn btn-block btn-sm btn-outline-success btn_82-table btn-popup" data-park-id="{{ $row->park_id }}">定期契約</button>
@elseif ($vacancy > 0 && !$inGrace)
@elseif (!$inGrace)
<button class="btn btn-block btn-sm btn-outline-danger btn_103-table btn-popup" data-park-id="{{ $row->park_id }}">販売期間外</button>
@else
@elseif (!$hasVacancy && $inGrace)
<button class="btn btn-block btn-sm btn-outline-danger btn_103-table btn-popup" data-park-id="{{ $row->park_id }}">空き待ち申込</button>
@endif
@empty
@else
<span class="text-muted"></span>
@endforelse
@endif
</td>
@endforeach
</tr>
@ -140,7 +147,7 @@
@endforelse
</tbody>
</table>
</div>
@php
$totalPages = ceil($parks_table_total / $parks_table_perPage);
$currentPage = $parks_table_page;

View File

@ -10,7 +10,7 @@
{{ $errors->first('month') }}
</div>
@endif
<form class="row form" action="{{ url('regular_contract/update_period') }}" method="post">
<form class="row form" action="{{ route('regular_contract.create_select_period') }}" method="post">
@csrf
<input type="hidden" name="contract_id" value="{{ $contract_id }}">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">

View File

@ -262,6 +262,7 @@ return null;
<button type="button" id="cancelModalBtn" class="btn btn-outline-secondary badge-pill custom-rounded-btn" style="background: transparent;" data-bs-toggle="modal" data-bs-target="#cancelModal">解約について</button>
@endif
</div>
<div style="display: flex; gap: 6px;">
@php
$has_receipt = DB::table('inv_publish')->where('contract_id', $contract->contract_id)->exists();
@endphp
@ -282,6 +283,14 @@ return null;
<a href="{{ url('receipt/input/' . $contract->contract_id) }}" class="btn btn-outline-secondary badge-pill custom-rounded-btn" style="background: transparent;">領収書発行</a>
@endif
@endif
@if($bg == 'alert-warning')
<a href="{{ url('seal/reissue/' . $contract->contract_id) }}" class="btn btn-outline-warning badge-pill custom-rounded-btn" style="background: transparent;">シール再発行</a>
@elseif($bg == 'alert-danger')
<a href="{{ url('seal/reissue/' . $contract->contract_id) }}" class="btn btn-outline-danger badge-pill custom-rounded-btn" style="background: transparent;">シール再発行</a>
@else
<a href="{{ url('seal/reissue/' . $contract->contract_id) }}" class="btn btn-outline-secondary badge-pill custom-rounded-btn" style="background: transparent;">シール再発行</a>
@endif
</div>
</div>
</td>
</tr>

View File

@ -36,7 +36,14 @@
</div>
<!-- 空き台数・契約情報 -->
<div class="mt-3">
@foreach($zones as $zone)
@php
$zonesByPtype = $zones->groupBy('ptype_id');
@endphp
@foreach($zonesByPtype as $ptypeId => $zonesGroup)
<div class="mb-3">
<strong>{{ $zonesGroup->first()->ptype_subject }}</strong>
<div style="display: flex; gap: 1em;">
@foreach($zonesGroup as $zone)
@php
$vacant = $vacancyData[$zone->psection_id . '_' . $zone->ptype_subject] ?? 0;
$grace = $city_grace_periods[$park->city_id] ?? null;
@ -55,21 +62,26 @@
if ($startDay > $endDay) {
// 月またぎ
$start = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $startTime));
// 前月の開始日~今月の終了日
$prevMonth = $now->copy()->subMonth();
$startPrev = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $prevMonth->year, $prevMonth->month, $startDay, $grace->update_grace_period_start_time));
$endCurr = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $endDay, $grace->update_grace_period_end_time));
// 今月の開始日~翌月の終了日
$startCurr = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $grace->update_grace_period_start_time));
$nextMonth = $month == 12 ? 1 : $month + 1;
$nextYear = $month == 12 ? $year + 1 : $year;
$end = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $nextYear, $nextMonth, $endDay, $endTime));
$endNext = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $nextYear, $nextMonth, $endDay, $grace->update_grace_period_end_time));
$isGracePeriod = $now->between($startPrev, $endCurr) || $now->between($startCurr, $endNext);
} else {
// 同月
$start = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $startTime));
$end = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $endDay, $endTime));
}
$start = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $startDay, $grace->update_grace_period_start_time));
$end = \Carbon\Carbon::createFromFormat('Y-m-d H:i', sprintf('%04d-%02d-%02d %s', $year, $month, $endDay, $grace->update_grace_period_end_time));
$isGracePeriod = $now->between($start, $end);
}
}
@endphp
<div class="mb-2">
<strong>{{ $zone->ptype_subject }}</strong><br>
{{ $zone->psection_subject }}:空き {{ $vacant }}
{{ $zone->psection_subject }}:空き {{ max(0, $vacant) }}
@if($isGracePeriod)
@if($vacant > 0)
<button type="button" class="btn btn-success btn-sm btn-contract"
@ -79,7 +91,7 @@
定期契約
</button>
@else
<button type="button" class="btn btn-danger btn_103 btn-reserve"
<button type="button" class="btn btn-danger btn-sm btn_103 btn-reserve"
data-park-id="{{ $park->park_id }}"
data-psection-id="{{ $zone->psection_id }}"
data-ptype-id="{{ $zone->ptype_id }}">
@ -87,7 +99,7 @@
</button>
@endif
@else
<button type="button" class="btn btn-danger btn_103 btn-reserve"
<button type="button" class="btn btn-danger btn-sm btn_103 btn-reserve"
data-park-id="{{ $park->park_id }}"
data-psection-id="{{ $zone->psection_id }}"
data-ptype-id="{{ $zone->ptype_id }}">
@ -98,6 +110,9 @@
@endforeach
</div>
</div>
@endforeach
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button>
</div>

View File

@ -0,0 +1,27 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">定期契約情報確認 > シール再発行</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-success">選択した駐輪場</h3>
</div>
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-warning">
<span class="small">定期契約ID: {{ $contract->contract_id }}<br>{{ $contract->park_name }}</span>
</h3>
<p class="text-center"><br>こちらのシールを再発行します。<br>よろしいですか?</p>
</div>
<div class="row">
<div class="col-12 col-md-5 offset-md-1 mt10">
<a href="{{ url('regular_contract/info') }}" class="btn btn-lg btn-block btn-outline-success">戻る</a>
</div>
<div class="col-12 col-md-5 mt10">
<a href="{{ url('seal/reissue/reason/' . $contract->contract_id) }}" class="btn btn-lg btn-block btn-success">進む</a>
</div>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,20 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">定期契約情報確認 > シール再発行</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-warning">
<span class="small">定期契約ID: {{ $contract->contract_id }}<br></span>
<span>{{ $contract->park_name }}</span>
</h3>
<p class="text-center"><br>こちらのシールの再発行準備が整いました。<br>上記駐輪場にてシールをお受け取りください。</p>
</div>
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<a href="{{ url('mypage') }}" class="btn btn-lg btn-block btn-outline-success">マイページへ戻る</a>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,86 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">ユーザー情報確認 > シール再発行理由</h4>
</header>
@if ($errors->any())
<div class="alert alert-danger text-center">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
</div>
@endif
<section class="container mt30 mb50">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h2 class="text-center alert-success">再発行手続き</h2>
</div>
<form method="POST" action="{{ url('seal/reissue/complete/' . $contract_id) }}">
@csrf
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="radio" name="reason" id="reason1" value="自動車の買い換え">
<label class="form-check-label" for="reason1">自動車の買い換え</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="reason" id="reason2" value="汚損">
<label class="form-check-label" for="reason2">汚損</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="reason" id="reason3" value="盗難">
<label class="form-check-label" for="reason3">盗難</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="reason" id="reason4" value="その他">
<label class="form-check-label" for="reason4">その他</label>
</div>
</div>
<div class="mb-4" id="other-reason-area" style="display: none;">
<textarea class="form-control" name="other_reason" rows="4" maxlength="255"
placeholder="その他の場合、こちらへ理由をご入力ください" style="resize: none;"></textarea>
<div class="text-right small"><span id="char-count">0</span>/255</div>
</div>
<div class="mb-4">
【再発行に関する注意事項】<br>
2回以上の再発行には別途手続きが必要です。<br>
紛失にはご注意ください。
</div>
</div>
<div class="row">
<div class="col-12 col-md-5 offset-md-1 mt10">
<a href="{{ url('seal/reissue/' . $contract_id) }}" class="btn btn-lg btn-block btn-outline-success">戻る</a>
</div>
<div class="col-12 col-md-5 mt10">
<button type="submit" class="btn btn-lg btn-block btn-success">進む</button>
</div>
</div>
</form>
</section>
</main>
<script>
document.addEventListener('DOMContentLoaded', function() {
// ラジオボタンの選択でテキストエリア表示切替
const radios = document.querySelectorAll('input[name="reason"]');
const otherArea = document.getElementById('other-reason-area');
radios.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === 'その他') {
otherArea.style.display = '';
} else {
otherArea.style.display = 'none';
}
});
});
// 文字数カウント
const textarea = document.querySelector('textarea[name="other_reason"]');
const charCount = document.getElementById('char-count');
if (textarea) {
textarea.addEventListener('input', function() {
charCount.textContent = this.value.length;
});
}
});
</script>
@endsection

View File

@ -16,7 +16,7 @@ return null;
@endphp
<main>
<header class="alert alert-success">
<h4 class="container">定期契約情報 > 定期契約情報を確認する</h4>
<h4 class="container">契約更新 > 定期契約を更新する</h4>
</header>
<section class="container mt30 mb50">
@if(count($contracts) > 0)
@ -294,7 +294,6 @@ return null;
</section>
</main>
@endsection
<!-- 解約についてモーダル -->
<div class="modal fade" id="cancelModal" tabindex="-1" aria-labelledby="cancelModalLabel" aria-hidden="true">
<div class="modal-dialog">
@ -313,9 +312,9 @@ return null;
border-radius: 2rem !important;
padding-left: 2rem !important;
padding-right: 2rem !important;
font-weight: 500;
min-width: 140px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
</style>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
@endsection

View File

@ -60,7 +60,7 @@
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user_category }}</h3>
</div>
@if ($user->ward_residents == 0 || $user->ward_residents == 2)
@if ($user_category === '一般')
<div class="col-12 col-md-3 offset-0 offset-md-2">
<label for="user_workplace">勤務先名</label>
</div>
@ -68,7 +68,7 @@
<h3>{{ $user->user_workplace }}</h3>
</div>
@endif
@if ($user->ward_residents == 1)
@if ($user_category === '学生')
<div class="col-12 col-md-3 offset-0 offset-md-2">
<label for="user_school">学校名</label>
</div>
@ -114,6 +114,9 @@
<div class="col-12 col-md-5 mt10">
<a class="btn btn-lg btn-block btn-outline-success" href="{{ url('/user/withdraw') }}">退会する</a>
</div>
<div class="col-12 col-md-4 offset-0 offset-md-4 mt50 mb50">
<a class="btn btn-lg btn-block btn-success" href="{{ url('/user/tag_reissue') }}">タグの再発行を行う</a>
</div>
</form>
</section>
</main>

View File

@ -0,0 +1,83 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">ユーザー情報確認 > タグ再発行申請</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="alert alert-success text-center pt20 pb10 mb30">
<h5>タグ再発行を行うお客様の情報をご確認ください。</h5>
<p>住所、電話番号、メールアドレス等に変更がある場合、再発行を申請する前にユーザー情報確認から<br class="pc">お客様の情報を最新状態へ更新をお願いいたします。</p>
</div>
<div class="row">
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_name">お名前</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_name }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_phonetic">フリガナ</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_phonetic }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_gender">性別</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_gender ?? '未入力' }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_regident">居住所</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_regident_zip }}{{ $user->user_regident_pre }}{{ $user->user_regident_city }}{{ $user->user_regident_add }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_homephone">自宅電話番号</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_homephone ?? '未入力' }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_mobile">携帯電話番号</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_mobile ?? '未入力' }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_primemail">メールアドレス</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_primemail }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_submail">予備メールアドレス</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3>{{ $user->user_submail ?? '未入力' }}</h3>
</div>
<div class="col-12 col-md-3 offset-0 offset-md-1">
<label for="user_pass">パスワード</label>
</div>
<div class="col-12 col-lg-6 mb10">
<h3></h3>
</div>
<div class="col-12 col-md-5 offset-0 offset-md-1 mt10">
<a href="{{ url('/user/edit') }}" class="btn btn-lg btn-block btn-success">ユーザー情報を変更する</a>
</div>
<div class="col-12 col-md-5 mt10">
<a href="{{ url('/user/tag_reissue/complete') }}" class="btn btn-lg btn-block btn-outline-success">再発行申請する</a>
</div>
<div class="col-12 col-md-6 offset-0 offset-md-3 mt50 mb50">
<a href="{{ url('/mypage') }}" class="btn btn-lg btn-block btn-outline-success">マイページへ戻る</a>
</div>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,17 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">ユーザー情報確認 > タグ再発行申請完了</h4>
</header>
<div class="row">
<div class="col-12 col-md-8 offset-0 offset-md-2 mt-4 mb30">
<h3 class="text-center">タグの再発行申請が完了しました。</h3>
<p class="mt30 text-center">現在オペレーターが確認中です。<br>タグの再発行までいましばらくお待ちください。</p>
<div class="col-12 col-md-6 offset-0 offset-md-3 mt50 mb50">
<a href="{{ url('/mypage') }}" class="btn btn-lg btn-block btn-outline-success">マイページへ戻る</a>
</div>
</div>
</div>
</main>
@endsection

View File

@ -0,0 +1,37 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">お知らせ > 過去のお知らせ</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-success">過去のお知らせ</h3>
</div>
<div class="row">
@forelse($informations as $information)
<div class="col-12 col-md-3 offset-0 offset-md-2">
<h5 class="text-success">
{{ \Carbon\Carbon::parse($information->entry_date)->format('Y年n月j日') }}
</h5>
</div>
<div class="col-12 col-lg-7 mb10">
<p class="h5 font-weight-normal">
{{ $information->user_information_history }}
</p>
</div>
@empty
<div class="col-12 text-center py-5">
<p>過去のお知らせはありません。</p>
</div>
@endforelse
</div>
<div class="d-flex justify-content-center mt-4">
{{ $informations->links('partials.paging') }}
</div>
<div class="col-12 col-md-6 offset-0 offset-md-3 mt-4 mb50">
<a href="{{ url('mypage') }}" class="btn btn-lg btn-block btn-outline-success">マイページへ戻る</a>
</div>
</section>
</main>
@endsection

View File

@ -0,0 +1,38 @@
@extends('layouts.app')
@section('content')
<main>
<header class="alert alert-success">
<h4 class="container">お知らせ > お知らせ一覧</h4>
</header>
<section id="" class="container mt30 mb50">
<div class="col-12 col-md-8 offset-0 offset-md-2 mb30">
<h3 class="text-center alert-success">お知らせ一覧</h3>
</div>
<div class="row">
@forelse($informations as $information)
<div class="col-12 col-md-3 offset-0 offset-md-2">
<h5 class="text-success">
{{ \Carbon\Carbon::parse($information->entry_date)->format('Y年n月j日') }}
</h5>
</div>
<div class="col-12 col-lg-7 mb10">
<p class="h5 font-weight-normal">
{{ $information->user_information_history }}
</p>
</div>
@empty
<div class="col-12 text-center py-5">
<p>お知らせはありません。</p>
</div>
@endforelse
<div class="col-12 col-md-5 offset-md-1 mt10">
<a href="{{ url('mypage') }}" class="btn btn-lg btn-block btn-outline-success">マイページへ戻る</a>
</div>
<div class="col-12 col-md-5 mt10">
<a href="{{ url('user_information/history') }}" class="btn btn-lg btn-block btn-outline-success">過去のお知らせ</a>
</div>
</div>
</section>
</main>
@endsection

View File

@ -1,4 +1,3 @@
<?php
use Illuminate\Http\Request;
@ -12,16 +11,20 @@ use App\Http\Controllers\InquiryConfirmController;
use App\Http\Controllers\LoginController;
use App\Http\Controllers\MemberRegistrationController;
use App\Http\Controllers\PasswordReminderController;
use App\Http\Controllers\MypageController;
use App\Http\Controllers\UserInfoController;
use App\Http\Controllers\UserEditController;
use App\Http\Controllers\UserEditConfirmController;
use App\Http\Controllers\UserWithdrawController;
use App\Http\Controllers\UserTagReissueController;
use App\Http\Controllers\RegularContractController;
use App\Http\Controllers\RegularContractCreateController;
use App\Http\Controllers\ParkingSearchController;
use App\Http\Controllers\ParkWaitlistController;
use App\Http\Controllers\ReceiptController;
use App\Http\Controllers\SealReissueController;
use App\Http\Controllers\ParkDetailController;
use App\Http\Controllers\UserInformationController;
// 画面遷移のみ
Route::get('/', function () { return view('general.swo1_1'); })->name('swo1_1');
@ -51,84 +54,70 @@ Route::get('/error', function () { return view('general.error'); })->name('error
// コントローラー経由
Route::post('/swo2_2', [MemberRegistrationController::class, 'sendMail'])->name('swo2_2');
Route::get('/swo2_3', [MemberRegistrationController::class, 'index'])->name('swo2_3')->middleware('signed');
Route::get('/swo2_3B', [MemberRegistrationController::class, 'indexBack'])->name('swo2_3B');
Route::post('/swo2_4', [MemberRegistrationController::class, 'confirm'])->name('swo2_4');
Route::post('/swo2_5', [MemberRegistrationController::class, 'complete'])->name('swo2_5');
Route::get('/swo4_1', [LoginController::class, 'login'])->name('swo4_1');
Route::get('/swo5_1', [ParkingSearchController::class, 'index'])->name('swo5_1');
Route::post('/swo5_2', [ParkingSearchController::class, 'search'])->name('swo5_2');
Route::get('/swo7_1', [InquiryConfirmController::class, 'index'])->name('swo7_1');
Route::post('/swo7_2',[InquiryConfirmController::class, 'confirm'])->name('swo7_2');
Route::post('/swo7_3',[InquiryConfirmController::class, 'complete'])->name('swo7_3');
Route::post('/swo8_3', [PasswordReminderController::class, 'sendMail'])->name('swo8_3');
// ログアウト処理
Route::get('/logout', function () {
// セッション全削除
session()->flush();
return redirect()->route('swo1_1');
})->name('logout');
// ログイン画面へのリダイレクト
Route::get('/login', function () {
return redirect()->route('swo8_1');
})->name('login');
// マイページ画面へのリダイレクト
Route::get('/mypage', function () {
return '
<div style="padding:2em;">
<h2>マイページ(仮)</h2>
<a href="' . route('user.info') . '" class="btn btn-primary">ユーザー情報を確認する</a>
</div>
';
})->name('mypage');
// マイページ
Route::get('/mypage', [MypageController::class, 'index'])->name('mypage');
// ユーザー情報確認画面
Route::get('/user/info', [UserInfoController::class, 'show'])
->name('user.info');
// ユーザー情報確認・編集
Route::get('/user/info', [UserInfoController::class, 'show'])->name('user.info');
Route::get('/user/edit', [UserEditController::class, 'show'])->name('user.edit');
Route::post('/user/edit', [UserEditController::class, 'update'])->name('user.edit.post');
Route::get('/user/edit/confirm', [UserEditConfirmController::class, 'show'])->name('user.confirm');
Route::post('/user/edit/submit', [UserEditConfirmController::class, 'submit'])->name('user.edit.submit');
Route::get('/user/edit/verify', [UserEditConfirmController::class, 'verify'])->name('user.edit.verify');
// ユーザー情報編集画面GET: 編集フォーム表示)
Route::get('/user/edit', [UserEditController::class, 'show'])
->name('user.edit');
// 退会
Route::get('/user/withdraw', [UserWithdrawController::class, 'showConfirm'])->name('user.withdraw');
Route::post('/user/withdraw/confirm', [UserWithdrawController::class, 'withdraw'])->name('user.withdraw.confirm');
// ユーザー情報編集POST: 編集内容保存)
Route::post('/user/edit', [UserEditController::class, 'update'])
->name('user.edit.post');
// ユーザー情報編集確認
Route::get('/user/edit/confirm', [UserEditConfirmController::class, 'show'])
->name('user.confirm');
// 入力内容確認画面から「変更を確定する」ボタン押下時(認証メール送信)
Route::post('/user/edit/submit', [UserEditConfirmController::class, 'submit'])
->name('user.edit.submit');
// 認証メール内URLクリック時変更確定処理
Route::get('/user/edit/verify', [UserEditConfirmController::class, 'verify'])
->name('user.edit.verify');
// 退会画面GET: 退会確認)
Route::get('/user/withdraw', [UserWithdrawController::class, 'showConfirm'])
->name('user.withdraw');
// 退会処理POST: 退会確定)
Route::post('/user/withdraw/confirm', [UserWithdrawController::class, 'withdraw'])
->name('user.withdraw.confirm');
// タグ再発行
Route::get('/user/tag_reissue', [UserTagReissueController::class, 'index'])->name('user.tag_reissue');
Route::get('/user/tag_reissue/complete', [UserTagReissueController::class, 'complete'])->name('user.tag_reissue.complete');
// 定期契約情報確認
Route::get('regular_contract/info', [RegularContractController::class, 'showInfo'])
->name('regular_contract.info');
Route::get('regular_contract/info', [RegularContractController::class, 'showInfo'])->name('regular_contract.info');
// 領収書宛名入力画面
Route::get('receipt/input/{contract_id}', [ReceiptController::class, 'input'])
->name('receipt.input');
Route::get('receipt/download/{contract_id}', [ReceiptController::class, 'download'])
->name('receipt.download');
// 領収書発行
Route::get('receipt/input/{contract_id}', [ReceiptController::class, 'input'])->name('receipt.input');
Route::get('receipt/download/{contract_id}', [ReceiptController::class, 'download'])->name('receipt.download');
Route::post('receipt/issue/{contract_id}', [ReceiptController::class, 'issue']);
// 新規定期契約画面
Route::get('regular_contract/create', [RegularContractCreateController::class, 'show'])
->name('regular_contract.create');
// シール再発行
Route::get('/seal/reissue/{contract_id}', [SealReissueController::class, 'index'])->name('seal.reissue');
Route::get('/seal/reissue/reason/{contract_id}', [SealReissueController::class, 'reason'])->name('seal.reissue.reason');
Route::post('/seal/reissue/complete/{contract_id}', [SealReissueController::class, 'complete'])->name('seal.reissue.complete');
// 新規定期契約
Route::get('regular_contract/create', [RegularContractCreateController::class, 'show'])->name('regular_contract.create');
Route::get('/api/park-detail/{park_id}', [ParkDetailController::class, 'show']);
Route::get('/regular-contract/regulationCheck', [RegularContractCreateController::class, 'regulationCheck']);
Route::get('/regular-contract/regulation', [RegularContractCreateController::class, 'showRegulation'])
->name('regular_contract.regulation');
Route::get('/regular-contract/regulation', [RegularContractCreateController::class, 'showRegulation'])->name('regular_contract.regulation');
Route::post('/regular-contract/insertRegulation', [RegularContractCreateController::class, 'insertRegulation']);
Route::get('/regular-contract/input', [RegularContractCreateController::class, 'showContractForm'])->name('regular_contract.input');
Route::post('/regular_contract/input/check', [RegularContractCreateController::class, 'inputCheck'])->name('regular_contract.input.check');
Route::get('/regular-contract/upload_identity_create', [RegularContractCreateController::class, 'showUploadIdentityCreate'])->name('regular_contract.upload_identity_create');
Route::post('regular_contract/confirm_upload_identity/{contract_id}', [RegularContractCreateController::class, 'confirmUploadIdentity'])->name('regular_contract.confirm_upload_identity');
// SHJ-1バッチ処理結果表示ページ
@ -136,42 +125,39 @@ Route::get('/regular-contract/upload_identity_success', [RegularContractCreateCo
Route::get('/regular-contract/upload_identity_fail', [RegularContractCreateController::class, 'showUploadIdentityFail'])->name('regular_contract.upload_identity_fail');
Route::get('regular_contract/create_confirm', [RegularContractCreateController::class, 'createConfirm'])->name('regular_contract.create_confirm');
Route::post('/regular_contract/create_confirm_next/{contract_id}', [RegularContractCreateController::class, 'createConfirmNext'])->name('regular_contract.create_confirm_next');
Route::post('regular_contract/create_select_period', [RegularContractCreateController::class, 'selectPeriod'])
->name('regular_contract.create_select_period');
Route::get('/regular_contract/create_confirm_error/{contract_id}', [RegularContractCreateController::class, 'createConfirmNext'])->name('regular_contract.create_confirm_error');
Route::post('/regular_contract/create_select_period', [RegularContractCreateController::class, 'selectPeriod'])->name('regular_contract.create_select_period');
// 定期契約更新
Route::get('regular_contract/update', [RegularContractController::class, 'showInfo'])
->name('regular_contract.update');
Route::get('regular_contract/update', [RegularContractController::class, 'showInfo'])->name('regular_contract.update');
Route::get('regular_contract/update/{contract_id}', [RegularContractController::class, 'update']);
// 契約区分確認
Route::get('regular_contract/confirm_category/{contract_id}', [RegularContractController::class, 'confirmCategory'])
->name('regular_contract.confirm_category');
Route::get('regular_contract/confirm_category_next/{contract_id}', [RegularContractController::class, 'confirmCategoryNext'])
->name('regular_contract.confirm_category_next');
// 本人確認書類アップロード
Route::get('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentity'])
->name('regular_contract.upload_identity');
// 本人確認書類確認中
Route::post('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentitySubmit'])
->name('regular_contract.upload_identity_submit');
// 利用期間選択
Route::get('regular_contract/select_period/{contract_id}', [RegularContractController::class, 'selectPeriod'])
->name('regular_contract.select_period');
Route::post('regular_contract/update_period', [RegularContractController::class, 'updatePeriod'])
->name('regular_contract.update_period');
Route::get('regular_contract/confirm_category/{contract_id}', [RegularContractController::class, 'confirmCategory'])->name('regular_contract.confirm_category');
Route::get('regular_contract/confirm_category_next/{contract_id}', [RegularContractController::class, 'confirmCategoryNext'])->name('regular_contract.confirm_category_next');
Route::get('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentity'])->name('regular_contract.upload_identity');
Route::post('regular_contract/upload_identity/{contract_id}', [RegularContractController::class, 'uploadIdentitySubmit'])->name('regular_contract.upload_identity_submit');
Route::get('regular_contract/select_period/{contract_id}', [RegularContractController::class, 'selectPeriod'])->name('regular_contract.select_period');
Route::post('regular_contract/update_period', [RegularContractController::class, 'updatePeriod'])->name('regular_contract.update_period');
// 定期契約履歴
Route::get('regular_contract/history', [RegularContractController::class, 'showHistory'])
->name('regular_contract.history');
Route::get('regular_contract/history', [RegularContractController::class, 'showHistory'])->name('regular_contract.history');
// 空き待ち状況確認画面
Route::get('park_waitlist', [ParkWaitlistController::class, 'index'])
->name('park_waitlist.index');
// 駐輪場検索
Route::get('park_search', [RegularContractCreateController::class, 'show'])->name('park_search');
Route::post('/login', function (Request $request) {
$user_id = $request->input('user_id');
Session::put('user_id', $user_id); // 入力されたIDをそのまま保存
return redirect('/user/info'); // 認証なしでリダイレクト
});
// 空き待ち状況確認
Route::get('park_waitlist', [ParkWaitlistController::class, 'index'])->name('park_waitlist.index');
Route::get('/park-waitlist/check', [ParkWaitlistController::class, 'check'])->name('park_waitlist.check');
Route::get('/park-waitlist/create', [ParkWaitlistController::class, 'create'])->name('park_waitlist.create');
Route::get('/api/park-detail-wait/{reserve_id}', [ParkDetailController::class, 'showWait']);
Route::get('/park_waitlist/cancel/{reserve_id}', [ParkWaitlistController::class, 'cancelConfirm'])->name('park_waitlist.cancel');
Route::post('/park_waitlist/cancel/{reserve_id}', [ParkWaitlistController::class, 'cancel'])->name('park_waitlist.cancel.post');
Route::get('/park_waitlist/cancel/complete', function () {
return view('park_waitlist.cancel_complete');
})->name('park_waitlist.cancel.complete');
// 会員へのお知らせ
Route::get('/user_information', [UserInformationController::class, 'index'])->name('user_information.index');
Route::get('/user_information/history', [UserInformationController::class, 'history'])->name('user_information.history');
// ウェルネット決済画面(仮)
Route::get('/wellnet/payment', function (): mixed {