- Added buttons for assigning diagnosis and starting diagnosis based on job card status in the job card view. - Implemented a modal for assigning technicians for diagnosis, including form validation and technician selection. - Updated routes to include a test route for job cards. - Created a new Blade view for testing inspection inputs. - Developed comprehensive feature tests for the estimate module, including creation, viewing, editing, and validation of estimates. - Added tests for estimate model relationships and statistics calculations. - Introduced a basic feature test for job cards index.
756 lines
25 KiB
PHP
756 lines
25 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Diagnosis;
|
|
|
|
use App\Models\Diagnosis;
|
|
use App\Models\Estimate;
|
|
use App\Models\EstimateLineItem;
|
|
use App\Models\JobCard;
|
|
use App\Models\Part;
|
|
use App\Models\ServiceItem;
|
|
use App\Models\Timesheet;
|
|
use Livewire\Component;
|
|
use Livewire\WithFileUploads;
|
|
|
|
class Create extends Component
|
|
{
|
|
use WithFileUploads;
|
|
|
|
public JobCard $jobCard;
|
|
|
|
public $customer_reported_issues = '';
|
|
|
|
public $diagnostic_findings = '';
|
|
|
|
public $root_cause_analysis = '';
|
|
|
|
public $recommended_repairs = '';
|
|
|
|
public $additional_issues_found = '';
|
|
|
|
public $priority_level = 'medium';
|
|
|
|
public $estimated_repair_time = '';
|
|
|
|
public $parts_required = [];
|
|
|
|
public $labor_operations = [];
|
|
|
|
public $special_tools_required = [];
|
|
|
|
public $safety_concerns = '';
|
|
|
|
public $diagnostic_codes = [];
|
|
|
|
public $test_results = [];
|
|
|
|
public $photos = [];
|
|
|
|
public $notes = '';
|
|
|
|
public $environmental_impact = '';
|
|
|
|
public $customer_authorization_required = false;
|
|
|
|
// Timesheet tracking
|
|
public $currentTimesheet = null;
|
|
|
|
public $timesheets = [];
|
|
|
|
public $selectedDiagnosisType = 'general_inspection';
|
|
|
|
public $diagnosisTypes = [
|
|
'general_inspection' => 'General Inspection',
|
|
'electrical_diagnosis' => 'Electrical Diagnosis',
|
|
'engine_diagnosis' => 'Engine Diagnosis',
|
|
'transmission_diagnosis' => 'Transmission Diagnosis',
|
|
'brake_diagnosis' => 'Brake System Diagnosis',
|
|
'suspension_diagnosis' => 'Suspension Diagnosis',
|
|
'air_conditioning' => 'Air Conditioning Diagnosis',
|
|
'computer_diagnosis' => 'Computer/ECU Diagnosis',
|
|
'emissions_diagnosis' => 'Emissions Diagnosis',
|
|
'noise_diagnosis' => 'Noise/Vibration Diagnosis',
|
|
];
|
|
|
|
// Parts integration
|
|
public $availableParts = [];
|
|
|
|
public $partSearch = '';
|
|
|
|
public $partSearchTerm = '';
|
|
|
|
public $partCategoryFilter = '';
|
|
|
|
public $filteredParts;
|
|
|
|
// Service items integration
|
|
public $availableServiceItems = [];
|
|
|
|
public $serviceItemSearch = '';
|
|
|
|
public $serviceSearchTerm = '';
|
|
|
|
public $serviceCategoryFilter = '';
|
|
|
|
public $filteredServiceItems;
|
|
|
|
// UI state
|
|
public $showPartsSection = false;
|
|
|
|
public $showLaborSection = false;
|
|
|
|
public $showDiagnosticCodesSection = false;
|
|
|
|
public $showTestResultsSection = false;
|
|
|
|
public $showAdvancedOptions = false;
|
|
|
|
public $showTimesheetSection = true;
|
|
|
|
public $createEstimateAutomatically = true;
|
|
|
|
protected $rules = [
|
|
'customer_reported_issues' => 'required|string',
|
|
'diagnostic_findings' => 'required|string|min:20',
|
|
'root_cause_analysis' => 'required|string|min:20',
|
|
'recommended_repairs' => 'required|string|min:10',
|
|
'priority_level' => 'required|in:low,medium,high,urgent',
|
|
'estimated_repair_time' => 'required|numeric|min:0.5|max:40',
|
|
'safety_concerns' => 'nullable|string',
|
|
'environmental_impact' => 'nullable|string',
|
|
'notes' => 'nullable|string',
|
|
'photos.*' => 'nullable|image|max:5120',
|
|
'selectedDiagnosisType' => 'required|string',
|
|
];
|
|
|
|
protected $messages = [
|
|
'diagnostic_findings.min' => 'Please provide detailed diagnostic findings (at least 20 characters).',
|
|
'root_cause_analysis.min' => 'Please provide a thorough root cause analysis (at least 20 characters).',
|
|
'recommended_repairs.min' => 'Please specify the recommended repairs (at least 10 characters).',
|
|
'estimated_repair_time.max' => 'Estimated repair time cannot exceed 40 hours. For longer repairs, consider breaking into phases.',
|
|
'photos.*.max' => 'Each photo must be less than 5MB.',
|
|
'selectedDiagnosisType.required' => 'Please select a diagnosis type.',
|
|
];
|
|
|
|
public function mount(JobCard $jobCard)
|
|
{
|
|
// Validate that initial inspection has been completed
|
|
if ($jobCard->status === 'received') {
|
|
session()->flash('error', 'Initial vehicle inspection must be completed before starting diagnosis. Please complete the inspection first.');
|
|
|
|
return redirect()->route('job-cards.show', $jobCard);
|
|
}
|
|
|
|
if (! $jobCard->incomingInspection) {
|
|
session()->flash('error', 'No incoming inspection record found. Please complete the initial inspection before proceeding to diagnosis.');
|
|
|
|
return redirect()->route('inspections.create', ['jobCard' => $jobCard, 'type' => 'incoming']);
|
|
}
|
|
|
|
$this->jobCard = $jobCard->load(['customer', 'vehicle', 'timesheets', 'incomingInspection']);
|
|
$this->customer_reported_issues = $jobCard->customer_reported_issues ?? '';
|
|
|
|
// Initialize arrays to prevent null issues - load from session if available
|
|
$this->parts_required = session()->get("diagnosis_parts_{$jobCard->id}", []);
|
|
$this->labor_operations = session()->get("diagnosis_labor_{$jobCard->id}", []);
|
|
$this->timesheets = [];
|
|
$this->diagnostic_codes = session()->get("diagnosis_codes_{$jobCard->id}", []);
|
|
$this->test_results = [];
|
|
$this->special_tools_required = [];
|
|
|
|
// Initialize filtered collections
|
|
$this->filteredParts = collect();
|
|
$this->filteredServiceItems = collect();
|
|
|
|
// Load existing timesheets for this job card related to diagnosis
|
|
$this->loadTimesheets();
|
|
|
|
// Load available parts and service items
|
|
$this->loadAvailableParts();
|
|
$this->loadAvailableServiceItems();
|
|
|
|
// Initialize with one empty part and labor operation for convenience if none exist
|
|
if (empty($this->parts_required)) {
|
|
$this->addPart();
|
|
}
|
|
if (empty($this->labor_operations)) {
|
|
$this->addLaborOperation();
|
|
}
|
|
}
|
|
|
|
public function updatedPartsRequired()
|
|
{
|
|
// Save parts to session whenever they change
|
|
session()->put("diagnosis_parts_{$this->jobCard->id}", $this->parts_required);
|
|
}
|
|
|
|
public function updatedLaborOperations()
|
|
{
|
|
// Save labor operations to session whenever they change
|
|
session()->put("diagnosis_labor_{$this->jobCard->id}", $this->labor_operations);
|
|
}
|
|
|
|
public function updatedDiagnosticCodes()
|
|
{
|
|
// Save diagnostic codes to session whenever they change
|
|
session()->put("diagnosis_codes_{$this->jobCard->id}", $this->diagnostic_codes);
|
|
}
|
|
|
|
public function loadTimesheets()
|
|
{
|
|
$this->timesheets = Timesheet::where('job_card_id', $this->jobCard->id)
|
|
->where('entry_type', 'manual')
|
|
->where('description', 'like', '%diagnosis%')
|
|
->with('user')
|
|
->orderBy('created_at', 'desc')
|
|
->get()
|
|
->toArray();
|
|
}
|
|
|
|
public function loadAvailableParts()
|
|
{
|
|
$query = Part::where('status', 'active');
|
|
|
|
if (! empty($this->partSearch)) {
|
|
$query->where(function ($q) {
|
|
$q->where('name', 'like', '%'.$this->partSearch.'%')
|
|
->orWhere('part_number', 'like', '%'.$this->partSearch.'%')
|
|
->orWhere('description', 'like', '%'.$this->partSearch.'%');
|
|
});
|
|
}
|
|
|
|
$this->availableParts = $query->limit(20)->get()->toArray();
|
|
}
|
|
|
|
public function loadAvailableServiceItems()
|
|
{
|
|
$query = ServiceItem::query();
|
|
|
|
if (! empty($this->serviceItemSearch)) {
|
|
$query->where(function ($q) {
|
|
$q->where('service_name', 'like', '%'.$this->serviceItemSearch.'%')
|
|
->orWhere('description', 'like', '%'.$this->serviceItemSearch.'%')
|
|
->orWhere('category', 'like', '%'.$this->serviceItemSearch.'%');
|
|
});
|
|
}
|
|
|
|
$this->availableServiceItems = $query->limit(20)->get()->toArray();
|
|
}
|
|
|
|
// Computed properties for filtered collections
|
|
public function updatedPartSearchTerm()
|
|
{
|
|
$this->updateFilteredParts();
|
|
}
|
|
|
|
public function updatedPartCategoryFilter()
|
|
{
|
|
$this->updateFilteredParts();
|
|
}
|
|
|
|
public function updateFilteredParts()
|
|
{
|
|
try {
|
|
// If no search criteria provided, return empty collection
|
|
if (empty($this->partSearchTerm) && empty($this->partCategoryFilter)) {
|
|
$this->filteredParts = collect();
|
|
|
|
return;
|
|
}
|
|
|
|
// Start with active parts only
|
|
$query = Part::where('status', 'active');
|
|
|
|
// Add search term filter if provided
|
|
if (! empty($this->partSearchTerm)) {
|
|
$searchTerm = trim($this->partSearchTerm);
|
|
$query->where(function ($q) use ($searchTerm) {
|
|
$q->where('name', 'like', '%'.$searchTerm.'%')
|
|
->orWhere('part_number', 'like', '%'.$searchTerm.'%')
|
|
->orWhere('description', 'like', '%'.$searchTerm.'%')
|
|
->orWhere('manufacturer', 'like', '%'.$searchTerm.'%');
|
|
});
|
|
}
|
|
|
|
// Add category filter if provided
|
|
if (! empty($this->partCategoryFilter)) {
|
|
$query->where('category', $this->partCategoryFilter);
|
|
}
|
|
|
|
// Order by name for consistent results
|
|
$query->orderBy('name');
|
|
|
|
// Get results and assign to property
|
|
$this->filteredParts = $query->limit(20)->get();
|
|
|
|
// Log for debugging
|
|
\Log::info('Parts search executed', [
|
|
'search_term' => $this->partSearchTerm,
|
|
'category_filter' => $this->partCategoryFilter,
|
|
'results_count' => $this->filteredParts->count(),
|
|
'results' => $this->filteredParts->pluck('name')->toArray(),
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
// Log error and return empty collection
|
|
\Log::error('Error in updateFilteredParts', [
|
|
'error' => $e->getMessage(),
|
|
'search_term' => $this->partSearchTerm,
|
|
'category_filter' => $this->partCategoryFilter,
|
|
]);
|
|
|
|
$this->filteredParts = collect();
|
|
}
|
|
}
|
|
|
|
public function getFilteredServiceItemsProperty()
|
|
{
|
|
$query = ServiceItem::query();
|
|
|
|
if (! empty($this->serviceSearchTerm)) {
|
|
$query->where(function ($q) {
|
|
$q->where('name', 'like', '%'.$this->serviceSearchTerm.'%')
|
|
->orWhere('description', 'like', '%'.$this->serviceSearchTerm.'%');
|
|
});
|
|
}
|
|
|
|
if (! empty($this->serviceCategoryFilter)) {
|
|
$query->where('category', $this->serviceCategoryFilter);
|
|
}
|
|
|
|
return $query->limit(20)->get();
|
|
}
|
|
|
|
public function updatedPartSearch()
|
|
{
|
|
$this->loadAvailableParts();
|
|
}
|
|
|
|
public function updatedServiceSearchTerm()
|
|
{
|
|
$this->updateFilteredServiceItems();
|
|
}
|
|
|
|
public function updatedServiceCategoryFilter()
|
|
{
|
|
$this->updateFilteredServiceItems();
|
|
}
|
|
|
|
public function updateFilteredServiceItems()
|
|
{
|
|
try {
|
|
// If no search criteria provided, return empty collection
|
|
if (empty($this->serviceSearchTerm) && empty($this->serviceCategoryFilter)) {
|
|
$this->filteredServiceItems = collect();
|
|
|
|
return;
|
|
}
|
|
|
|
// Start with active service items only
|
|
$query = ServiceItem::where('status', 'active');
|
|
|
|
// Add search term filter if provided
|
|
if (! empty($this->serviceSearchTerm)) {
|
|
$searchTerm = trim($this->serviceSearchTerm);
|
|
$query->where(function ($q) use ($searchTerm) {
|
|
$q->where('service_name', 'like', '%'.$searchTerm.'%')
|
|
->orWhere('description', 'like', '%'.$searchTerm.'%');
|
|
});
|
|
}
|
|
|
|
// Add category filter if provided
|
|
if (! empty($this->serviceCategoryFilter)) {
|
|
$query->where('category', $this->serviceCategoryFilter);
|
|
}
|
|
|
|
// Order by name for consistent results
|
|
$query->orderBy('service_name');
|
|
|
|
// Get results
|
|
$results = $query->limit(20)->get();
|
|
|
|
$this->filteredServiceItems = $results;
|
|
} catch (\Exception $e) {
|
|
// Log error and return empty collection
|
|
\Log::error('Error in updateFilteredServiceItems', [
|
|
'error' => $e->getMessage(),
|
|
'search_term' => $this->serviceSearchTerm,
|
|
'category_filter' => $this->serviceCategoryFilter,
|
|
]);
|
|
|
|
$this->filteredServiceItems = collect();
|
|
}
|
|
}
|
|
|
|
public function startTimesheet()
|
|
{
|
|
// End any currently running timesheet
|
|
if ($this->currentTimesheet) {
|
|
$this->endTimesheet();
|
|
}
|
|
|
|
$this->currentTimesheet = Timesheet::create([
|
|
'job_card_id' => $this->jobCard->id,
|
|
'user_id' => auth()->id(),
|
|
'entry_type' => 'manual',
|
|
'description' => 'Diagnosis: '.($this->diagnosisTypes[$this->selectedDiagnosisType] ?? 'General Diagnosis'),
|
|
'date' => now()->toDateString(),
|
|
'start_time' => now(),
|
|
'hourly_rate' => auth()->user()->hourly_rate ?? 85.00,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
$this->loadTimesheets();
|
|
session()->flash('timesheet_message', 'Timesheet started for '.($this->diagnosisTypes[$this->selectedDiagnosisType] ?? 'Diagnosis'));
|
|
}
|
|
|
|
public function endTimesheet()
|
|
{
|
|
if (! $this->currentTimesheet) {
|
|
return;
|
|
}
|
|
|
|
$timesheet = Timesheet::find($this->currentTimesheet['id']);
|
|
if ($timesheet && ! $timesheet->end_time) {
|
|
$endTime = now();
|
|
$totalMinutes = $timesheet->start_time->diffInMinutes($endTime);
|
|
$billableHours = round($totalMinutes / 60, 2);
|
|
|
|
$timesheet->update([
|
|
'end_time' => $endTime,
|
|
'hours_worked' => $billableHours,
|
|
'billable_hours' => $billableHours,
|
|
'total_amount' => $billableHours * $timesheet->hourly_rate,
|
|
'status' => 'submitted',
|
|
]);
|
|
|
|
session()->flash('timesheet_message', 'Timesheet ended. Total time: '.$billableHours.' hours');
|
|
}
|
|
|
|
$this->currentTimesheet = null;
|
|
$this->loadTimesheets();
|
|
}
|
|
|
|
public function addPartFromCatalog($partId)
|
|
{
|
|
$part = Part::find($partId);
|
|
if ($part) {
|
|
$this->parts_required[] = [
|
|
'part_id' => $part->id,
|
|
'part_name' => $part->name,
|
|
'part_number' => $part->part_number,
|
|
'quantity' => 1,
|
|
'estimated_cost' => $part->sell_price,
|
|
'availability' => $part->quantity_on_hand > 0 ? 'in_stock' : 'out_of_stock',
|
|
];
|
|
$this->updatedPartsRequired(); // Save to session
|
|
}
|
|
}
|
|
|
|
public function addServiceItemFromCatalog($serviceItemId)
|
|
{
|
|
$serviceItem = ServiceItem::find($serviceItemId);
|
|
if ($serviceItem) {
|
|
$this->labor_operations[] = [
|
|
'service_item_id' => $serviceItem->id,
|
|
'operation' => $serviceItem->service_name,
|
|
'description' => $serviceItem->description,
|
|
'estimated_hours' => $serviceItem->estimated_hours,
|
|
'labor_rate' => $serviceItem->labor_rate,
|
|
'category' => $serviceItem->category,
|
|
'complexity' => 'medium',
|
|
];
|
|
$this->updatedLaborOperations(); // Save to session
|
|
}
|
|
}
|
|
|
|
public function addPart()
|
|
{
|
|
$this->parts_required[] = [
|
|
'part_id' => null,
|
|
'part_name' => '',
|
|
'part_number' => '',
|
|
'quantity' => 1,
|
|
'estimated_cost' => 0,
|
|
'availability' => 'in_stock',
|
|
];
|
|
$this->updatedPartsRequired(); // Save to session
|
|
}
|
|
|
|
public function removePart($index)
|
|
{
|
|
unset($this->parts_required[$index]);
|
|
$this->parts_required = array_values($this->parts_required);
|
|
$this->updatedPartsRequired(); // Save to session
|
|
}
|
|
|
|
public function addLaborOperation()
|
|
{
|
|
$this->labor_operations[] = [
|
|
'service_item_id' => null,
|
|
'operation' => '',
|
|
'description' => '',
|
|
'estimated_hours' => 0,
|
|
'labor_rate' => 85.00,
|
|
'category' => '',
|
|
'complexity' => 'medium',
|
|
];
|
|
$this->updatedLaborOperations(); // Save to session
|
|
}
|
|
|
|
public function removeLaborOperation($index)
|
|
{
|
|
unset($this->labor_operations[$index]);
|
|
$this->labor_operations = array_values($this->labor_operations);
|
|
$this->updatedLaborOperations(); // Save to session
|
|
}
|
|
|
|
public function saveProgress()
|
|
{
|
|
// Manually save current progress to session
|
|
$this->updatedPartsRequired();
|
|
$this->updatedLaborOperations();
|
|
$this->updatedDiagnosticCodes();
|
|
|
|
session()->flash('progress_saved', 'Progress saved successfully!');
|
|
}
|
|
|
|
public function addDiagnosticCode()
|
|
{
|
|
$this->diagnostic_codes[] = [
|
|
'code' => '',
|
|
'description' => '',
|
|
'system' => '',
|
|
'severity' => 'medium',
|
|
];
|
|
}
|
|
|
|
public function removeDiagnosticCode($index)
|
|
{
|
|
unset($this->diagnostic_codes[$index]);
|
|
$this->diagnostic_codes = array_values($this->diagnostic_codes);
|
|
}
|
|
|
|
public function addTestResult()
|
|
{
|
|
$this->test_results[] = [
|
|
'test_name' => '',
|
|
'result' => '',
|
|
'specification' => '',
|
|
'status' => 'pass',
|
|
];
|
|
}
|
|
|
|
public function removeTestResult($index)
|
|
{
|
|
unset($this->test_results[$index]);
|
|
$this->test_results = array_values($this->test_results);
|
|
}
|
|
|
|
public function addSpecialTool()
|
|
{
|
|
$this->special_tools_required[] = [
|
|
'tool_name' => '',
|
|
'tool_type' => '',
|
|
'availability' => 'available',
|
|
];
|
|
}
|
|
|
|
public function removeSpecialTool($index)
|
|
{
|
|
unset($this->special_tools_required[$index]);
|
|
$this->special_tools_required = array_values($this->special_tools_required);
|
|
}
|
|
|
|
public function togglePartsSection()
|
|
{
|
|
$this->showPartsSection = ! $this->showPartsSection;
|
|
}
|
|
|
|
public function toggleLaborSection()
|
|
{
|
|
$this->showLaborSection = ! $this->showLaborSection;
|
|
}
|
|
|
|
public function toggleDiagnosticCodesSection()
|
|
{
|
|
$this->showDiagnosticCodesSection = ! $this->showDiagnosticCodesSection;
|
|
}
|
|
|
|
public function toggleTestResultsSection()
|
|
{
|
|
$this->showTestResultsSection = ! $this->showTestResultsSection;
|
|
}
|
|
|
|
public function toggleAdvancedOptions()
|
|
{
|
|
$this->showAdvancedOptions = ! $this->showAdvancedOptions;
|
|
}
|
|
|
|
public function toggleTimesheetSection()
|
|
{
|
|
$this->showTimesheetSection = ! $this->showTimesheetSection;
|
|
}
|
|
|
|
public function calculateTotalEstimatedCost()
|
|
{
|
|
$partsCost = array_sum(array_map(function ($part) {
|
|
return ($part['quantity'] ?? 1) * ($part['estimated_cost'] ?? 0);
|
|
}, $this->parts_required));
|
|
|
|
$laborCost = 0;
|
|
foreach ($this->labor_operations as $operation) {
|
|
$laborCost += ($operation['estimated_hours'] ?? 0) * ($operation['labor_rate'] ?? 85);
|
|
}
|
|
|
|
// Include diagnostic time costs
|
|
$diagnosticCost = collect($this->timesheets)->sum('total_amount');
|
|
|
|
return $partsCost + $laborCost + $diagnosticCost;
|
|
}
|
|
|
|
private function createEstimateFromDiagnosis($diagnosis)
|
|
{
|
|
// Calculate totals
|
|
$partsCost = array_sum(array_map(function ($part) {
|
|
return ($part['quantity'] ?? 1) * ($part['estimated_cost'] ?? 0);
|
|
}, $this->parts_required));
|
|
|
|
$laborCost = 0;
|
|
foreach ($this->labor_operations as $operation) {
|
|
$laborCost += ($operation['estimated_hours'] ?? 0) * ($operation['labor_rate'] ?? 85);
|
|
}
|
|
|
|
$subtotal = $partsCost + $laborCost;
|
|
$taxRate = 0.0875; // 8.75% tax rate - should be configurable
|
|
$taxAmount = $subtotal * $taxRate;
|
|
$totalAmount = $subtotal + $taxAmount;
|
|
|
|
// Create the estimate
|
|
$estimate = Estimate::create([
|
|
'job_card_id' => $this->jobCard->id,
|
|
'diagnosis_id' => $diagnosis->id,
|
|
'estimate_number' => 'EST-'.str_pad(Estimate::max('id') + 1, 6, '0', STR_PAD_LEFT),
|
|
'customer_id' => $this->jobCard->customer_id,
|
|
'vehicle_id' => $this->jobCard->vehicle_id,
|
|
'prepared_by_id' => auth()->id(),
|
|
'status' => 'draft',
|
|
'priority_level' => $this->priority_level,
|
|
'estimated_completion_date' => now()->addHours($this->estimated_repair_time),
|
|
'subtotal' => $subtotal,
|
|
'tax_rate' => $taxRate,
|
|
'tax_amount' => $taxAmount,
|
|
'total_amount' => $totalAmount,
|
|
'notes' => 'Auto-generated from diagnosis: '.$diagnosis->id,
|
|
'validity_period_days' => 30,
|
|
]);
|
|
|
|
// Create line items for parts
|
|
foreach ($this->parts_required as $part) {
|
|
if (! empty($part['part_name'])) {
|
|
EstimateLineItem::create([
|
|
'estimate_id' => $estimate->id,
|
|
'type' => 'part',
|
|
'part_id' => $part['part_id'] ?? null,
|
|
'description' => $part['part_name'],
|
|
'part_number' => $part['part_number'] ?? null,
|
|
'quantity' => $part['quantity'] ?? 1,
|
|
'unit_price' => $part['estimated_cost'] ?? 0,
|
|
'total_price' => ($part['quantity'] ?? 1) * ($part['estimated_cost'] ?? 0),
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Create line items for labor
|
|
foreach ($this->labor_operations as $operation) {
|
|
if (! empty($operation['operation'])) {
|
|
EstimateLineItem::create([
|
|
'estimate_id' => $estimate->id,
|
|
'type' => 'labor',
|
|
'service_item_id' => $operation['service_item_id'] ?? null,
|
|
'description' => $operation['operation'],
|
|
'labor_hours' => $operation['estimated_hours'] ?? 0,
|
|
'labor_rate' => $operation['labor_rate'] ?? 85,
|
|
'total_price' => ($operation['estimated_hours'] ?? 0) * ($operation['labor_rate'] ?? 85),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $estimate;
|
|
}
|
|
|
|
public function save()
|
|
{
|
|
$this->validate();
|
|
|
|
// End any active timesheet
|
|
if ($this->currentTimesheet) {
|
|
$this->endTimesheet();
|
|
}
|
|
|
|
// Handle photo uploads
|
|
$photoUrls = [];
|
|
if ($this->photos) {
|
|
foreach ($this->photos as $photo) {
|
|
$photoUrls[] = $photo->store('diagnosis', 'public');
|
|
}
|
|
}
|
|
|
|
$diagnosis = Diagnosis::create([
|
|
'job_card_id' => $this->jobCard->id,
|
|
'service_coordinator_id' => auth()->id(),
|
|
'customer_reported_issues' => $this->customer_reported_issues,
|
|
'diagnostic_findings' => $this->diagnostic_findings,
|
|
'root_cause_analysis' => $this->root_cause_analysis,
|
|
'recommended_repairs' => $this->recommended_repairs,
|
|
'additional_issues_found' => $this->additional_issues_found,
|
|
'priority_level' => $this->priority_level,
|
|
'estimated_repair_time' => $this->estimated_repair_time,
|
|
'parts_required' => array_filter($this->parts_required, function ($part) {
|
|
return ! empty($part['part_name']);
|
|
}),
|
|
'labor_operations' => array_filter($this->labor_operations, function ($operation) {
|
|
return ! empty($operation['operation']);
|
|
}),
|
|
'special_tools_required' => array_filter($this->special_tools_required, function ($tool) {
|
|
return ! empty($tool['tool_name']);
|
|
}),
|
|
'safety_concerns' => $this->safety_concerns,
|
|
'diagnostic_codes' => array_filter($this->diagnostic_codes, function ($code) {
|
|
return ! empty($code['code']);
|
|
}),
|
|
'test_results' => array_filter($this->test_results, function ($result) {
|
|
return ! empty($result['test_name']);
|
|
}),
|
|
'photos' => $photoUrls,
|
|
'notes' => $this->notes,
|
|
'environmental_impact' => $this->environmental_impact,
|
|
'customer_authorization_required' => $this->customer_authorization_required,
|
|
'diagnosis_status' => 'completed',
|
|
'diagnosis_date' => now(),
|
|
]);
|
|
|
|
// Update job card status
|
|
$this->jobCard->update(['status' => 'diagnosis_completed']);
|
|
|
|
// Create estimate automatically
|
|
$estimate = $this->createEstimateFromDiagnosis($diagnosis);
|
|
|
|
// Clear session data after successful diagnosis creation
|
|
session()->forget([
|
|
"diagnosis_parts_{$this->jobCard->id}",
|
|
"diagnosis_labor_{$this->jobCard->id}",
|
|
"diagnosis_codes_{$this->jobCard->id}",
|
|
]);
|
|
|
|
session()->flash('message', 'Diagnosis completed successfully! Estimate #'.$estimate->estimate_number.' has been created automatically.');
|
|
|
|
return redirect()->route('estimates.show', $estimate);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.diagnosis.create');
|
|
}
|
|
}
|