md app
This commit is contained in:
@@ -0,0 +1,357 @@
|
|||||||
|
# پلن رفتن به تولید — ارتقای پشته، جداسازی انتشار، جابهجایی از henza
|
||||||
|
|
||||||
|
سند برنامهریزی، نوشتهشده در ۲۰۲۶-۱۰-۰۴. **هیچ قدمی هنوز اجرا نشده.**
|
||||||
|
|
||||||
|
> این سند طوری نوشته شده که **روی سیستم دیگری، بدون گفتگوی قبلی، قابل ادامه باشد.** از §۰ شروع کنید.
|
||||||
|
>
|
||||||
|
> نسخهها و تاریخهای این سند مال روز نوشتناند. قبل از اجرای هر قدم، با
|
||||||
|
> `node scripts/check-majors.mjs` دوباره بسنجید.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خلاصه
|
||||||
|
|
||||||
|
- **هدف:** سیستم زندهی henza که الان زیر بار است، روی پشتهی جدید برود — **با همان ظاهر برای کاربر**، سریعتر و امنتر.
|
||||||
|
- **تصمیم مخزن:** پروژهی جدید ساخته نمیشود. همین مونوریپو «نسخهی بعدی تولید» است و امنیت تولید با **جداسازی انتشار** تأمین میشود (§۱).
|
||||||
|
- **سرعت از بکاند میآید، نه از بازنویسی ظاهر.** اپ کاربر قدیم همین حالا Vue 3 است (§۲).
|
||||||
|
- **بزرگترین ریسک داده است، نه کد.** ابزار انتقال داده از دیتابیس henza به Postgres هنوز وجود ندارد (§۷).
|
||||||
|
|
||||||
|
### ترتیب اجرا
|
||||||
|
|
||||||
|
| # | قدم | بخش | وابسته به |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | راهاندازی روی هر سیستم + commit و push اسناد | §۰ | — |
|
||||||
|
| 2 | اندازهگیری مبنا روی سیستم زنده | §۳ | — |
|
||||||
|
| 3 | ارتقای پشته | §۵ | — |
|
||||||
|
| 4 | جداسازی انتشار | §۶ | — |
|
||||||
|
| 5 | پورت اپ کلاسیک | `docs/PWA-PORT-PLAN.md` | تصمیمهای مالک در آن سند |
|
||||||
|
| 6 | ابزار مهاجرت داده و تطبیق | §۷ | — (موازی با ۵ قابل انجام است) |
|
||||||
|
| 7 | اجرای موازی روی staging | §۸ | ۵ و ۶ |
|
||||||
|
| 8 | بستن مسدودکنندهها | §۹ | — |
|
||||||
|
| 9 | روز جابهجایی | §۱۰ | همه |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۰ — ادامه روی سیستم دیگر
|
||||||
|
|
||||||
|
### چه چیزی با git منتقل میشود و چه چیزی نه
|
||||||
|
|
||||||
|
| چیز | در git مونوریپو؟ | روی سیستم جدید |
|
||||||
|
|---|---|---|
|
||||||
|
| کد مونوریپو و `docs/` | بله، **بعد از commit و push** | clone از `http://get.feep.ir/s.esmaeili/gold-bank.git` |
|
||||||
|
| همین سند، `docs/PWA-PORT-PLAN.md` و `scripts/check-majors.mjs` | **امروز نه** — هنوز commit نشدهاند | قبل از ترک این سیستم، commit و push |
|
||||||
|
| `henza_pwa`، `henza_server`، `henza_admin` | **نه** — هر کدام مخزن git جدای خودش است و مونوریپو آنها را track نمیکند | جدا clone و روی commit پینشده checkout (جدول بعد) |
|
||||||
|
| `.env` | نه — ignore شده، و باید همینطور بماند | از `.env.example` بسازید؛ secret ها را از جای امن بیاورید |
|
||||||
|
| `CLAUDE.md` | نه — در ریشهی workspace قبلی (`E:\gold bank`) بوده و اسناد parity به §2، §3، §4 و §6 آن ارجاع میدهند | اگر نسخهاش را دارید به مخزن اضافه کنید؛ بازسازی قواعدش در `docs/PWA-PORT-PLAN.md` آمده |
|
||||||
|
| skill `reference-parity-audit` | نه — روی ماشین قبلی | اگر دارید منتقل کنید؛ وگرنه D12 و همین اسناد روش را میگویند |
|
||||||
|
| حافظهی Claude | نه — محلی است | همین اسناد جایش را میگیرند |
|
||||||
|
|
||||||
|
### مخزنهای مرجع — commit های پینشده
|
||||||
|
|
||||||
|
| مخزن | remote | commit | نکته |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `henza_pwa` | `http://get.feep.ir/henzagold/henza_pwa.git` | `b171fff` (2026-09-02) | مرجع پورت کلاسیک |
|
||||||
|
| `henza_server` | `http://get.feep.ir/henzagold/henza_server.git` | `37c7d7a` (2026-09-17) | روی سیستم فعلی سه فایل حذفشدهی commitنشده دارد (`resources/js/app.js`، `resources/js/bootstrap.js`، `webpack.mix.js`). اینها اسکلت Laravel Mixاند و به API ربطی ندارند؛ clone تازه آنها را دارد و در parity فرقی ایجاد نمیکند |
|
||||||
|
| `henza_admin` | `http://get.feep.ir/henzagold/henza_admin.git` | `1b67448` (2026-08-11) | — |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone http://get.feep.ir/s.esmaeili/gold-bank.git
|
||||||
|
cd gold-bank
|
||||||
|
git checkout release/delivery # یا هر شاخهای که این اسناد رویش commit شدهاند
|
||||||
|
git clone http://get.feep.ir/henzagold/henza_pwa.git && git -C henza_pwa checkout b171fff
|
||||||
|
git clone http://get.feep.ir/henzagold/henza_server.git && git -C henza_server checkout 37c7d7a
|
||||||
|
git clone http://get.feep.ir/henzagold/henza_admin.git && git -C henza_admin checkout 1b67448
|
||||||
|
cp .env.example .env # بعد secret ها را پر کنید
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### دو تله
|
||||||
|
|
||||||
|
1. **پوشههای `henza_*` در `.gitignore` نیستند.** یک `git add .` یا `git add -A` آنها را بهصورت «embedded repository» اضافه میکند، و clone روی سیستم دیگر بهجایشان پوشهی خالی میگیرد. یا این سه نام به `.gitignore` اضافه شوند، یا همیشه فایلها با نام add شوند.
|
||||||
|
2. **remote ها `http://` هستند، نه `https://`.** نام کاربری و رمز git بدون رمزنگاری منتقل میشوند.
|
||||||
|
|
||||||
|
### ابزار روی سیستم جدید
|
||||||
|
|
||||||
|
- **Node 24.** سیستم فعلی 24.21 دارد؛ `engines` در `package.json` میگوید `>=20`؛ ایمیج Docker API روی 20 است (§۵.۱).
|
||||||
|
- **pnpm 9** (`packageManager: pnpm@9.0.0`).
|
||||||
|
- **build روی خود ماشین انجام میشود، نه داخل Docker** (DEPLOY-03). Dockerfile ها فقط خروجی ساختهشده (`release/` و `dist/`) را کپی میکنند، پس Node و سیستمعامل ماشین build روی خروجی اثر دارند. قبل از `docker compose build` همیشه `pnpm build:release` اجرا شود.
|
||||||
|
- **ترتیب خواندن:** همین سند ← `docs/parity/PROGRESS.md` ← `docs/parity/DECISIONS.md` ← `docs/PWA-PORT-PLAN.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۱ — تصمیم مخزن: پروژهی جدید نه
|
||||||
|
|
||||||
|
**وضعیت:** پیشنهاد (۲۰۲۶-۱۰-۰۴)؛ مالک خواست نوشته شود.
|
||||||
|
|
||||||
|
1. **اپها و API با هم تکامل پیدا میکنند.** اپ کلاسیک برای ۱۸ endpoint تازه به `apps/api` وابسته است، و ادمین و اپها یک دیتابیس و یک schema دارند. جدا کردن اپ و API در دو مخزن یعنی هر تغییر دو PR، و ناهمخوانی نسخهی کلاینت و سرور.
|
||||||
|
2. **امنیت تولید از جداسازی انتشار میآید، نه جداسازی مخزن.** هر اپ همین حالا Dockerfile و سرویس جدا دارد. فقط `build:release` همه را با هم میسازد، پس خرابی یک اپ آزمایشی انتشار تولید را هم میخواباند. راهحل در §۶.
|
||||||
|
3. **تاریخچه و قواعد اینجاست.** تاریخچهی git و کل `docs/parity` (تصمیمها، PROTECTED، ISSUES) در همین مخزن است؛ مخزن تازه یا گمشان میکند یا دو نسخه میسازد.
|
||||||
|
|
||||||
|
**چه وقت پروژهی جدید درست است:** اگر `user-app` و `user-app-v2` قرار است محصول، برند یا تیم دیگری شوند. اگر قرار است هرگز منتشر نشوند، بایگانیشان کنید (یک tag، و خروج از انتشار) — باز هم بدون مخزن جدید. حذفشان طبق D18 و `PROTECTED.md` تأیید صریح مالک میخواهد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۲ — واقعیت دربارهی «سرعت»
|
||||||
|
|
||||||
|
| بخش | در تولید امروز | وضعیت | جایگزین در مونوریپو |
|
||||||
|
|---|---|---|---|
|
||||||
|
| سرور | Laravel 8 + PHP `^7.3` (`henza_server/composer.json`) | پشتیبانی امنیتی Laravel 8 در ژانویهی ۲۰۲۳ و PHP 7.x در ۲۰۲۲ تمام شده | `apps/api` — NestJS + Prisma + Postgres |
|
||||||
|
| ادمین | Vue 2.6 + Vuex 3 + Vuesax + Tailwind 1 + vue-cli 3 (`henza_admin/package.json`) | Vue 2 پایان ۲۰۲۳ تمام شد | `apps/admin-panel` — Vue 3 |
|
||||||
|
| اپ کاربر | Vue 3 + Vite + Tailwind 3 — lockfile آن Vue 3.5.40 دارد و هر ۲۳ route آن lazy است | **پشته بهروز است** | `apps/user-app-classic` (`docs/PWA-PORT-PLAN.md`) |
|
||||||
|
|
||||||
|
پس بردن اپ کاربر روی «Vue 3، Tailwind، Pinia» بهخودیخود سریعش نمیکند. اهرمهای واقعی سرعت در آن:
|
||||||
|
|
||||||
|
1. **کل CSS ant-design** با `import 'ant-design-vue/dist/antd.less'` (`henza_pwa/src/main.ts:7`) → فقط استایل کامپوننتهای استفادهشده.
|
||||||
|
2. **کل CSS vant، دو بار:** `vant/lib/index.css` هم در `main.ts:10` و هم در `main.ts:11`.
|
||||||
|
3. **درخواستهای تکراری:** `/price` از دو مسیر جدا (`store/store.ts:114` و `Dashboard.vue:634`)؛ `/front` دو بار (`home/index.vue:184` و `Mixins/getProducts.ts:46`).
|
||||||
|
4. **بدون صفحهبندی:** `/get-orders` و `/get-my-findoc` کل داده را برمیگردانند.
|
||||||
|
5. **گزارش حساب** از یک سیستم حسابداری بیرونی proxy میشد (D24)؛ در هدف از دفتر داخلی خوانده میشود.
|
||||||
|
|
||||||
|
همهی اینها زیر کاپوتاند و ظاهر را عوض نمیکنند، پس با قرارداد «دقیقاً مثل مرجع» در `docs/PWA-PORT-PLAN.md` سازگارند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۳ — اندازهگیری مبنا (قدم ۲)
|
||||||
|
|
||||||
|
بدون عدد «قبل»، معلوم نیست کندی کجاست و بعداً هم نمیشود نشان داد سریعتر شده. **روی تولید آزمون بار (load test) اجرا نشود.**
|
||||||
|
|
||||||
|
| لایه | ابزار | چه چیزی ثبت شود |
|
||||||
|
|---|---|---|
|
||||||
|
| اپ — مهمان | `npx lighthouse https://app.henzagold.com/home --only-categories=performance --output=json --output-path=baseline-home.json` (پیشفرض Lighthouse شبیهسازی موبایل است)؛ همین برای `/search` و یک `/product/<id>` | LCP، TBT، حجم JS و CSS منتقلشده، تعداد درخواستها |
|
||||||
|
| اپ — واردشده | Chrome DevTools ← Performance و Network، روی یک گوشی متوسط واقعی | زمان تا نمایش میز کار؛ تعداد و زمان درخواستهای شروع |
|
||||||
|
| سرور | لاگ nginx با `$request_time` یا لاگ Laravel، در یک روز کاری | p50 و p95 برای `/front`، `/get-products`، `/me-data`، `/get-orders`، `/get-my-findoc`، `/price`، `/fin_tahesab/*`، `/set-order` |
|
||||||
|
| دیتابیس | slow query log (مثلاً بالای 0.5 ثانیه)، یک روز | ده کوئری کندتر |
|
||||||
|
|
||||||
|
| سنجه | مبنا (henza) | بعد از جابهجایی |
|
||||||
|
|---|---|---|
|
||||||
|
| LCP خانه (موبایل) | | |
|
||||||
|
| حجم JS + CSS خانه | | |
|
||||||
|
| تعداد درخواست تا میز کار | | |
|
||||||
|
| p95 `/front` | | |
|
||||||
|
| p95 `/get-products` | | |
|
||||||
|
| p95 `/set-order` | | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۴ — استاندارد نسخهها
|
||||||
|
|
||||||
|
«دو نسخه عقبتر» بهعنوان قاعدهی ثابت جواب نمیدهد: دو نسخه قبل از vue-router فعلی، router مخصوص Vue 2 است که مرده؛ و دو نسخه قبل از NestJS فعلی، همان نسخهای است که الان دارید و از ژانویه patch نگرفته.
|
||||||
|
|
||||||
|
**قاعده:** جدیدترین major که حدود ۶ ماه از انتشارش گذشته، هنوز patch میگیرد و وابستگیها پشتیبانیاش میکنند. **دو استثنا:**
|
||||||
|
|
||||||
|
- **ابزارهای زمان build** (TypeScript، vue-tsc، Vite) روی سرور اجرا نمیشوند؛ patch نگرفتنشان ریسک امنیتی زمان اجرا ندارد.
|
||||||
|
- **کتابخانههایی که ظاهر را میسازند** مستقل از سنشان ثابت میمانند (جدول دوم).
|
||||||
|
|
||||||
|
دادهی npm در ۲۰۲۶-۱۰-۰۴ — با `node scripts/check-majors.mjs` تازه کنید:
|
||||||
|
|
||||||
|
| بسته | جدیدترین | پیشنهاد | الان در مونوریپو | خط فعلی آخرین بار کی patch شد | نوع |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Node | 26 (هنوز LTS نیست) | **24 LTS** | Docker API: **20** | Node 20 از ۲۰۲۶-۰۴-۳۰ پشتیبانی ندارد | زمان اجرا |
|
||||||
|
| NestJS | 12.1.2 (یکماهه) | **11.x** — فعال | `^10.4.0` | 10.4.22 در ۲۰۲۶-۰۱-۱۰ | زمان اجرا |
|
||||||
|
| Prisma | 8 (RC) / 7.10.0 | **6.19.x** حالا؛ 7 بعد از تولید | `^5.19.0` | 5.22.0 در ۲۰۲۴-۱۱-۰۵ | زمان اجرا |
|
||||||
|
| ایمیج nginx | `latest` | **tag ثابت** | `latest` | — | زمان اجرا |
|
||||||
|
| Vue | 3.5.43 | **3.5.x** | `^3.5.0` | فعال | کلاینت |
|
||||||
|
| vue-router | 5.3.1 | **4.6.4** (آخرین 4.x) | `^4.4.0` | 4.6.4 در ۲۰۲۵-۱۲-۱۱ | کلاینت |
|
||||||
|
| Pinia | 4.0.3 (سهماهه) | **2.3.1** (آخرین 2.x) | `^2.2.0` | 2.3.1 در ۲۰۲۵-۰۱-۲۰ | کلاینت |
|
||||||
|
| Vite | 8.3.2 | **7.3.x** | `^5.4.0` | 5.4.21 در ۲۰۲۵-۱۰-۲۰ | build |
|
||||||
|
| @vitejs/plugin-vue | 6.0.9 | **6.0.x** | `^5.1.0` | 5.2.4 در ۲۰۲۵-۰۵-۰۹ | build |
|
||||||
|
| vite-plugin-pwa | 2.0.0 (یکروزه) | **1.3.x** | `^0.20.5` | 0.21.2 در ۲۰۲۵-۰۳-۲۱ | build |
|
||||||
|
| TypeScript | 7.0.2 (سهماهه، کامپایلر تازه) | **5.9.3** | `^5.5.0` | 5.9.3 در ۲۰۲۵-۰۹-۳۰ | build |
|
||||||
|
| vue-tsc | 3.3.12 | **3.x** | `^2.1.0` | 2.2.12 در ۲۰۲۵-۰۷-۰۲ | build |
|
||||||
|
|
||||||
|
**چرا Pinia 3 نه:** خط 3 هم ۳۳۳ روز است release نداشته، و برای پروژهی Vue 3 چیزی بیشتر از 2 نمیدهد. تنها خط فعال 4 است که هنوز سه ماه بیشتر عمر ندارد — بعداً.
|
||||||
|
**چرا Prisma 6 با اینکه ۱۸۶ روز release نداشته:** پلهی کوچکتر و کمریسکتر از 5 است (§۵.۷)؛ مقصد بلندمدت 7 است که بعد از تولید میآید.
|
||||||
|
|
||||||
|
سازگاری این ترکیب بررسی شد: `@vitejs/plugin-vue@6` با Vite 5 تا 8، `vite-plugin-pwa@1.3` با Vite 3 تا 8، `vue-tsc@3` با TypeScript ≥ 5.0، Vite 7 با Node ≥ 20.19، و NestJS 11 با Node ≥ 20 جور است.
|
||||||
|
|
||||||
|
### کتابخانههایی که بهخاطر ظاهر ثابت میمانند
|
||||||
|
|
||||||
|
| بسته | نسخه | چرا |
|
||||||
|
|---|---|---|
|
||||||
|
| `ant-design-vue` | 3.2.20 برای اپهای کاربر؛ 4.2.6 برای ادمین | v4 استایل را به CSS-in-JS برده. **هیچکدام از دو خط از نوامبر ۲۰۲۴ release نداشتهاند** — ریسک بلندمدتی که بهخاطر ظاهر پذیرفته میشود |
|
||||||
|
| `tailwindcss` | 3.0.18 | Tailwind 4 بازنویسی است و پیشفرضهایش ظاهر را عوض میکند |
|
||||||
|
| `vant` | 4.10.x (اپ کلاسیک دقیقاً 4.10.0) | — |
|
||||||
|
| `swiper` | 7.4.1 | — |
|
||||||
|
| `@vueuse/core` | 9.13.0 | مرجع با همین نسخه است؛ ارتقا رفتار `useTransition` و `useInfiniteScroll` را عوض میکند و تست جدا میخواهد |
|
||||||
|
|
||||||
|
**پین:** نسخهها بدون `^` و `~`، lockfile commitشده، و `pnpm install --frozen-lockfile` روی ماشین build.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۵ — قدمهای ارتقا (قدم ۳)
|
||||||
|
|
||||||
|
**قواعد همهی زیرقدمها:**
|
||||||
|
|
||||||
|
- هر زیرقدم یک commit یا PR جدا روی شاخهی خودش (`chore/upgrade-*`)، با تست کامل، **بدون کار فیچر قاطی**.
|
||||||
|
- `admin-panel`، `user-app` و `user-app-v2` در `PROTECTED.md` هستند: ارتقای ابزارشان **تأیید صریح مالک** و اسکرینشات قبل و بعد از صفحههای اصلی میخواهد. هیچ تغییر ظاهری یا رفتاری مجاز نیست.
|
||||||
|
- برای دیتابیس، روند D14 و D15 برقرار است: هرگز `migrate reset` و هرگز دست زدن به `_prisma_migrations`.
|
||||||
|
|
||||||
|
ترتیب از کمریسک به پرریسک است، بهجز Node که اول میآید چون Vite 7 به Node ≥ 20.19 نیاز دارد و Node 20 تمام شده.
|
||||||
|
|
||||||
|
### ۵.۱ — Node 24
|
||||||
|
|
||||||
|
- **تغییر:** `apps/api/Dockerfile` از `node:20-bookworm` به `node:24-bookworm`؛ `engines.node` به `">=24 <25"`؛ فایل `.nvmrc` با مقدار `24`.
|
||||||
|
- **ممکن است بشکند:** ریسک پایین — `apps/api/package.json` هیچ وابستگی native ندارد (بررسی شد).
|
||||||
|
- **سنجش:** `pnpm build:release`؛ بالا آمدن API در Docker؛ `apps/api/scripts/auth-smoke.sh` (۴۶ assertion)؛ `pnpm test` در api.
|
||||||
|
|
||||||
|
### ۵.۲ — پین ایمیج nginx
|
||||||
|
|
||||||
|
- **تغییر:** نسخهی واقعی `latest` فعلی را پیدا کنید (`docker run --rm docker.arvancloud.ir/nginx:latest nginx -v`) و در سه Dockerfile همان tag را بنویسید.
|
||||||
|
- **رفتار:** بیتغییر؛ فقط از این به بعد هر build همان nginx را میگیرد.
|
||||||
|
|
||||||
|
### ۵.۳ — پین دقیق و patch های داخل همان خط
|
||||||
|
|
||||||
|
- **تغییر:** حذف `^` و `~` از وابستگیهای همهی `package.json` ها؛ بالا بردن به آخرین patch در همان major: Vue 3.5.43، vue-router 4.6.4، Pinia 2.3.1، TypeScript 5.9.3؛ `pnpm install --frozen-lockfile` روی ماشین build و در CI.
|
||||||
|
- **سنجش:** روی یک clone تازه، `pnpm install --frozen-lockfile` بدون تغییر lockfile تمام شود؛ typecheck و build همهی اپها.
|
||||||
|
|
||||||
|
### ۵.۴ — vue-tsc 3
|
||||||
|
|
||||||
|
- **ممکن است بشکند:** vue-tsc 3 خطاهای تایپی تازه در template ها پیدا میکند. همه درست شوند، نه خاموش.
|
||||||
|
- **سنجش:** typecheck تمیز در `admin-panel`، `user-app` و `user-app-v2`.
|
||||||
|
|
||||||
|
### ۵.۵ — Vite 7، plugin-vue 6، vite-plugin-pwa 1.3
|
||||||
|
|
||||||
|
- **مهمترین تله:** طبق راهنمای مهاجرت Vite 7، پیشفرض `build.target` از `'modules'` به `'baseline-widely-available'` عوض شده — یعنی مرورگرهای جدیدتر (حدود Chrome 107 و Safari 16). **هیچکدام از `vite.config.ts` ها `build.target` ندارند** (بررسی شد)، پس گوشیهای قدیمیتر کاربران بیصدا از کار میافتند. ← `build.target` صریحاً روی مقدار قبلی، `['es2020', 'edge88', 'firefox78', 'chrome87', 'safari14']`، گذاشته شود، مگر مالک بر اساس آمار گوشیهای کاربران تصمیم دیگری بگیرد. قبل از اجرا با راهنمای رسمی تطبیق داده شود.
|
||||||
|
- `vite-plugin-pwa@1.3` به `workbox-build` و `workbox-window` ^7.4.1 نیاز دارد.
|
||||||
|
- **سنجش:** build هر سه اپ؛ سناریوی دود (ورود، کاتالوگ، سبد)؛ نصب PWA و جریان بهروزرسانی؛ اسکرینشات قبل و بعد.
|
||||||
|
|
||||||
|
### ۵.۶ — NestJS 11 (همهی بستههای `@nestjs/*` با هم)
|
||||||
|
|
||||||
|
- **ممکن است بشکند:** NestJS 11 روی Express 5 است. route با wildcard یا `forRoutes('*')` در کد نیست (بررسی شد) — رایجترین شکست صدق نمیکند. ولی parser پیشفرض query در Express 5 «simple» شده؛ هر endpoint که query تودرتو میخواند باید چک شود. با راهنمای مهاجرت رسمی تطبیق داده شود.
|
||||||
|
- **سنجش:** jest کامل؛ `auth-smoke.sh`؛ تستهای توکن متقابل (T1).
|
||||||
|
|
||||||
|
### ۵.۷ — Prisma 6.19
|
||||||
|
|
||||||
|
- **ممکن است بشکند:** مهمترین شکستهای Prisma 6 اینجا صدق نمیکنند (بررسی شد): در migration ها رابطهی many-to-many ضمنی نیست، و فیلد `Bytes` و `NotFoundError` استفاده نشدهاند.
|
||||||
|
- **سنجش:** بعد از ارتقا، `prisma migrate diff --from-schema-datasource --to-schema-datamodel --script` باید «empty migration» بدهد؛ `prisma migrate status`؛ jest؛ دود.
|
||||||
|
|
||||||
|
### ۵.۸ — بعداً، نه قبل از تولید
|
||||||
|
|
||||||
|
Prisma 7، Pinia 4، vue-router 5، Vite 8، TypeScript 6 و 7، NestJS 12 — هر کدام وقتی پخته شد، جدا و با تست. **Tailwind 4 و ant-design-vue 4 هرگز برای اپهای کاربر**، چون ظاهر عوض میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۶ — جداسازی انتشار (قدم ۴)
|
||||||
|
|
||||||
|
- **مجموعهی تولید:** `apps/api`، `apps/admin-panel`، و `apps/user-app-classic` وقتی آماده شد. **آزمایشی:** `apps/user-app`، `apps/user-app-v2`.
|
||||||
|
- **اسکریپت تازهی `build:prod`** فقط برای مجموعهی تولید. `build:release` فعلی دست نمیخورد، چون `DELIVERY.md` به آن تکیه دارد.
|
||||||
|
- **`docker-compose.yml`:** سرویسهای آزمایشی زیر `profiles: ["experimental"]` بروند، تا `docker compose up` فقط تولید را بالا بیاورد و آزمایشیها با `--profile experimental` بیایند. `DELIVERY.md` همزمان بهروز شود.
|
||||||
|
- **شاخهها:** `main` همیشه آمادهی تولید؛ کار روی شاخههای `chore/upgrade-*` و `feature/classic-*`؛ شاخهی انتشار از `main` بریده شود. امروز همهچیز روی `release/delivery` است.
|
||||||
|
- **DEPLOY-03 — build داخل Docker با multi-stage:** حالا که کار ممکن است بین ماشینها جابهجا شود، اولویتش بالاتر است. خروجی نباید به Node و سیستمعامل ماشین build وابسته باشد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۷ — مهاجرت داده و تطبیق (قدم ۶) — بزرگترین شکاف
|
||||||
|
|
||||||
|
**واقعیت:** ابزار انتقال داده از دیتابیس henza به Postgres نه در مخزن هست و نه در اسناد (جستوجوی ۲۰۲۶-۱۰-۰۴). موتور دیتابیس henza هم از مخزن معلوم نیست — `config/database.php` پیشفرض `'localhost'` دارد و `.env.example` تنظیم DB ندارد. احتمالاً MySQL است؛ از سرور تولید تأیید شود.
|
||||||
|
|
||||||
|
**ویژگیهای اجباری ابزار:**
|
||||||
|
|
||||||
|
- تکرارپذیر و idempotent؛ همیشه روی **کپی** دادهی تولید.
|
||||||
|
- برای هر موجودیت یک جدول نگاشت `legacyId ← id جدید` نگه دارد. لازم است برای:
|
||||||
|
- تطبیق؛
|
||||||
|
- ترتیب ۹ دستهی خانه که با id عددی هاردکد شده (Q5 در `docs/PWA-PORT-PLAN.md`)؛
|
||||||
|
- لینکهای قدیمی محصول (`/product/123`) که کاربران فرستادهاند — بدون نگاشت، به خانه redirect میشوند؛
|
||||||
|
- سبدهای باز `appCart` روی گوشیها.
|
||||||
|
- یک گزارش تطبیق بسازد (جدول دوم).
|
||||||
|
|
||||||
|
| henza | مقصد | نکته |
|
||||||
|
|---|---|---|
|
||||||
|
| مشتری (`customers`) | `Customer` | نام ← `firstName`/`lastName` (D0)؛ `type` ← `CustomerKind`؛ `percent` ← `makingChargeOffsetPercent` (D9)؛ `gram_level` ← `goldCreditLimitGrams`؛ `remain` ← `goldDebtGrams`؛ `rial_remain` ← `fiatDebtRial` **با بررسی واحد** (نقص #19 در `docs/PWA-PORT-PLAN.md`) |
|
||||||
|
| کارشناس فروش (`customers.type = 1`) | `AdminUser` با نقش `SALES_EXPERT` + `SalesExpertProfile` | D17 و D21 |
|
||||||
|
| مشتری تک (`SingleCustomer`/`login`) | `Customer` با `kind = RETAIL` | D13 — داده حفظ شود، حتی اگر خارج از کار است |
|
||||||
|
| کاربران ادمین (`users`) و `permission` | `AdminUser` + `UserType` + `Permission` | — |
|
||||||
|
| محصول، تنوع، تصویر، دسته، برند، کالکشن | مدلهای همنام | **فایلهای تصویر هم کپی شوند** (`UPLOAD_DIR`) |
|
||||||
|
| سفارشها — عمده و تک، دو جدول جدا در henza | `Order` + `OrderItem` + `OrderStatusEvent` | `sts` عددی ← `OrderStatus` |
|
||||||
|
| سفارش ساخت | `PreOrder` | — |
|
||||||
|
| اسناد مالی (`cash`) | `LedgerEntry` | فقط تأییدشدهها در موجودی حساب میشوند (D24) |
|
||||||
|
| کیف پول و cashback | مدلهای موجود | D13 — حفظ، بدون کار |
|
||||||
|
| علاقهمندی، تخفیف و استثنا، اعلان، شهر، تنظیمات | مدلهای موجود یا تازه | علاقهمندی جدول تازه میخواهد (`docs/PWA-PORT-PLAN.md`) |
|
||||||
|
|
||||||
|
| بررسی تطبیق | آستانه |
|
||||||
|
|---|---|
|
||||||
|
| تعداد مشتری، کارشناس و کاربر ادمین | دقیقاً برابر |
|
||||||
|
| بدهی طلای هر مشتری (`remain` ↔ `goldDebtGrams`) | **صفر اختلاف** |
|
||||||
|
| بدهی ریالی هر مشتری | **صفر اختلاف**، بعد از تبدیل واحد |
|
||||||
|
| سقف اعتبار و آفست اجرت هر مشتری | صفر اختلاف |
|
||||||
|
| تعداد سفارش به تفکیک وضعیت؛ جمع وزن سفارشهای باز | برابر |
|
||||||
|
| تعداد و جمع اسناد تأییدشده و تأییدنشده | برابر |
|
||||||
|
| تعداد محصول، تنوع و تصویر؛ فایل تصویر گمشده | برابر؛ صفر |
|
||||||
|
|
||||||
|
**شمارهی تکراری بین مشتری و کارشناس** قبل از مهاجرت فهرست شود. یکتایی شماره بین `Customer` و `AdminUser` امروز اجرا نمیشود — هر چهار بررسی تداخل فقط `adminUser` را کوئری میکنند — و در `ISSUES.md` هم ثبت نشده. قاعدهی مالک در D21: «کارشناس برنده است».
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۸ — اجرای موازی روی staging (قدم ۷)
|
||||||
|
|
||||||
|
- یک دامنهی staging؛ هر شب دادهی تولید با ابزار §۷ منتقل و تطبیق شود.
|
||||||
|
- **پیامک در staging خاموش، یا فقط به شمارهی تست.** دادهی واقعی یعنی شمارهی واقعی مشتریها.
|
||||||
|
- کاربران داخلی جریانهای واقعی را روی staging امتحان کنند.
|
||||||
|
- **معیار آمادگی:** مثلاً سه شب پیاپی تطبیق تمیز، و هیچ باگ مسدودکنندهی باز.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۹ — مسدودکنندههای قبل از تولید (قدم ۸)
|
||||||
|
|
||||||
|
| مورد | منبع |
|
||||||
|
|---|---|
|
||||||
|
| مقدار جدا برای `JWT_CUSTOMER_SECRET` (REL-03) | `DELIVERY.md` §A1 |
|
||||||
|
| حذف `OTP_MASTER_CODE` (REL-01) | §A2 |
|
||||||
|
| `JWT_SECRET` واقعی | §A3 |
|
||||||
|
| اعتبارنامهی SMS (REL-02) | §A4 |
|
||||||
|
| چرخاندن اعتبارنامههای نشتکرده: پنل SMS (SEC-12)، توکنهای PayPing و Tahesab و pepper کیف پول (SEC-06). **همزمان با سیستم قدیم**، چون همان حسابهای زندهاند؛ چرخاندن یکطرفه پیامک سیستم قدیم را قطع میکند | §B |
|
||||||
|
| MON-08 — سرور اجرت و تخفیف را از کلاینت نپذیرد. وضعیت فعلی چک شود | `docs/parity/ISSUES.md` |
|
||||||
|
| MON-05 — اجرای `canPlaceOrders` و `verificationStep` | `docs/parity/ISSUES.md` |
|
||||||
|
| یکتایی شماره بین `Customer` و `AdminUser` | §۷ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۱۰ — روز جابهجایی (قدم ۹)
|
||||||
|
|
||||||
|
### شرطهای go/no-go
|
||||||
|
|
||||||
|
- همهی موارد §۹ بستهاند.
|
||||||
|
- معیار آمادگی §۸ برقرار است.
|
||||||
|
- برگشت (rollback) یک بار تمرین شده.
|
||||||
|
- به کاربران اطلاع داده شده: زمان قطعی؛ یک بار ورود دوباره با OTP، چون توکنهای قدیمی باطل میشوند؛ خالی شدن سبدهای باز، مگر با نگاشت §۷ منتقل شوند.
|
||||||
|
|
||||||
|
### گامها
|
||||||
|
|
||||||
|
1. سیستم قدیم در حالت نگهداری؛ نوشتن قفل.
|
||||||
|
2. مهاجرت نهایی و گزارش تطبیق ← **تصمیم go/no-go.**
|
||||||
|
3. سوئیچ مسیرها روی **همان دامنهها**:
|
||||||
|
- service worker جدید در همان مسیر `/sw.js`، تا جای قبلی را بگیرد.
|
||||||
|
- نسخهی اپ کلاسیک بالاتر از `1.0.7` باشد، تا دیالوگ مرجع «نسخه جدید هنزا منتشر شد!» یک بار برای همه بیاید و اپ تازه شود (`henza_pwa/src/App.vue:56-74`).
|
||||||
|
4. اولین باز شدن بعد از سوئیچ ممکن است پوستهی کششدهی قدیمی را نشان دهد که به API قدیم (`henzagold.com/oapi/api/`) درخواست میفرستد. آن مسیر تا مدتی پاسخ تمیز (مثلاً 503) بدهد، نه timeout.
|
||||||
|
5. آزمون دود: ورود با OTP؛ موجودی میز کار برابر گزارش تطبیق؛ ثبت سفارش روی یک مشتری تست؛ ورود ادمین و دیدن همان سفارش؛ رسیدن پیامک.
|
||||||
|
6. باز کردن برای همه.
|
||||||
|
|
||||||
|
### برگشت
|
||||||
|
|
||||||
|
- سیستم قدیم چند روز (مثلاً ۱۴ روز) فقطخواندنی بماند؛ پشتیبان کامل از هر دو دیتابیس.
|
||||||
|
- **مهلت تصمیم برگشت از قبل تعیین شود** (مثلاً ۲ ساعت بعد از باز کردن). بعد از آن، سفارشها و اسناد تازه فقط در سیستم جدیدند و برگشت یعنی انتقال دستی آنها به دیتابیس قدیم.
|
||||||
|
- **برگشت سمت گوشیها فوری نیست:** service worker جدید روی گوشیها کش شده. برای برگرداندن، نسخهی قدیم باید با شمارهی نسخهی بالاتر دوباره منتشر شود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §۱۱ — بعد از جابهجایی
|
||||||
|
|
||||||
|
- مقایسه با مبنای §۳ و ثبت نتیجه در جدول همان بخش.
|
||||||
|
- پایش خطا و زمان پاسخ در هفتهی اول.
|
||||||
|
- بعد: ارتقاهای §۵.۸؛ وصل کردن `user-app` و `user-app-v2` به قابلیتهای تازهی بکاند (تنش ۱ در `docs/PWA-PORT-PLAN.md`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# وضعیت اجرا
|
||||||
|
|
||||||
|
| # | قدم | وضعیت |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | راهاندازی روی هر سیستم + commit و push اسناد | ⬜ |
|
||||||
|
| 2 | اندازهگیری مبنا | ⬜ |
|
||||||
|
| 3 | ارتقای پشته (۵.۱ تا ۵.۷) | ⬜ |
|
||||||
|
| 4 | جداسازی انتشار | ⬜ |
|
||||||
|
| 5 | پورت اپ کلاسیک | ⬜ منتظر تصمیمهای مالک در `docs/PWA-PORT-PLAN.md` |
|
||||||
|
| 6 | ابزار مهاجرت داده و تطبیق | ⬜ |
|
||||||
|
| 7 | اجرای موازی روی staging | ⬜ |
|
||||||
|
| 8 | بستن مسدودکنندهها | ⬜ |
|
||||||
|
| 9 | روز جابهجایی | ⬜ |
|
||||||
@@ -0,0 +1,538 @@
|
|||||||
|
# پلن پورت اپ کاربر کلاسیک — 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 |
|
||||||
|
| محل `<transition>` | ۷ زنده + ۲ کامنتشده (از جمله **انتقال بین صفحهها**) | 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: <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 مرورگر یا گوشی **در کل اپ** از کار میافتد | محدود به خود صفحهی محصول — نیت ظاهری کد همین است، نشتش به کل اپ خرابی است |
|
||||||
|
|
||||||
|
### تنش ۵ — سرور هدف از مرجع سختگیرتر است
|
||||||
|
|
||||||
|
سرور مرجع تقریباً هر چیزی را قبول میکرد. سرور هدف بیشتر رد میکند: قاعدهی موجودی و
|
||||||
|
پیشسفارش (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 | **انتقال بین صفحهها** | **ندارد.** `<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` |
|
||||||
|
| 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.
|
||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
> Read this file FIRST at the start of every session and after every `/compact`.
|
> Read this file FIRST at the start of every session and after every `/compact`.
|
||||||
> Rules live in `CLAUDE.md` (workspace root). Method lives in the `reference-parity-audit` skill.
|
> Rules live in `CLAUDE.md` (workspace root). Method lives in the `reference-parity-audit` skill.
|
||||||
|
> Neither is in this repository — see `docs/GO-LIVE-PLAN.md` §0.
|
||||||
|
|
||||||
|
## New workstreams (2026-10-04) — plans only, nothing executed
|
||||||
|
|
||||||
|
| Plan | What | Waiting on |
|
||||||
|
|---|---|---|
|
||||||
|
| `docs/GO-LIVE-PLAN.md` | Stack upgrade, release isolation, data migration and cutover from the live henza system. **§0 explains how to continue on another machine.** | Owner go-ahead per step |
|
||||||
|
| `docs/PWA-PORT-PLAN.md` | Third user app `apps/user-app-classic`: a pixel-exact port of `henza_pwa@b171fff` onto the new API. | Owner rulings on its tensions 1–5 and Q1–Q9 |
|
||||||
|
|
||||||
|
The "Current state" table below is the parity project's and was last updated 2026-09-22.
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
|
|||||||
Submodule
+1
Submodule henza_admin added at 1b67448143
Submodule
+1
Submodule henza_pwa added at b171ffffb5
Submodule
+1
Submodule henza_server added at 37c7d7a92f
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// For each npm package, prints its four newest major lines: first release,
|
||||||
|
// latest version and when that latest version was published. A line with no
|
||||||
|
// release for a long time has probably stopped receiving patches.
|
||||||
|
//
|
||||||
|
// Backs the version table in docs/GO-LIVE-PLAN.md §4. Re-run it before acting
|
||||||
|
// on that table — the numbers there are only true for the day they were taken.
|
||||||
|
//
|
||||||
|
// node scripts/check-majors.mjs # the packages the plan tracks
|
||||||
|
// node scripts/check-majors.mjs vite pinia # any packages
|
||||||
|
//
|
||||||
|
// Goes through the npm CLI rather than fetch() so the machine's registry or
|
||||||
|
// mirror settings (.npmrc) apply.
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
const TRACKED = [
|
||||||
|
'vue', 'vite', '@vitejs/plugin-vue', 'vite-plugin-pwa', 'vue-router', 'pinia',
|
||||||
|
'typescript', 'vue-tsc', 'tailwindcss', 'ant-design-vue', 'vant', '@vueuse/core',
|
||||||
|
'@nestjs/core', 'prisma',
|
||||||
|
];
|
||||||
|
// Package names only — they are interpolated into a shell command.
|
||||||
|
const PACKAGE_NAME = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;
|
||||||
|
const STALE_AFTER_DAYS = 180;
|
||||||
|
|
||||||
|
const pkgs = process.argv.length > 2 ? process.argv.slice(2) : TRACKED;
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
for (const pkg of pkgs) {
|
||||||
|
if (!PACKAGE_NAME.test(pkg)) {
|
||||||
|
console.log(`\n${pkg}: not a package name, skipped`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let published;
|
||||||
|
try {
|
||||||
|
published = JSON.parse(
|
||||||
|
execSync(`npm view ${pkg} time --json`, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
timeout: 60_000,
|
||||||
|
stdio: ['ignore', 'pipe', 'ignore'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
console.log(`\n${pkg}: npm view failed`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = new Map();
|
||||||
|
for (const [version, date] of Object.entries(published)) {
|
||||||
|
// Stable releases only; this also drops the `created` / `modified` keys.
|
||||||
|
if (!/^\d+\.\d+\.\d+$/.test(version)) continue;
|
||||||
|
const major = Number(version.split('.')[0]);
|
||||||
|
const line = lines.get(major) ?? { firstVersion: version, first: date, lastVersion: version, last: date };
|
||||||
|
if (date < line.first) Object.assign(line, { firstVersion: version, first: date });
|
||||||
|
if (date > line.last) Object.assign(line, { lastVersion: version, last: date });
|
||||||
|
lines.set(major, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n${pkg}`);
|
||||||
|
const newestFirst = [...lines].sort(([a], [b]) => b - a).slice(0, 4);
|
||||||
|
for (const [major, line] of newestFirst) {
|
||||||
|
const idleDays = Math.floor((now - Date.parse(line.last)) / 86_400_000);
|
||||||
|
const stale = idleDays > STALE_AFTER_DAYS ? ` <- no release for ${idleDays} days` : '';
|
||||||
|
console.log(
|
||||||
|
` v${String(major).padEnd(3)} first ${line.firstVersion.padEnd(9)} ${line.first.slice(0, 10)}` +
|
||||||
|
` latest ${line.lastVersion.padEnd(9)} ${line.last.slice(0, 10)}${stale}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user