230 lines
9.2 KiB
PHP
230 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Appointments;
|
|
|
|
use Livewire\Component;
|
|
use App\Models\Appointment;
|
|
use App\Models\Customer;
|
|
use App\Models\Vehicle;
|
|
use App\Models\Technician;
|
|
use Carbon\Carbon;
|
|
|
|
class Create extends Component
|
|
{
|
|
// Form fields
|
|
public $customer_id = '';
|
|
public $vehicle_id = '';
|
|
public $assigned_technician_id = '';
|
|
public $scheduled_date = '';
|
|
public $scheduled_time = '';
|
|
public $estimated_duration_minutes = 60;
|
|
public $appointment_type = 'maintenance';
|
|
public $service_requested = '';
|
|
public $customer_notes = '';
|
|
public $internal_notes = '';
|
|
|
|
// Options
|
|
public $customers = [];
|
|
public $vehicles = [];
|
|
public $technicians = [];
|
|
public $availableTimeSlots = [];
|
|
|
|
public $appointmentTypes = [
|
|
'maintenance' => 'Maintenance',
|
|
'repair' => 'Repair',
|
|
'inspection' => 'Inspection',
|
|
'estimate' => 'Estimate',
|
|
'pickup' => 'Pickup',
|
|
'delivery' => 'Delivery'
|
|
];
|
|
|
|
public $durationOptions = [
|
|
30 => '30 minutes',
|
|
60 => '1 hour',
|
|
90 => '1.5 hours',
|
|
120 => '2 hours',
|
|
150 => '2.5 hours',
|
|
180 => '3 hours',
|
|
240 => '4 hours',
|
|
];
|
|
|
|
protected $rules = [
|
|
'customer_id' => 'required|exists:customers,id',
|
|
'vehicle_id' => 'required|exists:vehicles,id',
|
|
'scheduled_date' => 'required|date|after_or_equal:today',
|
|
'scheduled_time' => 'required',
|
|
'estimated_duration_minutes' => 'required|integer|min:15|max:480',
|
|
'appointment_type' => 'required|in:maintenance,repair,inspection,estimate,pickup,delivery',
|
|
'service_requested' => 'required|string|max:500',
|
|
'customer_notes' => 'nullable|string|max:1000',
|
|
'internal_notes' => 'nullable|string|max:1000',
|
|
];
|
|
|
|
protected $messages = [
|
|
'customer_id.required' => 'Please select a customer.',
|
|
'vehicle_id.required' => 'Please select a vehicle.',
|
|
'scheduled_date.required' => 'Please select an appointment date.',
|
|
'scheduled_date.after_or_equal' => 'Appointment date cannot be in the past.',
|
|
'scheduled_time.required' => 'Please select an appointment time.',
|
|
'service_requested.required' => 'Please describe the service requested.',
|
|
];
|
|
|
|
public function mount()
|
|
{
|
|
$this->loadInitialData();
|
|
$this->scheduled_date = Carbon::tomorrow()->format('Y-m-d');
|
|
}
|
|
|
|
public function loadInitialData()
|
|
{
|
|
$this->customers = Customer::orderBy('first_name')->orderBy('last_name')->get();
|
|
$this->technicians = Technician::where('status', 'active')->orderBy('first_name')->orderBy('last_name')->get();
|
|
}
|
|
|
|
public function updatedCustomerId()
|
|
{
|
|
if ($this->customer_id) {
|
|
$this->vehicles = Vehicle::where('customer_id', $this->customer_id)->get();
|
|
$this->vehicle_id = '';
|
|
} else {
|
|
$this->vehicles = [];
|
|
$this->vehicle_id = '';
|
|
}
|
|
}
|
|
|
|
public function updatedScheduledDate()
|
|
{
|
|
if ($this->scheduled_date) {
|
|
$this->loadAvailableTimeSlots();
|
|
}
|
|
}
|
|
|
|
public function updatedAssignedTechnicianId()
|
|
{
|
|
if ($this->scheduled_date) {
|
|
$this->loadAvailableTimeSlots();
|
|
}
|
|
}
|
|
|
|
public function loadAvailableTimeSlots()
|
|
{
|
|
$date = Carbon::parse($this->scheduled_date);
|
|
$technicianId = $this->assigned_technician_id;
|
|
|
|
// Generate time slots from 8 AM to 5 PM
|
|
$slots = [];
|
|
$startTime = $date->copy()->setTime(8, 0);
|
|
$endTime = $date->copy()->setTime(17, 0);
|
|
|
|
while ($startTime->lt($endTime)) {
|
|
$timeSlot = $startTime->format('H:i');
|
|
|
|
// Check if this time slot is available for the technician
|
|
$isAvailable = true;
|
|
if ($technicianId) {
|
|
$startDateTime = Carbon::parse($this->scheduled_date . ' ' . $timeSlot);
|
|
$endDateTime = $startDateTime->copy()->addMinutes($this->estimated_duration_minutes);
|
|
|
|
$conflictingAppointments = Appointment::where('assigned_technician_id', $technicianId)
|
|
->where(function($query) use ($startDateTime, $endDateTime) {
|
|
$query->where(function($q) use ($startDateTime, $endDateTime) {
|
|
// Check if new appointment overlaps with existing ones
|
|
$q->where(function($subQ) use ($startDateTime, $endDateTime) {
|
|
// New appointment starts during existing appointment
|
|
$subQ->where('scheduled_datetime', '<=', $startDateTime)
|
|
->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) > ?', [$startDateTime]);
|
|
})->orWhere(function($subQ) use ($startDateTime, $endDateTime) {
|
|
// New appointment ends during existing appointment
|
|
$subQ->where('scheduled_datetime', '<', $endDateTime)
|
|
->where('scheduled_datetime', '>=', $startDateTime);
|
|
});
|
|
});
|
|
})
|
|
->exists();
|
|
|
|
$isAvailable = !$conflictingAppointments;
|
|
}
|
|
|
|
if ($isAvailable) {
|
|
$slots[] = [
|
|
'value' => $timeSlot,
|
|
'label' => $startTime->format('g:i A'),
|
|
];
|
|
}
|
|
|
|
$startTime->addMinutes(30);
|
|
}
|
|
|
|
$this->availableTimeSlots = $slots;
|
|
}
|
|
|
|
public function save()
|
|
{
|
|
$this->validate();
|
|
|
|
try {
|
|
// Check for conflicts one more time
|
|
$scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time);
|
|
$endDateTime = $scheduledDateTime->copy()->addMinutes($this->estimated_duration_minutes);
|
|
|
|
if ($this->assigned_technician_id) {
|
|
$conflicts = Appointment::where('assigned_technician_id', $this->assigned_technician_id)
|
|
->where(function($query) use ($scheduledDateTime, $endDateTime) {
|
|
$query->where(function($q) use ($scheduledDateTime, $endDateTime) {
|
|
// Check if new appointment overlaps with existing ones
|
|
$q->where(function($subQ) use ($scheduledDateTime, $endDateTime) {
|
|
// New appointment starts during existing appointment
|
|
$subQ->where('scheduled_datetime', '<=', $scheduledDateTime)
|
|
->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) > ?', [$scheduledDateTime]);
|
|
})->orWhere(function($subQ) use ($scheduledDateTime, $endDateTime) {
|
|
// New appointment ends during existing appointment
|
|
$subQ->where('scheduled_datetime', '<', $endDateTime)
|
|
->where('scheduled_datetime', '>=', $scheduledDateTime);
|
|
})->orWhere(function($subQ) use ($scheduledDateTime, $endDateTime) {
|
|
// New appointment completely contains existing appointment
|
|
$subQ->where('scheduled_datetime', '>=', $scheduledDateTime)
|
|
->whereRaw('DATE_ADD(scheduled_datetime, INTERVAL estimated_duration_minutes MINUTE) <= ?', [$endDateTime]);
|
|
});
|
|
});
|
|
})
|
|
->exists();
|
|
|
|
if ($conflicts) {
|
|
$this->addError('scheduled_time', 'This time slot conflicts with another appointment for the selected technician.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Combine date and time into scheduled_datetime
|
|
$scheduledDateTime = Carbon::parse($this->scheduled_date . ' ' . $this->scheduled_time);
|
|
|
|
$appointment = Appointment::create([
|
|
'customer_id' => $this->customer_id,
|
|
'vehicle_id' => $this->vehicle_id,
|
|
'assigned_technician_id' => $this->assigned_technician_id ?: null,
|
|
'scheduled_datetime' => $scheduledDateTime,
|
|
'estimated_duration_minutes' => $this->estimated_duration_minutes,
|
|
'appointment_type' => $this->appointment_type,
|
|
'service_requested' => $this->service_requested,
|
|
'customer_notes' => $this->customer_notes,
|
|
'internal_notes' => $this->internal_notes,
|
|
'status' => 'scheduled',
|
|
'created_by' => auth()->id(),
|
|
]);
|
|
|
|
session()->flash('message', 'Appointment scheduled successfully!');
|
|
return redirect()->route('appointments.index');
|
|
|
|
} catch (\Exception $e) {
|
|
$this->addError('general', 'Error creating appointment: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.appointments.create')->layout('components.layouts.app', [
|
|
'title' => 'Schedule Appointment'
|
|
]);
|
|
}
|
|
}
|