前バージョン戻し
All checks were successful
Deploy main / deploy (push) Successful in 23s

This commit is contained in:
OU.ZAIKOU 2026-02-05 11:26:52 +09:00
parent db681b219a
commit 4fa9a8a297
3 changed files with 290 additions and 247 deletions

View File

@ -1,47 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\CityRequest;
use App\Models\City;
use App\Services\CityService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;
use App\Models\City;
final class CityController extends Controller
class CityController extends Controller
{
public function index(Request $request, CityService $service): View|RedirectResponse
public function list(Request $request)
{
$sort = (string) $request->input('sort', 'city_id');
$sortType = (string) $request->input('sort_type', 'asc');
$page = (int) $request->get('page', 1);
$sort = $request->input('sort', 'city_id');
$sortType = $request->input('sort_type', 'asc');
$page = $request->get('page', 1);
$menuAccessService = app(\App\Services\MenuAccessService::class);
// メニューアクセス制御: 非ソーリンユーザーは所属自治体のみ表示
$query = City::query();
if (!$menuAccessService->isSorin()) {
$operator = auth()->user();
if ($operator && isset($operator->management_id)) {
$query->where('management_id', $operator->management_id);
}
}
if ($request->filled('city_name')) {
$query->where('city_name', 'like', '%' . $request->input('city_name') . '%');
}
// 排序处理
// ソート処
if (!empty($sort)) {
$query->orderBy($sort, $sortType);
}
$list = $query->paginate(20);
// 页码越界处
// インデックス超過処
if ($list->total() > 0 && $page > $list->lastPage()) {
return redirect()->route('cities.index', [
return redirect()->route('city', [
'sort' => $sort,
'sort_type' => $sortType,
]);
}
return view('admin.cities.index', [
return view('admin.CityMaster.list', [
'isMethodPost' => $request->isMethod('post'),
'sort' => $sort,
'sort_type' => $sortType,
'list' => $list,
@ -49,7 +55,7 @@ final class CityController extends Controller
]);
}
public function create(): View
public function add(Request $request)
{
$inputs = [
'city_name' => '',
@ -112,6 +118,12 @@ final class CityController extends Controller
abort(404);
}
// メニューアクセス制御確認
$menuAccessService = app(\App\Services\MenuAccessService::class);
if (!$menuAccessService->canAccessCity($city->city_id)) {
abort(403, 'この自治体へのアクセス権限がありません。');
}
if ($request->isMethod('POST')) {
$rules = [
'city_name' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'],
@ -174,4 +186,53 @@ final class CityController extends Controller
return redirect()->route('city')->with('error', __('削除に失敗しました。'));
}
}
/**
* 自治体ダッシュボード
*/
public function dashboard(Request $request, $city_id)
{
$city = City::find($city_id);
if (!$city) {
return redirect()->route('city')->with('error', '指定された自治体が見つかりません。');
}
// この自治体に関連する駐輪場データを取得
$parks = \App\Models\Park::where('city_id', $city_id)->get();
$parkIds = $parks->pluck('park_id')->toArray();
// この自治体の統計情報を取得
$contractsCount = 0;
$usersCount = 0;
$waitingCount = 0;
if (!empty($parkIds)) {
// 契約数を取得
$contractsCount = \App\Models\RegularContract::whereIn('park_id', $parkIds)->count();
// この自治体の駐輪場で契約しているユニークユーザー数を取得
$userIds = \App\Models\RegularContract::whereIn('park_id', $parkIds)
->distinct()
->pluck('user_id')
->toArray();
$usersCount = count(array_filter($userIds));
// 予約待ち人数を取得valid_flag = 1 かつ reserve_order が設定されているもの)
$waitingCount = DB::table('reserve')
->whereIn('park_id', $parkIds)
->where('valid_flag', 1)
->whereNotNull('reserve_order')
->where('reserve_order', '>', 0)
->count();
}
$stats = [
'parks_count' => $parks->count(),
'contracts_count' => $contractsCount,
'users_count' => $usersCount,
'waiting_count' => $waitingCount,
];
return view('admin.CityMaster.dashboard', compact('city', 'parks', 'stats'));
}
}

View File

@ -300,6 +300,7 @@
<a href="{{ route('tagissue', ['city_id' => $city->city_id]) }}" class="nav-link @if ($currentRoute === 'tagissue') active @endif">
<span style="margin-left:20px;">タグ発行キュー処理、履歴表示</span>
</a>
</li>
</ul>
</li>
@ -522,54 +523,62 @@
</ul>
</li>
<!-- SWA-39〜SWA-42: システムマスタ -->
@php
// 利用者マスタ:ルート名がここに含まれている場合、展開&ハイライト
$userGroupRoutes = [
'users', // 利用者マスタ
'regularcontracts', // 定期契約マスタ
'reserves', // 定期予約マスタ
'usertypes', // 定期予約マスタ
];
$systemRoutes = ['opes', 'devices', 'operator_ques', 'settings', 'mail_templates'];
@endphp
<li
class="nav-item has-treeview @if(in_array(app('router')->currentRouteName(), $userGroupRoutes)) menu-open @endif">
<a href="#"
class="nav-link @if(in_array(app('router')->currentRouteName(), $userGroupRoutes)) active @endif">
<i class="nav-icon fa fa-dashboard"></i>
<p>
利用者マスタ
<i class="right fa fa-angle-down"></i>
</p>
<li class="nav-item has-treeview @if (in_array($current, $systemRoutes)) menu-open @endif">
<a href="#" class="nav-link @if (in_array($current, $systemRoutes)) active @endif">
<i class="nav-icon fa fa-cogs"></i>
<p>システムマスタ<i class="right fa fa-angle-down"></i></p>
</a>
<ul class="nav nav-treeview" @if(in_array(app('router')->currentRouteName(), $userGroupRoutes)) style="display: block;" @else style="display: none;" @endif>
<ul class="nav nav-treeview" style="display: @if (in_array($current, $systemRoutes)) block @else none @endif;">
<li class="nav-item">
<a href="{{ route('users') }}"
class="nav-link @if(app('router')->is('users')) active @endif">
<span style="margin-left:20px;">利用者マスタ</span>
<a href="{{ route('opes') }}" class="nav-link @if ($current === 'opes') active @endif">
<span style="margin-left:20px;">オペレーターマスタ</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('regularcontracts') }}"
class="nav-link @if(app('router')->is('regularcontracts')) active @endif">
<span style="margin-left:20px;">定期契約マスタ</span>
<a href="{{ route('devices') }}" class="nav-link @if ($current === 'devices') active @endif">
<span style="margin-left:20px;">デバイス管理マスタ</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('reserves') }}"
class="nav-link @if(app('router')->is('reserves')) active @endif">
<span style="margin-left:20px;">定期予約マスタ</span>
<a href="{{ route('operator_ques') }}" class="nav-link @if ($current === 'operator_ques') active @endif">
<span style="margin-left:20px;">オペレータキュー</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('usertypes') }}"
class="nav-link @if(app('router')->is('usertypes')) active @endif">
<span style="margin-left:20px;">利用者分類マスタ</span>
<a href="{{ route('mail_templates') }}" class="nav-link @if ($current === 'mail_templates') active @endif">
<span style="margin-left:20px;">メール送信テンプレート</span>
</a>
</li>
</ul>
</li>
<!-- SWA-43〜SWA-45: マスタ管理 -->
@php
$masterRoutes = ['inv_settings', 'lottery.master', 'lottery.setting'];
@endphp
<li class="nav-item has-treeview @if (in_array($current, $masterRoutes)) menu-open @endif">
<a href="#" class="nav-link @if (in_array($current, $masterRoutes)) active @endif">
<i class="nav-icon fa fa-object-group"></i>
<p>マスタ管理<i class="right fa fa-angle-down"></i></p>
</a>
<ul class="nav nav-treeview" style="display: @if (in_array($current, $masterRoutes)) block @else none @endif;">
<li class="nav-item">
<a href="{{ route('inv_settings') }}" class="nav-link @if ($current === 'inv_settings') active @endif">
<span style="margin-left:20px;">インボイス設定</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('lottery.master') }}" class="nav-link @if ($current === 'lottery.master') active @endif">
<span style="margin-left:20px;">駐輪場抽選応募マスタ</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('lottery.setting') }}" class="nav-link @if ($current === 'lottery.setting') active @endif">
<span style="margin-left:20px;">抽選申込設定マスタ</span>
</a>
</li>
</ul>
@ -599,104 +608,121 @@
$masterRoutes = ['inv_settings', 'lottery.master', 'lottery.setting'];
@endphp
<!-- 駐輪場マスタ -->
<li class="nav-item has-treeview @if(in_array($current, $parkingRoutes)) menu-open @endif">
<a href="#" class="nav-link @if(in_array($current, $parkingRoutes)) active @endif">
<i class="nav-icon fa fa-th"></i>
<p>
{{ __("駐輪場マスタ") }}
<i class="right fa fa-angle-down"></i>
</p>
<!-- SWA-03, SWA-04: ホーム -->
<li class="nav-item has-treeview @if (in_array($current, $homeRoutes)) menu-open @endif">
<a href="#" class="nav-link @if (in_array($current, $homeRoutes)) active @endif">
<i class="nav-icon fa fa-home"></i>
<p>ホーム<i class="right fa fa-angle-down"></i></p>
</a>
<ul class="nav nav-treeview"
style="display: @if(in_array($current, $parkingRoutes)) block @else none @endif;">
<ul class="nav nav-treeview" style="display: @if (in_array($current, $homeRoutes)) block @else none @endif;">
<li class="nav-item">
<a href="{{ route('parks') }}"
class="nav-link @if($current === 'parks') active @endif">
<span style="margin-left:20px;">{{ __("駐輪場マスタ") }}</span>
<a href="{{ route('dashboard') }}" class="nav-link @if ($current === 'dashboard') active @endif">
<span style="margin-left:20px;">ダッシュボード</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('city') }}"
class="nav-link @if($current === 'city') active @endif">
<span style="margin-left:20px;">{{ __("市区マスタ") }}</span>
<a href="{{ route('information') }}" class="nav-link @if ($current === 'information') active @endif">
<span style="margin-left:20px;">常時表示インフォメーション</span>
</a>
</li>
</ul>
</li>
<!-- SWA-05, SWA-06, SWA-07: タグ・シール管理 -->
<li class="nav-item has-treeview @if (in_array($current, $tagSealRoutes)) menu-open @endif">
<a href="#" class="nav-link @if (in_array($current, $tagSealRoutes)) active @endif">
<i class="nav-icon fa fa-repeat"></i>
<p>タグ・シール管理<i class="right fa fa-angle-down"></i></p>
</a>
<ul class="nav nav-treeview" style="display: @if (in_array($current, $tagSealRoutes)) block @else none @endif;">
<li class="nav-item">
<a href="{{ route('tag.reissue') }}" class="nav-link @if ($current === 'tag.reissue') active @endif">
<span style="margin-left:20px;">タグ再発行</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('pricelist') }}"
class="nav-link @if($current === 'pricelist') active @endif">
<span style="margin-left:20px;">{{ __("料金一覧表") }}</span>
<a href="{{ route('seal.reissue') }}" class="nav-link @if ($current === 'seal.reissue') active @endif">
<span style="margin-left:20px;">シール再発行</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('prices') }}"
class="nav-link @if($current === 'prices') active @endif">
<span style="margin-left:20px;">{{ __("駐輪場所、料金マスタ") }}</span>
<a href="{{ route('seals') }}" class="nav-link @if ($current === 'seals') active @endif">
<span style="margin-left:20px;">シール発行履歴</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('psections') }}"
class="nav-link @if($current === 'psections') active @endif">
<span style="margin-left:20px;">{{ __("車種区分マスタ") }}</span>
<a href="{{ route('tagissue')}}" class="nav-link @if ($current === 'tagissue') active @endif">
<span style="margin-left:20px;">タグ発行キュー処理、履歴表示</span>
</a>
</li>
</ul>
</li>
<!-- SWA-08〜SWA-15: 定期駐輪管理 -->
<li class="nav-item has-treeview @if (in_array($current, $parkingRoutes)) menu-open @endif">
<a href="#" class="nav-link @if (in_array($current, $parkingRoutes)) active @endif">
<i class="nav-icon fa fa-repeat"></i>
<p>定期駐輪管理<i class="right fa fa-angle-down"></i></p>
</a>
<ul class="nav nav-treeview" style="display: @if (in_array($current, $parkingRoutes)) block @else none @endif;">
<li class="nav-item">
<a href="{{ route('contractor') }}" class="nav-link @if ($current === 'contractor') active @endif">
<span style="margin-left:20px;">契約者一覧</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('ptypes') }}"
class="nav-link @if($current === 'ptypes') active @endif">
<span style="margin-left:20px;">{{ __("駐輪分類マスタ") }}</span>
<a href="{{ route('contractor_List') }}" class="nav-link @if ($current === 'contractor_List') active @endif">
<span style="margin-left:20px;">未更新者一覧</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('zones') }}"
class="nav-link @if($current === 'zones') active @endif">
<span style="margin-left:20px;">{{ __("ゾーンマスタ") }}</span>
<a href="{{ route('update_candidate') }}" class="nav-link @if ($current === 'update_candidate') active @endif">
<span style="margin-left:20px;">更新予定者一覧</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('pplaces') }}"
class="nav-link @if($current === 'pplaces') active @endif">
<span style="margin-left:20px;">{{ __("駐輪車室マスタ") }}</span>
<a href="{{ route('reservation') }}" class="nav-link @if ($current === 'reservation') active @endif">
<span style="margin-left:20px;">予約者一覧</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('stations') }}"
class="nav-link @if($current === 'stations') active @endif">
<span style="margin-left:20px;">{{ __("近傍駅マスタ") }}</span>
<a href="{{ route('personal') }}" class="nav-link @if ($current === 'personal') active @endif">
<span style="margin-left:20px;">本人確認手動処理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('regular_types') }}"
class="nav-link @if($current === 'regular_types') active @endif">
<span style="margin-left:20px;">{{ __("定期種別マスタ") }}</span>
<a href="{{ route('refund') }}" class="nav-link @if ($current === 'refund') active @endif">
<span style="margin-left:20px;">返金処理</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('terms') }}"
class="nav-link @if($current === 'terms') active @endif">
<span style="margin-left:20px;">{{ __("利用規約マスタ") }}</span>
</a>
</li>
<!-- <li class="nav-item">
<a href="{{ route('jurisdiction_parkings') }}" class="nav-link @if($current === 'jurisdiction_parkings') active @endif">
<span style="margin-left:20px;">{{ __("管轄駐輪場") }}</span>
</a>
</li> -->
<li class="nav-item">
<a href="{{ route('print_areas') }}"
class="nav-link @if($current === 'print_areas') active @endif">
<span style="margin-left:20px;">{{ __("シール印刷範囲マスタ") }}</span>
<a href="{{ route('periodical') }}" class="nav-link @if ($current === 'periodical') active @endif">
<span style="margin-left:20px;">定期利用・契約状況</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('contract_allowable_cities') }}"
class="nav-link @if($current === 'contract_allowable_cities') active @endif">
<span style="margin-left:20px;">{{ __("契約許容市区マスタ") }}</span>
<a href="{{ route('using_status') }}" class="nav-link @if ($current === 'using_status') active @endif">
<span style="margin-left:20px;">区画別利用率状況</span>
</a>
</li>
</ul>
</li>
<!-- SWA-17, SWA-18: 集計業務 -->
<li class="nav-item has-treeview @if (in_array($current, $aggregateRoutes)) menu-open @endif">
<a href="#" class="nav-link @if (in_array($current, $aggregateRoutes)) active @endif">
<i class="nav-icon fa fa-calculator"></i>
<p>集計業務<i class="right fa fa-angle-down"></i></p>
</a>
<ul class="nav nav-treeview" style="display: @if (in_array($current, $aggregateRoutes)) block @else none @endif;">
<li class="nav-item">
<a href="{{ route('sales.report') }}" class="nav-link @if ($current === 'sales.report') active @endif">
<span style="margin-left:20px;">売上年報/月報/日報</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('managers') }}"
class="nav-link @if($current === 'managers') active @endif">
<span style="margin-left:20px;">{{ __("駐輪場管理者マスタ") }}</span>
<a href="{{ route('sales.detail') }}" class="nav-link @if ($current === 'sales.detail') active @endif">
<span style="margin-left:20px;">売上詳細</span>
</a>
</li>
</ul>

