feat: enhance clinic API to resolve contact fields from address record and add tests for contact field resolution
This commit is contained in:
+2
-1
@@ -157,7 +157,7 @@ Get clinic detail.
|
||||
}
|
||||
```
|
||||
|
||||
> `city`/`state`/`map` are resolved from the clinic's **address** (`DoctorAddress` linked by `clinic_id`), not from columns on the clinic. Each is an array with a single object (or empty `[]` if the clinic has no address). `doctors` is a **count**; the actual doctor list comes from `GET /api/v1/clinic/doctor-list/{clinicUuid}` (`doctor_list` here is always `null`).
|
||||
> `city`/`state`/`map`/`location`/`phone`/`phone_number` are all resolved from the clinic's **address** (`DoctorAddress` linked by `clinic_id`), not from columns on the clinic. `location` and `phone`/`phone_number` fall back to the deprecated `clinics.address` / `clinics.telephone` columns only when the address record has no value — reading them from different rows made one response describe two different places. Each is an array with a single object (or empty `[]` if the clinic has no address). `doctors` is a **count**; the actual doctor list comes from `GET /api/v1/clinic/doctor-list/{clinicUuid}` (`doctor_list` here is always `null`).
|
||||
|
||||
### معنای `is_active`
|
||||
|
||||
@@ -265,6 +265,7 @@ List clinics with pagination.
|
||||
| `doctors_count` | integer | Number of doctors linked to the clinic |
|
||||
| `city` | string\|null | City name, resolved from the clinic's address (`DoctorAddress`) |
|
||||
| `state` | string\|null | Province name, resolved from the clinic's address (`DoctorAddress`) |
|
||||
| `phone` / `phone_number` | string\|null | Contact number from the clinic's address (`DoctorAddress`), falling back to the deprecated `clinics.telephone` column |
|
||||
| `24_7` | boolean | Open 24/7 flag |
|
||||
| `field_working_days` | string\|null | Working days/hours description |
|
||||
|
||||
|
||||
@@ -160,9 +160,9 @@ class ClinicController extends BaseController
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'کلینیک یافت نشد', 404);
|
||||
}
|
||||
|
||||
[$stateData, $cityData, $map] = $this->loadLocationData($clinic);
|
||||
[$stateData, $cityData, $map, $street, $telephone] = $this->loadLocationData($clinic);
|
||||
|
||||
return $this->success(['data' => $clinic->toDetailArray($stateData, $cityData, $map)]);
|
||||
return $this->success(['data' => $clinic->toDetailArray($stateData, $cityData, $map, $street, $telephone)]);
|
||||
}
|
||||
|
||||
#[OA\Patch(
|
||||
@@ -237,9 +237,9 @@ class ClinicController extends BaseController
|
||||
$this->hydrateClinic($clinic, $data);
|
||||
$this->clinicRepo->save($clinic);
|
||||
|
||||
[$stateData, $cityData, $map] = $this->loadLocationData($clinic);
|
||||
[$stateData, $cityData, $map, $street, $telephone] = $this->loadLocationData($clinic);
|
||||
|
||||
return $this->success(['data' => $clinic->toDetailArray($stateData, $cityData, $map)]);
|
||||
return $this->success(['data' => $clinic->toDetailArray($stateData, $cityData, $map, $street, $telephone)]);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
@@ -284,8 +284,8 @@ class ClinicController extends BaseController
|
||||
|
||||
return $this->paginated(
|
||||
array_map(function (Clinic $c) use ($locations) {
|
||||
$loc = $locations[$c->getId()] ?? ['city' => null, 'state' => null];
|
||||
return $c->toListArray($loc['city'], $loc['state']);
|
||||
$loc = $locations[$c->getId()] ?? ['city' => null, 'state' => null, 'telephone' => null];
|
||||
return $c->toListArray($loc['city'], $loc['state'], $loc['telephone']);
|
||||
}, $result['items']),
|
||||
$result['total'],
|
||||
$result['page'],
|
||||
@@ -612,11 +612,17 @@ class ClinicController extends BaseController
|
||||
$provinceData = [];
|
||||
$cityData = [];
|
||||
$map = ['latitude' => null, 'longitude' => null];
|
||||
$street = null;
|
||||
$telephone = null;
|
||||
|
||||
// City/province/coordinates live on the clinic's address (DoctorAddress
|
||||
// linked by clinicId), not on the deprecated columns of the clinic itself.
|
||||
// The street text and phone come from the same record, otherwise one
|
||||
// response mixes two rows: city from the address, street from the clinic.
|
||||
$address = $this->addressRepo->findOneByClinic((int) $clinic->getId());
|
||||
if ($address !== null) {
|
||||
$street = $address->getAddress();
|
||||
$telephone = $address->getTelephone();
|
||||
$province = $address->getProvince();
|
||||
if ($province !== null) {
|
||||
$provinceData = ['uuid' => $province->getUuid(), 'id' => (string) $province->getId(), 'name' => $province->getName()];
|
||||
@@ -636,7 +642,7 @@ class ClinicController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
return [$provinceData, $cityData, $map];
|
||||
return [$provinceData, $cityData, $map, $street, $telephone];
|
||||
}
|
||||
|
||||
private function handleFileUpload(Request $request, string $subDir): JsonResponse
|
||||
|
||||
@@ -187,20 +187,33 @@ class Clinic
|
||||
|
||||
private function touch(): void { $this->updatedAt = time(); }
|
||||
|
||||
public function toDetailArray(array $provinceData = [], array $cityData = [], ?array $map = null): array
|
||||
{
|
||||
/**
|
||||
* $street/$telephone come from the clinic's DoctorAddress record. The columns
|
||||
* on this entity are the deprecated legacy source and are only a fallback:
|
||||
* mixing the two makes a single response describe two different places.
|
||||
*/
|
||||
public function toDetailArray(
|
||||
array $provinceData = [],
|
||||
array $cityData = [],
|
||||
?array $map = null,
|
||||
?string $street = null,
|
||||
?string $telephone = null
|
||||
): array {
|
||||
$phone = $this->firstFilled($telephone, $this->telephone);
|
||||
$address = $this->firstFilled($street, $this->address);
|
||||
|
||||
return [
|
||||
'id' => (string) $this->id,
|
||||
'uuid' => $this->uuid,
|
||||
'name' => $this->name,
|
||||
'title' => $this->name,
|
||||
'is_active' => $this->isActive,
|
||||
'phone' => $this->telephone,
|
||||
'phone' => $phone,
|
||||
'logo' => $this->clinicLogo,
|
||||
'images_clinic' => $this->imagesClinic ?? [],
|
||||
'social_media' => $this->socialMedia,
|
||||
'clinic_logo' => $this->clinicLogo,
|
||||
'phone_number' => $this->telephone,
|
||||
'phone_number' => $phone,
|
||||
'caption' => $this->info,
|
||||
'list_bime' => array_map(fn(Insurance $i) => [
|
||||
'uuid' => $i->getUuid(), 'id' => (string) $i->getId(), 'name' => $i->getName(),
|
||||
@@ -220,7 +233,7 @@ class Clinic
|
||||
'doctor_list' => null,
|
||||
'city' => $cityData ? [$cityData] : [],
|
||||
'state' => $provinceData ? [$provinceData] : [],
|
||||
'location' => $this->address,
|
||||
'location' => $address,
|
||||
'map' => $map ?? [
|
||||
'latitude' => $this->latitude !== null ? (string) $this->latitude : null,
|
||||
'longitude' => $this->longitude !== null ? (string) $this->longitude : null,
|
||||
@@ -230,15 +243,28 @@ class Clinic
|
||||
];
|
||||
}
|
||||
|
||||
public function toListArray(?string $city = null, ?string $state = null): array
|
||||
private function firstFilled(?string ...$values): ?string
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
if ($value !== null && trim($value) !== '') {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function toListArray(?string $city = null, ?string $state = null, ?string $telephone = null): array
|
||||
{
|
||||
$phone = $this->firstFilled($telephone, $this->telephone);
|
||||
|
||||
return [
|
||||
'id' => (string) $this->id,
|
||||
'uuid' => $this->uuid,
|
||||
'name' => $this->name,
|
||||
'title' => $this->name,
|
||||
'phone' => $this->telephone,
|
||||
'phone_number' => $this->telephone,
|
||||
'phone' => $phone,
|
||||
'phone_number' => $phone,
|
||||
'logo' => $this->clinicLogo,
|
||||
'clinic_logo' => $this->clinicLogo,
|
||||
'images_clinic' => $this->imagesClinic ?? [],
|
||||
|
||||
@@ -101,7 +101,7 @@ class ClinicRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
$rows = $this->getEntityManager()->createQueryBuilder()
|
||||
->select('addr.clinicId AS clinic_id', 'cityCat.name AS city', 'provinceCat.name AS state')
|
||||
->select('addr.clinicId AS clinic_id', 'cityCat.name AS city', 'provinceCat.name AS state', 'addr.telephone AS telephone')
|
||||
->from(DoctorAddress::class, 'addr')
|
||||
->leftJoin('addr.city', 'cityCat')
|
||||
->leftJoin('addr.province', 'provinceCat')
|
||||
@@ -114,7 +114,7 @@ class ClinicRepository extends ServiceEntityRepository
|
||||
foreach ($rows as $row) {
|
||||
$id = (int) $row['clinic_id'];
|
||||
if (!isset($map[$id])) {
|
||||
$map[$id] = ['city' => $row['city'], 'state' => $row['state']];
|
||||
$map[$id] = ['city' => $row['city'], 'state' => $row['state'], 'telephone' => $row['telephone']];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Clinic;
|
||||
|
||||
use App\Clinic\Entity\Clinic;
|
||||
use App\Doctor\Entity\DoctorAddress;
|
||||
use App\Location\Entity\City;
|
||||
use App\Location\Entity\Province;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* GET /api/v1/clinic/{uuid} used to build one response out of two rows: city,
|
||||
* province and coordinates came from the clinic's DoctorAddress while the street
|
||||
* text and phone still came from the deprecated columns on the clinic itself.
|
||||
* A clinic whose address record said Karaj then rendered a Yazd street on the
|
||||
* public site. Contact fields must all resolve from the address record, and only
|
||||
* fall back to the legacy columns when that record has nothing to offer.
|
||||
*/
|
||||
class ClinicContactSourceTest extends ApiTestCase
|
||||
{
|
||||
public function testContactFieldsComeFromTheAddressRecord(): void
|
||||
{
|
||||
$clinic = $this->createClinic('یزد، خیابان قدیمی', '03531234567');
|
||||
$this->createAddressFor($clinic, 'کرج، بلوار طالقانی', '02634567890');
|
||||
|
||||
$data = $this->fetchClinic($clinic);
|
||||
|
||||
$this->assertSame('کرج، بلوار طالقانی', $data['location']);
|
||||
$this->assertSame('02634567890', $data['phone']);
|
||||
$this->assertSame('02634567890', $data['phone_number']);
|
||||
$this->assertSame('کرج', $data['city'][0]['name'] ?? null);
|
||||
$this->assertSame('البرز', $data['state'][0]['name'] ?? null);
|
||||
}
|
||||
|
||||
public function testLegacyColumnsAreUsedWhenTheClinicHasNoAddressRecord(): void
|
||||
{
|
||||
$clinic = $this->createClinic('یزد، خیابان قدیمی', '03531234567');
|
||||
|
||||
$data = $this->fetchClinic($clinic);
|
||||
|
||||
$this->assertSame('یزد، خیابان قدیمی', $data['location']);
|
||||
$this->assertSame('03531234567', $data['phone']);
|
||||
$this->assertSame([], $data['city']);
|
||||
}
|
||||
|
||||
public function testBlankAddressFieldsFallBackInsteadOfBlankingTheContactBlock(): void
|
||||
{
|
||||
$clinic = $this->createClinic('یزد، خیابان قدیمی', '03531234567');
|
||||
$this->createAddressFor($clinic, ' ', null);
|
||||
|
||||
$data = $this->fetchClinic($clinic);
|
||||
|
||||
$this->assertSame('یزد، خیابان قدیمی', $data['location']);
|
||||
$this->assertSame('03531234567', $data['phone']);
|
||||
$this->assertSame('کرج', $data['city'][0]['name'] ?? null);
|
||||
}
|
||||
|
||||
private function createClinic(?string $legacyAddress, ?string $legacyPhone): Clinic
|
||||
{
|
||||
$clinic = new Clinic($this->createUser(['ROLE_CLINIC']));
|
||||
$clinic->setName('کلینیک تست تماس');
|
||||
$clinic->setAddress($legacyAddress);
|
||||
$clinic->setTelephone($legacyPhone);
|
||||
$this->em->persist($clinic);
|
||||
$this->em->flush();
|
||||
|
||||
return $clinic;
|
||||
}
|
||||
|
||||
private function createAddressFor(Clinic $clinic, ?string $street, ?string $phone): DoctorAddress
|
||||
{
|
||||
$province = new Province('البرز');
|
||||
$city = new City('کرج', $province);
|
||||
$this->em->persist($province);
|
||||
$this->em->persist($city);
|
||||
|
||||
$address = DoctorAddress::forClinic((int) $clinic->getId());
|
||||
$address->setAddress($street);
|
||||
$address->setTelephone($phone);
|
||||
$address->setCity($city);
|
||||
$address->setProvince($province);
|
||||
$this->em->persist($address);
|
||||
$this->em->flush();
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
private function fetchClinic(Clinic $clinic): array
|
||||
{
|
||||
$this->client->request('GET', '/api/v1/clinic/' . $clinic->getUuid());
|
||||
$this->assertSame(200, $this->responseCode());
|
||||
|
||||
return json_decode($this->client->getResponse()->getContent(), true)['data']['data'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user