get(); return view('user/booking-foto', compact('foto')); } public function detail($id) { $foto = PaketFoto::findOrFail($id); \Carbon\Carbon::setLocale('id'); // Jika add-ons disimpan di DB, ambil juga: $additionals = Additional::all(); // Logika Tanggal $start = \Carbon\Carbon::now(); // Mulai hari ini $end = \Carbon\Carbon::now()->addMonth(); // Maksimal 1 bulan ke depan // Untuk navigasi panah $prevMonth = $start->copy()->subMonth(); $nextMonth = $start->copy()->addMonth(); // translatedFormat akan mengikuti locale 'id' di config Anda $currentMonthLabel = $start->isoFormat('MMMM YYYY'); return view('user.detail-foto', compact('foto', 'additionals', 'start', 'end', 'currentMonthLabel', 'prevMonth', 'nextMonth')); } public function loadCalendar(Request $request) { \Carbon\Carbon::setLocale('id'); $month = $request->month ?? date('m'); $year = $request->year ?? date('Y'); $start = \Carbon\Carbon::createFromDate($year, $month, 1); // Data Navigasi $prevMonth = $start->copy()->subMonth(); $nextMonth = $start->copy()->addMonth(); $currentMonthLabel = $start->isoFormat('MMMM YYYY'); $html = view('user.components.calendar-grid', compact( 'start', 'prevMonth', 'nextMonth', 'currentMonthLabel' ))->render(); // Return JSON agar JavaScript bisa membacanya sebagai data.html return response()->json(['html' => $html]); } public function cekSlot(Request $request) { $booked = BookingFoto::where('tgl_booking', $request->tanggal) ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima', 'selesai']) ->get(['jam_mulai']); return response()->json($booked); } public function formulir(Request $request) { // 1. Ambil Data Paket $foto = PaketFoto::findOrFail($request->id_paket); $durasiDasar = $foto->durasi; // Ambil durasi paket asli // 2. Hitung Total Add-ons & Tambahan Menit $addonsDetails = []; $totalAddon = 0; $tambahanMenit = 0; // Inisialisasi tambahan waktu if ($request->has('addons')) { foreach ($request->addons as $id => $qty) { if ($qty > 0) { $add = Additional::find($id); if ($add) { $subtotal = $add->harga * $qty; $totalAddon += $subtotal; // LOGIKA TAMBAHAN WAKTU BERDASARKAN ID if ($id == 4) { // Tambah waktu/5 menit $tambahanMenit += (5 * $qty); } elseif ($id == 6) { // Tambah 10 menit sesi Spotlight $tambahanMenit += (10 * $qty); } $addonsDetails[] = [ 'nama' => $add->nama, 'qty' => $qty, 'subtotal' => $subtotal, 'id' => $id ]; } } } } // 3. Hitung Ulang Jam Selesai $totalDurasi = $durasiDasar + $tambahanMenit; $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai); $jamSelesaiBaru = $jamMulai->copy()->addMinutes($totalDurasi)->format('H:i'); // 4. Hitung Grand Total $grandTotal = $foto->harga + $totalAddon; // 5. Logika Deadline Pembayaran (Tetap sama) if (!session()->has('payment_deadline')) { $deadline = now()->addHours(2); session()->put('payment_deadline', $deadline); } else { $deadline = session('payment_deadline'); } $sisaWaktu = now()->diffInSeconds($deadline, false); if ($sisaWaktu <= 0) { session()->forget(['payment_deadline', 'addons']); return redirect()->route('booking.foto')->with('error', 'Waktu pembayaran telah habis.'); } // 6. Kirim jam_selesai yang sudah diperbarui ke View return view('user.pembayaran-foto', compact( 'foto', 'request', 'addonsDetails', 'grandTotal', 'sisaWaktu', 'jamSelesaiBaru' // Variabel baru untuk ditampilkan di blade )); } public function cancelBooking() { session()->forget(['payment_deadline', 'addons']); // Hapus session timer & data return redirect()->route('booking.foto'); // Kembali ke katalog utama } public function store(Request $request) { $validator = Validator::make($request->all(), [ 'id_paket' => 'required|exists:paket_fotos,id_paket', 'tgl_booking' => 'required|date|after_or_equal:today', 'jam_mulai' => 'required', 'nama' => 'required|string|min:3|max:100', 'no_wa' => 'required|numeric|digits_between:10,15', 'bukti_bayar' => 'required|image|mimes:jpeg,png,jpg|max:2048', ], [ // Detail Pesan Kustom 'required' => 'Kolom :attribute wajib diisi.', 'string' => 'Input :attribute harus berupa teks valid.', 'min' => ':attribute terlalu pendek, minimal :min karakter.', 'max' => ':attribute terlalu panjang, maksimal :max karakter.', 'numeric' => ':attribute harus berupa angka.', 'digits_between' => ':attribute harus antara :min sampai :max digit.', 'date' => 'Format tanggal pada :attribute tidak valid.', 'after_or_equal' => ':attribute tidak boleh tanggal yang sudah lewat.', 'image' => ':attribute harus berupa file gambar.', 'mimes' => 'Format :attribute harus jpeg, png, atau jpg.', 'max.file' => 'Ukuran :attribute maksimal adalah 2MB.', ], [ 'nama' => 'nama pemesan', 'no_wa' => 'nomor WhatsApp', 'bukti_bayar' => 'bukti pembayaran', ]); DB::beginTransaction(); // Mulai Transaksi Database try { // 2. Ambil Data Paket & Hitung Waktu $paket = PaketFoto::findOrFail($request->id_paket); // Asumsi durasi default 20 menit (atau ambil dari database jika ada kolom durasi) $durasiMenit = $paket->durasi; $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai); $jamSelesai = $jamMulai->copy()->addMinutes($durasiMenit); // 3. Cek Slot Sekali Lagi (Mencegah Race Condition) $isTaken = BookingFoto::where('tgl_booking', $request->tgl_booking) ->where('jam_mulai', $request->jam_mulai) ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima', 'selesai']) ->exists(); if ($isTaken) { return back()->with('error', 'Mohon maaf, slot waktu ini baru saja diambil orang lain.'); } // 4. Simpan/Update Data Pelanggan $pelanggan = Pelanggan::firstOrCreate( ['no_wa' => $request->no_wa], ['nama' => $request->nama] ); // 6. Hitung Grand Total (Paket + Additional) // Kita hitung ulang di server agar aman dari manipulasi inspect element $grandTotal = $paket->harga; $listAdditional = []; $totalDurasi = $durasiMenit; if ($request->has('addons')) { foreach ($request->addons as $idAddon => $qty) { if ($qty > 0) { $add = \App\Models\Additional::find($idAddon); if ($add) { $subtotal = $add->harga * $qty; $grandTotal += $subtotal; // Logika Tambah Waktu berdasarkan ID yang sebelumnya kita bahas if ($idAddon == 4) $totalDurasi += (5 * $qty); // Tambah 5 menit if ($idAddon == 6) $totalDurasi += (10 * $qty); // Tambah 10 menit $listAdditional[] = [ 'id_additional' => $idAddon, 'qty' => $qty, 'subtotal' => $subtotal, 'nama_barang' => $add->nama ]; } } } } $jamMulai = \Carbon\Carbon::createFromFormat('H:i', $request->jam_mulai); $jamSelesai = $jamMulai->copy()->addMinutes($totalDurasi); // 3. Cek Slot Sekali Lagi (Mencegah Race Condition) $isTaken = \App\Models\BookingFoto::where('tgl_booking', $request->tgl_booking) ->where('jam_mulai', $request->jam_mulai) ->whereIn('status_booking', ['menunggu_verifikasi', 'diterima']) ->exists(); if ($isTaken) { return back()->with('error', 'Mohon maaf, slot waktu ini baru saja diambil orang lain.'); } // 4. Simpan Data Pelanggan $pelanggan = \App\Models\Pelanggan::create([ 'nama' => $request->nama, 'no_wa' => $request->no_wa ]); // 5. Upload Bukti Bayar $pathBukti = null; if ($request->hasFile('bukti_bayar')) { $file = $request->file('bukti_bayar'); $namaFile = 'bukti_' . time() . '.' . $file->getClientOriginalExtension(); $pathBukti = $file->storeAs('img/payment/foto', $namaFile, 'public'); } // 6. Simpan Booking Utama $booking = \App\Models\BookingFoto::create([ 'no_invoice' => 'INV-FOTO-' . strtoupper(\Illuminate\Support\Str::random(6)), 'id_pelanggan' => $pelanggan->id_pelanggan, 'id_paket' => $paket->id_paket, 'tgl_booking' => $request->tgl_booking, 'jam_mulai' => $request->jam_mulai, 'jam_selesai' => $jamSelesai->format('H:i'), 'total_bayar' => $grandTotal, 'bukti_bayar' => $pathBukti, 'status_booking' => 'menunggu_verifikasi' ]); // 7. Simpan Detail Additional foreach ($listAdditional as $item) { \App\Models\DetailAdditional::create([ 'id_booking' => $booking->id_booking, 'id_additional' => $item['id_additional'], 'qty' => $item['qty'], 'subtotal' => $item['subtotal'] ]); } \Illuminate\Support\Facades\DB::commit(); $txtAddons = ""; if (count($listAdditional) > 0) { $txtAddons = "*Additional:*"; // Judul foreach ($listAdditional as $item) { // Ambil nama yang tadi kita titip $txtAddons .= "\n- " . $item['nama_barang'] . " (" . $item['qty'] . "x)"; } $txtAddons .= "\n"; // Kasih jarak baris } // 8. Redirect ke WhatsApp $pesan = "Halo Admin Flo.do! Saya sudah melakukan pembayaran untuk invoice {$booking->no_invoice}:\n\n" . "*Data Pemesan:*\n" . "Nama: {$request->nama}\n" . "WA: {$request->no_wa}\n\n" . "*Detail Booking:*\n" . "Nama Paket: {$paket->nama}\n" . $txtAddons . "Tanggal: " . \Carbon\Carbon::parse($request->tgl_booking)->translatedFormat('l, d F Y') . "\n" . "Jam: {$request->jam_mulai} - {$jamSelesai->format('H:i')} WIB\n" . "Total: Rp " . number_format($grandTotal, 0, ',', '.') . "\n\n" . "Mohon segera diproses, ya! Terima kasih."; $urlWA = "https://wa.me/6282337687878?text=" . urlencode($pesan); session()->forget('payment_deadline'); return redirect()->route('booking.foto')->with([ 'success' => 'Pesanan Berhasil Dibuat!', 'waUrl' => $urlWA ]); } catch (\Exception $e) { \Illuminate\Support\Facades\DB::rollBack(); return back()->with('error', 'Error: ' . $e->getMessage())->withInput(); } } }