Files
clinicpro/docs/api/README.md
T
hamedandClaude Opus 4.8 00cb9aaa1a feat(admin): normalize Persian/Arabic digits in every numeric field
Users typing on a Persian keyboard produced two distinct failures. Fields with
type="number" silently returned an empty string — the browser rejects Persian
digits, so the value was lost and saved as empty or zero. Text fields passed the
Persian characters straight through to the database, where a mobile stored as
۰۹۱۲… never matches 09… again. The secretary form hit the second case with no
validation at all.

Frontend:
- Adds digitsOnly() and the national-code schemas to lib/utils, plus lib/forms
  with numericField()/latinDigitsField() wrappers for React Hook Form fields.
- Converts every type="number" input to type="text" inputMode="numeric" with
  digit normalization; none remain. Fields that legitimately carry non-digits
  (sheba, landline) only get the digits translated, keeping IR and separators.
- Points the patient national-code and mobile schemas at the shared normalizing
  schemas, which accept Persian input instead of rejecting it.
- Drops two duplicate local digit converters in favour of the shared helper.

Backend:
- Adds NumericFieldNormalizerSubscriber, translating digits in whitelisted
  numeric keys of JSON request bodies under /api/v1/ before controllers run, so
  nobat724_front and clinic-pro-tauri are covered too. Translation only — no
  characters are stripped, non-string values and other keys are untouched.

Three component tests asserted on role="spinbutton" and numeric input values;
both are properties of type="number", so they were updated to match the new
text inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 10:38:56 +03:30

124 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```json
// Success
{ "success": true, "data": { ... } }
// Paginated
{ "success": true, "data": [...], "meta": { "totalRecords": 100, "totalPages": 5, "currentPage": 1 } }
// Error
{ "success": false, "data": null, "errors": [{ "code": "ERR_XXX_000", "message": "..." }] }
```
---
## Persian digit normalization (global)
Persian (`۰-۹`) and Arabic (`٠-٩`) digits sent in numeric request fields are translated to Latin **server-side, before the controller runs**`src/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.
```jsonc
// 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](auth.md) | Authentication — OTP, Login, JWT | 8 |
| [doctor.md](doctor.md) | Doctor profile & addresses | 11 |
| [clinic.md](clinic.md) | Clinics | 7 |
| [clinic-invitation.md](clinic-invitation.md) | Doctor invitations to clinics | 8 |
| [appointment.md](appointment.md) | Appointments & slot booking | 6 |
| [appointment-settings.md](appointment-settings.md) | Weekly schedule, date overrides, holidays | 14 |
| [payment.md](payment.md) | Payments (Mellat / Sep) | 5 |
| [settlement.md](settlement.md) | Wallet & settlement requests | 7 |
| [rating.md](rating.md) | Ratings, comments, likes | 9 |
| [secretary.md](secretary.md) | Doctor secretaries | 5 |
| [representation.md](representation.md) | Representations (agents) | 6 |
| [sms.md](sms.md) | SMS send & templates | 10 |
| [blog.md](blog.md) | Blog posts | 6 |
| [specialty.md](specialty.md) | Medical specialties | 5 |
| [insurance.md](insurance.md) | Insurances & doctor-insurance links | 10 |
| [doctor-service.md](doctor-service.md) | Doctor services | 5 |
| [tag.md](tag.md) | Blog tags | 5 |
| [location.md](location.md) | Provinces & cities | 10 |
| [user-profile.md](user-profile.md) | User medical profile | 4 |
| [admin.md](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 |