feat: paginate customer orders and polish the dashboard hero

Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-17 13:11:42 +03:30
co-authored by Cursor
parent f550172567
commit afb068c643
89 changed files with 4518 additions and 1325 deletions
+5 -5
View File
@@ -1,13 +1,13 @@
# برند پروژه (زرتاج) — چک‌لیست تغییر نام
# برند پروژه (تهران تاج) — چک‌لیست تغییر نام
نام برند **زرتاج / Zartaj** است. برای عوض کردن نام دوباره **لازم نیست کل کد را بگردید**؛ فقط موارد همین فهرست.
نام برند **تهران تاج / Tehran Taj** است. برای عوض کردن نام دوباره **لازم نیست کل کد را بگردید**؛ فقط موارد همین فهرست.
## ۱. جاهایی که باید عوض شوند (فقط همین‌ها)
| چه چیزی | کجا | توضیح |
|---|---|---|
| نام فارسی، نام لاتین، عنوان تب مرورگر، آدرس سایت | [`brand.ts`](../brand.ts) در ریشهٔ پروژه | پنل ادمین، اپ مشتری و اپ مشتری v2 همه از همین یک فایل می‌خوانند: عنوان صفحه، نام نصب PWA، عنوان صفحهٔ ورود، پیام خوش‌آمد، متن‌های راهنما، لینک محصول. |
| نام برند در **همهٔ پیامک‌ها** | متغیر محیطی `BRAND_NAME` روی سرور API | خالی باشد = «زرتاج» (پیش‌فرض در [`apps/api/src/common/brand.ts`](../apps/api/src/common/brand.ts)). بعد از تغییر فقط API را ری‌استارت کنید. |
| نام برند در **همهٔ پیامک‌ها** | متغیر محیطی `BRAND_NAME` روی سرور API | خالی باشد = «تهران تاج» (پیش‌فرض در [`apps/api/src/common/brand.ts`](../apps/api/src/common/brand.ts)). بعد از تغییر فقط API را ری‌استارت کنید. |
| لینک سایت در پیامک خوش‌آمد | متغیر محیطی `BRAND_SITE_URL` روی سرور API | پیش‌فرض فعلاً `https://bnk.montzar.ir` است (دامنهٔ زنده). |
| لوگو | فایل‌های تصویر با نام ثابت `brand-*` (پایین) | نام فایل‌ها عمومی است؛ فقط خود تصویر را با همان نام جایگزین کنید. |
@@ -35,7 +35,7 @@
| کلیدهای مرورگر مثل `gold-bank:token` | تغییر = خروج همهٔ مشتری‌ها از اپ |
| جدول `_goldbank_migrations`، نام دیتابیس `goldbank`، سرویس داکر `goldbank-api` | تغییر = خراب شدن مایگریشن و استقرار روی سرور |
| نام پکیج‌ها `@gold-bank/*` و پوشهٔ پروژه | فقط نام فنی |
| شناسهٔ برند محصول `brand-montzar` در دیتابیس | کلید اصلی است و محصولات به آن وصل‌اند؛ عنوانش به «زرتاج طلا» تغییر کرد |
| شناسهٔ برند محصول `brand-montzar` در دیتابیس | کلید اصلی است و محصولات به آن وصل‌اند؛ عنوانش به «تهران تاج طلا» تغییر کرد |
| پوشه‌های `henza_admin`، `henza_pwa`، `henza_server` و ارجاع‌های کد به آن‌ها | کد سیستم قدیمی، فقط برای مرجع |
| آدرس‌های `henzagold.com` در توضیحات کد | سابقهٔ سیستم قدیمی، اجرا نمی‌شوند |
| پوشهٔ `docs/` (سوابق) | سابقهٔ کار است، نه متن برنامه |
@@ -45,7 +45,7 @@
برای اطمینان از اینکه نام قدیمی جایی در متن‌های برنامه نمانده:
```bash
grep -rn "منتزر\|Montzar\|هنزا" apps/*/src apps/*/index.html apps/api/prisma/seed.ts
grep -rn "زرتاج\|Zartaj\|منتزر\|Montzar\|هنزا" apps/*/src apps/*/index.html apps/api/prisma/seed.ts brand.ts
```
خروجی خالی یعنی تمیز است.
+2 -2
View File
@@ -118,8 +118,8 @@ ProductImage: @@index([productId])
| ۱ | مهاجرت ایندکس‌ها | ✅ **انجام شد** (`cbb93de`) — `Product` هیچ ایندکسی نداشت |
| ۲ | مسیرهای سرور + تست | ✅ **انجام شد** (`cbb93de`) — صفحه‌بندی opt-in، فیلتر سمت سرور، ۱۷ تست |
| ۳ | صفحه محصول، سبد، علاقه‌مندی، دسته‌ها، برچسب آیتم سفارش | ✅ **انجام شد** — پنج مصرف‌کننده منتقل شد |
| ۴ | `SearchView` / `NewestView` / rails خانه → صفحه‌بندی واقعی | ⏳ **باقی** — پرریسک‌ترین (فیلتر + اسکرول) |
| ۵ | حذف خوانش کل کاتالوگ و هشدار لاگ | ⏳ فقط وقتی هیچ مصرف‌کننده‌ای نمانده |
| ۴ | `SearchView` / `NewestView` / rails خانه → صفحه‌بندی واقعی | ✅ **انجام شد 2026-10-17** — هر دو اپ؛ facets + `listProductsPaged`؛ multi-brand با `brandId=a,b` |
| ۵ | حذف خوانش کل کاتالوگ و هشدار لاگ | ⏳ `catalog.load` هنوز یک صفحهٔ ۱۰۰تایی می‌گیرد برای callerهای باقی‌مانده؛ هشدار لاگ بعد از پاک‌سازی |
### یادداشت مرحله ۳ (انجام‌شده)
+384
View File
@@ -0,0 +1,384 @@
# راهنمای ارائه — گلدبانک
> تاریخ: ۲۰۲۶-۱۰-۱۴ (۱۴۰۵/۰۷/۲۲) · بر اساس کد فعلی مخزن. اعداد آزمون‌ها از تاریخ خودشان نقل شده‌اند.
> این سند برای ارائه است: اول منطق کار را توضیح می‌دهد، بعد جزئیات فنی. نام فایل‌ها فقط در پیوست آمده است.
---
## ۱. نگاه کلی
گلدبانک از سه برنامه و یک سرور تشکیل شده:
| بخش | کاربر | کار |
|---|---|---|
| **سرور (API)** | — | تمام قانون‌های کسب‌وکار: مشتری، محصول، سفارش، بدهی، اتصال به حسابداری |
| **پنل ادمین** | مدیر، کارشناس فروش، حسابدار، سرپرست کارگاه | مدیریت مشتریان، محصولات، سفارش‌ها، تنظیمات |
| **اپ مشتری** (دو نسخه) | مشتری و مغازه‌دار | ثبت‌نام، ورود با پیامک، کاتالوگ، سفارش، مانده و گزارش حساب |
سه موضوع اصلی که باید در ارائه روشن شود:
1. **اشخاص** — چه کسی مشتری است، چطور تأیید می‌شود و چه سقفی دارد.
2. **محصولات** — کالا، واریانت، عکس، موجودی و اجرت.
3. **اتصال به حسابداری** — مهم‌ترین بخش؛ اپ با ته‌حساب یا زرین همگام می‌شود.
**اصل طراحی که همه‌ی بخش‌ها روی آن ساخته شده:** دفتر داخلی (`LedgerEntry`) فقط افزودنی است. هیچ ردیفی ویرایش یا حذف نمی‌شود؛ بدهی هر مشتری از جمع این ردیف‌ها به دست می‌آید و ستون‌های مانده در مشتری فقط کش آن‌ها هستند.
---
## ۲. اشخاص
### ۲.۱ انواع «شخص» در سامانه
- **مشتری (Customer):** کلید شناسایی او **شماره موبایل** است و یکتاست. کد ملی اختیاری است (از ۲۰۲۶-۱۰-۰۷ به بعد). نوع مشتری یکی از این‌هاست:
- مغازه‌دار اپ، مغازه‌دار حضوری، بنکدار، سرپرست کارگاه، تک‌فروشی.
- **کاربر ادمین (AdminUser):** نقش‌ها: مدیر کل، تأییدکننده‌ی سیستم، **کارشناس فروش**، سرپرست کارگاه، حسابدار.
- کارشناس فروش هم یک حساب معاملاتی مشتری دارد و با همان پیامک وارد اپ می‌شود.
### ۲.۲ چرخه‌ی زندگی یک مشتری
1. **ثبت‌نام در اپ:** مشتری فرم را پر می‌کند (نام، فروشگاه، تلفن، آدرس، شهر). حساب **غیرفعال** ساخته می‌شود و وضعیت آن «مرحله‌ی اول ارسال شد» است. با شماره‌ای که قبلاً ثبت شده، ثبت‌نام تازه پذیرفته نمی‌شود. تعداد ثبت‌نام از هر IP محدود است.
2. **بررسی ادمین در دو مرحله:**
- تأیید مرحله‌ی اول: حساب فعال می‌شود و وضعیت «مرحله‌ی دوم» می‌گیرد.
- تأیید مرحله‌ی دوم: وضعیت **تأییدشده (VERIFIED)**. همین لحظه حساب در حسابداری ساخته یا لینک می‌شود (بخش ۵).
- رد: وضعیت **ردشده** با دلیلی که ادمین می‌نویسد. دلیل برای مشتری‌های قدیمی که دلیل نداشتند، متن ثابت «فاقد شرح علت» است.
3. **ورود با کد پیامکی (OTP):** کد هش می‌شود، انقضا دارد، تعداد تلاش‌های غلط محدود است و ارسال برای هر شماره و هر IP محدود است. فقط حساب فعال و غیرآرشیو وارد می‌شود.
4. **سه کلید جدا برای کنترل:**
- `isActive`: حساب روشن یا خاموش.
- `canPlaceOrders`: «معلق کردن سفارش» — مشتری از اپ سفارش نمی‌دهد. ادمین می‌تواند از پنل برایش سفارش ثبت کند.
- `isArchived`: آرشیو.
### ۲.۳ سقف اعتبار و بدهی
- هر مشتری یک **سقف گرمی** دارد (`goldCreditLimitGrams`) و یک **بدهی طلای داخلی** (`goldDebtGrams`).
- وقتی سفارش ثبت می‌شود: اگر `وزن سفارش + بدهی فعلی` از سقف بیشتر شود، سفارش در وضعیت **معلق** ثبت می‌شود و مشتری پیامک می‌گیرد.
- **این چک فقط با دفتر داخلی انجام می‌شود، نه با حسابداری.** مانده‌ی حسابداری به مشتری نشان داده می‌شود، اما سقف اعتبار دست نمی‌خورد (تصمیم مالک، D27).
### ۲.۴ کارشناس فروش (معرف)
- هر مشتری می‌تواند یک کارشناس فروش معرف داشته باشد.
- **سفارش، کارشناسِ همان لحظه‌ی ثبت را می‌گیرد.** اگر بعداً معرف مشتری عوض شود، فروش‌های گذشته جابه‌جا نمی‌شوند (حکم مالک).
- کارشناس می‌تواند با «ورود به حساب مشتری» به جای او سفارش ثبت کند. سفارش کاملاً مال مشتری است، ولی در لاگ ثبت می‌شود که چه کسی واقعاً آن را ثبت کرده است.
### ۲.۵ صفحات ادمین برای مشتریان
- **لیست‌ها:** عمده (همه)، بنکدار، مغازه‌دار، تأییدنشده، آرشیو، تک‌فروشی، کارشناسان فروش.
- **عملیات:** تأیید یا رد مرحله‌ها، فعال و غیرفعال، معلق سفارش، آرشیو، شارژ کیف پول، ثبت بازپرداخت، داشبورد مشتری، سفارش‌ها و اسناد او.
- **بازپرداخت** (طلای آب‌شده، حواله، چک، نقد): ثبت آن **در انتظار تأیید** است. تا ادمین تأیید نکند روی بدهی اثر نمی‌گذارد. این تفاوت عمدی با سیستم قدیم است.
---
## ۳. محصولات
### ۳.۱ ساختار
- **محصول (Product):** عنوان، کد محصول (یکتا)، دسته‌بندی، برند، نوع نمایش (سایت، موبایل یا هر دو)، فعال یا غیرفعال، مجموعه‌ها.
- **واریانت (ProductVariant):** گزینه‌ای که مشتری می‌خرد؛ مثلاً یک وزن مشخص. دارای وزن راش، درصد اجرت پیش‌فرض، موجودی، پیش‌سفارش و تصویر نمایشی.
- **عکس (ProductImage):** چهار نقش دارد: شاخص سایت، شاخص موبایل، گالری سایت، گالری موبایل.
### ۳.۲ قانون‌های مهم
- **بدون عکس، نه نمایش داده می‌شود و نه قابل سفارش است.** این برای همه‌ی محصولات است، نه فقط محصولات حسابداری.
- **موجودی صفر** یعنی قابل سفارش نیست، مگر واریانت پیش‌سفارش باشد.
- **فهرست کاتالوگ صفحه‌بندی شده است** چون تعداد محصولات به حدود ۱۶ هزار می‌رسد.
- **محصول متصل به حسابداری:** عنوان و کد آن در پنل ادمین قفل است و فقط در حسابداری تغییر می‌کند. عکس، توضیح، واریانت، دسته‌بندی و درصد اجرت آزاد است و در اپ تعیین می‌شود.
- **موجودی حسابداری** (اگر محصول متصل باشد): صفر یا کمتر یعنی ناموجود در اپ. موجودی هنگام سفارش **همان لحظه** از حسابداری خوانده می‌شود. اگر حسابداری در ۲٫۵ ثانیه جواب نداد، آخرین موجودی ذخیره‌شده ملاک است.
### ۳.۳ محاسبه‌ی وزن سفارش و اجرت
برای هر قلم سفارش:
> **وزن کل = وزن راش × (۱ + (اجرت − تخفیف) ÷ ۱۰۰) × تعداد**
- **اجرت** = درصد اجرت واریانت + درصد اختصاصی مشتری − **بزرگ‌ترین** تخفیف فعال (روی محصول، دسته یا برند).
- این فرمول همان فرمول قدیمی است. مدیر می‌تواند ضرایب، کف و سقف و گرد کردن آن را در پنل تغییر دهد.
- **سرور خودش اجرت را حساب می‌کند.** اعدادی که اپ مشتری می‌فرستد نادیده گرفته می‌شود، پس دستکاری درخواست در قیمت اثری ندارد. اگر اختلاف باشد، در لاگ ثبت می‌شود.
- بخش ریالی ثابت (مثلاً سنگ) از اپ مشتری صفر است و فقط ادمین می‌تواند آن را وارد کند.
---
## ۴. سفارش و بدهی
### ۴.۱ وضعیت‌های سفارش
| وضعیت | معنی |
|---|---|
| معلق | از سقف گرمی گذشته؛ منتظر تصمیم |
| ثبت‌شده | سفارش آمده |
| تأیید مدیر | مدیر تأیید کرده |
| تأمین | در حال تأمین کالا |
| آماده‌سازی | در حال آماده‌سازی |
| تحویل به مسئول فروش | **نقطه‌ی شناسایی بدهی** (پیش‌فرض) |
| تحویل به مشتری | تحویل نهایی؛ بدهی هم شناخته می‌شود |
| لغو | سفارش کنسل شده |
### ۴.۲ منطق بدهی
- بدهی طلا و ریال سفارش **فقط در دو وضعیت تحویل** (به مسئول فروش یا به مشتری) ثبت می‌شود.
- هر بار که وضعیت عوض می‌شود، سیستم بدهی را با وضعیت سفارش **هم‌تراز** می‌کند: فقط اختلاف را به دفتر اضافه می‌کند. پس:
- تغییر وضعیت دوباره به «تحویل» بدهی را دوبار ثبت نمی‌کند (این مشکل قبلاً بود و برطرف شده است؛ ACC-03).
- برگشت به زیر «تحویل» یا لغو، بدهی را برمی‌دارد.
- ثبت بدهی در همان تراکنش تغییر وضعیت انجام می‌شود، پس یا هر دو ثبت می‌شوند یا هیچ‌کدام.
### ۴.۳ سفارش‌های ساختنی (طراحی سفارشی)
**تعریف:** درخواست طراحی و ساخت یک قطعه‌ی سفارشی است (رنگ، سایز، رنگ مینا، رنگ سنگ، عکس طرح). **این کار با «پیش‌سفارش» فرق دارد:** پیش‌سفارش یک گزینه‌ی واریانت است که با موجودی صفر هم قابل خرید است (بخش ۳). سفارش ساختنی یک رکورد جدا (`PreOrder`) است.
**چیزی که هر رکورد دارد:**
- مشتری، شهر، کارگاه، عکس طرح، رنگ‌ها و سایز، وزن و قیمت تخمینی، توضیح.
- وضعیت‌ها: ثبت شده ← تأیید مدیر ← تأیید کارگاه ← آماده ارسال ← تحویل شده. یا لغو.
- تاریخچه‌ی تغییرات (`PreOrderStatusEvent`).
**منطق پنل ادمین** (صفحه «سفارش‌های ساختنی»، مجوز `order.makeOrder`):
- فهرست با جستجو بر اساس نام و موبایل مشتری.
- مشاهده‌ی جزئیات و ویرایش **رنگ محصول، رنگ مینا و رنگ سنگ** (فقط همین سه فیلد ذخیره می‌شوند).
- حذف فقط وقتی ممکن است که سفارش هنوز به سفارش قطعی تبدیل نشده باشد.
**وضعیت واقعی آن در سامانه (برای ارائه‌ی صادقانه):**
- **ثبت جدید وجود ندارد.** نه سرور راهی برای ساختن سفارش ساختنی دارد، نه پنل ادمین و نه اپ‌های مشتری. رکوردها فقط از داده‌ی سیستم قدیم منتقل شده‌اند.
- **تبدیل به سفارش قطعی وجود ندارد.** فیلد `convertedOrderId` در مدل هست، ولی راهی برای پر کردن آن نوشته نشده است. مدل می‌گوید «مدیر تبدیل می‌کند»، اما این کار در کد فعلی امکان‌پذیر نیست.
- **تخصیص کارگاه ذخیره نمی‌شود:** پنل گزینه‌ی انتخاب کارگاه نشان می‌دهد، ولی هنگام ذخیره فقط سه فیلد رنگ فرستاده می‌شود.
- **تعداد ثابت است:** هر سفارش ساختنی تعداد ۱ حساب می‌شود؛ فیلد تعداد در پنل ذخیره نمی‌شود.
- **تغییر وضعیت:** سرور این کار را می‌پذیرد، ولی پنل آن را نشان نمی‌دهد.
### ۴.۴ لیست تامین
**تعریف:** فهرست سفارش‌هایی که به مرحله‌ی **«در حال تأمین»** رسیده‌اند؛ یعنی مرحله‌ای که کالا یا مواد لازم برای ساخت تهیه می‌شود. مجوز `order.tamin` (صفحه‌ی «لیست تامین»).
**منطق نمایش:**
- فهرست شامل **سفارش‌هایی است که حداقل یک‌بار** به وضعیت «در حال تأمین» رسیده‌اند، حتی اگر حالا جلوتر رفته باشند (تأمین شده یا آماده‌سازی). این یعنی فهرست «تاریخچه‌ی تأمین» است، نه فقط «الان در تأمین».
- ستون‌ها: شماره، مشتری، موبایل، وزن ورودی، اجرت، تعداد آیتم، تاریخ ثبت و تاریخ ویرایش.
- با کلیک روی یک سفارش، جزئیات آیتم‌ها باز می‌شود: نام محصول، عنوان واریانت، وزن نهایی، تعداد.
**تغییر وضعیت از این فهرست:**
- با مجوز `order.tamin.state` می‌توان وضعیت را ثبت کرد. هر ثبت یک یادداشت دارد و در تاریخچه‌ی سفارش می‌نشیند.
- وضعیت آخر را می‌توان حذف کرد (با مجوز `order.tamin.state.remove`)؛ سفارش به وضعیت قبلی برمی‌گردد.
- هر تغییر وضعیت **همان منطق بدهی و سند حسابداری** سفارش (بخش ۴.۲ و ۵.۸) را اجرا می‌کند. یعنی ثبت یا حذف وضعیت می‌تواند بدهی و سند حسابداری را جابه‌جا کند.
**نقص‌های شناخته‌شده (باید گفته شود):**
- **ستون «وضعیت تأیید مشتری» خالی است.** پنل این ستون و فیلتر آن را دارد، ولی سرور این فیلد را برنمی‌گرداند. پس این فیلتر عملاً کاری نمی‌کند.
- **نام صفحه با عنوان آن یکی نیست:** عنوان کارت «لیست سفارش‌های عمده» است، همان عنوان فهرست عمده. این عمداً در نسخه‌ی قدیم هم همین بود و تغییر داده نشده است.
---
## ۵. اتصال به حسابداری (بخش مهم)
### ۵.۱ خلاصه
اپ به **یکی** از دو نرم‌افزار حسابداری که روی سرور شرکت نصب است وصل می‌شود:
- **ته‌حساب** (Tahesab)
- **زرین** (Zarrin)
انتخاب با یک سوییچ در پنل ادمین انجام می‌شود: `بدون اتصال / ته‌حساب / زرین`. هر بخش هم جداگانه روشن یا خاموش می‌شود. تا وقتی ادمین روشن نکند، سیستم هیچ ارتباطی برقرار نمی‌کند.
### ۵.۲ آنچه همگام می‌شود
| بخش | جهت | کار |
|---|---|---|
| **مشتریان** | حسابداری ← اپ | حساب‌های گروه‌های انتخاب‌شده وارد اپ یا به مشتری موجود لینک می‌شوند |
| **مشتریان** | اپ ← حسابداری | مشتری تأییدشده در حسابداری ساخته می‌شود؛ ویرایش مشخصات هم فرستاده می‌شود |
| **مانده** | حسابداری ← اپ | مانده‌ی طلا و ریال مشتری، نمایش در پروفایل |
| **گزارش حساب** | حسابداری ← اپ | صورت‌حساب مشتری از حسابداری خوانده می‌شود تا با مانده یکی باشد |
| **کارساخته / محصول** | دو طرفه | عنوان، کد و موجودی همگام؛ محصول تازه‌ی حسابداری در اپ ساخته می‌شود |
| **سند سفارش** | اپ ← حسابداری | سند موقت (پیش‌فاکتور) در وضعیتی که مدیر تعیین کرده ثبت می‌شود |
**عمداً همگام نمی‌شود:** اسناد دریافت و بازپرداخت (طلای آب‌شده، نقد، چک)، کیف پول. این‌ها در اپ می‌مانند.
### ۵.۳ لحظه‌ای بودن — چطور کار می‌کند
سه لایه، چون هیچ‌کدام از دو نرم‌افزار وب‌هوک ندارند:
1. **صف خروجی (Outbox):** هر رویداد لازم برای حسابداری (مثلاً ثبت سفارش) **در همان تراکنش** دیتابیس یک ردیف در صف می‌نویسد. پس اگر حسابداری خاموش باشد، هیچ رویدادی گم نمی‌شود و ثبت سفارش در اپ متوقف نمی‌شود.
2. **کارگر (worker) داخل سرور:**
- هر **۲ ثانیه** صف را می‌فرستد.
- هر **۵ ثانیه** فید تغییرات حسابداری را می‌خواند؛ فقط حساب‌ها و کارساخته‌های تغییرکرده دوباره خوانده می‌شوند.
3. **شبکه‌ی اطمینان:** هر **۳۰ ثانیه** کارهای کامل اجرا می‌شود تا چیزی که فید نمی‌بیند (حذف، مشتری جدید بدون سند) گم نشود.
قوانین تکمیلی:
- **قفل:** اگر چند نمونه‌ی سرور در حال اجرا باشند، فقط یکی کارها را اجرا می‌کند.
- **شکست:** تلاش دوباره با فاصله‌ی نمایی، حداکثر ۸ بار؛ بعد وضعیت «ناموفق» و نمایش در پنل.
- **تکرار خطرناک:** اگر نتیجه‌ی یک درخواست نامشخص باشد و تکرار آن سند را دو بار ثبت کند، کار به وضعیت **«نیاز به بررسی»** می‌رود و خودکار دوباره فرستاده نمی‌شود.
- ته‌حساب: قبل از تلاش دوباره، سیستم استعلام می‌کند که سند قبلاً ثبت نشده باشد.
- زرین: کلید ضد تکرار ندارد، پس در صورت timeout فقط بررسی دستی.
- **مانده در پروفایل مشتری:** اگر قدیمی‌تر از ۵ ثانیه باشد، همان لحظه از حسابداری خوانده می‌شود (مهلت ۳ ثانیه). اگر جواب نداد، آخرین مقدار نشان داده می‌شود.
### ۵.۴ مشتری — ورود، لینک و ساخت
**منبع:** فقط گروه‌هایی که در تنظیمات انتخاب شده‌اند. اگر هیچ گروهی انتخاب نشده باشد، ورود متوقف می‌شود (چون «همه» یعنی صندوق‌ها و کارمندان هم مشتری اپ شوند).
**کلید تطبیق:** شماره موبایل نرمال‌شده (ارقام فارسی به انگلیسی، `912…` به `0912…`).
- موبایل در اپ هست ← **لینک** می‌شود و کد حسابداری ذخیره می‌شود.
- موبایل در اپ نیست ← **مشتری تازه در اپ** ساخته می‌شود:
- وضعیت «تأییدنشده»، سقف اعتبار صفر، ورود فقط با پیامک.
- کد ملی از حسابداری گرفته می‌شود اگر معتبر و آزاد باشد؛ وگرنه **خود شماره موبایل** به‌عنوان کد ملی ذخیره می‌شود. این یک راه‌حل موقت است و باید بعداً پاک شود (بخش ۸).
- موبایل خالی، یا دو حساب با یک موبایل ← به **فهرست مغایرت** می‌رود و خودکار اقدامی نمی‌شود.
**ارسال به حسابداری (push):**
- وقتی مشتری **تأیید نهایی** شد، ابتدا با موبایل جستجو می‌شود. اگر پیدا شد لینک می‌شود؛ اگر نه، حساب تازه در گروه مشخص‌شده ساخته می‌شود.
- این کار فقط یک‌بار برای مشتری بدون لینک انجام می‌شود.
- تغییر نام، تلفن یا آدرس مشتری لینک‌شده هم فرستاده می‌شود.
### ۵.۵ مانده
- **علامت:** در اپ، **مثبت یعنی بدهکار**. ته‌حساب مانده‌ی منفی را بدهکار نشان می‌دهد و در درایور برعکس می‌شود. در زرین پیش‌فرض «منفی = بدهکار» است و قابل تنظیم است.
- **نمایش:** در پروفایل مشتری دو مانده کنار هم: طلا (گرم) و ریال، به همراه زمان آخرین به‌روزرسانی.
- **اعتبار:** سقف اعتبار با مانده‌ی حسابداری حساب نمی‌شود؛ همان دفتر داخلی است.
### ۵.۶ گزارش حساب
- وقتی اتصال روشن باشد، گزارش از حسابداری خوانده می‌شود تا با مانده یکی باشد.
- اگر حسابداری جواب ندهد، گزارش **از دفتر داخلی** نشان داده می‌شود و در صفحه برچسب منبع می‌خورد. صفحه باز می‌ماند.
- **امنیت:** جزئیات هر سند فقط وقتی نشان داده می‌شود که کد حساب سند با کد لینک‌شده‌ی همان مشتری یکی باشد.
- **تفاوت دو حسابداری:** در ته‌حساب، سند موقت روی مانده اثر ندارد تا حسابدار آن را نهایی کند. در زرین، سند موقت همان لحظه روی مانده اثر دارد.
### ۵.۷ کارساخته و محصول
- **از حسابداری به اپ:** کارساخته‌ی جدید ← محصول جدید در اپ، **بدون عکس و بدون واریانت**. تا ادمین عکس و واریانت نگذارد، در اپ دیده نمی‌شود.
- **از اپ به حسابداری:**
- **زرین:** محصول جدید در پنل ← کارساخته‌ی جدید در زرین ساخته می‌شود. (ساخت کارساخته روی نصب واقعی هنوز آزمایش نشده است.)
- **ته‌حساب:** API ساخت کارساخته ندارد. نام کار با **اولین سند** آن در ته‌حساب ساخته می‌شود و از آن به بعد لینک می‌شود. این محدودیت از خود API است.
- **موجودی:** در پنل ادمین نشان داده می‌شود. صفر یا کمتر ← ناموجود در اپ.
### ۵.۸ سند سفارش
- **زمان ثبت:** هر وضعیتی که مدیر در تنظیمات مشخص کرده باشد (پیش‌فرض: تحویل به مسئول فروش) یا بعد از آن. سند **موقت** (پیش‌فاکتور) ثبت می‌شود و **یک‌بار** فرستاده می‌شود.
- **برگشت:** اگر سفارش به وضعیت زیر آن برگردد یا لغو شود، سند پس گرفته می‌شود. اگر سند هنوز موقت باشد، حذف می‌شود؛ اگر در حسابداری نهایی شده باشد، کار به «بررسی» می‌رود.
- **ته‌حساب:** هر قلم یک ردیف سند است. کد سند از شناسه‌ی سفارش ساخته می‌شود تا دوبار ثبت نشود.
- **زرین:** سند چندردیفی است و پیشرفت هر ردیف ذخیره می‌شود. اگر وسط کار قطع شود، از همان‌جا ادامه می‌دهد.
- **پذیرش:** بعد از ثبت، تغییر مانده‌ی وزنی حسابداری باید دقیقاً برابر وزن سفارش باشد. این معیار روی سرور تست ته‌حساب آزمایش شده است.
- **اجرت در ته‌حساب:** اجرت به صورت **افزایش عیار** فرستاده می‌شود (مثلاً ۷۵۰ با ۱۰٪ اجرت ← ۸۲۵). وزن فیزیکی کالا ثابت می‌ماند.
### ۵.۹ پنل مدیریت اتصال
صفحه‌ی **«اتصال حسابداری»** با مجوز `config.accounting`:
1. **اتصال:** سوییچ درایور، آدرس، توکن (فقط نوشتنی؛ هرگز نمایش داده نمی‌شود)، گواهی CA برای نصب‌های self-signed، دکمه‌ی «تست اتصال».
2. **نگاشت:** گروه‌های مشتری برای ورود، گروه مشتری تازه، گروه کارساخته به دسته‌بندی، نوع کارساخته، عیار پیش‌فرض، بازه‌ی گزارش.
3. **قابلیت‌ها و زمان‌بندی:** روشن یا خاموش کردن هر بخش، قانون ثبت سند، دکمه‌های «سینک الان».
4. **لاگ:** صف کارها با فیلتر وضعیت، تلاش دوباره، «حل‌شده»، و درخواست و پاسخ هر کار (بدون توکن).
5. **مغایرت‌ها:** موبایل تکراری یا خالی، کارساخته بی‌دسته، سندهای «نیاز به بررسی».
دکمه‌های **«سینک با …»** در لیست مشتریان عمده و لیست محصولات هم هست. این دکمه‌ها مستقل از سوییچ قابلیت‌ها کار می‌کنند و فقط اتصال برقرار بودن لازم است.
**پیش‌فرض:** اتصال خاموش است و همه‌ی قابلیت‌ها غیرفعال. تا ادمین تنظیم نکند، اپ هیچ کاری با حسابداری نمی‌کند.
### ۵.۱۰ امنیت اتصال
- **توکن:** با AES-256-GCM رمز می‌شود. کلید آن از متغیر `ACCOUNTING_SECRET_KEY` می‌آید.
- اگر این متغیر تنظیم نشده باشد، از `JWT_SECRET` استفاده می‌شود و هشدار داده می‌شود.
- **تغییر `JWT_SECRET` توکن ذخیره‌شده را غیرقابل خواندن می‌کند** و ادمین باید دوباره وارد کند.
- **هیچ پاسخی توکن را برنمی‌گرداند.** مسیرهای عمومی تنظیمات (`const`) کلیدهای `accounting.*` را نمی‌دهند و نمی‌پذیرند.
- **TLS هرگز خاموش نمی‌شود.** برای نصب‌های با گواهی شخصی، می‌توان گواهی CA را به‌صورت PEM داد.
- **هشدار برای ارائه:** نصب زرین مالک روی **http بدون رمزنگاری** و در دسترس اینترنت است. توصیه: https یا VPN و توکن قوی. این موضوع باید صریح گفته شود.
### ۵.۱۱ وضعیت آزمون (با تاریخ)
| مورد | نتیجه | تاریخ و منبع |
|---|---|---|
| ته‌حساب روی سرور تست عمومی | ۲۹ از ۲۹ بررسی؛ شامل لینک دوباره‌ی همان حساب و سند با وزن دقیق | ۲۰۲۶-۱۰-۰۵ (طرح، بخش ۱۰) |
| زرین روی نصب مالک (v228) — خواندن | همه‌ی بررسی‌ها؛ مانده، صفحه‌بندی کارساخته، گردش | ۲۰۲۶-۱۰-۱۲ (ACC-01) |
| زرین روی نصب مالک — نوشتن | ۲۰ از ۲۰ روی حساب آزمایشی ۹۵۳ | ۲۰۲۶-۱۰-۱۲ (ACC-01) |
| تست‌های واحد درایورها | ۴۳ تست؛ کل سرور ۱۸۰ تست | ۲۰۲۶-۱۰-۰۵ (طرح، بخش ۱۰) |
**آزمون‌نشده:** ساخت کارساخته در زرین روی نصب واقعی؛ سنگ در سند زرین؛ فید تغییرات زرین روی نصب واقعی (زرین با بازه‌ی کارساخته و خواندن لحظه‌ی سفارش جایگزین را پوشش می‌دهد).
### ۵.۱۲ آنچه باید صادقانه گفته شود
- **سند موقت ته‌حساب روی مانده اثر ندارد** تا حسابدار نهایی کند (ACC-02). پس مانده‌ای که مشتری می‌بیند تا نهایی شدن سند، سفارش‌های تازه را شامل نمی‌شود.
- **لحظه‌ای بودن یعنی «چند ثانیه بعد از ثبت در حسابداری»** و نه «در همان لحظه‌ی ثبت». هیچ‌کدام از دو نرم‌افزار وب‌هوک ندارند.
- **قابلیت‌های ناتمام:** ساخت کارساخته در زرین، سنگ در زرین.
- **حساب آزمایشی روی نصب مالک:** برای آزمون نوشتن در زرین، حساب آزمایشی ۹۵۳ («تست اپ گلدبانک») با تأیید مالک ساخته شد. زرین API حذف حساب ندارد، پس این حساب در نصب مالک باقی می‌ماند.
---
## ۶. ترتیب پیشنهادی ارائه (حدود ۲۰ دقیقه)
1. **نگاه کلی** (۲ دقیقه): سه برنامه، اصل دفتر افزودنی.
2. **اشخاص** (۴ دقیقه): ثبت‌نام ← تأیید دو مرحله‌ای ← ورود پیامکی ← سقف اعتبار ← معرف.
3. **محصولات** (۳ دقیقه): واریانت و عکس، قانون بدون عکس، موجودی، فرمول اجرت.
4. **سفارش و بدهی** (۳ دقیقه): وضعیت‌ها، نقطه‌ی شناسایی بدهی، سفارش ساختنی و لیست تامین (بخش ۴.۳ و ۴.۴). بخش ساختنی را با صراحت درباره‌ی نقص‌هایش ارائه کنید.
5. **حسابداری** (۷ دقیقه): سوییچ ← مشتری تأییدشده ← ساخت در حسابداری؛ سفارش ← سند موقت؛ مانده در پروفایل؛ لاگ.
6. **باز و تصمیم‌ها** (۲ دقیقه): بخش ۸.
**هشدار برای نمایش زنده:** برای نشان دادن نوشتن در حسابداری فقط از **سرور تست** استفاده کنید. نوشتن روی نصب مالک بدون تأیید جداگانه‌ی او انجام نشود.
---
## ۷. واژه‌نامه
| واژه | معنی |
|---|---|
| **کارساخته** | کار/محصول ساخته‌شده‌ی طلا؛ در حسابداری یک «کالا» است و در اپ یک «محصول» |
| **ته‌حساب / زرین** | دو نرم‌افزار حسابداری که اپ با آن‌ها همگام می‌شود |
| **مانده** | بدهی یا بستانکاری مشتری؛ در اپ مثبت = بدهکار |
| **سند موقت (پیش‌فاکتور)** | سندی که برای حسابدار ثبت می‌شود و هنوز نهایی نیست |
| **راش (وزن راش)** | وزن خام کالا قبل از اجرت |
| **اجرت** | درصدی که به وزن راش اضافه می‌شود تا وزن کل سفارش به دست آید |
| **عیار** | خلوص طلا بر حسب هزارم؛ ۷۵۰ = ۱۸ عیار |
| **صف خروجی (Outbox)** | فهرست کارهایی که باید به حسابداری فرستاده شود |
| **لینک** | وصل شدن یک مشتری یا کارساخته‌ی اپ به رکورد متناظر در حسابداری |
| **مغایرت** | موردی که سیستم خودکار تصمیم نمی‌گیرد و باید ادمین بررسی کند |
| **معلق** | سفارشی که از سقف گرمی گذشته و منتظر تصمیم است |
| **پیش‌سفارش** | گزینه‌ی واریانت که با موجودی صفر هم قابل خرید است |
| **سفارش ساختنی** | درخواست طراحی و ساخت سفارشی (رنگ، سایز، طرح)؛ رکورد جدا از سفارش خرید |
| **تأمین** | مرحله‌ای که کالا یا مواد لازم برای ساخت سفارش تهیه می‌شود (وضعیت «در حال تأمین») |
| **لیست تامین** | فهرست سفارش‌هایی که حداقل یک‌بار به مرحله‌ی تأمین رسیده‌اند |
---
## ۸. باز و تصمیم‌های لازم قبل از راه‌اندازی
| # | موضوع | وضعیت |
|---|---|---|
| ۱ | **رمز مدیریتی OTP** (`OTP_MASTER_CODE`) در نسخه‌ی تست است و باید قبل از نسخه‌ی واقعی حذف شود. | باز |
| ۲ | **کد ملی = موبایل** برای مشتری‌هایی که از حسابداری ساخته شده‌اند. بدهی داده است و باید پاک شود. | باز (DATA-MIGRATION-MAP §10.4) |
| ۳ | **نصب زرین روی http بدون رمز** — رمزنگاری یا VPN. | باز، تصمیم مالک |
| ۴ | **سند موقت ته‌حساب** روی مانده اثر ندارد تا نهایی شود. | باز، به‌عنوان قانون کار قبول شده (ACC-02) |
| ۵ | **مهاجرت دیتابیس خالی** در ACC-04 گزارش شده. یادداشت‌ها می‌گویند برطرف شده؛ قبل از نصب تازه بررسی شود. | بررسی لازم |
| ۶ | **ساخت کارساخته در زرین** و **فید تغییرات زرین** روی نصب واقعی تأیید نشده‌اند. | آزمون‌نشده |
| ۷ | **ACC-03** (بدهی تکراری) برطرف شده است؛ طرح ۱۰/۰۵ هنوز آن را باز نشان می‌دهد، ولی ملاک فعلی `ISSUES.md` است. | برطرف (۲۰۲۶-۱۰-۰۸) |
| ۸ | **سفارش ساختنی:** ثبت جدید، تبدیل به سفارش قطعی، ذخیره‌ی کارگاه و تعداد در پنل. تصمیم لازم: آیا این بخش در نسخه‌ی اول لازم است؟ | باز، نیاز به تصمیم مالک |
| ۹ | **لیست تامین:** ستون و فیلتر «تأیید مشتری» داده ندارد. | باز (کم‌اهمیت) |
---
## پیوست فنی — نقشه‌ی فایل‌ها
برای پاسخ به سؤال‌های فنی:
**داده و قوانین کلی**
- [apps/api/prisma/schema.prisma](../apps/api/prisma/schema.prisma) — مدل‌ها: `Customer`, `AdminUser`, `Product`, `ProductVariant`, `Order`, `LedgerEntry`, `AccountingLink`, `AccountingOutbox`
- [apps/api/src/modules/accounting/ledger.service.ts](../apps/api/src/modules/accounting/ledger.service.ts) — دفتر داخلی و `syncOrderDebt`
**اشخاص**
- [customer-auth.service.ts](../apps/api/src/modules/customer-auth/customer-auth.service.ts) — OTP، ثبت‌نام، سقف ارسال
- [admin-customers.service.ts](../apps/api/src/modules/admin-customers/admin-customers.service.ts) — تأیید دو مرحله‌ای، سقف اعتبار، فهرست‌ها
**محصولات و سفارش**
- [catalog.service.ts](../apps/api/src/modules/catalog/catalog.service.ts) — قانون عکس و موجودی
- [admin-products.service.ts](../apps/api/src/modules/admin-products/admin-products.service.ts) — قفل عنوان و کد محصول متصل
- [wage-formula.ts](../apps/api/src/modules/pricing/wage-formula.ts) — فرمول اجرت
- [orders.service.ts](../apps/api/src/modules/orders/orders.service.ts) — ثبت سفارش و تغییر وضعیت
- [admin-orders.service.ts](../apps/api/src/modules/admin-orders/admin-orders.service.ts) — `listTamin` (لیست تامین) و `removeStatusEvent`
- [admin-preorders.service.ts](../apps/api/src/modules/admin-preorders/admin-preorders.service.ts) — سفارش‌های ساختنی (فهرست، ویرایش، حذف)
- [MakeOrder.vue](../apps/admin-panel/src/views/order/MakeOrder.vue) و [TaminList.vue](../apps/admin-panel/src/views/order/TaminList.vue) — صفحه‌های پنل این دو فهرست
- [docs/PRODUCT-MODULE.md](PRODUCT-MODULE.md) — جزئیات فرم محصول در پنل
**اتصال به حسابداری**
- [accounting-sync.service.ts](../apps/api/src/modules/accounting-sync/accounting-sync.service.ts) — منطق همگام‌سازی (مشتری، مانده، کارساخته، سند)
- [accounting-hooks.service.ts](../apps/api/src/modules/accounting-sync/accounting-hooks.service.ts) — نقاط ورود از بقیه‌ی برنامه (سفارش، مشتری، محصول)
- [accounting-worker.service.ts](../apps/api/src/modules/accounting-sync/accounting-worker.service.ts) — صف، زمان‌بند، فید تغییرات، قفل، تلاش دوباره
- [accounting-config.ts](../apps/api/src/modules/accounting-sync/accounting-config.ts) — ساختار تنظیمات و پیش‌فرض‌ها
- [providers/tahesab.provider.ts](../apps/api/src/modules/accounting-sync/providers/tahesab.provider.ts) و [providers/zarrin.provider.ts](../apps/api/src/modules/accounting-sync/providers/zarrin.provider.ts) — درایورها
- [accounting-http.ts](../apps/api/src/modules/accounting-sync/accounting-http.ts) — کلاینت HTTP و TLS
- [secret-box.ts](../apps/api/src/modules/accounting-sync/secret-box.ts) — رمزنگاری توکن
- [accounting-admin.controller.ts](../apps/api/src/modules/accounting-sync/accounting-admin.controller.ts) — مسیرهای پنل ادمین
- [customer-report.service.ts](../apps/api/src/modules/customer-report/customer-report.service.ts) — گزارش و مانده برای مشتری
**مستندات مرتبط**
- [ACCOUNTING-INTEGRATION-PLAN.md](ACCOUNTING-INTEGRATION-PLAN.md) — طرح کامل، از جمله بخش‌های ۱۰ تا ۱۳ (وضعیت اجرا، فید لحظه‌ای، زرین)
- [parity/DECISIONS.md](parity/DECISIONS.md) — تصمیم‌های مالک؛ **D27** اتصال به حسابداری
- [parity/ISSUES.md](parity/ISSUES.md) — **ACC-01 تا ACC-09**؛ ملاک وضعیت فعلی
+259
View File
@@ -0,0 +1,259 @@
# چک‌لیست تکرار ایرادها (کاری — قدم‌به‌قدم)
منبع جزئیات: [`parity/CHANGELOG.md`](parity/CHANGELOG.md)، [`parity/ISSUES.md`](parity/ISSUES.md)، [`parity/DECISIONS.md`](parity/DECISIONS.md).
این سند برای **همین ریپو (gold-bank / تهران تاج)** است تا ایرادها را verify کنیم یا باقی‌مانده‌ها را ببندیم — و در عین حال قالب تکرار برای پروژهٔ مشابه.
> قبل از هر تغییر **UI سمت اپ**: از مالک (Honar) بپرس. منطق/رفتار بدون سوال؛ ظاهر نه.
> اپ مبنا برای منطق: هر دو `apps/user-app` و `apps/user-app-v2` (مگر خلافش گفته شود).
---
## دامنهٔ تکرار (قوانین سخت)
| موضوع | تکرار؟ | قانون |
|---|---|---|
| ایرادهای امنیتی / پولی / دامنه / UX کارکردی | بله | منطق یکسان |
| فونت IRANSans (اپ + ادمین) | بله | همان فونت و wiring |
| لوگو / فاویکون / آیکن برند / wordmark | **خیر** | برند تهران تاج دست‌نخورده — [`BRANDING.md`](BRANDING.md) |
| کاروسل / hero / ShowcaseSlider | **خیر** | **اصلاً دست نزن** (حتی در verify) |
| فیچر اپ مثل PWA install، pull-refresh، incomplete-profile banner | بله | **فقط منطق/رفتار**؛ UI با زبان طراحی خودِ اپ |
| دیزاین / layout ادمین | بله | ادمین قابل‌رفرنس از همین ریپو (بدون تعویض لوگو) |
---
## نحوهٔ کار
| نماد وضعیت | معنا |
|---|---|
| `انجام‌شده — verify` | در parity به‌عنوان FIXED ثبت شده؛ فقط smoke/خواندن کد، بازپیاده‌سازی نکن |
| `باقی‌مانده` | هنوز باید پیاده یا اصلاح شود |
| `منتظر تصمیم` | منتظر ruling مالک |
| `عمداً خارج` | یخ‌زده یا deferred — دست نزن |
چک‌باکس‌ها:
- `[x]` = بسته / verify پاس شده در همین دور کار
- `[ ]` = هنوز باز یا verify نشده
**ترتیب پیشنهادی:** ۱ امنیت → ۲ پول/سفارش → ۳ مشتری → ۴ UX اپ (منطق) → ۵ کاتالوگ/SMS → ۶ فیچر اپ → ۷ ادمین → ۸ حسابداری.
---
## یخ‌زده (عمداً خارج — دست نزن)
| موضوع | قانون |
|---|---|
| کاروسل / hero اسپرینگ / ShowcaseSlider | هیچ پورت دیزاین Hedena؛ هیچ بازنویسی layout |
| لوگو / فاویکون / brand-* / wordmark | فقط طبق [`BRANDING.md`](BRANDING.md)؛ از Hedena کپی نکن |
- [ ] تأیید کلامی: کاروسل و برند در این دور کار دست نخورده‌اند
---
## ۱) امنیت و احراز هویت (اولویت بحرانی)
| ID | ایراد | مسیر تقریبی | فیکس | وضعیت | انجام |
|---|---|---|---|---|---|
| SEC-09 | روت‌های ledger با `@Public()` قابل نوشتن | API `accounting.controller.ts` | حذف `@Public` سطح کلاس؛ rid روی هر روت: `wallet.list`, `wallet.charge`, `document.create`, `document.list.confirm` | انجام‌شده — verify | [ ] |
| SEC-10 | `PATCH orders/:id/status` عمومی + `actorAdminUserId` جعلی | API `orders.controller.ts` | فقط ادمین؛ rid `order.list.sts.submit` | انجام‌شده — verify | [ ] |
| SEC-08 | `GET /customers` کل دفتر مشتری را لو می‌داد | API customers (حذف)؛ اپ Login / `api/client` | حذف روت‌های با `customerId`؛ `/customer/me*` از JWT | انجام‌شده — verify | [ ] |
| SEC-03 / D6 | OTP تأیید نمی‌شد | API `customer-auth/` + `CustomerOtp`؛ اپ Login | bcrypt، انقضا ۲ دقیقه، ۵ تلاش، یک‌بارمصرف، throttle | انجام‌شده — verify | [ ] |
| T1 | سشن مشتری جدا نبود | `customer-auth/`، `JWT_CUSTOMER_SECRET`، `typ: 'customer'` | JWT جدا از ادمین؛ خواندن مجدد DB در هر درخواست | انجام‌شده — verify | [ ] |
| SEC-02 | `POST /orders` عمومی با `customerId` از body | `orders.controller/service`، DTO، Cart | `@CustomerAuth()`؛ customerId فقط از توکن | انجام‌شده — verify | [ ] |
| SEC-02b | `GET /orders/:id` IDOR | همان ماژول | `getOwnedById` → **404** نه 403؛ ادمین مسیر جدا | انجام‌شده — verify | [ ] |
| SEC-11 | OTP در `SmsMessage.body` plaintext | `customer-auth.service` `deliver()` | فقط خط audit سانسورشده | انجام‌شده — verify | [ ] |
| SEC-04 | JWT ضعیف پیش‌فرض + CORS باز | boot API / CORS | production بدون `JWT_SECRET` بالا نیاید؛ `CORS_ORIGINS` | انجام‌شده — verify | [ ] |
| SEC-05 | seed دمو (پسورد = موبایل) در production | `prisma/seed.ts`، `bootstrap.ts` | دمو فقط با `SEED_DEMO_DATA=true` | انجام‌شده — verify | [ ] |
| SEC-07 | `setRoles` رشتهٔ دلخواه | admin-users / roles | فقط `ALL_RIDS` (+ والدین درخت) | انجام‌شده — verify | [ ] |
| DEPLOY-02 | env خالی `""` در Docker JWT را می‌شکست | `common/env.ts` (`envOr`) | blank = unset | انجام‌شده — verify | [ ] |
| DEPLOY-01 | compose بدون JWT/SMS/OTP | `docker-compose.yml` | forward همهٔ envهای auth/SMS | انجام‌شده — verify | [ ] |
| REL-01 | master OTP در production | `customer-auth`، boot checks | `OTP_MASTER_CODE`؛ در prod ممنوع مگر فلگ تست | انجام‌شده — verify | [ ] |
| REL-03 | `JWT_CUSTOMER_SECRET` fallback به secret ادمین | customer JWT helper | secret جدا؛ enforce در production | انجام‌شده — verify | [ ] |
Smoke پیشنهادی: `apps/api/scripts/auth-smoke.sh` (در صورت در دسترس بودن سرور محلی).
---
## ۲) پول، قیمت، سفارش
| ID | ایراد | مسیر تقریبی | فیکس | وضعیت | انجام |
|---|---|---|---|---|---|
| MON-08 | کلاینت می‌توانست wage/discount/rial بگذارد | `orders.service`، `pricing/wage-formula`، wage-quote | قیمت فقط سرور؛ نادیده کلاینت؛ لاگ mismatch | انجام‌شده — verify | [ ] |
| ACC-03 / MON-04 | بدهی دوبار با ورود مجدد به `DELIVERED_TO_AGENT` | `LedgerService.syncOrderDebt` | sync به هدف؛ فقط delta؛ rollback/cancel درست | انجام‌شده — verify | [ ] |
| MON-05 | `canPlaceOrders` اعمال نمی‌شد | مسیر create سفارش | اپ → 403؛ ادمین می‌تواند برای مشتری ثبت کند | انجام‌شده — verify | [ ] |
| MON-01 | سقف موجودی در سبد نبود (v2) | `cartStore`، CartItem/SingleProduct | `capQty`/`maxQty` + disable + toast | انجام‌شده — verify | [ ] |
| ORD-01 | ویرایش وضعیت سفارش retail خراب | admin `order/List.vue` | body از `tempRow.id` مثل Omde/Tamin | انجام‌شده — verify | [ ] |
| کانال سفارش اپ | سفارش اپ به‌اشتباه RETAIL | `orders.service`، DTO، migration، admin-orders | پیش‌فرض/ذخیره **WHOLESALE** برای مسیر اپ + migrate ردیف‌های قبلی | **انجام شد 2026-10-17** — `placedBy=customer` ⇒ WHOLESALE؛ migration `20261017120000_app_orders_wholesale_channel` | [x] |
---
## ۳) مشتری / اشخاص / نام / درصد
| ID | ایراد | مسیر تقریبی | فیکس | وضعیت | انجام |
|---|---|---|---|---|---|
| CUST-01 | `fname: fullName, lname: ''` نام‌ها را یکی می‌کرد | migration `firstName`/`lastName`؛ فرم‌ها | ستون‌های واقعی؛ حذف `split(' ')` | انجام‌شده — verify (schema دارد) | [ ] |
| CUST-02 | `percent` از کمیسیون نماینده | `makingChargeOffsetPercent`؛ CRUD مشتری | ستون خود مشتری؛ پیش‌فرض ۳؛ required در create | انجام‌شده — verify | [ ] |
| D17 / CUST-03 | فرم کارشناس فروش ناقص | `SalesExpertProfile`؛ `ReagentCustomer.vue` | پروفایل جدا؛ autofill شهر→کمیسیون | انجام‌شده — verify (فرم ncode/city/gram دارد) | [ ] |
| PERS-02 | شهر flat به‌جای درخت استان | `City.parentId`، `CityTreeSelect` | درخت استان؛ ارث‌بری درصد از ancestor | انجام‌شده — verify | [ ] |
| PERS-03 | تاریخ تولد نبود | `Customer.birthDate`، DateInput | فیلد جلالی create/update | انجام‌شده — verify | [ ] |
| D19 | موبایل نرمال نمی‌شد / SMS خوش‌آمد | admin-customers / admin-users | `faToEn` قبل uniqueness؛ welcome SMS fire-and-forget | انجام‌شده — verify | [ ] |
---
## ۴) لاگین اپ / act-as / ناوبری / تازگی داده
> فقط رفتار را port/verify کن؛ ظاهر Login/Dialog/Nav را از زبان طراحی خودِ اپ بگیر — کپی پیکسلی Hedena نکن. قبل از تغییر UI از Honar بپرس.
| ID | ایراد | مسیر تقریبی | فیکس | وضعیت | انجام |
|---|---|---|---|---|---|
| AUTH-01 | شماره ثبت‌نشده → موفقیت جعلی → OTP | `requestOtp`، Login | **404** + پیام فارسی | انجام‌شده — verify | [ ] |
| AUTH-02 | شکست SMS → موفقیت جعلی | `deliver` | **503** + «SMS در دسترس نیست» | انجام‌شده — verify | [ ] |
| AUTH-03 | مشکل حساب کارشناس مثل «ثبت‌نشده» | `resolveExpertTradingAccount` | پیام‌های **403** جدا | انجام‌شده — verify | [ ] |
| APP-03 | صفحات شخصی مهمان silent redirect | `router/index.ts` | toast «ابتدا وارد شوید» + نگه داشتن `?redirect=` | انجام‌شده — verify | [ ] |
| EXP-01…03 | «ورود به حساب مشتری» برای کارشناس نبود | JWT strategy، `X-Act-As-Customer`، session، ActingAsBar | act-as با هدر (نه توکن minted)؛ پاک کردن cache/cart؛ نوار دائمی | انجام‌شده — verify | [ ] |
| NAV-01…04 | back همیشه route ثابت؛ login/401 تاریخچه مرده | `PageBar`، `navStore`، Login، `main.ts` | back تاریخچه‌دار؛ `replace` بعد login/401 | انجام‌شده — verify | [ ] |
| READ-01…03 | keep-alive صفحه را دوباره fetch نمی‌کرد | Orders/Transactions/Home/Dashboard، `queryCache` | refresh روی `onMounted`+`onActivated` | انجام‌شده — verify | [ ] |
---
## ۵) کاتالوگ / علاقه‌مندی / SMS / آپلود
| ID | ایراد | مسیر تقریبی | فیکس | وضعیت | انجام |
|---|---|---|---|---|---|
| STUB-03 | کل کاتالوگ یکجا؛ pagination فقط کلاینت | catalog API + اپ‌ها | page/filter/sort سرور؛ `?ids=`، `/:id`، `/facets` | **مرحله ۴ انجام شد 2026-10-17** — Search/Newest/Home/Collection روی هر دو اپ؛ UI همان زبان طراحی | [x] |
| FAV-01 | علاقه‌مندی فقط memory | API `customer/me/favorites*`، `favoritesStore` | load/toggle سمت سرور | انجام‌شده — verify | [ ] |
| SMS-03 | بدون SMS ثبت سفارش | `orders.service` notify + `ORDER_ADMIN_MOBILES` | SMS بعد commit به ادمین/مشتری/کارشناس | انجام‌شده — verify | [ ] |
| STUB-05 | موبایل ادمین hardcode | env | `PROFILE_ADMIN_MOBILES`، `ORDER_ADMIN_MOBILES` | انجام‌شده — verify | [ ] |
| UPL-01 | اپ نمی‌توانست مدرک آپلود کند | `POST customer/me/documents` | آپلود برای complete-profile | انجام‌شده — verify | [ ] |
| D22–D24 | complete-profile / گزارش بدون IDOR | customer modules | whitelist write؛ گزارش scoped | انجام‌شده — verify | [ ] |
---
## ۶) فیچرهای اپ (منطق بله — دیزاین خیر)
| تم | چه چیزی port شود | چه چیزی port نشود | وضعیت | انجام |
|---|---|---|---|---|
| فونت IRANSans | فایل‌های فونت + `fonts.less` / wiring در اپ و ادمین | — | ادمین wired (`farsi.less`)؛ اپ‌ها را verify کن | [ ] |
| PWA install prompt | `beforeinstallprompt`، prompt قابل dismiss، logic | ظاهر مودال Hedena | **باقی‌مانده** — `usePwaInstall` / InstallPromptModal در ریپو نیست؛ UI بعد از تأیید Honar | [ ] |
| Pull-to-refresh / incomplete-profile / gate login | رفتار و شرط‌ها | layout/illustration/رنگ Hedena | pull-refresh و complete-profile موجود — verify رفتار | [ ] |
| کاروسل / ShowcaseSlider / hero | — | **اصلاً تکرار نکن** | عمداً خارج | — |
| لوگو / فاویکون / brand assets | — | **اصلاً تکرار نکن** | عمداً خارج | — |
رفرنس منطق PWA (اگر از پروژهٔ مرجع بیاید — فقط رفتار، نه UI): composable `usePwaInstall` + مودال dismiss/defer؛ markup را با کامپوننت‌های خود اپ بازنویسی کن.
---
## ۷) ادمین / پرمیشن / UX (دیزاین قابل‌رفرنس)
| ID / تم | ایراد | مسیر تقریبی | فیکس | وضعیت | انجام |
|---|---|---|---|---|---|
| PERM-01 | ridهای یتیم dashboard | migration merge → `dashboard.view` | merge/حذف ridهای قدیمی | انجام‌شده — verify | [ ] |
| PERM-02 | ridهای enforced ولی غایب از درخت | `permissionConst.ts` | افزودن `newsletters`، `persons.list.sellers.view` | **منتظر تصمیم** | [ ] |
| D30 | ویژگی‌های retail/بانک/شعبه فقط super-admin | `super-admin.ts`، منو/گارد | gate با موبایل ثابت؛ hide + 403 | انجام‌شده — verify (در صورت پیاده) | [ ] |
| super-admin rids | سوپرادمین همه rid نداشت | `super-admin.ts`، `permissionConst` | `ALL_RIDS`؛ اسلایدر زیر ثابت‌ها | انجام‌شده — verify | [ ] |
| Order UX | وضعیت سفارش UX ضعیف | Listها / مودال وضعیت | drawer راست + confirm خروج — **دیزاین از ادمین همین ریپو** | باقی‌مانده / verify — فایل‌های `OrderStatusModal.vue` / `OrderViewModal.vue` در این ریپو پیدا نشد؛ List.vue فعلی را بسنج | [ ] |
| Icon migration | وابستگی `@ant-design/icons-vue` | Icon محلی + `icons.json` | آیکون محلی | **باقی‌مانده** — ادمین هنوز از `@ant-design/icons-vue` زیاد استفاده می‌کند | [ ] |
| Dashboard unify / فرم محصول و مشتری | chrome ناهماهنگ | `Dashboard.vue`، AddProduct، AddCustomer | unify بصری (بدون تعویض لوگو) | بخشی انجام‌شده — PROTECTED dashboard chrome؛ verify | [ ] |
| فونت ادمین | خوانایی | `farsi.less` / IRANSans | همان فونت؛ لوگو را عوض نکن | انجام‌شده — verify | [ ] |
| PROMO-TAK-01 | type_customer / ridهای `.tak` | promotions | ثبت و/یا gate API | باقی‌مانده (logged) | [ ] |
| CINY-01 | `ciny_ids` در update نادیده | admin-products | update هم trays را ذخیره کند | باقی‌مانده (logged) | [ ] |
| ANLY-01 | analytics dwell-time | — | — | عمداً خارج — deferred، اولویت پایین | — |
---
## ۸) حسابداری یکپارچه
| ID | ایراد | فیکس | وضعیت | انجام |
|---|---|---|---|---|
| ACC-06 | TLS خودامضا / hostname fail | cert pin؛ هرگز verify را خاموش نکن | انجام‌شده — verify | [ ] |
| ACC-07 | «متصل است ولی sync نمی‌شود» | رد provider ناقص؛ `inactiveReason`؛ گروه‌های انتخاب‌شده الزامی | انجام‌شده — verify | [ ] |
| ACC-08/09 | sync کند / بدون on-demand | poll سریع + دکمه sync + وضعیت زنده | انجام‌شده — verify | [ ] |
جزئیات: [`ACCOUNTING-INTEGRATION-PLAN.md`](ACCOUNTING-INTEGRATION-PLAN.md).
---
## صف باقی‌ماندهٔ واقعی (اولویت اجرا بعد از verify امنیت)
1. ~~کانال WHOLESALE~~ ✅
2. ~~STUB-03 مرحله ۴~~ ✅ (منتظر چک UI تو)
3. **PWA install** — فقط منطق؛ UI بعد از سؤال از Honar
4. **PERM-02** — بعد از ruling
5. **Icon محلی ادمین** / Order drawer UX — در صورت نیاز
6. **PROMO-TAK-01**، **CINY-01**
---
## پرامپت آماده برای Cursor پروژهٔ دوم
کپی کن و در چت پروژهٔ مشابه بگذار:
```text
این پروژه شبیه hedena_gold / gold-banking-monorepo است و همان ایرادها را دارد.
همان فیکس‌ها را با منطق یکسان پیاده کن. اولویت با امنیت و پول است.
قوانین دامنه (سخت):
- فونت IRANSans را برای اپ و ادمین بیاور (فایل + wiring).
- لوگو / فاویکون / آیکن برند / wordmark را عوض نکن و از Hedena کپی نکن.
- کاروسل / hero اسپرینگ / showcase دیزاین‌شدهٔ Hedena را اصلاً تکرار نکن.
- فیچرهای اپ مثل PWA install، pull-refresh، incomplete-profile banner: فقط منطق/رفتار را port کن؛ UI را با زبان طراحی و کامپوننت‌های خودِ این اپ بساز — از مودال/رنگ/illustration هدنا کپی نکن.
- پنل ادمین یکی است: برای دیزاین drawer سفارش، Icon محلی، dashboard unify، فرم‌ها می‌توانی مستقیم از ادمین مرجع رفرنس بگیری (باز هم بدون تعویض لوگو).
- قبل از هر تغییر UI سمت اپ از مالک بپرس.
A) امنیت
- روت‌های ledger و تغییر وضعیت سفارش را از Public دربیاور؛ فقط ادمین با rid مناسب
- GET لیست مشتری برای login را حذف کن؛ سشن مشتری با JWT جدا (JWT_CUSTOMER_SECRET، typ=customer)
- OTP واقعی: bcrypt، انقضا ۲ دقیقه، ۵ تلاش، یک‌بارمصرف، throttle؛ OTP را در SMS audit plaintext ذخیره نکن
- POST /orders پشت CustomerAuth؛ customerId فقط از توکن؛ GET order با مالکیت → 404 نه 403
- production بدون JWT_SECRET بالا نیاید؛ CORS از CORS_ORIGINS؛ seed دمو فقط با SEED_DEMO_DATA
- env خالی "" را unset بگیر (envOr)؛ JWT مشتری fallback به secret ادمین نداشته باشد
- OTP_MASTER_CODE در production ممنوع مگر فلگ تست
B) پول/سفارش
- wage/discount/rial را از کلاینت نپذیر؛ فرمول سرور + لاگ mismatch
- sync بدهی سفارش idempotent با delta (نه پست دوباره)
- canPlaceOrders در اپ 403 کند؛ ادمین بتواند برای مشتری ثبت کند
- سقف موجودی در سبد (capQty/maxQty)
- سفارش‌های اپ را WHOLESALE ذخیره کن نه RETAIL (اگر دامنه B2B است)
C) مشتری
- firstName/lastName جدا؛ percent خود مشتری (makingChargeOffsetPercent) نه کمیسیون نماینده
- پروفایل کارشناس فروش کامل؛ درخت شهر استان؛ تاریخ تولد؛ faToEn روی موبایل
D) اپ (منطق بله — دیزاین از خودِ این پروژه)
- requestOtp: 404 ثبت‌نشده، 503 SMS fail، 403 پیام جدا برای کارشناس
- toast ورود برای صفحات شخصی + redirect=
- act-as کارشناس با هدر X-Act-As-Customer (نه mint توکن)
- back تاریخچه‌دار؛ بعد login/401 از replace استفاده کن
- صفحات keep-alive را روی activate دوباره fetch کن
- PWA install: beforeinstallprompt + dismiss/defer؛ UI با کامپوننت‌های خود اپ
- IRANSans را وصل کن؛ لوگو و کاروسل را دست نزن
E) کاتالوگ/SMS
- pagination/فیلتر سرور برای محصولات؛ favorites سمت سرور
- SMS ثبت سفارش بعد commit؛ شماره‌ها از env نه hardcode
- آپلود مدرک مشتری برای complete-profile
F) ادمین (دیزاین قابل‌رفرنس)
- ridهای یتیم dashboard را merge کن
- ویژگی‌های خطرناک فقط super-admin؛ سوپرادمین ALL_RIDS
- Order status UX به صورت drawer راست + confirm logout — layout از ادمین مرجع
- Icon محلی به‌جای @ant-design/icons-vue اگر همان وابستگی را دارید
- IRANSans در ادمین؛ لوگو را عوض نکن
برای هر آیتم: اول محل معادل در این ریپو را پیدا کن، بعد فیکس را با تست/smoke کوتاه اعمال کن.
جزئیات کامل: در ریپوی مرجع فایل docs/REPLICATION-CHECKLIST.md
```
---
## منبع برای جزئیات بیشتر
- لاگ کامل What/Why/Target: [`parity/CHANGELOG.md`](parity/CHANGELOG.md)
- وضعیت FIXED/باز: [`parity/ISSUES.md`](parity/ISSUES.md)
- تصمیم‌های محصول: [`parity/DECISIONS.md`](parity/DECISIONS.md)
- برند / لوگو: [`BRANDING.md`](BRANDING.md)
- کاتالوگ: [`CATALOG-PAGINATION-PLAN.md`](CATALOG-PAGINATION-PLAN.md)
- حسابداری: [`ACCOUNTING-INTEGRATION-PLAN.md`](ACCOUNTING-INTEGRATION-PLAN.md)
- دیزاین ادمین قابل‌رفرنس: `apps/admin-panel/src/views/order/`، `apps/admin-panel/src/assets/styles/farsi.less`