fix(appointment): fall back to the resource's supervising doctor on public booking
A laser device is not a doctor, so booking one from the public site sent resource_uuid and no doctor_uuid and got back "doctor_uuid یا resource_uuid الزامی است" — a message telling the caller to send something it had already sent. The panel path had resolved this from ClinicResource.supervisor since it was written; only the public path had not, and the field was defined but never read there. A resource with no supervisor now gets its own message pointing at the actual fix, instead of the generic one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+13
-1
@@ -250,7 +250,7 @@ Book an appointment slot.
|
||||
| `slot_start` | integer | ✅ | Slot start (Unix timestamp) |
|
||||
| `slot_end` | integer | ⚠️ | Slot end (Unix timestamp). **با `service_item_uuids` نادیده گرفته میشود** و سرور خودش حساب میکند (به مقدار کلاینت اعتماد نمیشود)؛ در آن حالت الزامی هم نیست. بدون سرویس، مقدار کلاینت حفظ میشود و الزامی است |
|
||||
| `service_item_uuids` | string[] | ❌ | یک یا چند UUID سرویس. سرویسها **ذخیره** میشوند (`service_items`)، اولین سرویس سرویسِ اصلی (`service_item`) است، و مدت/بافر روی نوبت ثبت میشود (`service_total_minutes` / `service_buffer_minutes`). UUID ناموجود، سرویسِ غیرbookable، سرویس بدون مدت، یا سرویسِ محیطی دیگر ⇒ `422` |
|
||||
| `resource_uuid` | string (UUID) | ⚠️ | منبعی که نوبت **برایش** گرفته میشود (دستگاه، اتاق، یا خودِ پزشک). اگر داده شود `doctor_uuid` اختیاری است و برای منبعِ پزشک از خودش استنتاج میشود؛ محل نوبت هم از شعبهٔ همان منبع میآید. منبع باید در همان محیط رزرو باشد و اگر سرویس انتخابشده را ارائه ندهد ⇒ `422` |
|
||||
| `resource_uuid` | string (UUID) | ⚠️ | منبعی که نوبت **برایش** گرفته میشود (دستگاه، اتاق، یا خودِ پزشک). اگر داده شود `doctor_uuid` اختیاری است: برای منبعِ پزشک از خودش استنتاج میشود و برای دستگاه/اتاق از **پزشک ناظرِ** همان منبع؛ محل نوبت هم از شعبهٔ همان منبع میآید. منبع باید در همان محیط رزرو باشد و اگر سرویس انتخابشده را ارائه ندهد ⇒ `422` |
|
||||
| `for_self` | boolean | ❌ | `true` (default) = patient is the logged-in payer; `false` = booking for someone else |
|
||||
| `patient_name` | string | ⚠️ | Required when `for_self=false`; otherwise filled from the payer's profile |
|
||||
| `patient_mobile` | string | ⚠️ | Required when `for_self=false`; otherwise the payer's mobile |
|
||||
@@ -263,6 +263,18 @@ Book an appointment slot.
|
||||
> **`doctor_uuid` یا `resource_uuid`:** دستکم یکی الزامی است؛ نبودِ هر دو ⇒ `422`. مسیر
|
||||
> قدیمیِ فقط-`doctor_uuid` دستنخورده است و سایت عمومی همان را میفرستد.
|
||||
>
|
||||
> **دستگاه پزشک نیست.** وقتی `resource_uuid` یک دستگاه یا اتاق است، پزشک از
|
||||
> `ClinicResource.supervisor` برداشته میشود — اپراتور کار را میکند و پزشک پاسخگوی
|
||||
> بالینی است. اگر منبع ناظر نداشته باشد پیام مخصوص خودش برمیگردد، نه پیامِ عمومیِ
|
||||
> «`doctor_uuid` یا `resource_uuid` لازم است» که کلاینت هر دو را فرستاده بود:
|
||||
>
|
||||
> ```json
|
||||
> {"success":false,"data":null,"errors":[{"code":"ERR_VALIDATION_002","message":"این منبع پزشک ناظر ندارد؛ ابتدا در تنظیمات منابع پزشک ناظر را مشخص کنید","field":"resource_uuid"}]}
|
||||
> ```
|
||||
>
|
||||
> مسیر پنل (`POST /api/v1/my/appointment`) این رفتار را از قبل داشت؛ این تغییر مسیر
|
||||
> عمومی را با آن همتراز کرد.
|
||||
>
|
||||
> **پاسخ:** علاوه بر فیلدهای قبلی، `resource` (`uuid`, `name`, `type`) و `service_option`
|
||||
> (`uuid`, `name`) برمیگردند. نوبتهای پیش از مدل منبعمحور هر دو را `null` دارند، پس
|
||||
> کلاینت باید با `null` کنار بیاید.
|
||||
|
||||
@@ -456,7 +456,9 @@ class AppointmentController extends BaseController
|
||||
|
||||
/**
|
||||
* منبع میتواند جای پزشک بنشیند: نوبتِ «دستگاه لیزر ۲» پزشکی ندارد که uuidش
|
||||
* فرستاده شود. اگر منبع خودش پزشک باشد، پزشک از آن استنتاج میشود.
|
||||
* فرستاده شود. اگر منبع خودش پزشک باشد، پزشک از آن استنتاج میشود؛ وگرنه
|
||||
* پزشکِ ناظرِ همان منبع مینشیند — دستگاه را اپراتور کار میکند و پزشک فقط
|
||||
* پاسخگوی بالینی است.
|
||||
*/
|
||||
$resourceUuid = trim((string) ($data['resource_uuid'] ?? ''));
|
||||
$resource = null;
|
||||
@@ -472,6 +474,21 @@ class AppointmentController extends BaseController
|
||||
$doctorUuid = $resource->subject()->getUuid();
|
||||
}
|
||||
|
||||
if ($doctorUuid === '' && $resource->getSupervisor() !== null) {
|
||||
$doctorUuid = $resource->getSupervisor()->getUuid();
|
||||
}
|
||||
|
||||
// پیام عمومیِ «doctor_uuid یا resource_uuid لازم است» اینجا گمراهکننده بود:
|
||||
// کلاینت هر دو را فرستاده و مشکل نبودِ ناظر روی خودِ منبع است.
|
||||
if ($doctorUuid === '') {
|
||||
return $this->error(
|
||||
ErrorCodes::ERR_VALIDATION_002,
|
||||
'این منبع پزشک ناظر ندارد؛ ابتدا در تنظیمات منابع پزشک ناظر را مشخص کنید',
|
||||
422,
|
||||
'resource_uuid',
|
||||
);
|
||||
}
|
||||
|
||||
// رزرو **برای یک منبع** یعنی رزرو در شعبهٔ همان منبع؛ فرستادن جداگانهٔ
|
||||
// `clinic_uuid` فقط راهی برای ناسازگار کردن این دو بود.
|
||||
if ($clinicUuid === null && $resource->getAddress()->getClinicId() !== null) {
|
||||
|
||||
@@ -105,6 +105,39 @@ class BookForResourceTest extends ApiTestCase
|
||||
self::assertSame('دکتر یوسفی', $row['resource']['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* دستگاه لیزر پزشک نیست؛ پزشکِ ناظرش مینشیند.
|
||||
*
|
||||
* مسیر پنل این را از قبل داشت و مسیر عمومی نداشت — یعنی همان رزرو از سایت
|
||||
* ۴۲۲ میگرفت با پیامی که میگفت `resource_uuid` بفرست، در حالی که فرستاده بود.
|
||||
*/
|
||||
public function testBookingForADeviceFallsBackToItsSupervisor(): void
|
||||
{
|
||||
[$clinic, $doctor, $address] = $this->clinic();
|
||||
$device = $this->resource($clinic, $address, 'دستگاه لیزر ۲');
|
||||
$device->setSupervisor($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
[$res] = $this->book(['resource_uuid' => $device->getUuid()]);
|
||||
|
||||
self::assertSame(201, $this->responseCode(), json_encode($res, JSON_UNESCAPED_UNICODE));
|
||||
self::assertSame($device->getUuid(), $res['data']['data']['resource']['uuid']);
|
||||
self::assertSame($doctor->getUuid(), $res['data']['data']['doctor']['uuid']);
|
||||
}
|
||||
|
||||
/** پیام باید بگوید ناظر ندارد، نه اینکه uuid نفرستادی. */
|
||||
public function testADeviceWithoutASupervisorSaysSo(): void
|
||||
{
|
||||
[$clinic, , $address] = $this->clinic();
|
||||
$device = $this->resource($clinic, $address, 'دستگاه بیناظر');
|
||||
|
||||
[$res] = $this->book(['resource_uuid' => $device->getUuid()]);
|
||||
|
||||
self::assertSame(422, $this->responseCode(), json_encode($res, JSON_UNESCAPED_UNICODE));
|
||||
self::assertSame('resource_uuid', $res['errors'][0]['field']);
|
||||
self::assertStringContainsString('پزشک ناظر', $res['errors'][0]['message']);
|
||||
}
|
||||
|
||||
public function testTheOldDoctorOnlyPathIsUntouched(): void
|
||||
{
|
||||
[, $doctor] = $this->clinic();
|
||||
|
||||
Reference in New Issue
Block a user