195 خطوط
7.8 KiB
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',
|
|
]);
|
|
}
|
|
}
|
|
}
|