Files
hamed de1a78a235 feat: Implement SMS sending functionality with KavehNegar and Rangineh providers
- Add SendSmsMessage class for encapsulating SMS message data.
- Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS.
- Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates.
- Develop SendSmsHandler for handling SMS sending messages.
- Create SmsService to manage SMS dispatching and logging.
- Add UserProfileController for managing user profiles with CRUD operations.
- Implement UserProfile entity and repository for user profile data management.
- Update symfony.lock and bootstrap.php for project dependencies and environment setup.
2026-06-09 22:00:34 +03:30

8.8 KiB
Raw Permalink Blame History

تسک ۱۴: ماژول منشی

توضیح

مدیریت منشی‌های دکترها که می‌توانند نوبت‌ها و پرداخت‌ها را مدیریت کنند. هر دکتر بسته به پلن اشتراک می‌تواند ۱ یا ۳ منشی فعال داشته باشد.

Endpoint ها

متد مسیر توضیح نیاز به Auth
POST /api/v1/secretary ایجاد منشی بله (Doctor/Admin)
PATCH /api/v1/secretary/{uuid} ویرایش منشی بله (Doctor/Admin)
GET /api/v1/secretary/{uuid} دریافت اطلاعات منشی بله
DELETE /api/v1/secretary/{uuid} حذف منشی بله (Doctor/Admin)
GET /api/v1/secretaries/{doctorUuid} لیست منشی‌های دکتر بله

پیش‌نیازها

  • تسک ۰۱، ۰۲، ۰۵ (Doctor)

زمان تخمینی

۵ تا ۶ ساعت


سیستم مجوزها — Resource-Based Permissions (مقیاس‌پذیر)

فیلد permissions در جدول doctor_secretaries یک JSON ساختاریافته با نسخه‌بندی است. طراحی به گونه‌ای است که در آینده بتوان منابع (resources) و عملیات (actions) جدید اضافه کرد بدون تغییر در ساختار جدول.

ساختار JSON

{
  "version": 1,
  "resources": {
    "appointments": {
      "view":          true,
      "create":        true,
      "cancel":        false,
      "update_status": true
    },
    "addresses": {
      "view":   true,
      "create": true,
      "update": true,
      "delete": false
    },
    "clinic_info": {
      "view":   true,
      "update": false
    },
    "insurances": {
      "view":   true,
      "create": true,
      "update": true,
      "delete": false
    }
  }
}

منابع و عملیات فعلی

Resource Actions توضیح
appointments view, create, cancel, update_status نوبت‌ها
addresses view, create, update, delete آدرس‌های مطب/کلینیک
clinic_info view, update اطلاعات مطب یا کلینیک
insurances view, create, update, delete بیمه‌ها

مقیاس‌پذیری — اضافه کردن Resource جدید در آینده

برای اضافه کردن Resource جدید (مثلاً patients یا reports) فقط کافی است:

  1. کلید جدید به JSON اضافه شود — بدون migration جدید
  2. کد Permission Checker به صورت خودکار آن را پشتیبانی می‌کند
  3. منشی‌های موجود که کلید جدید را ندارند، به صورت پیش‌فرض false دارند

پیاده‌سازی PHP — SecretaryPermissionChecker

// src/Secretary/Security/SecretaryPermissionChecker.php
class SecretaryPermissionChecker
{
    /**
     * بررسی مجوز منشی برای یک عملیات روی یک منبع
     * مثال: $checker->can($secretary, 'appointments', 'create')
     */
    public function can(Secretary $secretary, string $resource, string $action): bool
    {
        if (!$secretary->isActive()) {
            return false;
        }

        $permissions = $secretary->getPermissions();
        return (bool) ($permissions['resources'][$resource][$action] ?? false);
    }

    /**
     * بررسی دسترسی کامل به یک منبع (همه actions باید true باشند)
     */
    public function canAll(Secretary $secretary, string $resource, array $actions): bool
    {
        return array_reduce(
            $actions,
            fn($carry, $action) => $carry && $this->can($secretary, $resource, $action),
            true
        );
    }
}

مثال استفاده در Controller:

// در AppointmentController
if (!$this->permissionChecker->can($secretary, 'appointments', 'create')) {
    throw new AccessDeniedHttpException('منشی مجاز به ثبت نوبت نیست');
}

// در InsuranceController
if (!$this->permissionChecker->can($secretary, 'insurances', 'delete')) {
    throw new AccessDeniedHttpException('منشی مجاز به حذف بیمه نیست');
}

