Files
tehran_taj/docs/REPLICATION-CHECKLIST.md
T

260 lines
21 KiB
Markdown
Raw Normal View History

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