all(), [ 'tailor_id' => 'required|exists:users,id', 'appointment_date' => 'required|date|after_or_equal:today', 'appointment_time' => 'required|date_format:H:i', 'service_type' => 'required|in:Perbaikan,Jahit Baru', 'category' => 'required|in:Atasan,Bawahan,Terusan', 'design_photo' => 'required|image|max:2048', // max 2MB 'notes' => 'nullable|string', 'payment_method' => 'required|string|max:50' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } try { // Check if tailor exists and is actually a tailor $tailor = User::findOrFail($request->tailor_id); if (!$tailor->isPenjahit()) { return $this->sendError('Error validasi.', ['tailor_id' => 'ID yang dipilih bukan penjahit'], 422); } // Convert appointment time to Carbon instance $requestedTime = Carbon::parse($request->appointment_time); $requestedDate = Carbon::parse($request->appointment_date); // Calculate time slot (2 hours) $slotStart = $requestedTime->copy(); $slotEnd = $requestedTime->copy()->addHours(2); // Check if there is any booking that overlaps with this time slot $existingBooking = Booking::where('tailor_id', $request->tailor_id) ->where('appointment_date', $request->appointment_date) ->where('status', '!=', 'dibatalkan') ->where(function($query) use ($slotStart, $slotEnd) { $query->where(function($q) use ($slotStart, $slotEnd) { // Check if existing booking's time slot overlaps with requested slot $q->where(function($q) use ($slotStart, $slotEnd) { $q->whereRaw('TIME(appointment_time) BETWEEN ? AND ?', [ $slotStart->format('H:i:s'), $slotEnd->format('H:i:s') ]); }) ->orWhere(function($q) use ($slotStart, $slotEnd) { $q->whereRaw('TIME(DATE_ADD(appointment_time, INTERVAL 2 HOUR)) BETWEEN ? AND ?', [ $slotStart->format('H:i:s'), $slotEnd->format('H:i:s') ]); }); }); }) ->first(); if ($existingBooking) { return $this->sendError('Error validasi.', [ 'appointment' => 'Jadwal tidak tersedia. Penjahit sudah memiliki booking pada waktu tersebut (durasi 2 jam)' ], 422); } $booking = new Booking($request->all()); $booking->customer_id = Auth::id(); $booking->status = 'reservasi'; $booking->payment_status = 'unpaid'; $booking->payment_method = $request->payment_method; // Generate transaction code $booking->transaction_code = $this->generateTransactionCode(); // Handle design photo upload if ($request->hasFile('design_photo')) { $path = $request->file('design_photo')->store('design_photos', 'public'); $booking->design_photo = $path; } $booking->save(); // Load relationships $booking->load(['customer', 'tailor']); return $this->sendResponse($booking, 'Booking berhasil dibuat.'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat membuat booking'], 500); } } /** * Get customer's bookings with filter */ public function customerBookings(Request $request) { try { $query = Booking::where('customer_id', Auth::id()) ->with(['tailor', 'ratings']); // Filter by status if provided if ($request->has('status')) { $query->where('status', $request->status); } $bookings = $query->orderBy('created_at', 'desc')->get(); // Transform the bookings to add completion date information $transformedBookings = $bookings->map(function ($booking) { $data = $booking->toArray(); // Add formatted completed_at date if booking is completed if ($booking->status === 'selesai' && $booking->completed_at) { $data['completed_at_formatted'] = Carbon::parse($booking->completed_at)->format('Y-m-d'); } // Add rating information if exists if ($booking->ratings->count() > 0) { $rating = $booking->ratings->first(); $data['rating'] = [ 'id' => $rating->id, 'rating' => $rating->rating, 'review' => $rating->review, 'created_at' => $rating->created_at, 'updated_at' => $rating->updated_at ]; } else { $data['rating'] = null; } // Pastikan transaction_code dan payment_method selalu tersedia di respons $data['transaction_code'] = $booking->transaction_code; $data['payment_method'] = $booking->payment_method; return $data; }); return $this->sendResponse($transformedBookings, 'Data booking berhasil diambil.'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengambil data booking'], 500); } } /** * Get tailor's bookings with filter */ public function tailorBookings(Request $request) { try { $query = Booking::where('tailor_id', Auth::id()) ->with(['customer', 'ratings']); // Filter by status if provided if ($request->has('status')) { $query->where('status', $request->status); } $bookings = $query->orderBy('created_at', 'desc')->get(); // Transform bookings to include rating information $transformedBookings = $bookings->map(function ($booking) { $data = $booking->toArray(); // Add rating information if exists if ($booking->ratings->count() > 0) { $rating = $booking->ratings->first(); $data['rating'] = [ 'id' => $rating->id, 'rating' => $rating->rating, 'review' => $rating->review, 'created_at' => $rating->created_at, 'updated_at' => $rating->updated_at ]; } else { $data['rating'] = null; } // Pastikan transaction_code dan payment_method selalu tersedia di respons $data['transaction_code'] = $booking->transaction_code; $data['payment_method'] = $booking->payment_method; return $data; }); return $this->sendResponse($transformedBookings, 'Data booking berhasil diambil.'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengambil data booking'], 500); } } /** * Update booking status */ public function updateStatus(Request $request, Booking $booking) { $validator = Validator::make($request->all(), [ 'status' => 'required|in:diproses,selesai,dibatalkan', 'total_price' => 'required_if:status,diproses|numeric|min:0', 'completion_date' => 'required_if:status,diproses|date|after_or_equal:today' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } try { // Check if the authenticated user is the tailor of this booking if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk mengubah status booking ini'], 403); } // Validate status transition if ($request->status === 'diproses' && !$booking->canBeProcessed()) { return $this->sendError('Error validasi.', ['status' => 'Booking tidak dapat diproses. Pastikan pembayaran sudah dilakukan.'], 422); } if ($request->status === 'selesai' && !$booking->canBeCompleted()) { return $this->sendError('Error validasi.', ['status' => 'Booking tidak dapat diselesaikan. Status harus diproses terlebih dahulu.'], 422); } $booking->status = $request->status; if ($request->status === 'diproses') { $booking->total_price = $request->total_price; $booking->completion_date = $request->completion_date; } $booking->save(); return $this->sendResponse($booking, 'Status booking berhasil diupdate.'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengupdate status booking'], 500); } } /** * Cancel booking (for customer) */ public function cancelBooking(Booking $booking) { try { // Check if the authenticated user is the customer of this booking if ($booking->customer_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk membatalkan booking ini'], 403); } // Check if booking can be cancelled if (!$booking->canBeCancelled()) { return $this->sendError('Error validasi.', ['status' => 'Booking tidak dapat dibatalkan karena sudah diproses'], 422); } $booking->status = 'dibatalkan'; $booking->save(); return $this->sendResponse($booking, 'Booking berhasil dibatalkan.'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat membatalkan booking'], 500); } } /** * Update payment status */ public function updatePaymentStatus(Request $request, Booking $booking) { $validator = Validator::make($request->all(), [ 'payment_status' => 'required|in:paid,success' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } try { // Periksa apakah pengguna yang terautentikasi adalah penjahit dari booking ini if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk mengubah status pembayaran ini'], 403); } // Periksa apakah status booking valid untuk perubahan status pembayaran if (!in_array($booking->status, ['reservasi', 'diproses', 'selesai'])) { return $this->sendError('Invalid status', ['error' => 'Status pembayaran hanya dapat diubah untuk booking yang aktif'], 422); } // Tentukan status pembayaran berdasarkan metode pembayaran if ($booking->payment_method === 'COD') { $booking->payment_status = 'success'; } else { $booking->payment_status = 'paid'; } $booking->save(); // Memberikan informasi lebih detail pada respons return $this->sendResponse([ 'booking' => [ 'id' => $booking->id, 'customer_name' => $booking->customer->name, 'payment_status' => $booking->payment_status, 'payment_method' => $booking->payment_method, 'status' => $booking->status, 'total_price' => $booking->total_price, 'appointment_date' => $booking->appointment_date ] ], 'Status pembayaran berhasil diubah menjadi ' . $booking->payment_status . ' oleh penjahit.'); } catch (\Exception $e) { \Log::error('Error in updatePaymentStatus: ' . $e->getMessage(), [ 'booking_id' => $booking->id, 'tailor_id' => Auth::id(), 'exception' => $e ]); return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengupdate status pembayaran'], 500); } } /** * Tailor accepts booking and changes status to "diproses" */ public function acceptBooking(Booking $booking) { try { // Check if user is the tailor if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk menerima pesanan ini'], 403); } // Debug: Log current booking status \Log::info('Current booking status: ' . $booking->status); // Check if booking can be processed if (!in_array($booking->status, ['pending', 'reservasi'])) { return $this->sendError('Invalid status', ['error' => 'Pesanan tidak dapat diproses. Status saat ini: ' . $booking->status], 422); } // Update booking status to diproses $booking->status = 'diproses'; $booking->accepted_at = now(); $booking->save(); // Load customer relationship for response with correct column name $booking->load(['customer:id,name,email,phone_number']); return $this->sendResponse([ 'booking' => [ 'id' => $booking->id, 'customer_name' => $booking->customer->name, 'customer_phone' => $booking->customer->phone_number, 'service_type' => $booking->service_type, 'category' => $booking->category, 'appointment_date' => Carbon::parse($booking->appointment_date)->format('Y-m-d'), 'appointment_time' => $booking->appointment_time, 'design_photo' => $booking->design_photo, 'notes' => $booking->notes, 'status' => $booking->status, 'accepted_at' => Carbon::parse($booking->accepted_at)->format('Y-m-d H:i:s') ] ], 'Pesanan berhasil diterima dan diproses'); } catch (\Exception $e) { \Log::error('Error in acceptBooking: ' . $e->getMessage()); return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat memproses pesanan'], 500); } } /** * Reject booking by tailor */ public function rejectBooking(Request $request, Booking $booking) { try { // Validate request $validator = Validator::make($request->all(), [ 'rejection_reason' => 'required|string|max:255' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } // Check if the authenticated user is the tailor assigned to this booking if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk menolak booking ini'], 403); } // Check if booking is in reservasi status if ($booking->status !== 'reservasi') { return $this->sendError('Error validasi.', ['error' => 'Booking ini tidak dapat ditolak karena status bukan reservasi'], 422); } $booking->update([ 'status' => 'dibatalkan', 'rejection_reason' => $request->rejection_reason, 'rejected_at' => now() ]); // Load the customer relationship $booking->load('customer'); // TODO: Send notification to customer that booking has been rejected return $this->sendResponse($booking, 'Booking telah ditolak'); } catch (\Exception $e) { // Log error detail untuk keperluan debugging \Log::error('Error in rejectBooking: ' . $e->getMessage(), [ 'booking_id' => $booking->id, 'exception' => $e ]); return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat menolak booking'], 500); } } /** * Get booking details */ public function show(Booking $booking) { try { // Check if user is authorized to view this booking if ($booking->customer_id !== Auth::id() && $booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses ke pesanan ini'], 403); } // Load relationships $booking->load(['customer:id,name,phone_number', 'tailor:id,name,profile_photo']); $response = [ 'id' => $booking->id, 'transaction_code' => $booking->transaction_code, 'customer' => [ 'name' => $booking->customer->name, 'phone_number' => $booking->customer->phone_number ], 'tailor' => [ 'name' => $booking->tailor->name, 'profile_photo' => $booking->tailor->profile_photo ], 'appointment_date' => Carbon::parse($booking->appointment_date)->format('Y-m-d'), 'appointment_time' => $booking->appointment_time, 'service_type' => $booking->service_type, 'category' => $booking->category, 'status' => $booking->status, 'total_price' => $booking->total_price, 'completion_date' => $booking->completion_date ? Carbon::parse($booking->completion_date)->format('Y-m-d') : null, 'payment_status' => $booking->payment_status, 'payment_method' => $booking->payment_method, 'notes' => $booking->notes, 'measurements' => $booking->measurements, 'repair_details' => $booking->repair_details, 'repair_photo' => $booking->repair_photo, 'repair_notes' => $booking->repair_notes, 'completion_photo' => $booking->completion_photo, 'completion_notes' => $booking->completion_notes, 'pickup_date' => $booking->pickup_date ? Carbon::parse($booking->pickup_date)->format('Y-m-d') : null ]; return $this->sendResponse($response, 'Detail pesanan berhasil diambil'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengambil detail pesanan'], 500); } } /** * Update measurements for the booking */ public function updateMeasurements(Request $request, Booking $booking) { try { // Check if the authenticated user is the tailor assigned to this booking if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk mengupdate ukuran pesanan ini'], 403); } // Check if booking is in correct status if ($booking->status !== 'diproses') { return $this->sendError('Error validasi.', ['error' => 'Ukuran hanya dapat diinput setelah booking diterima dan dalam status diproses'], 422); } // Validate measurements based on category $validator = Validator::make($request->all(), [ 'measurements' => 'required|array', 'measurements.lingkar_pinggang' => 'required_if:category,Bawahan,Terusan|numeric', 'measurements.lingkar_pinggul' => 'required_if:category,Bawahan,Terusan|numeric', 'measurements.lingkar_paha' => 'required_if:category,Bawahan|numeric', 'measurements.lingkar_lutut' => 'required_if:category,Bawahan|numeric', 'measurements.panjang_celana' => 'required_if:category,Bawahan|numeric', 'measurements.lingkar_badan' => 'required_if:category,Atasan,Terusan|numeric', 'measurements.lingkar_dada' => 'required_if:category,Atasan,Terusan|numeric', 'measurements.panjang_lengan' => 'required_if:category,Atasan,Terusan|numeric', 'measurements.lingkar_lengan' => 'required_if:category,Atasan,Terusan|numeric', 'measurements.lingkar_pergelangan' => 'required_if:category,Atasan,Terusan|numeric', 'measurements.panjang_baju' => 'required_if:category,Atasan,Terusan|numeric', 'measurements.lebar_bahu' => 'required_if:category,Atasan,Terusan|numeric', 'measurements.panjang_rok' => 'required_if:category,Terusan|numeric', 'measurements.lingkar_pinggang_dress' => 'required_if:category,Terusan|numeric', 'measurements.lingkar_ketiak' => 'required_if:category,Terusan|numeric', 'measurements.panjang_dress' => 'required_if:category,Terusan|numeric' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } // Update measurements $booking->measurements = $request->measurements; $booking->save(); return $this->sendResponse($booking, 'Ukuran pesanan berhasil diupdate'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengupdate ukuran pesanan'], 500); } } /** * Complete booking and change status to "selesai" */ public function completeBooking(Request $request, Booking $booking) { try { // Check if user is the tailor if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk menyelesaikan pesanan ini'], 403); } // Check if booking status is diproses if ($booking->status !== 'diproses') { return $this->sendError('Invalid status', ['error' => 'Hanya pesanan dengan status diproses yang dapat diselesaikan'], 422); } // Validate request $validator = Validator::make($request->all(), [ 'completion_photo' => 'required|image|mimes:jpeg,jpg,png|max:2048', 'completion_notes' => 'nullable|string|max:500', 'total_price' => 'nullable|numeric|min:0' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } // Upload completion photo if ($request->hasFile('completion_photo')) { $photo = $request->file('completion_photo'); // Buat direktori jika belum ada $storagePath = storage_path('app/public/completion_photos'); if (!file_exists($storagePath)) { mkdir($storagePath, 0755, true); } // Simpan file dengan nama yang unik $filename = 'completion_' . time() . '_' . $booking->id . '.' . $photo->getClientOriginalExtension(); $photo->move($storagePath, $filename); // Simpan path relatif ke database untuk akses via URL /storage/completion_photos/... $booking->completion_photo = 'completion_photos/' . $filename; } // Update price if provided if ($request->has('total_price')) { $booking->total_price = $request->total_price; } // Update booking $booking->status = 'selesai'; $booking->completion_notes = $request->completion_notes; $booking->completed_at = now(); $booking->save(); // Load relationships $booking->load(['customer:id,name,phone_number', 'tailor:id,name']); // Tambahkan URL lengkap untuk akses gambar $completionPhotoUrl = $booking->completion_photo ? url('storage/' . $booking->completion_photo) : null; return $this->sendResponse([ 'id' => $booking->id, 'status' => $booking->status, 'completion_photo' => $booking->completion_photo, 'completion_photo_url' => $completionPhotoUrl, 'completion_notes' => $booking->completion_notes, 'total_price' => $booking->total_price, 'completed_at' => Carbon::parse($booking->completed_at)->format('Y-m-d H:i:s'), 'customer' => [ 'name' => $booking->customer->name, 'phone_number' => $booking->customer->phone_number ], 'tailor' => [ 'name' => $booking->tailor->name ] ], 'Pesanan berhasil diselesaikan'); } catch (\Exception $e) { \Log::error('Error in completeBooking: ' . $e->getMessage()); return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat menyelesaikan pesanan'], 500); } } /** * Update repair details for the booking */ public function updateRepairDetails(Request $request, Booking $booking) { try { // Check if the authenticated user is the tailor assigned to this booking if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk mengupdate detail perbaikan'], 403); } // Validate repair details $validator = Validator::make($request->all(), [ 'repair_details' => 'required|string|max:1000', 'repair_photo' => 'nullable|image|max:2048', // max 2MB 'repair_notes' => 'nullable|string|max:1000' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } // Handle repair photo upload if ($request->hasFile('repair_photo')) { $path = $request->file('repair_photo')->store('repair_photos', 'public'); $booking->repair_photo = $path; } // Update repair details $booking->repair_details = $request->repair_details; $booking->repair_notes = $request->repair_notes; $booking->save(); return $this->sendResponse($booking, 'Detail perbaikan berhasil diupdate'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengupdate detail perbaikan'], 500); } } /** * Get tailor's bookings by status */ public function tailorBookingsByStatus($status) { try { // Validate status if (!in_array($status, ['pending', 'diterima', 'diproses', 'selesai', 'dibatalkan', 'reservasi'])) { return $this->sendError('Error validasi.', ['status' => 'Status tidak valid'], 422); } $bookings = Booking::where('tailor_id', Auth::id()) ->where('status', $status) ->with(['customer']) ->orderBy('created_at', 'desc') ->get(); return $this->sendResponse($bookings, 'Data booking ' . $status . ' berhasil diambil.'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengambil data booking'], 500); } } /** * Simplified booking creation from tailor detail page */ public function bookTailor(Request $request, $tailorId) { try { // Set timezone to Asia/Jakarta date_default_timezone_set('Asia/Jakarta'); // Get today's date in Asia/Jakarta timezone $today = Carbon::now('Asia/Jakarta')->startOfDay(); // Validate request $validator = Validator::make($request->all(), [ 'appointment_date' => ['required', 'date', function ($attribute, $value, $fail) use ($today) { $appointmentDate = Carbon::parse($value)->startOfDay(); if ($appointmentDate->lt($today)) { $fail('Tanggal booking harus hari ini atau setelah hari ini.'); } }], 'appointment_time' => 'required|date_format:H:i', 'service_type' => 'required|in:Perbaikan,Jahit Baru', 'category' => 'required|in:Atasan,Bawahan,Terusan', 'design_photo' => 'required|image|mimes:jpeg,png,jpg|max:2048', 'notes' => 'nullable|string', 'payment_method' => 'required|string|max:50' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } // Check if tailor exists and is a valid tailor $tailor = User::where('id', $tailorId) ->where('role', 'penjahit') ->first(); if (!$tailor) { return $this->sendError('Not found.', ['error' => 'Penjahit tidak ditemukan'], 404); } // Convert appointment time to Carbon instance $requestedTime = Carbon::parse($request->appointment_time); $requestedDate = Carbon::parse($request->appointment_date); // Calculate time slot (2 hours) $slotStart = $requestedTime->copy(); $slotEnd = $requestedTime->copy()->addHours(2); // Check if there is any booking that overlaps with this time slot $existingBooking = Booking::where('tailor_id', $tailorId) ->where('appointment_date', $request->appointment_date) ->where('status', '!=', 'dibatalkan') ->where(function($query) use ($slotStart, $slotEnd) { $query->where(function($q) use ($slotStart, $slotEnd) { // Check if existing booking's time slot overlaps with requested slot $q->where(function($q) use ($slotStart, $slotEnd) { $q->whereRaw('TIME(appointment_time) BETWEEN ? AND ?', [ $slotStart->format('H:i:s'), $slotEnd->format('H:i:s') ]); }) ->orWhere(function($q) use ($slotStart, $slotEnd) { $q->whereRaw('TIME(DATE_ADD(appointment_time, INTERVAL 2 HOUR)) BETWEEN ? AND ?', [ $slotStart->format('H:i:s'), $slotEnd->format('H:i:s') ]); }); }); }) ->first(); if ($existingBooking) { return $this->sendError('Error validasi.', [ 'appointment' => 'Jadwal tidak tersedia. Penjahit sudah memiliki booking pada waktu tersebut (durasi 2 jam)' ], 422); } // Create booking $booking = new Booking(); $booking->tailor_id = $tailorId; $booking->customer_id = Auth::id(); $booking->appointment_date = $request->appointment_date; $booking->appointment_time = $request->appointment_time; $booking->service_type = $request->service_type; $booking->category = $request->category; $booking->notes = $request->notes; $booking->status = 'reservasi'; $booking->payment_status = 'unpaid'; $booking->payment_method = $request->payment_method; // Generate transaction code $booking->transaction_code = $this->generateTransactionCode(); // Handle design photo if provided if ($request->hasFile('design_photo')) { $path = $request->file('design_photo')->store('design_photos', 'public'); $booking->design_photo = $path; } $booking->save(); // Load relationships $booking->load(['customer', 'tailor']); return $this->sendResponse([ 'booking' => $booking, 'message' => 'Booking berhasil dibuat', 'next_steps' => [ 'payment' => 'Silakan lakukan pembayaran untuk mengkonfirmasi booking', 'cancellation' => 'Anda dapat membatalkan booking sebelum melakukan pembayaran' ] ], 'Booking berhasil dibuat'); } catch (\Exception $e) { \Log::error('Error creating booking: ' . $e->getMessage()); return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat membuat booking: ' . $e->getMessage()], 500); } } /** * Get customer bookings by status */ public function customerBookingsByStatus($status) { try { $allowedStatuses = ['reservasi', 'dikonfirmasi', 'diproses', 'selesai', 'dibatalkan']; if (!in_array($status, $allowedStatuses)) { return $this->sendError('Invalid status', ['error' => 'Status tidak valid'], 422); } $bookings = Booking::where('customer_id', Auth::id()) ->where('status', $status) ->with(['tailor:id,name,profile_photo', 'ratings']) ->orderBy('created_at', 'desc') ->get() ->map(function ($booking) use ($status) { $data = [ 'id' => $booking->id, 'transaction_code' => $booking->transaction_code, 'tailor_name' => $booking->tailor->name, 'tailor_photo' => $booking->tailor->profile_photo, 'payment_method' => $booking->payment_method, 'payment_status' => $booking->payment_status ]; if ($status === 'selesai') { $data = array_merge($data, [ 'service_type' => $booking->service_type, 'category' => $booking->category, 'appointment_date' => Carbon::parse($booking->appointment_date)->format('Y-m-d'), 'completed_at' => $booking->completed_at ? Carbon::parse($booking->completed_at)->format('Y-m-d') : null, 'total_price' => $booking->total_price ?? 0, 'has_rating' => $booking->ratings->count() > 0, 'rating_id' => $booking->ratings->first() ? $booking->ratings->first()->id : null ]); } else if ($status === 'diproses') { $data = array_merge($data, [ 'service_type' => $booking->service_type, 'appointment_date' => Carbon::parse($booking->appointment_date)->format('Y-m-d'), 'completed_at' => $booking->completed_at ? Carbon::parse($booking->completed_at)->format('Y-m-d') : null, 'total_price' => $booking->total_price ?? 0, 'design_photo' => $booking->design_photo ? '/storage/' . $booking->design_photo : null, 'repair_photo' => $booking->repair_photo ? '/storage/' . $booking->repair_photo : null, 'repair_details' => $booking->repair_details, 'repair_notes' => $booking->repair_notes, 'category' => $booking->category ]); } else if (in_array($status, ['reservasi', 'dikonfirmasi'])) { $data = array_merge($data, [ 'service_type' => $booking->service_type, 'category' => $booking->category, 'appointment_date' => Carbon::parse($booking->appointment_date)->format('Y-m-d'), 'appointment_time' => $booking->appointment_time, 'design_photo' => $booking->design_photo ? '/storage/' . $booking->design_photo : null, 'notes' => $booking->notes, 'status' => $status === 'reservasi' ? 'Menunggu Konfirmasi' : 'Dikonfirmasi', 'status_code' => $status ]); } return $data; }); return $this->sendResponse([ 'bookings' => $bookings, 'total' => $bookings->count() ], 'Daftar pesanan berhasil diambil'); } catch (\Exception $e) { \Log::error('Error getting bookings: ' . $e->getMessage()); return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengambil data pesanan'], 500); } } /** * Get completed booking details with measurements */ public function getCompletedBookingDetails(Booking $booking) { try { // Check if user is authorized to view this booking if ($booking->customer_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses ke pesanan ini'], 403); } // Check if booking is completed if ($booking->status !== 'selesai') { return $this->sendError('Invalid status', ['error' => 'Pesanan belum selesai'], 422); } // Load relationships and measurements $booking->load(['tailor:id,name,profile_photo']); $response = [ 'id' => $booking->id, 'transaction_code' => $booking->transaction_code, 'tailor_name' => $booking->tailor->name, 'tailor_photo' => $booking->tailor->profile_photo, 'service_type' => $booking->service_type, 'category' => $booking->category, 'appointment_date' => Carbon::parse($booking->appointment_date)->format('Y-m-d'), 'completed_at' => $booking->completed_at ? Carbon::parse($booking->completed_at)->format('Y-m-d') : null, 'total_price' => $booking->total_price ?? 0, 'payment_status' => $booking->payment_status, 'payment_method' => $booking->payment_method, 'measurements' => $booking->measurements ?? [], 'completion_photo' => $booking->completion_photo, 'completion_notes' => $booking->completion_notes, 'pickup_date' => $booking->pickup_date ]; return $this->sendResponse($response, 'Detail pesanan selesai berhasil diambil'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengambil detail pesanan'], 500); } } /** * Process final payment and set pickup date for completed booking */ public function processCompletionPayment(Request $request, Booking $booking) { try { // Add debugging \Log::info('Attempting completion payment', [ 'booking_id' => $booking->id, 'status' => $booking->status, 'payment_status' => $booking->payment_status, 'tailor_id' => $booking->tailor_id, 'auth_id' => Auth::id() ]); // Check if user is authorized (penjahit) if ($booking->tailor_id !== Auth::id()) { \Log::warning('Unauthorized payment attempt', [ 'booking_id' => $booking->id, 'tailor_id' => $booking->tailor_id, 'auth_id' => Auth::id() ]); return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk menandai pembayaran selesai'], 403); } // Validate booking status if ($booking->status !== 'selesai') { \Log::warning('Invalid booking status for payment', [ 'booking_id' => $booking->id, 'status' => $booking->status ]); return $this->sendError('Invalid status', ['error' => 'Pembayaran hanya dapat dilakukan untuk pesanan yang sudah selesai'], 422); } // Check if already paid if ($booking->payment_status === 'paid' || $booking->payment_status === 'success') { \Log::warning('Booking already paid', ['booking_id' => $booking->id]); return $this->sendError('Already paid', ['error' => 'Pesanan ini sudah dibayar'], 422); } // Validate request $validator = Validator::make($request->all(), [ 'pickup_date' => 'required|date|after_or_equal:today' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } // Update booking with payment and pickup date if ($booking->payment_method === 'COD') { $booking->payment_status = 'success'; } else { $booking->payment_status = 'paid'; } $booking->pickup_date = $request->pickup_date; $booking->save(); // Load customer for response $booking->load('customer:id,name,phone_number'); return $this->sendResponse([ 'booking' => [ 'id' => $booking->id, 'customer_name' => $booking->customer->name, 'customer_phone' => $booking->customer->phone_number, 'payment_status' => $booking->payment_status, 'payment_method' => $booking->payment_method, 'status' => $booking->status, 'pickup_date' => Carbon::parse($booking->pickup_date)->format('Y-m-d') ], 'message' => 'Pembayaran telah dikonfirmasi oleh penjahit', 'pickup_info' => [ 'date' => Carbon::parse($booking->pickup_date)->format('Y-m-d'), 'status' => 'Sudah dibayar dengan status ' . $booking->payment_status ] ], 'Pembayaran telah dikonfirmasi dan tanggal pengambilan berhasil diatur'); } catch (\Exception $e) { \Log::error('Error processing completion payment: ' . $e->getMessage(), [ 'booking_id' => $booking->id, 'exception' => $e ]); return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat memproses pembayaran'], 500); } } /** * Get all bookings (Admin only) * * @param Request $request * @return JsonResponse */ public function getAllBookings(Request $request): JsonResponse { $status = $request->query('status'); $perPage = $request->query('per_page', 10); $query = Booking::with(['customer', 'tailor']) ->orderBy('created_at', 'desc'); if ($status) { $query->where('status', $status); } $bookings = $query->paginate($perPage); return response()->json([ 'status' => 'success', 'message' => 'Bookings retrieved successfully', 'data' => $bookings ]); } /** * Update booking price and completion date * * @param Request $request * @param Booking $booking * @return JsonResponse */ public function updatePrice(Request $request, Booking $booking): JsonResponse { try { // Check if the authenticated user is the tailor of this booking if ($booking->tailor_id !== Auth::id()) { return $this->sendError('Unauthorized.', ['error' => 'Anda tidak memiliki akses untuk mengubah harga booking ini'], 403); } // Validate request $validator = Validator::make($request->all(), [ 'total_price' => 'required|numeric|min:0', 'completion_date' => 'required|date|after_or_equal:today' ]); if ($validator->fails()) { return $this->sendError('Error validasi.', $validator->errors(), 422); } // Update price and completion date $booking->total_price = $request->total_price; $booking->completion_date = $request->completion_date; $booking->save(); return $this->sendResponse([ 'id' => $booking->id, 'total_price' => $booking->total_price, 'completion_date' => Carbon::parse($booking->completion_date)->format('Y-m-d'), 'status' => $booking->status, 'customer' => [ 'name' => $booking->customer->name, 'phone_number' => $booking->customer->phone_number ] ], 'Harga dan tanggal selesai pesanan berhasil diupdate'); } catch (\Exception $e) { return $this->sendError('Error.', ['error' => 'Terjadi kesalahan saat mengupdate harga dan tanggal selesai pesanan'], 500); } } /** * Generate unique transaction code * * @return string */ private function generateTransactionCode() { // Format: TRX-{YYYYMMDD}-{6 random characters} $date = Carbon::now()->format('Ymd'); $random = strtoupper(substr(md5(uniqid()), 0, 6)); $code = "TRX-{$date}-{$random}"; // Check if code already exists, if so, regenerate while (Booking::where('transaction_code', $code)->exists()) { $random = strtoupper(substr(md5(uniqid()), 0, 6)); $code = "TRX-{$date}-{$random}"; } return $code; } }