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