From 2a9b28581ce769b524320f7c82416d1c898b69aa Mon Sep 17 00:00:00 2001 From: pand03-CEO Date: Sun, 4 Oct 2026 07:15:54 +0700 Subject: [PATCH] Rapihin setup/member/lot-parking + fitur intercom, exclude WIP controller & file sampah dari git Kumpulan perubahan yang udah numpuk di working tree: refactor Setup (Gate/Vehicle/Member), rapihin memberin & stiker, service parking baru (ParkingUtilsServices, NetworkService), model/migration lot-parking & DHCP reservation, dan fitur baru Intercom (controller/model/migration/ view). ReportTransaksiController versi lokal ini WIP (~90%, masih didebug) - versi yang bener ada di server, jadi dikeluarin dari git (ditambahin ke .gitignore) biar gak numpuk nindihin punya server pas git pull di sana. Sekalian keluarin dump mentah tabel softseting (1.8MB, nyasar ke-commit), file .bak, dan arsip ad-hoc (api.tgz, c.zip) yang emang bukan source code. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EAJc2mrpj2cSTe4oowLPzQ --- .gitignore | 12 + app/Helpers/ParkingUtils.php | 15 + .../Controllers/Api/V1/ConfigParameter.php | 3 +- .../Controllers/Api/V1/IntercomController.php | 128 +++ .../Api/V1/LocationSiteInfoController.php | 19 + .../Controllers/Api/V1/MemberinController.php | 125 --- .../V1/ParkingLot/ParkingLotController.php | 230 ++++ .../Apps/Memberin/SelfMemberinController.php | 13 +- app/Http/Controllers/Auth/LoginController.php | 8 + .../Controllers/Intercom/UIController.php | 54 + app/Http/Controllers/LevelMenuController.php | 84 -- .../Parking/Audit/AuditReportController.php | 67 ++ .../Parking/MemberStikerController.php | 323 ++++-- .../Operasional/TransaksiParkirController.php | 114 ++ .../Parking/ReportTransaksiController.php | 999 ------------------ .../Gate/MasterConfigNetworkController.php | 185 ++++ .../Gate/MasterGateHardwareController.php | 29 +- .../Member/MasterTarifStikerController.php | 206 ++++ .../Setup/Member/StasiunKerjaController.php | 177 ++++ .../Setup/Vehicle/MasterVehicleController.php | 62 +- .../ModifiedVehiclepriceController.php | 58 +- .../Controllers/Parking/SetupController.php | 59 +- .../ParkingLot/ParkingLotComponent.php | 165 +++ ...rkingSetupUserLevelLevelMenuController.php | 84 -- app/Http/Kernel.php | 1 + .../Middleware/UseBootstrapSessionStore.php | 20 + app/Models/ConfigHardware.php | 10 +- app/Models/ConfigPosNetwork.php | 21 + app/Models/IntercomCall.php | 24 + app/Models/IntercomDevice.php | 21 + app/Models/JenisMobil.php | 9 +- app/Models/LotParking/LotParkingLog.php | 25 + app/Models/LotParking/SiteParking.php | 25 + app/Models/MasterTarifStiker.php | 25 + app/Models/Origin/MasterJenisLangganan.php | 65 ++ app/Models/Origin/MasterStasiunKerja.php | 88 ++ app/Models/Origin/MasterTarifStiker.php | 90 ++ app/Models/StikerTrans.php | 48 +- app/Models/StikerTransDetail.php | 38 +- app/Services/MakeStikerServices.php | 122 +-- app/Services/Parking/NetworkService.php | 195 ++++ app/Services/Parking/ParkingUtilsServices.php | 43 + app/Services/Parking/RateParkingService.php | 10 +- composer.json | 3 +- composer.lock | 95 +- config/network.php | 7 + ..._103742_create_dhcp_reservations_table.php | 35 + ...26_08_20_064557_create_lot_parking_log.php | 33 + ...026_08_30_092544_create_sessions_table.php | 35 + ...26_09_18_000001_create_intercom_tables.php | 46 + public/images/lot/car_red copy@.png | Bin 0 -> 73097 bytes public/images/lot/green.png | Bin 0 -> 14460 bytes public/images/lot/green_car.png | Bin 0 -> 16415 bytes public/images/lot/red.png | Bin 0 -> 14063 bytes public/images/lot/red_car.png | Bin 0 -> 13912 bytes public/js/member/index.js | 184 ++-- public/js/member/member-register.js | 18 +- public/js/setup/vehicle.js | 81 +- public/js/stikerMember/index.js | 202 ++-- public/js/stikerMember/member-register.js | 4 +- resources/views/Intercom/UI/index.blade.php | 235 ++++ resources/views/Parking/Audit/index.blade.php | 379 +++++++ .../setup/member/stasiunkerja.blade.php | 143 +++ .../setup/member/tarifstiker.blade.php | 150 +++ .../{member => memberin}/create.blade.php | 0 .../apps/{member => memberin}/index.blade.php | 32 +- resources/views/auth/login.blade.php | 18 +- resources/views/dashboard.blade.php | 22 +- resources/views/layouts/master.blade.php | 22 +- resources/views/livewire/counter.blade.php | 3 + resources/views/livewire/index.blade.php | 70 ++ .../livewire/parking-lot-component.blade.php | 44 + .../views/livewire/partial-lot.blade.php | 42 + resources/views/livewire/space.blade.php | 200 ++++ .../member.blade.php | 0 .../modal-add.blade.php | 0 .../operasional/transaksi-parkir.blade.php | 206 +++- resources/views/setup.blade.php | 31 + .../setup/UserLevel/level-user.blade.php | 81 +- resources/views/setup/gate/gate.blade.php | 100 +- resources/views/setup/index.blade.php | 603 +++++++++++ .../views/setup/vehicle/vehicle.blade.php | 69 +- .../setup/vehicle/vehicleprice.blade.php | 22 +- .../views/stiker/details-stiker.blade.php | 27 + .../views/stiker/extend-stiker.blade.php | 87 +- resources/views/stiker/stiker.blade.php | 448 ++------ .../views/stiker/tambah-stiker.blade.php | 33 +- resources/views/stiker/ubah-stiker.blade.php | 12 +- resources/views/welcome.blade.php | 171 +-- routes/api.php | 38 +- routes/web.php | 56 +- 91 files changed, 5716 insertions(+), 2470 deletions(-) create mode 100644 app/Helpers/ParkingUtils.php create mode 100644 app/Http/Controllers/Api/V1/IntercomController.php create mode 100644 app/Http/Controllers/Api/V1/LocationSiteInfoController.php delete mode 100644 app/Http/Controllers/Api/V1/MemberinController.php create mode 100644 app/Http/Controllers/Api/V1/ParkingLot/ParkingLotController.php create mode 100644 app/Http/Controllers/Intercom/UIController.php delete mode 100644 app/Http/Controllers/LevelMenuController.php create mode 100644 app/Http/Controllers/Parking/Audit/AuditReportController.php delete mode 100755 app/Http/Controllers/Parking/ReportTransaksiController.php create mode 100644 app/Http/Controllers/Parking/Setup/Gate/MasterConfigNetworkController.php create mode 100644 app/Http/Controllers/Parking/Setup/Member/MasterTarifStikerController.php create mode 100644 app/Http/Controllers/Parking/Setup/Member/StasiunKerjaController.php create mode 100644 app/Http/Controllers/ParkingLot/ParkingLotComponent.php delete mode 100644 app/Http/Controllers/ParkingSetupUserLevelLevelMenuController.php create mode 100644 app/Http/Middleware/UseBootstrapSessionStore.php create mode 100644 app/Models/ConfigPosNetwork.php create mode 100644 app/Models/IntercomCall.php create mode 100644 app/Models/IntercomDevice.php create mode 100644 app/Models/LotParking/LotParkingLog.php create mode 100644 app/Models/LotParking/SiteParking.php create mode 100644 app/Models/MasterTarifStiker.php create mode 100644 app/Models/Origin/MasterJenisLangganan.php create mode 100644 app/Models/Origin/MasterStasiunKerja.php create mode 100644 app/Models/Origin/MasterTarifStiker.php create mode 100644 app/Services/Parking/NetworkService.php create mode 100644 app/Services/Parking/ParkingUtilsServices.php create mode 100644 config/network.php create mode 100644 database/migrations/2026_08_18_103742_create_dhcp_reservations_table.php create mode 100644 database/migrations/2026_08_20_064557_create_lot_parking_log.php create mode 100644 database/migrations/2026_08_30_092544_create_sessions_table.php create mode 100644 database/migrations/2026_09_18_000001_create_intercom_tables.php create mode 100644 public/images/lot/car_red copy@.png create mode 100644 public/images/lot/green.png create mode 100644 public/images/lot/green_car.png create mode 100644 public/images/lot/red.png create mode 100644 public/images/lot/red_car.png create mode 100644 resources/views/Intercom/UI/index.blade.php create mode 100644 resources/views/Parking/Audit/index.blade.php create mode 100644 resources/views/Parking/setup/member/stasiunkerja.blade.php create mode 100644 resources/views/Parking/setup/member/tarifstiker.blade.php rename resources/views/apps/{member => memberin}/create.blade.php (100%) rename resources/views/apps/{member => memberin}/index.blade.php (90%) create mode 100644 resources/views/livewire/counter.blade.php create mode 100644 resources/views/livewire/index.blade.php create mode 100644 resources/views/livewire/parking-lot-component.blade.php create mode 100644 resources/views/livewire/partial-lot.blade.php create mode 100644 resources/views/livewire/space.blade.php rename resources/views/{member => note_usemember}/member.blade.php (100%) rename resources/views/{member => note_usemember}/modal-add.blade.php (100%) create mode 100644 resources/views/setup.blade.php create mode 100644 resources/views/setup/index.blade.php create mode 100644 resources/views/stiker/details-stiker.blade.php 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 0000000000000000000000000000000000000000..e9eaa274fd0abe125bc6d07cc40cb5f0e731795f GIT binary patch literal 73097 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 JGo-ZL{};eFigW+~ literal 0 HcmV?d00001 diff --git a/public/images/lot/green.png b/public/images/lot/green.png new file mode 100644 index 0000000000000000000000000000000000000000..6ee4e56ef2ca6468dc5fbf1238e5326da576ea9f GIT binary patch literal 14460 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 e@PHflhyRFR>R^GbvFJZH-z7!mL~4W#0{Jl$ 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 Xma?^AN{O<6zf8$WDoNCc8HfCTJlUo; literal 0 HcmV?d00001 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"); + }); +});