CRM/backend/app/Services/ImportService.php

195 خطوط
7.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\ImportBatch;
use App\Models\ImportBatchRow;
use App\Models\Contact;
use App\Models\ContactPhone;
use App\Models\Lead;
use App\Models\LeadStatus;
use App\Models\PipelineStage;
use App\Models\Setting;
use App\Services\ActivityLogger;
use App\Services\NotificationService;
use Maatwebsite\Excel\Facades\Excel;
use Illuminate\Support\Facades\DB;
class ImportService
{
public function preview(string $filePath): array
{
$rows = Excel::toArray([], $filePath)[0] ?? [];
if (empty($rows)) {
throw new \Exception('فایل اکسل خالی است');
}
$headers = $rows[0];
$dataRows = array_slice($rows, 1);
return [
'headers' => $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) {
\App\Models\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',
]);
}
}
}