Files
clinicpro/docs/new_feture/taskes/task-03-resource-calendar/architecture.md
T
hamed 021d0eb6b2 feat: implement cancellation policy, no-show tracking, and waitlist management
- Add implementation notes for cancellation and waitlist features.
- Create task documentation outlining goals, current status, and acceptance criteria for cancellation policy and resource utilization reporting.
- Establish architecture for domain events and outbox pattern to ensure reliable event publishing.
- Define database schema for domain events and necessary queries for resource utilization and plan accuracy reports.
- Implement detailed implementation notes covering edge cases, testing strategies, and documentation requirements.
2026-07-30 11:43:58 +03:30

140 lines
6.2 KiB
Markdown

# معماری — تسک ۰۳
## ساختار فایل
```
src/Resource/
├── Entity/
│ ├── ResourceCalendar.php # شیفت تکرارشوندهٔ هفتگی
│ └── ResourceException.php # مرخصی/غیبت/سرویس دستگاه/تعطیلی موردی
├── Controller/
│ ├── ResourceCalendarController.php
│ └── ResourceExceptionController.php
├── Service/
│ ├── ResourceCalendarService.php
│ └── ResourceAvailabilityService.php ← قلب این تسک
└── Repository/…
src/Holiday/
├── Entity/
│ ├── NationalHoliday.php
│ └── TenantHolidayOverride.php
├── Controller/HolidayController.php
├── Service/HolidayResolver.php
└── Command/ImportNationalHolidaysCommand.php
```
## `ResourceAvailabilityService` — قرارداد
```php
final class ResourceAvailabilityService
{
/**
* بازه‌های آزادِ خام یک منبع (بدون در نظر گرفتن نوبت‌ها).
* خروجی: بازه‌های مرتب و ادغام‌شده، بر حسب Unix timestamp.
*
* @return array<array{start:int, end:int}>
*/
public function rawWindows(ClinicResource $resource, int $from, int $to): array;
/**
* چرا این روز خالی است. null یعنی خالی نیست.
* همان قرارداد SlotCalculatorService::explainEmptyDay — پنل به دلیل نیاز دارد.
*/
public function explainEmptyDay(ClinicResource $resource, int $dayStart): ?string;
public const EMPTY_NO_CALENDAR = 'no_calendar';
public const EMPTY_NATIONAL_HOLIDAY = 'national_holiday';
public const EMPTY_RESOURCE_EXCEPTION = 'resource_exception';
public const EMPTY_OUTSIDE_BRANCH_HOURS = 'outside_branch_hours';
public const EMPTY_DAY_OFF = 'day_off';
}
```
## الگوریتم `rawWindows`
```
ورودی: منبع، [from, to)
۱. یک بار برای کل بازه واکشی کن (نه per-day):
- branch_working_hours شعبهٔ منبع (۱ کوئری)
- resource_calendars منبع (۱ کوئری)
- resource_exceptions متداخل با بازه (۱ کوئری)
- national_holidays متداخل با بازه (۱ کوئری)
- tenant_holiday_overrides محیط (۱ کوئری)
۲. برای هر روز از from تا to:
الف) اگر تعطیل رسمی است و override با is_working=true ندارد → روز را رد کن
ب) بازه‌های شیفت منبع آن روزِ هفته را بگیر
ج) اگر شعبه ساعت کاری تعریف‌شده دارد → تقاطع بگیر
اگر ندارد → بازهٔ منبع دست‌نخورده می‌ماند
د) استثناهای متداخل را کسر کن (اتحاد استثناها، بعد تفاضل)
ه) بازه‌های حاصل را به لیست اضافه کن
۳. ادغام بازه‌های مجاور و مرتب‌سازی
```
**پنج کوئری ثابت برای هر بازه، نه رشد خطی با تعداد روز.** این دقیقاً همان کاری است که
`SlotCalculatorService::findNextAvailableStart()` امروز برای پزشک می‌کند و باید حفظ شود؛
تسک ۰۶ روی همین حساب می‌کند که بتواند زیر نیم ثانیه بماند.
## عملیات بازه — یک جای واحد
تقاطع، اتحاد، تفاضل و ادغامِ بازه‌ها در سه تسک بعدی هم لازم است. یک کلاس بدون وابستگی:
```php
// src/Shared/Time/IntervalSet.php
final class IntervalSet
{
/** @param array<array{start:int,end:int}> $intervals */
public static function normalize(array $intervals): array; // مرتب + ادغام مجاور
public static function intersect(array $a, array $b): array;
public static function subtract(array $from, array $minus): array;
public static function union(array $a, array $b): array;
public static function totalSeconds(array $intervals): int;
}
```
خالص و بدون I/O → تست واحد سریع و بدون دیتابیس. هر جای دیگری که بازه جمع/کم می‌کند
باید از این استفاده کند، وگرنه سه پیاده‌سازی با سه باگ مرزی متفاوت خواهیم داشت.
## تعطیلات رسمی
```php
class NationalHoliday // سراسری — GlobalTables::ENTITIES
{
private int $date; // نیمه‌شب روز، Unix
private string $title; // «عید فطر»
private bool $isOfficial; // تعطیل رسمی یا مناسبت غیرتعطیل
}
class TenantHolidayOverride // per محیط
{
use TenantOwnedTrait;
private int $date;
private bool $isWorking; // true = این تعطیل رسمی برای ما کاری است
private ?string $note;
}
```
`HolidayResolver::isClosedFor(EntityContext $ctx, int $dayStart): bool` تنها نقطهٔ ترکیب
این دو است. `Holiday` موجود (per پزشک) دست‌نخورده می‌ماند و در حالت `slot`/`service`
همچنان مرجع است؛ در حالت `resource` هر دو منبع اعمال می‌شوند (اتحاد).
## import تعطیلات
```bash
ddev exec php bin/console app:holiday:import --year=1405 --file=var/holidays-1405.json
```
فایل JSON با تاریخ شمسی؛ تبدیل با `jalaali-js` معادل PHP در `src/Shared/Time/JalaliDate.php`
(اگر نبود، بساز). عمداً از سرویس آنلاین نمی‌خوانیم: تعطیلات ایران سالانه با مصوبه تغییر
می‌کنند و وابستگی به یک API خارجی یعنی جستجوی وقت به آن گره می‌خورد.
## پنل ادمین
- `ResourceCalendarPage.tsx` — گرید هفت‌روزه، هر روز چند بازه، درگ ندارد (فرم ساده)
- `ResourceExceptionsPage.tsx` — لیست + `PersianDatePicker` برای بازه
- `HolidaysSettingsPage.tsx` — تعطیلات رسمی سال با تیک «ما این روز کار می‌کنیم»
- هر سه زیرصفحه‌اند → `backTo` اجباری