createUser(['ROLE_DOCTOR']); $doctor = new Doctor($owner, 'دکتر تست'); $this->em->persist($doctor); $this->em->flush(); return [$owner, $doctor]; } private function patientRecord(Doctor $doctor): PatientRecord { $patient = $this->createUser(['ROLE_USER']); $record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId()); $this->em->persist($record); $this->em->flush(); return $record; } private function setField(object $obj, string $prop, mixed $value): void { $ref = new \ReflectionProperty($obj, $prop); $ref->setAccessible(true); $ref->setValue($obj, $value); } public function testShowIncludesSessionPaymentsConsumablesAndDiscount(): void { [$owner, $doctor] = $this->doctor(); $record = $this->patientRecord($doctor); $session = new PatientSession($record); $session->setServicesTotalRials(2_400_000) ->setFinalPriceRials(2_200_000) ->setDiscount('amount', 200_000, 200_000) ->setSessionAt(1_700_000_000) ->setPaidAt(1_700_100_000); $this->em->persist($session); $this->em->flush(); $payment = new SessionPayment($session, 'wallet', 1_500_000, 1_700_100_000); $payment->setCreatedByName('منشی تست'); $this->em->persist($payment); $session->addPayment($payment); $item = new InventoryItem('doctor', $doctor->getId(), 'عینک'); $item->setPrice(20_000); $this->em->persist($item); $this->em->flush(); $consumable = new SessionConsumable($session, $item, 2); $this->em->persist($consumable); $session->addConsumable($consumable); $this->em->flush(); $invoice = new Invoice('doctor', $doctor->getId()); $invoice->setPatientSessionId($session->getId()) ->setPatientRecordId($record->getId()); $this->em->persist($invoice); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/billing/invoices/' . $invoice->getUuid(), $owner); self::assertSame(200, $this->responseCode()); $data = $res['data']['data']; self::assertSame($invoice->getUuid(), $data['uuid']); self::assertIsArray($data['session']); $s = $data['session']; self::assertSame($session->getUuid(), $s['uuid']); self::assertSame(1_700_000_000, $s['session_at']); self::assertSame(1_700_100_000, $s['paid_at']); self::assertSame(2_400_000, $s['services_total_rials']); self::assertSame(2_200_000, $s['final_price_rials']); self::assertSame(200_000, $s['discount_rials']); // payments — real rows, not heuristics self::assertCount(1, $s['payments']); self::assertSame('wallet', $s['payments'][0]['method']); self::assertSame(1_500_000, $s['payments'][0]['amount_rials']); self::assertSame('منشی تست', $s['payments'][0]['created_by_name']); self::assertSame(1_500_000, $s['paid_total_rials']); // consumables — snapshot price × quantity self::assertCount(1, $s['consumables']); self::assertSame('عینک', $s['consumables'][0]['item_name']); self::assertSame(2, $s['consumables'][0]['quantity']); self::assertSame(40_000, $s['consumables'][0]['line_total_rials']); self::assertSame(40_000, $s['consumables_total_rials']); } /** * ستون «ثبت‌کننده» باید نامِ زندهٔ کاربر و شناسه‌های لینکش را بدهد؛ عکسِ * `created_by_name` ممکن است شمارهٔ موبایلِ زمانی باشد که کاربر پروفایل نداشته. */ public function testShowResolvesRecorderIdentityFromUser(): void { [$owner, $doctor] = $this->doctor(); $record = $this->patientRecord($doctor); $recorderDoctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر ثبت‌کننده'); $this->em->persist($recorderDoctor); $recorder = $recorderDoctor->getUser(); $recorder->setRealName('دکتر ثبت‌کننده'); $session = new PatientSession($record); $this->em->persist($session); $this->em->flush(); $payment = new SessionPayment($session, 'cash', 500_000, 1_700_100_000); // عکسِ کهنه: همان چیزی که هنگام ثبت ذخیره شده بود. $payment->setCreatedByName($recorder->getMobileNumber())->setCreatedBy($recorder); $this->em->persist($payment); $session->addPayment($payment); $invoice = new Invoice('doctor', $doctor->getId()); $invoice->setPatientSessionId($session->getId()); $this->em->persist($invoice); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/billing/invoices/' . $invoice->getUuid(), $owner); self::assertSame(200, $this->responseCode()); $row = $res['data']['data']['session']['payments'][0]; self::assertSame('دکتر ثبت‌کننده', $row['created_by_name']); self::assertSame($recorder->getUuid(), $row['created_by']['user_uuid']); self::assertSame('doctor', $row['created_by']['role']); self::assertSame($recorderDoctor->getUuid(), $row['created_by']['doctor_uuid']); } /** کاربرِ حذف‌شده: هویتی نیست، پس همان عکسِ ذخیره‌شده می‌ماند. */ public function testShowKeepsSnapshotNameWhenRecorderUnknown(): void { [$owner, $doctor] = $this->doctor(); $record = $this->patientRecord($doctor); $session = new PatientSession($record); $this->em->persist($session); $this->em->flush(); $payment = new SessionPayment($session, 'cash', 100_000, 1_700_100_000); $payment->setCreatedByName('منشی قدیمی'); $this->em->persist($payment); $session->addPayment($payment); $invoice = new Invoice('doctor', $doctor->getId()); $invoice->setPatientSessionId($session->getId()); $this->em->persist($invoice); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/billing/invoices/' . $invoice->getUuid(), $owner); $row = $res['data']['data']['session']['payments'][0]; self::assertSame('منشی قدیمی', $row['created_by_name']); self::assertNull($row['created_by']); } public function testShowSessionNullWhenInvoiceHasNoSession(): void { [$owner, $doctor] = $this->doctor(); $record = $this->patientRecord($doctor); $invoice = new Invoice('doctor', $doctor->getId()); $invoice->setPatientRecordId($record->getId()); $this->em->persist($invoice); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/billing/invoices/' . $invoice->getUuid(), $owner); self::assertSame(200, $this->responseCode()); self::assertNull($res['data']['data']['session']); } public function testShowNotFoundForUnknownUuid(): void { [$owner] = $this->doctor(); $this->authJson('GET', '/api/v1/billing/invoices/00000000-0000-0000-0000-000000000000', $owner); self::assertSame(404, $this->responseCode()); } public function testShowNotFoundForOtherTenant(): void { [, $doctor] = $this->doctor(); $invoice = new Invoice('doctor', $doctor->getId()); $this->em->persist($invoice); $this->em->flush(); [$other] = $this->doctor(); $this->authJson('GET', '/api/v1/billing/invoices/' . $invoice->getUuid(), $other); self::assertSame(404, $this->responseCode()); } public function testShowSessionEmptyPaymentsAndConsumables(): void { [$owner, $doctor] = $this->doctor(); $record = $this->patientRecord($doctor); $session = new PatientSession($record); $this->em->persist($session); $this->em->flush(); $invoice = new Invoice('doctor', $doctor->getId()); $invoice->setPatientSessionId($session->getId()); $this->em->persist($invoice); $this->em->flush(); $res = $this->authJson('GET', '/api/v1/billing/invoices/' . $invoice->getUuid(), $owner); self::assertSame(200, $this->responseCode()); $s = $res['data']['data']['session']; self::assertSame([], $s['payments']); self::assertSame([], $s['consumables']); self::assertSame(0, $s['paid_total_rials']); self::assertSame(0, $s['discount_rials']); } }