# طول درمان و پرونده درمان چندجلسه‌ای (فاز اول: لیزر)

## پروژه

`clinicpro` (backend + پنل ادمین). هیچ تغییری در `nobat724_front` و `clinic-pro-tauri` لازم نیست.

## زمینه

این سند خروجی یک جلسه grilling است. ۲۳ تصمیم قفل شد، شش ADR و یک glossary نوشته شد.
پیش از شروع این‌ها را بخوان:

- `clinicpro/CONTEXT.md` — واژگان رسمی این دامنه
- `clinicpro/docs/adr/0001-treatment-sessions-are-not-appointments.md`
- `clinicpro/docs/adr/0002-treatment-areas-are-snapshotted.md`
- `clinicpro/docs/adr/0003-resource-backed-appointments-drop-the-doctor-slot-key.md`
- `clinicpro/docs/adr/0004-session-parameters-are-json-keyed-by-resource-type.md`
- `clinicpro/docs/adr/0005-treatment-workflows-are-tagged-services.md`
- `clinicpro/docs/adr/0006-clinical-record-is-separate-from-the-visit-record.md`
- `clinicpro/docs/architecture/resource-first-model.md`

از واژگان `CONTEXT.md` استفاده کن. مترادف نساز.

## هدف

کلینیک بتواند سرویسی تعریف کند که درمانش چند جلسه طول می‌کشد، و سیستم برای هر بیمار
پرونده درمان بسازد، جلسات را بشمارد، سررسید جلسه بعد را حساب کند، و اپراتور بتواند
برای هر ناحیه بدن، دستگاه و پارامترهایش را ثبت کند.

فاز اول فقط لیزر. ولی هیچ‌جای کد نباید کلمه «لیزر» را بداند، جز پیاده‌سازی workflow.

## آنچه از قبل هست و باید استفاده شود

| چیز | کجا | نکته |
|---|---|---|
| دسته‌بندی گرافی خدمات | `CatalogCategory` + `CatalogCategoryInclude` | نواحی بدن همین‌جا تعریف می‌شوند |
| بستار گذرا و تشخیص تعارض | `CategoryClosureResolver` | `descendants()` و `overlaps()` |
| سرویس | `ServiceItem` با `catalogCategory` | قیمت و مدت اینجاست |
| نوع منبع داینامیک | `ResourceType` | کدهای سیستمی `doctor` / `staff` / `room` |
| منبع با پزشک ناظر | `ClinicResource.supervisor` | تعریف شده ولی در مسیر رزرو خوانده نمی‌شود |
| اشغال منبع در سطح دیتابیس | `OccupancyBucket` | `uniq_bucket_resource_seat(resource_id, bucket_at, seat)` |
| پرسنل و نقش | `ClinicStaff` + `ROLE_STAFF` | `ClinicStaff.user` اختیاری است |
| داشبورد پرسنل | `GET /api/v1/dashboard/staff` | فیلتر روی `a.staff` |
| صفحه پرسنل | `assets/admin/pages/StaffMyServicesPage.tsx` | فقط سرویس‌های تخصیص‌یافته |
| مراجعه مالی | `PatientSession` | در `AppointmentConfirmationService::onConfirmed` ساخته می‌شود |

## آنچه نیست

هیچ موجودیتی برای پرونده درمان، جلسه درمان، ثبت ناحیه، پروتکل، یا حوزه فعالیت.
`ServiceItem.sessionCount` هست ولی هیچ منطقی از آن استفاده نمی‌کند — عملاً مرده.

---

## تصمیم‌های قفل‌شده

این‌ها در جلسه grilling تصمیم‌گیری شده‌اند. اجرایشان کن، دوباره طراحی نکن.
اگر جایی از کد با یکی از این‌ها تناقض داشت، **متوقف شو و بپرس**؛ خودت تصمیم را عوض نکن.

