Files
clinicpro/tests/Patient/SessionInsuranceShareTest.php
T
hamed b3a5cda808 Refactor insurance share calculation logic in PatientService
- Consolidated the calculation of patient and insurance shares into a single method using BillingCalculator.
- Introduced new fields in PatientSession to store breakdown of insurance shares and patient share.
- Updated the API responses to include the new fields for consistency across payment, invoice, and claims dashboard.
- Added migration to backfill existing sessions with appropriate values for the new fields.
- Implemented tests to ensure the correctness of the new logic and verify that the breakdown sums to the gross total.
- Redesigned the claims dashboard to provide a more user-friendly overview of patient claims and their statuses.
2026-07-18 22:56:46 +03:30

154 lines
6.2 KiB
PHP

<?php
namespace App\Tests\Patient;
use App\Auth\Entity\User;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceSection;
use App\Doctor\Entity\Doctor;
use App\Insurance\Entity\Insurance;
use App\Insurance\Entity\TenantInsurance;
use App\Insurance\Entity\TenantServiceCoverage;
use App\Insurance\Enum\InsuranceType;
use App\Patient\Entity\PatientRecord;
use App\Tests\ApiTestCase;
/**
* POST /api/v1/patient/{uuid}/session — the insurance share breakdown persisted on
* the session. The payment page, the invoice modal and the claims dashboard all read
* these fields, so they must come out of BillingCalculator and never be re-derived.
*/
class SessionInsuranceShareTest extends ApiTestCase
{
/** @return array{0: User, 1: Doctor, 2: PatientRecord} */
private function tenant(): array
{
$owner = $this->createUser(['ROLE_DOCTOR']);
$doctor = new Doctor($owner, 'دکتر تست');
$this->em->persist($doctor);
$this->em->flush();
$patient = $this->createUser(['ROLE_USER']);
$record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
$this->em->persist($record);
$this->em->flush();
return [$owner, $doctor, $record];
}
private function serviceItem(Doctor $doctor, int $priceRials, bool $insuranceCovered): ServiceItem
{
$section = new ServiceSection('doctor', $doctor->getId(), 'جراحی');
$this->em->persist($section);
$item = new ServiceItem($section, 'جراحی بینی', $priceRials);
$item->setInsuranceCovered($insuranceCovered);
$this->em->persist($item);
$this->em->flush();
return $item;
}
/** Active contract covering $coveragePercent of every covered service. */
private function contract(Doctor $doctor, float $coveragePercent, ?ServiceItem $item = null): Insurance
{
$insurance = new Insurance('تامین اجتماعی', InsuranceType::Basic);
$this->em->persist($insurance);
$this->em->flush();
$contract = new TenantInsurance('doctor', $doctor->getId(), $insurance->getId());
$contract->setCoveragePercent($coveragePercent)->setActive(true);
$this->em->persist($contract);
$this->em->flush();
if ($item !== null) {
$coverage = new TenantServiceCoverage($contract->getId(), $item->getId());
$coverage->setCovered(true)->setCoveragePercent($coveragePercent);
$this->em->persist($coverage);
$this->em->flush();
}
return $insurance;
}
public function testSessionStoresInsuranceAndPatientSharesForACoveredService(): void
{
[$owner, $doctor, $record] = $this->tenant();
$item = $this->serviceItem($doctor, 40_000_000, true);
$insurance = $this->contract($doctor, 70.0, $item);
$res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
'visit_price_rials' => 3_000_000,
'insurance_base_id' => $insurance->getId(),
'services' => [['service_item_uuid' => $item->getUuid(), 'quantity' => 1]],
]);
self::assertSame(201, $this->responseCode());
$session = $res['data'];
// 70% of both the visit and the service is carried by the insurer.
self::assertSame(43_000_000, $session['gross_total_rials']);
self::assertSame(30_100_000, $session['base_insurance_rials']);
self::assertSame(0, $session['supplementary_insurance_rials']);
self::assertSame(12_900_000, $session['patient_share_rials']);
// The payable amount is the patient share, not the gross total.
self::assertSame(12_900_000, $session['final_price_rials']);
self::assertSame(12_900_000, $session['remaining_rials']);
}
public function testTheBreakdownAlwaysSumsBackToTheGrossTotal(): void
{
[$owner, $doctor, $record] = $this->tenant();
$item = $this->serviceItem($doctor, 1_234_567, true);
$insurance = $this->contract($doctor, 33.33, $item);
$res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
'visit_price_rials' => 987_654,
'insurance_base_id' => $insurance->getId(),
'services' => [['service_item_uuid' => $item->getUuid(), 'quantity' => 3]],
]);
self::assertSame(201, $this->responseCode());
$s = $res['data'];
self::assertSame(
$s['gross_total_rials'],
$s['base_insurance_rials'] + $s['supplementary_insurance_rials'] + $s['patient_share_rials'],
);
}
public function testAServiceWithoutCoverageLeavesTheWholeAmountToThePatient(): void
{
[$owner, $doctor, $record] = $this->tenant();
$item = $this->serviceItem($doctor, 40_000_000, false);
$insurance = $this->contract($doctor, 70.0);
$res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
'visit_price_rials' => 0,
'insurance_base_id' => $insurance->getId(),
'services' => [['service_item_uuid' => $item->getUuid(), 'quantity' => 1]],
]);
self::assertSame(201, $this->responseCode());
$s = $res['data'];
self::assertSame(0, $s['base_insurance_rials']);
self::assertSame(40_000_000, $s['patient_share_rials']);
self::assertSame(40_000_000, $s['final_price_rials']);
}
public function testSessionWithoutInsuranceKeepsTheFullAmountAsThePatientShare(): void
{
[$owner, $doctor, $record] = $this->tenant();
$item = $this->serviceItem($doctor, 2_400_000, true);
$res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
'visit_price_rials' => 500_000,
'services' => [['service_item_uuid' => $item->getUuid(), 'quantity' => 1]],
]);
self::assertSame(201, $this->responseCode());
$s = $res['data'];
self::assertSame(2_900_000, $s['gross_total_rials']);
self::assertSame(0, $s['base_insurance_rials']);
self::assertSame(2_900_000, $s['patient_share_rials']);
}
}