ultimatepos/Modules/AssetExchange/Services/InquirySlaService.php

172 lines
5.4 KiB
PHP

<?php
namespace Modules\AssetExchange\Services;
use App\Business;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Modules\AssetExchange\Models\PriceInquiry;
use Modules\AssetExchange\Models\ResearchTask;
use Modules\AssetExchange\Utils\AssetExchangeUtil;
class InquirySlaService
{
public function __construct(
protected AssetExchangeUtil $aexUtil
) {
}
public function slaSettings(int $businessId): array
{
$settings = $this->aexUtil->getAexSettings($businessId);
return [
'inquiry_sla_hours' => max(1, (int) ($settings['inquiry_sla_hours'] ?? 48)),
'inquiry_sla_warning_percent' => min(99, max(1, (int) ($settings['inquiry_sla_warning_percent'] ?? 75))),
'research_task_sla_days' => max(1, (int) ($settings['research_task_sla_days'] ?? 7)),
];
}
/**
* @return 'ok'|'warning'|'overdue'|'breached'|'na'
*/
public function inquirySlaStatus(PriceInquiry $inquiry, ?array $sla = null): string
{
if (! in_array($inquiry->status, ['sent', 'partial'], true)) {
return 'na';
}
$sla = $sla ?? $this->slaSettings((int) $inquiry->business_id);
$now = now();
if ($inquiry->due_date && $inquiry->due_date->lt($now->copy()->startOfDay())) {
return 'overdue';
}
$sentAt = $this->resolveSentAt($inquiry);
if (! $sentAt) {
return 'na';
}
$deadline = $sentAt->copy()->addHours($sla['inquiry_sla_hours']);
$warningAt = $sentAt->copy()->addHours(
(int) floor($sla['inquiry_sla_hours'] * ($sla['inquiry_sla_warning_percent'] / 100))
);
$pendingSuppliers = $inquiry->recipients
? $inquiry->recipients->where('recipient_type', 'supplier')->where('status', 'pending')->count()
: 0;
if ($pendingSuppliers === 0) {
return 'ok';
}
if ($now->gte($deadline)) {
return 'breached';
}
if ($now->gte($warningAt)) {
return 'warning';
}
return 'ok';
}
public function snapshot(Business $business): array
{
if (! Schema::hasTable('aex_price_inquiries')) {
return [
'inquiry_breached' => 0,
'inquiry_warning' => 0,
'inquiry_overdue_due' => 0,
'research_overdue' => 0,
'urgent_items' => [],
];
}
$sla = $this->slaSettings($business->id);
$inquiries = PriceInquiry::where('business_id', $business->id)
->whereIn('status', ['sent', 'partial'])
->with(['listing', 'recipients'])
->orderByDesc('updated_at')
->limit(200)
->get();
$breached = 0;
$warning = 0;
$overdueDue = 0;
$urgent = collect();
foreach ($inquiries as $inquiry) {
$status = $this->inquirySlaStatus($inquiry, $sla);
if ($status === 'breached') {
$breached++;
} elseif ($status === 'warning') {
$warning++;
} elseif ($status === 'overdue') {
$overdueDue++;
}
if (in_array($status, ['breached', 'overdue', 'warning'], true)) {
$urgent->push([
'type' => 'inquiry',
'id' => $inquiry->id,
'code' => $inquiry->inquiry_code,
'title' => $inquiry->title,
'listing' => $inquiry->listing?->title,
'sla_status' => $status,
'url' => action([\Modules\AssetExchange\Http\Controllers\PriceInquiryController::class, 'show'], [$inquiry->id]),
]);
}
}
$researchOverdue = 0;
if (Schema::hasTable('aex_research_tasks')) {
$researchOverdue = ResearchTask::where('business_id', $business->id)
->whereNotIn('status', ['completed', 'cancelled'])
->whereDate('due_date', '<', now()->toDateString())
->count();
}
return [
'inquiry_breached' => $breached,
'inquiry_warning' => $warning,
'inquiry_overdue_due' => $overdueDue,
'research_overdue' => $researchOverdue,
'urgent_items' => $urgent->take(15)->values()->all(),
'sla' => $sla,
];
}
public function applyUrgentFilter($query, string $filter, int $businessId)
{
if ($filter !== 'urgent') {
return $query;
}
$sla = $this->slaSettings($businessId);
$ids = PriceInquiry::where('business_id', $businessId)
->whereIn('status', ['sent', 'partial'])
->with('recipients')
->get()
->filter(fn ($inq) => in_array($this->inquirySlaStatus($inq, $sla), ['breached', 'overdue', 'warning'], true))
->pluck('id')
->all();
return $query->whereIn('id', $ids ?: [0]);
}
protected function resolveSentAt(PriceInquiry $inquiry): ?Carbon
{
if ($inquiry->relationLoaded('recipients')) {
$sent = $inquiry->recipients->whereNotNull('sent_at')->min('sent_at');
if ($sent) {
return Carbon::parse($sent);
}
}
return $inquiry->updated_at ? Carbon::parse($inquiry->updated_at) : null;
}
}