resolveEntity($user); if ($entityId === null) { return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403); } $data = json_decode($request->getContent(), true) ?? []; $sessionUuid = trim($data['session_uuid'] ?? ''); if ($sessionUuid === '') { return $this->error(ErrorCodes::ERR_VALIDATION_001, 'session_uuid الزامی است', 422); } $session = $this->sessionRepo->findByUuid($sessionUuid); if ($session === null || !$this->ownsSession($session, $entityType, $entityId)) { return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'مراجعه یافت نشد', 404); } $invoice = $this->invoiceService->createFromSession($session, $entityType, $entityId); return $this->success(['data' => $invoice->toArray()], 201); } #[Route('/api/v1/billing/invoices/{uuid}', methods: ['GET'])] public function show(string $uuid, #[CurrentUser] User $user): JsonResponse { [$entityType, $entityId] = $this->resolveEntity($user); $invoice = $this->invoiceRepo->findByUuid($uuid); if ($invoice === null || $invoice->getEntityType() !== $entityType || $invoice->getEntityId() !== $entityId) { return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'صورتحساب یافت نشد', 404); } return $this->success(['data' => $invoice->toArray()]); } #[Route('/api/v1/billing/invoices/{uuid}/finalize', methods: ['POST'])] public function finalize(string $uuid, #[CurrentUser] User $user): JsonResponse { [$entityType, $entityId] = $this->resolveEntity($user); $invoice = $this->invoiceRepo->findByUuid($uuid); if ($invoice === null || $invoice->getEntityType() !== $entityType || $invoice->getEntityId() !== $entityId) { return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'صورتحساب یافت نشد', 404); } $this->invoiceService->finalize($invoice); return $this->success(['data' => $invoice->toArray()]); } // ── Claims (مطالبات بیمه) ────────────────────────────────────────────────── #[Route('/api/v1/billing/claims', methods: ['POST'])] public function createClaim(Request $request, #[CurrentUser] User $user): JsonResponse { [$entityType, $entityId] = $this->resolveEntity($user); if ($entityId === null) { return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403); } $data = json_decode($request->getContent(), true) ?? []; $invoiceUuid = trim($data['invoice_uuid'] ?? ''); if ($invoiceUuid === '') { return $this->error(ErrorCodes::ERR_VALIDATION_001, 'invoice_uuid الزامی است', 422); } $invoice = $this->invoiceRepo->findByUuid($invoiceUuid); if ($invoice === null || $invoice->getEntityType() !== $entityType || $invoice->getEntityId() !== $entityId) { return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'صورتحساب یافت نشد', 404); } $claims = $this->claimService->createFromInvoice($invoice); return $this->success(['data' => array_map(fn(Claim $c) => $c->toArray(), $claims)], 201); } #[Route('/api/v1/billing/claims', methods: ['GET'])] public function listClaims(Request $request, #[CurrentUser] User $user): JsonResponse { [$entityType, $entityId] = $this->resolveEntity($user); if ($entityId === null) { return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403); } $status = $request->query->get('status') ?: null; $claims = $this->claimRepo->findByTenant($entityType, $entityId, $status); return $this->success(['data' => array_map(fn(Claim $c) => $c->toArray(), $claims)]); } #[Route('/api/v1/billing/claims/{uuid}/{action}', methods: ['POST'], requirements: ['action' => 'submit|approve|reject|pay'])] public function transitionClaim(string $uuid, string $action, Request $request, #[CurrentUser] User $user): JsonResponse { [$entityType, $entityId] = $this->resolveEntity($user); $claim = $this->claimRepo->findByUuid($uuid); if ($claim === null || $claim->getEntityType() !== $entityType || $claim->getEntityId() !== $entityId) { return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'مطالبه یافت نشد', 404); } $data = json_decode($request->getContent(), true) ?? []; $target = match ($action) { 'submit' => Claim::STATUS_SUBMITTED, 'approve' => Claim::STATUS_APPROVED, 'reject' => Claim::STATUS_REJECTED, 'pay' => Claim::STATUS_PAID, }; if ($action === 'reject' && trim($data['reason'] ?? '') === '') { return $this->error(ErrorCodes::ERR_VALIDATION_001, 'دلیل رد الزامی است', 422); } $this->claimService->transition($claim, $target, [ 'approved_rials' => isset($data['approved_rials']) ? (int) $data['approved_rials'] : null, 'paid_rials' => isset($data['paid_rials']) ? (int) $data['paid_rials'] : null, 'reason' => trim($data['reason'] ?? ''), ]); return $this->success(['data' => $claim->toArray()]); } #[Route('/api/v1/billing/reports/insurance-debt', methods: ['GET'])] public function insuranceDebt(#[CurrentUser] User $user): JsonResponse { [$entityType, $entityId] = $this->resolveEntity($user); if ($entityId === null) { return $this->error(ErrorCodes::ERR_FORBIDDEN_001, 'پروفایل یافت نشد', 403); } return $this->success(['data' => $this->claimRepo->debtReport($entityType, $entityId)]); } private function ownsSession($session, string $entityType, int $entityId): bool { $record = $session->getRecord(); return $record->getEntityType() === $entityType && $record->getEntityId() === $entityId; } private function resolveEntity(User $user): array { if ($user->hasRole('ROLE_DOCTOR')) { $doctor = $this->doctorRepo->findByUser($user); return $doctor !== null ? ['doctor', $doctor->getId()] : ['doctor', null]; } if ($user->hasRole('ROLE_CLINIC')) { $clinic = $this->clinicRepo->findByUser($user); return $clinic !== null ? ['clinic', $clinic->getId()] : ['clinic', null]; } return ['unknown', null]; } }