diff --git a/.gitignore b/.gitignore index 450b1b0..243642c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,15 @@ yarn-error.log /.vscode /Data_lokasi /public/js + +# ReportTransaksiController yg bener/lebih lengkap ada di server (90%, +# masih didebug di sana) - versi lokal ini WIP eksperimen, jangan sampe +# ke-push & numpuk balik nindihin punya server pas git pull di server. +/app/Http/Controllers/Parking/Report/ReportTransaksiController.php + +# File backup manual (.bak) & dump/arsip mentah yang numpuk gak sengaja - +# bukan source code, gak perlu ditrack git. +*.bak +/softseting +*.tgz +*.zip diff --git a/app/Helpers/ParkingUtils.php b/app/Helpers/ParkingUtils.php new file mode 100644 index 0000000..9b9fa6d --- /dev/null +++ b/app/Helpers/ParkingUtils.php @@ -0,0 +1,15 @@ +json([ 'status' => 'success', - // 'desc' => 'Parameter undefined', 'details' => $location - ],400); + ]); } diff --git a/app/Http/Controllers/Api/V1/IntercomController.php b/app/Http/Controllers/Api/V1/IntercomController.php new file mode 100644 index 0000000..43c4539 --- /dev/null +++ b/app/Http/Controllers/Api/V1/IntercomController.php @@ -0,0 +1,128 @@ +get(); + } + + public function queue() + { + return IntercomCall::whereIn('status', ['waiting', 'active']) + ->orderBy('requested_at') + ->limit(20)->get(); + } + + public function history(Request $request) + { + return IntercomCall::orderByDesc('id')->limit($request->input('limit', 50))->get(); + } + + // Dipanggil Go relay saat ESP32 kirim MQTT intercom/request + public function request(Request $request) + { + $data = $request->validate([ + 'device_id' => 'required|string|max:20', + 'arah' => 'sometimes|string|in:in,out', + ]); + + $deviceId = strtoupper($data['device_id']); + + $device = IntercomDevice::firstOrCreate( + ['device_id' => $deviceId], + ['nama' => $deviceId, 'status' => 'idle'] + ); + + // Kalau device lagi active, tetap masuk antrean (tidak ditolak) + $call = IntercomCall::create([ + 'device_id' => $deviceId, + 'arah' => $data['arah'] ?? 'in', + 'status' => 'waiting', + 'requested_at' => now(), + ]); + + $device->update(['status' => 'calling', 'last_seen_at' => now()]); + + Log::info("Intercom request {$deviceId} call_id={$call->id}"); + + return response()->json(['status' => true, 'call_id' => $call->id, 'device_id' => $deviceId]); + } + + // Admin klik [Terima] di dashboard + public function accept(Request $request) + { + $data = $request->validate(['call_id' => 'required|integer']); + $call = IntercomCall::findOrFail($data['call_id']); + + // Tutup sesi active lain di device yang sama (single private session) + IntercomCall::where('device_id', $call->device_id) + ->where('status', 'active')->update(['status' => 'ended', 'ended_at' => now(), 'ended_by' => 'admin-switch']); + + $call->update(['status' => 'active', 'started_at' => now()]); + IntercomDevice::where('device_id', $call->device_id)->update(['status' => 'active']); + + // Go relay yang bertugas publish MQTT intercom/invite/{DEVICE} + buka UDP port. + // Laravel cuma ubah status, tidak publish MQTT langsung. + return response()->json(['status' => true, 'call' => $call]); + } + + public function end(Request $request) + { + $data = $request->validate([ + 'call_id' => 'required|integer', + 'ended_by' => 'sometimes|string|max:20', + 'recording_path' => 'sometimes|string|max:255', + ]); + + $call = IntercomCall::findOrFail($data['call_id']); + $now = now(); + $durasi = $call->started_at ? $now->diffInSeconds($call->started_at) : ($call->requested_at ? $now->diffInSeconds($call->requested_at) : 0); + + $call->update([ + 'status' => $call->status === 'waiting' && ($data['ended_by'] ?? '') === 'timeout' ? 'missed' : 'ended', + 'ended_at' => $now, + 'durasi_detik' => $durasi, + 'ended_by' => $data['ended_by'] ?? 'admin', + 'recording_path' => $data['recording_path'] ?? $call->recording_path, + ]); + + // Balikin status device ke idle kalau tidak ada sesi lain + $masihAktif = IntercomCall::where('device_id', $call->device_id)->whereIn('status', ['waiting', 'active'])->exists(); + if (!$masihAktif) { + IntercomDevice::where('device_id', $call->device_id)->update(['status' => 'idle']); + } + + return response()->json(['status' => true, 'call' => $call]); + } + + // Heartbeat / LWT dari Go relay: ESP32 online/offline + public function heartbeat(Request $request) + { + $data = $request->validate([ + 'device_id' => 'required|string|max:20', + 'status' => 'sometimes|string|max:20', + 'ip_address' => 'sometimes|string|max:45', + ]); + + IntercomDevice::updateOrCreate( + ['device_id' => strtoupper($data['device_id'])], + ['status' => $data['status'] ?? 'idle', 'ip_address' => $data['ip_address'] ?? null, 'last_seen_at' => now()] + ); + + return response()->json(['status' => true]); + } +} diff --git a/app/Http/Controllers/Api/V1/LocationSiteInfoController.php b/app/Http/Controllers/Api/V1/LocationSiteInfoController.php new file mode 100644 index 0000000..a3795be --- /dev/null +++ b/app/Http/Controllers/Api/V1/LocationSiteInfoController.php @@ -0,0 +1,19 @@ +json([ + "status" => true, + "data" => $site, + ]); + } +} diff --git a/app/Http/Controllers/Api/V1/MemberinController.php b/app/Http/Controllers/Api/V1/MemberinController.php deleted file mode 100644 index 5d06dd1..0000000 --- a/app/Http/Controllers/Api/V1/MemberinController.php +++ /dev/null @@ -1,125 +0,0 @@ -select('a.*','jm.nama as vehicle') - ->leftjoin('jenis_mobil as jm','jm.id','=','a.jenis_mobil') - ->where('nopol', $id) - ->orderByDesc('tgl_edited') - ->first(); - - $akhir = Carbon::parse($data->akhir); - - if(!$data) { - return response()->json([ - 'status'=>false, - 'message'=>'Member tidak ditemukan' - ]); - } - - if($dateNow->greaterThan($akhir)){ - - return response()->json([ - 'status' => 'expired', - 'expired' => true, - 'message' => 'Member expired', - 'data' => $data - ]); - } else { - return response()->json([ - 'status' => 'active', - 'expired' => false, - 'message' => 'Member aktif', - 'data' => $data - ]); - } - - - Log::info(json_encode($data)); - } - - /** - * Show the form for editing the specified resource. - * - * @param int $id - * @return \Illuminate\Http\Response - */ - public function edit($id) - { - // - } - - /** - * Update the specified resource in storage. - * - * @param \Illuminate\Http\Request $request - * @param int $id - * @return \Illuminate\Http\Response - */ - public function update(Request $request, $id) - { - // - } - - /** - * Remove the specified resource from storage. - * - * @param int $id - * @return \Illuminate\Http\Response - */ - public function destroy($id) - { - // - } -} diff --git a/app/Http/Controllers/Api/V1/ParkingLot/ParkingLotController.php b/app/Http/Controllers/Api/V1/ParkingLot/ParkingLotController.php new file mode 100644 index 0000000..75187c0 --- /dev/null +++ b/app/Http/Controllers/Api/V1/ParkingLot/ParkingLotController.php @@ -0,0 +1,230 @@ +space_parking; + + // Array untuk menyimpan data parking per lantai + $parkingData = []; + + for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) { + // Melakukan query pada LotParkingLog berdasarkan area dan lot_number + $log = LotParkingLog::where("area", $lantai->sitearea_id) + ->where("lot_number", $lotNumber) + ->select("lot_number", "status") + ->first(); + + // Jika data tidak ditemukan, buat object baru dengan lot_number dari pengulangan for + if (!$log) { + $log = (object) [ + "lot_number" => $lotNumber, + "status" => 0, + ]; + } + + // Menyimpan hasil query dalam array + $parkingData[] = $log; + } + + // Menyimpan data parking per lantai dalam array berdasarkan area + $parkingDataByArea[] = [ + "lantai" => $lantai->sitearea_id, + "data" => $parkingData, + ]; + } + + // Melakukan dump and die untuk melihat hasil query sementara + // dd(json_encode($parkingDataByArea)); + // dd($getArea->toJson()); + return view("headoffice.space", [ + "areas" => $getArea, + "parkingDataByArea" => $parkingDataByArea, + ]); + + return view("headoffice.space"); + } + + public function getParkingData() + { + Log::info("getParkingData method called"); + + $getArea = SiteParking::all(); // Mengambil semua data dari model SiteParking + $parkingDataByArea = []; // Array untuk menyimpan hasil query berdasarkan area + + foreach ($getArea as $lantai) { + $spaceParking = $lantai->space_parking; + + // Array untuk menyimpan data parking per lantai + $parkingData = []; + + for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) { + // Melakukan query pada LotParkingLog berdasarkan area dan lot_number + $log = LotParkingLog::where("area", $lantai->sitearea_id) + ->where("lot_number", $lotNumber) + ->select("lot_number", "status") + ->first(); + + // Jika data tidak ditemukan, buat object baru dengan lot_number dari pengulangan for + if (!$log) { + $log = (object) [ + "lot_number" => $lotNumber, + "status" => 0, + ]; + } + + // Menyimpan hasil query dalam array + $parkingData[] = $log; + } + + // Menyimpan data parking per lantai dalam array berdasarkan area + $parkingDataByArea[] = [ + "lantai" => $lantai->sitearea_id, + "data" => $parkingData, + ]; + + Log::info($parkingDataByArea); + } + + return response()->json($parkingDataByArea); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + */ + public function create() + { + // + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + $area = $request->site_area; + $lotNumber = $request->lot_number; + Log::info($area); + Log::info($lotNumber); + $exisLot = LotParkingLog::where("area", $area)->where("lot_number", $lotNumber)->where("status", 1)->first(); + + if (!$exisLot) { + $LotParking = LotParkingLog::create([ + "area" => $area, + "lot_number" => $lotNumber, + "status" => 1, + "update_at" => null, + ]); + + Log::info("Lot " . $lotNumber . " di " . $area . " terisi."); + + return response()->json([ + "status" => "success", + "message" => "Lot No " . $lotNumber . " terisi", + "data" => $LotParking, + ]); + } else { + return response()->json([ + "status" => "error", + "message" => "Kendaraan belum keluar", + ]); + } + } + + /** + * Display the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function show($id) + { + // + } + + /** + * Show the form for editing the specified resource. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function edit($id) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param int $id + * @return \Illuminate\Http\Response + */ + public function update(Request $request) + { + Log::info(json_encode($request->all())); + $area = $request->site_area; + $lotNumber = $request->lot_number; + Log::info($area); + Log::info($lotNumber); + $exisLot = LotParkingLog::where("area", $area) + ->where("lot_number", $lotNumber) + ->where("status", 1) + ->orderBy("updated_at", "desc") + ->first(); + + if (!$exisLot) { + return response()->json([ + "status" => "error", + "message" => "Lot masih kosong", + ]); + } else { + $exisLot->update([ + "status" => 0, + ]); + + Log::info("Lot " . $lotNumber . " di " . $area . " keluar area."); + + return response()->json([ + "status" => "success", + "message" => "Kendaraan telah keluar", + "data" => $exisLot, + ]); + } + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return \Illuminate\Http\Response + */ + public function destroy($id) + { + // + } +} diff --git a/app/Http/Controllers/Apps/Memberin/SelfMemberinController.php b/app/Http/Controllers/Apps/Memberin/SelfMemberinController.php index cf480d8..a84a5d9 100644 --- a/app/Http/Controllers/Apps/Memberin/SelfMemberinController.php +++ b/app/Http/Controllers/Apps/Memberin/SelfMemberinController.php @@ -16,19 +16,19 @@ class SelfMemberinController extends Controller */ public function index(Request $request) { - $jenis = $request->query('jenis'); + $jenis = $request->query("jenis"); Log::info($jenis); - $tarifStiker = DB::table('tarif_stiker'); + $tarifStiker = DB::table("tarif_stiker"); if ($jenis) { - $tarifStiker->where('stasiunkerja_id', $jenis); + $tarifStiker->where("stasiunkerja_id", $jenis); } $produk = $tarifStiker->get(); Log::info($produk); - return view('apps.member.index', compact('produk')); + return view("apps.memberin.index", compact("produk")); } /** @@ -61,10 +61,7 @@ public function store(Request $request) public function show($id) { Log::info($id); - $tarifStiker = DB::table('tarif_stiker') - ->where('stasiunkerja_id', $id) - ->orderby('tarif') - ->get(); + $tarifStiker = DB::table("tarif_stiker")->where("stasiunkerja_id", $id)->orderby("tarif")->get(); return $tarifStiker; } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 8620ee4..3347c35 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -6,6 +6,7 @@ use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; +use Illuminate\Validation\ValidationException; class LoginController extends Controller @@ -56,4 +57,11 @@ protected function credentials (Request $request) 'password' => $request->input('password'), ]; } + + protected function sendFailedLoginResponse(Request $request) + { + throw ValidationException::withMessages([ + 'login' => 'Username atau password tidak sesuai.', + ]); + } } diff --git a/app/Http/Controllers/Intercom/UIController.php b/app/Http/Controllers/Intercom/UIController.php new file mode 100644 index 0000000..3c74000 --- /dev/null +++ b/app/Http/Controllers/Intercom/UIController.php @@ -0,0 +1,54 @@ +recording_path) { + abort(404, 'Belum ada rekaman untuk sesi ini'); + } + + $path = $call->recording_path; + + // Amankan: hanya basename, cari di kandidat folder (relatif project + /opt). + $candidates = [ + $path, + storage_path('app/intercom/' . basename($path)), + '/opt/intercom-relay/storage/' . basename($path), + ]; + + $real = null; + foreach ($candidates as $c) { + if ($c && is_file($c)) { + $real = $c; + break; + } + } + + if (!$real) { + abort(404, 'File rekaman tidak ditemukan di server: ' . basename($path)); + } + + // File dari Go relay berupa raw PCM 16kHz mono 16-bit. + // Disajikan sebagai octet-stream biar bisa di-download / ffplay. + // Nanti kalau relay sudah tulis header WAV, ganti ke audio/wav. + return response()->file($real, [ + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment; filename="' . basename($real) . '"', + ]); + } +} diff --git a/app/Http/Controllers/LevelMenuController.php b/app/Http/Controllers/LevelMenuController.php deleted file mode 100644 index 0227cd2..0000000 --- a/app/Http/Controllers/LevelMenuController.php +++ /dev/null @@ -1,84 +0,0 @@ -from("transaksi_parkir as tp") + ->leftJoin("pegawai as p", "p.nomer", "=", "tp.adm"); + + // Tanggal wajib + $query->whereBetween("waktu_keluar", [$request->start_date . " 00:00:00", $request->end_date . " 23:59:59"]); + + // Status + if ($request->filled("status_transaksi")) { + $query->where("status_transaksi", $request->status_transaksi); + } else { + // Default hanya -1 dan -2 + $query->whereIn("status_transaksi", [0, -1, -2]); + } + + // Alasan tidak boleh NULL + if ($request->filter_alasan == "1") { + $query->whereNotNull("alasan"); + } + + $totalTransaksi = (clone $query)->count(); + + $totalBayar = (clone $query)->sum("bayar_keluar"); + + $data = $query + ->select([ + "tp.id", + "no_pol", + "waktu_masuk", + "waktu_keluar", + "status_transaksi", + "bayar_keluar", + "alasan", + "cara_bayar", + "p.nama as operator", + ]) + ->orderByDesc("waktu_keluar") + ->get(); + + return response()->json([ + "response" => true, + "data" => $data, + "summary" => [ + "total_transaksi" => $totalTransaksi, + "total_bayar" => $totalBayar, + ], + ]); + } +} diff --git a/app/Http/Controllers/Parking/MemberStikerController.php b/app/Http/Controllers/Parking/MemberStikerController.php index fa22946..402b87c 100644 --- a/app/Http/Controllers/Parking/MemberStikerController.php +++ b/app/Http/Controllers/Parking/MemberStikerController.php @@ -3,11 +3,14 @@ namespace App\Http\Controllers\Parking; use App\Http\Controllers\Controller; -use App\Models\MemberCard; +use App\Models\SoftSettings; use App\Models\StikerTrans; use App\Models\StikerTransDetail; +use App\Models\MemberCard; +use App\Services\MakeStikerServices; use Carbon\Carbon; use Exception; +use Illuminate\Support\Str; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -35,12 +38,22 @@ public function index(Request $request) public function getMember(Request $request) { - // Log::info(json_encode("nopol : " . $request->nopol)); + Log::info(json_encode("nama : " . $request->input("search-by"))); + Log::info(json_encode("value : " . $request->input("search_value"))); + Log::info(json_encode("isActive : " . $request->input("isActive"))); // Log::info(json_encode("veh : " . $request->vehicle)); // Log::info(json_encode("dept : " . $request->depart)); - // Log::info(json_encode("mem : " . $request->member)); + // Log::info(json_encode("mem : " . $request->jenismember)); // Log::info(json_encode("trans : " . $request->notrans)); // Log::info(json_encode("isactive : " . gettype($request->isactive))); + $searchBy = $request->input("search-by"); + $searchValue = trim($request->input("search_value", "")); + + $memberType = $request->input("member_type"); + $unitKerja = $request->input("departement"); + $vehicle = $request->input("vehicle"); + $isActive = $request->input("isActive"); + if ($request->ajax()) { $dataStiker = DB::table("transaksi_stiker as ts") ->leftJoin("detail_transaksi_stiker as dts", "dts.notrans", "=", "ts.notrans") @@ -55,40 +68,47 @@ public function getMember(Request $request) jm.nama vehicle, dts.jenis_member, cm.no_card as no_card'), ) ->distinct(); - // ->select(DB::raw('ts.notrans as notrans , ts.nama , ts.alamat as alamat , ts.telepon as telepon , ts.awal as awal , ts.akhir as akhir, sk.nama as unit_kerja, jl.nama as jenis_member, ts.no_id')) - if ($request->nama) { - $dataStiker = $dataStiker->where("ts.nama", "like", "%" . $request->nama . "%"); + if ($searchBy && $searchValue !== "") { + switch ($searchBy) { + case "nama": + $nama = strtolower($searchValue); + $NAMA = strtoupper($searchValue); + $dataStiker->where("ts.nama", "ILIKE", "%{$searchValue}%"); + break; + case "alamat": + $dataStiker->where("ts.alamat", "like", "%{$searchValue}%"); + break; + case "nopol": + $dataStiker->where("dts.nopol", "like", "%{$searchValue}%"); + break; + case "nocard": + $dataStiker->where("cm.nocard", "like", "%{$searchValue}%"); + break; + case "notrans": + $dataStiker->where("ts.notrans", "like", "%{$searchValue}%"); + break; + // case "": + // $dataStiker->where("ts.nama", "like", "%{$searchValue}%"); + // break; + } } - if ($request->notrans) { - $dataStiker = $dataStiker->where("ts.notrans", "like", "%" . $request->notrans . "%"); + if ($memberType) { + $dataStiker->where("ts.jenis_langganan", $memberType); } - if ($request->nopol) { - $dataStiker = $dataStiker->where("ts.unit_kerja", "like", "%" . $request->depart . "%"); + if ($unitKerja) { + $dataStiker->where("ts.unit_kerja", $unitKerja); } - if ($request->member) { - $dataStiker = $dataStiker->where("ts.jenis_langganan", "like", "%" . $request->member . "%"); + if ($vehicle) { + $dataStiker->where("dts.jenis_mobil", $vehicle); } - if ($request->nopol) { - $dataStiker = $dataStiker->where("dts.nopol", "like", "%" . $request->nopol . "%"); + if ($request->input("isActive") == "1") { + $dataStiker->whereRaw("NOW() BETWEEN ts.awal AND ts.akhir"); } - - if ($request->vehicle) { - $dataStiker = $dataStiker->where("dts.jenis_mobil", "like", "%" . $request->vehicle . "%"); - } - - if ($request->nocard) { - $dataStiker = $dataStiker->where("cm.no_card", "like", "%" . $request->nocard . "%"); - } - - if ($request->isactive === "1") { - $dataStiker = $dataStiker->whereRaw("now() between ts.awal and ts.akhir"); - } - // Log::info($dataStiker->toSql()); $dataStiker = $dataStiker->get(); @@ -132,22 +152,47 @@ public function checkField(Request $request) return response()->json(["response" => true, "message" => "Data sudah ada"]); } - public function store(Request $request) + public function store(Request $request, MakeStikerServices $makeStiker) { Log::info("Data yang diterima : " . json_encode($request->all())); $mountYearNow = Carbon::now()->format("m/Y"); - $dataStiker = $request->input("formData"); - $dataVehicle = $request->input("nopolData"); - $dataCard = $request->input("cardNumber"); - Log::info("Data Stiker : " . json_encode($dataStiker)); - Log::info("Data Kendaraan : " . json_encode($dataVehicle)); - Log::info("Data kartu : " . json_encode($dataCard)); // $cleanedData = stripslashes(trim($data)); // Log::debug('Modified : ' . json_encode($cleanedData)); // $dataStiker = json_decode($cleanedData, false); // Log::info('Debuggind data : ' . json_encode($dataStiker)); + $getTrans = DB::table("indexnum") + ->whereIn("nama", ["TRX", "IDN"]) + ->pluck("value", "nama"); + $noTrans = $getTrans->get("TRX"); + $noID = $getTrans->get("IDN"); + // Log::info("Get Trans : " . json_encode($getTrans)); + // Log::info("No Trans : " . $noTrans); + // Log::info("No ID : " . $noID); + + $noTransFormat = str_pad($noTrans, 5, "0", STR_PAD_LEFT); + $noIDFormat = str_pad($noID, 5, "0", STR_PAD_LEFT); + + $getLocation = SoftSettings::first(); + Log::info("Namalokasi : " . json_encode($getLocation->namalokasi)); + $ignore = ["RS", "PT", "CV", "TBK"]; + $nama = preg_replace("/[^A-Za-z0-9 ]/", "", $getLocation->namalokasi); + $kata = preg_split("/\s+/", trim($nama)); + $identity = ""; + foreach ($kata as $k) { + if (in_array(strtoupper($k), $ignore)) { + continue; + } + + $identity .= strtoupper($k[0]); + } + + $randomNumber = strtoupper(Str::random(6)); + + $newTrans = "TRX-" . $identity . "-" . Carbon::now()->format("Ymd-His") . "-" . $randomNumber; + $newID = "IDN-" . $identity . "-" . Carbon::now()->format("Ymd-His") . "-" . $randomNumber; + // $noid = $seqId->value . "-IDT-" . $mountYearNow; // $validate = Validator::make($request->all(),[ // 'nama' => 'required|string|unique', // // 'nopol' => 'required|string|unique:detail_transaksi_stiker,nopol', @@ -158,84 +203,131 @@ public function store(Request $request) // return response()->json(['response' => false, 'message' => 'pastikan nama, Nomor Polisi, dan data kartu sudah terisi']); // } - $dateNow = Carbon::now()->format("Y-m-d H:m:s"); - $seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first(); - $seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first(); + // $dateNow = Carbon::now()->format("Y-m-d H:m:s"); + // $seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first(); + // $seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first(); - $notrans = $seqTrans->value . "/STK/" . $mountYearNow; - $noid = $seqId->value . "/IDT/" . $mountYearNow; - $user = Auth::user(); + // $notrans = $seqTrans->value . "/STK/" . $mountYearNow; + // $noid = $seqId->value . "/IDT/" . $mountYearNow; + // $user = Auth::user(); - $periode = explode(" - ", $dataStiker["periode"]); - Log::info("Parcing Periode : " . json_encode($periode)); - $awal = Carbon::createFromFormat("d-m-Y H:i", $periode[0]); - $akhir = Carbon::createFromFormat("d-m-Y H:i", $periode[1]); + // // Log::info($user); + + $dataStiker = $request->input("formData"); + $dataVehicle = $request->input("nopolData"); + $dataCard = $request->input("cardNumber"); + Log::info("Data Stiker : " . json_encode($dataStiker)); + Log::info("Data Kendaraan : " . json_encode($dataVehicle)); + Log::info("Data kartu : " . json_encode($dataCard)); + + if ($dataStiker["periode"]) { + $periode = explode(" - ", $dataStiker["periode"]); + Log::info("Parcing Periode : " . json_encode($periode)); + $awal = Carbon::createFromFormat("d-m-Y H:i", $periode[0]); + $akhir = Carbon::createFromFormat("d-m-Y H:i", $periode[1]); + } else { + $awal = Carbon::createFromFormat("d-m-Y H:i", $dataStiker["awal"]); + $akhir = Carbon::createFromFormat("d-m-Y H:i", $dataStiker["akhir"]); + } Log::info("Awal : " . $awal . " Akhir : " . $akhir); - // Log::info($user); - try { - $dataStiker = [ - "jenis_transaksi" => $dataStiker["jenis_transaksi"], // 0 = transaksi baru , 1 = perpanjangan - "notrans" => $notrans, - "no_id" => $noid, - "nama" => $dataStiker["nama"], - "alamat" => $dataStiker["address"] ?? "", - "telepon" => $dataStiker["phone"] ?? "", - "unit_kerja" => $dataStiker["depart"], - "jenis_stiker" => $dataStiker["member"], - "awal" => $awal, - "akhir" => $akhir, - "harga" => $dataStiker["harga"] ?? 0, - "keterangan" => $dataStiker["note"] ?? "", - "tanggal" => $dateNow, - "operator" => $user->nomer, - ]; + $dataStiker["notrans"] = $newTrans; + $dataStiker["no_id"] = $newID; + $dataStiker["awal"] = $awal; + $dataStiker["akhir"] = $akhir; - DB::table("transaksi_stiker")->insert($dataStiker); - - if ($dataVehicle) { - foreach ($dataVehicle as $nopol) { - $dataVehicle = [ - "notrans" => $notrans, - "nopol" => $nopol["nopol"], - "jenis_mobil" => $nopol["vehicle"], - "jenis_member" => $nopol["member"], - "merk" => $nopol["merk"], - ]; - DB::table("detail_transaksi_stiker")->insert($dataVehicle); - } - } else { - return 0; + if (!empty($dataVehicle)) { + foreach ($dataVehicle as &$vehicle) { + $vehicle["notrans"] = $newTrans; } - - if ($dataCard) { - foreach ($dataCard as $cardNumber) { - $dataCard = [ - "notrans" => $noid, - "no_card" => $cardNumber["nocard"], - "no_urut" => $cardNumber["nourut"], - ]; - - DB::table("card_member")->insert($dataCard); - } - } else { - return 0; - } - - DB::table("indexnum") - ->where("nama", "sticker") - ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); - DB::table("indexnum") - ->where("nama", "identitas") - ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); - $response = true; - $msg = "Data berhasil di simpan"; - } catch (Exception $e) { - $response = false; - $msg = "Gagal menyimpan data " . $e->getMessage() . $e->getLine(); } - return response()->json(["response" => $response, "pesan" => $msg]); + if (!empty($dataCard)) { + foreach ($dataCard as &$cardNumber) { + $cardNumber["notrans"] = $newID; + } + } + + $createData = $makeStiker->stikerCreate( + [ + "dataSticker" => $dataStiker, + "dataVehicle" => $dataVehicle ?? null, + "dataCard" => $dataCard ?? null, + ], + $awal, + $akhir, + ); + + Log::info("Status penyimpanan : " . $createData); + + // try { + // $dataStiker = [ + // "jenis_transaksi" => $dataStiker["jenis_transaksi"], // 0 = transaksi baru , 1 = perpanjangan + // "notrans" => $notrans, + // "no_id" => $noid, + // "nama" => $dataStiker["nama"], + // "alamat" => $dataStiker["address"] ?? "", + // "telepon" => $dataStiker["phone"] ?? "", + // "unit_kerja" => $dataStiker["depart"], + // "jenis_stiker" => $dataStiker["member"], + // "awal" => $awal, + // "akhir" => $akhir, + // "harga" => $dataStiker["harga"] ?? 0, + // "keterangan" => $dataStiker["note"] ?? "", + // "tanggal" => $dateNow, + // "operator" => $user->nomer, + // ]; + + // DB::table("transaksi_stiker")->insert($dataStiker); + + // if ($dataVehicle) { + // foreach ($dataVehicle as $nopol) { + // $dataVehicle = [ + // "notrans" => $notrans, + // "nopol" => $nopol["nopol"], + // "jenis_mobil" => $nopol["vehicle"], + // "jenis_member" => $nopol["member"], + // "merk" => $nopol["merk"], + // ]; + // DB::table("detail_transaksi_stiker")->insert($dataVehicle); + // } + // } else { + // return 0; + // } + + // if ($dataCard) { + // foreach ($dataCard as $cardNumber) { + // $dataCard = [ + // "notrans" => $noid, + // "no_card" => $cardNumber["nocard"], + // "no_urut" => $cardNumber["nourut"], + // ]; + + // DB::table("card_member")->insert($dataCard); + // } + // } else { + // return 0; + // } + + // DB::table("indexnum") + // ->where("nama", "sticker") + // ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); + // DB::table("indexnum") + // ->where("nama", "identitas") + // ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); + // $response = true; + // $msg = "Data berhasil di simpan"; + // } catch (Exception $e) { + // $response = false; + // $msg = "Gagal menyimpan data " . $e->getMessage() . $e->getLine(); + // } + + return $createData; + // return response()->json([ + // // "response" => $response, + // "response" => $createData, + // "pesan" => $createData, + // ]); } /** @@ -247,8 +339,9 @@ public function store(Request $request) public function show($id) { Log::info("Data yang diterima : " . json_encode($id)); - $dataStiker = StikerTrans::with("stiker_details.vehicle", "memberCard") - ->select( + $dataStiker = StikerTrans + // with("stiker_details.vehicle", "memberCard") + ::select( "transaksi_stiker.notrans", "transaksi_stiker.nama", "transaksi_stiker.alamat", @@ -258,13 +351,31 @@ public function show($id) "transaksi_stiker.jenis_langganan", "transaksi_stiker.unit_kerja", "transaksi_stiker.no_id", + "transaksi_stiker.harga", "jl.nama as jmember", "sk.nama as dept", + // "detail_transaksi_stiker.nama", + // "detail_transaksi_stiker.tarif", ) ->leftJoin("jenis_langganan as jl", "jl.id", "=", "transaksi_stiker.jenis_langganan") ->leftJoin("stasiunkerja as sk", "sk.kode", "=", "transaksi_stiker.unit_kerja") ->where("notrans", "like", "%" . $id . "%") ->first(); + + $vehicleDetails = StikerTransDetail::query() + ->from("detail_transaksi_stiker as dts") + ->select("jm.nama as vehicle", "ts.tarif", "dts.*") + ->leftJoin("jenis_mobil as jm", "dts.jenis_mobil", "=", "jm.id") + ->leftJoin("tarif_stiker as ts", function ($join) { + $join->on("dts.jenis_member", "=", "ts.jenis_langganan")->on("dts.jenis_mobil", "=", "ts.id_mobil"); + }) + ->where("notrans", $dataStiker->notrans) + ->get(); + + $cardMember = MemberCard::where("notrans", $dataStiker->no_id)->get(); + + $dataStiker["stiker_details"] = $vehicleDetails; + $dataStiker["memberCard"] = $cardMember; // $dataStiker = DB::table('transaksi_stiker as ts') // ->leftjoin('detail_transaksi_stiker as dts','dts.notrans','=','ts.notrans') // ->leftJoin('jenis_langganan as jl','jl.id','=','ts.jenis_langganan') diff --git a/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php b/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php index af99ced..4233745 100644 --- a/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php +++ b/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php @@ -4,6 +4,8 @@ use App\Http\Controllers\Controller; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Symfony\Component\HttpFoundation\StreamedResponse; class TransaksiParkirController extends Controller { @@ -17,6 +19,118 @@ public function index() return view ('operasional.transaksi-parkir'); } + /** + * Data transaksi parkir dengan filter pencarian. + * + * Filter (semua opsional): + * - field: id | no_pol | no_barcode, + q: kata kunci + * - tgl_awal / tgl_akhir (Y-m-d): periode kolom tanggal + * - status_kendaraan: 1 = masuk, 0 = keluar + * - status_transaksi: 0 = casual, 3 = member, 32 = denda + * - export=csv: unduh hasil filter sebagai CSV + */ + public function data(Request $request) + { + $request->validate([ + 'field' => 'nullable|in:id,no_pol,no_barcode', + 'q' => 'nullable|string|max:100', + 'tgl_awal' => 'nullable|date_format:Y-m-d', + 'tgl_akhir' => 'nullable|date_format:Y-m-d|after_or_equal:tgl_awal', + 'status_kendaraan' => 'nullable|in:0,1', + 'status_transaksi' => 'nullable|in:0,3,32', + 'per_page' => 'nullable|integer|min:1|max:500', + ]); + + $query = $this->filteredQuery($request); + + if ($request->query('export') === 'csv') { + return $this->exportCsv($query); + } + + $perPage = (int) $request->input('per_page', 50); + $rows = $query->paginate($perPage); + + return response()->json([ + 'status' => true, + 'data' => $rows->items(), + 'meta' => [ + 'current_page' => $rows->currentPage(), + 'per_page' => $rows->perPage(), + 'total' => $rows->total(), + 'last_page' => $rows->lastPage(), + ], + ]); + } + + protected function filteredQuery(Request $request) + { + $query = DB::table('transaksi_parkir as tp') + ->leftJoin('jenis_mobil as jm', 'jm.id', '=', 'tp.id_kendaraan') + ->select( + 'tp.id', + 'tp.no_pol', + 'tp.no_barcode', + 'tp.id_kendaraan', + 'jm.nama as nama_kendaraan', + 'tp.status', + 'tp.status_transaksi', + 'tp.waktu_masuk', + 'tp.waktu_keluar', + 'tp.tanggal' + ); + + // 1. Pencarian: id / no_pol / no_barcode + if ($request->filled('q')) { + $field = $request->input('field', 'id'); + $query->where("tp.{$field}", 'ILIKE', '%' . $request->input('q') . '%'); + } + + // 2. Periode awal - akhir (kolom tanggal) + if ($request->filled('tgl_awal')) { + $query->whereDate('tp.tanggal', '>=', $request->input('tgl_awal')); + } + if ($request->filled('tgl_akhir')) { + $query->whereDate('tp.tanggal', '<=', $request->input('tgl_akhir')); + } + + // 3. Status kendaraan: 1 = masuk, 0 = keluar + if ($request->filled('status_kendaraan')) { + $query->where('tp.status', (int) $request->input('status_kendaraan')); + } + + // 4. Status transaksi: 0 = casual, 3 = member, 32 = denda + if ($request->filled('status_transaksi')) { + $query->where('tp.status_transaksi', (int) $request->input('status_transaksi')); + } + + return $query->orderBy('tp.tanggal', 'desc'); + } + + protected function exportCsv($query): StreamedResponse + { + $filename = 'transaksi-parkir-' . now()->format('Ymd-His') . '.csv'; + + return response()->streamDownload(function () use ($query) { + $out = fopen('php://output', 'w'); + fputcsv($out, ['ID', 'No. Pol', 'Barcode', 'Kendaraan', 'Status Kendaraan', 'Status Transaksi', 'Waktu Masuk', 'Waktu Keluar']); + $query->chunk(1000, function ($rows) use ($out) { + foreach ($rows as $r) { + fputcsv($out, [ + $r->id, + $r->no_pol, + $r->no_barcode, + trim(($r->id_kendaraan ?? '') . ' ' . ($r->nama_kendaraan ?? '')), + $r->status, + $r->status_transaksi, + $r->waktu_masuk, + $r->waktu_keluar, + ]); + } + }); + fclose($out); + }, $filename, ['Content-Type' => 'text/csv']); + } + /** * Show the form for creating a new resource. * diff --git a/app/Http/Controllers/Parking/ReportTransaksiController.php b/app/Http/Controllers/Parking/ReportTransaksiController.php deleted file mode 100755 index 0ac2bb8..0000000 --- a/app/Http/Controllers/Parking/ReportTransaksiController.php +++ /dev/null @@ -1,999 +0,0 @@ -all()); - - $awal = $request->tanggal_mulai; - $akhir = $request->tanggal_selesai; - $isShift = $request->shift === "on"; - $dateBy = $isShift ? "pklogin" : "waktu_keluar"; - $paymentMethod = $request->payment_method === "null" ? null : $request->payment_method; - - // ๐Ÿ”น Base select - $select = [ - "np.nama as gate", - "tp.id_pintu_keluar", - "p.nama as petugas", - "tp.id_op_keluar", - "jm.nama as kendaraan", - "tp.status_transaksi", - DB::raw("count(*) as jumlah_transaksi"), - DB::raw("sum(tp.bayar_keluar) as income_transaksi"), - ]; - - // ๐Ÿ”น Tambah shift kalau aktif - if ($isShift) { - $select[] = "tp.id_shift_keluar"; - } - - // ๐Ÿ”น Base group - $groupBy = ["tp.id_pintu_keluar", "np.nama", "p.nama", "tp.id_op_keluar", "jm.nama", "tp.status_transaksi"]; - - if ($isShift) { - $groupBy[] = "tp.id_shift_keluar"; - } - - $query = DB::table("transaksi_parkir as tp") - ->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar") - ->leftJoin("nama_pos as np", "np.id", "=", "tp.id_pintu_keluar") - ->leftJoin("jenis_mobil as jm", "jm.id", "=", "tp.id_kendaraan") - ->select($select) - - // ๐Ÿ”น Filter tanggal (dynamic field) - ->where("tp.status", 0) - - ->whereBetween($dateBy, [$awal . " 00:00:00", $akhir . " 23:59:59"]) - - // // ๐Ÿ”น STATUS - // ->when($request->status_transaksi, fn($q, $v) => - // $q->where('tp.status_transaksi', $v) - // ) - - // ๐Ÿ”น PINTU - ->when($request->id_pintu_keluar, fn($q, $v) => $q->whereIn("tp.id_pintu_keluar", (array) $v)) - - // ๐Ÿ”น Status Transaksi - // ->when($request->status_transaksi, function ($q) use ($request) { - ->when(isset($request->status_transaksi), function ($q) use ($request) { - return match ($request->status_transaksi) { - // '0' => $q->whereNotIn('tp.status_transaksi', ['3','-1'])->whereNotNull('alasan'), - "0" => $q->where("tp.status_transaksi", "0")->whereNull("alasan"), - "3" => $q->where("tp.status_transaksi", "3")->whereNull("alasan"), - "-1" => $q->where("tp.status_transaksi", "-1")->whereNotNull("alasan"), - "null" => $q->whereNull("alasan"), - }; - }) - - // ๐Ÿ”น CARA BAYAR - ->when($request->cara_bayar, function ($q) use ($request) { - return match ($request->cara_bayar) { - "cash" => $q->where("tp.cara_bayar", "!=", 3), - "cashless" => $q->where("tp.cara_bayar", 3), - default => $q, - }; - }) - - ->when($paymentMethod, function ($q) use ($paymentMethod) { - return $q->where("tp.rep_bank", $paymentMethod); - - // return match ($request->payment_method) { - // 2 => $q->where('tp.rep_bank', 2), - // 3 => $q->where('tp.rep_bank', 3), - // 4 => $q->where('tp.rep_bank', 4), - // 5 => $q->where('tp.rep_bank', 5), - // 9 => $q->where('tp.rep_bank', 9), - // default => $q - // }; - }) - - // // ๐Ÿ”น PETUGAS - // ->when($request->id_op_keluar, fn($q, $v) => - // $q->whereIn('tp.id_op_keluar', (array) $v) - // ) - - // // ๐Ÿ”น KENDARAAN - // ->when($request->id_kendaraan, fn($q, $v) => - // $q->whereIn('tp.id_kendaraan', (array) $v) - // ) - - ->groupBy($groupBy) - - ->orderBy("tp.id_pintu_keluar") - ->when($isShift, fn($q) => $q->orderBy("tp.id_shift_keluar")) - ->orderBy("tp.id_op_keluar") - ->orderBy("jm.nama") - ->orderBy("tp.status_transaksi"); - - return response()->json([ - "byGateData" => $query->get(), - ]); - } - - public function allReport() - { - return view("report.all-report"); - } - - public function allReportData(Request $request) - { - return response()->json([ - "vehicle" => $this->vehicleReport($request), - "officer" => $this->reportOfficer($request), - "payment" => $this->paymentType($request), - ]); - } - - public function vehicleReport(Request $request) - { - Log::info("== allReportData START =="); - Log::info($request->harian); - Log::info($request->tanggal); - $selectDate = $request->tanggal; - $start = microtime(true); - $harian = $request->harian ?? 1; - $dateNow = "2026-01-25"; - $startDate = $selectDate ? $selectDate : $dateNow; - $endDate = $selectDate ? $selectDate : $dateNow; - - Log::info("Tanggal laporan", [ - "start" => $startDate, - "end" => $endDate, - ]); - - // ========================= - // QUERY UTAMA (LEFT JOIN) - // ========================= - Log::info("Query transaksi dimulai"); - - // $dateColumn = $harian == 1 - // ? 'tp.waktu_keluar' - // : 'tp.pklogin'; - if ($harian == 1) { - $isReport = "harian"; - // $dateColumn = ["tp.waktu_keluar"]; - $dateColumn = ["tp.waktu_masuk", "tp.waktu_keluar"]; - } else { - $isReport = "shift"; - // $dateColumn = ["tp.pklogin"]; - $dateColumn = ["tp.pmlogin", "tp.pklogin"]; - } - - $transactions = DB::table("transaksi_parkir as tp") - ->leftJoin("jenis_mobil as jm", "jm.id", "=", "tp.id_kendaraan") - ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk") - ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar") - ->where(function ($q) use ($dateColumn, $startDate, $endDate, $selectDate) { - $q->whereBetween($dateColumn[0], [$startDate . " 00:00:00", $endDate . " 23:59:59"])->orWhereBetween( - $dateColumn[0], - [$startDate . " 00:00:00", $endDate . " 23:59:59"], - ); - // $q->whereRaw('DATE(tp.waktu_masuk) = ?', [$selectDate]) - // ->orWhereRaw('DATE(tp.waktu_keluar) = ?', [$selectDate]); - }) - // ->whereRaw("DATE($dateColumn) = ?", [$dateNow]) - // ->whereBetween($dateColumn, [$startDate, $endDate]) - ->select( - "tp.id", - "jm.nama as jenis_mobil", - "tp.id_kendaraan", - - "pm.nama as kasir_masuk", - "pk.nama as kasir_keluar", - - "tp.id_op_masuk", - - "tp.waktu_masuk", - "tp.waktu_keluar", - "tp.status", - "tp.bayar_masuk", - "tp.bayar_keluar", - "tp.status_transaksi", - "tp.cara_bayar", - ) - ->get(); - - Log::info("Query transaksi selesai", [ - "rows" => $transactions->count(), - "query_time_ms" => round((microtime(true) - $start) * 1000, 2), - ]); - - // ========================= - // REKAP PER KENDARAAN - // ========================= - Log::info("Rekap kendaraan dimulai"); - - $tunai = $transactions - ->sortBy("id_kendaraan") - ->groupBy("id_kendaraan") - ->map(function ($items, $idKendaraan) use ($startDate, $endDate) { - $start = $startDate . " 00:00:00"; - $end = $endDate . " 00:00:00"; - // ====================== - // PISAH MASUK & KELUAR - // ====================== - // $masuk = $items; - $masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true)); - - // $masuk = $items->where("status", 1); - // ->whereBetween("waktu_masuk", [$startDate . " 00:00:00", $endDate . " 23:59:59"]); - // ->filter(function ($item) use ($start, $end) { - // $waktuMasuk = $item->waktu_masuk; - // $pmLogin = $item->pmlogin ?? null; - - // $inWaktuMasuk = $waktuMasuk && $waktuMasuk >= $start && $waktuMasuk <= $end; - // $inPmMasuk = $pmLogin && $pmLogin >= $start && $pmLogin <= $end; - - // return $inWaktuMasuk || $inPmMasuk; - // }); - // $keluar = $items->where('status', 0); - $keluar = $items->filter(fn($r) => (int) $r->status === 0); - // $tunai = $keluar->where('cara_bayar', '!=', 3); - $tunai = $keluar->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true)); - - // ====================== - // MASUK - // ====================== - $masukCasual = $masuk->where("status_transaksi", "0"); - $masukMember = $masuk->where("status_transaksi", "3"); - - // ====================== - // KELUAR - // ====================== - // $keluarCasual = $tunai->where('status_transaksi', '0'); - // $keluarMember = $tunai->where('status_transaksi', '3'); - // $keluarLost = $tunai->where('status_transaksi', '32'); - $keluarCasual = $tunai->filter( - fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null, - ); - - $keluarMember = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3"); - - $keluarLost = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32"); - - $keluarManual = $tunai->filter(fn($r) => data_get($r, "id_op_masuk") === null); - - $keluarMasalah = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "-1"); - - // ====================== - // PEMBAYARAN (KELUAR) - // ====================== - // $nonTunai = $keluar->where('cara_bayar', 3); - // $tunai = $keluar->where('cara_bayar', '!=', 3); - $totalCash = - $keluarCasual->sum("bayar_keluar") + - $keluarLost->sum("bayar_keluar") + - $keluarMember->sum("bayar_keluar") + - $keluarManual->sum("bayar_keluar"); - - return [ - "id_kendaraan" => $idKendaraan, - "jenis_mobil" => $items->first()->jenis_mobil, - "total" => $items->count(), - "tunaiOut" => $tunai->count(), - // ========= MASUK ========= - "masuk" => [ - "casual" => [ - "jumlah" => $masuk->count(), - "income" => $masukCasual->sum("bayar_masuk"), - ], - "member" => [ - "jumlah" => $masukMember->count(), - "income" => $masukMember->sum("bayar_masuk"), - ], - ], - - // ========= KELUAR ========= - "keluar" => [ - "casual" => [ - "jumlah" => $keluarCasual->count(), - "income" => $keluarCasual->sum("bayar_keluar"), - ], - "lost" => [ - "jumlah" => $keluarLost->count(), - "income" => $keluarLost->sum("bayar_keluar"), - ], - "member" => [ - "jumlah" => $keluarMember->count(), - "income" => $keluarMember->sum("bayar_keluar"), - ], - "manual" => [ - "jumlah" => $keluarManual->count(), - "income" => $keluarManual->sum("bayar_keluar"), - ], - "masalah" => [ - "jumlah" => $keluarMasalah->count(), - "income" => $keluarMasalah->sum("bayar_keluar"), - ], - ], - "totalCash" => $totalCash, - ]; - }) - ->values(); - - $cashless = $transactions - ->sortBy("id_kendaraan") - ->groupBy("id_kendaraan") - ->map(function ($items, $idKendaraan) { - // ====================== - // PISAH MASUK & KELUAR - // ====================== - // $masuk = $items->where("status", 1); - $masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true)); - // $keluar = $items->where('status', 0); - // $nonTunai = $keluar->where('cara_bayar', 3); - $keluar = $items->filter(fn($r) => (int) $r->status === 0); - // $tunai = $keluar->where('cara_bayar', '!=', 3); - $nonTunai = $keluar->filter(fn($row) => (string) data_get($row, "cara_bayar") === "3"); - - // ====================== - // MASUK - // ====================== - $masukCasual = $masuk; - $masukMember = $masuk->where("status_transaksi", "3"); - - // ====================== - // KELUAR - // ====================== - // $keluarCasual = $nonTunai->where('status_transaksi', '0'); - // $keluarMember = $nonTunai->where('status_transaksi', '3'); - // $keluarLost = $nonTunai->where('status_transaksi', '32'); - $keluarCasual = $nonTunai->filter( - fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null, - ); - $keluarMember = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3"); - $keluarLost = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32"); - $keluarManual = $nonTunai->filter(fn($r) => data_get($r, "id_op_masuk") === null); - - $totalCashless = - $keluarCasual->sum("bayar_keluar") + - $keluarLost->sum("bayar_keluar") + - $keluarMember->sum("bayar_keluar") + - $keluarManual->sum("bayar_keluar"); - - // ====================== - // PEMBAYARAN (KELUAR) - // ====================== - - return [ - "id_kendaraan" => $idKendaraan, - "jenis_mobil" => $items->first()->jenis_mobil, - "total" => $items->count(), - "nonTunaiOut" => $nonTunai->count(), - - // ========= MASUK ========= - "masuk" => [ - "casual" => [ - "jumlah" => $masuk->count(), - "income" => $masukCasual->sum("bayar_masuk"), - ], - "member" => [ - "jumlah" => $masukMember->count(), - "income" => $masukMember->sum("bayar_masuk"), - ], - ], - - // ========= KELUAR ========= - "keluar" => [ - "casual" => [ - "jumlah" => $keluarCasual->count(), - "income" => $keluarCasual->sum("bayar_keluar"), - ], - "lost" => [ - "jumlah" => $keluarLost->count(), - "income" => $keluarLost->sum("bayar_keluar"), - ], - "member" => [ - "jumlah" => $keluarMember->count(), - "income" => $keluarMember->sum("bayar_keluar"), - ], - "manual" => [ - "jumlah" => $keluarManual->count(), - "income" => $keluarManual->sum("bayar_keluar"), - ], - ], - "totalCashless" => $totalCashless, - ]; - }) - ->values(); - - $transactions = DB::table("transaksi_parkir as tp") - ->leftJoin("jenis_mobil as jm", "jm.id", "=", "tp.id_kendaraan") - ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk") - ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar") - ->whereDate("waktu_masuk", "<", $dateNow) - ->select( - "tp.id_kendaraan", - "jm.nama as jenis_mobil", - DB::raw("SUM(CASE WHEN tp.status = 1 THEN 1 ELSE 0 END) as overnight"), - DB::raw("SUM(CASE WHEN tp.status = 3 THEN 1 ELSE 0 END) as onexpired"), - ) - ->groupBy("tp.id_kendaraan", "jm.nama") - ->orderBy("tp.id_kendaraan") - // dd($transactions->toSql()); - ->get(); - - $onCheck = $transactions - // ->sortBy('id_kendaraan') - // ->groupBy('id_kendaraan') - ->map(function ($items) { - // $overNight = $items->filter(fn ($r) => (int) $r->status === 1); - // $onExpired = $items->filter(fn ($r) => (int) $r->status === 3); - - return [ - "id_kendaraan" => $items->id_kendaraan, - "jenis_mobil" => $items->jenis_mobil, - "overNight" => (int) $items->overnight, - "onExpired" => (int) $items->onexpired, - ]; - }) - ->values(); - - Log::info("Rekap kendaraan selesai", [ - "total_kendaraan" => $tunai->count(), - ]); - - Log::info("== allReportData END ==", [ - "total_time_ms" => round((microtime(true) - $start) * 1000, 2), - ]); - - return [ - // 'date' => $startDate->format('Y-m-d'),รท - "date" => $startDate, - "reportTime" => $isReport, - "tunai" => $tunai, - "nontunai" => $cashless, - "overNight" => $onCheck, - // 'transactions' => $transactions->groupBy('id_kendaraan') - ]; - } - - // ==========###################### Begin Laporan per petugas #######################===============// - public function reportOfficer(Request $request) - { - Log::info("== Officer Report START =="); - Log::info($request->harian); - Log::info($request->tanggal); - $selectDate = $request->tanggal; - $start = microtime(true); - $harian = $request->harian ?? 1; - // $selectDate = Carbon::createFromFormat("", $tanggalRequest)->format("Y-m-d"); - // $dateNow = Carbon::now()->format('Y-m-d'); - $dateNow = "2026-01-25"; - // $startDate = Carbon::today()->startOfDay(); - // $endDate = Carbon::today()->endOfDay(); - // $startDate = $selectDate . ' 00:00:00'; - // $endDate = $selectDate . ' 23:59:59'; - $startDate = $selectDate ? $selectDate : $dateNow; - $endDate = $selectDate ? $selectDate : $dateNow; - - Log::info("Tanggal laporan", [ - "start" => $startDate, - "end" => $endDate, - ]); - - // ========================= - // QUERY UTAMA (LEFT JOIN) - // ========================= - Log::info("Query transaksi dimulai"); - - // $dateColumn = $harian == 1 - // ? 'tp.waktu_keluar' - // : 'tp.pklogin'; - if ($harian == 1) { - $dateColumn = ["tp.waktu_masuk", "tp.waktu_keluar"]; - $dateColumn = ["tp.waktu_keluar"]; - } else { - $dateColumn = ["tp.pmlogin", "tp.pklogin"]; - $dateColumn = ["tp.pklogin"]; - } - - $transactions = DB::table("transaksi_parkir as tp") - ->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar") - // ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk") - // ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar") - ->where(function ($q) use ($dateColumn, $startDate, $endDate, $selectDate) { - $q->whereBetween($dateColumn[0], [$startDate . " 00:00:00", $endDate . " 23:59:59"])->orWhereBetween( - $dateColumn[0], - [$startDate . " 00:00:00", $endDate . " 23:59:59"], - ); - // $q->whereRaw('DATE(tp.waktu_masuk) = ?', [$selectDate]) - // ->orWhereRaw('DATE(tp.waktu_keluar) = ?', [$selectDate]); - }) - // ->whereNull("id_op_masuk") - // ->where("manual", 1) - // ->whereRaw("DATE($dateColumn) = ?", [$dateNow]) - // ->whereBetween($dateColumn, [$startDate, $endDate]) - ->select( - "p.nomer as id_petugas", - "p.nama as petugas", - // "tp.id_kendaraan", - - // "pm.nama as kasir_masuk", - // "pk.nama as kasir_keluar", - "tp.id_op_masuk", // โœ… ADA - "tp.id_op_keluar", - - "tp.waktu_masuk", - "tp.waktu_keluar", - "tp.status", - "tp.bayar_masuk", - "tp.bayar_keluar", - "tp.status_transaksi", - "tp.cara_bayar", - ) - ->get(); - // Log::info("Result nya : " . json_encode($transactions)); - - Log::info("Query transaksi selesai", [ - "rows" => $transactions->count(), - "query_time_ms" => round((microtime(true) - $start) * 1000, 2), - ]); - - // ========================= - // REKAP PER KENDARAAN - // ========================= - Log::info("Rekap petugas dimulai"); - - $tunai = $transactions - ->sortBy("petugas") - ->groupBy("id_op_keluar") - ->map(function ($items, $id_op_keluar) { - // ====================== - // PISAH MASUK & KELUAR - // ====================== - // $masuk = $items->where('status', 1); - $masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true)); - // $keluar = $items->where('status', 0); - $keluar = $items->filter(fn($r) => (int) $r->status === 0); - // $tunai = $keluar->where('cara_bayar', '!=', 3); - - // ====================== - // MASUK - // ====================== - // $masukCasual = $masuk->where('status_transaksi', '0'); - // $masukMember = $masuk->where('status_transaksi', '3'); - - // ====================== - // KELUAR - // ====================== - // $keluarCasual = $tunai->where('status_transaksi', '0'); - // $keluarMember = $tunai->where('status_transaksi', '3'); - // $keluarLost = $tunai->where('status_transaksi', '32'); - $tunai = $keluar->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true)); - - $keluarCasual = $tunai->filter( - fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null, - ); - - $keluarMember = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3"); - - $keluarLost = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32"); - - $keluarManual = $tunai->filter(fn($r) => data_get($r, "id_op_masuk") === null); - - $keluarMasalah = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "-1"); - - // ====================== - // PEMBAYARAN (KELUAR) - // ====================== - // $nonTunai = $keluar->where('cara_bayar', 3); - // $tunai = $keluar->where('cara_bayar', '!=', 3); - $totalCash = - $keluarCasual->sum("bayar_keluar") + - $keluarLost->sum("bayar_keluar") + - $keluarMember->sum("bayar_keluar") + - $keluarManual->sum("bayar_keluar"); - - Log::info("Transaksi tunai : "); - - return [ - "id_petugas" => $id_op_keluar, - "petugas" => $items->first()->petugas, - "total" => 0, - "tunaiOut" => $tunai->count(), - // ========= MASUK ========= - "masuk" => [ - "casual" => [ - "jumlah" => 0, - "income" => $masuk->sum("bayar_masuk"), - ], - // 'member' => [ - // 'jumlah' => $masukMember->count(), - // 'income' => $masukMember->sum('bayar_masuk'), - // ], - ], - - // ========= KELUAR ========= - "keluar" => [ - "casual" => [ - "jumlah" => $keluarCasual->count(), - "income" => $keluarCasual->sum("bayar_keluar"), - ], - "lost" => [ - "jumlah" => $keluarLost->count(), - "income" => $keluarLost->sum("bayar_keluar"), - ], - "member" => [ - "jumlah" => $keluarMember->count(), - "income" => $keluarMember->sum("bayar_keluar"), - ], - "manual" => [ - "jumlah" => $keluarManual->count(), - "income" => $keluarManual->sum("bayar_keluar"), - ], - "masalah" => [ - "jumlah" => $keluarMasalah->count(), - "income" => $keluarMasalah->sum("bayar_keluar"), - ], - ], - "totalCash" => $totalCash, - ]; - }) - ->values(); - - $cashless = $transactions - ->sortBy("id_op_keluar") - ->groupBy("id_op_keluar") - ->map(function ($items, $id_op_keluar) { - // ====================== - // PISAH MASUK & KELUAR - // ====================== - // $masuk = $items->where('status', 1); - $masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true)); - // $keluar = $items->where('status', 0); - // $nonTunai = $keluar->where('cara_bayar', 3); - $keluar = $items->filter(fn($r) => (int) $r->status === 0); - // $tunai = $keluar->where('cara_bayar', '!=', 3); - $nonTunai = $keluar->filter(fn($row) => (string) data_get($row, "cara_bayar") === "3"); - - // ====================== - // MASUK - // ====================== - // $masukCasual = $masuk->where('status_transaksi', '0'); - // $masukMember = $masuk->where('status_transaksi', '3'); - - // ====================== - // KELUAR - // ====================== - // $keluarCasual = $nonTunai->where('status_transaksi', '0'); - // $keluarMember = $nonTunai->where('status_transaksi', '3'); - // $keluarLost = $nonTunai->where('status_transaksi', '32'); - $keluarCasual = $nonTunai->filter( - fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null, - ); - $keluarMember = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3"); - $keluarLost = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32"); - $keluarManual = $nonTunai->filter(fn($r) => data_get($r, "id_op_masuk") === null); - - $totalCashless = - $keluarCasual->sum("bayar_keluar") + - $keluarLost->sum("bayar_keluar") + - $keluarMember->sum("bayar_keluar") + - $keluarManual->sum("bayar_keluar"); - - // ====================== - // PEMBAYARAN (KELUAR) - // ====================== - - return [ - "id_petugas" => $id_op_keluar, - "petugas" => $items->first()->petugas, - "total" => 0, - "nonTunaiOut" => $nonTunai->count(), - - // ========= MASUK ========= - "masuk" => [ - "casual" => [ - "jumlah" => 0, - "income" => $masuk->sum("bayar_masuk"), - ], - "member" => [ - "jumlah" => $masuk->count(), - "income" => $masuk->sum("bayar_masuk"), - ], - ], - - // ========= KELUAR ========= - "keluar" => [ - "casual" => [ - "jumlah" => $keluarCasual->count(), - "income" => $keluarCasual->sum("bayar_keluar"), - ], - "lost" => [ - "jumlah" => $keluarLost->count(), - "income" => $keluarLost->sum("bayar_keluar"), - ], - "member" => [ - "jumlah" => $keluarMember->count(), - "income" => $keluarMember->sum("bayar_keluar"), - ], - "manual" => [ - "jumlah" => $keluarManual->count(), - "income" => $keluarManual->sum("bayar_keluar"), - ], - ], - "totalCashless" => $totalCashless, - ]; - }) - ->values(); - - // $transactions = DB::table("transaksi_parkir as tp") - // ->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar") - // ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk") - // ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar") - // ->whereDate("waktu_masuk", "<", $dateNow) - // ->select( - // "tp.id_kendaraan", - // "p.nama as jenis_mobil", - // "tp.id_op_keluar", - // "p.nama as petugas", - // DB::raw("SUM(CASE WHEN tp.status = 1 THEN 1 ELSE 0 END) as overnight"), - // DB::raw("SUM(CASE WHEN tp.status = 3 THEN 1 ELSE 0 END) as onexpired"), - // ) - // ->groupBy("tp.id_op_keluar", "petugas") - // ->orderBy("tp.id_op_keluar", "asc") - // ->groupBy("p.nomer", "p.nama") - // ->orderBy("p.nomer") - // dd($transactions->toSql()); - // ->get(); - - // $onCheck = $transactions - // ->sortBy('id_kendaraan') - // ->groupBy('id_kendaraan') - // ->map(function ($items) { - // $overNight = $items->filter(fn ($r) => (int) $r->status === 1); - // $onExpired = $items->filter(fn ($r) => (int) $r->status === 3); - - // return [ - // "id_petugas" => $items->id_op_keluar, - // "petugas" => $items->petugas, - // "overNight" => (int) $items->overnight, - // "onExpired" => (int) $items->onexpired, - // ]; - // }) - // ->values(); - - Log::info("Rekap kendaraan selesai", [ - "total_kendaraan" => $tunai->count(), - ]); - - Log::info("== allReportData END ==", [ - "total_time_ms" => round((microtime(true) - $start) * 1000, 2), - ]); - - return [ - "date" => $startDate, - "tunai" => $tunai, - "nontunai" => $cashless, - ]; - } - // ========== End Laporan per petugas ===============/// - - // ==========###################### Begin Laporan per petugas #######################===============// - public function paymentType(Request $request) - { - Log::info("== Officer Report START =="); - Log::info($request->harian); - Log::info($request->tanggal); - $selectDate = $request->tanggal; - $start = microtime(true); - $harian = $request->harian ?? 1; - $dateNow = "2026-01-25"; - $startDate = $selectDate ? $selectDate : $dateNow; - $endDate = $selectDate ? $selectDate : $dateNow; - - Log::info("Tanggal laporan", [ - "start" => $startDate, - "end" => $endDate, - ]); - // ============= QUERY UTAMA (LEFT JOIN) ========================= - Log::info("Query transaksi dimulai"); - - if ($harian == 1) { - // $dateColumn = ["tp.waktu_keluar"]; - $dateColumn = ["tp.waktu_masuk", "tp.waktu_keluar"]; - } else { - // $dateColumn = ["tp.pklogin"]; - $dateColumn = ["tp.pmlogin", "tp.pklogin"]; - } - - $transactions = DB::table("transaksi_parkir as tp") - // ->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar") - // ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk") - // ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar") - ->where(function ($q) use ($dateColumn, $startDate, $endDate, $selectDate) { - $q->whereBetween($dateColumn[0], [$startDate . " 00:00:00", $endDate . " 23:59:59"])->orWhereBetween( - $dateColumn[0], - [$startDate . " 00:00:00", $endDate . " 23:59:59"], - ); - // $q->whereRaw('DATE(tp.waktu_masuk) = ?', [$selectDate]) - // ->orWhereRaw('DATE(tp.waktu_keluar) = ?', [$selectDate]); - }) - // ->whereRaw("DATE($dateColumn) = ?", [$dateNow]) - // ->whereBetween($dateColumn, [$startDate, $endDate]) - ->select( - "rep_bank as bankType", - // "p.nomer as id_petugas", - // "p.nama as petugas", - // "tp.id_kendaraan", - - // "pm.nama as kasir_masuk", - // "pk.nama as kasir_keluar", - "tp.id_op_masuk", // โœ… ADA - "tp.id_op_keluar", - - "tp.waktu_masuk", - "tp.waktu_keluar", - "tp.status", - "tp.bayar_masuk", - "tp.bayar_keluar", - "tp.status_transaksi", - "tp.cara_bayar", - ) - ->get(); - Log::info("Query transaksi selesai", [ - "rows" => $transactions->count(), - "query_time_ms" => round((microtime(true) - $start) * 1000, 2), - ]); - - Log::info("Rekap payment dimulai"); - - $tunai = $transactions - ->sortBy("bankType") - ->groupBy("bankType") - ->map(function ($items, $bankType) { - $keluar = $items->filter(fn($r) => (int) $r->status === 0); - $tunai = $keluar->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true)); - $keluarCasual = $tunai->filter( - fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null, - ); - $keluarMember = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3"); - $keluarLost = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32"); - $keluarManual = $tunai->filter(fn($r) => data_get($r, "id_op_masuk") === null); - $keluarMasalah = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "-1"); - - $totalCash = - $keluarCasual->sum("bayar_keluar") + - $keluarLost->sum("bayar_keluar") + - $keluarMember->sum("bayar_keluar") + - $keluarManual->sum("bayar_keluar"); - - Log::info("Transaksi tunai : "); - - return [ - "typePayment" => $bankType, - "petugas" => $items->first()->bankType, - "total" => $items->count(), - "tunaiOut" => $tunai->count(), - "masuk" => [ - "casual" => [ - "jumlah" => 0, - "income" => 0, - ], - ], - "keluar" => [ - "casual" => [ - "jumlah" => $keluarCasual->count(), - "income" => $keluarCasual->sum("bayar_keluar"), - ], - "lost" => [ - "jumlah" => $keluarLost->count(), - "income" => $keluarLost->sum("bayar_keluar"), - ], - "member" => [ - "jumlah" => $keluarMember->count(), - "income" => $keluarMember->sum("bayar_keluar"), - ], - "manual" => [ - "jumlah" => $keluarManual->count(), - "income" => $keluarManual->sum("bayar_keluar"), - ], - "masalah" => [ - "jumlah" => $keluarMasalah->count(), - "income" => $keluarMasalah->sum("bayar_keluar"), - ], - ], - "totalCash" => $totalCash, - ]; - }) - ->values(); - - $cashless = $transactions - ->sortBy("bankType") - ->groupBy("bankType") - ->map(function ($items, $bankType) { - $keluar = $items->filter(fn($r) => (int) $r->status === 0); - $nonTunai = $keluar->filter(fn($row) => (string) data_get($row, "cara_bayar") === "3"); - - $keluarCasual = $nonTunai->filter( - fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null, - ); - $keluarMember = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3"); - $keluarLost = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32"); - $keluarManual = $nonTunai->filter(fn($r) => data_get($r, "id_op_masuk") === null); - - $totalCashless = - $keluarCasual->sum("bayar_keluar") + - $keluarLost->sum("bayar_keluar") + - $keluarMember->sum("bayar_keluar") + - $keluarManual->sum("bayar_keluar"); - - // ====================== - // PEMBAYARAN (KELUAR) - // ====================== - - return [ - "typePayment" => $bankType, - "petugas" => $items->first()->bankType, - "total" => $items->count(), - "nonTunaiOut" => $nonTunai->count(), - "masuk" => [ - "casual" => [ - "jumlah" => 0, - "income" => 0, - ], - ], - "keluar" => [ - "casual" => [ - "jumlah" => $keluarCasual->count(), - "income" => $keluarCasual->sum("bayar_keluar"), - ], - "lost" => [ - "jumlah" => $keluarLost->count(), - "income" => $keluarLost->sum("bayar_keluar"), - ], - "member" => [ - "jumlah" => $keluarMember->count(), - "income" => $keluarMember->sum("bayar_keluar"), - ], - "manual" => [ - "jumlah" => $keluarManual->count(), - "income" => $keluarManual->sum("bayar_keluar"), - ], - ], - "totalCashless" => $totalCashless, - ]; - }) - ->values(); - - Log::info("Rekap kendaraan selesai", [ - "total_kendaraan" => $tunai->count(), - ]); - - Log::info("== allReportData END ==", [ - "total_time_ms" => round((microtime(true) - $start) * 1000, 2), - ]); - - return [ - "date" => $startDate, - "tunai" => $tunai, - "nontunai" => $cashless, - ]; - } - // ========== End Laporan payment Type ===============/// -} diff --git a/app/Http/Controllers/Parking/Setup/Gate/MasterConfigNetworkController.php b/app/Http/Controllers/Parking/Setup/Gate/MasterConfigNetworkController.php new file mode 100644 index 0000000..6a24553 --- /dev/null +++ b/app/Http/Controllers/Parking/Setup/Gate/MasterConfigNetworkController.php @@ -0,0 +1,185 @@ +get(); + + $dhcpStatus = $dhcp->status(); + + return view("setup.index", compact("reservations", "dhcpStatus")); + } + + public function status(NetworkService $dhcp) + { + return response()->json($dhcp->status()); + } + + public function store(Request $request) + { + $data = $request->validate([ + "hostname" => ["required", "string", "max:100", 'regex:/^[a-zA-Z0-9_-]+$/'], + + "mac_address" => ["required", "string", 'regex:/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/'], + + "ip_address" => ["required", "ip"], + + "enabled" => ["nullable", "boolean"], + ]); + + $data["mac_address"] = strtoupper($data["mac_address"]); + + $data["enabled"] = $request->boolean("enabled"); + + // Cek duplicate MAC + if (ConfigPosNetwork::where("mac_address", $data["mac_address"])->exists()) { + return response()->json( + [ + "success" => false, + "message" => "MAC address already registered.", + ], + 422, + ); + } + + // Cek duplicate IP + if (ConfigPosNetwork::where("ip_address", $data["ip_address"])->exists()) { + return response()->json( + [ + "success" => false, + "message" => "IP address already registered.", + ], + 422, + ); + } + + $reservation = ConfigPosNetwork::create($data); + + return response()->json( + [ + "success" => true, + "message" => "DHCP reservation created successfully.", + "data" => $reservation, + ], + 201, + ); + } + + public function update(Request $request, ConfigPosNetwork $reservation) + { + $data = $request->validate([ + "hostname" => ["required", "string", "max:100", 'regex:/^[a-zA-Z0-9_-]+$/'], + + "mac_address" => ["required", "string", 'regex:/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/'], + + "ip_address" => ["required", "ip"], + + "enabled" => ["nullable", "boolean"], + ]); + + $data["mac_address"] = strtoupper($data["mac_address"]); + + $data["enabled"] = $request->boolean("enabled"); + + // Cek duplicate MAC, + // kecuali reservation yang sedang diedit + if ( + ConfigPosNetwork::where("mac_address", $data["mac_address"])->where("id", "!=", $reservation->id)->exists() + ) { + return response()->json( + [ + "success" => false, + "message" => "MAC address already registered.", + ], + 422, + ); + } + + // Cek duplicate IP + if (ConfigPosNetwork::where("ip_address", $data["ip_address"])->where("id", "!=", $reservation->id)->exists()) { + return response()->json( + [ + "success" => false, + "message" => "IP address already registered.", + ], + 422, + ); + } + + $reservation->update($data); + + return response()->json([ + "success" => true, + "message" => "DHCP reservation updated successfully.", + "data" => $reservation->fresh(), + ]); + } + + public function destroy(ConfigPosNetwork $reservation) + { + $reservation->delete(); + + return response()->json([ + "success" => true, + "message" => "Reservation deleted successfully.", + ]); + } + + public function test(NetworkService $dhcp) + { + try { + $result = $dhcp->test(); + + return response()->json([ + "success" => true, + "message" => "DHCP configuration is valid.", + "data" => $result, + ]); + } catch (\Throwable $e) { + return response()->json( + [ + "success" => false, + "message" => $e->getMessage(), + ], + 422, + ); + } + } + + public function apply(NetworkService $dhcp) + { + try { + $dhcp->apply(); + + return response()->json([ + "success" => true, + "message" => "DHCP configuration applied successfully.", + ]); + } catch (\Throwable $e) { + return response()->json( + [ + "success" => false, + "message" => $e->getMessage(), + ], + 500, + ); + } + } + + public function logs(NetworkService $dhcp) + { + return response()->json([ + "success" => true, + "logs" => $dhcp->logs(30), + ]); + } +} diff --git a/app/Http/Controllers/Parking/Setup/Gate/MasterGateHardwareController.php b/app/Http/Controllers/Parking/Setup/Gate/MasterGateHardwareController.php index af77070..584f595 100644 --- a/app/Http/Controllers/Parking/Setup/Gate/MasterGateHardwareController.php +++ b/app/Http/Controllers/Parking/Setup/Gate/MasterGateHardwareController.php @@ -5,7 +5,6 @@ use App\Http\Controllers\Controller; use App\Models\ConfigHardware; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Log; class MasterGateHardwareController extends Controller { @@ -48,9 +47,11 @@ public function store(Request $request) */ public function show($id) { - Log::info($id); + // # ambil konfigurasi hardware berdasarkan id_pos (id pintu) $gateDetails = ConfigHardware::where('id_pos', $id)->first(); - return $gateDetails; + + // # kalau belum ada datanya, kembalikan objek kosong biar form tetap kebuka + return response()->json($gateDetails ?? (object) []); } /** @@ -73,8 +74,26 @@ public function edit($id) */ public function update(Request $request, $id) { - Log::info($id); - Log::info($request->all()); + // # hanya kolom yang terdaftar di fillable yang boleh disimpan + $allowed = (new ConfigHardware())->getFillable(); + $data = $request->input('data', []); + + $filtered = []; + foreach ((array) $data as $key => $value) { + if (in_array($key, $allowed, true)) { + // # ubah string kosong jadi null biar cocok dengan kolom integer + $filtered[$key] = $value === '' ? null : $value; + } + } + + // # simpan (buat baru kalau id_pos belum punya baris hardware) + $hardware = ConfigHardware::updateOrCreate(['id_pos' => $id], $filtered); + + return response()->json([ + 'success' => true, + 'message' => 'Konfigurasi hardware berhasil disimpan.', + 'data' => $hardware, + ]); } /** diff --git a/app/Http/Controllers/Parking/Setup/Member/MasterTarifStikerController.php b/app/Http/Controllers/Parking/Setup/Member/MasterTarifStikerController.php new file mode 100644 index 0000000..3cc12d6 --- /dev/null +++ b/app/Http/Controllers/Parking/Setup/Member/MasterTarifStikerController.php @@ -0,0 +1,206 @@ +get(["kode", "nama"]); + $jenisLanggananList = MasterJenisLangganan::orderBy("id")->get(["id", "nama"]); + + return view("Parking.setup.member.tarifstiker", compact("stasiunKerjaList", "jenisLanggananList")); + } + + /** + * Ambil seluruh data tarif stiker, sekalian join nama stasiun kerja + * biar gak perlu N+1 query pas render tabel. + */ + public function data(): JsonResponse + { + $data = MasterTarifStiker::with(["stasiunKerja:kode,nama"]) + ->orderByDesc("tarif_id") + ->get(); + + return response()->json([ + "success" => true, + "data" => $data, + ]); + } + + /** + * Ambil satu data berdasarkan tarif_id. + */ + public function show(string $tarif_id): JsonResponse + { + $item = MasterTarifStiker::with(["stasiunKerja:kode,nama"])->find($tarif_id); + + if (!$item) { + return response()->json( + [ + "success" => false, + "message" => "Data tarif stiker tidak ditemukan.", + ], + 404, + ); + } + + return response()->json([ + "success" => true, + "data" => $item, + ]); + } + + /** + * Simpan data tarif stiker baru. + * tarif_id tidak wajib diisi dari form โ€” kalau kosong, di-generate + * otomatis (max(tarif_id) + 1) karena tabel ini tidak punya + * auto-increment/serial asli di database. + */ + public function store(Request $request): JsonResponse + { + $validator = Validator::make($request->all(), [ + "tarif_id" => ["nullable", "integer", "unique:tarif_stiker,tarif_id"], + "id_mobil" => ["required", "string", "max:10"], + "jenis_langganan" => ["required", "string", "max:50"], + "tarif" => ["required", "numeric"], + "last_member" => ["nullable", "string", "max:5"], + "tgl_akhir" => ["nullable", "string", "max:2"], + "jenis" => ["nullable", "string", "max:30"], + "jenis_member" => ["nullable", "string", "max:50"], + "hitung_awal_bulan" => ["nullable", "integer"], + "available_online" => ["nullable", "boolean"], + "langganan" => ["nullable", "string", "max:32", "exists:stasiunkerja,kode"], + "kode_akses" => ["nullable", "string", "max:5"], + ]); + + if ($validator->fails()) { + return response()->json( + [ + "success" => false, + "message" => "Validasi gagal.", + "errors" => $validator->errors(), + ], + 422, + ); + } + + $payload = $validator->validated(); + $payload["available_online"] = $request->boolean("available_online"); + + // Transaksi + lock untuk menghindari race condition kalau + // ada 2 request bersamaan sama-sama generate tarif_id baru. + $item = DB::transaction(function () use ($payload) { + if (empty($payload["tarif_id"])) { + $maxId = MasterTarifStiker::lockForUpdate()->max("tarif_id"); + $payload["tarif_id"] = ($maxId ?? 0) + 1; + } + + return MasterTarifStiker::create($payload); + }); + + $item->load("stasiunKerja:kode,nama"); + + return response()->json( + [ + "success" => true, + "message" => "Data tarif stiker berhasil ditambahkan.", + "data" => $item, + ], + 201, + ); + } + + /** + * Perbarui data tarif stiker berdasarkan tarif_id. + */ + public function update(Request $request, string $tarif_id): JsonResponse + { + $item = MasterTarifStiker::find($tarif_id); + + if (!$item) { + return response()->json( + [ + "success" => false, + "message" => "Data tarif stiker tidak ditemukan.", + ], + 404, + ); + } + + $validator = Validator::make($request->all(), [ + "id_mobil" => ["required", "string", "max:10"], + "jenis_langganan" => ["required", "string", "max:50"], + "tarif" => ["required", "numeric"], + "last_member" => ["nullable", "string", "max:5"], + "tgl_akhir" => ["nullable", "string", "max:2"], + "jenis" => ["nullable", "string", "max:30"], + "jenis_member" => ["nullable", "string", "max:50"], + "hitung_awal_bulan" => ["nullable", "integer"], + "available_online" => ["nullable", "boolean"], + "langganan" => ["nullable", "string", "max:32", "exists:stasiunkerja,kode"], + "kode_akses" => ["nullable", "string", "max:5"], + ]); + + if ($validator->fails()) { + return response()->json( + [ + "success" => false, + "message" => "Validasi gagal.", + "errors" => $validator->errors(), + ], + 422, + ); + } + + $payload = $validator->validated(); + $payload["available_online"] = $request->boolean("available_online"); + + $item->update($payload); + $item->load("stasiunKerja:kode,nama"); + + return response()->json([ + "success" => true, + "message" => "Data tarif stiker berhasil diperbarui.", + "data" => $item, + ]); + } + + /** + * Hapus data tarif stiker berdasarkan tarif_id. + */ + public function destroy(string $tarif_id): JsonResponse + { + $item = MasterTarifStiker::find($tarif_id); + + if (!$item) { + return response()->json( + [ + "success" => false, + "message" => "Data tarif stiker tidak ditemukan.", + ], + 404, + ); + } + + $item->delete(); + + return response()->json([ + "success" => true, + "message" => "Data tarif stiker berhasil dihapus.", + ]); + } +} diff --git a/app/Http/Controllers/Parking/Setup/Member/StasiunKerjaController.php b/app/Http/Controllers/Parking/Setup/Member/StasiunKerjaController.php new file mode 100644 index 0000000..5af850b --- /dev/null +++ b/app/Http/Controllers/Parking/Setup/Member/StasiunKerjaController.php @@ -0,0 +1,177 @@ +get(); + + return response()->json([ + "success" => true, + "data" => $data, + ]); + } + + /** + * Ambil satu data berdasarkan kode (dipakai saat buka form edit). + */ + public function show(string $kode): JsonResponse + { + $item = MasterStasiunKerja::find($kode); + + if (!$item) { + return response()->json( + [ + "success" => false, + "message" => "Data stasiun kerja tidak ditemukan.", + ], + 404, + ); + } + + return response()->json([ + "success" => true, + "data" => $item, + ]); + } + + /** + * Simpan data stasiun kerja baru. + */ + public function store(Request $request): JsonResponse + { + $validator = Validator::make($request->all(), [ + "kode" => ["required", "string", "max:50", Rule::unique("stasiunkerja", "kode")], + "nama" => ["required", "string", "max:255"], + "margin" => ["nullable", "integer"], + "spv" => ["nullable", "string", "max:20"], + "target" => ["nullable", "integer"], + "id_lokasi" => ["nullable", "string", "max:10"], + "lantai" => ["nullable", "string", "max:4"], + "status" => ["nullable", "integer"], + "alamat" => ["nullable", "string", "max:255"], + "bank" => ["nullable", "string", "max:64"], + "telp" => ["nullable", "string", "max:32"], + "kode_akses" => ["nullable", "string", "max:5"], + ]); + + if ($validator->fails()) { + return response()->json( + [ + "success" => false, + "message" => "Validasi gagal.", + "errors" => $validator->errors(), + ], + 422, + ); + } + + $item = MasterStasiunKerja::create($validator->validated()); + + return response()->json( + [ + "success" => true, + "message" => "Data stasiun kerja berhasil ditambahkan.", + "data" => $item, + ], + 201, + ); + } + + /** + * Perbarui data stasiun kerja berdasarkan kode. + */ + public function update(Request $request, string $kode): JsonResponse + { + $item = MasterStasiunKerja::find($kode); + + if (!$item) { + return response()->json( + [ + "success" => false, + "message" => "Data stasiun kerja tidak ditemukan.", + ], + 404, + ); + } + + $validator = Validator::make($request->all(), [ + "nama" => ["required", "string", "max:255"], + "margin" => ["nullable", "integer"], + "spv" => ["nullable", "string", "max:20"], + "target" => ["nullable", "integer"], + "id_lokasi" => ["nullable", "string", "max:10"], + "lantai" => ["nullable", "string", "max:4"], + "status" => ["nullable", "integer"], + "alamat" => ["nullable", "string", "max:255"], + "bank" => ["nullable", "string", "max:64"], + "telp" => ["nullable", "string", "max:32"], + "kode_akses" => ["nullable", "string", "max:5"], + ]); + + if ($validator->fails()) { + return response()->json( + [ + "success" => false, + "message" => "Validasi gagal.", + "errors" => $validator->errors(), + ], + 422, + ); + } + + $item->update($validator->validated()); + + return response()->json([ + "success" => true, + "message" => "Data stasiun kerja berhasil diperbarui.", + "data" => $item, + ]); + } + + /** + * Hapus data stasiun kerja berdasarkan kode. + */ + public function destroy(string $kode): JsonResponse + { + $item = MasterStasiunKerja::find($kode); + + if (!$item) { + return response()->json( + [ + "success" => false, + "message" => "Data stasiun kerja tidak ditemukan.", + ], + 404, + ); + } + + $item->delete(); + + return response()->json([ + "success" => true, + "message" => "Data stasiun kerja berhasil dihapus.", + ]); + } +} diff --git a/app/Http/Controllers/Parking/Setup/Vehicle/MasterVehicleController.php b/app/Http/Controllers/Parking/Setup/Vehicle/MasterVehicleController.php index 18596a7..7563275 100644 --- a/app/Http/Controllers/Parking/Setup/Vehicle/MasterVehicleController.php +++ b/app/Http/Controllers/Parking/Setup/Vehicle/MasterVehicleController.php @@ -4,7 +4,9 @@ use App\Http\Controllers\Controller; use App\Models\VehicleItem; +use App\Models\MasterTarif; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class MasterVehicleController extends Controller { @@ -40,7 +42,38 @@ public function create() */ public function store(Request $request) { - // + $request->merge([ + 'id' => strtoupper((string) $request->input('id')), + 'short_cut' => strtoupper((string) $request->input('short_cut')), + ]); + + $data = $request->validate([ + 'id' => 'required|string|max:16|alpha_num|unique:jenis_mobil,id', + 'nama' => 'required|string|max:100', + 'short_cut' => 'required|string|max:16', + 'tarif' => 'required|numeric|min:0', + ]); + + $vehicle = DB::transaction(function () use ($data) { + $vehicle = VehicleItem::create([ + 'id' => strtoupper($data['id']), + 'nama' => $data['nama'], + 'short_cut' => strtoupper($data['short_cut']), + 'tanggal' => now(), + 'orang' => 0, + 'validasi' => 0, + ]); + + MasterTarif::create([ + 'id_mobil' => $vehicle->id, + 'tarif' => $data['tarif'], + 'tanggal' => now(), + ]); + + return $vehicle; + }); + + return response()->json(['success' => true, 'message' => 'Jenis kendaraan dan tarif awal berhasil ditambahkan.', 'data' => $vehicle], 201); } /** @@ -51,7 +84,10 @@ public function store(Request $request) */ public function show($id) { - // + $vehicle = VehicleItem::findOrFail($id); + $tarif = MasterTarif::find($id); + + return response()->json(['success' => true, 'data' => $vehicle, 'tarif' => $tarif]); } /** @@ -62,7 +98,7 @@ public function show($id) */ public function edit($id) { - // + return response()->json(['success' => true, 'data' => VehicleItem::findOrFail($id)]); } /** @@ -74,7 +110,18 @@ public function edit($id) */ public function update(Request $request, $id) { - // + $data = $request->validate([ + 'nama' => 'required|string|max:100', + 'short_cut' => 'required|string|max:16', + ]); + + $vehicle = VehicleItem::findOrFail($id); + $vehicle->update([ + 'nama' => $data['nama'], + 'short_cut' => strtoupper($data['short_cut']), + ]); + + return response()->json(['success' => true, 'message' => 'Jenis kendaraan berhasil diperbarui.', 'data' => $vehicle]); } /** @@ -85,6 +132,11 @@ public function update(Request $request, $id) */ public function destroy($id) { - // + DB::transaction(function () use ($id) { + MasterTarif::where('id_mobil', $id)->delete(); + VehicleItem::findOrFail($id)->delete(); + }); + + return response()->json(['success' => true, 'message' => 'Jenis kendaraan dan tarif terkait berhasil dihapus.']); } } diff --git a/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php b/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php index e14f005..f2ce4ef 100644 --- a/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php +++ b/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php @@ -49,12 +49,13 @@ public function store(Request $request) */ public function show($id) { - // Log::info($id); - $id = str($id); - $vehicleRates = DB::table("tarif")->where("id_mobil", $id)->first(); + $vehicleRates = DB::table("tarif")->where("id_mobil", (string) $id)->first(); - Log::info(json_encode($vehicleRates)); - return $vehicleRates; + if (!$vehicleRates) { + return response()->json(['status' => false, 'message' => 'Tarif kendaraan tidak ditemukan.'], 404); + } + + return response()->json($vehicleRates); } /** @@ -77,14 +78,43 @@ public function edit($id) */ public function update(Request $request, $id) { - Log::info($request->all()); - // $id = $request->id_mobil; - $dataQuery = MasterTarif::find($id); - try { - Log::info($request->all()); + if (!$dataQuery) { + return response()->json(['status' => false, 'message' => 'Tarif kendaraan tidak ditemukan.'], 404); + } - $dataQuery->fill($request->all()); + $validated = $request->validate([ + 'waktu_gratis' => 'nullable|integer|min:0', + 'tarif_period' => 'nullable|numeric|min:0', + 'frek_interval' => 'nullable|integer|min:1', + 'interval' => 'nullable|integer|min:1', + 'tarif_interval' => 'nullable|numeric|min:0', + 'bayar_dimuka' => 'nullable|boolean', + 'asuransi_incl' => 'nullable|numeric|min:0', + 'frek_waktu2' => 'nullable|integer|min:1', + 'waktu2' => 'nullable|integer|min:1', + 'tarif2' => 'nullable|numeric|min:0', + 'tarif' => 'required|numeric|min:0', + 'waktu_tarif' => 'nullable|integer|min:1', + 'maksimum' => 'nullable|numeric|min:0', + 'maksimum_mode' => 'required|in:hour,day', + 'maksimum_hour' => 'nullable|integer|min:1', + 'maksimum_day_minute' => 'nullable|integer|min:0', + 'denda' => 'nullable|numeric|min:0', + 'denda_member' => 'nullable|integer|min:0', + 'denda_only_casual' => 'nullable|boolean', + 'denda_only' => 'nullable|boolean', + ]); + + try { + $data = collect($validated)->except(['maksimum_mode', 'maksimum_hour', 'maksimum_day_minute'])->all(); + $data['time_base'] = $validated['maksimum_mode'] === 'hour' ? 1 : 0; + $data['time_base_maks'] = $validated['maksimum_mode'] === 'hour' ? $validated['maksimum_hour'] : null; + $data['min_per_hari'] = $validated['maksimum_mode'] === 'day' ? $validated['maksimum_day_minute'] : null; + $data['denda_only_casual'] = $request->boolean('denda_only_casual') ? 1 : 0; + $data['denda_only'] = $request->boolean('denda_only') ? 1 : 0; + + $dataQuery->fill($data); if (!$dataQuery->isDirty()) { return response()->json([ @@ -92,7 +122,6 @@ public function update(Request $request, $id) "message" => "Tidak ada perubahan data", ]); } - Log::info("Data yang di simpan" . json_encode($dataQuery->getDirty())); $dataQuery->save(); return response()->json([ @@ -100,10 +129,7 @@ public function update(Request $request, $id) "message" => "Data berhasil di ubah", ]); } catch (\Throwable $th) { - Log::info("Data yang di simpan" . json_encode($dataQuery->getDirty())); - - // Log::info($request->all()); - + Log::error('Gagal memperbarui tarif kendaraan', ['id_mobil' => $id, 'error' => $th->getMessage()]); return response()->json([ "status" => false, "message" => "Gagal menyimpan data " . $th->getMessage(), diff --git a/app/Http/Controllers/Parking/SetupController.php b/app/Http/Controllers/Parking/SetupController.php index 73032cf..1a1d271 100644 --- a/app/Http/Controllers/Parking/SetupController.php +++ b/app/Http/Controllers/Parking/SetupController.php @@ -11,18 +11,31 @@ class SetupController extends Controller { public function index() { - $status = [ - 'pgcrypto' => $this->checkPgcrypto(), - 'pegawai_columns' => $this->checkPegawaiColumns(), - 'users_view' => $this->checkUsersView(), - 'veri_time' => Schema::hasColumn('transaksi_parkir', 'veri_time'), - ]; + $status = $this->status(); - return view('welcome', compact('status')); + return view('welcome', ['isReady' => $this->isReady($status)]); + } + + public function setup() + { + $status = $this->status(); + + if ($this->isReady($status)) { + return redirect()->route('login'); + } + + return view('setup', compact('status')); } public function run() { + if ($this->isReady($this->status())) { + return response()->json([ + 'success' => false, + 'message' => 'Setup database sudah selesai dan tidak perlu dijalankan ulang.', + ], 403); + } + DB::beginTransaction(); try { @@ -66,6 +79,18 @@ public function run() }); } + // 5. Laravel database sessions for authenticated web requests. + if (!Schema::hasTable('sessions')) { + Schema::create('sessions', function ($table) { + $table->string('id')->primary(); + $table->unsignedBigInteger('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + DB::commit(); // return redirect()->back()->with('success', 'Setup berhasil dijalankan โœ…'); @@ -84,6 +109,26 @@ public function run() } } + private function status(): array + { + return [ + 'pgcrypto' => $this->checkPgcrypto(), + 'pegawai_columns' => $this->checkPegawaiColumns(), + 'users_view' => $this->checkUsersView(), + 'veri_time' => Schema::hasColumn('transaksi_parkir', 'veri_time'), + 'sessions' => Schema::hasTable('sessions'), + ]; + } + + private function isReady(array $status): bool + { + return $status['pgcrypto'] + && $status['users_view'] + && $status['veri_time'] + && $status['sessions'] + && !in_array(false, $status['pegawai_columns'], true); + } + /* ========================= Helper ========================= */ diff --git a/app/Http/Controllers/ParkingLot/ParkingLotComponent.php b/app/Http/Controllers/ParkingLot/ParkingLotComponent.php new file mode 100644 index 0000000..950b411 --- /dev/null +++ b/app/Http/Controllers/ParkingLot/ParkingLotComponent.php @@ -0,0 +1,165 @@ +space_parking; + + // Array untuk menyimpan data parking per lantai + $parkingData = []; + + for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) { + // Melakukan query pada LotParkingLog berdasarkan area dan lot_number + $log = LotParkingLog::where("area", $lantai->sitearea_id) + ->where("lot_number", $lotNumber) + ->select("lot_number", "status") + ->first(); + + // Jika data tidak ditemukan, buat object baru dengan lot_number dari pengulangan for + if (!$log) { + $log = (object) [ + "lot_number" => $lotNumber, + "status" => 0, + ]; + } + + // Menyimpan hasil query dalam array + $parkingData[] = $log; + } + + // Menyimpan data parking per lantai dalam array berdasarkan area + $parkingDataByArea[] = [ + "lantai" => $lantai->sitearea_id, + "data" => $parkingData, + ]; + } + + // Melakukan dump and die untuk melihat hasil query sementara + // dd(json_encode($parkingDataByArea)); + // dd($getArea->toJson()); + return view("livewire.space", [ + "areas" => $getArea, + "parkingDataByArea" => $parkingDataByArea, + ]); + + // return view("liveware.space"); + } + + public function getParkingData() + { + Log::info("getParkingData method called"); + + $getArea = SiteParking::get(); + + Log::info("Mendapatkan area: " . json_encode($getArea->toArray())); + + $parkingDataByArea = []; + + foreach ($getArea as $lantai) { + $areaName = $lantai->name; + $spaceParking = (int) $lantai->space_parking; + + Log::info("Mencari lot di lantai: {$areaName}, jumlah lot: {$spaceParking}"); + + $parkingData = []; + + for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) { + $log = LotParkingLog::where("area", $areaName) + ->where("lot_number", $lotNumber) + ->where("status", 1) + ->select("lot_number", "status") + ->orderByDesc("updated_at") + ->first(); + + Log::info("Lot {$areaName} #{$lotNumber}: " . json_encode($log)); + + if (!$log) { + $log = (object) [ + "lot_number" => $lotNumber, + "status" => 0, + ]; + } + + $parkingData[] = $log; + } + + $parkingDataByArea[] = [ + "lantai" => $areaName, + "data" => $parkingData, + ]; + } + + return response()->json($parkingDataByArea); + } + // public function index() + // { + // $parkingDataByArea = $this->getParkingData(); + + // return view("livewire.index", compact("parkingDataByArea")); + // } + + /** + * Endpoint AJAX untuk refresh data parking + */ + public function data() + { + $parkingDataByArea = $this->getParkingData(); + + return view("livewire.partial-lot", compact("parkingDataByArea")); + } + + /** + * Ambil data parking + */ + // private function getParkingData() + // { + // $getArea = SiteParking::all(); + + // $parkingDataByArea = []; + + // foreach ($getArea as $lantai) { + // $spaceParking = $lantai->space_parking; + // $parkingData = []; + + // for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) { + // $log = LotParkingLog::where("area", $lantai->sitearea_id) + // ->where("lot_number", $lotNumber) + // ->select("lot_number", "status") + // ->orderByDesc("created_at") + // ->first(); + + // if (!$log) { + // $log = (object) [ + // "lot_number" => $lotNumber, + // "status" => 0, + // ]; + // } + + // $parkingData[] = $log; + // } + + // $parkingDataByArea[] = [ + // "lantai" => $lantai->sitearea_id, + // "data" => $parkingData, + // ]; + // } + + // return $parkingDataByArea; + // } +} diff --git a/app/Http/Controllers/ParkingSetupUserLevelLevelMenuController.php b/app/Http/Controllers/ParkingSetupUserLevelLevelMenuController.php deleted file mode 100644 index f88daca..0000000 --- a/app/Http/Controllers/ParkingSetupUserLevelLevelMenuController.php +++ /dev/null @@ -1,84 +0,0 @@ - [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \App\Http\Middleware\UseBootstrapSessionStore::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, diff --git a/app/Http/Middleware/UseBootstrapSessionStore.php b/app/Http/Middleware/UseBootstrapSessionStore.php new file mode 100644 index 0000000..8e629a5 --- /dev/null +++ b/app/Http/Middleware/UseBootstrapSessionStore.php @@ -0,0 +1,20 @@ + 'file']); + } + + return $next($request); + } +} diff --git a/app/Models/ConfigHardware.php b/app/Models/ConfigHardware.php index ddf3de8..f289760 100644 --- a/app/Models/ConfigHardware.php +++ b/app/Models/ConfigHardware.php @@ -11,10 +11,18 @@ class ConfigHardware extends Model protected $table = 'config_pos_hardware'; + // # tabel ini tidak punya kolom id, kuncinya id_pos (satu baris per pintu) + protected $primaryKey = 'id_pos'; + public $incrementing = false; + protected $keyType = 'string'; + + // # tabel ini tidak punya kolom created_at / updated_at + public $timestamps = false; + protected $fillable = [ 'printer1_avail','printer1_conn','printer1_spd','printer1_stdfile','printer1_autocutter','printer1_bgconnect', 'printer1_dummy','video1_avail','video1_conn','video1_channel','video1_version','feedisplay_avail', - 'feedisplay_conn','feedisplay_paralel','feedisplay_version','bg_avail',' bg_conn ','bg_paralel','bg_value', + 'feedisplay_conn','feedisplay_paralel', 'feedisplay_version','bg_avail','bg_conn','bg_paralel','bg_value', 'sound_avail','sound_conn','sound_version','sound_partisi','id_pos','sizeable','pjg_lcd','video1_bright', 'video2_avail','video2_conn','video2_channel','video2_version','video2_bright','video2_sizeable','smartcard_avail', 'smartcard_speed','smartcard_port','printer2_avail','printer2_conn','printer2_spd','printer2_stdfile','printer2_autocutter', diff --git a/app/Models/ConfigPosNetwork.php b/app/Models/ConfigPosNetwork.php new file mode 100644 index 0000000..4020679 --- /dev/null +++ b/app/Models/ConfigPosNetwork.php @@ -0,0 +1,21 @@ + "boolean", + ]; +} diff --git a/app/Models/IntercomCall.php b/app/Models/IntercomCall.php new file mode 100644 index 0000000..132f62a --- /dev/null +++ b/app/Models/IntercomCall.php @@ -0,0 +1,24 @@ + 'datetime', + 'started_at' => 'datetime', + 'ended_at' => 'datetime', + ]; +} diff --git a/app/Models/IntercomDevice.php b/app/Models/IntercomDevice.php new file mode 100644 index 0000000..3fd8725 --- /dev/null +++ b/app/Models/IntercomDevice.php @@ -0,0 +1,21 @@ + 'datetime', + ]; +} diff --git a/app/Models/JenisMobil.php b/app/Models/JenisMobil.php index cb384c1..dc80651 100644 --- a/app/Models/JenisMobil.php +++ b/app/Models/JenisMobil.php @@ -9,14 +9,11 @@ class JenisMobil extends Model { use HasFactory; - protected $table = 'jenis_mobil'; + protected $table = "jenis_mobil"; protected $casts = [ - 'id' => 'string', - ]; - - protected $fillable = [ - 'nama','short_cut' + "id" => "string", ]; + protected $fillable = ["id", "nama", "short_cut", "validasi"]; } diff --git a/app/Models/LotParking/LotParkingLog.php b/app/Models/LotParking/LotParkingLog.php new file mode 100644 index 0000000..c6503c3 --- /dev/null +++ b/app/Models/LotParking/LotParkingLog.php @@ -0,0 +1,25 @@ +belongsTo(User::class,'id','id'); + // } + + // public function siteGroups() + // { + // return $this->belongsTo(User::class, 'site_group', 'company'); + // } +} diff --git a/app/Models/LotParking/SiteParking.php b/app/Models/LotParking/SiteParking.php new file mode 100644 index 0000000..0ee2e89 --- /dev/null +++ b/app/Models/LotParking/SiteParking.php @@ -0,0 +1,25 @@ +belongsTo(User::class,'id','id'); + // } + + // public function siteGroups() + // { + // return $this->belongsTo(User::class, 'site_group', 'company'); + // } +} diff --git a/app/Models/MasterTarifStiker.php b/app/Models/MasterTarifStiker.php new file mode 100644 index 0000000..24dee9c --- /dev/null +++ b/app/Models/MasterTarifStiker.php @@ -0,0 +1,25 @@ +hasOne(StikerTransDetail::class, "jenis_member", "jenis_langganan"); + } +} diff --git a/app/Models/Origin/MasterJenisLangganan.php b/app/Models/Origin/MasterJenisLangganan.php new file mode 100644 index 0000000..3abaee8 --- /dev/null +++ b/app/Models/Origin/MasterJenisLangganan.php @@ -0,0 +1,65 @@ + + */ + protected $fillable = ["id", "nama", "bayar", "tarif", "periode_awal", "masa_berlaku", "maks", "jenis_mobil"]; + + /** + * Casting tipe data kolom. + * + * @var array + */ + protected $casts = [ + "bayar" => "integer", + "tarif" => "integer", + "periode_awal" => "datetime", + "masa_berlaku" => "integer", + "maks" => "integer", + ]; +} diff --git a/app/Models/Origin/MasterStasiunKerja.php b/app/Models/Origin/MasterStasiunKerja.php new file mode 100644 index 0000000..8f1b95a --- /dev/null +++ b/app/Models/Origin/MasterStasiunKerja.php @@ -0,0 +1,88 @@ + + */ + protected $fillable = [ + "kode", + "nama", + "margin", + "spv", + "target", + "id_lokasi", + "lantai", + "status", + "alamat", + "bank", + "telp", + "kode_akses", + ]; + + /** + * Casting tipe data kolom. + * + * @var array + */ + protected $casts = [ + "margin" => "integer", + "target" => "integer", + "status" => "integer", + ]; + + /** + * Relasi ke tarif_stiker. + * stasiunkerja.kode = tarif_stiker.langganan + * + * @return HasMany + */ + public function tarifStiker(): HasMany + { + return $this->hasMany(MasterTarifStiker::class, "langganan", "kode"); + } +} diff --git a/app/Models/Origin/MasterTarifStiker.php b/app/Models/Origin/MasterTarifStiker.php new file mode 100644 index 0000000..6ff79be --- /dev/null +++ b/app/Models/Origin/MasterTarifStiker.php @@ -0,0 +1,90 @@ + + */ + protected $fillable = [ + "id_mobil", + "jenis_langganan", + "tarif", + "last_member", + "tgl_akhir", + "jenis", + "jenis_member", + "hitung_awal_bulan", + "tarif_id", + "available_online", + "langganan", + "kode_akses", + ]; + + /** + * Casting tipe data kolom. + * + * @var array + */ + protected $casts = [ + "tarif" => "float", + "hitung_awal_bulan" => "integer", + "tarif_id" => "integer", + "available_online" => "boolean", + ]; + + /** + * Relasi ke stasiunkerja. + * tarif_stiker.langganan = stasiunkerja.kode + * + * @return BelongsTo + */ + public function stasiunKerja(): BelongsTo + { + return $this->belongsTo(MasterStasiunKerja::class, "langganan", "kode"); + } +} diff --git a/app/Models/StikerTrans.php b/app/Models/StikerTrans.php index 9abd587..6426f31 100644 --- a/app/Models/StikerTrans.php +++ b/app/Models/StikerTrans.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Models\StikerTransDetail; +use App\Models\MemberCard; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -9,24 +11,50 @@ class StikerTrans extends Model { use HasFactory; - protected $table = 'transaksi_stiker'; + protected $table = "transaksi_stiker"; protected $fillable = [ - 'notrans','nama','alamat', 'telepon', 'jenis_transaksi', 'awal', 'harga', 'keterangan', 'tanggal' , 'operator' , - 'akhir' , 'maks' , 'no_id' , 'unit_kerja', 'no_induk' , 'jenis_stiker' , 'hari_ke' ,'jenis_langganan' , - 'exit_pass' , 'no_kuitansi', 'tgl_edited', 'tipe_exit_pass', 'seq_code' , 'unitno' , 'area' , 'reserved', - 'batasan_jam', 'batasan_awal', 'batasan_akhir', 'asuransi' + "notrans", + "nama", + "alamat", + "telepon", + "jenis_transaksi", + "awal", + "harga", + "keterangan", + "tanggal", + "operator", + "akhir", + "maks", + "no_id", + "unit_kerja", + "no_induk", + "jenis_stiker", + "hari_ke", + "jenis_langganan", + "exit_pass", + "no_kuitansi", + "tgl_edited", + "tipe_exit_pass", + "seq_code", + "unitno", + "area", + "reserved", + "batasan_jam", + "batasan_awal", + "batasan_akhir", + "asuransi", ]; - public $with = ['stiker_details', 'memberCard']; + // public $with = ["stiker_details", "memberCard"]; - public function stiker_details () + public function stiker_details() { - return $this->hasMany(StikerTransDetail::class, 'notrans', 'notrans'); + return $this->hasMany(StikerTransDetail::class, "notrans", "notrans"); } - public function memberCard () + public function memberCard() { - return $this->hasMany(MemberCard::class, 'notrans', 'no_id'); + return $this->hasMany(MemberCard::class, "notrans", "no_id"); } } diff --git a/app/Models/StikerTransDetail.php b/app/Models/StikerTransDetail.php index 1af36de..d649800 100644 --- a/app/Models/StikerTransDetail.php +++ b/app/Models/StikerTransDetail.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Models\StikerTrans; +use App\Models\VehicleItem; +use App\Models\MasterTarifStiker; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; @@ -10,22 +13,43 @@ class StikerTransDetail extends Model { use HasFactory; - protected $table = 'detail_transaksi_stiker'; + protected $table = "detail_transaksi_stiker"; protected $fillable = [ - 'notrans', 'nopol', 'jenis_mobil', 'adm', 'kategori', - 'jenis_member', 'akses', 'akses_out', 'status', 'merk', - 'tipe', 'tahun', 'warna', 'keterangan', + "notrans", + "nopol", + "jenis_mobil", + "adm", + "kategori", + "jenis_member", + "akses", + "akses_out", + "status", + "merk", + "tipe", + "tahun", + "warna", + "keterangan", ]; - public function stiker_details () + public function stiker_details() { - return $this->belongsTo(StikerTrans::class, 'notrans', 'notrans'); + return $this->belongsTo(StikerTrans::class, "notrans", "notrans"); + } + + public function tarifStiker() + { + return $this->hasOne(MasterTarifStiker::class, "jenis_member", "jenis_langganan"); } // Relasi ke tabel jenis_mobil public function vehicle() { - return $this->belongsTo(VehicleItem::class, 'id', 'jenis_mobil'); + return $this->belongsTo(VehicleItem::class, "jenis_mobil", "id"); + } + + public function details() + { + return $this->with(["vehicle:id, nama", "tarifStiker:jenis_langganan,tarif"]); } } diff --git a/app/Services/MakeStikerServices.php b/app/Services/MakeStikerServices.php index 1bfc3bb..36f7d05 100755 --- a/app/Services/MakeStikerServices.php +++ b/app/Services/MakeStikerServices.php @@ -7,116 +7,104 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Auth; class MakeStikerServices { public function stikerCreate($request, Carbon $awal, Carbon $akhir) { - Log::info(json_encode($sticker)); - return DB::transaction(function () use ($sticker) { + Log::info("Data yang akan di proses : " . json_encode($request)); + return DB::transaction(function () use ($request) { // LOCK index agar aman - $index = DB::table("indexnum")->where("nama", "sticker")->lockForUpdate()->first(); - $mountYearNow = Carbon::now()->format("m/Y"); + // $index = DB::table("indexnum")->where("nama", "sticker")->lockForUpdate()->first(); + // $mountYearNow = Carbon::now()->format("m-Y"); + // $nextIndex = str_pad($index->value + 1, 4, "0", STR_PAD_LEFT); - $nextIndex = str_pad($index->value + 1, 4, "0", STR_PAD_LEFT); + // Ambil sequence Stiker dan Identitas + // $seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first(); + // $seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first(); - $seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first(); - $seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first(); - $notrans = $seqTrans->value . "/STK/" . $mountYearNow; - $noid = $seqId->value . "/IDT/" . $mountYearNow; + // Parsing format stiker dan identitas + // $notrans = $seqTrans->value . "-STK-" . $mountYearNow; + // $noid = $seqId->value . "-IDT-" . $mountYearNow; - $transStiker = TransaksiStiker::where("notrans", $sticker->notrans)->first(); - $detailTransStiker = DetailTransaksiStiker::where("notrans", $sticker->notrans)->first(); - Log::info(json_encode($transStiker)); - Log::info(json_encode($detailTransStiker)); - // HEADER + // $periode = explode(" - ", $dataStiker["periode"]); + // $awal = Carbon::createFromFormat("d-m-Y H:i", $periode[0]); + // $akhir = Carbon::createFromFormat("d-m-Y H:i", $periode[1]); + $user = Auth::user(); + $dateNow = Carbon::now()->format("Y-m-d H:i:s"); + + // Parcing data yang diterima + $dataStiker = $request["dataSticker"]; + $dataVehicle = $request["dataVehicle"]; + $dataCard = $request["dataCard"]; try { $dataStiker = [ "jenis_transaksi" => $dataStiker["jenis_transaksi"], // 0 = transaksi baru , 1 = perpanjangan - "notrans" => $notrans, - "no_id" => $noid, + "notrans" => $dataStiker["notrans"], + "no_id" => $dataStiker["no_id"], "nama" => $dataStiker["nama"], "alamat" => $dataStiker["address"] ?? "", "telepon" => $dataStiker["phone"] ?? "", "unit_kerja" => $dataStiker["depart"], "jenis_stiker" => $dataStiker["member"], - "awal" => $awal, - "akhir" => $akhir, + "awal" => $dataStiker["awal"], + "akhir" => $dataStiker["akhir"], "harga" => $dataStiker["harga"] ?? 0, "keterangan" => $dataStiker["note"] ?? "", "tanggal" => $dateNow, - "operator" => $user->nomer, + "operator" => $user?->nomer ?? "", ]; DB::table("transaksi_stiker")->insert($dataStiker); - // DETAIL - DB::table("detail_transaksi_stiker")->insert([ - "notrans" => $notrans, - "nopol" => $detailTransStiker->nopol, - "jenis_mobil" => $detailTransStiker->jenis_mobil, - "adm" => $detailTransStiker->adm, - "kategori" => $detailTransStiker->kategori, - "jenis_member" => $detailTransStiker->jenis_member, - "akses" => $detailTransStiker->akses, - "akses_out" => $detailTransStiker->akses_out, - "status" => $detailTransStiker->status, - "merk" => $detailTransStiker->merk, - "tipe" => $detailTransStiker->tipe, - "tahun" => $detailTransStiker->tahun, - "warna" => $detailTransStiker->warna, - "keterangan" => $detailTransStiker->keterangan, - ]); - DB::table("transaksi_stiker")->insert($dataStiker); - - if ($dataVehicle) { + if (!empty($dataVehicle)) { foreach ($dataVehicle as $nopol) { - $dataVehicle = [ - "notrans" => $notrans, + Log::info(json_encode($nopol)); + $produkMember = explode(" - ", $nopol["member"]); + $insertVehicle = [ + "notrans" => $nopol["notrans"], "nopol" => $nopol["nopol"], "jenis_mobil" => $nopol["vehicle"], - "jenis_member" => $nopol["member"], + "jenis_member" => $produkMember[0], "merk" => $nopol["merk"], ]; - DB::table("detail_transaksi_stiker")->insert($dataVehicle); + DB::table("detail_transaksi_stiker")->insert($insertVehicle); } - } else { - return 0; } - if ($dataCard) { + if (!empty($dataCard)) { foreach ($dataCard as $cardNumber) { - $dataCard = [ - "notrans" => $noid, + $insertCard = [ + "notrans" => $cardNumber["no_id"], "no_card" => $cardNumber["nocard"], "no_urut" => $cardNumber["nourut"], ]; - DB::table("card_member")->insert($dataCard); + DB::table("card_member")->insert($insertCard); } - } else { - return 0; } - DB::table("indexnum") - ->where("nama", "sticker") - ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); - DB::table("indexnum") - ->where("nama", "identitas") - ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); - $response = true; - $msg = "Data berhasil di simpan"; + // DB::table("indexnum") + // ->where("nama", "sticker") + // // ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); + // ->update(["value" => $newNoTrans]); + // DB::table("indexnum") + // ->where("nama", "identitas") + // // ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]); + // ->update(["value" => $newNoID]); + + return response()->json([ + "status" => true, + "message" => "Data berhasil di simpan", + ]); } catch (Exception $e) { - $response = false; - $msg = "Gagal menyimpan data " . $e->getMessage() . $e->getLine(); + return response()->json([ + "status" => false, + "message" => "Gagal menyimpan data " . $e->getMessage() . $e->getLine(), + ]); } - - DB::table("indexnum") - ->where("nama", "sticker") - ->update(["value" => $nextIndex]); - - return $notrans; }); } diff --git a/app/Services/Parking/NetworkService.php b/app/Services/Parking/NetworkService.php new file mode 100644 index 0000000..23e30f1 --- /dev/null +++ b/app/Services/Parking/NetworkService.php @@ -0,0 +1,195 @@ + function_exists("posix_geteuid") ? posix_geteuid() : null, + + "user" => get_current_user(), + + "temp_file" => $this->tempFile, + + "file_exists" => file_exists($this->tempFile), + + "dir_writable" => is_writable(dirname($this->tempFile)), + + "file_writable" => file_exists($this->tempFile) ? is_writable($this->tempFile) : null, + ]); + $config = $this->generate(); + + if (file_put_contents($this->tempFile, $config) === false) { + throw new RuntimeException("Failed to write temporary DHCP configuration."); + } + + $process = new Process(["sudo", "/usr/local/bin/dhcp-reservation", "apply"]); + + $process->run(); + + if (!$process->isSuccessful()) { + throw new RuntimeException(trim($process->getErrorOutput() ?: $process->getOutput())); + } + // if (!$process->isSuccessful()) { + // throw new RuntimeException(...); + // } + + // HANYA disimpan kalau apply benar-benar berhasil + $this->saveLastApplied(); + } + + public function status(): array + { + $service = "isc-dhcp-server"; + + $running = false; + + $process = new \Symfony\Component\Process\Process(["systemctl", "is-active", $service]); + + $process->run(); + + if ($process->isSuccessful()) { + $running = trim($process->getOutput()) === "active"; + } + + return [ + "running" => $running, + "hostname" => $this->hostname(), + "ip" => $this->serverIp(), + "interface" => config("network.dhcp_interface", "eth0"), + "status" => $running ? "running" : "stopped", + "active_clients" => $this->activeClients(), + "last_applied" => $this->lastApplied(), + ]; + } + + public function lastApplied(): ?string + { + if (!file_exists($this->lastAppliedFile)) { + return null; + } + + return trim(file_get_contents($this->lastAppliedFile)); + } + + public function logs(int $limit = 30): array + { + $process = new Process([ + "journalctl", + "-u", + $this->dhcpServiceName(), + "-n", + (string) $limit, + "--no-pager", + "-o", + "short", + ]); + + $process->setTimeout(5); + $process->run(); + + if (!$process->isSuccessful()) { + return []; + } + + return collect(preg_split('/\r\n|\r|\n/', trim($process->getOutput())))->filter()->values()->all(); + } + + private function dhcpServiceName(): string + { + return config("network.dhcp_service", "isc-dhcp-server"); + } + + private function hostname(): string + { + return trim(gethostname() ?: php_uname("n")); + } + + private function serverIp(): ?string + { + $interface = config("network.dhcp_interface", "enx00e04c6802a9"); + + $process = new Process(["ip", "-4", "-o", "addr", "show", "dev", $interface]); + + $process->run(); + + if (!$process->isSuccessful()) { + return null; + } + + if (preg_match("/inet\s+([0-9.]+)\//", $process->getOutput(), $matches)) { + return $matches[1]; + } + + return null; + } + + private function generate(): string + { + $reservations = ConfigPosNetwork::query()->where("enabled", true)->orderBy("hostname")->get(); + + $config = ""; + + foreach ($reservations as $reservation) { + $config .= <<hostname} { + hardware ethernet {$reservation->mac_address}; + fixed-address {$reservation->ip_address}; + } + + CONF; + } + + return ltrim($config); + } + + private function activeClients(): int + { + $leaseFile = "/var/lib/dhcp/dhcpd.leases"; + + if (!file_exists($leaseFile)) { + return 0; + } + + $content = file_get_contents($leaseFile); + + preg_match_all("/lease\s+([0-9.]+)\s*\{(.*?)\}/s", $content, $matches, PREG_SET_ORDER); + + $leases = []; + + foreach ($matches as $match) { + $ip = $match[1]; + $block = $match[2]; + + preg_match("/binding state\s+(\w+);/", $block, $state); + + $leases[$ip] = [ + "state" => $state[1] ?? null, + ]; + } + + return collect($leases)->where("state", "active")->count(); + } + + private function saveLastApplied(): void + { + $directory = dirname($this->lastAppliedFile); + + if (!is_dir($directory)) { + mkdir($directory, 0755, true); + } + + file_put_contents($this->lastAppliedFile, now()->toDateTimeString()); + } +} diff --git a/app/Services/Parking/ParkingUtilsServices.php b/app/Services/Parking/ParkingUtilsServices.php new file mode 100644 index 0000000..262e5d7 --- /dev/null +++ b/app/Services/Parking/ParkingUtilsServices.php @@ -0,0 +1,43 @@ +statusabsen == 1; + } + + private function pgConnectSecond($site_config = null) + { + try { + Config::set("database.connections.pgsql_second", [ + "driver" => "pgsql", + "host" => $site_config->site_host, + "user" => $site_config->site_user, + "password" => $site_config->site_password, + "database" => $site_config->site_database, + "port" => $site_config->site_port, + "charset" => "utf8", + "prefix" => "", + "schema" => "public", + ]); + return DB::table("pgsql_second"); + } catch (\Exception $e) { + Log::error("Failed connect to database " . $e->getMessage()); + return null; + } + } + + public function useTableParkir($user) + { + $table = $this->isMirror($user) ? "parkir_transaksi" : "transaksi_parkir"; + + return DB::table("$table as tp"); + } +} diff --git a/app/Services/Parking/RateParkingService.php b/app/Services/Parking/RateParkingService.php index f954e04..5b594ee 100644 --- a/app/Services/Parking/RateParkingService.php +++ b/app/Services/Parking/RateParkingService.php @@ -19,16 +19,20 @@ public function parkingTimeCalc($vehicle, $times) Log::info("Mulai perhitungan tarif dengan durasi : " . $duration . " (Rp. " . $parkingPrice . ")"); // Grace Periode - // if ($duration < $tarifVehicle->waktu_gratis) { + // waktu_gratis kesimpen dalam MENIT (sama kayak interval/waktu2/waktu_tarif + // di bawah), tapi $duration dalam DETIK -- makanya WAJIB dikali 60 dulu biar + // satuannya sama, sama kayak pola interval*60/waktu2*60/waktu_tarif*60 yang + // dipake di bagian lain fungsi ini. + // if ($duration < $tarifVehicle->waktu_gratis * 60) { // Log::info("GP Rules : "); // return $parkingPrice; // } elseif ( // $tarifVehicle->waktu_gratis !== 0 && - // $tarifVehicle->tarif_periode !== 0 + // $tarifVehicle->tarif_period !== 0 // ) { // Log::info("GP Rules : "); // $parkingPrice = $parkingPrice + $tarifVehicle->tarif_period; - // $duration = $duration - $tarifVehicle->waktu_gratis; + // $duration = $duration - ($tarifVehicle->waktu_gratis * 60); // Log::info( // "Setelah GP : " . // $parkingPrice . diff --git a/composer.json b/composer.json index dafa936..7a37aef 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "laravel/framework": "^9.0", "laravel/sanctum": "^2.14", "laravel/tinker": "^2.7", - "laravel/ui": "^4.6" + "laravel/ui": "^4.6", + "yajra/laravel-datatables-oracle": "^10.11" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index ca6b707..0611ab0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "006c4ddfd87627edd18542c501fd181e", + "content-hash": "073567f86abe3c9b30d540058b70a726", "packages": [ { "name": "asm89/stack-cors", @@ -5793,6 +5793,93 @@ "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "yajra/laravel-datatables-oracle", + "version": "v10.11.4", + "source": { + "type": "git", + "url": "https://github.com/yajra/laravel-datatables.git", + "reference": "a11dc9cf5ec7f7dc68ad0d5959c5e7aec0af29a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/a11dc9cf5ec7f7dc68ad0d5959c5e7aec0af29a5", + "reference": "a11dc9cf5ec7f7dc68ad0d5959c5e7aec0af29a5", + "shasum": "" + }, + "require": { + "illuminate/database": "^9|^10", + "illuminate/filesystem": "^9|^10", + "illuminate/http": "^9|^10", + "illuminate/support": "^9|^10", + "illuminate/view": "^9|^10", + "php": "^8.0.2" + }, + "require-dev": { + "algolia/algoliasearch-client-php": "^3.4", + "larastan/larastan": "^2.4", + "laravel/scout": "^10.5", + "meilisearch/meilisearch-php": "^1.4", + "orchestra/testbench": "^8", + "yajra/laravel-datatables-html": "^9.3.4|^10" + }, + "suggest": { + "yajra/laravel-datatables-buttons": "Plugin for server-side exporting of dataTables.", + "yajra/laravel-datatables-editor": "Plugin to use DataTables Editor (requires a license).", + "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.", + "yajra/laravel-datatables-fractal": "Plugin for server-side response using Fractal.", + "yajra/laravel-datatables-html": "Plugin for server-side HTML builder of dataTables." + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "DataTables": "Yajra\\DataTables\\Facades\\DataTables" + }, + "providers": [ + "Yajra\\DataTables\\DataTablesServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "Yajra\\DataTables\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arjay Angeles", + "email": "aqangeles@gmail.com" + } + ], + "description": "jQuery DataTables API for Laravel 4|5|6|7|8|9|10", + "keywords": [ + "datatables", + "jquery", + "laravel" + ], + "support": { + "issues": "https://github.com/yajra/laravel-datatables/issues", + "source": "https://github.com/yajra/laravel-datatables/tree/v10.11.4" + }, + "funding": [ + { + "url": "https://github.com/sponsors/yajra", + "type": "github" + } + ], + "time": "2024-02-28T05:00:23+00:00" } ], "packages-dev": [ @@ -8326,12 +8413,12 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.0" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/config/network.php b/config/network.php new file mode 100644 index 0000000..3baa27c --- /dev/null +++ b/config/network.php @@ -0,0 +1,7 @@ + env("DHCP_INTERFACE", "wlp1s0"), + + "dhcp_service" => env("DHCP_SERVICE", "isc-dhcp-server"), +]; diff --git a/database/migrations/2026_08_18_103742_create_dhcp_reservations_table.php b/database/migrations/2026_08_18_103742_create_dhcp_reservations_table.php new file mode 100644 index 0000000..151fa71 --- /dev/null +++ b/database/migrations/2026_08_18_103742_create_dhcp_reservations_table.php @@ -0,0 +1,35 @@ +string("id_pos"); + $table->string("hostname", 100); + $table->string("mac_address", 17)->unique(); + $table->string("ip_address")->unique(); + $table->boolean("enabled")->default(true); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists("dhcp_reservations"); + } +}; diff --git a/database/migrations/2026_08_20_064557_create_lot_parking_log.php b/database/migrations/2026_08_20_064557_create_lot_parking_log.php new file mode 100644 index 0000000..7911ba0 --- /dev/null +++ b/database/migrations/2026_08_20_064557_create_lot_parking_log.php @@ -0,0 +1,33 @@ +uuid(); + $table->string("area", 8); + $table->string("lot_number", 8); + $table->smallInteger("status"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists("lot_parking_log"); + } +}; diff --git a/database/migrations/2026_08_30_092544_create_sessions_table.php b/database/migrations/2026_08_30_092544_create_sessions_table.php new file mode 100644 index 0000000..06c3e45 --- /dev/null +++ b/database/migrations/2026_08_30_092544_create_sessions_table.php @@ -0,0 +1,35 @@ +string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('sessions'); + } +}; diff --git a/database/migrations/2026_09_18_000001_create_intercom_tables.php b/database/migrations/2026_09_18_000001_create_intercom_tables.php new file mode 100644 index 0000000..5290ea4 --- /dev/null +++ b/database/migrations/2026_09_18_000001_create_intercom_tables.php @@ -0,0 +1,46 @@ +id(); + $table->string('device_id', 20)->unique(); // "C1".."C5", "ADMIN" + $table->string('nama', 100)->nullable(); // "Pos Pintu 1", "Pusat" + $table->string('mac_address', 17)->nullable()->unique(); + $table->string('ip_address', 45)->nullable(); + $table->integer('udp_port')->nullable(); // 5001..5005, ADMIN 5000 + $table->string('status', 20)->default('offline'); // offline|idle|calling|busy|active + $table->timestamp('last_seen_at')->nullable(); + $table->timestamps(); + }); + + // Log tiap sesi private Pusat<->Client. Mirip transaksi_parkir tapi versi suara. + Schema::create('intercom_calls', function (Blueprint $table) { + $table->id(); + $table->string('device_id', 20)->index(); + $table->string('arah', 20)->default('in'); // in: client->pusat, out: pusat->client + $table->string('status', 20)->default('waiting'); // waiting|active|ended|missed|cancelled + $table->timestamp('requested_at')->nullable(); + $table->timestamp('started_at')->nullable(); + $table->timestamp('ended_at')->nullable(); + $table->integer('durasi_detik')->default(0); + $table->string('recording_path', 255)->nullable(); // storage/app/intercom/xxx.wav + $table->string('ended_by', 20)->nullable(); // client|admin|timeout + $table->timestamps(); + + $table->index(['device_id', 'status']); + }); + } + + public function down() + { + Schema::dropIfExists('intercom_calls'); + Schema::dropIfExists('intercom_devices'); + } +}; diff --git a/public/images/lot/car_red copy@.png b/public/images/lot/car_red copy@.png new file mode 100644 index 0000000..e9eaa27 Binary files /dev/null and b/public/images/lot/car_red copy@.png differ diff --git a/public/images/lot/green.png b/public/images/lot/green.png new file mode 100644 index 0000000..6ee4e56 Binary files /dev/null and b/public/images/lot/green.png differ diff --git a/public/images/lot/green_car.png b/public/images/lot/green_car.png new file mode 100644 index 0000000..3cdcdfc Binary files /dev/null and b/public/images/lot/green_car.png differ diff --git a/public/images/lot/red.png b/public/images/lot/red.png new file mode 100644 index 0000000..44f4930 Binary files /dev/null and b/public/images/lot/red.png differ diff --git a/public/images/lot/red_car.png b/public/images/lot/red_car.png new file mode 100644 index 0000000..e457b8f Binary files /dev/null and b/public/images/lot/red_car.png differ diff --git a/public/js/member/index.js b/public/js/member/index.js index eb80901..02a8540 100644 --- a/public/js/member/index.js +++ b/public/js/member/index.js @@ -1,74 +1,101 @@ $(document).ready(function () { - - $(document).on('change', '.form-select', function () { - if ($('option:selected', this).val() == '') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'none'); + $(document).on("change", ".form-select", function () { + if ($("option:selected", this).val() == "") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "none"); // $(".site").val(''); - } else if ($('option:selected', this).val() == 'nama') { - $(".nama").css('display', 'block'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'none'); - $(".input-default").val(''); - } else if ($('option:selected', this).val() == 'nopol') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'block'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'none'); - $(".input-default").val(''); - } else if ($('option:selected', this).val() == 'nocard') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'block'); - $(".notrans").css('display', 'none'); - $(".input-default").val(''); - } else if ($('option:selected', this).val() == 'notrans') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'block'); - $(".input-default").val(''); + } else if ($("option:selected", this).val() == "nama") { + $(".nama").css("display", "block"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "none"); + $(".input-default").val(""); + } else if ($("option:selected", this).val() == "nopol") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "block"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "none"); + $(".input-default").val(""); + } else if ($("option:selected", this).val() == "nocard") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "block"); + $(".notrans").css("display", "none"); + $(".input-default").val(""); + } else if ($("option:selected", this).val() == "notrans") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "block"); + $(".input-default").val(""); } else { - $(".position").css('display', 'none'); - $(".level-area").css('display', 'none'); - $(".input-default").val(''); + $(".position").css("display", "none"); + $(".level-area").css("display", "none"); + $(".input-default").val(""); } }); - - var url = $('#example').data('url'); - $('#search-button').on('click', function () { - // Ambil nilai dari selected option - var option = $('.select-option').val(); + var url = $("#example").data("url"); - // Cek jika option yang dipilih bukan 'Pencarian Data...' yang berarti pilihan valid - if (option && option !== '') { - var inputField = $('.input-default:visible'); + $("#search-button").on("click", function () { + let searchBy = $("#search-by").val(); + let searchValue = $("#search-value").val().trim(); - // Periksa apakah input tersebut kosong - if (inputField.length === 0 || inputField.val().trim() === '') { - // Jika kosong, tampilkan alert - alert('Harap masukkan ' + (option === 'nopol' ? 'No. Polisi' : option === 'nocard' ? 'No. Card' : option === 'nama' ? 'Nama' : option === 'notrans' ? 'No. Transaksi' : 'No. Transaksi')); - inputField.focus(); - return; // Hentikan proses jika input kosong - } + let memberType = $("#member-type").val(); + let department = $("#department").val(); + let vehicle = $("#vehicle").val(); + + // ========================= + // VALIDASI PENCARIAN + // ========================= + + if (searchBy !== "" && searchValue === "") { + let label = { + nama: "Nama", + alamat: "Alamat", + nopol: "No. Polisi", + nocard: "No. Card", + notrans: "No. Transaksi", + }; + + alert("Harap masukkan " + (label[searchBy] || "kata kunci")); + + $("#search-value").focus(); + + return; } - console.log(option); - var formData = $('#search-form .input-default:visible').val(); - console.log(formData); - + // ========================= + // DEBUG + // ========================= + + console.log({ + searchBy, + searchValue, + memberType, + department, + vehicle, + }); + + // ========================= + // AJAX + // ========================= $.ajax({ url: url, type: "POST", - data: $('#search-form').serialize(), - headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}, - dataType: 'json', + data: $("#search-form").serialize(), + headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") }, + dataType: "json", + beforeSend: function () { + $("#search-button").prop("disabled", true).html(` + + Mencari... + `); + }, success: function (response) { - var tbody = $('#example tbody'); + var tbody = $("#example tbody"); tbody.empty(); if (response.length !== 0) { @@ -98,34 +125,35 @@ $(document).ready(function () { data-awal="${element.awal}" data-akhir="${element.akhir}" > - ${rowIndex === 0 ? `${index + 1}` : ''} - ${rowIndex === 0 ? `${element.notrans}` : ''} - ${rowIndex === 0 ? `${element.nama}` : ''} - ${rowIndex === 0 ? `${element.alamat}` : ''} - ${rowIndex === 0 ? `${element.telepon}` : ''} - ${rowIndex === 0 ? `${element.jmember}` : ''} - ${rowIndex === 0 ? `${element.dept}` : ''} - ${rowIndex === 0 ? `${element.awal}
${element.akhir}` : ''} - ${rowIndex === 0 ? ` ${[...new Set(rows.map(r => `${r.nopol || 'No. Polisi tidak ada'} (${r.vehicle || ''})`))].join('
')} ` : ''} - ${rowIndex === 0 ? `${[...new Set(rows.map(r => r.no_card || 'Tidak ada kartu'))].join('
')}` : ''} + ${rowIndex === 0 ? `${index + 1}` : ""} + ${rowIndex === 0 ? `${element.notrans}` : ""} + ${rowIndex === 0 ? `${element.nama}` : ""} + ${rowIndex === 0 ? `${element.alamat}` : ""} + ${rowIndex === 0 ? `${element.telepon}` : ""} + ${rowIndex === 0 ? `${element.jmember}` : ""} + ${rowIndex === 0 ? `${element.dept}` : ""} + ${rowIndex === 0 ? `${element.awal}
${element.akhir}` : ""} + ${rowIndex === 0 ? ` ${[...new Set(rows.map((r) => `${r.nopol || "No. Polisi tidak ada"} (${r.vehicle || ""})`))].join("
")} ` : ""} + ${rowIndex === 0 ? `${[...new Set(rows.map((r) => r.no_card || "Tidak ada kartu"))].join("
")}` : ""} `; - // ${rowIndex === 0 ? ` - // - // - // - // ` : ''} + // ${rowIndex === 0 ? ` + // + // + // + // ` : ''} tbody.append(rowHtml); }); }); } else { alert("Data tidak ditemukan."); - $('#example tbody').html( + $("#example tbody").html( `

Tidak ditemukan data

- `); + `, + ); } - } - }) - }) -}); \ No newline at end of file + }, + }); + }); +}); diff --git a/public/js/member/member-register.js b/public/js/member/member-register.js index 567f4f5..62dd3a2 100644 --- a/public/js/member/member-register.js +++ b/public/js/member/member-register.js @@ -243,6 +243,8 @@ $(document).ready(function () { cardNumber: cardCollect, }; + console.log("FORM ADD:", allData); + if ($(".tambah-member").valid()) { $.ajax({ url: $("#add-member").attr("action"), @@ -250,23 +252,27 @@ $(document).ready(function () { data: allData, headers: { "X-CSRF-token": $('meta[name="csrf-token"]').attr("content") }, beforeSend: function () { - tombol.attr("disabled", true); + // tombol.attr("disabled", true); }, success: function (r) { // alert('Data berhasil dikirim'); - $("#modal-member").modal("hide"); if (r.response == true) { + $("#modal-member").modal("hide"); Swal.fire({ title: "Berhasil!!", - text: "Member anda telah terdaftar !!", + text: "Member berhasil di daftarkan.. !!", timer: 5000, - showConfirmButton: !1, + // showConfirmButton: !1, }).then(function () { // Reload halaman setelah SweetAlert ditutup - location.reload(); // Ini akan me-reload halaman + // location.reload(); // Ini akan me-reload halaman }); } else if (r.response == false) { - Swal.fire("Oops...", "Terjadi kendala !!", "silahkan hubungi admin", "error"); + Swal.fire({ + icon: "error", + title: "Oops...", + text: r.message || "Terjadi kendala, silakan hubungi admin.", + }); } console.log(r); }, diff --git a/public/js/setup/vehicle.js b/public/js/setup/vehicle.js index cff4522..c99345f 100644 --- a/public/js/setup/vehicle.js +++ b/public/js/setup/vehicle.js @@ -1,5 +1,13 @@ $(document).ready(function () { + // Pastikan setiap request AJAX non-GET membawa token CSRF terbaru. + // Mencegah 419 "CSRF token mismatch" walau ada call yang lupa kirim header. + $(document).ajaxSend(function (event, jqxhr, settings) { + if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type || '')) { + jqxhr.setRequestHeader('X-CSRF-TOKEN', $('meta[name="csrf-token"]').attr('content')); + } + }); + $.validator.addClassRules('no-spinner-time', { required: true, min: 1 @@ -30,14 +38,10 @@ $(document).ready(function () { let nama = $(this).data('nama'); console.log(id); - let url = "{{ route('vehicle-price.show',':id') }}"; - url = url.replace(':id', id); - $.ajax({ url: '/setup/master/price/' + id, type: 'GET', - success: function (r) { - console.log(r); + success: function (r) { console.log(r); $('#id_mobil').val(r.id_mobil); $('.title').text('Ubah Tarif Kendaraan ' + nama); $('#waktu_gratis').val(r.waktu_gratis); // waktu kurang dari n pertama @@ -53,35 +57,33 @@ $(document).ready(function () { $('#tarif').val(r.tarif); // tarif berikut nya $('#waktu_tarif').val(r.waktu_tarif); // per n menit $('#maksimum').val(r.maksimum); - if(r.time_base == 1) { - $('#maks_cond').val(r.time_base_maks).show(); - $('#maksimum-cond').val(0).trigger('change'); - $('.maks_cond_day').hide(); - $('#maks_cond_day').hide(); - } else { - $('#maks_cond').hide(); - $('#maksimum-cond').val(1).trigger('change'); - $('.maks_cond_day').show(); - $('#maks_cond_day').val(r.min_per_hari).show(); - } + $('#maksimum_mode').val(r.time_base == 1 ? 'hour' : 'day'); + $('#maksimum_hour').val(r.time_base_maks || 24); + $('#maksimum_day_minute').val(r.min_per_hari || 0); + $('#maksimum_mode').trigger('change'); $('#denda').val(r.denda); $('#denda_member').val(r.denda_member); + $('#denda_only_casual').prop('checked', Number(r.denda_only_casual) === 1); + $('#denda_only').prop('checked', Number(r.denda_only) === 1); + $('#asuransi_incl').val(r.asuransi_incl || 0); + bootstrap.Tab.getOrCreateInstance(document.querySelector('#modal-vehicle .nav-link[href="#tarif"]')).show(); + }, + error: function (xhr) { + Swal.fire({icon: 'error', title: 'Gagal memuat tarif', text: xhr.responseJSON?.message || 'Data tarif tidak ditemukan.'}); } }); }); - $('#maksimum-cond').on('change', function() { - let selectMaxCond = $(this).val(); - if (selectMaxCond == 0 ) { - $('#maks_cond').show().val(24); + $('#maksimum_mode').on('change', function() { + let mode = $(this).val(); + if (mode === 'hour') { + $('#maksimum_hour').show(); $('.maks_cond_day').hide(); - $('#maks_cond_day').val(1).hide(); - } else if (selectMaxCond == 1) { - $('#maks_cond').hide(); - $('.maks_cond_day').show(); - $('#maks_cond_day').show().val(60); + $('#maksimum_day_minute').hide(); } else { - + $('#maksimum_hour').hide(); + $('.maks_cond_day').show(); + $('#maksimum_day_minute').show(); } }); @@ -91,11 +93,16 @@ $(document).ready(function () { return false; } + let id = $('#id_mobil').val(); + if (!id) { + Swal.fire({icon: 'warning', title: 'ID kendaraan kosong', text: 'Tutup modal lalu klik tombol Tarif lagi.'}); + return false; + } + let vehicleRate = $('#show-tarif').serialize(); - let id = $('#id_mobil').val(); let url = $(this).data('url'); - url = url.replace(':id', id); + url = url.replace(':id', encodeURIComponent(id)); console.log(url); $.ajax({ @@ -106,11 +113,21 @@ $(document).ready(function () { }, data: vehicleRate, success: function(r) { - console.log(r); - + if (!r.status) { + Swal.fire({icon: 'info', title: r.message || 'Tidak ada perubahan data'}); + return; + } $('#modal-vehicle').modal('hide'); - } + Swal.fire({icon: 'success', title: 'Tarif diperbarui', timer: 1500, showConfirmButton: false}); + }, + error: function(xhr) { + if (xhr.status === 419) { + Swal.fire({icon: 'warning', title: 'Sesi berakhir', text: 'Token CSRF kedaluwarsa. Halaman akan dimuat ulang untuk token baru.', confirmButtonText: 'Muat ulang'}).then(() => window.location.reload()); + return; + } + Swal.fire({icon: 'error', title: 'Gagal memperbarui tarif', text: xhr.responseJSON?.message || 'Silakan periksa kembali data tarif.'}); + } }); }); -}); \ No newline at end of file +}); diff --git a/public/js/stikerMember/index.js b/public/js/stikerMember/index.js index eb80901..6824287 100644 --- a/public/js/stikerMember/index.js +++ b/public/js/stikerMember/index.js @@ -1,74 +1,101 @@ $(document).ready(function () { - - $(document).on('change', '.form-select', function () { - if ($('option:selected', this).val() == '') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'none'); + $(document).on("change", ".form-select", function () { + if ($("option:selected", this).val() == "") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "none"); // $(".site").val(''); - } else if ($('option:selected', this).val() == 'nama') { - $(".nama").css('display', 'block'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'none'); - $(".input-default").val(''); - } else if ($('option:selected', this).val() == 'nopol') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'block'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'none'); - $(".input-default").val(''); - } else if ($('option:selected', this).val() == 'nocard') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'block'); - $(".notrans").css('display', 'none'); - $(".input-default").val(''); - } else if ($('option:selected', this).val() == 'notrans') { - $(".nama").css('display', 'none'); - $(".nopol").css('display', 'none'); - $(".nocard").css('display', 'none'); - $(".notrans").css('display', 'block'); - $(".input-default").val(''); + } else if ($("option:selected", this).val() == "nama") { + $(".nama").css("display", "block"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "none"); + $(".input-default").val(""); + } else if ($("option:selected", this).val() == "nopol") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "block"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "none"); + $(".input-default").val(""); + } else if ($("option:selected", this).val() == "nocard") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "block"); + $(".notrans").css("display", "none"); + $(".input-default").val(""); + } else if ($("option:selected", this).val() == "notrans") { + $(".nama").css("display", "none"); + $(".nopol").css("display", "none"); + $(".nocard").css("display", "none"); + $(".notrans").css("display", "block"); + $(".input-default").val(""); } else { - $(".position").css('display', 'none'); - $(".level-area").css('display', 'none'); - $(".input-default").val(''); + $(".position").css("display", "none"); + $(".level-area").css("display", "none"); + $(".input-default").val(""); } }); - - var url = $('#example').data('url'); - $('#search-button').on('click', function () { - // Ambil nilai dari selected option - var option = $('.select-option').val(); + var url = $("#example").data("url"); - // Cek jika option yang dipilih bukan 'Pencarian Data...' yang berarti pilihan valid - if (option && option !== '') { - var inputField = $('.input-default:visible'); + $("#search-button").on("click", function () { + let searchBy = $("#search-by").val(); + let searchValue = $("#search-value").val().trim(); - // Periksa apakah input tersebut kosong - if (inputField.length === 0 || inputField.val().trim() === '') { - // Jika kosong, tampilkan alert - alert('Harap masukkan ' + (option === 'nopol' ? 'No. Polisi' : option === 'nocard' ? 'No. Card' : option === 'nama' ? 'Nama' : option === 'notrans' ? 'No. Transaksi' : 'No. Transaksi')); - inputField.focus(); - return; // Hentikan proses jika input kosong - } + let memberType = $("#member-type").val(); + let department = $("#department").val(); + let vehicle = $("#vehicle").val(); + let isActive = $("#isActive").is(":checked") ? 1 : 0; + + // ========================= + // VALIDASI PENCARIAN + // ========================= + + if (searchBy !== "" && searchValue === "") { + let label = { + nama: "Nama", + alamat: "Alamat", + nopol: "No. Polisi", + nocard: "No. Card", + notrans: "No. Transaksi", + }; + + let message = "Harap masukkan " + (label[searchBy] || "kata kunci"); + + // Highlight input + $("#search-value").addClass("is-invalid").focus(); + + // Tampilkan pesan + $("#search-status").removeClass("text-success").addClass("text-danger").text(message); + + $("#search-value").focus(); + + return; } - console.log(option); - var formData = $('#search-form .input-default:visible').val(); - console.log(formData); - + let searchData = { + searchBy: searchBy, + searchValue: searchValue, + memberType: memberType, + department: department, + vehicle: vehicle, + isActive: isActive, + }; + + // ========================= + // AJAX + // ========================= + $.ajax({ url: url, type: "POST", - data: $('#search-form').serialize(), - headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}, - dataType: 'json', + // data: $("#search-form").serialize(), + data: searchData, + headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") }, + dataType: "json", success: function (response) { - var tbody = $('#example tbody'); + var tbody = $("#example tbody"); tbody.empty(); if (response.length !== 0) { @@ -98,34 +125,51 @@ $(document).ready(function () { data-awal="${element.awal}" data-akhir="${element.akhir}" > - ${rowIndex === 0 ? `${index + 1}` : ''} - ${rowIndex === 0 ? `${element.notrans}` : ''} - ${rowIndex === 0 ? `${element.nama}` : ''} - ${rowIndex === 0 ? `${element.alamat}` : ''} - ${rowIndex === 0 ? `${element.telepon}` : ''} - ${rowIndex === 0 ? `${element.jmember}` : ''} - ${rowIndex === 0 ? `${element.dept}` : ''} - ${rowIndex === 0 ? `${element.awal}
${element.akhir}` : ''} - ${rowIndex === 0 ? ` ${[...new Set(rows.map(r => `${r.nopol || 'No. Polisi tidak ada'} (${r.vehicle || ''})`))].join('
')} ` : ''} - ${rowIndex === 0 ? `${[...new Set(rows.map(r => r.no_card || 'Tidak ada kartu'))].join('
')}` : ''} + ${rowIndex === 0 ? `${index + 1}` : ""} + ${rowIndex === 0 ? oldAdmin(notrans, rowspan) : ""} + ${rowIndex === 0 ? `${element.nama}` : ""} + ${rowIndex === 0 ? `${element.alamat}` : ""} + ${rowIndex === 0 ? `${element.telepon}` : ""} + ${rowIndex === 0 ? `${element.jmember}` : ""} + ${rowIndex === 0 ? `${element.dept}` : ""} + ${rowIndex === 0 ? `${element.awal}
${element.akhir}` : ""} + ${rowIndex === 0 ? ` ${[...new Set(rows.map((r) => `${r.nopol || "No. Polisi tidak ada"} (${r.vehicle || ""})`))].join("
")} ` : ""} + ${rowIndex === 0 ? `${[...new Set(rows.map((r) => r.no_card || "Tidak ada kartu"))].join("
")}` : ""} `; - // ${rowIndex === 0 ? ` - // - // - // - // ` : ''} + // ${rowIndex === 0 ? ` + // + // + // + // ` : ''} tbody.append(rowHtml); }); }); } else { - alert("Data tidak ditemukan."); - $('#example tbody').html( + // alert("Data tidak ditemukan."); + $("#example tbody").html( `

Tidak ditemukan data

- `); + `, + ); } - } - }) - }) -}); \ No newline at end of file + }, + }); + }); + + function oldAdmin(value, rowSpan) { + let hasSlash = value.includes("/"); + if (hasSlash) { + return ` + ${value} + + `; + } + return ` + ${value} + `; + } +}); diff --git a/public/js/stikerMember/member-register.js b/public/js/stikerMember/member-register.js index 567f4f5..0620b95 100644 --- a/public/js/stikerMember/member-register.js +++ b/public/js/stikerMember/member-register.js @@ -255,7 +255,7 @@ $(document).ready(function () { success: function (r) { // alert('Data berhasil dikirim'); $("#modal-member").modal("hide"); - if (r.response == true) { + if (r.status == true) { Swal.fire({ title: "Berhasil!!", text: "Member anda telah terdaftar !!", @@ -265,7 +265,7 @@ $(document).ready(function () { // Reload halaman setelah SweetAlert ditutup location.reload(); // Ini akan me-reload halaman }); - } else if (r.response == false) { + } else if (r.status == false) { Swal.fire("Oops...", "Terjadi kendala !!", "silahkan hubungi admin", "error"); } console.log(r); diff --git a/resources/views/Intercom/UI/index.blade.php b/resources/views/Intercom/UI/index.blade.php new file mode 100644 index 0000000..fa70212 --- /dev/null +++ b/resources/views/Intercom/UI/index.blade.php @@ -0,0 +1,235 @@ +@extends('layouts.master') + +@section('styles') + + +@endsection + +@section('content') +
+
+
+
+
+

Intercom Pusat โ€” local network only

+

Client ketok via 1 tombol → masuk antrean → operator TERIMA → sesi private (client lain tidak dengar).

+
+
+ menghubungkanโ€ฆ + +
+
+ +
+ {{-- ANTREAN --}} +
+
+
+
Antrean 0
+

Bunyi ting tiap ada request baru. Klik TERIMA untuk buka sesi private.

+

Tidak ada antrean.

+
+
+
+ + {{-- SESI AKTIF --}} +
+
+
+
Sesi Aktif
+

Belum ada sesi. Terima salah satu antrean.

+
+ Voice tetap lewat hardware/headset via Go relay UDP :5000. + Dashboard ini kontrolnya (terima / end / panggil). + Tombol fisik TALK = tahan buat ngomong (PTT half-duplex). +
+
+
+
+
+ + {{-- STATUS SEMUA UNIT --}} +
+
+
+
+
Status Unit
+
+
+
+
+
+ + {{-- RIWAYAT --}} +
+
+
+
+
Riwayat 50 terakhir
+
+ + + + + +
IDUnitArahStatusDimintaDurasiRekaman
Memuatโ€ฆ
+
+

Rekaman masih raw PCM 16kHz mono โ€” klik unduh lalu putar: ffplay -f s16le -ar 16000 file.raw

+
+
+
+
+ +
+
+
+@endsection + +@section('javascript') + +@endsection diff --git a/resources/views/Parking/Audit/index.blade.php b/resources/views/Parking/Audit/index.blade.php new file mode 100644 index 0000000..3126862 --- /dev/null +++ b/resources/views/Parking/Audit/index.blade.php @@ -0,0 +1,379 @@ +@extends('layouts.master') + +@section('styles') + + +@endsection + +@section('content') + +
+ +
+
+

Audit Report

+ + Transaksi dengan status transaksi bermasalah + +
+
+ + + {{-- FILTER --}} +
+ +
+ + {{-- FILTER --}} +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + {{-- SUMMARY --}} +
+ +
+
+ Total Transaksi + +

+ 0 +

+
+
+ +
+
+ Total Bayar + +

+ Rp 0 +

+
+
+ +
+ + + {{-- TABLE --}} +
+ + + + + + + + + + + + + + + + + + + + {{-- kosong saat pertama dibuka --}} + + + +
IDNomer PolisiWaktu MasukWaktu KeluarStatusBayar KeluarAlasanJenis PembayaranPetugas
+ +
+ +
+ +
+ +
+ +@endsection + + +@section('javascript') + + + +@endsection diff --git a/resources/views/Parking/setup/member/stasiunkerja.blade.php b/resources/views/Parking/setup/member/stasiunkerja.blade.php new file mode 100644 index 0000000..d83f4b6 --- /dev/null +++ b/resources/views/Parking/setup/member/stasiunkerja.blade.php @@ -0,0 +1,143 @@ +@extends('layouts.master') {{-- sesuaikan dengan layout master project kamu --}} + +@section('content') +
+ +
+

Data Stasiun Kerja

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + {{-- diisi lewat JS --}} + +
KodeNamaMarginSPVTargetLokasiLantaiStatusAlamatBankTelpKode AksesAksi
+
+ +
+ +{{-- ===================== MODAL FORM ===================== --}} + + +@endsection + +@section('javascript') + + +@endsection diff --git a/resources/views/Parking/setup/member/tarifstiker.blade.php b/resources/views/Parking/setup/member/tarifstiker.blade.php new file mode 100644 index 0000000..a312843 --- /dev/null +++ b/resources/views/Parking/setup/member/tarifstiker.blade.php @@ -0,0 +1,150 @@ +@extends('layouts.master') {{-- sesuaikan dengan layout master project kamu --}} + +@section('content') +
+ +
+

Data Tarif Stiker

+ +
+ +
+ + + + + + + + + + + + + + + + + {{-- diisi lewat JS --}} + +
+ Tarif ID โ–ผ + ID MobilJenis LanggananTarifJenisJenis MemberLangganan (Stasiun Kerja)OnlineKode AksesAksi
+
+ +
+ +{{-- ===================== MODAL FORM ===================== --}} + + +@endsection + +@section('javascript') + + +@endsection diff --git a/resources/views/apps/member/create.blade.php b/resources/views/apps/memberin/create.blade.php similarity index 100% rename from resources/views/apps/member/create.blade.php rename to resources/views/apps/memberin/create.blade.php diff --git a/resources/views/apps/member/index.blade.php b/resources/views/apps/memberin/index.blade.php similarity index 90% rename from resources/views/apps/member/index.blade.php rename to resources/views/apps/memberin/index.blade.php index 9f688f7..a8576af 100644 --- a/resources/views/apps/member/index.blade.php +++ b/resources/views/apps/memberin/index.blade.php @@ -10,7 +10,7 @@
- {{-- Logo parkq-systems --}} @@ -31,6 +31,11 @@ class="img-fluid logo-img"> --}}
+
+ No. Transaksi + +
+
Nama @@ -52,11 +57,11 @@ class="img-fluid logo-img"> --}}
Member masih aktif
- +

