docs(prompt): add booking-locations multi-context prompt
Companion to the clinicpro context-separation change. A doctor now has one booking schedule per context (personal practice + one per clinic), and every booking endpoint takes an optional clinic_uuid where omitting it means the personal practice — not a wildcard. This site sends no clinic_uuid anywhere, so today it shows no availability at all for clinic-only doctors and silently books into the wrong location for doctors who work in both. The prompt covers the new /api/v1/appointment-booking-locations contract, threading the selected location through the booking state, and the JSON-LD follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,355 @@
|
|||||||
|
# انتخاب محل نوبتدهی (مطب شخصی / کلینیک) در جریان رزرو
|
||||||
|
|
||||||
|
## پروژه
|
||||||
|
|
||||||
|
`nobat724_front` — پرامپت همتای backend که **قبلاً اجرا شده** است:
|
||||||
|
`clinicpro/.claude/prompt/context-separation-clinic-vs-doctor-booking.md`
|
||||||
|
|
||||||
|
قرارداد API از سمت backend آماده است؛ این پرامپت فقط سمت مصرفکننده را مینویسد.
|
||||||
|
|
||||||
|
## زمینه
|
||||||
|
|
||||||
|
تا پیش از این، هر پزشک در کل سیستم **یک** برنامهٔ نوبتدهی داشت (`weekly_schedules` با
|
||||||
|
`UNIQUE(doctor_id)`). حالا برنامه per-context است: یک برنامهٔ مطب شخصی + یکی به ازای هر کلینیکی
|
||||||
|
که پزشک در آن عضو است. ستون `clinic_id` روی `weekly_schedules`، `date_overrides` و `holidays`
|
||||||
|
اضافه شده (`NULL` = مطب شخصی).
|
||||||
|
|
||||||
|
سرویسها هم polymorphicاند و بین این دو محیط **هرگز** مشترک نمیشوند: سرویسهای کلینیک فقط در
|
||||||
|
نوبتدهی کلینیک و سرویسهای شخصی فقط در مطب شخصی. قیمت و مدت سرویس بین محلها متفاوت است.
|
||||||
|
|
||||||
|
## مشکل / هدف
|
||||||
|
|
||||||
|
**این یک شکست خاموش است، نه یک قابلیت جدید.**
|
||||||
|
|
||||||
|
همهٔ endpointهای رزرو حالا پارامتر اختیاری `clinic_uuid` میگیرند و **نبودِ آن یعنی «مطب شخصی»** —
|
||||||
|
نه «هر محلی که پیدا شد». سایت الان هیچجا `clinic_uuid` نمیفرستد، پس:
|
||||||
|
|
||||||
|
- برای پزشکی که فقط در کلینیک کار میکند، سایت **هیچ نوبتی نشان نمیدهد** (برنامهٔ شخصی ندارد).
|
||||||
|
- برای پزشکی که هر دو را دارد، سایت فقط ظرفیت مطب شخصی را نشان میدهد و بیمار بدون اطلاع نوبت
|
||||||
|
را در محل اشتباه رزرو میکند.
|
||||||
|
- در حالت سرویسی، `POST /api/v1/appointment` با سرویسی که به آن محل تعلق ندارد `422` میگیرد.
|
||||||
|
|
||||||
|
هدف: بیمار **محل** را آگاهانه انتخاب کند و آن انتخاب تا لحظهٔ ثبت نوبت حمل شود.
|
||||||
|
|
||||||
|
## قرارداد API (آمادهٔ مصرف)
|
||||||
|
|
||||||
|
### endpoint جدید
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/appointment-booking-locations/{doctorUuid} (عمومی، بدون auth)
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"doctor_uuid": "550e8400-…",
|
||||||
|
"booking_locations": [
|
||||||
|
{
|
||||||
|
"location_uuid": "0f0b…",
|
||||||
|
"type": "personal",
|
||||||
|
"title": "مطب شخصی",
|
||||||
|
"address": "یزد، خیابان …",
|
||||||
|
"clinic_uuid": null,
|
||||||
|
"booking_mode": "slot",
|
||||||
|
"buffer_minutes": 0,
|
||||||
|
"services": [],
|
||||||
|
"next_available_at": 1755000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"location_uuid": "7c21…",
|
||||||
|
"type": "clinic",
|
||||||
|
"title": "کلینیک علی بهروزی",
|
||||||
|
"address": "یزد، بلوار …",
|
||||||
|
"clinic_uuid": "41e325c4-e825-4067-8438-5d828ecaee09",
|
||||||
|
"booking_mode": "service",
|
||||||
|
"buffer_minutes": 10,
|
||||||
|
"services": [
|
||||||
|
{ "uuid": "…", "name": "ویزیت", "duration_minutes": 20, "price_rials": 500000,
|
||||||
|
"service_section": { "uuid": "…", "name": "عمومی" } }
|
||||||
|
],
|
||||||
|
"next_available_at": 1754900000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
نکات قرارداد:
|
||||||
|
|
||||||
|
- آرایه **از قبل مرتب است** بر اساس `next_available_at` صعودی؛ محلهای بدون ظرفیت آخر میآیند.
|
||||||
|
پس `booking_locations[0]` پیشفرضِ درست است — دوباره مرتب نکن.
|
||||||
|
- `next_available_at` میتواند `null` باشد (تا ۳۰ روز آینده ظرفیتی نیست).
|
||||||
|
- `location_uuid` میتواند `null` باشد (آن محیط هنوز آدرس ثبتشده ندارد) — برای انتخاب از
|
||||||
|
`clinic_uuid` استفاده کن، نه `location_uuid`.
|
||||||
|
- `booking_mode` **per-location** است: یک پزشک میتواند در مطب شخصی اسلاتی و در کلینیک سرویسی باشد.
|
||||||
|
- `services` فقط در حالت `service` پُر است.
|
||||||
|
- پاسخ double-nested است (`json.data.data`) مثل بقیهٔ endpointها.
|
||||||
|
|
||||||
|
### پارامتر جدید روی endpointهای موجود
|
||||||
|
|
||||||
|
| endpoint | محل پارامتر |
|
||||||
|
|---|---|
|
||||||
|
| `GET /api/v1/appointment-slots` | query `clinic_uuid` |
|
||||||
|
| `GET /api/v1/appointment-service-slots` | query `clinic_uuid` |
|
||||||
|
| `GET /api/v1/appointment-booking-services/{doctorUuid}` | query `clinic_uuid` |
|
||||||
|
| `GET /api/v1/appointment-settings/month-availability/{doctorUuid}` | query `clinic_uuid` |
|
||||||
|
| `POST /api/v1/appointment` | body `clinic_uuid` |
|
||||||
|
|
||||||
|
هر چهار `GET` مقدار `clinic_uuid` را در پاسخ echo میکنند تا کلاینت بفهمد کدام محیط جواب داده.
|
||||||
|
اگر پزشک عضو آن کلینیک نباشد → `404 ERR_VALIDATION_002` («محل نوبتدهی یافت نشد»).
|
||||||
|
سرویسِ متعلق به محیط دیگر در `POST` → `422 ERR_VALIDATION_001`
|
||||||
|
(«سرویس انتخابشده به این محل نوبتدهی تعلق ندارد»).
|
||||||
|
|
||||||
|
مستندات کامل: `clinicpro/docs/api/appointment.md` → بخش «Booking context (2026-07)».
|
||||||
|
|
||||||
|
## فایلهای مرتبط
|
||||||
|
|
||||||
|
| فایل | نقش |
|
||||||
|
|---|---|
|
||||||
|
| `services/response.js:52-72` | همهٔ فراخوانیهای رزرو |
|
||||||
|
| `components/appointment/index.js:45` | `AppointmentPage` — نگهدارندهٔ کل state رزرو |
|
||||||
|
| `components/appointment/Container.js:15-113` | مسیریابی مرحلهها (prop drilling) |
|
||||||
|
| `components/appointment/service/index.js:11` | `ServiceSelect` |
|
||||||
|
| `components/appointment/date/index.js` | wrapper مرحلهٔ تاریخ (`locateVisit` مرده) |
|
||||||
|
| `app/component/date/datePicker/index.js:24` | `DatePicker` — تقویم ماه + `getMonthAvailability` |
|
||||||
|
| `app/component/date/dateTime/index.js:11` | `DateTime` — اسلاتها + resolve آدرس |
|
||||||
|
| `app/component/date/dateTime/hours/index.js:17-26` | کارت «آدرس مطب:» |
|
||||||
|
| `components/appointment/detail/SubmitData.js:138-160` | payload نهایی `postAppointment` |
|
||||||
|
| `components/appointment/location/Address.js` | سایدبار آدرسها (نمایشی) |
|
||||||
|
| `app/doctor/[slug]/page.js:107-157` | JSON-LD صفحهٔ پزشک |
|
||||||
|
| `components/doctor/appointmentList/index.js:33` | CTA ورود به رزرو |
|
||||||
|
| `lib/appointmentSlots.js` | `adaptSlots` / `adaptServiceSlots` |
|
||||||
|
|
||||||
|
## وضعیت فعلی
|
||||||
|
|
||||||
|
### ۱. هیچ فراخوانیای محل را نمیفرستد
|
||||||
|
|
||||||
|
`services/response.js:52-72`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
getAppointmentSlots: (doctor_uuid, date) =>
|
||||||
|
api.get(
|
||||||
|
`api/v1/appointment-slots?doctor_uuid=${doctor_uuid}&date=${date}`,
|
||||||
|
removeTokenHead
|
||||||
|
),
|
||||||
|
getMonthAvailability: (doctor_uuid, year, month) =>
|
||||||
|
api.get(`api/v1/appointment-settings/month-availability/${doctor_uuid}`, {
|
||||||
|
params: { year, month },
|
||||||
|
...removeTokenHead,
|
||||||
|
}),
|
||||||
|
getBookingServices: (doctor_uuid) =>
|
||||||
|
api.get(`api/v1/appointment-booking-services/${doctor_uuid}`, removeTokenHead),
|
||||||
|
getServiceSlots: (doctor_uuid, date, serviceItemUuids = []) =>
|
||||||
|
api.get(
|
||||||
|
`api/v1/appointment-service-slots?doctor_uuid=${doctor_uuid}&date=${date}` +
|
||||||
|
serviceItemUuids
|
||||||
|
.map((u) => `&service_item_uuids[]=${encodeURIComponent(u)}`)
|
||||||
|
.join(""),
|
||||||
|
removeTokenHead
|
||||||
|
),
|
||||||
|
postAppointment: (data) => api.post(`api/v1/appointment`, data, { requireAuth: true }),
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۲. آدرس فقط نمایشی است و از `doctor.address` میآید
|
||||||
|
|
||||||
|
`app/component/date/dateTime/index.js:33-35` — تنها جایی که آدرس یک اسلات حل میشود:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const locationAddress = hour?.location_id
|
||||||
|
? doctor?.address?.find((a) => String(a.id) === String(hour.location_id))?.address ?? null
|
||||||
|
: null;
|
||||||
|
```
|
||||||
|
|
||||||
|
هیچ محلی قابل انتخاب نیست و هیچ id محلی در state رزرو ذخیره نمیشود.
|
||||||
|
|
||||||
|
### ۳. payload نهایی
|
||||||
|
|
||||||
|
`components/appointment/detail/SubmitData.js:138-158`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const appointmentPayload = {
|
||||||
|
doctor_uuid: doctor.uuid,
|
||||||
|
slot_start: selectedSlot.start,
|
||||||
|
slot_end: selectedSlot.end,
|
||||||
|
for_self: !isForAnother,
|
||||||
|
note: data?.patient_reason?.value || "",
|
||||||
|
patient_national_code: data?.national_code?.value || "",
|
||||||
|
patient_gender: data?.gender?.value?.id || data?.gender?.value || "",
|
||||||
|
city_id: cityId ?? null,
|
||||||
|
...(selectedServiceUuids?.length ? { service_item_uuids: selectedServiceUuids } : {}),
|
||||||
|
...(isForAnother ? { patient_name, patient_mobile, patient_reason } : {}),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۴. state مرده
|
||||||
|
|
||||||
|
`components/appointment/date/index.js:15` — `locateVisit` با `doctor?.multiwork` گیت شده، مقدار
|
||||||
|
اولیهاش `true` است و هیچ فرزندی `false` نمیکند. عملاً کد مرده است. **حذفش کن** و جای آن انتخاب
|
||||||
|
واقعی محل بنشان.
|
||||||
|
|
||||||
|
### ۵. JSON-LD بدون ساعات کاری
|
||||||
|
|
||||||
|
`app/doctor/[slug]/page.js:107-157` — `workLocation` وجود دارد ولی هیچ
|
||||||
|
`openingHoursSpecification` و هیچ `availableService` ندارد.
|
||||||
|
|
||||||
|
## وظایف
|
||||||
|
|
||||||
|
### ۱. لایهٔ سرویس — `services/response.js`
|
||||||
|
|
||||||
|
یک helper بساز تا `clinic_uuid` تکرار نشود:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const withClinic = (params, clinicUuid) =>
|
||||||
|
clinicUuid ? { ...params, clinic_uuid: clinicUuid } : params;
|
||||||
|
```
|
||||||
|
|
||||||
|
و امضاها را گسترش بده (پارامتر آخر، اختیاری، تا هیچ call site موجودی نشکند):
|
||||||
|
|
||||||
|
```js
|
||||||
|
getBookingLocations: (doctor_uuid) =>
|
||||||
|
api.get(`api/v1/appointment-booking-locations/${doctor_uuid}`, removeTokenHead),
|
||||||
|
|
||||||
|
getAppointmentSlots: (doctor_uuid, date, clinic_uuid = null) =>
|
||||||
|
api.get("api/v1/appointment-slots", {
|
||||||
|
params: withClinic({ doctor_uuid, date }, clinic_uuid),
|
||||||
|
...removeTokenHead,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getMonthAvailability: (doctor_uuid, year, month, clinic_uuid = null) =>
|
||||||
|
api.get(`api/v1/appointment-settings/month-availability/${doctor_uuid}`, {
|
||||||
|
params: withClinic({ year, month }, clinic_uuid),
|
||||||
|
...removeTokenHead,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getBookingServices: (doctor_uuid, clinic_uuid = null) =>
|
||||||
|
api.get(`api/v1/appointment-booking-services/${doctor_uuid}`, {
|
||||||
|
params: withClinic({}, clinic_uuid),
|
||||||
|
...removeTokenHead,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getServiceSlots: (doctor_uuid, date, serviceItemUuids = [], clinic_uuid = null) =>
|
||||||
|
api.get("api/v1/appointment-service-slots", {
|
||||||
|
params: withClinic({ doctor_uuid, date, "service_item_uuids[]": serviceItemUuids }, clinic_uuid),
|
||||||
|
...removeTokenHead,
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
**دقت:** `getAppointmentSlots` و `getServiceSlots` الان query را دستی میچسبانند. با انتقال به
|
||||||
|
`params`، سریالسازی آرایهٔ `service_item_uuids[]` توسط axios انجام میشود — بررسی کن خروجی همچنان
|
||||||
|
`?service_item_uuids[]=a&service_item_uuids[]=b` باشد (نه `service_item_uuids[0]=a`). اگر نبود،
|
||||||
|
`paramsSerializer` بده یا همان روش دستی را با افزودن `clinic_uuid` نگه دار.
|
||||||
|
|
||||||
|
### ۲. state انتخاب محل — `components/appointment/index.js`
|
||||||
|
|
||||||
|
`AppointmentPage` نگهدارندهٔ state است؛ محل هم همانجا بنشیند:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const [bookingLocations, setBookingLocations] = useState([]);
|
||||||
|
const [selectedLocation, setSelectedLocation] = useState(null); // یک آیتم از booking_locations
|
||||||
|
```
|
||||||
|
|
||||||
|
هنگام mount، `getBookingLocations(doctor.uuid)` را صدا بزن. سپس:
|
||||||
|
|
||||||
|
- اگر `?clinic_uuid=` یا `?location=` در URL بود → همان را انتخاب کن.
|
||||||
|
- وگرنه `booking_locations[0]` (آرایه از قبل بر اساس زودترین نوبت آزاد مرتب است).
|
||||||
|
- اگر آرایه خالی بود → پیام «برای این پزشک نوبتدهی آنلاین فعال نیست» و ادامه نده.
|
||||||
|
|
||||||
|
**بحرانی:** `booking_mode` و `services` را از `selectedLocation` بخوان، نه از فراخوانی جدای
|
||||||
|
`getBookingServices`. `booking_locations` هر دو را از قبل دارد و یک درخواست کمتر میزند. `state`های
|
||||||
|
`bookingMode` و `bookingServices` موجود (`index.js:46-67`) باید از `selectedLocation` مشتق شوند:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const bookingMode = selectedLocation?.booking_mode ?? "slot";
|
||||||
|
const bookingServices = selectedLocation?.services ?? [];
|
||||||
|
```
|
||||||
|
|
||||||
|
`getBookingServices` را فقط برای سازگاری در `response.js` نگه دار ولی از این جریان حذفش کن.
|
||||||
|
|
||||||
|
### ۳. تعویض محل باید state وابسته را پاک کند
|
||||||
|
|
||||||
|
وقتی کاربر محل را عوض میکند، اینها **باید** reset شوند وگرنه ترکیب نامعتبر ساخته میشود
|
||||||
|
(سرویس کلینیک A + اسلات کلینیک B → خطای ۴۲۲ در انتها):
|
||||||
|
|
||||||
|
```js
|
||||||
|
const changeLocation = (loc) => {
|
||||||
|
setSelectedLocation(loc);
|
||||||
|
setSelectedServiceUuids([]);
|
||||||
|
setSelectedSlot(null);
|
||||||
|
setSelectedDate(null);
|
||||||
|
setStep(loc.booking_mode === "service" ? SERVICE_STEP : DATE_STEP);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### ۴. UI انتخاب محل
|
||||||
|
|
||||||
|
اگر `booking_locations.length > 1`، یک مرحله/کارت انتخاب محل **قبل از** انتخاب سرویس و تاریخ
|
||||||
|
نشان بده. اگر فقط یک محل بود، مرحله را نشان نده و مستقیم انتخابش کن.
|
||||||
|
|
||||||
|
هر کارت: `title`، `address`، و برچسب زودترین نوبت. برای `next_available_at` از `jalali-moment`
|
||||||
|
استفاده کن (همان الگوی بقیهٔ پروژه) و اگر `null` بود «فعلاً نوبت خالی ندارد» با ظاهر غیرفعال.
|
||||||
|
|
||||||
|
`components/appointment/location/Address.js` (سایدبار نمایشی) باید محل انتخابشده را برجسته کند.
|
||||||
|
`components/appointment/date/index.js` → `locateVisit` مرده را حذف کن.
|
||||||
|
|
||||||
|
**الزامی:** از کامپوننتها، تم، فونت (Vazir) و MUI v5 + Tailwind موجود استفاده کن — طراحی جدید
|
||||||
|
نساز. RTL رعایت شود.
|
||||||
|
|
||||||
|
### ۵. حمل محل تا لحظهٔ ثبت
|
||||||
|
|
||||||
|
`app/component/date/datePicker/index.js` و `app/component/date/dateTime/index.js` باید
|
||||||
|
`clinicUuid` را prop بگیرند و به فراخوانیها بدهند. `DatePicker` الان `doctorUuid` را از
|
||||||
|
`useParams().doctorId` میخواند (`index.js:25-26`) — `clinicUuid` را بهصورت prop بده، نه از URL.
|
||||||
|
|
||||||
|
`components/appointment/detail/SubmitData.js:138`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const appointmentPayload = {
|
||||||
|
doctor_uuid: doctor.uuid,
|
||||||
|
clinic_uuid: selectedLocation?.clinic_uuid ?? null,
|
||||||
|
slot_start: selectedSlot.start,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
آدرس نمایشی در `app/component/date/dateTime/index.js:33-35` را از `selectedLocation.address`
|
||||||
|
بگیر — نه از `doctor.address.find(...)`، چون `location_id` عددی است و در محیط کلینیک ممکن است در
|
||||||
|
`doctor.address` نباشد.
|
||||||
|
|
||||||
|
### ۶. لینکپذیری و SEO
|
||||||
|
|
||||||
|
- CTA در `components/doctor/appointmentList/index.js:33` باید محل را حمل کند وقتی صفحهٔ پزشک
|
||||||
|
محلی را برجسته کرده: `/appointment/${doctorSlug}?clinic_uuid=${clinicUuid}`.
|
||||||
|
- `app/appointment/[doctorId]/page.js` باید `searchParams` را بخواند (و `await` کند طبق App Router).
|
||||||
|
- `app/doctor/[slug]/page.js` — JSON-LD را با ساعات کاری هر محل غنی کن. `booking_locations` را
|
||||||
|
server-side بگیر (با `fetchReq` از `lib/req.js` و `revalidate` مشابه `getDoctorAddresses`) و برای
|
||||||
|
هر محل یک `MedicalClinic` با `openingHoursSpecification` بساز و به `workLocation` وصل کن.
|
||||||
|
در حالت سرویسی، `availableService` هم از `services` قابل ساخت است.
|
||||||
|
|
||||||
|
اگر endpoint ساعات هفتگی عمومی در دسترس نبود، این بند را محدود کن به افزودن
|
||||||
|
`availableService` و در PR ذکر کن که `openingHoursSpecification` نیاز به یک endpoint عمومی
|
||||||
|
جدید در `clinicpro` دارد.
|
||||||
|
- هر صفحه باید `generateMetadata` داشته باشد و `params` همیشه `await` شود.
|
||||||
|
|
||||||
|
### ۷. سازگاری و حالتهای مرزی
|
||||||
|
|
||||||
|
- پزشکی که فقط مطب شخصی دارد → یک آیتم با `type: "personal"`؛ UI نباید تغییری حس شود.
|
||||||
|
- پزشکی که فقط کلینیک دارد → قبلاً هیچ نوبتی نشان داده نمیشد؛ حالا باید کار کند. **این را حتماً
|
||||||
|
دستی تست کن.**
|
||||||
|
- `booking_locations` خالی → پیام روشن، نه صفحهٔ خالی.
|
||||||
|
- `422` سرویسِ ناسازگار → پیام فارسی خطای backend را نشان بده (پیام آماده است).
|
||||||
|
|
||||||
|
## نکات مهم
|
||||||
|
|
||||||
|
- **این تغییر بدون بهروزرسانی این سایت، ظرفیت واقعی پزشکان کلینیکی را از سایت حذف میکند.**
|
||||||
|
اولویت بالا.
|
||||||
|
- پاسخها double-nested هستند: `json?.data?.data ?? json?.data`.
|
||||||
|
- برای auth از `{ requireAuth: true }` استفاده کن (کوکی `access_token` → Bearer). endpointهای
|
||||||
|
اسلات عمومیاند و `removeTokenHead` میگیرند.
|
||||||
|
- slug پزشک = `uuid`.
|
||||||
|
- تاریخها شمسی (`jalali-moment`)، رشتههای جدید فارسی.
|
||||||
|
- شهر از subdomain: server با `lib/getStateInfo.js`، client با `useProvince()`. `city_id` موجود در
|
||||||
|
payload را دست نزن.
|
||||||
|
- تست دستی با کلینیک `41e325c4-e825-4067-8438-5d828ecaee09` و «دکتر تست» (`09100652121`) که در
|
||||||
|
همان کلینیک سرویس bookable دارد.
|
||||||
|
- بعد از تغییرات: `npm run lint` و `npm run build` هر دو باید سبز باشند.
|
||||||
Reference in New Issue
Block a user