210 lines
6.5 KiB
PHP
210 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace Modules\Crm\Utils;
|
|
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Carbon\CarbonInterval;
|
|
use Modules\Crm\Entities\CrmCallLog;
|
|
use Modules\Crm\Entities\CrmPhoneFollowUp;
|
|
use Modules\Crm\Entities\CrmPhoneFollowUpLog;
|
|
|
|
class PhoneListUtil
|
|
{
|
|
public static function normalizeNationalNumber(?string $number): string
|
|
{
|
|
if (empty($number)) {
|
|
return '';
|
|
}
|
|
|
|
$number = trim($number);
|
|
$first_character = substr($number, 0, 1);
|
|
|
|
if ($first_character === '0') {
|
|
return ltrim($number, '0');
|
|
}
|
|
|
|
if ($first_character === '+') {
|
|
try {
|
|
$phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance();
|
|
$number_obj = $phoneUtil->parse($number);
|
|
|
|
return (string) $number_obj->getNationalNumber();
|
|
} catch (\Exception $e) {
|
|
return preg_replace('/\D+/', '', $number);
|
|
}
|
|
}
|
|
|
|
return preg_replace('/\D+/', '', $number);
|
|
}
|
|
|
|
public function numbersMatch(?string $a, ?string $b): bool
|
|
{
|
|
$na = self::normalizeNationalNumber($a);
|
|
$nb = self::normalizeNationalNumber($b);
|
|
|
|
return ! empty($na) && $na === $nb;
|
|
}
|
|
|
|
public function findActiveByNumber(int $business_id, string $number): ?CrmPhoneFollowUp
|
|
{
|
|
$phones = CrmPhoneFollowUp::where('business_id', $business_id)
|
|
->where('status', '!=', 'converted')
|
|
->get();
|
|
|
|
foreach ($phones as $phone) {
|
|
if ($this->numbersMatch($phone->mobile_number, $number)) {
|
|
return $phone;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function createFromImportRow(int $business_id, int $user_id, array $row): array
|
|
{
|
|
$mobile = trim($row['mobile_number'] ?? '');
|
|
if (empty($mobile)) {
|
|
return ['status' => 'invalid', 'msg' => __('crm::lang.phone_number_required')];
|
|
}
|
|
|
|
$exists = CrmPhoneFollowUp::where('business_id', $business_id)
|
|
->where('mobile_number', $mobile)
|
|
->where('status', '!=', 'converted')
|
|
->exists();
|
|
|
|
if ($exists) {
|
|
return ['status' => 'skipped'];
|
|
}
|
|
|
|
$assigned_to = null;
|
|
if (! empty($row['assigned_to'])) {
|
|
$assigned_to = $this->resolveAssignedUserId($business_id, $row['assigned_to']);
|
|
}
|
|
|
|
CrmPhoneFollowUp::create([
|
|
'business_id' => $business_id,
|
|
'mobile_number' => $mobile,
|
|
'contact_name' => $row['contact_name'] ?? null,
|
|
'company_name' => $row['company_name'] ?? null,
|
|
'city' => $row['city'] ?? null,
|
|
'province' => $row['province'] ?? null,
|
|
'source' => $row['source'] ?? null,
|
|
'priority' => ! empty($row['priority']) ? (int) $row['priority'] : 3,
|
|
'notes' => $row['notes'] ?? null,
|
|
'status' => 'pending',
|
|
'assigned_to' => $assigned_to,
|
|
'created_by' => $user_id,
|
|
]);
|
|
|
|
return ['status' => 'created'];
|
|
}
|
|
|
|
public function resolveAssignedUserId(int $business_id, string $value): ?int
|
|
{
|
|
$value = trim($value);
|
|
if (empty($value)) {
|
|
return null;
|
|
}
|
|
|
|
if (is_numeric($value)) {
|
|
$user = User::where('business_id', $business_id)->find((int) $value);
|
|
|
|
return $user?->id;
|
|
}
|
|
|
|
$user = User::where('business_id', $business_id)
|
|
->where(function ($q) use ($value) {
|
|
$q->where('username', $value)
|
|
->orWhere('email', $value)
|
|
->orWhereRaw("CONCAT(COALESCE(first_name, ''), ' ', COALESCE(last_name, '')) like ?", ["%{$value}%"]);
|
|
})
|
|
->first();
|
|
|
|
return $user?->id;
|
|
}
|
|
|
|
public function syncFromCallLog(array $callLogData, ?int $call_log_id = null): void
|
|
{
|
|
if (empty($callLogData['business_id']) || empty($callLogData['mobile_number'])) {
|
|
return;
|
|
}
|
|
|
|
$phone = $this->findActiveByNumber($callLogData['business_id'], $callLogData['mobile_number']);
|
|
if (empty($phone)) {
|
|
return;
|
|
}
|
|
|
|
$duration = ! empty($callLogData['duration']) ? (int) $callLogData['duration'] : 0;
|
|
$call_type = $callLogData['call_type'] ?? 'call';
|
|
$duration_text = $duration > 0
|
|
? CarbonInterval::seconds($duration)->cascade()->forHumans()
|
|
: '-';
|
|
$note = __('crm::lang.auto_synced_from_call_log', [
|
|
'type' => $call_type,
|
|
'duration' => $duration_text,
|
|
]);
|
|
|
|
CrmPhoneFollowUpLog::create([
|
|
'phone_follow_up_id' => $phone->id,
|
|
'status' => 'contacted',
|
|
'note' => $note,
|
|
'created_by' => $callLogData['created_by'],
|
|
'call_log_id' => $call_log_id,
|
|
'is_auto' => true,
|
|
]);
|
|
|
|
$phone->update([
|
|
'status' => $phone->status === 'pending' ? 'contacted' : $phone->status,
|
|
'last_follow_up_at' => $callLogData['start_time'] ?? now(),
|
|
'follow_up_count' => $phone->follow_up_count + 1,
|
|
]);
|
|
}
|
|
|
|
public function getCallLogCount(int $business_id, string $mobile_number): int
|
|
{
|
|
if (! config('constants.enable_crm_call_log')) {
|
|
return 0;
|
|
}
|
|
|
|
$normalized = self::normalizeNationalNumber($mobile_number);
|
|
if (empty($normalized)) {
|
|
return 0;
|
|
}
|
|
|
|
return CrmCallLog::where('business_id', $business_id)
|
|
->where(function ($q) use ($normalized, $mobile_number) {
|
|
$q->where('mobile_number', $mobile_number)
|
|
->orWhere('mobile_number', 'like', "%{$normalized}");
|
|
})
|
|
->count();
|
|
}
|
|
|
|
public function getCallLogsForPhone(CrmPhoneFollowUp $phone)
|
|
{
|
|
if (! config('constants.enable_crm_call_log')) {
|
|
return collect();
|
|
}
|
|
|
|
$logs = CrmCallLog::where('business_id', $phone->business_id)
|
|
->latest('start_time')
|
|
->limit(100)
|
|
->get();
|
|
|
|
return $logs->filter(function ($log) use ($phone) {
|
|
return $this->numbersMatch($log->mobile_number, $phone->mobile_number);
|
|
})->values();
|
|
}
|
|
|
|
public function getReminderSettings(int $business_id): array
|
|
{
|
|
$crmUtil = new CrmUtil();
|
|
$settings = $crmUtil->getCrmSettings($business_id);
|
|
|
|
return [
|
|
'enabled' => ! empty($settings['enable_phone_list_reminder']),
|
|
'days' => max(1, (int) ($settings['phone_list_reminder_days'] ?? 1)),
|
|
];
|
|
}
|
|
}
|