Files
clinicpro/docs/api/clinic-invitation.md
T

388 lines
9.0 KiB
Markdown

# Clinic Doctor Invitation API
> **Prefix:** `/api/v1/admin/clinic/...` (admin) and `/api/v1/clinic-invitation/...` (public)
Admins invite doctors to clinics via SMS. The doctor receives a secure 96-char token link valid for 72 hours.
---
## POST `/api/v1/admin/clinic/{uuid}/invite-doctor`
Send an invitation to a doctor (by mobile number) to join a clinic.
**Permission:** `ROLE_ADMIN`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | string (UUID) | Clinic UUID |
### Request Body (`application/json`)
```json
{
"mobile": "09123456789",
"name": "دکتر علی احمدی",
"specialty": "قلب و عروق"
}
```
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `mobile` | string | ✅ | Format: `09XXXXXXXXX` |
| `name` | string | ❌ | Doctor's display name |
| `specialty` | string | ❌ | Specialty label for SMS |
### Response `201`
```json
{
"success": true,
"data": {
"uuid": "inv-uuid-...",
"mobile": "09123456789",
"invited_name": "دکتر علی احمدی",
"invited_specialty": "قلب و عروق",
"status": "pending",
"invited_at": 1717000000,
"expires_at": 1717259200,
"token_used": false,
"clinic": { "uuid": "...", "name": "کلینیک الوند" }
}
}
```
> SMS is dispatched **asynchronously** via Symfony Messenger → Redis queue.
> SMS text: `"دکتر گرامی، کلینیک {name} شما را برای همکاری دعوت کرده است.\nبرای بررسی: {link}\nاین لینک تا ۷۲ ساعت معتبر است."`
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing or invalid token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
| `ERR_VALIDATION_001` | 422 | Invalid mobile format |
---
## GET `/api/v1/admin/clinic/{uuid}/invitations`
List all invitations for a clinic.
**Permission:** `ROLE_ADMIN`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | string (UUID) | Clinic UUID |
### Query Parameters
| Param | Type | Required | Default |
|-------|------|----------|---------|
| `page` | integer | ❌ | 1 |
| `limit` | integer | ❌ | 50 |
### Response `200`
```json
{
"success": true,
"data": [
{
"uuid": "...",
"mobile": "09123456789",
"invited_name": "دکتر علی احمدی",
"invited_specialty": "قلب و عروق",
"status": "pending",
"invited_at": 1717000000,
"expires_at": 1717259200,
"responded_at": null,
"token_used": false,
"doctor": null
}
],
"meta": {
"totalRecords": 5,
"totalPages": 1,
"currentPage": 1
}
}
```
**Invitation Status Values:**
| Value | Description |
|-------|-------------|
| `pending` | Sent, awaiting response |
| `accepted` | Doctor accepted |
| `rejected` | Doctor rejected |
| `suspended` | Suspended by admin |
| `removed` | Removed |
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
---
## POST `/api/v1/admin/clinic/invitation/{invUuid}/resend`
Resend the invitation SMS with a fresh token and reset expiry to +72 hours.
**Permission:** `ROLE_ADMIN`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `invUuid` | string (UUID) | Invitation UUID |
### Response `200`
```json
{
"success": true,
"data": { "message": "دعوتنامه مجدداً ارسال شد" }
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Invitation not found |
---
## PATCH `/api/v1/admin/clinic/invitation/{invUuid}/status`
Change the status of an invitation (e.g., suspend or remove).
**Permission:** `ROLE_ADMIN`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `invUuid` | string (UUID) | Invitation UUID |
### Request Body
```json
{
"status": "suspended"
}
```
| Field | Type | Required | Allowed Values |
|-------|------|----------|----------------|
| `status` | string | ✅ | `pending`, `suspended`, `removed` |
### Response `200`
```json
{
"success": true,
"data": { "status": "suspended" }
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Invitation not found |
| `ERR_VALIDATION_001` | 422 | Invalid status value |
---
## DELETE `/api/v1/admin/clinic/invitation/{invUuid}`
Delete an invitation.
**Permission:** `ROLE_ADMIN`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `invUuid` | string (UUID) | Invitation UUID |
### Response `204`
Empty body.
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_AUTH_006` | 403 | Not admin |
| `ERR_NOT_FOUND_001` | 404 | Invitation not found |
---
## GET `/api/v1/clinic-invitation/{token}`
View invitation details by token (used on the doctor-facing landing page).
**Permission:** `PUBLIC`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `token` | string | 96-char hex token from SMS link |
### Response `200`
```json
{
"success": true,
"data": {
"invitation": {
"uuid": "...",
"mobile": "09123456789",
"invited_name": "دکتر علی احمدی",
"invited_specialty": "قلب و عروق",
"status": "pending",
"expires_at": 1717259200
},
"clinic": {
"uuid": "...",
"name": "کلینیک الوند",
"city": "تهران",
"clinic_logo": "https://..."
},
"is_usable": true
}
}
```
> `is_usable: false` when: token already used, expired, or status is not `pending`
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Token not found |
---
## POST `/api/v1/clinic-invitation/{token}/accept`
Doctor accepts the invitation via SMS link.
**Side-effect:** If a doctor profile exists for this mobile, they are added to `clinic_doctors`. If the invitation's doctor FK was null (doctor registered after invite), the match is resolved at accept time using the mobile number.
**Permission:** `PUBLIC`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `token` | string | 96-char hex token |
### Response `200`
```json
{
"success": true,
"data": { "message": "دعوتنامه پذیرفته شد" }
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Token not found |
| `ERR_NOT_FOUND_001` | 410 | Token expired or already used |
---
## POST `/api/v1/clinic-invitation/{token}/reject`
Doctor rejects the invitation.
**Permission:** `PUBLIC`
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `token` | string | 96-char hex token |
### Response `200`
```json
{
"success": true,
"data": { "message": "دعوتنامه رد شد" }
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Token not found |
| `ERR_NOT_FOUND_001` | 410 | Token expired or already used |
---
## GET `/api/v1/doctor/invitations`
Returns all pending invitations for the authenticated doctor.
**Permission:** `ROLE_DOCTOR`
### Response `200`
```json
{
"success": true,
"data": [
{
"uuid": "...",
"mobile": "09xxxxxxxxx",
"invited_name": "دکتر علی",
"invited_specialty": "قلب",
"status": "pending",
"token_used": false,
"invited_at": 1718000000,
"expires_at": 1718259200,
"responded_at": null,
"doctor": { "uuid": "...", "name": "علی احمدی" },
"clinic": { "uuid": "...", "name": "کلینیک نور", "logo": null }
}
]
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Doctor profile not found for user |
---
## POST `/api/v1/doctor/invitation/{invUuid}/respond`
Doctor accepts or rejects an invitation from their panel (no SMS token needed).
**Permission:** `ROLE_DOCTOR`
**Side-effect on accept:** Doctor is added to `clinic_doctors`. If doctor FK was null at invite time, it is resolved via mobile number at respond time.
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `invUuid` | string | UUID of the invitation |
### Request Body
```json
{ "action": "accept" }
```
| Field | Type | Values |
|-------|------|--------|
| `action` | string | `accept` \| `reject` |
### Response `200`
```json
{
"success": true,
"data": { "message": "دعوتنامه پذیرفته شد" }
}
```
### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Invitation not found or not owned by doctor |
| `ERR_NOT_FOUND_001` | 410 | Invitation expired or already used |
| `ERR_VALIDATION_001` | 422 | action is not accept or reject |