سند برنامهریزی. **قبل از تأیید شما هیچ کدی نوشته نمیشود.**
> **مرجع پینشده:** `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` دست نمیخورند. حتی بازساختاردهی (مثلاً بیرون کشیدن کد مشترک) تأیید جدا میخواهد.
**ارقام فارسی از فونت میآیند، نه از تبدیل.** مرجع هیچ تبدیل رقمی ندارد (§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) | تکرار |
- ثبت سند، سفارش ساخت، مدیریت مشتری توسط کارشناس، ویرایش سفارش مشتری، آنالیتیکس و فاکتور اصلاً وجود ندارند.
ساختن اینها در `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: <customerId>`.
- سرور در **هر درخواست** چک میکند `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` بیرون از `<Form>` است و `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 مرورگر یا گوشی **در کل اپ** از کار میافتد | محدود به خود صفحهی محصول — نیت ظاهری کد همین است، نشتش به کل اپ خرابی است |
### تنش ۵ — سرور هدف از مرجع سختگیرتر است
سرور مرجع تقریباً هر چیزی را قبول میکرد. سرور هدف بیشتر رد میکند: قاعدهی موجودی و
ولی 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 |
| 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 | **انتقال بین صفحهها** | **ندارد.**`<transition>` در `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` |
- **۲۲ 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 میماند (مثلاً نتایج جستوجو بعد از برگشت حفظ میشوند). این بخشی از حس ناوبری مرجع است و حفظ میشود.
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.
- معادل `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 فیلتر» / «اعمال فیلتر»؛ بازه فقط وقتی «فعال» حساب میشود که هر دو سرش عوض شده باشد؛ بنکدار اسلایدر اجرت را نمیبیند.
- **بکاند:** تابلوی قیمت (Q4)؛ `grams_sell` برای کارشناس.
- **کوییرکها:** «بدهکار گرمی» / «بستانکار گرمی» با قدر مطلق؛ «فروش گرمی» فقط برای کارشناس؛ کاشیها برای مهمان → دیالوگ ورود؛ دیالوگ «ّکاربر فعال شود؟» با همان تشدید سرگردان؛ دیالوگ خروج؛ متنهای toast قیمت؛ سرتیتر تاریخ و ساعت قیمت که همیشه خالی است (#7 — کوییرک بصری).
- **تصمیمها:** #5، #6، #8، #19؛ تنش ۲ برای دکمهی «انتخاب مشتری» / «خروج از حساب مشتری».
### Batch D — سفارشها، علاقهمندیها، پروفایل، اسناد، گزارش · حجم بکاند: زیاد
- ویرایش پروفایل: **فقط فیلدهای فرم** (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).
- **کوییرکها:** کلید `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، یا ⏸️.
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 | زیر آستانهی توافقشده در فاز ۰ |
| `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.