silahkan pilih produk di bawah ini :

@@ -75,6 +80,12 @@ class="img-fluid logo-img"> --}}
+
+ No. Transaksi + + +
+
Nama @@ -99,10 +110,10 @@ class="img-fluid logo-img"> --}}
- +
@@ -107,11 +107,11 @@
- +

Congratulations Johnson

-

You have done 57.6% more sales today. +

You have done 57.6% more sales today. Check your new badge in your profile.

@@ -426,17 +426,7 @@
- - -
-@endsection \ No newline at end of file +@endsection diff --git a/resources/views/layouts/master.blade.php b/resources/views/layouts/master.blade.php index de4b38f..a350d0a 100644 --- a/resources/views/layouts/master.blade.php +++ b/resources/views/layouts/master.blade.php @@ -22,12 +22,24 @@
- + @include('layouts.partial.horizontal-menu') - + @yield('content') -
+ + + + + + @@ -42,7 +54,7 @@ - + @@ -50,4 +62,4 @@ @yield('javascript') - \ No newline at end of file + diff --git a/resources/views/livewire/counter.blade.php b/resources/views/livewire/counter.blade.php new file mode 100644 index 0000000..6a52160 --- /dev/null +++ b/resources/views/livewire/counter.blade.php @@ -0,0 +1,3 @@ +
+ {{-- If your happiness depends on money, you will never be happy with yourself. --}} +
diff --git a/resources/views/livewire/index.blade.php b/resources/views/livewire/index.blade.php new file mode 100644 index 0000000..71a42f1 --- /dev/null +++ b/resources/views/livewire/index.blade.php @@ -0,0 +1,70 @@ +@extends('layouts.master') + +@section('title', 'DHCP Reservations') + +@section('content') +
+
+
+
+
+