پیش‌فرض هنگام ایجاد منشی

{
  "version": 1,
  "resources": {
    "appointments": {
      "view":          true,
      "create":        true,
      "cancel":        false,
      "update_status": true
    },
    "addresses": {
      "view":   true,
      "create": false,
      "update": false,
      "delete": false
    },
    "clinic_info": {
      "view":   true,
      "update": false
    },
    "insurances": {
      "view":   true,
      "create": false,
      "update": false,
      "delete": false
    }
  }
}

POST /api/v1/secretary

// Request
{
  "mobile_number": "09120671756",
  "doctor_uuid": "61be915b-...",
  "permissions": {
    "version": 1,
    "resources": {
      "appointments": {
        "view": true,
        "create": true,
        "cancel": false,
        "update_status": true
      },
      "addresses": {
        "view": true,
        "create": false,
        "update": false,
        "delete": false
      },
      "clinic_info": {
        "view": true,
        "update": false
      },
      "insurances": {
        "view": true,
        "create": false,
        "update": false,
        "delete": false
      }
    }
  }
}

// Response 201
{
  "success": true,
  "data": {
    "uuid": "...",
    "user": { "uuid": "...", "realname": "فاطمه رضایی", "mobile": "09120671756" },
    "doctor": { "uuid": "...", "name": "دکتر احمدی" },
    "active": true,
    "permissions": { ... },
    "created_at": 1748000000
  }
}

// Response 422 — حد مجاز منشی
{
  "success": false,
  "errors": [{ "code": "ERR_SECRETARY_001", "message": "پلن فعلی اجازه منشی بیشتر را نمی‌دهد" }]
}

قانون بررسی پلن (سمت سرور):

پلن بیسیک  → max 1 منشی فعال
پلن پیشرفته → max 3 منشی فعال

هنگام POST /secretary:
  activeCount = COUNT(*) WHERE doctor_id=X AND active=true
  if activeCount >= maxAllowed → 422

PATCH /api/v1/secretary/{uuid}

// Request (فقط resources موردنظر — deep merge با پیش‌فرض‌ها)
{
  "active": false,
  "permissions": {
    "resources": {
      "insurances": {
        "create": true,
        "update": true
      }
    }
  }
}
// نکته: فقط resources/actions ارسال‌شده تغییر می‌کنند — بقیه دست‌نخورده می‌مانند

// Response 200 { "success": true, "data": { ... } }


---

## GET /api/v1/secretary/{uuid}

```json
{
  "success": true,
  "data": {
    "uuid": "...",
    "user": {
      "uuid": "...",
      "realname": "فاطمه رضایی",
      "mobile": "09120671756",
      "picture": null
    },
    "doctor": { "uuid": "...", "name": "دکتر احمدی" },
    "active": true,
    "permissions": {
      "version": 1,
      "resources": {
        "appointments": { "view": true, "create": true, "cancel": false, "update_status": true },
        "addresses":    { "view": true, "create": false, "update": false, "delete": false },
        "clinic_info":  { "view": true, "update": false },
        "insurances":   { "view": true, "create": false, "update": false, "delete": false }
      }
    },
    "created_at": 1748000000
  }
}

GET /api/v1/secretaries/{doctorUuid}

{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "user": { "uuid": "...", "realname": "فاطمه رضایی", "mobile": "09120671756" },
      "active": true,
      "permissions": { ... },
      "created_at": 1748000000
    }
  ]
}

نکات مهم

  • کاربر منشی: هنگام ایجاد منشی با mobile_number، ابتدا بررسی می‌شود آیا کاربر با این شماره وجود دارد — اگر نه، کاربر جدید ایجاد می‌شود
  • ROLE: کاربر منشی باید role doctor_s_secretary داشته باشد
  • لاگین منشی: منشی می‌تواند با username/password لاگین کند (تسک ۰۲)
  • بررسی پلن: کاملاً سمت سرور انجام می‌شود، قابل دور زدن نیست
  • نوبت آفلاین: نوبتی که منشی ثبت می‌کند (appointments.create) کمیسیون نماینده ندارد
  • PATCH permissions: فقط resources/actions ارسال‌شده تغییر می‌کنند (deep merge) — بقیه دست‌نخورده
  • Resource ناشناخته: اگر resource جدیدی در JSON باشد که سرور نمی‌شناسد، نادیده گرفته می‌شود (forward compat)
  • پیش‌فرض false: اگر resource یا action در JSON وجود نداشته باشد → false (deny by default)