schema.prisma
· 26 KiB · Text
Raw
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
previewFeatures = []
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
/**
* Enums
*/
// Enums for Document
enum DocumentType {
Design_Development
Detail_Engineering_Design
For_Construction
Shop_Drawing
Sitemap
As_Build_Drawing
Work_Permit
Method_Of_Work
Material_Testing
Site_Instruction
}
enum VersionStatus {
IN_REVIEW
APPROVED
APPROVED_WITH_NOTES
NEED_IMPROVEMENT
REJECTED
REJECTED_WITH_NOTES
REJECTED_RESUBMISSION
SCHEDULED // version reserved on document-service for a not-yet-sent scheduled transmittal
}
enum RevisionStatus {
IN_REVIEW
APPROVED
APPROVED_WITH_NOTES
NEED_IMPROVEMENT
REJECTED
REJECTED_WITH_NOTES
REJECTED_RESUBMISSION
}
enum DocumentPageStatus {
queue // Initial state
extracting_preview // Phase 1: Extracting preview (150 DPI)
preview_ready // Phase 1 complete: Preview available
extracting_quality // Phase 2: Extracting full quality
compressing // Optional: WebP compression
ready // Final state: Full quality available
}
// Enums for Queue
enum QueueDownloadStatus {
PENDING
IN_PROGRESS
COMPLETED
FAILED
EXPIRED
}
enum QueueDownloadType {
DIRECTORY
DOCUMENT
}
enum QueueExtractorStatus {
PENDING
IN_PROGRESS
COMPLETED
FAILED
EXPIRED
}
// Enums for Change Log (CDC)
enum Operation {
CREATE
UPDATE
DELETE
}
enum AutodeskTranslationStatus {
PENDING
IN_PROGRESS
SUCCESS
FAILED
}
/**
* Models
*/
model Folder {
id String @id @unique @db.VarChar(50)
projectId String @db.VarChar(100)
parentId String? @db.VarChar(50)
modelType DocumentType?
name String @db.VarChar(255)
description String? @db.VarChar(500)
companyId String? @db.VarChar(50)
createdBy String? @db.VarChar(150)
createdAt DateTime @default(now())
updatedBy String? @db.VarChar(150)
updatedAt DateTime?
inTrash Boolean @default(false)
parent Folder? @relation("ParentChildren", fields: [parentId], references: [id], onDelete: Cascade)
children Folder[] @relation("ParentChildren")
documentVersions Version[]
documentRevisions Revision[]
@@index([projectId, parentId, modelType, companyId])
@@map("folders")
}
model Document {
id String @id @unique @db.VarChar(50)
projectId String @db.VarChar(100)
companyId String? @db.VarChar(50)
modelType DocumentType?
filename String @db.VarChar(255)
inTrash Boolean @default(false)
activeVersionId String? @unique @db.VarChar(50)
activeRevisionId String? @unique @db.VarChar(50)
activeTransmittalId String? @db.VarChar(255)
uploadedBy String? @db.VarChar(150)
uploadedAt DateTime? @default(now())
updatedBy String? @db.VarChar(150)
updatedAt DateTime?
activeVersion Version? @relation("DocumentActiveVersion", fields: [activeVersionId], references: [id])
activeRevision Revision? @relation("DocumentActiveRevision", fields: [activeRevisionId], references: [id])
versions Version[] @relation("DocumentVersions")
revisions Revision[] @relation("DocumentRevisions")
relations Document[] @relation("DocumentRelation")
related Document[] @relation("DocumentRelation")
comments DocumentComments[]
pages DocumentPages[]
document3DMetadata Document3DMetadata[] @relation("Document3DMetadatas")
@@index([projectId, modelType, activeTransmittalId])
@@map("documents")
}
model DocumentPages {
id String @id @unique @db.VarChar(50)
documentId String @db.VarChar(50)
versionId String @db.VarChar(50)
revisionId String @db.VarChar(50)
queueId String? @db.VarChar(50) // Optional: tracks which queue job created this page
status DocumentPageStatus @default(queue)
pageOrder Int @default(0)
pageName String? @db.VarChar(255)
pageType String? @db.VarChar(255)
pageContent String? @db.Text
pageWidth Float @default(0)
pageHeight Float @default(0)
filePath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths
fileSize Float @default(0)
fileExtension String? @db.VarChar(10)
fileMimeType String? @db.VarChar(255)
// DPI tracking for two-phase extraction
dpi Int @default(150) // Current DPI (150 for preview, 300-600 for quality)
targetDpi Int? // Target DPI detected from PDF analysis
extractionPreset String? @db.VarChar(50) // 'preview' | 'technical' | 'technical_archival' | 'graphics'
// Dual file paths for preview/quality images
previewPath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths
qualityPath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths
compressed Boolean @default(false)
createdBy String? @db.VarChar(150)
createdAt DateTime @default(now())
updatedBy String? @db.VarChar(150)
updatedAt DateTime? @updatedAt
// Relations
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
version Version @relation(fields: [versionId], references: [id], onDelete: Cascade)
revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade)
queue QueueExtractor? @relation(fields: [queueId], references: [id], onDelete: SetNull)
@@index([documentId])
@@index([versionId])
@@index([revisionId])
@@index([queueId])
@@index([documentId, pageOrder])
@@index([status])
@@index([documentId, versionId, revisionId])
@@map("document_pages")
}
model DocumentComments {
id String @id @unique @db.VarChar(50)
parentId String? @db.VarChar(50) // For nested comments/replies
documentId String @db.VarChar(50)
comments String @db.Text
editorData String? @db.Text // JSON data from Editor.js
editorConfig String? @db.Text // Editor.js configuration
createdBy String? @db.VarChar(150)
createdAt DateTime @default(now())
updatedBy String? @db.VarChar(150)
updatedAt DateTime?
deletedBy String? @db.VarChar(150)
deletedAt DateTime?
// Relations
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
parent DocumentComments? @relation("DocumentCommentReplies", fields: [parentId], references: [id], onDelete: Restrict)
replies DocumentComments[] @relation("DocumentCommentReplies")
attachments DocumentCommentAttachments[]
@@index([documentId])
@@index([parentId])
@@index([createdBy])
@@index([deletedAt]) // For soft delete queries
@@map("document_comments")
}
model DocumentCommentAttachments {
id String @id @unique @db.VarChar(50)
commentId String @db.VarChar(50)
fileName String @db.VarChar(255)
path String @db.Text
extension String @db.VarChar(10)
createdBy String? @db.VarChar(150)
createdAt DateTime @default(now())
updatedBy String? @db.VarChar(150)
updatedAt DateTime?
comment DocumentComments @relation(fields: [commentId], references: [id], onDelete: Cascade)
@@index([commentId])
@@map("document_comment_attachments")
}
// -------------------------------------
// 3D Model Metadata (Autodesk APS)
// -------------------------------------
model Document3DMetadata {
id String @id @unique @db.VarChar(50)
documentId String @db.VarChar(50)
versionId String @db.VarChar(50)
revisionId String @unique @db.VarChar(50)
s3Key String @db.VarChar(512)
autodeskObjectId String? @db.Text
autodeskUrn String? @db.Text // autodeskObjectId Base64 encoded URN untuk Viewer
bucketKey String @db.VarChar(100)
// Translation / Conversion Status
translationStatus AutodeskTranslationStatus @default(PENDING)
translationProgress String? @db.VarChar(50)
translationErrorMessage String? @db.Text
createdBy String? @db.VarChar(150) // uuid
createdAt DateTime @default(now())
updatedBy String? @db.VarChar(150) // uuid
updatedAt DateTime? @updatedAt
document Document @relation(name: "Document3DMetadatas", fields: [documentId], references: [id], onDelete: Cascade)
version Version @relation(name: "Version3DMetadatas", fields: [versionId], references: [id], onDelete: Cascade)
revision Revision @relation(name: "Revision3DMetadata", fields: [revisionId], references: [id], onDelete: Cascade)
@@index([documentId])
@@index([translationStatus])
@@map("document_3d_metadatas")
}
model Version {
id String @id @unique @db.VarChar(50)
key String @unique
documentId String @db.VarChar(50)
folderId String? @db.VarChar(50)
version Int @default(0)
filePath String @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths
fileSize Float
fileHash Json? // { value: string, type: 'sha256', calculatedAt: ISO datetime }
status VersionStatus?
remark String? @db.Text
description String? @db.Text
companyId String? @db.VarChar(50)
inTrash Boolean @default(false)
transmittalId String? @db.VarChar(255)
transmittalStatus String? @db.VarChar(255)
transmittalTimestamp DateTime?
publishedRevisionId String? @unique @db.VarChar(50) // Track which revision was published for this version
archivedBy String? @db.VarChar(150)
archivedAt Int? @db.UnsignedInt
approvedBy String? @db.VarChar(150)
approvedAt Int? @db.UnsignedInt
uploadedBy String? @db.VarChar(150)
uploadedAt DateTime @default(now())
updatedBy String? @db.VarChar(150)
updatedAt DateTime?
folder Folder? @relation(fields: [folderId], references: [id])
document Document @relation("DocumentVersions", fields: [documentId], references: [id], onDelete: Cascade)
documentActive Document? @relation("DocumentActiveVersion")
publishedRevision Revision? @relation("VersionPublishedRevision", fields: [publishedRevisionId], references: [id])
revisions Revision[] @relation("VersionRevisions")
pages DocumentPages[]
document3DMetadatas Document3DMetadata[] @relation("Version3DMetadatas")
@@index([documentId, folderId, version, companyId, transmittalId])
@@map("versions")
}
model Revision {
id String @id @unique @db.VarChar(50)
key String @unique
documentId String @db.VarChar(50)
folderId String? @db.VarChar(50)
versionId String @db.VarChar(50) // Reference to the version this revision belongs to
revision Int @default(0)
filePath String @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths
fileSize Float
fileHash Json? // { value: string, type: 'sha256', calculatedAt: ISO datetime }
status RevisionStatus @default(IN_REVIEW)
remark String? @db.Text
description String? @db.Text
companyId String? @db.VarChar(50)
inTrash Boolean @default(false)
transmittalId String? @db.VarChar(255)
transmittalStatus String? @db.VarChar(255)
transmittalTimestamp DateTime?
archivedBy String? @db.VarChar(150)
archivedAt Int? @db.UnsignedInt
approvedBy String? @db.VarChar(150)
approvedAt Int? @db.UnsignedInt
uploadedBy String? @db.VarChar(150)
uploadedAt DateTime @default(now())
updatedBy String? @db.VarChar(150)
updatedAt DateTime?
folder Folder? @relation(fields: [folderId], references: [id])
version Version @relation("VersionRevisions", fields: [versionId], references: [id], onDelete: Cascade)
document Document @relation("DocumentRevisions", fields: [documentId], references: [id], onDelete: Cascade)
documentActive Document? @relation("DocumentActiveRevision")
versionPublished Version? @relation("VersionPublishedRevision")
pages DocumentPages[]
document3DMetadata Document3DMetadata? @relation("Revision3DMetadata")
@@index([documentId, folderId, versionId, revision, companyId, transmittalId])
@@map("revisions")
}
model QueueDownload {
id String @id @unique @db.VarChar(50)
projectId String @db.VarChar(100)
companyId String? @db.VarChar(50)
directoryId String? @db.VarChar(50) // Made optional for document type
modelType DocumentType?
// Queue type and data for flexible downloads
queueType QueueDownloadType @default(DIRECTORY)
queueData Json? // Store the flexible data array as JSON
archiveName String? @db.VarChar(255) // Store the generated archive name
status QueueDownloadStatus @default(PENDING)
progress Float @default(0) // 0-100 percentage
// Metadata
totalSize BigInt? // Total size in bytes
compressedSize BigInt? // Final compressed size in bytes
totalDirectories Int? // Total directories count
totalDocuments Int? // Total documents count
// File paths
workingPath String? @db.VarChar(500) // Temporary working directory path
finalPath String? @db.VarChar(500) // Final compressed file path
downloadUrl String? @db.VarChar(500) // Download URL/path
// Timestamps
requestedBy String? @db.VarChar(150)
requestedAt DateTime @default(now())
startedAt DateTime?
completedAt DateTime?
expiresAt DateTime? // Auto-expire after 1 day
// Error handling
errorMessage String? @db.Text
retryCount Int @default(0)
@@index([projectId, companyId, status])
@@index([status, expiresAt])
@@index([queueType])
@@map("queue_downloads")
}
model QueueExtractor {
id String @id @unique @db.VarChar(50)
projectId String @db.VarChar(100)
companyId String? @db.VarChar(50)
queueType String? // Null for now as per requirement
queueData Json // Store document info: { documentId, versionId, revisionId?, modelType? }
status QueueExtractorStatus @default(PENDING)
progress Float @default(0) // 0-100 percentage
// Metadata
totalSize BigInt? // Total size in bytes
compressedSize BigInt? // Final compressed size in bytes (after WebP conversion)
totalPages Int @default(0) // Total pages extracted
// File paths
workingPath String? @db.VarChar(500) // Temporary working directory path
finalPath String? @db.VarChar(500) // Final output directory path
// Timestamps
requestedBy String? @db.VarChar(150)
requestedAt DateTime @default(now())
startedAt DateTime?
completedAt DateTime?
// Error handling
errorMessage String? @db.Text
retryCount Int @default(0)
// Phase tracking for two-phase extraction
queuePhase String? @db.VarChar(20) // 'preview' | 'quality'
detectedDpi Int? // Max DPI detected from pdfimages -list
detectedType String? @db.VarChar(50) // 'technical' | 'graphics' | 'mixed'
selectedPreset String? @db.VarChar(50) // Final preset used for extraction
// Relations
pages DocumentPages[]
@@index([projectId, companyId, status])
@@index([status])
@@map("queue_extractors")
}
model ChangeLog {
id String @id @default(cuid())
tableName String
recordId String
operation Operation
timestamp DateTime @default(now())
processed Boolean @default(false)
@@map("change_logs")
}
// -------------------------------------
// Metrics scope-related models
// -------------------------------------
// Metrics for tracking the usage of the system
model Metrics {
id String @id @default(uuid())
projectId String @db.VarChar(100)
companyId String? @db.VarChar(50)
totalUsers Int @default(0) @map("total_users")
totalDocuments Int @default(0) @map("total_documents")
totalDocumentsPublish Int @default(0) @map("total_documents_publish")
totalDocumentsType Json @default("{}") @map("total_documents_type")
totalDocumentsPublishType Json @default("{}") @map("total_documents_publish_type")
totalDocumentsTrashed Int @default(0) @map("total_documents_trashed")
totalDocumentsTrashedType Json @default("{}") @map("total_documents_trashed_type")
totalDocumentsExtracted Int @default(0) @map("total_documents_extracted")
totalStatus Json @default("{}") @map("total_status")
totalStatusType Json @default("{}") @map("total_status_type")
totalStatusPublish Json @default("{}") @map("total_status_publish")
totalStatusPublishType Json @default("{}") @map("total_status_publish_type")
totalDirectories Int @default(0) @map("total_directories")
totalDirectoriesTrashed Int @default(0) @map("total_directories_trashed")
totalVersions Int @default(0) @map("total_versions")
totalRevisions Int @default(0) @map("total_revisions")
totalDocumentComments Int @default(0) @map("total_doc_comments")
totalDocumentCommentReplies Int @default(0) @map("total_doc_comment_replies")
totalDocumentCommentAttachments Int @default(0) @map("total_doc_comment_attachments")
usedStorage Float @default(0.0) @map("used_storage")
usedStorageFormatted String @default("0 B") @map("used_storage_formatted") @db.VarChar(100)
usedStorageDetailed Json @default("{}") @map("used_storage_detailed")
createdAt DateTime @default(now())
updatedAt DateTime?
deletedAt DateTime?
@@index([projectId, companyId])
@@index([projectId, createdAt])
@@index([projectId, updatedAt])
@@index([projectId, companyId, deletedAt])
@@map("metrics")
}
// -------------------------------------
// Audit log scope-related models
// -------------------------------------
// Audit logs for security tracking
model AuditLog {
id String @id @default(uuid())
user_id String?
action String // 'login', 'logout', 'create', 'update', 'delete', 'view'
resource String? // table name or resource type
resource_id String?
old_values Json?
new_values Json?
ip_address String?
user_agent String?
location String? // geo location
status String @default("success") // 'success', 'failed', 'blocked'
created_at DateTime @default(now())
@@index([user_id])
@@index([action])
@@index([resource])
@@index([resource_id])
@@index([status])
@@index([created_at])
@@map("audit_logs")
}
// ============================================================================
// MPP Gantt Cache
// Menyimpan hasil raw ekstraksi Aspose Tasks Cloud agar tidak perlu
// memanggil API Aspose setiap kali view Gantt dibuka.
//
// Hierarki relasi:
// MppSnapshot (1) ──< MppTaskItem (tasks.taskItem[])
// ──< MppTaskDetail (taskDetails[])
// ──< MppTaskLink (taskLinks[])
// ──< MppResource (resources.resourceItem[])
// ──< MppTaskAssignment (taskAssignments[].list[] — di-flatten)
// ============================================================================
model MppSnapshot {
id String @id @default(uuid()) @db.VarChar(50)
projectId String @db.VarChar(100)
fileName String @db.VarChar(255)
/// Path folder di Aspose Cloud Storage (opsional)
folder String? @db.VarChar(500)
extractedAt DateTime @default(now())
taskItems MppTaskItem[]
taskDetails MppTaskDetail[]
taskLinks MppTaskLink[]
resources MppResource[]
taskAssignments MppTaskAssignment[]
/// Satu entri cache per kombinasi projectId + fileName
@@unique([projectId, fileName], name: "projectId_fileName")
@@index([projectId])
@@map("mpp_snapshots")
}
model MppTaskItemLink {
id String @id @default(uuid()) @db.VarChar(50)
href String
rel String
type String?
title String?
mppTaskItems MppTaskItem[]
@@map("mpp_task_item_links")
}
/// tasks.taskItem[] — daftar ringkas task (uid, nama, tanggal, durasi)
model MppTaskItem {
id String @id @default(uuid()) @db.VarChar(50)
snapshotId String @db.VarChar(50)
uid Int
/// id (bukan uid) dari Aspose — urutan task di dalam file
asposeId Int @map("aspose_id")
name String @db.VarChar(500)
start DateTime
finish DateTime
duration String @db.VarChar(50)
link MppTaskItemLink @relation(fields: [mppTaskItemLinkId], references: [id])
snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade)
mppTaskItemLinkId String @db.VarChar(50)
@@unique([snapshotId, uid])
@@index([snapshotId])
@@map("mpp_task_items")
}
/// taskDetails[] — detail lengkap per task termasuk hierarki dan prioritas
model MppTaskDetail {
id String @id @default(uuid()) @db.VarChar(50)
snapshotId String @db.VarChar(50)
uid Int
asposeId Int @map("aspose_id")
name String @db.VarChar(500)
start DateTime
finish DateTime
duration String @db.VarChar(50)
/// 0–100
percentComplete Int @default(0) @map("percent_complete")
/// Numerik Aspose: 500=Normal, 700=High, 300=Low, dst.
priority Int @default(500)
isSummary Boolean @default(false) @map("is_summary")
isMilestone Boolean @default(false) @map("is_milestone")
outlineLevel Int @default(0) @map("outline_level")
outlineNumber String? @map("outline_number") @db.VarChar(50)
wbs String? @db.VarChar(50)
/// Array uid child task, disimpan sebagai JSON int[].
/// Kunci untuk invert parent-child di aggregator.
subtasksUids Json @map("subtasks_uids")
/// Objek Task lengkap dari Aspose — untuk keperluan di masa depan
/// tanpa harus memanggil API lagi.
rawData Json @map("raw_data")
snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade)
@@unique([snapshotId, uid])
@@index([snapshotId])
@@map("mpp_task_details")
}
/// taskLinks[] — dependency antar task (FinishToStart, dll.)
model MppTaskLink {
id String @id @default(uuid()) @db.VarChar(50)
snapshotId String @db.VarChar(50)
/// Index 1-based dari Aspose (dipakai untuk update/delete link)
asposeIndex Int @map("aspose_index")
predecessorUid Int @map("predecessor_uid")
successorUid Int @map("successor_uid")
/// "FinishToStart" | "StartToStart" | "FinishToFinish" | "StartToFinish"
linkType String @map("link_type") @db.VarChar(50)
lag Int @default(0)
lagFormat String? @map("lag_format") @db.VarChar(50)
snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade)
@@unique([snapshotId, asposeIndex])
@@index([snapshotId])
@@map("mpp_task_links")
}
/// resources.resourceItem[] — daftar resource/orang dalam proyek
model MppResource {
id String @id @default(uuid()) @db.VarChar(50)
snapshotId String @db.VarChar(50)
uid Int
asposeId Int @map("aspose_id")
/// null untuk resource uid=0 (placeholder "Unassigned" bawaan Aspose)
name String? @db.VarChar(500)
snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade)
@@unique([snapshotId, uid])
@@index([snapshotId])
@@map("mpp_resources")
}
/// taskAssignments[].list[] — flatten dari semua container per task.
/// Menyimpan pasangan taskUid↔resourceUid untuk resolve PIC di aggregator.
model MppTaskAssignment {
id String @id @default(uuid()) @db.VarChar(50)
snapshotId String @db.VarChar(50)
/// uid assignment dari Aspose
uid Int
taskUid Int @map("task_uid")
resourceUid Int @map("resource_uid")
snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade)
@@unique([snapshotId, uid])
@@index([snapshotId])
@@index([snapshotId, taskUid])
@@map("mpp_task_assignments")
}
| 1 | // This is your Prisma schema file, |
| 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema |
| 3 | |
| 4 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? |
| 5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init |
| 6 | |
| 7 | generator client { |
| 8 | provider = "prisma-client-js" |
| 9 | previewFeatures = [] |
| 10 | } |
| 11 | |
| 12 | datasource db { |
| 13 | provider = "mysql" |
| 14 | url = env("DATABASE_URL") |
| 15 | } |
| 16 | |
| 17 | /** |
| 18 | * Enums |
| 19 | */ |
| 20 | // Enums for Document |
| 21 | enum DocumentType { |
| 22 | Design_Development |
| 23 | Detail_Engineering_Design |
| 24 | For_Construction |
| 25 | Shop_Drawing |
| 26 | Sitemap |
| 27 | As_Build_Drawing |
| 28 | |
| 29 | Work_Permit |
| 30 | Method_Of_Work |
| 31 | Material_Testing |
| 32 | Site_Instruction |
| 33 | } |
| 34 | |
| 35 | enum VersionStatus { |
| 36 | IN_REVIEW |
| 37 | APPROVED |
| 38 | APPROVED_WITH_NOTES |
| 39 | NEED_IMPROVEMENT |
| 40 | REJECTED |
| 41 | REJECTED_WITH_NOTES |
| 42 | REJECTED_RESUBMISSION |
| 43 | SCHEDULED // version reserved on document-service for a not-yet-sent scheduled transmittal |
| 44 | } |
| 45 | |
| 46 | enum RevisionStatus { |
| 47 | IN_REVIEW |
| 48 | APPROVED |
| 49 | APPROVED_WITH_NOTES |
| 50 | NEED_IMPROVEMENT |
| 51 | REJECTED |
| 52 | REJECTED_WITH_NOTES |
| 53 | REJECTED_RESUBMISSION |
| 54 | } |
| 55 | |
| 56 | enum DocumentPageStatus { |
| 57 | queue // Initial state |
| 58 | extracting_preview // Phase 1: Extracting preview (150 DPI) |
| 59 | preview_ready // Phase 1 complete: Preview available |
| 60 | extracting_quality // Phase 2: Extracting full quality |
| 61 | compressing // Optional: WebP compression |
| 62 | ready // Final state: Full quality available |
| 63 | } |
| 64 | |
| 65 | // Enums for Queue |
| 66 | enum QueueDownloadStatus { |
| 67 | PENDING |
| 68 | IN_PROGRESS |
| 69 | COMPLETED |
| 70 | FAILED |
| 71 | EXPIRED |
| 72 | } |
| 73 | |
| 74 | enum QueueDownloadType { |
| 75 | DIRECTORY |
| 76 | DOCUMENT |
| 77 | } |
| 78 | |
| 79 | enum QueueExtractorStatus { |
| 80 | PENDING |
| 81 | IN_PROGRESS |
| 82 | COMPLETED |
| 83 | FAILED |
| 84 | EXPIRED |
| 85 | } |
| 86 | |
| 87 | // Enums for Change Log (CDC) |
| 88 | enum Operation { |
| 89 | CREATE |
| 90 | UPDATE |
| 91 | DELETE |
| 92 | } |
| 93 | |
| 94 | enum AutodeskTranslationStatus { |
| 95 | PENDING |
| 96 | IN_PROGRESS |
| 97 | SUCCESS |
| 98 | FAILED |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Models |
| 103 | */ |
| 104 | model Folder { |
| 105 | id String @id @unique @db.VarChar(50) |
| 106 | projectId String @db.VarChar(100) |
| 107 | parentId String? @db.VarChar(50) |
| 108 | modelType DocumentType? |
| 109 | name String @db.VarChar(255) |
| 110 | description String? @db.VarChar(500) |
| 111 | companyId String? @db.VarChar(50) |
| 112 | createdBy String? @db.VarChar(150) |
| 113 | createdAt DateTime @default(now()) |
| 114 | updatedBy String? @db.VarChar(150) |
| 115 | updatedAt DateTime? |
| 116 | inTrash Boolean @default(false) |
| 117 | |
| 118 | parent Folder? @relation("ParentChildren", fields: [parentId], references: [id], onDelete: Cascade) |
| 119 | children Folder[] @relation("ParentChildren") |
| 120 | documentVersions Version[] |
| 121 | documentRevisions Revision[] |
| 122 | |
| 123 | @@index([projectId, parentId, modelType, companyId]) |
| 124 | @@map("folders") |
| 125 | } |
| 126 | |
| 127 | model Document { |
| 128 | id String @id @unique @db.VarChar(50) |
| 129 | projectId String @db.VarChar(100) |
| 130 | companyId String? @db.VarChar(50) |
| 131 | modelType DocumentType? |
| 132 | filename String @db.VarChar(255) |
| 133 | inTrash Boolean @default(false) |
| 134 | activeVersionId String? @unique @db.VarChar(50) |
| 135 | activeRevisionId String? @unique @db.VarChar(50) |
| 136 | activeTransmittalId String? @db.VarChar(255) |
| 137 | |
| 138 | uploadedBy String? @db.VarChar(150) |
| 139 | uploadedAt DateTime? @default(now()) |
| 140 | updatedBy String? @db.VarChar(150) |
| 141 | updatedAt DateTime? |
| 142 | |
| 143 | activeVersion Version? @relation("DocumentActiveVersion", fields: [activeVersionId], references: [id]) |
| 144 | activeRevision Revision? @relation("DocumentActiveRevision", fields: [activeRevisionId], references: [id]) |
| 145 | versions Version[] @relation("DocumentVersions") |
| 146 | revisions Revision[] @relation("DocumentRevisions") |
| 147 | relations Document[] @relation("DocumentRelation") |
| 148 | related Document[] @relation("DocumentRelation") |
| 149 | comments DocumentComments[] |
| 150 | pages DocumentPages[] |
| 151 | document3DMetadata Document3DMetadata[] @relation("Document3DMetadatas") |
| 152 | |
| 153 | @@index([projectId, modelType, activeTransmittalId]) |
| 154 | @@map("documents") |
| 155 | } |
| 156 | |
| 157 | model DocumentPages { |
| 158 | id String @id @unique @db.VarChar(50) |
| 159 | documentId String @db.VarChar(50) |
| 160 | versionId String @db.VarChar(50) |
| 161 | revisionId String @db.VarChar(50) |
| 162 | queueId String? @db.VarChar(50) // Optional: tracks which queue job created this page |
| 163 | status DocumentPageStatus @default(queue) |
| 164 | |
| 165 | pageOrder Int @default(0) |
| 166 | pageName String? @db.VarChar(255) |
| 167 | pageType String? @db.VarChar(255) |
| 168 | pageContent String? @db.Text |
| 169 | pageWidth Float @default(0) |
| 170 | pageHeight Float @default(0) |
| 171 | |
| 172 | filePath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths |
| 173 | fileSize Float @default(0) |
| 174 | fileExtension String? @db.VarChar(10) |
| 175 | fileMimeType String? @db.VarChar(255) |
| 176 | |
| 177 | // DPI tracking for two-phase extraction |
| 178 | dpi Int @default(150) // Current DPI (150 for preview, 300-600 for quality) |
| 179 | targetDpi Int? // Target DPI detected from PDF analysis |
| 180 | extractionPreset String? @db.VarChar(50) // 'preview' | 'technical' | 'technical_archival' | 'graphics' |
| 181 | |
| 182 | // Dual file paths for preview/quality images |
| 183 | previewPath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths |
| 184 | qualityPath String? @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths |
| 185 | |
| 186 | compressed Boolean @default(false) |
| 187 | |
| 188 | createdBy String? @db.VarChar(150) |
| 189 | createdAt DateTime @default(now()) |
| 190 | updatedBy String? @db.VarChar(150) |
| 191 | updatedAt DateTime? @updatedAt |
| 192 | |
| 193 | // Relations |
| 194 | document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) |
| 195 | version Version @relation(fields: [versionId], references: [id], onDelete: Cascade) |
| 196 | revision Revision @relation(fields: [revisionId], references: [id], onDelete: Cascade) |
| 197 | queue QueueExtractor? @relation(fields: [queueId], references: [id], onDelete: SetNull) |
| 198 | |
| 199 | @@index([documentId]) |
| 200 | @@index([versionId]) |
| 201 | @@index([revisionId]) |
| 202 | @@index([queueId]) |
| 203 | @@index([documentId, pageOrder]) |
| 204 | @@index([status]) |
| 205 | @@index([documentId, versionId, revisionId]) |
| 206 | @@map("document_pages") |
| 207 | } |
| 208 | |
| 209 | model DocumentComments { |
| 210 | id String @id @unique @db.VarChar(50) |
| 211 | parentId String? @db.VarChar(50) // For nested comments/replies |
| 212 | documentId String @db.VarChar(50) |
| 213 | comments String @db.Text |
| 214 | editorData String? @db.Text // JSON data from Editor.js |
| 215 | editorConfig String? @db.Text // Editor.js configuration |
| 216 | createdBy String? @db.VarChar(150) |
| 217 | createdAt DateTime @default(now()) |
| 218 | updatedBy String? @db.VarChar(150) |
| 219 | updatedAt DateTime? |
| 220 | deletedBy String? @db.VarChar(150) |
| 221 | deletedAt DateTime? |
| 222 | |
| 223 | // Relations |
| 224 | document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) |
| 225 | parent DocumentComments? @relation("DocumentCommentReplies", fields: [parentId], references: [id], onDelete: Restrict) |
| 226 | replies DocumentComments[] @relation("DocumentCommentReplies") |
| 227 | attachments DocumentCommentAttachments[] |
| 228 | |
| 229 | @@index([documentId]) |
| 230 | @@index([parentId]) |
| 231 | @@index([createdBy]) |
| 232 | @@index([deletedAt]) // For soft delete queries |
| 233 | @@map("document_comments") |
| 234 | } |
| 235 | |
| 236 | model DocumentCommentAttachments { |
| 237 | id String @id @unique @db.VarChar(50) |
| 238 | commentId String @db.VarChar(50) |
| 239 | fileName String @db.VarChar(255) |
| 240 | path String @db.Text |
| 241 | extension String @db.VarChar(10) |
| 242 | createdBy String? @db.VarChar(150) |
| 243 | createdAt DateTime @default(now()) |
| 244 | updatedBy String? @db.VarChar(150) |
| 245 | updatedAt DateTime? |
| 246 | |
| 247 | comment DocumentComments @relation(fields: [commentId], references: [id], onDelete: Cascade) |
| 248 | |
| 249 | @@index([commentId]) |
| 250 | @@map("document_comment_attachments") |
| 251 | } |
| 252 | |
| 253 | // ------------------------------------- |
| 254 | // 3D Model Metadata (Autodesk APS) |
| 255 | // ------------------------------------- |
| 256 | model Document3DMetadata { |
| 257 | id String @id @unique @db.VarChar(50) |
| 258 | documentId String @db.VarChar(50) |
| 259 | versionId String @db.VarChar(50) |
| 260 | revisionId String @unique @db.VarChar(50) |
| 261 | |
| 262 | s3Key String @db.VarChar(512) |
| 263 | |
| 264 | autodeskObjectId String? @db.Text |
| 265 | autodeskUrn String? @db.Text // autodeskObjectId Base64 encoded URN untuk Viewer |
| 266 | bucketKey String @db.VarChar(100) |
| 267 | |
| 268 | // Translation / Conversion Status |
| 269 | translationStatus AutodeskTranslationStatus @default(PENDING) |
| 270 | translationProgress String? @db.VarChar(50) |
| 271 | translationErrorMessage String? @db.Text |
| 272 | |
| 273 | createdBy String? @db.VarChar(150) // uuid |
| 274 | createdAt DateTime @default(now()) |
| 275 | updatedBy String? @db.VarChar(150) // uuid |
| 276 | updatedAt DateTime? @updatedAt |
| 277 | |
| 278 | document Document @relation(name: "Document3DMetadatas", fields: [documentId], references: [id], onDelete: Cascade) |
| 279 | version Version @relation(name: "Version3DMetadatas", fields: [versionId], references: [id], onDelete: Cascade) |
| 280 | revision Revision @relation(name: "Revision3DMetadata", fields: [revisionId], references: [id], onDelete: Cascade) |
| 281 | |
| 282 | @@index([documentId]) |
| 283 | @@index([translationStatus]) |
| 284 | @@map("document_3d_metadatas") |
| 285 | } |
| 286 | |
| 287 | model Version { |
| 288 | id String @id @unique @db.VarChar(50) |
| 289 | key String @unique |
| 290 | documentId String @db.VarChar(50) |
| 291 | folderId String? @db.VarChar(50) |
| 292 | version Int @default(0) |
| 293 | filePath String @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths |
| 294 | fileSize Float |
| 295 | fileHash Json? // { value: string, type: 'sha256', calculatedAt: ISO datetime } |
| 296 | status VersionStatus? |
| 297 | remark String? @db.Text |
| 298 | description String? @db.Text |
| 299 | companyId String? @db.VarChar(50) |
| 300 | inTrash Boolean @default(false) |
| 301 | transmittalId String? @db.VarChar(255) |
| 302 | transmittalStatus String? @db.VarChar(255) |
| 303 | transmittalTimestamp DateTime? |
| 304 | publishedRevisionId String? @unique @db.VarChar(50) // Track which revision was published for this version |
| 305 | |
| 306 | archivedBy String? @db.VarChar(150) |
| 307 | archivedAt Int? @db.UnsignedInt |
| 308 | approvedBy String? @db.VarChar(150) |
| 309 | approvedAt Int? @db.UnsignedInt |
| 310 | uploadedBy String? @db.VarChar(150) |
| 311 | uploadedAt DateTime @default(now()) |
| 312 | updatedBy String? @db.VarChar(150) |
| 313 | updatedAt DateTime? |
| 314 | |
| 315 | folder Folder? @relation(fields: [folderId], references: [id]) |
| 316 | document Document @relation("DocumentVersions", fields: [documentId], references: [id], onDelete: Cascade) |
| 317 | documentActive Document? @relation("DocumentActiveVersion") |
| 318 | publishedRevision Revision? @relation("VersionPublishedRevision", fields: [publishedRevisionId], references: [id]) |
| 319 | revisions Revision[] @relation("VersionRevisions") |
| 320 | pages DocumentPages[] |
| 321 | document3DMetadatas Document3DMetadata[] @relation("Version3DMetadatas") |
| 322 | |
| 323 | @@index([documentId, folderId, version, companyId, transmittalId]) |
| 324 | @@map("versions") |
| 325 | } |
| 326 | |
| 327 | model Revision { |
| 328 | id String @id @unique @db.VarChar(50) |
| 329 | key String @unique |
| 330 | documentId String @db.VarChar(50) |
| 331 | folderId String? @db.VarChar(50) |
| 332 | versionId String @db.VarChar(50) // Reference to the version this revision belongs to |
| 333 | revision Int @default(0) |
| 334 | filePath String @db.VarChar(512) // Increased from 255 for SHA256 hash-based paths |
| 335 | fileSize Float |
| 336 | fileHash Json? // { value: string, type: 'sha256', calculatedAt: ISO datetime } |
| 337 | status RevisionStatus @default(IN_REVIEW) |
| 338 | remark String? @db.Text |
| 339 | description String? @db.Text |
| 340 | companyId String? @db.VarChar(50) |
| 341 | inTrash Boolean @default(false) |
| 342 | transmittalId String? @db.VarChar(255) |
| 343 | transmittalStatus String? @db.VarChar(255) |
| 344 | transmittalTimestamp DateTime? |
| 345 | |
| 346 | archivedBy String? @db.VarChar(150) |
| 347 | archivedAt Int? @db.UnsignedInt |
| 348 | approvedBy String? @db.VarChar(150) |
| 349 | approvedAt Int? @db.UnsignedInt |
| 350 | uploadedBy String? @db.VarChar(150) |
| 351 | uploadedAt DateTime @default(now()) |
| 352 | updatedBy String? @db.VarChar(150) |
| 353 | updatedAt DateTime? |
| 354 | |
| 355 | folder Folder? @relation(fields: [folderId], references: [id]) |
| 356 | version Version @relation("VersionRevisions", fields: [versionId], references: [id], onDelete: Cascade) |
| 357 | document Document @relation("DocumentRevisions", fields: [documentId], references: [id], onDelete: Cascade) |
| 358 | documentActive Document? @relation("DocumentActiveRevision") |
| 359 | versionPublished Version? @relation("VersionPublishedRevision") |
| 360 | pages DocumentPages[] |
| 361 | document3DMetadata Document3DMetadata? @relation("Revision3DMetadata") |
| 362 | |
| 363 | @@index([documentId, folderId, versionId, revision, companyId, transmittalId]) |
| 364 | @@map("revisions") |
| 365 | } |
| 366 | |
| 367 | model QueueDownload { |
| 368 | id String @id @unique @db.VarChar(50) |
| 369 | projectId String @db.VarChar(100) |
| 370 | companyId String? @db.VarChar(50) |
| 371 | directoryId String? @db.VarChar(50) // Made optional for document type |
| 372 | modelType DocumentType? |
| 373 | |
| 374 | // Queue type and data for flexible downloads |
| 375 | queueType QueueDownloadType @default(DIRECTORY) |
| 376 | queueData Json? // Store the flexible data array as JSON |
| 377 | archiveName String? @db.VarChar(255) // Store the generated archive name |
| 378 | |
| 379 | status QueueDownloadStatus @default(PENDING) |
| 380 | progress Float @default(0) // 0-100 percentage |
| 381 | |
| 382 | // Metadata |
| 383 | totalSize BigInt? // Total size in bytes |
| 384 | compressedSize BigInt? // Final compressed size in bytes |
| 385 | totalDirectories Int? // Total directories count |
| 386 | totalDocuments Int? // Total documents count |
| 387 | |
| 388 | // File paths |
| 389 | workingPath String? @db.VarChar(500) // Temporary working directory path |
| 390 | finalPath String? @db.VarChar(500) // Final compressed file path |
| 391 | downloadUrl String? @db.VarChar(500) // Download URL/path |
| 392 | |
| 393 | // Timestamps |
| 394 | requestedBy String? @db.VarChar(150) |
| 395 | requestedAt DateTime @default(now()) |
| 396 | startedAt DateTime? |
| 397 | completedAt DateTime? |
| 398 | expiresAt DateTime? // Auto-expire after 1 day |
| 399 | |
| 400 | // Error handling |
| 401 | errorMessage String? @db.Text |
| 402 | retryCount Int @default(0) |
| 403 | |
| 404 | @@index([projectId, companyId, status]) |
| 405 | @@index([status, expiresAt]) |
| 406 | @@index([queueType]) |
| 407 | @@map("queue_downloads") |
| 408 | } |
| 409 | |
| 410 | model QueueExtractor { |
| 411 | id String @id @unique @db.VarChar(50) |
| 412 | projectId String @db.VarChar(100) |
| 413 | companyId String? @db.VarChar(50) |
| 414 | queueType String? // Null for now as per requirement |
| 415 | queueData Json // Store document info: { documentId, versionId, revisionId?, modelType? } |
| 416 | |
| 417 | status QueueExtractorStatus @default(PENDING) |
| 418 | progress Float @default(0) // 0-100 percentage |
| 419 | |
| 420 | // Metadata |
| 421 | totalSize BigInt? // Total size in bytes |
| 422 | compressedSize BigInt? // Final compressed size in bytes (after WebP conversion) |
| 423 | totalPages Int @default(0) // Total pages extracted |
| 424 | |
| 425 | // File paths |
| 426 | workingPath String? @db.VarChar(500) // Temporary working directory path |
| 427 | finalPath String? @db.VarChar(500) // Final output directory path |
| 428 | |
| 429 | // Timestamps |
| 430 | requestedBy String? @db.VarChar(150) |
| 431 | requestedAt DateTime @default(now()) |
| 432 | startedAt DateTime? |
| 433 | completedAt DateTime? |
| 434 | |
| 435 | // Error handling |
| 436 | errorMessage String? @db.Text |
| 437 | retryCount Int @default(0) |
| 438 | |
| 439 | // Phase tracking for two-phase extraction |
| 440 | queuePhase String? @db.VarChar(20) // 'preview' | 'quality' |
| 441 | detectedDpi Int? // Max DPI detected from pdfimages -list |
| 442 | detectedType String? @db.VarChar(50) // 'technical' | 'graphics' | 'mixed' |
| 443 | selectedPreset String? @db.VarChar(50) // Final preset used for extraction |
| 444 | |
| 445 | // Relations |
| 446 | pages DocumentPages[] |
| 447 | |
| 448 | @@index([projectId, companyId, status]) |
| 449 | @@index([status]) |
| 450 | @@map("queue_extractors") |
| 451 | } |
| 452 | |
| 453 | model ChangeLog { |
| 454 | id String @id @default(cuid()) |
| 455 | tableName String |
| 456 | recordId String |
| 457 | operation Operation |
| 458 | timestamp DateTime @default(now()) |
| 459 | processed Boolean @default(false) |
| 460 | |
| 461 | @@map("change_logs") |
| 462 | } |
| 463 | |
| 464 | // ------------------------------------- |
| 465 | // Metrics scope-related models |
| 466 | // ------------------------------------- |
| 467 | // Metrics for tracking the usage of the system |
| 468 | model Metrics { |
| 469 | id String @id @default(uuid()) |
| 470 | projectId String @db.VarChar(100) |
| 471 | companyId String? @db.VarChar(50) |
| 472 | |
| 473 | totalUsers Int @default(0) @map("total_users") |
| 474 | totalDocuments Int @default(0) @map("total_documents") |
| 475 | totalDocumentsPublish Int @default(0) @map("total_documents_publish") |
| 476 | totalDocumentsType Json @default("{}") @map("total_documents_type") |
| 477 | totalDocumentsPublishType Json @default("{}") @map("total_documents_publish_type") |
| 478 | totalDocumentsTrashed Int @default(0) @map("total_documents_trashed") |
| 479 | totalDocumentsTrashedType Json @default("{}") @map("total_documents_trashed_type") |
| 480 | totalDocumentsExtracted Int @default(0) @map("total_documents_extracted") |
| 481 | totalStatus Json @default("{}") @map("total_status") |
| 482 | totalStatusType Json @default("{}") @map("total_status_type") |
| 483 | totalStatusPublish Json @default("{}") @map("total_status_publish") |
| 484 | totalStatusPublishType Json @default("{}") @map("total_status_publish_type") |
| 485 | totalDirectories Int @default(0) @map("total_directories") |
| 486 | totalDirectoriesTrashed Int @default(0) @map("total_directories_trashed") |
| 487 | totalVersions Int @default(0) @map("total_versions") |
| 488 | totalRevisions Int @default(0) @map("total_revisions") |
| 489 | totalDocumentComments Int @default(0) @map("total_doc_comments") |
| 490 | totalDocumentCommentReplies Int @default(0) @map("total_doc_comment_replies") |
| 491 | totalDocumentCommentAttachments Int @default(0) @map("total_doc_comment_attachments") |
| 492 | usedStorage Float @default(0.0) @map("used_storage") |
| 493 | usedStorageFormatted String @default("0 B") @map("used_storage_formatted") @db.VarChar(100) |
| 494 | usedStorageDetailed Json @default("{}") @map("used_storage_detailed") |
| 495 | |
| 496 | createdAt DateTime @default(now()) |
| 497 | updatedAt DateTime? |
| 498 | deletedAt DateTime? |
| 499 | |
| 500 | @@index([projectId, companyId]) |
| 501 | @@index([projectId, createdAt]) |
| 502 | @@index([projectId, updatedAt]) |
| 503 | @@index([projectId, companyId, deletedAt]) |
| 504 | @@map("metrics") |
| 505 | } |
| 506 | |
| 507 | // ------------------------------------- |
| 508 | // Audit log scope-related models |
| 509 | // ------------------------------------- |
| 510 | // Audit logs for security tracking |
| 511 | model AuditLog { |
| 512 | id String @id @default(uuid()) |
| 513 | user_id String? |
| 514 | action String // 'login', 'logout', 'create', 'update', 'delete', 'view' |
| 515 | resource String? // table name or resource type |
| 516 | resource_id String? |
| 517 | old_values Json? |
| 518 | new_values Json? |
| 519 | ip_address String? |
| 520 | user_agent String? |
| 521 | location String? // geo location |
| 522 | status String @default("success") // 'success', 'failed', 'blocked' |
| 523 | created_at DateTime @default(now()) |
| 524 | |
| 525 | @@index([user_id]) |
| 526 | @@index([action]) |
| 527 | @@index([resource]) |
| 528 | @@index([resource_id]) |
| 529 | @@index([status]) |
| 530 | @@index([created_at]) |
| 531 | @@map("audit_logs") |
| 532 | } |
| 533 | |
| 534 | // ============================================================================ |
| 535 | // MPP Gantt Cache |
| 536 | // Menyimpan hasil raw ekstraksi Aspose Tasks Cloud agar tidak perlu |
| 537 | // memanggil API Aspose setiap kali view Gantt dibuka. |
| 538 | // |
| 539 | // Hierarki relasi: |
| 540 | // MppSnapshot (1) ──< MppTaskItem (tasks.taskItem[]) |
| 541 | // ──< MppTaskDetail (taskDetails[]) |
| 542 | // ──< MppTaskLink (taskLinks[]) |
| 543 | // ──< MppResource (resources.resourceItem[]) |
| 544 | // ──< MppTaskAssignment (taskAssignments[].list[] — di-flatten) |
| 545 | // ============================================================================ |
| 546 | |
| 547 | model MppSnapshot { |
| 548 | id String @id @default(uuid()) @db.VarChar(50) |
| 549 | projectId String @db.VarChar(100) |
| 550 | fileName String @db.VarChar(255) |
| 551 | /// Path folder di Aspose Cloud Storage (opsional) |
| 552 | folder String? @db.VarChar(500) |
| 553 | extractedAt DateTime @default(now()) |
| 554 | |
| 555 | taskItems MppTaskItem[] |
| 556 | taskDetails MppTaskDetail[] |
| 557 | taskLinks MppTaskLink[] |
| 558 | resources MppResource[] |
| 559 | taskAssignments MppTaskAssignment[] |
| 560 | |
| 561 | /// Satu entri cache per kombinasi projectId + fileName |
| 562 | @@unique([projectId, fileName], name: "projectId_fileName") |
| 563 | @@index([projectId]) |
| 564 | @@map("mpp_snapshots") |
| 565 | } |
| 566 | |
| 567 | model MppTaskItemLink { |
| 568 | id String @id @default(uuid()) @db.VarChar(50) |
| 569 | href String |
| 570 | rel String |
| 571 | type String? |
| 572 | title String? |
| 573 | mppTaskItems MppTaskItem[] |
| 574 | |
| 575 | @@map("mpp_task_item_links") |
| 576 | } |
| 577 | |
| 578 | /// tasks.taskItem[] — daftar ringkas task (uid, nama, tanggal, durasi) |
| 579 | model MppTaskItem { |
| 580 | id String @id @default(uuid()) @db.VarChar(50) |
| 581 | snapshotId String @db.VarChar(50) |
| 582 | uid Int |
| 583 | /// id (bukan uid) dari Aspose — urutan task di dalam file |
| 584 | asposeId Int @map("aspose_id") |
| 585 | name String @db.VarChar(500) |
| 586 | start DateTime |
| 587 | finish DateTime |
| 588 | duration String @db.VarChar(50) |
| 589 | link MppTaskItemLink @relation(fields: [mppTaskItemLinkId], references: [id]) |
| 590 | |
| 591 | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) |
| 592 | mppTaskItemLinkId String @db.VarChar(50) |
| 593 | |
| 594 | @@unique([snapshotId, uid]) |
| 595 | @@index([snapshotId]) |
| 596 | @@map("mpp_task_items") |
| 597 | } |
| 598 | |
| 599 | /// taskDetails[] — detail lengkap per task termasuk hierarki dan prioritas |
| 600 | model MppTaskDetail { |
| 601 | id String @id @default(uuid()) @db.VarChar(50) |
| 602 | snapshotId String @db.VarChar(50) |
| 603 | uid Int |
| 604 | asposeId Int @map("aspose_id") |
| 605 | name String @db.VarChar(500) |
| 606 | start DateTime |
| 607 | finish DateTime |
| 608 | duration String @db.VarChar(50) |
| 609 | /// 0–100 |
| 610 | percentComplete Int @default(0) @map("percent_complete") |
| 611 | /// Numerik Aspose: 500=Normal, 700=High, 300=Low, dst. |
| 612 | priority Int @default(500) |
| 613 | isSummary Boolean @default(false) @map("is_summary") |
| 614 | isMilestone Boolean @default(false) @map("is_milestone") |
| 615 | outlineLevel Int @default(0) @map("outline_level") |
| 616 | outlineNumber String? @map("outline_number") @db.VarChar(50) |
| 617 | wbs String? @db.VarChar(50) |
| 618 | /// Array uid child task, disimpan sebagai JSON int[]. |
| 619 | /// Kunci untuk invert parent-child di aggregator. |
| 620 | subtasksUids Json @map("subtasks_uids") |
| 621 | /// Objek Task lengkap dari Aspose — untuk keperluan di masa depan |
| 622 | /// tanpa harus memanggil API lagi. |
| 623 | rawData Json @map("raw_data") |
| 624 | |
| 625 | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) |
| 626 | |
| 627 | @@unique([snapshotId, uid]) |
| 628 | @@index([snapshotId]) |
| 629 | @@map("mpp_task_details") |
| 630 | } |
| 631 | |
| 632 | /// taskLinks[] — dependency antar task (FinishToStart, dll.) |
| 633 | model MppTaskLink { |
| 634 | id String @id @default(uuid()) @db.VarChar(50) |
| 635 | snapshotId String @db.VarChar(50) |
| 636 | /// Index 1-based dari Aspose (dipakai untuk update/delete link) |
| 637 | asposeIndex Int @map("aspose_index") |
| 638 | predecessorUid Int @map("predecessor_uid") |
| 639 | successorUid Int @map("successor_uid") |
| 640 | /// "FinishToStart" | "StartToStart" | "FinishToFinish" | "StartToFinish" |
| 641 | linkType String @map("link_type") @db.VarChar(50) |
| 642 | lag Int @default(0) |
| 643 | lagFormat String? @map("lag_format") @db.VarChar(50) |
| 644 | |
| 645 | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) |
| 646 | |
| 647 | @@unique([snapshotId, asposeIndex]) |
| 648 | @@index([snapshotId]) |
| 649 | @@map("mpp_task_links") |
| 650 | } |
| 651 | |
| 652 | /// resources.resourceItem[] — daftar resource/orang dalam proyek |
| 653 | model MppResource { |
| 654 | id String @id @default(uuid()) @db.VarChar(50) |
| 655 | snapshotId String @db.VarChar(50) |
| 656 | uid Int |
| 657 | asposeId Int @map("aspose_id") |
| 658 | /// null untuk resource uid=0 (placeholder "Unassigned" bawaan Aspose) |
| 659 | name String? @db.VarChar(500) |
| 660 | |
| 661 | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) |
| 662 | |
| 663 | @@unique([snapshotId, uid]) |
| 664 | @@index([snapshotId]) |
| 665 | @@map("mpp_resources") |
| 666 | } |
| 667 | |
| 668 | /// taskAssignments[].list[] — flatten dari semua container per task. |
| 669 | /// Menyimpan pasangan taskUid↔resourceUid untuk resolve PIC di aggregator. |
| 670 | model MppTaskAssignment { |
| 671 | id String @id @default(uuid()) @db.VarChar(50) |
| 672 | snapshotId String @db.VarChar(50) |
| 673 | /// uid assignment dari Aspose |
| 674 | uid Int |
| 675 | taskUid Int @map("task_uid") |
| 676 | resourceUid Int @map("resource_uid") |
| 677 | |
| 678 | snapshot MppSnapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) |
| 679 | |
| 680 | @@unique([snapshotId, uid]) |
| 681 | @@index([snapshotId]) |
| 682 | @@index([snapshotId, taskUid]) |
| 683 | @@map("mpp_task_assignments") |
| 684 | } |