# پلن پورت اپ کاربر کلاسیک — henza_pwa → apps/user-app-classic سند برنامه‌ریزی. **قبل از تأیید شما هیچ کدی نوشته نمی‌شود.** > **مرجع پین‌شده:** `henza_pwa` در کامیت `b171fff` (2026-09-02، «fee in bonakdar removed»). > هر ارجاع `path:line` در این سند به همین نسخه است و نسبت به `henza_pwa/` نوشته شده. > منبع اصلی شواهد: `docs/parity/_inv-ref-pwa.md` (سیاهه‌ی کامل مرجع) به‌علاوه‌ی > راستی‌آزمایی مستقیم روی کد. اگر مرجع عوض شد، این سند باید دوباره راستی‌آزمایی شود. --- ## خواسته 1. یک اپ کاربر **سوم** در مونوریپو، کنار `user-app` و `user-app-v2`. 2. طراحی آن = **طراحی قبلی همین سیستم**، یعنی `henza_pwa`. 3. همه‌ی صفحه‌ها، کارکردها، عملیات، انیمیشن‌ها و ناوبری **دقیقاً مثل مرجع**. 4. با **تکنولوژی جدید و کد بهتر**. 5. **هیچ چیزی از بین نرود.** --- ## قرارداد «دقیقاً مثل مرجع» | لایه | قاعده | |---|---| | **آنچه کاربر می‌بیند و انجام می‌دهد** | **عیناً مرجع.** چیدمان و ابعاد، رنگ‌ها، فونت، آیکون‌ها، متن‌های فارسی (بایت‌به‌بایت — حتی فاصله‌ی دوتایی بعد از «لطفا» و غلط املایی «کارکتر»)، ارقام، ترتیب و برچسب تب‌ها، پیام‌های اعتبارسنجی، رنگ و مدت toast ها، پارامترهای انیمیشن، مسیرها و نام route ها، رفتار guard ها، محاسبه‌ها و گرد کردن‌ها. | | **زیر کاپوت** | **بازنویسی‌شده و بهتر.** TypeScript با `strict`، Pinia، composable ها، یک لایه‌ی HTTP تایپ‌شده، تنظیمات از env، حذف کد مرده و تکراری، تست. | | **کوییرک بصری بی‌خطر** | **عیناً تکرار می‌شود** — مثلاً notify ورود که حتی برای موفقیت قرمز است، یا تشدید سرگردان در «ّکاربر فعال شود؟». | | **استثنا** | **فقط خرابی‌های کارکردی مرجع، طبق D4.** هر کدام شماره دارد، در §تنش ۴ و §چیزی که پورت نمی‌شود فهرست شده، و هیچ‌کدام بی‌صدا نیست. | «هیچ چیزی از بین نرود» دو معنا دارد و هر دو تعهد این پلن است: 1. **هیچ صفحه، کارکرد، انیمیشن یا متنی از مرجع جا نمی‌ماند** — با سرشماری خودکار اثبات می‌شود (§معیار تمام شد). 2. **هیچ چیز موجود در مونوریپو آسیب نمی‌بیند** — `user-app`، `user-app-v2` و `admin-panel` دست نمی‌خورند؛ تغییرات `apps/api` فقط افزایشی است. --- ## قواعد حاکم (ارث‌برده از پروژه) 1. `henza_pwa/`، `henza_server/` و `henza_admin/` **فقط خواندنی‌اند.** برای اجرای مرجع یک کپی بیرون از آن‌ها ساخته می‌شود؛ حتی `node_modules` هم داخلشان نصب نمی‌شود. 2. **D4** — رفتار تکرار می‌شود، خرابی نه. هر عدم‌تکرار عمدی در ماتریس: «عمداً تکرار نشد — ISSUE #n». 3. **D2** — چیزی که مرجع ندارد ساخته نمی‌شود. یعنی اپ کلاسیک صفحه‌ی اعلان‌ها، تنظیمات ظاهر و تب پنجم ناوبری را **ندارد**، چون مرجع ندارد. 4. **PROTECTED.md** — `user-app` و `user-app-v2` دست نمی‌خورند. حتی بازساختاردهی (مثلاً بیرون کشیدن کد مشترک) تأیید جدا می‌خواهد. 5. **D13** — سطوح فروشگاه تک‌فروشی (کیف پول، cashback، قیمت محصول طبق D16) دست نمی‌خورند. 6. **D12** — ماتریس فیلد به فیلد، و تست رفت‌وبرگشت برای هر موجودیتی که endpoint نوشتنی تازه می‌گیرد. 7. **D18** — تأیید دائمی برای شکاف‌های بی‌ابهام؛ توقف فقط برای موارد فهرست D18. 8. **T1 / D21** — مشتری همیشه از توکن، هرگز از بدنه یا URL؛ ادعای `typ`؛ تست توکن متقابل. 9. **D25** — خواندن env فقط با `envOr` / `envOrNull`. 10. **D15** — تست‌هایی که ورودی فارسی دارند، ورودی را از فایل UTF-8 می‌خوانند. > ⚠️ **`CLAUDE.md` در این مخزن نیست.** اسناد parity مدام به §2، §3، §4 و §6 آن ارجاع می‌دهند، > ولی فایل در ریشه‌ی workspace قبلی بوده. قواعدش از روی همین ارجاع‌ها بازسازی شده: > §2 منطق اپ‌های کاربر باید یکسان باشد و فقط طراحی فرق کند؛ §3 باگ مرجع تکرار و ثبت شود، > مگر حفره‌ی امنیتی در جریان پول — آنجا توقف و سؤال؛ §4 PROTECTED فقط افزایشی؛ > §6 هر حذف داده در CHANGELOG ثبت شود. > **پیشنهاد: نسخه‌ی اصلی را به مخزن برگردانید** تا متن اصلی جای این بازسازی را بگیرد. --- ## دامنه‌ی واقعی کار (اعداد راستی‌آزمایی‌شده) | سنجه | عدد | نحوه‌ی راستی‌آزمایی | |---|---|---| | route | **22** (۲۱ واقعی + catch-all) | `_inv-ref-pwa.md` §3 | | فایل `.vue` غیر vendored | **77** فایل، **12,445** خط | `find src -name '*.vue'` بدون دو پوشه‌ی DatePicker | | فایل‌های `views/` | 34 — سه‌تا مرده: `login.vue` (کل فایل کامنت)، `user/src/components/report.vue` (صفحه‌ی دیباگ)، `NotificationList.vue` (سوار نشده) | §6.1 | | کامپوننت اپ | 40 | §9 | | store | ۹ singleton دست‌ساز + ۱ Pinia | §7 | | endpoint یکتا | **44** در ۵۸ محل فراخوانی → **۳۹ ردیف کارکردی زنده** بعد از کنار گذاشتن مرده‌ها | §2.4 + §نقشه‌ی endpoint در همین سند | | پوشش API هدف از آن ۳۹ ردیف | ✅ **12** · 🟡 **9** · ❌ **18** | §نقشه‌ی endpoint | | `@keyframes` | 8 | grep | | محل `` | ۷ زنده + ۲ کامنت‌شده (از جمله **انتقال بین صفحه‌ها**) | grep | | GSAP / `useTransition` | ۱ / ۱ — هر دو در ناوبری پایین | `navigation.vue:83,110` | | overlay | ۲۰ `Drawer`، ۳ `Modal`، ۲ `van-action-sheet` | grep | | asset | ۲۴ svg، ۹ png، ۱ jpg، ۱ ico، فونت Yekan Bakh FaNum (۸ وزن × ۳ فرمت)، `icons.json` | `find src/assets public` | | نقص شناخته‌شده‌ی مرجع | 20 | §13 | --- ## «تکنولوژی جدید» — چه چیزی عوض می‌شود و چه چیزی نه ### کتابخانه‌های UI روی نسخه‌ی lockfile مرجع قفل می‌شوند **یافته:** `yarn.lock` مرجع خودش Vue **3.5.40** را resolve کرده. پس «تکنولوژی جدید» در کتابخانه‌های UI معنایی ندارد — و نباید داشته باشد، چون ظاهر پیکسلی دقیقاً به همین نسخه‌ها وابسته است. مثلاً ant-design-vue 4 استایل را به CSS-in-JS برده و ظاهر عوض می‌شود. | بسته | نسخه‌ی دقیق (بدون `^`) | |---|---| | `ant-design-vue` | 3.2.20 | | `vant` | 4.10.0 — v1/v2 روی 4.10.2 هستند؛ اپ کلاسیک نسخه‌ی مرجع را می‌گیرد | | `swiper` | 7.4.1 | | `gsap` | 3.15.0 | | `tailwindcss` | 3.0.18 | | `@vueuse/core` | 9.13.0 | | `moment-jalaali` | 0.10.4 | | `@lottiefiles/dotlottie-vue` | 0.4.18 | | `vue3-lazyload` | 0.2.5-beta | | `vue` / `vue-router` | 3.5.x / 4.x — همان خانواده‌ی lockfile | **ارقام فارسی از فونت می‌آیند، نه از تبدیل.** مرجع هیچ تبدیل رقمی ندارد (§8.1)؛ ارقام به‌خاطر فونت FaNum فارسی دیده می‌شوند. اپ کلاسیک هم تبدیل رقم اضافه نمی‌کند. ### آنچه واقعاً نو می‌شود | مرجع | اپ کلاسیک | چرا | |---|---|---| | Vite 3 + plugin-vue 2، TS 4.4 بدون strict، vue-tsc 0.29 | Vite 5، TS 5 با `strict`، vue-tsc 2 — همان زنجیره‌ی v1/v2 | ابزار نگهداری‌شده؛ خطا در زمان build | | axios 0.25 با سه instance؛ interceptor خطا promise را برنمی‌گرداند؛ response interceptor ندارد | یک client تایپ‌شده؛ توکن در هر درخواست؛ رفتار 401 مرجع (`myStore.init()`) به‌صورت سراسری | نقص #1 و #2 | | ۹ singleton کلاسی دست‌ساز | Pinia — **با همان شکل state و همان getter های readonly** | یکدستی با v1/v2 | | Mixin ها (`getProducts`، `product`، `favorite`، ...) | composable های تایپ‌شده با تست واحد | قابل تست | | URL و کلید هاردکد در ۵ فایل | متغیرهای `VITE_*` | پیکربندی محیط‌ها | | دو درخت vendored از DatePicker (fork از ant-design-vue) | `generatePicker` عمومی ant-design-vue 3 با config جلالی — **به شرط اثبات ظاهر یکسان در Phase 0** | حذف هزاران خط کد شخص ثالث | | ۵ کتابخانه‌ی تاریخ | `moment-jalaali` برای فرمت‌ها (خروجی یکسان) + dayjs/jalaliday فقط برای picker | کمتر، با تست خروجی | | ۴ کامپوننت تقریباً یکسان `ScrollFetch*` | یک کامپوننت با prop | تکرار | | `addTransaction.vue` و `addFindoc.vue` تکراری | یک فرم، دو پوسته (صفحه و drawer) | تکرار | | کد مرده (§چیزی که پورت نمی‌شود) | پورت نمی‌شود | — | | PWA در `mode: "development"` | production | نقص #17 | | بدون تست | تست واحد (adapter، formatter، validator، موتور تخفیف) + e2e و مقایسه‌ی تصویری با Playwright | اثبات «دقیقاً» | --- ## تنش‌هایی که قبل از شروع باید تصمیمشان را بدانید ### تنش ۱ — ۱۸ endpoint مرجع در API هدف نیست از ۳۹ ردیف زنده، **۱۸ تا در هدف معادل ندارند و ۹ تا ناقص‌اند** (§نقشه‌ی endpoint). بدون آن‌ها «دقیقاً مثل مرجع» ممکن نیست. وضعیت امروز v1/v2 این را نشان می‌دهد: - ثبت‌نام **هیچ چیزی ذخیره نمی‌کند** — کامنت خود `RegisterView.vue:17` این را می‌گوید. - علاقه‌مندی‌ها **فقط محلی‌اند** (`favoritesStore.ts:1-3`). - ثبت سند، سفارش ساخت، مدیریت مشتری توسط کارشناس، ویرایش سفارش مشتری، آنالیتیکس و فاکتور اصلاً وجود ندارند. ساختن این‌ها در `apps/api` افزایشی است (route و جدول/ستون جدید). **ولی با `CLAUDE.md` §2 تعارض دارد:** منطق اپ‌های کاربر باید یکسان باشد. به محض اینکه اپ کلاسیک ثبت‌نام و علاقه‌مندی واقعی داشته باشد، v1 و v2 عقب می‌افتند. **پیشنهاد:** بک‌اند **یک بار**، افزایشی و تست‌شده ساخته شود. وصل کردن v1/v2 به آن یک کار جدا و ثبت‌شده باشد — نه بخشی از این پروژه. **تصمیم شما لازم است** که §2 شامل اپ کلاسیک هم می‌شود یا نه. ### تنش ۲ — «انتخاب مشتری»: کار کردن به‌جای مشتری **در مرجع:** کارشناس فروش (type 1) روی یک مشتری می‌زند → `toggleActiveUser` توکن خود آن مشتری را به‌عنوان `activeChildToken` ذخیره می‌کند، و این توکن **روی همه‌ی درخواست‌ها اولویت دارد** (`store/tokenManage.ts:8-10`، `store/myDataStore.ts:159-169`). از آن لحظه کل اپ به‌جای مشتری کار می‌کند: میز کار موجودی مشتری را نشان می‌دهد، سفارش سبد روی حساب مشتری با `from_parent: true` ثبت می‌شود تا پیامک به کارشناس برسد (`views/cart.vue:162-164`)، و دکمه به «خروج از حساب مشتری» تبدیل می‌شود. **در هدف:** D21 **سازوکار** را رد کرد — ساختن توکن مشتری برای کارشناس یک ابزار ربودن نشست است. `CustomerChoiceView` در v1 با لمس کارت فقط یک drawer جزئیات باز می‌کند (`openDetail`). پس **خود این قابلیت** امروز در هدف وجود ندارد. **پیشنهاد:** همان قابلیت، با سازوکار امن: - توکن خود کارشناس + هدر `X-Acting-As: `. - سرور در **هر درخواست** چک می‌کند `referredByAgentId` آن مشتری = همین کارشناس. - موضوع همه‌ی route های مشتری = آن مشتری. - سفارش با پرچم `from_parent` ثبت می‌شود. - هر تعویض در لاگ ثبت می‌شود. - **هیچ توکنی ساخته نمی‌شود** و سمت کلاینت فقط شناسه‌ی مشتری انتخاب‌شده نگه داشته می‌شود. **تأیید شما لازم است**، چون امنیت و جریان پول است (`CLAUDE.md` §3). این کار ریسک REL-03 را هم بالاتر می‌برد — §پیش‌نیازها. ### تنش ۳ — نقش کارگاه (type 5) در اپ **در مرجع:** سرپرست کارگاه یک ردیف مشتری با type 5 است. کاشی «سفارش ساخت» برایش مستقیم به صفحه‌ی وضعیت می‌رود و دکمه‌های «تایید سفارش» و «ارسال سفارش» را می‌بیند (`makeOrderStatus.vue:68-75`). **در هدف دو نامزد هست:** - `AdminUser` با نقش `WORKSHOP_SUPERVISOR` — `Workshop.supervisorId` به اینجا اشاره می‌کند و `PreOrder.workshopId` به `Workshop`. - `Customer` با `kind = WORKSHOP_SUPERVISOR` — این مقدار در `CustomerKind` هست و برخلاف `SALES_AGENT` منسوخ اعلام نشده. D21 فقط به `SALES_EXPERT` اجازه‌ی ورود به اپ‌ها را داده. **پیشنهاد:** `AdminUser`، چون مسیر داده‌ای تمیز همین است. الگوی D21 به `WORKSHOP_SUPERVISOR` گسترش پیدا کند، با دامنه‌ی محدود به `PreOrder` های کارگاه خودش. **تصمیم شما لازم است**، از جمله اینکه مشتری با `kind = WORKSHOP_SUPERVISOR` در این صورت چه نقشی دارد. ### تنش ۴ — خرابی‌های مرجع که به منطق کسب‌وکار می‌خورند D18 برای این‌ها توقف می‌خواهد (مورد ۴). شماره‌ها از `_inv-ref-pwa.md` §13 است. | # | خرابی در مرجع | اثر برای کاربر | پیشنهاد من | |---|---|---|---| | 4 | دکمه‌ی ثبت در صفحه‌ی `addTransaction` بیرون از `
` است و `html-type="submit"` ندارد (`addTransaction.vue:84-97`) | ثبت سند از **صفحه** کار نمی‌کند؛ فقط از drawer | درست شود — یک فرم مشترک، هر دو پوسته کار کنند | | 5 | کاشی «سفارش مشتریان» تابع را صدا نمی‌زند (`Dashboard.vue:119`) | کارشناس از میز کار به آن صفحه نمی‌رسد | درست شود — D3 خود صفحه را خواسته | | 6 | کادرهای ته‌حساب طلایی و ریالی اندیس‌ها و formatter ها را جابه‌جا خوانده‌اند (`Dashboard.vue:182-192`) | موجودی طلا و ریال در کادر اشتباه و با قالب اشتباه | هر کادر مقدار درست را با قالب متناسب با برچسبش نشان دهد | | 8 | دکمه‌ی به‌روزرسانی قیمت، فهرستی را تازه می‌کند که نمایش داده نمی‌شود (`store.ts:107` در برابر `Dashboard.vue:632`) | به‌روزرسانی بی‌اثر | **قبلاً تصمیم گرفته شده (D4):** فهرست دیده‌شده تازه شود | | 9 | «ارسال سفارش» همان `{sts: 2}` «تایید سفارش» را می‌فرستد (`makeOrderStatus.vue:212,234`) | ارسال در سرور ثبت نمی‌شود | تایید → «تایید کارگاه»، ارسال → «ساخته شده آماده ارسال». مقدار دقیق `PreOrderStatus` را شما تعیین کنید | | 12 | radio «حواله به» **اندیس** آرایه را می‌فرستد نه متن مقصد (`addTransaction.vue:50`). سرور مرجع کل بدنه را بی‌تغییر ذخیره می‌کرد (`ApiMobileController::setFindoc`) | مقصد حواله به‌صورت عدد ذخیره می‌شد | سرور اندیس را به متن مقصد نگاشت کند؛ UI بی‌تغییر | | 14 | در `editUser` سه آپلودر مدرک شناسه را دور می‌ریزند و دو تا یک `fileList` مشترک دارند (`editUser.vue:65,69`) | آپلود «موفق» دیده می‌شود ولی چیزی ذخیره نمی‌شود | **عیناً تکرار شود و ثبت شود** — وصل کردنش داده‌ی احراز را از صفحه‌ای عوض می‌کند که مرجع هرگز اجازه نداده بود | | 15 | شرط infinite scroll گزارش `tableData.value.count` را می‌خواند که هرگز مقدار نمی‌گیرد (`report/index.vue:234`) | فقط صفحه‌ی اول گزارش دیده می‌شود | درست شود — endpoint های D24 صفحه‌بندی دارند | | 19 | پسوند ` تومان ` روی فیلدی به نام `rial_remain` (`filters/index.ts:13`)؛ ستون هدف `fiatDebtRial` به ریال است | اگر واحدها نخوانند، بدهی ده برابر کم یا زیاد خوانده می‌شود | اول در `henza_server` واحد واقعی `rial_remain` معلوم شود؛ بعد adapter **همان عدد و برچسبی** را نشان دهد که مرجع برای همان بدهی نشان می‌داد | | — | `singleProduct` دکمه‌ی back را می‌رباید: `history.pushState` + `window.onpopstate = () => history.go(1)`، سراسری و هرگز برداشته نمی‌شود (`singleProduct.vue:376-379`) | بعد از دیدن اولین محصول، back مرورگر یا گوشی **در کل اپ** از کار می‌افتد | محدود به خود صفحه‌ی محصول — نیت ظاهری کد همین است، نشتش به کل اپ خرابی است | ### تنش ۵ — سرور هدف از مرجع سخت‌گیرتر است سرور مرجع تقریباً هر چیزی را قبول می‌کرد. سرور هدف بیشتر رد می‌کند: قاعده‌ی موجودی و پیش‌سفارش (PROTECTED §C.1)، MON-05 وقتی اجرا شود، DTO های اعتبارسنجی، قیدهای یکتایی. ولی UI مرجع در بیشتر خطاها **هیچ چیزی نشان نمی‌دهد** — فقط `console.log(e)`، مثلاً در `Register.vue:340-342`، `cart.vue:189-191` و ثبت سند. پس درخواستی که مرجع بی‌صدا قبول می‌کرد، در اپ کلاسیک بی‌صدا شکست می‌خورد و کاربر گیج می‌شود. | مورد | پیشنهاد من | |---|---| | **سقف موجودی:** سبد مرجع سقف ندارد؛ قاعده‌ی موجودی هدف (`capQty`/`maxQty` در v1) وجود دارد | سقف **بی‌صدا** در دکمه‌ی `+` اعمال شود — بدون هیچ عنصر یا متن تازه | | **خطاهای بی‌صدا** | هر جا مرجع بی‌صدا بوده بی‌صدا بماند، **مگر** جایی که سرور هدف ردی می‌کند که مرجع هرگز نداشت. آنجا نزدیک‌ترین پیام **موجود** مرجع نشان داده شود (مثلاً «خطا در ثبت سفارش» از `makeOrder.vue:229`). هیچ متن تازه‌ای ساخته نمی‌شود | ### تصمیم‌های کوچک‌تر | # | سؤال | پیشنهاد من | |---|---|---| | Q1 | نام، مسیر و پورت اپ | `apps/user-app-classic`، پورت **5183** (`USER_APP_CLASSIC_PORT`)، سرویس docker با همین نام. «classic» و نه «v3»، چون v3 «جدیدترین» را القا می‌کند در حالی که این قدیمی‌ترین طراحی است | | Q2 | کد مشترک بین سه اپ | فعلاً داخل خود اپ در `src/core/` (api، adapter، قواعد دامنه)، با مرز تمیز تا بعداً قابل استخراج باشد. پکیج مشترک یعنی بازساختاردهی v1/v2 → PROTECTED → تصمیم جدا | | Q3 | فیلتر رنگ در جست‌وجو — هدف اصلاً داده‌ی رنگ ندارد (`Product` و `ProductVariant` فقط `brand` دارند) | مدل افزایشی `Color` و ارتباطش با variant؛ ویرایشش در پنل ادمین کار جداگانه است. تا آن موقع فهرست خالی — همان چیزی که مرجع با داده‌ی خالی نشان می‌داد | | Q4 | تابلوی قیمت میز کار: مرجع ۱۴ ردیف دارد (۸ طلا و سکه + ۶ ارز و انس، `store.ts:118-119`)؛ هدف فقط نرخ طلا به ازای عیار | ردیف‌هایی که هدف داده‌ی واقعی برایشان دارد نمایش داده شوند؛ بقیه حذف (قاعده‌ی صداقت، مثل REP-01) تا منبع قیمت تأیید شود. منبع خارجی = تصمیم شما | | Q5 | ترتیب دسته‌های خانه: آرایه‌ی هاردکد `[37, 45, 31, 32, 42, 26, 36, 35, 34]` با شناسه‌ی عددی legacy (`home/index.vue:189-204`)؛ شناسه‌ی `Category` در هدف cuid است و نه legacyId دارد نه ستون ترتیب | ستون افزایشی ترتیب روی `Category` و پر کردنش از نگاشت مهاجرت. پر کردن = تغییر داده‌ی موجود → D18 توقف #5 → تأیید لازم | | Q6 | آنالیتیکس مهمان: مرجع برای مهمان هم لاگ می‌فرستد؛ `CustomerActivityLog.customerId` اجباری است و ستون پایان ندارد | فقط کاربر واردشده لاگ شود؛ ستون افزایشی `endedAt` برای معادل `log/update` | | Q7 | اعلان‌ها، FCM و پرکردن خودکار OTP — هر سه در مرجع سوار نیستند یا غیرفعال‌اند | ساخته نمی‌شوند — دقیقاً مثل مرجع | | Q8 | اجرای مرجع برای مقایسه‌ی تصویری | کپی در `.parity/ref-pwa/` (gitignored) و رهگیری شبکه با Playwright؛ خود مرجع دست نمی‌خورد | | Q9 | وضعیت D19 (حالت تحویل) | این پلن فرض می‌کند D19 تمام شده و D18 برقرار است | --- ## پیش‌نیازهای باز از کارهای قبلی | مورد | چرا این اپ به آن نیاز دارد | Batch | |---|---|---| | **D9 قدم ۵** — جابه‌جایی `filterPercentPay` به اندازه‌ی آفست مشتری | اسلایدر اجرت در فیلتر جست‌وجو | B | | **شکاف یکتایی شماره بین `Customer` و `AdminUser`** — هر چهار بررسی تداخل فقط `adminUser` را چک می‌کنند؛ در `ISSUES.md` ثبت نشده | ثبت‌نام و «افزودن کاربر» هر دو مشتری می‌سازند | A، F | | **MON-08** — سرور اجرت و تخفیف را خودش بسازد | ثبت سفارش از سبد | E | | **MON-05** — اجرای `canPlaceOrders` و `verificationStep` | مرجع فقط در کلاینت چک می‌کند (`sts_verify_step_two === 2`) | E | | **REL-03** — مقدار جدای `JWT_CUSTOMER_SECRET` | کار به‌جای مشتری (تنش ۲) ریسک آن را بیشتر می‌کند | قبل از F | --- ## نقشه‌ی endpoint ها — مرجع ↔ هدف ✅ موجود (فقط adapter شکل) · 🟡 ناقص · ❌ وجود ندارد | # | مرجع | هدف امروز | وضعیت | Batch | |---|---|---|---|---| | 1 | `POST /web/auth` | `POST /customer/auth/request-otp` | ✅ | A | | 2 | `POST /user-verfiy` | `POST /customer/auth/verify-otp` | ✅ — رد ورود برای `sts_verify_step_one == 3` بررسی شود | A | | 3 | `POST /major/register` | — | ❌ | A | | 4 | `POST /major/complete` | `POST /customer/me/complete-profile` | ✅ (D22) | A | | 5 | `POST file/create` | `POST /customer/me/documents` | ✅ با `purpose` | A، D، G | | 6 | `POST /me-data` | `GET /customer/me` | 🟡 هیچ‌کدام از `filterBrand`، `filterColor`، `filterGram`، `filterPercentPay`، `favorits`، `to`، `reagent`، `grams_sell`، `mcode` را ندارد | Phase 2 | | 7 | `POST /front` | `GET /catalog/products` | 🟡 دسته‌ها، `more` و `ciny` آماده نیستند | B | | 8 | `POST /get-products` (Query) | `GET /catalog/products` | 🟡 فیلتر و صفحه‌بندی سمت سرور ندارد (STUB-03) | B | | 9 | `POST /get-products` (`products_id`) | `GET /catalog/products` | 🟡 | B، E | | 10 | `GET /ciny/{cat}` | داخل catalog (`collections`) | 🟡 | B | | 11 | `GET /product/{id}` | — فقط فهرست | 🟡 | B | | 12 | `GET /config` | `GET /home/slider` | 🟡 `about` ندارد | B | | 13 | `GET /promotion` | — | ❌ | B | | 14 | `GET /ignore` | — | ❌ | B | | 15 | `GET /price` | `GET /pricing/latest` | 🟡 فقط طلا به ازای عیار (Q4) | C | | 16 | `GET /tahesab/me` | `GET /customer/me/report/balances` | ✅ (D24) | C | | 17 | `POST /add-to-fav` | — | ❌ (v1/v2 محلی) | D | | 18 | `POST /get-favorites` | — | ❌ | D | | 19 | `POST /get-orders` | `GET /customer/me/orders` | ✅ فیلتر `sts` در کلاینت | D | | 20 | `POST /update-profile` | — | ❌ | D | | 21 | `POST /get-my-findoc` | `GET /customer/me/ledger-entries` | 🟡 شکل متفاوت | D | | 22 | `POST /set-findoc` | — | ❌ | D | | 23 | `GET /fin_tahesab/table` | `GET /customer/me/report` | ✅ (D24) | D | | 24 | `GET /fin_tahesab/my_detail/{offset}` | `GET /customer/me/report/detail` | ✅ | D | | 25 | `GET /fin_tahesab/detail/{code}` | `GET /customer/me/report/:id` | ✅ | D | | 26 | `report/order?order_id` — فاکتور با `window.open` | — | ❌ | D | | 27 | `report/doc?doc_id` — فایل سند | — | ❌ | D | | 28 | `POST /set-order` | `POST /orders` | ✅ (MON-08) | E | | 29 | `POST /my-child` | `POST /expert/my-customers` | ✅ | F | | 30 | `POST /customer/toggle-active` | — | ❌ | F | | 31 | `POST /customer/create` | — | ❌ | F | | 32 | `POST /customer/update` | — | ❌ | F | | 33 | `GET admin/major/list_type` | — | ❌ از `CustomerKind` ساخته می‌شود | F | | 34 | `POST /order/child` | `POST /expert/child-orders` | ✅ | F | | 35 | `PUT /order/child/{id}` | — | ❌ | F | | 36 | `POST /create-custom-order` | — مدل `PreOrder` فقط در ادمین | ❌ | G | | 37 | `GET /get-custom-orders` | — | ❌ | G | | 38 | `POST /update-custom-order/{id}` | — | ❌ | G | | 39 | `POST /log/create/{key}` + `GET /log/update/{id}` | — | ❌ | G | **ساخته نمی‌شوند (در مرجع مرده‌اند):** `POST /hesab_detail/table` (هرگز صدا زده نمی‌شود)، تکرار `GET /ignore` در `getCustomerListType`، تکرار مرده‌ی `toggle-active` در Dashboard، `POST /cities/list` (داده گرفته می‌شود ولی UI آبشاری کامنت است و فیلد شهر متن آزاد است — `Register.vue:58-64`)، و سه endpoint اعلان (کامپوننتشان سوار نیست). --- ## مشخصات حس و حرکت — انیمیشن‌ها و ناوبری این بخش همان چیزی است که یک پورت سرسری از دست می‌دهد. هر ردیف در Phase 0 با ضبط frame اثبات می‌شود. ### انیمیشن‌ها | # | انیمیشن | پارامتر دقیق | محرک | شاهد | |---|---|---|---|---| | 1 | فرورفتگی (notch) ناوبری پایین | `gsap.to('.from-path', {duration: .3, attr: {d: draw()}})` | تغییر تب | `components/ui/navigation.vue:50-86` | | 2 | جابه‌جایی آیکون فعال | `useTransition` با `TransitionPresets.easeInOutCubic`، ‏150ms | تغییر تب | `navigation.vue:110-117` | | 3 | **shutter هدر** | `@keyframes shutter` در 1s با `cubic-bezier(0.33, 1, 0.68, 1)`؛ `max-height` از 0 تا 120px و `opacity` از 0 تا 1 | **هر تغییر route**: `afterEach` پرچم را false و بعد از 100ms دوباره true می‌کند | `components/appHeader.vue:1-45`، `router/index.ts:273-286` | | 4 | فاصله‌ی محتوا از هدر | `margin-top = headerHeight` وقتی `transitionEnded` | همراه ۳ | `components/content.vue:2` | | 5 | **انتقال بین صفحه‌ها** | **ندارد.** `` در `App.vue:6,17` کامنت است. CSS `slideDown` (`App.vue:151-166`) و `setPgaTransition` زنده‌اند ولی به هیچ چیز وصل نیستند | — | **اضافه نمی‌شود** | | 6 | باز شدن بخش فیلتر | `@keyframes drop` با `mode="out-in" appear` | باز و بسته کردن | `components/dropDown.vue:11,40` | | 7 | تعویض تصویر گالری | `slideUp` با `mode="out-in"` | تغییر تصویر یا تنوع | `views/singleProduct.vue:28`، `components/slider/slider.vue:22` | | 8 | اسپینر | `@keyframes rotate` و `rotateOpacity` (سه حلقه) | بارگذاری | `components/spinner.vue:104,115` | | 9 | ریل سفارش ساخت | `@keyframes gradientMove` و `colorChange` روی رابط مرحله‌ی جاری | نمایش وضعیت | `makeOrderStatus.vue:283,295` | | 10 | دیالوگ‌ها و notify | قواعد `animation:` | باز شدن | `DialogVant.vue`، `DialogVantOneButton.vue`، `NotifyVant.vue` | | 11 | فهرست اسناد | `TransitionGroup name="list"` + کوییرک `testBind` (#13) | بارگذاری فهرست | `transactions.vue:17,177-205` | | 12 | notify قدیمی | `@keyframes notify-in` | — | `customNotification.vue:47`، `Notification.vue:81` — **اول بررسی شود سوار هستند یا نه** | | 13 | اسلایدر خانه | Swiper با `loop` و `autoplay` | خودکار | `slider/homeSlider.vue:7,10` | | 14 | اسلایدر محصول | Swiper با ماژول `Zoom`، بدون loop | لمس | `slider/slider.vue:43-45` | | 15 | overlay ها | انیمیشن خود کتابخانه برای ۲۰ Drawer، ۳ Modal و ۲ action-sheet | — | با قفل نسخه‌ها تضمین می‌شود | | 16 | دیالوگ نسخه‌ی جدید | Lottie | تغییر `version` | `App.vue:22-38` | ### زمان‌بندی‌ها | چه چیزی | مقدار | شاهد | |---|---|---| | دیالوگ تشویق به ورود در خانه | 5 ثانیه، **در هر بازدید** — گارد «فقط یک بار» کامنت شده | `home/index.vue:210-216` | | شمارش معکوس OTP | 120 ثانیه، با صفر پیشرو زیر 10 | `Login.vue:122-131,187-189` | | toast ها | 4000ms | `Dashboard.vue:269-284` و بقیه | | debounce جست‌وجو | 700ms | `components/search/SearchInput.vue` | | بازگرداندن پرچم انتقال هدر | 100ms | `router/index.ts:273-286` | ### ناوبری - **۲۲ route با همان path، name و meta**، از جمله حروف و نام‌های عجیب: `/ChildOrder`، `/customerChoice`، `/addTransaction`، `/makeOrder_status`، `/complete_profile`، `userPage::hasSubRoute`. خانه `/home` است و `/` به آن redirect می‌شود. - **`beforeEach`** همان شش قدم `_inv-ref-pwa.md` §5.1، با تصمیم‌های D3: - مهمان روی پنج route محافظت‌شده → toast «ابتدا وارد شوید» + خانه. - کاربر واردشده روی `/login` یا `/` → خانه. - تله‌ی cold-boot (`isLoaded`) — D3 گفته تکرار شود. - `completeProfile` بسته است وقتی `sts_verify_step_two ∈ {1, 2}`. - route هایی که مرجع در guard ندارد (`addTransaction`، `makeOrderList`، `makeOrderStatus`، `addUser`، `editUser`، `ChildOrder`، `customerChoice`، و `report`/`reportDetail` که خودشان redirect می‌کنند) در اپ کلاسیک هم در guard نیستند؛ سرور هدف به‌هرحال 401 می‌دهد. - **ناوبری پایین:** چهار تب به ترتیب index — کاربر (۱)، سبد (۲)، جست‌وجو (۳)، خانه (۴)؛ پیش‌فرض فعال ۴. فقط روی همین چهار route دیده می‌شود، و وقتی پنل فیلتر باز است پنهان می‌شود. - **`keep-alive` + `suspense` با کلید `route.name`** — هر صفحه‌ی دیده‌شده mount می‌ماند (مثلاً نتایج جست‌وجو بعد از برگشت حفظ می‌شوند). این بخشی از حس ناوبری مرجع است و حفظ می‌شود. - `scrollBehavior`: بازگرداندن `savedPosition`، وگرنه بالای صفحه. - layout `FullPage` (بدون ناوبری پایین) برای همان ۸ route مرجع؛ بقیه `Master`. - ربودن دکمه‌ی back در صفحه‌ی محصول — تنش ۴. --- ## ساختار فازها ### Phase 0 — تصمیم‌ها و زیرساخت اثبات (بدون کد اپ) 1. پاسخ شما به تنش‌های ۱ تا ۵ و Q1 تا Q9. 2. **اجرای مرجع:** کپی `henza_pwa@b171fff` در `.parity/ref-pwa/` (gitignored)، `yarn install --frozen-lockfile`، اجرا روی پورت 3030. اگر toolchain قدیمی روی Node فعلی بالا نیاید → ریسک R1. 3. **یک مجموعه‌داده با دو خروجی:** JSON به شکل API مرجع (برای رهگیری درخواست‌های `https://henzagold.com/oapi/**` در Playwright) و seed دیتابیس برای API هدف. یک منبع، دو projection — تا هر دو اپ دقیقاً یک داده را نشان دهند. 4. **پرسوناها:** مهمان؛ مشتری تأییدنشده؛ مشتری تأییدشده (`sts_verify_step_two = 2`)؛ بنکدار (type 4)؛ کارشناس فروش (type 1)، و کارشناس در حال کار به‌جای مشتری؛ کارگاه (type 5). 5. **مقایسه‌ی تصویری:** Playwright روی یک ماشین و یک مرورگر برای هر دو اپ؛ viewport های 390×844 و 768×1024 (کانتینر مرجع `max-w-screen-md` است)؛ نواحی پویا مثل تاریخ و تایمر ماسک می‌شوند. آستانه‌ی قبول همین‌جا توافق می‌شود. 6. **بررسی انیمیشن:** ضبط frame در زمان‌های ثابت پس از محرک (مثلاً 0، 150، 300 و 1000ms) برای هر ردیف جدول انیمیشن. 7. **spike تقویم جلالی:** `generatePicker` عمومی + config جلالی کنار fork مرجع. اگر مقایسه‌ی تصویری صفر نشد، fork به‌صورت vendored نگه داشته می‌شود. 8. ساخت `docs/parity/03-PWA-CLASSIC-MATRIX.md` — فیلد به فیلد طبق D12. 🛑 **Gate — توقف و تأیید شما.** ### Phase 1 — پایه‌ی `apps/user-app-classic` - اسکلت Vite 5 + Vue 3.5 + TS strict + Pinia + vue-router، با نسخه‌های قفل‌شده‌ی بالا. - **asset ها بایت‌به‌بایت با checksum:** فونت‌ها، `icons.json` و `Icon.vue`، تصاویر `assets/img` و empty، favicon، آیکون‌های manifest، فایل Lottie، `whatsapp.png`. - `themeConfig/colors.json`، متغیرهای less، `tailwind.config.js` و `postcss-rtl` — همان مقادیر. - `index.html` همان: عنوان «طلای منتزر»، `theme-color` ‏`#1C2D56`، قفل zoom، دکمه‌ی شناور واتس‌اپ (شماره از env، با مقدار پیش‌فرض مرجع). `bale.png` در `public/` هست ولی در `index.html` استفاده نمی‌شود — بررسی می‌شود. - PWA: `injectManifest`، `sw.ts` فقط precache، manifest همان، **mode production**. - زیرساخت: پورت 5183، `Dockerfile` و nginx به الگوی v1، سرویس compose، اضافه شدن به `build:release`، به‌روزرسانی `DELIVERY.md`. ### Phase 2 — لایه‌ی core - **client HTTP:** base از env، توکن در هر درخواست؛ 401 سراسری = همان کار `myStore.init()` مرجع (پاک کردن `appToken`، `appCart` و کلید acting-as، رفتن به خانه). - **adapter ها — قلب کار:** DTO هدف → شکل‌هایی که view های مرجع انتظار دارند (`MyData`، `Product`، `Variant`، `cartItem`، سفارش با `sts` عددی، ردیف سند، ردیف گزارش). view ها با همان نام فیلدهای مرجع bind می‌شوند. **قاعده‌ی صداقت:** چیزی که هدف ندارد `null` می‌شود و ردیفش پنهان — هرگز مقدار ساختگی. - **نگاشت enum ها:** - `CustomerKind` → `type` مرجع (۲، ۳، ۴، ۵؛ و ۱ برای کارشناس). - `OrderStatus` هدف (۹ وضعیت) → `sts` مرجع (۸ تب). - `PreOrderStatus` → رشته‌های فارسی `sts_str_fa`. - `LedgerSourceType` → نوع سند ۱ تا ۴ و ۹. - **بک‌اند پایه:** معادل کامل `POST /me-data` (ردیف ۶ نقشه) — همه‌ی صفحه‌ها به آن وابسته‌اند. - **store های Pinia با همان state مرجع:** base، me (از همین حالا با جای `_activeUser` برای تنش ۲)، cart (`useStorage('appCart')` با همان کلید و شکل)، nav، query، token، transition، windowData، report. - **composable ها:** `getProducts` با فرمول دقیق صفحه‌بندی `floor(count/limit) - 1`؛ موتور تخفیف (۴ نوع، نوع ۳ خاموش، بیشترین `discount_percent`)؛ `getWage`؛ `getHasDiscountWage`؛ علاقه‌مندی خوش‌بینانه. - **formatter ها با همان خروجی:** ` تومان ` با فاصله‌ها، truncate (نه round) در `useTowDecimal`، حذف علامت در `useFormat`. **validator ها با همان پیام‌ها**، ولی بدون پرچم `g` (نقص #18). - تست واحد برای همه‌ی این‌ها، با نمونه‌های مرجع. ### Phase 3 — پوسته، ناوبری، انیمیشن - `App.vue` با `keep-alive`/`suspense`، دو layout، چک نسخه + دیالوگ Lottie، به‌روزرسانی بی‌صدای service worker. - router با ۲۲ route و guard ها؛ hook آنالیتیکس (وصلش در Batch G). - ناوبری پایین با GSAP، `appHeader` با shutter، `content` با margin. - کامپوننت‌های پایه: `DialogVant`، `DialogVantOneButton`، `NotifyVant`، `spinner`، `empty`، `WorkActionSheet`، `container`، `ScrollFetch`، `SearchInput`، `otpInput`، `SingleImageUploader`، `InputDatePicker`. - هر ردیف جدول انیمیشن با harness فاز ۰ اثبات می‌شود. ### Batch A — ورود، ثبت‌نام، تکمیل پروفایل · حجم بک‌اند: کم - **صفحه‌ها:** `LoginRegister/index.vue`، `Login.vue`، `Register.vue`، `completeProfile.vue`، `input/otpInput.vue`، `input/SingleOtpInput.vue`. - **بک‌اند:** - معادل `POST /major/register` — مشتری تازه با `verificationStep = UNVERIFIED`، با بررسی یکتایی شماره در **هر دو** جدول. - رد ورود برای `sts_verify_step_one == 3` با پیام «مجوز ورود ندارید» — نگاشتش به وضعیت هدف بررسی می‌شود. - تکمیل پروفایل و آپلود مدارک: موجود (D22). - **کوییرک‌هایی که حفظ می‌شوند:** OTP چهاررقمی (APP-01)؛ رنگ notify همیشه قرمز `#DC2626`، حتی برای موفقیت؛ برچسب دکمه «دریافت رمز عبور» / «دریافت مجدد رمز عبور»؛ دیالوگ موفقیت ثبت‌نام که با کلیک بیرون بسته نمی‌شود؛ فیلد شهر متن آزاد؛ تلفن فروشگاه بدون الگو. ### Batch B — کاتالوگ · حجم بک‌اند: زیاد - **صفحه‌ها:** `home/index.vue`، `search/searchPage.vue`، `search/collection/index.vue` و `CollectionCard.vue`، `newest.vue`، `singleProduct.vue` (صفحه و حالت drawer)، `components/product/*`، `components/search/*`، `components/slider/*`، `viewAll.vue`. - **بک‌اند:** - معادل `POST /front`: دسته‌ها با `total`، `icon` و `image`؛ محصولات جدید؛ `more`؛ `ciny`. - معادل `POST /get-products`: همه‌ی کلیدهای `Query` (`colors`، `brands`، `limit_percent`، `limit_gram`، `has_discount`، `text`، `cats`، `ciny_id`، `page`، `limit`) با پاسخ `{rows, count, limit}`، و حالت `products_id` برای rehydrate سبد. سمت سرور، چون صفحه‌بندی مرجع سمت سرور است. - معادل `GET /product/{id}` و `GET /ciny/{cat}`. - معادل `GET /config` (`about` + `main_slider`). - معادل `GET /promotion` و `GET /ignore` برای کاربر واردشده. - **کوییرک‌ها:** ترتیب ۹ دسته (Q5)؛ دیالوگ ورود در هر بازدید بعد از ۵ ثانیه؛ ارتفاع اسلایدر `min(width, 768) / 2.2125`؛ تنوع پیش‌فرض = کمترین `percentage_pay` با fallback «بدون تنوع»؛ تخفیف نوع ۳ خاموش؛ «افزودن به سبد» فقط با `sts_verify_step_two === 2`؛ نمونه‌ی تصادفی ۶ کالکشن؛ برچسب «N فیلتر» / «اعمال فیلتر»؛ بازه فقط وقتی «فعال» حساب می‌شود که هر دو سرش عوض شده باشد؛ بنکدار اسلایدر اجرت را نمی‌بیند. - **وابسته به:** D9 قدم ۵، Q3، Q5، تصمیم دکمه‌ی back. ### Batch C — میز کار (`/user`) · حجم بک‌اند: کم تا متوسط - **صفحه‌ها:** `views/user/index.vue`، `Dashboard.vue`، `about.vue`. - **بک‌اند:** تابلوی قیمت (Q4)؛ `grams_sell` برای کارشناس. - **کوییرک‌ها:** «بدهکار گرمی» / «بستانکار گرمی» با قدر مطلق؛ «فروش گرمی» فقط برای کارشناس؛ کاشی‌ها برای مهمان → دیالوگ ورود؛ دیالوگ «ّکاربر فعال شود؟» با همان تشدید سرگردان؛ دیالوگ خروج؛ متن‌های toast قیمت؛ سرتیتر تاریخ و ساعت قیمت که همیشه خالی است (#7 — کوییرک بصری). - **تصمیم‌ها:** #5، #6، #8، #19؛ تنش ۲ برای دکمه‌ی «انتخاب مشتری» / «خروج از حساب مشتری». ### Batch D — سفارش‌ها، علاقه‌مندی‌ها، پروفایل، اسناد، گزارش · حجم بک‌اند: زیاد - **صفحه‌ها:** `Orders.vue`، `product/order.vue`، `product/orderItem.vue`، `favorits.vue`، `profile.vue`، `editUser.vue`، `transactions.vue`، `addTransaction.vue`، `addFindoc.vue`، `report/index.vue`، `report/detail.vue`، `Accounting.vue`، `AccountingDetail.vue`. - **بک‌اند:** - علاقه‌مندی: جدول افزایشی، toggle و list. - ویرایش پروفایل: **فقط فیلدهای فرم** (whitelist). مرجع کل بدنه را روی ردیف مشتری می‌نوشت (`ApiMobileController::updateProfile` — همان خانواده‌ی SEC-14). ستون‌های افزایشی `birthDate` و `marriageDate` (PERS-03). - اسناد: فهرست به شکل مرجع از `LedgerEntry`. ثبت سند به‌صورت ورودی **تأییدنشده**، با نرمال‌سازی آب‌شده `value × ayar / 750` (همان `REPAYMENT_MELTED_GOLD`). پیامک به مدیران از env، به‌جای دو شماره‌ی هاردکد مرجع (الگوی SEC-15). مرجع اینجا هم کل بدنه را ذخیره می‌کرد. - فاکتور سفارش و فایل سند (معادل `report/order` و `report/doc`). - گزارش: موجود (D24). - **کوییرک‌ها:** ۸ تب وضعیت با همان رنگ‌ها (۰ و ۲ بدون رنگ)؛ ریل چهارمرحله‌ای با `current = 1` پیش‌فرض؛ جست‌وجوی دقیق شماره در کلاینت؛ مودال تمام‌صفحه؛ باز شدن فاکتور در پنجره‌ای به نام `_bank`؛ **دو دکمه‌ی شناور هم‌پوشان** در اسناد، با همان ترتیب لایه‌ها؛ رنگ و آیکون ردیف برای `type == 9`؛ چهار تب فرم سند و متن ثابت تب «پولی»؛ دکمه‌ی «افزودن کاربر» که هنگام کار به‌جای مشتری پنهان است (#11). - **تصمیم‌ها:** #4، #12، #14، #15. ### Batch E — سبد و ثبت سفارش · حجم بک‌اند: متوسط - **صفحه‌ها:** `cart.vue`، `product/cartItem.vue`. - **بک‌اند:** MON-08؛ MON-05؛ پرچم `from_parent` (تنش ۲). - **کوییرک‌ها:** کلید `appCart` و شکلش؛ حذف بی‌صدای ردیف‌هایی که محصولشان دیگر نیست؛ وقتی تعداد ۱ است، منفی به سطل زباله تبدیل می‌شود؛ drawer یادداشت 280px با textarea پنج‌خطی؛ جمع **فقط وزن** با `.toFixed(2)`؛ دیالوگ تأیید؛ صفحه‌ی موفقیت با شماره‌ی سفارش؛ بنکدار اجرت را نمی‌بیند. - **تصمیم‌ها:** تنش ۵. ### Batch F — کارشناس فروش · حجم بک‌اند: زیاد - **صفحه‌ها:** `customerChoice.vue`، `addUser.vue`، `ChildOrders.vue`، و بخش‌های کارشناس در Dashboard و profile. - **بک‌اند:** - کار به‌جای مشتری (تنش ۲). - ساخت، ویرایش و فعال/غیرفعال کردن مشتری توسط کارشناس، با دامنه‌ی `referredByAgentId` و یکتایی شماره در هر دو جدول. - فهرست نوع مشتری (معادل `admin/major/list_type`) از `CustomerKind`. - ویرایش سفارش مشتری و ثبت نهایی (معادل `PUT /order/child/{id}`). - **کوییرک‌ها:** پیش‌پر کردن از `sessionStorage.editCustomer`؛ برگشت به `customerChoice` یا `profile` بر اساس `query.from`؛ ۲۲ منطقه‌ی تهران؛ بنکدار → `sts = -1`؛ toast شماره‌ی تکراری با قرمز `#b10f0f`؛ جست‌وجوی نام در کلاینت؛ ویرایش وزن خام (بدون اجرت)؛ دو متن تأیید. ### Batch G — سفارش ساخت + آنالیتیکس · حجم بک‌اند: متوسط - **صفحه‌ها:** `makeOrder.vue`، `makeOrderStatus.vue`، کاشی سفارش ساخت در Dashboard، `router/customAnalytics.ts`. - **بک‌اند:** - ثبت و فهرست سفارش ساخت مشتری روی مدل موجود `PreOrder` (`colorName`، `sizeSpec`، `minaColor`، `stoneColor`، `description`، تصویر) + ستون افزایشی تعداد — فیلد `sum` مرجع در `PreOrder` نیست. - تأیید و ارسال توسط کارگاه (تنش ۳، #9). - آنالیتیکس روی `CustomerActivityLog` + ستون افزایشی `endedAt` (Q6). - **کوییرک‌ها:** نگاشت ۵ مرحله از رشته‌ی فارسی `sts_str_fa` — سرور باید **همان رشته‌ها** را بدهد؛ `confirmedOrders` فقط در حافظه است و با reload از دست می‌رود؛ کاشی برای type 5 مستقیم و برای بقیه مودال دوگزینه‌ای؛ اسپینری که به‌خاطر سایه‌انداختن متغیر `loading` هرگز دیده نمی‌شود. ### Phase نهایی — ممیزی و تحویل - همه‌ی ردیف‌های ماتریس: ✅، یا ➖ با شماره‌ی ISSUE، یا ⏸️. - گزارش مقایسه‌ی تصویری همه‌ی صفحه‌ها × پرسوناها × viewport ها. - سرشماری‌ها (§معیار تمام شد). - typecheck، lint، build، `docker compose`، `build:release`. - به‌روزرسانی `DELIVERY.md` و `PROGRESS.md`. --- ## روش کار هر Batch 1. خواندن **خط‌به‌خط** فایل‌های مرجع آن batch — نه فقط inventory. 2. ردیف‌های ماتریس، فیلد به فیلد. 3. شکاف‌های بک‌اند: endpoint افزایشی + تست (رفت‌وبرگشت، توکن متقابل، IDOR). 4. پورت view ها: template و style تا جای ممکن عین مرجع؛ script بازنویسی‌شده. 5. مقایسه‌ی تصویری و انیمیشن در برابر مرجع. 6. typecheck، lint، build. 7. گزارش کوتاه: چه پورت شد، چه عمداً نشد (با شماره)، چه باگ تازه‌ای در مرجع پیدا شد. تحت D18 بین batch ها توقف نمی‌شود؛ فقط برای موارد توقف D18 و gate فاز ۰. --- ## معیار «تمام شد» — اثبات خودکار اینکه چیزی از دست نرفت | سرشماری | چطور | قبول | |---|---|---| | route | استخراج path، name و meta از هر دو router | ۲۲ از ۲۲، یکسان | | رشته‌ها | استخراج هر literal فارسی از فایل‌های زنده‌ی مرجع (template و script) | همه در اپ کلاسیک موجود؛ هر غایب یا در §چیزی که پورت نمی‌شود هست یا خطاست | | asset | checksum | همه یکسان | | endpoint | هر ردیف نقشه | ✅ یا تصمیم ثبت‌شده | | تصویر | مقایسه‌ی هر صفحه × پرسونا × viewport | زیر آستانه‌ی توافق‌شده در فاز ۰ | | انیمیشن | frame های زمان ثابت | منطبق | | ماتریس | — | بدون ردیف باز | | کیفیت | typecheck strict، lint، build، compose | سبز | --- ## چیزی که پورت نمی‌شود (و چرا) هر مورد «کد مرده» قبل از کنار گذاشتن با جست‌وجوی importer تأیید می‌شود. | مورد | چرا | شاهد | |---|---|---| | `src/views/login.vue` | کل فایل کامنت است | `login.vue:1,201` | | `user/src/components/report.vue` | صفحه‌ی دیباگ، سوار نشده | `_inv-ref-pwa.md` §6.1 | | `NotificationList.vue` و سه endpoint اعلان | در مرجع سوار نیست | `home/index.vue:5` | | FCM (`Mixins/fireBase.js`) و `firebase-messaging-sw.js` | در مرجع غیرفعال؛ کلید API هاردکد | `App.vue:105` | | پرکردن خودکار OTP (Web OTP API) | غیرفعال | `App.vue:100` | | `POST /cities/list` | UI آبشاری کامنت است | `Register.vue:58-64` | | `cax`، `noneAuthUrl`، `canNavigate`، `getHomeRouteForLoggedInUser`، `getCustomerListType`، `loadingStore`، `filters sample.js`، `Date.vue`، `UploadPic.vue`، `updateProfile` بی‌مصرف در `makeOrder.vue`، `logOut` دوم در `user/index.vue` | کد مرده | `_inv-ref-pwa.md` §2.2، §5.1، §8.6، §9 | | دو fetch تکراری در `singleProduct` | یک fetch؛ نتیجه‌ی دیده‌شده یکسان | #20 | | `wage_weight` و `weight` ثابت در payload سفارش | سرور نادیده می‌گیرد | DEAD-06 | | حلقه‌ی `setInterval(reload)` | یک reload | #10 | | کلید analytics به شکل `undefined` | کلید از نام route | #16 | | PWA در dev mode | production | #17 | --- ## ریسک‌ها | # | ریسک | اثر | کاهش | |---|---|---|---| | R1 | مرجع روی Node فعلی بالا نیاید (Vite 3، plugin-vue 2، وابستگی `webpack 5.0.0`) | بدون مقایسه‌ی تصویری، «دقیقاً» فقط با خواندن کد ادعا می‌شود | Node قدیمی‌تر فقط در همان کپی؛ اگر نشد، اعلام صریح سطح اطمینان پایین‌تر | | R2 | تفاوت داده بین دو API | adapter وسوسه می‌شود چیزی جعل کند | قاعده‌ی صداقت؛ تست adapter با fixture | | R3 | تقویم جلالی | ظاهر متفاوت | spike فاز ۰؛ fallback به fork | | R4 | حجم بک‌اند | بیشتر کار در `apps/api` است، نه در UI | batch بندی بر اساس وابستگی | | R5 | رندر فونت بین ماشین‌ها | تفاوت کاذب در مقایسه‌ی تصویری | هر دو اپ روی یک ماشین و یک مرورگر | | R6 | `keep-alive` | state کهنه بعد از برگشت — که رفتار مرجع است | حفظ، با تست سناریوی برگشت | | R7 | v1/v2 عقب می‌افتند (`CLAUDE.md` §2) | ناسازگاری منطقی بین اپ‌ها | تنش ۱ | | R8 | شناسه‌های عددی دسته‌ها | ترتیب اشتباه دسته‌ها در خانه | Q5 | --- # وضعیت اجرا هنوز شروع نشده. **قدم بعدی:** پاسخ شما به تنش‌های ۱ تا ۵ و Q1 تا Q9؛ بعد Phase 0.