170 lines
6.4 KiB
PHP
170 lines
6.4 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\Report;
|
|
use App\Models\User;
|
|
use App\Services\FileStorageService;
|
|
use App\Services\Shift\ShiftScheduleService;
|
|
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;
|
|
|
|
public function __construct(FileStorageService $fileStorageService, WalletService $walletService, ShiftScheduleService $shiftScheduleService)
|
|
{
|
|
$this->fileStorageService = $fileStorageService;
|
|
$this->walletService = $walletService;
|
|
$this->shiftScheduleService = $shiftScheduleService;
|
|
}
|
|
|
|
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 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,
|
|
]);
|
|
|
|
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));
|
|
}
|
|
|
|
protected function isDoctor(User $user): bool
|
|
{
|
|
return $user->level === UserEnum::Doctor->value;
|
|
}
|
|
}
|