ultimatepos/Modules/AssetExchange/Services/AssetExchangeNotificationService.php

278 lines
11 KiB
PHP

<?php
namespace Modules\AssetExchange\Services;
use App\Business;
use App\Contact;
use App\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Schema;
use Modules\AssetExchange\Models\Notification;
use Modules\AssetExchange\Models\PriceInquiry;
class AssetExchangeNotificationService
{
public function notify(
Business $business,
string $type,
string $title,
string $message,
?User $user = null,
?int $contactId = null,
?array $data = null,
string $priority = 'normal',
int $escalationLevel = 0
): ?Notification {
if (! Schema::hasTable('aex_notifications')) {
return null;
}
$notification = Notification::create([
'business_id' => $business->id,
'user_id' => $user?->id,
'contact_id' => $contactId,
'type' => $type,
'priority' => $priority,
'escalation_level' => $escalationLevel,
'title' => $title,
'message' => $message,
'data' => $data,
'channels' => ['in_app'],
'scheduled_at' => now(),
'sent_at' => now(),
'escalated_at' => $escalationLevel > 0 ? now() : null,
]);
try {
$recipientEmail = $user?->email;
if (! $recipientEmail && $contactId) {
$recipientEmail = Contact::where('business_id', $business->id)->where('id', $contactId)->value('email');
}
if (! empty($recipientEmail) && ! empty(config('mail.default'))) {
Mail::raw($message, function ($mail) use ($recipientEmail, $title) {
$mail->to($recipientEmail)->subject($title);
});
}
} catch (\Throwable) {
// Keep notification flow non-blocking when mail is unavailable.
}
return $notification;
}
public function notifyInquiryRecipients(Business $business, PriceInquiry $inquiry): Collection
{
$sent = collect();
$inquiry->loadMissing('listing', 'recipients');
foreach ($inquiry->recipients as $recipient) {
if ($recipient->recipient_type === 'supplier' && $recipient->contact_id) {
$users = User::where('business_id', $business->id)
->where('crm_contact_id', $recipient->contact_id)
->get();
if ($users->isEmpty()) {
$sent->push($this->notify(
$business,
'aex_inquiry_assigned',
'استعلام قیمت جدید',
'یک استعلام قیمت جدید برای شما ثبت شده است: '.$inquiry->inquiry_code,
null,
$recipient->contact_id,
[
'inquiry_id' => $inquiry->id,
'url' => route('supplier.asset_exchange.inquiries.show', [$inquiry->id]),
],
'high'
));
} else {
foreach ($users as $user) {
$sent->push($this->notify(
$business,
'aex_inquiry_assigned',
'استعلام قیمت جدید',
'یک استعلام قیمت جدید برای شما ثبت شده است: '.$inquiry->inquiry_code,
$user,
$recipient->contact_id,
[
'inquiry_id' => $inquiry->id,
'url' => route('supplier.asset_exchange.inquiries.show', [$inquiry->id]),
],
'high'
));
}
}
}
if ($recipient->recipient_type === 'internal_user' && $recipient->user_id) {
$user = User::where('business_id', $business->id)->find($recipient->user_id);
if ($user) {
$sent->push($this->notify(
$business,
'aex_internal_research_assigned',
'وظیفه استعلام/تحقیق داخلی',
'برای شما یک استعلام/تحقیق داخلی در AssetExchange اختصاص یافت: '.$inquiry->inquiry_code,
$user,
null,
[
'inquiry_id' => $inquiry->id,
'url' => action([\Modules\AssetExchange\Http\Controllers\PriceInquiryController::class, 'show'], [$inquiry->id]),
],
'high'
));
}
}
}
return $sent->filter();
}
public function escalateOverdueItems(Business $business): int
{
$count = 0;
if (\Illuminate\Support\Facades\Schema::hasTable('aex_price_inquiries')) {
$slaService = app(InquirySlaService::class);
$sla = $slaService->slaSettings($business->id);
$inquiries = PriceInquiry::where('business_id', $business->id)
->whereIn('status', ['sent', 'partial'])
->with(['listing', 'recipients'])
->get();
foreach ($inquiries as $inq) {
$slaStatus = $slaService->inquirySlaStatus($inq, $sla);
if (! in_array($slaStatus, ['breached', 'overdue'], true)) {
continue;
}
$exists = Notification::where('business_id', $business->id)
->where('type', 'aex_inquiry_overdue')
->where('data->inquiry_id', $inq->id)
->where('escalation_level', '>=', 1)
->exists();
if ($exists) {
continue;
}
$message = $slaStatus === 'breached'
? 'استعلام '.$inq->inquiry_code.' از مهلت پاسخ SLA عبور کرده و هنوز پاسخ تأمین‌کننده ثبت نشده است.'
: 'استعلام '.$inq->inquiry_code.' از موعد پاسخ عبور کرده است.';
$this->notify(
$business,
'aex_inquiry_overdue',
'استعلام قیمت دیرکرد دارد',
$message,
null,
null,
[
'inquiry_id' => $inq->id,
'sla_status' => $slaStatus,
'url' => action([\Modules\AssetExchange\Http\Controllers\PriceInquiryController::class, 'show'], [$inq->id]),
],
'critical',
1
);
$count++;
}
}
if (\Illuminate\Support\Facades\Schema::hasTable('aex_research_tasks')) {
$overdueTasks = \Modules\AssetExchange\Models\ResearchTask::where('business_id', $business->id)
->whereNotIn('status', ['completed', 'cancelled'])
->whereDate('due_date', '<', now()->toDateString())
->with('researchCase')
->get();
foreach ($overdueTasks as $task) {
$exists = Notification::where('business_id', $business->id)
->where('type', 'aex_research_task_overdue')
->where('data->research_task_id', $task->id)
->where('escalation_level', '>=', 1)
->exists();
if ($exists) {
continue;
}
$this->notify(
$business,
'aex_research_task_overdue',
'وظیفه تحقیق عقب‌افتاده',
'وظیفه «'.$task->title.'» از موعد انجام عبور کرده است.',
$task->assignee,
null,
[
'research_task_id' => $task->id,
'research_case_id' => $task->research_case_id,
'url' => action([\Modules\AssetExchange\Http\Controllers\ResearchCaseController::class, 'workspace'], [$task->research_case_id]),
],
'high',
1
);
$count++;
}
}
return $count;
}
public function dailyDigest(Business $business): array
{
$summary = [
'overdue_inquiries' => 0,
'pending_inquiries' => 0,
'overdue_tasks' => 0,
'open_research_cases' => 0,
];
if (\Illuminate\Support\Facades\Schema::hasTable('aex_price_inquiries')) {
$slaService = app(InquirySlaService::class);
$slaSnapshot = $slaService->snapshot($business);
$summary['overdue_inquiries'] = ($slaSnapshot['inquiry_breached'] ?? 0) + ($slaSnapshot['inquiry_overdue_due'] ?? 0);
$summary['pending_inquiries'] = PriceInquiry::where('business_id', $business->id)
->whereIn('status', ['draft', 'sent', 'partial'])
->count();
}
if (\Illuminate\Support\Facades\Schema::hasTable('aex_research_tasks')) {
$summary['overdue_tasks'] = \Modules\AssetExchange\Models\ResearchTask::where('business_id', $business->id)
->whereNotIn('status', ['completed', 'cancelled'])
->whereDate('due_date', '<', now()->toDateString())
->count();
}
if (\Illuminate\Support\Facades\Schema::hasTable('aex_research_cases')) {
$summary['open_research_cases'] = \Modules\AssetExchange\Models\ResearchCase::where('business_id', $business->id)
->whereIn('status', ['open', 'in_progress', 'review'])
->count();
}
$message = 'خلاصه روزانه AssetExchange — '
.'استعلام معوق: '.$summary['overdue_inquiries']
.' | استعلام باز: '.$summary['pending_inquiries']
.' | وظیفه تحقیق معوق: '.$summary['overdue_tasks']
.' | پرونده تحقیق باز: '.$summary['open_research_cases'];
$admins = User::where('business_id', $business->id)->where('is_admin', 1)->get();
foreach ($admins as $admin) {
$this->notify(
$business,
'aex_daily_digest',
'گزارش روزانه AssetExchange',
$message,
$admin,
null,
['summary' => $summary, 'url' => action([\Modules\AssetExchange\Http\Controllers\DashboardController::class, 'index'])],
'normal'
);
}
return $summary;
}
}