2025-12-02 18:56:38 +09:00
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// Prisma Schema - Customer Portal BFF
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// IMPORTANT: When building Docker images, the Prisma client must be regenerated
|
|
|
|
|
|
// from the production directory structure. See prisma/README.md for details.
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
2025-08-20 18:02:50 +09:00
|
|
|
|
generator client {
|
2025-12-02 10:05:11 +09:00
|
|
|
|
provider = "prisma-client-js"
|
|
|
|
|
|
// Only include engines we actually need:
|
|
|
|
|
|
// - native: for local development
|
|
|
|
|
|
// - linux-musl-openssl-3.0.x: for Alpine production
|
|
|
|
|
|
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
2025-08-20 18:02:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
datasource db {
|
|
|
|
|
|
provider = "postgresql"
|
2025-12-11 14:00:54 +09:00
|
|
|
|
// Prisma 7+: URL is configured via prisma.config.ts for migrations
|
|
|
|
|
|
// and via --url flag for studio. Runtime uses the adapter in PrismaClient.
|
2025-08-20 18:02:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model User {
|
2025-12-11 14:00:54 +09:00
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
email String @unique
|
|
|
|
|
|
passwordHash String? @map("password_hash")
|
|
|
|
|
|
role UserRole @default(USER)
|
|
|
|
|
|
|
2025-10-07 17:38:39 +09:00
|
|
|
|
// Authentication state only - profile data comes from WHMCS
|
2025-12-11 14:00:54 +09:00
|
|
|
|
mfaSecret String? @map("mfa_secret")
|
|
|
|
|
|
emailVerified Boolean @default(false) @map("email_verified")
|
|
|
|
|
|
failedLoginAttempts Int @default(0) @map("failed_login_attempts")
|
|
|
|
|
|
lockedUntil DateTime? @map("locked_until")
|
|
|
|
|
|
lastLoginAt DateTime? @map("last_login_at")
|
|
|
|
|
|
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
2025-09-06 10:01:44 +09:00
|
|
|
|
auditLogs AuditLog[]
|
|
|
|
|
|
idMapping IdMapping?
|
2025-12-18 18:12:20 +09:00
|
|
|
|
residenceCardSubmission ResidenceCardSubmission?
|
2025-12-23 11:36:44 +09:00
|
|
|
|
notifications Notification[]
|
2025-08-20 18:02:50 +09:00
|
|
|
|
|
|
|
|
|
|
@@map("users")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model IdMapping {
|
2025-09-06 10:01:44 +09:00
|
|
|
|
userId String @id @map("user_id")
|
|
|
|
|
|
whmcsClientId Int @unique @map("whmcs_client_id")
|
|
|
|
|
|
sfAccountId String? @map("sf_account_id")
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
2025-08-20 18:02:50 +09:00
|
|
|
|
|
|
|
|
|
|
@@map("id_mappings")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model AuditLog {
|
2025-09-06 10:01:44 +09:00
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
userId String? @map("user_id")
|
2025-08-20 18:02:50 +09:00
|
|
|
|
action AuditAction
|
2025-09-06 10:01:44 +09:00
|
|
|
|
resource String?
|
|
|
|
|
|
details Json?
|
|
|
|
|
|
ipAddress String? @map("ip_address")
|
|
|
|
|
|
userAgent String? @map("user_agent")
|
|
|
|
|
|
success Boolean @default(true)
|
|
|
|
|
|
error String?
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
user User? @relation(fields: [userId], references: [id])
|
2025-08-20 18:02:50 +09:00
|
|
|
|
|
|
|
|
|
|
@@index([userId, action])
|
|
|
|
|
|
@@index([action, createdAt])
|
2025-09-06 10:01:44 +09:00
|
|
|
|
@@index([createdAt])
|
2025-08-20 18:02:50 +09:00
|
|
|
|
@@map("audit_logs")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-06 10:01:44 +09:00
|
|
|
|
enum UserRole {
|
|
|
|
|
|
USER
|
|
|
|
|
|
ADMIN
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 18:02:50 +09:00
|
|
|
|
enum AuditAction {
|
|
|
|
|
|
LOGIN_SUCCESS
|
|
|
|
|
|
LOGIN_FAILED
|
|
|
|
|
|
LOGOUT
|
|
|
|
|
|
SIGNUP
|
|
|
|
|
|
PASSWORD_RESET
|
|
|
|
|
|
PASSWORD_CHANGE
|
|
|
|
|
|
ACCOUNT_LOCKED
|
|
|
|
|
|
ACCOUNT_UNLOCKED
|
|
|
|
|
|
PROFILE_UPDATE
|
|
|
|
|
|
MFA_ENABLED
|
|
|
|
|
|
MFA_DISABLED
|
|
|
|
|
|
API_ACCESS
|
2025-09-26 18:28:47 +09:00
|
|
|
|
SYSTEM_MAINTENANCE
|
2025-08-20 18:02:50 +09:00
|
|
|
|
}
|
2025-09-06 10:01:52 +09:00
|
|
|
|
|
2025-12-18 18:12:20 +09:00
|
|
|
|
enum ResidenceCardStatus {
|
|
|
|
|
|
PENDING
|
|
|
|
|
|
VERIFIED
|
|
|
|
|
|
REJECTED
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model ResidenceCardSubmission {
|
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
userId String @unique @map("user_id")
|
|
|
|
|
|
status ResidenceCardStatus @default(PENDING)
|
|
|
|
|
|
filename String
|
|
|
|
|
|
mimeType String @map("mime_type")
|
|
|
|
|
|
sizeBytes Int @map("size_bytes")
|
|
|
|
|
|
content Bytes @db.ByteA
|
|
|
|
|
|
submittedAt DateTime @default(now()) @map("submitted_at")
|
|
|
|
|
|
reviewedAt DateTime? @map("reviewed_at")
|
|
|
|
|
|
reviewerNotes String? @map("reviewer_notes")
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@map("residence_card_submissions")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-06 10:01:52 +09:00
|
|
|
|
// Per-SIM daily usage snapshot used to build full-month charts
|
|
|
|
|
|
model SimUsageDaily {
|
|
|
|
|
|
id Int @id @default(autoincrement())
|
|
|
|
|
|
account String
|
|
|
|
|
|
date DateTime @db.Date
|
|
|
|
|
|
usageMb Float
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([account, date])
|
|
|
|
|
|
@@index([account, date])
|
|
|
|
|
|
@@map("sim_usage_daily")
|
|
|
|
|
|
}
|
2025-11-22 18:11:43 +09:00
|
|
|
|
|
|
|
|
|
|
model SimVoiceOptions {
|
|
|
|
|
|
account String @id
|
|
|
|
|
|
voiceMailEnabled Boolean @default(false) @map("voice_mail_enabled")
|
|
|
|
|
|
callWaitingEnabled Boolean @default(false) @map("call_waiting_enabled")
|
|
|
|
|
|
internationalRoamingEnabled Boolean @default(false) @map("international_roaming_enabled")
|
|
|
|
|
|
networkType String @default("4G") @map("network_type")
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
|
|
|
|
|
|
|
|
@@map("sim_voice_options")
|
|
|
|
|
|
}
|
2025-11-29 16:42:08 +09:00
|
|
|
|
|
|
|
|
|
|
// Call history from SFTP monthly imports (domestic calls)
|
|
|
|
|
|
model SimCallHistoryDomestic {
|
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
account String // Customer phone number (e.g., "08077052946")
|
|
|
|
|
|
callDate DateTime @db.Date @map("call_date") // Date the call was made
|
|
|
|
|
|
callTime String @map("call_time") // Start time of the call (HHMMSS)
|
|
|
|
|
|
calledTo String @map("called_to") // Phone number called
|
|
|
|
|
|
location String? // Location info
|
|
|
|
|
|
durationSec Int @map("duration_sec") // Duration in seconds (320 = 32.0 sec)
|
|
|
|
|
|
chargeYen Int @map("charge_yen") // Call charge in JPY
|
|
|
|
|
|
month String // YYYY-MM format for filtering
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([account, callDate, callTime, calledTo])
|
|
|
|
|
|
@@index([account, month])
|
|
|
|
|
|
@@index([account, callDate])
|
|
|
|
|
|
@@map("sim_call_history_domestic")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Call history from SFTP monthly imports (international calls)
|
|
|
|
|
|
model SimCallHistoryInternational {
|
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
account String // Customer phone number
|
|
|
|
|
|
callDate DateTime @db.Date @map("call_date") // Date the call was made
|
|
|
|
|
|
startTime String @map("start_time") // Start time of the call
|
|
|
|
|
|
stopTime String? @map("stop_time") // Stop time (if available)
|
|
|
|
|
|
country String? // Country/location for international calls
|
|
|
|
|
|
calledTo String @map("called_to") // Phone number called
|
|
|
|
|
|
durationSec Int @map("duration_sec") // Duration in seconds
|
|
|
|
|
|
chargeYen Int @map("charge_yen") // Call charge in JPY
|
|
|
|
|
|
month String // YYYY-MM format for filtering
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([account, callDate, startTime, calledTo])
|
|
|
|
|
|
@@index([account, month])
|
|
|
|
|
|
@@index([account, callDate])
|
|
|
|
|
|
@@map("sim_call_history_international")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SMS history from SFTP monthly imports
|
|
|
|
|
|
model SimSmsHistory {
|
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
account String // Customer phone number
|
|
|
|
|
|
smsDate DateTime @db.Date @map("sms_date") // Date the SMS was sent
|
|
|
|
|
|
smsTime String @map("sms_time") // Time the SMS was sent
|
|
|
|
|
|
sentTo String @map("sent_to") // Phone number SMS was sent to
|
|
|
|
|
|
smsType SmsType @default(DOMESTIC) @map("sms_type") // SMS or 国際SMS
|
|
|
|
|
|
month String // YYYY-MM format for filtering
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([account, smsDate, smsTime, sentTo])
|
|
|
|
|
|
@@index([account, month])
|
|
|
|
|
|
@@index([account, smsDate])
|
|
|
|
|
|
@@map("sim_sms_history")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum SmsType {
|
|
|
|
|
|
DOMESTIC // SMS
|
|
|
|
|
|
INTERNATIONAL // 国際SMS
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Track which months have been imported
|
|
|
|
|
|
model SimHistoryImport {
|
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
month String @unique // YYYY-MM format
|
|
|
|
|
|
talkFile String? @map("talk_file") // Filename imported
|
|
|
|
|
|
smsFile String? @map("sms_file") // Filename imported
|
|
|
|
|
|
talkRecords Int @default(0) @map("talk_records") // Records imported
|
|
|
|
|
|
smsRecords Int @default(0) @map("sms_records") // Records imported
|
|
|
|
|
|
importedAt DateTime @default(now()) @map("imported_at")
|
|
|
|
|
|
status String @default("completed") // completed, failed, partial
|
|
|
|
|
|
|
|
|
|
|
|
@@map("sim_history_imports")
|
|
|
|
|
|
}
|
2025-12-23 11:36:44 +09:00
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
// Notifications - In-app notifications synced with Salesforce email triggers
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
model Notification {
|
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
userId String @map("user_id")
|
|
|
|
|
|
|
|
|
|
|
|
// Notification content
|
|
|
|
|
|
type NotificationType
|
|
|
|
|
|
title String
|
|
|
|
|
|
message String?
|
|
|
|
|
|
|
|
|
|
|
|
// Action (optional CTA button)
|
|
|
|
|
|
actionUrl String? @map("action_url")
|
|
|
|
|
|
actionLabel String? @map("action_label")
|
|
|
|
|
|
|
|
|
|
|
|
// Source tracking for deduplication
|
|
|
|
|
|
source NotificationSource @default(SALESFORCE)
|
|
|
|
|
|
sourceId String? @map("source_id") // SF Account ID, Order ID, etc.
|
|
|
|
|
|
|
|
|
|
|
|
// Status
|
|
|
|
|
|
read Boolean @default(false)
|
|
|
|
|
|
readAt DateTime? @map("read_at")
|
|
|
|
|
|
dismissed Boolean @default(false)
|
|
|
|
|
|
|
|
|
|
|
|
// Timestamps
|
|
|
|
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
|
|
expiresAt DateTime @map("expires_at") // 30 days from creation
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@index([userId, read, dismissed])
|
|
|
|
|
|
@@index([userId, createdAt])
|
|
|
|
|
|
@@index([expiresAt])
|
|
|
|
|
|
@@map("notifications")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum NotificationType {
|
|
|
|
|
|
ELIGIBILITY_ELIGIBLE
|
|
|
|
|
|
ELIGIBILITY_INELIGIBLE
|
|
|
|
|
|
VERIFICATION_VERIFIED
|
|
|
|
|
|
VERIFICATION_REJECTED
|
|
|
|
|
|
ORDER_APPROVED
|
|
|
|
|
|
ORDER_ACTIVATED
|
|
|
|
|
|
ORDER_FAILED
|
|
|
|
|
|
CANCELLATION_SCHEDULED
|
|
|
|
|
|
CANCELLATION_COMPLETE
|
|
|
|
|
|
PAYMENT_METHOD_EXPIRING
|
|
|
|
|
|
INVOICE_DUE
|
|
|
|
|
|
SYSTEM_ANNOUNCEMENT
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum NotificationSource {
|
|
|
|
|
|
SALESFORCE
|
|
|
|
|
|
WHMCS
|
|
|
|
|
|
PORTAL
|
|
|
|
|
|
SYSTEM
|
|
|
|
|
|
}
|