Files
clinicpro/docs/api/clinic.md
T
hamed e4ddd38f0c feat: add per-doctor permissions management in clinics
- Implement DoctorPermissionsModal for managing doctor permissions in clinics.
- Create usePermissions hook to handle user permissions context.
- Add migration for clinic_doctor_permissions table with default permissions.
- Develop ClinicDoctorPermissionController for handling permissions API.
- Create ClinicDoctorPermission entity to manage permissions data.
- Implement ClinicDoctorPermissionRepository for database interactions.
- Add ClinicDoctorPermissionChecker for permission validation logic.
- Write tests for clinic doctor permissions functionality.
2026-07-18 09:44:13 +03:30

19 KiB
Raw Blame History

Clinic API

Prefix: /api/v1/clinic, /api/v1/clinics


POST /api/v1/clinic

Create a new clinic.

Permission: AUTH — any authenticated user becomes the clinic owner

Request Body (application/json)

{
  "name": "کلینیک الوند",
  "info": "توضیحات کلینیک",
  "address": "تهران، خیابان ولیعصر",
  "telephone": "02112345678",
  "working_days": "شنبه تا چهارشنبه",
  "is_24_7": false,
  "latitude": 35.6892,
  "longitude": 51.3890,
  "state": "تهران",
  "city": "تهران",
  "image_clinic": [
    { "url": "https://..." }
  ],
  "clinic_logo": "https://...",
  "doctors": ["uuid1", "uuid2"],
  "specialties": [1, 2],
  "doctor_services": [3, 4],
  "insurance": [5, 6]
}
Field Type Required Description
name string Clinic name
info string Description
address string Full address
telephone string Contact number
working_days string Working days description
is_24_7 boolean Open 24/7 flag
latitude float Latitude for map
longitude float Longitude for map
state string Province name
city string City name
image_clinic object[] Gallery images [{url: "..."}]max 5; more returns ERR_VALIDATION_001 (422)
clinic_logo string Logo URL
doctors string[] Doctor UUIDs to associate
specialties integer[] Specialty IDs
doctor_services integer[] Service IDs
insurance integer[] Insurance IDs
social_media object Social media URLs — keys: instagram, telegram, aparat, youtube, linkedin. Values are validated as URLs; invalid/empty values are stored as null.

Response 201

{
  "success": true,
  "data": {
    "data": {
      "uuid": "550e8400-...",
      "name": "کلینیک الوند",
      "info": "...",
      "address": "...",
      "telephone": "02112345678",
      "working_days": "...",
      "is_24_7": false,
      "latitude": 35.6892,
      "longitude": 51.3890,
      "state": "تهران",
      "city": "تهران",
      "images_clinic": [{ "url": "https://..." }],
      "clinic_logo": "https://...",
      "is_active": true,
      "doctors": [],
      "specialties": [],
      "doctor_services": [],
      "insurance": [],
      "tags": [],
      "created_at": 1717000000
    }
  }
}

⚠️ Double-nested: Frontend extracts with data?.data?.data

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing or invalid token

GET /api/v1/clinic/{uuid}

Get clinic detail.

Permission: PUBLIC

Path Parameters

Param Type Description
uuid string (UUID) Clinic UUID

Response 200

{
  "success": true,
  "data": {
    "data": {
      "id": "233",
      "uuid": "550e8400-...",
      "name": "کلینیک الوند",
      "title": "کلینیک الوند",
      "is_active": true,
      "phone": "02112345678",
      "phone_number": "02112345678",
      "logo": "/uploads/clinics/logo/...",
      "clinic_logo": "/uploads/clinics/logo/...",
      "images_clinic": [{ "url": "/uploads/clinics/gallery/..." }],
      "social_media": {
        "instagram": "https://instagram.com/clinic.example",
        "telegram": "https://t.me/clinic_example",
        "aparat": null,
        "youtube": null,
        "linkedin": null
      },
      "caption": "توضیحات کلینیک",
      "list_bime": [],
      "specialties": [{ "uuid": "...", "id": "1", "name": "قلب", "parent": null }],
      "services": [],
      "clinic_specialty": [{ "uuid": "...", "id": "1", "name": "قلب", "parent": null }],
      "doctors": 5,
      "doctor_list": null,
      "city": [{ "uuid": "...", "id": "132", "name": "یزد", "parent": "100" }],
      "state": [{ "uuid": "...", "id": "100", "name": "یزد" }],
      "location": "یزد، خیابان اصلی، پلاک 101",
      "map": { "latitude": "31.868", "longitude": "54.330" },
      "24_7": false,
      "field_working_days": "شنبه تا پنجشنبه ۸ تا ۱۸"
    }
  }
}

