feat(services): match Figma خدمات page in settings shell + multi-staff
Render the clinic services page (sections → services) inside the settings sub-navigation shell (SettingsLayout, "خدمات" active) to match the Figma settings design. Restyle section cards to show the service count and a status toggle with edit/delete actions, and service cards with labelled price/duration and personnel chips. A service can now have multiple personnel: add an additive many-to-many ServiceItem↔ClinicStaff (staffMembers, EAGER) while keeping the legacy single `staff` column mirrored for backward compatibility. Endpoints accept `staff_uuids[]` (falling back to the legacy single `staff_uuid`) and return `staff_members[]`; the section list now reports `items_count`. Backfill-safe: pre-migration rows fall back to the single staff in toArray. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\ClinicService;
|
||||
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\ClinicService\Entity\ServiceSection;
|
||||
use App\ClinicService\Repository\ServiceItemRepository;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Staff\Entity\ClinicStaff;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* A service can carry multiple personnel (staffMembers). toArray must expose
|
||||
* them as `staff_members`, keep the legacy single `staff` (first member) for
|
||||
* back-compat, and the section list must report `items_count`.
|
||||
*/
|
||||
class ServiceItemMultiStaffTest extends ApiTestCase
|
||||
{
|
||||
public function testMultiStaffToArrayAndSectionCount(): void
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$s1 = new ClinicStaff('doctor', $doctor->getId(), 'مریم امینی');
|
||||
$s2 = new ClinicStaff('doctor', $doctor->getId(), 'سحر رحمانی');
|
||||
$this->em->persist($s1);
|
||||
$this->em->persist($s2);
|
||||
$this->em->flush();
|
||||
|
||||
$section = new ServiceSection('doctor', $doctor->getId(), 'کندلا');
|
||||
$item = new ServiceItem($section, 'فول بادی', 3_500_000);
|
||||
$item->setStaffMembers([$s1, $s2]); // staff already managed (as in the controller)
|
||||
$this->em->persist($section);
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
$sectionUuid = $section->getUuid();
|
||||
$itemUuid = $item->getUuid();
|
||||
$this->em->clear();
|
||||
|
||||
/** @var ServiceItemRepository $repo */
|
||||
$repo = static::getContainer()->get(ServiceItemRepository::class);
|
||||
$reloaded = $repo->findByUuid($itemUuid); // uuid — db_test is never reset
|
||||
self::assertNotNull($reloaded);
|
||||
self::assertCount(2, $reloaded->getStaffMembers());
|
||||
|
||||
$arr = $reloaded->toArray();
|
||||
self::assertCount(2, $arr['staff_members']);
|
||||
// primary (legacy single) mirrors the first member
|
||||
self::assertSame('مریم امینی', $arr['staff']['full_name']);
|
||||
|
||||
// batch count
|
||||
$sectionEntity = $reloaded->getSection();
|
||||
$counts = $repo->countBySections([$sectionEntity]);
|
||||
self::assertSame(1, $counts[$sectionUuid]);
|
||||
|
||||
// endpoint exposes items_count
|
||||
$resp = $this->authJson('GET', '/api/v1/service-sections', $owner);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
$row = $resp['data'][0] ?? [];
|
||||
self::assertSame(1, $row['items_count'] ?? null);
|
||||
}
|
||||
|
||||
public function testCreateAndUpdateItemWithMultipleStaffViaApi(): void
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$a = new ClinicStaff('doctor', $doctor->getId(), 'الف');
|
||||
$b = new ClinicStaff('doctor', $doctor->getId(), 'ب');
|
||||
$c = new ClinicStaff('doctor', $doctor->getId(), 'ج');
|
||||
$section = new ServiceSection('doctor', $doctor->getId(), 'بخش');
|
||||
foreach ([$a, $b, $c, $section] as $e) { $this->em->persist($e); }
|
||||
$this->em->flush();
|
||||
|
||||
// create with two staff members
|
||||
$created = $this->authJson('POST', '/api/v1/service-item', $owner, [
|
||||
'section_uuid' => $section->getUuid(),
|
||||
'name' => 'سرویس چندنفره',
|
||||
'price_rials' => 1_000,
|
||||
'staff_uuids' => [$a->getUuid(), $b->getUuid()],
|
||||
]);
|
||||
self::assertSame(201, $this->responseCode());
|
||||
self::assertCount(2, $created['data']['staff_members']);
|
||||
|
||||
// update: replace with a single different staff
|
||||
$itemUuid = $created['data']['uuid'];
|
||||
$updated = $this->authJson('PATCH', '/api/v1/service-item/' . $itemUuid, $owner, [
|
||||
'staff_uuids' => [$c->getUuid()],
|
||||
]);
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertCount(1, $updated['data']['staff_members']);
|
||||
self::assertSame('ج', $updated['data']['staff_members'][0]['full_name']);
|
||||
}
|
||||
|
||||
public function testLegacySingleStaffFallsBackInToArray(): void
|
||||
{
|
||||
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$staff = new ClinicStaff('doctor', $doctor->getId(), 'کاربر قدیمی');
|
||||
$section = new ServiceSection('doctor', $doctor->getId(), 'بخش');
|
||||
$item = new ServiceItem($section, 'خدمت قدیمی');
|
||||
$item->setStaff($staff); // legacy single-staff path, no members
|
||||
$this->em->persist($staff);
|
||||
$this->em->persist($section);
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
|
||||
$arr = $item->toArray();
|
||||
self::assertCount(1, $arr['staff_members']);
|
||||
self::assertSame('کاربر قدیمی', $arr['staff_members'][0]['full_name']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user