build: rebuild API release bundle for the admin/expert table split
app.js, seed.js, the Prisma client in release/vendor and the new migration 20261026120000_split_admin_table, built with `pnpm build:release` (API only; the admin panel and user apps are unchanged by this work). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
06c8a5f434
commit
e2ec30f241
+92
-48
@@ -140,6 +140,61 @@ model City {
|
||||
@@index([parentId])
|
||||
}
|
||||
|
||||
/// A staff account for the ADMIN PANEL — and nothing else.
|
||||
///
|
||||
/// Split out of AdminUser (which now holds sales experts only) so that:
|
||||
/// * the same mobile can be an admin AND a sales expert — they are two
|
||||
/// separate roles, each unique only within its own table;
|
||||
/// * the expert table (production: ~12,000 rows) is never written by panel
|
||||
/// staff management.
|
||||
/// Migration 20261026120000_split_admin_table copies the existing non-expert
|
||||
/// AdminUser rows here WITH THEIR IDS, so issued panel tokens and every
|
||||
/// "who did this" column keep resolving. The old rows are left in AdminUser,
|
||||
/// dormant; no code reads them as admins any more.
|
||||
///
|
||||
/// ⚠️ fullName/firstName/lastName are kept in sync by the DB trigger
|
||||
/// `admin_sync_full_name_trg` (it shares the function admin_user_sync_full_name) — same semantics as AdminUser: the parts win.
|
||||
model Admin {
|
||||
id String @id @default(cuid())
|
||||
|
||||
fullName String
|
||||
firstName String?
|
||||
lastName String?
|
||||
|
||||
mobile String @unique
|
||||
passwordHash String
|
||||
/// Never SALES_EXPERT — that role lives in AdminUser. Enforced in
|
||||
/// AdminUsersService, not in the database, so the enum stays shared.
|
||||
role AdminRole
|
||||
userTypeId Int?
|
||||
userType UserType? @relation(fields: [userTypeId], references: [id])
|
||||
branchId String?
|
||||
branch Branch? @relation(fields: [branchId], references: [id])
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
supervisedWorkshops Workshop[] @relation("WorkshopSupervisor")
|
||||
branchAllocations BranchAllocation[] @relation("AllocationAuthor")
|
||||
postedLedgerEntries LedgerEntry[]
|
||||
orderStatusEvents OrderStatusEvent[]
|
||||
preOrderStatusEvents PreOrderStatusEvent[]
|
||||
sentSmsMessages SmsMessage[] @relation("SmsAuthor")
|
||||
sentNotifications Notification[] @relation("NotificationAuthor")
|
||||
uploadedFiles UploadedFile[] @relation("FileUploader")
|
||||
|
||||
/// Issued admin-panel tokens, recorded only while session tracking is on
|
||||
/// (AUTH_SESSION_TRACKING, or implicitly whenever the IAM is enabled) so a
|
||||
/// token can be revoked before it expires. See AdminSession.
|
||||
sessions AdminSession[]
|
||||
|
||||
@@index([role])
|
||||
@@index([userTypeId])
|
||||
@@index([firstName])
|
||||
@@index([lastName])
|
||||
}
|
||||
|
||||
/// A SALES EXPERT (role SALES_EXPERT). Panel staff live in `Admin`; rows here with
|
||||
/// any other role are pre-split leftovers and are ignored by the code.
|
||||
model AdminUser {
|
||||
id String @id @default(cuid())
|
||||
|
||||
@@ -185,26 +240,13 @@ model AdminUser {
|
||||
pasazhName String? // legacy pasazh_name
|
||||
memberCode String? // legacy mcode
|
||||
|
||||
referredCustomers Customer[] @relation("ReferralAgent")
|
||||
referredCustomers Customer[] @relation("ReferralAgent")
|
||||
/// Orders credited to this sales expert — see Order.salesAgentId.
|
||||
creditedOrders Order[] @relation("OrderSalesAgent")
|
||||
supervisedWorkshops Workshop[] @relation("WorkshopSupervisor")
|
||||
branchAllocations BranchAllocation[] @relation("AllocationAuthor")
|
||||
postedLedgerEntries LedgerEntry[]
|
||||
orderStatusEvents OrderStatusEvent[]
|
||||
preOrderStatusEvents PreOrderStatusEvent[]
|
||||
sentSmsMessages SmsMessage[] @relation("SmsAuthor")
|
||||
sentNotifications Notification[] @relation("NotificationAuthor")
|
||||
uploadedFiles UploadedFile[] @relation("FileUploader")
|
||||
creditedOrders Order[] @relation("OrderSalesAgent")
|
||||
|
||||
/// Only ever set for role = SALES_EXPERT (D17).
|
||||
salesExpertProfile SalesExpertProfile?
|
||||
|
||||
/// Issued admin-panel tokens, recorded only while session tracking is on
|
||||
/// (AUTH_SESSION_TRACKING, or implicitly whenever the IAM is enabled) so a
|
||||
/// token can be revoked before it expires. See AdminSession.
|
||||
sessions AdminSession[]
|
||||
|
||||
@@index([role])
|
||||
@@index([userTypeId])
|
||||
@@index([avatarFileId])
|
||||
@@ -274,8 +316,8 @@ model AdminSession {
|
||||
/// The token's `jti` claim. Unique: one row per issued token.
|
||||
jti String @unique
|
||||
|
||||
adminUserId String
|
||||
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
|
||||
adminId String @map("adminUserId")
|
||||
admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
||||
|
||||
/// Set when the login was verified by the IAM: its session id (`sid`) and the
|
||||
/// stable user id (`sub`) there. Back-channel logout arrives keyed by these —
|
||||
@@ -294,7 +336,7 @@ model AdminSession {
|
||||
revokedAt DateTime?
|
||||
revokedReason String?
|
||||
|
||||
@@index([adminUserId])
|
||||
@@index([adminId], map: "AdminSession_adminUserId_idx")
|
||||
@@index([iamSessionId])
|
||||
@@index([iamSub])
|
||||
@@index([expiresAt])
|
||||
@@ -571,6 +613,7 @@ model Branch {
|
||||
cityId String?
|
||||
city City? @relation(fields: [cityId], references: [id])
|
||||
adminUsers AdminUser[]
|
||||
admins Admin[]
|
||||
orders Order[]
|
||||
allocations BranchAllocation[]
|
||||
}
|
||||
@@ -583,7 +626,7 @@ model Workshop {
|
||||
phone String?
|
||||
/// The supervisor created alongside the workshop by the legacy Add form.
|
||||
supervisorId String?
|
||||
supervisor AdminUser? @relation("WorkshopSupervisor", fields: [supervisorId], references: [id])
|
||||
supervisor Admin? @relation("WorkshopSupervisor", fields: [supervisorId], references: [id])
|
||||
isActive Boolean @default(true)
|
||||
preOrders PreOrder[]
|
||||
}
|
||||
@@ -826,15 +869,15 @@ model OrderItem {
|
||||
}
|
||||
|
||||
model OrderStatusEvent {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
fromStatus OrderStatus?
|
||||
toStatus OrderStatus
|
||||
note String?
|
||||
actorAdminUserId String?
|
||||
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
fromStatus OrderStatus?
|
||||
toStatus OrderStatus
|
||||
note String?
|
||||
actorAdminId String? @map("actorAdminUserId")
|
||||
actorAdmin Admin? @relation(fields: [actorAdminId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orderId])
|
||||
}
|
||||
@@ -875,15 +918,15 @@ model PreOrder {
|
||||
}
|
||||
|
||||
model PreOrderStatusEvent {
|
||||
id String @id @default(cuid())
|
||||
preOrderId String
|
||||
preOrder PreOrder @relation(fields: [preOrderId], references: [id])
|
||||
id String @id @default(cuid())
|
||||
preOrderId String
|
||||
preOrder PreOrder @relation(fields: [preOrderId], references: [id])
|
||||
/// Field-level diff, e.g. {"status": {"old": "PLACED", "new": "ADMIN_APPROVED"}}
|
||||
changes Json
|
||||
actorAdminUserId String?
|
||||
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id])
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
changes Json
|
||||
actorAdminId String? @map("actorAdminUserId")
|
||||
actorAdmin Admin? @relation(fields: [actorAdminId], references: [id])
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([preOrderId])
|
||||
}
|
||||
@@ -912,10 +955,10 @@ model LedgerEntry {
|
||||
order Order? @relation(fields: [orderId], references: [id])
|
||||
|
||||
/// Pending entries don't count toward the cached customer balance until confirmed.
|
||||
confirmed Boolean @default(false)
|
||||
confirmedAt DateTime?
|
||||
postedByAdminUserId String?
|
||||
postedByAdminUser AdminUser? @relation(fields: [postedByAdminUserId], references: [id])
|
||||
confirmed Boolean @default(false)
|
||||
confirmedAt DateTime?
|
||||
postedByAdminId String? @map("postedByAdminUserId")
|
||||
postedByAdmin Admin? @relation(fields: [postedByAdminId], references: [id])
|
||||
|
||||
/// Instrument metadata from the legacy "cash" document form. Which fields are
|
||||
/// filled depends on sourceType: a melted-gold receipt carries lab/hallmark and
|
||||
@@ -997,6 +1040,7 @@ model UserType {
|
||||
|
||||
permissions Permission[]
|
||||
adminUsers AdminUser[]
|
||||
admins Admin[]
|
||||
|
||||
@@map("user_type")
|
||||
}
|
||||
@@ -1251,8 +1295,8 @@ model BranchAllocation {
|
||||
branch Branch @relation(fields: [branchId], references: [id])
|
||||
|
||||
/// Legacy `allocator_user` — the admin who filed the allocation.
|
||||
allocatorAdminUserId String?
|
||||
allocatorAdminUser AdminUser? @relation("AllocationAuthor", fields: [allocatorAdminUserId], references: [id])
|
||||
allocatorAdminId String? @map("allocatorAdminUserId")
|
||||
allocatorAdmin Admin? @relation("AllocationAuthor", fields: [allocatorAdminId], references: [id])
|
||||
|
||||
note String?
|
||||
createdAt DateTime @default(now())
|
||||
@@ -1299,8 +1343,8 @@ model SmsMessage {
|
||||
/// How many numbers the send actually resolved to.
|
||||
sentCount Int @default(0)
|
||||
|
||||
sentByAdminUserId String?
|
||||
sentByAdminUser AdminUser? @relation("SmsAuthor", fields: [sentByAdminUserId], references: [id])
|
||||
sentByAdminId String? @map("sentByAdminUserId")
|
||||
sentByAdmin Admin? @relation("SmsAuthor", fields: [sentByAdminId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -1313,8 +1357,8 @@ model Notification {
|
||||
body String
|
||||
audience MessageAudience @default(ALL_CUSTOMERS)
|
||||
|
||||
sentByAdminUserId String?
|
||||
sentByAdminUser AdminUser? @relation("NotificationAuthor", fields: [sentByAdminUserId], references: [id])
|
||||
sentByAdminId String? @map("sentByAdminUserId")
|
||||
sentByAdmin Admin? @relation("NotificationAuthor", fields: [sentByAdminId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -1379,8 +1423,8 @@ model UploadedFile {
|
||||
/// 'customer.nationalId'. Kept loose so a new form needs no schema change.
|
||||
purpose String?
|
||||
|
||||
uploadedByAdminUserId String?
|
||||
uploadedByAdminUser AdminUser? @relation("FileUploader", fields: [uploadedByAdminUserId], references: [id])
|
||||
uploadedByAdminId String? @map("uploadedByAdminUserId")
|
||||
uploadedByAdmin Admin? @relation("FileUploader", fields: [uploadedByAdminId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
customerAvatars Customer[] @relation("CustomerAvatar")
|
||||
|
||||
Reference in New Issue
Block a user