refactor(pricing): make the service the only price source
Price lists, annual tariffs and per-branch price overrides each answered
"what does this service cost?" differently, so a single date could carry
several answers and nobody could say which one was right. Price now lives
only on ServiceItem.price_rials, edited from the services page.
- drop PriceList/PriceListItem, their repositories and the seven
/api/v1/price-list(s) endpoints; PricingController keeps only quote and
the appointment price snapshot
- drop Tariff, TariffRepository, TariffService and the two
/service-items/{uuid}/tariffs endpoints; creating or repricing a service
no longer upserts a current-year tariff
- drop price_rials from ServiceBranchOverride; the entity stays for its
duration columns, which DurationCalculator and ServiceSelectionValidator
still read
- InvoiceService reads the item price directly
- PricingEngine collapses to a single source; breakdown.sources always
reports service_item, keeping the response contract intact
- remove the price-lists admin page, its route and settings-menu entry, the
tariff modal and the service detail tariffs tab; useAppointmentInvoice
moves to its own hook file
Migration drops price_lists, price_list_items, service_tariffs and the
override price column.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-45
@@ -267,7 +267,7 @@ refresh مستقیم هم کار کند، بنابراین فیلترکردن س
|
||||
|
||||
**Response 201:** ServiceItem object (شامل `insurance_covered`)
|
||||
|
||||
> **قیمت واحد:** هنگام ساخت سرویس، یک تعرفه برای **سال جاری** با همان `price_rials` بهصورت خودکار ثبت میشود. قیمت سرویس = تعرفهی سال جاری است و همهجا (صورتحساب، مراجعه، مطالبات) از همین قیمت استفاده میشود.
|
||||
> **قیمت واحد:** `price_rials` تنها منبع قیمت است و همهجا (صورتحساب، مراجعه، مطالبات) از همین عدد استفاده میشود.
|
||||
|
||||
---
|
||||
|
||||
@@ -313,49 +313,11 @@ refresh مستقیم هم کار کند، بنابراین فیلترکردن س
|
||||
|
||||
---
|
||||
|
||||
## تعرفهی نسخهدار سالانه (Tariff) — فاز ۳ سیستم صورتحساب
|
||||
|
||||
هر خدمت میتواند برای هر سال شمسی یک تعرفه داشته باشد. اگر تعرفهی سالی ثبت نشود، به `price_rials` خود خدمت fallback میشود (`TariffService::resolvePrice`). سال جاری شمسی سمت سرور با `IntlDateFormatter` (تقویم persian) محاسبه میشود.
|
||||
|
||||
### GET /api/v1/service-items/{uuid}/tariffs
|
||||
|
||||
لیست تعرفههای یک خدمت + قیمت پیشفرض + سال جاری.
|
||||
|
||||
**Permission:** `IS_AUTHENTICATED_FULLY` (مالک خدمت)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"current_year": 1405,
|
||||
"default_price_rials": 500000,
|
||||
"data": [
|
||||
{ "uuid": "…", "service_item_id": 12, "year": 1405, "price_rials": 600000, "is_active": true },
|
||||
{ "uuid": "…", "service_item_id": 12, "year": 1404, "price_rials": 500000, "is_active": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PUT /api/v1/service-items/{uuid}/tariffs/{year}
|
||||
|
||||
ثبت/بهروزرسانی تعرفهی یک سال (upsert). `year` بین ۱۳۹۰ تا ۱۵۰۰.
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{ "price_rials": 600000 }
|
||||
```
|
||||
|
||||
**Response 200:** `{ success, data: { …tariff } }`
|
||||
|
||||
> اگر `year` برابر **سال جاری** باشد، `ServiceItem.price_rials` هم با همین مقدار همگام میشود (قیمت واحد). تعرفهی سالهای دیگر فقط برای محاسبهی صورتحساب همان سال (`TariffService::resolvePrice`) بهکار میرود و قیمت پایهی سرویس را تغییر نمیدهد. همچنین `PATCH /service-item/{uuid}` با تغییر `price_rials`، تعرفهی سال جاری را upsert میکند.
|
||||
|
||||
**Errors:**
|
||||
| Code | HTTP | توضیح |
|
||||
|------|------|-------|
|
||||
| ERR_SERVICE_NOT_FOUND | 404 | سرویس یافت نشد |
|
||||
| ERR_VALIDATION_001 | 422 | سال نامعتبر |
|
||||
## قیمت خدمت — تنها یک منبع
|
||||
|
||||
قیمت هر خدمت فقط `ServiceItem.price_rials` است و با `PATCH /api/v1/service-item/{uuid}`
|
||||
عوض میشود. تعرفهٔ نسخهدار سالانه (`GET|PUT /api/v1/service-items/{uuid}/tariffs[/{year}]`)
|
||||
حذف شده و آن مسیرها `404` میدهند؛ جزئیات زنجیرهٔ محاسبه در [pricing.md](pricing.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -485,17 +447,20 @@ caller's personal ones.
|
||||
- **حلقهٔ پیشنیاز** هنگام ثبت `422` میگیرد، نه در اعتبارسنجی انتخاب: «الف نیازمند ب»
|
||||
و «ب نیازمند الف» اگر هر دو ذخیره میشدند، هیچ انتخابی هرگز معتبر نمیشد.
|
||||
|
||||
## قیمت و مدت اختصاصی شعبه
|
||||
## مدت اختصاصی شعبه
|
||||
|
||||
`PUT /api/v1/service-item/{uuid}/branch-overrides` — جایگزینی کامل.
|
||||
|
||||
```json
|
||||
{ "overrides": [{ "address_uuid": "…", "price_rials": 900000, "solo_duration_minutes": 25 }] }
|
||||
{ "overrides": [{ "address_uuid": "…", "solo_duration_minutes": 25, "additional_duration_minutes": 10 }] }
|
||||
```
|
||||
|
||||
هر فیلد تهیپذیر است و `null` یعنی «همان مقدار خودِ سرویس» — **نه صفر**.
|
||||
override فقط وقتی اعمال میشود که `branch_uuid` به `validate` داده شود.
|
||||
|
||||
**قیمت اینجا نیست.** `price_rials` از این اندپوینت حذف شده؛ ارسالش نادیده گرفته میشود
|
||||
و در پاسخ هم نمیآید.
|
||||
|
||||
## دستهٔ درختی
|
||||
|
||||
`GET /api/v1/service-categories/tree` · `POST/PATCH/DELETE /api/v1/service-category[/{uuid}]`
|
||||
|
||||
Reference in New Issue
Block a user