Files
clinicpro/docs/api/discount.md
T
hamedandClaude Fable 5 18d9cc9812 feat(discount): admin Discount Management tab + uuid-based rule targets
Switch rule target references from int ids to uuids (frontend-friendly; the
engine compares uuids directly) via a migration. Add a Discount Management
tab to the subscription page with a full CRUD UI (DiscountTab): table + modal
form with per-type dynamic target fields (tenant tag / service cascade /
amount / visit count / specific patient / occasion + validity window),
priority and combinable/active toggles. Verified TSC + build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:02:03 +03:30

97 lines
4.1 KiB
Markdown

# Discount API
Generic per-tenant discount rules and their evaluation for a patient session.
Owner is resolved from the authenticated user (`ROLE_DOCTOR` → doctor, `ROLE_CLINIC` → clinic). All responses use the shared envelope; single/object payloads are double-nested (`data.data`).
## Rule types (`type`)
| type | target field(s) | meaning |
|------|-----------------|---------|
| `patient_tag` | `target_tag_uuid` (TenantTag uuid) | patient carries the tag |
| `invoice_amount` | `min_amount_rials` | session `final_price_rials` ≥ threshold |
| `specific_patient` | `target_record_uuid` (PatientRecord uuid) | a specific patient's record |
| `occasion` | `valid_from`/`valid_to`, optional `occasion_kind: birthday` | date window; `birthday` also requires today == patient birthday (month/day) |
| `service` | `target_service_item_uuid` (ServiceItem uuid) | session contains that service (discount base = that service's line total) |
| `visit_count` | `min_visit_count` | patient's session count ≥ threshold |
Shared fields: `discount_type` (`percent`|`fixed`), `value` (percent 0..100 or rials), `priority` (int, higher first), `combinable` (bool), `active` (bool), `valid_from`/`valid_to` (unix, nullable).
---
## GET `/api/v1/admin/discount-rules`
List the owner's rules (array hydration, priority desc). **Auth:** doctor/clinic.
### Response `200`
```json
{ "success": true, "data": { "data": [ { "uuid": "…", "name": "بالای ۱۰۰ هزار → ۱۰٪", "type": "invoice_amount", "discount_type": "percent", "value": 10, "priority": 5, "combinable": false, "active": true, "min_amount_rials": 1000000 } ] } }
```
## POST `/api/v1/admin/discount-rules`
Create a rule. **Auth:** doctor/clinic.
### Request Body
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `name` | string | ✅ | |
| `type` | string | ✅ | one of the rule types above |
| `discount_type` | string | ❌ | `percent` (default) or `fixed` |
| `value` | int | ❌ | percent or rials |
| `priority` | int | ❌ | default 0 |
| `combinable` | bool | ❌ | default false |
| `active` | bool | ❌ | default true |
| `valid_from` / `valid_to` | int (unix) | ❌ | validity window |
| `target_tag_uuid` / `target_record_uuid` / `target_service_item_uuid` | string (uuid) | ❌ | per-type target |
| `min_amount_rials` / `min_visit_count` | int | ❌ | per-type threshold |
| `occasion_kind` | string | ❌ | `birthday` or null |
### Response `201`
Created rule object (double-nested `data.data`).
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_001` | 422 | missing name / invalid type |
| `ERR_AUTH_006` | 403 | user is not a doctor/clinic owner |
## PATCH `/api/v1/admin/discount-rules/{uuid}`
Update a rule (owner-scoped). Same body fields (all optional). **Auth:** doctor/clinic.
`404 ERR_VALIDATION_002` if the rule is not found for this owner.
## DELETE `/api/v1/admin/discount-rules/{uuid}`
Delete a rule (owner-scoped). **Auth:** doctor/clinic. Response `200``{ data: { data: { deleted: true } } }`.
---
## GET `/api/v1/session/{uuid}/discount-suggestions`
Evaluate all active owner rules against a session and return applicable discounts. **Auth:** doctor/clinic; the session's record must belong to the caller's owner.
### Response `200`
```json
{
"success": true,
"data": { "data": [
{
"rule_uuid": "740bcfc4-…",
"rule_name": "بالای ۱۰۰ هزار → ۱۰٪",
"type": "invoice_amount",
"discount_type": "percent",
"value": 10,
"discount_rials": 700000,
"combinable": false,
"priority": 5
}
] }
}
```
Ordered by priority desc. Each `discount_rials` is capped at the session's unpaid remainder. Rules that don't apply (or compute to 0) are omitted.
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_002` | 404 | session not found |
| `ERR_AUTH_006` | 403 | not the owner / not a doctor-clinic user |
---
> Applying a suggested discount goes through `PATCH /api/v1/session/{uuid}` with `discount_rule_uuid` (see `patient.md`); the applied rule is recorded on the session as `applied_discount_rule_id` / `applied_discount_rule_label` for audit and reporting.