Implement SMS panel user flow and patient records system; add wallet charging, automatic reminders, and patient session management with detailed database schema and user flows.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# معماری — تسک ۱۰: مدیریت پرسنل (Staff)
|
||||
|
||||
## ساختار فایلها
|
||||
|
||||
```
|
||||
src/Staff/
|
||||
├── Controller/
|
||||
│ └── StaffController.php
|
||||
├── Entity/
|
||||
│ └── ClinicStaff.php
|
||||
├── Repository/
|
||||
│ └── ClinicStaffRepository.php
|
||||
└── Service/
|
||||
└── StaffService.php
|
||||
```
|
||||
|
||||
## Entity: ClinicStaff
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Staff\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity(repositoryClass: \App\Staff\Repository\ClinicStaffRepository::class)]
|
||||
#[ORM\Table(name: 'clinic_staff')]
|
||||
#[ORM\Index(columns: ['entity_type', 'entity_id', 'active'], name: 'idx_clinic_staff_entity')]
|
||||
class ClinicStaff
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 36, unique: true)]
|
||||
private string $uuid;
|
||||
|
||||
// polymorphic — 'doctor' یا 'clinic'
|
||||
#[ORM\Column(type: 'string', length: 10)]
|
||||
private string $entityType;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $entityId;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 200)]
|
||||
private string $fullName;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 20, nullable: true)]
|
||||
private ?string $phone = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 100, nullable: true)]
|
||||
private ?string $jobTitle = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $address = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 10, nullable: true)]
|
||||
private ?string $nationalCode = null;
|
||||
|
||||
#[ORM\Column(type: 'boolean')]
|
||||
private bool $active = true;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $createdAt;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $updatedAt;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->uuid = (string) Uuid::v4();
|
||||
$this->createdAt = time();
|
||||
$this->updatedAt = time();
|
||||
}
|
||||
|
||||
// getters/setters ...
|
||||
}
|
||||
```
|
||||
|
||||
## Controller: StaffController
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Staff\Controller;
|
||||
|
||||
use App\Shared\Controller\BaseController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
#[Route('/api/v1')]
|
||||
class StaffController extends BaseController
|
||||
{
|
||||
#[Route('/staff', methods: ['GET'])]
|
||||
public function list(): JsonResponse
|
||||
{
|
||||
// 1. از JWT: entity_type و entity_id (db_key + db_uuid) را بگیر
|
||||
// 2. StaffRepository::findByEntity($entityType, $entityId) صدا بزن
|
||||
// 3. return $this->success($items);
|
||||
}
|
||||
|
||||
#[Route('/staff', methods: ['POST'])]
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
// 1. validate: full_name اجباری
|
||||
// 2. ClinicStaff جدید با entityType/entityId از JWT بساز
|
||||
// 3. persist و flush
|
||||
// 4. return $this->success($staff->toArray());
|
||||
}
|
||||
|
||||
#[Route('/staff/{uuid}', methods: ['PATCH'])]
|
||||
public function update(string $uuid, Request $request): JsonResponse
|
||||
{
|
||||
// 1. find by uuid → 404 اگر نبود
|
||||
// 2. بررسی مالکیت: staff->entityType/entityId == JWT claim → 403 اگر نبود
|
||||
// 3. ویرایش فیلدهای موجود در body
|
||||
// 4. return $this->success($staff->toArray());
|
||||
}
|
||||
|
||||
#[Route('/staff/{uuid}/toggle', methods: ['PATCH'])]
|
||||
public function toggle(string $uuid): JsonResponse
|
||||
{
|
||||
// 1. find → ownership check
|
||||
// 2. active = !active
|
||||
// 3. return $this->success(['active' => $staff->isActive()]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Service: StaffService
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Staff\Service;
|
||||
|
||||
class StaffService
|
||||
{
|
||||
public function toArray(ClinicStaff $staff): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $staff->getUuid(),
|
||||
'full_name' => $staff->getFullName(),
|
||||
'phone' => $staff->getPhone(),
|
||||
'job_title' => $staff->getJobTitle(),
|
||||
'address' => $staff->getAddress(),
|
||||
'national_code' => $staff->getNationalCode(),
|
||||
'active' => $staff->isActive(),
|
||||
'created_at' => $staff->getCreatedAt(),
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## نحوه خواندن entity_type / entity_id از JWT
|
||||
|
||||
```php
|
||||
// در BaseController یا trait:
|
||||
// JWT payload شامل: db_key ('doctor'/'clinic') و db_uuid (uuid entity)
|
||||
// باید uuid را به id تبدیل کنیم:
|
||||
$entityType = $this->getUser()->getDbKey(); // 'doctor' یا 'clinic'
|
||||
$entityUuid = $this->getUser()->getDbUuid();
|
||||
// سپس از Repository مربوطه id واقعی را بگیریم
|
||||
```
|
||||
|
||||
## وابستگیهای آینده
|
||||
|
||||
- `src/ClinicService/Entity/ServiceItem.php` → `staff_id FK → clinic_staff.id`
|
||||
- `src/Patient/Entity/SessionService.php` → `staff_id FK → clinic_staff.id`
|
||||
@@ -0,0 +1,51 @@
|
||||
# پایگاه داده — تسک ۱۰: مدیریت پرسنل (Staff)
|
||||
|
||||
## جدول: clinic_staff
|
||||
|
||||
| ستون | نوع | توضیح |
|
||||
|------|-----|-------|
|
||||
| id | INT UNSIGNED AUTO_INCREMENT PK | |
|
||||
| uuid | CHAR(36) UNIQUE NOT NULL | |
|
||||
| entity_type | VARCHAR(10) NOT NULL | `'doctor'` یا `'clinic'` |
|
||||
| entity_id | INT NOT NULL | FK به doctors.id یا clinics.id (bسته به entity_type) |
|
||||
| full_name | VARCHAR(200) NOT NULL | نام و نامخانوادگی — اجباری |
|
||||
| phone | VARCHAR(20) NULL | |
|
||||
| job_title | VARCHAR(100) NULL | عنوان شغل |
|
||||
| address | TEXT NULL | |
|
||||
| national_code | CHAR(10) NULL | |
|
||||
| active | TINYINT(1) NOT NULL DEFAULT 1 | غیرفعال = soft delete |
|
||||
| created_at | INT NOT NULL | Unix timestamp |
|
||||
| updated_at | INT NOT NULL | Unix timestamp |
|
||||
|
||||
## ایندکسها
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_clinic_staff_entity
|
||||
ON clinic_staff(entity_type, entity_id, active);
|
||||
```
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **هیچ FK خارجی** برای entity_id تعریف نمیشود — چون entity_type polymorphic است و میتواند doctor یا clinic باشد. مسئولیت یکپارچگی با لایه application است.
|
||||
- `active = 0` به معنای حذف منطقی است — ردیف هرگز از جدول پاک نمیشود
|
||||
- وقتی staff غیرفعال میشود، service_items و session_services قبلی دستنخورده میمانند
|
||||
|
||||
## Migration نمونه
|
||||
|
||||
```sql
|
||||
CREATE TABLE clinic_staff (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
uuid CHAR(36) NOT NULL UNIQUE,
|
||||
entity_type VARCHAR(10) NOT NULL,
|
||||
entity_id INT NOT NULL,
|
||||
full_name VARCHAR(200) NOT NULL,
|
||||
phone VARCHAR(20) NULL,
|
||||
job_title VARCHAR(100) NULL,
|
||||
address TEXT NULL,
|
||||
national_code CHAR(10) NULL,
|
||||
active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at INT NOT NULL,
|
||||
updated_at INT NOT NULL,
|
||||
INDEX idx_clinic_staff_entity (entity_type, entity_id, active)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
# تسک ۱۰: مدیریت پرسنل (Staff)
|
||||
|
||||
## توضیح
|
||||
پیادهسازی سیستم مدیریت پرسنل کلینیک/مطب. پرسنل حساب کاربری ندارند و نیازی به لاگین ندارند.
|
||||
این تسک **پیشنیاز مستقیم** تسک ۱۳ (سرویسها) و تسک ۱۵ (پرونده بیمار) است.
|
||||
|
||||
## Endpoint ها
|
||||
|
||||
| متد | مسیر | توضیح | نیاز به Auth |
|
||||
|-----|------|-------|-------------|
|
||||
| GET | `/api/v1/staff` | لیست پرسنل context جاری | بله (Doctor/Clinic) |
|
||||
| POST | `/api/v1/staff` | ایجاد پرسنل جدید | بله |
|
||||
| PATCH | `/api/v1/staff/{uuid}` | ویرایش اطلاعات | بله (مالک) |
|
||||
| PATCH | `/api/v1/staff/{uuid}/toggle` | فعال/غیرفعال (soft delete) | بله (مالک) |
|
||||
|
||||
**مهم:** حذف سخت (`DELETE`) ممنوع است — تاریخچه سرویسها و سشنهای بیمار به پرسنل ارجاع دارند.
|
||||
|
||||
## پیشنیازها
|
||||
- تسک ۰۲ (Auth — JWT + switch-context)
|
||||
- تسک ۰۵ (Doctor entity)
|
||||
- تسک ۰۶ (Clinic entity)
|
||||
|
||||
## زمان تخمینی
|
||||
۶ تا ۸ ساعت
|
||||
|
||||
## نمونه Request
|
||||
|
||||
### POST /api/v1/staff
|
||||
```json
|
||||
{
|
||||
"full_name": "علی رضایی",
|
||||
"phone": "09121234567",
|
||||
"job_title": "پرستار",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"national_code": "0012345678"
|
||||
}
|
||||
```
|
||||
|
||||
### PATCH /api/v1/staff/{uuid}
|
||||
```json
|
||||
{
|
||||
"full_name": "علی رضایی",
|
||||
"job_title": "سرپرستار",
|
||||
"phone": "09129999999"
|
||||
}
|
||||
```
|
||||
|
||||
### PATCH /api/v1/staff/{uuid}/toggle
|
||||
```json
|
||||
{}
|
||||
```
|
||||
(body خالی — فقط وضعیت active را toggle میکند)
|
||||
|
||||
## نمونه Response
|
||||
|
||||
### GET /api/v1/staff
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
||||
"full_name": "علی رضایی",
|
||||
"phone": "09121234567",
|
||||
"job_title": "پرستار",
|
||||
"address": "تهران، خیابان ولیعصر",
|
||||
"national_code": "0012345678",
|
||||
"active": true,
|
||||
"created_at": 1718000000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/v1/staff (موفق)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
||||
"full_name": "علی رضایی",
|
||||
"phone": "09121234567",
|
||||
"job_title": "پرستار",
|
||||
"address": null,
|
||||
"national_code": "0012345678",
|
||||
"active": true,
|
||||
"created_at": 1718000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PATCH /api/v1/staff/{uuid}/toggle
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "active": false }
|
||||
}
|
||||
```
|
||||
|
||||
## کدهای خطا
|
||||
|
||||
| کد HTTP | توضیح |
|
||||
|---------|-------|
|
||||
| 401 | JWT معتبر نیست |
|
||||
| 403 | کاربر مالک این پرسنل نیست |
|
||||
| 404 | uuid پرسنل پیدا نشد |
|
||||
| 422 | `full_name` خالی است |
|
||||
@@ -0,0 +1,86 @@
|
||||
# جریان کاربری — تسک ۱۰: مدیریت پرسنل (Staff)
|
||||
|
||||
## جریان ایجاد پرسنل جدید
|
||||
|
||||
```
|
||||
مدیر کلینیک/دکتر وارد پنل میشود
|
||||
│
|
||||
▼
|
||||
GET /api/v1/staff
|
||||
→ لیست پرسنل فعلی context جاری نمایش داده میشود
|
||||
│
|
||||
▼
|
||||
کاربر روی «افزودن پرسنل» کلیک میکند → Modal باز میشود
|
||||
│
|
||||
▼
|
||||
POST /api/v1/staff
|
||||
{ full_name, phone?, job_title?, address?, national_code? }
|
||||
│
|
||||
├─► 422: full_name خالی است → نمایش خطا در فرم
|
||||
│
|
||||
└─► 201: پرسنل ایجاد شد → لیست refresh میشود
|
||||
```
|
||||
|
||||
## جریان ویرایش پرسنل
|
||||
|
||||
```
|
||||
کاربر روی آیکن ویرایش در جدول کلیک میکند
|
||||
│
|
||||
▼
|
||||
Modal با اطلاعات فعلی پر میشود
|
||||
│
|
||||
▼
|
||||
PATCH /api/v1/staff/{uuid}
|
||||
{ full_name, phone, job_title, ... }
|
||||
│
|
||||
├─► 403: این پرسنل متعلق به شما نیست
|
||||
└─► 200: ویرایش موفق → Modal بسته میشود
|
||||
```
|
||||
|
||||
## جریان غیرفعالسازی (Soft Delete)
|
||||
|
||||
```
|
||||
کاربر روی toggle در جدول کلیک میکند
|
||||
│
|
||||
▼
|
||||
PATCH /api/v1/staff/{uuid}/toggle
|
||||
│
|
||||
▼
|
||||
active: true → false یا false → true
|
||||
│
|
||||
▼
|
||||
در لیست: badge وضعیت تغییر میکند (فعال/غیرفعال)
|
||||
```
|
||||
|
||||
## چرا حذف سخت ممنوع است
|
||||
|
||||
```
|
||||
clinic_staff (active=false)
|
||||
│
|
||||
├── service_items.staff_id → هنوز به پرسنل ارجاع دارد
|
||||
│ (تاریخچه سرویسها حفظ میشود)
|
||||
│
|
||||
└── session_services.staff_id → سشنهای قبلی بیمار
|
||||
نام انجامدهنده را نشان میدهند
|
||||
```
|
||||
|
||||
اگر پرسنل حذف سخت میشد:
|
||||
- `service_items.staff_id` → NULL (انجامدهنده سرویس گم میشد)
|
||||
- `session_services.staff_id` → NULL (تاریخچه ویزیت ناقص میشد)
|
||||
|
||||
## نمایش در Frontend (StaffPage.tsx)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ پرسنل کلینیک [+ افزودن] │
|
||||
├──────────┬──────────┬──────────┬────────────────┤
|
||||
│ نام │ سمت │ تلفن │ وضعیت عملیات│
|
||||
├──────────┼──────────┼──────────┼────────────────┤
|
||||
│ علی رضایی│ پرستار │ 0912... │ ✅فعال ✏️ 🔘 │
|
||||
│ مریم نوری│ منشی │ 0913... │ ❌غیرفعال✏️ 🔘│
|
||||
└──────────┴──────────┴──────────┴────────────────┘
|
||||
```
|
||||
|
||||
- ستون آخر: آیکن ویرایش (Modal) + toggle وضعیت
|
||||
- غیرفعالها با رنگ کمتر نمایش داده میشوند اما از لیست حذف نمیشوند
|
||||
- فیلتر: «فقط فعالها» / «همه» — query param `?active=1`
|
||||
Reference in New Issue
Block a user