AdminUser held both panel staff and sales experts, so one mobile could not be both. Staff now live in "Admin" (panel only); sales experts stay in "AdminUser" untouched. Uniqueness is per table. - migration 20261026120000_split_admin_table: additive only. Creates "Admin", copies non-expert rows with their ids, re-points the "who did this" foreign keys (ledger, order status, sms, notification, upload, allocation, workshop, admin session) to "Admin" without moving data. Never writes to "AdminUser". - login, JWT, SSO, branch/workshop supervisors and the users grid use "Admin"; SALES_EXPERT create/edit still goes through admin/users into "AdminUser". - an admin cannot be turned into an expert or the reverse (different tables). - manual/: optional phase-2 script to drop the dormant admin copies from "AdminUser", and a read-only expert-login diagnostic. - docs/ADMIN-TABLE-SPLIT.md: what changed, deploy checklist, rollback. Tested on a copy with 12,000 experts: expert rows byte-identical (md5) before and after; full migration chain applies and matches schema.prisma. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
79 lines
5.6 KiB
Markdown
79 lines
5.6 KiB
Markdown
# جدا شدن جدول ادمینها از کارشناسان فروش
|
|
|
|
## چه چیزی عوض شد
|
|
|
|
| | قبل | بعد |
|
|
|---|---|---|
|
|
| پرسنل پنل (مدیر ارشد، تأییدکننده، سرپرست کارگاه، حسابدار) | جدول `AdminUser` | جدول جدید **`Admin`** |
|
|
| کارشناس فروش | جدول `AdminUser` | جدول `AdminUser` (**بدون هیچ تغییری**) |
|
|
| یکتایی شماره موبایل | یک شماره = فقط یک نقش | یکتا **در هر جدول جدا**؛ یک شماره میتواند هم ادمین باشد هم کارشناس |
|
|
|
|
ادمین فقط برای پنل است. کارشناس فقط برای اپها. توکن اپ کارشناس هیچوقت به پنل دسترسی نمیدهد
|
|
(`JwtStrategy` فقط در `Admin` میگردد).
|
|
|
|
## مایگریشن — `20261026120000_split_admin_table`
|
|
|
|
فقط **افزودنی** است. به `AdminUser` (کارشناسها) چیزی نمینویسد، حذف نمیکند و قفل نمیکند.
|
|
|
|
1. جدول `Admin` را میسازد.
|
|
2. ردیفهای غیرکارشناس `AdminUser` را با **همان `id`** در `Admin` کپی میکند
|
|
(توکنهای صادرشده و همهٔ ستونهای «چه کسی انجام داد» معتبر میمانند).
|
|
3. کلید خارجیهای «ثبتکننده» را از `AdminUser` به `Admin` منتقل میکند. **نام ستونها و دیتا عوض نمیشود**،
|
|
فقط مقصد constraint:
|
|
`LedgerEntry.postedByAdminUserId`، `OrderStatusEvent` و `PreOrderStatusEvent.actorAdminUserId`،
|
|
`BranchAllocation.allocatorAdminUserId`، `SmsMessage` و `Notification.sentByAdminUserId`،
|
|
`UploadedFile.uploadedByAdminUserId`، `Workshop.supervisorId`، `AdminSession.adminUserId`.
|
|
4. بهصورت `NOT VALID` اضافه و بعد validate میشوند؛ اگر ردیف قدیمیای به id غیرادمین اشاره کند، deploy شکست
|
|
نمیخورد و فقط یک `NOTICE` چاپ میشود.
|
|
|
|
بدون تغییر ماندند (چون دربارهٔ کارشناساند): `Order.salesAgentId`، `Customer.referredByAgentId`،
|
|
`SalesExpertProfile.adminUserId`.
|
|
|
|
قابل اجرای مجدد است (idempotent).
|
|
|
|
### آزمایششده روی
|
|
|
|
کپی دیتابیس با ۱۲۰۰۰ کارشناس: چکسام (md5) تمام ردیفهای `SALES_EXPERT` قبل و بعد از مایگریشن **یکسان** بود.
|
|
زنجیرهٔ کامل مایگریشنها روی دیتابیس خالی اجرا شد و `prisma migrate diff` هیچ اختلافی بین `schema.prisma`
|
|
و دیتابیس نشان نداد.
|
|
|
|
## قبل از دیپلوی روی سرور اصلی
|
|
|
|
1. از دیتابیس **بکاپ** بگیرید.
|
|
2. (اختیاری، فقطخواندنی) ببینید ردیفی هست که به id غیرادمین اشاره کند؛ برای هر جدول:
|
|
|
|
```sql
|
|
-- نمونه برای دفتر حساب؛ برای بقیهٔ جدولها/ستونها همین الگو
|
|
SELECT count(*) FROM "LedgerEntry" l
|
|
WHERE l."postedByAdminUserId" IS NOT NULL
|
|
AND NOT EXISTS (SELECT 1 FROM "Admin" a WHERE a."id" = l."postedByAdminUserId");
|
|
```
|
|
|
|
قبل از اجرای مایگریشن `Admin` هنوز وجود ندارد؛ این کوئری را بعد از دیپلوی بزنید. جواب `0` یعنی همه چیز سالم است.
|
|
3. ابتدا مایگریشن (با بوت شدن API جدید خودکار اجرا میشود)، سپس همان کد.
|
|
چون additive است، کد قدیمی در فاصله هم کار میکند.
|
|
|
|
## یک محدودیت تا «فاز ۲»
|
|
|
|
ردیفهای قدیمی ادمینها در `AdminUser` **باقی میمانند** (غیرفعال، بدون استفاده در کد). شمارهٔ آنها هنوز در
|
|
`AdminUser` قفل است، پس برای **ادمینهای موجود** ساختن کارشناس با همان شماره خطای «قبلاً ثبت شده» میدهد.
|
|
(ادمینهای تازهساخته و شمارههای جدید مشکلی ندارند.)
|
|
|
|
برای آزاد کردن آن شمارهها، بعد از اطمینان از درستی کار:
|
|
`apps/api/prisma/manual/20261026_remove_dormant_admin_rows.sql`
|
|
— دستی است، عمداً داخل `migrations/` نیست، فقط ردیفهای **غیرکارشناس** را که در `Admin` کپی دارند حذف میکند،
|
|
و اگر تعداد کارشناسها یک عدد هم تغییر کند خودش rollback میکند.
|
|
|
|
## برگشت (Rollback)
|
|
|
|
کد قبلی را دوباره deploy کنید. چیزی حذف نشده، پس کد قدیمی همهٔ ادمینهای پیشین را در `AdminUser` پیدا میکند.
|
|
ادمینهایی که **بعد** از جدا شدن ساخته شدهاند فقط در `Admin` هستند و باید دوباره ساخته شوند.
|
|
|
|
## تغییر رفتار پنل
|
|
|
|
- جدول «کاربران» فقط ادمینها را نشان میدهد؛ کارشناسها از بخش کارشناسان.
|
|
- نقش «کارشناس فروش» از لیست نقشهای فرم ادمین حذف شد؛ فرم کارشناس همان `POST admin/users` را با
|
|
`role: SALES_EXPERT` میزند و در `AdminUser` ساخته میشود.
|
|
- نقش یک ادمین به کارشناس (و برعکس) قابل تغییر نیست، چون دو جدول جدا هستند.
|
|
- کارشناس دیگر نمیتواند با رمز خودش وارد پنل شود (قبلاً چون در همان جدول بود، میتوانست).
|