- 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.
134 lines
4.7 KiB
Markdown
134 lines
4.7 KiB
Markdown
# نکات پیادهسازی — تسک ۱۶: ماژول Representation (داشبورد دکتر)
|
||
|
||
## مهم: این ماژول دو نقش دارد
|
||
۱. **مدیریت نمایندگی (Multi-tenant)** — هر نماینده یک دامنه دارد
|
||
۲. **داشبورد دکتر** — آمار نوبتها، درآمد، بیماران
|
||
|
||
## ساختار Representation در Drupal
|
||
```php
|
||
// فیلدهای entity (clinic_pro, bundle=representation):
|
||
field_domain_name // دامنه سایت نماینده (مثل: http://yasuj-nobat.localhost:3000/)
|
||
field_city // entity reference به category (شهر)
|
||
field_active // boolean
|
||
field_commission_percent // درصد کمیسیون
|
||
|
||
// دکتر به نماینده از طریق field_representation لینک میشود
|
||
// نوبت هم با field_representation لینک میشود (از HTTP Host)
|
||
```
|
||
|
||
## شناسایی نماینده از Host
|
||
```php
|
||
// در زمان ثبت نوبت:
|
||
$host = 'http://yasuj-nobat.localhost:3000/';
|
||
$representation = $this->representationRepo->findByDomainName($host);
|
||
// در Symfony: $request->getSchemeAndHttpHost() . '/'
|
||
```
|
||
|
||
## endpoint: my-doctor (برای بیمار)
|
||
```
|
||
GET /api/v1/representation/my-doctor/{userId}
|
||
```
|
||
→ لیست دکترهایی که این کاربر نوبت گرفته را برمیگرداند.
|
||
فیلد search: `field_representation = $representationId`
|
||
|
||
## endpoint: my-appointments (برای نماینده)
|
||
```
|
||
GET /api/v1/representation/my-appointments/{representationId}
|
||
```
|
||
→ تمام نوبتهای مرتبط با این نماینده
|
||
کوئری روی `appointment.field_representation = $id`
|
||
|
||
## endpoint: filter (داشبورد ماهانه)
|
||
```
|
||
GET /api/v1/representation/filter/{representationId}?timestamp=...
|
||
```
|
||
→ از `timestamp` برای تعیین ماه جاری شمسی استفاده میکند
|
||
|
||
```php
|
||
// محاسبه بازه ماه جاری شمسی:
|
||
$persianMonthRange = $jalaliService->getCurrentPersianMonthRange($timestamp);
|
||
$startTimestamp = $persianMonthRange['start'];
|
||
|
||
// آمار پرداختها در این ماه:
|
||
// field_status = 'received' AND field_representation = $id AND created >= $startTimestamp
|
||
```
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"payments_total": { "total_price": 12500000, "count": 25 },
|
||
"total_patients": 142,
|
||
"today_appointments": 8
|
||
}
|
||
```
|
||
|
||
## endpoint: yearly-income (درآمد سالانه)
|
||
```
|
||
GET /api/v1/representation/yearly-income/{id}?timestamp=...
|
||
```
|
||
|
||
این endpoint از تقویم **شمسی (جلالی)** استفاده میکند:
|
||
```php
|
||
// محاسبه بازه سال شمسی:
|
||
$persianYearRange = $jalaliService->getPersianYearRange($timestamp);
|
||
$jalaliYear = $persianYearRange['year'];
|
||
|
||
// Loop از ماه ۱ تا ۱۲ شمسی:
|
||
for ($month = 1; $month <= 12; $month++) {
|
||
$monthRange = $this->getMonthRangeByYearAndMonth($jalaliYear, $month);
|
||
$income = $this->getMonthIncome($representationId, $monthRange['start'], $monthRange['end']);
|
||
// income = SUM(payment.amount) WHERE status='received' AND representation=$id AND created BETWEEN start AND end
|
||
}
|
||
```
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"year": 1403,
|
||
"monthly_income": [
|
||
{ "month": 1, "income": 8500000 },
|
||
{ "month": 2, "income": 9200000 },
|
||
...
|
||
{ "month": 12, "income": 0 }
|
||
]
|
||
}
|
||
```
|
||
|
||
## JalaliDateService پیادهسازی در Symfony
|
||
از کد `custom_service/src/jalali/JalaliDateService.php` برای پیادهسازی استفاده کن.
|
||
توابع مورد نیاز:
|
||
```php
|
||
class JalaliDateService {
|
||
public function getCurrentPersianMonthRange(?int $timestamp): array;
|
||
// return: ['start' => timestamp, 'end' => timestamp]
|
||
|
||
public function getPersianYearRange(?int $timestamp): array;
|
||
// return: ['year' => int, 'start' => timestamp, 'end' => timestamp]
|
||
|
||
public function getMonthRangeByYearAndMonth(int $year, int $month): array;
|
||
// return: ['start' => timestamp, 'end' => timestamp]
|
||
|
||
public function gregorianToPersian(int $gy, int $gm, int $gd): array;
|
||
// return: [$jy, $jm, $jd]
|
||
|
||
public function persianToGregorian(int $jy, int $jm, int $jd): array;
|
||
// return: [$gy, $gm, $gd]
|
||
}
|
||
```
|
||
|
||
## کشینگ آمار داشبورد
|
||
آمار داشبورد را با Redis کش کن (TTL = 5 دقیقه):
|
||
```php
|
||
$cacheKey = "dashboard_representation_{$representation->getId()}";
|
||
// بعد از هر payment جدید → cache invalidate
|
||
```
|
||
|
||
## مجوزها
|
||
```
|
||
GET /representation/{uuid} → دکتر مرتبط یا ROLE_ADMIN
|
||
GET /representation/my-appointments/{id} → دکتر/نماینده یا ROLE_ADMIN
|
||
GET /representation/my-doctor/{userId} → کاربر خودش
|
||
GET /representation/filter/{id} → دکتر/نماینده یا ROLE_ADMIN
|
||
GET /representation/yearly-income/{id} → دکتر/نماینده یا ROLE_ADMIN
|
||
```
|