| 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 | } |
Akihira77 / schema.prisma
Last active 5 months ago
Revision 6fcfd136321428c5144855099e7dd88abf7b8afb