- 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.
126 lines
5.1 KiB
Markdown
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`
|