1. **حوزه فعالیت از `Specialty` جداست.** `Specialty` قرارداد سایت عمومی است و دست نمی‌خورد.
2. **هر کلینیک یک حوزه فعالیت دارد.** نال یعنی تنظیم‌نشده و رفتار امروز.
3. **جلسه درمان موجودیت مستقل است**، نه `Appointment`.
4. **زمان‌بندی با لیست صریح گام‌ها.** فاصله ثابت نداریم.
5. **دوره پایان مشخص دارد.** دوره بی‌پایان وجود ندارد.
6. **همه جلسات از ابتدا ساخته می‌شوند**، ولی فقط جلسه بعدی نوبت می‌گیرد.
7. **ناحیه درمان همان `CatalogCategory` است.** هیچ entity جدیدی برای ناحیه ساخته نشود.
8. **نواحی از دسته سرویس مشتق می‌شوند**، هنگام رزرو انتخاب نمی‌شوند.
9. **فهرست نواحی هنگام ساخت پرونده قفل می‌شود** (snapshot).
10. **پزشک ناظرِ منبع روی نوبت می‌نشیند.** پرسنل انجام می‌دهد.
11. **نوبتی که منبع دارد `active_slot_key` ندارد.** حفاظت فقط با `OccupancyBucket`.
12. **`TreatmentProtocol` موجودیت جداست**، یک‌به‌یک با `ServiceItem`.
13. **پارامترهای دستگاه در JSON**، تعریفشان روی `ResourceType`.
14. **Workflow با tagged service.** موتور داده‌محور نداریم.
15. **حوزه فعالیت را فقط ادمین پلتفرم می‌سازد.**
16. **جلسه وضعیت مستقل دارد** و نوبت را هم‌گام می‌کند. بستن جلسه با ناحیه ناتمام مجاز است.
17. **`TreatmentSession` بالینی است، `PatientSession` مالی.** هیچ فیلد پولی روی جلسه درمان.
18. **قیمت هر جلسه با قیمت روز.** پکیج قیمت نداریم. قیمت هیچ‌وقت قفل نمی‌شود.
19. **جلسه بعد پیشنهاد می‌شود، منشی تأیید می‌کند.** رزرو خودکار بدون انسان نداریم.
20. **حوزه فعالیت فقط workflow را انتخاب می‌کند.** داشبورد اختصاصی به‌ازای تخصص نداریم.
21. **سررسید هر جلسه نسبی به تاریخ واقعی جلسه قبل است.**
22. **no-show جلسه را نمی‌سوزاند.** تعداد جلسات ثابت می‌ماند.
23. **زمان واقعی جدا ثبت می‌شود.** `slotStart` و `slotEnd` هرگز بازنویسی نمی‌شوند.

---

## وظایف

هر وظیفه را جداگانه پیاده کن، تست بنویس، و بعد سراغ بعدی برو.

### ۱. حوزه فعالیت کلینیک

موجودیت جدید در `src/PracticeDomain/Entity/PracticeDomain.php`:

```php
// جدول سراسری (global) — نه per-tenant. در GlobalTables ثبت شود.
id, uuid, code (unique), name, sort_order, active, created_at, updated_at
```

`code` پایدار است و workflow به آن bind می‌شود. بعد از ساخت قابل ویرایش نیست.

روی `Clinic` یک `ManyToOne` نال‌پذیر اضافه کن:

```php
#[ORM\ManyToOne(targetEntity: PracticeDomain::class)]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
private ?PracticeDomain $practiceDomain = null;
```

migration نباید مقدار پیش‌فرض برای کلینیک‌های موجود بگذارد. نال بماند.

اندپوینت‌ها:

- `GET  /api/v1/practice-domains` — فهرست فعال‌ها. برای همه نقش‌های پنلی.
- `POST /api/v1/admin/practice-domains` — فقط `ROLE_ADMIN`.
- `PATCH /api/v1/admin/practice-domains/{uuid}` — فقط `name` و `active` و `sort_order`.
- `PATCH /api/v1/clinic/{uuid}/practice-domain` — مدیر کلینیک انتخاب می‌کند.

در پاسخ هر حوزه یک فیلد `has_workflow` بگذار که از `TreatmentWorkflowRegistry` می‌آید.
پنل ادمین پلتفرم باید بتواند نشان دهد کدام حوزه هنوز workflow ندارد.

seed اولیه با migration: `beauty` = «کلینیک زیبایی».

### ۲. پروتکل درمان

سه موجودیت در `src/Treatment/Entity/`:

