$data['customer_id'], 'vehicle_id' => $data['vehicle_id'], 'service_advisor_id' => $data['service_advisor_id'], 'branch_code' => $data['branch_code'] ?? config('app.default_branch_code', 'ACC'), 'arrival_datetime' => $data['arrival_datetime'] ?? now(), 'mileage_in' => $data['mileage_in'] ?? null, 'fuel_level_in' => $data['fuel_level_in'] ?? null, 'customer_reported_issues' => $data['customer_reported_issues'] ?? '', 'vehicle_condition_notes' => $data['vehicle_condition_notes'] ?? '', 'keys_location' => $data['keys_location'] ?? 'service_desk', 'personal_items_removed' => $data['personal_items_removed'] ?? false, 'photos_taken' => $data['photos_taken'] ?? false, 'expected_completion_date' => $data['expected_completion_date'] ?? null, 'priority' => $data['priority'] ?? 'medium', 'notes' => $data['notes'] ?? null, 'status' => 'received', // Initial status ]); // STEP 2: Create incoming inspection checklist automatically if (isset($data['inspection_checklist'])) { $this->performInitialInspection($jobCard, $data, $data['inspector_id']); } return $jobCard; }); } /** * STEP 2: Initial Inspection by Service Supervisor * Perform arrival inspection checklist */ public function performInitialInspection(JobCard $jobCard, array $inspectionData, int $inspectorId): JobCard { // Update job card with inspection data $jobCard->update([ 'status' => JobCard::STATUS_INSPECTED, 'mileage_in' => $inspectionData['mileage_in'], 'fuel_level_in' => $inspectionData['fuel_level_in'], 'incoming_inspection_data' => $inspectionData['inspection_checklist'], ]); return $jobCard->fresh(); } /** * STEP 3: Assignment to Service Coordination * Assign job card to service coordinator and start diagnosis */ public function assignToServiceCoordinator(JobCard $jobCard, int $serviceCoordinatorId): Diagnosis { // Validate workflow progression if ($jobCard->status !== JobCard::STATUS_INSPECTED) { throw new \InvalidArgumentException('Job card must be inspected before assignment to service coordinator'); } $diagnosis = Diagnosis::create([ 'job_card_id' => $jobCard->id, 'service_coordinator_id' => $serviceCoordinatorId, 'customer_reported_issues' => $jobCard->customer_reported_issues, 'diagnosis_status' => 'in_progress', 'diagnosis_date' => now(), ]); $jobCard->update(['status' => 'assigned_for_diagnosis']); return $diagnosis; } /** * STEP 4: Start Diagnostic Process with Timesheet Tracking */ public function startDiagnosisTimesheet(Diagnosis $diagnosis, int $technicianId): void { // Start timesheet for diagnosis $timesheet = Timesheet::create([ 'job_card_id' => $diagnosis->job_card_id, 'technician_id' => $technicianId, 'task_type' => 'diagnosis', 'start_time' => now(), 'description' => 'Diagnostic assessment', 'status' => 'in_progress', ]); $diagnosis->update([ 'diagnosis_status' => 'in_progress', 'assigned_technician_id' => $technicianId, 'started_at' => now(), ]); } /** * STEP 5: Complete diagnosis and create estimate with customer notifications */ public function completeDiagnosis(Diagnosis $diagnosis, array $diagnosisData, array $estimateItems): Estimate { return DB::transaction(function () use ($diagnosis, $diagnosisData, $estimateItems) { // Update diagnosis $diagnosis->update([ 'diagnostic_findings' => $diagnosisData['diagnostic_findings'], 'root_cause_analysis' => $diagnosisData['root_cause_analysis'], 'recommended_repairs' => $diagnosisData['recommended_repairs'], 'additional_issues_found' => $diagnosisData['additional_issues_found'] ?? null, 'priority_level' => $diagnosisData['priority_level'] ?? 'medium', 'estimated_repair_time' => $diagnosisData['estimated_repair_time'], 'parts_required' => $diagnosisData['parts_required'] ?? [], 'labor_operations' => $diagnosisData['labor_operations'] ?? [], 'special_tools_required' => $diagnosisData['special_tools_required'] ?? [], 'safety_concerns' => $diagnosisData['safety_concerns'] ?? null, 'customer_authorization_required' => $diagnosisData['customer_authorization_required'] ?? false, 'diagnosis_status' => 'completed', 'notes' => $diagnosisData['notes'] ?? null, 'completed_at' => now(), ]); // Create estimate $estimate = Estimate::create([ 'job_card_id' => $diagnosis->job_card_id, 'diagnosis_id' => $diagnosis->id, 'estimate_number' => $this->generateEstimateNumber($diagnosis->jobCard->branch_code), 'prepared_by_id' => $diagnosis->service_coordinator_id, 'total_labor_cost' => $estimateItems['total_labor_cost'], 'total_parts_cost' => $estimateItems['total_parts_cost'], 'total_other_cost' => $estimateItems['total_other_cost'] ?? 0, 'tax_amount' => $estimateItems['tax_amount'], 'total_amount' => $estimateItems['total_amount'], 'status' => 'sent', 'notes' => $estimateItems['notes'] ?? null, 'valid_until' => $estimateItems['valid_until'] ?? now()->addDays(30), ]); // Create estimate line items foreach ($estimateItems['line_items'] as $item) { $estimate->lineItems()->create($item); } // Update job card status $diagnosis->jobCard->update(['status' => 'estimate_sent']); // STEP 5: Send notifications to customer (email + SMS) $this->notificationService->sendEstimateNotification($estimate); return $estimate; }); } /** * STEP 6: Handle estimate approval and notify team */ public function approveEstimate(Estimate $estimate, string $approvalMethod = 'portal'): WorkOrder { return DB::transaction(function () use ($estimate, $approvalMethod) { // Update estimate $estimate->update([ 'customer_approval_status' => 'approved', 'customer_approved_at' => now(), 'customer_approval_method' => $approvalMethod, 'status' => 'approved', ]); // Update job card $estimate->jobCard->update(['status' => 'approved']); // Notify team members about approval $this->notificationService->notifyEstimateApproved($estimate); // Create work order return $this->createWorkOrder($estimate); }); } /** * STEP 7: Parts Procurement & Inventory Management */ public function initiatePartsProcurement(Estimate $estimate): array { $procurementStatus = []; foreach ($estimate->lineItems()->where('type', 'part')->get() as $item) { // Check inventory availability $part = Part::find($item->part_id); if (!$part || $part->current_stock < $item->quantity) { // Create purchase order if parts are out of stock $procurementStatus[] = [ 'part_id' => $item->part_id, 'part_name' => $item->description, 'required_quantity' => $item->quantity, 'available_stock' => $part->current_stock ?? 0, 'shortage' => $item->quantity - ($part->current_stock ?? 0), 'status' => 'procurement_required', 'action' => 'create_purchase_order' ]; } else { // Reserve parts from inventory $part->decrement('current_stock', $item->quantity); $part->increment('reserved_stock', $item->quantity); $procurementStatus[] = [ 'part_id' => $item->part_id, 'part_name' => $item->description, 'required_quantity' => $item->quantity, 'status' => 'reserved', 'action' => 'reserved_from_stock' ]; } } return $procurementStatus; } /** * Assign work order to technician and start work */ public function assignWorkOrder(WorkOrder $workOrder, int $technicianId): void { $workOrder->update([ 'assigned_technician_id' => $technicianId, 'status' => 'assigned', 'actual_start_time' => now(), ]); $workOrder->jobCard->update(['status' => 'in_progress']); } /** * Complete work order and perform quality check */ public function completeWorkOrder(WorkOrder $workOrder, int $qualityCheckerId): void { $workOrder->update([ 'status' => 'quality_check', 'actual_completion_time' => now(), 'quality_checked_by_id' => $qualityCheckerId, 'quality_check_date' => now(), 'completion_percentage' => 100, ]); } /** * STEP 8: Final Inspection & Quality Assurance * Perform outgoing inspection and final quality check */ public function performOutgoingInspection(JobCard $jobCard, array $inspectionData, int $inspectorId): void { // Create outgoing inspection $outgoingInspection = VehicleInspection::create([ 'job_card_id' => $jobCard->id, 'vehicle_id' => $jobCard->vehicle_id, 'inspector_id' => $inspectorId, 'inspection_type' => 'outgoing', 'current_mileage' => $inspectionData['mileage_out'], 'fuel_level' => $inspectionData['fuel_level_out'], 'inspection_checklist' => $inspectionData['inspection_checklist'], 'photos' => $inspectionData['photos'] ?? [], 'overall_condition' => $inspectionData['overall_condition'], 'inspection_date' => now(), 'notes' => $inspectionData['notes'] ?? null, ]); // Compare with incoming inspection using service $incomingInspection = $jobCard->incomingInspection; if ($incomingInspection) { $differences = $this->inspectionService->compareInspections($incomingInspection, $outgoingInspection); if (!empty($differences)) { // Generate quality alert for significant discrepancies $qualityAlert = $this->inspectionService->generateQualityAlert($jobCard, $differences); if (!empty($qualityAlert)) { $this->notificationService->sendQualityAlert($jobCard, $differences); $outgoingInspection->update([ 'discrepancies_found' => $differences, 'follow_up_required' => true, ]); $jobCard->update(['status' => 'quality_review_required']); return; } } } // No discrepancies, mark as completed $jobCard->update([ 'status' => 'completed', 'mileage_out' => $inspectionData['mileage_out'], 'fuel_level_out' => $inspectionData['fuel_level_out'], 'completion_datetime' => now(), ]); // Notify customer that vehicle is ready $this->notificationService->notifyVehicleReady($jobCard); } /** * STEP 9: Accounting & Invoicing */ public function generateFinalInvoice(JobCard $jobCard): array { $estimate = $jobCard->estimates()->where('status', 'approved')->first(); $actualLabor = $jobCard->timesheets()->sum('billable_hours'); $actualParts = $jobCard->workOrders()->with('usedParts')->get() ->flatMap->usedParts->sum('actual_cost'); $invoiceData = [ 'job_card_id' => $jobCard->id, 'estimate_amount' => $estimate->total_amount, 'actual_labor_cost' => $actualLabor * $estimate->labor_rate, 'actual_parts_cost' => $actualParts, 'tax_amount' => ($actualLabor + $actualParts) * $estimate->tax_rate / 100, 'total_amount' => ($actualLabor + $actualParts) * (1 + $estimate->tax_rate / 100), 'variance_from_estimate' => null, ]; $invoiceData['variance_from_estimate'] = $invoiceData['total_amount'] - $estimate->total_amount; return $invoiceData; } /** * STEP 10: Vehicle Delivery & Job Closure */ public function closeJobCard(JobCard $jobCard, array $deliveryData): void { $jobCard->update([ 'status' => 'delivered', 'delivery_method' => $deliveryData['delivery_method'], 'customer_satisfaction_rating' => $deliveryData['satisfaction_rating'] ?? null, 'completion_datetime' => now(), 'delivered_by_id' => $deliveryData['delivered_by_id'] ?? auth()->id(), 'delivery_notes' => $deliveryData['delivery_notes'] ?? null, ]); // Archive all associated documents $this->archiveJobCardDocuments($jobCard); } /** * STEP 11: Archive job card documents */ private function archiveJobCardDocuments(JobCard $jobCard): void { // This would typically move documents to long-term storage // For now, we'll just mark them as archived $jobCard->update(['archived_at' => now()]); // Update related records $jobCard->inspections()->update(['archived' => true]); $jobCard->timesheets()->update(['archived' => true]); $jobCard->estimates()->update(['archived' => true]); $jobCard->workOrders()->update(['archived' => true]); } /** * Get workflow status for a job card */ public function getWorkflowStatus(JobCard $jobCard): array { return [ 'job_card' => $jobCard, 'incoming_inspection' => $jobCard->incomingInspection, 'diagnosis' => $jobCard->diagnosis, 'estimate' => $jobCard->estimates()->latest()->first(), 'work_orders' => $jobCard->workOrders, 'outgoing_inspection' => $jobCard->outgoingInspection, 'completion_percentage' => $this->calculateCompletionPercentage($jobCard), ]; } /** * Calculate completion percentage for a job card */ private function calculateCompletionPercentage(JobCard $jobCard): int { $steps = [ 'received' => 10, 'in_diagnosis' => 25, 'estimate_sent' => 40, 'approved' => 50, 'in_progress' => 75, 'quality_check' => 90, 'completed' => 95, 'delivered' => 100, ]; return $steps[$jobCard->status] ?? 0; } }