feat(patient): complete "اطلاعات پرونده" demographic form

Backend:
- Add profile columns field_of_study, province_id, city_id, postal_code,
  referral_source (UserProfile + migration).
- Extend PATCH /api/v1/patient/{uuid} to persist all demographic fields
  and return them in the patient profile payload.
- Support editable mobile (login identifier): validation, uniqueness,
  User.setMobileNumber, new ERR_PROFILE_002.
- Update docs/api/patient.md.

Frontend:
- New reusable Input, Field, and PatientRecordInfoForm (RHF + Zod).
- usePatient/useUpdatePatient hooks and patientForm mapping helpers.
- Extend the existing "info" tab in MyPatientsPage to the full field set
  via the shared form (province/city/insurance options, Jalali date).

Tests: Patient entity + PATCH integration (PHPUnit); form, hooks, and
mapping helpers (Vitest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-13 11:51:18 +03:30
co-authored by Claude Opus 4.8
parent 9d8de9bc33
commit 580bc983b3
19 changed files with 1042 additions and 128 deletions
+30
View File
@@ -55,12 +55,27 @@ class UserProfile
#[ORM\Column(type: 'string', length: 100, nullable: true)]
private ?string $education = null;
#[ORM\Column(name: 'field_of_study', type: 'string', length: 100, nullable: true)]
private ?string $fieldOfStudy = null;
#[ORM\Column(type: 'string', length: 100, nullable: true)]
private ?string $job = null;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $address = null;
#[ORM\Column(name: 'province_id', type: 'integer', nullable: true)]
private ?int $provinceId = null;
#[ORM\Column(name: 'city_id', type: 'integer', nullable: true)]
private ?int $cityId = null;
#[ORM\Column(name: 'postal_code', type: 'string', length: 20, nullable: true)]
private ?string $postalCode = null;
#[ORM\Column(name: 'referral_source', type: 'string', length: 100, nullable: true)]
private ?string $referralSource = null;
#[ORM\Column(name: 'home_phone', type: 'string', length: 30, nullable: true)]
private ?string $homePhone = null;
@@ -116,8 +131,13 @@ class UserProfile
public function getBloodType(): ?string { return $this->bloodType; }
public function getMaritalStatus(): ?string { return $this->maritalStatus; }
public function getEducation(): ?string { return $this->education; }
public function getFieldOfStudy(): ?string { return $this->fieldOfStudy; }
public function getJob(): ?string { return $this->job; }
public function getAddress(): ?string { return $this->address; }
public function getProvinceId(): ?int { return $this->provinceId; }
public function getCityId(): ?int { return $this->cityId; }
public function getPostalCode(): ?string { return $this->postalCode; }
public function getReferralSource(): ?string { return $this->referralSource; }
public function getHomePhone(): ?string { return $this->homePhone; }
public function getWorkPhone(): ?string { return $this->workPhone; }
public function getInsuranceId(): ?string { return $this->insuranceId; }
@@ -140,8 +160,13 @@ class UserProfile
public function setBloodType(?string $v): self { $this->bloodType = $v; $this->touch(); return $this; }
public function setMaritalStatus(?string $v): self { $this->maritalStatus = $v; $this->touch(); return $this; }
public function setEducation(?string $v): self { $this->education = $v; $this->touch(); return $this; }
public function setFieldOfStudy(?string $v): self { $this->fieldOfStudy = $v; $this->touch(); return $this; }
public function setJob(?string $v): self { $this->job = $v; $this->touch(); return $this; }
public function setAddress(?string $v): self { $this->address = $v; $this->touch(); return $this; }
public function setProvinceId(?int $v): self { $this->provinceId = $v; $this->touch(); return $this; }
public function setCityId(?int $v): self { $this->cityId = $v; $this->touch(); return $this; }
public function setPostalCode(?string $v): self { $this->postalCode = $v; $this->touch(); return $this; }
public function setReferralSource(?string $v): self { $this->referralSource = $v; $this->touch(); return $this; }
public function setHomePhone(?string $v): self { $this->homePhone = $v; $this->touch(); return $this; }
public function setWorkPhone(?string $v): self { $this->workPhone = $v; $this->touch(); return $this; }
public function setInsuranceId(?string $v): self { $this->insuranceId = $v; $this->touch(); return $this; }
@@ -170,8 +195,13 @@ class UserProfile
'blood_type' => $this->bloodType,
'marital_status' => $this->maritalStatus,
'education' => $this->education,
'field_of_study' => $this->fieldOfStudy,
'job' => $this->job,
'address' => $this->address,
'province_id' => $this->provinceId,
'city_id' => $this->cityId,
'postal_code' => $this->postalCode,
'referral_source' => $this->referralSource,
'home_phone' => $this->homePhone,
'work_phone' => $this->workPhone,
'insurance_id' => $this->insuranceId,