```php
// TreatmentProtocol — وجود این ردیف یعنی سوییچ «طول درمان» روشن است
id, uuid, service_item_id (unique, ON DELETE CASCADE),
supervisor_doctor_id (nullable), active, created_at, updated_at

// TreatmentProtocolStep — گام‌های دوره
id, protocol_id, step_number, offset_days, created_at
// UNIQUE(protocol_id, step_number)
// offset_days یعنی «فاصله از جلسهٔ قبل»، نه از شروع دوره. گام ۱ همیشه offset_days = 0.

// TreatmentProtocolStaff — پرسنل مجاز به انجام
id, protocol_id, staff_id
// UNIQUE(protocol_id, staff_id)
```

قواعد اعتبارسنجی:

- حداقل دو گام. پروتکل یک‌جلسه‌ای معنی ندارد؛ آن یعنی سوییچ خاموش.
- `step_number` پیوسته از ۱.
- گام اول `offset_days = 0`. بقیه بزرگ‌تر از صفر.
- حداقل یک پرسنل مجاز.
- `supervisor_doctor_id` باید پزشکِ همان محیط باشد.

`ServiceItem::$sessionCount` را `@deprecated` علامت بزن، از `toArray()` بیرون **نبر**
(پنل و تایپ TS به آن وابسته‌اند) ولی هیچ منطق جدیدی از آن نخوان. تعداد جلسات
همیشه `count(protocol.steps)` است.

اندپوینت‌ها زیر `/api/v1/clinic-services/{serviceUuid}/treatment-protocol`:
`GET`، `PUT` (کل پروتکل با گام‌ها و پرسنل یکجا)، `DELETE` (خاموش کردن سوییچ).

### ۳. پرونده و جلسه درمان

```php
// TreatmentCase
id, uuid, entity_type, entity_id,          // tenant
patient_record_id, service_item_id, protocol_id,
supervisor_doctor_id (nullable),
status,                                     // active | completed | abandoned
total_sessions,                             // snapshot از تعداد گام‌ها
opened_at, closed_at (nullable),
created_at, updated_at

// TreatmentCaseArea — snapshot نواحی (تصمیم ۹)
id, case_id, catalog_category_id, name_snapshot, sort_order
// UNIQUE(case_id, catalog_category_id)

// TreatmentSession
id, uuid, case_id, session_number,
appointment_id (nullable, ON DELETE SET NULL),
performed_by_staff_id (nullable),           // تصمیم ۲۳
status,                                     // planned | booked | in_progress | done | cancelled | no_show
due_at (nullable),                          // تخمینی، بعد از هر جلسه بازمحاسبه می‌شود
started_at (nullable), finished_at (nullable),
note (nullable),
created_at, updated_at
// UNIQUE(case_id, session_number)

// SessionAreaRecord
id, uuid, session_id, case_area_id,
resource_id (nullable),                     // دستگاه — در سطح ناحیه
status,                                     // pending | in_progress | completed | skipped
parameters JSON (nullable),                 // { "energy": 18, "pulse": 3, "shots": 212 }
started_at (nullable), finished_at (nullable),
note (nullable),
created_at, updated_at
// UNIQUE(session_id, case_area_id)
```

**هیچ ستون پولی روی این جدول‌ها نگذار.** ADR-0006.

`name_snapshot` روی `TreatmentCaseArea` عمدی است: اگر مدیر بعداً اسم دسته را عوض کند،
سابقه درمان نباید تغییر کند.

نواحی هنگام ساخت پرونده اینطور حساب می‌شوند:

```
leaves = برگ‌های CategoryClosureResolver::descendants(service.catalogCategory)
اگر descendants خالی بود → خودِ service.catalogCategory تنها ناحیه است
```

«برگ» یعنی دسته‌ای که خودش `descendants` ندارد. دسته‌های میانی فقط گروه‌بندی‌اند و
ناحیه درمان نیستند.

### ۴. اتصال پزشک ناظر به مسیر رزرو

در `AppointmentController` بلوکی که پزشک را از منبع استنتاج می‌کند (حدود خط ۴۷۱)
یک fallback اضافه کن:

```php
if ($doctorUuid === '' && $resource->subject() instanceof Doctor) {
    $doctorUuid = $resource->subject()->getUuid();
}
// جدید:
if ($doctorUuid === '' && $resource->getSupervisor() !== null) {
    $doctorUuid = $resource->getSupervisor()->getUuid();
}
```

