'0', '۱' => '1', '۲' => '2', '۳' => '3', '۴' => '4', '۵' => '5', '۶' => '6', '۷' => '7', '۸' => '8', '۹' => '9', '٠' => '0', '١' => '1', '٢' => '2', '٣' => '3', '٤' => '4', '٥' => '5', '٦' => '6', '٧' => '7', '٨' => '8', '٩' => '9'])); if (! $digits) { return null; } if (str_starts_with($digits, '0098')) { $digits = '0'.substr($digits, 4); } if (str_starts_with($digits, '98')) { $digits = '0'.substr($digits, 2); } return $digits; } public static function normalizeWebsite(?string $website): ?string { if (! $website) { return null; } $value = strtolower(trim($website)); $value = preg_replace('#^https?://#', '', $value); $value = preg_replace('#^www\.#', '', $value); return rtrim($value, '/'); } public static function normalizeName(?string $name): ?string { return $name ? Str::of($name)->lower()->squish()->toString() : null; } public function companySuggestions(array $payload, ?int $excludeId = null, ?User $user = null): array { $name = self::normalizeName($payload['name'] ?? null); $website = self::normalizeWebsite($payload['website'] ?? null); $email = strtolower((string) ($payload['email'] ?? '')); $phone = self::normalizePhone($payload['phone'] ?? null); $query = Company::query()->with('owner:id,name')->limit(10); if ($user) { AccessControl::scopeCompanies($query, $user); } if ($excludeId) { $query->whereKeyNot($excludeId); } $query->where(function ($q) use ($name, $website, $email, $phone) { if ($name) { $q->orWhere('normalized_name', $name); } if ($website) { $q->orWhere('normalized_website', $website); } if ($email) { $q->orWhereHas('contacts', fn ($contact) => $contact->where('email', $email)); } if ($phone) { $q->orWhereHas('contacts.phones', fn ($phoneQuery) => $phoneQuery->where('phone', 'like', "%{$phone}%")); } }); return $query->get()->map(fn (Company $company) => [ 'id' => $company->id, 'type' => 'company', 'title' => $company->name, 'reason' => 'شباهت در نام شرکت، وب‌سایت، ایمیل یا شماره تماس', ])->values()->all(); } public function leadSuggestions(array $payload, ?int $excludeId = null, ?User $user = null): array { $phone = self::normalizePhone($payload['phone'] ?? null); $email = strtolower((string) ($payload['email'] ?? '')); $company = self::normalizeName($payload['company'] ?? null); $query = Lead::query()->limit(10); if ($user) { AccessControl::scopeLeads($query, $user, false); } if ($excludeId) { $query->whereKeyNot($excludeId); } $query->where(function ($q) use ($phone, $email, $company) { if ($phone) { $q->orWhere('phone', 'like', "%{$phone}%")->orWhere('phone_secondary', 'like', "%{$phone}%"); } if ($email) { $q->orWhere('email', $email); } if ($company) { $q->orWhereRaw('LOWER(company) = ?', [$company]); } }); return $query->get()->map(fn (Lead $lead) => [ 'id' => $lead->id, 'type' => 'lead', 'title' => $lead->company ?: $lead->full_name, 'reason' => 'شباهت در تلفن، ایمیل یا نام شرکت', ])->values()->all(); } public function mergeCompanies(Company $source, Company $target, int $userId): Company { return DB::transaction(function () use ($source, $target, $userId) { Contact::where('company_id', $source->id)->update(['company_id' => $target->id]); Lead::where('company_id', $source->id)->update(['company_id' => $target->id]); Deal::where('company_id', $source->id)->update(['company_id' => $target->id]); Attachment::where('attachable_type', Company::class)->where('attachable_id', $source->id)->update(['attachable_id' => $target->id]); Note::where('notable_type', Company::class)->where('notable_id', $source->id)->update(['notable_id' => $target->id]); MergeHistory::create([ 'entity_type' => 'company', 'source_id' => $source->id, 'target_id' => $target->id, 'merged_fields' => $source->only(['name', 'website', 'city', 'industry']), 'merged_by' => $userId, ]); $source->delete(); ActivityLogger::log('company_merged', "Company {$source->id} merged into {$target->id}", $target); return $target->fresh(['contacts.phones', 'leads', 'deals']); }); } }