$headers, 'total_rows' => count($dataRows), 'preview' => array_slice($dataRows, 0, 5), ]; } public function confirm(int $batchId, array $columnMapping, ?int $campaignId = null, ?array $agentIds = null, ?int $assignedById = null): ImportBatch { $batch = ImportBatch::with('rows')->findOrFail($batchId); $defaultStatus = LeadStatus::where('is_default', true)->first(); $defaultStage = PipelineStage::where('is_default', true)->first(); $assignedStage = PipelineStage::where('slug', 'waiting_call')->first(); $duplicatePolicy = Setting::where('key', 'duplicate_phone_policy')->value('value') ?? 'warn'; DB::transaction(function () use ($batch, $columnMapping, $campaignId, $agentIds, $assignedById, $defaultStatus, $defaultStage, $assignedStage, $duplicatePolicy) { $agentIndex = 0; $agentCount = count($agentIds ?? []); foreach ($batch->rows as $row) { if ($row->status !== 'pending') { continue; } $data = $row->original_data; $leadData = $this->mapColumns($data, $columnMapping); if (! $leadData['company']) { $row->update(['status' => 'failed', 'error' => 'نام کسب‌وکار الزامی است']); $batch->increment('failed_rows'); continue; } if (! $leadData['phone']) { $row->update(['status' => 'failed', 'error' => 'شماره تلفن الزامی است']); $batch->increment('failed_rows'); continue; } $existing = Lead::where('phone', $leadData['phone'])->first(); if ($existing) { if ($duplicatePolicy === 'allow') { // Continue and create a new lead below. } elseif ($duplicatePolicy === 'block') { $row->update(['status' => 'failed', 'lead_id' => $existing->id, 'error' => 'شماره تلفن تکراری است']); $batch->increment('failed_rows'); continue; } else { $row->update(['status' => 'skipped', 'lead_id' => $existing->id, 'error' => 'شماره تلفن تکراری است؛ نیازمند بررسی یا ادغام']); $batch->increment('skipped_rows'); continue; } } $leadData['first_name'] = $leadData['first_name'] ?: $leadData['company']; $leadData['last_name'] = $leadData['last_name'] ?: 'رابط'; $leadData['lead_status_id'] = $defaultStatus?->id; $leadData['pipeline_stage_id'] = $defaultStage?->id; $leadData['campaign_id'] = $campaignId; $leadData['priority'] = isset($leadData['priority']) && $leadData['priority'] !== '' ? (int) $leadData['priority'] : 0; $leadData['lead_score'] = isset($leadData['lead_score']) && $leadData['lead_score'] !== '' ? (int) $leadData['lead_score'] : 0; $leadData['interest_level'] = $leadData['interest_level'] ?: 'cold'; if ($agentCount > 0) { $leadData['assigned_to'] = $agentIds[$agentIndex % $agentCount]; $leadData['assigned_by'] = $assignedById; $leadData['is_unassigned'] = false; $leadData['pipeline_stage_id'] = $assignedStage?->id ?? $leadData['pipeline_stage_id']; $agentIndex++; } $lead = Lead::create($leadData); $this->createPrimaryContact($lead, $assignedById); if ($leadData['assigned_to'] ?? null) { LeadAssignment::create([ 'lead_id' => $lead->id, 'user_id' => $leadData['assigned_to'], 'assigned_by' => $assignedById, 'method' => 'import', ]); NotificationService::notifyAssignment((int) $leadData['assigned_to'], $lead->full_name ?: ($lead->company ?? "لید {$lead->id}")); } $row->update(['status' => 'imported', 'lead_id' => $lead->id]); $batch->increment('imported_rows'); } $batch->update([ 'campaign_id' => $campaignId, 'column_mapping' => $columnMapping, 'status' => 'completed', ]); }); ActivityLogger::log('import_completed', "Import batch {$batchId} completed", $batch); return $batch->fresh(); } public function rollback(int $batchId): void { $batch = ImportBatch::with('rows')->findOrFail($batchId); DB::transaction(function () use ($batch) { foreach ($batch->rows as $row) { if ($row->status === 'imported' && $row->lead_id) { $row->lead?->delete(); $row->update(['status' => 'rolled_back']); } } $batch->update(['status' => 'rolled_back', 'can_rollback' => false]); }); ActivityLogger::log('import_rolled_back', "Import batch {$batchId} rolled back", $batch); } private function mapColumns(array $data, array $mapping): array { $leadData = [ 'company' => null, 'first_name' => null, 'last_name' => null, 'phone' => null, 'phone_secondary' => null, 'email' => null, 'city' => null, 'province' => null, 'source' => null, 'product_interest' => null, 'priority' => null, 'lead_score' => null, 'interest_level' => null, 'notes' => null, 'tags' => null, ]; foreach ($mapping as $field => $columnIndex) { if (isset($data[$columnIndex])) { $leadData[$field] = is_string($data[$columnIndex]) ? trim($data[$columnIndex]) : $data[$columnIndex]; } } return $leadData; } private function createPrimaryContact(Lead $lead, ?int $createdById): void { $contact = Contact::create([ 'lead_id' => $lead->id, 'name' => trim(($lead->first_name ?? '').' '.($lead->last_name ?? '')) ?: ($lead->company ?? 'مخاطب اولیه'), 'role' => 'رابط', 'description' => 'مخاطب اولیه import', 'status' => 'active', 'is_primary' => true, 'primary_reason' => 'مخاطب اولیه import', 'created_by' => $createdById, ]); foreach (array_filter([$lead->phone, $lead->phone_secondary]) as $phone) { ContactPhone::create([ 'contact_id' => $contact->id, 'phone' => $phone, 'type' => 'mobile', 'status' => 'active', ]); } } }