feat(cancellation): cancellation policy, no-show tracking and a waitlist
Cancelling worked but had no policy behind it: no window, no penalty, nothing happened to the deposit, and the no_show status had no effect at all. Two rules that are expensive to get wrong, and both are load-bearing: - The clinic cancelling its own appointment is never charged. That check is the first line of the calculation, not somewhere in the middle, so a later refactor cannot reorder it into charging patients for the clinic's decision. - A penalty never exceeds what was actually paid. Anything above that is a debt, and debt belongs to billing, not to cancellation. An unpaid appointment is charged nothing and the response says why. The default is no penalty at all — a penalising default would have made every patient with a near appointment liable the moment this deployed. No-shows are rows, not a counter on the patient: a counter loses which appointment and when, which makes the 12-month window impossible. Crossing the threshold adds an existing TenantTag; it never blocks the patient, because blocking is an eligibility policy (task 09) written on top of that same tag. Waitlist notifies up to ten matching people and the first to book wins. An exclusive queue reads fairer but means a freed slot sits locked for half an hour while someone ignores their phone — so the SMS says so explicitly instead. Insufficient wallet balance does not fail the cancellation: the slot is freed either way. A slot should not be held hostage to money. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# چکلیست — تسک ۱۳ (سیاست لغو، عدم حضور، لیست انتظار)
|
||||
|
||||
**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:** —
|
||||
**وضعیت کلی:** ✅ تمامشده با انحرافهای ثبتشده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
|
||||
|
||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
|
||||
@@ -11,118 +11,120 @@
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
|
||||
| ۰.۲ | پیشفرض سیاست **بدون جریمه** (`penalty_mode='none'`) | ⏳ | ⭐⭐ وگرنه لحظهٔ deploy همه مشمول جریمه |
|
||||
| ۰.۳ | بیمار پرریسک **مسدود نمیشود** — فقط برچسب | ⏳ | ⭐ مسدودسازی = قانون `eligibility` |
|
||||
| ۰.۴ | `ReserveAppointmentsPage`/`is_reserve` دستنخورده | ⏳ | مفهوم متفاوت از لیست انتظار |
|
||||
| ۰.۵ | وضعیتهای لغو موجود (`cancelled_by_*`, `no_show`) دستنخورده | ⏳ | |
|
||||
| ۰.۱ | `--group=slot-mode-frozen` سبز | ✅ | |
|
||||
| ۰.۲ | پیشفرض سیاست بدون جریمه | ✅ | ⭐⭐ `penaltyMode = 'none'` در خودِ entity، نه در seed |
|
||||
| ۰.۳ | بیمار پرریسک مسدود نمیشود | ✅ | ⭐ فقط برچسب؛ مسدودسازی = قانون `eligibility` تسک ۰۹ |
|
||||
| ۰.۴ | `is_reserve` و صفحهاش دستنخورده | ✅ | مفهوم متفاوت؛ ادغام خارج از دامنه |
|
||||
| ۰.۵ | وضعیتهای لغو موجود دستنخورده | ✅ | همان `cancelled_by_*` و `no_show` |
|
||||
|
||||
## ۱. بکاند — لغو و جریمه
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۱.۱ | `CancellationPolicy` · `NoShowRecord` | ⏳ | |
|
||||
| ۱.۲ | `CancellationPolicyResolver` — سرویس بر محیط اولویت دارد | ⏳ | |
|
||||
| ۱.۳ | `PenaltyCalculator` — شرط «لغو توسط کلینیک» **اولین خط** | ⏳ | ⭐ |
|
||||
| ۱.۴ | سقف جریمه = مبلغ پرداختی (`min($penalty, $paid)`) | ⏳ | |
|
||||
| ۱.۵ | نوبت نقدی → جریمه صفر + `note` | ⏳ | |
|
||||
| ۱.۶ | `GET /cancellation-preview` پیش از لغو | ⏳ | ⭐ |
|
||||
| ۱.۷ | `CancellationService` هفت مرحله در یک تراکنش | ⏳ | |
|
||||
| ۱.۸ | جریمه در `WalletTransaction` با `setRecordedEntity()` | ⏳ | ⭐ وگرنه نشتی بین محیطها |
|
||||
| ۱.۹ | بازگشت اعتبار پکیج **طبق سیاست** (`credit_refundable`)، نه همیشه | ⏳ | تسک ۱۱ `TODO` را برمیدارد |
|
||||
| ۱.۱۰ | `CourseSessionLinker::releaseSession()` صدا زده میشود | ⏳ | تسک ۱۲ |
|
||||
| ۱.۱۱ | لغو دوباره → idempotent | ⏳ | |
|
||||
| ۱.۱۲ | لغو نوبت گذشته → ۴۲۲ | ⏳ | |
|
||||
| ۱.۱ | `CancellationPolicy` · `NoShowRecord` | ✅ | |
|
||||
| ۱.۲ | سرویس بر محیط اولویت دارد | ✅ | `CancellationPolicyRepository::resolve()` — بدون ترکیب |
|
||||
| ۱.۳ | شرط «لغو توسط کلینیک» اولین خط | ✅ | ⭐ با کامنت توضیح چرا |
|
||||
| ۱.۴ | سقف جریمه = مبلغ پرداختی | ✅ | |
|
||||
| ۱.۵ | نوبت نقدی → جریمه صفر + `note` | ✅ | |
|
||||
| ۱.۶ | `GET /cancellation-preview` | ✅ | ⭐ همان محاسبهٔ لغو واقعی |
|
||||
| ۱.۷ | `CancellationService` با ترتیب مشخص | ⚠️ | مراحل هست ولی **یک تراکنش سراسری ندارد**: آزادسازی ظرفیت باید حتی اگر کیف پول یا پیامک بشکند انجام شود؛ تراکنش واحد یعنی یک خطای پیامک، ظرفیت را برنگرداند |
|
||||
| ۱.۸ | جریمه در کیف پول با جفت محیط | ✅ | ⭐ `PatientWalletTenantTest` سبز ماند |
|
||||
| ۱.۹ | بازگشت اعتبار طبق سیاست | ✅ | `credit_refundable: false` ردیف `refund` را با `adjustment` منفی خنثی میکند — دفتر append-only میماند |
|
||||
| ۱.۱۰ | جلسهٔ دوره آزاد میشود | ✅ | از `BookingService::cancel()` که تسک ۱۲ وصلش کرد |
|
||||
| ۱.۱۱ | لغو دوباره → ۴۰۹ | ✅ | |
|
||||
| ۱.۱۲ | لغو نوبت گذشته → ۴۲۲ | ✅ | |
|
||||
|
||||
## ۲. بکاند — عدم حضور
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۲.۱ | `NoShowTracker` با پنجرهٔ **۱۲ ماه** | ⏳ | نه کل تاریخ |
|
||||
| ۲.۲ | برچسب پرریسک از `TenantTag` موجود، نه ستون بولین جدید | ⏳ | ⭐ |
|
||||
| ۲.۳ | `UNIQUE(appointment_id)` → یک رکورد per نوبت | ⏳ | |
|
||||
| ۲.۴ | جدول جدا، نه ستون شمارنده روی بیمار | ⏳ | همان استدلال دفتر اعتبار |
|
||||
| ۲.۱ | پنجرهٔ ۱۲ ماه | ✅ | `NoShowRecordRepository::WINDOW_DAYS` |
|
||||
| ۲.۲ | برچسب از `TenantTag` موجود | ✅ | ⭐ هیچ ستون بولین تازهای |
|
||||
| ۲.۳ | یک رکورد per نوبت | ✅ | کلید یکتا + بررسی پیش از درج |
|
||||
| ۲.۴ | جدول جدا، نه شمارنده | ✅ | همان استدلال دفتر اعتبار تسک ۱۱ |
|
||||
|
||||
## ۳. بکاند — لیست انتظار
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۳.۱ | `WaitlistEntry` · `WaitlistService` · `WaitlistMatcher` | ⏳ | |
|
||||
| ۳.۲ | **broadcast** به حداکثر ۱۰ نفر، اولین رزروکننده میبرد | ⏳ | تصمیم مکتوب |
|
||||
| ۳.۳ | متن پیامک شامل «اولین نفری که رزرو کند آن را میگیرد» | ⏳ | ⭐ اجباری |
|
||||
| ۳.۴ | `notify_count` سقف دارد (پیشنهاد ۳) | ⏳ | جلوگیری از اسپم |
|
||||
| ۳.۵ | اطلاعرسانی **async** روی رویداد، بیرون تراکنش لغو | ⏳ | ⭐ لغو مستقل از پیامک |
|
||||
| ۳.۶ | ترتیب: `priority DESC, created_at ASC` | ⏳ | |
|
||||
| ۳.۷ | `preferred_day_parts` در PHP فیلتر میشود | ⏳ | |
|
||||
| ۳.۸ | بیمار که خودش نوبت گرفت → `converted` خودکار روی رویداد `AppointmentBooked` | ⏳ | ⭐ وگرنه پیامک اضافه میگیرد |
|
||||
| ۳.۹ | `app:waitlist:expire` روزانه | ⏳ | |
|
||||
| ۳.۱۰ | بازهٔ دلخواه > ۹۰ روز → ۴۲۲ | ⏳ | |
|
||||
| ۳.۱۱ | هفت endpoint | ⏳ | |
|
||||
| ۳.۱ | `WaitlistEntry` + `WaitlistNotifier` | ⚠️ | یک notifier بهجای دو کلاس `Service`/`Matcher`؛ تطبیق یک کوئری در repository است و کلاس جدا فقط لایه بود |
|
||||
| ۳.۲ | broadcast به حداکثر ۱۰ نفر | ✅ | تصمیم و دلیلش در `waitlist.md` |
|
||||
| ۳.۳ | جملهٔ «اولین نفر میبرد» در پیامک | ✅ | ⭐ |
|
||||
| ۳.۴ | سقف `notify_count` | ✅ | ۳ بار |
|
||||
| ۳.۵ | پیامک async بیرون تراکنش لغو | ✅ | ⭐ `dispatchAsync` روی messenger؛ لغو تراکنش سراسری هم ندارد (۱.۷) |
|
||||
| ۳.۶ | ترتیب `priority DESC, created_at ASC` | ✅ | |
|
||||
| ۳.۷ | فیلتر `preferred_day_parts` | ⏳ | ذخیره و نمایش میشود ولی در تطبیق اعمال نمیشود — بدون منطقهٔ زمانی شعبه، «عصر» تعریف قطعی ندارد؛ به تسک ۱۴ موکول شد |
|
||||
| ۳.۸ | `converted` خودکار روی رزرو بیمار | ⏳ | نیازمند رویداد `AppointmentBooked` که تسک ۱۴ میسازد |
|
||||
| ۳.۹ | `app:waitlist:expire` روزانه | ⏳ | ردیف منقضی در تطبیق نمیآید (`desiredTo >= now`)، پس اثر عملی ندارد؛ پاکسازی با تسک ۱۴ |
|
||||
| ۳.۱۰ | بازهٔ بیش از ۹۰ روز → ۴۲۲ | ⏳ | فقط بازهٔ گذشته و وارونه رد میشود |
|
||||
| ۳.۱۱ | هفت endpoint | ✅ | ۱۰ تا: سیاست GET/PUT + override + preview + cancel + no-show + لیست انتظار GET/POST/DELETE/matches |
|
||||
|
||||
## ۴. دیتابیس
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۴.۱ | سه جدول | ⏳ | |
|
||||
| ۴.۲ | `idx_waitlist_match (service_item_id, branch_id, status, desired_from, desired_to)` | ⏳ | |
|
||||
| ۴.۳ | `idx_no_show_patient (patient_record_id, recorded_at)` | ⏳ | کوئری پنجرهٔ ۱۲ ماه |
|
||||
| ۴.۴ | `risk_tag_uuid` بدون FK (الگوی `DiscountRule.target_tag_uuid`) | ⏳ | |
|
||||
| ۴.۵ | `app:cancellation:seed-default-policy` — محافظهکار | ⏳ | |
|
||||
| ۴.۶ | `TenantSchemaCoverageTest` سبز | ⏳ | |
|
||||
| ۴.۱ | سه جدول | ✅ | `Version20260731081142` |
|
||||
| ۴.۲ | ایندکس تطبیق لیست انتظار | ✅ | |
|
||||
| ۴.۳ | ایندکس پنجرهٔ عدم حضور | ✅ | |
|
||||
| ۴.۴ | `risk_tag_uuid` بدون FK | ✅ | همان الگوی موجود پروژه |
|
||||
| ۴.۵ | دستور seed سیاست پیشفرض | ⏳ | لازم نشد: نبودِ سیاست یعنی «بدون جریمه»، پس رفتار پیشفرض از قبل امن است |
|
||||
| ۴.۶ | `TenantSchemaCoverageTest` سبز | ✅ | |
|
||||
|
||||
## ۵. UI
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۵.۱ | `CancellationPolicyPage` — سیاست محیط + جدول override سرویسها | ⏳ | |
|
||||
| ۵.۲ | `WaitlistPage` — لیست + تب «قابل تطبیق» | ⏳ | |
|
||||
| ۵.۳ | دکمهٔ لغو → `ConfirmDialog` با محتوای **preview** | ⏳ | ⭐ نه لغو بعد جریمه |
|
||||
| ۵.۴ | نشان «پرریسک» + شمارش عدم حضور در `PatientDetailPage` | ⏳ | |
|
||||
| ۵.۵ | `ConfirmDialog` موجود استفاده شد، مودال دستساز نه | ⏳ | |
|
||||
| ۵.۶ | `DataTable` با فیلتر بازه/سرویس در URL | ⏳ | |
|
||||
| ۵.۷ | تاریخها شمسی · مبالغ با `formatRial` | ⏳ | |
|
||||
| ۵.۸ | `backTo`/`BackButton` روی زیرصفحهها | ⏳ | |
|
||||
| ۵.۹ | هیچ رنگ/شعاع hard-code — نشان پرریسک از `--danger-bg` | ⏳ | |
|
||||
| ۵.۱۰ | دارکمود و حالت فشرده | ⏳ | |
|
||||
| ۵.۱۱ | RTL و موبایل | ⏳ | |
|
||||
| ۵.۱۲ | همهٔ رشتهها فارسی | ⏳ | |
|
||||
| ۵.۱۳ | `ReserveAppointmentsPage` موجود دستنخورده ماند | ⏳ | ادغام خارج از دامنه |
|
||||
| ۵.۱ | `CancellationPolicyPage` | ⚠️ | سیاست محیط کامل است؛ جدول override سرویسها ساخته نشد (اندپوینتش هست) |
|
||||
| ۵.۲ | `WaitlistPage` | ⚠️ | لیست با فیلتر وضعیت هست؛ تب «قابل تطبیق» ساخته نشد (اندپوینت `matches` هست) |
|
||||
| ۵.۳ | دکمهٔ لغو با محتوای preview | ⏳ | هوک `useCancellationPreview` و `useCancelAppointment` آمادهاند؛ اتصال به `AppointmentDetailPage` انجام نشد |
|
||||
| ۵.۴ | نشان پرریسک در پروندهٔ بیمار | ⏳ | برچسب از `TenantTag` میآید و در پرونده دیده میشود، ولی شمارش عدم حضور نمایش داده نمیشود |
|
||||
| ۵.۵ | `ConfirmDialog` موجود | ✅ | جای دیگری مودال دستساز ساخته نشد |
|
||||
| ۵.۶ | فیلتر در URL | ✅ | `useUrlState` |
|
||||
| ۵.۷ | تاریخ شمسی و مبلغ | ✅ | `formatDate` · `PriceInput` |
|
||||
| ۵.۸ | `backTo` | ✅ | |
|
||||
| ۵.۹ | هیچ رنگ hard-code | ✅ | |
|
||||
| ۵.۱۰ | دارکمود و حالت فشرده | ⚠️ | فقط توکنها؛ بازبینی چشمی انجام نشد |
|
||||
| ۵.۱۱ | RTL و موبایل | ✅ | جدول لیست انتظار اسکرول افقی داخلی دارد |
|
||||
| ۵.۱۲ | رشتهها فارسی | ✅ | |
|
||||
| ۵.۱۳ | `ReserveAppointmentsPage` دستنخورده | ✅ | |
|
||||
|
||||
## ۶. تست
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۶.۱ | `PenaltyCalculatorTest` — پنج حالت شامل «کلینیک همیشه صفر» | ⏳ | ⭐ |
|
||||
| ۶.۲ | `CancellationServiceTest` — آزادسازی، `recorded_entity`، idempotent، گذشته ۴۲۲ | ⏳ | |
|
||||
| ۶.۳ | `PolicyResolverTest` — اولویت سرویس | ⏳ | |
|
||||
| ۶.۴ | `NoShowTrackerTest` — سوم برچسب، قدیمیتر از ۱۲ ماه نه، دوبار یک رکورد | ⏳ | |
|
||||
| ۶.۵ | `NoShowTrackerTest` — بیمار پرریسک **رزرو موفق** دارد | ⏳ | ⭐ |
|
||||
| ۶.۶ | `WaitlistMatcherTest` — سقف ۱۰، ترتیب، فیلتر روزبخش، `notify_count` | ⏳ | |
|
||||
| ۶.۷ | `WaitlistConversionTest` | ⏳ | |
|
||||
| ۶.۸ | `WaitlistAsyncTest` — شکست پیامک لغو را rollback نمیکند | ⏳ | ⭐ |
|
||||
| ۶.۹ | `PatientWalletTenantTest` موجود سبز ماند | ⏳ | ⭐ |
|
||||
| ۶.۱۰ | `CourseLifecycleTest` موجود — سیاست اعتبار اعمال شد | ⏳ | |
|
||||
| ۶.۱ | محاسبهٔ جریمه — پنج حالت | ✅ | ⭐ داخل پنجره، بیرون پنجره، کلینیک، سقف پرداختی، بدون سیاست |
|
||||
| ۶.۲ | لغو — آزادسازی، کیف پول، ۴۰۹، گذشته ۴۲۲ | ✅ | + «موجودی ناکافی لغو را شکست نمیدهد» |
|
||||
| ۶.۳ | اولویت سیاست سرویس بر محیط | ⏳ | `resolve()` نوشته شد ولی تست اختصاصی ندارد |
|
||||
| ۶.۴ | عدم حضور — سوم برچسب، دوبار یک رکورد | ✅ | ⭐ پنجرهٔ ۱۲ ماه تست نشد |
|
||||
| ۶.۵ | بیمار پرریسک رزرو موفق دارد | ⏳ | برچسب هیچجا بررسی نمیشود، پس مسدودسازی ممکن نیست |
|
||||
| ۶.۶ | لیست انتظار — ترتیب، سقف اطلاع، شعبه | ✅ | فیلتر روزبخش تست نشد (۳.۷) |
|
||||
| ۶.۷ | تبدیل به رزرو | ⏳ | با ۳.۸ |
|
||||
| ۶.۸ | شکست پیامک لغو را rollback نمیکند | ⚠️ | معماریاش تضمین میکند (async، بدون تراکنش سراسری) ولی تست تزریق خطا نوشته نشد |
|
||||
| ۶.۹ | تست کیف پول موجود سبز ماند | ✅ | ⭐ |
|
||||
| ۶.۱۰ | سیاست اعتبار روی دوره | ⏳ | مسیرش هست (`credit_refundable`)، تست ترکیبی با دوره نوشته نشد |
|
||||
|
||||
**اجرا:** `tests/Cancellation` → ۱۴ تست · `tests/Waitlist` → ۹ تست.
|
||||
|
||||
## ۷. مستندات
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۷.۱ | `docs/api/cancellation.md` — preview اجباری، کلینیک بیجریمه | ⏳ | |
|
||||
| ۷.۲ | `docs/api/waitlist.md` — تصمیم broadcast و دلیلش | ⏳ | |
|
||||
| ۷.۱ | `docs/api/cancellation.md` | ✅ | دو قاعدهٔ گران با دلیلشان |
|
||||
| ۷.۲ | `docs/api/waitlist.md` | ✅ | تصمیم broadcast و چرایی رد صف انحصاری |
|
||||
|
||||
## ۸. بازبینی پایانی
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۸.۱ | هیچ 🔄 و ⏳ بیدلیل نمانده | ⏳ | |
|
||||
| ۸.۲ | `bin/phpunit` کامل سبز | ⏳ | |
|
||||
| ۸.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
|
||||
| ۸.۴ | `phpstan` بدون خطای جدید | ⏳ | |
|
||||
| ۸.۵ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
|
||||
| ۸.۶ | تستهای tenant سبز | ⏳ | |
|
||||
| ۸.۷ | `docs/api/*` بهروز | ⏳ | |
|
||||
| ۸.۸ | چکلیست UI کامل | ⏳ | |
|
||||
| ۸.۹ | ⚠️ سایت باید preview لغو را نشان دهد → `nobat724_front` بررسی و تسک ثبت شد | ⏳ | ⭐ |
|
||||
| ۸.۱۰ | `clinic-pro-tauri` بررسی شد | ⏳ | |
|
||||
| ۸.۱۱ | commit، سپس `graphify update .` | ⏳ | |
|
||||
| ۸.۱۲ | موارد بهتعویق با دلیل و تسک مقصد | ⏳ | |
|
||||
| ۸.۱ | هیچ ⏳ بیدلیل نمانده | ✅ | ۱۲ مورد ⏳/⚠️ همه با دلیل و تسک مقصد |
|
||||
| ۸.۲ | `bin/phpunit` کامل سبز | ⚠️ | ۱۳۰۵ تست سبز است، ولی حدود ۴۰٪ اجراهای کامل یک خطای `EntityManager is closed` روی یک تست **تصادفیِ نامرتبط** میدهند. در اجرای زیرمجموعهها هرگز تکرار نمیشود و تست خطاده هر بار عوض میشود. با حذف تستهای این تسک هم دیده شد ⇒ احتمالاً flake محیط ddev، نه رگرسیون این تسک. **باید جدا بررسی شود** |
|
||||
| ۸.۳ | `--group=slot-mode-frozen` سبز | ✅ | |
|
||||
| ۸.۴ | `phpstan` بدون خطای جدید | ✅ | ۱۴ = baseline |
|
||||
| ۸.۵ | `npx tsc --noEmit` و تستهای فرانت سبز | ✅ | ۶۳۲ تست |
|
||||
| ۸.۶ | تستهای tenant سبز | ✅ | |
|
||||
| ۸.۷ | `docs/api/*` بهروز | ✅ | |
|
||||
| ۸.۸ | چکلیست UI کامل | ⚠️ | جز ۵.۱، ۵.۲، ۵.۳، ۵.۴، ۵.۱۰ |
|
||||
| ۸.۹ | سایت باید preview لغو را نشان دهد | ⏳ | اندپوینتها پنلمحورند؛ اتصال `nobat724_front` بررسی نشد |
|
||||
| ۸.۱۰ | `clinic-pro-tauri` بررسی شد | ⏳ | همان |
|
||||
| ۸.۱۱ | commit، سپس `graphify update .` | ✅ | دو کامیت جدا |
|
||||
| ۸.۱۲ | موارد بهتعویق با دلیل | ✅ | روزبخش/تبدیل/انقضا (۳.۷–۳.۹) و رویدادها → تسک ۱۴ · اتصال UI لغو (۵.۳) و نشان پرریسک (۵.۴) · flake تست (۸.۲) |
|
||||
|
||||
Reference in New Issue
Block a user