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

5.1 KiB

معماری — تسک ۰۱

ساختار فایل

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.

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 برای هر روز از ۹۰ روز.

#[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().

اتاق

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.tsxDataTable + 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