Commit ff7f1d41 authored by Administrator's avatar Administrator

Update prisma/schema.prisma via Son of Anton

parent 0f56e115
// ============================================================ // This is your main Prisma schema file.
// AL-ARCADE HR PLATFORM v3.0 "THE GRIND" // Since we're using multi-file schemas, ensure your package.json
// Complete Prisma Schema ALL modules, ALL phases // has the correct prisma configuration.
// ============================================================
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
previewFeatures = ["fullTextSearch", "fullTextIndex"] previewFeatures = ["multiSchema", "prismaSchemaFolder"]
} }
datasource db { datasource db {
...@@ -13,51 +12,34 @@ datasource db { ...@@ -13,51 +12,34 @@ datasource db {
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
// ============================================================ // ============================================
// ENUMS // ENUMS
// ============================================================ // ============================================
enum Role { enum Role {
SUPER_ADMIN SUPER_ADMIN
ADMIN ADMIN
TEAM_LEAD PROJECT_LEADER
CONTRACTOR CONTRACTOR
} }
enum ContractorType { enum ContractorType {
FULL_TIME FULL_TIME
PART_TIME INTERN
PROJECT_BASED
} }
enum UserStatus { enum ContractorStatus {
INVITED
ONBOARDING ONBOARDING
ACTIVE ACTIVE
ON_PIP ON_PIP
SUSPENDED SUSPENDED
OFFBOARDING
OFFBOARDED OFFBOARDED
} }
enum InviteStatus { enum DayType {
PENDING IN_OFFICE
ACCEPTED REMOTE
EXPIRED OFF
REVOKED
}
enum BoardVisibility {
PUBLIC
PRIVATE
TEAM
}
enum BoardMemberRole {
OWNER
ADMIN
MEMBER
VIEWER
} }
enum CardPriority { enum CardPriority {
...@@ -68,163 +50,137 @@ enum CardPriority { ...@@ -68,163 +50,137 @@ enum CardPriority {
NONE NONE
} }
enum DeductionType { enum ColumnType {
MANUAL BACKLOG
AUTO_LATE_REPORT TODO
AUTO_MISSED_REPORT DOING
AUTO_PIP FROZEN
PRESET IN_REVIEW
} DONE
CUSTOM
enum DeductionStatus {
PENDING
APPROVED
REJECTED
APPEALED
REVERSED
}
enum AdjustmentType {
ADVANCE
REIMBURSEMENT
BONUS
CORRECTION
OTHER
} }
enum AdjustmentStatus { enum DeductionCategory {
PENDING A
APPROVED B
REJECTED C
D
} }
enum BountyStatus { enum DeductionStatus {
PENDING DRAFT
EARNED PENDING_ADMIN_REVIEW
PAID PENDING_ACKNOWLEDGMENT
PENDING_RESPONSE
UPHELD
REDUCED
DISMISSED
AUTO_APPLIED
CANCELLED CANCELLED
} }
enum PayrollStatus { enum ReportStatus {
DRAFT DRAFT
CALCULATING SUBMITTED
REVIEW LATE
APPROVED APPROVED
PAID AUTO_APPROVED
CANCELLED FLAGGED_VAGUE
} FLAGGED_INCONSISTENT
REVISION_REQUESTED
enum NotificationType { AMENDED
PASSIVE UNREPORTED
IMPORTANT
BLOCKING
}
enum NotificationCategory {
CARD
BOARD
SALARY
DEDUCTION
BOUNTY
ADJUSTMENT
PAYROLL
REPORT
EVALUATION
PIP
MESSAGE
MEETING
NOTICE
POLICY
ONBOARDING
UNAVAILABILITY
SCHEDULE
SYSTEM
}
enum ConversationType {
DIRECT
GROUP
}
enum MessageType {
TEXT
FILE
SYSTEM
} }
enum ReportStatus { enum PayrollStatus {
DRAFT PENDING_CALCULATION
CALCULATED
UNDER_REVIEW
SUBMITTED SUBMITTED
APPROVED APPROVED
REJECTED REJECTED
AMENDMENT_REQUESTED PROCESSING
PAID
} }
enum EvaluationStatus { enum EvaluationStatus {
DRAFT PENDING_TECHNICAL
IN_PROGRESS PENDING_PROFESSIONAL
COMPLETED COMPILED
ACKNOWLEDGED ACKNOWLEDGED
RESPONDED
} }
enum PIPStatus { enum PipStatus {
ACTIVE ACTIVE
PASSED PASSED
FAILED FAILED
EXTENDED
CANCELLED CANCELLED
} }
enum LearningGoalStatus { enum LearningGoalStatus {
NOT_STARTED ACTIVE
IN_PROGRESS OVERDUE
COMPLETED PASSED
CANCELLED FAILED
EXTENDED
} }
enum UnavailabilityType { enum NotificationType {
VACATION BLOCKING
SICK IMPORTANT
PERSONAL INFORMATIONAL
EMERGENCY }
enum NoticeType {
GENERAL_ANNOUNCEMENT
OFFICIAL_WARNING
POLICY_UPDATE
CUSTOM
}
enum AdjustmentType {
POSITIVE
NEGATIVE
}
enum AdjustmentCategory {
ADVANCE
REIMBURSEMENT
BONUS
CORRECTION
LOAN
OTHER OTHER
} }
enum RequestStatus { enum AdjustmentStatus {
PENDING PENDING_APPROVAL
APPROVED APPROVED
REJECTED REJECTED
CANCELLED
}
enum MeetingStatus {
SCHEDULED
IN_PROGRESS
COMPLETED
CANCELLED
} }
enum ContractStatus { enum InviteStatus {
DRAFT
ACTIVE ACTIVE
USED
EXPIRED EXPIRED
TERMINATED REVOKED
} }
enum OffboardingStatus { enum MeetingStatus {
PENDING SCHEDULED
IN_PROGRESS
COMPLETED COMPLETED
CANCELLED CANCELLED
} }
enum WebhookStatus { enum UnavailabilityReason {
ACTIVE PERSONAL
INACTIVE MEDICAL
FAILED RELIGIOUS
EMERGENCY
OTHER
} }
enum RecurrencePattern { enum RecurrenceType {
DAILY DAILY
WEEKLY WEEKLY
BIWEEKLY BIWEEKLY
...@@ -232,357 +188,219 @@ enum RecurrencePattern { ...@@ -232,357 +188,219 @@ enum RecurrencePattern {
CUSTOM CUSTOM
} }
enum AuditAction { enum LabelScope {
CREATE ORGANIZATION
UPDATE BOARD
DELETE }
ARCHIVE
RESTORE enum ApiKeyScope {
LOGIN READ_ONLY
LOGOUT READ_WRITE
PASSWORD_RESET ADMIN
PASSWORD_CHANGE }
ROLE_CHANGE
STATUS_CHANGE enum ConversationType {
APPROVE DIRECT
REJECT GROUP
SUBMIT }
MOVE
ASSIGN enum TerminationType {
UNASSIGN VOLUNTARY
LOCK FOR_CAUSE
UNLOCK MUTUAL
ACKNOWLEDGE CONTRACT_EXPIRY
BULK_ACTION }
EXPORT
IMPORT // ============================================
SETTINGS_CHANGE // CORE MODELS
GENERATE // ============================================
SEND
}
// ============================================================
// AUTH & USERS
// ============================================================
model User { model User {
id String @id @default(uuid()) id String @id @default(uuid())
email String @unique
username String @unique username String @unique
passwordHash String passwordHash String
firstName String firstName String
lastName String lastName String
displayName String? nameArabic String?
nationalId String? @unique
dateOfBirth DateTime?
phone String?
phoneSecondary String?
address String?
avatar String? avatar String?
role Role @default(CONTRACTOR) role Role @default(CONTRACTOR)
contractorType ContractorType? contractorType ContractorType?
status UserStatus @default(INVITED) status ContractorStatus @default(ONBOARDING)
department String? weeklySchedule Json?
title String? baseSalaryPiasters Int?
phone String? actualSalaryPiasters Int?
timezone String @default("Africa/Cairo") bankName String?
bio String? bankAccountNumber String?
dateOfBirth DateTime? bankAccountHolderName String?
startDate DateTime? emergencyContactName String?
emergencyContactPhone String?
emergencyContactRelationship String?
currentStreak Int @default(0)
bestStreak Int @default(0)
forcePasswordChange Boolean @default(false)
activatedAt DateTime?
contractSignedAt DateTime?
lastLoginAt DateTime? lastLoginAt DateTime?
isOnline Boolean @default(false) loginAttempts Int @default(0)
lockedUntil DateTime?
deletedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
// Relations Auth & Sessions
sessions Session[]
passwordResetTokens PasswordResetToken[]
// Relations Onboarding
sentInvites Invite[] @relation("InviteSender")
receivedInvite Invite? @relation("InviteRecipient")
onboardingProgress ContractorOnboarding?
// Relations Boards & Cards // Relations
ownedBoards Board[] @relation("BoardCreator") boards BoardMember[]
boardMemberships BoardMember[] assignedCards CardAssignee[]
watchedCards CardWatcher[]
createdCards Card[] @relation("CardCreator") createdCards Card[] @relation("CardCreator")
cardAssignments CardAssignee[] comments Comment[]
cardWatches CardWatcher[] attachments Attachment[]
cardComments CardComment[] reports DailyReport[] @relation("ReportAuthor")
cardActivities CardActivity[] @relation("CardActivityActor") reviewedReports DailyReport[] @relation("ReportReviewer")
lockedCards Card[] @relation("CardLocker") deductions Deduction[] @relation("DeductionTarget")
initiatedDeductions Deduction[] @relation("DeductionInitiator")
// Relations Financial reviewedDeductions Deduction[] @relation("DeductionReviewer")
contractorSalaries ContractorSalary[] @relation("SalaryContractor") bountyPayouts BountyPayout[]
createdSalaries ContractorSalary[] @relation("SalaryCreator") adjustments Adjustment[] @relation("AdjustmentTarget")
earnedBounties Bounty[] @relation("BountyContractor") createdAdjustments Adjustment[] @relation("AdjustmentCreator")
createdBounties Bounty[] @relation("BountyCreator") reviewedAdjustments Adjustment[] @relation("AdjustmentReviewer")
receivedDeductions Deduction[] @relation("DeductionContractor") evaluations Evaluation[] @relation("EvaluationTarget")
createdDeductions Deduction[] @relation("DeductionCreator") techEvaluator Evaluation[] @relation("TechEvaluator")
approvedDeductions Deduction[] @relation("DeductionApprover") profEvaluator Evaluation[] @relation("ProfEvaluator")
reversedDeductions Deduction[] @relation("DeductionReverser") pips Pip[] @relation("PipTarget")
receivedAdjustments SalaryAdjustment[] @relation("AdjustmentContractor") createdPips Pip[] @relation("PipCreator")
createdAdjustments SalaryAdjustment[] @relation("AdjustmentCreator") learningGoals LearningGoal[] @relation("GoalTarget")
approvedAdjustments SalaryAdjustment[] @relation("AdjustmentApprover") createdGoals LearningGoal[] @relation("GoalCreator")
payrollItems PayrollItem[] unavailability Unavailability[]
generatedPayrolls PayrollPeriod[] @relation("PayrollGenerator") scheduleRequests ScheduleChangeRequest[] @relation("ScheduleRequester")
approvedPayrolls PayrollPeriod[] @relation("PayrollApprover") reviewedSchedules ScheduleChangeRequest[] @relation("ScheduleReviewer")
conversations ConversationParticipant[]
// Relations Communication
conversationParticipations ConversationParticipant[]
sentMessages Message[] sentMessages Message[]
createdNotices Notice[] @relation("NoticeCreator")
createdPolicies Policy[] @relation("PolicyCreator")
policyAcknowledgments PolicyAcknowledgment[]
// Relations Notifications
notifications Notification[] notifications Notification[]
notificationPreferences NotificationPreference[] noticeAcknowledgments NoticeAcknowledgment[]
policyAcknowledgments PolicyAcknowledgment[]
// Relations Reports
dailyReports DailyReport[] @relation("ReportContractor")
reviewedReports DailyReport[] @relation("ReportReviewer")
// Relations Performance
evaluationsReceived Evaluation[] @relation("EvaluationContractor")
evaluationsGiven Evaluation[] @relation("EvaluationEvaluator")
createdEvalTemplates EvaluationTemplate[]
pipsReceived PIP[] @relation("PIPContractor")
pipsManaged PIP[] @relation("PIPManager")
learningGoals LearningGoal[]
competencyAssessments CompetencyAssessment[] @relation("AssessmentContractor")
givenAssessments CompetencyAssessment[] @relation("AssessmentAssessor")
// Relations Time & Scheduling
unavailabilityRequests UnavailabilityRequest[] @relation("UnavailabilityContractor")
reviewedUnavailabilities UnavailabilityRequest[] @relation("UnavailabilityReviewer")
scheduleChangeRequests ScheduleChangeRequest[] @relation("ScheduleChangeContractor")
reviewedScheduleChanges ScheduleChangeRequest[] @relation("ScheduleChangeReviewer")
workSchedule WorkSchedule?
// Relations Meetings
createdMeetings Meeting[] @relation("MeetingCreator")
meetingParticipations MeetingParticipant[]
// Relations Contracts & Offboarding
contracts Contract[] contracts Contract[]
offboardingProcesses OffboardingProcess[] @relation("OffboardingContractor") meetings MeetingInvitee[]
initiatedOffboardings OffboardingProcess[] @relation("OffboardingInitiator") createdMeetings Meeting[] @relation("MeetingCreator")
invites Invite[]
// Relations Admin refreshTokens RefreshToken[]
apiKeys ApiKey[] payrollLines PayrollLine[]
createdWebhooks Webhook[] createdNotices Notice[]
sessions Session[]
// Relations Notes & Activity
privateNotesAbout PrivateNote[] @relation("NoteSubject")
privateNotesAuthored PrivateNote[] @relation("NoteAuthor")
activityFeedItems ActivityFeedItem[] @relation("ActivityActor")
uploadedFiles File[]
// Relations Templates
createdBoardTemplates BoardTemplate[]
createdCardTemplates CardTemplate[]
@@index([email])
@@index([username]) @@index([username])
@@index([role])
@@index([status]) @@index([status])
@@index([role])
@@index([contractorType])
@@index([deletedAt]) @@index([deletedAt])
@@index([department])
@@map("users")
} }
model Session { model RefreshToken {
id String @id @default(uuid()) id String @id @default(uuid())
token String @unique
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
refreshToken String @unique
ipAddress String?
userAgent String?
expiresAt DateTime expiresAt DateTime
lastActiveAt DateTime @default(now())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([userId]) @@index([userId])
@@index([token])
@@index([expiresAt]) @@index([expiresAt])
@@map("sessions")
} }
model PasswordResetToken { model Session {
id String @id @default(uuid()) id String @id @default(uuid())
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique ipAddress String?
expiresAt DateTime userAgent String?
usedAt DateTime? lastActivity DateTime @default(now())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([token])
@@index([userId]) @@index([userId])
@@map("password_reset_tokens")
} }
// ============================================================ model Setting {
// SYSTEM SETTINGS
// ============================================================
model SystemSetting {
id String @id @default(uuid()) id String @id @default(uuid())
key String @unique key String @unique
value Json value Json
description String? description String?
category String @default("general")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
@@index([key]) @@index([key])
@@index([category])
@@map("system_settings")
} }
// ============================================================ model AuditTrail {
// AUDIT TRAIL (IMMUTABLE)
// ============================================================
model AuditLog {
id String @id @default(uuid()) id String @id @default(uuid())
userId String? userId String?
action AuditAction action String
entityType String entityType String
entityId String? entityId String?
oldValue Json? method String?
newValue Json? url String?
metadata Json? before Json?
after Json?
ipAddress String? ipAddress String?
userAgent String? userAgent String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
// NO updatedAt. NO deletedAt. IMMUTABLE.
@@index([userId]) @@index([userId])
@@index([entityType, entityId]) @@index([entityType])
@@index([action]) @@index([action])
@@index([createdAt]) @@index([createdAt])
@@map("audit_logs")
} }
// ============================================================ // ============================================
// ONBOARDING // BOARDS & KANBAN
// ============================================================ // ============================================
model Invite {
id String @id @default(uuid())
email String
role Role @default(CONTRACTOR)
senderId String
sender User @relation("InviteSender", fields: [senderId], references: [id])
recipientId String? @unique
recipient User? @relation("InviteRecipient", fields: [recipientId], references: [id])
token String @unique
status InviteStatus @default(PENDING)
message String?
expiresAt DateTime
acceptedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
@@index([token])
@@index([status])
@@map("invites")
}
model OnboardingChecklist { model Board {
id String @id @default(uuid()) id String @id @default(uuid())
name String name String
description String? description String?
role Role @default(CONTRACTOR) key String @unique
isActive Boolean @default(true) visibility String @default("PRIVATE")
items OnboardingChecklistItem[] icon String?
contractors ContractorOnboarding[] color String?
createdAt DateTime @default(now()) allowContractorCreation Boolean @default(true)
updatedAt DateTime @updatedAt autoArchiveDoneCardsDays Int @default(30)
isArchived Boolean @default(false)
nextCardNumber Int @default(1)
deletedAt DateTime? deletedAt DateTime?
@@map("onboarding_checklists")
}
model OnboardingChecklistItem {
id String @id @default(uuid())
checklistId String
checklist OnboardingChecklist @relation(fields: [checklistId], references: [id], onDelete: Cascade)
title String
description String?
order Int
isRequired Boolean @default(true)
progressItems ContractorOnboardingItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([checklistId])
@@map("onboarding_checklist_items")
}
model ContractorOnboarding {
id String @id @default(uuid())
contractorId String @unique
contractor User @relation(fields: [contractorId], references: [id], onDelete: Cascade)
checklistId String
checklist OnboardingChecklist @relation(fields: [checklistId], references: [id])
completedAt DateTime?
items ContractorOnboardingItem[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) columns BoardColumn[]
@@map("contractor_onboardings") members BoardMember[]
} cards Card[]
labels Label[] @relation("BoardLabels")
model ContractorOnboardingItem { savedFilters SavedFilter[]
id String @id @default(uuid())
onboardingId String
onboarding ContractorOnboarding @relation(fields: [onboardingId], references: [id], onDelete: Cascade)
checklistItemId String
checklistItem OnboardingChecklistItem @relation(fields: [checklistItemId], references: [id])
isCompleted Boolean @default(false)
completedAt DateTime?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([onboardingId, checklistItemId]) @@index([key])
@@map("contractor_onboarding_items") @@index([isArchived])
@@index([deletedAt])
} }
// ============================================================ model BoardColumn {
// BOARDS & KANBAN
// ============================================================
model Board {
id String @id @default(uuid()) id String @id @default(uuid())
boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
name String name String
description String? type ColumnType
visibility BoardVisibility @default(TEAM)
createdById String
createdBy User @relation("BoardCreator", fields: [createdById], references: [id])
prefix String?
color String?
icon String? icon String?
isArchived Boolean @default(false) position Int
archivedAt DateTime? wipLimit Int?
columns Column[]
members BoardMember[]
labels Label[]
cards Card[]
savedFilters SavedFilter[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([createdById]) cards Card[]
@@index([visibility])
@@index([deletedAt]) @@index([boardId])
@@index([isArchived]) @@index([type])
@@map("boards")
} }
model BoardMember { model BoardMember {
...@@ -591,116 +409,74 @@ model BoardMember { ...@@ -591,116 +409,74 @@ model BoardMember {
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role BoardMemberRole @default(MEMBER) role String @default("MEMBER")
joinedAt DateTime @default(now()) createdAt DateTime @default(now())
@@unique([boardId, userId]) @@unique([boardId, userId])
@@index([boardId])
@@index([userId]) @@index([userId])
@@map("board_members")
} }
model BoardTemplate { model Label {
id String @id @default(uuid()) id String @id @default(uuid())
name String name String
description String? color String
structure Json // { columns: [...], labels: [...] } textColor String @default("#FFFFFF")
createdById String scope LabelScope @default(ORGANIZATION)
createdBy User @relation(fields: [createdById], references: [id]) boardId String?
isPublic Boolean @default(false) board Board? @relation("BoardLabels", fields: [boardId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@map("board_templates")
}
model Column { cards CardLabel[]
id String @id @default(uuid())
boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
name String
position Float
color String?
wipLimit Int?
isDone Boolean @default(false)
cards Card[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([scope])
@@index([boardId]) @@index([boardId])
@@index([boardId, position])
@@map("columns")
} }
model Label { model Card {
id String @id @default(uuid()) id String @id @default(uuid())
boardId String boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
name String columnId String
color String column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Cascade)
cards CardLabel[] cardNumber String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([boardId])
@@map("labels")
}
// ============================================================
// CARDS
// ============================================================
model Card {
id String @id @default(uuid())
title String title String
description String? description String?
boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
columnId String
column Column @relation(fields: [columnId], references: [id])
position Float
priority CardPriority @default(NONE) priority CardPriority @default(NONE)
position Float @default(0)
dueDate DateTime? dueDate DateTime?
startDate DateTime?
estimatedHours Float? estimatedHours Float?
actualHours Float?
bountyPiasters Int @default(0) bountyPiasters Int @default(0)
coverImage String? bountyPaidOut Boolean @default(false)
isLocked Boolean @default(false) frozenReason String?
lockedById String? version Int @default(1)
lockedBy User? @relation("CardLocker", fields: [lockedById], references: [id]) isArchived Boolean @default(false)
lockedAt DateTime? completedAt DateTime?
parentCardId String? firstDoingAt DateTime?
parentCard Card? @relation("CardSubtasks", fields: [parentCardId], references: [id]) totalFrozenMs Int @default(0)
subtasks Card[] @relation("CardSubtasks")
createdById String createdById String
createdBy User @relation("CardCreator", fields: [createdById], references: [id]) createdBy User @relation("CardCreator", fields: [createdById], references: [id])
completedAt DateTime? deletedAt DateTime?
archivedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
// Relations
assignees CardAssignee[] assignees CardAssignee[]
watchers CardWatcher[] watchers CardWatcher[]
labels CardLabel[] labels CardLabel[]
comments CardComment[] comments Comment[]
attachments Attachment[]
checklists Checklist[] checklists Checklist[]
attachments CardAttachment[] bountyPayouts BountyPayout[]
activities CardActivity[] deductions Deduction[]
bounties Bounty[]
reportEntries DailyReportEntry[]
@@index([boardId]) @@index([boardId])
@@index([columnId]) @@index([columnId])
@@index([createdById]) @@index([cardNumber])
@@index([priority]) @@index([isArchived])
@@index([dueDate]) @@index([dueDate])
@@index([deletedAt]) @@index([deletedAt])
@@index([boardId, columnId, position]) @@index([createdById])
@@index([parentCardId])
@@map("cards")
} }
model CardAssignee { model CardAssignee {
...@@ -709,11 +485,11 @@ model CardAssignee { ...@@ -709,11 +485,11 @@ model CardAssignee {
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
assignedAt DateTime @default(now()) createdAt DateTime @default(now())
@@unique([cardId, userId]) @@unique([cardId, userId])
@@index([cardId])
@@index([userId]) @@index([userId])
@@map("card_assignees")
} }
model CardWatcher { model CardWatcher {
...@@ -722,11 +498,11 @@ model CardWatcher { ...@@ -722,11 +498,11 @@ model CardWatcher {
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
addedAt DateTime @default(now()) createdAt DateTime @default(now())
@@unique([cardId, userId]) @@unique([cardId, userId])
@@index([cardId])
@@index([userId]) @@index([userId])
@@map("card_watchers")
} }
model CardLabel { model CardLabel {
...@@ -737,843 +513,489 @@ model CardLabel { ...@@ -737,843 +513,489 @@ model CardLabel {
label Label @relation(fields: [labelId], references: [id], onDelete: Cascade) label Label @relation(fields: [labelId], references: [id], onDelete: Cascade)
@@unique([cardId, labelId]) @@unique([cardId, labelId])
@@map("card_labels") @@index([cardId])
@@index([labelId])
} }
model CardComment { // ============================================
// COMMENTS, CHECKLISTS, ATTACHMENTS
// ============================================
model Comment {
id String @id @default(uuid()) id String @id @default(uuid())
cardId String cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
authorId String authorId String
author User @relation(fields: [authorId], references: [id]) author User @relation(fields: [authorId], references: [id])
content String content String
isEdited Boolean @default(false) editedAt DateTime?
parentId String? editableUntil DateTime?
parent CardComment? @relation("CommentReplies", fields: [parentId], references: [id]) deletedAt DateTime?
replies CardComment[] @relation("CommentReplies")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([cardId]) @@index([cardId])
@@index([authorId]) @@index([authorId])
@@index([parentId])
@@map("card_comments")
} }
model Checklist { model Checklist {
id String @id @default(uuid()) id String @id @default(uuid())
cardId String cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
title String name String
position Float position Int @default(0)
items ChecklistItem[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
items ChecklistItem[]
@@index([cardId]) @@index([cardId])
@@map("checklists")
} }
model ChecklistItem { model ChecklistItem {
id String @id @default(uuid()) id String @id @default(uuid())
checklistId String checklistId String
checklist Checklist @relation(fields: [checklistId], references: [id], onDelete: Cascade) checklist Checklist @relation(fields: [checklistId], references: [id], onDelete: Cascade)
title String text String
isCompleted Boolean @default(false) isChecked Boolean @default(false)
completedAt DateTime? position Int @default(0)
assigneeId String? checkedAt DateTime?
position Float
dueDate DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([checklistId]) @@index([checklistId])
@@map("checklist_items")
} }
model CardAttachment { model Attachment {
id String @id @default(uuid()) id String @id @default(uuid())
cardId String cardId String?
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade) card Card? @relation(fields: [cardId], references: [id], onDelete: Cascade)
fileId String uploadedById String
file File @relation(fields: [fileId], references: [id]) uploadedBy User @relation(fields: [uploadedById], references: [id])
createdAt DateTime @default(now()) fileName String
fileSize Int
@@index([cardId]) mimeType String
@@map("card_attachments") storagePath String
} entityType String @default("card")
entityId String?
model CardActivity {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
actorId String
actor User @relation("CardActivityActor", fields: [actorId], references: [id])
action String // "moved", "assigned", "commented", "priority_changed", etc.
details Json? // { from: "To Do", to: "In Progress" }
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([cardId]) @@index([cardId])
@@index([cardId, createdAt]) @@index([entityType, entityId])
@@map("card_activities")
}
model CardTemplate {
id String @id @default(uuid())
name String
description String?
boardId String?
defaultColumn String?
priority CardPriority @default(NONE)
bountyPiasters Int @default(0)
templateData Json // { checklists: [...], labels: [...], description: "..." }
createdById String
createdBy User @relation(fields: [createdById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
recurringCards RecurringCard[]
@@map("card_templates")
} }
model RecurringCard { // ============================================
id String @id @default(uuid()) // FINANCIAL
templateId String // ============================================
template CardTemplate @relation(fields: [templateId], references: [id])
boardId String
columnId String
pattern RecurrencePattern
cronExpression String?
customDays Json? // [1,3,5] for Mon/Wed/Fri
nextRunAt DateTime
lastRunAt DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([isActive, nextRunAt]) model Deduction {
@@map("recurring_cards")
}
model SavedFilter {
id String @id @default(uuid()) id String @id @default(uuid())
boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
userId String userId String
name String user User @relation("DeductionTarget", fields: [userId], references: [id])
filters Json // { priority: [...], assignees: [...], labels: [...], dueDate: ... } initiatedById String?
isDefault Boolean @default(false) initiatedBy User? @relation("DeductionInitiator", fields: [initiatedById], references: [id])
createdAt DateTime @default(now()) reviewedById String?
updatedAt DateTime @updatedAt reviewedBy User? @relation("DeductionReviewer", fields: [reviewedById], references: [id])
cardId String?
@@index([boardId, userId]) card Card? @relation(fields: [cardId], references: [id], onDelete: SetNull)
@@map("saved_filters") category DeductionCategory
} subCategory String
status DeductionStatus @default(DRAFT)
// ============================================================ amountPiasters Int
// FINANCIAL SALARY appliedAmountPiasters Int?
// ============================================================ description String
evidence Json?
model ContractorSalary { violationDate DateTime?
id String @id @default(uuid()) acknowledgedAt DateTime?
contractorId String contractorResponse String?
contractor User @relation("SalaryContractor", fields: [contractorId], references: [id]) respondedAt DateTime?
baseSalaryPiasters Int reviewNotes String?
contractorType ContractorType reviewedAt DateTime?
effectiveDate DateTime payrollMonth Int?
endDate DateTime? payrollYear Int?
workDaysPerWeek Int @default(5)
hoursPerDay Float @default(8)
isActive Boolean @default(true)
notes String?
createdById String
createdBy User @relation("SalaryCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) @@index([userId])
@@index([contractorId, isActive]) @@index([status])
@@index([effectiveDate]) @@index([category])
@@map("contractor_salaries") @@index([payrollMonth, payrollYear])
@@index([createdAt])
} }
// ============================================================ model BountyPayout {
// FINANCIAL BOUNTIES
// ============================================================
model Bounty {
id String @id @default(uuid()) id String @id @default(uuid())
cardId String cardId String
card Card @relation(fields: [cardId], references: [id]) card Card @relation(fields: [cardId], references: [id])
contractorId String userId String
contractor User @relation("BountyContractor", fields: [contractorId], references: [id]) user User @relation(fields: [userId], references: [id])
amountPiasters Int amountPiasters Int
status BountyStatus @default(PENDING) splitPercentage Float @default(100)
earnedAt DateTime? payrollMonth Int
payrollItemId String? payrollYear Int
payrollItem PayrollItem? @relation(fields: [payrollItemId], references: [id]) paidAt DateTime @default(now())
createdById String
createdBy User @relation("BountyCreator", fields: [createdById], references: [id])
month Int
year Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contractorId]) @@index([userId])
@@index([contractorId, month, year])
@@index([cardId]) @@index([cardId])
@@index([status]) @@index([payrollMonth, payrollYear])
@@map("bounties")
}
// ============================================================
// FINANCIAL DEDUCTIONS
// ============================================================
model DeductionPreset {
id String @id @default(uuid())
name String
description String?
amountPiasters Int
type DeductionType
isActive Boolean @default(true)
deductions Deduction[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@map("deduction_presets")
}
model Deduction {
id String @id @default(uuid())
contractorId String
contractor User @relation("DeductionContractor", fields: [contractorId], references: [id])
amountPiasters Int
type DeductionType
reason String
presetId String?
preset DeductionPreset? @relation(fields: [presetId], references: [id])
triggerCardId String?
triggerReportId String?
status DeductionStatus @default(PENDING)
approvedById String?
approvedBy User? @relation("DeductionApprover", fields: [approvedById], references: [id])
approvedAt DateTime?
appealReason String?
appealedAt DateTime?
reversedById String?
reversedBy User? @relation("DeductionReverser", fields: [reversedById], references: [id])
reversedAt DateTime?
reversalReason String?
month Int
year Int
payrollItemId String?
payrollItem PayrollItem? @relation(fields: [payrollItemId], references: [id])
createdById String
createdBy User @relation("DeductionCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([contractorId])
@@index([contractorId, month, year])
@@index([status])
@@index([type])
@@index([deletedAt])
@@map("deductions")
} }
// ============================================================ model Adjustment {
// FINANCIAL SALARY ADJUSTMENTS
// ============================================================
model SalaryAdjustment {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation("AdjustmentContractor", fields: [contractorId], references: [id]) user User @relation("AdjustmentTarget", fields: [userId], references: [id])
amountPiasters Int
type AdjustmentType
reason String
status AdjustmentStatus @default(PENDING)
approvedById String?
approvedBy User? @relation("AdjustmentApprover", fields: [approvedById], references: [id])
approvedAt DateTime?
month Int
year Int
payrollItemId String?
payrollItem PayrollItem? @relation(fields: [payrollItemId], references: [id])
createdById String createdById String
createdBy User @relation("AdjustmentCreator", fields: [createdById], references: [id]) createdBy User @relation("AdjustmentCreator", fields: [createdById], references: [id])
reviewedById String?
reviewedBy User? @relation("AdjustmentReviewer", fields: [reviewedById], references: [id])
type AdjustmentType
category AdjustmentCategory
amountPiasters Int
description String
status AdjustmentStatus @default(PENDING_APPROVAL)
effectiveMonth Int
effectiveYear Int
reviewNotes String?
reviewedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([contractorId]) @@index([userId])
@@index([contractorId, month, year])
@@index([status]) @@index([status])
@@map("salary_adjustments") @@index([effectiveMonth, effectiveYear])
} }
// ============================================================ model Payroll {
// FINANCIAL PAYROLL
// ============================================================
model PayrollPeriod {
id String @id @default(uuid()) id String @id @default(uuid())
month Int month Int
year Int year Int
status PayrollStatus @default(DRAFT) status PayrollStatus @default(PENDING_CALCULATION)
totalGrossPiasters Int @default(0) totalGrossPiasters Int @default(0)
totalDeductionsPiasters Int @default(0)
totalBountiesPiasters Int @default(0)
totalAdjustmentsPiasters Int @default(0)
totalNetPiasters Int @default(0) totalNetPiasters Int @default(0)
contractorCount Int @default(0) contractorCount Int @default(0)
generatedById String? calculatedAt DateTime?
generatedBy User? @relation("PayrollGenerator", fields: [generatedById], references: [id]) submittedAt DateTime?
approvedById String?
approvedBy User? @relation("PayrollApprover", fields: [approvedById], references: [id])
approvedAt DateTime? approvedAt DateTime?
paidAt DateTime? paidAt DateTime?
notes String? notes String?
items PayrollItem[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
lines PayrollLine[]
@@unique([month, year]) @@unique([month, year])
@@index([status]) @@index([status])
@@map("payroll_periods")
} }
model PayrollItem { model PayrollLine {
id String @id @default(uuid()) id String @id @default(uuid())
payrollPeriodId String payrollId String
payrollPeriod PayrollPeriod @relation(fields: [payrollPeriodId], references: [id], onDelete: Cascade) payroll Payroll @relation(fields: [payrollId], references: [id], onDelete: Cascade)
contractorId String userId String
contractor User @relation(fields: [contractorId], references: [id]) user User @relation(fields: [userId], references: [id])
baseSalaryPiasters Int actualSalaryPiasters Int
totalBountiesPiasters Int @default(0) totalBountiesPiasters Int @default(0)
totalAdjustmentsPiasters Int @default(0)
totalDeductionsPiasters Int @default(0) totalDeductionsPiasters Int @default(0)
netSalaryPiasters Int totalAdjustmentsPiasters Int @default(0)
breakdown Json? // detailed breakdown netPayablePiasters Int @default(0)
bounties Bounty[] breakdown Json?
deductions Deduction[]
adjustments SalaryAdjustment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([payrollPeriodId, contractorId])
@@index([contractorId])
@@map("payroll_items")
}
// ============================================================
// COMMUNICATION CONVERSATIONS & MESSAGES
// ============================================================
model Conversation {
id String @id @default(uuid())
type ConversationType
name String?
avatar String?
lastMessageAt DateTime?
participants ConversationParticipant[]
messages Message[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([type])
@@index([lastMessageAt])
@@map("conversations")
}
model ConversationParticipant {
id String @id @default(uuid())
conversationId String
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
isAdmin Boolean @default(false)
isMuted Boolean @default(false)
lastReadAt DateTime?
joinedAt DateTime @default(now())
leftAt DateTime?
@@unique([conversationId, userId])
@@index([userId])
@@map("conversation_participants")
}
model Message {
id String @id @default(uuid())
conversationId String
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
senderId String
sender User @relation(fields: [senderId], references: [id])
type MessageType @default(TEXT)
content String?
fileId String?
file File? @relation(fields: [fileId], references: [id])
replyToId String?
replyTo Message? @relation("MessageReplies", fields: [replyToId], references: [id])
replies Message[] @relation("MessageReplies")
isEdited Boolean @default(false)
isPinned Boolean @default(false)
readBy MessageRead[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([conversationId])
@@index([conversationId, createdAt])
@@index([senderId])
@@map("messages")
}
model MessageRead {
id String @id @default(uuid())
messageId String
message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
userId String
readAt DateTime @default(now())
@@unique([messageId, userId])
@@map("message_reads")
}
// ============================================================
// COMMUNICATION NOTICES & POLICIES
// ============================================================
model Notice {
id String @id @default(uuid())
title String
content String
priority NotificationType @default(IMPORTANT)
isPinned Boolean @default(false)
targetRoles Role[]
publishedAt DateTime?
expiresAt DateTime?
createdById String
createdBy User @relation("NoticeCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([publishedAt])
@@index([deletedAt])
@@map("notices")
}
model Policy {
id String @id @default(uuid())
title String
content String
version Int @default(1)
requiresAck Boolean @default(true)
targetRoles Role[]
effectiveDate DateTime
createdById String
createdBy User @relation("PolicyCreator", fields: [createdById], references: [id])
acknowledgments PolicyAcknowledgment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([effectiveDate])
@@index([deletedAt])
@@map("policies")
}
model PolicyAcknowledgment {
id String @id @default(uuid())
policyId String
policy Policy @relation(fields: [policyId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
acknowledgedAt DateTime @default(now())
@@unique([policyId, userId])
@@map("policy_acknowledgments")
}
// ============================================================
// NOTIFICATIONS
// ============================================================
model Notification {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
type NotificationType @default(PASSIVE)
category NotificationCategory
title String
message String
actionUrl String?
metadata Json?
isRead Boolean @default(false)
readAt DateTime?
acknowledgedAt DateTime?
expiresAt DateTime?
createdAt DateTime @default(now())
@@index([userId])
@@index([userId, isRead])
@@index([userId, category])
@@index([createdAt])
@@map("notifications")
}
model NotificationPreference {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
category NotificationCategory
inApp Boolean @default(true)
sound Boolean @default(true)
enabled Boolean @default(true)
@@unique([userId, category])
@@map("notification_preferences")
}
// ============================================================
// DAILY REPORTS
// ============================================================
model DailyReport {
id String @id @default(uuid())
contractorId String
contractor User @relation("ReportContractor", fields: [contractorId], references: [id])
date DateTime @db.Date
status ReportStatus @default(DRAFT)
summary String?
submittedAt DateTime?
reviewedById String?
reviewedBy User? @relation("ReportReviewer", fields: [reviewedById], references: [id])
reviewedAt DateTime?
reviewNotes String?
isLate Boolean @default(false)
entries DailyReportEntry[]
amendments ReportAmendment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([contractorId, date])
@@index([contractorId])
@@index([contractorId, date])
@@index([status])
@@index([date])
@@map("daily_reports")
}
model DailyReportEntry {
id String @id @default(uuid())
reportId String
report DailyReport @relation(fields: [reportId], references: [id], onDelete: Cascade)
description String
hoursSpent Float
cardId String?
card Card? @relation(fields: [cardId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([reportId])
@@index([cardId])
@@map("daily_report_entries")
}
model ReportAmendment {
id String @id @default(uuid())
reportId String
report DailyReport @relation(fields: [reportId], references: [id], onDelete: Cascade)
content String
requestedAt DateTime @default(now())
resolvedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([reportId]) @@index([payrollId])
@@map("report_amendments") @@index([userId])
} }
// ============================================================ // ============================================
// PERFORMANCE EVALUATIONS // REPORTS
// ============================================================ // ============================================
model EvaluationTemplate { model DailyReport {
id String @id @default(uuid()) id String @id @default(uuid())
name String userId String
description String? user User @relation("ReportAuthor", fields: [userId], references: [id])
criteria EvaluationCriteria[] reviewedById String?
evaluations Evaluation[] reviewedBy User? @relation("ReportReviewer", fields: [reviewedById], references: [id])
isActive Boolean @default(true) reportDate DateTime
createdById String status ReportStatus @default(DRAFT)
createdBy User @relation(fields: [createdById], references: [id]) taskEntries Json?
blockers String?
additionalNotes String?
mood String?
totalHours Float?
submittedAt DateTime?
reviewedAt DateTime?
reviewNotes String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@map("evaluation_templates") amendments ReportAmendment[]
@@index([userId])
@@index([reportDate])
@@index([status])
} }
model EvaluationCriteria { model ReportAmendment {
id String @id @default(uuid()) id String @id @default(uuid())
templateId String reportId String
template EvaluationTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade) report DailyReport @relation(fields: [reportId], references: [id], onDelete: Cascade)
name String reason String
description String? content Json
weight Float // percentage (0-100) status String @default("PENDING")
maxScore Int @default(5) reviewNotes String?
order Int
scores EvaluationScore[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([templateId]) @@index([reportId])
@@map("evaluation_criteria")
} }
// ============================================
// EVALUATIONS & PERFORMANCE
// ============================================
model Evaluation { model Evaluation {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation("EvaluationContractor", fields: [contractorId], references: [id]) user User @relation("EvaluationTarget", fields: [userId], references: [id])
evaluatorId String techEvaluatorId String?
evaluator User @relation("EvaluationEvaluator", fields: [evaluatorId], references: [id]) techEvaluator User? @relation("TechEvaluator", fields: [techEvaluatorId], references: [id])
templateId String? profEvaluatorId String?
template EvaluationTemplate? @relation(fields: [templateId], references: [id]) profEvaluator User? @relation("ProfEvaluator", fields: [profEvaluatorId], references: [id])
period String // "2024-Q1", "2024-01", etc.
month Int month Int
year Int year Int
status EvaluationStatus @default(DRAFT) status EvaluationStatus @default(PENDING_TECHNICAL)
technicalScores Json?
technicalScore Float?
technicalNotes String?
professionalScores Json?
professionalScore Float?
professionalNotes String?
autoMetrics Json?
overallScore Float? overallScore Float?
overallComments String? rating String?
autoMetrics Json? // snapshot of computed metrics contractorResponse String?
acknowledgedAt DateTime? acknowledgedAt DateTime?
scores EvaluationScore[] respondedAt DateTime?
compiledAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) @@unique([userId, month, year])
@@index([evaluatorId]) @@index([userId])
@@index([contractorId, month, year])
@@index([status]) @@index([status])
@@map("evaluations") @@index([month, year])
} }
model EvaluationScore { model Pip {
id String @id @default(uuid()) id String @id @default(uuid())
evaluationId String userId String
evaluation Evaluation @relation(fields: [evaluationId], references: [id], onDelete: Cascade) user User @relation("PipTarget", fields: [userId], references: [id])
criteriaId String createdById String
criteria EvaluationCriteria @relation(fields: [criteriaId], references: [id]) createdBy User @relation("PipCreator", fields: [createdById], references: [id])
score Float status PipStatus @default(ACTIVE)
comments String? durationDays Int
startDate DateTime @default(now())
endDate DateTime
specificIssues String
improvementTargets String
successCriteria String
consequenceOfFailure String @default("Termination of engagement.")
checkInSchedule String @default("WEEKLY")
checkIns Json?
resultNotes String?
acknowledgedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@unique([evaluationId, criteriaId]) @@index([userId])
@@map("evaluation_scores") @@index([status])
} }
// ============================================================ model LearningGoal {
// PERFORMANCE PIPs
// ============================================================
model PIP {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation("PIPContractor", fields: [contractorId], references: [id]) user User @relation("GoalTarget", fields: [userId], references: [id])
managerId String createdById String
manager User @relation("PIPManager", fields: [managerId], references: [id]) createdBy User @relation("GoalCreator", fields: [createdById], references: [id])
reason String title String
status PIPStatus @default(ACTIVE) description String
startDate DateTime competencyAreaId String?
endDate DateTime competencyArea CompetencyArea? @relation(fields: [competencyAreaId], references: [id])
extendedDate DateTime? deadline DateTime
goals PIPGoal[] status LearningGoalStatus @default(ACTIVE)
checkIns PIPCheckIn[] assessmentMethod String?
outcome String? passCriteria String?
completedAt DateTime? assessmentNotes String?
assessedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) @@index([userId])
@@index([status]) @@index([status])
@@index([managerId])
@@map("pips")
} }
model PIPGoal { model CompetencyArea {
id String @id @default(uuid()) id String @id @default(uuid())
pipId String name String
pip PIP @relation(fields: [pipId], references: [id], onDelete: Cascade)
title String
description String? description String?
targetDate DateTime? position Int @default(0)
isCompleted Boolean @default(false)
completedAt DateTime?
order Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([pipId]) learningGoals LearningGoal[]
@@map("pip_goals")
} }
model PIPCheckIn { // ============================================
// COMMUNICATION
// ============================================
model Notification {
id String @id @default(uuid()) id String @id @default(uuid())
pipId String userId String
pip PIP @relation(fields: [pipId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
meetingId String? @unique type NotificationType @default(INFORMATIONAL)
meeting Meeting? @relation(fields: [meetingId], references: [id]) title String
notes String? message String?
progress String? link String?
date DateTime isRead Boolean @default(false)
isAcknowledged Boolean @default(false)
acknowledgedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([pipId]) @@index([userId])
@@map("pip_check_ins") @@index([type])
@@index([isRead])
@@index([createdAt])
} }
// ============================================================ model Conversation {
// PERFORMANCE LEARNING GOALS & COMPETENCIES
// ============================================================
model LearningGoal {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String name String?
contractor User @relation(fields: [contractorId], references: [id]) type ConversationType @default(DIRECT)
title String lastMessageAt DateTime?
description String?
status LearningGoalStatus @default(NOT_STARTED)
targetDate DateTime?
completedAt DateTime?
progress Int @default(0) // 0-100
resources Json? // [{ title, url, type }]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) participants ConversationParticipant[]
@@index([status]) messages Message[]
@@map("learning_goals")
@@index([lastMessageAt])
} }
model Competency { model ConversationParticipant {
id String @id @default(uuid()) id String @id @default(uuid())
name String conversationId String
description String? conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
category String? userId String
levels Json // [{ level: 1, description: "..." }, ...] user User @relation(fields: [userId], references: [id], onDelete: Cascade)
assessments CompetencyAssessment[] lastReadAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@map("competencies") @@unique([conversationId, userId])
@@index([conversationId])
@@index([userId])
} }
model CompetencyAssessment { model Message {
id String @id @default(uuid()) id String @id @default(uuid())
competencyId String conversationId String
competency Competency @relation(fields: [competencyId], references: [id]) conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
contractorId String senderId String
contractor User @relation("AssessmentContractor", fields: [contractorId], references: [id]) sender User @relation(fields: [senderId], references: [id])
assessorId String content String
assessor User @relation("AssessmentAssessor", fields: [assessorId], references: [id]) attachments Json?
level Int deletedAt DateTime?
notes String?
assessedAt DateTime @default(now())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([contractorId]) @@index([conversationId])
@@index([competencyId, contractorId]) @@index([senderId])
@@map("competency_assessments") @@index([createdAt])
} }
// ============================================================ model Notice {
// TIME & SCHEDULING
// ============================================================
model WorkSchedule {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String @unique title String
contractor User @relation(fields: [contractorId], references: [id]) content String
workDays Json // [1,2,3,4,5] = Mon-Fri type NoticeType @default(GENERAL_ANNOUNCEMENT)
startTime String // "09:00" isBlocking Boolean @default(false)
endTime String // "17:00" targetRoles Json?
timezone String @default("Africa/Cairo") createdById String
effectiveDate DateTime createdBy User @relation(fields: [createdById], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@map("work_schedules") acknowledgments NoticeAcknowledgment[]
@@index([type])
@@index([createdAt])
} }
model UnavailabilityRequest { model NoticeAcknowledgment {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String noticeId String
contractor User @relation("UnavailabilityContractor", fields: [contractorId], references: [id]) notice Notice @relation(fields: [noticeId], references: [id], onDelete: Cascade)
type UnavailabilityType userId String
startDate DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade)
endDate DateTime
reason String?
status RequestStatus @default(PENDING)
reviewedById String?
reviewedBy User? @relation("UnavailabilityReviewer", fields: [reviewedById], references: [id])
reviewedAt DateTime?
reviewNotes String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contractorId]) @@unique([noticeId, userId])
@@index([status]) @@index([noticeId])
@@index([startDate, endDate]) @@index([userId])
@@map("unavailability_requests")
} }
// ============================================
// SCHEDULING & TIME
// ============================================
model Holiday { model Holiday {
id String @id @default(uuid()) id String @id @default(uuid())
name String name String
date DateTime @db.Date startDate DateTime
endDate DateTime
isRecurring Boolean @default(false) isRecurring Boolean @default(false)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([startDate])
}
model Unavailability {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
startDate DateTime
endDate DateTime
reason UnavailabilityReason @default(PERSONAL)
notes String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@unique([date]) @@index([userId])
@@map("holidays") @@index([startDate])
} }
model ScheduleChangeRequest { model ScheduleChangeRequest {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation("ScheduleChangeContractor", fields: [contractorId], references: [id]) user User @relation("ScheduleRequester", fields: [userId], references: [id])
currentData Json // snapshot of current schedule
requestedData Json // requested changes
reason String
status RequestStatus @default(PENDING)
reviewedById String? reviewedById String?
reviewedBy User? @relation("ScheduleChangeReviewer", fields: [reviewedById], references: [id]) reviewedBy User? @relation("ScheduleReviewer", fields: [reviewedById], references: [id])
reviewedAt DateTime? currentSchedule Json
reviewNotes String? proposedSchedule Json
effectiveDate DateTime effectiveDate DateTime
reason String
status String @default("PENDING")
reviewNotes String?
reviewedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) @@index([userId])
@@index([status]) @@index([status])
@@map("schedule_change_requests")
} }
// ============================================================
// MEETINGS
// ============================================================
model Meeting { model Meeting {
id String @id @default(uuid()) id String @id @default(uuid())
title String title String
...@@ -1581,225 +1003,192 @@ model Meeting { ...@@ -1581,225 +1003,192 @@ model Meeting {
startTime DateTime startTime DateTime
endTime DateTime endTime DateTime
location String? location String?
meetingUrl String?
status MeetingStatus @default(SCHEDULED) status MeetingStatus @default(SCHEDULED)
recurrence String?
notes Json?
createdById String createdById String
createdBy User @relation("MeetingCreator", fields: [createdById], references: [id]) createdBy User @relation("MeetingCreator", fields: [createdById], references: [id])
participants MeetingParticipant[]
pipCheckIn PIPCheckIn?
notes String?
agenda String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
invitees MeetingInvitee[]
@@index([startTime]) @@index([startTime])
@@index([createdById])
@@index([status]) @@index([status])
@@map("meetings")
} }
model MeetingParticipant { model MeetingInvitee {
id String @id @default(uuid()) id String @id @default(uuid())
meetingId String meetingId String
meeting Meeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) meeting Meeting @relation(fields: [meetingId], references: [id], onDelete: Cascade)
userId String userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
status RequestStatus @default(PENDING) // PENDING=invited, APPROVED=accepted, REJECTED=declined attended Boolean @default(false)
respondedAt DateTime?
@@unique([meetingId, userId]) @@unique([meetingId, userId])
@@index([meetingId])
@@index([userId]) @@index([userId])
@@map("meeting_participants")
} }
// ============================================================ // ============================================
// CONTRACTS & OFFBOARDING // DOCUMENTS
// ============================================================ // ============================================
model Contract { model Contract {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation(fields: [contractorId], references: [id]) user User @relation(fields: [userId], references: [id])
title String contractorType String?
content String? contractText String?
fileId String? scheduleSnapshot Json?
file File? @relation(fields: [fileId], references: [id]) salaryAtSigning Int?
status ContractStatus @default(DRAFT)
startDate DateTime
endDate DateTime?
salaryPiasters Int?
contractorType ContractorType?
terms Json?
signedAt DateTime? signedAt DateTime?
signatureData Json?
startDate DateTime?
endDate DateTime?
status String @default("ACTIVE")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([contractorId]) @@index([userId])
@@index([status]) @@index([status])
@@map("contracts")
} }
model OffboardingProcess { model Policy {
id String @id @default(uuid())
title String
content String
version Int @default(1)
requiresAcknowledgment Boolean @default(true)
publishedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
acknowledgments PolicyAcknowledgment[]
@@index([title])
}
model PolicyAcknowledgment {
id String @id @default(uuid())
policyId String
policy Policy @relation(fields: [policyId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([policyId, userId])
@@index([policyId])
@@index([userId])
}
// ============================================
// OFFBOARDING
// ============================================
model Termination {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation("OffboardingContractor", fields: [contractorId], references: [id]) type TerminationType
initiatedById String reason String
initiatedBy User @relation("OffboardingInitiator", fields: [initiatedById], references: [id])
status OffboardingStatus @default(PENDING)
reason String?
effectiveDate DateTime effectiveDate DateTime
items OffboardingItem[] noticeDate DateTime @default(now())
notes String? finalSettlement Json?
completedAt DateTime? checklistItems Json?
status String @default("INITIATED")
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) @@index([userId])
@@index([status]) @@index([status])
@@map("offboarding_processes")
} }
model OffboardingItem { // ============================================
// ONBOARDING
// ============================================
model Invite {
id String @id @default(uuid()) id String @id @default(uuid())
offboardingId String code String @unique
offboarding OffboardingProcess @relation(fields: [offboardingId], references: [id], onDelete: Cascade) contractorType ContractorType
title String status InviteStatus @default(ACTIVE)
description String? welcomeNote String?
isCompleted Boolean @default(false) expiresAt DateTime
completedAt DateTime? usedAt DateTime?
order Int usedById String?
createdById String
createdBy User @relation(fields: [createdById], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([offboardingId]) @@index([code])
@@map("offboarding_items") @@index([status])
} }
// ============================================================ // ============================================
// API KEYS & WEBHOOKS // API & INTEGRATIONS
// ============================================================ // ============================================
model ApiKey { model ApiKey {
id String @id @default(uuid()) id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
name String name String
keyHash String @unique keyHash String @unique
keyPrefix String // first 8 chars for identification keyPrefix String
permissions Json // ["cards:read", "cards:write", ...] scope ApiKeyScope @default(READ_ONLY)
expiresAt DateTime? description String?
lastUsedAt DateTime?
isActive Boolean @default(true) isActive Boolean @default(true)
lastUsedAt DateTime?
expiresAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([keyHash]) @@index([keyHash])
@@index([userId]) @@index([isActive])
@@map("api_keys")
} }
model Webhook { model Webhook {
id String @id @default(uuid()) id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
name String
url String url String
secret String secret String?
events String[] // ["card.created", "card.moved", ...] events Json
status WebhookStatus @default(ACTIVE) isActive Boolean @default(true)
failCount Int @default(0) lastTriggeredAt DateTime?
deliveries WebhookDelivery[] failureCount Int @default(0)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([userId]) @@index([isActive])
@@index([status])
@@map("webhooks")
} }
model WebhookDelivery { model SavedFilter {
id String @id @default(uuid())
webhookId String
webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade)
event String
payload Json
responseCode Int?
responseBody String?
success Boolean
attemptCount Int @default(1)
deliveredAt DateTime @default(now())
@@index([webhookId])
@@index([webhookId, deliveredAt])
@@map("webhook_deliveries")
}
// ============================================================
// FILES
// ============================================================
model File {
id String @id @default(uuid()) id String @id @default(uuid())
originalName String boardId String
storagePath String board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
storageKey String @unique userId String
mimeType String name String
sizeBytes Int filters Json
uploadedById String
uploadedBy User @relation(fields: [uploadedById], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
// Reverse relations @@index([boardId, userId])
cardAttachments CardAttachment[]
messages Message[]
contracts Contract[]
@@index([uploadedById])
@@index([storageKey])
@@map("files")
} }
// ============================================================ model RecurringCardDefinition {
// PRIVATE NOTES (Admin-only, invisible to contractor)
// ============================================================
model PrivateNote {
id String @id @default(uuid()) id String @id @default(uuid())
subjectId String boardId String
subject User @relation("NoteSubject", fields: [subjectId], references: [id]) title String
authorId String description String?
author User @relation("NoteAuthor", fields: [authorId], references: [id]) labels Json?
content String priority CardPriority @default(NONE)
assigneeIds Json?
checklists Json?
estimatedHours Float?
recurrenceType RecurrenceType
recurrenceConfig Json?
isActive Boolean @default(true)
nextRunAt DateTime?
lastRunAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([subjectId])
@@index([authorId])
@@map("private_notes")
}
// ============================================================ @@index([boardId])
// ACTIVITY FEED @@index([isActive])
// ============================================================
model ActivityFeedItem {
id String @id @default(uuid())
actorId String
actor User @relation("ActivityActor", fields: [actorId], references: [id])
action String // "completed_card", "earned_bounty", "submitted_report", etc.
entityType String
entityId String
metadata Json? // { cardTitle: "...", boardName: "...", amount: 5000 }
visibility String @default("org") // "org", "team", "admin"
createdAt DateTime @default(now())
@@index([actorId])
@@index([entityType, entityId])
@@index([createdAt])
@@index([visibility, createdAt])
@@map("activity_feed_items")
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment