Files
clinicpro/docs/new_feture/taskes/task-01-branch-room/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

126 lines
5.1 KiB
Markdown

# معماری — تسک ۰۱
## ساختار فایل
```
src/Branch/
├── Controller/
│ ├── BranchController.php # CRUD شعبه + ساعت کاری
│ └── RoomController.php # CRUD اتاق
├── Entity/
│ ├── Branch.php
│ ├── BranchWorkingHours.php
│ └── Room.php
├── Repository/
│ ├── BranchRepository.php
│ ├── BranchWorkingHoursRepository.php
│ └── RoomRepository.php
├── Service/
│ ├── BranchService.php # ساخت/ویرایش/حذف + قواعد حذف
│ └── WorkingHoursService.php # اعتبارسنجی و ذخیرهٔ هفت روز
└── Command/
└── BackfillBranchCommand.php # app:branch:backfill
assets/admin/pages/
├── BranchesPage.tsx
├── BranchFormPage.tsx # شامل تب ساعت کاری
└── RoomsPage.tsx
```
## لایه‌بندی
`BranchController` نازک است: اعتبارسنجی ورودی + `EntityContextResolver` + صدا زدن سرویس.
همهٔ قواعد (حذف امن، یکتایی نام در محیط، نرمال‌سازی ساعت) در `BranchService` و
`WorkingHoursService`.
```php
final class BranchService
{
public function __construct(
private readonly BranchRepository $branches,
private readonly RoomRepository $rooms,
private readonly EntityManagerInterface $em,
) {}
public function create(EntityContext $ctx, BranchInput $input): Branch
{
$branch = new Branch($input->name);
$branch->assignTenant($ctx); // ← اجباری، وگرنه flush می‌شکند
// ...
}
/** حذف فقط وقتی هیچ اتاق یا منبعِ فعالی به شعبه وصل نیست. */
public function delete(Branch $branch): void
{
if ($this->rooms->countActiveByBranch($branch) > 0) {
throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'شعبه دارای اتاق فعال است', 422);
}
// ...
}
}
```
## رابطهٔ Branch با DoctorAddress
`DoctorAddress` حذف نمی‌شود. یک ستون `branch_id` تهی‌پذیر می‌گیرد:
```
DoctorAddress.branch_id ──▶ branches.id (nullable, ON DELETE SET NULL)
```
دلیل: `location_id` در JSON برنامهٔ هفتگی به `doctor_addresses.id` اشاره دارد و در
`SlotCalculatorService` و `AppointmentController::bookingLocations()` و سایت عمومی مصرف می‌شود.
تغییر آن قرارداد یعنی شکستن سه کلاینت. پس شعبه یک **لایهٔ بالاتر** می‌نشیند و آدرس به آن
لینک می‌شود، نه برعکس.
`BackfillBranchCommand` برای هر محیطی که آدرس دارد یک شعبه با نام آدرس می‌سازد و
`branch_id` را پر می‌کند. dry-run پیش‌فرض، `--force` برای اجرا.
## ساعت کاری شعبه
مثل `WeeklySchedule` یک JSON نیست — جدول جداست، چون تسک ۰۳ باید بتواند
`WHERE branch_id = ? AND day = ?` بزند بدون خواندن و decode کردن JSON برای هر روز از ۹۰ روز.
```php
#[ORM\Entity]
#[ORM\Table(name: 'branch_working_hours')]
#[ORM\UniqueConstraint(name: 'uniq_branch_day_seq', columns: ['branch_id', 'day_of_week', 'sequence'])]
class BranchWorkingHours
{
private int $dayOfWeek; // 0=شنبه … 6=جمعه — همان قرارداد SlotCalculatorService
private int $startMinute; // دقیقه از نیمه‌شب، 0..1440
private int $endMinute;
private int $sequence; // چند بازه در روز (صبح/عصر)
}
```
`startMinute`/`endMinute` به‌جای رشتهٔ `"08:30"` ذخیره می‌شوند تا مقایسه و تقاطع در تسک ۰۶
حسابی باشد نه رشته‌ای. تبدیل به `H:i` فقط در `toArray()`.
## اتاق
```php
class Room
{
use TenantOwnedTrait;
private Branch $branch;
private string $name;
private ?string $roomType = null; // متن آزاد — نوعِ اتاق را کلینیک تعریف می‌کند
private int $capacity = 1; // چند بیمار هم‌زمان (اتاق تزریق سه‌تخته = 3)
private bool $active = true;
}
```
`capacity` از همین‌جا شروع می‌شود چون مستند بند ۶ صریح می‌گوید سه تخت = **یک منبع با
ظرفیت سه**، نه سه منبع. تسک ۰۲ همین معنا را روی `Resource` تکرار می‌کند و اتاق را
به‌عنوان یک `Resource` با `resource_type=room` منعکس می‌کند.
## پنل ادمین
- `BranchesPage.tsx``DataTable` + `PageHeader` با `backTo`، وضعیت لیست در URL با `useUrlState`
- `BranchFormPage.tsx` — دو تب: مشخصات / ساعت کاری. `SearchableSelect` برای شهر
(هرگز `<select>` بومی)
- `RoomsPage.tsx` — زیرصفحهٔ شعبه، `<BackButton fallback="/admin/branches" />`
- مسیرها در `App.tsx`: `/admin/branches`, `/admin/branches/new`, `/admin/branches/:uuid`,
`/admin/branches/:uuid/rooms`