|
@@ -3,34 +3,49 @@
|
|
|
namespace App\Services;
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
use App\Enums\AppointmentStatusEnum;
|
|
use App\Enums\AppointmentStatusEnum;
|
|
|
|
|
+use App\Enums\AppointmentTypeEnum;
|
|
|
use App\Enums\NotificationRecipientEnum;
|
|
use App\Enums\NotificationRecipientEnum;
|
|
|
|
|
+use App\Enums\PartnerAgreementTypeEnum;
|
|
|
|
|
+use App\Enums\UserStatusEnum;
|
|
|
use App\Models\Appointment;
|
|
use App\Models\Appointment;
|
|
|
|
|
+use App\Models\PartnerAgreementService;
|
|
|
|
|
+use App\Models\User;
|
|
|
use Carbon\Carbon;
|
|
use Carbon\Carbon;
|
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
class AppointmentService
|
|
class AppointmentService
|
|
|
{
|
|
{
|
|
|
|
|
+ private const RELATIONS = [
|
|
|
|
|
+ 'user',
|
|
|
|
|
+ 'userDependent',
|
|
|
|
|
+ 'partnerAgreement',
|
|
|
|
|
+ 'partnerAgreementService',
|
|
|
|
|
+ 'exams.partnerAgreementService',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
public function __construct(protected NotificationService $notificationService) {}
|
|
public function __construct(protected NotificationService $notificationService) {}
|
|
|
public function getAll(): Collection
|
|
public function getAll(): Collection
|
|
|
{
|
|
{
|
|
|
- return Appointment::with(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService'])
|
|
|
|
|
|
|
+ return Appointment::with(self::RELATIONS)
|
|
|
->orderBy('date', 'desc')
|
|
->orderBy('date', 'desc')
|
|
|
->get();
|
|
->get();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function getAllByUser(int $userId): Collection
|
|
public function getAllByUser(int $userId): Collection
|
|
|
{
|
|
{
|
|
|
- return Appointment::with(['userDependent', 'partnerAgreement', 'partnerAgreementService'])
|
|
|
|
|
|
|
+ return Appointment::with(self::RELATIONS)
|
|
|
->where('user_id', $userId)
|
|
->where('user_id', $userId)
|
|
|
- ->orderBy('date', 'desc')
|
|
|
|
|
|
|
+ ->orderByRaw('COALESCE(appointments.requested_at, appointments.created_at) DESC')
|
|
|
|
|
+ ->orderBy('id', 'desc')
|
|
|
->get();
|
|
->get();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function getAllByPartnerUser(int $userId): Collection
|
|
public function getAllByPartnerUser(int $userId): Collection
|
|
|
{
|
|
{
|
|
|
- return Appointment::with(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService'])
|
|
|
|
|
|
|
+ return Appointment::with(self::RELATIONS)
|
|
|
->whereHas('partnerAgreement', fn($q) => $q->where('user_id', $userId))
|
|
->whereHas('partnerAgreement', fn($q) => $q->where('user_id', $userId))
|
|
|
->orderBy('date', 'desc')
|
|
->orderBy('date', 'desc')
|
|
|
->get();
|
|
->get();
|
|
@@ -38,12 +53,12 @@ class AppointmentService
|
|
|
|
|
|
|
|
public function findById(int $id): ?Appointment
|
|
public function findById(int $id): ?Appointment
|
|
|
{
|
|
{
|
|
|
- return Appointment::with(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService'])->find($id);
|
|
|
|
|
|
|
+ return Appointment::with(self::RELATIONS)->find($id);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function findByIdForPartnerUser(int $id, int $userId): ?Appointment
|
|
public function findByIdForPartnerUser(int $id, int $userId): ?Appointment
|
|
|
{
|
|
{
|
|
|
- return Appointment::with(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService'])
|
|
|
|
|
|
|
+ return Appointment::with(self::RELATIONS)
|
|
|
->whereHas('partnerAgreement', fn($q) => $q->where('user_id', $userId))
|
|
->whereHas('partnerAgreement', fn($q) => $q->where('user_id', $userId))
|
|
|
->find($id);
|
|
->find($id);
|
|
|
}
|
|
}
|
|
@@ -53,8 +68,30 @@ class AppointmentService
|
|
|
$data['order_number'] = $this->generateOrderNumber();
|
|
$data['order_number'] = $this->generateOrderNumber();
|
|
|
$data['requested_at'] = now();
|
|
$data['requested_at'] = now();
|
|
|
|
|
|
|
|
- return Appointment::create($data)
|
|
|
|
|
- ->load(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService']);
|
|
|
|
|
|
|
+ return Appointment::create($data)->load(self::RELATIONS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function createConsulta(array $data, User $authUser): Appointment
|
|
|
|
|
+ {
|
|
|
|
|
+ $creatingForOther = isset($data['user_id']) && (int) $data['user_id'] !== $authUser->id;
|
|
|
|
|
+
|
|
|
|
|
+ $data['type'] = AppointmentTypeEnum::CONSULTA;
|
|
|
|
|
+
|
|
|
|
|
+ if ($creatingForOther || $authUser->status === UserStatusEnum::ACTIVE) {
|
|
|
|
|
+ $data['status'] = AppointmentStatusEnum::CONFIRMADO;
|
|
|
|
|
+ $data['auto_approved'] = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $data['status'] = AppointmentStatusEnum::PENDENTE;
|
|
|
|
|
+ $data['auto_approved'] = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $appointment = $this->create($data);
|
|
|
|
|
+
|
|
|
|
|
+ if ($creatingForOther) {
|
|
|
|
|
+ $this->notifyCreation($appointment);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $appointment;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function notifyCreation(Appointment $model): void
|
|
public function notifyCreation(Appointment $model): void
|
|
@@ -94,7 +131,7 @@ class AppointmentService
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$model->update($data);
|
|
$model->update($data);
|
|
|
- return $model->fresh(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService']);
|
|
|
|
|
|
|
+ return $model->fresh(self::RELATIONS);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function delete(int $id): bool
|
|
public function delete(int $id): bool
|
|
@@ -108,24 +145,38 @@ class AppointmentService
|
|
|
return $model->delete();
|
|
return $model->delete();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ public function isFrozen(Appointment $appointment): bool
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($appointment->status === AppointmentStatusEnum::RECUSADO) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $appointment->isExame() && $appointment->accepted_at !== null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public function getAdminCounters(): array
|
|
public function getAdminCounters(): array
|
|
|
{
|
|
{
|
|
|
return [
|
|
return [
|
|
|
- 'pendentes' => Appointment::where('status', AppointmentStatusEnum::PENDENTE)->count(),
|
|
|
|
|
- 'aprovados' => Appointment::where('status', AppointmentStatusEnum::CONFIRMADO)->count(),
|
|
|
|
|
- 'recusados' => Appointment::where('status', AppointmentStatusEnum::RECUSADO)->count(),
|
|
|
|
|
|
|
+ 'pendentes' => Appointment::where('status', AppointmentStatusEnum::PENDENTE)->count(),
|
|
|
|
|
+ 'aguardando_aceite' => Appointment::where('status', AppointmentStatusEnum::AGUARDANDO_ACEITE)->count(),
|
|
|
|
|
+ 'aprovados' => Appointment::where('status', AppointmentStatusEnum::CONFIRMADO)->count(),
|
|
|
|
|
+ 'recusados' => Appointment::where('status', AppointmentStatusEnum::RECUSADO)->count(),
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function getAllPaginated(array $filters = [], int $perPage = 10): LengthAwarePaginator
|
|
public function getAllPaginated(array $filters = [], int $perPage = 10): LengthAwarePaginator
|
|
|
{
|
|
{
|
|
|
- $query = Appointment::with(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService'])
|
|
|
|
|
|
|
+ $query = Appointment::with(self::RELATIONS)
|
|
|
->orderBy('requested_at', 'desc');
|
|
->orderBy('requested_at', 'desc');
|
|
|
|
|
|
|
|
if (!empty($filters['status'])) {
|
|
if (!empty($filters['status'])) {
|
|
|
$query->where('status', $filters['status']);
|
|
$query->where('status', $filters['status']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (!empty($filters['type'])) {
|
|
|
|
|
+ $query->where('type', $filters['type']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (!empty($filters['search'])) {
|
|
if (!empty($filters['search'])) {
|
|
|
$term = '%' . mb_strtolower($filters['search']) . '%';
|
|
$term = '%' . mb_strtolower($filters['search']) . '%';
|
|
|
$query->where(function ($q) use ($term) {
|
|
$query->where(function ($q) use ($term) {
|
|
@@ -137,6 +188,8 @@ class AppointmentService
|
|
|
$pq->whereRaw('UNACCENT(LOWER(company_name)) LIKE UNACCENT(?)', [$term]);
|
|
$pq->whereRaw('UNACCENT(LOWER(company_name)) LIKE UNACCENT(?)', [$term]);
|
|
|
})->orWhereHas('partnerAgreementService', function ($sq) use ($term) {
|
|
})->orWhereHas('partnerAgreementService', function ($sq) use ($term) {
|
|
|
$sq->whereRaw('UNACCENT(LOWER(name)) LIKE UNACCENT(?)', [$term]);
|
|
$sq->whereRaw('UNACCENT(LOWER(name)) LIKE UNACCENT(?)', [$term]);
|
|
|
|
|
+ })->orWhereHas('exams.partnerAgreementService', function ($eq) use ($term) {
|
|
|
|
|
+ $eq->whereRaw('UNACCENT(LOWER(name)) LIKE UNACCENT(?)', [$term]);
|
|
|
})->orWhereRaw('UNACCENT(LOWER(order_number)) LIKE UNACCENT(?)', [$term]);
|
|
})->orWhereRaw('UNACCENT(LOWER(order_number)) LIKE UNACCENT(?)', [$term]);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -162,7 +215,7 @@ class AppointmentService
|
|
|
'source' => 'appointment',
|
|
'source' => 'appointment',
|
|
|
'source_id' => $model->id,
|
|
'source_id' => $model->id,
|
|
|
], $model->user_id);
|
|
], $model->user_id);
|
|
|
- return $model->fresh(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService']);
|
|
|
|
|
|
|
+ return $model->fresh(self::RELATIONS);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function reject(int $id): ?Appointment
|
|
public function reject(int $id): ?Appointment
|
|
@@ -177,7 +230,7 @@ class AppointmentService
|
|
|
'source' => 'appointment',
|
|
'source' => 'appointment',
|
|
|
'source_id' => $model->id,
|
|
'source_id' => $model->id,
|
|
|
], $model->user_id);
|
|
], $model->user_id);
|
|
|
- return $model->fresh(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService']);
|
|
|
|
|
|
|
+ return $model->fresh(self::RELATIONS);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function approveByPartner(int $id, int $userId, string $date, string $time): ?Appointment
|
|
public function approveByPartner(int $id, int $userId, string $date, string $time): ?Appointment
|
|
@@ -198,7 +251,7 @@ class AppointmentService
|
|
|
'source' => 'appointment',
|
|
'source' => 'appointment',
|
|
|
'source_id' => $model->id,
|
|
'source_id' => $model->id,
|
|
|
], $model->user_id);
|
|
], $model->user_id);
|
|
|
- return $model->fresh(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService']);
|
|
|
|
|
|
|
+ return $model->fresh(self::RELATIONS);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function rejectByPartner(int $id, int $userId): ?Appointment
|
|
public function rejectByPartner(int $id, int $userId): ?Appointment
|
|
@@ -213,7 +266,153 @@ class AppointmentService
|
|
|
'source' => 'appointment',
|
|
'source' => 'appointment',
|
|
|
'source_id' => $model->id,
|
|
'source_id' => $model->id,
|
|
|
], $model->user_id);
|
|
], $model->user_id);
|
|
|
- return $model->fresh(['user', 'userDependent', 'partnerAgreement', 'partnerAgreementService']);
|
|
|
|
|
|
|
+ return $model->fresh(self::RELATIONS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+ // Exames
|
|
|
|
|
+ // ------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ public function getExamsByPartnerUser(int $userId): Collection
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->examsForPartnerQuery($userId)
|
|
|
|
|
+ ->orderBy('requested_at', 'desc')
|
|
|
|
|
+ ->get();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function findExamForPartnerUser(int $id, int $userId): ?Appointment
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->examsForPartnerQuery($userId)->find($id);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function examsForPartnerQuery(int $userId)
|
|
|
|
|
+ {
|
|
|
|
|
+ return Appointment::with(self::RELATIONS)
|
|
|
|
|
+ ->where('type', AppointmentTypeEnum::EXAME)
|
|
|
|
|
+ ->whereHas('partnerAgreement', fn($q) => $q
|
|
|
|
|
+ ->where('user_id', $userId)
|
|
|
|
|
+ ->where('type', PartnerAgreementTypeEnum::AGREEMENT));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function createExam(array $data): Appointment
|
|
|
|
|
+ {
|
|
|
|
|
+ $serviceIds = $data['service_ids'];
|
|
|
|
|
+ unset($data['service_ids']);
|
|
|
|
|
+
|
|
|
|
|
+ return DB::transaction(function () use ($data, $serviceIds) {
|
|
|
|
|
+ $services = PartnerAgreementService::whereIn('id', $serviceIds)->get();
|
|
|
|
|
+
|
|
|
|
|
+ $data['type'] = AppointmentTypeEnum::EXAME;
|
|
|
|
|
+ $data['status'] = AppointmentStatusEnum::AGUARDANDO_ACEITE;
|
|
|
|
|
+ $data['auto_approved'] = false;
|
|
|
|
|
+ $data['partner_agreement_service_id'] = null;
|
|
|
|
|
+ $data['service_price'] = $services->sum(fn($s) => (float) $this->examPrice($s));
|
|
|
|
|
+
|
|
|
|
|
+ $appointment = $this->create($data);
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($services as $service) {
|
|
|
|
|
+ $appointment->exams()->create([
|
|
|
|
|
+ 'partner_agreement_service_id' => $service->id,
|
|
|
|
|
+ 'service_price' => $this->examPrice($service),
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $this->notifyExamIssued($appointment, $services->count());
|
|
|
|
|
+
|
|
|
|
|
+ return $appointment->fresh(self::RELATIONS);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function acceptExam(int $id, int $userId): ?Appointment
|
|
|
|
|
+ {
|
|
|
|
|
+ $model = $this->pendingExamForUser($id, $userId);
|
|
|
|
|
+
|
|
|
|
|
+ if (!$model) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $model->update([
|
|
|
|
|
+ 'status' => AppointmentStatusEnum::CONFIRMADO,
|
|
|
|
|
+ 'accepted_at' => now(),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $this->notifyPartnerExamDecision($model, accepted: true);
|
|
|
|
|
+
|
|
|
|
|
+ return $model->fresh(self::RELATIONS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function refuseExam(int $id, int $userId, ?string $reason = null): ?Appointment
|
|
|
|
|
+ {
|
|
|
|
|
+ $model = $this->pendingExamForUser($id, $userId);
|
|
|
|
|
+
|
|
|
|
|
+ if (!$model) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $model->update([
|
|
|
|
|
+ 'status' => AppointmentStatusEnum::RECUSADO,
|
|
|
|
|
+ 'refused_at' => now(),
|
|
|
|
|
+ 'refused_by_user_id' => $userId,
|
|
|
|
|
+ 'refusal_reason' => $reason,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $this->notifyPartnerExamDecision($model, accepted: false);
|
|
|
|
|
+
|
|
|
|
|
+ return $model->fresh(self::RELATIONS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function pendingExamForUser(int $id, int $userId): ?Appointment
|
|
|
|
|
+ {
|
|
|
|
|
+ return Appointment::with(self::RELATIONS)
|
|
|
|
|
+ ->where('user_id', $userId)
|
|
|
|
|
+ ->where('type', AppointmentTypeEnum::EXAME)
|
|
|
|
|
+ ->where('status', AppointmentStatusEnum::AGUARDANDO_ACEITE)
|
|
|
|
|
+ ->find($id);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function examPrice(PartnerAgreementService $service): ?string
|
|
|
|
|
+ {
|
|
|
|
|
+ return $service->associate_price ?? $service->price;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function notifyExamIssued(Appointment $model, int $examCount): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $clinic = $model->partnerAgreement?->company_name;
|
|
|
|
|
+ $dependent = $model->userDependent?->name;
|
|
|
|
|
+ $target = $dependent ? "para o dependente {$dependent}" : 'para você';
|
|
|
|
|
+ $plural = $examCount === 1 ? 'exame' : 'exames';
|
|
|
|
|
+
|
|
|
|
|
+ $this->notificationService->createAutoForUser([
|
|
|
|
|
+ 'title' => 'Guia de exames para aprovação',
|
|
|
|
|
+ 'message' => "{$clinic} gerou uma guia com {$examCount} {$plural} {$target}. Acesse seus agendamentos para aceitar ou recusar a guia #{$model->order_number}.",
|
|
|
|
|
+ 'recipient' => NotificationRecipientEnum::ASSOCIADO,
|
|
|
|
|
+ 'source' => 'appointment',
|
|
|
|
|
+ 'source_id' => $model->id,
|
|
|
|
|
+ ], $model->user_id);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function notifyPartnerExamDecision(Appointment $model, bool $accepted): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $partnerUserId = $model->partnerAgreement?->user_id;
|
|
|
|
|
+
|
|
|
|
|
+ if (!$partnerUserId) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $associate = $model->user?->name;
|
|
|
|
|
+
|
|
|
|
|
+ $message = $accepted
|
|
|
|
|
+ ? "{$associate} aceitou a guia de exames #{$model->order_number}."
|
|
|
|
|
+ : "{$associate} recusou a guia de exames #{$model->order_number}. Gere uma nova guia com os ajustes necessários."
|
|
|
|
|
+ . ($model->refusal_reason ? " Motivo: {$model->refusal_reason}" : '');
|
|
|
|
|
+
|
|
|
|
|
+ $this->notificationService->createAutoForUser([
|
|
|
|
|
+ 'title' => $accepted ? 'Guia de exames aceita' : 'Guia de exames recusada',
|
|
|
|
|
+ 'message' => $message,
|
|
|
|
|
+ 'recipient' => NotificationRecipientEnum::PARCEIRO,
|
|
|
|
|
+ 'source' => 'appointment',
|
|
|
|
|
+ 'source_id' => $model->id,
|
|
|
|
|
+ ], $partnerUserId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private function generateOrderNumber(): string
|
|
private function generateOrderNumber(): string
|