Files
tehran_taj/docs/GO-LIVE-PLAN.md
2026-10-04 20:11:09 +03:30

358 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# پلن رفتن به تولید — ارتقای پشته، جداسازی انتشار، جابه‌جایی از 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 | روز جابه‌جایی | ⬜ |