Files
radicall/app/Services/Document/CaseService.php
T

185 lines
7.0 KiB
PHP

<?php
namespace App\Services\Document;
use App\Enums\DocumentStatusEnum;
use App\Enums\InvoiceStatusEnum;
use App\Enums\MediaCollectionEnum;
use App\Enums\ReportStatusEnum;
use App\Enums\UserEnum;
use App\Enums\WalletTransactionReasonEnum;
use App\Models\Document;
use App\Models\Medium;
use App\Models\Report;
use App\Models\User;
use App\Services\FileStorageService;
use App\Services\Shift\ShiftScheduleService;
use App\Services\Sms\SmsNotificationService;
use App\Services\Wallet\WalletService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class CaseService
{
protected FileStorageService $fileStorageService;
protected WalletService $walletService;
protected ShiftScheduleService $shiftScheduleService;
protected SmsNotificationService $smsNotificationService;
public function __construct(FileStorageService $fileStorageService, WalletService $walletService, ShiftScheduleService $shiftScheduleService, SmsNotificationService $smsNotificationService)
{
$this->fileStorageService = $fileStorageService;
$this->walletService = $walletService;
$this->shiftScheduleService = $shiftScheduleService;
$this->smsNotificationService = $smsNotificationService;
}
public function pendingForUser(User $user): Builder
{
$statuses = $this->isDoctor($user)
? [DocumentStatusEnum::InProgress->value]
: [DocumentStatusEnum::Registered->value, DocumentStatusEnum::InProgress->value];
return Document::with(['pet', 'requester'])
->whereIn('status', $statuses)
->where(fn ($query) => $this->scopeVisibleTo($query, $user))
->latest('submitted_at');
}
public function canAccess(Document $document, User $user): bool
{
return $document->requester_id === $user->id
|| $document->assignments()->where('user_id', $user->id)->exists();
}
public function canAccessMedium(Medium $medium, User $user): bool
{
$documents = $medium->documents()->get()
->merge($medium->reports()->with('document')->get()->pluck('document'));
return $documents->contains(fn ($document) => $this->canAccess($document, $user));
}
public function canReport(Document $document, User $user): bool
{
return $this->isDoctor($user)
&& $document->status === DocumentStatusEnum::InProgress
&& $document->assignments()->where('user_id', $user->id)->exists();
}
public function submitReport(Document $document, User $doctor, array $data, array $files = []): Report
{
return DB::transaction(function () use ($document, $doctor, $data, $files) {
if (!$this->canReport($document, $doctor)) {
throw ValidationException::withMessages(['document' => 'امکان ثبت گزارش برای این پرونده وجود ندارد.']);
}
$report = $document->reports()->create([
'doctor_id' => $doctor->id,
'status' => ReportStatusEnum::Approved->value,
'technique' => $data['technique'] ?? null,
'finding' => $data['finding'] ?? null,
'diagnose' => $data['diagnose'] ?? null,
'recommendation' => $data['recommendation'] ?? null,
'submitted_at' => now(),
]);
foreach ($files as $file) {
$medium = $this->fileStorageService->storeFile($file);
$report->media()->attach($medium->id, ['collection' => MediaCollectionEnum::Attachments->value]);
}
$document->update([
'status' => DocumentStatusEnum::Completed->value,
'finalized_at' => now(),
'finalized_by' => $doctor->id,
]);
$this->smsNotificationService->reportReady($document);
return $report;
});
}
public function reject(Document $document, User $doctor, string $reason): Document
{
return DB::transaction(function () use ($document, $doctor, $reason) {
if (!$this->canReport($document, $doctor)) {
throw ValidationException::withMessages(['document' => 'امکان رد این پرونده وجود ندارد.']);
}
$document->update([
'status' => DocumentStatusEnum::Rejected->value,
'finalized_at' => now(),
'finalized_by' => $doctor->id,
'rejection_reason' => $reason,
]);
$invoice = $document->invoice;
if ($invoice && $invoice->status === InvoiceStatusEnum::Paid) {
$invoice->update(['status' => InvoiceStatusEnum::Canceled->value]);
$this->walletService->deposit($document->requester, $invoice->amount, WalletTransactionReasonEnum::Refund);
}
return $document;
});
}
public function statsForUser(User $user): array
{
$monthStart = now()->startOfMonth();
$isDoctor = $this->isDoctor($user);
$reviewedQuery = Document::where('status', DocumentStatusEnum::Completed->value)
->where('finalized_at', '>=', $monthStart);
if ($isDoctor) {
$reviewedQuery->where('finalized_by', $user->id);
} else {
$reviewedQuery->where('requester_id', $user->id);
}
$amountQuery = $isDoctor
? Document::where('documents.finalized_by', $user->id)
->where('documents.status', DocumentStatusEnum::Completed->value)
->where('documents.finalized_at', '>=', $monthStart)
->join('invoices', 'invoices.document_id', '=', 'documents.id')
->where('invoices.status', InvoiceStatusEnum::Paid->value)
: Document::where('documents.requester_id', $user->id)
->join('invoices', 'invoices.document_id', '=', 'documents.id')
->where('invoices.status', InvoiceStatusEnum::Paid->value)
->where('invoices.paid_at', '>=', $monthStart);
$nextShift = $isDoctor ? $this->shiftScheduleService->nextShiftStart($user, now()) : null;
return [
'pending_cases' => $this->pendingForUser($user)->count(),
'monthly_reviewed' => $reviewedQuery->count(),
'monthly_amount' => (int) $amountQuery->sum('invoices.amount'),
'next_shift_hours' => $nextShift ? (int) ceil(now()->diffInMinutes($nextShift, true) / 60) : null,
'is_doctor' => $isDoctor,
];
}
protected function scopeVisibleTo($query, User $user): void
{
if ($this->isDoctor($user)) {
$query->whereHas('assignments', fn ($assignmentQuery) => $assignmentQuery->where('user_id', $user->id));
return;
}
$query->where('requester_id', $user->id)
->orWhereHas('assignments', fn ($assignmentQuery) => $assignmentQuery->where('user_id', $user->id));
}
public function isDoctor(User $user): bool
{
return $user->level === UserEnum::Doctor->value;
}
}