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,501 +188,321 @@ enum RecurrencePattern { ...@@ -232,501 +188,321 @@ 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 nameArabic String?
displayName String? nationalId String? @unique
avatar String? dateOfBirth DateTime?
role Role @default(CONTRACTOR) phone String?
contractorType ContractorType? phoneSecondary String?
status UserStatus @default(INVITED) address String?
department String? avatar String?
title String? role Role @default(CONTRACTOR)
phone String? contractorType ContractorType?
timezone String @default("Africa/Cairo") status ContractorStatus @default(ONBOARDING)
bio String? weeklySchedule Json?
dateOfBirth DateTime? baseSalaryPiasters Int?
startDate DateTime? actualSalaryPiasters Int?
lastLoginAt DateTime? bankName String?
isOnline Boolean @default(false) bankAccountNumber String?
createdAt DateTime @default(now()) bankAccountHolderName String?
updatedAt DateTime @updatedAt emergencyContactName String?
deletedAt DateTime? emergencyContactPhone String?
emergencyContactRelationship String?
currentStreak Int @default(0)
bestStreak Int @default(0)
forcePasswordChange Boolean @default(false)
activatedAt DateTime?
contractSignedAt DateTime?
lastLoginAt DateTime?
loginAttempts Int @default(0)
lockedUntil DateTime?
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations Auth & Sessions // Relations
boards BoardMember[]
assignedCards CardAssignee[]
watchedCards CardWatcher[]
createdCards Card[] @relation("CardCreator")
comments Comment[]
attachments Attachment[]
reports DailyReport[] @relation("ReportAuthor")
reviewedReports DailyReport[] @relation("ReportReviewer")
deductions Deduction[] @relation("DeductionTarget")
initiatedDeductions Deduction[] @relation("DeductionInitiator")
reviewedDeductions Deduction[] @relation("DeductionReviewer")
bountyPayouts BountyPayout[]
adjustments Adjustment[] @relation("AdjustmentTarget")
createdAdjustments Adjustment[] @relation("AdjustmentCreator")
reviewedAdjustments Adjustment[] @relation("AdjustmentReviewer")
evaluations Evaluation[] @relation("EvaluationTarget")
techEvaluator Evaluation[] @relation("TechEvaluator")
profEvaluator Evaluation[] @relation("ProfEvaluator")
pips Pip[] @relation("PipTarget")
createdPips Pip[] @relation("PipCreator")
learningGoals LearningGoal[] @relation("GoalTarget")
createdGoals LearningGoal[] @relation("GoalCreator")
unavailability Unavailability[]
scheduleRequests ScheduleChangeRequest[] @relation("ScheduleRequester")
reviewedSchedules ScheduleChangeRequest[] @relation("ScheduleReviewer")
conversations ConversationParticipant[]
sentMessages Message[]
notifications Notification[]
noticeAcknowledgments NoticeAcknowledgment[]
policyAcknowledgments PolicyAcknowledgment[]
contracts Contract[]
meetings MeetingInvitee[]
createdMeetings Meeting[] @relation("MeetingCreator")
invites Invite[]
refreshTokens RefreshToken[]
payrollLines PayrollLine[]
createdNotices Notice[]
sessions Session[] sessions Session[]
passwordResetTokens PasswordResetToken[]
// Relations Onboarding
sentInvites Invite[] @relation("InviteSender")
receivedInvite Invite? @relation("InviteRecipient")
onboardingProgress ContractorOnboarding?
// Relations Boards & Cards
ownedBoards Board[] @relation("BoardCreator")
boardMemberships BoardMember[]
createdCards Card[] @relation("CardCreator")
cardAssignments CardAssignee[]
cardWatches CardWatcher[]
cardComments CardComment[]
cardActivities CardActivity[] @relation("CardActivityActor")
lockedCards Card[] @relation("CardLocker")
// Relations Financial
contractorSalaries ContractorSalary[] @relation("SalaryContractor")
createdSalaries ContractorSalary[] @relation("SalaryCreator")
earnedBounties Bounty[] @relation("BountyContractor")
createdBounties Bounty[] @relation("BountyCreator")
receivedDeductions Deduction[] @relation("DeductionContractor")
createdDeductions Deduction[] @relation("DeductionCreator")
approvedDeductions Deduction[] @relation("DeductionApprover")
reversedDeductions Deduction[] @relation("DeductionReverser")
receivedAdjustments SalaryAdjustment[] @relation("AdjustmentContractor")
createdAdjustments SalaryAdjustment[] @relation("AdjustmentCreator")
approvedAdjustments SalaryAdjustment[] @relation("AdjustmentApprover")
payrollItems PayrollItem[]
generatedPayrolls PayrollPeriod[] @relation("PayrollGenerator")
approvedPayrolls PayrollPeriod[] @relation("PayrollApprover")
// Relations Communication
conversationParticipations ConversationParticipant[]
sentMessages Message[]
createdNotices Notice[] @relation("NoticeCreator")
createdPolicies Policy[] @relation("PolicyCreator")
policyAcknowledgments PolicyAcknowledgment[]
// Relations Notifications
notifications Notification[]
notificationPreferences NotificationPreference[]
// 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[]
offboardingProcesses OffboardingProcess[] @relation("OffboardingContractor")
initiatedOffboardings OffboardingProcess[] @relation("OffboardingInitiator")
// Relations Admin
apiKeys ApiKey[]
createdWebhooks Webhook[]
// 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())
userId String token String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId String
token String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade)
refreshToken String @unique expiresAt DateTime
ipAddress String? createdAt DateTime @default(now())
userAgent String?
expiresAt DateTime
lastActiveAt 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) id String @id @default(uuid())
// ============================================================ userId String?
action String
model AuditLog { entityType String
id String @id @default(uuid()) entityId String?
userId String? method String?
action AuditAction url String?
entityType String before Json?
entityId String? after Json?
oldValue Json? ipAddress String?
newValue Json? userAgent String?
metadata Json? createdAt DateTime @default(now())
ipAddress String?
userAgent String?
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
// ============================================================
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 {
id String @id @default(uuid())
name String
description String?
role Role @default(CONTRACTOR)
isActive Boolean @default(true)
items OnboardingChecklistItem[]
contractors ContractorOnboarding[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
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())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@map("contractor_onboardings")
}
model ContractorOnboardingItem {
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])
@@map("contractor_onboarding_items")
} }
// ============================================================ // ============================================
// BOARDS & KANBAN // BOARDS & KANBAN
// ============================================================ // ============================================
model Board { model Board {
id String @id @default(uuid()) id String @id @default(uuid())
name String name String
description String? description String?
visibility BoardVisibility @default(TEAM) key String @unique
createdById String visibility String @default("PRIVATE")
createdBy User @relation("BoardCreator", fields: [createdById], references: [id]) icon String?
prefix String? color String?
color String? allowContractorCreation Boolean @default(true)
icon String? autoArchiveDoneCardsDays Int @default(30)
isArchived Boolean @default(false) isArchived Boolean @default(false)
archivedAt DateTime? nextCardNumber Int @default(1)
columns Column[] deletedAt DateTime?
members BoardMember[] createdAt DateTime @default(now())
labels Label[] updatedAt DateTime @updatedAt
cards Card[]
columns BoardColumn[]
members BoardMember[]
cards Card[]
labels Label[] @relation("BoardLabels")
savedFilters SavedFilter[] savedFilters SavedFilter[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([createdById]) @@index([key])
@@index([visibility])
@@index([deletedAt])
@@index([isArchived]) @@index([isArchived])
@@map("boards") @@index([deletedAt])
} }
model BoardMember { model BoardColumn {
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)
userId String name String
user User @relation(fields: [userId], references: [id], onDelete: Cascade) type ColumnType
role BoardMemberRole @default(MEMBER) icon String?
joinedAt DateTime @default(now()) position Int
wipLimit Int?
@@unique([boardId, userId]) createdAt DateTime @default(now())
@@index([userId]) updatedAt DateTime @updatedAt
@@map("board_members")
}
model BoardTemplate { cards Card[]
id String @id @default(uuid())
name String
description String?
structure Json // { columns: [...], labels: [...] }
createdById String
createdBy User @relation(fields: [createdById], references: [id])
isPublic Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@map("board_templates") @@index([boardId])
@@index([type])
} }
model Column { model BoardMember {
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 userId String
position Float user User @relation(fields: [userId], references: [id], onDelete: Cascade)
color String? role String @default("MEMBER")
wipLimit Int?
isDone Boolean @default(false)
cards Card[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([boardId, userId])
@@index([boardId]) @@index([boardId])
@@index([boardId, position]) @@index([userId])
@@map("columns")
} }
model Label { model Label {
id String @id @default(uuid()) id String @id @default(uuid())
boardId String name String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) color String
name String textColor String @default("#FFFFFF")
color String scope LabelScope @default(ORGANIZATION)
cards CardLabel[] boardId String?
createdAt DateTime @default(now()) board Board? @relation("BoardLabels", fields: [boardId], references: [id], onDelete: Cascade)
updatedAt DateTime @updatedAt createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cards CardLabel[]
@@index([scope])
@@index([boardId]) @@index([boardId])
@@map("labels")
} }
// ============================================================
// CARDS
// ============================================================
model Card { model Card {
id String @id @default(uuid()) id String @id @default(uuid())
title String
description String?
boardId String boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
columnId String columnId String
column Column @relation(fields: [columnId], references: [id]) column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Cascade)
position Float cardNumber String
priority CardPriority @default(NONE) title String
description String?
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) bountyPaidOut Boolean @default(false)
coverImage String? frozenReason String?
isLocked Boolean @default(false) version Int @default(1)
lockedById String? isArchived Boolean @default(false)
lockedBy User? @relation("CardLocker", fields: [lockedById], references: [id])
lockedAt DateTime?
parentCardId String?
parentCard Card? @relation("CardSubtasks", fields: [parentCardId], references: [id])
subtasks Card[] @relation("CardSubtasks")
createdById String
createdBy User @relation("CardCreator", fields: [createdById], references: [id])
completedAt DateTime? completedAt DateTime?
archivedAt DateTime? firstDoingAt DateTime?
createdAt DateTime @default(now()) totalFrozenMs Int @default(0)
updatedAt DateTime @updatedAt createdById String
createdBy User @relation("CardCreator", fields: [createdById], references: [id])
deletedAt DateTime? deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations assignees CardAssignee[]
assignees CardAssignee[] watchers CardWatcher[]
watchers CardWatcher[] labels CardLabel[]
labels CardLabel[] comments Comment[]
comments CardComment[] 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 {
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)
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 {
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)
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,359 +513,374 @@ model CardLabel { ...@@ -737,359 +513,374 @@ 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 { // ============================================
id String @id @default(uuid()) // COMMENTS, CHECKLISTS, ATTACHMENTS
cardId String // ============================================
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
authorId String model Comment {
author User @relation(fields: [authorId], references: [id]) id String @id @default(uuid())
content String cardId String
isEdited Boolean @default(false) card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
parentId String? authorId String
parent CardComment? @relation("CommentReplies", fields: [parentId], references: [id]) author User @relation(fields: [authorId], references: [id])
replies CardComment[] @relation("CommentReplies") content String
createdAt DateTime @default(now()) editedAt DateTime?
updatedAt DateTime @updatedAt editableUntil DateTime?
deletedAt DateTime? deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@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 {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
fileId String
file File @relation(fields: [fileId], references: [id])
createdAt DateTime @default(now())
@@index([cardId])
@@map("card_attachments")
} }
model CardActivity { 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)
actorId String uploadedById String
actor User @relation("CardActivityActor", fields: [actorId], references: [id]) uploadedBy User @relation(fields: [uploadedById], references: [id])
action String // "moved", "assigned", "commented", "priority_changed", etc. fileName String
details Json? // { from: "To Do", to: "In Progress" } fileSize Int
createdAt DateTime @default(now()) mimeType String
storagePath String
entityType String @default("card")
entityId String?
createdAt DateTime @default(now())
@@index([cardId]) @@index([cardId])
@@index([cardId, createdAt]) @@index([entityType, entityId])
@@map("card_activities")
} }
model CardTemplate { // ============================================
id String @id @default(uuid()) // FINANCIAL
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[] model Deduction {
id String @id @default(uuid())
userId String
user User @relation("DeductionTarget", fields: [userId], references: [id])
initiatedById String?
initiatedBy User? @relation("DeductionInitiator", fields: [initiatedById], references: [id])
reviewedById String?
reviewedBy User? @relation("DeductionReviewer", fields: [reviewedById], references: [id])
cardId String?
card Card? @relation(fields: [cardId], references: [id], onDelete: SetNull)
category DeductionCategory
subCategory String
status DeductionStatus @default(DRAFT)
amountPiasters Int
appliedAmountPiasters Int?
description String
evidence Json?
violationDate DateTime?
acknowledgedAt DateTime?
contractorResponse String?
respondedAt DateTime?
reviewNotes String?
reviewedAt DateTime?
payrollMonth Int?
payrollYear Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("card_templates") @@index([userId])
@@index([status])
@@index([category])
@@index([payrollMonth, payrollYear])
@@index([createdAt])
} }
model RecurringCard { model BountyPayout {
id String @id @default(uuid()) id String @id @default(uuid())
templateId String cardId String
template CardTemplate @relation(fields: [templateId], references: [id]) card Card @relation(fields: [cardId], references: [id])
boardId String userId String
columnId String user User @relation(fields: [userId], references: [id])
pattern RecurrencePattern amountPiasters Int
cronExpression String? splitPercentage Float @default(100)
customDays Json? // [1,3,5] for Mon/Wed/Fri payrollMonth Int
nextRunAt DateTime payrollYear Int
lastRunAt DateTime? paidAt DateTime @default(now())
isActive Boolean @default(true) createdAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([isActive, nextRunAt]) @@index([userId])
@@map("recurring_cards") @@index([cardId])
@@index([payrollMonth, payrollYear])
} }
model SavedFilter { model Adjustment {
id String @id @default(uuid()) id String @id @default(uuid())
boardId String userId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade) user User @relation("AdjustmentTarget", fields: [userId], references: [id])
userId String createdById String
name String createdBy User @relation("AdjustmentCreator", fields: [createdById], references: [id])
filters Json // { priority: [...], assignees: [...], labels: [...], dueDate: ... } reviewedById String?
isDefault Boolean @default(false) reviewedBy User? @relation("AdjustmentReviewer", fields: [reviewedById], references: [id])
createdAt DateTime @default(now()) type AdjustmentType
updatedAt DateTime @updatedAt category AdjustmentCategory
amountPiasters Int
description String
status AdjustmentStatus @default(PENDING_APPROVAL)
effectiveMonth Int
effectiveYear Int
reviewNotes String?
reviewedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([boardId, userId]) @@index([userId])
@@map("saved_filters") @@index([status])
} @@index([effectiveMonth, effectiveYear])
}
// ============================================================
// FINANCIAL SALARY model Payroll {
// ============================================================ id String @id @default(uuid())
month Int
model ContractorSalary { year Int
id String @id @default(uuid()) status PayrollStatus @default(PENDING_CALCULATION)
contractorId String totalGrossPiasters Int @default(0)
contractor User @relation("SalaryContractor", fields: [contractorId], references: [id]) totalNetPiasters Int @default(0)
baseSalaryPiasters Int contractorCount Int @default(0)
contractorType ContractorType calculatedAt DateTime?
effectiveDate DateTime submittedAt DateTime?
endDate DateTime? approvedAt DateTime?
workDaysPerWeek Int @default(5) paidAt DateTime?
hoursPerDay Float @default(8) notes String?
isActive Boolean @default(true) createdAt DateTime @default(now())
notes String? updatedAt DateTime @updatedAt
createdById String
createdBy User @relation("SalaryCreator", fields: [createdById], references: [id]) lines PayrollLine[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt @@unique([month, year])
@@index([status])
@@index([contractorId]) }
@@index([contractorId, isActive])
@@index([effectiveDate]) model PayrollLine {
@@map("contractor_salaries") id String @id @default(uuid())
} payrollId String
payroll Payroll @relation(fields: [payrollId], references: [id], onDelete: Cascade)
// ============================================================ userId String
// FINANCIAL BOUNTIES user User @relation(fields: [userId], references: [id])
// ============================================================ actualSalaryPiasters Int
totalBountiesPiasters Int @default(0)
model Bounty { totalDeductionsPiasters Int @default(0)
totalAdjustmentsPiasters Int @default(0)
netPayablePiasters Int @default(0)
breakdown Json?
createdAt DateTime @default(now())
@@index([payrollId])
@@index([userId])
}
// ============================================
// REPORTS
// ============================================
model DailyReport {
id String @id @default(uuid()) id String @id @default(uuid())
cardId String userId String
card Card @relation(fields: [cardId], references: [id]) user User @relation("ReportAuthor", fields: [userId], references: [id])
contractorId String reviewedById String?
contractor User @relation("BountyContractor", fields: [contractorId], references: [id]) reviewedBy User? @relation("ReportReviewer", fields: [reviewedById], references: [id])
amountPiasters Int reportDate DateTime
status BountyStatus @default(PENDING) status ReportStatus @default(DRAFT)
earnedAt DateTime? taskEntries Json?
payrollItemId String? blockers String?
payrollItem PayrollItem? @relation(fields: [payrollItemId], references: [id]) additionalNotes String?
createdById String mood String?
createdBy User @relation("BountyCreator", fields: [createdById], references: [id]) totalHours Float?
month Int submittedAt DateTime?
year Int reviewedAt DateTime?
reviewNotes String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([contractorId]) amendments ReportAmendment[]
@@index([contractorId, month, year])
@@index([cardId]) @@index([userId])
@@index([reportDate])
@@index([status]) @@index([status])
@@map("bounties")
} }
// ============================================================ model ReportAmendment {
// FINANCIAL DEDUCTIONS id String @id @default(uuid())
// ============================================================ reportId String
report DailyReport @relation(fields: [reportId], references: [id], onDelete: Cascade)
reason String
content Json
status String @default("PENDING")
reviewNotes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
model DeductionPreset { @@index([reportId])
id String @id @default(uuid()) }
name String
description String? // ============================================
amountPiasters Int // EVALUATIONS & PERFORMANCE
type DeductionType // ============================================
isActive Boolean @default(true)
deductions Deduction[] model Evaluation {
createdAt DateTime @default(now()) id String @id @default(uuid())
updatedAt DateTime @updatedAt userId String
deletedAt DateTime? user User @relation("EvaluationTarget", fields: [userId], references: [id])
techEvaluatorId String?
techEvaluator User? @relation("TechEvaluator", fields: [techEvaluatorId], references: [id])
profEvaluatorId String?
profEvaluator User? @relation("ProfEvaluator", fields: [profEvaluatorId], references: [id])
month Int
year Int
status EvaluationStatus @default(PENDING_TECHNICAL)
technicalScores Json?
technicalScore Float?
technicalNotes String?
professionalScores Json?
professionalScore Float?
professionalNotes String?
autoMetrics Json?
overallScore Float?
rating String?
contractorResponse String?
acknowledgedAt DateTime?
respondedAt DateTime?
compiledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, month, year])
@@index([userId])
@@index([status])
@@index([month, year])
}
model Pip {
id String @id @default(uuid())
userId String
user User @relation("PipTarget", fields: [userId], references: [id])
createdById String
createdBy User @relation("PipCreator", fields: [createdById], references: [id])
status PipStatus @default(ACTIVE)
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())
updatedAt DateTime @updatedAt
@@map("deduction_presets") @@index([userId])
@@index([status])
} }
model Deduction { model LearningGoal {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation("DeductionContractor", fields: [contractorId], references: [id]) user User @relation("GoalTarget", fields: [userId], references: [id])
amountPiasters Int createdById String
type DeductionType createdBy User @relation("GoalCreator", fields: [createdById], references: [id])
reason String title String
presetId String? description String
preset DeductionPreset? @relation(fields: [presetId], references: [id]) competencyAreaId String?
triggerCardId String? competencyArea CompetencyArea? @relation(fields: [competencyAreaId], references: [id])
triggerReportId String? deadline DateTime
status DeductionStatus @default(PENDING) status LearningGoalStatus @default(ACTIVE)
approvedById String? assessmentMethod String?
approvedBy User? @relation("DeductionApprover", fields: [approvedById], references: [id]) passCriteria String?
approvedAt DateTime? assessmentNotes String?
appealReason String? assessedAt DateTime?
appealedAt DateTime? createdAt DateTime @default(now())
reversedById String? updatedAt DateTime @updatedAt
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([userId])
@@index([contractorId, month, year])
@@index([status]) @@index([status])
@@index([type])
@@index([deletedAt])
@@map("deductions")
} }
// ============================================================ model CompetencyArea {
// FINANCIAL SALARY ADJUSTMENTS id String @id @default(uuid())
// ============================================================ name String
description String?
position Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
model SalaryAdjustment { learningGoals LearningGoal[]
id String @id @default(uuid()) }
contractorId String
contractor User @relation("AdjustmentContractor", fields: [contractorId], 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
createdBy User @relation("AdjustmentCreator", fields: [createdById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([contractorId]) // ============================================
@@index([contractorId, month, year]) // COMMUNICATION
@@index([status]) // ============================================
@@map("salary_adjustments")
}
// ============================================================
// FINANCIAL PAYROLL
// ============================================================
model PayrollPeriod {
id String @id @default(uuid())
month Int
year Int
status PayrollStatus @default(DRAFT)
totalGrossPiasters Int @default(0)
totalDeductionsPiasters Int @default(0)
totalBountiesPiasters Int @default(0)
totalAdjustmentsPiasters Int @default(0)
totalNetPiasters Int @default(0)
contractorCount Int @default(0)
generatedById String?
generatedBy User? @relation("PayrollGenerator", fields: [generatedById], references: [id])
approvedById String?
approvedBy User? @relation("PayrollApprover", fields: [approvedById], references: [id])
approvedAt DateTime?
paidAt DateTime?
notes String?
items PayrollItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([month, year]) model Notification {
@@index([status]) id String @id @default(uuid())
@@map("payroll_periods") userId String
} user User @relation(fields: [userId], references: [id], onDelete: Cascade)
type NotificationType @default(INFORMATIONAL)
model PayrollItem { title String
id String @id @default(uuid()) message String?
payrollPeriodId String link String?
payrollPeriod PayrollPeriod @relation(fields: [payrollPeriodId], references: [id], onDelete: Cascade) isRead Boolean @default(false)
contractorId String isAcknowledged Boolean @default(false)
contractor User @relation(fields: [contractorId], references: [id]) acknowledgedAt DateTime?
baseSalaryPiasters Int createdAt DateTime @default(now())
totalBountiesPiasters Int @default(0)
totalAdjustmentsPiasters Int @default(0) @@index([userId])
totalDeductionsPiasters Int @default(0) @@index([type])
netSalaryPiasters Int @@index([isRead])
breakdown Json? // detailed breakdown @@index([createdAt])
bounties Bounty[] }
deductions Deduction[]
adjustments SalaryAdjustment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([payrollPeriodId, contractorId])
@@index([contractorId])
@@map("payroll_items")
}
// ============================================================
// COMMUNICATION CONVERSATIONS & MESSAGES
// ============================================================
model Conversation { model Conversation {
id String @id @default(uuid()) id String @id @default(uuid())
type ConversationType name String?
name String? type ConversationType @default(DIRECT)
avatar String?
lastMessageAt DateTime? lastMessageAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
participants ConversationParticipant[] participants ConversationParticipant[]
messages Message[] messages Message[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([type])
@@index([lastMessageAt]) @@index([lastMessageAt])
@@map("conversations")
} }
model ConversationParticipant { model ConversationParticipant {
...@@ -1098,15 +889,12 @@ model ConversationParticipant { ...@@ -1098,15 +889,12 @@ model ConversationParticipant {
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) conversation Conversation @relation(fields: [conversationId], 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)
isAdmin Boolean @default(false)
isMuted Boolean @default(false)
lastReadAt DateTime? lastReadAt DateTime?
joinedAt DateTime @default(now()) createdAt DateTime @default(now())
leftAt DateTime?
@@unique([conversationId, userId]) @@unique([conversationId, userId])
@@index([conversationId])
@@index([userId]) @@index([userId])
@@map("conversation_participants")
} }
model Message { model Message {
...@@ -1115,691 +903,292 @@ model Message { ...@@ -1115,691 +903,292 @@ model Message {
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
senderId String senderId String
sender User @relation(fields: [senderId], references: [id]) sender User @relation(fields: [senderId], references: [id])
type MessageType @default(TEXT) content String
content String? attachments Json?
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? deletedAt DateTime?
createdAt DateTime @default(now())
@@index([conversationId]) @@index([conversationId])
@@index([conversationId, createdAt])
@@index([senderId]) @@index([senderId])
@@map("messages") @@index([createdAt])
}
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 { model Notice {
id String @id @default(uuid()) id String @id @default(uuid())
title String title String
content String content String
priority NotificationType @default(IMPORTANT) type NoticeType @default(GENERAL_ANNOUNCEMENT)
isPinned Boolean @default(false) isBlocking Boolean @default(false)
targetRoles Role[] targetRoles Json?
publishedAt DateTime?
expiresAt DateTime?
createdById String createdById String
createdBy User @relation("NoticeCreator", fields: [createdById], references: [id]) createdBy User @relation(fields: [createdById], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([publishedAt]) acknowledgments NoticeAcknowledgment[]
@@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 { @@index([type])
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]) @@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())
@@index([reportId])
@@map("report_amendments")
} }
// ============================================================ model NoticeAcknowledgment {
// PERFORMANCE EVALUATIONS id String @id @default(uuid())
// ============================================================ noticeId String
notice Notice @relation(fields: [noticeId], references: [id], onDelete: Cascade)
model EvaluationTemplate { userId String
id String @id @default(uuid()) user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String createdAt DateTime @default(now())
description String?
criteria EvaluationCriteria[]
evaluations Evaluation[]
isActive Boolean @default(true)
createdById String
createdBy User @relation(fields: [createdById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@map("evaluation_templates")
}
model EvaluationCriteria {
id String @id @default(uuid())
templateId String
template EvaluationTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
name String
description String?
weight Float // percentage (0-100)
maxScore Int @default(5)
order Int
scores EvaluationScore[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([templateId]) @@unique([noticeId, userId])
@@map("evaluation_criteria") @@index([noticeId])
@@index([userId])
} }
model Evaluation { // ============================================
id String @id @default(uuid()) // SCHEDULING & TIME
contractorId String // ============================================
contractor User @relation("EvaluationContractor", fields: [contractorId], references: [id])
evaluatorId String
evaluator User @relation("EvaluationEvaluator", fields: [evaluatorId], references: [id])
templateId String?
template EvaluationTemplate? @relation(fields: [templateId], references: [id])
period String // "2024-Q1", "2024-01", etc.
month Int
year Int
status EvaluationStatus @default(DRAFT)
overallScore Float?
overallComments String?
autoMetrics Json? // snapshot of computed metrics
acknowledgedAt DateTime?
scores EvaluationScore[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([evaluatorId])
@@index([contractorId, month, year])
@@index([status])
@@map("evaluations")
}
model EvaluationScore {
id String @id @default(uuid())
evaluationId String
evaluation Evaluation @relation(fields: [evaluationId], references: [id], onDelete: Cascade)
criteriaId String
criteria EvaluationCriteria @relation(fields: [criteriaId], references: [id])
score Float
comments String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([evaluationId, criteriaId])
@@map("evaluation_scores")
}
// ============================================================
// PERFORMANCE PIPs
// ============================================================
model PIP {
id String @id @default(uuid())
contractorId String
contractor User @relation("PIPContractor", fields: [contractorId], references: [id])
managerId String
manager User @relation("PIPManager", fields: [managerId], references: [id])
reason String
status PIPStatus @default(ACTIVE)
startDate DateTime
endDate DateTime
extendedDate DateTime?
goals PIPGoal[]
checkIns PIPCheckIn[]
outcome String?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([status])
@@index([managerId])
@@map("pips")
}
model PIPGoal { model Holiday {
id String @id @default(uuid()) id String @id @default(uuid())
pipId String name String
pip PIP @relation(fields: [pipId], references: [id], onDelete: Cascade) startDate DateTime
title String endDate DateTime
description String? isRecurring Boolean @default(false)
targetDate DateTime? notes String?
isCompleted Boolean @default(false)
completedAt DateTime?
order Int
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@index([pipId]) @@index([startDate])
@@map("pip_goals")
} }
model PIPCheckIn { model Unavailability {
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 startDate DateTime
meeting Meeting? @relation(fields: [meetingId], references: [id]) endDate DateTime
reason UnavailabilityReason @default(PERSONAL)
notes String? notes String?
progress String? createdAt DateTime @default(now())
date DateTime updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([pipId]) @@index([userId])
@@map("pip_check_ins") @@index([startDate])
} }
// ============================================================ model ScheduleChangeRequest {
// PERFORMANCE LEARNING GOALS & COMPETENCIES id String @id @default(uuid())
// ============================================================ userId String
user User @relation("ScheduleRequester", fields: [userId], references: [id])
reviewedById String?
reviewedBy User? @relation("ScheduleReviewer", fields: [reviewedById], references: [id])
currentSchedule Json
proposedSchedule Json
effectiveDate DateTime
reason String
status String @default("PENDING")
reviewNotes String?
reviewedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
model LearningGoal { @@index([userId])
id String @id @default(uuid())
contractorId String
contractor User @relation(fields: [contractorId], references: [id])
title String
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())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([status]) @@index([status])
@@map("learning_goals")
} }
model Competency { model Meeting {
id String @id @default(uuid()) id String @id @default(uuid())
name String title String
description String? description String?
category String? startTime DateTime
levels Json // [{ level: 1, description: "..." }, ...] endTime DateTime
assessments CompetencyAssessment[] location String?
createdAt DateTime @default(now()) status MeetingStatus @default(SCHEDULED)
updatedAt DateTime @updatedAt recurrence String?
deletedAt DateTime? notes Json?
createdById String
@@map("competencies") createdBy User @relation("MeetingCreator", fields: [createdById], references: [id])
} createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
model CompetencyAssessment { invitees MeetingInvitee[]
id String @id @default(uuid())
competencyId String
competency Competency @relation(fields: [competencyId], references: [id])
contractorId String
contractor User @relation("AssessmentContractor", fields: [contractorId], references: [id])
assessorId String
assessor User @relation("AssessmentAssessor", fields: [assessorId], references: [id])
level Int
notes String?
assessedAt DateTime @default(now())
createdAt DateTime @default(now())
@@index([contractorId]) @@index([startTime])
@@index([competencyId, contractorId]) @@index([status])
@@map("competency_assessments")
} }
// ============================================================ model MeetingInvitee {
// TIME & SCHEDULING id String @id @default(uuid())
// ============================================================ meetingId String
meeting Meeting @relation(fields: [meetingId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
attended Boolean @default(false)
model WorkSchedule { @@unique([meetingId, userId])
id String @id @default(uuid()) @@index([meetingId])
contractorId String @unique @@index([userId])
contractor User @relation(fields: [contractorId], references: [id])
workDays Json // [1,2,3,4,5] = Mon-Fri
startTime String // "09:00"
endTime String // "17:00"
timezone String @default("Africa/Cairo")
effectiveDate DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("work_schedules")
}
model UnavailabilityRequest {
id String @id @default(uuid())
contractorId String
contractor User @relation("UnavailabilityContractor", fields: [contractorId], references: [id])
type UnavailabilityType
startDate DateTime
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())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([status])
@@index([startDate, endDate])
@@map("unavailability_requests")
} }
model Holiday { // ============================================
id String @id @default(uuid()) // DOCUMENTS
name String // ============================================
date DateTime @db.Date
isRecurring Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([date]) model Contract {
@@map("holidays") id String @id @default(uuid())
} userId String
user User @relation(fields: [userId], references: [id])
contractorType String?
contractText String?
scheduleSnapshot Json?
salaryAtSigning Int?
signedAt DateTime?
signatureData Json?
startDate DateTime?
endDate DateTime?
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
model ScheduleChangeRequest { @@index([userId])
id String @id @default(uuid())
contractorId String
contractor User @relation("ScheduleChangeContractor", fields: [contractorId], references: [id])
currentData Json // snapshot of current schedule
requestedData Json // requested changes
reason String
status RequestStatus @default(PENDING)
reviewedById String?
reviewedBy User? @relation("ScheduleChangeReviewer", fields: [reviewedById], references: [id])
reviewedAt DateTime?
reviewNotes String?
effectiveDate DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([status]) @@index([status])
@@map("schedule_change_requests")
} }
// ============================================================ model Policy {
// MEETINGS 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
model Meeting { acknowledgments PolicyAcknowledgment[]
id String @id @default(uuid())
title String
description String?
startTime DateTime
endTime DateTime
location String?
meetingUrl String?
status MeetingStatus @default(SCHEDULED)
createdById String
createdBy User @relation("MeetingCreator", fields: [createdById], references: [id])
participants MeetingParticipant[]
pipCheckIn PIPCheckIn?
notes String?
agenda String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([startTime]) @@index([title])
@@index([createdById])
@@index([status])
@@map("meetings")
} }
model MeetingParticipant { model PolicyAcknowledgment {
id String @id @default(uuid()) id String @id @default(uuid())
meetingId String policyId String
meeting Meeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) policy Policy @relation(fields: [policyId], 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 createdAt DateTime @default(now())
respondedAt DateTime?
@@unique([meetingId, userId]) @@unique([policyId, userId])
@@index([policyId])
@@index([userId]) @@index([userId])
@@map("meeting_participants")
} }
// ============================================================ // ============================================
// CONTRACTS & OFFBOARDING // OFFBOARDING
// ============================================================ // ============================================
model Contract { model Termination {
id String @id @default(uuid()) id String @id @default(uuid())
contractorId String userId String
contractor User @relation(fields: [contractorId], references: [id]) type TerminationType
title String reason String
content String? effectiveDate DateTime
fileId String? noticeDate DateTime @default(now())
file File? @relation(fields: [fileId], references: [id]) finalSettlement Json?
status ContractStatus @default(DRAFT) checklistItems Json?
startDate DateTime status String @default("INITIATED")
endDate DateTime? createdAt DateTime @default(now())
salaryPiasters Int? updatedAt DateTime @updatedAt
contractorType ContractorType?
terms Json?
signedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([contractorId]) @@index([userId])
@@index([status])
@@map("contracts")
}
model OffboardingProcess {
id String @id @default(uuid())
contractorId String
contractor User @relation("OffboardingContractor", fields: [contractorId], references: [id])
initiatedById String
initiatedBy User @relation("OffboardingInitiator", fields: [initiatedById], references: [id])
status OffboardingStatus @default(PENDING)
reason String?
effectiveDate DateTime
items OffboardingItem[]
notes String?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([status]) @@index([status])
@@map("offboarding_processes")
} }
model OffboardingItem { // ============================================
id String @id @default(uuid()) // ONBOARDING
offboardingId String // ============================================
offboarding OffboardingProcess @relation(fields: [offboardingId], references: [id], onDelete: Cascade)
title String model Invite {
description String? id String @id @default(uuid())
isCompleted Boolean @default(false) code String @unique
completedAt DateTime? contractorType ContractorType
order Int status InviteStatus @default(ACTIVE)
createdAt DateTime @default(now()) welcomeNote String?
updatedAt DateTime @updatedAt expiresAt DateTime
usedAt DateTime?
usedById String?
createdById String
createdBy User @relation(fields: [createdById], references: [id])
createdAt DateTime @default(now())
@@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?
isActive Boolean @default(true)
lastUsedAt DateTime? lastUsedAt DateTime?
isActive Boolean @default(true) 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 url String
user User @relation(fields: [userId], references: [id]) secret String?
name String events Json
url String isActive Boolean @default(true)
secret String lastTriggeredAt DateTime?
events String[] // ["card.created", "card.moved", ...] failureCount Int @default(0)
status WebhookStatus @default(ACTIVE) createdAt DateTime @default(now())
failCount Int @default(0) updatedAt DateTime @updatedAt
deliveries WebhookDelivery[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([userId]) @@index([isActive])
@@index([status])
@@map("webhooks")
} }
model WebhookDelivery { model SavedFilter {
id String @id @default(uuid()) id String @id @default(uuid())
webhookId String boardId String
webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade) board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
event String userId String
payload Json name String
responseCode Int? filters Json
responseBody String? createdAt DateTime @default(now())
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())
originalName String
storagePath String
storageKey String @unique
mimeType String
sizeBytes Int
uploadedById String
uploadedBy User @relation(fields: [uploadedById], references: [id])
createdAt DateTime @default(now())
// Reverse relations
cardAttachments CardAttachment[]
messages Message[]
contracts Contract[]
@@index([uploadedById])
@@index([storageKey])
@@map("files")
}
// ============================================================
// PRIVATE NOTES (Admin-only, invisible to contractor)
// ============================================================
model PrivateNote {
id String @id @default(uuid())
subjectId String
subject User @relation("NoteSubject", fields: [subjectId], references: [id])
authorId String
author User @relation("NoteAuthor", fields: [authorId], references: [id])
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([subjectId]) @@index([boardId, userId])
@@index([authorId])
@@map("private_notes")
} }
// ============================================================ model RecurringCardDefinition {
// ACTIVITY FEED id String @id @default(uuid())
// ============================================================ boardId String
title String
model ActivityFeedItem { description String?
id String @id @default(uuid()) labels Json?
actorId String priority CardPriority @default(NONE)
actor User @relation("ActivityActor", fields: [actorId], references: [id]) assigneeIds Json?
action String // "completed_card", "earned_bounty", "submitted_report", etc. checklists Json?
entityType String estimatedHours Float?
entityId String recurrenceType RecurrenceType
metadata Json? // { cardTitle: "...", boardName: "...", amount: 5000 } recurrenceConfig Json?
visibility String @default("org") // "org", "team", "admin" isActive Boolean @default(true)
createdAt DateTime @default(now()) nextRunAt DateTime?
lastRunAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([actorId]) @@index([boardId])
@@index([entityType, entityId]) @@index([isActive])
@@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