Files
clinicpro/tests/Billing/InvoiceShowSessionTest.php
T
hamedandClaude Opus 4.8 0e0e4ebe71 feat: enrich invoice summary with session payments, consumables and discount
Port the remaining gap of tauri /files/invoice-summary into the admin
invoice summary: GET /api/v1/billing/invoices/{uuid} now includes a
'session' key (full PatientSession payload) so InvoiceSummaryModal can
render the consumables table, the itemized payments table (method,
amount, date-time, recorder) and real discount/paid/remaining figures
instead of heuristics. Invoices without a source session keep the
previous behavior (session: null).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 14:38:38 +03:30

172 lines
6.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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']);
}
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']);
}
}