city/state/map are resolved from the clinic's address (DoctorAddress linked by clinic_id), not from columns on the clinic. Each is an array with a single object (or empty [] if the clinic has no address). doctors is a count; the actual doctor list comes from GET /api/v1/clinic/doctor-list/{clinicUuid} (doctor_list here is always null).

Errors

Code HTTP Description
ERR_NOT_FOUND_001 404 Clinic not found

PATCH /api/v1/clinic/{uuid}

Update a clinic.

Permission: AUTH — the clinic owner, ROLE_ADMIN, or a member doctor holding clinic_info.update (see Clinic Doctor Permissions)

Path Parameters

Param Type Description
uuid string (UUID) Clinic UUID

Request Body

Same fields as POST — all optional.

Response 200

Updated clinic object (same structure as GET).

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_FORBIDDEN_001 403 Not the owner
ERR_NOT_FOUND_001 404 Clinic not found

GET /api/v1/clinics

List clinics with pagination.

Permission: PUBLIC

Query Parameters

Param Type Required Description
page integer Default: 1
limit integer Default: 20
name string Search by clinic name
city integer City id — filters by the clinic address's city
state integer Province id — filters by the clinic address's province
specialty integer Specialty id
domain string دامنه‌ی سایتِ درخواست‌کننده. اگر دامنه‌ی یک نماینده سراسری باشد، فقط کلینیک‌های همان نماینده برمی‌گردند و city/state نادیده گرفته می‌شوند؛ دامنه شهری/ناشناخته اثری ندارد

city/state are matched against the clinic's address (DoctorAddress linked by clinic_id), not a field on the clinic itself.

Response 200

{
  "success": true,
  "data": [
    {
      "id": "3426",
      "uuid": "...",
      "name": "کلینیک الوند",
      "title": "کلینیک الوند",
      "phone": "02112345678",
      "phone_number": "02112345678",
      "logo": "/uploads/clinics/logo/...",
      "clinic_logo": "/uploads/clinics/logo/...",
      "images_clinic": [{ "url": "/uploads/clinics/gallery/..." }],
      "doctors_count": 4,
      "is_active": true,
      "created_at": 1781762386,
      "city": "تهران",
      "state": "تهران",
      "specialties": [{ "uuid": "...", "id": "7", "name": "..." }],
      "24_7": false,
      "field_working_days": "شنبه تا پنجشنبه ۸ تا ۲۰"
    }
  ],
  "meta": {
    "totalRecords": 30,
    "totalPages": 2,
    "currentPage": 1
  }
}
Field Type Description
clinic_logo / logo string|null Logo path (relative /uploads/... or absolute URL)
doctors_count integer Number of doctors linked to the clinic
city string|null City name, resolved from the clinic's address (DoctorAddress)
state string|null Province name, resolved from the clinic's address (DoctorAddress)
24_7 boolean Open 24/7 flag
field_working_days string|null Working days/hours description

GET /api/v1/clinic/doctor-list/{clinicUuid}

Get doctors associated with a clinic.

Permission: PUBLIC

Path Parameters

Param Type Description
clinicUuid string (UUID) Clinic UUID

Query Parameters

Param Type Required Description
page integer Default: 1
limit integer Default: 10, max 50
name string Filter by doctor name (LIKE)
specialty integer Specialty id
gender string man / woman
degree string expert / general / specialist / subspecialistplus
active 0|1 Only doctors with appointments enabled
sort string ASC / DESC by rating (default DESC)

Filters apply only within this clinic's linked doctors.

⚠️ Double-nested: the doctors array is at data.data (extract with data?.data?.data); pagination is at data.meta.

Response 200

