Files
clinicpro/docs/api
hamedandClaude Opus 5 b3c331f0cb perf(reports): read every resource's calendar in one batch, and close the owed tests
Writing the query-count test that task 14 owed showed the growth was real: one
resource cost 10 queries, six cost 33 — about five per resource, because the
available-minutes figure walked each resource's calendar on its own.

Holidays, tenant overrides and branch hours are identical for every resource in
a report, so they now load once outside the loop; shifts and exceptions load for
all resources in one query each. The batched path is a new method rather than a
change to rawAvailability, which the booking engine also calls. The test pins
the shape of the growth, not an exact count.

Also landed:

- app:segment:seed-templates with beauty, dental and physio presets. Building
  four segments and their requirements by hand is the first thing a new clinic
  must do and the most tedious; this gives them something to edit instead of an
  empty page. It refuses to touch a service that already has segments unless
  --force, and it will not invent resource types the tenant never defined.
- book-all is all-or-nothing, proven rather than asserted: with a calendar open
  one day a week and a 1-2 day protocol gap, session one finds a slot and
  session two cannot, and every session must come back planned.
- credit_refundable: false takes the credit back with a negative adjustment and
  deletes nothing — the ledger stays append-only.
- the segments editor has frontend tests, including that it sends back what the
  user sees and renders read-only without the permission.

useBranches now returns [] for a non-array payload instead of throwing
"branches.map is not a function" and taking the page down with it.

BookingLocationsScanTest built a Clinic around a Doctor loaded from a different
manager, which Doctrine treats as a new entity; it flushed fine most runs and
failed on cascade in others. It now loads the doctor from the same manager.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:22:22 +03:30
..

ClinicPro — API Documentation Index

Base URL: https://clinic-pro.ddev.site
API Prefix: /api/v1
Swagger UI: https://clinic-pro.ddev.site/api/doc — user: admin / pass: clinic123


Authentication

All protected endpoints require:

Authorization: Bearer <JWT_TOKEN>
Role Description
PUBLIC No token required
AUTH Any valid JWT
ROLE_ADMIN Admin user
ROLE_DOCTOR Doctor user
ROLE_CLINIC Clinic owner
ROLE_SECRETARY Secretary

Standard Response Envelope

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

// Paginated
{ "success": true, "data": [...], "meta": { "totalRecords": 100, "totalPages": 5, "currentPage": 1, "limit": 20 } }

// Error
{ "success": false, "data": null, "errors": [{ "code": "ERR_XXX_000", "message": "..." }] }

meta.limit اندازهٔ صفحهٔ واقعاً اعمال‌شده است. ریپازیتوری‌ها limit درخواستی را به سقف خودشان کاهش می‌دهند (مثلاً لیست پزشکان: سقف ۵۰)، پس برای پیمایش کامل به meta.totalPages تکیه کن — نه به این فرض که «تعداد آیتم کمتر از limit درخواستی یعنی صفحهٔ آخر».


Persian digit normalization (global)

Persian (۰) and Arabic (٠) digits sent in numeric request fields are translated to Latin server-side, before the controller runssrc/Shared/EventSubscriber/NumericFieldNormalizerSubscriber.php. Every client benefits: the React admin panel, nobat724_front, and clinic-pro-tauri.

Applies to POST / PUT / PATCH requests under /api/v1/ with a JSON body, recursively through nested arrays.

Normalized keys:

mobile, mobile_number, telephone, phone, notification_mobile,
national_code, postal_code,
card_number, account_number, sheba, shaba, iban,
price_rials, amount_rials, amount, free_visit_price_rials,
insurance_price_rials, patient_share_rials, visit_price_rials,
duration_minutes, duration, commission_percent, coverage,
coverage_percent, franchise, ceiling, tax_percent,
base_insurance_discount_percent, supplementary_discount_percent

Only digits are translated — no characters are stripped, so IR in a sheba and - in a landline survive. Non-string values (int, bool, null) and keys outside the list are untouched, so a name like منشی شماره ۲ keeps its Persian digit.

// request
{ "mobile_number": "۰۹۱۲۳۴۵۶۷۸۹", "national_code": "۰۰۱۲۳۴۵۶۷۸", "name": "منشی شماره ۲" }

// what the controller sees
{ "mobile_number": "09123456789", "national_code": "0012345678", "name": "منشی شماره ۲" }

Adding a new numeric field to any endpoint? Add its key to NUMERIC_KEYS in the subscriber, otherwise Persian digits reach the database.


Modules

File Domain Endpoints
auth.md Authentication — OTP, Login, JWT 8
doctor.md Doctor profile & addresses 11
clinic.md Clinics 7
clinic-invitation.md Doctor invitations to clinics 8
branch.md Branches (= addresses), working hours, rooms 8
resource.md Resources, types, skills, pools 16
resource-calendar.md Resource calendars, exceptions, national holidays 9
appointment-plan.md Appointment segments and plan preview 3
appointment-availability.md Multi-resource availability search 2
appointment-booking.md Holds, confirmation and multi-resource occupancy 4
pricing.md Date-ranged price lists and appointment invoices 8
appointment.md Appointments & slot booking 6
appointment-settings.md Weekly schedule, date overrides, holidays 14
payment.md Payments (Mellat / Sep) 5
settlement.md Wallet & settlement requests 7
rating.md Ratings, comments, likes 9
secretary.md Doctor secretaries 5
representation.md Representations (agents) 6
sms.md SMS send & templates 10
blog.md Blog posts 6
specialty.md Medical specialties 5
insurance.md Insurances & doctor-insurance links 10
doctor-service.md Doctor services 5
tag.md Blog tags 5
location.md Provinces & cities 10
user-profile.md User medical profile 4
admin.md Admin dashboard & management 25+

Error Code Reference

Code Message (FA) HTTP
ERR_AUTH_001 توکن JWT منقضی یا نامعتبر 401
ERR_AUTH_002 کد OTP نامعتبر 401
ERR_AUTH_003 کد OTP منقضی شده 401
ERR_AUTH_004 تعداد تلاش‌های OTP به حد مجاز رسیده 429
ERR_AUTH_005 نام کاربری یا رمز عبور اشتباه 401
ERR_AUTH_006 دسترسی ممنوع 403
ERR_VALIDATION_001 ورودی نامعتبر 422
ERR_VALIDATION_002 فیلد الزامی وارد نشده 422
ERR_NOT_FOUND_001 منبع درخواستی یافت نشد 404
ERR_CONFLICT_001 تداخل: منبع در حال استفاده 409
ERR_FORBIDDEN_001 دسترسی به این منبع مجاز نیست 403
ERR_PAYMENT_001 درگاه پرداخت در دسترس نیست 503
ERR_PAYMENT_002 مبلغ پرداخت نامعتبر 422
ERR_PAYMENT_003 وضعیت نوبت برای پرداخت مناسب نیست 422
ERR_FILE_001 فرمت فایل مجاز نیست 422
ERR_SMS_003 تمپلیت قبلاً ارسال شده 422
ERR_SECRETARY_001 پلن فعلی اجازه منشی بیشتر نمی‌دهد 422
ERR_RATE_LIMIT_001 درخواست‌های زیاد، بعداً تلاش کنید 429