297 lines
9.1 KiB
PHP
297 lines
9.1 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Validator;
|
||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||
|
||
// モデル
|
||
use App\Models\OperatorQue; // operator_que テーブル
|
||
use App\Models\User; // 利用者候補(user_seq / user_name / user_mobile / user_homephone)
|
||
use App\Models\Park; // 駐輪場候補(park_id / park_name)
|
||
|
||
class OperatorQueController extends Controller
|
||
{
|
||
/**
|
||
* 一覧
|
||
* ルート: operator_ques
|
||
*/
|
||
public function list(Request $request)
|
||
{
|
||
$sort = $request->input('sort', 'que_id');
|
||
$sort_type = $request->input('sort_type', 'asc');
|
||
$que_status = $request->input('que_status');
|
||
|
||
// 許可されたカラム名のリスト(DB定義に合わせて)
|
||
$allowedSorts = ['que_id', 'ope_id', 'que_status', 'created_at', 'updated_at', 'user_id', 'park_id', 'que_class'];
|
||
|
||
if (!in_array($sort, $allowedSorts)) {
|
||
$sort = 'que_id';
|
||
|
||
}
|
||
|
||
if (!in_array($sort_type, ['asc', 'desc'])) {
|
||
$sort_type = 'desc';
|
||
}
|
||
|
||
$query = OperatorQue::query();
|
||
|
||
// フィルタリング(絞り込み)
|
||
if (!empty($que_status)) {
|
||
$query->where('que_status', $que_status);
|
||
}
|
||
|
||
$list = $query->orderBy($sort, $sort_type)
|
||
->paginate(\App\Utils::item_per_page ?? 20);
|
||
|
||
// view に $que_status を渡す
|
||
return view('admin.operator_ques.list', compact('list', 'sort', 'sort_type', 'que_status'));
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 新規登録(画面/処理)
|
||
*/
|
||
public function add(Request $request)
|
||
{
|
||
if ($request->isMethod('get')) {
|
||
// 新規時は空のレコードを用意してフォーム描画
|
||
return view('admin.operator_ques.add', array_merge(
|
||
$this->formPayload(),
|
||
[
|
||
'isEdit' => false,
|
||
'record' => new OperatorQue(), // ← ★ _form.blade.php で使う用
|
||
'que_id' => null,
|
||
]
|
||
));
|
||
}
|
||
|
||
// POST時:バリデーション
|
||
$data = $this->validateRequest($request);
|
||
|
||
// 登録処理
|
||
OperatorQue::create($data);
|
||
|
||
return redirect()->route('operator_ques')->with('success', '登録しました。');
|
||
}
|
||
|
||
|
||
/**
|
||
* 編集(画面/処理)
|
||
*/
|
||
public function edit($id, Request $request)
|
||
{
|
||
$que = OperatorQue::findOrFail($id);
|
||
|
||
if ($request->isMethod('get')) {
|
||
return view('admin.operator_ques.edit', array_merge(
|
||
$this->formPayload($que),
|
||
[
|
||
'que_id' => $que->que_id,
|
||
'record' => $que,
|
||
]
|
||
));
|
||
}
|
||
|
||
$data = $this->validateRequest($request, $que->que_id);
|
||
|
||
$que->fill($data)->save();
|
||
|
||
return redirect()->route('operator_ques')->with('success', '更新しました。');
|
||
}
|
||
|
||
/**
|
||
* 詳細(参照)
|
||
*/
|
||
public function info($id)
|
||
{
|
||
$que = OperatorQue::findOrFail($id);
|
||
|
||
return view('admin.operator_ques.info', array_merge(
|
||
$this->formPayload($que),
|
||
['que_id' => $que->que_id]
|
||
));
|
||
}
|
||
|
||
/**
|
||
* 削除(複数可)
|
||
*/
|
||
public function delete(Request $request)
|
||
{
|
||
$ids = [];
|
||
if ($request->filled('id')) {
|
||
$ids[] = (int) $request->input('id');
|
||
}
|
||
if (is_array($request->input('pk'))) {
|
||
$ids = array_merge($ids, $request->input('pk'));
|
||
}
|
||
$ids = array_values(array_unique(array_map('intval', $ids)));
|
||
|
||
if (!$ids) {
|
||
return back()->with('error', '削除対象が選択されていません。');
|
||
}
|
||
|
||
OperatorQue::whereIn('que_id', $ids)->delete();
|
||
|
||
return redirect()->route('operator_ques')->with('success', '削除しました。');
|
||
}
|
||
|
||
/**
|
||
* CSV インポート
|
||
*/
|
||
public function import(Request $request)
|
||
{
|
||
$validator = Validator::make($request->all(), [
|
||
'file' => 'required|file|mimes:csv,txt|max:20480',
|
||
]);
|
||
if ($validator->fails()) {
|
||
return back()->withErrors($validator)->withInput();
|
||
}
|
||
|
||
$file = $request->file('file')->getRealPath();
|
||
if (!$handle = fopen($file, 'r')) {
|
||
return back()->with('error', 'CSVを読み取れません。');
|
||
}
|
||
|
||
$header = fgetcsv($handle) ?: [];
|
||
$header = array_map(fn($h) => trim(ltrim($h ?? '', "\xEF\xBB\xBF")), $header);
|
||
|
||
$fillable = (new OperatorQue())->getFillable();
|
||
$rows = [];
|
||
|
||
while (($row = fgetcsv($handle)) !== false) {
|
||
$assoc = [];
|
||
foreach ($header as $i => $key) {
|
||
if (in_array($key, $fillable, true)) {
|
||
$assoc[$key] = $row[$i] ?? null;
|
||
}
|
||
}
|
||
if ($assoc) {
|
||
$rows[] = $assoc;
|
||
}
|
||
}
|
||
fclose($handle);
|
||
|
||
DB::transaction(function () use ($rows) {
|
||
foreach ($rows as $data) {
|
||
OperatorQue::create($data);
|
||
}
|
||
});
|
||
|
||
return redirect()->route('operator_ques')->with('success', count($rows) . '件をインポートしました。');
|
||
}
|
||
|
||
/**
|
||
* CSV エクスポート
|
||
*/
|
||
public function export(): StreamedResponse
|
||
{
|
||
$filename = 'operator_que_' . now()->format('Ymd_His') . '.csv';
|
||
$fillable = (new OperatorQue())->getFillable(); // 見出しは fillable を流用
|
||
|
||
$response = new StreamedResponse(function () use ($fillable) {
|
||
$out = fopen('php://output', 'w');
|
||
// UTF-8 BOM
|
||
fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||
|
||
fputcsv($out, $fillable);
|
||
|
||
OperatorQue::orderBy('que_id')->chunk(500, function ($chunk) use ($out, $fillable) {
|
||
foreach ($chunk as $row) {
|
||
$line = [];
|
||
foreach ($fillable as $f) {
|
||
$line[] = $row->$f ?? '';
|
||
}
|
||
fputcsv($out, $line);
|
||
}
|
||
});
|
||
fclose($out);
|
||
});
|
||
|
||
$response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
|
||
$response->headers->set('Content-Disposition', "attachment; filename={$filename}");
|
||
|
||
return $response;
|
||
}
|
||
|
||
/**
|
||
* フォームに渡す値/候補
|
||
*/
|
||
private function formPayload(?OperatorQue $que = null): array
|
||
{
|
||
// 値
|
||
$payload = [
|
||
'que_id' => $que->que_id ?? '',
|
||
'user_id' => $que->user_id ?? '',
|
||
'contract_id' => $que->contract_id ?? '',
|
||
'park_id' => $que->park_id ?? '',
|
||
'que_class' => $que->que_class ?? '',
|
||
'que_comment' => $que->que_comment ?? '',
|
||
'que_status' => $que->que_status ?? '',
|
||
'que_status_comment' => $que->que_status_comment?? '',
|
||
'work_instructions' => $que->work_instructions ?? '',
|
||
];
|
||
|
||
// 候補
|
||
$payload['users'] = $this->fetchUsers();
|
||
$payload['parks'] = $this->fetchParks();
|
||
|
||
return $payload;
|
||
}
|
||
|
||
/**
|
||
* バリデーション
|
||
* ※ 実テーブルの型に合わせて必要に応じて調整
|
||
*/
|
||
private function validateRequest(Request $request, $queId = null): array
|
||
{
|
||
$rules = [
|
||
'user_id' => 'nullable|integer',
|
||
'contract_id' => 'nullable|integer',
|
||
'park_id' => 'nullable|integer',
|
||
'que_class' => 'required|integer',
|
||
'que_comment' => 'nullable|string|max:2000',
|
||
'que_status' => 'required|integer',
|
||
'que_status_comment' => 'nullable|string|max:2000',
|
||
'work_instructions' => 'nullable|string|max:2000',
|
||
// 'operator_id' => 'nullable|integer', // ログインユーザIDを使うなら不要
|
||
];
|
||
|
||
return $request->validate($rules);
|
||
}
|
||
|
||
/**
|
||
* 利用者候補(user_seq, user_name, user_mobile, user_homephone)
|
||
* Blade 側では $users をそのまま @foreach
|
||
*/
|
||
private function fetchUsers()
|
||
{
|
||
try {
|
||
return User::select('user_seq', 'user_name', 'user_mobile', 'user_homephone')
|
||
->orderBy('user_name')
|
||
->get();
|
||
} catch (\Throwable $e) {
|
||
return DB::table('users')
|
||
->select(['user_seq', 'user_name', 'user_mobile', 'user_homephone'])
|
||
->orderBy('user_name')
|
||
->get();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 駐輪場候補(park_id => park_name)
|
||
*/
|
||
private function fetchParks()
|
||
{
|
||
try {
|
||
return Park::orderBy('park_name')->pluck('park_name', 'park_id');
|
||
} catch (\Throwable $e) {
|
||
return DB::table('parks')->orderBy('park_name')->pluck('park_name', 'park_id');
|
||
}
|
||
}
|
||
}
|