feat: add withdraw request functionality and UI components
This commit is contained in:
@@ -3,25 +3,167 @@
|
||||
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', [
|
||||
DocumentStatusEnum::Registered->value,
|
||||
DocumentStatusEnum::InProgress->value,
|
||||
])
|
||||
->where(function ($query) use ($user) {
|
||||
$query->where('requester_id', $user->id)
|
||||
->orWhereHas('assignments', function ($assignmentQuery) use ($user) {
|
||||
$assignmentQuery->where('user_id', $user->id);
|
||||
});
|
||||
})
|
||||
->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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user