{
  "success": true,
  "data": {
    "data": [
      {
        "id": "1207",
        "uuid": "...",
        "name": "دکتر آرمان رضایی",
        "gender": "man",
        "degree": "specialist",
        "img": [],
        "specialties": [{ "uuid": "...", "id": "2", "name": "داخلی عمومی" }],
        "satisfaction": "96",
        "point": "4.8",
        "free_turn": "پنجشنبه 09:0013:00",
        "hours_of_work": "شنبه تا چهارشنبه | پنجشنبه",
        "active": true
      }
    ],
    "meta": { "totalRecords": 3, "totalPages": 1, "currentPage": 1 }
  }
}
Field Type Description
free_turn string Next available appointment (e.g. پنجشنبه 09:0013:00), or نوبت آزادی موجود نیست if the doctor has no active weekly schedule
hours_of_work string Working-days summary, or برنامه کاری تنظیم نشده when unscheduled
active boolean true only when appointments are enabled and the doctor has an active schedule

free_turn/hours_of_work/active are computed from each doctor's WeeklySchedule (loaded in bulk by the endpoint). Without a schedule they fall back to the "not set" values.

Errors

Code HTTP Description
ERR_NOT_FOUND_001 404 Clinic not found

DELETE /api/v1/admin/clinic/{clinicUuid}/doctor/{doctorUuid}

Detach a doctor from a clinic. This removes the clinic↔doctor link (the clinic_doctors association) and the doctor's clinic_doctor_permissions row; it does not delete the doctor or change the doctor's own active appointment flag.

Permission: AUTH — the caller must be ROLE_ADMIN or the owner of this clinic (ROLE_CLINIC whose user owns clinicUuid). Any other authenticated user gets 403.

Path Parameters

Param Type Description
clinicUuid string (UUID) Clinic UUID
doctorUuid string (UUID) Doctor UUID

Response 200

{ "success": true, "data": { "message": "پزشک از کلینیک جدا شد" } }

Errors

Code HTTP Description
ERR_ACCESS_DENIED 403 Caller is neither an admin nor the clinic owner
ERR_VALIDATION_002 404 Clinic not found
ERR_NOT_FOUND_001 404 Doctor not found, or doctor not linked to this clinic

Clinic Doctor Permissions

Each doctor attached to a clinic has a permission envelope scoped to that clinic only — the doctor's own practice is never affected. Rows live in clinic_doctor_permissions (one per clinic+doctor) and are created lazily with defaults for doctors who joined before this feature existed.

The envelope is always returned in full ({version, resources}); it is never flattened.

{
  "version": 1,
  "resources": {
    "appointments":         { "view": true, "create": true,  "cancel": true,  "update_status": true },
    "appointment_settings": { "view": true, "update": true },
    "patients":             { "view": true, "create": true,  "update": true,  "delete": false },
    "payments":             { "view": true, "create": false, "update": false, "delete": false },
    "services":             { "view": true, "update": false },
    "clinic_info":          { "view": true, "update": false }
  }
}

active: false revokes everything at once regardless of the individual flags. The clinic owner and ROLE_ADMIN bypass all checks and can never be locked out.

Unknown resources and unknown actions in a PATCH body are silently ignored, so a client cannot invent permission keys.


GET /api/v1/admin/clinic/{clinicUuid}/doctor-permissions

List the permission rows of every doctor in the clinic.

Permission: AUTH — clinic owner or ROLE_ADMIN

Response 200

{
  "success": true,
  "data": [
    {
      "uuid": "ce200cde-826d-11f1-b923-c282b864cdcc",
      "clinic_uuid": "41e325c4-e825-4067-8438-5d828ecaee09",
      "doctor_uuid": "bcabb3a8-cae3-45ec-876c-548f9c1e1569",
      "doctor_name": "دکتر تست",
      "active": true,
      "permissions": { "version": 1, "resources": { "...": {} } },
      "created_at": 1784352916,
      "updated_at": 1784352916
    }
  ]
}

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_ACCESS_DENIED 403 Neither admin nor the clinic owner
ERR_NOT_FOUND_001 404 Clinic not found

GET /api/v1/admin/clinic/{clinicUuid}/doctor/{doctorUuid}/permissions

Read one doctor's permissions. Creates the row with defaults if it does not exist yet.

Permission: AUTH — clinic owner or ROLE_ADMIN

Path Parameters

Param Type Description
clinicUuid string (UUID) Clinic UUID
doctorUuid string (UUID) Doctor UUID — must already be attached to this clinic

Response 200

Single permission object (same shape as one item of the list above).

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_ACCESS_DENIED 403 Neither admin nor the clinic owner
ERR_NOT_FOUND_001 404 Clinic not found, or doctor not attached to this clinic

PATCH /api/v1/admin/clinic/{clinicUuid}/doctor/{doctorUuid}/permissions

