Add tests and implementation for ServiceDetailPage and PriceInput components
- Implement PriceInput component tests to validate Persian and Arabic numeral handling, input formatting, and controlled behavior. - Create ServiceDetailPage component with detailed service information, including pricing, insurance coverage, and editing capabilities. - Add API tests for service item detail retrieval and coverage synchronization with insurance contracts. - Ensure proper error handling and user feedback for service item retrieval and coverage management.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\ClinicService;
|
||||
|
||||
use App\Auth\Entity\User;
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\ClinicService\Entity\ServiceSection;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* GET /api/v1/service-item/{uuid} backs the dedicated service detail page, which
|
||||
* must survive a hard refresh — hence a single-item fetch instead of filtering
|
||||
* the full list client-side.
|
||||
*/
|
||||
class ServiceItemDetailApiTest extends ApiTestCase
|
||||
{
|
||||
/** @return array{0: User, 1: ServiceItem} */
|
||||
private function makeDoctorWithItem(string $sectionName = 'تزریقات'): array
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر تست جزئیات');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$section = new ServiceSection('doctor', $doctor->getId(), $sectionName);
|
||||
$item = new ServiceItem($section, 'سرم ۵۰۰cc');
|
||||
$item->setPriceRials(850_000)->setDurationMinutes(30)->setBookable(true);
|
||||
$this->em->persist($section);
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
|
||||
return [$owner, $item];
|
||||
}
|
||||
|
||||
public function testReturnsTheItemWithEverythingTheDetailPageNeeds(): void
|
||||
{
|
||||
[$owner, $item] = $this->makeDoctorWithItem();
|
||||
|
||||
$body = $this->authJson('GET', '/api/v1/service-item/' . $item->getUuid(), $owner);
|
||||
|
||||
$this->assertSame(200, $this->responseCode());
|
||||
$row = $body['data'];
|
||||
$this->assertSame($item->getUuid(), $row['uuid']);
|
||||
$this->assertSame('سرم ۵۰۰cc', $row['name']);
|
||||
$this->assertSame('تزریقات', $row['section_name'], 'breadcrumb به نام بخش نیاز دارد');
|
||||
$this->assertSame($item->getSection()->getUuid(), $row['section_uuid']);
|
||||
$this->assertSame(850_000, $row['price_rials']);
|
||||
$this->assertSame(30, $row['duration_minutes']);
|
||||
$this->assertTrue($row['bookable']);
|
||||
$this->assertIsInt($row['created_at']);
|
||||
$this->assertIsInt($row['updated_at']);
|
||||
$this->assertArrayHasKey('staff_members', $row);
|
||||
$this->assertArrayHasKey('insurance_covered', $row);
|
||||
}
|
||||
|
||||
public function testUnknownUuidReturns404(): void
|
||||
{
|
||||
[$owner] = $this->makeDoctorWithItem();
|
||||
|
||||
$this->authJson('GET', '/api/v1/service-item/00000000-0000-4000-8000-000000000000', $owner);
|
||||
|
||||
$this->assertSame(404, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testAnotherTenantCannotReadTheItem(): void
|
||||
{
|
||||
[, $item] = $this->makeDoctorWithItem();
|
||||
|
||||
$stranger = $this->createUser(['ROLE_DOCTOR']);
|
||||
$this->em->persist(new Doctor($stranger, 'دکتر غریبه'));
|
||||
$this->em->flush();
|
||||
|
||||
$this->authJson('GET', '/api/v1/service-item/' . $item->getUuid(), $stranger);
|
||||
|
||||
$this->assertSame(404, $this->responseCode(), 'وجود سرویس نباید به tenant دیگر لو برود');
|
||||
}
|
||||
|
||||
public function testRequiresAuthentication(): void
|
||||
{
|
||||
[, $item] = $this->makeDoctorWithItem();
|
||||
|
||||
$this->client->request('GET', '/api/v1/service-item/' . $item->getUuid());
|
||||
|
||||
$this->assertSame(401, $this->responseCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Insurance;
|
||||
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\ClinicService\Entity\ServiceSection;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Insurance\Entity\TenantInsurance;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* The «این خدمت شامل بیمه میشود» switch was removed from the service form, so
|
||||
* ServiceItem::insuranceCovered is no longer set by hand. Saving a coverage row
|
||||
* is now the single source of truth and must keep the flag in sync — session
|
||||
* pricing (CreateStep) reads it.
|
||||
*/
|
||||
class ServiceCoverageSyncsItemFlagTest extends ApiTestCase
|
||||
{
|
||||
/** @return array{0: \App\Auth\Entity\User, 1: TenantInsurance, 2: ServiceItem} */
|
||||
private function makeDoctorContractAndItem(): array
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر تست همگامسازی');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$contract = new TenantInsurance(TenantInsurance::TYPE_DOCTOR, $doctor->getId(), 1);
|
||||
$section = new ServiceSection(TenantInsurance::TYPE_DOCTOR, $doctor->getId(), 'بخش');
|
||||
$this->em->persist($contract);
|
||||
$this->em->persist($section);
|
||||
$this->em->flush();
|
||||
|
||||
$item = new ServiceItem($section, 'سرم ۵۰۰cc');
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
|
||||
return [$owner, $contract, $item];
|
||||
}
|
||||
|
||||
/** EntityManager بین requestها clear میشود، پس پرچم باید دوباره از DB خوانده شود. */
|
||||
private function isInsured(string $uuid): bool
|
||||
{
|
||||
return $this->em->getRepository(ServiceItem::class)
|
||||
->findOneBy(['uuid' => $uuid])
|
||||
->isInsuranceCovered();
|
||||
}
|
||||
|
||||
private function putCoverage(
|
||||
\App\Auth\Entity\User $owner,
|
||||
TenantInsurance $contract,
|
||||
ServiceItem $item,
|
||||
bool $covered,
|
||||
): void {
|
||||
$this->authJson(
|
||||
'PUT',
|
||||
'/api/v1/billing/tenant-insurances/' . $contract->getUuid() . '/service-coverage',
|
||||
$owner,
|
||||
['service_item_uuid' => $item->getUuid(), 'covered' => $covered, 'coverage_percent' => 70],
|
||||
);
|
||||
}
|
||||
|
||||
public function testSavingCoveredCoverageMarksItemAsInsured(): void
|
||||
{
|
||||
[$owner, $contract, $item] = $this->makeDoctorContractAndItem();
|
||||
$this->assertFalse($item->isInsuranceCovered(), 'یک خدمت تازه نباید تحت پوشش باشد');
|
||||
|
||||
$this->putCoverage($owner, $contract, $item, true);
|
||||
|
||||
$this->assertSame(200, $this->responseCode());
|
||||
$this->assertTrue($this->isInsured($item->getUuid()));
|
||||
}
|
||||
|
||||
public function testRemovingTheOnlyCoverageClearsTheFlag(): void
|
||||
{
|
||||
[$owner, $contract, $item] = $this->makeDoctorContractAndItem();
|
||||
$uuid = $item->getUuid();
|
||||
|
||||
$this->putCoverage($owner, $contract, $item, true);
|
||||
$this->assertTrue($this->isInsured($uuid));
|
||||
|
||||
$this->putCoverage($owner, $contract, $item, false);
|
||||
|
||||
$this->assertSame(200, $this->responseCode());
|
||||
$this->assertFalse($this->isInsured($uuid));
|
||||
}
|
||||
|
||||
public function testFlagStaysSetWhileAnotherContractStillCoversTheItem(): void
|
||||
{
|
||||
[$owner, $first, $item] = $this->makeDoctorContractAndItem();
|
||||
$uuid = $item->getUuid();
|
||||
|
||||
$second = new TenantInsurance(TenantInsurance::TYPE_DOCTOR, $first->getEntityId(), 2);
|
||||
$this->em->persist($second);
|
||||
$this->em->flush();
|
||||
|
||||
$this->putCoverage($owner, $first, $item, true);
|
||||
$this->putCoverage($owner, $second, $item, true);
|
||||
|
||||
$this->putCoverage($owner, $first, $item, false);
|
||||
|
||||
$this->assertTrue($this->isInsured($uuid), 'قرارداد دوم هنوز پوشش دارد');
|
||||
}
|
||||
|
||||
public function testUnknownServiceItemIsRejected(): void
|
||||
{
|
||||
[$owner, $contract] = $this->makeDoctorContractAndItem();
|
||||
|
||||
$this->authJson(
|
||||
'PUT',
|
||||
'/api/v1/billing/tenant-insurances/' . $contract->getUuid() . '/service-coverage',
|
||||
$owner,
|
||||
['service_item_uuid' => 'no-such-uuid', 'covered' => true],
|
||||
);
|
||||
|
||||
$this->assertSame(422, $this->responseCode());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user