diff --git a/docs/analysis/class-diagram.md b/docs/analysis/class-diagram.md new file mode 100644 index 0000000..714f6f4 --- /dev/null +++ b/docs/analysis/class-diagram.md @@ -0,0 +1,297 @@ +# نمودار کلاس (Class Diagram) – سامانه مدیریت و گزارش خرابی دانشگاهی + +**تهیه‌کننده:** ارشیا همتی‌زاده +**بازبین:** امیرحسن عارف اصل +**آخرین به‌روزرسانی:** ۱۴۰۵/۰۸/۱۴ + +--- + +## مقدمه + +این سند نمودار کلاس سامانهٔ مدیریت و گزارش خرابی دانشگاهی را بر اساس مستندات موجود در مخزن ارائه می‌دهد. + +> **نکته:** چون پروژه هنوز وارد فاز کدنویسی نشده است، این نمودار یک **مدل دامنه (Domain Model)** است و کلاس‌های فنی مانند Repository، Controller و جدول پایگاه‌داده را شامل نمی‌شود. برای مشاهدهٔ مدل دامنهٔ تفصیلی، به `domain-model.md` (در صورت وجود) مراجعه کنید. + +## ۱. بازیگران و نقش‌ها در نمودار + +| بازیگر | کلاس متناظر | مستند مرجع | +|---|---|---| +| کاربر سیستم | `User` | FR-01، FR-02، US-15، AD-01 | +| گزارش‌دهنده | `Reporter` (انتزاعی) | requirements §۲ | +| دانشجو | `Student` | US-01 تا US-05، AD-02 | +| استاد | `Professor` | US-02 تا US-05، AD-02 | +| کارمند تأسیسات | `FacilitiesStaff` | FR-06 تا FR-09، FR-11، FR-16، FR-17، US-06 تا US-10، US-17، AD-03 | +| تعمیرکار | `Technician` | FR-10 تا FR-13، US-11 تا US-14، AD-04 | +| مدیر | `Manager` | FR-17، US-17، AD-03 | + +## ۲. نمودار کلاس + +```mermaid +classDiagram + direction LR + + class User["کاربر سیستم"] { + +String نام_کاربری + +String رمز_عبور_هش‌شده + +Boolean فعال + +ورود(نام_کاربری, رمز_عبور) Boolean + +خروج() void + +بررسی_دسترسی(عملیات) Boolean + } + + class Reporter["گزارش‌دهنده"] { + <> + +ثبت_گزارش(عنوان, توضیحات, محل, نوع_تجهیز) FaultReport + +افزودن_اطلاعات_تکمیلی(گزارش, محتوا) void + +مشاهده_گزارش‌های_خود() List + +مشاهده_تاریخچه(گزارش) List + +تأیید_تعمیر(گزارش) void + +رد_تعمیر(گزارش, دلیل) void + } + + class Student["دانشجو"] + class Professor["استاد"] + + class FacilitiesStaff["کارمند تأسیسات"] { + +بررسی_گزارش‌ها(فیلتر) List + +تعیین_اولویت(گزارش, اولویت) void + +تخصیص_تعمیرکار(گزارش, تعمیرکار) Assignment + +تغییر_وضعیت(گزارش, وضعیت) void + +مشاهده_تاریخچه(گزارش) List + +مشاهده_داشبورد() Dashboard + } + + class Technician["تعمیرکار"] { + +مشاهده_مأموریت‌ها() List + +شروع_تعمیر(گزارش) void + +ثبت_نتیجه_تعمیر(گزارش, توضیحات) RepairResult + +درخواست_اطلاعات(گزارش, توضیحات) InfoRequest + } + + class Manager["مدیر"] { + +مشاهده_داشبورد_مدیریتی() Dashboard + } + + class FaultReport["گزارش خرابی"] { + +String شناسه + +String عنوان + +String توضیحات + +ReportStatus وضعیت + +Priority اولویت + +DateTime تاریخ_ثبت + +اعتبارسنجی() Boolean + +تغییر_وضعیت(وضعیت_جدید) void + +بستن() void + +افزودن_رویداد(اقدام, انجام‌دهنده) void + } + + class Location["محل خرابی"] { + +String ساختمان + +String طبقه + +String اتاق + } + + class ReportStatus["وضعیت گزارش"] { + <> + ثبت‌شده + ارجاع‌شده + در_حال_تعمیر + تعمیر_انجام‌شده + بسته‌شده + } + + class Priority["اولویت"] { + <> + کم + متوسط + زیاد + فوری + } + + class EquipmentType["نوع تجهیز"] { + <> + روشنایی + تهویه + آسانسور + برق + پروژکتور + سایر + } + + class Assignment["تخصیص تعمیرکار"] { + +DateTime تاریخ_تخصیص + +String وضعیت + } + + class RepairResult["نتیجه تعمیر"] { + +String توضیحات + +DateTime تاریخ_ثبت + } + + class ReporterFeedback["بازخورد گزارش‌دهنده"] { + +Boolean تأیید_شده + +String دلیل_رد + +DateTime تاریخ + +اعتبارسنجی_دلیل() Boolean + } + + class SupplementaryInfo["اطلاعات تکمیلی"] { + +String نوع + +String محتوا + +DateTime تاریخ + } + + class InfoRequest["درخواست اطلاعات تکمیلی"] { + +String توضیحات + +DateTime تاریخ + +Boolean پاسخ_داده_شده + } + + class ReportHistoryEntry["رویداد تاریخچه"] { + +String اقدام + +String توضیحات + +DateTime تاریخ + } + + class Notification["اعلان"] { + +String رویداد + +String کانال + +DateTime تاریخ + +Boolean خوانده_شده + } + + class Dashboard["داشبورد مدیریتی"] { + +int تعداد_کل_گزارش‌ها + +int تعداد_گزارش‌های_باز + +int تعداد_درخواست‌های_فوری + +محاسبه_آمار() void + } + + class AuthService["سرویس احراز هویت"] { + <> + +اعتبارسنجی(نام_کاربری, رمز_عبور) Boolean + } + + User <|-- Reporter + Reporter <|-- Student + Reporter <|-- Professor + User <|-- FacilitiesStaff + User <|-- Technician + User <|-- Manager + + Reporter "1" --> "*" FaultReport : ثبت_می‌کند + FaultReport "*" --> "1" Location : در_محل + FaultReport "*" --> "1" EquipmentType : نوع_تجهیز + FaultReport "1" --> "1" ReportStatus : وضعیت + FaultReport "1" --> "0..1" Priority : اولویت + + FaultReport "1" *-- "*" SupplementaryInfo : اطلاعات_تکمیلی + SupplementaryInfo "*" --> "1" Reporter : ارائه‌دهنده + InfoRequest "1" --> "0..1" SupplementaryInfo : پاسخ + + FaultReport "1" *-- "*" InfoRequest : درخواست‌ها + InfoRequest "*" --> "1" User : درخواست‌دهنده + + FaultReport "1" *-- "*" Assignment : تخصیص‌ها + Assignment "*" --> "1" Technician : تعمیرکار + Assignment "*" --> "1" FacilitiesStaff : تخصیص‌دهنده + + FaultReport "1" *-- "*" RepairResult : نتایج_تعمیر + RepairResult "*" --> "1" Technician : ثبت‌کننده + + FaultReport "1" *-- "*" ReporterFeedback : بازخوردها + ReporterFeedback "*" --> "1" Reporter : تأییدکننده + ReporterFeedback "*" --> "0..1" RepairResult : درباره_نتیجه + + FaultReport "1" *-- "*" ReportHistoryEntry : تاریخچه + ReportHistoryEntry "*" --> "1" User : انجام‌دهنده + + Notification "*" --> "1" User : گیرنده + Notification "*" --> "1" FaultReport : مربوط_به + + FacilitiesStaff ..> Dashboard : مشاهده + Manager ..> Dashboard : مشاهده + Dashboard ..> FaultReport : محاسبه_از + User ..> AuthService : احراز_هویت + + note for Manager "طبق FR-17 و US-17 فقط «مشاهده داشبورد» برای مدیر ذکر شده؛ سطح دسترسی کامل او پرسش باز ۹ در activity.md است." + note for Priority "سطوح رسمی اولویت در نیازمندی‌ها تعیین نشده (پرسش باز ۳). این مقادیر موقت‌اند." + note for ReportStatus "فهرست رسمی وضعیت‌ها و انتقال‌های مجاز تعیین نشده (پرسش باز ۴). «باز» در FR-17 و US-06 به‌معنای گزارش بسته‌نشده است، نه یک وضعیت رسمی." + note for Assignment "پرسش باز ۵: آیا یک گزارش هم‌زمان به یک تعمیرکار تخصیص می‌یابد یا چند تعمیرکار؟" + note for InfoRequest "پرسش باز ۶: چه کسانی مجاز به درخواست اطلاعات و پاسخ به آن هستند؟" + note for ReporterFeedback "دلیل رد اجباری است (US-05.feature). تأیید فقط توسط گزارش‌دهنده همان گزارش و فقط در وضعیت «تعمیر انجام‌شده» مجاز است." + note for FaultReport "عنوان حداکثر ۱۵۰ کاراکتر؛ فیلدهای اجباری: عنوان، توضیحات، محل خرابی، نوع تجهیز (US-01.feature). وضعیت اولیه: ثبت‌شده." + note for AuthService "از سناریوی «سرویس احراز هویت در دسترس نیست» در US-15.feature. روش احراز هویت و بازیابی رمز در مستندات مشخص نشده." +``` + +## ۳. جدول ردیابی (Traceability) + +| عنصر نمودار | نوع | مستند مرجع | +|---|---|---| +| `User` / کاربر سیستم | کلاس | FR-01، FR-02، US-15، AD-01؛ activity §۱ | +| `Reporter` / گزارش‌دهنده | کلاس انتزاعی | requirements §۲ («گزارش‌دهنده = دانشجو یا استاد»)، US-01 تا US-05، AD-02، AD-05 | +| `Student` / دانشجو | کلاس | US-01، US-03، US-04، US-05، AD-02 | +| `Professor` / استاد | کلاس | US-02، US-03، US-04، US-05، AD-02 | +| `FacilitiesStaff` / کارمند تأسیسات | کلاس | FR-06 تا FR-09، FR-11، FR-16، FR-17، US-06 تا US-10، US-17، AD-03 | +| `Technician` / تعمیرکار | کلاس | FR-10 تا FR-13، US-11 تا US-14، AD-04 | +| `Manager` / مدیر | کلاس | FR-17، US-17، AD-03؛ activity §۱ | +| `FaultReport` / گزارش خرابی | کلاس | FR-03 تا FR-05، FR-11، FR-14 تا FR-16، US-01 تا US-05، US-09، US-10، AD-02 تا AD-06 | +| `ReportStatus` / وضعیت گزارش | enum | US-01 (`ثبت‌شده`)، US-05 (`ارجاع‌شده`، `در حال تعمیر`، `تعمیر انجام‌شده`، `بسته‌شده`)، US-12، AD-03 تا AD-05 | +| `Priority` / اولویت | enum موقت | FR-07، US-07، AD-03؛ سطوح دقیق: پرسش باز ۳ | +| `Location` / محل خرابی | کلاس | FR-03، US-01؛ دامنه فضاها: ابهام README | +| `EquipmentType` / نوع تجهیز | enum | FR-03، US-01؛ مثال‌های README: روشنایی، تهویه، آسانسور، برق، پروژکتور | +| `Assignment` / تخصیص تعمیرکار | کلاس | FR-09، US-08، US-11، AD-03، AD-04 | +| `RepairResult` / نتیجه تعمیر | کلاس | FR-12، US-13، AD-04 | +| `ReporterFeedback` / بازخورد گزارش‌دهنده | کلاس | FR-14، US-05، AD-05، US-05.feature | +| `SupplementaryInfo` / اطلاعات تکمیلی | کلاس | FR-04، US-04، US-14، AD-02، AD-04 | +| `InfoRequest` / درخواست اطلاعات | کلاس | FR-13، US-14، AD-04 | +| `ReportHistoryEntry` / تاریخچه | کلاس | FR-16، US-03، US-05، US-10، AD-03 | +| `Notification` / اعلان | کلاس | FR-15، US-16، AD-04 | +| `Dashboard` / داشبورد | کلاس (Read Model) | FR-17، US-17، AD-03 | +| `AuthService` / سرویس احراز هویت | سرویس خارجی | US-15.feature: «سرویس احراز هویت در دسترس نیست» | + +## ۴. محدودیت‌های اعمال‌شده از فایل‌های Feature + +| محدودیت | منبع | محل بازتاب در نمودار | +|---|---|---| +| عنوان حداکثر ۱۵۰ کاراکتر | US-01.feature | `note for FaultReport` | +| عنوان، توضیحات، محل، نوع تجهیز اجباری‌اند | US-01.feature | `FaultReport.اعتبارسنجی()` + note | +| وضعیت اولیه «ثبت‌شده» | US-01.feature | `ReportStatus` + note | +| دلیل رد بازخورد اجباری است | US-05.feature | `ReporterFeedback.دلیل_رد` + `اعتبارسنجی_دلیل()` | +| تأیید فقط توسط گزارش‌دهنده همان گزارش | US-05.feature | رابطه `ReporterFeedback --> Reporter` + note | +| تأیید فقط در وضعیت «تعمیر انجام‌شده» | US-05.feature | `note for ReporterFeedback` | +| تأیید دوباره نباید وضعیت را دوباره تغییر دهد (Idempotent) | US-05.feature | `note for ReporterFeedback` | +| کنترل دسترسی مبتنی بر نقش و جلوگیری از دسترسی مستقیم به صفحات نقش دیگر | US-15.feature | `User.بررسی_دسترسی()` + ارث‌بری نقش‌ها | +| پیام خطای ورود نباید مشخص کند کدام فیلد اشتباه است | US-15.feature | `User.ورود()` + `note for AuthService` | + +## ۵. نکات و پرسش‌های باز + +موارد زیر در مستندات به‌عنوان پرسش باز علامت‌گذاری شده‌اند و در نمودار فقط به‌صورت `note` آمده‌اند، نه به‌عنوان قانون قطعی: + +1. **فهرست رسمی وضعیت‌ها و انتقال‌های مجاز** — پرسش باز ۴ در [`activity.md`](activity.md). +2. **سطوح و معیارهای اولویت** — پرسش باز ۳ در [`activity.md`](activity.md). +3. **یک یا چند تعمیرکار برای هر گزارش** — پرسش باز ۵ در [`activity.md`](activity.md). +4. **چه کسانی می‌توانند درخواست اطلاعات بدهند یا پاسخ دهند** — پرسش باز ۶ در [`activity.md`](activity.md). +5. **جریان دقیق پس از رد تعمیر توسط گزارش‌دهنده** — پرسش باز ۷ در [`activity.md`](activity.md). +6. **کانال اعلان‌ها: داخل سامانه، پیامک یا ایمیل** — پرسش باز ۸ در [`activity.md`](activity.md) و بخش پرسش‌های باز [`requirements-specification.md`](requirements-specification.md). +7. **سطح دسترسی دقیق مدیر** — پرسش باز ۹ در [`activity.md`](activity.md). +8. **دامنه فضاها: آموزشی/عمومی یا شامل خوابگاه و اداری** — ابهام باقی‌مانده در [`README.md`](../../README.md). +9. **آیا یک کاربر می‌تواند چند نقش داشته باشد؟** — در مستندات صریحاً مشخص نشده است. اگر پاسخ «بله» باشد، ارث‌بری `User` به نقش‌ها باید به **ترکیب (Composition)** با کلاس `نقش` تغییر کند. +10. **روش احراز هویت و بازیابی رمز عبور** — در [`activity.md`](activity.md) §۲ به‌عنوان نامشخص علامت‌گذاری شده است. + +## ۶. مواردی که عمداً مدل نشده‌اند + +موارد زیر در مستندات تحلیل تعریف نشده‌اند و آگاهانه از نمودار حذف شده‌اند: + +- **Repository، Controller، Database، API** — مربوط به فاز طراحی فنی هستند. +- **سرویس ذخیره‌سازی** (US-01.feature) — فقط یک سناریوی خطاست، نه یک مؤلفهٔ معماری تعریف‌شده. +- **«باز» به‌عنوان وضعیت** — در FR-17 و US-06 به‌معنای گزارش بسته‌نشده است؛ وضعیت رسمی نیست. +- **«نیازمند پیگیری» به‌عنوان وضعیت** — در US-05 به‌صورت «علامت‌گذاری برای پیگیری بیشتر» آمده، نه وضعیت رسمی. + +## ۷. پیشنهاد اصلاح استاندارد + +استاندارد فعلی [`CONTRIBUTING.md`](../../CONTRIBUTING.md) §۷ شناسهٔ `AD-NN` را برای Activity Diagram تعریف کرده است اما شناسه‌ای برای Class Diagram ندارد. پیشنهاد می‌شود شناسهٔ `CD-NN` (مثلاً `CD-01` برای همین سند) به استاندارد اضافه شود تا ارجاع‌دهی یکدست بماند. + +--- + +**وضعیت سند:** نسخه اولیه (Draft) – آماده بازبینی \ No newline at end of file diff --git a/docs/analysis/data-model.md b/docs/analysis/data-model.md new file mode 100644 index 0000000..5774249 --- /dev/null +++ b/docs/analysis/data-model.md @@ -0,0 +1,823 @@ +# مدل داده (Data Model) – سامانه مدیریت و گزارش خرابی دانشگاهی + +**تهیه‌کننده:** ارشیا همتی‌زاده +**بازبین:** رضا آدوسی +**آخرین به‌روزرسانی:** ۱۴۰۵/۰۸/۱۴ + +--- + +## مقدمه + +این سند مدل دادهٔ منطقی سامانهٔ مدیریت و گزارش خرابی دانشگاهی را بر اساس مستندات تحلیل موجود ارائه می‌دهد. هدف، تعریف ساختار ذخیره‌سازی مفاهیم دامنه، کلیدها، محدودیت‌ها و روابط بین آن‌ها است؛ به‌گونه‌ای که پیاده‌سازی هر DBMS رابطه‌ای (PostgreSQL، MySQL، SQL Server و ...) از آن ممکن باشد. + +> **نکتهٔ مهم:** مدل داده با مدل دامنه یکی نیست. مدل داده به «چگونه ذخیره کنیم» پاسخ می‌دهد و مدل دامنه به «چه قواعدی در کسب‌وکار حاکم است». برخی ساده‌سازی‌های عمدی در این سند (مثل ادغام دانشجو و استاد در یک جدول) در بخش «تصمیم‌های مدل‌سازی» توضیح داده شده‌اند. + +**قرارداد نام‌گذاری:** نام جدول‌ها و ستون‌ها به انگلیسی و `snake_case` است (استاندارد فنی پایگاه‌داده). برچسب‌های فارسی در جداول مرجع (`label_fa`) نگهداری می‌شوند تا تغییر متن فارسی نیازی به مهاجرت داده نداشته باشد. این تصمیم با بند «نام بازیگران: فقط نام فارسی» در `CONTRIBUTING.md` §۷ تعارضی ندارد، زیرا آن بند دربارهٔ **متن مستندات** است، نه نام‌گذاری فنی. + +## ۱. تصمیم‌های مدل‌سازی + +| # | تصمیم | گزینهٔ جایگزین | دلیل انتخاب | وابسته به | +|---|---|---|---|---| +| D1 | دانشجو و استاد در یک جدول `users` با ستون `role_code` | دو جدول جدا یا ارث‌بری جدولی | در همهٔ مستندات، دانشجو و استاد رفتار دادهٔ یکسانی دارند و فقط نقششان متفاوت است | — | +| D2 | `location_text` به‌صورت متن آزاد | جدول `locations` با ساختمان/طبقه/اتاق | `US-01.feature` محل را به‌صورت **یک فیلد متنی** با مقدار «ساختمان الف - کلاس ۱۰۲» تعریف کرده است | ابهام دامنه در `README.md` | +| D3 | وضعیت و اولویت و نوع تجهیز و نقش به‌صورت **جدول مرجع** | `ENUM` پایگاه‌داده یا `CHECK` | فهرست وضعیت‌ها و اولویت‌ها در پرسش‌های باز ۳ و ۴ تعریف‌نشده‌اند و باید بدون مهاجرت schema قابل تغییر باشند | پرسش باز ۳ و ۴ | +| D4 | یک گزارش می‌تواند **چند نتیجهٔ تعمیر** داشته باشد | یک نتیجهٔ واحد | `AD-06` مسیر بازگشت پس از رد را نشان می‌دهد؛ اگر این مسیر تأیید شود، چرخهٔ تعمیر تکرار می‌شود | پرسش باز ۷ | +| D5 | تأیید/رد به‌صورت جدول `report_feedbacks` با `UNIQUE (report_id, repair_result_id)` | فقط یک ردیف بازخورد per report | محدودیت یکتا، کلیک دوباره روی «تأیید» را در سطح داده بی‌اثر می‌کند (الزام `US-05.feature`) | — | +| D6 | حذف فیزیکی گزارش ممنوع؛ آرشیو = وضعیت `CLOSED` | ستون `deleted_at` | `NFR-05` از حذف ناخواسته منع می‌کند و بستن گزارش همان نقش آرشیو را دارد | نیاز به حذف منطقی: پرسش باز ۱۲ | +| D7 | `version` برای قفل خوش‌بینانه روی `fault_reports` و `users` | قفل بدبینانه | جلوگیری از بازنویسی هم‌زمان تغییرات (مرتبط با الزام idempotent در `US-05.feature`) | — | +| D8 | `report_no` جدا از `id` | استفاده از `id` به‌عنوان شناسه نمایشی | `US-05.feature` از قالب `R-100` استفاده می‌کند و `US-01.feature` «شناسه یکتا» را نمایش می‌دهد | فرمت تولید: پرسش باز ۱۱ | + +## ۲. فهرست جدول‌ها + +| # | جدول | نوع | توضیح | Aggregate | +|---|---|---|---|---| +| ۱ | `roles` | مرجع | نقش‌های کاربری | — | +| ۲ | `report_statuses` | مرجع | وضعیت‌های گزارش | — | +| ۳ | `priorities` | مرجع | سطوح اولویت | — | +| ۴ | `equipment_types` | مرجع | انواع تجهیز | — | +| ۵ | `notification_channels` | مرجع | کانال‌های اعلان | — | +| ۶ | `users` | اصلی | کاربران سامانه | کاربر | +| ۷ | `fault_reports` | اصلی | گزارش‌های خرابی | گزارش خرابی | +| ۸ | `report_supplementary_info` | اصلی | اطلاعات تکمیلی گزارش | گزارش خرابی | +| ۹ | `report_info_requests` | اصلی | درخواست‌های اطلاعات تکمیلی | گزارش خرابی | +| ۱۰ | `report_assignments` | اصلی | تخصیص تعمیرکار | گزارش خرابی | +| ۱۱ | `repair_results` | اصلی | نتایج تعمیر | گزارش خرابی | +| ۱۲ | `report_feedbacks` | اصلی | تأیید/رد گزارش‌دهنده | گزارش خرابی | +| ۱۳ | `report_history` | اصلی | تاریخچهٔ اقدامات (append-only) | گزارش خرابی | +| ۱۴ | `notifications` | اصلی | اعلان‌ها | اعلان | +| ۱۵ | `report_status_transitions` | اختیاری | انتقال‌های مجاز وضعیت | — | + +> **داشبورد** جدول ندارد؛ یک `VIEW` یا کوئری تجمیعی روی `fault_reports` است (طبق `class-diagram.md` که آن را `Read Model` علامت زده است). + +## ۳. نمودار ERD + +> نمودار به‌صورت انگلیسی نوشته شده تا در همهٔ رندررها (از جمله GitHub) درست نمایش داده شود. توضیحات فارسی در بخش‌های ۴ و ۵ آمده است. + +```mermaid +erDiagram + ROLES ||--o{ USERS : "assigned to" + USERS ||--o{ FAULT_REPORTS : "registers" + REPORT_STATUSES ||--o{ FAULT_REPORTS : "has" + PRIORITIES |o--o{ FAULT_REPORTS : "has" + EQUIPMENT_TYPES ||--o{ FAULT_REPORTS : "classifies" + + FAULT_REPORTS ||--o{ REPORT_SUPPLEMENTARY_INFO : "contains" + USERS ||--o{ REPORT_SUPPLEMENTARY_INFO : "provides" + + FAULT_REPORTS ||--o{ REPORT_INFO_REQUESTS : "contains" + USERS ||--o{ REPORT_INFO_REQUESTS : "requests" + REPORT_SUPPLEMENTARY_INFO |o--o| REPORT_INFO_REQUESTS : "answers" + + FAULT_REPORTS ||--o{ REPORT_ASSIGNMENTS : "contains" + USERS ||--o{ REPORT_ASSIGNMENTS : "is technician" + USERS ||--o{ REPORT_ASSIGNMENTS : "is assigner" + + FAULT_REPORTS ||--o{ REPAIR_RESULTS : "contains" + USERS ||--o{ REPAIR_RESULTS : "records" + + FAULT_REPORTS ||--o{ REPORT_FEEDBACKS : "contains" + REPAIR_RESULTS ||--o{ REPORT_FEEDBACKS : "about" + USERS ||--o{ REPORT_FEEDBACKS : "gives" + + FAULT_REPORTS ||--o{ REPORT_HISTORY : "logs" + USERS ||--o{ REPORT_HISTORY : "acts" + + USERS ||--o{ NOTIFICATIONS : "receives" + FAULT_REPORTS ||--o{ NOTIFICATIONS : "concerns" + NOTIFICATION_CHANNELS ||--o{ NOTIFICATIONS : "via" + + ROLES { + varchar code PK + varchar label_fa + smallint sort_order + boolean is_active + } + REPORT_STATUSES { + varchar code PK + varchar label_fa + smallint sort_order + boolean is_active + } + PRIORITIES { + varchar code PK + varchar label_fa + smallint sort_order + boolean is_active + } + EQUIPMENT_TYPES { + varchar code PK + varchar label_fa + smallint sort_order + boolean is_active + } + NOTIFICATION_CHANNELS { + varchar code PK + varchar label_fa + boolean is_active + } + USERS { + bigserial id PK + varchar username UK + varchar password_hash + varchar role_code FK + varchar display_name + boolean is_active + timestamptz created_at + timestamptz updated_at + integer version + } + FAULT_REPORTS { + bigserial id PK + varchar report_no UK + varchar title + text description + varchar location_text + varchar equipment_type_code FK + varchar status_code FK + varchar priority_code FK + bigint reporter_id FK + timestamptz created_at + timestamptz updated_at + timestamptz closed_at + integer version + } + REPORT_SUPPLEMENTARY_INFO { + bigserial id PK + bigint report_id FK + varchar info_type + text content_text + varchar attachment_ref + bigint provided_by_id FK + timestamptz created_at + } + REPORT_INFO_REQUESTS { + bigserial id PK + bigint report_id FK + bigint requested_by_id FK + text description + timestamptz requested_at + timestamptz responded_at + bigint response_info_id FK + } + REPORT_ASSIGNMENTS { + bigserial id PK + bigint report_id FK + bigint technician_id FK + bigint assigned_by_id FK + timestamptz assigned_at + varchar status + } + REPAIR_RESULTS { + bigserial id PK + bigint report_id FK + bigint technician_id FK + text description + timestamptz recorded_at + } + REPORT_FEEDBACKS { + bigserial id PK + bigint report_id FK + bigint repair_result_id FK + bigint reporter_id FK + boolean is_approved + text rejection_reason + timestamptz created_at + } + REPORT_HISTORY { + bigserial id PK + bigint report_id FK + varchar action_code + bigint actor_id FK + jsonb details + timestamptz occurred_at + } + NOTIFICATIONS { + bigserial id PK + bigint recipient_id FK + bigint report_id FK + varchar event_code + varchar channel_code FK + boolean is_read + timestamptz created_at + timestamptz read_at + } +``` + +## ۴. جداول مرجع (Lookup) + +همهٔ جداول مرجع ساختار یکسانی دارند: `code` (کلید پایدار انگلیسی)، `label_fa` (برچسب فارسی)، `sort_order` و `is_active`. تغییر متن فارسی بدون مهاجرت schema ممکن است و افزودن مقدار جدید نیاز به تغییر کد ندارد. + +### ۴.۱. `roles` — نقش‌ها + +| code | label_fa | sort_order | مرجع | +|---|---|---|---| +| `STUDENT` | دانشجو | ۱۰ | requirements §۲ | +| `PROFESSOR` | استاد | ۲۰ | requirements §۲ | +| `FACILITIES_STAFF` | کارمند تأسیسات | ۳۰ | requirements §۲ | +| `TECHNICIAN` | تعمیرکار | ۴۰ | requirements §۲ | +| `MANAGER` | مدیر | ۵۰ | requirements §۲ | + +> **نکته:** «کاربر سیستم» در `AD-01` و `US-15` یک **نقش** نیست، بلکه عنوان عمومی هر کاربر احراز هویت‌شده است (طبق `activity.md` §۱). بنابراین در این جدول مقدار ندارد. + +### ۴.۲. `report_statuses` — وضعیت‌های گزارش + +| code | label_fa | sort_order | مستند مرجع | +|---|---|---|---| +| `SUBMITTED` | ثبت‌شده | ۱۰ | `US-01.feature`، `US-05.feature` | +| `ASSIGNED` | ارجاع‌شده | ۲۰ | `US-05.feature` | +| `IN_REPAIR` | در حال تعمیر | ۳۰ | `US-05.feature`، `US-12` | +| `REPAIRED` | تعمیر انجام‌شده | ۴۰ | `US-05.feature` | +| `CLOSED` | بسته‌شده | ۵۰ | `US-05.feature` | + +> ⚠️ **پرسش باز ۴:** این فهرست از مثال‌های `US-05.feature` استخراج شده و **رسمی نیست**. «باز» (در `FR-17` و `US-06`) یک وضعیت نیست؛ به‌معنای «هر گزارشی که `status_code <> 'CLOSED'` است» می‌باشد و نباید ردیفی در این جدول داشته باشد. «نیازمند پیگیری» نیز فعلاً برچسب است، نه وضعیت (پرسش باز ۷). + +### ۴.۳. `priorities` — سطوح اولویت + +| code | label_fa | sort_order | مستند مرجع | +|---|---|---|---| +| `LOW` | کم | ۱۰ | **فرض موقت** | +| `MEDIUM` | متوسط | ۲۰ | **فرض موقت** | +| `HIGH` | زیاد | ۳۰ | **فرض موقت** | +| `URGENT` | فوری | ۴۰ | `US-17` (واژهٔ «فوری» آمده) | + +> ⚠️ **پرسش باز ۳:** سطوح رسمی و معیار هر سطح در نیازمندی‌ها تعیین نشده است. سه سطح اول **فرض موقت** هستند و باید پیش از پیاده‌سازی تأیید شوند. + +### ۴.۴. `equipment_types` — انواع تجهیز + +| code | label_fa | مستند مرجع | +|---|---|---| +| `LIGHTING` | روشنایی | `US-01.feature` | +| `HVAC` | تهویه | `README.md` | +| `ELEVATOR` | آسانسور | `README.md` | +| `ELECTRICAL` | برق | `README.md` | +| `PROJECTOR` | پروژکتور | `README.md` | +| `OTHER` | سایر | پیشنهادی (پوشش موارد دیگر) | + +### ۴.۵. `notification_channels` — کانال‌های اعلان + +| code | label_fa | وضعیت | +|---|---|---| +| `IN_APP` | داخل سامانه | **تأییدنشده** | +| `SMS` | پیامک | **تأییدنشده** | +| `EMAIL` | ایمیل | **تأییدنشده** | + +> ⚠️ **پرسش باز ۸:** هیچ کانالی در نیازمندی‌ها تأیید نشده است. این سه مقدار فقط به‌عنوان **دادهٔ نمونه** درج شده‌اند و باید پس از تصمیم‌گیری تیم نهایی شوند. + +## ۵. جداول اصلی (Entities) + +### ۵.۱. `users` — کاربران سامانه + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | شناسه فنی | — | +| `username` | `varchar(50)` | خیر | — | UNIQUE، `CHECK (btrim(username) <> '')` | نام کاربری | `FR-01`، `US-15.feature` | +| `password_hash` | `varchar(255)` | خیر | — | — | هش رمز (هرگز رمز خام) | `FR-01`، `NFR-01` | +| `role_code` | `varchar(30)` | خیر | — | FK → `roles.code` | نقش کاربر | `FR-02` | +| `display_name` | `varchar(100)` | بله | NULL | — | نام نمایشی (در مستندات الزامی نشده) | — | +| `is_active` | `boolean` | خیر | `true` | — | فعال/غیرفعال | — | +| `created_at` | `timestamptz` | خیر | `now()` | — | زمان ایجاد | — | +| `updated_at` | `timestamptz` | خیر | `now()` | — | آخرین تغییر | — | +| `version` | `integer` | خیر | `1` | — | قفل خوش‌بینانه | — | + +**نکته:** `US-15.feature` الزام می‌کند پیام خطای ورود مشخص نکند کدام فیلد اشتباه است. این یک قاعدهٔ **لایهٔ اپلیکیشن** است، نه محدودیت داده. + +> ⚠️ **پرسش باز ۹ (مهم):** اگر تصمیم تیم این باشد که یک کاربر می‌تواند **چند نقش** داشته باشد، باید ستون `role_code` حذف و جدول `user_roles (user_id, role_code)` اضافه شود. مدل فعلی بر پایهٔ **یک نقش per کاربر** است و با `class-diagram.md` (که ارث‌بری دارد) هم‌خوان است، ولی با `domain-model.md` (که نقش را Value Object چندمقداری دیده) هم‌خوان نیست. **پس از پاسخ به پرسش باز ۹، هر سه سند باید هم‌زمان به‌روزرسانی شوند.** + +### ۵.۲. `fault_reports` — گزارش‌های خرابی + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | شناسه فنی | — | +| `report_no` | `varchar(20)` | خیر | — | UNIQUE | شناسهٔ نمایشی (مثل `R-100`) | `US-05.feature` | +| `title` | `varchar(150)` | خیر | — | `CHECK (char_length(title) BETWEEN 1 AND 150)` | عنوان | `US-01.feature`، `FR-03` | +| `description` | `text` | خیر | — | `CHECK (btrim(description) <> '')` | توضیحات | `US-01.feature`، `FR-03` | +| `location_text` | `varchar(255)` | خیر | — | `CHECK (btrim(location_text) <> '')` | محل خرابی (متن آزاد) | `US-01.feature`، `FR-03` | +| `equipment_type_code` | `varchar(30)` | خیر | — | FK → `equipment_types.code` | نوع تجهیز | `US-01.feature`، `FR-03` | +| `status_code` | `varchar(30)` | خیر | `'SUBMITTED'` | FK → `report_statuses.code` | وضعیت جاری | `US-01.feature` | +| `priority_code` | `varchar(30)` | بله | NULL | FK → `priorities.code` | اولویت (تا زمان تعیین خالی است) | `FR-07`، `US-07` | +| `reporter_id` | `bigint` | خیر | — | FK → `users.id` | گزارش‌دهنده | `FR-03`، `US-01`، `US-02` | +| `created_at` | `timestamptz` | خیر | `now()` | — | زمان ثبت | — | +| `updated_at` | `timestamptz` | خیر | `now()` | — | آخرین تغییر | — | +| `closed_at` | `timestamptz` | بله | NULL | — | زمان بستن گزارش | `US-05.feature` | +| `version` | `integer` | خیر | `1` | — | قفل خوش‌بینانه | `US-05.feature` (idempotency) | + +**محدودیت ترکیبی:** `UNIQUE (id, reporter_id)` — برای اینکه جدول بازخورد بتواند تضمین کند فقط گزارش‌دهندهٔ همان گزارش تأیید می‌کند (بخش ۶). + +> **نکتهٔ D2:** `location_text` عمداً متن آزاد است چون `US-01.feature` محل را به‌صورت یک فیلد متنی تعریف کرده است. اگر دامنهٔ سامانه گسترش یابد (ابهام `README.md` دربارهٔ خوابگاه و فضاهای اداری)، در آینده می‌توان `location_text` را نگه داشت و جدول ساختاریافتهٔ `locations` را به‌عنوان مکمل اضافه کرد. `class-diagram.md` فعلاً نسخهٔ ساختاریافته را نشان می‌دهد و این تفاوت باید در بازبینی هم‌راستا شود. + +> **نکتهٔ D6:** حذف فیزیکی ردیف مجاز نیست (`NFR-05`). بستن گزارش با `status_code = 'CLOSED'` و مقدارگرفتن `closed_at` انجام می‌شود. + +### ۵.۳. `report_supplementary_info` — اطلاعات تکمیلی + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | — | — | +| `report_id` | `bigint` | خیر | — | FK → `fault_reports.id` | گزارش مرتبط | `FR-04`، `US-04` | +| `info_type` | `varchar(20)` | خیر | — | `CHECK (info_type IN ('TEXT','IMAGE'))` | نوع محتوا | `FR-04` | +| `content_text` | `text` | بله | NULL | `CHECK (info_type <> 'TEXT' OR content_text IS NOT NULL)` | متن توضیح | `FR-04` | +| `attachment_ref` | `varchar(500)` | بله | NULL | `CHECK (info_type <> 'IMAGE' OR attachment_ref IS NOT NULL)` | مسیر/شناسهٔ فایل تصویر | `FR-04` | +| `provided_by_id` | `bigint` | خیر | — | FK → `users.id` | ارائه‌دهنده | `US-04` | +| `created_at` | `timestamptz` | خیر | `now()` | — | زمان ارائه | — | + +> **پرسش باز ۶:** `US-04` فقط گزارش‌دهنده را ارائه‌دهنده می‌داند، اما `AD-04` نشان می‌دهد تعمیرکار نیز می‌تواند درخواست اطلاعات دهد. اینکه چه کسی **مجاز به ارائه** است در مستندات صریح نیست؛ مدل فعلی هر کاربری را می‌پذیرد و کنترل آن به لایهٔ اپلیکیشن واگذار شده است. + +### ۵.۴. `report_info_requests` — درخواست اطلاعات تکمیلی + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | — | — | +| `report_id` | `bigint` | خیر | — | FK → `fault_reports.id` | گزارش مرتبط | `FR-13`، `US-14` | +| `requested_by_id` | `bigint` | خیر | — | FK → `users.id` | درخواست‌دهنده | `US-14`، `AD-04` | +| `description` | `text` | خیر | — | `CHECK (btrim(description) <> '')` | متن درخواست | `US-14` | +| `requested_at` | `timestamptz` | خیر | `now()` | — | زمان درخواست | — | +| `responded_at` | `timestamptz` | بله | NULL | — | زمان پاسخ | `AD-04` | +| `response_info_id` | `bigint` | بله | NULL | FK → `report_supplementary_info.id`، UNIQUE | پاسخ مرتبط | `AD-04` | + +> **پرسش باز ۶:** اینکه چه کسانی مجاز به **درخواست** هستند (فقط تعمیرکار یا کارمند تأسیسات هم) در مستندات مشخص نیست. ستون `requested_by_id` به `users` ارجاع می‌دهد و نقش در `report_history` ثبت می‌شود. + +### ۵.۵. `report_assignments` — تخصیص تعمیرکار (مأموریت) + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | — | — | +| `report_id` | `bigint` | خیر | — | FK → `fault_reports.id` | گزارش | `FR-09`، `US-08` | +| `technician_id` | `bigint` | خیر | — | FK → `users.id` | تعمیرکار | `US-08`، `US-11` | +| `assigned_by_id` | `bigint` | خیر | — | FK → `users.id` | تخصیص‌دهنده (کارمند تأسیسات) | `AD-03` | +| `assigned_at` | `timestamptz` | خیر | `now()` | — | زمان تخصیص | — | +| `status` | `varchar(20)` | خیر | `'ASSIGNED'` | `CHECK (status IN ('ASSIGNED','STARTED','DONE','CANCELLED'))` | وضعیت تخصیص | `US-12`، `US-13` | + +> ⚠️ **پرسش باز ۵:** آیا یک گزارش می‌تواند هم‌زمان چند تخصیص فعال داشته باشد؟ مدل فعلی این را **مجاز** می‌داند (چند ردیف per report). اگر تصمیم نهایی «یک تعمیرکار در هر لحظه» باشد، باید یک **ایندکس یگانهٔ جزئی** اضافه شود: +> `CREATE UNIQUE INDEX ... ON report_assignments (report_id) WHERE status IN ('ASSIGNED','STARTED');` + +### ۵.۶. `repair_results` — نتایج تعمیر + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | — | — | +| `report_id` | `bigint` | خیر | — | FK → `fault_reports.id` | گزارش | `FR-12`، `US-13` | +| `technician_id` | `bigint` | خیر | — | FK → `users.id` | ثبت‌کننده | `US-13` | +| `description` | `text` | خیر | — | `CHECK (btrim(description) <> '')` | توضیحات اقدامات | `FR-12` | +| `recorded_at` | `timestamptz` | خیر | `now()` | — | زمان ثبت | — | + +> **نکتهٔ D4:** چند نتیجه per گزارش مجاز است تا اگر مسیر «بازگشت پس از رد» (`AD-06`) تأیید شد، چرخهٔ تعمیر تکرارشدنی باشد (پرسش باز ۷). + +### ۵.۷. `report_feedbacks` — بازخورد گزارش‌دهنده (تأیید/رد) + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | — | — | +| `report_id` | `bigint` | خیر | — | FK (ترکیبی) | گزارش | `FR-14`، `US-05` | +| `repair_result_id` | `bigint` | خیر | — | FK → `repair_results.id` | نتیجهٔ تعمیر مورد بررسی | `US-05.feature` | +| `reporter_id` | `bigint` | خیر | — | FK (ترکیبی) | تأییدکننده | `US-05.feature` | +| `is_approved` | `boolean` | خیر | — | — | تأیید (`true`) یا رد (`false`) | `US-05.feature` | +| `rejection_reason` | `text` | بله | NULL | `CHECK` (بخش ۶) | دلیل رد | `US-05.feature` | +| `created_at` | `timestamptz` | خیر | `now()` | — | زمان ثبت | — | + +**محدودیت‌های کلیدی:** + +- `UNIQUE (report_id, repair_result_id)` → مانع ثبت دو بازخورد برای یک نتیجهٔ تعمیر؛ همین، کلیک دوباره روی «تأیید» را در سطح داده بی‌اثر می‌کند (`US-05.feature`). +- `FOREIGN KEY (report_id, reporter_id) REFERENCES fault_reports (id, reporter_id)` → **در سطح داده** تضمین می‌کند فقط گزارش‌دهندهٔ همان گزارش می‌تواند بازخورد بدهد (`US-05.feature`). +- `CHECK (is_approved OR (rejection_reason IS NOT NULL AND btrim(rejection_reason) <> ''))` → دلیل رد اجباری (`US-05.feature`). +- `CHECK (NOT is_approved OR rejection_reason IS NULL)` → در حالت تأیید، دلیل رد نباید مقدار داشته باشد. + +> **محدودیتی که در داده قابل اعمال نیست:** «تأیید فقط در وضعیت `REPAIRED` مجاز است» یک قاعدهٔ **بین‌جدولی** است و باید در لایهٔ اپلیکیشن (داخل یک تراکنش با قفل ردیف گزارش) یا با تریگر پیاده شود. در بخش ۶ توضیح داده شده است. + +### ۵.۸. `report_history` — تاریخچهٔ اقدامات + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | — | — | +| `report_id` | `bigint` | خیر | — | FK → `fault_reports.id` | گزارش | `FR-16`، `US-10` | +| `action_code` | `varchar(40)` | خیر | — | `CHECK` (بخش ۶) | نوع اقدام | `FR-16`، `US-05.feature` | +| `actor_id` | `bigint` | بله | NULL | FK → `users.id` | انجام‌دهنده (NULL برای اقدام سیستمی) | `FR-16` | +| `details` | `jsonb` | بله | NULL | — | جزئیات (مثلاً دلیل رد) | `US-05.feature` | +| `occurred_at` | `timestamptz` | خیر | `now()` | — | زمان وقوع | `US-10` | + +**مقادیر مجاز `action_code`:** `REPORT_SUBMITTED`، `SUPPLEMENTARY_INFO_ADDED`، `INFO_REQUESTED`، `INFO_PROVIDED`، `PRIORITY_ASSIGNED`، `TECHNICIAN_ASSIGNED`، `REPAIR_STARTED`، `REPAIR_RESULT_RECORDED`، `REPAIR_APPROVED`، `REPAIR_REJECTED`، `REPORT_CLOSED`. + +> **نکته:** این جدول **append-only** است. `UPDATE` و `DELETE` روی آن مجاز نیست و باید با مجوزهای پایگاه‌داده یا تریگر مسدود شود (`NFR-05`). + +### ۵.۹. `notifications` — اعلان‌ها + +| ستون | نوع | Null | پیش‌فرض | محدودیت | توضیح | مرجع | +|---|---|---|---|---|---|---| +| `id` | `bigserial` | خیر | — | PK | — | — | +| `recipient_id` | `bigint` | خیر | — | FK → `users.id` | گیرنده | `FR-15`، `US-16` | +| `report_id` | `bigint` | بله | NULL | FK → `fault_reports.id` | گزارش مرتبط | `US-16` | +| `event_code` | `varchar(40)` | خیر | — | `CHECK` | رویداد مبدأ | `FR-15` | +| `channel_code` | `varchar(20)` | خیر | — | FK → `notification_channels.code` | کانال ارسال | **پرسش باز ۸** | +| `is_read` | `boolean` | خیر | `false` | — | خوانده‌شده | — | +| `created_at` | `timestamptz` | خیر | `now()` | — | زمان ایجاد | — | +| `read_at` | `timestamptz` | بله | NULL | `CHECK ((is_read) = (read_at IS NOT NULL))` | زمان خواندن | — | + +> **مقادیر مجاز `event_code`:** همان مقادیر `action_code` در `report_history` (بخش ۵.۸) به‌اضافهٔ `LOGIN_FAILED` در صورت نیاز. + +## ۶. کلیدها و محدودیت‌ها — خلاصه + +### ۶.۱. کلیدهای اصلی و یکتا + +| جدول | PK | UK | +|---|---|---| +| `roles` | `code` | — | +| `report_statuses` | `code` | — | +| `priorities` | `code` | — | +| `equipment_types` | `code` | — | +| `notification_channels` | `code` | — | +| `users` | `id` | `username` | +| `fault_reports` | `id` | `report_no`، `(id, reporter_id)` | +| `report_supplementary_info` | `id` | — | +| `report_info_requests` | `id` | `response_info_id` | +| `report_assignments` | `id` | — | +| `repair_results` | `id` | — | +| `report_feedbacks` | `id` | `(report_id, repair_result_id)` | +| `report_history` | `id` | — | +| `notifications` | `id` | — | + +### ۶.۲. محدودیت‌های `CHECK` تأییدشده + +| قاعده | جدول | مستند مرجع | +|---|---|---| +| عنوان بین ۱ و ۱۵۰ کاراکتر | `fault_reports` | `US-01.feature` | +| عنوان، توضیحات، محل، نوع تجهیز خالی نباشند | `fault_reports` | `US-01.feature` | +| وضعیت اولیه `SUBMITTED` | `fault_reports` (DEFAULT) | `US-01.feature` | +| دلیل رد هنگام رد اجباری باشد | `report_feedbacks` | `US-05.feature` | +| دلیل رد هنگام تأیید خالی باشد | `report_feedbacks` | `US-05.feature` | +| تأییدکننده همان گزارش‌دهنده باشد | `report_feedbacks` (FK ترکیبی) | `US-05.feature` | +| یک بازخورد per نتیجهٔ تعمیر | `report_feedbacks` (UNIQUE) | `US-05.feature` | +| `read_at` با `is_read` هم‌خوان باشد | `notifications` | — | + +### ۶.۳. قواعدی که در داده قابل اعمال نیستند (لایهٔ اپلیکیشن یا تریگر) + +| قاعده | دلیل | مستند مرجع | +|---|---|---| +| تأیید فقط در وضعیت `REPAIRED` | قاعدهٔ بین‌جدولی | `US-05.feature` | +| تغییر وضعیت فقط از طریق انتقال‌های مجاز | وابسته به پرسش باز ۴ | `activity.md` | +| `closed_at` هنگام `CLOSED` پر باشد | قاعدهٔ بین‌ستونیِ وابسته به جدول مرجع | `US-05.feature` | +| دسترسی مبتنی بر نقش | قاعدهٔ لایهٔ اپلیکیشن | `FR-02`، `US-15.feature` | +| پیام خطای ورود مبهم باشد | قاعدهٔ لایهٔ اپلیکیشن | `US-15.feature` | + +## ۷. ایندکس‌ها + +| جدول | ایندکس | نوع | دلیل | +|---|---|---|---| +| `users` | `(username)` | UNIQUE | ورود سریع، `US-15` | +| `fault_reports` | `(report_no)` | UNIQUE | جست‌وجو با شناسهٔ نمایشی | +| `fault_reports` | `(reporter_id, created_at DESC)` | عادی | «گزارش‌های من»، `FR-05`، `US-03` | +| `fault_reports` | `(status_code, priority_code, created_at DESC)` | عادی | فهرست و فیلتر گزارش‌های باز، `FR-08`، `US-06` | +| `fault_reports` | `(report_id)` روی `(status_code)` WHERE `status_code <> 'CLOSED'` | جزئی | کوئری «گزارش‌های باز» و داشبورد، `FR-17` | +| `report_assignments` | `(technician_id, status)` | عادی | «مأموریت‌های من»، `FR-10`، `US-11` | +| `report_assignments` | `(report_id)` WHERE `status IN ('ASSIGNED','STARTED')` | جزئی (اختیاری) | فقط اگر پاسخ پرسش باز ۵ «یک تعمیرکار هم‌زمان» باشد | +| `report_feedbacks` | `(report_id, repair_result_id)` | UNIQUE | ایدمپوتنسی تأیید، `US-05.feature` | +| `report_history` | `(report_id, occurred_at DESC)` | عادی | نمایش تاریخچه، `FR-16`، `US-10` | +| `notifications` | `(recipient_id, is_read, created_at DESC)` | عادی | فهرست اعلان‌های خوانده‌نشده | +| `report_supplementary_info` | `(report_id, created_at)` | عادی | نمایش اطلاعات تکمیلی | + +## ۸. ماشین حالت در داده + +جدول `report_status_transitions` (اختیاری) ماشین حالت را از کد به **داده** منتقل می‌کند و مستقیماً به پرسش باز ۴ پاسخ می‌دهد: + +| from_status_code | to_status_code | action_code | وضعیت | +|---|---|---|---| +| `SUBMITTED` | `ASSIGNED` | `TECHNICIAN_ASSIGNED` | ✅ تأییدشده (`AD-03`، `FR-09`) | +| `ASSIGNED` | `IN_REPAIR` | `REPAIR_STARTED` | ✅ تأییدشده (`AD-04`، `US-12`) | +| `IN_REPAIR` | `REPAIRED` | `REPAIR_RESULT_RECORDED` | ✅ تأییدشده (`AD-04`، `US-13`) | +| `REPAIRED` | `CLOSED` | `REPAIR_APPROVED` | ✅ تأییدشده (`AD-05`، `US-05.feature`) | +| `REPAIRED` | ؟ | `REPAIR_REJECTED` | ❓ **پرسش باز ۷** | +| `IN_REPAIR` | ؟ (انتظار اطلاعات) | `INFO_REQUESTED` | ❓ **پرسش باز ۶** | + +مزیت: انتقال‌های تأییدشده همین حالا قابل seed هستند و انتقال‌های نامشخص بعداً **فقط با درج ردیف** اضافه می‌شوند، بدون تغییر کد. + +## ۹. یادداشت‌های نرمال‌سازی و نگهداشت + +- **فرم نرمال سوم (3NF):** همهٔ جداول در 3NF هستند؛ نام‌های تکراری (وضعیت، اولویت، نوع تجهیز، نقش، کانال) به جداول مرجع منتقل شده‌اند. +- **عدم تکرار داده:** وضعیت جاری **فقط** در `fault_reports.status_code` نگهداری می‌شود و در `report_history` به‌عنوان رویداد ثبت می‌شود؛ وضعیت در دو جا ذخیره نمی‌شود. +- **`report_history` به‌عنوان audit trail:** این جدول عمداً denormalize نشده و جزئیات در `jsonb` نگهداری می‌شود تا افزودن فیلدهای آینده بدون مهاجرت ممکن باشد. +- **نگهداشت (`NFR-05`):** حذف فیزیکی گزارش و تاریخچه ممنوع است. سیاست دقیق نگهداشت و آرشیو باید تعیین شود (پرسش باز ۱۲). +- **قفل خوش‌بینانه:** `version` روی `fault_reports` و `users` برای جلوگیری از بازنویسی هم‌زمان. هنگام `UPDATE` باید `WHERE version = ?` اعمال و `version = version + 1` شود. +- **منطقهٔ زمانی:** همهٔ زمان‌ها `timestamptz` (با منطقهٔ زمانی) هستند، نه `timestamp` ساده. + +## ۱۰. ردیابی به مستندات + +| عنصر داده | مستند مرجع | +|---|---| +| `users`، `roles` | `FR-01`، `FR-02`، `US-15`، `AD-01` | +| `fault_reports` | `FR-03` تا `FR-05`، `FR-11`، `FR-14` تا `FR-16`، `US-01` تا `US-05`، `US-09`، `US-10`، `AD-02` تا `AD-06` | +| `report_supplementary_info` | `FR-04`، `US-04`، `AD-02`، `AD-04` | +| `report_info_requests` | `FR-13`، `US-14`، `AD-04` | +| `report_assignments` | `FR-09`، `US-08`، `US-11`، `AD-03`، `AD-04` | +| `repair_results` | `FR-12`، `US-13`، `AD-04` | +| `report_feedbacks` | `FR-14`، `US-05`، `AD-05`، `US-05.feature` | +| `report_history` | `FR-16`، `US-03`، `US-05`، `US-10`، `AD-03` | +| `notifications` | `FR-15`، `US-16`، `AD-04` | +| `report_statuses` | `US-01.feature`، `US-05.feature`، `US-12`، `AD-03` تا `AD-05` | +| `priorities` | `FR-07`، `US-07`، `AD-03` (سطوح: پرسش باز ۳) | +| `equipment_types` | `FR-03`، `US-01.feature`، `README.md` | +| محدودیت ۱۵۰ کاراکتر و فیلدهای اجباری | `US-01.feature` | +| محدودیت‌های تأیید/رد | `US-05.feature` | +| یکتایی نام کاربری و هش رمز | `FR-01`، `US-15.feature`، `NFR-01` | + +## ۱۱. پیوست — DDL مرجع (PostgreSQL) + +> ⚠️ این پیوست **غیرالزام‌آور** است و فقط برای نشان‌دادن نگاشت مدل منطقی به یک پیاده‌سازی مشخص آورده شده. اگر تیم DBMS دیگری انتخاب کند (مثلاً MySQL)، این DDL باید معادل‌سازی شود و بخش‌های `jsonb`، `bigserial`، `timestamptz` و ایندکس‌های جزئی نیاز به تطبیق دارند. + +```sql +-- ========================================================= +-- مرجع DDL – PostgreSQL +-- ========================================================= + +-- ۱) جداول مرجع +CREATE TABLE roles ( + code varchar(30) PRIMARY KEY, + label_fa varchar(60) NOT NULL, + sort_order smallint NOT NULL DEFAULT 0, + is_active boolean NOT NULL DEFAULT true +); + +CREATE TABLE report_statuses ( + code varchar(30) PRIMARY KEY, + label_fa varchar(60) NOT NULL, + sort_order smallint NOT NULL DEFAULT 0, + is_active boolean NOT NULL DEFAULT true +); + +CREATE TABLE priorities ( + code varchar(30) PRIMARY KEY, + label_fa varchar(60) NOT NULL, + sort_order smallint NOT NULL DEFAULT 0, + is_active boolean NOT NULL DEFAULT true +); + +CREATE TABLE equipment_types ( + code varchar(30) PRIMARY KEY, + label_fa varchar(60) NOT NULL, + sort_order smallint NOT NULL DEFAULT 0, + is_active boolean NOT NULL DEFAULT true +); + +CREATE TABLE notification_channels ( + code varchar(20) PRIMARY KEY, + label_fa varchar(60) NOT NULL, + is_active boolean NOT NULL DEFAULT true +); + +-- ۲) دادهٔ اولیهٔ جداول مرجع +INSERT INTO roles (code, label_fa, sort_order) VALUES + ('STUDENT', 'دانشجو', 10), + ('PROFESSOR', 'استاد', 20), + ('FACILITIES_STAFF', 'کارمند تأسیسات', 30), + ('TECHNICIAN', 'تعمیرکار', 40), + ('MANAGER', 'مدیر', 50); + +INSERT INTO report_statuses (code, label_fa, sort_order) VALUES + ('SUBMITTED', 'ثبت‌شده', 10), + ('ASSIGNED', 'ارجاع‌شده', 20), + ('IN_REPAIR', 'در حال تعمیر', 30), + ('REPAIRED', 'تعمیر انجام‌شده', 40), + ('CLOSED', 'بسته‌شده', 50); + +-- سطوح اولویت موقت هستند (پرسش باز ۳) +INSERT INTO priorities (code, label_fa, sort_order) VALUES + ('LOW', 'کم', 10), + ('MEDIUM', 'متوسط', 20), + ('HIGH', 'زیاد', 30), + ('URGENT', 'فوری', 40); + +INSERT INTO equipment_types (code, label_fa, sort_order) VALUES + ('LIGHTING', 'روشنایی', 10), + ('HVAC', 'تهویه', 20), + ('ELEVATOR', 'آسانسور', 30), + ('ELECTRICAL', 'برق', 40), + ('PROJECTOR', 'پروژکتور', 50), + ('OTHER', 'سایر', 60); + +-- کانال‌ها تأییدنشده‌اند (پرسش باز ۸) +INSERT INTO notification_channels (code, label_fa) VALUES + ('IN_APP', 'داخل سامانه'), + ('SMS', 'پیامک'), + ('EMAIL', 'ایمیل'); + +-- ۳) کاربران +CREATE TABLE users ( + id bigserial PRIMARY KEY, + username varchar(50) NOT NULL, + password_hash varchar(255) NOT NULL, + role_code varchar(30) NOT NULL REFERENCES roles(code), + display_name varchar(100), + is_active boolean NOT NULL DEFAULT true, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + version integer NOT NULL DEFAULT 1, + CONSTRAINT uq_users_username UNIQUE (username), + CONSTRAINT ck_users_username_not_blank CHECK (btrim(username) <> '') +); + +-- ۴) گزارش خرابی +CREATE TABLE fault_reports ( + id bigserial PRIMARY KEY, + report_no varchar(20) NOT NULL, + title varchar(150) NOT NULL, + description text NOT NULL, + location_text varchar(255) NOT NULL, + equipment_type_code varchar(30) NOT NULL REFERENCES equipment_types(code), + status_code varchar(30) NOT NULL DEFAULT 'SUBMITTED' + REFERENCES report_statuses(code), + priority_code varchar(30) REFERENCES priorities(code), + reporter_id bigint NOT NULL REFERENCES users(id), + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + closed_at timestamptz, + version integer NOT NULL DEFAULT 1, + CONSTRAINT uq_fault_reports_no UNIQUE (report_no), + CONSTRAINT uq_fault_reports_id_reporter UNIQUE (id, reporter_id), + CONSTRAINT ck_fault_reports_title CHECK (char_length(title) BETWEEN 1 AND 150), + CONSTRAINT ck_fault_reports_description CHECK (btrim(description) <> ''), + CONSTRAINT ck_fault_reports_location CHECK (btrim(location_text) <> '') +); + +-- ۵) اطلاعات تکمیلی +CREATE TABLE report_supplementary_info ( + id bigserial PRIMARY KEY, + report_id bigint NOT NULL REFERENCES fault_reports(id), + info_type varchar(20) NOT NULL, + content_text text, + attachment_ref varchar(500), + provided_by_id bigint NOT NULL REFERENCES users(id), + created_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT ck_supp_info_type CHECK (info_type IN ('TEXT','IMAGE')), + CONSTRAINT ck_supp_info_content CHECK ( + (info_type = 'TEXT' AND content_text IS NOT NULL) OR + (info_type = 'IMAGE' AND attachment_ref IS NOT NULL) + ) +); + +-- ۶) درخواست اطلاعات +CREATE TABLE report_info_requests ( + id bigserial PRIMARY KEY, + report_id bigint NOT NULL REFERENCES fault_reports(id), + requested_by_id bigint NOT NULL REFERENCES users(id), + description text NOT NULL, + requested_at timestamptz NOT NULL DEFAULT now(), + responded_at timestamptz, + response_info_id bigint UNIQUE REFERENCES report_supplementary_info(id), + CONSTRAINT ck_info_request_description CHECK (btrim(description) <> '') +); + +-- ۷) تخصیص تعمیرکار +CREATE TABLE report_assignments ( + id bigserial PRIMARY KEY, + report_id bigint NOT NULL REFERENCES fault_reports(id), + technician_id bigint NOT NULL REFERENCES users(id), + assigned_by_id bigint NOT NULL REFERENCES users(id), + assigned_at timestamptz NOT NULL DEFAULT now(), + status varchar(20) NOT NULL DEFAULT 'ASSIGNED', + CONSTRAINT ck_assignment_status + CHECK (status IN ('ASSIGNED','STARTED','DONE','CANCELLED')) +); + +-- ۸) نتیجهٔ تعمیر +CREATE TABLE repair_results ( + id bigserial PRIMARY KEY, + report_id bigint NOT NULL REFERENCES fault_reports(id), + technician_id bigint NOT NULL REFERENCES users(id), + description text NOT NULL, + recorded_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT ck_repair_result_description CHECK (btrim(description) <> '') +); + +-- ۹) بازخورد گزارش‌دهنده +CREATE TABLE report_feedbacks ( + id bigserial PRIMARY KEY, + report_id bigint NOT NULL, + repair_result_id bigint NOT NULL REFERENCES repair_results(id), + reporter_id bigint NOT NULL, + is_approved boolean NOT NULL, + rejection_reason text, + created_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT uq_feedback_per_result UNIQUE (report_id, repair_result_id), + CONSTRAINT fk_feedback_reporter + FOREIGN KEY (report_id, reporter_id) + REFERENCES fault_reports (id, reporter_id), + CONSTRAINT ck_feedback_rejection_reason_required + CHECK (is_approved OR (rejection_reason IS NOT NULL + AND btrim(rejection_reason) <> '')), + CONSTRAINT ck_feedback_rejection_reason_absent + CHECK (NOT is_approved OR rejection_reason IS NULL) +); + +-- ۱۰) تاریخچه (append-only) +CREATE TABLE report_history ( + id bigserial PRIMARY KEY, + report_id bigint NOT NULL REFERENCES fault_reports(id), + action_code varchar(40) NOT NULL, + actor_id bigint REFERENCES users(id), + details jsonb, + occurred_at timestamptz NOT NULL DEFAULT now(), + CONSTRAINT ck_history_action CHECK (action_code IN ( + 'REPORT_SUBMITTED','SUPPLEMENTARY_INFO_ADDED','INFO_REQUESTED', + 'INFO_PROVIDED','PRIORITY_ASSIGNED','TECHNICIAN_ASSIGNED', + 'REPAIR_STARTED','REPAIR_RESULT_RECORDED','REPAIR_APPROVED', + 'REPAIR_REJECTED','REPORT_CLOSED' + )) +); + +-- جلوگیری از تغییر تاریخچه (NFR-05) +CREATE RULE report_history_no_update AS + ON UPDATE TO report_history DO INSTEAD NOTHING; +CREATE RULE report_history_no_delete AS + ON DELETE TO report_history DO INSTEAD NOTHING; + +-- ۱۱) اعلان‌ها +CREATE TABLE notifications ( + id bigserial PRIMARY KEY, + recipient_id bigint NOT NULL REFERENCES users(id), + report_id bigint REFERENCES fault_reports(id), + event_code varchar(40) NOT NULL, + channel_code varchar(20) NOT NULL REFERENCES notification_channels(code), + is_read boolean NOT NULL DEFAULT false, + created_at timestamptz NOT NULL DEFAULT now(), + read_at timestamptz, + CONSTRAINT ck_notifications_read_consistency + CHECK (is_read = (read_at IS NOT NULL)) +); + +-- ۱۲) ماشین حالت (اختیاری – پاسخ به پرسش باز ۴) +CREATE TABLE report_status_transitions ( + from_status_code varchar(30) NOT NULL REFERENCES report_statuses(code), + to_status_code varchar(30) NOT NULL REFERENCES report_statuses(code), + action_code varchar(40) NOT NULL, + is_enabled boolean NOT NULL DEFAULT true, + PRIMARY KEY (from_status_code, to_status_code, action_code) +); + +-- فقط انتقال‌های تأییدشده seed می‌شوند؛ +-- انتقال‌های نامشخص (پرسش باز ۶ و ۷) عمداً درج نشده‌اند. +INSERT INTO report_status_transitions + (from_status_code, to_status_code, action_code) VALUES + ('SUBMITTED', 'ASSIGNED', 'TECHNICIAN_ASSIGNED'), + ('ASSIGNED', 'IN_REPAIR', 'REPAIR_STARTED'), + ('IN_REPAIR', 'REPAIRED', 'REPAIR_RESULT_RECORDED'), + ('REPAIRED', 'CLOSED', 'REPAIR_APPROVED'); + +-- ۱۳) ایندکس‌ها +CREATE INDEX ix_fault_reports_reporter + ON fault_reports (reporter_id, created_at DESC); +CREATE INDEX ix_fault_reports_status_priority + ON fault_reports (status_code, priority_code, created_at DESC); +CREATE INDEX ix_fault_reports_open + ON fault_reports (created_at DESC) + WHERE status_code <> 'CLOSED'; +CREATE INDEX ix_assignments_technician + ON report_assignments (technician_id, status); +CREATE INDEX ix_history_report + ON report_history (report_id, occurred_at DESC); +CREATE INDEX ix_notifications_recipient + ON notifications (recipient_id, is_read, created_at DESC); +CREATE INDEX ix_supp_info_report + ON report_supplementary_info (report_id, created_at); + +-- ۱۴) داشبورد – VIEW (نه جدول) +CREATE VIEW v_dashboard_summary AS +SELECT + count(*) FILTER (WHERE status_code <> 'CLOSED') AS open_reports, + count(*) FILTER (WHERE priority_code = 'URGENT' + AND status_code <> 'CLOSED') AS urgent_open_reports, + count(*) AS total_reports +FROM fault_reports; +``` + +## ۱۲. نکات و پرسش‌های باز + +پرسش‌های باز موجود در [`activity.md`](activity.md) که مستقیماً بر مدل داده اثر می‌گذارند، به‌همراه پرسش‌های جدیدی که خود مدل داده ایجاد کرده است: + +### ۱۲.۱. پرسش‌های باز اثرگذار بر مدل داده + +| # | پرسش | اثر بر مدل داده | مرجع | +|---|---|---|---| +| ۳ | سطوح و معیارهای اولویت چیست؟ | مقادیر `priorities` موقت‌اند | `activity.md` | +| ۴ | فهرست رسمی وضعیت‌ها و انتقال‌های مجاز چیست؟ | محتوای `report_statuses` و `report_status_transitions` | `activity.md` | +| ۵ | یک گزارش به یک یا چند تعمیرکار تخصیص می‌یابد؟ | افزودن ایندکس یگانهٔ جزئی در صورت «یک تعمیرکار» | `activity.md` | +| ۶ | چه کسانی مجاز به درخواست/ارائهٔ اطلاعات هستند؟ | کنترل در لایهٔ اپلیکیشن؛ ستون‌ها فعلاً عمومی‌اند | `activity.md` | +| ۷ | پس از رد تعمیر چه اتفاقی می‌افتد؟ | انتقال جدید در `report_status_transitions`؛ شاید ستون `is_flagged_for_followup` | `activity.md` | +| ۸ | کانال اعلان چیست؟ | مقادیر `notification_channels` تأییدنشده | `activity.md` | +| ۹ | آیا یک کاربر می‌تواند چند نقش داشته باشد؟ | **تعیین‌کننده:** ستون `role_code` یا جدول `user_roles` | `activity.md` | +| ۱۰ | روش احراز هویت و بازیابی رمز چیست؟ | ممکن است جدول `password_reset_tokens` و `login_attempts` لازم شود | `activity.md` | + +### ۱۲.۲. پرسش‌های باز جدید (ایجادشده توسط مدل داده) + +| # | پرسش | پیشنهاد پیش‌فرض | +|---|---|---| +| ۱۱ | فرمت و روش تولید `report_no` چیست؟ (توالی، تصادفی، شامل سال؟) | `R-` + توالی یکتا؛ الگوی دقیق باید تعیین شود | +| ۱۲ | آیا حذف منطقی گزارش لازم است یا بستن (`CLOSED`) کافی است؟ | فعلاً بستن کافی است (`NFR-05`) | +| ۱۳ | آیا ثبت تلاش‌های ورود ناموفق برای audit لازم است؟ | در مستندات الزام نشده؛ در صورت نیاز جدول `login_attempts` اضافه شود | +| ۱۴ | آیا محل خرابی باید ساختاریافته شود (ساختمان/طبقه/اتاق)؟ | تا تعیین ابهام دامنه، `location_text` کافی است (تصمیم D2) | +| ۱۵ | سیاست نگهداشت و آرشیو گزارش‌های بسته‌شده چیست؟ | باید در `NFR-05` یا سند جداگانه تعیین شود | + +### ۱۲.۳. ناسازگاری‌هایی که باید در بازبینی حل شوند + +1. **`location_text` در برابر `Location` ساختاریافته:** `class-diagram.md` و `domain-model.md` محل را ساختاریافته مدل کرده‌اند، اما `US-01.feature` آن را یک فیلد متنی می‌داند. تصمیم D2 این سند، متن آزاد است. باید یکی از دو سند همراستا شود. +2. **ارث‌بری نقش در برابر ستون `role_code`:** `class-diagram.md` از ارث‌بری استفاده می‌کند، `domain-model.md` نقش را Value Object چندمقداری دیده و این سند یک ستون واحد دارد. پاسخ پرسش باز ۹ باید **هر سه سند را هم‌زمان** به‌روزرسانی کند. +3. **`Dashboard`:** در `class-diagram.md` یک کلاس است، در این سند یک `VIEW`. این تفاوت عمدی است و باید در `class-diagram.md` با برچسب `Read Model` شفاف‌تر شود. + +--- + +**وضعیت سند:** نسخه اولیه (Draft) – آماده بازبینی \ No newline at end of file diff --git a/docs/analysis/software-architecture.md b/docs/analysis/software-architecture.md new file mode 100644 index 0000000..e31f026 --- /dev/null +++ b/docs/analysis/software-architecture.md @@ -0,0 +1,371 @@ +# معماری نرم‌افزار (Software Architecture) – سامانه مدیریت و گزارش خرابی دانشگاهی + + +**تهیه‌کننده:** ارشیا همتی‌زاده +**بازبین:** رضا آدوسی +**آخرین به‌روزرسانی:** ۱۴۰۵/۰۸/۱۴ + + +--- + +## مقدمه + +این سند معماری نرم‌افزاری سامانهٔ مدیریت و گزارش خرابی دانشگاهی را در سطح **منطقی و مفهومی** تعریف می‌کند. چون پروژه هنوز وارد فاز پیاده‌سازی نشده است (طبق `README.md`)، این سند **تعهد به فناوری خاصی ندارد** و انتخاب‌های فناورانه در بخش «پرسش‌های باز» علامت‌گذاری شده‌اند. + +هدف این سند پاسخ به سه پرسش است: + +1. سامانه از چه **مؤلفه‌هایی** تشکیل می‌شود و مرز مسئولیت هر کدام کجاست؟ +2. این مؤلفه‌ها چگونه با هم **همکاری** می‌کنند تا جریان‌های توصیف‌شده در `activity.md` محقق شوند؟ +3. کدام **نیروهای محرک معماری (Architectural Drivers)** این ساختار را توجیه می‌کنند؟ + +## ۱. نیروهای محرک معماری + +### ۱.۱. نیازمندی‌های غیرکارکردی + +| شناسه | نیازمندی | اثر معماری | +|---|---|---| +| `NFR-01` | امنیت اطلاعات | احراز هویت متمرکز، مجوزدهی سمت سرور، هش رمز، جلوگیری از دسترسی مستقیم به صفحات نقش دیگر | +| `NFR-02` | حفظ حریم خصوصی | فیلتر داده بر اساس نقش در لایهٔ برنامه (نه فقط در رابط کاربری) | +| `NFR-03` | کاربردپذیری | رابط RTL فارسی، اعتبارسنجی درجا، پیام خطای روشن | +| `NFR-04` | زمان پاسخ‌گویی | ایندکس‌گذاری هدفمند (طبق `data-model.md` §۷)، پرهیز از کوئری‌های سنگین در داشبورد | +| `NFR-05` | صحت و نگهداری اطلاعات | تاریخچهٔ append-only، قفل خوش‌بینانه، تراکنش‌های اتمیک | +| `NFR-06` | قابلیت نگهداری و توسعه | معماری لایه‌ای و ماژولار، جداسازی دامنه از زیرساخت | + +### ۱.۲. محدودیت‌ها + +| محدودیت | منبع | اثر | +|---|---|---| +| تیم سه‌نفره، پروژهٔ دانشگاهی | `README.md` | پرهیز از معماری‌های توزیع‌شده و پیچیده | +| هنوز فناوری انتخاب نشده | `README.md` («هنوز کدنویسی و پیاده‌سازی انجام نشده») | معماری باید فناوری‌مستقل بماند | +| فهرست وضعیت‌ها و اولویت‌ها نهایی نیست | پرسش‌های باز ۳ و ۴ در `activity.md` | این‌ها باید **داده** باشند، نه کد | +| دامنهٔ فضاها مشخص نیست | ابهام `README.md` | مدل محل خرابی باید قابل گسترش بماند | + +## ۲. سبک معماری انتخاب‌شده + +**سبک پیشنهادی: مونولیت ماژولار لایه‌ای (Modular Layered Monolith)** + +### ۲.۱. مقایسهٔ گزینه‌ها + +| گزینه | مزیت | عیب | تناسب با این پروژه | +|---|---|---|---| +| **مونولیت لایه‌ای** | سادگی استقرار، دیباگ آسان، مناسب تیم کوچک | مقیاس‌پذیری مستقل ماژول‌ها ندارد | ✅ **انتخاب شد** | +| میکروسرویس | استقلال استقرار، مقیاس‌پذیری انتخابی | هزینهٔ عملیاتی بالا، نیاز به DevOps، پیچیدگی تراکنش توزیع‌شده | ❌ برای تیم سه‌نفره و پروژهٔ درسی توجیه ندارد | +| بدون لایه (اسکریپت‌محور) | سرعت شروع | نقض `NFR-06`، عدم قابلیت نگهداری | ❌ | + +### ۲.۲. توجیه + +- `NFR-06` قابلیت نگهداری را الزام می‌کند؛ لایه‌بندی این را تضمین می‌کند. +- `NFR-05` صحت داده را الزام می‌کند؛ تراکنش در یک پایگاه‌دادهٔ واحد بسیار ساده‌تر از تراکنش توزیع‌شده است. +- مرزهای دامنه (بخش ۳) از `domain-model.md` گرفته شده‌اند تا در آیندهٔ دور اگر لازم شد، جداسازی به میکروسرویس **ممکن** باشد، بدون بازنویسی دامنه. + +## ۳. مرزهای ماژول (Bounded Contexts) + +مرزها از `domain-model.md` §۱ گرفته شده‌اند: + +| ماژول | مسئولیت | مفاهیم اصلی | نیازمندی‌ها | +|---|---|---|---| +| **M1 — هویت و دسترسی** | احراز هویت، تشخیص نقش، مجوزدهی | کاربر، نقش، نشست | `FR-01`، `FR-02`، `US-15` | +| **M2 — گزارش خرابی** | ثبت، پیگیری، اطلاعات تکمیلی، بازخورد گزارش‌دهنده | گزارش، اطلاعات تکمیلی، بازخورد | `FR-03`–`FR-05`، `FR-14`، `US-01`–`US-05` | +| **M3 — مدیریت تعمیر** | بررسی، اولویت‌بندی، تخصیص، ثبت نتیجه | اولویت، تخصیص، نتیجهٔ تعمیر | `FR-06`–`FR-13`، `US-06`–`US-14` | +| **M4 — اطلاع‌رسانی** | واکنش به رویدادهای دامنه و ارسال اعلان | اعلان، کانال | `FR-15`، `US-16` | +| **M5 — نظارت** | داشبورد و آمار تجمیعی | داشبورد (Read Model) | `FR-17`، `US-17` | + +> **نکته:** M2 و M3 در `data-model.md` روی یک جدول مرکزی (`fault_reports`) کار می‌کنند. جداسازی این دو **ماژول منطقی** است، نه فیزیکی. مزیت: هر ماژول فقط متدهای مجاز خودش را روی گزارش فراخوانی می‌کند و قانون «تغییر وضعیت فقط از مسیر مجاز» در یک نقطه کنترل می‌شود. + +## ۴. لایه‌های معماری + +```mermaid +flowchart TB + subgraph P["لایهٔ نمایش (Presentation)"] + P1["رابط وب RTL
SCR-01 تا SCR-12"] + end + subgraph A["لایهٔ برنامه (Application)"] + A1["سرویس‌های کاربردی
Use Cases"] + A2["اعتبارسنجی ورودی"] + A3["کنترل دسترسی"] + end + subgraph D["لایهٔ دامنه (Domain)"] + D1["موجودیت‌ها و Value Objectها"] + D2["سیاست‌های دامنه"] + D3["ماشین حالت گزارش"] + D4["رویدادهای دامنه"] + end + subgraph I["لایهٔ زیرساخت (Infrastructure)"] + I1["دسترسی به داده"] + I2["سرویس احراز هویت"] + I3["ارسال اعلان"] + I4["ذخیره‌سازی فایل"] + end + + P --> A + A --> D + A --> I + D -.->|"بدون وابستگی به زیرساخت"| I + + classDef pres fill:#e3f2fd,stroke:#1565c0 + classDef app fill:#fff3e0,stroke:#ef6c00 + classDef dom fill:#e8f5e9,stroke:#2e7d32 + classDef inf fill:#f3e5f5,stroke:#6a1b9a + class P1 pres + class A1,A2,A3 app + class D1,D2,D3,D4 dom + class I1,I2,I3,I4 inf +``` + +**قاعدهٔ وابستگی:** جهت وابستگی همیشه به سمت **دامنه** است. دامنه به هیچ لایهٔ دیگری وابسته نیست. این قاعده `NFR-06` را تضمین می‌کند و اجازه می‌دهد در آینده پایگاه‌داده یا سرویس احراز هویت عوض شود بدون تغییر منطق کسب‌وکار. + +## ۵. نمای ظرف (Container View) + +```mermaid +flowchart TB + subgraph Client["مرورگر کاربر"] + C1["برنامهٔ وب
(رابط RTL فارسی)"] + end + + subgraph Server["سرور برنامه"] + S1["API / لایهٔ برنامه"] + S2["ماژول‌های M1 تا M5"] + S3["کارگر پس‌زمینه
(پردازش رویدادها)"] + end + + subgraph Data["لایهٔ داده"] + DB[("پایگاه‌دادهٔ رابطه‌ای
مرجع: PostgreSQL")] + FS[("ذخیره‌سازی فایل
تصاویر اطلاعات تکمیلی")] + end + + subgraph External["سرویس‌های بیرونی"] + AUTH["سرویس احراز هویت"] + NOTIF["درگاه اعلان
(درون‌برنامه / پیامک / ایمیل)"] + end + + C1 -->|"HTTPS / JSON"| S1 + S1 --> S2 + S2 --> DB + S2 --> FS + S2 --> AUTH + S2 -.->|"انتشار رویداد"| S3 + S3 --> NOTIF + + classDef ext fill:#fff8e1,stroke:#f9a825,stroke-dasharray: 4 4 + class AUTH,NOTIF ext +``` + +> **توضیح:** پردازش اعلان (M4) به‌صورت **غیرهمگام** از طریق رویداد انجام می‌شود تا `NFR-04` حفظ شود: ثبت گزارش نباید منتظر ارسال پیامک بماند. کانال دقیق اعلان هنوز پرسش باز ۸ است؛ بنابراین `NOTIF` به‌عنوان یک **آداپتور قابل تعویض** مدل شده است. + +## ۶. جریان‌های کلیدی + +### ۶.۱. ورود به سامانه (AD-01، US-15) + +```mermaid +sequenceDiagram + actor U as کاربر + participant UI as رابط کاربری + participant APP as لایهٔ برنامه + participant AUTH as سرویس احراز هویت + participant DB as پایگاه‌داده + + U->>UI: نام کاربری و رمز عبور + UI->>UI: بررسی فیلد خالی + alt فیلد خالی + UI-->>U: «این فیلد الزامی است» + else کامل + UI->>APP: درخواست ورود + APP->>AUTH: اعتبارسنجی + alt سرویس در دسترس نیست + AUTH-->>APP: خطا + APP-->>UI: «خطا در ورود؛ لطفاً بعداً تلاش کنید» + else پاسخ دریافت شد + AUTH-->>APP: نتیجه + alt نامعتبر + APP-->>UI: «نام کاربری یا رمز عبور اشتباه است» + UI-->>U: خطای مبهم (بدون افشای فیلد اشتباه) + else معتبر + APP->>DB: ثبت رویداد ورود + APP-->>UI: نشست + نقش + UI-->>U: داشبورد متناسب با نقش + end + end + end +``` + +**الزامات امنیتی بازتاب‌یافته:** پیام خطا نباید مشخص کند کدام فیلد اشتباه است (`US-15.feature`). این قاعده در لایهٔ برنامه اعمال می‌شود، نه در رابط کاربری. + +### ۶.۲. ثبت گزارش خرابی (AD-02، US-01، US-02) + +```mermaid +sequenceDiagram + actor R as گزارش‌دهنده + participant UI as رابط کاربری + participant APP as لایهٔ برنامه + participant DOM as دامنه + participant DB as پایگاه‌داده + + R->>UI: پر کردن فرم + UI->>UI: اعتبارسنجی سمت کلاینت + Note over UI: اجباری: عنوان، توضیحات، محل، نوع تجهیز
حداکثر عنوان: ۱۵۰ کاراکتر + alt نامعتبر + UI-->>R: نمایش خطا کنار فیلد، حفظ مقادیر + else معتبر + UI->>APP: ارسال + APP->>APP: کنترل دسترسی (نقش مجاز) + APP->>DOM: اعتبارسنجی مجدد + alt نامعتبر + DOM-->>APP: نقض قاعده + APP-->>UI: خطای اعتبارسنجی + UI-->>R: نمایش خطا + else معتبر + APP->>DB: درج گزارش (وضعیت: ثبت‌شده) + APP->>DB: درج رویداد تاریخچه REPORT_SUBMITTED + APP-->>UI: شناسهٔ گزارش + UI-->>R: پیام موفقیت + شناسهٔ یکتا + APP-)APP: انتشار ReportSubmitted + end + end +``` + +**نکتهٔ مهم (الزام `US-01.feature`):** «اعتبارسنجی سمت کلاینت» فقط برای تجربهٔ کاربری است. اعتبارسنجی **معتبر** در لایهٔ برنامه و دامنه انجام می‌شود. اگر درخواست مستقیماً به API ارسال شود، همان قواعد باید اعمال شوند. + +### ۶.۳. تخصیص تعمیرکار (AD-03، US-08) + +```mermaid +sequenceDiagram + actor F as کارمند تأسیسات + participant APP as لایهٔ برنامه + participant DOM as دامنه + participant DB as پایگاه‌داده + participant N as ماژول اطلاع‌رسانی + + F->>APP: انتخاب گزارش + تعمیرکار + APP->>APP: کنترل دسترسی + APP->>DOM: درخواست تخصیص + DOM->>DOM: بررسی وضعیت مجاز + alt وضعیت مجاز نیست + DOM-->>APP: خطا + APP-->>F: پیام خطا + else مجاز + DOM->>DB: درج تخصیص + DOM->>DB: تغییر وضعیت به ارجاع‌شده + DOM->>DB: درج رویداد تاریخچه TECHNICIAN_ASSIGNED + DOM-)N: انتشار TechnicianAssigned + N->>N: ارسال اعلان (غیرهمگام) + APP-->>F: پیام موفقیت + end +``` + +### ۶.۴. تأیید یا رد تعمیر (AD-05، US-05) + +```mermaid +sequenceDiagram + actor R as گزارش‌دهنده + participant APP as لایهٔ برنامه + participant DOM as دامنه + participant DB as پایگاه‌داده + + R->>APP: درخواست تأیید/رد + APP->>APP: کنترل دسترسی + APP->>DB: خواندن گزارش با قفل خوش‌بینانه + APP->>DOM: بررسی قواعد تأیید + DOM->>DOM: ۱) کاربر = گزارش‌دهندهٔ همان گزارش؟
۲) وضعیت = تعمیر انجام‌شده؟
۳) آیا قبلاً بازخورد ثبت شده؟ + alt یکی از شروط نقض شد + DOM-->>APP: رد درخواست + APP-->>R: پیام متناسب + Note over APP,R: «دسترسی مجاز نیست» یا
«این گزارش در وضعیت قابل تأیید نیست» + else مجاز + alt رد بدون دلیل + APP-->>R: «ذکر دلیل رد الزامی است» + else + APP->>DB: درج بازخورد (UNIQUE روی نتیجهٔ تعمیر) + APP->>DB: درج رویداد تاریخچه + alt تأیید + APP->>DB: تغییر وضعیت به بسته‌شده + closed_at + end + APP-->>R: پیام موفقیت + end + end +``` + +**چرا قفل خوش‌بینانه؟** الزام ایدمپوتنسی در `US-05.feature`: «کلیک دوباره روی تأیید یک تغییر وضعیت بیشتر ایجاد نمی‌کند». ترکیب `UNIQUE (report_id, repair_result_id)` در `data-model.md` و بررسی `version` تضمین دوگانه می‌دهد. + +## ۷. دغدغه‌های عرضی (Cross-Cutting Concerns) + +| دغدغه | راهکار | لایه | مستند مرجع | +|---|---|---|---| +| **احراز هویت** | سرویس متمرکز؛ در صورت در دسترس نبودن، پیام «خطا در ورود؛ لطفاً بعداً تلاش کنید» و عدم ورود | برنامه | `FR-01`، `US-15.feature` | +| **مجوزدهی** | کنترل سمت سرور در **هر** درخواست؛ هرگز فقط مخفی‌کردن دکمه در رابط کاربری | برنامه | `FR-02`، `US-15.feature` | +| **اعتبارسنجی** | دو لایه: رابط کاربری (تجربهٔ کاربری) و دامنه (اعتبار واقعی) | نمایش + دامنه | `US-01.feature` | +| **ثبت تاریخچه** | هر تغییر معنادار یک ردیف در `report_history`؛ append-only | برنامه + داده | `FR-16`، `NFR-05` | +| **رویدادهای دامنه** | انتشار پس از commit موفق؛ مصرف غیرهمگام | دامنه → اطلاع‌رسانی | `FR-15` | +| **مدیریت خطا** | خطاهای قابل نمایش (اعتبارسنجی/مجوز) در برابر خطاهای فنی؛ در خطای فنی، پیام عمومی و ثبت جزئیات در لاگ | برنامه | `US-01.feature` | +| **زمان** | همهٔ زمان‌ها با منطقهٔ زمانی (`timestamptz`) | داده | `data-model.md` §۹ | +| **تراکنش** | تغییر وضعیت + درج تاریخچه در یک تراکنش | برنامه + داده | `NFR-05` | + +## ۸. تصمیم‌های معماری (ADR) + +| شناسه | تصمیم | جایگزین ردشده | دلیل | +|---|---|---|---| +| **ADR-01** | مونولیت ماژولار | میکروسرویس | تیم سه‌نفره، نیاز به تراکنش اتمیک، `NFR-06` | +| **ADR-02** | معماری لایه‌ای با وابستگی به سمت دامنه | لایه‌بندی بدون قاعده | جلوگیری از نشت منطق کسب‌وکار به رابط کاربری | +| **ADR-03** | مجوزدهی **سمت سرور** در هر درخواست | فقط مخفی‌کردن در رابط کاربری | `FR-02`، `US-15.feature` (سناریوی دسترسی مستقیم به آدرس) | +| **ADR-04** | اعلان بر پایهٔ **رویداد دامنه** و غیرهمگام | فراخوانی مستقیم ارسال اعلان در همان تراکنش | `NFR-04`، جداسازی M4 از M2/M3 | +| **ADR-05** | قفل خوش‌بینانه (`version`) | قفل بدبینانه | ایدمپوتنسی `US-05.feature` بدون بلوکه‌کردن | +| **ADR-06** | فهرست وضعیت‌ها و انتقال‌ها به‌صورت **داده** | `ENUM` یا `switch` در کد | پرسش‌های باز ۳ و ۴ هنوز بسته نشده‌اند | +| **ADR-07** | تاریخچهٔ append-only | لاگ فایل یا `audit` سطح پایگاه‌داده | قابل مشاهده برای کاربر (`FR-16`، `US-10`) | +| **ADR-08** | شناسه‌های فنی انگلیسی، متن رابط فارسی | همه‌چیز فارسی | تعارض با `CONTRIBUTING.md` ندارد؛ آن بند دربارهٔ متن مستندات است | + +## ۹. نمای استقرار (Deployment View) + +```mermaid +flowchart LR + subgraph Browser["دستگاه کاربر"] + B1["مرورگر"] + end + subgraph Host["میزبان (تک‌سرور یا Docker Compose)"] + W1["سرور برنامه"] + D1[("پایگاه‌داده")] + F1[("ذخیره‌سازی فایل")] + end + B1 -->|HTTPS| W1 + W1 --> D1 + W1 --> F1 + + classDef future fill:#f5f5f5,stroke:#9e9e9e,stroke-dasharray: 4 4 +``` + +**فرض:** برای یک پروژهٔ دانشگاهی، استقرار تک‌سروری (یا `docker compose` با سه سرویس) کافی است. جزئیات فناورانه در پرسش‌های باز آمده‌اند. + +## ۱۰. نگاشت نیازمندی‌های غیرکارکردی به راهکار معماری + +| NFR | راهکار معماری | محل تحقق | +|---|---|---| +| `NFR-01` امنیت | احراز هویت متمرکز، مجوزدهی سمت سرور، هش رمز، عدم افشای فیلد اشتباه | `M1`، لایهٔ برنامه | +| `NFR-02` حریم خصوصی | فیلتر نتایج بر اساس نقش در سرویس‌های کاربردی | لایهٔ برنامه | +| `NFR-03` کاربردپذیری | رابط RTL، اعتبارسنجی درجا، پیام‌های دقیق | لایهٔ نمایش (`ui-wireframes.md`) | +| `NFR-04` زمان پاسخ | ایندکس‌های `data-model.md` §۷، اعلان غیرهمگام، داشبورد به‌صورت نمای تجمیعی | داده + `M4` + `M5` | +| `NFR-05` صحت داده | تراکنش اتمیک، تاریخچهٔ append-only، قفل خوش‌بینانه، ممنوعیت حذف فیزیکی | دامنه + داده | +| `NFR-06` نگهداشت | لایه‌بندی، مرز ماژول، جداسازی دامنه از زیرساخت، ADRها | کل معماری | + +## ۱۱. ریسک‌های معماری + +| # | ریسک | اثر | کاهش | +|---|---|---|---| +| R1 | بسته‌نشدن پرسش باز ۹ (چند نقش برای کاربر) | تغییر در M1 و مدل داده و نمودار کلاس | تصمیم‌گیری پیش از پیاده‌سازی؛ در غیر این صورت تغییر سه سند | +| R2 | نهایی‌نشدن فهرست وضعیت‌ها (پرسش ۴) | تغییر ماشین حالت و انتقال‌های مجاز | ADR-06: انتقال‌ها به‌صورت داده | +| R3 | رشد دامنه به خوابگاه و فضاهای اداری | تأثیر بر مدل محل و مجوزها | مدل محل قابل گسترش (`data-model.md` D2) | +| R4 | انتخاب نشدن کانال اعلان (پرسش ۸) | تأخیر در `M4` | آداپتور قابل تعویض (بخش ۵) | +| R5 | انتخاب‌نشدن فناوری | عدم شروع پیاده‌سازی | پرسش باز ۱۶ | + +## ۱۲. نکات و پرسش‌های باز + +| # | پرسش | نیازمند تصمیم | +|---|---|---| +| ۱۶ | فناوری لایهٔ نمایش، برنامه و داده چیست؟ (زبان، فریم‌ورک، DBMS) | تیم — پیش از فاز پیاده‌سازی | +| ۱۷ | سرویس احراز هویت داخلی ساخته شود یا از راهکار آماده استفاده شود؟ | تیم | +| ۱۸ | ذخیره‌سازی تصاویر اطلاعات تکمیلی در فایل‌سیستم یا پایگاه‌داده؟ | تیم | +| ۱۹ | نیاز به API عمومی یا صرفاً مصرف داخلی رابط کاربری؟ | تیم | +| ۲۰ | سطح دسترسی دقیق مدیر (پرسش باز ۹ در `activity.md`) چه اثری بر M1 و M5 دارد؟ | تیم | +| ۲۱ | آیا نیاز به ثبت تلاش‌های ورود ناموفق و قفل حساب وجود دارد؟ (در مستندات نیامده) | تیم | +| ۲۲ | آیا رابط کاربری باید واکنش‌گرا (Responsive) باشد؟ (در `NFR-03` صریح نیست) | تیم | + +--- + +**وضعیت سند:** نسخه اولیه (Draft) – آماده بازبینی \ No newline at end of file diff --git a/docs/analysis/ui-wireframes.md b/docs/analysis/ui-wireframes.md new file mode 100644 index 0000000..607f668 --- /dev/null +++ b/docs/analysis/ui-wireframes.md @@ -0,0 +1,460 @@ +# رابط کاربری و وایرفریم‌ها (UI Wireframes) – سامانه مدیریت و گزارش خرابی دانشگاهی + + +**تهیه‌کننده:** ارشیا همتی‌زاده +**بازبین:** رضا آدوسی +**آخرین به‌روزرسانی:** ۱۴۰۵/۰۸/۱۴ + + +--- + +## مقدمه + +این سند فهرست صفحات، نقشهٔ ناوبری و طرح اولیهٔ چیدمان (Wireframe) رابط کاربری سامانه را ارائه می‌دهد. هدف، تبدیل داستان‌های کاربر و جریان‌های `activity.md` به صفحات مشخص است، **پیش از** انتخاب فناوری و شروع کدنویسی. + +> **نکتهٔ نمایش:** متن نهایی سامانه فارسی و راست‌به‌چپ (RTL) است. طرح‌های این سند با کاراکترهای ساده رسم شده‌اند و **جهت چیدمان آن‌ها نمایندهٔ جهت نهایی نیست**. در پیاده‌سازی، ستون «منو/اقدام» در سمت راست و ناحیهٔ محتوا در سمت چپ قرار می‌گیرد. + +## ۱. اصول طراحی + +| # | اصل | توضیح | مستند مرجع | +|---|---|---|---| +| P1 | **راست‌به‌چپ و فارسی** | همهٔ برچسب‌ها، پیام‌ها و تاریخ‌ها فارسی؛ تاریخ شمسی با ارقام فارسی | `NFR-03`، `CONTRIBUTING.md` §۷ | +| P2 | **ناوبری نقش‌محور** | هر کاربر فقط صفحات نقش خود را در منو می‌بیند | `FR-02`، `US-15` | +| P3 | **اعتبارسنجی درجا** | خطا کنار همان فیلد نمایش داده می‌شود، نه در بالای صفحه | `US-01.feature` | +| P4 | **حفظ ورودی در خطا** | در خطای اعتبارسنجی یا خطای سرور، مقادیر فرم پاک نمی‌شوند | `US-01.feature` | +| P5 | **افشا نکردن علت خطای ورود** | پیام ورود مبهم است و فیلد اشتباه را مشخص نمی‌کند | `US-15.feature` | +| P6 | **پنهان‌نکردن قابلیت به‌جای کنترل دسترسی** | مجوزدهی سمت سرور؛ رابط کاربری فقط بازتاب آن است | `ADR-03` در `software-architecture.md` | +| P7 | **بازخورد صریح وضعیت** | هر اقدام موفق یا ناموفق پیام روشن دارد | `NFR-03` | +| P8 | **نمایش وضعیت و اولویت به‌صورت بصری** | بج رنگی برای وضعیت و اولویت در همهٔ فهرست‌ها | `FR-08`، `US-06` | + +## ۲. فهرست صفحات + +| شناسه | صفحه | نقش‌های مجاز | داستان‌های کاربر | جریان فعالیت | +|---|---|---|---|---| +| `SCR-01` | ورود به سامانه | همه | `US-15` | `AD-01` | +| `SCR-02` | گزارش‌های من | دانشجو، استاد | `US-03` | `AD-02` | +| `SCR-03` | ثبت گزارش خرابی | دانشجو، استاد | `US-01`، `US-02` | `AD-02` | +| `SCR-04` | جزئیات گزارش (نمای گزارش‌دهنده) | دانشجو، استاد | `US-03`، `US-04`، `US-05` | `AD-02`، `AD-05` | +| `SCR-05` | فهرست گزارش‌های باز | کارمند تأسیسات | `US-06` | `AD-03` | +| `SCR-06` | جزئیات گزارش (نمای تأسیسات) | کارمند تأسیسات | `US-06`–`US-10` | `AD-03` | +| `SCR-07` | مأموریت‌های من | تعمیرکار | `US-11` | `AD-04` | +| `SCR-08` | جزئیات مأموریت | تعمیرکار | `US-12`، `US-13`، `US-14` | `AD-04` | +| `SCR-09` | داشبورد مدیریتی | کارمند تأسیسات، مدیر | `US-17` | `AD-03` | +| `SCR-10` | اعلان‌ها | همه | `US-16` | — | +| `SCR-11` | خطای دسترسی | همه | `US-15` | `AD-01` | +| `SCR-12` | مودال تأیید/رد تعمیر | دانشجو، استاد | `US-05` | `AD-05` | + +## ۳. نقشهٔ ناوبری + +```mermaid +flowchart TD + LOGIN["SCR-01
ورود"] --> ROLE{"نقش کاربر"} + + ROLE -->|"دانشجو / استاد"| R_HOME["SCR-02
گزارش‌های من"] + R_HOME --> R_NEW["SCR-03
ثبت گزارش"] + R_HOME --> R_DETAIL["SCR-04
جزئیات گزارش"] + R_DETAIL --> R_MODAL["SCR-12
تأیید یا رد تعمیر"] + + ROLE -->|"کارمند تأسیسات"| F_HOME["SCR-05
گزارش‌های باز"] + F_HOME --> F_DETAIL["SCR-06
جزئیات گزارش"] + F_HOME --> F_DASH["SCR-09
داشبورد"] + + ROLE -->|"تعمیرکار"| T_HOME["SCR-07
مأموریت‌های من"] + T_HOME --> T_DETAIL["SCR-08
جزئیات مأموریت"] + + ROLE -->|"مدیر"| M_DASH["SCR-09
داشبورد"] + + R_HOME -.-> NOTIF["SCR-10
اعلان‌ها"] + F_DETAIL -.-> NOTIF + T_DETAIL -.-> NOTIF + + R_HOME -.->|"دسترسی غیرمجاز"| ERR["SCR-11
خطای دسترسی"] + F_HOME -.-> ERR + T_HOME -.-> ERR +``` + +## ۴. وایرفریم‌ها + +در هر طرح، کادرهای شماره‌دار به عناصر مشخص اشاره دارند و جدول زیر آن‌ها مشخصات کامل هر عنصر را می‌دهد. این روش از مشکل نمایش راست‌به‌چپ متن فارسی در بلوک کد جلوگیری می‌کند. + +### ۴.۱. `SCR-01` — ورود به سامانه + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] [۲] │ +│ │ +│ ┌────────────────────────┐ │ +│ │ [۳] │ │ +│ │ │ │ +│ │ [۴] │ │ +│ │ ┌──────────────────┐ │ │ +│ │ │ [۵] │ │ │ +│ │ └──────────────────┘ │ │ +│ │ [۶] │ │ +│ │ ┌──────────────────┐ │ │ +│ │ │ [۷] │ │ │ +│ │ └──────────────────┘ │ │ +│ │ [۸] │ │ +│ │ ┌──────────────────┐ │ │ +│ │ │ [۹] │ │ │ +│ │ └──────────────────┘ │ │ +│ └────────────────────────┘ │ +│ │ +└──────────────────────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | رفتار | مرجع | +|---|---|---|---|---|---| +| ۱ | لوگو و نام سامانه | متن | «سامانه مدیریت و گزارش خرابی دانشگاهی» | — | `README.md` | +| ۲ | فضای خالی / زبان | — | فعلاً فقط فارسی | — | `NFR-03` | +| ۳ | عنوان فرم | تیتر | «ورود به سامانه» | — | `US-15` | +| ۴ | برچسب نام کاربری | متن | «نام کاربری» | — | `FR-01` | +| ۵ | ورودی نام کاربری | فیلد متنی | — | اجباری؛ در صورت خالی‌بودن: «این فیلد الزامی است» کنار فیلد | `US-15.feature` | +| ۶ | برچسب رمز عبور | متن | «رمز عبور» | — | `FR-01` | +| ۷ | ورودی رمز عبور | فیلد رمز | — | اجباری؛ ماسک‌شده | `US-15.feature` | +| ۸ | ناحیهٔ خطا | ناحیهٔ پیام | خالی در حالت عادی | در خطای ورود: «نام کاربری یا رمز عبور اشتباه است» — نام کاربری در فرم می‌ماند | `US-15.feature` | +| ۹ | دکمهٔ ورود | دکمهٔ اصلی | «ورود» | پس از موفقیت: انتقال به داشبورد نقش | `AD-01` | + +**حالت‌های خطا:** + +| حالت | پیام | رفتار | مرجع | +|---|---|---|---| +| فیلد خالی | «این فیلد الزامی است» (کنار همان فیلد) | ورود انجام نمی‌شود | `US-15.feature` | +| اعتبارنامهٔ نامعتبر | «نام کاربری یا رمز عبور اشتباه است» | نام کاربری حفظ می‌شود؛ فیلد اشتباه مشخص نمی‌شود | `US-15.feature` | +| سرویس احراز هویت در دسترس نیست | «خطا در ورود؛ لطفاً بعداً تلاش کنید» | ورود انجام نمی‌شود | `US-15.feature` | + +> **الزام امنیتی:** هیچ پیام خطایی نباید مشخص کند «نام کاربری وجود ندارد» یا «رمز اشتباه است». تنها پیام مشترک مجاز است (P5). + +### ۴.۲. `SCR-03` — ثبت گزارش خرابی + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] [۲] │ +├──────────────┬───────────────────────────────────────────────┤ +│ │ [۳] │ +│ [M1] │ ┌──────────────────────────────────────────┐ │ +│ [M2] │ │ [۴] │ │ +│ [M3] │ │ ┌──────────────────────────────────────┐ │ │ +│ [M4] │ │ │ [۵] │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ [۶] │ │ +│ │ │ ┌──────────────────────────────────────┐ │ │ +│ │ │ │ [۷] │ │ │ +│ │ │ │ │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ [۸] │ │ │ +│ │ │ ┌──────────────────────────────────────┐ │ │ +│ │ │ │ [۹] │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ [۱۰] │ │ │ +│ │ │ ┌──────────────────────────────────────┐ │ │ +│ │ │ │ [۱۱] │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ [۱۲] [۱۳] │ │ +│ │ └──────────────────────────────────────────┘ │ +└──────────────┴───────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | رفتار | مرجع | +|---|---|---|---|---|---| +| ۱ | نوار بالا | ناحیه | نام کاربر + خروج | — | `FR-01` | +| ۲ | زنگ اعلان | آیکون + شمارنده | تعداد خوانده‌نشده | کلیک → `SCR-10` | `US-16` | +| M1–M4 | منوی نقش | فهرست | «گزارش‌های من»، «ثبت گزارش»، «اعلان‌ها»، «خروج» | فقط آیتم‌های مجاز نقش | `FR-02` | +| ۳ | عنوان صفحه | تیتر | «ثبت گزارش خرابی» | — | `US-01`، `US-02` | +| ۴ | برچسب عنوان | متن | «عنوان» + نشان اجباری | — | `FR-03` | +| ۵ | ورودی عنوان | فیلد متنی | — | اجباری؛ حداکثر ۱۵۰ کاراکتر؛ شمارندهٔ کاراکتر | `US-01.feature` | +| ۶ | برچسب توضیحات | متن | «توضیحات» + نشان اجباری | — | `FR-03` | +| ۷ | ورودی توضیحات | ناحیهٔ متنی | — | اجباری | `US-01.feature` | +| ۸ | برچسب محل خرابی | متن | «محل خرابی» + نشان اجباری | — | `FR-03` | +| ۹ | ورودی محل | فیلد متنی | نمونه: «ساختمان الف - کلاس ۱۰۲» | اجباری | `US-01.feature` | +| ۱۰ | برچسب نوع تجهیز | متن | «نوع تجهیز» + نشان اجباری | — | `FR-03` | +| ۱۱ | انتخاب نوع تجهیز | فهرست کشویی | روشنایی، تهویه، آسانسور، برق، پروژکتور، سایر | اجباری | `US-01.feature`، `README.md` | +| ۱۲ | دکمهٔ انصراف | دکمهٔ ثانویه | «انصراف» | بازگشت به `SCR-02` | — | +| ۱۳ | دکمهٔ ثبت | دکمهٔ اصلی | «ثبت گزارش» | ارسال؛ در انتظار: غیرفعال + نشانگر | `AD-02` | + +**حالت‌های نتیجه:** + +| حالت | پیام | رفتار | مرجع | +|---|---|---|---| +| موفقیت | نمایش شناسهٔ یکتا | انتقال به `SCR-04` یا ماندن با پیام موفقیت | `US-01.feature` | +| فیلد اجباری خالی | «این فیلد الزامی است» کنار فیلد | هیچ گزارشی ذخیره نمی‌شود | `US-01.feature` | +| عنوان ۱۵۱ کاراکتر | «حداکثر طول عنوان ۱۵۰ کاراکتر است» | هیچ گزارشی ذخیره نمی‌شود | `US-01.feature` | +| خطای سرور | «خطا در ثبت گزارش؛ لطفاً دوباره تلاش کنید» | **مقادیر فرم حفظ می‌شوند**، هیچ گزارش ناقصی ذخیره نمی‌شود | `US-01.feature` | + +> **الزام P4:** در حالت خطای سرور، پاک‌کردن فرم **ممنوع** است. این رفتار در `US-01.feature` صریحاً الزام شده است. + +### ۴.۳. `SCR-05` — فهرست گزارش‌های باز (کارمند تأسیسات) + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] [۲] │ +├──────────────┬───────────────────────────────────────────────┤ +│ │ [۳] │ +│ [M1] │ [۴] [۵] [۶] [۷] │ +│ [M2] │ ┌──────────────────────────────────────────┐ │ +│ [M3] │ │ [۸] │ [۹] │ [۱۰] │ [۱۱] │ [۱۲] │ [۱۳] │ │ +│ │ ├──────────────────────────────────────────┤ │ +│ │ │ ... │ │ +│ │ │ ... │ │ +│ │ │ ... │ │ +│ │ └──────────────────────────────────────────┘ │ +│ │ [۱۴] │ +└──────────────┴───────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | رفتار | مرجع | +|---|---|---|---|---|---| +| ۱ | نوار بالا | ناحیه | نام کاربر + خروج | — | — | +| ۲ | زنگ اعلان | آیکون | — | → `SCR-10` | `US-16` | +| M1–M4 | منوی نقش | فهرست | «گزارش‌های باز»، «داشبورد»، «اعلان‌ها»، «خروج» | — | `FR-02` | +| ۳ | عنوان صفحه | تیتر | «گزارش‌های باز» | — | `US-06` | +| ۴ | فیلتر وضعیت | کشویی چندگزینه | از `report_statuses` | پیش‌فرض: همه به‌جز «بسته‌شده» | `FR-08`، `US-06` | +| ۵ | فیلتر اولویت | کشویی چندگزینه | از `priorities` | — | `FR-08` | +| ۶ | جست‌وجو | فیلد متنی | جست‌وجو در عنوان و شناسه | — | `FR-08` | +| ۷ | دکمهٔ پاک‌کردن فیلتر | دکمهٔ ثانویه | «پاک‌کردن فیلترها» | بازنشانی | — | +| ۸ | ستون شناسه | ستون جدول | `report_no` | قابل مرتب‌سازی | — | +| ۹ | ستون عنوان | ستون جدول | عنوان گزارش | لینک به `SCR-06` | — | +| ۱۰ | ستون محل | ستون جدول | محل خرابی | — | `FR-03` | +| ۱۱ | ستون اولویت | ستون جدول | بج اولویت | رنگ‌بندی بر اساس سطح | `FR-07` | +| ۱۲ | ستون وضعیت | ستون جدول | بج وضعیت | رنگ‌بندی بر اساس وضعیت | `FR-11` | +| ۱۳ | ستون تاریخ | ستون جدول | تاریخ ثبت (شمسی) | مرتب‌سازی پیش‌فرض: نزولی | — | +| ۱۴ | صفحه‌بندی | ناحیه | تعداد در صفحه | — | `NFR-04` | + +> **تعریف «باز»:** گزارشی که وضعیت آن «بسته‌شده» نیست. «باز» یک وضعیت رسمی نیست (`data-model.md` §۴.۲). + +### ۴.۴. `SCR-06` — جزئیات گزارش (نمای تأسیسات) + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] [۲] │ +├──────────────┬───────────────────────────────────────────────┤ +│ [M1] │ [۳] [۴] [۵] │ +│ [M2] │ ┌──────────────────┬────────────────────────┐ │ +│ [M3] │ │ [۶] │ [۷] │ │ +│ │ │ [۸] │ [۹] │ │ +│ │ │ [۱۰] │ [۱۱] │ │ +│ │ │ [۱۲] │ [۱۳] │ │ +│ │ │ │ [۱۴] │ │ +│ │ │ │ [۱۵] │ │ +│ │ └──────────────────┴────────────────────────┘ │ +│ │ [۱۶] │ +│ │ ┌──────────────────────────────────────────┐ │ +│ │ │ ... │ │ +│ │ └──────────────────────────────────────────┘ │ +└──────────────┴───────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | رفتار | مرجع | +|---|---|---|---|---|---| +| ۳ | عنوان گزارش | تیتر | عنوان + شناسه | — | `FR-03` | +| ۴ | بج وضعیت | بج | وضعیت جاری | — | `FR-11` | +| ۵ | بج اولویت | بج | اولویت (در صورت تعیین) | — | `FR-07` | +| ۶ | توضیحات | متن | توضیحات گزارش‌دهنده | — | `FR-03` | +| ۷ | محل خرابی | متن | متن محل | — | `FR-03` | +| ۸ | نوع تجهیز | متن | برچسب فارسی نوع | — | `FR-03` | +| ۹ | گزارش‌دهنده | متن | نام نمایشی + نقش | — | — | +| ۱۰ | تاریخ ثبت | متن | تاریخ شمسی | — | — | +| ۱۱ | اطلاعات تکمیلی | فهرست | متن/تصویر + ارائه‌دهنده + زمان | — | `FR-04`، `US-04` | +| ۱۲ | نتیجهٔ تعمیر | ناحیه | توضیحات + تعمیرکار + زمان | فقط در صورت وجود | `FR-12` | +| ۱۳ | تعیین اولویت | کشویی + دکمه | «ذخیرهٔ اولویت» | فقط نقش تأسیسات | `FR-07`، `US-07` | +| ۱۴ | تخصیص تعمیرکار | کشویی + دکمه | فهرست تعمیرکاران + «تخصیص» | فقط نقش تأسیسات | `FR-09`، `US-08` | +| ۱۵ | تغییر وضعیت | کشویی + دکمه | فقط انتقال‌های مجاز | گزینه‌های غیرمجاز نمایش داده نمی‌شوند | `FR-11`، `US-09` | +| ۱۶ | تاریخچه | تایم‌لاین | فهرست اقدامات به ترتیب زمانی | — | `FR-16`، `US-10` | + +> **الزام ADR-06:** گزینه‌های کشویی «تغییر وضعیت» باید از جدول انتقال‌های مجاز خوانده شوند، نه از کد. انتقال‌های نامشخص (پرسش‌های باز ۶ و ۷) نباید در رابط کاربری ظاهر شوند تا زمانی که تیم تصمیم بگیرد. + +### ۴.۵. `SCR-07` — مأموریت‌های من (تعمیرکار) + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] [۲] │ +├──────────────┬───────────────────────────────────────────────┤ +│ [M1] │ [۳] │ +│ [M2] │ ┌──────────────────────────────────────────┐ │ +│ │ │ [۴] │ [۵] │ [۶] │ [۷] │ [۸] │ [۹] │ │ +│ │ ├──────────────────────────────────────────┤ │ +│ │ │ ... │ │ +│ │ └──────────────────────────────────────────┘ │ +└──────────────┴───────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | رفتار | مرجع | +|---|---|---|---|---|---| +| ۳ | عنوان صفحه | تیتر | «مأموریت‌های من» | — | `US-11` | +| ۴ | ستون شناسه | ستون | `report_no` | — | — | +| ۵ | ستون عنوان | ستون | عنوان گزارش | لینک به `SCR-08` | — | +| ۶ | ستون محل | ستون | محل خرابی | — | — | +| ۷ | ستون اولویت | ستون | بج اولویت | — | `FR-07` | +| ۸ | ستون وضعیت | ستون | وضعیت تخصیص | — | `US-12`، `US-13` | +| ۹ | ستون زمان تخصیص | ستون | تاریخ شمسی | — | — | + +> **واژه‌شناسی:** «مأموریت» در `US-11` و `AD-04` همان «تخصیص» در `FR-09` است. رابط کاربری از واژهٔ «مأموریت» استفاده می‌کند چون زبان تعمیرکار است. + +### ۴.۶. `SCR-08` — جزئیات مأموریت (تعمیرکار) + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] [۲] │ +├──────────────┬───────────────────────────────────────────────┤ +│ [M1] │ [۳] [۴] │ +│ │ ┌──────────────────────────────────────────┐ │ +│ │ │ [۵] [۶] [۷] │ │ +│ │ └──────────────────────────────────────────┘ │ +│ │ [۸] │ +│ │ ┌──────────────────────────────────────────┐ │ +│ │ │ [۹] │ │ +│ │ └──────────────────────────────────────────┘ │ +│ │ [۱۰] [۱۱] [۱۲] │ +│ │ [۱۳] │ +│ │ ┌──────────────────────────────────────────┐ │ +│ │ │ ... │ │ +│ │ └──────────────────────────────────────────┘ │ +└──────────────┴───────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | رفتار | مرجع | +|---|---|---|---|---|---| +| ۳ | عنوان + شناسه | تیتر | — | — | — | +| ۴ | بج وضعیت | بج | — | — | `FR-11` | +| ۵ | توضیحات خرابی | متن | — | — | `FR-03` | +| ۶ | محل خرابی | متن | — | — | — | +| ۷ | نوع تجهیز | متن | — | — | — | +| ۸ | دکمهٔ «شروع تعمیر» | دکمه | فقط در وضعیت «ارجاع‌شده» | تغییر وضعیت به «در حال تعمیر» | `US-12` | +| ۹ | فرم ثبت نتیجه | ناحیهٔ متنی + دکمه | توضیحات اقدامات | فقط پس از شروع تعمیر | `FR-12`، `US-13` | +| ۱۰ | دکمهٔ «ثبت نتیجه» | دکمهٔ اصلی | — | تغییر وضعیت به «تعمیر انجام‌شده» | `US-13` | +| ۱۱ | دکمهٔ «درخواست اطلاعات» | دکمهٔ ثانویه | — | باز شدن فرم درخواست | `FR-13`، `US-14` | +| ۱۲ | فرم درخواست اطلاعات | ناحیهٔ متنی + دکمه | متن درخواست | ارسال به گزارش‌دهنده | `US-14`، `AD-04` | +| ۱۳ | تاریخچه | تایم‌لاین | — | — | `FR-16` | + +> **پرسش باز ۶:** مکانیزم دقیق «انتظار برای دریافت اطلاعات» در `AD-04` علامت‌گذاری شده است. آیا وضعیت گزارش باید تغییر کند؟ تا تصمیم‌گیری، رابط کاربری فقط نشانگر «در انتظار پاسخ» نمایش می‌دهد و وضعیت گزارش را تغییر نمی‌دهد. + +### ۴.۷. `SCR-09` — داشبورد مدیریتی + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] [۲] │ +├──────────────┬───────────────────────────────────────────────┤ +│ [M1] │ [۳] │ +│ [M2] │ ┌───────────┬───────────┬───────────┐ │ +│ │ │ [۴] │ [۵] │ [۶] │ │ +│ │ └───────────┴───────────┴───────────┘ │ +│ │ [۷] │ +│ │ ┌──────────────────────────────────────────┐ │ +│ │ │ [۸] │ │ +│ │ └──────────────────────────────────────────┘ │ +└──────────────┴───────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | مرجع | +|---|---|---|---|---| +| ۳ | عنوان | تیتر | «داشبورد» | `US-17` | +| ۴ | کارت آمار | کارت | تعداد گزارش‌های باز | `FR-17` | +| ۵ | کارت آمار | کارت | تعداد درخواست‌های فوری | `FR-17`، `US-17` | +| ۶ | کارت آمار | کارت | تعداد کل گزارش‌ها | `FR-17` | +| ۷ | نمودار / فهرست | ناحیه | توزیع گزارش‌ها بر اساس وضعیت | `FR-17` | +| ۸ | فهرست کوتاه | جدول | آخرین گزارش‌های فوری | `US-17` | + +> **تفکیک نقش:** مدیر فقط مشاهده دارد (`FR-17`، `US-17`). کارمند تأسیسات علاوه بر داشبورد به `SCR-05` و `SCR-06` هم دسترسی دارد. سطح دسترسی کامل مدیر پرسش باز ۹ در `activity.md` است. + +### ۴.۸. `SCR-12` — مودال تأیید یا رد تعمیر + +```text +┌──────────────────────────────────────────────────────────────┐ +│ [۱] │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ [۲] │ │ +│ │ [۳] │ │ +│ │ ┌────────────────────────────────────────────────────┐ │ │ +│ │ │ [۴] │ │ │ +│ │ └────────────────────────────────────────────────────┘ │ │ +│ │ [۵] │ │ +│ │ [۶] [۷] │ │ +│ └────────────────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────┘ +``` + +| # | عنصر | نوع | محتوا | رفتار | مرجع | +|---|---|---|---|---|---| +| ۱ | پوشش پس‌زمینه | لایه | — | کلیک بیرون: بستن مودال | — | +| ۲ | عنوان مودال | تیتر | «تأیید یا رد تعمیر» | — | `US-05` | +| ۳ | نتیجهٔ تعمیر | متن | توضیحات ثبت‌شده | — | `FR-12` | +| ۴ | دلیل رد | ناحیهٔ متنی | فقط اگر «رد» انتخاب شود | اجباری در حالت رد | `US-05.feature` | +| ۵ | ناحیهٔ خطا | پیام | — | «ذکر دلیل رد الزامی است» | `US-05.feature` | +| ۶ | دکمهٔ تأیید | دکمهٔ اصلی | «تأیید تکمیل تعمیر» | بستن گزارش | `FR-14` | +| ۷ | دکمهٔ رد | دکمهٔ خطر | «رد تعمیر» | ثبت رد + پیگیری | `US-05.feature` | + +**حالت‌های خطا:** + +| حالت | پیام | رفتار | مرجع | +|---|---|---|---| +| رد بدون دلیل | «ذکر دلیل رد الزامی است» | وضعیت بدون تغییر می‌ماند | `US-05.feature` | +| کاربر غیرمجاز | «دسترسی مجاز نیست» | وضعیت بدون تغییر | `US-05.feature` | +| وضعیت نامناسب | «این گزارش در وضعیت قابل تأیید نیست» | عملیات انجام نمی‌شود | `US-05.feature` | +| کلیک دوباره | — | فقط یک تغییر وضعیت و یک ورودی تاریخچه | `US-05.feature` | + +## ۵. اجزای مشترک (Component Inventory) + +| شناسه | جزء | توضیح | استفاده در | مرجع | +|---|---|---|---|---| +| `CMP-01` | بج وضعیت | نمایش `label_fa` با رنگ متناظر | `SCR-02`، `SCR-04`، `SCR-05`، `SCR-06`، `SCR-07` | `FR-11` | +| `CMP-02` | بج اولویت | نمایش `label_fa` با رنگ متناظر | `SCR-05`، `SCR-06`، `SCR-07` | `FR-07` | +| `CMP-03` | جدول داده | ستون‌بندی، مرتب‌سازی، صفحه‌بندی | `SCR-02`، `SCR-05`، `SCR-07` | `NFR-04` | +| `CMP-04` | فیلد فرم با اعتبارسنجی | برچسب + نشان اجباری + پیام خطای درجا | `SCR-01`، `SCR-03`، `SCR-12` | `US-01.feature` | +| `CMP-05` | تایم‌لاین تاریخچه | فهرست رویدادهای `report_history` | `SCR-04`، `SCR-06`، `SCR-08` | `FR-16` | +| `CMP-06` | آپلود تصویر | انتخاب و پیش‌نمایش تصویر | `SCR-04` | `FR-04` | +| `CMP-07` | زنگ اعلان | شمارندهٔ خوانده‌نشده + لینک به `SCR-10` | همهٔ صفحات داخلی | `US-16` | +| `CMP-08` | نوار پیام | پیام موفقیت/خطای سطح صفحه | همهٔ صفحات | `NFR-03` | +| `CMP-09` | منوی نقش‌محور | آیتم‌های منو بر اساس نقش | همهٔ صفحات داخلی | `FR-02` | +| `CMP-10` | کادر تأیید | برای اقدام‌های برگشت‌ناپذیر (بستن گزارش) | `SCR-12` | `US-05` | + +## ۶. قرارداد رنگی (پیشنهادی) + +> رنگ‌ها **پیشنهادی** هستند و باید با پرسش باز ۲۴ تأیید شوند. + +| وضعیت / اولویت | رنگ پیشنهادی | توضیح | +|---|---|---| +| ثبت‌شده | خاکستری | تازه ثبت شده، هنوز بررسی نشده | +| ارجاع‌شده | آبی | در انتظار شروع تعمیر | +| در حال تعمیر | نارنجی | در جریان | +| تعمیر انجام‌شده | سبز روشن | در انتظار بازخورد گزارش‌دهنده | +| بسته‌شده | سبز تیره | پایان یافته | +| اولویت کم | خاکستری | — | +| اولویت متوسط | آبی | — | +| اولویت زیاد | نارنجی | — | +| اولویت فوری | قرمز | باید در داشبورد برجسته شود (`US-17`) | + +> **الزام دسترس‌پذیری:** رنگ نباید تنها حامل معنا باشد؛ متن برچسب همیشه در کنار رنگ نمایش داده می‌شود. + +## ۷. پیام‌های استاندارد رابط کاربری + +این جدول، **متن دقیق** پیام‌ها را از فایل‌های Feature استخراج کرده تا در پیاده‌سازی، متن تغییر نکند: + +| شناسه | متن پیام | شرایط | مرجع | +|---|---|---|---| +| `MSG-01` | «این فیلد الزامی است» | فیلد اجباری خالی | `US-01.feature`، `US-15.feature` | +| `MSG-02` | «حداکثر طول عنوان ۱۵۰ کاراکتر است» | عنوان بیش از ۱۵۰ کاراکتر | `US-01.feature` | +| `MSG-03` | «خطا در ثبت گزارش؛ لطفاً دوباره تلاش کنید» | خطای سرور در ذخیره | `US-01.feature` | +| `MSG-04` | «نام کاربری یا رمز عبور اشتباه است» | اعتبارنامهٔ نامعتبر | `US-15.feature` | +| `MSG-05` | «خطا در ورود؛ لطفاً بعداً تلاش کنید» | سرویس احراز هویت در دسترس نیست | `US-15.feature` | +| `MSG-06` | «دسترسی مجاز نیست» | تلاش برای دسترسی غیرمجاز | `US-15.feature`، `US-05.feature` | +| `MSG-07` | «ذکر دلیل رد الزامی است» | رد تعمیر بدون دلیل | `US-05.feature` | +| `MSG-08` | «این گزارش در وضعیت قابل تأیید نیست» | تأیید در وضعیت نامناسب | `US-05.feature` | + +## ۸. نکات و پرسش‌های باز + +| # | پرسش | اثر | +|---|---|---| +| ۲۳ | فناوری و فریم‌ورک رابط کاربری چیست؟ | تعیین روش پیاده‌سازی همهٔ صفحات | +| ۲۴ | پالت رنگی و هویت بصری دانشگاه چیست؟ | بخش ۶ موقت است | +| ۲۵ | آیا رابط کاربری باید واکنش‌گرا (موبایل) باشد؟ | در `NFR-03` صریح نیست؛ بر چیدمان جدول‌ها اثر دارد | +| ۲۶ | سطح دسترسی دقیق مدیر (پرسش باز ۹ در `activity.md`) چیست؟ | تعیین آیتم‌های منوی مدیر | +| ۲۷ | آیا گزارش‌دهنده می‌تواند در هر زمان اطلاعات تکمیلی اضافه کند یا فقط پس از درخواست؟ | رفتار دکمهٔ «افزودن اطلاعات» در `SCR-04` | +| ۲۸ | کانال اعلان (پرسش باز ۸) چیست؟ | محتوای `SCR-10` و `CMP-07` | +| ۲۹ | آیا صفحهٔ «پروفایل کاربر» یا «تغییر رمز» لازم است؟ | در مستندات نیامده | +| ۳۰ | آیا گزارش‌دهنده می‌تواند گزارش خود را ویرایش یا لغو کند؟ | در مستندات نیامده | + +> **پرسش ۲۷ مهم است:** `AD-02` این را به‌عنوان نکتهٔ مدل‌سازی علامت زده و `US-04` زمان آن را محدود نکرده است. تا تصمیم‌گیری، رابط کاربری دکمهٔ «افزودن اطلاعات» را فقط پس از درخواست نمایش می‌دهد (محافظه‌کارانه‌ترین حالت). + +--- + +**وضعیت سند:** نسخه اولیه (Draft) – آماده بازبینی \ No newline at end of file