input('sort', 'contract_id'); $sortType = strtolower($request->input('sort_type', 'desc')) === 'asc' ? 'asc' : 'desc'; // ===== 絞り込み(テキスト系)===== // フォームの name 属性と完全一致させる&既定値は空文字にして Blade が未定義にならないようにする $contract_qr_id = trim((string) $request->input('contract_qr_id', '')); $user_id = trim((string) $request->input('user_id', '')); $park_id = trim((string) $request->input('park_id', '')); $user_phonetic = trim((string) $request->input('user_phonetic', '')); // フリガナ $phone = trim((string) $request->input('phone', '')); // 電話(携帯/自宅) $email = trim((string) $request->input('email', '')); // メール $usertype_name_kw = trim((string) $request->input('usertype_name', '')); // 利用者分類名 $park_name_kw = trim((string) $request->input('park_name', '')); // 駐輪場名 // ===== 絞り込み(日付範囲)===== $reserve_from = $request->input('reserve_date_from', ''); $reserve_to = $request->input('reserve_date_to', ''); $created_from = $request->input('contract_created_from', ''); $created_to = $request->input('contract_created_to', ''); $updated_from = $request->input('contract_updated_from', ''); $updated_to = $request->input('contract_updated_to', ''); $canceled_from = $request->input('contract_canceled_from', ''); $canceled_to = $request->input('contract_canceled_to', ''); // ===== 列挙(全て/0/1)===== $contract_flag = $request->input('contract_flag', ''); $contract_permission = $request->input('contract_permission', ''); $tag_qr_flag = $request->input('tag_qr_flag', ''); $update_flag = $request->input('update_flag', ''); $contract_cancel_flag = $request->input('contract_cancel_flag', ''); // ===== クエリ(結合込み)===== $q = DB::table('regular_contract as rc') ->leftJoin('user as u', 'u.user_id', '=', 'rc.user_id') ->leftJoin('usertype as t', 't.user_categoryid', '=', 'rc.user_categoryid') ->leftJoin('park as p', 'p.park_id', '=', 'rc.park_id') ->select([ // rc 'rc.contract_id', 'rc.contract_qr_id', 'rc.user_id', 'rc.user_categoryid', 'rc.reserve_id', 'rc.park_id', 'rc.price_parkplaceid', 'rc.user_securitynum', 'rc.reserve_date', 'rc.contract_reserve', 'rc.contract_created_at', 'rc.contract_updated_at', 'rc.contract_cancelday', 'rc.contract_flag', 'rc.contract_permission', 'rc.contract_cancel_flag', 'rc.tag_qr_flag', 'rc.update_flag', 'rc.park_position', 'rc.ope_id', // user 'u.user_name', 'u.user_phonetic', 'u.user_mobile', 'u.user_homephone', 'u.user_primemail', // usertype & park DB::raw('t.print_name as usertype_name'), DB::raw('p.park_name as park_name'), ]); // ===== LIKE / キーワード ===== if ($contract_qr_id !== '') { $q->where('rc.contract_qr_id', 'like', "%{$contract_qr_id}%"); } if ($user_id !== '') { $q->where('rc.user_id', 'like', "%{$user_id}%"); } if ($park_id !== '') { $q->where('rc.park_id', 'like', "%{$park_id}%"); } if ($user_phonetic !== '') { $q->where('u.user_phonetic', 'like', "%{$user_phonetic}%"); } if ($email !== '') { $q->where('u.user_primemail', 'like', "%{$email}%"); } if ($usertype_name_kw !== '') { $q->where('t.print_name', 'like', "%{$usertype_name_kw}%"); } if ($park_name_kw !== '') { $q->where('p.park_name', 'like', "%{$park_name_kw}%"); } if ($phone !== '') { $q->where(function ($w) use ($phone) { $w->where('u.user_mobile', 'like', "%{$phone}%") ->orWhere('u.user_homephone', 'like', "%{$phone}%"); }); } // ===== 日付範囲 ===== if ($reserve_from) { $q->whereDate('rc.reserve_date', '>=', $reserve_from); } if ($reserve_to) { $q->whereDate('rc.reserve_date', '<=', $reserve_to); } if ($created_from) { $q->whereDate('rc.contract_created_at', '>=', $created_from); } if ($created_to) { $q->whereDate('rc.contract_created_at', '<=', $created_to); } if ($updated_from) { $q->whereDate('rc.contract_updated_at', '>=', $updated_from); } if ($updated_to) { $q->whereDate('rc.contract_updated_at', '<=', $updated_to); } if ($canceled_from) { $q->whereDate('rc.contract_cancelday', '>=', $canceled_from); } if ($canceled_to) { $q->whereDate('rc.contract_cancelday', '<=', $canceled_to); } // ===== 列挙フィルタ ===== if ($contract_flag !== '') { $q->where('rc.contract_flag', (int) $contract_flag); } if ($contract_permission !== '') { $q->where('rc.contract_permission', (int) $contract_permission); } if ($tag_qr_flag !== '') { $q->where('rc.tag_qr_flag', (int) $tag_qr_flag); } if ($update_flag !== '') { $q->where('rc.update_flag', (int) $update_flag); } if ($contract_cancel_flag !== '') { $q->where('rc.contract_cancel_flag', (int) $contract_cancel_flag); } // ===== ソート(仮想列は結合側にマッピング)===== $sortable = [ 'contract_id', 'contract_qr_id', 'user_id', 'user_categoryid', 'reserve_id', 'park_id', 'price_parkplaceid', 'user_securitynum', 'reserve_date', 'contract_reserve', 'contract_created_at', 'contract_updated_at', 'contract_cancelday', 'contract_flag', 'contract_permission', 'contract_cancel_flag', 'tag_qr_flag', 'update_flag', 'park_position', 'ope_id', // 結合先の見出し列 'user_name', 'user_phonetic', 'user_mobile', 'user_homephone', 'user_primemail', 'usertype_name', 'park_name', ]; if (!in_array($sort, $sortable, true)) { $sort = 'contract_id'; } $sortMap = [ 'user_name' => 'u.user_name', 'user_phonetic' => 'u.user_phonetic', 'user_mobile' => 'u.user_mobile', 'user_homephone' => 'u.user_homephone', 'user_primemail' => 'u.user_primemail', 'usertype_name' => 't.print_name', 'park_name' => 'p.park_name', ]; $sortColumn = $sortMap[$sort] ?? ('rc.' . $sort); $list = $q->orderBy($sortColumn, $sortType)->paginate(50); // ===== 画面へ(Blade 側が参照するすべての変数を渡す)===== return view('admin.regularcontracts.list', [ 'list' => $list, 'sort' => $sort, 'sort_type' => $sortType, // 入力保持(テキスト) 'contract_qr_id' => $contract_qr_id, 'user_id' => $user_id, 'park_id' => $park_id, 'user_phonetic' => $user_phonetic, 'phone' => $phone, 'email' => $email, 'usertype_name' => $usertype_name_kw, 'park_name' => $park_name_kw, // 入力保持(日付) 'reserve_date_from' => $reserve_from, 'reserve_date_to' => $reserve_to, 'contract_created_from' => $created_from, 'contract_created_to' => $created_to, 'contract_updated_from' => $updated_from, 'contract_updated_to' => $updated_to, 'contract_canceled_from' => $canceled_from, 'contract_canceled_to' => $canceled_to, // 入力保持(列挙) 'contract_flag' => $contract_flag, 'contract_permission' => $contract_permission, 'tag_qr_flag' => $tag_qr_flag, 'update_flag' => $update_flag, 'contract_cancel_flag' => $contract_cancel_flag, ]); } /** * 定期契約編集(GET: 画面表示 / POST: 更新実行) * - 主キー: contract_id */ public function edit(Request $request, $id) { $id = (int) $id; if ($request->isMethod('get')) { $row = DB::table('regular_contract')->where('contract_id', $id)->first(); if (!$row) { abort(404); } return view('admin.regularcontracts.edit', [ 'row' => $row, 'contract_id' => $id, ]); } $v = Validator::make($request->all(), [ 'user_id' => ['required', 'integer'], 'park_id' => ['required', 'integer'], // 任意項目 'contract_qr_id' => ['nullable', 'string', 'max:255'], 'user_categoryid' => ['nullable', 'integer'], 'reserve_id' => ['nullable', 'integer'], 'price_parkplaceid' => ['nullable', 'integer'], 'user_securitynum' => ['nullable', 'string', 'max:255'], 'reserve_date' => ['nullable', 'date'], 'contract_reserve' => ['nullable', 'string', 'max:255'], 'contract_created_at' => ['nullable', 'date'], 'contract_updated_at' => ['nullable', 'date'], 'contract_cancelday' => ['nullable', 'date'], 'contract_flag' => ['nullable', 'integer'], 'contract_permission' => ['nullable', 'integer'], 'contract_cancel_flag' => ['nullable', 'integer'], 'tag_qr_flag' => ['nullable', 'integer'], 'park_position' => ['nullable', 'string', 'max:255'], 'ope_id' => ['nullable', 'integer'], ]); if ($v->fails()) { return back()->withErrors($v)->withInput(); } $data = [ 'contract_qr_id' => $request->input('contract_qr_id'), 'user_id' => (int) $request->input('user_id'), 'user_categoryid' => $request->input('user_categoryid'), 'reserve_id' => $request->input('reserve_id'), 'park_id' => (int) $request->input('park_id'), 'price_parkplaceid' => $request->input('price_parkplaceid'), 'user_securitynum' => $request->input('user_securitynum'), 'reserve_date' => $request->input('reserve_date'), 'contract_reserve' => $request->input('contract_reserve'), 'contract_created_at' => $request->input('contract_created_at'), 'contract_updated_at' => $request->input('contract_updated_at'), 'contract_cancelday' => $request->input('contract_cancelday'), 'contract_flag' => $request->input('contract_flag'), 'contract_permission' => $request->input('contract_permission'), 'contract_cancel_flag' => $request->input('contract_cancel_flag'), 'tag_qr_flag' => $request->input('tag_qr_flag'), 'park_position' => $request->input('park_position'), 'ope_id' => $request->input('ope_id'), 'updated_at' => now(), ]; DB::table('regular_contract')->where('contract_id', $id)->update($data); return redirect()->route('regularcontracts')->with('success', '定期契約を更新しました。'); } /** * 定期契約削除 * - 物理削除(必要なら cancel フラグ運用に切替) */ public function delete(Request $request) { $id = (int) $request->input('id'); DB::table('regular_contract')->where('contract_id', $id)->delete(); // 例:論理削除運用にする場合(必要なら運用側で切替) // DB::table('regular_contract')->where('contract_id', $id)->update([ // 'contract_cancel_flag' => 1, // 'contract_cancelday' => now(), // 'updated_at' => now(), // ]); return redirect()->route('regularcontracts')->with('success', '定期契約を削除しました。'); } /** * 定期契約インポート(仮実装) */ public function import(Request $request) { if ($request->isMethod('get')) { // GET で来たら一覧へ return redirect()->route('regularcontracts'); } // ファイル必須 & 形式チェック $request->validate([ 'file' => ['required', 'file', 'mimetypes:text/plain,text/csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], ], [], [ 'file' => 'インポートファイル', ]); $file = $request->file('file'); // TODO: ここで実際のインポート処理(CSV/XLSXの解析とレコード登録)を書く // 例:Storage::putFile('imports', $file); で一旦保存してバッチに回す etc. return redirect()->route('regularcontracts')->with('success', 'インポートを受け付けました。'); } /** * 定期契約エクスポート(仮実装) * - 現時点では何もしません。ルーティング確認用のプレーンテキストを返します。 * - 後で CSV / Excel 出力処理に置き換えてください。 */ public function export(Request $request) { // ── 出力タイプ(通常 / SMBC / 役所) ────────────────────────────── $type = $request->query('type'); // null | smbc | city // ── 出力ファイル名 ─────────────────────────────────────────────── $downloadName = '定期契約マスタ.csv'; if ($type === 'smbc') $downloadName = '定期契約マスタ_SMBC.csv'; if ($type === 'city') $downloadName = '定期契約マスタ_役所提出用.csv'; // ── 生成先(storage/app/tmp 配下の一時ファイル) ───────────────── $tmpDir = storage_path('app/tmp'); if (!is_dir($tmpDir)) { @mkdir($tmpDir, 0755, true); } $tmpPath = $tmpDir . '/' . uniqid('regularcontracts_', true) . '.csv'; // ── CSV を作成(Excel を考慮し UTF-8 BOM を付与) ─────────────── $fp = fopen($tmpPath, 'w+'); if ($fp === false) { abort(500, 'CSV一時ファイルを作成できませんでした。'); } // Excel 対策:BOM fwrite($fp, "\xEF\xBB\xBF"); // ヘッダー行(必要に応じて列を増減) fputcsv($fp, ['定期契約ID', '利用者ID', '駐輪場ID', '契約日時']); // ── データ取得(大量件数に備え chunk で分割取得) ──────────────── // ※ list() と同等の JOIN/SELECT を最低限に簡略化 DB::table('regular_contract as rc') ->leftJoin('user as u', 'u.user_id', '=', 'rc.user_id') ->orderBy('rc.contract_id', 'asc') ->select([ 'rc.contract_qr_id', 'rc.user_id', 'rc.park_id', 'rc.contract_created_at', ]) ->chunk(1000, function ($rows) use ($fp) { foreach ($rows as $r) { fputcsv($fp, [ $r->contract_qr_id, $r->user_id, $r->park_id, $r->contract_created_at, ]); } }); fclose($fp); // ── ダウンロードレスポンス(送信後に一時ファイル削除) ──────────── return response()->download( $tmpPath, $downloadName, [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => 'attachment; filename="' . $downloadName . '"', 'Pragma' => 'no-cache', 'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0', 'Expires' => '0', ] )->deleteFileAfterSend(true); } // 追加:新規登録(GET: 画面表示 / POST: 登録実行) public function add(Request $request) { // 画面表示 if ($request->isMethod('get')) { return view('admin.regularcontracts.add'); } // ========= バリデーション ========= // ※ 必須最小限。その他は任意(nullable) $v = Validator::make( $request->all(), [ 'user_id' => ['required', 'integer'], 'park_id' => ['required', 'integer'], 'contract_qr_id' => ['nullable', 'string', 'max:255'], 'user_categoryid' => ['nullable', 'integer'], 'reserve_id' => ['nullable', 'integer'], 'price_parkplaceid' => ['nullable', 'integer'], 'reserve_date' => ['nullable', 'date'], 'contract_created_at' => ['nullable', 'date'], 'contract_cancelday' => ['nullable', 'date'], 'contract_permission' => ['nullable', 'integer'], 'contract_cancel_flag' => ['nullable', 'integer'], 'tag_qr_flag' => ['nullable', 'integer'], 'update_flag' => ['nullable', 'integer'], 'park_position' => ['nullable', 'string', 'max:255'], 'ope_id' => ['nullable', 'integer'], // 画面の「定期有効月数」は DB の contract_valid_months に保存する 'enable_months' => ['nullable', 'integer', 'min:0'], ], [], [ 'user_id' => '利用者ID', 'park_id' => '駐輪場ID', 'contract_qr_id' => '定期契約QRID', 'user_categoryid' => '利用者分類ID', 'reserve_id' => '定期予約ID', 'price_parkplaceid' => '駐輪場所ID', 'reserve_date' => '予約日時', 'contract_created_at' => '契約日時', 'contract_cancelday' => '解約日時', 'contract_permission' => 'シール発行許可', 'contract_cancel_flag' => '解約フラグ', 'tag_qr_flag' => 'タグ・QR', 'update_flag' => '(更新元)契約更新済フラグ', 'park_position' => '駐輪位置番号', 'ope_id' => 'オペレータID', 'enable_months' => '定期有効月数', ] ); if ($v->fails()) { return back()->withErrors($v)->withInput(); } // ========= 登録データ作成 ========= // ここでは「regular_contract」テーブルに確実にある列を中心に保存します。 // 追加したい列があれば、同様にキーを増やして下さい。 $data = [ 'contract_qr_id' => $request->input('contract_qr_id'), 'user_id' => (int) $request->input('user_id'), 'user_categoryid' => $request->input('user_categoryid'), 'reserve_id' => $request->input('reserve_id'), 'park_id' => (int) $request->input('park_id'), 'price_parkplaceid' => $request->input('price_parkplaceid'), 'reserve_date' => $request->input('reserve_date'), 'contract_created_at' => $request->input('contract_created_at') ?: now(), // 未指定なら現在時刻 'contract_cancelday' => $request->input('contract_cancelday'), 'contract_permission' => $request->input('contract_permission'), 'contract_cancel_flag' => $request->input('contract_cancel_flag'), 'tag_qr_flag' => $request->input('tag_qr_flag'), 'update_flag' => $request->input('update_flag'), 'park_position' => $request->input('park_position'), 'ope_id' => $request->input('ope_id'), // 画面の enable_months → DB の contract_valid_months 'contract_valid_months' => $request->input('enable_months'), 'created_at' => now(), 'updated_at' => now(), ]; DB::table('regular_contract')->insert($data); return redirect() ->route('regularcontracts') ->with('success', '定期契約を登録しました。'); } }