# Pricing API — لیست قیمت بازه‌دار و فاکتور تفکیک‌شده

> **Base:** `/api/v1` · **Auth:** JWT
> مکمل [clinic-services.md](clinic-services.md) و [appointment-booking.md](appointment-booking.md).

---

## دو شکافی که پر شد

زنجیرهٔ قیمت از قبل وجود داشت و کار می‌کرد
(`ServiceItem → Tariff → بیمه → DiscountRule → Invoice → Payment`). دو چیز کم بود:

۱. **`Tariff` فقط سال دارد.** تغییر تعرفه از اول مهر قابل بیان نبود. حالا `PriceList`
   بازهٔ دقیق می‌گیرد و `Tariff` لایهٔ پشتیبان می‌ماند.
۲. **روی نوبت فقط یک عدد بود.** بعد از تغییر قیمت یا تخفیف نمی‌شد گفت آن ۲٬۴۰۰٬۰۰۰
   ریال از چه تشکیل شده بود. حالا `PriceSnapshot` فاکتور تفکیک‌شدهٔ لحظهٔ ثبت را
   نگه می‌دارد.

## زنجیرهٔ قیمت‌گذاری

```
قیمت پایه → + آیتم‌ها → − تخفیف → − بیمهٔ پایه → − تکمیلی → + مالیات → بیعانه
```

برای **هر** سرویس، اولین منبعی که پیدا شود برنده است:

| اولویت | منبع | از کجا |
|---|---|---|
| ۱ | override شعبه | تسک ۰۴ |
| ۲ | لیست قیمتِ حاکم بر آن تاریخ | همین تسک |
| ۳ | `Tariff` سال | لایهٔ موجود |
| ۴ | `ServiceItem.price_rials` | همیشه هست |

مرحلهٔ چهارم ضامن است که **هرگز صفر یا خطا** برنگردد — تاریخی که هیچ لیستی نمی‌پوشاند
باید قیمت بدهد. `breakdown.sources` می‌گوید هر قیمت از کدام لایه آمده.

### دو تصمیم محاسباتی

**مالیات روی سهم بیمار حساب می‌شود، نه روی کل.** بیمار مالیاتِ سهمی که بیمه می‌دهد را
نمی‌پردازد.

**تخفیف بیشتر از مبلغ، مبلغ را صفر می‌کند نه منفی.** بدهی منفی یعنی کلینیک به بیمار
پول بدهکار شود، که هیچ‌جای این جریان معنا ندارد.

`max_total_discount_percent` سقف جمع تخفیف‌هاست: چند تخفیفِ جداگانه که هرکدام منطقی‌اند،
با هم می‌توانند مبلغ را بی‌معنا کنند.

---

## `POST /api/v1/pricing/quote`

```json
{
  "service_uuid": "…",
  "branch_uuid": "…",
  "item_uuids": ["…"],
  "at": 1785562200,
  "policy": {
    "discount_percent": 10,
    "max_total_discount_percent": 25,
    "insurance_base_percent": 20,
    "insurance_supplementary_percent": 50,
    "tax_percent": 10,
    "deposit_percent": 30
  }
}
```

`at` اختیاری است (پیش‌فرض الان) و تعیین می‌کند کدام لیست قیمت حاکم است.

**۲۰۰:** همان شکلی که `price_snapshot` دارد — عمداً یکی، تا «قیمتی که نشان دادیم» و
«قیمتی که ثبت کردیم» نتوانند واگرا شوند.

```json
{
  "base_rials": 10000000, "items_rials": 2000000, "discount_rials": 1200000,
  "insurance_base_rials": 2160000, "insurance_supplementary_rials": 4320000,
  "tax_rials": 432000, "final_rials": 4752000, "deposit_rials": 1425600,
  "breakdown": { "discounts": [ … ], "sources": { "<service-uuid>": "price_list" } }
}
```

---

## لیست قیمت

| متد | مسیر |
|---|---|
| GET/POST | `/api/v1/price-lists` |
| GET/PATCH/DELETE | `/api/v1/price-list/{uuid}` |
| PUT | `/api/v1/price-list/{uuid}/items` |
| POST | `/api/v1/price-list/{uuid}/activate` |

`address_uuid` تهی‌پذیر است: `null` یعنی «همهٔ شعبه‌های این محیط». لیستِ مخصوصِ یک شعبه
بر لیست عمومی **مقدم** است و با آن **تداخل حساب نمی‌شود** — وگرنه تعریف استثنا برای یک
شعبه ناممکن می‌شد.

**لیست تا فعال نشده هیچ اثری ندارد.** ساختن پیش‌نویس نباید قیمت امروز را عوض کند.

`activate` بازهٔ هم‌پوشان با لیست فعالِ **هم‌دامنه** را `422` می‌کند: یک تاریخ نباید دو
قیمت داشته باشد.

---

## فاکتور نوبت

`GET /api/v1/appointment/{uuid}/price-snapshot`

فاکتور هنگام `POST /appointment-confirm` و با قیمت‌های **همان لحظه** ثبت می‌شود. اگر
بعداً محاسبه می‌شد، تغییر تعرفه بین ثبت و صدور فاکتور عدد دیگری می‌داد.

> **قانون پنجم مستند:** «تغییر قیمت هرگز نوبت‌های ثبت‌شده را عوض نمی‌کند.»
> `PriceSnapshot` هیچ setter ای ندارد و کلید یکتای `appointment_id` دو فاکتور برای یک
> نوبت را در سطح دیتابیس غیرممکن می‌کند. اصلاح قیمت با ردیف تازه و ابطال قبلی انجام
> می‌شود، نه با بازنویسی.

نوبتِ بدون سرویس (ویزیت سادهٔ حالت اسلاتی) هم فاکتور می‌گیرد، با همان
`visit_price_rials` موجود — خالی گذاشتنش یعنی گزارش مالی یک ردیف کم دارد.

---

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

| جدول | وضعیت |
|---|---|
| `price_lists` · `price_snapshots` | جفت محیط |
| `price_list_items` | `AGGREGATE_CHILDREN` — ریشه `PriceList` |

## تست‌ها

```bash
ddev exec php bin/phpunit tests/Pricing   # ۱۲ تست
```

مهم‌ترینش `testBookedAppointmentKeepsItsOriginalInvoiceAfterAPriceChange` است: نوبت ثبت
می‌شود، قیمت سرویس دو برابر می‌شود، `quote` عدد جدید می‌دهد و فاکتور نوبت **همان عدد
قبلی** را. بدون آن، قانون پنجم فقط یک ادعاست.

---

## قوانین دستهٔ «قیمت»

تخفیفی که موتور قوانین می‌دهد **کنار** تخفیف دستیِ درخواست می‌نشیند نه به‌جایش، و
شناسه و نسخهٔ هر قانون در `breakdown.sources.applied_policies` ثبت می‌شود:

```json
"breakdown": {
  "sources": {
    "applied_policies": [
      { "uuid": "…", "name": "تخفیف همین سرویس", "version": 2 }
    ]
  }
}
```

جزئیات دسته‌ها و اثرها: [policy.md](policy.md)

---

## پکیج

`quote` یک `patient_uuid` اختیاری می‌گیرد؛ با آن، پکیج معتبرِ بیمار قیمت پایهٔ سرویس را
می‌پوشاند و `package_will_be_consumed` روشن می‌شود. **پیش‌نمایش هرگز مصرف نمی‌کند** —
مصرف در ثبت نهایی است.

جزئیات: [package.md](package.md)
