# Pricing API — پیش‌نمایش قیمت و فاکتور تفکیک‌شده > **Base:** `/api/v1` · **Auth:** JWT > مکمل [clinic-services.md](clinic-services.md) و [appointment-booking.md](appointment-booking.md). --- ## تنها یک منبع قیمت قیمت هر سرویس فقط از خودِ سرویس می‌آید: `ServiceItem.price_rials`، همان عددی که در [صفحهٔ سرویس‌ها](clinic-services.md) ویرایش می‌شود. لایه‌های پیشین — **لیست قیمت بازه‌دار** (`price_lists`)، **تعرفهٔ سالانه** (`service_tariffs`) و **قیمت اختصاصی شعبه** (`service_branch_overrides.price_rials`) — حذف شده‌اند: هرکدام جواب متفاوتی به «این سرویس چند است؟» می‌دادند و یک تاریخ می‌توانست چند قیمت داشته باشد. override شعبه سر جایش است ولی فقط **مدت** را تعیین می‌کند. ## زنجیرهٔ قیمت‌گذاری ``` قیمت پایه → + آیتم‌ها → − تخفیف → − بیمهٔ پایه → − تکمیلی → + مالیات → بیعانه ``` `breakdown.sources` برای هر سرویس همیشه `service_item` است — قرارداد پاسخ حفظ شده تا مصرف‌کننده‌ها نشکنند. `PriceSnapshot` فاکتور تفکیک‌شدهٔ لحظهٔ ثبت را نگه می‌دارد: بعد از تغییر قیمت یا تخفیف، باید بشود گفت آن ۲٬۴۰۰٬۰۰۰ ریال از چه تشکیل شده بود. ### دو تصمیم محاسباتی **مالیات روی سهم بیمار حساب می‌شود، نه روی کل.** بیمار مالیاتِ سهمی که بیمه می‌دهد را نمی‌پردازد. **تخفیف بیشتر از مبلغ، مبلغ را صفر می‌کند نه منفی.** بدهی منفی یعنی کلینیک به بیمار پول بدهکار شود، که هیچ‌جای این جریان معنا ندارد. `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_item" } } } ``` --- ## اندپوینت‌های حذف‌شده این مسیرها دیگر وجود ندارند و `404` می‌دهند: | متد | مسیر | |---|---| | 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` | | GET | `/api/v1/service-items/{uuid}/tariffs` | | PUT | `/api/v1/service-items/{uuid}/tariffs/{year}` | برای تغییر قیمت، `PATCH /api/v1/service-item/{uuid}` با `price_rials` را صدا بزنید. --- ## فاکتور نوبت `GET /api/v1/appointment/{uuid}/price-snapshot` فاکتور هنگام `POST /appointment-confirm` و با قیمت‌های **همان لحظه** ثبت می‌شود. اگر بعداً محاسبه می‌شد، تغییر قیمت سرویس بین ثبت و صدور فاکتور عدد دیگری می‌داد. > **قانون پنجم مستند:** «تغییر قیمت هرگز نوبت‌های ثبت‌شده را عوض نمی‌کند.» > `PriceSnapshot` هیچ setter ای ندارد و کلید یکتای `appointment_id` دو فاکتور برای یک > نوبت را در سطح دیتابیس غیرممکن می‌کند. اصلاح قیمت با ردیف تازه و ابطال قبلی انجام > می‌شود، نه با بازنویسی. نوبتِ بدون سرویس (ویزیت سادهٔ حالت اسلاتی) هم فاکتور می‌گیرد، با همان `visit_price_rials` موجود — خالی گذاشتنش یعنی گزارش مالی یک ردیف کم دارد. --- ## طبقه‌بندی محیط | جدول | وضعیت | |---|---| | `price_snapshots` | جفت محیط | ## تست‌ها ```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)