Files
clinicpro/docs/api/appointment-plan.md
T
hamedandClaude Opus 5 22c89fbae4 feat(plan): multi-segment appointments with per-segment resource requirements
Section 7 of the design document, and the reason the whole resource layer exists.
A laser session is not one block: numbing cream (5 min, room + operator), waiting for
it to work (30 min, room only), the laser itself (20 min, room + operator + device),
aftercare (5 min, room + operator). Under the single-interval model the operator is
locked for all 60 minutes while actually working 30 — half the capacity thrown away.

AppointmentPlanBuilder turns (service, selected items, branch, patient) into a plan:
segments with offsets, durations and resource requirements. It deliberately assigns
no absolute time and no specific resource — that is the next task. This only produces
the *shape* of the appointment.

Segment duration comes from one of two sources. A fixed segment carries its own
number; an item-driven one gets its duration from task 04's DurationCalculator, so
"the laser itself" grows with two treated areas while "waiting for the cream" does
not. One number could not have expressed that.

Three contracts worth stating:

- A service with no segment templates falls back to a single continuous segment
  requiring the doctor resource — exactly today's behaviour. Without it every
  existing service would have become unplannable overnight.
- A segment with no requirements is valid: "waiting at home" consumes time but
  occupies nothing.
- same_gender_as_patient with an unknown patient gender is a 422, not a silently
  dropped requirement. Dropping it quietly would route the patient to a resource the
  clinic said must not serve them.

When no resource qualifies, the error names the role, the skill and the branch —
"no female operator with the skill «Alexandrite laser» is available at «Central»" —
rather than an empty result the caller has to interpret (section 10).

occupancy_offset carries each requirement's setup/cleanup minutes for the availability
engine. It is taken as the maximum across candidates, because the builder does not yet
know which resource will be picked and under-reserving means the next appointment
lands on top of the cleanup.

11 tests covering the document's reference example (offsets 0/5/35/55, total 60),
item-driven scaling, the no-template fallback, all three gender-constraint outcomes,
merging and both caps. 1186 tests overall. phpstan back at its 14-error baseline;
slot-mode frozen contract green.

The admin segments page is not built; the checklist records it with a target. The
backend and preview endpoint are complete and consumable without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 21:07:30 +03:30

5.8 KiB
Raw Blame History

Appointment Plan API — بخش‌های نوبت و سازندهٔ برنامه

Base: /api/v1 · Auth: JWT وابسته به resource.md (منبع و مهارت) و clinic-services.md (مدت آیتم‌ها).


چرا نوبت یک تکه نیست

بند ۷ مستند. یک جلسهٔ لیزر:

بخش مدت اتاق اپراتور دستگاه
مالیدن کرم بی‌حسی ۵ اشغال اشغال آزاد
انتظار اثر کرم ۳۰ اشغال آزاد آزاد
خود لیزر ۲۰ اشغال اشغال اشغال
مراقبت بعد ۵ اشغال اشغال آزاد

با مدل تک‌بازه‌ای، اپراتور ۶۰ دقیقه قفل می‌شود در حالی که ۳۰ دقیقه کار می‌کند — نصف ظرفیت هدر می‌رود.

⚠️ این بخش هیچ زمان مطلقی و هیچ منبع مشخصی تعیین نمی‌کند. فقط شکل نوبت را می‌سازد؛ پیدا کردن وقت و منبع آزاد کارِ تسک بعدی است.


GET/PUT /api/v1/service-item/{uuid}/segments

PUT جایگزینی کامل است. هر بخش:

فیلد نوع توضیح
sequence int ترتیب اجرا
name string الزامی
duration_source fixed | items پیش‌فرض fixed
duration_minutes int برای fixed باید مثبت باشد
patient_present bool پیش‌فرض true؛ «انتظار در خانه» خلافش است
mergeable bool با چند آیتم یک بار می‌آید
requirements array نیازمندی منبع

