市区マスタ機能のメソッド整理し、ルーティングを更新

This commit is contained in:
kin.rinzen 2026-01-23 18:20:48 +09:00
parent f850707848
commit 466dc98e17
8 changed files with 217 additions and 172 deletions

View File

@ -1,44 +1,50 @@
<?php <?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator; use App\Http\Requests\CityRequest;
use Illuminate\Support\Facades\DB;
use App\Models\City; use App\Models\City;
use App\Services\CityService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CityController extends Controller final class CityController extends Controller
{ {
public function list(Request $request) public function index(Request $request, CityService $service): View|RedirectResponse
{ {
$sort = $request->input('sort', 'city_id'); $sort = (string) $request->input('sort', 'city_id');
$sortType = $request->input('sort_type', 'asc'); $sortType = (string) $request->input('sort_type', 'asc');
$page = $request->get('page', 1); $page = (int) $request->get('page', 1);
$query = City::query(); // ソート許可(安全 + 規約)
$sortable = ['city_id', 'city_name', 'print_layout', 'city_remarks', 'created_at', 'updated_at'];
if ($request->filled('city_name')) { if (!in_array($sort, $sortable, true)) {
$query->where('city_name', 'like', '%' . $request->input('city_name') . '%'); $sort = 'city_id';
} }
// 排序处理 $sortType = strtolower($sortType);
if (!empty($sort)) { if (!in_array($sortType, ['asc', 'desc'], true)) {
$query->orderBy($sort, $sortType); $sortType = 'asc';
} }
$list = $query->paginate(20); $list = $service->paginateList(
$request->input('city_name'),
$sort,
$sortType
);
// 页码越界处理
if ($list->total() > 0 && $page > $list->lastPage()) { if ($list->total() > 0 && $page > $list->lastPage()) {
return redirect()->route('city', [ return redirect()->route('cities.index', [
'sort' => $sort, 'sort' => $sort,
'sort_type' => $sortType, 'sort_type' => $sortType,
]); ]);
} }
return view('admin.CityMaster.list', [ return view('admin.cities.index', [
'isMethodPost' => $request->isMethod('post'),
'sort' => $sort, 'sort' => $sort,
'sort_type' => $sortType, 'sort_type' => $sortType,
'list' => $list, 'list' => $list,
@ -46,129 +52,58 @@ class CityController extends Controller
]); ]);
} }
public function add(Request $request) public function create(): View
{ {
$inputs = [ return view('admin.cities.create', [
'city_name' => '', 'record' => new City(),
'print_layout' => '',
'city_user' => '',
'city_remarks' => '',
];
if ($request->isMethod('POST')) {
$rules = [
'city_name' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'],
'print_layout' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'],
'city_user' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'],
'city_remarks' => ['nullable', 'string', 'max:20'],
];
$messages = [
'city_name.required' => '市区名は必須です。',
'city_name.regex' => '市区名は全角で入力してください。',
'print_layout.required' => '印字レイアウトファイルは必須です。',
'print_layout.regex' => '印字レイアウトファイルは全角で入力してください。',
'city_user.required' => '顧客M入力不要フィールドIDは必須です。',
'city_user.regex' => '顧客M入力不要フィールドIDは全角で入力してください。',
'city_remarks.max' => '備考は20文字以内で入力してください。',
];
$validator = Validator::make($request->all(), $rules, $messages);
$inputs = array_merge($inputs, $request->all());
if (!$validator->fails()) {
$maxId = DB::table('city')->max('city_id');
$newCityId = $maxId ? $maxId + 1 : 1;
$city = new City();
$city->city_id = $newCityId;
$city->fill($request->only([
'city_name',
'print_layout',
'city_user',
'city_remarks',
]));
if ($city->save()) {
$request->session()->flash('success', __('登録に成功しました'));
return redirect()->route('city');
} else {
$request->session()->flash('error', __('登録に失敗しました'));
}
} else {
$inputs['errorMsg'] = $validator->errors()->all();
}
}
return view('admin.CityMaster.add', $inputs);
}
public function edit(Request $request, $pk, $view = '')
{
$city = City::find($pk);
if (!$city) {
abort(404);
}
if ($request->isMethod('POST')) {
$rules = [
'city_name' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'],
'print_layout' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'],
'city_user' => ['required', 'string', 'max:10', 'regex:/^[^ -~。-゚]+$/u'],
'city_remarks' => ['nullable', 'string', 'max:20'],
];
$messages = [
'city_name.required' => '市区名は必須です。',
'city_name.regex' => '市区名は全角で入力してください。',
'print_layout.required' => '印字レイアウトファイルは必須です。',
'print_layout.regex' => '印字レイアウトファイルは全角で入力してください。',
'city_user.required' => '顧客M入力不要フィールドIDは必須です。',
'city_user.regex' => '顧客M入力不要フィールドIDは全角で入力してください。',
'city_remarks.max' => '備考は20文字以内で入力してください。',
];
$validator = Validator::make($request->all(), $rules, $messages);
if (!$validator->fails()) {
$city->fill($request->only([
'city_name',
'print_layout',
'city_user',
'city_remarks',
]));
if ($city->save()) {
$request->session()->flash('success', __('更新に成功しました'));
return redirect()->route('city');
} else {
$request->session()->flash('error', __('更新に失敗しました'));
}
} else {
return view('admin.CityMaster.edit', [
'city' => $city,
'errorMsg' => $validator->errors()->all(),
]);
}
}
return view($view ?: 'admin.CityMaster.edit', [
'city' => $city,
]); ]);
} }
public function info(Request $request, $pk) public function store(CityRequest $request, CityService $service): RedirectResponse
{ {
return $this->edit($request, $pk, 'CityMaster.info'); $service->create($request->validated());
return redirect()
->route('cities.index')
->with('success', __('登録に成功しました'));
} }
public function delete(Request $request) public function edit(int $id): View
{ {
$arr_pk = $request->get('pk'); $city = City::findOrFail($id);
if (!$arr_pk) {
return redirect()->route('city')->with('error', __('削除する市区を選択してください。')); return view('admin.cities.edit', [
} 'record' => $city,
if (City::destroy($arr_pk)) { ]);
return redirect()->route('city')->with('success', __("削除が完了しました。")); }
} else {
return redirect()->route('city')->with('error', __('削除に失敗しました。')); public function update(CityRequest $request, int $id, CityService $service): RedirectResponse
{
$city = City::findOrFail($id);
$service->update($city, $request->validated());
return redirect()
->route('cities.index')
->with('success', __('更新に成功しました'));
}
public function destroy(Request $request): RedirectResponse
{
$ids = $request->input('pk');
// pk が単体でも配列でも受けられるようにする(編集画面/一覧画面両対応)
if ($ids === null || $ids === '' || $ids === []) {
return redirect()
->route('cities.index')
->with('error', __('削除する市区を選択してください。'));
} }
$ids = is_array($ids) ? $ids : [$ids];
$deleted = City::destroy($ids);
return $deleted
? redirect()->route('cities.index')->with('success', __('削除が完了しました。'))
: redirect()->route('cities.index')->with('error', __('削除に失敗しました。'));
} }
} }

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
final class CityRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'city_name' => ['required', 'string', 'max:20', 'regex:/^[^ -~。-゚]+$/u'],
'print_layout' => ['required', 'string', 'max:255', 'regex:/^[A-Za-z0-9]+$/'],
'city_remarks' => ['nullable', 'string', 'max:255'],
];
}
public function messages(): array
{
return [
'city_name.required' => '市区名は必須です。',
'city_name.regex' => '市区名は全角文字で入力してください。',
'city_name.max' => '市区名は20文字以内で入力してください。',
'print_layout.required' => '印字レイアウトファイルは必須です。',
'print_layout.regex' => '印字レイアウトファイルは半角英数字で入力してください。',
'print_layout.max' => '印字レイアウトファイルは255文字以内で入力してください。',
'city_remarks.max' => '備考は255文字以内で入力してください。',
];
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\City;
use App\Utils;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
final class CityService
{
public function paginateList(
?string $cityName,
string $sort,
string $sortType
): LengthAwarePaginator {
$query = City::query();
if ($cityName !== null && $cityName !== '') {
$query->where('city_name', 'like', '%' . $cityName . '%');
}
$query->orderBy($sort, $sortType);
return $query->paginate(Utils::item_per_page);
}
public function create(array $validated): City
{
return City::create($this->payload($validated));
}
public function update(City $city, array $validated): City
{
$city->fill($this->payload($validated));
$city->save();
return $city;
}
private function payload(array $validated): array
{
return [
'city_name' => $validated['city_name'],
'print_layout' => $validated['print_layout'],
'city_remarks' => $validated['city_remarks'] ?? null,
];
}
}

View File

@ -89,20 +89,6 @@
</div> </div>
</div> </div>
<!-- {{-- 顧客M入力不要フィールドID --}}
<div class="form-group col-3">
<label class="form-label required">{{ __('顧客M入力不要フィールドID') }}</label>
</div>
<div class="form-group col-9">
<div class="input-group">
<input type="text"
name="city_user"
class="form-control form-control-lg"
placeholder="{{ __('顧客M入力不要フィールドID') }}"
value="{{ old('city_user', $record->city_user ?? ($city_user ?? '')) }}">
</div>
</div> -->
{{-- 備考 --}} {{-- 備考 --}}
<div class="form-group col-3"> <div class="form-group col-3">
<label>{{ __('備考') }}</label> <label>{{ __('備考') }}</label>

View File

@ -11,9 +11,11 @@
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<ol class="breadcrumb float-sm-right text-sm"> <ol class="breadcrumb float-sm-right text-sm">
<li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li>
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a href="{{ route('city') }}">市区マスタ</a> <a href="{{ route('home') }}">ホーム</a>
</li>
<li class="breadcrumb-item">
<a href="{{ route('cities.index') }}">市区マスタ</a>
</li> </li>
<li class="breadcrumb-item active">新規</li> <li class="breadcrumb-item active">新規</li>
</ol> </ol>
@ -30,9 +32,10 @@
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card"> <div class="card">
{{-- 新規登録フォーム --}}
<form id="form_add" <form id="form_add"
method="post" method="POST"
action="{{ route('city_add') }}?back={{ urlencode(request()->get('back', request()->fullUrl())) }}" action="{{ route('cities.store') }}?back={{ urlencode(request()->get('back', request()->fullUrl())) }}"
enctype="multipart/form-data"> enctype="multipart/form-data">
@csrf @csrf

View File

@ -13,7 +13,7 @@
<ol class="breadcrumb float-sm-right text-sm"> <ol class="breadcrumb float-sm-right text-sm">
<li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li> <li class="breadcrumb-item"><a href="{{ route('home') }}">ホーム</a></li>
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a href="{{ route('city') }}">市区マスタ</a> <a href="{{ route('cities.index') }}">市区マスタ</a>
</li> </li>
<li class="breadcrumb-item active">編集</li> <li class="breadcrumb-item active">編集</li>
</ol> </ol>
@ -33,9 +33,9 @@
{{-- 編集フォーム --}} {{-- 編集フォーム --}}
<form id="form_edit" <form id="form_edit"
action="{{ route('city_edit', ['id' => $record->city_id]) }}?back={{ urlencode(request()->get('back', request()->fullUrl())) }}" action="{{ route('cities.update', ['id' => $record->city_id]) }}?back={{ urlencode(request()->get('back', request()->fullUrl())) }}"
method="POST" method="POST"
enctype="multipart/form-data"> enctype="multipart/form-data">
@csrf @csrf
@include('admin.cities._form', [ @include('admin.cities._form', [
@ -46,7 +46,7 @@
{{-- 削除フォーム(非表示) --}} {{-- 削除フォーム(非表示) --}}
<form id="form_delete" <form id="form_delete"
action="{{ route('city_delete') }}" action="{{ route('cities.destroy') }}"
method="POST" method="POST"
style="display:none;"> style="display:none;">
@csrf @csrf
@ -59,5 +59,4 @@
</div> </div>
</section> </section>
<!-- /.content -->
@endsection @endsection

View File

@ -24,7 +24,7 @@
<div class="container-fluid"> <div class="container-fluid">
{{-- 並び替え用 hidden --}} {{-- 並び替え用 hidden --}}
<form method="GET" action="{{ route('city') }}" id="list-form"> <form method="GET" action="{{ route('cities.index') }}" id="list-form">
<input type="hidden" name="sort" id="sort" value="{{ $sort ?? '' }}"> <input type="hidden" name="sort" id="sort" value="{{ $sort ?? '' }}">
<input type="hidden" name="sort_type" id="sort_type" value="{{ $sort_type ?? '' }}"> <input type="hidden" name="sort_type" id="sort_type" value="{{ $sort_type ?? '' }}">
</form> </form>
@ -33,7 +33,7 @@
<div class="col-lg-12 mb-3 px-0"> <div class="col-lg-12 mb-3 px-0">
<button type="button" <button type="button"
class="btn btn-sm btn-primary mr10" class="btn btn-sm btn-primary mr10"
onclick="location.href='{{ route('city_add') }}?back={{ urlencode(request()->fullUrl()) }}'"> onclick="location.href='{{ route('cities.create') }}?back={{ urlencode(request()->fullUrl()) }}'">
新規 新規
</button> </button>
@ -62,7 +62,7 @@
</div> </div>
</div> </div>
{{-- フラッシュメッセージ(利用者分類マスタと同じ見せ方) --}} {{-- フラッシュメッセージ --}}
<div class="form col-lg-12 px-0"> <div class="form col-lg-12 px-0">
@if(Session::has('success')) @if(Session::has('success'))
<div class="alert alert-success alert-dismissible"> <div class="alert alert-success alert-dismissible">
@ -85,7 +85,7 @@
{{-- テーブル --}} {{-- テーブル --}}
<div class="col-lg-12 mb20 px-0"> <div class="col-lg-12 mb20 px-0">
<div class="table-responsive"> <div class="table-responsive">
<form id="form_delete" method="POST" action="{{ route('city_delete') }}"> <form id="form_delete" method="POST" action="{{ route('cities.destroy') }}">
@csrf @csrf
<table class="table table-bordered dataTable text-nowrap"> <table class="table table-bordered dataTable text-nowrap">
@ -118,7 +118,7 @@
<td style="background-color:#faebd7;"> <td style="background-color:#faebd7;">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<input type="checkbox" name="pk[]" value="{{ $city->city_id }}"> <input type="checkbox" name="pk[]" value="{{ $city->city_id }}">
<a href="{{ route('city_edit', ['id' => $city->city_id]) }}?back={{ urlencode(request()->fullUrl()) }}" <a href="{{ route('cities.edit', ['id' => $city->city_id]) }}?back={{ urlencode(request()->fullUrl()) }}"
class="btn btn-sm btn-outline-primary ml10"> class="btn btn-sm btn-outline-primary ml10">
編集 編集
</a> </a>

View File

@ -151,12 +151,45 @@ Route::middleware('auth')->group(function () {
// sou end // sou end
// ou start // ou start
// 市区マスタ // // 市区マスタ
Route::match(['get', 'post'], '/city', [CityController::class, 'list'])->name('city'); // Route::match(['get', 'post'], '/city', [CityController::class, 'list'])->name('city');
Route::match(['get', 'post'], '/city/add', [CityController::class, 'add'])->name('city_add'); // Route::match(['get', 'post'], '/city/add', [CityController::class, 'add'])->name('city_add');
Route::match(['get', 'post'], '/city/edit/{id}', [CityController::class, 'edit'])->where(['id' => '[0-9]+'])->name('city_edit'); // Route::match(['get', 'post'], '/city/edit/{id}', [CityController::class, 'edit'])->where(['id' => '[0-9]+'])->name('city_edit');
Route::match(['get', 'post'], '/city/info/{id}', [CityController::class, 'info'])->where(['id' => '[0-9]+'])->name('city_info'); // 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'); // Route::match(['get', 'post'], '/city/delete', [CityController::class, 'delete'])->name('city_delete');
// 市区マスタcities.*
Route::prefix('cities')->group(function () {
// 一覧(画面)
Route::get('/', [\App\Http\Controllers\Admin\CityController::class, 'index'])
->name('cities.index');
// 新規(画面)
Route::get('/create', [\App\Http\Controllers\Admin\CityController::class, 'create'])
->name('cities.create');
// 新規(登録)
Route::post('/', [\App\Http\Controllers\Admin\CityController::class, 'store'])
->name('cities.store');
// 編集(画面)
Route::get('/{id}/edit', [\App\Http\Controllers\Admin\CityController::class, 'edit'])
->whereNumber('id')
->name('cities.edit');
// 編集(更新)
Route::post('/{id}', [\App\Http\Controllers\Admin\CityController::class, 'update'])
->whereNumber('id')
->name('cities.update');
// 削除(複数削除 pk[]
Route::post('/delete', [\App\Http\Controllers\Admin\CityController::class, 'destroy'])
->name('cities.destroy');
});
// 駐輪場マスタ // 駐輪場マスタ
Route::get('/parks', [ParkController::class, 'list'])->name('parks'); Route::get('/parks', [ParkController::class, 'list'])->name('parks');