Update one doctor's permissions. Deep merge — only the resources/actions present in the body change; everything else keeps its current value.

Permission: AUTH — clinic owner or ROLE_ADMIN

Request Body

{
  "permissions": { "resources": { "payments": { "create": true } } },
  "active": true
}
Field Type Required Description
permissions object {resources: {<resource>: {<action>: bool}}}. The bare {<resource>: {...}} form is also accepted.
active bool false revokes all access to this clinic

Response 200

Updated permission object.

Errors

Code HTTP Description
ERR_AUTH_001 401 Missing token
ERR_ACCESS_DENIED 403 Neither admin nor the clinic owner
ERR_NOT_FOUND_001 404 Clinic not found, or doctor not attached to this clinic
ERR_VALIDATION_001 422 permissions is not an object

Upload clinic logo.

Permission: AUTH

Request

Content-Type: multipart/form-data

Field Type Required Max Size
file binary 5MB

Response 200

{
  "success": true,
  "data": {
    "url": "https://clinic-pro.ddev.site/uploads/clinic/logo_abc.jpg",
    "uuid": "...",
    "filename": "logo_abc.jpg",
    "filemime": "image/jpeg",
    "filesize": 102400
  }
}

Errors

Code HTTP Description
ERR_FILE_001 422 Invalid file type
ERR_AUTH_001 401 Missing token

POST /file/upload/clinic_pro/clinic/field_image_clinic

Upload clinic gallery image.

Permission: AUTH

Request

Content-Type: multipart/form-data

Field Type Required Max Size
file binary 5MB

Response 200

{
  "success": true,
  "data": {
    "url": "https://clinic-pro.ddev.site/uploads/clinic/gallery_abc.jpg",
    "uuid": "...",
    "filename": "gallery_abc.jpg",
    "filemime": "image/jpeg",
    "filesize": 307200
  }
}

After uploading, use the returned url inside image_clinic: [{ "url": "..." }] when calling PATCH clinic.

Errors

Code HTTP Description
ERR_FILE_001 422 Invalid file type
ERR_AUTH_001 401 Missing token

Clinic Address Management

GET /api/v1/clinic/{clinicUuid}/addresses

Permission: Public

Returns all addresses registered for a clinic (type=clinic entries).

Response 200

{
  "success": true,
  "data": [
    {
      "id": "12",
      "uuid": "abc-123",
      "type": "clinic",
      "clinic_id": "5",
      "name": "شعبه مرکزی",
      "address": "تهران، خیابان ولیعصر...",
      "telephone": "02112345678",
      "map": { "latitude": "35.699", "longitude": "51.337" },
      "city": { "id": "1", "name": "تهران" },
      "province": { "id": "8", "name": "تهران" }
    }
  ]
}

POST /api/v1/clinic/{clinicUuid}/address

Permission: Clinic owner or ROLE_ADMIN

Creates a new address for the clinic. The address will appear in available-locations for doctors belonging to this clinic.

Request

{
  "name": "شعبه مرکزی",
  "address": "تهران، خیابان ولیعصر...",
  "telephone": "02112345678",
  "latitude": 35.699,
  "longitude": 51.337,
  "city_id": 123,
  "province_id": 7
}
Field Type Required
name string
address string
telephone string
latitude float
longitude float
city_id integer
province_id integer

Response 201

{ "success": true, "data": { "id": "12", "uuid": "...", "type": "clinic", ... } }

Errors

Code HTTP Description
ERR_AUTH_006 403 Not the clinic owner
ERR_VALIDATION_002 404 Clinic not found

PATCH /api/v1/clinic/{clinicUuid}/address/{addressUuid}

Permission: Clinic owner or ROLE_ADMIN

Updates an existing clinic address. Same body fields as POST (all optional).

Response 200

{ "success": true, "data": { ... } }

DELETE /api/v1/clinic/{clinicUuid}/address/{addressUuid}

Permission: Clinic owner or ROLE_ADMIN

Deletes a clinic address.

A clinic must retain at least one address — attempting to delete the last address returns 409.

Errors

Code HTTP Description
ERR_CONFLICT_001 409 Cannot delete the last address
ERR_VALIDATION_002 404 Address or clinic not found
ERR_AUTH_006 403 Not the clinic owner

Removed endpoint

POST /api/v1/clinic-pro/doctor-address/from-clinic/{clinicUuid}removed. Use clinic address management endpoints instead.