docs: migration map — verified data facts, corrections, import tool
DATA-MIGRATION-MAP.md: - §10.7 is rewritten from the full dump loaded into MySQL. The legacy debt formula is confirmed (1,590/1,591 balances). The earlier claims that new_city is a cities5 id, that birthdays are clean and that types 3/4/5 are unknown were wrong and are corrected in a table. Favourites come from major_favorites, not fav. The role-number clash (legacy 86 «حسابدار» vs target 86 «کارشناس فروش») is recorded. - §10.8 adds the tool's usage, safety rules, test result, and what it deliberately does not migrate. - §11 and §5.5 are brought up to date. The rejected count is 7, not 14. migration-precheck-3.sql: two bugs that would have failed on the server are fixed: the reserved word `both` and a collation clash between cities.title and customers.new_city. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
be5ca829d2
commit
92831ed33e
+238
-19
@@ -3,9 +3,10 @@
|
||||
> سند **نگاشت**، نه پلن اجرا. هیچ کدی بر اساس این سند نوشته نشده.
|
||||
> پیشنیاز ابزار مهاجرت دادهی [`GO-LIVE-PLAN.md`](./GO-LIVE-PLAN.md) §۷.
|
||||
|
||||
**منبع:** `oapi-struncture.sql` — دامپ Navicat، تاریخ ۰۵/۱۰/۲۰۲۶
|
||||
**منبع اول:** `oapi-struncture.sql` — دامپ Navicat فقط ساختار، تاریخ ۰۵/۱۰/۲۰۲۶
|
||||
**منبع دوم:** `oapi.sql` — دامپ کامل با داده، تاریخ ۰۴/۱۰/۲۰۲۶، **۲۷۴٬۷۷۳ سطر**
|
||||
**مقصد:** `apps/api/prisma/schema.prisma` — ۴۳ مدل، ۴۷۷ فیلد
|
||||
**تاریخ نگارش:** ۱۴ مهر ۱۴۰۵ (۲۰۲۶-۱۰-۰۶)
|
||||
**تاریخ نگارش:** ۱۴ مهر ۱۴۰۵ (۲۰۲۶-۱۰-۰۶) · بهروز ۱۵ مهر ۱۴۰۵ (دیتای واقعی)
|
||||
|
||||
---
|
||||
|
||||
@@ -19,7 +20,22 @@
|
||||
| جداول | **۸۰** |
|
||||
| ستونها | **۱٬۰۸۷** |
|
||||
| Engine | `InnoDB` (هر ۸۰ جدول) |
|
||||
| محتوا | **فقط ساختار — صفر `INSERT`** |
|
||||
| محتوا | **دامپ کامل — `oapi.sql` دارد `INSERT`** |
|
||||
|
||||
### ۰.۱ شمارش ردیفهای کلیدی (از `oapi.sql`)
|
||||
|
||||
| جدول | ردیف | یادداشت |
|
||||
|---|---|---|
|
||||
| `customers` | **۱٬۶۱۲** | `AUTO_INCREMENT=1720` — ۱۰۸ شکاف |
|
||||
| `cashes` | **۶۴۸** | اسناد دفتر مالی |
|
||||
| `products_productvarient` | **۷٬۴۴۳** | شامل حذفشدهها |
|
||||
| `product` | **۳٬۲۷۲** | محصولات |
|
||||
| `image` | **۱۷٬۹۵۵** | فایلهای فیزیکی: ۱۷٬۸۲۶ (در `storage/`) |
|
||||
| `products_productimage` | **۵٬۴۱۷** | رابطه product→image |
|
||||
| `users` | **۲۲** | ادمین/کارشناسان |
|
||||
| `orders_orders` | **۵۲۰** | سفارشها (به تأخیر افتاد §۷.۲) |
|
||||
| `wallets` | **۸۰** | کیفپول خردهفروشی |
|
||||
| `fav` | **۲۵۱** | مورد علاقه (migration شد §۱۰) |
|
||||
|
||||
این فایل شکافی را پر کرد که قبلاً باز بود: جداول دوران Django
|
||||
(`product`، `products_productvarient`، `category_collection` و …) هیچ migration
|
||||
@@ -462,17 +478,20 @@ model Customer {
|
||||
| | |
|
||||
|---|---|
|
||||
| ستون `verificationRejectionReason` | ✅ **ساخته میشود** (خواست مالک · برای آینده) |
|
||||
| مقدارش در مهاجرت ۱۴ ردیف | **`NULL`** — چون مبدأ دادهای ندارد |
|
||||
| مقدارش در مهاجرت **۷ ردیف** | متن ثابت مالک (پایین) — علت واقعی در مبدأ نیست |
|
||||
| `verificationRejectedAt` | **`NULL`** — legacy زمان رد را هم ثبت نمیکرد |
|
||||
|
||||
⚠️ **این یک شکاف دادهی ذاتی است، نه نقص ابزار مهاجرت.** علت رد آن ۱۴
|
||||
⚠️ **این یک شکاف دادهی ذاتی است، نه نقص ابزار مهاجرت.** علت رد آن ۷
|
||||
مشتری در سیستم قدیم هرگز ذخیره نشده بود و **قابل بازیابی نیست**. ستون
|
||||
ساخته میشود تا از این پس پر شود؛ برای این ۱۴ نفر خالی میماند.
|
||||
ساخته میشود تا از این پس پر شود.
|
||||
|
||||
### ✅ تصمیم مالک ۲۰۲۶-۱۰-۰۷ — یادداشت خودکار درج شود
|
||||
|
||||
> «فیلد علت برای این ۱۴ ردیف نباید کامپلت NULL رها شود.»
|
||||
|
||||
(عدد درست **۷** است: ۲ رد در گام دو، ۳ در گام یک، ۲ در هر دو. عدد ۱۴ را
|
||||
من اشتباه جمع کرده بودم و مالک بر اساس آن نوشت؛ تصمیم تغییری نمیکند.)
|
||||
|
||||
مقدار دقیق که ابزار درج میکند:
|
||||
|
||||
```
|
||||
@@ -941,10 +960,16 @@ legacy شش ستون برای نسخههای تغییراندازهیاف
|
||||
| `request_fashions` | `name`, `mobile` | درخواست «مد» — فرم تماس | ❌ ندارد |
|
||||
| `request_logs` | `customer_id`, `ip`, `device`, `data`, `user_agent`, `route` | لاگ درخواست | تقریباً `CustomerActivityLog` |
|
||||
|
||||
### ✅ تصمیم مالک ۲۰۲۶-۱۰-۰۶ — `fav` لازم است
|
||||
### ✅ تصمیم مالک ۲۰۲۶-۱۰-۰۶ — علاقهمندیها لازماند
|
||||
|
||||
> «بله» — علاقهمندیها مهماند.
|
||||
|
||||
🔴 **تصحیح ۲۰۲۶-۱۰-۰۷: منبع درست `fav` نیست.** `fav` مال فروشگاه خرده است
|
||||
(`Customer\FavController`، guard `api-singleCustomer`). اپ عمده
|
||||
**`major_favorites`** (۳٬۳۰۵ ردیف) را میخواند — §۱۰.۷. متن زیر دربارهی
|
||||
`fav` نوشته شده؛ تصمیم مالک و مدل `CustomerFavorite` همان میمانند، فقط منبع
|
||||
عوض شد.
|
||||
|
||||
**یک migration افزایشی لازم است.** چون additive است، با `PROTECTED.md` §G
|
||||
تضادی ندارد و قابلیت موجودی را لمس نمیکند.
|
||||
|
||||
@@ -1436,7 +1461,7 @@ migration در `13:42:36` در `_goldbank_migrations` ثبت شد — پیش ا
|
||||
|
||||
**۱. رد در سیستم جدید `REJECTED` نمینویسد.**
|
||||
`verify()` در `admin-customers.service.ts:955-969` هنگام رد:
|
||||
گام ۱ → `UNVERIFIED`، گام ۲ → `STEP_ONE_SUBMITTED`. یعنی ۱۴ مشتری مهاجر
|
||||
گام ۱ → `UNVERIFIED`، گام ۲ → `STEP_ONE_SUBMITTED`. یعنی ۷ مشتری مهاجر
|
||||
`REJECTED` میشوند، ولی **هر ردِ تازه در پنل همان کاری را میکند که مالک
|
||||
برای مهاجرت «کاملاً مردود» خواند** (برگشت به صف). و
|
||||
`verificationRejectionReason` که مالک «برای نیازمندیهای آتی» خواست، از
|
||||
@@ -1471,13 +1496,202 @@ legacy دکمهی تکمیل پروفایل را فقط وقتی `sts_verify_
|
||||
|
||||
| # | سؤال | چرا مسدودکننده است |
|
||||
|---|---|---|
|
||||
| ۱ | **مبلغ سند مالی در کدام ستون است؟** | `remain`/`rial_remain` همه `NULL`اند. بدون جواب، `LedgerEntry.amount` خالی میماند و **کل دفتر مالی بیمبلغ منتقل میشود** |
|
||||
| ۱ | ~~مبلغ سند مالی در کدام ستون است؟~~ | ✅ **حل — §۱۰.۷** |
|
||||
| ۲ | آیا «مسئول فروش برنده» برای ۲۷ گروه تکراری کافی است؟ | اگر گروهی کارشناس نداشته باشد، قاعده بیاثر است |
|
||||
| ۳ | سه ستون تصویر → چهار نقش `ProductImageRole` | نگاشت غلط = تصویر غلط روی محصول |
|
||||
| ۴ | تأیید §۵.۷ — `reagent_id` به `customers` اشاره میکند؟ | اگر غلط باشد، رابطهی کارشناس معرف از بین میرود |
|
||||
|
||||
---
|
||||
|
||||
## ۱۰.۷ — یافتههای دامپ کامل (`oapi.sql` — ۲۰۲۶-۱۰-۰۷)
|
||||
|
||||
`oapi.sql` (دامپ Navicat با داده، تاریخ ۰۴/۱۰/۲۰۲۶، ۶۳ مگابایت) در یک MySQL
|
||||
محلی روی Docker بارگذاری و هر ادعا با کوئری روی همان داده بررسی شد. این
|
||||
بخش ابتدا توسط نشست موازی `gold-bank-79` نوشته شد. **چند ادعای آن نسخه
|
||||
غلط بود و اینجا تصحیح شده است** (جدول پایین).
|
||||
|
||||
⚠️ دامپ **دو روز قدیمیتر** از اجرای پیشبررسی روی سرور زنده است (مثلاً
|
||||
۷٬۴۴۳ تنوع اینجا در برابر ۷٬۴۷۴ زنده). برای ساخت و آزمون کافی است.
|
||||
**برای مهاجرت واقعی دامپ تازه گرفته شود.**
|
||||
|
||||
`oapi.sql` دادهی شخصی مشتریان است: در `.gitignore` قرار گرفت و هرگز commit
|
||||
نشده است.
|
||||
|
||||
### تصحیح ادعاهای نسخهی قبلی این بخش
|
||||
|
||||
| ادعای قبلی | واقعیت (کوئری روی داده) |
|
||||
|---|---|
|
||||
| `remain`/`rial_remain` صفرند | **NULL** هستند — در هر ۳۸۳ ردیف زنده؛ ساختار `DEFAULT NULL` دارد |
|
||||
| `new_city` شناسهی عددی `cities5` است | **هر ۱۷۲ مقدار نام شهرِ تایپشده است؛ صفر مقدار عددی** — §۵.۹ |
|
||||
| `birth_date` همه بین ۱۹۵۰ و ۲۰۱۰، بدون نیاز به پالایش | **۳۵۷ مورد بعد از ۲۰۱۰ تا ۲۰۳۱؛ ۱۹۰ مورد برابر تاریخ ثبتنام** |
|
||||
| `type` ۳ و ۴ و ۵ نامعلوم، نیاز به تأیید مالک | در `Customer::$TYPES` (Customer.php:85-92) تعریف شدهاند — §۵.۳ |
|
||||
| `LedgerEntry.goldGrams = value`، `rialAmount = price` | این فیلدها وجود ندارند؛ مدل `amount` + `currency` + `direction` است |
|
||||
| شمارش انواع سند: ۹ → ۴۸۲، ۲ → ۶۳، … | شامل حذفشدهها بود؛ زنده: ۹ → ۲۸۰، ۲ → ۳۷، ۱ → ۳۳، ۴ → ۳۲، ۳ → ۱ |
|
||||
| علاقهمندی = `fav` (۲۵۱) | `fav` مال فروشگاه خرده است (guard `api-singleCustomer`)؛ اپ عمده **`major_favorites`** (۳٬۳۰۵) — پایین |
|
||||
|
||||
**درست بود:** مبلغ سند در `value` (گرم) و `price` (ریال) است؛ پوشهی تصاویر
|
||||
`F:\New folder (2)\storage` با **۱۷٬۸۳۲ فایل (۱.۸ گیگابایت)** وجود دارد.
|
||||
|
||||
### 🔴 فرمول بدهی legacy — با داده تأیید شد
|
||||
|
||||
`CustomerController::updateRemain` (Api/CustomerController.php:42-51):
|
||||
|
||||
```
|
||||
remain = Σ orders.input (sts > 4) − Σ cashes.value (confirmed = 1, type 1–4)
|
||||
rial_remain = Σ orders.rial_input (sts > 4)
|
||||
```
|
||||
|
||||
هر دو مدل `SoftDeletes` دارند، پس حذفشدهها بیروناند. بازمحاسبه روی
|
||||
دامپ: **طلا ۱٬۵۹۰ از ۱٬۵۹۱ مشتری، ریال ۱٬۵۹۱ از ۱٬۵۹۱** با ماندهی ذخیرهشده
|
||||
برابر است. جمع کل ۴۲۷٫۵۰۴۲ گرم — همان عدد پیشبررسی روی سرور زنده.
|
||||
|
||||
سه پیامد که قبلاً دیده نشده بود:
|
||||
|
||||
1. **سند نوع ۹ («سفارش») در بدهی نیست.** بدهی از جدول `orders` میآید؛ نوع ۹
|
||||
فقط آینهی آن در فهرست است. انتقال هر دو = **دوبار شمردن بدهی هر سفارش**.
|
||||
2. **ریال هرگز بدهی ریالی را کم نمیکند.** پرداخت پولی (نوع ۴) با قیمت روز به
|
||||
گرم تبدیل میشود (`price ÷ (price_per_gram ÷ 4.331802)` — قیمت هر مثقال)
|
||||
و بدهی **طلا** را کم میکند.
|
||||
3. **چک (نوع ۳) اثری در مانده ندارد** — مقدار گرمیاش صفر است.
|
||||
|
||||
**تنها استثنا — مشتری `#125`:** ماندهی ذخیرهشده −۹٫۸۹۵۱ گرم، بازمحاسبه صفر.
|
||||
تنها سندش (۴۸٫۳۴ گرم آبشده) در ۲۰۲۱ حذف شده و مشتری در ۲۰۲۲ دوباره
|
||||
ویرایش شده — مانده بهاحتمال زیاد **دستی** وارد شده (ستون در `$fillable`
|
||||
بود، SEC-14). ✅ **تصمیم مالک ۲۰۲۶-۱۰-۰۷:** همان −۹٫۸۹۵۱ منتقل شود، بهصورت
|
||||
یک سند صریح «تراز انتقالی».
|
||||
|
||||
### ✅ تاریخ تولد — پالایش (تصمیم مالک ۲۰۲۶-۱۰-۰۷)
|
||||
|
||||
از ۹۱۵ تاریخ ثبتشده فقط **۵۴۱** واقعی است:
|
||||
|
||||
| دسته | تعداد | چرا کنار گذاشته شد |
|
||||
|---|---|---|
|
||||
| برابر روز ثبتنام | ۱۹۰ | پیشفرض فرم — در سال ۲۰۲۵ از ۱۷۶ مورد، ۱۶۵ |
|
||||
| ساعت دارد یا سال بعد از ۲۰۰۸ | ۱۸۴ | تاریخ تولد نمیتواند باشد |
|
||||
| **نگه داشته شد** | **۵۴۱** | |
|
||||
|
||||
### ✅ شهر — نگاشت دوگانه (تصمیم مالک ۲۰۲۶-۱۰-۰۷)
|
||||
|
||||
ثبتنام اپ روی **همه** `city_id = 5` (تهران) میگذاشت
|
||||
(Major/CustomerController.php:21) و شهر واقعی را در `new_city` بهصورت
|
||||
متن. از ۹۲ مشتری که هر دو را دارند، ۹۱ همان تهرانِ اجباریاند.
|
||||
|
||||
- متن تایپشده هست → عیناً در `Customer.cityNameRaw`؛ `cityId` فقط با تطابق
|
||||
دقیق نام (پس از یکسانسازی «ي/ك») — تهرانِ اجباری نادیده گرفته میشود
|
||||
- متن نیست → همان `city_id` قدیم
|
||||
|
||||
یکسانسازی «ي/ك» اهمیت داشت: تطابق با جدول ۱۵تایی از ۳۲ به ۷۲ رسید.
|
||||
|
||||
### نقشها — تداخل شماره (`config_statics`)
|
||||
|
||||
نوع کاربر در legacy یک ردیف `config_statics` است (UserController.php:26).
|
||||
عناوین: ۸۵ مدیر · ۸۶ **حسابدار** · ۱۰۲ ویرایشگر · ۱۰۴ مدیر اصلی · ۱۰۵ مدیران
|
||||
· ۱۰۸ انباردار · ۱۳۲ بیکار · ۱۳۵ سئو · ۱۳۹ مدیر شعبه.
|
||||
|
||||
🔴 در مقصد (seed) **۸۶ = کارشناس فروش**. کپی شمارهبهشماره دسترسی ۷۱ گانهی
|
||||
حسابدار را به همهی کارشناسان میداد. ابزار **با عنوان** نگاشت میکند
|
||||
(۸۶ حسابدار → ۸۷ حسابدار)؛ فقط ۸۵ با ۸۵ برابر گرفته شده (هر دو نقش
|
||||
تمامدسترسیاند).
|
||||
|
||||
۷ کارشناس فروش با موبایل یک کاربر ادمین مشترکاند — ۳ تا مدیر ارشد.
|
||||
قاعدهی «کارشناس برنده» روی آنها **مدیر ارشد را به کارشناس تنزل میداد و از
|
||||
پنل بیرون میکرد**. ابزار نقش ادمین را نگه میدارد و معرفیها به همان ادمین
|
||||
وصل میشوند.
|
||||
|
||||
### علاقهمندیها — جدول درست `major_favorites` است
|
||||
|
||||
`fav` (۲۵۱) را `Customer\FavController` زیر guard `api-singleCustomer`
|
||||
(ورود مشتری خرده) مینویسد؛ `customer_id` آن به جدول دیگری اشاره دارد. اپ
|
||||
عمده `Major\Favorite` = **`major_favorites`** (۳٬۳۰۵ ردیف) را میخواند
|
||||
(ApiMobileController.php:73-465). ساختار `CustomerFavorite` بیتغییر جواب
|
||||
میدهد؛ فقط منبع عوض شد. کامنت جدول در دیتابیس هنوز «legacy fav» میگوید —
|
||||
در migration بعدی اصلاح شود.
|
||||
|
||||
### سایر یافتهها
|
||||
|
||||
- **زمانها ساعت تهراناند** (`config/app.php:70` → `Asia/Tehran`). ابزار
|
||||
با قواعد ICU تبدیل میکند، با ساعت تابستانی ایران تا ۲۰۲۲.
|
||||
- **۴۷۳ از ۶۱۰ ردیف سفارش زنده** به تنوعِ حذفشده اشاره دارند → تنوع و محصول
|
||||
حذفشدهی ارجاعشده با `isActive = false` منتقل میشوند.
|
||||
- **همهی ۱٬۲۵۰ محصولِ چنددسته** هم در درخت سایت (نوع ۱) هم اپ (نوع ۲)
|
||||
هستند؛ مقصد یک `categoryId` دارد که اپ نشان میدهد → **دستهی اپ** نگه
|
||||
داشته میشود.
|
||||
- **۲۷ گروه موبایل تکراری: در هیچکدام بیش از یک ردیف پول ندارد** و هیچ
|
||||
کارشناسی درگیر نیست → ادغام از نظر مالی بیخطر است.
|
||||
- **سرپرست کارگاه** در legacy مشتری است (`belongsTo(Customer)`)، در مقصد
|
||||
ادمین → پیوند ساخته نمیشود.
|
||||
- **مشتری `#594`** بهجای موبایل کلمهی «تست» دارد؛ با یک سفارش (`#666`،
|
||||
وضعیت ۱، بدون بدهی) کنار گذاشته میشود.
|
||||
- **۶ تنوع** اجرت ریالی (`rial_pay`) دارند؛ مقصد فیلدی برایش ندارد.
|
||||
- **عدد ردشدهها ۷ است، نه ۱۴** (۲ گام دو، ۳ گام یک، ۲ هر دو). عدد ۱۴ در
|
||||
گزارش قبلی اشتباه جمع شده بود.
|
||||
|
||||
---
|
||||
|
||||
## ۱۰.۸ — ابزار انتقال (`apps/api/scripts/legacy-import/`)
|
||||
|
||||
### اجرا
|
||||
|
||||
```
|
||||
cd apps/api
|
||||
npx ts-node --transpile-only scripts/legacy-import/index.ts \
|
||||
--source "mysql://USER:PASS@HOST:PORT/oapi" \
|
||||
--target "postgresql://USER:PASS@HOST:5432/goldbank_import_test" \
|
||||
--images "F:/New folder (2)/storage" --upload-dir "<UPLOAD_DIR مقصد>" \
|
||||
--report ./legacy-import-report.md --yes
|
||||
```
|
||||
|
||||
بدون `--yes` فقط پیشبررسی میکند و چیزی نمینویسد. `--target` هرگز از
|
||||
`.env` خوانده نمیشود. اگر نام دیتابیس مقصد `import` یا `staging` نداشته
|
||||
باشد، `--confirm-db=<همان نام>` هم لازم است.
|
||||
|
||||
### قواعد ایمنی (همه در کد)
|
||||
|
||||
- منبع فقط خوانده میشود.
|
||||
- قبل از اجرا وجود ستونهای migration های آمادهسازی چک میشود.
|
||||
- هرگز نمینویسد: `accounting*` مشتری و محصول، `ProductVariant.imageId`،
|
||||
جداول `Accounting*`. هیچ `ProductImage`ی حذف نمیشود.
|
||||
- ردیفی که از قبل در مقصد بوده (`matched`) فقط فیلدهای **خالیاش** پر میشود.
|
||||
- مستقیم از Prisma مینویسد، نه از سرویسها: **نه پیامکی میرود، نه چیزی به
|
||||
نرمافزار حسابداری صف میشود.**
|
||||
- نگاشت شناسهها در schema جدای `legacy_import.id_map` در مقصد است (بیرون از
|
||||
`public`، پس تغییر schema اپ نیست). اجرای دوباره بهروزرسانی میکند، تکرار
|
||||
نمیسازد؛ اجرایی که نیمهکاره ماند با اجرای بعدی تمام میشود. بعد از
|
||||
جابهجایی: `DROP SCHEMA legacy_import CASCADE` — یا نگه داشته شود برای
|
||||
redirect لینکهای قدیمی محصول.
|
||||
|
||||
### نتیجهی آزمون — ۲۰۲۶-۱۰-۰۷
|
||||
|
||||
مقصد: `goldbank_import_test`، کپی دیتابیس توسعه با ۵۹ مشتری و ۲۸ محصول
|
||||
واقعی (تا ادغام هم آزموده شود). سه اجرای پیاپی:
|
||||
|
||||
| | نتیجه |
|
||||
|---|---|
|
||||
| مشتری ساختهشده | ۱٬۵۰۳ · ادغام با موجود ۱۳ · ادغام تکراری ۳۲ |
|
||||
| بدهی طلا، مشتریهای ساختهشده | **۱٬۵۰۳ از ۱٬۵۰۳ برابر legacy (±۰٫۰۰۰۱ گرم)** |
|
||||
| بدهی ریال | **۱٬۵۰۳ از ۱٬۵۰۳** |
|
||||
| جمع بدهی طلا | legacy ۴۲۷٫۵۰۳۹ · مقصد ۴۲۷٫۵۰۴۱ (دو گِرد ۰٫۰۰۰۱) |
|
||||
| سند تراز انتقالی | فقط مشتری `#125`: بستانکار ۹٫۸۹۵۱ |
|
||||
| دادهی حسابداری (۵۱ مشتری، ۱۸ محصول، ۷۴ پیوند) | **بایتبهبایت یکسان** با دیتابیس توسعه |
|
||||
| `imageId` روی تنوع وارداتی | **۰** |
|
||||
| کارشناس | ۴۹ ساخته شد با پروفایل · ۸ حساب معاملاتی · ۷ ادمین تنزل **نیافت** |
|
||||
| محصول / تنوع | ۱٬۴۷۳ / ۱٬۶۱۲ (شامل ۲۱۵ / ۳۴۷ حذفشدهی غیرفعال برای تاریخچه) |
|
||||
| سفارش / ردیف | ۲۹۹ از ۳۰۰ (`#666` مشتری تست) / ۶۰۸ |
|
||||
| سند مالی | ۸۶ بدهی سفارش + ۹۵ پرداخت تأییدشده + ۸ تأییدنشده |
|
||||
| علاقهمندی | ۸۷۴ (۵۳۴ به محصول حذفشده و ۵۹ به مشتری منتقلنشده کنار رفت) |
|
||||
| سفارش ساختنی | ۲۰ از ۲۰ |
|
||||
| تصویر | ۶٬۱۹۶ فایل (۵۱۸ مگابایت) کپی شد · ۳ فایل در storage قدیم نبود |
|
||||
| اجرای دوم و سوم | **صفر ردیف تکراری** |
|
||||
|
||||
### آنچه این ابزار عمداً منتقل نمیکند
|
||||
|
||||
خردهفروشی (`orders_orders`، `login`، `fav`، `wallets`، `transactions*` —
|
||||
D13 و §۷.۲)، تاریخچهی وضعیت سفارش (`order_activity`)، تخفیفها
|
||||
(`major_promotion*`)، اعلانها، تاریخچهی پیامک (`messages`)، لاگها،
|
||||
نظرات، `cinies`. هرکدام لازم شد، قدم جدا اضافه میشود.
|
||||
|
||||
---
|
||||
|
||||
## ۱۱. جمعبندی — کارهای لازم قبل از نوشتن ابزار
|
||||
|
||||
### 🔴 مسدودکننده (بدون اینها ابزار در میانه میشکند یا داده خراب میکند)
|
||||
@@ -1487,25 +1701,30 @@ legacy دکمهی تکمیل پروفایل را فقط وقتی `sts_verify_
|
||||
| ۱ | ~~شمارش تکرار~~ | ✅ **انجام شد** — ۲۷ گروه موبایل، ۰ کد ملی (§۱۰.۴) |
|
||||
| ۱.۱ | ~~`nationalCode`~~ | ✅ **حل — nullable شود** (§۱۰.۴) |
|
||||
| ۱.۲ | ~~`passwordHash`~~ | ✅ **حل — hash غیرقابلورود** (§۱۰.۴) |
|
||||
| ۱.۳ | 🔴 **مبلغ سند مالی کجاست؟** `cashes.remain` همه `NULL` | ۱۰.۶ |
|
||||
| ۱.۳ | ~~مبلغ سند کجاست~~ | ✅ **حل — فرمول بدهی legacy با داده تأیید شد** (§۱۰.۷) |
|
||||
| ۱.۴ | ~~`REJECTED`~~ | ✅ **حل — enum + دو ستون علت** (§۵.۵) |
|
||||
| ۱.۵ | 🔴 **ترتیب دومرحلهای** — کارشناسان قبل از مشتریان | ۵.۷ |
|
||||
| ۲ | آستانهی تطبیق عددی جای «صفر اختلاف» — **تأیید شد: ۵۶ ردیف گِرد میشوند** | ۴ · ۱۰.۴ |
|
||||
| ۳ | قاعدهی `WHERE deleted_at IS NULL` — **حیاتیتر از انتظار: ۴۵٪ واریانتها** | ۵.۸ · ۱۰.۴ |
|
||||
| ۱.۵ | ~~ترتیب دومرحلهای~~ | ✅ **در ابزار** — کارشناسان قبل از مشتریان (§۱۰.۸) |
|
||||
| ۱.۶ | ~~`birth_date`~~ | ✅ **حل — پالایش، ۵۴۱ از ۹۱۵** (§۱۰.۷) |
|
||||
| ۱.۷ | ~~`customers.type` ۳/۴/۵~~ | ✅ **سؤال لازم نبود** — در `Customer::$TYPES` تعریف شدهاند (§۵.۳) |
|
||||
| ۲ | ~~آستانهی تطبیق~~ | ✅ **در ابزار** — ±۰٫۰۰۰۱ گرم، ±۱ ریال (§۱۰.۸) |
|
||||
| ۳ | ~~قاعدهی `deleted_at`~~ | ✅ **در ابزار** — بهجز حذفشدههای ارجاعشده در سفارش (§۱۰.۷) |
|
||||
| ۴ | ~~تصمیم `orders_orders`~~ | ✅ **حل — به تأخیر افتاد** (§۷.۲) |
|
||||
| ۵ | تصحیح D0: `fname`/`lname` مستقیم، تجزیه نشود | ۲ |
|
||||
| ۶ | **allow-list ستونهای نوشتنی** — ۷ ستون مقصد-تنها لمس نشوند | **۱۰.۵** |
|
||||
| ۷ | **کلید تطبیق ردیفهای موجود مقصد** | **۱۰.۵** |
|
||||
| ۵ | ~~تصحیح D0~~ | ✅ **در ابزار** — `fname`/`lname` مستقیم |
|
||||
| ۶ | ~~allow-list ستونها~~ | ✅ **در ابزار** — حسابداری بایتبهبایت یکسان ماند (§۱۰.۸) |
|
||||
| ۷ | ~~کلید تطبیق~~ | ✅ **موبایل** (مالک) — ادمین تنزل نمییابد (§۱۰.۷) |
|
||||
|
||||
### 🔶 تصمیم مالک
|
||||
|
||||
| # | مورد | § |
|
||||
|---|---|---|
|
||||
| ۱ | ~~۴ ستون دادهی غیرقابلبازسازی~~ | ✅ **حل — تولد و شهر منتقل، توکن و سالگرد حذف** (§۵.۲) |
|
||||
| ۱.۱ | 🔶 `new_city` → `cityId` یا ستون متنی؟ | **۵.۹** |
|
||||
| ۱.۱ | ~~`new_city`~~ | ✅ **حل — نگاشت دوگانه: متن در `cityNameRaw` + `cityId` با تطابق دقیق** (مالک · §۱۰.۷) |
|
||||
| ۲ | ~~تفکیک `STEP_TWO_SUBMITTED` از `VERIFIED`~~ | ✅ **حل** (§۵.۵) |
|
||||
| ۳ | `type IS NULL` چه `CustomerKind` شود | ۵.۳ |
|
||||
| ۴ | ~~`fav`~~ | ✅ **حل — لازم است، migration افزایشی** (§۱۰) |
|
||||
| ۳ | ~~`type IS NULL`~~ | ✅ **`APP_SHOPKEEPER`** — ثبتنام اپ `type = 2` میگذارد (Major/CustomerController.php:44)؛ NULL قدیمیتر از آن است |
|
||||
| ۴ | ~~علاقهمندی~~ | ✅ **حل — از `major_favorites`، نه `fav`** (§۱۰.۷) |
|
||||
| ۴.۲ | ~~مشتری `#125`~~ | ✅ **همان −۹٫۸۹۵۱ با سند تراز انتقالی** (مالک · §۱۰.۷) |
|
||||
| ۴.۳ | 🔶 **رد در پنل جدید `REJECTED` نمینویسد** و مشتری ردشده با ارسال دوباره خودکار تأیید میشود | §۱۰.۵.۵ |
|
||||
| ۴.۴ | 🔶 **مدیر ارشدی که کارشناس هم بود** — نقش ادمین نگه داشته شد؛ آیا حساب کارشناسی هم لازم است؟ | §۱۰.۷ |
|
||||
| ۴.۱ | `cinies`/`ciny_products` — چیدمان نمایشی | ۱۰ |
|
||||
| ۵ | شمارندههای denormalized محاسبه شوند یا صفر بمانند | ۵.۲ |
|
||||
| ۶ | نسخههای تغییراندازهیافتهی تصویر | ۹ |
|
||||
|
||||
@@ -23,7 +23,7 @@ SELECT '=== new_city vs city_id ===' AS section;
|
||||
|
||||
SELECT
|
||||
SUM(new_city IS NOT NULL AND new_city <> '' AND city_id IS NULL) AS only_new_city,
|
||||
SUM(new_city IS NOT NULL AND new_city <> '' AND city_id IS NOT NULL) AS both,
|
||||
SUM(new_city IS NOT NULL AND new_city <> '' AND city_id IS NOT NULL) AS both_set,
|
||||
SUM((new_city IS NULL OR new_city = '') AND city_id IS NOT NULL) AS only_city_id,
|
||||
SUM((new_city IS NULL OR new_city = '') AND city_id IS NULL) AS neither
|
||||
FROM customers WHERE deleted_at IS NULL;
|
||||
@@ -32,7 +32,9 @@ FROM customers WHERE deleted_at IS NULL;
|
||||
-- اگر بالا باشد، اکثرشان قابل تبدیل به city_id هستند و ستون متنی لازم نیست.
|
||||
SELECT 'new_city matches an existing cities.title' AS metric, COUNT(*) AS n
|
||||
FROM customers c
|
||||
JOIN cities ci ON TRIM(ci.title) = TRIM(c.new_city)
|
||||
-- The two columns have different collations (unicode_ci vs general_ci), so
|
||||
-- both sides are pinned to one; without it MySQL refuses the comparison.
|
||||
JOIN cities ci ON TRIM(ci.title) COLLATE utf8mb4_general_ci = TRIM(c.new_city) COLLATE utf8mb4_general_ci
|
||||
WHERE c.deleted_at IS NULL AND c.new_city IS NOT NULL AND c.new_city <> ''
|
||||
AND c.city_id IS NULL;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user