+ Lot Parking +

+
+
+ +
+ {{-- Container yang akan di-refresh --}} +
+ @include('livewire.partial-lot', ['parkingDataByArea' => $parkingDataByArea]) +
+
+
+
+
+@endsection + +@section('javascript') + +@endsection diff --git a/resources/views/livewire/parking-lot-component.blade.php b/resources/views/livewire/parking-lot-component.blade.php new file mode 100644 index 0000000..fb78366 --- /dev/null +++ b/resources/views/livewire/parking-lot-component.blade.php @@ -0,0 +1,44 @@ +
+ +
+
+
+
+
+

Lot Parking

+
+
+
+
+ @foreach ($parkingDataByArea as $areaData) +
+

Lantai: {{ $areaData['lantai'] }}

+
+
+ @foreach ($areaData['data'] as $log) +
+ {{ $log->status == 1 ? 'Red Car' : 'Green Car' }} + {{ $log->lot_number }} +
+ @endforeach +
+
+
+ @endforeach +
+
+
+
+
+
diff --git a/resources/views/livewire/partial-lot.blade.php b/resources/views/livewire/partial-lot.blade.php new file mode 100644 index 0000000..e29c09c --- /dev/null +++ b/resources/views/livewire/partial-lot.blade.php @@ -0,0 +1,42 @@ +
+ + @foreach ($parkingDataByArea as $areaData) + +
+ +

