Files
customerTicket/docs/CHAHARSU_PAYMENTS_IMPLEMENTATION_PLAN.md
2026-10-06 10:03:22 +03:30

402 lines
64 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# طرح ادغام چهارسو و مدیریت اعلامیه و فیش واریز
تاریخ بررسی: ۲۰۲۶/۱۰/۰۵ — وضعیت: طرح مبنای پیاده‌سازی؛ گزارش اجرا و تفاوت مسیر نهایی در [راهنمای پیاده‌سازی](CHAHARSU_PAYMENTS_IMPLEMENTATION.md) ثبت شده است.
## ۱. نظر کارشناسی و تصمیم معماری
این قابلیت یک زیرسامانه مالی با گردش تأیید است؛ انتقال HTML نمونه به قالب‌های اصلی به‌تنهایی آن را کامل نمی‌کند. پیشنهاد، افزودن اپ مستقل `apps.payments` داخل معماری Django Modular Monolith موجود و استفاده از زیرساخت کاربران، واحدها، پیامک، رسانه خصوصی و برند فعلی است. منطق مالی در `apps/tickets/views.py` یا مدل `Ticket` انباشته نشود.
چهار مفهوم باید مستقل بمانند:
| مفهوم | معنی |
| --- | --- |
| حساب شرکت | حساب بانکی مدیریت‌شده و وضعیت مجاز بودن استفاده از آن |
| اعلامیه واریز | دستور پرداخت اختصاصی کارشناس برای یک مشتری، مبلغ و مقصد مشخص |
| فیش واریز | مدرک پرداخت مشتری؛ با اعلامیه یا مستقل از آن |
| تأیید فیش | تصمیم اشخاص مجاز درباره یک نسخه مشخص از مدرک، مطابق سیاست تأیید |
**تأیید مربوط به فیش است.** اعلامیه پیش از دریافت مدرک صرفاً صادر می‌شود. وضعیت پرداخت اعلامیه از فیش‌های تأییدشده محاسبه می‌شود؛ ایجاد اعلامیه، بارگذاری تصویر یا ارسال پیامک به معنی تأیید پرداخت نیست. این قابلیت به‌خودی‌خود تطبیق خودکار با تراکنش بانک یا سند حسابداری ایجاد نمی‌کند.
اصل مهم: دسترسی گسترده اولیه برای ایجاد و مشاهده در پنل کارشناسان، مستقل از مجوز محدود تأیید است. این تصمیم نباید دسترسی تیکت‌ها یا واحدهای سازمانی را گسترده کند.
## ۲. مبنای بررسی و حدود قطعیت
درخت پروژه `PROJECT_TREE.txt`، گراف موجود Graphify، اسناد سازمانی و طراحی، سورس بخش‌های مؤثر، فایل‌های تست پروژه و E2E و ابزارهای مرورگر بررسی شدند. مسیر تحلیل، درخت پروژه → Graphify برای معماری و ارتباط اسناد → Ripwire برای نمادها و وابستگی سورس → تطبیق مستقیم با سورس و تست‌ها بود. Graphify و Ripwire برای یک سؤال هم‌زمان اجرا نشدند.
نمونه‌های بررسی Graphify: ارتباط‌های `UnitMembership`، `SMSTemplate` و مفهوم نمونه بودن صفحه شماره حساب‌ها. Ripwire برای برنامه قابلیت، بدنه سیاست دسترسی و ارسال پیامک، استفاده‌های `StaffProfile` و شعاع اثر `_send_sms` استفاده شد. ارتباط‌های استنباطی گراف با سورس تطبیق داده شدند؛ گراف جای حقیقت اجرایی نیست. شمارش‌های Ripwire حد پایین‌اند و resolveهای مبهم و فایل‌های بدون grammar وجود دارند؛ پوشش Django ORM، template و URL باید با خواندن مستقیم تکمیل شود.
در این مرحله تست‌ها **خوانده شدند و اجرا نشدند**؛ نتیجه سبز یا صحت اجرای فعلی ادعا نمی‌شود. دیتابیس عملیاتی، افراد دارای مجوز فعلی و متن زنده الگوهای پیامک استعلام نشدند. شیوه دیتابیس‌محور از مدل‌ها، migrationها، فرم‌ها، worker و تست‌ها استخراج شده است؛ پیش از استقرار باید تنظیمات واقعی، با دسترسی فقط‌خواندنی، تطبیق داده شوند.
کد، قالب، CSS، migration و داده فعلی تغییر نمی‌کند؛ خروجی این مرحله همین سند است. تغییرات از قبل موجود در `PROJECT_TREE.txt` و `apps/customers/migrations/0005_contacteditlog.py` خارج از این کارند.
## ۳. واقعیت فعلی و نقاط حساس
| حوزه | شاهد در مخزن | نتیجه برای طراحی |
| --- | --- | --- |
| کاربران | `apps/accounts/models.py:User` از `AbstractUser` و دارای `account_type` است | `is_staff` صرفاً به معنی اختیار مالی نیست؛ مشتری و کارشناس تفکیک شوند |
| نقش‌ها | `apps/staff/models.py:StaffProfile` و `ROLE_GROUPS` | ذخیره پروفایل، گروه نقش را همگام می‌کند؛ مجوز مالی به نام اشخاص یا عنوان شغلی hardcode نشود |
| واحدها | `OrganizationalUnit.permissions` و `UnitMembership.permissions`، عضویت فعال و `ticket_scope` | مجوز مستقیم، گروهی، واحدی و شخص در واحد موجودند؛ دامنه مالی مستقل تعریف شود |
| سیاست تیکت | `apps/tickets/policies.py:TicketPolicy` | `can_operate` برای کارشناس فعالِ دارای دسترسی تیکت گسترده است؛ برای تأیید مالی قابل استفاده نیست |
| همگام‌سازی نقش | `apps/core/management/commands/bootstrap.py` | `group.permissions.set(...)` می‌تواند مجوز گروهی سفارشی را حذف کند؛ اجرای bootstrap برای اعطای مجوز مالی مناسب نیست |
| صفحه مشتری | `customer_list`، `templates/tickets/customer_list.html` | `/tickets/` هم خانه و هم سوابق است؛ سوابق باید مقصد مستقل پیدا کند |
| ورود و PWA | `apps/accounts/views.py`، `config/urls.py`، `templates/base.html` | نام مسیر `tickets:list` و start_url فعلی در مهاجرت خانه حفظ شوند |
| پیامک | `NotificationJob`، `enqueue` و `process_notifications` | صف پایدار و dedupe وجود دارد؛ worker فعلاً فقط رویدادهای شناخته‌شده تیکت را می‌فرستد |
| متن پیامک | `SMSTemplate` و `_render_sms` | الگوی فعال واحد، سپس عمومی؛ allowed_tags و context کنترل می‌شوند |
| فایل | `Attachment.message` اجباری و مسیر وابسته به تیکت است | فیش مستقل نباید با تیکت یا پیام ساختگی ذخیره شود |
| تحویل فایل | `apps/attachments/delivery.py` و Nginx | امضای کوتاه‌عمر پس از احراز مجوز؛ view مخصوص مالی لازم است |
| تاریخ | `apps/core/templatetags/jalali.py` و `jdatetime` | نمایش شمسی و ذخیره میلادی؛ برای زمان رویدادها UTC و نمایش ایران |
| طراحی | `design-prototypes/chaharsu/STYLE_GUIDE.md` و README | prototype قرارداد UX است، نه پنل، دیتابیس یا ارسال واقعی |
دو سند سازمانی قدیمی در بعضی توضیحات با وضعیت امروز یکسان نیستند: توضیح دسترسی legacy بدون واحد با `viewable_q` فعلی تفاوت دارد؛ توضیح نبود استعلام دلیوری نیز با worker و تست‌های جدید دلیوری به‌روز نیست. در این موارد سورس و تست فعلی مبناست. وضعیت تحویل SMS با تأیید مالی اشتباه نشود.
## ۴. معماری و مرز مسئولیت‌ها
```mermaid
flowchart TD
C[چهارسو: مشتری احراز هویت‌شده] --> CV[payments customer views/forms]
S[میزکار کارشناسان] --> SV[payments staff views/forms]
B[بانک‌ها و تنظیمات مجاز] --> SV
CV --> P[PaymentPolicy و selectors محدودشده]
SV --> P
P --> W[services: صدور، ثبت، تصمیم، تغییر وضعیت]
W --> D[(حساب، اعلامیه، فیش، چرخه و ممیزی)]
W --> O[(NotificationJob با dedupe)]
O --> N[worker موجود و رویدادهای مالی]
N --> T[(SMSTemplate)]
N --> SMS[provider پیامک موجود]
CV --> F[رسانه خصوصی با مجوز مالی]
SV --> F
```
ساختار پیشنهادی: `models.py`، `policies.py`، `selectors.py`، `forms.py`، `services/` برای حساب/اعلامیه/فیش/تأیید، `notifications.py`، `customer_views.py`، `staff_views.py`، URLهای جدا و `admin.py`. همه تغییرات وضعیت از سرویس واحد عبور کنند؛ فرم، admin و view مستقیماً وضعیت تأیید را ننویسند.
برای اتصال به واحدها، `owning_unit` و `created_by` در اعلامیه ثبت شود. استقلال مالی به معنی بی‌نیازی از ثبت منشأ سازمانی نیست. اطلاعات تیکت، در صورت نیاز آینده، فقط ارتباط اختیاری خواهد داشت؛ مبنای دسترسی مالی نباشد.
## ۵. مدل داده پیشنهادی
نام‌ها پیشنهادی‌اند؛ قبل از کدنویسی در migration design نهایی شوند.
| مدل | داده‌های اصلی و قرارداد |
| --- | --- |
| `BankInstitution` | نام/کد بانک مبدأ و نام‌های تاریخی، فعال بودن، ترتیب؛ «سایر» با متن اختیاری؛ کاتالوگ محلی قابل نگهداری |
| `CompanyBankAccount` | نام نمایشی حساب، بانک، صاحب حساب، شماره حساب، شبا، کارت اختیاری، سه پرچم فعال، ترتیب، سازنده/ویرایشگر، زمان، نسخه |
| `PaymentNotice` | public UUID، شماره نمایشی یکتا، مشتری، واحد، صادرکننده، موضوع، مبلغ صحیح ریال، حساب مرجع، شناسه واریز اختیاری، شماره فاکتور، توضیحات، وضعیت صدور/لغو، تاریخ صدور و snapshot اطلاعات پرداخت |
| `PaymentReceipt` | public UUID، مشتری، `notice` اختیاری، ثبت‌کننده و نوع CUSTOMER/STAFF، نسخه جاری، وضعیت جاری و زمان ثبت |
| `PaymentReceiptRevision` | نسخه تغییرناپذیر: مبلغ واقعی، تاریخ پرداخت میلادی، حساب مقصد واقعی و snapshot آن، بانک مبدأ، شماره پیگیری اختیاری، توضیحات و فایل |
| `ReceiptDocument` | فایل خصوصی با UUID، نام اصلی، MIME واقعی، اندازه، SHA-256، آپلودکننده و زمان؛ مرتبط با نسخه فیش |
| `PaymentApprovalPolicy` | نسخه تنظیمات، روش تأیید، افراد منتخب، ارشدهای مجاز و واحد/دامنه سیاست؛ ویرایش با مجوز و لاگ |
| `ReceiptApprovalCycle` | نسخه فیش، snapshot سیاست، حد نصاب/روش، وضعیت، نهایی‌کننده و زمان؛ تنها یک چرخه جاری |
| `ReceiptApprovalAssignment` | افراد و نقش عادی/ارشدِ همان چرخه؛ مستقل از تغییر تنظیمات آینده |
| `ReceiptDecision` | چرخه، کاربر تصمیم‌گیر، APPROVE/REJECT/SENIOR_APPROVE، دلیل و زمان؛ یک رأی معتبر هر شخص در هر چرخه |
| `PaymentAuditEvent` | عملیات، موضوع، actor و snapshot هویت، زمان، قبل/بعد مجاز، دلیل، request ID و ارتباط نسخه/چرخه |
| `PaymentAccessConfiguration` | حالت ALL_ACTIVE_STAFF یا EXPLICIT برای مشاهده/ایجاد؛ جدا از سیاست تأیید |
| `PaymentNotification` | پیوند مالی به `NotificationJob`، رویداد و نسخه/چرخه، context و مشخصات الگوی مصرف‌شده؛ امکان گزارش پیامک بدون تیکت |
| `PaymentNotificationAttempt` | سابقه تغییرناپذیر هر تلاش مالی، زمان شروع/پایان، نتیجه، کد و مرجع provider؛ تلاش متوقف‌شده نیز قابل تشخیص و مستقل از reset شمارنده job |
شماره حساب، کارت، شبا، شناسه واریز و فاکتور رشته‌اند تا صفر ابتدایی از بین نرود. ارقام استاندارد ذخیره ASCII؛ نمایش می‌تواند فارسی باشد. شبا با طول/کشور/checksum و کارتِ موجود با قواعد شماره معتبر بررسی شود؛ اعتبار شماره اثبات مالکیت حساب نیست. همه حساب‌ها الزاماً کارت ندارند.
مبلغ `BigIntegerField` با قید مثبت و سقف مشخص قابل پشتیبانی سیستم پیشنهاد می‌شود. در Python محاسبه صحیح، در JS رشته عددی/BigInt؛ تبدیل تومان بدون float و بدون گرد کردن. سقف عملیاتی و سقف جمع مبالغ جدا کنترل شوند. مبلغ expected اعلامیه و paid فیش دو فیلد متفاوت‌اند.
قیود پایگاه داده: شماره/public_id یکتا، نسخه یکتا در هر فیش، رأی یکتای `(cycle, reviewer)`، چرخه جاری یکتا، dedupe پیامک یکتا، مبلغ مثبت و حالت‌های سازگار. هم‌مالک بودن notice و receipt و صلاحیت حساب در سرویس و فرم دوباره بررسی شوند؛ `CheckConstraint` معمولی نمی‌تواند تمام روابط بین جدول‌ها را تضمین کند.
روی مشتری/زمان، وضعیت/زمان، واحد/زمان، حساب/زمان و چرخه/کاربر index لازم است. حذف حساب یا رکورد مرجع استفاده‌شده با `PROTECT` یا آرشیو محدود شود؛ حذف فیش تأییدشده عملیات CRUD عادی نیست.
## ۶. مجوزها و دامنه مشاهده
«همه» در این طرح یعنی **کارشناسان داخلی فعال**، نه مشتریان، کاربران غیرفعال یا هر حساب دارای `is_staff`. پروفایل غیرفعال نیز مجوز ندارد. استثنای admin بدون StaffProfile مانند رفتار فعلی باید صریح و آزموده تعریف شود.
| اقدام | وضعیت اولیه | قابلیت محدودسازی بعدی |
| --- | --- | --- |
| دیدن میزکار اعلامیه‌ها و فیش‌ها | همه کارشناسان فعال | مجوز مشاهده و دامنه سراسری/واحدی/تخصیص شخصی |
| ایجاد اعلامیه | همه کارشناسان فعال | مجوز ایجاد و واحدهای مجاز |
| ثبت فیش توسط staff برای مشتری | همه کارشناسان فعال، با ثبت هویت ثبت‌کننده | مجوز مستقل ثبت فیش برای دیگری |
| مشاهده مشتری | فقط اعلامیه‌ها/فیش‌های خودش | همیشه object-level؛ تغییرپذیر به مشاهده همگانی نیست |
| ارسال مشتری | فقط برای خودش؛ notice باید متعلق به خودش باشد | کنترل مقصد و ارتباط در سرور |
| رأی تأیید یا رد | فقط شخص دارای مجوز و تخصیص در چرخه | ALL_SELECTED یا حد نصاب تعیین‌شده |
| تأیید ارشد | شخص دارای مجوز ویژه و تعیین‌شده به‌عنوان ارشد | هرگز صرف عنوان مدیرعامل یا `is_staff` |
| مدیریت بانک‌ها | مدیرعامل فعال، superuser و افراد دارای مجوز صریح | مجوز اختصاصی؛ سایر نقش‌ها به‌صورت پیش‌فرض ممنوع |
| تغییر سیاست/افراد تأیید | افراد دارای مجوز مدیریت تأیید | جدا از مشاهده یا ایجاد اعلامیه |
| مدیریت الگوهای مالی SMS | مجوز مربوط به SMSTemplate و دامنه واحد/عمومی | ایجاد اعلامیه مجوز تغییر متن SMS نمی‌دهد |
کدهای پیشنهادی: `view_paymentnotice`، `add_paymentnotice`، `view_paymentreceipt`، `submit_customer_receipt`، `approve_paymentreceipt`، `senior_approve_paymentreceipt`، `manage_company_bankaccounts`، `manage_payment_approval_policy`، `manage_payment_access` و `view_payment_audit`. دامنه سراسری مجوز جدا داشته باشد؛ مجوز واحدی فقط برای همان واحد اعمال شود.
`PaymentPolicy` الگوی ترکیب permissionهای Django و عضویت واحدی را به کار می‌گیرد، اما به `TicketPolicy.can_operate` یا `tickets.view_all_tickets` متکی نمی‌شود. مشتری حتی با حدس UUID، تغییر notice_id، account_id، پارامتر جست‌وجو، export یا URL فایل به داده دیگری دسترسی نمی‌گیرد.
پیشنهاد برای فاز اول: حالت ALL_ACTIVE_STAFF فقط در مشاهده/ایجاد فعال باشد. مجوزهای تأیید به هیچ گروه فروش یا مالی به‌طور عمومی seed نشوند. کاربران تأییدکننده و ارشد از admin/تنظیمات انتخاب شوند. superuser بتواند سیاست را مدیریت کند، اما امضای مالیِ خارج از چرخه را به‌صورت پنهان جایگزین رأی نکند؛ برای تأیید ارشد باید در مسیر صریح آن قرار گیرد.
مجوز مالی مستقیم یا membership اختصاصی در برابر `StaffProfile.save` و bootstrap پایدارتر است؛ اگر گروه‌های مالی مستقل ایجاد شدند، bootstrap گروه‌های مدیریت‌شده موجود نباید آن‌ها را بازنویسی کند. نیازی به چند boolean مالی در مدل `User` یا بازنویسی enum نقش‌ها نیست.
## ۷. تأیید چندنفره و ارشد
روش اولیه پیشنهادی `ALL_SELECTED` است: اگر الف و ب انتخاب شوند، رأی هر دو الزامی است؛ تأیید دو شخص دیگر جای آن‌ها را نمی‌گیرد. `QUORUM` برای «دو نفر از یک مجموعه» مفهوم دیگری است و فقط با نیاز کسب‌وکار فعال شود. حالت تک‌نفره همان ALL_SELECTED با یک شخص است.
هنگام ارسال فیش، سیاست معتبر و افراد آن در چرخه snapshot شوند. هیچ چرخه‌ای با فهرست خالی، شخص غیرفعال یا حد نصاب غیرممکن قابل شروع نیست. سازنده اعلامیه یا مشتری نتواند سیاست ضعیف‌تری انتخاب کند؛ سیاست از تنظیمات مدیریت تعیین شود.
```mermaid
stateDiagram-v2
[*] --> PENDING: ثبت نسخه فیش
PENDING --> PARTIALLY_APPROVED: رأی عادی و ناتمام بودن شرط
PARTIALLY_APPROVED --> APPROVED: تکمیل شرط تأیید
PENDING --> APPROVED: تک‌نفره یا تأیید ارشد مجاز
PARTIALLY_APPROVED --> APPROVED: تأیید ارشد مجاز
PENDING --> REJECTED: رد با دلیل
PARTIALLY_APPROVED --> REJECTED: رد با دلیل
REJECTED --> PENDING: نسخه اصلاحی و چرخه جدید
```
پیشنهاد پیش‌فرض: یک رد معتبر، چرخه را خاتمه دهد؛ اصلاح مشتری نسخه و چرخه جدید ایجاد کند. این قاعده و ممنوعیت تأیید مدرکی که همان کارشناس ثبت کرده، تصمیم‌های کسب‌وکاری نیازمند تعیین‌اند؛ در بخش ۱۵ ثبت شده‌اند.
هر اقدام در `transaction.atomic` با قفل ردیف فیش و چرخه جاری انجام شود. مجوز جاری، نسخه مورد انتظار، وضعیت و رأی تکراری داخل قفل بررسی شوند. قید یکتا لایه دوم دفاع است. ترتیب قفل‌ها ثابت باشد؛ در PostgreSQL، در صورت join nullable، الگوی قفل `of=("self",)` مانند تجربه تست Announcement رعایت شود.
رأی تکراری همان کاربر شمارش را بالا نبرد. رأی آخر و تأیید ارشد هم‌زمان فقط یک انتقال نهایی و یک پیامک تولید کنند. درخواست صفحه قدیمی نتواند نسخه جدید را تأیید کند؛ پاسخ «اطلاعات تغییر کرده؛ دوباره بررسی کنید» بدهد.
ارشد با اقدام مشخص «تأیید نهایی ارشد»، نمایش اثر و ثبت دلیل، انتظار سایر افراد را پایان می‌دهد. این اختیار برای چرخه جاریِ در انتظار است؛ رأی رد یا تأیید نهایی پیشین را پاک نمی‌کند. اصلاح تصمیم نهایی باید مسیر مجاز و ممیزی‌شده جدا داشته باشد.
تغییر مبلغ، تاریخ، مقصد یا تصویر پس از شروع تصمیم‌گیری، نسخه جدید و چرخه جدید می‌خواهد. رأی روی نسخه قبلی اعتبار نسخه جدید نیست. خروج کاربر از سازمان مانع رأی آینده او می‌شود، اما سوابق رأی قبلی را حذف نمی‌کند. جایگزینی افراد چرخه معلق با مجوز مدیریت، دلیل، بستن چرخه قبلی و ثبت چرخه تازه انجام شود.
## ۸. مدیریت بانک‌ها و کنترل جریان مالی
در sidebar راست «بانک‌ها» فقط برای مدیرعامل/admin مجاز و کاربران تعیین‌شده نمایش داده شود. کنترل endpoint و عملیات bulk مستقل از پنهان‌کردن منو لازم است.
سه پرچم روی **حساب شرکت** باشند، نه فقط نام بانک؛ ممکن است دو حساب یک بانک سیاست متفاوت داشته باشند:
- `is_active`: قطع کلی انتخاب جدید.
- `available_to_staff`: قابل انتخاب برای صدور اعلامیه توسط کارشناس.
- `available_to_customers`: قابل انتخاب در فرم فیش مستقل مشتری؛ فقط نام حساب نمایش داده می‌شود.
eligibility staff برابر `is_active AND available_to_staff` و eligibility مستقل مشتری برابر `is_active AND available_to_customers` است. وضعیت کلی غیرفعال بر دو گزینه دیگر اولویت دارد. فعال کردن چند حساب یا خاموش کردن چند حساب با POST، CSRF، مجوز، transaction و لاگ هر حساب انجام شود.
ویرایش، افزودن، جست‌وجو، فیلتر سه وضعیت و صفحه‌بندی؛ انتخاب ردیف‌ها و تأیید تغییر جمعی. «انتخاب همه» روشن کند منظور صفحه جاری است یا تمام نتیجه فیلتر؛ در فاز اول صفحه جاری ساده‌تر است. رکورد استفاده‌نشده با مجوز قابل حذف؛ استفاده‌شده آرشیو شود و علت عدم حذف نمایش داده شود.
**تعارض مهم سیاست مقصد:** حساب فقط-staff ممکن است در اعلامیه اختصاصی مشتری باشد ولی در dropdown مستقل مشتری نباشد. پیشنهاد: نمایش و انتخاب همان حساب صرفاً در context اعلامیه معتبر خودش مجاز باشد؛ dropdown مستقل همچنان فقط حساب‌های customer-enabled را ارائه کند. اگر خاموش کردن customer باید اعلامیه‌های موجود را هم متوقف کند، باید اعلامیه نیز صریحاً لغو/جایگزین شود. این رفتار پیش از اجرا تعیین شود.
خاموش‌کردن حساب نباید سوابق مالی را پنهان یا بررسی فیش پرداخت گذشته را متوقف کند. در فرم قدیمی، تغییر eligibility در لحظه POST دوباره بررسی شود؛ برای پرداختی که قبلاً انجام شده، مسیر استثنای ثبت واقعیت با دلیل و بررسی کارشناس لازم است. بازگرداندن خودکار حساب دیگری یا تغییر بی‌خبر مقصد ممنوع است.
اطلاعات بانکی در اعلامیه صادرشده snapshot شوند. تغییر اطلاعات واقعی حساب بهتر است رکورد/نسخه جدید بسازد؛ تغییر نام نمایشی نیز سابقه اعلان قبلی را بازنویسی نکند. شناسه واریز و مبلغ روی اعلامیه‌اند، چون هر بار ممکن است متفاوت باشند.
## ۹. میزکار اعلامیه‌ها و بررسی فیش‌ها
نام پیشنهادی منو «اعلامیه واریز» و عنوان صفحه «اعلامیه‌ها و فیش‌های واریزی» است. دکمه اصلی «ایجاد اعلامیه واریز»؛ ثبت فیش توسط کارشناس اقدام ثانویه. در همان میزکار، تب‌های «اعلامیه‌ها»، «فیش‌ها» و فیلتر «نیازمند اقدام من» وجود داشته باشند. تب فیش‌ها **فیش مستقل را نیز** نشان دهد؛ query با inner join روی notice آن را حذف نکند.
فرم صدور: انتخاب مشتری از فهرست فعلی مخاطبان، موضوع، حساب مجاز staff، مبلغ ریال، شناسه اختیاری، فاکتور و توضیحات. نام مشتری و صاحب حساب مشخص باشد. انتخاب مشتری موجود، مشخصات تماس او را ناخواسته ویرایش نکند؛ فرم ایجاد تیکت امروز مسئولیت‌های بیشتری دارد و نباید عیناً reuse شود.
قانون انتشار: draft پیامک ندارد؛ «صدور و اطلاع‌رسانی» اعلامیه را برای مشتری قابل مشاهده و job پیامک را در همان transaction ایجاد می‌کند. شکست پیامک اعلامیه را حذف نمی‌کند؛ وضعیت جدا «پیامک ناموفق» و مسیر retry مجاز نمایش داده شود.
فیلترهای مشترک: متن/شماره، مشتری و موبایل، فاکتور، بازه تاریخ، حساب، صادرکننده و واحد. فیش‌ها: وضعیت، مرتبط/مستقل، تأییدکننده و «اقدام من». وضعیت ارسال/تحویل SMS فیلتر جداست. مرتب‌سازی allowlist؛ پیش‌فرض تازه‌ترین با tie-breaker ID و صفحه‌بندی سمت سرور، مثلاً ۲۵ ردیف با سقف مشخص.
فیلترها با query string حفظ شوند؛ تغییر فیلتر page را بازنشانی کند. تاریخ فیلتر شمسی نمایش و در سرور میلادی معتبر شود؛ فیلتر datetime بر اساس ابتدا/انتهای روز ایران باشد. جست‌وجوی ارقام فارسی و موبایل با الگوی normalize موجود سازگار شود. قبل از search/pagination/count، queryset مجاز محدود شود.
ردیف فیش: مشتری، مبلغ، تاریخ، حساب، شماره اعلامیه یا «مستقل»، وضعیت و پیشرفت «۱ از ۲ تأیید». جزئیات: تصویر قابل بزرگ‌نمایی، داده پرداخت، اختلاف با اعلامیه و timeline نام/زمان/دلیل تصمیم‌ها. رأی عادی با متن «ثبت تأیید من» و نتیجه روشن؛ در تأیید چندنفره از عبارت گمراه‌کننده «فیش تأیید شد» استفاده نشود.
دکمه رد دلیل اجباری دارد. افراد بدون مجوز، اطلاعات و سوابق را می‌بینند ولی دکمه تصمیم ندارند؛ POST جعلی نیز ممنوع است. نام کسانی که هنوز باید تأیید کنند و نهایی‌کننده عادی/ارشد مشخص باشد. جدول در موبایل به کارت خوانا تبدیل شود؛ اطلاعات اصلی در ستون‌های باریک بریده نشوند.
badge sidebar: **تعداد فیش‌های در انتظار بررسی در دامنه قابل مشاهده**، شامل مستقل و مرتبط و تأیید جزئی؛ هر فیش یک بار، نه تعداد رأی یا اعلامیه. «نیازمند اقدام من» شمارش مستقل داخل صفحه دارد. ایجاد اعلامیه بدون دریافت فیش این badge را افزایش نمی‌دهد. پس از رأی آخر کم می‌شود؛ پس از رأی جزئی باقی می‌ماند.
شمارنده از selector مشترک میزکار استفاده کند؛ `distinct`/Exists و prefetch مناسب، بدون N+1. refresh اولیه سرور و در صورت نیاز polling محلی مشابه shares با توقف در صفحه پنهان، و پاسخ JSON `private,no-store`. badge جدا از `staff_unread_shares` باشد؛ دسترسی گسترده مالی، count تیکت‌های واحدهای دیگر را تغییر ندهد.
## ۱۰. چهارسو و قرارداد UX مشتری
مسیر `/tickets/` و نام `tickets:list` برای ورود و PWA حفظ شود، اما مقصد به خانه خدمات تبدیل شود. سوابق به `/tickets/requests/` با نام `tickets:history` منتقل شود؛ لینک‌های فعلی «درخواست‌های من» به آن مقصد اصلاح شوند. جزئیات تیکت، Magic Link و ثبت درخواست فعلی باقی بمانند.
مسیرهای پیشنهادی مالی مشتری `/payments/notices/`، `/payments/receipts/new/` و `/payments/receipts/<uuid>/`؛ staff زیر `/staff/payments/` و `/staff/banks/`. include جدا با namespace مستقل؛ بدون تغییر namespace تیکت و بدون تداخل الگوهای UUID موجود.
خانه مطابق نمونه: کارت مشترک «ارتباط با کارشناسان» با دو اقدام ثبت درخواست/درخواست‌های من؛ جمله «سؤال بپرسید، درخواست ثبت کنید یا برای پیگیری و هماهنگی با کارشناسان در ارتباط باشید.» در موبایل و دسکتاپ دیده شود. دو خدمت مالی جدا؛ هیچ فهرست «درخواست‌های اخیر» در خانه قرار نگیرد.
«شماره حساب‌ها» در واقع فهرست اعلامیه‌های اختصاصی است؛ catalog کامل حساب‌های شرکت برای مشتری نمایش داده نشود. کارت‌های compact بازشونده با مبلغ/موضوع/وضعیت؛ اطلاعات تکمیلی و توضیحات **داخل کارت بازشده همیشه نمایان** و بدون accordion دوم. راهنمای کوتاه درخواست اطلاعات و شناسه از کارشناس باقی بماند.
کپی تکی فقط مبلغ، حساب، شبا، کارت و شناسه موجود؛ مقدار عددی بدون جداکننده نمایشی. «کپی کامل اطلاعات» از snapshot کامل و مجاز اعلامیه ساخته شود، نه DOM قابل مشاهده؛ بانک، صاحب حساب، فاکتور، توضیحات و واحد ریال را هم شامل شود. Clipboard فقط بعد از موفقیت بازخورد بدهد؛ fallback انتخاب دستی و Escape و بازگشت فوکوس حفظ شود.
فرم فیش: مرتبط اولین گزینه پیشنهادی و مستقل همیشه در دسترس. ورود از اعلامیه همان مورد را انتخاب کند؛ تغییر حالت فایل، مبلغ و تاریخ را پاک نکند. حساب مقصد اجباری و فقط نام‌ها در dropdown؛ مبدأ اختیاری با بانک‌های محلی، نام‌های تاریخی و «سایر». داده‌های fixture و ID ساختگی وارد production نشوند.
مبلغ ریال اجباری با هزارگان و معادل دقیق تومان به حروف؛ تاریخ کامل و تصویر اجباری. سه select روز/ماه/سال شمسی و «امروز» مینیمال حفظ شود. `paid_date` خروجی میلادی `YYYY-MM-DD`، بدون تبدیل midnight به timestamp؛ روز ابتدا خالی. بازه سال و ممنوعیت آینده از سیاست سرور بیاید. jdatetime سمت سرور، کامل بودن تاریخ، کبیسه، تاریخ غیرآینده و تطبیق شمسی/میلادی را بررسی کند؛ hidden input قابل اعتماد نیست. بدون JS نیز سه مقدار شمسی با فرم سرور قابل اعتبارسنجی و تبدیل باشند.
مقصد متفاوت با اعلامیه: مبلغ یا انتخاب کاربر خودکار بازنویسی نشود. ثبت واقعی با نشان «مغایرت مقصد» و دلیل/بررسی ویژه پیشنهاد می‌شود؛ ارتباط فیش با اعلامیه به‌تنهایی تطبیق یا تسویه آن نیست. چنین فیشی قبل از تعیین تکلیف مجاز، در جمع تسویه اعلامیه نیاید.
اعتبارسنجی مرورگر از قواعد واقعی سرور تغذیه شود؛ خطای فارسی زیر فیلد + summary لینک‌دار + aria-invalid/describedby + هدایت به اولین کنترل. پاسخ validation سرور همان اطلاعات را حفظ کند؛ مرورگر ممکن است انتخاب مجدد فایل را لازم کند و باید صریح بگوید. submit دوباره با idempotency token و وضعیت pending مدیریت شود. موفقیت فقط بعد از commit واقعی: «فیش ثبت شد و در انتظار بررسی است».
ظاهر از `STYLE_GUIDE.md`: وزیرمتن موجود، RTL، متن عملیاتی حداقل ۱۴px، ورودی ۱۶px، کنترل اصلی حداقل ۴۴px، رنگ سرمه‌ای/سفید و سطوح موجود. جفت‌رنگ‌های دارای کنتراست ناکافی نمونه تکثیر نشوند. همه فونت، SVG، JS و CSS محلی و versioned؛ CDN یا fetch دارایی خارجی ممنوع. داده مالی از سرور خود پروژه می‌آید؛ SMS بک‌اند وابستگی CDN رابط محسوب نمی‌شود.
SVGها در include محلی مشترک؛ CSS مالی scoped و asset صفحه‌ای، بدون تغییر عمومی بی‌هدف. امضا از `developer_credit` دیتابیس فعلی تأمین شود. تنظیمات hero/PageTitle برای خانه جدید جدا و بدون overwrite متن سفارشی موجود طراحی شوند. شرط دیدن سه خدمت روی ۳۲۰×۵۶۸ در حالت معمول آزموده شود؛ بزرگ‌نمایی متن و نمایش خطا نباید قربانی حذف اسکرول شوند.
## ۱۱. پیامک مالی با زیرساخت موجود
`NotificationJob.ticket` nullable است و `enqueue` می‌تواند با `ticket=None` job بسازد. اما `_send_sms` رویداد ناشناخته را رد می‌کند و `_sms_context` به `job.ticket` وابسته است. بنابراین افزودن Event به مدل به‌تنهایی کافی نیست.
پیشنهاد: `PaymentNotification` در اپ مالی، job موجود را به notice/receipt revision/cycle مربوط کند؛ worker handler مالی مشخص داشته باشد و context مالی را مستقل بسازد. نیازی به تیکت صوری، worker دوم یا تغییر nullable بودن Attachment برای این اتصال نیست. وابستگی migration از payments به notifications قابل طراحی است؛ notifications نباید به models مالی در سطح module import دوری پیدا کند.
دو رویداد جدید در `SMSTemplate.Event`:
- `PAYMENT_NOTICE_CREATED`: فقط هنگام صدور اعلامیه، یک بار برای مشتری.
- `PAYMENT_RECEIPT_APPROVED`: فقط انتقال نهایی چرخه به APPROVED؛ رأی جزئی پیامک «تأیید شد» ندارد.
تگ‌های مالی محدود مانند `{recipient_name}`، `{company_name}`، `{notice_number}`، `{receipt_number}`، `{amount_rial}`، `{invoice_number}`، `{payment_link}`. allowed_tags هر رویداد با context واقعی آن سازگار باشد؛ قالب مالی نتواند اشتباهاً `{magic_link}` تیکت طلب کند. برای فیش مستقل، فاکتور/اعلامیه غایب متن خالی یا fallback مناسب دارد. renderer عمومی موجود قابل استفاده است؛ context تیکت قابل استفاده مستقیم نیست.
defaultهای عمومی با data migration idempotent و `get_or_create` ایجاد شوند؛ متن‌های ویرایش‌شده مشتری overwrite نشوند. الگوی واحدی فعال، سپس الگوی عمومی فعال مانند امروز. متن از DB قابل تغییر بماند؛ UI ویرایش برای allowed_tags مالی و preview بدون ارسال واقعی به‌روز شود. نبود template/context معتبر، خطای قابل مشاهده و قابل پیگیری باشد.
ثبت business event، ممیزی و enqueue job در همان transaction انجام شود؛ `on_commit` صرفاً برای wake-up اختیاری و نه جای ذخیره outbox. کلید dedupe شامل نوع رویداد و notice version یا receipt cycle نهایی باشد. نتیجه SMS موفق/ناموفق اثری بر صحت تصمیم مالی ندارد.
برای ممیزی دقیق، context، template ID/updated_at و متن مصرف‌شده در اولین تلاش ثبت و در retry همان متن استفاده شود؛ ویرایش template روی اعلان‌های آینده اثر کند. متن و payload فقط در دسترس اشخاص مجاز گزارش باشد، در log عمومی یا خطای کاربر چاپ نشود. سابقه تلاش‌ها append-only لازم است؛ job فعلی شمارنده و آخرین خطا دارد و به‌تنهایی تاریخچه کامل تمام تلاش‌ها نیست.
retry پایه SMS فعلی و استعلام delivery حفظ شوند. retry خاص ۴۵ دقیقه‌ای امروز فقط برای `STAFF_TICKET_CREATED` است؛ خودکار به مالی تعمیم داده نشود. debounce/cancel مربوط به خواندن مکالمه `STAFF_REPLY` نیز به پیامک مالی تعمیم داده نشود. خرابی پیامک مالی پردازش سایر jobها را متوقف نکند.
لینک SMS مالی به صفحه احراز هویت‌شده خود پروژه و `next` امن اشاره کند. Magic Link امروز مجوز فقط یک تیکت می‌دهد و session کامل مشتری نمی‌سازد؛ برای اطلاعات بانکی/فیش استفاده نشود. ادامه مقصد پس از OTP باید با تست مسیر ورود پشتیبانی شود. ایجاد access token جدید مالی تصمیم جداست و در این طرح لازم نیست.
در سورس فعلی، ورود OTP و رمز مقصد را از `session["next"]` می‌خوانند؛ دریافت و اعتبارسنجی `GET next` برای لینک مالی مسیر آماده‌ای نیست. در طرح اجرایی باید مقصد محلی allowlistشده هنگام ورود ذخیره، پس از ورود و بررسی مالکیت مصرف و از redirect به دامنه خارجی جلوگیری شود. مسیر ورود از SMS برای کاربر واردشده، مهمان و حساب مشتری دیگر جدا آزموده شود.
dedupe ثبت job را یکتا می‌کند؛ crash پس از پذیرش provider و قبل از save می‌تواند ارسال دوباره ایجاد کند. بدون idempotency provider، ادعای exactly-once SMS نکنیم. وضعیت مبهم ارسال در ممیزی و گزارش قابل تشخیص باشد؛ رأی و تأیید نهایی داخل DB باید یکتا بمانند.
## ۱۲. فایل خصوصی و ثبت سوابق
برای فیش `ReceiptDocument` با storage فعلی ولی مسیر خصوصی مالی مستقل استفاده شود. اعتبارسنجی موجود magic/MIME/سایز قابل استخراج و reuse است؛ وابستگی به Message/Ticket حذف شود و تست پیوست‌های قدیمی حفظ شود. تصویر باید واقعاً قابل decode و محدودیت ابعاد/حجم داشته باشد؛ سیاست IMAGE واقعی DB منبع محدودیت UI و سرور است. مقادیر نمونه ۱۰MB قانون دائمی نیستند.
view تصویر/download ابتدا مالک مشتری یا دامنه staff مالی را کنترل کند، سپس از signed_media_redirect موجود استفاده کند. URL مستقیم `/media/...` مجوز نیست؛ در DEBUG و Nginx نیز این مرز آزموده شود. امضای صادرشده تا انقضای کوتاه‌عمر یک bearer URL است؛ ابطال مجوز، URL قبلاً صادرشده را فوری باطل نمی‌کند. اگر ابطال فوری لازم شد، تحویل فایل از مسیر کنترل‌شونده هر درخواست انتخاب شود.
فایل و DB transaction واحد ندارند؛ خطای validation/rollback پس از ذخیره باید orphan cleanup داشته باشد. نام ذخیره UUID و نسخه ثابت؛ جایگزینی فایل پس از رأی ممنوع. هش یکسان مدرک هشدار تکراری بدهد، اما به‌تنهایی دلیل رد قطعی نیست؛ یک تصویر ممکن است چند پرداخت را نشان دهد.
ممیزی اقدامات: ایجاد/ویرایش/صدور/لغو اعلامیه، ثبت/نسخه اصلاحی فیش، رأی/رد/ارشد، تغییر سیاست و تخصیص، CRUD/فعال‌سازی جمعی حساب، retry و تلاش ارسال پیامک. هر رویداد actor ID + snapshot نام/شناسه عمومی، زمان aware UTC و نمایش شمسی ایران، دلیل و request ID داشته باشد. timeline جزئیات فیش، تمام رأی‌دهندگان و نهایی‌کننده را نشان دهد.
تغییر permission مالی در UserAdmin، GroupAdmin، واحد یا membership نیز عملیات مالی مؤثر است و باید actor و قبل/بعد ثبت شود. صرف log سرویس تأیید این تغییرات را پوشش نمی‌دهد؛ adminهای موجود با hook ممیزی اختصاصی permissionهای payments و فرم مدیریت دسترسی به سرویس ممیزی متصل شوند. تغییر برنامه‌ای فاقد actor به نام SYSTEM و با شناسه عملیات ثبت شود؛ به signal بدون امکان تشخیص actor اکتفا نشود.
بازدید جزئیات و درخواست فایل حساس نیز access log داشته باشد. دانلود واقعی در Nginx با redirect موفق از Django یکسان نیست؛ برای اثبات انتقال فایل باید log رسانه با correlation بررسی شود. کپی Clipboard محلی، server event قطعی ندارد؛ «ثبت هر کار» برای عملیات معتبر سرور تضمین می‌شود، telemetry کپی فقط گزارش کمکی است و موفقیت واقعی انتقال به پیام‌رسان قابل اثبات نیست.
audit و decision در admin readonly و حذف/ویرایش عادی ممنوع؛ تمام مسیرهای UI/admin/service این قرارداد را رعایت کنند. نگهداری سوابق هنگام حذف کاربر با PROTECT یا SET_NULL + snapshot تعیین شود. DB admin همچنان اختیار فنی دارد؛ audit اپلیکیشن ادعای مصونیت مطلق از تغییر دیتابیس نیست. مهلت نگهداری فایل و log باید سیاست مشخص داشته باشد.
## ۱۳. دامنه تغییر و ریسک رگرسیون
| مسیر | تغییر مورد انتظار | ریسک و کنترل |
| --- | --- | --- |
| `apps/payments/` جدید | مدل، سرویس، policy، فرم، view و migration | بیشترین منطق جدید؛ تست مستقل دامنه و concurrency |
| `config/settings/base.py` و `config/urls.py` | ثبت app و URLهای مستقل | حفظ routeهای موجود و start_url |
| `apps/accounts/views.py` | ادامه امن مقصد مالی پس از OTP/رمز | حفظ ورود فعلی و جلوگیری از open redirect یا دورزدن مالکیت |
| `apps/tickets/customer_urls.py` و `customer_list` | خانه خدمات + history مستقل | تغییر آگاهانه انتظارات تست خانه، حفظ new/detail/magic |
| `templates/base.html` و context processor | منو مالی/بانک، badge و active section | desktop/mobile، permissions، shares counter و CSS isolation |
| قالب/assetهای مالی جدید | انتقال قرارداد نمونه به Django | بدون fixture، CDN یا موفقیت جعلی؛ version تمام assetها |
| `apps/notifications/models.py` و worker | Event و routing/context مالی | شکست event جدید نباید رویدادهای قدیمی را خراب کند |
| فرم/admin SMS موجود | tags و تنظیم template مالی | unit/global access و عدم overwrite متن DB |
| admin کاربران/گروه/واحد و membership | ممیزی تغییر مجوزهای مالی | ثبت actor بدون تغییر رفتار مجوزهای تیکت |
| اعتبارسنجی attachments | استخراج validator مشترک در صورت نیاز | مرز مجوز فایل تیکت تغییر نکند |
| تنظیمات branding/PageTitle | متن خانه خدمات و امضا | حفظ تنظیمات سفارشی و تست branding |
| production seed | اضافه کردن تنظیمات مالیِ مجاز | عدم وارد کردن outbox/تأییدها یا حساب واقعی ناخواسته |
| تست‌ها و اسناد QA | تست‌های جدید و اصلاح موارد تغییر محصول | عدم تضعیف assertions امنیتی موجود |
از تغییر ساختار بنیادی `User`، `StaffProfile.Role`، مجوزهای تیکت و routing Magic Link برای این قابلیت پرهیز شود. new financial permissions در existing admin قابل مدیریت‌اند؛ افزودن inline/read-only وضعیت در UserAdmin فقط اگر UX مدیریت لازم داشت انجام شود.
عدد دقیق فایل‌های نهایی تا قراردادهای بخش ۱۵ قطعی نیست؛ تغییر محدود به چند HTML نیست. پیش از هر ویرایش غیرساده، `--impact` و `--uses` برای symbol واقعی هدف، و پس از آن `--test-gate` و `--quality-delta` مطابق دستور پروژه اجرا شوند. یافته ابزار، جای آزمون dynamic URL/template/ORM را نمی‌گیرد.
## ۱۴. برنامه اجرایی مرحله‌بندی‌شده و معیار خروج
| مرحله | کار | شرط پایان |
| --- | --- | --- |
| ۰ | تثبیت قراردادهای بخش ۱۵، baseline تست‌ها و بررسی فقط‌خواندنی تنظیمات واقعی | تصمیم‌ها ثبت و رگرسیون‌های قبلی از جدید تفکیک شوند |
| ۱ | اپ payments، schema/constraints، PermissionPolicy، audit و تنظیم دسترسی | مشتری دیگر/کارشناس غیرفعال/POST جعلی دسترسی ندارد؛ ALL_STAFF فقط view/create را باز می‌کند |
| ۲ | مدیریت حساب staff/admin و bulk state | سه پرچم و snapshot/آرشیو درست؛ فقط افراد مجاز CRUD دارند |
| ۳ | صدور اعلامیه، فرم مشتری picker و SMS template/handler/outbox | یک اعلامیه اختصاصی + یک job؛ failure مستقل و retry قابل پیگیری |
| ۴ | خانه چهارسو و history مستقل و صفحه اعلامیه مشتری | UX نمونه با داده واقعی، سه خدمت خانه و عدم افشای حساب عمومی |
| ۵ | ثبت فیش مستقل/مرتبط، فایل خصوصی، تاریخ/مبلغ/مقصد و revision | validation دوطرفه، idempotency، ownership، خطاهای قابل فهم و ثبت واقعی |
| ۶ | رأی تک/چندنفره و ارشد، timeline و SMS نهایی | raceها و رأی تکراری امن؛ قبل از پایان شرط هیچ پیامک تأیید ارسال نشود |
| ۷ | میزکار جست‌وجو/فیلتر/صفحه‌بندی، شمارنده sidebar | فیش مستقل حذف نشود؛ badge دقیق و queries محدود/بدون N+1 |
| ۸ | یکپارچگی، E2E، PostgreSQL concurrency و مستند عملیات | مجموعه رگرسیون + سناریوهای جدید + بازبینی رسانه/PWA پاس شوند |
| ۹ | استقرار مرحله‌ای، آموزش، پایش و بازگشت UI | دسترسی/سیاست/worker پیش از باز شدن ارسال مشتری آماده باشند |
ترتیب وابستگی: مدل و policy → حساب‌ها → اعلامیه → فیش → تأیید → میزکار/شمارنده → یکپارچگی. UI را می‌توان پیش‌تر preview کرد، اما ارسال مشتری قبل از آماده شدن مسیر بررسی/تأیید فعال نشود. هیچ نیاز اجباری مانند چندنفره یا ارشد به «بعداً» منتقل نشود.
feature flag برای customer hub و عملیات مالی، مهاجرت‌های افزایشی و rollout ابتدا داخلی پیشنهاد می‌شود. خاموش کردن قابلیت، داده ثبت‌شده را حذف نکند و دسترسی staff برای رسیدگی باقی بماند. rollback کد تا قبل از اولین فیش ساده‌تر است؛ بعد از آن برگشت مخرب schema ممنوع و forward fix لازم است.
استقرار شامل migrate، build محلی CSS، collectstatic و rollout web/notification-worker سازگار با هر دو رویداد جدید است. worker جدید پیش از صدور event مالی فعال شود؛ worker قدیمی رویداد مالی را Unsupported می‌داند. schema تغییرات قدیم را حذف نکند. backup دیتابیس و media، آموزش افراد تأیید و چک تنظیمات template لازم‌اند.
برآورد زمان دقیق بدون تصمیم‌های سیاستی و baseline اجرا قابل اتکا نیست. پروژه باید در تحویل‌های قابل آزمون بالا مدیریت شود؛ تکمیل صرف صفحه یا مدل معیار اتمام نیست.
## ۱۵. تصمیم‌های کسب‌وکاری پیش از پیاده‌سازی
این موارد مانع تهیه طرح نیستند؛ پیش از کدنویسی سیاست مالی باید پاسخ ثبت‌شده داشته باشند:
1. افراد اولیه دارای تأیید، ارشد و مدیریت سیاست چه کسانی‌اند؟ انتخاب از DB، نه username hardcode.
2. آیا هر اعلامیه سیاست مخصوص دارد یا سیاست پیش‌فرض واحد؟ پیشنهاد اولیه سیاست نسخه‌دار واحد + fallback عمومی، انتخاب توسط مدیر مجاز.
3. «دو نفر» یعنی هر دو فرد مشخص یا دو نفر از یک مجموعه؟ پیش‌فرض این سند همه افراد منتخب است.
4. رد یک نفر چرخه را می‌بندد؟ پیشنهاد بله، اصلاح با نسخه تازه؛ تأیید ارشد روی چرخه ردشده نیازمند گردش جداست.
5. آیا ثبت‌کننده staff می‌تواند فیش ثبت‌شده خودش را تأیید کند؟ پیشنهاد جلوگیری برای تفکیک وظایف، ولی باید با نیاز شرکت تطبیق داده شود.
6. پرداخت جزئی/چند فیش برای یک اعلامیه مجاز است؟ پیشنهاد مدل یک‌به‌چند از ابتدا؛ تسویه بر جمع مبالغ معتبر تأییدشده، اضافه‌پرداخت با وضعیت مشخص. لغو اعلامیه پرداخت‌شده تسویه را پاک نکند.
7. حساب staff-only در اعلامیه اختصاصی قابل پرداخت مشتری باشد؟ پیشنهاد بله فقط در context آن اعلامیه؛ حساب globally-disabled و اعلامیه قدیمی چه رفتار/مهلت ثبت پرداخت گذشته‌ای دارند؟
8. مشتری مقصدی متفاوت با اعلامیه انتخاب کرد چه شود؟ پیشنهاد ثبت واقعیت با پرچم مغایرت و بررسی ویژه، بدون تسویه خودکار اعلامیه.
9. مبلغ دقیق چه سقفی دارد، تاریخ پرداخت تا چند سال قبل مجاز است و آیا فیش بیش از یک تصویر نیاز دارد؟ پیشنهاد اولیه یک تصویر اجباری مطابق نمونه و سیاست IMAGE سرور.
10. تغییر/لغو تصمیم نهایی، سیاست نگهداری فایل و لاگ، و پیامک اصلاح/رد لازم است؟ دو SMS الزامی فعلی صدور و تأیید نهایی‌اند؛ رویدادهای اضافی نیاز جدا هستند.
## ۱۶. تست‌های موجود بررسی‌شده و قرارداد رگرسیون
| فایل/گروه | قرارداد مؤثر |
| --- | --- |
| `tests/test_permission_architecture.py` | نقش، membership، unit isolation، SELF/UNIT، مجوز عملیات و جعل گزینه فرم |
| `tests/test_role_workflow_regression.py` | نمایندگان نقش‌ها، واحد اولیه، public/private، فرم/گزینه و rollback پیوست |
| `tests/test_organizational_ticketing.py` | dispatch ایزوله، contact shared با group واحدی، audit، template واحدی، فایل، magic و retry |
| `tests/test_domain.py` | OTP، تیکت/پیام، attachment policy، SMS outbox/template، debounce/retry/delivery و Magic Link |
| `tests/test_views.py` | home/history فعلی، branding، sidebar، PWA، magic scope، polling و no-store |
| `tests/test_staff_sharing.py` | اشتراک کل/یک پیام، فایل خصوصی، ابطال، CSRF و عدم افشای همسایه‌ها |
| `tests/test_staff_shared_queue.py` و `test_staff_inline_sharing.py` | مجوز عملیاتی همه نقش‌های فعال روی تیکت قابل مشاهده؛ قابل تعمیم به approval نیست |
| `tests/test_announcements.py` | صف تماس صوتی، قفل PostgreSQL و عدم توقف کار worker در خطای provider |
| `tests/test_voip_calls.py` | مجوز/مسیر تماس و عدم تغییر غیرضروری داده در عملیات |
| `tests/test_branding_operations.py` | متن‌ها و عنوان‌های DB، امضا و layout پنل |
| `tests/test_customer_category_catalog.py` و `test_customer_unit_categories.py` | category واحدی، گزینه‌های فعال و جست‌وجوی معنایی فرم مشتری |
| `tests/test_deployment.py` | private media/Nginx، worker، شبکه و PostgreSQL در استقرار |
| `tests/test_production_seed.py` | allowlist مدل‌ها، permissions با natural keys، import و marker تکرارپذیر |
| `tests/test_env.py` | تنظیم محیط و اولویت منبع تنظیمات |
| `tests/test_jalali_templates.py` و `test_persian_text.py` | تاریخ نمایشی شمسی و حفظ رشته‌های فنی/اعداد در normalization |
| `apps/phone_contacts_api/test_api.py` | API مخاطب، token، فعال بودن و normalization شماره؛ داده مالی نباید به این API افزوده شود |
| `tests/js/chat-coach.test.mjs` | رفتار JS مکالمه؛ انتقال CSS/JS نباید آن را خراب کند |
| `tests/e2e/test_public_ticket_workflows_e2e.py` | مسیر واقعی ثبت/پیام/فایل مشتری و کارشناس، scope Magic Link |
| `tests/e2e/test_organization_permissions_e2e.py` | رفتار واقعی نقش‌ها، گزینه‌های AJAX و مرز واحد |
| `tests/e2e/test_staff_operations_ui.py` | فرم staff، picker، برند، sidebar، گزارش دلیوری و موبایل |
| `tests/e2e/test_staff_sharing_e2e.py` | اشتراک واقعی و فایل در مرورگر |
| `tests/e2e/test_customer_unit_categories_e2e.py` | جست‌وجوی دسته مشتری و انتخاب واحد |
| `tests/e2e/test_chat_coach.py` | راهنمای مکالمه و تعامل مرورگر |
| `tests/e2e/test_mobile_ticket_viewport.py` | صفحه‌کلید، composer، viewport و اسکرول موبایل |
| `tests/e2e/test_pwa_versioning.py` | URL/cache نسخه‌دار و lifecycle service worker |
| `tests/e2e/test_external_environment_smoke.py` | smoke فقط‌خواندنی محیط خارجی؛ جدا از آزمون DB محلی |
| `design-prototypes/ticket-new-wizard/test_prototype_e2e.py` | تست مستقل prototype روی پورت ۴۱۷۳؛ تست production چهارسو نیست |
`pytest.ini`، fixtureهای `tests/e2e/conftest.py`، config/README و Page Objectهای auth/base/customer_new_ticket/sharing/tickets نیز مبنا قرار گرفتند. اجرای تست مستقل prototype نیازمند سرور خودش است و نباید با مجموعه live_server اشتباه شود.
تست‌های home که حضور `recent-requests` در `/tickets/` را الزام می‌کنند با تغییر محصول باید به history منتقل شوند؛ assertions مجوز، فایل، PWA و branding تضعیف نشوند. تیکت‌های تاریخی با دسته «فیش واریزی» خودکار به رکورد تأییدشده مالی تبدیل نشوند.
## ۱۷. پوشش آزمون جدید و معیار پذیرش نهایی
### دامنه، مجوز و دیتابیس
- مشتری الف نتواند اعلامیه/فیش/فایل مشتری ب را ببیند یا مرتبط کند؛ staff غیرفعال، مجوز لغوشده، CSRF و POST جعلی پوشش داده شوند.
- ALL_STAFF مشاهده/ایجاد بدون مجوز تأیید؛ حالت EXPLICIT واقعاً queryset/جزئیات/count/files را محدود کند؛ role finance مجوز تأیید خودکار نگیرد.
- مدیرعامل/کاربر مجاز بانک‌ها را مدیریت کند؛ سه flag، bulk، stale form، حساب استفاده‌شده و snapshot تاریخی پوشش داده شوند.
- مبلغ صفر/منفی/اعشاری/بسیار بزرگ، رقم فارسی/عربی، هزارگان نامعتبر، تاریخ ناقص/آینده/اسفند کبیسه و تغییر روز در ماه کوتاه آزموده شوند.
- فیش مستقل account label فقط، فیش مرتبط با owner معتبر، مقصد متفاوت و expected/paid متفاوت، چند فیش/پرداخت جزئی طبق سیاست مصوب.
- فایل spoof، خراب، خالی/بزرگ، decode و مجوز دانلود؛ rollback DB و پاک‌سازی فایل باقی‌مانده.
- idempotency ثبت، رأی تکراری، شرط دو شخص مشخص، رأی آخر هم‌زمان، approve/reject race، ارشد/رأی عادی هم‌زمان، stale revision و تغییر مجوز حین اقدام.
- قفل‌ها و unique constraints با PostgreSQL و `TransactionTestCase` یا pytest transactional و connectionهای جدا؛ SQLite اثبات رفتار lock PostgreSQL نیست.
- اصلاح نسخه رأی‌های قدیمی را معتبر نکند؛ رد و ارشد دلیل/actor/time ثبت کنند؛ سوابق قابل حذف عادی نباشند.
- صدور فقط یک outbox، رأی جزئی صفر SMS تأیید، نهایی شدن یک SMS، نبود template، unit/global override، tag غلط، provider failure/retry و کارکرد رویدادهای قدیمی.
- شمارنده شامل مستقل/جزئی، حذف تکرار join، فیلتر permission قبل از count، pagination پایدار و تعداد query محدود.
### مرورگر و تجربه کاربر
- ورود مشتری به خانه چهارسو و مسیر جدا تاریخچه؛ خانه بدون سوابق و سه خدمت در اندازه معمول ۳۲۰×۵۶۸، بدون overflow.
- صفحات مستقل شماره حساب‌ها/فیش، اطلاعات تکمیلی نمایان، کپی تکی/کامل و fallback Clipboard با اطلاعات مجاز کامل.
- فیش مرتبط اول، مستقل در دسترس، حفظ داده هنگام تغییر نوع، مقصد فقط نام، خطا و فوکوس مناسب.
- ورودی مبلغ: تایپ وسط متن، selection، Backspace/Delete کنار جداکننده، paste ارقام مختلف، مبلغ بزرگ و معادل دقیق تومان.
- تاریخ سه‌قسمتی، امروز به وقت ایران، تغییر ماه کبیسه، ارسال Gregorian و حفظ تاریخ در تغییر حالت؛ حالت نبود پشتیبانی Intl/JS مسیر قابل استفاده سرور داشته باشد.
- ثبت واقعی → صفحه پیگیری وضعیت؛ پیام موفقیت جعلی prototype حذف شود؛ reload و double click فیش تکراری نسازد.
- کارشناس صدور و مشاهده همه/محدود، فیش مستقل در میزکار، جست‌وجو/فیلتر/page، تأیید دو نفر و ارشد، timeline و badge.
- فونت/asset/SVG همگی از پروژه؛ intercept شبکه دارایی خارجی، PWA عدم cache مالی/فایل و نسخه‌های جدید را کنترل کند.
- desktop/mobile sidebar active state، shares badge و composer صفحات قدیمی رگرسیون نداشته باشند؛ keyboard، zoom، safe-area و متن بلند بررسی شوند.
تست‌های E2E جدید زیر `tests/e2e/` و Page Objectهای مالی زیر `tests/e2e/pages/`؛ fixture Chromium/BrowserContext function-scoped فعلی، marker e2e و live_server transactional. بدون sleep ثابت و assertions تنها روی نتیجه قابل مشاهده. provider پیامک mock شود؛ تست‌ها پیامک واقعی نفرستند. screenshot/trace شکست از fixture موجود استفاده کند.
فرمان‌های اجرای آینده از WSL و با venv مخزن:
```bash
.venv/Scripts/python.exe -m pytest tests -m "not e2e" -q
.venv/Scripts/python.exe -m pytest apps/phone_contacts_api/test_api.py -q
.venv/Scripts/python.exe -m pytest tests/e2e -m "e2e and not external_e2e" -q
node --test tests/js/chat-coach.test.mjs
```
تست‌های جدید قفل/هم‌زمانی نیز به suite افزوده و روی PostgreSQL اجرا شوند. تغییر frontend caching/PWA به‌طور خاص باید `tests/test_views.py` و `tests/e2e/test_pwa_versioning.py` را پاس کند. این فرمان‌ها در مرحله تحلیل اجرا نشده‌اند.
معیار تحویل: یک کارشناس اعلامیه اختصاصی صادر کند، پیامک از template DB صف شود، مشتری فقط مورد خودش را ببیند و فیش مستقل/مرتبط ثبت کند، اشخاص دقیقاً تعیین‌شده یا ارشد مجاز تأیید کنند، همه سوابق و نسخه‌ها ثبت و قابل بررسی باشند، SMS نهایی فقط بعد از پایان شرط ارسال شود، و جریان‌های فعلی تیکت/دسترسی/فایل/PWA سالم باقی بمانند.