4029 lines
66 KiB
Markdown
4029 lines
66 KiB
Markdown
# مشخصات کامل فنی و محصول
|
|
## سیستم تیکتینگ مشتریان و کارشناسان فروش
|
|
|
|
**نسخه سند:** 1.0
|
|
**نوع پروژه:** Web Application
|
|
**معماری:** Django Modular Monolith
|
|
**Backend:** Django + PostgreSQL
|
|
**Frontend:** Django Templates + HTMX + Alpine.js + Vanilla JavaScript + Tailwind CSS
|
|
**Deployment:** Docker / Docker Compose / Linux
|
|
**File Storage:** Local Server Storage
|
|
**Real-time:** نیاز نیست
|
|
**Redis:** نیاز نیست در نسخه اول
|
|
**Celery:** نیاز نیست در نسخه اول
|
|
**WebSocket:** نیاز نیست
|
|
**Microservice:** ممنوع/غیرضروری در V1
|
|
|
|
---
|
|
|
|
# 1. هدف پروژه
|
|
|
|
هدف، طراحی و پیادهسازی یک سیستم تیکتینگ ساده، سریع، امن و قابل توسعه برای ارتباط مشتریان با کارشناسان فروش شرکت است.
|
|
|
|
مشتریان عمدتاً برای مواردی مانند:
|
|
|
|
- استعلام قیمت
|
|
- درصد تخفیف
|
|
- شرایط فروش
|
|
- موجودی
|
|
- شرایط ارسال
|
|
- درخواست نمایندگی
|
|
- مشاوره فروش
|
|
- سؤالات مربوط به محصولات
|
|
- شکایات
|
|
- سایر مسائل فروش
|
|
|
|
با کارشناسان ارتباط برقرار میکنند.
|
|
|
|
سیستم نباید تبدیل به یک Messenger دائمی شبیه WhatsApp یا Telegram شود.
|
|
|
|
هر Ticket باید نماینده یک موضوع یا مسئله مشخص باشد.
|
|
|
|
مشتری برای موضوع جدید باید Ticket جدید ایجاد کند.
|
|
|
|
داخل یک Ticket امکان چند مرحله سؤال و جواب وجود دارد، اما فلسفه سیستم بر اساس Ticketing باقی میماند.
|
|
|
|
---
|
|
|
|
# 2. اهداف اصلی
|
|
|
|
سیستم باید:
|
|
|
|
1. ورود مشتری با شماره موبایل و OTP را فراهم کند.
|
|
2. ثبتنام اجباری سنتی نداشته باشد.
|
|
3. تعیین نام، نام خانوادگی و Password اختیاری باشد.
|
|
4. مشتری بتواند چند Ticket همزمان داشته باشد.
|
|
5. مشتری بتواند کارشناس موردنظر خود را انتخاب کند.
|
|
6. در صورت عدم انتخاب کارشناس، Ticket وارد صف عمومی کارشناسان شود.
|
|
7. کارشناس بتواند Ticket عمومی را برای خودش بردارد.
|
|
8. بعد از برداشتن Ticket، اطلاعات آن از سایر کارشناسان مخفی شود.
|
|
9. مدیران مجاز همیشه بتوانند همه Ticketها را مشاهده کنند.
|
|
10. امکان پاسخ متنی و Attachmentهای مختلف وجود داشته باشد.
|
|
11. Voice مستقیماً داخل مرورگر ضبط شود.
|
|
12. امکان Edit پیام وجود داشته باشد.
|
|
13. تاریخچه Edit کاملاً حفظ شود.
|
|
14. حذف پیام کارشناس به صورت Soft Delete باشد.
|
|
15. مدیر بتواند متن اصلی پیام حذفشده را مشاهده کند.
|
|
16. امکان Internal Note وجود داشته باشد.
|
|
17. امکان Transfer Ticket وجود داشته باشد.
|
|
18. امکان Share کردن Ticket با Expert دیگر وجود داشته باشد.
|
|
19. امکان Mention کارشناس وجود داشته باشد.
|
|
20. Notification مشتری از طریق SMS انجام شود.
|
|
21. Notification کارشناسان از طریق Dashboard و Bale انجام شود.
|
|
22. مشتری از طریق Magic Link بتواند سریع Ticket را باز کند.
|
|
23. Permission System منعطف باشد.
|
|
24. Priority و SLA قابل استفاده باشند.
|
|
25. فایلها روی Storage محلی Server ذخیره شوند.
|
|
|
|
---
|
|
|
|
# 3. محدوده کاربران
|
|
|
|
حداکثر بار مورد انتظار در نسخه فعلی:
|
|
|
|
- حدود 200 مشتری
|
|
- حداکثر 20 کارشناس فروش
|
|
- یک شرکت
|
|
- بدون Multi-Tenancy
|
|
- حجم پیام و Ticket نسبتاً پایین
|
|
|
|
در نتیجه نیازی به معماری پیچیده Distributed وجود ندارد.
|
|
|
|
---
|
|
|
|
# 4. معماری کلان
|
|
|
|
معماری سیستم باید:
|
|
|
|
**Modular Monolith**
|
|
|
|
باشد.
|
|
|
|
تمام Business Logic داخل یک Django Project قرار میگیرد ولی Domainها به Appهای منطقی جدا تقسیم میشوند.
|
|
|
|
ساختار پیشنهادی:
|
|
|
|
```text
|
|
project/
|
|
│
|
|
├── config/
|
|
│ ├── settings/
|
|
│ ├── urls.py
|
|
│ ├── asgi.py
|
|
│ └── wsgi.py
|
|
│
|
|
├── apps/
|
|
│ │
|
|
│ ├── accounts/
|
|
│ ├── customers/
|
|
│ ├── staff/
|
|
│ ├── tickets/
|
|
│ ├── messaging/
|
|
│ ├── attachments/
|
|
│ ├── notifications/
|
|
│ ├── integrations/
|
|
│ │ ├── sms/
|
|
│ │ └── bale/
|
|
│ │
|
|
│ └── core/
|
|
│
|
|
├── templates/
|
|
├── static/
|
|
├── media/
|
|
├── tests/
|
|
│
|
|
├── docker/
|
|
├── docker-compose.yml
|
|
├── Dockerfile
|
|
└── manage.py
|
|
```
|
|
|
|
---
|
|
|
|
# 5. معماری لایهای داخل Appها
|
|
|
|
Business Logic نباید مستقیماً داخل Viewها نوشته شود.
|
|
|
|
ساختار پیشنهادی برای Appهایی مثل tickets:
|
|
|
|
```text
|
|
tickets/
|
|
├── models.py
|
|
├── forms.py
|
|
├── urls.py
|
|
├── selectors.py
|
|
├── policies.py
|
|
├── services/
|
|
│ ├── create_ticket.py
|
|
│ ├── claim_ticket.py
|
|
│ ├── transfer_ticket.py
|
|
│ ├── ticket_access.py
|
|
│ └── status.py
|
|
├── views/
|
|
├── admin.py
|
|
└── tests/
|
|
```
|
|
|
|
### Responsibility
|
|
|
|
`views`
|
|
|
|
فقط:
|
|
|
|
- HTTP
|
|
- Forms
|
|
- Redirect
|
|
- HTMX response
|
|
|
|
را مدیریت کند.
|
|
|
|
`services`
|
|
|
|
Business Logic را اجرا کند.
|
|
|
|
`selectors`
|
|
|
|
Queryهای خواندن اطلاعات را مدیریت کند.
|
|
|
|
`policies`
|
|
|
|
Permission و authorization را مدیریت کند.
|
|
|
|
---
|
|
|
|
# 6. اصل بسیار مهم Authorization
|
|
|
|
هیچ Permission مهمی نباید فقط در:
|
|
|
|
- Template
|
|
- JavaScript
|
|
- HTMX
|
|
- CSS
|
|
|
|
اعمال شود.
|
|
|
|
تمام Permissionها باید Server Side کنترل شوند.
|
|
|
|
مخفیکردن یک Button در UI به معنی جلوگیری از عملیات نیست.
|
|
|
|
---
|
|
|
|
# 7. User Model
|
|
|
|
از ابتدای پروژه باید Custom User Model ایجاد شود.
|
|
|
|
بعداً تغییر `AUTH_USER_MODEL` بسیار هزینهبر است.
|
|
|
|
مدل مفهومی:
|
|
|
|
```text
|
|
User
|
|
---------
|
|
id
|
|
public_id
|
|
username
|
|
mobile
|
|
email
|
|
password
|
|
account_type
|
|
is_active
|
|
is_staff
|
|
created_at
|
|
updated_at
|
|
last_login
|
|
```
|
|
|
|
`account_type`:
|
|
|
|
```text
|
|
CUSTOMER
|
|
STAFF
|
|
```
|
|
|
|
کاربر Customer میتواند Password نداشته باشد.
|
|
|
|
در این حالت:
|
|
|
|
```python
|
|
user.set_unusable_password()
|
|
```
|
|
|
|
استفاده شود.
|
|
|
|
---
|
|
|
|
# 8. CustomerProfile
|
|
|
|
```text
|
|
CustomerProfile
|
|
----------------
|
|
user
|
|
first_name
|
|
last_name
|
|
customer_type
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
`customer_type` اختیاری است.
|
|
|
|
مقادیر اولیه:
|
|
|
|
```text
|
|
PERSON
|
|
CONSUMER
|
|
SHOP
|
|
REPRESENTATIVE
|
|
COMPANY
|
|
OTHER
|
|
```
|
|
|
|
نمایش فارسی:
|
|
|
|
```text
|
|
شخص
|
|
مصرفکننده نهایی
|
|
فروشگاه
|
|
نمایندگی
|
|
شرکت
|
|
سایر
|
|
```
|
|
|
|
---
|
|
|
|
# 9. StaffProfile
|
|
|
|
```text
|
|
StaffProfile
|
|
-------------
|
|
user
|
|
display_name
|
|
avatar
|
|
mobile
|
|
bale_chat_id
|
|
bale_notifications_enabled
|
|
is_customer_selectable
|
|
sort_order
|
|
is_active
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
فقط کارشناسانی که:
|
|
|
|
```text
|
|
is_customer_selectable = True
|
|
```
|
|
|
|
دارند در صفحه انتخاب کارشناس به مشتری نمایش داده شوند.
|
|
|
|
---
|
|
|
|
# 10. احراز هویت مشتری
|
|
|
|
روش اصلی:
|
|
|
|
```text
|
|
Mobile
|
|
↓
|
|
OTP
|
|
↓
|
|
Login
|
|
```
|
|
|
|
Password اجباری نیست.
|
|
|
|
پس از ورود موفق، سیستم میتواند پیشنهاد دهد:
|
|
|
|
> برای ورود آسانتر در آینده میتوانید رمز عبور تعیین کنید.
|
|
|
|
ولی این درخواست نباید Blocking باشد.
|
|
|
|
---
|
|
|
|
# 11. ورود با Password
|
|
|
|
در صورت تعیین Password، مشتری میتواند در آینده با:
|
|
|
|
```text
|
|
mobile + password
|
|
```
|
|
|
|
هم وارد شود.
|
|
|
|
با این حال روش OTP همچنان باید در صفحه Login در اولویت UX باشد.
|
|
|
|
---
|
|
|
|
# 12. Staff Login
|
|
|
|
کارشناسان و مدیران صفحه Login مستقل داشته باشند.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
/staff/login/
|
|
```
|
|
|
|
Customer login:
|
|
|
|
```text
|
|
/login/
|
|
```
|
|
|
|
Staff Login با Username/Password انجام شود.
|
|
|
|
---
|
|
|
|
# 13. OTP Architecture
|
|
|
|
سیستم SMS نباید وابسته به Provider خاص باشد.
|
|
|
|
از Adapter Pattern و Protocol استفاده شود.
|
|
|
|
Interface مفهومی:
|
|
|
|
```python
|
|
class SMSProvider(Protocol):
|
|
|
|
def send_otp(
|
|
self,
|
|
mobile: str,
|
|
code: str,
|
|
) -> SMSResult:
|
|
...
|
|
|
|
def send_message(
|
|
self,
|
|
mobile: str,
|
|
message: str,
|
|
) -> SMSResult:
|
|
...
|
|
```
|
|
|
|
Providerهای آینده میتوانند شامل:
|
|
|
|
```text
|
|
KavenegarProvider
|
|
SMSIrProvider
|
|
IPPanelProvider
|
|
FarazSMSProvider
|
|
CustomSMSProvider
|
|
```
|
|
|
|
باشند.
|
|
|
|
Business Logic حق ندارد مستقیم API Provider را Call کند.
|
|
|
|
---
|
|
|
|
# 14. OTPChallenge Model
|
|
|
|
```text
|
|
OTPChallenge
|
|
-------------
|
|
id
|
|
mobile
|
|
purpose
|
|
code_hash
|
|
expires_at
|
|
attempt_count
|
|
max_attempts
|
|
verified_at
|
|
created_at
|
|
request_ip
|
|
user_agent
|
|
```
|
|
|
|
Raw OTP نباید داخل Database ذخیره شود.
|
|
|
|
---
|
|
|
|
# 15. تنظیمات OTP
|
|
|
|
مقادیر اولیه پیشنهادی:
|
|
|
|
```text
|
|
OTP Length: 6 digits
|
|
Expire: 2 minutes
|
|
Resend cooldown: 60 seconds
|
|
Max verification attempts: 5
|
|
```
|
|
|
|
تمام موارد باید از Settings قابل تغییر باشند.
|
|
|
|
Rate Limiting نیز باید برای:
|
|
|
|
- mobile
|
|
- IP
|
|
|
|
اعمال شود.
|
|
|
|
Redis برای این حجم کاربر لازم نیست.
|
|
|
|
Rate Limit میتواند با PostgreSQL انجام شود.
|
|
|
|
---
|
|
|
|
# 16. شماره موبایل
|
|
|
|
شمارهها باید قبل از ذخیره Normalize شوند.
|
|
|
|
برای مثال:
|
|
|
|
```text
|
|
09121234567
|
|
```
|
|
|
|
به شکل Canonical:
|
|
|
|
```text
|
|
+989121234567
|
|
```
|
|
|
|
ذخیره شود.
|
|
|
|
تمام جستجو و Unique Constraint بر اساس شماره Normalizeشده باشد.
|
|
|
|
---
|
|
|
|
# 17. TicketCategory
|
|
|
|
Category در Database ذخیره شود.
|
|
|
|
```text
|
|
TicketCategory
|
|
---------------
|
|
id
|
|
title
|
|
slug
|
|
description
|
|
icon
|
|
sort_order
|
|
is_active
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
مثال:
|
|
|
|
```text
|
|
استعلام قیمت
|
|
درصد تخفیف
|
|
شرایط فروش
|
|
موجودی
|
|
شرایط ارسال
|
|
درخواست نمایندگی
|
|
شکایت
|
|
مشاوره
|
|
سایر
|
|
```
|
|
|
|
انتخاب Category برای Customer اجباری است.
|
|
|
|
Category غیرفعال برای Ticketهای قدیمی باقی بماند ولی در Ticket جدید نمایش داده نشود.
|
|
|
|
---
|
|
|
|
# 18. Ticket Model
|
|
|
|
مدل اصلی:
|
|
|
|
```text
|
|
Ticket
|
|
----------------------
|
|
id
|
|
public_id
|
|
ticket_number
|
|
customer
|
|
category
|
|
subject
|
|
|
|
status
|
|
priority
|
|
|
|
selected_expert
|
|
owner
|
|
|
|
origin_visibility
|
|
|
|
created_at
|
|
updated_at
|
|
last_activity_at
|
|
|
|
first_response_at
|
|
last_customer_message_at
|
|
last_staff_message_at
|
|
|
|
first_response_due_at
|
|
resolution_due_at
|
|
|
|
resolved_at
|
|
closed_at
|
|
reopened_at
|
|
```
|
|
|
|
---
|
|
|
|
# 19. Ticket Number
|
|
|
|
Ticket باید شماره قابل خواندن داشته باشد.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
TK-1405-001254
|
|
```
|
|
|
|
اما Permission و URL نباید بر اساس این شماره امنیت ایجاد کنند.
|
|
|
|
URLها از `public_id` یا UUID استفاده کنند.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
/tickets/9d7b.../
|
|
```
|
|
|
|
---
|
|
|
|
# 20. Subject
|
|
|
|
Subject اجباری است.
|
|
|
|
محدوده پیشنهادی:
|
|
|
|
```text
|
|
5 تا 200 character
|
|
```
|
|
|
|
نمونه:
|
|
|
|
> استعلام قیمت لوله 110
|
|
|
|
---
|
|
|
|
# 21. اولین پیام Ticket
|
|
|
|
Ticket علاوه بر Subject باید یک Initial Message داشته باشد.
|
|
|
|
Initial Message میتواند شامل:
|
|
|
|
- Text
|
|
- Attachment
|
|
- Text + Attachments
|
|
|
|
باشد.
|
|
|
|
UX باید مشتری را تشویق کند مسئله را کامل توضیح دهد.
|
|
|
|
---
|
|
|
|
# 22. Ticket Status
|
|
|
|
Statusهای اصلی:
|
|
|
|
```text
|
|
NEW
|
|
OPEN
|
|
WAITING_CUSTOMER
|
|
WAITING_STAFF
|
|
RESOLVED
|
|
CLOSED
|
|
REOPENED
|
|
```
|
|
|
|
---
|
|
|
|
# 23. معنی Statusها
|
|
|
|
### NEW
|
|
|
|
Ticket ایجاد شده ولی هنوز رسیدگی واقعی شروع نشده است.
|
|
|
|
### OPEN
|
|
|
|
کارشناس Ticket را برداشته یا رسیدگی به آن را شروع کرده است.
|
|
|
|
### WAITING_CUSTOMER
|
|
|
|
کارشناس پاسخ داده و اکنون انتظار پاسخ مشتری وجود دارد.
|
|
|
|
### WAITING_STAFF
|
|
|
|
مشتری پاسخ داده و سیستم منتظر پاسخ کارشناس است.
|
|
|
|
### RESOLVED
|
|
|
|
کارشناس مسئله را حلشده اعلام کرده است.
|
|
|
|
### CLOSED
|
|
|
|
Ticket بسته شده است.
|
|
|
|
### REOPENED
|
|
|
|
Ticket بسته یا Resolve شده بوده و Customer دوباره پیام داده است.
|
|
|
|
---
|
|
|
|
# 24. Reopen
|
|
|
|
اگر Ticket:
|
|
|
|
```text
|
|
RESOLVED
|
|
```
|
|
|
|
یا:
|
|
|
|
```text
|
|
CLOSED
|
|
```
|
|
|
|
باشد و Customer پیام جدید ارسال کند:
|
|
|
|
Ticket خودکار به:
|
|
|
|
```text
|
|
REOPENED
|
|
```
|
|
|
|
تغییر وضعیت دهد.
|
|
|
|
این عملیات باید Transactional باشد.
|
|
|
|
---
|
|
|
|
# 25. Priority
|
|
|
|
Priorityهای اولیه:
|
|
|
|
```text
|
|
NORMAL
|
|
IMPORTANT
|
|
URGENT
|
|
```
|
|
|
|
Default:
|
|
|
|
```text
|
|
NORMAL
|
|
```
|
|
|
|
Customer اجازه تغییر Priority ندارد.
|
|
|
|
کارشناس یا کاربران دارای Permission میتوانند Priority را تغییر دهند.
|
|
|
|
---
|
|
|
|
# 26. منطق انتخاب Expert توسط Customer
|
|
|
|
سه حالت مهم وجود دارد.
|
|
|
|
## حالت اول: Customer Expert انتخاب میکند
|
|
|
|
مثلاً Customer:
|
|
|
|
```text
|
|
احمد
|
|
```
|
|
|
|
را انتخاب میکند.
|
|
|
|
در این حالت:
|
|
|
|
```text
|
|
selected_expert = Ahmad
|
|
owner = NULL
|
|
```
|
|
|
|
و فقط:
|
|
|
|
- Ahmad
|
|
- مدیران دارای دسترسی کامل
|
|
|
|
میتوانند Ticket را مشاهده کنند.
|
|
|
|
سایر کارشناسان نباید حتی محتوای Ticket را ببینند.
|
|
|
|
این Ticket وارد Public Queue نمیشود.
|
|
|
|
---
|
|
|
|
# 27. عدم انتخاب Expert
|
|
|
|
اگر Customer کارشناسی انتخاب نکند:
|
|
|
|
```text
|
|
selected_expert = NULL
|
|
owner = NULL
|
|
```
|
|
|
|
Ticket وارد Public Queue میشود.
|
|
|
|
تمام کارشناسان مجاز میتوانند:
|
|
|
|
- عنوان
|
|
- محتوا
|
|
- Attachment
|
|
- Customer
|
|
- سابقه مجاز
|
|
|
|
را مشاهده کنند.
|
|
|
|
---
|
|
|
|
# 28. Claim / Lock Ticket
|
|
|
|
در Public Queue هر Expert میتواند:
|
|
|
|
> برداشتن تیکت
|
|
|
|
یا:
|
|
|
|
> اختصاص به من
|
|
|
|
را انتخاب کند.
|
|
|
|
پس:
|
|
|
|
```text
|
|
owner = current_user
|
|
```
|
|
|
|
خواهد شد.
|
|
|
|
---
|
|
|
|
# 29. Race Condition در Claim
|
|
|
|
ممکن است دو Expert همزمان روی:
|
|
|
|
> اختصاص به من
|
|
|
|
کلیک کنند.
|
|
|
|
Claim باید با:
|
|
|
|
```python
|
|
transaction.atomic()
|
|
```
|
|
|
|
و:
|
|
|
|
```python
|
|
select_for_update()
|
|
```
|
|
|
|
پیادهسازی شود.
|
|
|
|
فقط اولین Expert موفق شود.
|
|
|
|
Expert دوم پیام مناسب دریافت کند:
|
|
|
|
> این تیکت لحظاتی قبل توسط «احمد رضایی» برداشته شد.
|
|
|
|
---
|
|
|
|
# 30. رفتار Ticket بعد از Claim
|
|
|
|
بعد از Claim:
|
|
|
|
فقط:
|
|
|
|
- Owner
|
|
- کارشناسان Shared
|
|
- مدیران دارای Permission
|
|
|
|
حق مشاهده محتوای Ticket را دارند.
|
|
|
|
سایر کارشناسان فقط اطلاعات محدود زیر را ببینند:
|
|
|
|
```text
|
|
Ticket Title
|
|
Owner
|
|
Locked/Assigned indicator
|
|
```
|
|
|
|
و نباید قادر باشند:
|
|
|
|
- Message را بخوانند
|
|
- Attachment ببینند
|
|
- Customer information ببینند
|
|
- Reply کنند
|
|
- Internal Note ببینند
|
|
|
|
---
|
|
|
|
# 31. Ticket انتخابشده توسط Customer
|
|
|
|
یک تفاوت مهم:
|
|
|
|
اگر Customer از ابتدا Expert خاصی انتخاب کرده باشد، سایر کارشناسان حتی Metadata آن را در Public Queue نبینند.
|
|
|
|
این Ticket خصوصی محسوب میشود.
|
|
|
|
---
|
|
|
|
# 32. TicketAccessGrant
|
|
|
|
برای دادن دسترسی به Expertهای دیگر مدل جداگانه ایجاد شود.
|
|
|
|
```text
|
|
TicketAccessGrant
|
|
------------------
|
|
id
|
|
ticket
|
|
user
|
|
granted_by
|
|
permission_level
|
|
created_at
|
|
revoked_at
|
|
```
|
|
|
|
در V1 میتوان `permission_level` را ساده نگه داشت:
|
|
|
|
```text
|
|
FULL
|
|
```
|
|
|
|
ولی فیلد از ابتدا وجود داشته باشد تا بعداً:
|
|
|
|
```text
|
|
VIEW_ONLY
|
|
REPLY
|
|
FULL
|
|
```
|
|
|
|
قابل توسعه باشد.
|
|
|
|
---
|
|
|
|
# 33. Share Ticket
|
|
|
|
Owner یا Manager دارای Permission بتواند Expert دیگری را به Ticket اضافه کند.
|
|
|
|
عملیات UI:
|
|
|
|
> اشتراک با کارشناس
|
|
|
|
پس از Share، Expert موردنظر:
|
|
|
|
- محتوای Ticket
|
|
- پیامها
|
|
- فایلها
|
|
|
|
را مشاهده میکند و در صورت FULL بودن دسترسی پاسخ میدهد.
|
|
|
|
---
|
|
|
|
# 34. Revoke Access
|
|
|
|
Owner یا مدیر مجاز بتواند Access یک Expert را حذف کند.
|
|
|
|
Manager دارای Full Access هرگز توسط Ticket Grant محدود نمیشود.
|
|
|
|
---
|
|
|
|
# 35. Transfer Ticket
|
|
|
|
Owner بتواند Ticket را بدون نیاز به تأیید مدیر به Expert دیگری Transfer کند.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
Ahmad → Reza
|
|
```
|
|
|
|
پس:
|
|
|
|
```text
|
|
owner = Reza
|
|
```
|
|
|
|
شود.
|
|
|
|
Transfer باید Transactional باشد.
|
|
|
|
---
|
|
|
|
# 36. دسترسی Owner قبلی بعد از Transfer
|
|
|
|
Default Behavior:
|
|
|
|
پس از Transfer، Owner قبلی دسترسی اختصاصی خود را از دست میدهد.
|
|
|
|
در صورت نیاز باید به عنوان Shared Expert به Ticket اضافه شود.
|
|
|
|
این رفتار از نشت ناخواسته اطلاعات جلوگیری میکند.
|
|
|
|
---
|
|
|
|
# 37. TicketEvent
|
|
|
|
Audit Log عمومی در V1 لازم نیست.
|
|
|
|
اما Domain History لازم است.
|
|
|
|
مدل:
|
|
|
|
```text
|
|
TicketEvent
|
|
------------
|
|
id
|
|
ticket
|
|
event_type
|
|
actor
|
|
metadata
|
|
created_at
|
|
```
|
|
|
|
Eventها:
|
|
|
|
```text
|
|
CREATED
|
|
CLAIMED
|
|
TRANSFERRED
|
|
SHARED
|
|
ACCESS_REVOKED
|
|
STATUS_CHANGED
|
|
PRIORITY_CHANGED
|
|
REOPENED
|
|
RESOLVED
|
|
CLOSED
|
|
```
|
|
|
|
این مدل برای:
|
|
|
|
- Timeline
|
|
- SLA
|
|
- Debug
|
|
- تاریخچه Transfer
|
|
|
|
استفاده میشود.
|
|
|
|
این سیستم جای Audit Log جامع را نمیگیرد.
|
|
|
|
---
|
|
|
|
# 38. Permissions
|
|
|
|
Role names نباید داخل Business Logic Hard-Code شوند.
|
|
|
|
یعنی کد نباید بگوید:
|
|
|
|
```python
|
|
if user.role == "manager":
|
|
```
|
|
|
|
بلکه:
|
|
|
|
```python
|
|
user.has_perm(...)
|
|
```
|
|
|
|
یا Ticket Policy Service استفاده شود.
|
|
|
|
---
|
|
|
|
# 39. Django Groups
|
|
|
|
برای ساده نگهداشتن معماری از:
|
|
|
|
- Django Groups
|
|
- Django Permissions
|
|
|
|
استفاده شود.
|
|
|
|
مثلاً Groupهای اولیه:
|
|
|
|
```text
|
|
Sales Expert
|
|
Sales Manager
|
|
Administrator
|
|
```
|
|
|
|
نام Group Business Logic نیست.
|
|
|
|
Permission تعیینکننده رفتار است.
|
|
|
|
---
|
|
|
|
# 40. Permissions پیشنهادی
|
|
|
|
```text
|
|
tickets.view_all_tickets
|
|
tickets.view_private_ticket
|
|
tickets.claim_ticket
|
|
tickets.transfer_ticket
|
|
tickets.share_ticket
|
|
tickets.change_ticket_status
|
|
tickets.change_ticket_priority
|
|
|
|
tickets.view_customer_history
|
|
|
|
messaging.add_internal_note
|
|
messaging.edit_own_message
|
|
messaging.delete_own_message
|
|
messaging.view_deleted_message_content
|
|
messaging.view_message_revisions
|
|
|
|
attachments.download_attachment
|
|
|
|
staff.manage_staff
|
|
tickets.manage_categories
|
|
attachments.manage_attachment_policy
|
|
```
|
|
|
|
---
|
|
|
|
# 41. مدیران
|
|
|
|
مدیرانی که:
|
|
|
|
```text
|
|
tickets.view_all_tickets
|
|
```
|
|
|
|
دارند باید بدون استثناء تمام Ticketها را مشاهده کنند.
|
|
|
|
فرقی نمیکند:
|
|
|
|
- Customer Expert انتخاب کرده باشد.
|
|
- Ticket Locked شده باشد.
|
|
- Ticket Shared نشده باشد.
|
|
- Ticket متعلق به Expert دیگری باشد.
|
|
|
|
---
|
|
|
|
# 42. TicketPolicyService
|
|
|
|
Permission Ticket باید در یک نقطه مرکزی باشد.
|
|
|
|
مثلاً:
|
|
|
|
```python
|
|
TicketPolicy.can_view(user, ticket)
|
|
|
|
TicketPolicy.can_reply(user, ticket)
|
|
|
|
TicketPolicy.can_claim(user, ticket)
|
|
|
|
TicketPolicy.can_transfer(user, ticket)
|
|
|
|
TicketPolicy.can_share(user, ticket)
|
|
|
|
TicketPolicy.can_view_deleted_content(user, ticket)
|
|
```
|
|
|
|
هیچ View نباید منطق Permission را دوباره بنویسد.
|
|
|
|
---
|
|
|
|
# 43. Message Model
|
|
|
|
```text
|
|
Message
|
|
---------------
|
|
id
|
|
public_id
|
|
ticket
|
|
author
|
|
|
|
body
|
|
message_type
|
|
|
|
is_internal
|
|
|
|
created_at
|
|
updated_at
|
|
edited_at
|
|
|
|
deleted_at
|
|
deleted_by
|
|
```
|
|
|
|
---
|
|
|
|
# 44. انواع Message
|
|
|
|
```text
|
|
PUBLIC
|
|
INTERNAL_NOTE
|
|
```
|
|
|
|
Customer فقط PUBLIC را مشاهده میکند.
|
|
|
|
---
|
|
|
|
# 45. Internal Note
|
|
|
|
Expert بتواند Note داخلی ایجاد کند.
|
|
|
|
مثال:
|
|
|
|
> این مشتری تخفیف ویژه درخواست کرده، قبل از اعلام قیمت با مدیر هماهنگ شود.
|
|
|
|
Internal Note:
|
|
|
|
- در Timeline کارشناسان نمایش داده شود.
|
|
- از پیامهای معمولی کاملاً متمایز باشد.
|
|
- Customer هرگز آن را نبیند.
|
|
- در SMS Customer اثر نداشته باشد.
|
|
|
|
---
|
|
|
|
# 46. Edit Message
|
|
|
|
Customer و Expert بتوانند Message خودشان را Edit کنند.
|
|
|
|
بعد از Edit:
|
|
|
|
```text
|
|
edited_at
|
|
```
|
|
|
|
ثبت شود.
|
|
|
|
در UI:
|
|
|
|
> ویرایش شده
|
|
|
|
نمایش داده شود.
|
|
|
|
---
|
|
|
|
# 47. MessageRevision
|
|
|
|
قبل از تغییر متن، نسخه قبلی ذخیره شود.
|
|
|
|
```text
|
|
MessageRevision
|
|
----------------
|
|
id
|
|
message
|
|
body
|
|
edited_by
|
|
created_at
|
|
```
|
|
|
|
مثلاً:
|
|
|
|
نسخه 1:
|
|
|
|
```text
|
|
قیمت 1,200,000 تومان
|
|
```
|
|
|
|
نسخه جدید:
|
|
|
|
```text
|
|
قیمت 1,250,000 تومان
|
|
```
|
|
|
|
هر دو باید قابل بازیابی برای Manager مجاز باشند.
|
|
|
|
---
|
|
|
|
# 48. مشاهده Revision
|
|
|
|
Customer الزاماً Revision History را نمیبیند.
|
|
|
|
Manager یا فرد دارای:
|
|
|
|
```text
|
|
messaging.view_message_revisions
|
|
```
|
|
|
|
میتواند تاریخچه را مشاهده کند.
|
|
|
|
---
|
|
|
|
# 49. Delete Message
|
|
|
|
Expert میتواند Message خودش را Delete کند.
|
|
|
|
اما:
|
|
|
|
**هیچ Hard Delete انجام نشود.**
|
|
|
|
ممنوع:
|
|
|
|
```python
|
|
message.delete()
|
|
```
|
|
|
|
به جای آن:
|
|
|
|
```text
|
|
deleted_at
|
|
deleted_by
|
|
```
|
|
|
|
ثبت شود.
|
|
|
|
Body اصلی در Database باقی بماند.
|
|
|
|
---
|
|
|
|
# 50. نمایش پیام حذفشده
|
|
|
|
Customer و کارشناسان عادی ببینند:
|
|
|
|
> این پیام توسط فرستنده حذف شده است.
|
|
|
|
Manager دارای Permission بتواند:
|
|
|
|
- Original Body
|
|
- Attachments
|
|
- Author
|
|
- Delete time
|
|
|
|
را مشاهده کند.
|
|
|
|
---
|
|
|
|
# 51. Attachments روی Message حذفشده
|
|
|
|
Attachmentهای Message حذفشده نباید از Disk حذف شوند.
|
|
|
|
مدیر باید همچنان بتواند آنها را مشاهده کند.
|
|
|
|
---
|
|
|
|
# 52. MessageMention
|
|
|
|
Mention نباید فقط Regex روی متن باشد.
|
|
|
|
مدل:
|
|
|
|
```text
|
|
MessageMention
|
|
---------------
|
|
message
|
|
mentioned_user
|
|
created_at
|
|
```
|
|
|
|
در Frontend Autocomplete Expert نمایش داده شود.
|
|
|
|
---
|
|
|
|
# 53. Mention و Access
|
|
|
|
Mention بهتنهایی نباید Permission امنیتی را دور بزند.
|
|
|
|
اگر Expert Mentionشده به Ticket دسترسی ندارد:
|
|
|
|
سیستم باید ابتدا Access مناسب ایجاد کند یا UI به فرستنده اعلام کند:
|
|
|
|
> برای Mention کردن این کارشناس ابتدا باید دسترسی تیکت به او داده شود.
|
|
|
|
Owner و Manager میتوانند:
|
|
|
|
> Share + Mention
|
|
|
|
را در یک Flow انجام دهند.
|
|
|
|
---
|
|
|
|
# 54. Bale Notification برای Mention
|
|
|
|
پس از Mention، Expert از طریق Bale Notification دریافت کند.
|
|
|
|
مثال:
|
|
|
|
```text
|
|
تیکت TK-1405-1254
|
|
|
|
علی رضایی شما را Mention کرد:
|
|
|
|
«لطفاً شرایط تخفیف این مشتری را بررسی کنید.»
|
|
|
|
مشاهده تیکت:
|
|
https://support.example.com/...
|
|
```
|
|
|
|
---
|
|
|
|
# 55. Attachment Model
|
|
|
|
```text
|
|
Attachment
|
|
----------------
|
|
id
|
|
public_id
|
|
message
|
|
|
|
original_name
|
|
stored_name
|
|
relative_path
|
|
|
|
attachment_type
|
|
|
|
mime_type
|
|
extension
|
|
file_size
|
|
|
|
uploaded_by
|
|
created_at
|
|
```
|
|
|
|
---
|
|
|
|
# 56. Attachment Types
|
|
|
|
سیستم باید حداقل پشتیبانی کند:
|
|
|
|
```text
|
|
IMAGE
|
|
VIDEO
|
|
VOICE
|
|
PDF
|
|
WORD
|
|
EXCEL
|
|
ZIP
|
|
RAR
|
|
OTHER
|
|
```
|
|
|
|
---
|
|
|
|
# 57. AttachmentPolicy
|
|
|
|
محدودیتها نباید Hard-Code باشند.
|
|
|
|
مدل:
|
|
|
|
```text
|
|
AttachmentPolicy
|
|
-----------------
|
|
attachment_type
|
|
is_enabled
|
|
|
|
max_file_size
|
|
max_files_per_message
|
|
|
|
allowed_extensions
|
|
allowed_mime_types
|
|
|
|
sort_order
|
|
updated_at
|
|
```
|
|
|
|
---
|
|
|
|
# 58. محدودیت اولیه پیشنهادی
|
|
|
|
این اعداد فقط Default هستند و باید قابل تغییر باشند.
|
|
|
|
```text
|
|
IMAGE 10 MB
|
|
VOICE 25 MB
|
|
VIDEO 100 MB
|
|
PDF 30 MB
|
|
WORD 30 MB
|
|
EXCEL 30 MB
|
|
ZIP 50 MB
|
|
RAR 50 MB
|
|
```
|
|
|
|
---
|
|
|
|
# 59. Multiple Attachments
|
|
|
|
هر Message باید بتواند چند Attachment داشته باشد.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
متن
|
|
+ 4 تصویر
|
|
+ یک PDF
|
|
+ یک Voice
|
|
```
|
|
|
|
مجاز باشد.
|
|
|
|
---
|
|
|
|
# 60. Attachment Composer UI
|
|
|
|
قبل از ارسال، UI باید نشان دهد:
|
|
|
|
- Thumbnail تصویر
|
|
- نام فایل
|
|
- Size
|
|
- Type
|
|
- Progress
|
|
- حذف قبل از ارسال
|
|
- Error فایل نامعتبر
|
|
|
|
---
|
|
|
|
# 61. File Validation
|
|
|
|
صرفاً Extension کافی نیست.
|
|
|
|
Server باید بررسی کند:
|
|
|
|
1. Extension
|
|
2. MIME Type
|
|
3. File Signature
|
|
|
|
برای File Signature استفاده از `libmagic` یا ابزار مشابه توصیه میشود.
|
|
|
|
---
|
|
|
|
# 62. ZIP و RAR
|
|
|
|
ZIP/RAR نباید روی Server Extract شوند.
|
|
|
|
فقط به عنوان Attachment ذخیره شوند.
|
|
|
|
این موضوع برای جلوگیری از:
|
|
|
|
- path traversal
|
|
- zip bombs
|
|
- فایلهای اجرایی داخلی
|
|
|
|
مهم است.
|
|
|
|
---
|
|
|
|
# 63. File Name Security
|
|
|
|
نام واقعی فایل کاربر نباید مستقیماً نام فایل Storage باشد.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
invoice.pdf
|
|
```
|
|
|
|
روی Disk به:
|
|
|
|
```text
|
|
1b3ef72c-....pdf
|
|
```
|
|
|
|
تبدیل شود.
|
|
|
|
`original_name` فقط Metadata است.
|
|
|
|
---
|
|
|
|
# 64. Media Storage
|
|
|
|
Storage کاملاً Local Server است.
|
|
|
|
ساختار پیشنهادی:
|
|
|
|
```text
|
|
/media/
|
|
└── tickets/
|
|
└── 2026/
|
|
└── 09/
|
|
└── <ticket_uuid>/
|
|
└── <message_uuid>/
|
|
├── file_uuid.jpg
|
|
├── file_uuid.pdf
|
|
└── file_uuid.webm
|
|
```
|
|
|
|
---
|
|
|
|
# 65. Private Media
|
|
|
|
فایلهای Ticket نباید مستقیماً از Public `/media/` قابل دسترسی باشند.
|
|
|
|
این URL ناامن است:
|
|
|
|
```text
|
|
https://site.com/media/tickets/xxx.pdf
|
|
```
|
|
|
|
چون Permission را دور میزند.
|
|
|
|
Download باید ابتدا Django Authorization را بررسی کند.
|
|
|
|
---
|
|
|
|
# 66. Nginx X-Accel-Redirect
|
|
|
|
روش پیشنهادی:
|
|
|
|
1. درخواست Download وارد Django میشود.
|
|
2. Django Permission را بررسی میکند.
|
|
3. در صورت مجاز بودن Header مخصوص Nginx برگردانده میشود.
|
|
4. Nginx فایل را Efficient ارسال میکند.
|
|
|
|
Location فایلها در Nginx باید:
|
|
|
|
```text
|
|
internal
|
|
```
|
|
|
|
باشد.
|
|
|
|
---
|
|
|
|
# 67. Voice Recording
|
|
|
|
Voice با:
|
|
|
|
```text
|
|
MediaDevices.getUserMedia()
|
|
MediaRecorder
|
|
```
|
|
|
|
پیادهسازی شود.
|
|
|
|
نیازی به Plugin خارجی نیست.
|
|
|
|
---
|
|
|
|
# 68. Voice UI
|
|
|
|
Voice Recorder شامل:
|
|
|
|
```text
|
|
Record
|
|
Stop
|
|
Pause
|
|
Resume
|
|
Timer
|
|
Preview
|
|
Delete
|
|
Re-record
|
|
Send
|
|
```
|
|
|
|
باشد.
|
|
|
|
حداقل امکانات:
|
|
|
|
```text
|
|
🎤 ضبط
|
|
⏱ مدت ضبط
|
|
▶ پخش
|
|
🗑 حذف
|
|
➤ ارسال
|
|
```
|
|
|
|
---
|
|
|
|
# 69. Browser Compatibility Voice
|
|
|
|
Format ضبط ممکن است بین Browserها متفاوت باشد.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
audio/webm
|
|
audio/ogg
|
|
audio/mp4
|
|
```
|
|
|
|
Frontend باید با:
|
|
|
|
```javascript
|
|
MediaRecorder.isTypeSupported()
|
|
```
|
|
|
|
Format مناسب را انتخاب کند.
|
|
|
|
Server نباید فقط یک MIME Type را قبول کند.
|
|
|
|
---
|
|
|
|
# 70. Video
|
|
|
|
در V1 نیازی به Transcoding Video نیست.
|
|
|
|
Video همان Format مجاز Upload میشود.
|
|
|
|
در Browser در صورت پشتیبانی:
|
|
|
|
```html
|
|
<video controls>
|
|
```
|
|
|
|
نمایش داده شود.
|
|
|
|
---
|
|
|
|
# 71. Image Preview
|
|
|
|
Image در Ticket Thumbnail داشته باشد.
|
|
|
|
با کلیک:
|
|
|
|
- Modal
|
|
- Full preview
|
|
|
|
نمایش داده شود.
|
|
|
|
---
|
|
|
|
# 72. PDF
|
|
|
|
PDF میتواند:
|
|
|
|
- Download شود.
|
|
- در Browser باز شود.
|
|
|
|
Inline preview اختیاری است.
|
|
|
|
---
|
|
|
|
# 73. Magic Link
|
|
|
|
وقتی Expert پاسخ عمومی برای Customer ارسال میکند، Customer توسط SMS مطلع شود.
|
|
|
|
SMS لینک امن Ticket داشته باشد.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
پاسخ جدیدی برای درخواست شما ثبت شد.
|
|
|
|
مشاهده:
|
|
https://support.example.com/t/AbCDef...
|
|
```
|
|
|
|
---
|
|
|
|
# 74. Magic Link Security
|
|
|
|
Token باید حداقل:
|
|
|
|
```text
|
|
256-bit secure random
|
|
```
|
|
|
|
باشد.
|
|
|
|
برای تولید:
|
|
|
|
```python
|
|
secrets.token_urlsafe()
|
|
```
|
|
|
|
یا روش معادل Cryptographically Secure استفاده شود.
|
|
|
|
---
|
|
|
|
# 75. TicketMagicLink Model
|
|
|
|
```text
|
|
TicketMagicLink
|
|
----------------
|
|
id
|
|
ticket
|
|
customer
|
|
|
|
token_hash
|
|
|
|
created_at
|
|
expires_at
|
|
|
|
used_at
|
|
revoked_at
|
|
|
|
created_reason
|
|
last_used_ip
|
|
```
|
|
|
|
Raw Token نباید در Database ذخیره شود.
|
|
|
|
فقط Hash ذخیره شود.
|
|
|
|
---
|
|
|
|
# 76. Magic Link Lifetime
|
|
|
|
Magic Link تا:
|
|
|
|
```text
|
|
7 روز
|
|
```
|
|
|
|
معتبر است.
|
|
|
|
---
|
|
|
|
# 77. Single-use Magic Link
|
|
|
|
برای امنیت بهتر، Magic Link باید نقش Bootstrap Login داشته باشد.
|
|
|
|
Flow:
|
|
|
|
```text
|
|
SMS Link
|
|
↓
|
|
Token Validation
|
|
↓
|
|
Customer Session ایجاد میشود
|
|
↓
|
|
Token مصرف میشود
|
|
↓
|
|
Redirect به URL تمیز Ticket
|
|
```
|
|
|
|
یعنی بعد از مصرف، Token داخل Address Bar باقی نماند.
|
|
|
|
---
|
|
|
|
# 78. Session پس از Magic Link
|
|
|
|
بعد از ورود با Magic Link، Customer بتواند:
|
|
|
|
- Ticket را بخواند.
|
|
- Reply کند.
|
|
- Attachment ارسال کند.
|
|
|
|
بدون OTP مجدد.
|
|
|
|
Session میتواند مطابق Policy سیستم تا چند روز معتبر باشد.
|
|
|
|
---
|
|
|
|
# 79. Magic Link Expired
|
|
|
|
اگر Link منقضی شده باشد:
|
|
|
|
Customer به OTP Login هدایت شود.
|
|
|
|
Destination Ticket حفظ شود.
|
|
|
|
Flow:
|
|
|
|
```text
|
|
Expired Magic Link
|
|
↓
|
|
OTP Login
|
|
↓
|
|
Successful Login
|
|
↓
|
|
Original Ticket
|
|
```
|
|
|
|
---
|
|
|
|
# 80. Referrer Security
|
|
|
|
صفحه Consume Magic Link باید Header مناسب داشته باشد:
|
|
|
|
```text
|
|
Referrer-Policy: no-referrer
|
|
```
|
|
|
|
تا Token به سایتهای خارجی Leak نشود.
|
|
|
|
---
|
|
|
|
# 81. SMS Notification Policy
|
|
|
|
پیشنهاد Default:
|
|
|
|
بعد از اولین پاسخ عمومی Expert پس از آخرین پیام Customer، SMS ارسال شود.
|
|
|
|
اگر Expert پشت سر هم 3 پیام ارسال کرد:
|
|
|
|
سه SMS جداگانه ارسال نشود.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
Customer Message
|
|
↓
|
|
Expert Message 1 → SMS
|
|
Expert Message 2 → No SMS
|
|
Expert Message 3 → No SMS
|
|
↓
|
|
Customer Reply
|
|
↓
|
|
Expert Message → SMS
|
|
```
|
|
|
|
این Policy باید قابل تغییر باشد.
|
|
|
|
---
|
|
|
|
# 82. Notification Architecture
|
|
|
|
برای External Notification از Database Outbox استفاده شود.
|
|
|
|
نیازی به Redis یا Celery نیست.
|
|
|
|
مدل:
|
|
|
|
```text
|
|
NotificationJob
|
|
----------------
|
|
id
|
|
channel
|
|
event_type
|
|
|
|
customer
|
|
staff_user
|
|
ticket
|
|
|
|
status
|
|
attempts
|
|
next_attempt_at
|
|
last_error
|
|
|
|
dedupe_key
|
|
|
|
created_at
|
|
sent_at
|
|
```
|
|
|
|
Channel:
|
|
|
|
```text
|
|
SMS
|
|
BALE
|
|
```
|
|
|
|
Status:
|
|
|
|
```text
|
|
PENDING
|
|
PROCESSING
|
|
SENT
|
|
FAILED
|
|
```
|
|
|
|
---
|
|
|
|
# 83. Notification Worker بدون Redis
|
|
|
|
در Docker Compose یک Process سبک اجرا شود:
|
|
|
|
```text
|
|
python manage.py process_notifications
|
|
```
|
|
|
|
Worker هر چند ثانیه PostgreSQL را برای Notificationهای Pending بررسی کند.
|
|
|
|
این Process بخشی از همان Django Project است و Microservice محسوب نمیشود.
|
|
|
|
---
|
|
|
|
# 84. مزیت Outbox
|
|
|
|
اگر Bale یا SMS Provider لحظهای Down باشد:
|
|
|
|
پاسخ Expert همچنان در Database ذخیره میشود.
|
|
|
|
Notification با Retry ارسال میشود.
|
|
|
|
در غیر این صورت External API میتواند UX سیستم را خراب کند.
|
|
|
|
---
|
|
|
|
# 85. OTP متفاوت است
|
|
|
|
OTP به دلیل اینکه Customer منتظر Code است بهتر است مستقیم ارسال شود.
|
|
|
|
OTP وارد Outbox عادی نشود.
|
|
|
|
Provider باید Timeout کوتاه داشته باشد.
|
|
|
|
---
|
|
|
|
# 86. Bale Adapter
|
|
|
|
Bale Integration هم نباید در Business Logic مستقیم استفاده شود.
|
|
|
|
Interface مفهومی:
|
|
|
|
```python
|
|
class StaffNotifier(Protocol):
|
|
|
|
def send_message(
|
|
self,
|
|
chat_id: str,
|
|
text: str,
|
|
) -> NotificationResult:
|
|
...
|
|
```
|
|
|
|
پیادهسازی:
|
|
|
|
```text
|
|
BaleProvider
|
|
```
|
|
|
|
---
|
|
|
|
# 87. Bale Notification Events
|
|
|
|
Bale حداقل برای این Eventها استفاده شود:
|
|
|
|
### New Public Ticket
|
|
|
|
برای کارشناسانی که Notification فعال دارند.
|
|
|
|
### Customer Selected Expert
|
|
|
|
فقط Expert انتخابشده.
|
|
|
|
### Mention
|
|
|
|
فقط فرد Mentionشده.
|
|
|
|
### Transfer
|
|
|
|
Expert مالک جدید.
|
|
|
|
### Share
|
|
|
|
Expertی که دسترسی دریافت کرده است.
|
|
|
|
---
|
|
|
|
# 88. Dashboard Notification
|
|
|
|
بدون WebSocket.
|
|
|
|
Ticketهای جدید یا نیازمند پاسخ با:
|
|
|
|
- Badge
|
|
- رنگ متفاوت
|
|
- Counter
|
|
|
|
نمایش داده شوند.
|
|
|
|
HTMX Polling سبک مثلاً هر 30 تا 60 ثانیه برای Counter مجاز است.
|
|
|
|
WebSocket لازم نیست.
|
|
|
|
---
|
|
|
|
# 89. Customer Ticket List
|
|
|
|
Customer Dashboard:
|
|
|
|
```text
|
|
تیکتهای من
|
|
```
|
|
|
|
هر Card:
|
|
|
|
```text
|
|
Ticket Number
|
|
Subject
|
|
Category
|
|
Status
|
|
Last Update
|
|
Selected/Assigned Expert
|
|
```
|
|
|
|
را نمایش دهد.
|
|
|
|
---
|
|
|
|
# 90. New Ticket Page
|
|
|
|
Form:
|
|
|
|
```text
|
|
Category *
|
|
Subject *
|
|
Expert انتخابی
|
|
Description / Message
|
|
Attachments
|
|
```
|
|
|
|
Expert Selection با Card:
|
|
|
|
```text
|
|
Avatar
|
|
Name
|
|
```
|
|
|
|
و گزینه:
|
|
|
|
> انتخاب کارشناس خاصی ندارم
|
|
|
|
---
|
|
|
|
# 91. Ticket Detail مشتری
|
|
|
|
صفحه شامل:
|
|
|
|
```text
|
|
Ticket header
|
|
Status
|
|
Category
|
|
Expert
|
|
Messages
|
|
Attachments
|
|
Reply form
|
|
Voice Recorder
|
|
```
|
|
|
|
باشد.
|
|
|
|
Internal Notes هرگز در HTML Customer Rendering نشوند.
|
|
|
|
---
|
|
|
|
# 92. Customer Profile
|
|
|
|
صفحه ساده:
|
|
|
|
```text
|
|
نام
|
|
نام خانوادگی
|
|
نوع مشتری
|
|
شماره موبایل
|
|
Password
|
|
```
|
|
|
|
Mobile تغییر مستقیم نداشته باشد مگر با OTP Verification جداگانه.
|
|
|
|
---
|
|
|
|
# 93. Staff Dashboard
|
|
|
|
Layout پیشنهادی Desktop:
|
|
|
|
```text
|
|
┌──────────────────────────────────────┐
|
|
│ Header / Search / Notifications │
|
|
├───────────────┬──────────────────────┤
|
|
│ Filters │ Ticket List │
|
|
│ │ │
|
|
│ New │ TK-... │
|
|
│ Mine │ Subject │
|
|
│ Shared │ Customer │
|
|
│ Waiting │ Status / Priority │
|
|
│ Closed │ │
|
|
└───────────────┴──────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
# 94. Staff Ticket Detail
|
|
|
|
Desktop Layout:
|
|
|
|
```text
|
|
┌──────────────┬──────────────────────┬───────────────┐
|
|
│ Ticket Info │ Conversation │ Customer Info │
|
|
│ │ │ │
|
|
│ Status │ Messages │ Mobile │
|
|
│ Priority │ Attachments │ Type │
|
|
│ Category │ Notes │ History │
|
|
│ Owner │ Reply │ Tickets │
|
|
│ │ │ │
|
|
└──────────────┴──────────────────────┴───────────────┘
|
|
```
|
|
|
|
در Mobile Responsive به ستونهای عمودی تبدیل شود.
|
|
|
|
---
|
|
|
|
# 95. Staff Ticket Tabs
|
|
|
|
حداقل:
|
|
|
|
```text
|
|
جدید
|
|
انتخابشده برای من
|
|
تیکتهای من
|
|
Shared with me
|
|
در انتظار من
|
|
در انتظار مشتری
|
|
حلشده
|
|
بستهشده
|
|
```
|
|
|
|
برای Expertهای دیگر:
|
|
|
|
Ticketهایی که از Public Queue توسط فرد دیگری Lock شدهاند میتوانند در بخش جدا:
|
|
|
|
> برداشتهشدهها
|
|
|
|
فقط با Title + Owner نمایش داده شوند.
|
|
|
|
---
|
|
|
|
# 96. Staff Search
|
|
|
|
Search حداقل روی:
|
|
|
|
```text
|
|
Ticket Number
|
|
Subject
|
|
Customer Mobile
|
|
Customer Name
|
|
```
|
|
|
|
کار کند.
|
|
|
|
---
|
|
|
|
# 97. Filters
|
|
|
|
فیلتر:
|
|
|
|
```text
|
|
Status
|
|
Category
|
|
Priority
|
|
Owner
|
|
Date
|
|
Unread
|
|
```
|
|
|
|
---
|
|
|
|
# 98. Ticket Read State
|
|
|
|
برای تشخیص Unread مدل:
|
|
|
|
```text
|
|
TicketReadState
|
|
----------------
|
|
ticket
|
|
user
|
|
last_read_at
|
|
last_read_message
|
|
```
|
|
|
|
وجود داشته باشد.
|
|
|
|
---
|
|
|
|
# 99. Customer History
|
|
|
|
در Sidebar Staff نمایش داده شود:
|
|
|
|
```text
|
|
نام
|
|
موبایل
|
|
نوع مشتری
|
|
تعداد Ticket
|
|
آخرین Ticketها
|
|
```
|
|
|
|
---
|
|
|
|
# 100. Privacy سابقه Customer
|
|
|
|
اگر Ticket قدیمی Private متعلق به Expert دیگری است:
|
|
|
|
Expert فعلی نباید الزاماً Full Content را ببیند.
|
|
|
|
Default:
|
|
|
|
Expert لیست کلی History را مشاهده کند، اما Full Content فقط بر اساس Ticket Permission.
|
|
|
|
Manager میتواند تمام History را مشاهده کند.
|
|
|
|
Permission اضافی:
|
|
|
|
```text
|
|
tickets.view_customer_full_history
|
|
```
|
|
|
|
میتواند بعداً به Expert خاص داده شود.
|
|
|
|
---
|
|
|
|
# 101. SLA
|
|
|
|
SLA از ابتدا در Data Model لحاظ شود.
|
|
|
|
حتی اگر Dashboard SLA در فاز اول ساده باشد.
|
|
|
|
حداقل Metrics:
|
|
|
|
```text
|
|
First Response Time
|
|
Resolution Time
|
|
Unanswered Tickets
|
|
Overdue Tickets
|
|
Tickets per Expert
|
|
Average Response Time
|
|
```
|
|
|
|
---
|
|
|
|
# 102. SLAConfig
|
|
|
|
Configuration قابل تغییر:
|
|
|
|
```text
|
|
SLAConfig
|
|
-----------
|
|
priority
|
|
first_response_minutes
|
|
resolution_minutes
|
|
is_active
|
|
```
|
|
|
|
مثلاً Manager میتواند مقادیر را بعداً تعیین کند.
|
|
|
|
---
|
|
|
|
# 103. SLA Snapshot
|
|
|
|
وقتی Ticket ساخته میشود:
|
|
|
|
```text
|
|
first_response_due_at
|
|
resolution_due_at
|
|
```
|
|
|
|
در Ticket ذخیره شود.
|
|
|
|
در نتیجه اگر SLA Config فردا تغییر کند، Deadline Ticket قبلی عوض نمیشود.
|
|
|
|
---
|
|
|
|
# 104. SLA V1
|
|
|
|
در V1 SLA بر اساس elapsed clock time محاسبه شود.
|
|
|
|
محاسبه ساعات اداری، تعطیلات و Calendar Business Hours خارج از Scope اولیه است و میتواند فاز بعد باشد.
|
|
|
|
---
|
|
|
|
# 105. First Response
|
|
|
|
`first_response_at`
|
|
|
|
زمان اولین PUBLIC Message از سمت Staff است.
|
|
|
|
Internal Note پاسخ محسوب نمیشود.
|
|
|
|
---
|
|
|
|
# 106. Security — XSS
|
|
|
|
متن Customer و Expert نباید Raw HTML Render شود.
|
|
|
|
مثلاً این:
|
|
|
|
```html
|
|
<script>alert(1)</script>
|
|
```
|
|
|
|
نباید اجرا شود.
|
|
|
|
Django Autoescape باید فعال بماند.
|
|
|
|
Message content Plain Text باشد.
|
|
|
|
---
|
|
|
|
# 107. CSRF
|
|
|
|
تمام POST Requestها باید CSRF Protection داشته باشند.
|
|
|
|
HTMX نیز CSRF Token ارسال کند.
|
|
|
|
---
|
|
|
|
# 108. Session Security
|
|
|
|
Production:
|
|
|
|
```text
|
|
SESSION_COOKIE_SECURE = True
|
|
CSRF_COOKIE_SECURE = True
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SameSite = Lax
|
|
```
|
|
|
|
TLS/HTTPS اجباری است.
|
|
|
|
---
|
|
|
|
# 109. Authorization Attachment
|
|
|
|
Download فایل باید همان Permission Ticket را بررسی کند.
|
|
|
|
کسی که Ticket را نمیتواند ببیند نباید فایل آن را هم Download کند.
|
|
|
|
---
|
|
|
|
# 110. Token Logging
|
|
|
|
Raw:
|
|
|
|
- OTP
|
|
- Magic Link Token
|
|
- Password
|
|
- API Token
|
|
|
|
نباید در Log ثبت شوند.
|
|
|
|
---
|
|
|
|
# 111. External API Secrets
|
|
|
|
SMS Token و Bale Token فقط از:
|
|
|
|
```text
|
|
Environment Variables
|
|
```
|
|
|
|
یا Secret Management خوانده شوند.
|
|
|
|
داخل Git ذخیره نشوند.
|
|
|
|
---
|
|
|
|
# 112. File Upload Security
|
|
|
|
Nginx global upload limit باید کمی بیشتر از بزرگترین File Policy باشد.
|
|
|
|
Server application دوباره Policy دقیق هر File را بررسی کند.
|
|
|
|
---
|
|
|
|
# 113. HTML/JS Frontend Strategy
|
|
|
|
Frontend SPA نیست.
|
|
|
|
Routing Server-side Django است.
|
|
|
|
HTMX برای:
|
|
|
|
- Reply
|
|
- Status change
|
|
- Claim
|
|
- Transfer
|
|
- Share
|
|
- Filter
|
|
- Pagination
|
|
- Notes
|
|
- Edit
|
|
- Delete
|
|
- Counter refresh
|
|
|
|
استفاده شود.
|
|
|
|
---
|
|
|
|
# 114. Alpine.js
|
|
|
|
Alpine برای Stateهای کوچک Frontend:
|
|
|
|
```text
|
|
Modal
|
|
Dropdown
|
|
Voice recorder state
|
|
File preview
|
|
Attachment queue
|
|
Tabs
|
|
Confirm dialogs
|
|
```
|
|
|
|
استفاده شود.
|
|
|
|
---
|
|
|
|
# 115. Vanilla JavaScript
|
|
|
|
برای APIهای Browser مانند:
|
|
|
|
```text
|
|
MediaRecorder
|
|
File API
|
|
Clipboard
|
|
Drag & Drop
|
|
```
|
|
|
|
Vanilla JavaScript استفاده شود.
|
|
|
|
---
|
|
|
|
# 116. Tailwind CSS
|
|
|
|
UI کاملاً Responsive باشد.
|
|
|
|
RTL فارسی در Design System از ابتدا لحاظ شود.
|
|
|
|
HTML Root:
|
|
|
|
```html
|
|
<html lang="fa" dir="rtl">
|
|
```
|
|
|
|
---
|
|
|
|
# 117. تاریخ
|
|
|
|
DateTime داخل Database استاندارد و timezone-aware ذخیره شود.
|
|
|
|
Backend بهتر است UTC نگهداری کند.
|
|
|
|
در UI بر اساس Timezone موردنظر شرکت نمایش داده شود.
|
|
|
|
Jalali صرفاً Presentation Layer است و نباید نوع Date Database را تغییر دهد.
|
|
|
|
---
|
|
|
|
# 118. Suggested URLs — Customer
|
|
|
|
```text
|
|
/login/
|
|
/login/otp/
|
|
/logout/
|
|
|
|
/profile/
|
|
|
|
/tickets/
|
|
/tickets/new/
|
|
/tickets/<uuid>/
|
|
/tickets/<uuid>/reply/
|
|
|
|
/t/<magic-token>/
|
|
```
|
|
|
|
---
|
|
|
|
# 119. Suggested URLs — Staff
|
|
|
|
```text
|
|
/staff/login/
|
|
/staff/logout/
|
|
|
|
/staff/
|
|
/staff/tickets/
|
|
/staff/tickets/<uuid>/
|
|
|
|
/staff/tickets/<uuid>/claim/
|
|
/staff/tickets/<uuid>/transfer/
|
|
/staff/tickets/<uuid>/share/
|
|
/staff/tickets/<uuid>/status/
|
|
/staff/tickets/<uuid>/priority/
|
|
|
|
/staff/tickets/<uuid>/reply/
|
|
/staff/tickets/<uuid>/note/
|
|
```
|
|
|
|
---
|
|
|
|
# 120. Message URLs
|
|
|
|
```text
|
|
/messages/<uuid>/edit/
|
|
/messages/<uuid>/delete/
|
|
/messages/<uuid>/revisions/
|
|
```
|
|
|
|
Permission Server Side اجباری است.
|
|
|
|
---
|
|
|
|
# 121. Attachment URL
|
|
|
|
```text
|
|
/files/<uuid>/download/
|
|
```
|
|
|
|
هیچ Raw media URL عمومی وجود نداشته باشد.
|
|
|
|
---
|
|
|
|
# 122. Django Admin
|
|
|
|
در V1 استفاده از Django Admin برای Configurationهای داخلی کاملاً قابل قبول است.
|
|
|
|
مثلاً:
|
|
|
|
- Categories
|
|
- File Policies
|
|
- SLA
|
|
- Groups
|
|
- Permissions
|
|
- Staff
|
|
- Customer types
|
|
|
|
نیازی نیست از روز اول برای همه تنظیمات Custom Admin Panel نوشته شود.
|
|
|
|
---
|
|
|
|
# 123. Database Constraints
|
|
|
|
حداقل موارد زیر Constraint داشته باشند:
|
|
|
|
```text
|
|
mobile unique
|
|
ticket_number unique
|
|
public_id unique
|
|
one TicketReadState per ticket/user
|
|
one active TicketAccessGrant per ticket/user
|
|
```
|
|
|
|
---
|
|
|
|
# 124. Database Indexes
|
|
|
|
Index روی:
|
|
|
|
```text
|
|
Ticket.status
|
|
Ticket.priority
|
|
Ticket.owner
|
|
Ticket.selected_expert
|
|
Ticket.customer
|
|
Ticket.category
|
|
Ticket.created_at
|
|
Ticket.last_activity_at
|
|
|
|
Message.ticket
|
|
Message.created_at
|
|
|
|
NotificationJob.status
|
|
NotificationJob.next_attempt_at
|
|
|
|
OTPChallenge.mobile
|
|
OTPChallenge.created_at
|
|
```
|
|
|
|
ایجاد شود.
|
|
|
|
---
|
|
|
|
# 125. Transactions
|
|
|
|
عملیات زیر Atomic باشند:
|
|
|
|
```text
|
|
Claim Ticket
|
|
Transfer
|
|
Share/Revoke
|
|
Customer Reply + Reopen
|
|
Expert Reply + Status Update
|
|
Message Edit + Revision
|
|
Message Soft Delete
|
|
```
|
|
|
|
---
|
|
|
|
# 126. Service — create_ticket
|
|
|
|
ورودی:
|
|
|
|
```text
|
|
customer
|
|
category
|
|
subject
|
|
selected_expert
|
|
body
|
|
attachments
|
|
```
|
|
|
|
عملیات:
|
|
|
|
1. Validation
|
|
2. Create Ticket
|
|
3. Create Initial Message
|
|
4. Save Attachments
|
|
5. Create TicketEvent
|
|
6. Update timestamps
|
|
7. Queue Bale Notification
|
|
|
|
همه عملیات Database باید Transactional باشند.
|
|
|
|
---
|
|
|
|
# 127. Service — reply_ticket
|
|
|
|
بررسی:
|
|
|
|
```text
|
|
can_reply()
|
|
```
|
|
|
|
سپس:
|
|
|
|
- Create Message
|
|
- Attach Files
|
|
- Update last activity
|
|
- Update waiting status
|
|
- Generate Notification Job
|
|
- Reopen اگر لازم است
|
|
|
|
---
|
|
|
|
# 128. Status بعد از Customer Reply
|
|
|
|
اگر Ticket:
|
|
|
|
```text
|
|
OPEN
|
|
WAITING_CUSTOMER
|
|
```
|
|
|
|
باشد:
|
|
|
|
```text
|
|
WAITING_STAFF
|
|
```
|
|
|
|
شود.
|
|
|
|
اگر:
|
|
|
|
```text
|
|
RESOLVED
|
|
CLOSED
|
|
```
|
|
|
|
باشد:
|
|
|
|
```text
|
|
REOPENED
|
|
```
|
|
|
|
شود.
|
|
|
|
---
|
|
|
|
# 129. Status بعد از Staff Reply
|
|
|
|
بعد از PUBLIC Staff reply:
|
|
|
|
```text
|
|
WAITING_CUSTOMER
|
|
```
|
|
|
|
شود.
|
|
|
|
اگر اولین Staff reply است:
|
|
|
|
```text
|
|
first_response_at
|
|
```
|
|
|
|
ثبت شود.
|
|
|
|
---
|
|
|
|
# 130. Claim Status
|
|
|
|
هنگام Claim یک Ticket `NEW`:
|
|
|
|
```text
|
|
OPEN
|
|
```
|
|
|
|
شود.
|
|
|
|
Claim لزوماً Staff Reply محسوب نمیشود.
|
|
|
|
---
|
|
|
|
# 131. Resolved vs Closed
|
|
|
|
`RESOLVED`:
|
|
|
|
کارشناس معتقد است مسئله حل شده ولی Ticket هنوز منطقی قابل ادامه است.
|
|
|
|
`CLOSED`:
|
|
|
|
Ticket رسماً بسته شده است.
|
|
|
|
هر دو با Customer reply میتوانند Reopen شوند.
|
|
|
|
---
|
|
|
|
# 132. Internal Note Status
|
|
|
|
Internal Note نباید Ticket Status را به:
|
|
|
|
```text
|
|
WAITING_CUSTOMER
|
|
```
|
|
|
|
تغییر دهد.
|
|
|
|
---
|
|
|
|
# 133. Mention Notification
|
|
|
|
Internal Note نیز میتواند Mention داشته باشد.
|
|
|
|
اگر Expert دسترسی دارد، Bale Notification ارسال شود.
|
|
|
|
---
|
|
|
|
# 134. Notification Failure
|
|
|
|
Failure SMS/Bale نباید باعث Rollback شدن Message شود.
|
|
|
|
External Notification Eventual Delivery است.
|
|
|
|
---
|
|
|
|
# 135. Retry
|
|
|
|
Notification Worker:
|
|
|
|
```text
|
|
attempt 1
|
|
attempt 2
|
|
attempt 3
|
|
...
|
|
```
|
|
|
|
با Backoff انجام دهد.
|
|
|
|
تعداد Max Retry Configurable باشد.
|
|
|
|
Failure نهایی در Admin قابل مشاهده باشد.
|
|
|
|
---
|
|
|
|
# 136. Logging
|
|
|
|
Application Logging شامل:
|
|
|
|
```text
|
|
request errors
|
|
provider errors
|
|
notification failures
|
|
permission denial
|
|
worker errors
|
|
file upload failures
|
|
```
|
|
|
|
باشد.
|
|
|
|
ولی Sensitive Data در Log ماسک شود.
|
|
|
|
---
|
|
|
|
# 137. Docker Architecture
|
|
|
|
Production Compose:
|
|
|
|
```text
|
|
nginx
|
|
web
|
|
worker
|
|
db
|
|
```
|
|
|
|
ساختار:
|
|
|
|
```text
|
|
Internet
|
|
↓
|
|
Nginx
|
|
↓
|
|
Gunicorn
|
|
↓
|
|
Django
|
|
↓
|
|
PostgreSQL
|
|
```
|
|
|
|
Worker:
|
|
|
|
```text
|
|
PostgreSQL
|
|
↑
|
|
Django Notification Worker
|
|
↓
|
|
SMS / Bale APIs
|
|
```
|
|
|
|
---
|
|
|
|
# 138. Docker Volumes
|
|
|
|
```text
|
|
postgres_data
|
|
media_data
|
|
static_data
|
|
```
|
|
|
|
Media Volume باید Persistent باشد.
|
|
|
|
Container Recreation نباید فایل Customer را حذف کند.
|
|
|
|
---
|
|
|
|
# 139. Redis
|
|
|
|
در V1:
|
|
|
|
```text
|
|
Redis = NOT REQUIRED
|
|
```
|
|
|
|
دلایل:
|
|
|
|
- فقط 20 Expert
|
|
- حدود 200 Customer
|
|
- WebSocket نداریم
|
|
- Cache سنگین نداریم
|
|
- Notification Queue میتواند DB-backed باشد
|
|
|
|
---
|
|
|
|
# 140. زمان اضافهشدن Redis
|
|
|
|
Redis فقط در صورت نیاز واقعی اضافه شود، مثلاً:
|
|
|
|
```text
|
|
WebSocket
|
|
High-volume queue
|
|
Heavy cache
|
|
Distributed rate limiting
|
|
Thousands of concurrent sessions
|
|
```
|
|
|
|
---
|
|
|
|
# 141. Celery
|
|
|
|
Celery نیز در V1 لازم نیست.
|
|
|
|
Database Notification Worker کافی است.
|
|
|
|
اگر Background Tasks بسیار زیاد شدند:
|
|
|
|
```text
|
|
Redis + Celery
|
|
```
|
|
|
|
بدون تغییر Business Logic اصلی قابل اضافهشدن باشد.
|
|
|
|
---
|
|
|
|
# 142. Backup
|
|
|
|
چون فایلها Local هستند، Backup بخش حیاتی سیستم است.
|
|
|
|
Backup باید شامل:
|
|
|
|
```text
|
|
PostgreSQL
|
|
Media Volume
|
|
Environment configuration backup
|
|
```
|
|
|
|
باشد.
|
|
|
|
---
|
|
|
|
# 143. PostgreSQL Backup
|
|
|
|
حداقل Automated:
|
|
|
|
```text
|
|
pg_dump
|
|
```
|
|
|
|
به صورت منظم انجام شود.
|
|
|
|
---
|
|
|
|
# 144. Media Backup
|
|
|
|
`media_data` باید جدا Backup شود.
|
|
|
|
فقط Database Backup کافی نیست.
|
|
|
|
زیرا Attachmentها داخل DB ذخیره نمیشوند.
|
|
|
|
---
|
|
|
|
# 145. Restore Test
|
|
|
|
تیم DevOps باید Restore Database + Media را حداقل قبل از Production Release تست کند.
|
|
|
|
Backup بدون Restore Test قابل اعتماد نیست.
|
|
|
|
---
|
|
|
|
# 146. Development Environment
|
|
|
|
Development:
|
|
|
|
```text
|
|
Docker Compose
|
|
PostgreSQL
|
|
Django runserver/Gunicorn optional
|
|
Tailwind watcher
|
|
```
|
|
|
|
SQLite فقط برای تست بسیار ساده محلی قابل استفاده است.
|
|
|
|
Environment اصلی Development و Production بهتر است PostgreSQL باشد تا اختلاف رفتار کاهش یابد.
|
|
|
|
---
|
|
|
|
# 147. Database نهایی
|
|
|
|
انتخاب قطعی:
|
|
|
|
```text
|
|
PostgreSQL
|
|
```
|
|
|
|
SQLite برای Production این پروژه توصیه نمیشود.
|
|
|
|
---
|
|
|
|
# 148. API
|
|
|
|
در V1 نیازی به Django REST Framework نیست.
|
|
|
|
چون:
|
|
|
|
- Mobile App نداریم.
|
|
- SPA نداریم.
|
|
- Frontend Django Template است.
|
|
|
|
Viewهای Django + HTMX کافی هستند.
|
|
|
|
---
|
|
|
|
# 149. DRF Future
|
|
|
|
Service Layer باید طوری نوشته شود که در آینده اگر:
|
|
|
|
- Mobile App
|
|
- Public API
|
|
- CRM Integration
|
|
|
|
ایجاد شد، DRF بتواند همان Serviceها را استفاده کند.
|
|
|
|
---
|
|
|
|
# 150. Testing Strategy
|
|
|
|
سه سطح اصلی:
|
|
|
|
```text
|
|
Unit Tests
|
|
Service Tests
|
|
Integration/View Tests
|
|
```
|
|
|
|
---
|
|
|
|
# 151. Permission Tests
|
|
|
|
مهمترین Tests پروژه Permission Tests هستند.
|
|
|
|
مثلاً باید ثابت شود:
|
|
|
|
Customer A هرگز Ticket Customer B را نمیبیند.
|
|
|
|
Expert A نباید Ticket Private Expert B را ببیند.
|
|
|
|
Expert نباید بعد از Lock توسط Expert دیگر محتوای Ticket را ببیند.
|
|
|
|
Manager باید تمام موارد را ببیند.
|
|
|
|
---
|
|
|
|
# 152. Claim Concurrency Test
|
|
|
|
Test همزمانی ضروری:
|
|
|
|
دو Expert همزمان Claim میکنند.
|
|
|
|
فقط یکی موفق شود.
|
|
|
|
---
|
|
|
|
# 153. Magic Link Tests
|
|
|
|
تست:
|
|
|
|
```text
|
|
valid link
|
|
expired link
|
|
used link
|
|
revoked link
|
|
wrong ticket
|
|
wrong customer
|
|
invalid token
|
|
```
|
|
|
|
---
|
|
|
|
# 154. OTP Tests
|
|
|
|
تست:
|
|
|
|
```text
|
|
valid OTP
|
|
expired OTP
|
|
wrong OTP
|
|
maximum attempts
|
|
resend cooldown
|
|
rate limit
|
|
```
|
|
|
|
---
|
|
|
|
# 155. Message Tests
|
|
|
|
تست:
|
|
|
|
```text
|
|
edit
|
|
revision creation
|
|
soft delete
|
|
manager sees deleted content
|
|
customer sees tombstone
|
|
```
|
|
|
|
---
|
|
|
|
# 156. Attachment Tests
|
|
|
|
تست:
|
|
|
|
```text
|
|
valid type
|
|
invalid type
|
|
size too large
|
|
multiple files
|
|
private download
|
|
unauthorized download
|
|
```
|
|
|
|
---
|
|
|
|
# 157. Ticket Status Tests
|
|
|
|
تمام Transitionها تست شوند.
|
|
|
|
خصوصاً:
|
|
|
|
```text
|
|
CLOSED + customer reply → REOPENED
|
|
RESOLVED + customer reply → REOPENED
|
|
staff public reply → WAITING_CUSTOMER
|
|
customer reply → WAITING_STAFF
|
|
internal note → no waiting change
|
|
```
|
|
|
|
---
|
|
|
|
# 158. Notification Tests
|
|
|
|
External APIها Mock شوند.
|
|
|
|
Test شود:
|
|
|
|
```text
|
|
SMS queued
|
|
Bale queued
|
|
retry
|
|
failure
|
|
deduplication
|
|
```
|
|
|
|
---
|
|
|
|
# 159. Coding Standards
|
|
|
|
پیشنهاد:
|
|
|
|
```text
|
|
Ruff
|
|
Black-compatible formatting
|
|
Type hints for Services
|
|
pytest
|
|
pytest-django
|
|
```
|
|
|
|
Business-critical Functions Type Hint داشته باشند.
|
|
|
|
---
|
|
|
|
# 160. JavaScript Standards
|
|
|
|
JavaScript پیچیده Global نوشته نشود.
|
|
|
|
Componentها جدا:
|
|
|
|
```text
|
|
voice-recorder.js
|
|
attachment-uploader.js
|
|
ticket-actions.js
|
|
mention-autocomplete.js
|
|
```
|
|
|
|
---
|
|
|
|
# 161. Template Structure
|
|
|
|
```text
|
|
templates/
|
|
├── base.html
|
|
├── customer/
|
|
├── staff/
|
|
├── tickets/
|
|
│ └── partials/
|
|
└── components/
|
|
```
|
|
|
|
HTMX partialها از Page Templateها جدا باشند.
|
|
|
|
---
|
|
|
|
# 162. HTMX Conventions
|
|
|
|
HTMX Endpoint باید در صورت:
|
|
|
|
```text
|
|
HX-Request
|
|
```
|
|
|
|
Partial برگرداند.
|
|
|
|
و در Request عادی Full Page یا Redirect مناسب بدهد.
|
|
|
|
---
|
|
|
|
# 163. Error UX
|
|
|
|
Errorها به فارسی قابل فهم باشند.
|
|
|
|
بد:
|
|
|
|
> IntegrityError
|
|
|
|
خوب:
|
|
|
|
> این تیکت لحظاتی قبل توسط کارشناس دیگری برداشته شده است.
|
|
|
|
---
|
|
|
|
# 164. Empty States
|
|
|
|
صفحات Empty State مناسب داشته باشند.
|
|
|
|
مثلاً:
|
|
|
|
> هنوز تیکتی ثبت نکردهاید.
|
|
|
|
و CTA:
|
|
|
|
> ثبت تیکت جدید
|
|
|
|
---
|
|
|
|
# 165. Loading States
|
|
|
|
برای:
|
|
|
|
- Upload
|
|
- Reply
|
|
- Claim
|
|
- Transfer
|
|
|
|
Loading/Disabled State وجود داشته باشد تا Double Submit رخ ندهد.
|
|
|
|
---
|
|
|
|
# 166. Responsive Design
|
|
|
|
Customer UI Mobile-first باشد.
|
|
|
|
چون Magic Link از SMS احتمالاً روی Mobile باز میشود.
|
|
|
|
Staff UI Desktop-first ولی Responsive باشد.
|
|
|
|
---
|
|
|
|
# 167. Accessibility
|
|
|
|
حداقل:
|
|
|
|
- Label برای Formها
|
|
- Keyboard navigation
|
|
- Button semantic
|
|
- Contrast مناسب
|
|
- Focus state
|
|
|
|
رعایت شود.
|
|
|
|
---
|
|
|
|
# 168. No Hard Delete Policy
|
|
|
|
در Business Data:
|
|
|
|
- Ticket
|
|
- Message
|
|
- Attachment
|
|
|
|
Hard Delete از UI نداشته باشیم.
|
|
|
|
در صورت نیاز Maintenance Admin جدا و بسیار محدود باشد.
|
|
|
|
---
|
|
|
|
# 169. Ticket Deletion
|
|
|
|
در V1 Customer و Expert نباید Ticket را Delete کنند.
|
|
|
|
Ticket میتواند Closed/Archived شود.
|
|
|
|
---
|
|
|
|
# 170. Archive
|
|
|
|
`ARCHIVED` فعلاً لازم نیست Status اصلی باشد.
|
|
|
|
در آینده میتواند به عنوان Flag:
|
|
|
|
```text
|
|
is_archived
|
|
```
|
|
|
|
اضافه شود.
|
|
|
|
در V1 Closed کافی است.
|
|
|
|
---
|
|
|
|
# 171. Selected Expert Availability
|
|
|
|
فقط Staff فعال و Customer-selectable در انتخاب Customer نمایش داده شود.
|
|
|
|
اگر Expert بعداً غیرفعال شد:
|
|
|
|
Ticketهای قبلی حذف نمیشوند.
|
|
|
|
Manager میتواند آنها را Transfer کند.
|
|
|
|
---
|
|
|
|
# 172. Customer Disable
|
|
|
|
اگر Customer غیرفعال شود:
|
|
|
|
Login جدید نداشته باشد.
|
|
|
|
سوابق Ticket حذف نشوند.
|
|
|
|
---
|
|
|
|
# 173. Staff Disable
|
|
|
|
Staff غیرفعال:
|
|
|
|
- Login ندارد.
|
|
- در Expert Selection نمایش داده نمیشود.
|
|
- History باقی میماند.
|
|
|
|
Ticketهای Active او باید توسط Manager قابل Transfer باشند.
|
|
|
|
---
|
|
|
|
# 174. Ticket Creation Notification
|
|
|
|
اگر Customer Expert انتخاب کرده:
|
|
|
|
Bale فقط برای همان Expert.
|
|
|
|
اگر انتخاب نکرده:
|
|
|
|
Bale برای Expertهای فعال و مجاز Queue عمومی.
|
|
|
|
---
|
|
|
|
# 175. Customer SMS
|
|
|
|
SMS فقط برای Public Staff Reply ارسال شود.
|
|
|
|
این موارد SMS ایجاد نکنند:
|
|
|
|
```text
|
|
Internal Note
|
|
Claim
|
|
Transfer
|
|
Priority change
|
|
Status internal changes
|
|
Mention
|
|
```
|
|
|
|
مگر در آینده Policy تغییر کند.
|
|
|
|
---
|
|
|
|
# 176. Message Composer Staff
|
|
|
|
Composer Staff حداقل Mode داشته باشد:
|
|
|
|
```text
|
|
پاسخ به مشتری
|
|
یادداشت داخلی
|
|
```
|
|
|
|
رنگ و ظاهر دو Mode کاملاً متفاوت باشد تا Expert اشتباهاً Note داخلی را برای Customer ارسال نکند.
|
|
|
|
---
|
|
|
|
# 177. Internal Note Safety
|
|
|
|
هنگام `INTERNAL_NOTE`:
|
|
|
|
Button باید واضح باشد:
|
|
|
|
> ثبت یادداشت داخلی
|
|
|
|
نه:
|
|
|
|
> ارسال
|
|
|
|
---
|
|
|
|
# 178. Attachment در Internal Note
|
|
|
|
Internal Note میتواند Attachment داشته باشد.
|
|
|
|
Customer نباید به آن دسترسی پیدا کند.
|
|
|
|
حتی اگر URL فایل را حدس بزند، Authorization باید مانع شود.
|
|
|
|
---
|
|
|
|
# 179. Message Ordering
|
|
|
|
Messageها بر اساس:
|
|
|
|
```text
|
|
created_at
|
|
id
|
|
```
|
|
|
|
ترتیب پایدار داشته باشند.
|
|
|
|
---
|
|
|
|
# 180. Pagination
|
|
|
|
اگر Ticket طولانی شد، Messageها نباید بدون محدودیت یکجا Load شوند.
|
|
|
|
در V1 میتوان آخرین مثلاً 50 Message را Load کرد و:
|
|
|
|
> نمایش پیامهای قبلی
|
|
|
|
داشت.
|
|
|
|
---
|
|
|
|
# 181. Ticket Philosophy
|
|
|
|
با وجود Conversation UI داخل Ticket، Customer باید برای موضوع متفاوت Ticket جدید ثبت کند.
|
|
|
|
در UI میتوان Text راهنما نمایش داد:
|
|
|
|
> اگر سؤال شما مربوط به موضوع دیگری است، لطفاً تیکت جدید ثبت کنید.
|
|
|
|
---
|
|
|
|
# 182. Initial Page Performance
|
|
|
|
Dashboard Staff نباید N+1 Query داشته باشد.
|
|
|
|
استفاده مناسب از:
|
|
|
|
```python
|
|
select_related()
|
|
prefetch_related()
|
|
```
|
|
|
|
ضروری است.
|
|
|
|
---
|
|
|
|
# 183. Query Count
|
|
|
|
Ticket List نباید برای هر Ticket Query جدا برای:
|
|
|
|
- Customer
|
|
- Category
|
|
- Owner
|
|
|
|
اجرا کند.
|
|
|
|
---
|
|
|
|
# 184. Database JSON
|
|
|
|
JSONField فقط برای Metadata متغیر مثل:
|
|
|
|
```text
|
|
TicketEvent.metadata
|
|
NotificationJob payload
|
|
```
|
|
|
|
استفاده شود.
|
|
|
|
Core Domain Data داخل JSON ذخیره نشود.
|
|
|
|
---
|
|
|
|
# 185. System Configuration
|
|
|
|
تنظیمات Runtime که مدیر ممکن است تغییر دهد بهتر است DB-driven باشند.
|
|
|
|
مثلاً:
|
|
|
|
```text
|
|
AttachmentPolicy
|
|
SLAConfig
|
|
TicketCategory
|
|
```
|
|
|
|
Secrets و Infrastructure settings در Environment بمانند.
|
|
|
|
---
|
|
|
|
# 186. Out of Scope V1
|
|
|
|
موارد زیر در V1 اجرا نشوند مگر Requirement جدید ایجاد شود:
|
|
|
|
```text
|
|
WebSocket
|
|
Live typing
|
|
Online status
|
|
Reaction
|
|
Video call
|
|
Voice call
|
|
Mobile app
|
|
Multi-company
|
|
Team hierarchy
|
|
Product catalog
|
|
ERP integration
|
|
Advanced BI
|
|
Elasticsearch
|
|
AI chatbot
|
|
S3
|
|
Object Storage
|
|
Kafka
|
|
Microservices
|
|
```
|
|
|
|
---
|
|
|
|
# 187. Recommended Development Phases
|
|
|
|
## Phase 1 — Foundation
|
|
|
|
- Django project
|
|
- Custom User
|
|
- PostgreSQL
|
|
- Docker
|
|
- Authentication
|
|
- Staff login
|
|
- Customer OTP
|
|
- Basic permissions
|
|
|
|
## Phase 2 — Ticket Core
|
|
|
|
- Category
|
|
- Ticket
|
|
- Status
|
|
- Priority
|
|
- Customer selection Expert
|
|
- Public Queue
|
|
- Claim
|
|
- Ticket detail
|
|
|
|
## Phase 3 — Messaging
|
|
|
|
- Reply
|
|
- Edit
|
|
- Revision
|
|
- Soft delete
|
|
- Internal Note
|
|
- Mentions
|
|
|
|
## Phase 4 — Permission
|
|
|
|
- Private selected Expert
|
|
- Locked Ticket
|
|
- TicketAccessGrant
|
|
- Share
|
|
- Revoke
|
|
- Transfer
|
|
- Manager full access
|
|
|
|
## Phase 5 — Attachments
|
|
|
|
- Images
|
|
- PDF
|
|
- Word
|
|
- Excel
|
|
- ZIP/RAR
|
|
- Video
|
|
- Multiple Attachments
|
|
- Private downloads
|
|
|
|
## Phase 6 — Voice
|
|
|
|
- MediaRecorder
|
|
- Preview
|
|
- Timer
|
|
- Upload
|
|
|
|
## Phase 7 — Notifications
|
|
|
|
- SMS Provider abstraction
|
|
- Bale Provider
|
|
- Database Outbox
|
|
- Worker
|
|
- Retry
|
|
|
|
## Phase 8 — Magic Link
|
|
|
|
- Secure token
|
|
- SMS link
|
|
- Session bootstrap
|
|
- 7-day expiry
|
|
- OTP fallback
|
|
|
|
## Phase 9 — Dashboard & SLA
|
|
|
|
- Filters
|
|
- Search
|
|
- Unread
|
|
- SLA metrics
|
|
- Customer history
|
|
|
|
## Phase 10 — Production Hardening
|
|
|
|
- Tests
|
|
- Permission tests
|
|
- Upload security
|
|
- Nginx protected media
|
|
- Logging
|
|
- Backup
|
|
- Docker production
|
|
- Security review
|
|
|
|
---
|
|
|
|
# 188. Definition of Done
|
|
|
|
یک Feature فقط زمانی Done محسوب شود که:
|
|
|
|
1. Business Logic تکمیل باشد.
|
|
2. Permission Server-side داشته باشد.
|
|
3. Validation داشته باشد.
|
|
4. Error UX مناسب داشته باشد.
|
|
5. Test داشته باشد.
|
|
6. Responsive باشد.
|
|
7. Security آن بررسی شده باشد.
|
|
8. Logging مناسب داشته باشد.
|
|
9. در Docker Production قابل اجرا باشد.
|
|
|
|
---
|
|
|
|
# 189. Acceptance Criteria — Customer Login
|
|
|
|
- Customer شماره موبایل وارد میکند.
|
|
- OTP دریافت میکند.
|
|
- OTP صحیح Login ایجاد میکند.
|
|
- OTP اشتباه محدود میشود.
|
|
- OTP منقضی Reject میشود.
|
|
- Customer بدون Password قابل استفاده است.
|
|
- Customer میتواند بعداً Password تعیین کند.
|
|
|
|
---
|
|
|
|
# 190. Acceptance Criteria — New Ticket
|
|
|
|
Customer باید بتواند:
|
|
|
|
- Category انتخاب کند.
|
|
- Subject وارد کند.
|
|
- Expert انتخاب کند یا انتخاب نکند.
|
|
- Message بنویسد.
|
|
- چند Attachment اضافه کند.
|
|
- Ticket ثبت کند.
|
|
|
|
Ticket در Dashboard صحیح Expertها ظاهر شود.
|
|
|
|
---
|
|
|
|
# 191. Acceptance Criteria — Selected Expert
|
|
|
|
اگر Customer احمد را انتخاب کند:
|
|
|
|
- احمد Full Ticket را میبیند.
|
|
- رضا Ticket را نمیبیند.
|
|
- Manager Ticket را میبیند.
|
|
|
|
---
|
|
|
|
# 192. Acceptance Criteria — Public Ticket
|
|
|
|
اگر Customer Expert انتخاب نکند:
|
|
|
|
- همه Expertهای مجاز Ticket را میبینند.
|
|
- همه تا قبل از Claim میتوانند پاسخ دهند.
|
|
|
|
---
|
|
|
|
# 193. Acceptance Criteria — Claim
|
|
|
|
وقتی احمد Ticket را Claim کرد:
|
|
|
|
- احمد Full Access دارد.
|
|
- رضا فقط Title + Owner را میبیند.
|
|
- رضا Content را نمیبیند.
|
|
- رضا Reply نمیتواند بکند.
|
|
- Manager Full Access دارد.
|
|
|
|
---
|
|
|
|
# 194. Acceptance Criteria — Shared Access
|
|
|
|
احمد میتواند رضا را Share کند.
|
|
|
|
بعد از Share:
|
|
|
|
- رضا Full Content را میبیند.
|
|
- رضا Reply میکند.
|
|
- Access قابل Revoke است.
|
|
|
|
---
|
|
|
|
# 195. Acceptance Criteria — Transfer
|
|
|
|
احمد Ticket را به رضا Transfer میکند.
|
|
|
|
- Owner = رضا
|
|
- تاریخچه Transfer ثبت میشود.
|
|
- نیاز به Manager Approval نیست.
|
|
- دسترسی احمد به صورت Default حذف میشود.
|
|
|
|
---
|
|
|
|
# 196. Acceptance Criteria — Edit
|
|
|
|
Expert/Customer Message خود را Edit میکند.
|
|
|
|
- متن جدید نمایش داده میشود.
|
|
- `edited` نمایش داده میشود.
|
|
- متن قبلی در Revision باقی میماند.
|
|
- Manager مجاز Revision را میبیند.
|
|
|
|
---
|
|
|
|
# 197. Acceptance Criteria — Delete
|
|
|
|
Expert Message خودش را Delete میکند.
|
|
|
|
Customer:
|
|
|
|
> این پیام توسط فرستنده حذف شده است.
|
|
|
|
Manager:
|
|
|
|
- متن اصلی
|
|
- Attachment
|
|
- زمان حذف
|
|
|
|
را میبیند.
|
|
|
|
---
|
|
|
|
# 198. Acceptance Criteria — Voice
|
|
|
|
در Browser:
|
|
|
|
- Record شروع شود.
|
|
- Timer کار کند.
|
|
- Stop کار کند.
|
|
- Preview کار کند.
|
|
- Delete/Re-record کار کند.
|
|
- Voice ارسال شود.
|
|
- در Ticket قابل پخش باشد.
|
|
|
|
---
|
|
|
|
# 199. Acceptance Criteria — Magic Link
|
|
|
|
Expert پاسخ میدهد.
|
|
|
|
Customer SMS دریافت میکند.
|
|
|
|
Customer Link را باز میکند.
|
|
|
|
بدون OTP:
|
|
|
|
- Ticket باز میشود.
|
|
- Message دیده میشود.
|
|
- Reply ممکن است.
|
|
|
|
Token بعد از مصرف از URL حذف شود.
|
|
|
|
Link منقضی Customer را به OTP هدایت کند.
|
|
|
|
---
|
|
|
|
# 200. Acceptance Criteria — Closed/Reopen
|
|
|
|
Expert Ticket را Close میکند.
|
|
|
|
Customer دوباره Reply میکند.
|
|
|
|
سیستم:
|
|
|
|
```text
|
|
CLOSED → REOPENED
|
|
```
|
|
|
|
کند.
|
|
|
|
Expert مربوطه Notification دریافت کند.
|
|
|
|
---
|
|
|
|
# 201. Acceptance Criteria — Internal Note
|
|
|
|
Expert Note ثبت میکند.
|
|
|
|
- Staff مجاز آن را میبینند.
|
|
- Customer آن را نمیبیند.
|
|
- SMS Customer ارسال نمیشود.
|
|
- Note ظاهری متفاوت دارد.
|
|
|
|
---
|
|
|
|
# 202. Acceptance Criteria — Mention
|
|
|
|
Expert مجاز:
|
|
|
|
```text
|
|
@reza
|
|
```
|
|
|
|
را Mention میکند.
|
|
|
|
اگر Reza Access دارد:
|
|
|
|
- Mention ذخیره شود.
|
|
- Bale Notification ارسال شود.
|
|
|
|
اگر Access ندارد:
|
|
|
|
سیستم نباید اطلاعات Ticket را Leak کند و باید Share Flow انجام شود.
|
|
|
|
---
|
|
|
|
# 203. Performance Target
|
|
|
|
برای Scale فعلی:
|
|
|
|
صفحات عادی باید در شبکه Local/Server مناسب معمولاً بدون Query/Processing غیرضروری پاسخ سریع داشته باشند.
|
|
|
|
Performance مهمتر از معماری پیچیده است.
|
|
|
|
Caching در V1 ضرورتی ندارد.
|
|
|
|
---
|
|
|
|
# 204. Final Technology Decision
|
|
|
|
Stack قطعی:
|
|
|
|
```text
|
|
Python
|
|
Django
|
|
PostgreSQL
|
|
|
|
Django Templates
|
|
HTMX
|
|
Alpine.js
|
|
Vanilla JavaScript
|
|
Tailwind CSS
|
|
|
|
Gunicorn
|
|
Nginx
|
|
|
|
Docker
|
|
Docker Compose
|
|
|
|
Local Protected Media Storage
|
|
```
|
|
|
|
Notification:
|
|
|
|
```text
|
|
SMS Adapter
|
|
Bale Adapter
|
|
Database Outbox Worker
|
|
```
|
|
|
|
---
|
|
|
|
# 205. Technology Not Required
|
|
|
|
```text
|
|
React NO
|
|
Vue NO
|
|
Next.js NO
|
|
Django REST NOT REQUIRED V1
|
|
Redis NO
|
|
Celery NO
|
|
WebSocket NO
|
|
Django Channels NO
|
|
S3 NO
|
|
MinIO NO
|
|
Kafka NO
|
|
Microservices NO
|
|
Elasticsearch NO
|
|
```
|
|
|
|
---
|
|
|
|
# 206. مهمترین قوانین Business
|
|
|
|
این قواعد باید به عنوان قوانین غیرقابل نقض پروژه در نظر گرفته شوند:
|
|
|
|
### Rule 1
|
|
Manager دارای Permission کامل همیشه تمام Ticketها را میبیند.
|
|
|
|
### Rule 2
|
|
Customer فقط Ticketهای خودش را میبیند.
|
|
|
|
### Rule 3
|
|
اگر Customer Expert انتخاب کند، Ticket برای سایر Expertها Private است.
|
|
|
|
### Rule 4
|
|
Ticket بدون Expert ابتدا Public Queue است.
|
|
|
|
### Rule 5
|
|
بعد از Claim، Ticket Private میشود.
|
|
|
|
### Rule 6
|
|
بعد از Claim، Expertهای دیگر فقط Title و Owner را میبینند.
|
|
|
|
### Rule 7
|
|
Owner یا Manager میتواند Expert دیگری را Share کند.
|
|
|
|
### Rule 8
|
|
Transfer نیاز به Manager Approval ندارد.
|
|
|
|
### Rule 9
|
|
Message Edit باید Revision History داشته باشد.
|
|
|
|
### Rule 10
|
|
Expert Message Delete همیشه Soft Delete است.
|
|
|
|
### Rule 11
|
|
مدیر مجاز Original Deleted Message را میبیند.
|
|
|
|
### Rule 12
|
|
Internal Note هرگز به Customer نمایش داده نمیشود.
|
|
|
|
### Rule 13
|
|
Attachment Permission وابسته به Ticket Permission است.
|
|
|
|
### Rule 14
|
|
Customer Reply روی Closed/Resolved باعث Reopen میشود.
|
|
|
|
### Rule 15
|
|
Priority را Customer تعیین نمیکند.
|
|
|
|
### Rule 16
|
|
Notification failure نباید Message را Rollback کند.
|
|
|
|
### Rule 17
|
|
Magic Link باید Secure، Expiring و Revocable باشد.
|
|
|
|
### Rule 18
|
|
هیچ فایل Ticket به صورت Public Media URL سرو نشود.
|
|
|
|
### Rule 19
|
|
هیچ Role Name داخل Business Logic Hard-Code نشود.
|
|
|
|
### Rule 20
|
|
هر Permission حساس باید در Backend enforce شود.
|
|
|
|
---
|
|
|
|
# 207. معیار معماری
|
|
|
|
هدف این پروژه ساخت «پیچیدهترین معماری ممکن» نیست.
|
|
|
|
هدف:
|
|
|
|
```text
|
|
Simple
|
|
Secure
|
|
Maintainable
|
|
Testable
|
|
Modular
|
|
Production-ready
|
|
```
|
|
|
|
است.
|
|
|
|
هر تکنولوژی جدید باید فقط زمانی اضافه شود که یک مشکل واقعی را حل کند.
|
|
|
|
برای Scale فعلی، Django + PostgreSQL به تنهایی بیش از اندازه کافی است.
|
|
|
|
---
|
|
|
|
# 208. نتیجه نهایی معماری
|
|
|
|
معماری نهایی:
|
|
|
|
```text
|
|
┌──────────────┐
|
|
│ Customer │
|
|
└──────┬───────┘
|
|
│
|
|
OTP / Magic Link
|
|
│
|
|
▼
|
|
┌──────────────┐ ┌───────────────┐
|
|
│ Sales Expert │────────▶│ Nginx │
|
|
└──────────────┘ └───────┬───────┘
|
|
│
|
|
▼
|
|
┌───────────────┐
|
|
│ Django │
|
|
│ Modular Mono │
|
|
└───────┬───────┘
|
|
│
|
|
┌───────────────┼───────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌────────────┐ ┌────────────┐ ┌──────────────┐
|
|
│ PostgreSQL │ │Local Media │ │Notification │
|
|
└────────────┘ └────────────┘ │ Outbox │
|
|
└──────┬───────┘
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ Django Worker │
|
|
└────────┬────────┘
|
|
│
|
|
┌─────────────┴────────────┐
|
|
▼ ▼
|
|
SMS API Bale API
|
|
```
|
|
|
|
این معماری برای نیاز فعلی پروژه ساده، امن، قابل توسعه و کاملاً مناسب است. |