duration_source: "items" یعنی مدت این بخش از آیتم‌های انتخاب‌شده می‌آید و با فرمول تسک ۰۴ حساب می‌شود. «خود لیزر» با دو ناحیه طولانی‌تر می‌شود، ولی «انتظار اثر کرم» نه — به همین دلیل دو منبع مدت لازم است و یک عدد ثابت کافی نیست.

هر نیازمندی:

فیلد توضیح
type_uuid نوع منبع (اتاق، اپراتور، دستگاه)
skill_uuid مهارت لازم
count پیش‌فرض ۱
occupancy exclusive (قفل کامل) یا shared (ظرفیت می‌شمارد)
constraints فعلاً فقط same_gender_as_patient

۴۲۲: نام خالی · بخش fixed با مدت صفر · مجموع بیش از ۴۸۰ دقیقه · occupancy یا constraint ناشناخته.


POST /api/v1/appointment-plan/preview

{
  "service_uuid": "…لیزر",
  "branch_uuid": "…شعبه",
  "item_uuids": ["…صورت", "…بیکینی"],
  "patient_gender": "female"
}

۲۰۰:

{
  "success": true,
  "data": {
    "total_minutes": 60,
    "segments": [
      { "sequence": 1, "name": "بی‌حسی موضعی", "offset_minutes": 0, "duration_minutes": 5,
        "patient_present": true, "mergeable": false,
        "requirements": [
          { "role": "room", "role_name": "اتاق", "skill_name": null, "count": 1,
            "occupancy": "exclusive", "constraints": [], "candidates": 3,
            "occupancy_offset": { "setup_minutes": 0, "cleanup_minutes": 0 } }
        ] }
    ]
  }
}

candidates تعداد منابع واجد شرایط است. occupancy_offset در نمای کاربر نمایش داده نمی‌شود؛ برای موتور جستجوی وقت است و محافظه‌کارانه از بیشترین مقدارِ کاندیدها گرفته می‌شود — کم گرفتنش یعنی نوبت بعدی روی زمان تمیزکاری بیفتد.

خطاها

کد کِی
ERR_NO_ELIGIBLE_RESOURCE (۴۲۲) هیچ منبعی شرایط یک بخش را ندارد
ERR_VALIDATION_002 (۴۲۲) قید جنسیت هست ولی جنسیت بیمار نامشخص است
ERR_VALIDATION_001 (۴۲۲) مدت بخش تعیین نشده · مجموع بیش از سقف
۴۰۴ سرویس یا شعبهٔ محیط دیگر

پیام ERR_NO_ELIGIBLE_RESOURCE انسانی است و نقش، مهارت و شعبه را می‌گوید: «هیچ اپراتور خانمی با مهارت «لیزر آلکساندرایت» در شعبهٔ «مرکزی» موجود نیست» (بند ۱۰).


سه قرارداد

۱. سرویس بدون الگوی بخش، همان رفتار امروز را می‌گیرد. یک بخش پیوسته به اندازهٔ کل مدت که منبعِ type=doctor را می‌گیرد. بدون این، هر سرویس موجود بی‌برنامه می‌شد.

۲. بخش بدون هیچ نیازمندی معتبر است. «انتظار در خانه» زمان می‌گیرد ولی هیچ منبعی اشغال نمی‌کند.

۳. قید جنسیت وقتی جنسیت بیمار نامشخص است نادیده گرفته نمی‌شود. ۴۲۲ می‌دهد، چون رد کردن بی‌صدا یعنی بیمار به منبعی می‌رسد که قرار نبود.

mergeable هم‌نام‌ها یک بار می‌آیند: «آماده‌سازی» با دو ناحیه یک بار انجام می‌شود. تکرار شدنِ کارِ اصلی با duration_source: "items" بیان می‌شود، نه با تکرار بخش.


طبقه‌بندی محیط

جدول وضعیت
segment_templates جفت محیط (از بخشِ سرویس مشتق می‌شود)
segment_requirements AGGREGATE_CHILDREN — ریشه SegmentTemplate

تست‌ها

ddev exec php bin/phpunit tests/Appointment/AppointmentPlanTest.php   # ۱۱ تست