Files
tehran_taj/docs/REPLICATION-CHECKLIST.md
s.esmaeiliandCursor afb068c643 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>
2026-10-17 13:11:42 +03:30

260 lines
21 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.
# چک‌لیست تکرار ایرادها (کاری — قدم‌به‌قدم)
منبع جزئیات: [`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`