اگر منبعی نه پزشک است نه ناظر دارد، خطای واضح بده:
«این منبع پزشک ناظر ندارد؛ ابتدا در تنظیمات منابع پزشک ناظر را مشخص کنید».
پیام فعلی (`doctor_uuid یا resource_uuid ...`) گمراه‌کننده است.

### ۵. رزرو منبع مستقل از پزشک

**این وظیفه بعد از بررسی داده واقعی بازنویسی شد. ADR-0003 را بخوان.**

آنچه با داده تأیید شد:

- دو مسیر رزرو داریم و هیچ‌کدام ردیف دیگری نمی‌سازد.
  مسیر پنل روی `appointments.resource_id` می‌نشیند، مسیر hold روی `resource_occupancy`.
- `bookAtomically` روی **پزشک** قفل می‌گیرد و `isSlotTaken` تداخل بازه‌ای را فقط روی پزشک
  می‌سنجد. منبع در آن کوئری نیست.
- در دیتابیس فعلی هر محیط چند منبع با یک پزشک ناظر مشترک دارد. کلینیک ۲ شش منبع با پزشک ۶،
  کلینیک ۳ سه منبع با پزشک ۹. پس این باگ همین حالا فعال است.
- برنامه هفتگی پزشک مانع نیست؛ `resolveSlotLocationId` فقط `null` برمی‌گرداند.

پنج تغییر:

۱. مسیر پنل هنگام رزرو `ResourceOccupancy` بسازد، همان‌طور که `HoldService` می‌سازد.
   منطق مشترک در یک سرویس باشد، در دو جا کپی نشود.

۲. لغو یا انقضای نوبت، ردیف اشغال را `released` کند.

۳. در `Appointment::refreshActiveSlotKey()` وقتی منبع هست کلید `null` بماند:

```php
$this->activeSlotKey = (!$this->isReserve
    && $this->resource === null
    && in_array($this->status, self::SLOT_OCCUPYING_STATUSES, true))
        ? sprintf('%d:%d', $this->doctor->getId(), $this->slotStart)
        : null;
```

`setResource()` باید `refreshActiveSlotKey()` را صدا بزند.

۴. `bookAtomically` وقتی نوبت منبع دارد روی پزشک قفل نگیرد و `isSlotTaken` را صدا نزند.
   تضمین یکتایی از `uniq_bucket_resource_seat` می‌آید که ظرفیت و `seat` را می‌فهمد.

۵. شعبه نوبتِ منبع‌دار از `ClinicResource.getAddress()` بیاید، نه از برنامه پزشک.

**قبل از شروع:** همه مسیرهای ساخت نوبت را فهرست کن و بنویس کدام‌ها منبع ست نمی‌کنند.
آن‌ها کلید پزشک و قفل پزشک را نگه می‌دارند. این فهرست باید در گزارش بیاید.

migration برای `active_slot_key` لازم نیست. برای ردیف‌های اشغالِ گذشتهٔ مسیر پنل یک
migration داده‌ای لازم است تا نوبت‌های فعالِ منبع‌دار موجود ردیف اشغال بگیرند.

### ۶. تعریف فیلد روی نوع منبع

ستون JSON روی `ResourceType`:

```php
#[ORM\Column(name: 'field_schema', type: 'json', nullable: true)]
private ?array $fieldSchema = null;
```

قالب هر فیلد:

```json
{ "key": "energy", "label": "انرژی", "type": "select",
  "options": [7, 8, 9, 10, 12, 14, 16, 18], "required": true, "sort_order": 1 }
```

`type` مجاز: `select` و `number` و `text`. همین سه تا، نه بیشتر.

اعتبارسنجی مقادیر `SessionAreaRecord.parameters` از همین schema می‌آید. یک سرویس
`FieldSchemaValidator` بنویس که هم در ذخیره اندپوینت استفاده شود هم در تست.

کلیدهای ناشناخته که در schema نیستند رد شوند، نه اینکه بی‌صدا ذخیره شوند.

seed اولیه: یک `ResourceType` با کد `laser_device` و نام «دستگاه لیزر» و همان سه فیلد
بالا به‌علاوه `shots` از نوع `number`. `is_system = false` تا مدیر بتواند ویرایشش کند.

### ۷. Workflow قابل توسعه

