238 lines
9.2 KiB
PHP
238 lines
9.2 KiB
PHP
<?php
|
||
|
||
namespace App\Tests\Billing;
|
||
|
||
use App\Auth\Entity\User;
|
||
use App\Billing\Entity\Invoice;
|
||
use App\Doctor\Entity\Doctor;
|
||
use App\Inventory\Entity\InventoryItem;
|
||
use App\Patient\Entity\PatientRecord;
|
||
use App\Patient\Entity\PatientSession;
|
||
use App\Patient\Entity\SessionConsumable;
|
||
use App\Patient\Entity\SessionPayment;
|
||
use App\Tests\ApiTestCase;
|
||
|
||
/**
|
||
* GET /api/v1/billing/invoices/{uuid} — invoice detail enriched with its
|
||
* source session (`session` key: payments, consumables, discount, paid totals).
|
||
*/
|
||
class InvoiceShowSessionTest extends ApiTestCase
|
||
{
|
||
/** @return array{0: User, 1: Doctor} owner user + their doctor profile */
|
||
private function doctor(): array
|
||
{
|
||
$owner = $this->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']);
|
||
}
|
||
}
|