172 lines
5.4 KiB
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;
|
|
}
|
|
}
|