```php
// src/Treatment/Workflow/TreatmentWorkflow.php
#[AutoconfigureTag('app.treatment_workflow')]
interface TreatmentWorkflow
{
    public function supports(?string $practiceDomainCode): bool;

    /** بعد از تأیید اولین نوبتِ یک سرویسِ پروتکل‌دار */
    public function openCase(Appointment $appointment, TreatmentProtocol $protocol): TreatmentCase;

    /** بعد از بسته شدن یک جلسه — سررسید جلسه بعد را حساب می‌کند */
    public function onSessionFinished(TreatmentSession $session): void;
}
```

`TreatmentWorkflowRegistry` با `#[TaggedIterator('app.treatment_workflow')]` ساخته شود و
اولین workflow ای که `supports()` بدهد را برگرداند. اگر هیچ‌کدام، `DefaultTreatmentWorkflow`
که رفتار عمومی دارد.

**هسته نباید بداند لیزر چیست.** `AppointmentConfirmationService` فقط این را می‌کند:

```
اگر سرویسِ نوبت پروتکل فعال دارد و بیمار پروندهٔ باز برای همان سرویس ندارد
    → registry->for(clinic.practiceDomain?.code)->openCase(...)
```

`LaserTreatmentWorkflow` در فاز اول تقریباً همان `DefaultTreatmentWorkflow` است.
جدا نگهش دار حتی اگر خالی باشد — نقطه اتصال آینده است.

### ۸. سررسید و جلسه بعد

قاعده محاسبه (تصمیم ۲۱):

```
due_at(session n) = finished_at(session n-1) + protocol.step[n].offset_days
جلسه ۱ سررسید ندارد؛ تاریخش همان نوبت اول است.
```

بعد از `finished_at` شدن هر جلسه، فقط `due_at` **جلسه بعدی** بازمحاسبه شود، نه کل دوره.
جلسات دورتر تخمین قبلی‌شان را نگه می‌دارند تا نوبتشان برسد.

no-show (تصمیم ۲۲): جلسه به `no_show` می‌رود، `session_number` عوض نمی‌شود،
`total_sessions` عوض نمی‌شود. یک جلسه جایگزین با همان شماره **ساخته نمی‌شود**؛ همان جلسه
دوباره به `planned` برمی‌گردد و `due_at` از تاریخ جلسه قبلِ **انجام‌شده** حساب می‌شود.

رزرو جلسه بعد (تصمیم ۱۹) **خودکار نیست**. اندپوینت پیشنهاد بده:

```
GET /api/v1/treatment-sessions/{uuid}/slot-suggestions
→ اسلات‌های آزاد منبع، از due_at به بعد، با استفاده از ResourceFreeTimeCalculator
```

منشی یکی را انتخاب می‌کند و مسیر عادی ساخت نوبت اجرا می‌شود، سپس نوبت به جلسه وصل می‌شود.

### ۹. اندپوینت‌های اجرای جلسه

همه زیر `ROLE_STAFF` یا بالاتر. علاوه بر نقش، بررسی کن پرسنل واقعاً به این جلسه دسترسی
دارد — الگویش در `DashboardController::staff` هست (`findActiveByUserAndEntity`).

```
GET   /api/v1/dashboard/staff/treatment-sessions      جلسات امروز پرسنل
GET   /api/v1/treatment-sessions/{uuid}               جزئیات + نواحی + فیلدهای دستگاه
POST  /api/v1/treatment-sessions/{uuid}/start         → in_progress، started_at
POST  /api/v1/treatment-sessions/{uuid}/finish        → done، finished_at، note
POST  /api/v1/session-areas/{uuid}/start              → in_progress، started_at
POST  /api/v1/session-areas/{uuid}/complete           → completed، parameters، finished_at
POST  /api/v1/session-areas/{uuid}/skip               → skipped
GET   /api/v1/treatment-cases                         فهرست پرونده‌ها با فیلتر
GET   /api/v1/treatment-cases/{uuid}                  پرونده + همه جلسات
```

هم‌گام‌سازی وضعیت نوبت (تصمیم ۱۶):

```
session start   → Appointment::STATUS_SALON
session finish  → Appointment::STATUS_COMPLETED
```

از `ALLOWED_TRANSITIONS` عبور کن، مستقیم `setStatus` نزن. اگر گذار مجاز نبود، جلسه را
ببند ولی نوبت را دست نزن و لاگ بگذار — خطای ۵۰۰ نده.

