From 0eb172517fed675a82419844d0f3cb60da61649b Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 18 Jul 2026 13:53:28 +0330 Subject: [PATCH] feat(seo): emit openingHoursSpecification per work location The doctor page listed where a doctor works but never when, so search engines had no working hours for any location. booking_locations now carries opening_hours per context, so each MedicalClinic in the Physician JSON-LD gets its own openingHoursSpecification, matched to the address by uuid. Verified against the rendered page: the clinic location emits five shifts with schema.org weekday URLs alongside the availableService entries. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../prompt/booking-locations-multi-context.md | 6 +++--- app/doctor/[slug]/page.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.claude/prompt/booking-locations-multi-context.md b/.claude/prompt/booking-locations-multi-context.md index 25269f9..9bd40b7 100644 --- a/.claude/prompt/booking-locations-multi-context.md +++ b/.claude/prompt/booking-locations-multi-context.md @@ -326,9 +326,9 @@ const appointmentPayload = { هر محل یک `MedicalClinic` با `openingHoursSpecification` بساز و به `workLocation` وصل کن. در حالت سرویسی، `availableService` هم از `services` قابل ساخت است. - اگر endpoint ساعات هفتگی عمومی در دسترس نبود، این بند را محدود کن به افزودن - `availableService` و در PR ذکر کن که `openingHoursSpecification` نیاز به یک endpoint عمومی - جدید در `clinicpro` دارد. + **انجام شد:** `booking_locations` حالا فیلد `opening_hours` دارد (شیفت‌های فعال هفته با نام + انگلیسی روز)، پس `openingHoursSpecification` مستقیماً از همان ساخته می‌شود و به endpoint + جدیدی نیاز نیست. - هر صفحه باید `generateMetadata` داشته باشد و `params` همیشه `await` شود. ### ۷. سازگاری و حالت‌های مرزی diff --git a/app/doctor/[slug]/page.js b/app/doctor/[slug]/page.js index 5680b47..1f7d7a8 100644 --- a/app/doctor/[slug]/page.js +++ b/app/doctor/[slug]/page.js @@ -129,6 +129,14 @@ async function Doctor({ params }) { ? await Promise.all([getDoctorAddresses(doctor.id), getBookingLocations(doctor.uuid)]) : [[], []]; + // ساعات کاری per-location است؛ با uuid آدرس به هر محل وصل می‌شود. + const openingHoursByLocation = bookingLocations.reduce((acc, location) => { + if (location.location_uuid && location.opening_hours?.length) { + acc[location.location_uuid] = location.opening_hours; + } + return acc; + }, {}); + // سرویس‌های قابل رزرو، تجمیع‌شده از همهٔ محل‌ها و یکتاشده بر اساس uuid. const bookableServices = Object.values( bookingLocations @@ -192,6 +200,14 @@ async function Doctor({ params }) { streetAddress: addr.address, }, ...(addr.telephone && { telephone: addr.telephone }), + ...(openingHoursByLocation[addr.uuid]?.length && { + openingHoursSpecification: openingHoursByLocation[addr.uuid].map((shift) => ({ + "@type": "OpeningHoursSpecification", + dayOfWeek: `https://schema.org/${shift.day}`, + opens: shift.opens, + closes: shift.closes, + })), + }), ...(addr.map?.latitude && addr.map?.longitude && { geo: { "@type": "GeoCoordinates",