$request->isMethod('post'), 'sort' => $request->input('sort', 'ope_id'), 'sort_type' => $request->input('sort_type', 'asc'), 'isExport' => false, ]; // Blade 側は $list / $sort / $sort_type を参照 $list = Ope::search($inputs); $sort = $inputs['sort']; $sort_type = $inputs['sort_type']; return view('admin.opes.list', compact('list', 'sort', 'sort_type')); } /** * 新規登録(GET 画面 / POST 保存) */ public function add(Request $request) { // ※機能(画面)一覧を取得(プルダウン用) $features = Feature::query() ->orderBy('id') ->get(['id', 'name']); // ※操作権限一覧を取得(チェックボックス用) $permissions = Permission::query() ->orderBy('id') ->get(['id', 'code', 'name']); if ($request->isMethod('get')) { return view('admin.opes.add', [ 'isEdit' => false, 'record' => new Ope(), 'ope_id' => null, 'ope_name' => '', 'ope_type' => '', 'ope_mail' => '', 'ope_phone' => '', 'ope_sendalart_que1' => 0, 'ope_sendalart_que2' => 0, 'ope_sendalart_que3' => 0, 'ope_sendalart_que4' => 0, 'ope_sendalart_que5' => 0, 'ope_sendalart_que6' => 0, 'ope_sendalart_que7' => 0, 'ope_sendalart_que8' => 0, 'ope_sendalart_que9' => 0, 'ope_sendalart_que10' => 0, 'ope_sendalart_que11' => 0, 'ope_sendalart_que12' => 0, 'ope_sendalart_que13' => 0, 'ope_auth1' => '', 'ope_auth2' => '', 'ope_auth3' => '', 'ope_auth4' => '', 'ope_quit_flag' => 0, 'ope_quitday' => '', // ▼追加:権限設定UI用 'features' => $features, 'permissions' => $permissions, 'selectedFeatureId' => old('feature_id', null), ]); } // 入力値を一旦取得 $data = $request->all(); // --- バリデーション --- $rules = [ 'login_id' => 'required|string|max:255|unique:ope,login_id', 'ope_name' => 'required|string|max:255', 'ope_type' => 'required|string|max:50', 'ope_mail' => [ 'required', function ($attribute, $value, $fail) { // ; でも , でもOK、保存時は ; に統一 $emails = array_map('trim', explode(';', str_replace(',', ';', $value))); foreach ($emails as $email) { if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) { $fail("無効なメールアドレス形式です: {$email}"); } } } ], 'ope_phone' => 'nullable|string|max:50', 'password' => 'required|string|min:8|confirmed', ]; $request->validate($rules); // --- 保存用にメールを ; 区切りに統一 --- $emails = array_filter(array_map('trim', explode(';', str_replace(',', ';', $data['ope_mail'])))); $data['ope_mail'] = implode(';', $emails); // 保存処理 $ope = new Ope(); $ope->fill($data); $ope->save(); return redirect()->route('opes')->with('success', '登録しました。'); } /** * 編集(GET 画面 / POST 更新) * ※権限(自治体×機能×操作)も同画面で設定する */ public function edit($id, Request $request) { $ope = Ope::getByPk($id); if (!$ope) abort(404); // ※機能(画面)一覧を取得(プルダウン用) $features = Feature::query() ->orderBy('id') ->get(['id', 'name']); // ※操作権限一覧を取得(チェックボックス用) $permissions = Permission::query() ->orderBy('id') ->get(['id', 'code', 'name']); // ※自治体ID(opeに紐づく想定) $municipalityId = (int)($ope->municipality_id ?? 0); if ($request->isMethod('get')) { return view('admin.opes.edit', [ 'isEdit' => true, 'record' => $ope, // ▼追加:権限設定UI用 'features' => $features, 'permissions' => $permissions, 'selectedFeatureId' => old('feature_id', null), ]); } /** * ▼権限設定の保存(feature_id + permission_ids[]) * ※画面側の保存ボタンを「権限も同時保存」にする場合はここで処理する * ※もし「基本情報の更新」と「権限更新」をボタンで分けたい場合は、別アクションに分離推奨 */ if ($request->has('feature_id')) { $request->validate([ 'feature_id' => ['required', 'integer', 'exists:features,id'], 'permission_ids' => ['nullable', 'array'], 'permission_ids.*' => ['integer', 'exists:permissions,id'], ]); $featureId = (int)$request->input('feature_id'); $permissionIds = array_map('intval', (array)$request->input('permission_ids', [])); DB::transaction(function () use ($municipalityId, $featureId, $permissionIds) { // ※機能単位で置換(自治体単位) OpePermission::replaceByFeature($municipalityId, $featureId, $permissionIds); }); } // 入力値を一旦取得 $data = $request->all(); // --- バリデーション --- $rules = [ 'login_id' => "required|string|max:255|unique:ope,login_id,{$id},ope_id", 'ope_name' => 'required|string|max:255', 'ope_type' => 'required|string|max:50', 'ope_phone' => 'nullable|string|max:50', 'ope_mail' => [ 'required', function ($attribute, $value, $fail) { $emails = array_map('trim', explode(';', str_replace(',', ';', $value))); foreach ($emails as $email) { if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) { $fail("無効なメールアドレス形式です: {$email}"); } } } ], 'password' => 'nullable|string|min:8|confirmed', ]; $request->validate($rules); // --- 保存用にメールを ; 区切りに統一 --- if (!empty($data['ope_mail'])) { $emails = array_filter(array_map('trim', explode(';', str_replace(',', ';', $data['ope_mail'])))); $data['ope_mail'] = implode(';', $emails); } // パスワード空なら更新しない if (empty($data['password'])) { unset($data['password']); } // 保存処理 $ope->fill($data); $ope->save(); return redirect()->route('opes')->with('success', '更新しました。'); } /** * 権限回顧(AJAX) * /opes/{id}/permissions?feature_id=xx * ※ope_permissionが自治体単位のため、opeの自治体IDで取得する */ public function getPermissionsByFeature(int $id, Request $request) { $ope = Ope::getByPk($id); if (!$ope) abort(404); $featureId = (int)$request->query('feature_id'); if ($featureId <= 0) { return response()->json([]); } $municipalityId = (int)($ope->municipality_id ?? 0); $ids = OpePermission::query() ->where('municipality_id', $municipalityId) ->where('feature_id', $featureId) ->pluck('permission_id') ->values(); return response()->json($ids); } /** * 削除(単体 or 複数) */ public function delete(Request $request) { $ids = []; // 単体削除 if ($request->filled('id')) { $ids[] = (int)$request->input('id'); } // 複数削除 if ($request->filled('ids')) { $ids = array_merge($ids, array_map('intval', (array)$request->input('ids'))); } $ids = array_unique($ids); if (!$ids) { return back()->with('error', '削除対象が選択されていません。'); } Ope::deleteByPk($ids); return redirect()->route('opes')->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(); $handle = fopen($file, 'r'); if (!$handle) return back()->with('error', 'CSVを読み取れません。'); $header = fgetcsv($handle); $header = array_map(fn($h) => trim(ltrim($h ?? '', "\xEF\xBB\xBF")), $header); $fillable = (new Ope())->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) { Ope::create($data); } }); return redirect()->route('opes')->with('success', count($rows) . '件をインポートしました。'); } /** * CSVエクスポート */ public function export(): StreamedResponse { $filename = 'ope_' . now()->format('Ymd_His') . '.csv'; $fillable = (new Ope())->getFillable(); $response = new StreamedResponse(function () use ($fillable) { $out = fopen('php://output', 'w'); fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF)); // BOM fputcsv($out, $fillable); Ope::orderBy('ope_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; } }