View File

@ -178,6 +178,12 @@ Route::middleware('auth')->group(function () {
// sou end
// ou start
// 自治体ダッシュボード
Route::get('/city/{city_id}/dashboard', [CityController::class, 'dashboard'])
->where(['city_id' => '[0-9]+'])
->name('city_dashboard')
->middleware('check.city.access');
// 市区マスタ
Route::match(['get', 'post'], '/city', [CityController::class, 'list'])->name('city');
Route::match(['get', 'post'], '/city/add', [CityController::class, 'add'])->name('city_add');
@ -185,48 +191,15 @@ Route::middleware('auth')->group(function () {
Route::match(['get', 'post'], '/city/info/{id}', [CityController::class, 'info'])->where(['id' => '[0-9]+'])->name('city_info');
Route::match(['get', 'post'], '/city/delete', [CityController::class, 'delete'])->name('city_delete');
// 駐輪場マスタparks.*
Route::prefix('parks')->group(function () {
// 一覧(画面)
Route::get('/', [ParkController::class, 'index'])
->name('parks.index');
// 新規(画面)
Route::get('/create', [ParkController::class, 'create'])
->name('parks.create');
// 新規(登録)
Route::post('/', [ParkController::class, 'store'])
->name('parks.store');
// 編集(画面)
Route::get('/{id}/edit', [ParkController::class, 'edit'])
->whereNumber('id')
->name('parks.edit');
// 編集(更新)
Route::post('/{id}', [ParkController::class, 'update'])
->whereNumber('id')
->name('parks.update');
// 削除(複数削除 pk[]
Route::post('/delete', [ParkController::class, 'destroy'])
->name('parks.destroy');
// CSV 出力(必要なら)
Route::post('/export', [ParkController::class, 'export'])
->name('parks.export');
// CSV 取込(必要なら)
Route::post('/import', [ParkController::class, 'import'])
->name('parks.import');
// 重複チェック(必要なら)
Route::post('/check-duplicate', [ParkController::class, 'checkDuplicate'])
->name('parks.check_duplicate');
});
// 駐輪場マスタ
Route::get('/parks', [ParkController::class, 'list'])->name('parks');
Route::get('/parks/add', [ParkController::class, 'add'])->name('parks.add');
Route::post('/parks/add', [ParkController::class, 'add'])->name('parks.store');
Route::match(['get', 'post', 'put'], '/parks/edit/{id}', [ParkController::class, 'edit'])->name('parks.edit');
Route::put('/parks/edit/{id}', [ParkController::class, 'edit'])->name('parks.update');
Route::match(['get', 'post'], '/parks/delete', [ParkController::class, 'delete'])->name('parks.delete');
Route::get('/parks/export', [ParkController::class, 'export'])->name('parks.export');
Route::post('/parks/check-duplicate', [App\Http\Controllers\Admin\ParkController::class, 'checkDuplicate'])->name('parks.check_duplicate');
// 料金一覧表マスタ
Route::match(['get', 'post'], '/admin/pricelist', [PriceListController::class, 'list'])->name('pricelist');
@ -289,40 +262,12 @@ Route::middleware('auth')->group(function () {
Route::match(['get', 'post'], '/regularcontracts/info/{contract_id}', [RegularContractController::class, 'info'])->where(['contract_id' => '[0-9]+'])->name('regularcontracts_info');
Route::match(['get', 'post'], '/regularcontracts/delete', [RegularContractController::class, 'delete'])->name('regularcontracts_delete');
// 定期予約マスタreserves.*
Route::prefix('reserves')->group(function () {
// 一覧(画面)
Route::get('/', [ReservesController::class, 'index'])
->name('reserves.index');
// 新規(画面)
Route::get('/create', [ReservesController::class, 'create'])
->name('reserves.create');
// 新規(登録)
Route::post('/', [ReservesController::class, 'store'])
->name('reserves.store');
// 編集(画面)
Route::get('/{reserve_id}/edit', [ReservesController::class, 'edit'])
->whereNumber('reserve_id')
->name('reserves.edit');
// 編集(更新)
Route::post('/{reserve_id}', [ReservesController::class, 'update'])
->whereNumber('reserve_id')
->name('reserves.update');
// 削除(複数削除 pk[]
Route::post('/delete', [ReservesController::class, 'destroy'])
->name('reserves.destroy');
// エクスポート
Route::post('/export', [ReservesController::class, 'export'])
->name('reserves.export');
});
// 定期予約マスタ
Route::match(['get', 'post'], '/reserves', [ReservesController::class, 'list'])->name('reserves');
Route::match(['get', 'post'], '/reserves/add', [ReservesController::class, 'add'])->name('reserves_add');
Route::match(['get', 'post'], '/reserves/edit/{reserve_id}', [ReservesController::class, 'edit'])->name('reserves_edit');
Route::match(['get', 'post'], '/reserves/delete', [ReservesController::class, 'delete'])->name('reserves_delete');
Route::match(['get', 'post'], '/reserves/export', [ReservesController::class, 'export'])->name('reserves_export');
// 利用者分類マスタ
Route::match(['get', 'post'], '/usertypes', [UsertypeController::class, 'list'])->name('usertypes');
@ -333,6 +278,17 @@ Route::middleware('auth')->group(function () {
Route::match(['get', 'post'], '/usertypes/import', [UsertypeController::class, 'import'])->name('usertypes_import');
Route::match(['get', 'post'], '/usertypes/export', [UsertypeController::class, 'export'])->name('usertypes_export');
// 減免確認マスタ
Route::get('/reduction-confirm-master', [ReductionConfirmMasterController::class, 'list'])->name('reduction_confirm_list');
Route::post('/reduction-confirm-master', [ReductionConfirmMasterController::class, 'store'])->name('reduction_confirm_store');
// 駐輪規定マスタ
Route::match(['get', 'post'], '/admin/parking-regulations', [ParkingRegulationsController::class, 'list'])->name('parking_regulations_list');
Route::match(['get', 'post'], '/admin/parking-regulations/add', [ParkingRegulationsController::class, 'add'])->name('parking_regulations_add');
Route::match(['get', 'post'], '/admin/parking-regulations/edit/{seq}', [ParkingRegulationsController::class, 'edit'])->name('parking_regulations_edit')->where(['seq' => '[0-9]+']);
Route::post('/admin/parking-regulations/update/{seq}', [ParkingRegulationsController::class, 'update'])->name('parking_regulations_update');
Route::post('/admin/parking-regulations/delete', [ParkingRegulationsController::class, 'delete'])->name('parking_regulations_delete');
// 契約者一覧