+ Lantai: {{ $areaData['lantai'] }} +

+ +
+ +
+ + @foreach ($areaData['data'] as $log) + +
+ + {{ $log->status == 1 ? 'Red Car' : 'Green Car' }} + + + {{ $log->lot_number }} + + +
+ + @endforeach + +
+ +
+ +
+ + @endforeach + +
diff --git a/resources/views/livewire/space.blade.php b/resources/views/livewire/space.blade.php new file mode 100644 index 0000000..0af044f --- /dev/null +++ b/resources/views/livewire/space.blade.php @@ -0,0 +1,200 @@ +@extends('layouts.master') + +@section('title', 'Parking Space Information') + +@section('styles') + + {{-- --}} + +@endsection + +@section('content') + + + + + + +
+ + {{--
+ +
+
+
+
+
+

Lot Parking

+
+
+
+
+ +
+
+
+
+
+ +
--}} + +
+
+
+
+
+

Lot Parking

+
+
+
+
+ +
+
+
+
+
+ + + {{-- +
+
+
+
+
+

Treeview

+
+
+
+
+
+ @foreach ($parkingDataByArea as $areaData) +

Lantai: {{ $areaData['lantai'] }}

+
+
+ @foreach ($areaData['data'] as $log) +
+ @if ($log->status == 1) + +
  • Lot Number: {{ $log->lot_number }} - Status: {{ $log->status }}
  • + @else + +
  • Lot Number: {{ $log->lot_number }} - Status: {{ $log->status }}
  • + @endif +
    + @endforeach +
    +
    + @endforeach +
    +
    +
    +
    +
    +
    + --}} +
    + + +@endsection + +@section('javascript') + + +@endsection diff --git a/resources/views/member/member.blade.php b/resources/views/note_usemember/member.blade.php similarity index 100% rename from resources/views/member/member.blade.php rename to resources/views/note_usemember/member.blade.php diff --git a/resources/views/member/modal-add.blade.php b/resources/views/note_usemember/modal-add.blade.php similarity index 100% rename from resources/views/member/modal-add.blade.php rename to resources/views/note_usemember/modal-add.blade.php diff --git a/resources/views/operasional/transaksi-parkir.blade.php b/resources/views/operasional/transaksi-parkir.blade.php index ea8989f..ad19e4c 100644 --- a/resources/views/operasional/transaksi-parkir.blade.php +++ b/resources/views/operasional/transaksi-parkir.blade.php @@ -39,66 +39,226 @@
    - -
    + +
    - - + +
    - - - +
    - -
    + +
    - - + +
    - -
    -

    Harian

    - +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    + +
    -
    -
    +
    +
    +
    +
    +
    Hasil Transaksi
    + +
    +
    + + + + + + + + + + + + + + + + + +
    No.IDNo. PolBarcodeKendaraanWaktu MasukWaktu KeluarStatus KendaraanStatus Transaksi
    Memuat data...
    +
    +
    + + +
    -
    + @endsection @section('javascript') + @endsection \ No newline at end of file diff --git a/resources/views/setup.blade.php b/resources/views/setup.blade.php new file mode 100644 index 0000000..11391cf --- /dev/null +++ b/resources/views/setup.blade.php @@ -0,0 +1,31 @@ + + + + + + + Persiapan Aplikasi | P-Solution Parking + + + +@php + $checks = [ + ['Koneksi dan keamanan database', $status['pgcrypto']], + ['Identitas petugas', $status['pegawai_columns']['id']], + ['Informasi kontak petugas', $status['pegawai_columns']['email']], + ['Verifikasi akun petugas', $status['pegawai_columns']['email_verified_at']], + ['Keamanan sesi petugas', $status['pegawai_columns']['remember_token']], + ['Akses masuk aplikasi', $status['users_view']], + ['Riwayat verifikasi transaksi', $status['veri_time']], + ['Penyimpanan sesi aplikasi', $status['sessions']], + ]; + $readyCount = count(array_filter(array_column($checks, 1))); +@endphp +
    PP-Solution Parking

    Menyiapkan aplikasi untuk digunakan.

    Kami sedang memeriksa kebutuhan awal aplikasi. Data dan pengaturan yang sudah ada tetap aman, lalu aplikasi akan menyesuaikan diri dengan sistem parkir di lokasi ini.

    Status persiapan{{ $readyCount }}/{{ count($checks) }} bagian siap
      @foreach($checks as [$label, $ready])
    • {{ $ready ? 'โœ“' : '!' }}{{ $label }}{{ $ready ? 'SIAP' : 'PERLU DISIAPKAN' }}
    • @endforeach
    Proses ini tidak menghapus data transaksi atau data operasional yang sudah ada.
    Menyiapkan aplikasi...
    + + + diff --git a/resources/views/setup/UserLevel/level-user.blade.php b/resources/views/setup/UserLevel/level-user.blade.php index 7e6e8bc..278f40c 100644 --- a/resources/views/setup/UserLevel/level-user.blade.php +++ b/resources/views/setup/UserLevel/level-user.blade.php @@ -1,8 +1,7 @@ @extends('layouts.master') @section('styles') - + - - @if ($status['users_view'] ) -

    ๐Ÿš€ Laravel Setup Checker

    -

    ๐Ÿš€ Laravel Setup Checker

    - - @else - - @if(session('success')) -

    {{ session('success') }}

    - @endif - - @if(session('error')) -

    {{ session('error') }}

    - @endif - -
      -
    • pgcrypto: - - {{ $status['pgcrypto'] ? 'OK' : 'NOT FOUND' }} - -
    • - -
    • pegawai.id: - - {{ $status['pegawai_columns']['id'] ? 'OK' : 'MISSING' }} - -
    • - -
    • pegawai.email: - - {{ $status['pegawai_columns']['email'] ? 'OK' : 'MISSING' }} - -
    • - -
    • pegawai.email_verified_at: - - {{ $status['pegawai_columns']['email_verified_at'] ? 'OK' : 'MISSING' }} - -
    • - -
    • pegawai.remember_token: - - {{ $status['pegawai_columns']['remember_token'] ? 'OK' : 'MISSING' }} - -
    • - -
    • users view: - - {{ $status['users_view'] ? 'OK' : 'NOT FOUND' }} - -
    • - -
    • transaksi_parkir.veri_time: - - {{ $status['veri_time'] ? 'OK' : 'MISSING' }} - -
    • -
    - -
    - @csrf - - -
    -
    - Memproses setup databaseโ€ฆ -
    -
    - @endif - - - +
    +
    +
    PP-Solution Parking
    +

    Operasional parkir, siap dalam satu tempat.

    +

    Kelola aktivitas parkir, transaksi, member, dan pengaturan lokasi melalui sistem operasional P-Solution Parking.

    + @if($isReady) + + @else +

    Aplikasi perlu disiapkan untuk database di lokasi ini sebelum dapat digunakan.

    + + @endif +
    + +
    diff --git a/routes/api.php b/routes/api.php index 94de0a6..2fa949b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -6,10 +6,13 @@ use App\Http\Controllers\Api\V1\ConfigParameter; use App\Http\Controllers\Api\V1\DisplayContentController; use App\Http\Controllers\Api\V1\LaporanController; -use App\Http\Controllers\Api\V1\MemberinController; +use App\Http\Controllers\Api\V1\Memberin\MemberinController; use App\Http\Controllers\Api\V1\ParkingOperationController; use App\Http\Controllers\Api\V1\TransaksiMember; use App\Http\Controllers\Api\V1\TransaksiParkir; +use App\Http\Controllers\Api\V1\LocationSiteInfoController; +use App\Http\Controllers\Api\V1\ParkingLot\ParkingLotController; +use App\Http\Controllers\Api\V1\IntercomController; /* |-------------------------------------------------------------------------- @@ -34,18 +37,33 @@ Route::resource("parking", ParkingOperationController::class); Route::prefix("cashier")->group(function () { Route::post("login", [CashierOperationController::class, "login"]); - Route::post("getmember", [ - CashierOperationController::class, - "checkMember", - ]); - Route::post("transaksinside", [ - CashierOperationController::class, - "transaksiInside", - ]); + Route::post("getmember", [CashierOperationController::class, "checkMember"]); + Route::post("transaksinside", [CashierOperationController::class, "transaksiInside"]); // Route::middleware('CashierAuthentication')->group(function () { // }); }); }); + Route::get("location", [LocationSiteInfoController::class, "getLocation"]); + Route::resource("memberin", MemberinController::class)->names("memberin"); + Route::post("memberin/register-confirm", [MemberinController::class, "registerConfirm"]); + Route::post("memberin/renew-confirm", [MemberinController::class, "renewConfirm"]); + + // Parking Space + // Parking LOT Backend + // Route::resource("parking-lot", ParkingLotController::class); + Route::post("parking-lot", [ParkingLotController::class, "store"]); + Route::put("parking-lot", [ParkingLotController::class, "update"]); + + // Intercom: signaling + log. Voice via Go relay UDP, bukan via PHP. + Route::prefix("intercom")->group(function () { + Route::get("devices", [IntercomController::class, "devices"]); + Route::get("queue", [IntercomController::class, "queue"]); + Route::get("history", [IntercomController::class, "history"]); + Route::post("request", [IntercomController::class, "request"]); + Route::post("accept", [IntercomController::class, "accept"]); + Route::post("end", [IntercomController::class, "end"]); + Route::post("heartbeat", [IntercomController::class, "heartbeat"]); + }); }); Route::prefix("transaksi")->group(function () { @@ -54,8 +72,6 @@ Route::resource("parkir", TransaksiParkir::class); }); -Route::resource("member", MemberinController::class)->names("memberin"); - Route::prefix("report")->group(function () { Route::post("harian", [LaporanController::class, "harian"]); Route::post("payment", [LaporanController::class, "payment"]); diff --git a/routes/web.php b/routes/web.php index 2c45782..0dd03a1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,7 @@ name("setup.run"); +Route::get('/', [SetupController::class, 'index']); +Route::get('/setup', [SetupController::class, 'setup'])->name('setup.index'); +Route::post('/setup/run', [SetupController::class, 'run']) + ->middleware('throttle:3,1') + ->name('setup.run'); Route::resource("/apps", ModifiedTransaksi::class); Route::post("/scan", [ModifiedTransaksi::class, "scan"])->name("apps.scan"); @@ -62,18 +72,34 @@ Route::resource("gate", MasterGateController::class); Route::resource("gate-details", MasterGateParameterController::class); Route::resource("gate-hardware", MasterGateHardwareController::class)->names("hardware"); + Route::get("dhcp", [MasterConfigNetworkController::class, "index"])->name("dhcp.index"); + Route::get("/dhcp/status", [MasterConfigNetworkController::class, "status"])->name("dhcp.status"); + Route::post("/dhcp/apply", [MasterConfigNetworkController::class, "apply"])->name("dhcp.apply"); + Route::post("/dhcp/reservations", [MasterConfigNetworkController::class, "store"])->name( + "dhcp.reservations.store", + ); + Route::get("/dhcp/logs", [MasterConfigNetworkController::class, "logs"])->name("dhcp.logs"); Route::resource("vehicle", MasterVehicleController::class); Route::resource("price", ModifiedVehiclepriceController::class)->names("vehiclePrice"); Route::resource("level-user", LevelUserController::class)->names("levelUser"); Route::resource("level-menu", LevelMenuController::class)->names("levelMenu"); + Route::get("stasiunkerja/data", [StasiunKerjaController::class, "data"])->name("stasiunkerja.data"); + Route::resource("stasiunkerja", StasiunKerjaController::class) + ->parameters(["stasiunkerja" => "kode"]) // ganti {stasiunkerja} jadi {kode} di URI + ->except(["create", "edit"]); // gak perlu, form create/edit pakai modal di halaman index + Route::get("tarif-stiker/data", [MasterTarifStikerController::class, "data"])->name("tarif-stiker.data"); + + Route::resource("tarif-stiker", MasterTarifStikerController::class) + ->parameters(["tarif-stiker" => "tarif_id"]) + ->except(["create", "edit"]); }); }); Route::prefix("/transaksi")->group(function () { + Route::get("parkir/data", [TransaksiParkirController::class, "data"])->name("transaksiparkir.data"); Route::resource("parkir", TransaksiParkirController::class)->names("transaksiparkir"); Route::resource("stiker", MemberStikerController::class)->names("getStikerMember"); - // Route::post("stiker/data", [MemberStikerController::class, "getMember"]) - // ->names("getMember") + Route::post("stiker/data", [MemberStikerController::class, "getMember"])->name("getMember"); // ->where(["stiker"] => '.*'); Route::post("stiker/checkField", [MemberStikerController::class, "checkField"]); Route::resource("member", MemberStikerController::class); @@ -95,9 +121,15 @@ Route::get("/harian/data", [LaporanStiker::class, "dataStiker"])->name("stikerData"); }); }); + + Route::get("audit", [AuditReportController::class, "index"])->name("audit"); + Route::get("audit/data", [AuditReportController::class, "data"])->name("audit.data"); Route::prefix("/tools")->group(function () { Route::resource("stiker-extend", StikerExtendedController::class); Route::resource("change-trans", TransactionChangeController::class); + Route::get("parking-lot", [ParkingLotComponent::class, "index"]); + Route::get("parking-lot/data", [ParkingLotComponent::class, "getParkingData"])->name("parking-lot.data"); + // Route::get("parking-lot/data", [ParkingLotComponent::class, "data"])->name("parking-lot.data"); }); Route::prefix("/front-office")->group(function () { @@ -115,8 +147,10 @@ Route::resource("settings-register", MemberProductController::class); Route::get("streaming", [StreamerController::class, "index"]); Route::get("stream-snapshot", [StreamerController::class, "streamSnapshot"]); -}); -Route::get("phpinformation", function () { - phpinfo(); -})->name("phpmyinfo"); + // Intercom pusat: dashboard operator + download rekaman. Views di views/Intercom/UI/. + Route::prefix("intercom")->group(function () { + Route::get("/", [IntercomUIController::class, "index"])->name("intercom.ui"); + Route::get("/recording/{id}", [IntercomUIController::class, "recording"])->name("intercom.recording"); + }); +});