بستن جلسه با ناحیه ناتمام **مجاز است** (تصمیم ۱۶). فقط در پاسخ تعداد ناتمام‌ها را برگردان
تا پنل هشدار نشان دهد.

`started_at` و `finished_at` هرگز روی `Appointment` نوشته نشوند (تصمیم ۲۳).

### ۱۰. پنل ادمین

قواعد اجباری این پروژه:

- هر `select` باید `components/ui/SearchableSelect` باشد. `<select>` بومی ممنوع.
- هر بولین باید `components/ui/Switch` باشد. checkbox بومی ممنوع.
- صفحه جدید با تم و Layout و کامپوننت‌های موجود ساخته شود. طراحی جدید نکن.
- تاریخ‌ها شمسی. متن‌ها فارسی. RTL.

صفحه‌ها و تغییرها:

1. **فرم سرویس** — سوییچ «طول درمان». روشن که شد: جدول گام‌ها (شماره و فاصله از جلسه قبل)،
   `SearchableSelect` چندتایی پرسنل مجاز، `SearchableSelect` پزشک ناظر.
2. **تنظیمات کلینیک** — `SearchableSelect` حوزه فعالیت.
3. **صفحه نوع منابع** — ویرایشگر `fieldSchema`. افزودن و حذف فیلد، انتخاب نوع، گزینه‌ها.
4. **پنل ادمین پلتفرم** — CRUD حوزه‌های فعالیت با نشان «workflow دارد / ندارد».
5. **فهرست پرونده‌های درمان** — نام بیمار، سرویس، جلسه چندم از چند، وضعیت، سررسید بعدی، اپراتور.
6. **صفحه اجرای جلسه** — کارت هر ناحیه با دکمه شروع، تایمر زنده، فرم داینامیک از `fieldSchema`،
   دکمه اتمام ناحیه، دکمه لغو ناحیه. پایین صفحه یادداشت کلی و دکمه اتمام جلسه با هشدار
   نواحی ناتمام. اسکرین‌شات‌های مرجع در تیکت این کار هست.
7. **صف «جلسات بدون نوبت»** — جلساتی که `status = planned` و `due_at` گذشته یا نزدیک است.
   از هر ردیف مستقیم به انتخاب اسلات پیشنهادی.

تایمر فقط نمایشی است. زمان معتبر همان `started_at` و `finished_at` سرور است.

---

## ترتیب اجرا

```
۱ → ۲ → ۳ → ۶ → ۷ → ۴ → ۵ → ۸ → ۹ → ۱۰
```

وظیفه ۵ (کلید اسلات) روی پرترافیک‌ترین جدول سیستم است. بعد از وظیفه ۴ انجامش بده و
قبل و بعدش تست رگرسیون رزرو را کامل اجرا کن.

## تست

- unit برای `CategoryClosureResolver` در حالت برگ‌یابی نواحی
- unit برای محاسبه `due_at` با گام‌های نامساوی (سناریوی بوتاکس: ۰، ۱۵، ۳۰، ۳۰)
- unit برای `FieldSchemaValidator` با کلید ناشناخته و مقدار خارج از `options`
- functional: پزشک ناظر مشترک بین دو دستگاه، دو رزرو هم‌ساعت، هر دو باید موفق شوند
- functional: اتاق با `capacity = 3`، سه رزرو هم‌ساعت، هر سه باید موفق شوند
- functional: چرخه کامل یک دوره سه‌جلسه‌ای شامل یک no-show
- رگرسیون: رزرو بدون منبع همچنان با کلید پزشک از دوبار رزرو جلوگیری کند

## خارج از دامنه فاز اول

- seeder هوشمند با AI برای پیشنهاد دسته‌بندی و منابع
- گزارش‌های تحلیلی روی `parameters` (کوئری JSON بدون ایندکس)
- حوزه فعالیت دوم غیر از زیبایی
- داشبورد اختصاصی به‌ازای هر تخصص
- قیمت پکیج و قفل قیمت

## مستندسازی

طبق قانون ثابت این پروژه، `docs/api/*` در همان session به‌روز شود.
اگر تصمیمی در حین اجرا عوض شد، ADR مربوطه به‌روز شود یا ADR جدید نوشته شود.
