ultimatepos/Modules/Crm/Services/CallQueueService.php

252 lines
8.3 KiB
PHP

<?php
namespace Modules\Crm\Services;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Modules\Crm\Entities\CrmCallQueue;
use Modules\Crm\Entities\CrmCallSession;
use Modules\Crm\Entities\CrmPhoneFollowUp;
use Modules\Crm\Utils\TelemarketingUtil;
class CallQueueService
{
public function syncQueueForBusiness(int $business_id): void
{
$this->pruneInvalidQueueEntries($business_id);
$phones = CrmPhoneFollowUp::where('business_id', $business_id)
->where('status', 'pending')
->where('block_auto_recall', false)
->get();
foreach ($phones as $phone) {
$exists = CrmCallQueue::where('business_id', $business_id)
->where('phone_follow_up_id', $phone->id)
->exists();
if (! $exists) {
CrmCallQueue::create([
'business_id' => $business_id,
'phone_follow_up_id' => $phone->id,
'campaign_id' => $phone->campaign_id,
'status' => 'available',
'priority' => $phone->priority ?? 3,
]);
}
}
}
/**
* Remove queue slots for numbers already called or no longer pending.
*/
public function pruneInvalidQueueEntries(int $business_id): void
{
$now = Carbon::now();
CrmCallQueue::where('business_id', $business_id)
->whereIn('status', ['available', 'locked'])
->whereHas('phoneFollowUp', function ($q) {
$q->where('status', '!=', 'pending');
})
->update([
'status' => 'skipped',
'completed_at' => $now,
'locked_by' => null,
'locked_at' => null,
'expires_at' => null,
]);
$donePhoneIds = CrmCallQueue::where('business_id', $business_id)
->whereIn('status', ['completed', 'skipped'])
->pluck('phone_follow_up_id')
->unique();
if ($donePhoneIds->isNotEmpty()) {
CrmCallQueue::where('business_id', $business_id)
->whereIn('status', ['available', 'locked'])
->whereIn('phone_follow_up_id', $donePhoneIds)
->update([
'status' => 'skipped',
'completed_at' => $now,
'locked_by' => null,
'locked_at' => null,
'expires_at' => null,
]);
}
}
public function acquireNext(int $business_id, int $user_id, ?int $campaign_id = null): ?CrmCallQueue
{
return DB::transaction(function () use ($business_id, $user_id, $campaign_id) {
$this->syncQueueForBusiness($business_id);
$query = CrmCallQueue::where('crm_call_queues.business_id', $business_id)
->where('crm_call_queues.status', 'available')
->join('crm_phone_follow_ups', 'crm_call_queues.phone_follow_up_id', '=', 'crm_phone_follow_ups.id')
->where('crm_phone_follow_ups.status', 'pending')
->where('crm_phone_follow_ups.block_auto_recall', false)
->select('crm_call_queues.*')
->orderBy('crm_call_queues.priority')
->orderBy('crm_call_queues.id');
if (! auth()->user()->can('crm.telemarketing.view_all_queue') && ! auth()->user()->can('superadmin')) {
$query->where(function ($q) use ($user_id) {
$q->whereNull('crm_phone_follow_ups.assigned_to')
->orWhere('crm_phone_follow_ups.assigned_to', $user_id);
});
}
if (! empty($campaign_id)) {
$query->where('crm_call_queues.campaign_id', $campaign_id);
}
$queue = $query->lockForUpdate()->first();
if (empty($queue)) {
return null;
}
$lockMinutes = TelemarketingUtil::callLockMinutes($business_id);
$now = Carbon::now();
$queue->update([
'status' => 'locked',
'locked_by' => $user_id,
'locked_at' => $now,
'expires_at' => $now->copy()->addMinutes($lockMinutes),
]);
return $queue->fresh(['phoneFollowUp']);
});
}
public function release(int $queue_id, int $user_id, ?int $business_id = null): bool
{
$query = CrmCallQueue::query()->where('id', $queue_id);
if ($business_id) {
$query->where('business_id', $business_id);
}
$queue = $query->first();
if (empty($queue) || $queue->locked_by != $user_id) {
return false;
}
if ($queue->status === 'in_call') {
return false;
}
$queue->update([
'status' => 'available',
'locked_by' => null,
'locked_at' => null,
'expires_at' => null,
]);
return true;
}
public function startSession(int $queue_id, int $user_id, ?int $business_id = null): ?CrmCallSession
{
return DB::transaction(function () use ($queue_id, $user_id, $business_id) {
$query = CrmCallQueue::query()->where('id', $queue_id);
if ($business_id) {
$query->where('business_id', $business_id);
}
$queue = $query->lockForUpdate()->first();
if (empty($queue) || $queue->locked_by != $user_id) {
return null;
}
$session = CrmCallSession::create([
'business_id' => $queue->business_id,
'queue_id' => $queue->id,
'phone_follow_up_id' => $queue->phone_follow_up_id,
'user_id' => $user_id,
'started_at' => Carbon::now(),
]);
$queue->update([
'status' => 'in_call',
'call_session_id' => $session->id,
]);
return $session->load('phoneFollowUp');
});
}
public function endSession(int $session_id, int $user_id, ?string $outcome = null, ?int $business_id = null): ?CrmCallSession
{
return DB::transaction(function () use ($session_id, $user_id, $outcome, $business_id) {
$query = CrmCallSession::query()->where('id', $session_id);
if ($business_id) {
$query->where('business_id', $business_id);
}
$session = $query->first();
if (empty($session) || $session->user_id != $user_id || ! empty($session->ended_at)) {
return null;
}
$session->update([
'ended_at' => Carbon::now(),
'outcome' => $outcome,
]);
if (! empty($session->queue_id)) {
CrmCallQueue::where('id', $session->queue_id)->update([
'status' => 'completed',
'completed_at' => Carbon::now(),
]);
}
return $session->fresh();
});
}
public function getActiveSession(int $business_id, int $user_id): ?CrmCallSession
{
return CrmCallSession::where('business_id', $business_id)
->where('user_id', $user_id)
->whereNull('ended_at')
->with(['phoneFollowUp', 'queue'])
->latest('started_at')
->first();
}
public function getActiveQueue(int $business_id, int $user_id): ?CrmCallQueue
{
return CrmCallQueue::where('business_id', $business_id)
->where('locked_by', $user_id)
->whereIn('status', ['locked', 'in_call'])
->with('phoneFollowUp')
->latest('locked_at')
->first();
}
public function expireStaleLocks(): int
{
$expired = CrmCallQueue::whereIn('status', ['locked', 'in_call'])
->whereNotNull('expires_at')
->where('expires_at', '<', Carbon::now())
->get();
$count = 0;
foreach ($expired as $queue) {
if ($queue->status === 'in_call') {
continue;
}
$queue->update([
'status' => 'available',
'locked_by' => null,
'locked_at' => null,
'expires_at' => null,
]);
$count++;
}
return $count;
}
}