Commit 9dc41150 authored by Administrator's avatar Administrator

Update 30 files via Son of Anton

parents
// ============================================================
// AL-ARCADE HR PLATFORM v3.0 "THE GRIND"
// Complete Prisma Schema ALL modules, ALL phases
// ============================================================
generator client {
provider = "prisma-client-js"
previewFeatures = ["fullTextSearch", "fullTextIndex"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ============================================================
// ENUMS
// ============================================================
enum Role {
SUPER_ADMIN
ADMIN
TEAM_LEAD
CONTRACTOR
}
enum ContractorType {
FULL_TIME
PART_TIME
PROJECT_BASED
}
enum UserStatus {
INVITED
ONBOARDING
ACTIVE
ON_PIP
SUSPENDED
OFFBOARDING
OFFBOARDED
}
enum InviteStatus {
PENDING
ACCEPTED
EXPIRED
REVOKED
}
enum BoardVisibility {
PUBLIC
PRIVATE
TEAM
}
enum BoardMemberRole {
OWNER
ADMIN
MEMBER
VIEWER
}
enum CardPriority {
CRITICAL
HIGH
MEDIUM
LOW
NONE
}
enum DeductionType {
MANUAL
AUTO_LATE_REPORT
AUTO_MISSED_REPORT
AUTO_PIP
PRESET
}
enum DeductionStatus {
PENDING
APPROVED
REJECTED
APPEALED
REVERSED
}
enum AdjustmentType {
ADVANCE
REIMBURSEMENT
BONUS
CORRECTION
OTHER
}
enum AdjustmentStatus {
PENDING
APPROVED
REJECTED
}
enum BountyStatus {
PENDING
EARNED
PAID
CANCELLED
}
enum PayrollStatus {
DRAFT
CALCULATING
REVIEW
APPROVED
PAID
CANCELLED
}
enum NotificationType {
PASSIVE
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 {
DRAFT
SUBMITTED
APPROVED
REJECTED
AMENDMENT_REQUESTED
}
enum EvaluationStatus {
DRAFT
IN_PROGRESS
COMPLETED
ACKNOWLEDGED
}
enum PIPStatus {
ACTIVE
PASSED
FAILED
EXTENDED
CANCELLED
}
enum LearningGoalStatus {
NOT_STARTED
IN_PROGRESS
COMPLETED
CANCELLED
}
enum UnavailabilityType {
VACATION
SICK
PERSONAL
EMERGENCY
OTHER
}
enum RequestStatus {
PENDING
APPROVED
REJECTED
CANCELLED
}
enum MeetingStatus {
SCHEDULED
IN_PROGRESS
COMPLETED
CANCELLED
}
enum ContractStatus {
DRAFT
ACTIVE
EXPIRED
TERMINATED
}
enum OffboardingStatus {
PENDING
IN_PROGRESS
COMPLETED
CANCELLED
}
enum WebhookStatus {
ACTIVE
INACTIVE
FAILED
}
enum RecurrencePattern {
DAILY
WEEKLY
BIWEEKLY
MONTHLY
CUSTOM
}
enum AuditAction {
CREATE
UPDATE
DELETE
ARCHIVE
RESTORE
LOGIN
LOGOUT
PASSWORD_RESET
PASSWORD_CHANGE
ROLE_CHANGE
STATUS_CHANGE
APPROVE
REJECT
SUBMIT
MOVE
ASSIGN
UNASSIGN
LOCK
UNLOCK
ACKNOWLEDGE
BULK_ACTION
EXPORT
IMPORT
SETTINGS_CHANGE
GENERATE
SEND
}
// ============================================================
// AUTH & USERS
// ============================================================
model User {
id String @id @default(uuid())
email String @unique
username String @unique
passwordHash String
firstName String
lastName String
displayName String?
avatar String?
role Role @default(CONTRACTOR)
contractorType ContractorType?
status UserStatus @default(INVITED)
department String?
title String?
phone String?
timezone String @default("Africa/Cairo")
bio String?
dateOfBirth DateTime?
startDate DateTime?
lastLoginAt DateTime?
isOnline Boolean @default(false)
createdAt DateTime @default(now())
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
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([role])
@@index([status])
@@index([deletedAt])
@@index([department])
@@map("users")
}
model Session {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
refreshToken String @unique
ipAddress String?
userAgent String?
expiresAt DateTime
lastActiveAt DateTime @default(now())
createdAt DateTime @default(now())
@@index([userId])
@@index([token])
@@index([expiresAt])
@@map("sessions")
}
model PasswordResetToken {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
expiresAt DateTime
usedAt DateTime?
createdAt DateTime @default(now())
@@index([token])
@@index([userId])
@@map("password_reset_tokens")
}
// ============================================================
// SYSTEM SETTINGS
// ============================================================
model SystemSetting {
id String @id @default(uuid())
key String @unique
value Json
description String?
category String @default("general")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([key])
@@index([category])
@@map("system_settings")
}
// ============================================================
// AUDIT TRAIL (IMMUTABLE)
// ============================================================
model AuditLog {
id String @id @default(uuid())
userId String?
action AuditAction
entityType String
entityId String?
oldValue Json?
newValue Json?
metadata Json?
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
// NO updatedAt. NO deletedAt. IMMUTABLE.
@@index([userId])
@@index([entityType, entityId])
@@index([action])
@@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
// ============================================================
model Board {
id String @id @default(uuid())
name String
description String?
visibility BoardVisibility @default(TEAM)
createdById String
createdBy User @relation("BoardCreator", fields: [createdById], references: [id])
prefix String?
color String?
icon String?
isArchived Boolean @default(false)
archivedAt DateTime?
columns Column[]
members BoardMember[]
labels Label[]
cards Card[]
savedFilters SavedFilter[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([createdById])
@@index([visibility])
@@index([deletedAt])
@@index([isArchived])
@@map("boards")
}
model BoardMember {
id String @id @default(uuid())
boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role BoardMemberRole @default(MEMBER)
joinedAt DateTime @default(now())
@@unique([boardId, userId])
@@index([userId])
@@map("board_members")
}
model BoardTemplate {
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")
}
model Column {
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([boardId])
@@index([boardId, position])
@@map("columns")
}
model Label {
id String @id @default(uuid())
boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
name String
color String
cards CardLabel[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([boardId])
@@map("labels")
}
// ============================================================
// CARDS
// ============================================================
model Card {
id String @id @default(uuid())
title 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)
dueDate DateTime?
startDate DateTime?
estimatedHours Float?
actualHours Float?
bountyPiasters Int @default(0)
coverImage String?
isLocked Boolean @default(false)
lockedById String?
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?
archivedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
// Relations
assignees CardAssignee[]
watchers CardWatcher[]
labels CardLabel[]
comments CardComment[]
checklists Checklist[]
attachments CardAttachment[]
activities CardActivity[]
bounties Bounty[]
reportEntries DailyReportEntry[]
@@index([boardId])
@@index([columnId])
@@index([createdById])
@@index([priority])
@@index([dueDate])
@@index([deletedAt])
@@index([boardId, columnId, position])
@@index([parentCardId])
@@map("cards")
}
model CardAssignee {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
assignedAt DateTime @default(now())
@@unique([cardId, userId])
@@index([userId])
@@map("card_assignees")
}
model CardWatcher {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
addedAt DateTime @default(now())
@@unique([cardId, userId])
@@index([userId])
@@map("card_watchers")
}
model CardLabel {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
labelId String
label Label @relation(fields: [labelId], references: [id], onDelete: Cascade)
@@unique([cardId, labelId])
@@map("card_labels")
}
model CardComment {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
authorId String
author User @relation(fields: [authorId], references: [id])
content String
isEdited Boolean @default(false)
parentId String?
parent CardComment? @relation("CommentReplies", fields: [parentId], references: [id])
replies CardComment[] @relation("CommentReplies")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([cardId])
@@index([authorId])
@@index([parentId])
@@map("card_comments")
}
model Checklist {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id], onDelete: Cascade)
title String
position Float
items ChecklistItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([cardId])
@@map("checklists")
}
model ChecklistItem {
id String @id @default(uuid())
checklistId String
checklist Checklist @relation(fields: [checklistId], references: [id], onDelete: Cascade)
title String
isCompleted Boolean @default(false)
completedAt DateTime?
assigneeId String?
position Float
dueDate DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@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 {
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())
@@index([cardId])
@@index([cardId, createdAt])
@@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())
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])
@@map("recurring_cards")
}
model SavedFilter {
id String @id @default(uuid())
boardId String
board Board @relation(fields: [boardId], references: [id], onDelete: Cascade)
userId String
name String
filters Json // { priority: [...], assignees: [...], labels: [...], dueDate: ... }
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([boardId, userId])
@@map("saved_filters")
}
// ============================================================
// FINANCIAL SALARY
// ============================================================
model ContractorSalary {
id String @id @default(uuid())
contractorId String
contractor User @relation("SalaryContractor", fields: [contractorId], references: [id])
baseSalaryPiasters Int
contractorType ContractorType
effectiveDate DateTime
endDate DateTime?
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())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([contractorId, isActive])
@@index([effectiveDate])
@@map("contractor_salaries")
}
// ============================================================
// FINANCIAL BOUNTIES
// ============================================================
model Bounty {
id String @id @default(uuid())
cardId String
card Card @relation(fields: [cardId], references: [id])
contractorId String
contractor User @relation("BountyContractor", fields: [contractorId], references: [id])
amountPiasters Int
status BountyStatus @default(PENDING)
earnedAt DateTime?
payrollItemId String?
payrollItem PayrollItem? @relation(fields: [payrollItemId], references: [id])
createdById String
createdBy User @relation("BountyCreator", fields: [createdById], references: [id])
month Int
year Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([contractorId])
@@index([contractorId, month, year])
@@index([cardId])
@@index([status])
@@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")
}
// ============================================================
// FINANCIAL SALARY ADJUSTMENTS
// ============================================================
model SalaryAdjustment {
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])
@@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])
@@index([status])
@@map("payroll_periods")
}
model PayrollItem {
id String @id @default(uuid())
payrollPeriodId String
payrollPeriod PayrollPeriod @relation(fields: [payrollPeriodId], references: [id], onDelete: Cascade)
contractorId String
contractor User @relation(fields: [contractorId], references: [id])
baseSalaryPiasters Int
totalBountiesPiasters Int @default(0)
totalAdjustmentsPiasters Int @default(0)
totalDeductionsPiasters Int @default(0)
netSalaryPiasters Int
breakdown Json? // detailed breakdown
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 {
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())
@@index([reportId])
@@map("report_amendments")
}
// ============================================================
// PERFORMANCE EVALUATIONS
// ============================================================
model EvaluationTemplate {
id String @id @default(uuid())
name String
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])
@@map("evaluation_criteria")
}
model Evaluation {
id String @id @default(uuid())
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 {
id String @id @default(uuid())
pipId String
pip PIP @relation(fields: [pipId], references: [id], onDelete: Cascade)
title String
description String?
targetDate DateTime?
isCompleted Boolean @default(false)
completedAt DateTime?
order Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([pipId])
@@map("pip_goals")
}
model PIPCheckIn {
id String @id @default(uuid())
pipId String
pip PIP @relation(fields: [pipId], references: [id], onDelete: Cascade)
meetingId String? @unique
meeting Meeting? @relation(fields: [meetingId], references: [id])
notes String?
progress String?
date DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([pipId])
@@map("pip_check_ins")
}
// ============================================================
// PERFORMANCE LEARNING GOALS & COMPETENCIES
// ============================================================
model LearningGoal {
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])
@@map("learning_goals")
}
model Competency {
id String @id @default(uuid())
name String
description String?
category String?
levels Json // [{ level: 1, description: "..." }, ...]
assessments CompetencyAssessment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@map("competencies")
}
model CompetencyAssessment {
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([competencyId, contractorId])
@@map("competency_assessments")
}
// ============================================================
// TIME & SCHEDULING
// ============================================================
model WorkSchedule {
id String @id @default(uuid())
contractorId String @unique
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())
name String
date DateTime @db.Date
isRecurring Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([date])
@@map("holidays")
}
model ScheduleChangeRequest {
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])
@@map("schedule_change_requests")
}
// ============================================================
// MEETINGS
// ============================================================
model Meeting {
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([createdById])
@@index([status])
@@map("meetings")
}
model MeetingParticipant {
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)
status RequestStatus @default(PENDING) // PENDING=invited, APPROVED=accepted, REJECTED=declined
respondedAt DateTime?
@@unique([meetingId, userId])
@@index([userId])
@@map("meeting_participants")
}
// ============================================================
// CONTRACTS & OFFBOARDING
// ============================================================
model Contract {
id String @id @default(uuid())
contractorId String
contractor User @relation(fields: [contractorId], references: [id])
title String
content String?
fileId String?
file File? @relation(fields: [fileId], references: [id])
status ContractStatus @default(DRAFT)
startDate DateTime
endDate DateTime?
salaryPiasters Int?
contractorType ContractorType?
terms Json?
signedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([contractorId])
@@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])
@@map("offboarding_processes")
}
model OffboardingItem {
id String @id @default(uuid())
offboardingId String
offboarding OffboardingProcess @relation(fields: [offboardingId], references: [id], onDelete: Cascade)
title String
description String?
isCompleted Boolean @default(false)
completedAt DateTime?
order Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([offboardingId])
@@map("offboarding_items")
}
// ============================================================
// API KEYS & WEBHOOKS
// ============================================================
model ApiKey {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
name String
keyHash String @unique
keyPrefix String // first 8 chars for identification
permissions Json // ["cards:read", "cards:write", ...]
expiresAt DateTime?
lastUsedAt DateTime?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([keyHash])
@@index([userId])
@@map("api_keys")
}
model Webhook {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
name String
url String
secret String
events String[] // ["card.created", "card.moved", ...]
status WebhookStatus @default(ACTIVE)
failCount Int @default(0)
deliveries WebhookDelivery[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([userId])
@@index([status])
@@map("webhooks")
}
model WebhookDelivery {
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())
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([authorId])
@@map("private_notes")
}
// ============================================================
// ACTIVITY FEED
// ============================================================
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
{
"name": "@the-grind/shared",
"version": "1.0.0",
"description": "Shared types, enums, and socket events for The Grind HR Platform",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"dependencies": {},
"devDependencies": {
"typescript": "^5.3.3"
}
}
\ No newline at end of file
export enum ConversationType {
DIRECT = 'DIRECT',
GROUP = 'GROUP',
}
export enum MessageType {
TEXT = 'TEXT',
FILE = 'FILE',
SYSTEM = 'SYSTEM',
}
export enum NotificationType {
PASSIVE = 'PASSIVE',
IMPORTANT = 'IMPORTANT',
BLOCKING = 'BLOCKING',
}
export enum NotificationCategory {
CARD = 'CARD',
BOARD = 'BOARD',
SALARY = 'SALARY',
DEDUCTION = 'DEDUCTION',
BOUNTY = 'BOUNTY',
ADJUSTMENT = 'ADJUSTMENT',
PAYROLL = 'PAYROLL',
REPORT = 'REPORT',
EVALUATION = 'EVALUATION',
PIP = 'PIP',
MESSAGE = 'MESSAGE',
MEETING = 'MEETING',
NOTICE = 'NOTICE',
POLICY = 'POLICY',
ONBOARDING = 'ONBOARDING',
UNAVAILABILITY = 'UNAVAILABILITY',
SCHEDULE = 'SCHEDULE',
SYSTEM = 'SYSTEM',
}
\ No newline at end of file
export enum DeductionType {
MANUAL = 'MANUAL',
AUTO_LATE_REPORT = 'AUTO_LATE_REPORT',
AUTO_MISSED_REPORT = 'AUTO_MISSED_REPORT',
AUTO_PIP = 'AUTO_PIP',
PRESET = 'PRESET',
}
export enum DeductionStatus {
PENDING = 'PENDING',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
APPEALED = 'APPEALED',
REVERSED = 'REVERSED',
}
export enum AdjustmentType {
ADVANCE = 'ADVANCE',
REIMBURSEMENT = 'REIMBURSEMENT',
BONUS = 'BONUS',
CORRECTION = 'CORRECTION',
OTHER = 'OTHER',
}
export enum AdjustmentStatus {
PENDING = 'PENDING',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
}
export enum BountyStatus {
PENDING = 'PENDING',
EARNED = 'EARNED',
PAID = 'PAID',
CANCELLED = 'CANCELLED',
}
export enum PayrollStatus {
DRAFT = 'DRAFT',
CALCULATING = 'CALCULATING',
REVIEW = 'REVIEW',
APPROVED = 'APPROVED',
PAID = 'PAID',
CANCELLED = 'CANCELLED',
}
\ No newline at end of file
export * from './roles.enum';
export * from './kanban.enum';
export * from './financial.enum';
export * from './communication.enum';
export * from './reports.enum';
export * from './performance.enum';
export * from './status.enum';
\ No newline at end of file
export enum BoardVisibility {
PUBLIC = 'PUBLIC',
PRIVATE = 'PRIVATE',
TEAM = 'TEAM',
}
export enum BoardMemberRole {
OWNER = 'OWNER',
ADMIN = 'ADMIN',
MEMBER = 'MEMBER',
VIEWER = 'VIEWER',
}
export enum CardPriority {
CRITICAL = 'CRITICAL',
HIGH = 'HIGH',
MEDIUM = 'MEDIUM',
LOW = 'LOW',
NONE = 'NONE',
}
export enum RecurrencePattern {
DAILY = 'DAILY',
WEEKLY = 'WEEKLY',
BIWEEKLY = 'BIWEEKLY',
MONTHLY = 'MONTHLY',
CUSTOM = 'CUSTOM',
}
\ No newline at end of file
export enum EvaluationStatus {
DRAFT = 'DRAFT',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
ACKNOWLEDGED = 'ACKNOWLEDGED',
}
export enum PIPStatus {
ACTIVE = 'ACTIVE',
PASSED = 'PASSED',
FAILED = 'FAILED',
EXTENDED = 'EXTENDED',
CANCELLED = 'CANCELLED',
}
export enum LearningGoalStatus {
NOT_STARTED = 'NOT_STARTED',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
}
\ No newline at end of file
export enum ReportStatus {
DRAFT = 'DRAFT',
SUBMITTED = 'SUBMITTED',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
AMENDMENT_REQUESTED = 'AMENDMENT_REQUESTED',
}
\ No newline at end of file
export enum Role {
SUPER_ADMIN = 'SUPER_ADMIN',
ADMIN = 'ADMIN',
TEAM_LEAD = 'TEAM_LEAD',
CONTRACTOR = 'CONTRACTOR',
}
export enum ContractorType {
FULL_TIME = 'FULL_TIME',
PART_TIME = 'PART_TIME',
PROJECT_BASED = 'PROJECT_BASED',
}
export enum UserStatus {
INVITED = 'INVITED',
ONBOARDING = 'ONBOARDING',
ACTIVE = 'ACTIVE',
ON_PIP = 'ON_PIP',
SUSPENDED = 'SUSPENDED',
OFFBOARDING = 'OFFBOARDING',
OFFBOARDED = 'OFFBOARDED',
}
export enum InviteStatus {
PENDING = 'PENDING',
ACCEPTED = 'ACCEPTED',
EXPIRED = 'EXPIRED',
REVOKED = 'REVOKED',
}
\ No newline at end of file
export enum RequestStatus {
PENDING = 'PENDING',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
CANCELLED = 'CANCELLED',
}
export enum MeetingStatus {
SCHEDULED = 'SCHEDULED',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
}
export enum ContractStatus {
DRAFT = 'DRAFT',
ACTIVE = 'ACTIVE',
EXPIRED = 'EXPIRED',
TERMINATED = 'TERMINATED',
}
export enum OffboardingStatus {
PENDING = 'PENDING',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
}
export enum WebhookStatus {
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
FAILED = 'FAILED',
}
export enum UnavailabilityType {
VACATION = 'VACATION',
SICK = 'SICK',
PERSONAL = 'PERSONAL',
EMERGENCY = 'EMERGENCY',
OTHER = 'OTHER',
}
export enum AuditAction {
CREATE = 'CREATE',
UPDATE = 'UPDATE',
DELETE = 'DELETE',
ARCHIVE = 'ARCHIVE',
RESTORE = 'RESTORE',
LOGIN = 'LOGIN',
LOGOUT = 'LOGOUT',
PASSWORD_RESET = 'PASSWORD_RESET',
PASSWORD_CHANGE = 'PASSWORD_CHANGE',
ROLE_CHANGE = 'ROLE_CHANGE',
STATUS_CHANGE = 'STATUS_CHANGE',
APPROVE = 'APPROVE',
REJECT = 'REJECT',
SUBMIT = 'SUBMIT',
MOVE = 'MOVE',
ASSIGN = 'ASSIGN',
UNASSIGN = 'UNASSIGN',
LOCK = 'LOCK',
UNLOCK = 'UNLOCK',
ACKNOWLEDGE = 'ACKNOWLEDGE',
BULK_ACTION = 'BULK_ACTION',
EXPORT = 'EXPORT',
IMPORT = 'IMPORT',
SETTINGS_CHANGE = 'SETTINGS_CHANGE',
GENERATE = 'GENERATE',
SEND = 'SEND',
}
\ No newline at end of file
import { LiveSalaryData } from '../types/salary.types';
export interface HUDSubscribePayload {
contractorId: string;
month: number;
year: number;
}
export interface HUDUpdatePayload {
contractorId: string;
salary: LiveSalaryData;
timestamp: string;
}
export interface HUDBountyEarnedPayload {
contractorId: string;
bountyId: string;
cardTitle: string;
amountPiasters: number;
newNetPiasters: number;
newHealthPercentage: number;
}
export interface HUDDeductionAppliedPayload {
contractorId: string;
deductionId: string;
reason: string;
amountPiasters: number;
type: string;
newNetPiasters: number;
newHealthPercentage: number;
}
export interface HUDAdjustmentAppliedPayload {
contractorId: string;
adjustmentId: string;
reason: string;
amountPiasters: number;
type: string;
newNetPiasters: number;
newHealthPercentage: number;
}
\ No newline at end of file
export * from './socket-events';
export * from './hud.events';
export * from './kanban.events';
export * from './notification.events';
export * from './message.events';
\ No newline at end of file
import { CardSummary, CardMovePayload } from '../types/card.types';
import { ColumnData, LabelData } from '../types/board.types';
export interface KanbanCardCreatedPayload {
boardId: string;
card: CardSummary;
}
export interface KanbanCardUpdatedPayload {
boardId: string;
cardId: string;
changes: Partial<CardSummary>;
}
export interface KanbanCardMovedPayload extends CardMovePayload {
boardId: string;
movedBy: string;
}
export interface KanbanCardDeletedPayload {
boardId: string;
cardId: string;
columnId: string;
}
export interface KanbanCardLockedPayload {
boardId: string;
cardId: string;
lockedBy: {
id: string;
name: string;
};
}
export interface KanbanCardUnlockedPayload {
boardId: string;
cardId: string;
}
export interface KanbanColumnPayload {
boardId: string;
column: ColumnData;
}
export interface KanbanLabelPayload {
boardId: string;
label: LabelData;
}
export interface KanbanTypingPayload {
boardId: string;
cardId: string;
userId: string;
userName: string;
}
export interface KanbanViewingCardPayload {
boardId: string;
cardId: string;
userId: string;
userName: string;
userAvatar: string | null;
}
\ No newline at end of file
import { MessageData, ConversationSummary } from '../types/message.types';
export interface MessageNewPayload {
conversationId: string;
message: MessageData;
}
export interface MessageUpdatedPayload {
conversationId: string;
messageId: string;
content: string;
}
export interface MessageDeletedPayload {
conversationId: string;
messageId: string;
}
export interface MessageTypingPayload {
conversationId: string;
userId: string;
userName: string;
}
export interface MessageReadPayload {
conversationId: string;
userId: string;
messageId: string;
readAt: string;
}
export interface ConversationCreatedPayload {
conversation: ConversationSummary;
}
\ No newline at end of file
import { NotificationData, NotificationCountData } from '../types/notification.types';
export interface NotificationNewPayload {
notification: NotificationData;
}
export interface NotificationBlockingPayload {
notification: NotificationData;
requiresAcknowledgment: true;
}
export interface NotificationCountUpdatePayload {
counts: NotificationCountData;
}
export interface NotificationReadPayload {
notificationId: string;
}
export interface NotificationAcknowledgedPayload {
notificationId: string;
}
\ No newline at end of file
/**
* ALL Socket.io event names for The Grind.
* Namespace convention: domain:action
* Room convention: entity:${entityId} or user:${userId}
*/
// ============================================================
// CONNECTION
// ============================================================
export const SOCKET_EVENTS = {
CONNECTION: {
CONNECT: 'connect',
DISCONNECT: 'disconnect',
ERROR: 'connect_error',
AUTHENTICATE: 'auth:authenticate',
AUTHENTICATED: 'auth:authenticated',
UNAUTHORIZED: 'auth:unauthorized',
},
// ============================================================
// PRESENCE
// ============================================================
PRESENCE: {
USER_ONLINE: 'presence:user_online',
USER_OFFLINE: 'presence:user_offline',
USERS_STATUS: 'presence:users_status',
REQUEST_STATUS: 'presence:request_status',
},
// ============================================================
// HUD (Salary Health Bar)
// ============================================================
HUD: {
SUBSCRIBE: 'hud:subscribe',
UNSUBSCRIBE: 'hud:unsubscribe',
UPDATE: 'hud:update',
BOUNTY_EARNED: 'hud:bounty_earned',
DEDUCTION_APPLIED: 'hud:deduction_applied',
ADJUSTMENT_APPLIED: 'hud:adjustment_applied',
SALARY_CHANGED: 'hud:salary_changed',
},
// ============================================================
// KANBAN
// ============================================================
KANBAN: {
JOIN_BOARD: 'kanban:join_board',
LEAVE_BOARD: 'kanban:leave_board',
CARD_CREATED: 'kanban:card_created',
CARD_UPDATED: 'kanban:card_updated',
CARD_MOVED: 'kanban:card_moved',
CARD_DELETED: 'kanban:card_deleted',
CARD_ARCHIVED: 'kanban:card_archived',
CARD_ASSIGNED: 'kanban:card_assigned',
CARD_UNASSIGNED: 'kanban:card_unassigned',
CARD_LOCKED: 'kanban:card_locked',
CARD_UNLOCKED: 'kanban:card_unlocked',
COLUMN_CREATED: 'kanban:column_created',
COLUMN_UPDATED: 'kanban:column_updated',
COLUMN_DELETED: 'kanban:column_deleted',
COLUMN_REORDERED: 'kanban:column_reordered',
LABEL_CREATED: 'kanban:label_created',
LABEL_UPDATED: 'kanban:label_updated',
LABEL_DELETED: 'kanban:label_deleted',
COMMENT_ADDED: 'kanban:comment_added',
COMMENT_UPDATED: 'kanban:comment_updated',
COMMENT_DELETED: 'kanban:comment_deleted',
CHECKLIST_UPDATED: 'kanban:checklist_updated',
MEMBER_JOINED: 'kanban:member_joined',
MEMBER_LEFT: 'kanban:member_left',
BOARD_UPDATED: 'kanban:board_updated',
TYPING: 'kanban:typing',
VIEWING_CARD: 'kanban:viewing_card',
},
// ============================================================
// NOTIFICATIONS
// ============================================================
NOTIFICATION: {
NEW: 'notification:new',
READ: 'notification:read',
READ_ALL: 'notification:read_all',
ACKNOWLEDGED: 'notification:acknowledged',
COUNT_UPDATE: 'notification:count_update',
BLOCKING: 'notification:blocking',
},
// ============================================================
// MESSAGES
// ============================================================
MESSAGE: {
JOIN_CONVERSATION: 'message:join_conversation',
LEAVE_CONVERSATION: 'message:leave_conversation',
NEW: 'message:new',
UPDATED: 'message:updated',
DELETED: 'message:deleted',
TYPING: 'message:typing',
STOP_TYPING: 'message:stop_typing',
READ: 'message:read',
PINNED: 'message:pinned',
UNPINNED: 'message:unpinned',
CONVERSATION_CREATED: 'message:conversation_created',
PARTICIPANT_ADDED: 'message:participant_added',
PARTICIPANT_REMOVED: 'message:participant_removed',
},
// ============================================================
// ACTIVITY FEED
// ============================================================
ACTIVITY: {
NEW_ITEM: 'activity:new_item',
SUBSCRIBE: 'activity:subscribe',
UNSUBSCRIBE: 'activity:unsubscribe',
},
// ============================================================
// REPORTS
// ============================================================
REPORT: {
SUBMITTED: 'report:submitted',
REVIEWED: 'report:reviewed',
AMENDMENT_REQUESTED: 'report:amendment_requested',
REMINDER: 'report:reminder',
},
// ============================================================
// MEETINGS
// ============================================================
MEETING: {
SCHEDULED: 'meeting:scheduled',
UPDATED: 'meeting:updated',
CANCELLED: 'meeting:cancelled',
STARTING_SOON: 'meeting:starting_soon',
RESPONSE: 'meeting:response',
},
} as const;
// Room name helpers
export const ROOMS = {
user: (userId: string) => `user:${userId}` as const,
hud: (userId: string) => `hud:${userId}` as const,
board: (boardId: string) => `board:${boardId}` as const,
card: (cardId: string) => `card:${cardId}` as const,
conversation: (conversationId: string) => `conversation:${conversationId}` as const,
activity: (scope: 'org' | 'team' | string) => `activity:${scope}` as const,
} as const;
\ No newline at end of file
export * from './enums';
export * from './types';
export * from './events';
\ No newline at end of file
export interface ApiResponse<T = unknown> {
success: boolean;
data: T;
message?: string;
meta?: ApiMeta;
}
export interface ApiMeta {
page?: number;
limit?: number;
total?: number;
totalPages?: number;
}
export interface PaginatedResponse<T> {
success: boolean;
data: T[];
meta: Required<Pick<ApiMeta, 'page' | 'limit' | 'total' | 'totalPages'>>;
}
export interface PaginationQuery {
page?: number;
limit?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
search?: string;
}
export interface BulkActionResult {
success: number;
failed: number;
errors: Array<{ id: string; error: string }>;
}
\ No newline at end of file
import { Role } from '../enums';
export interface AuthTokenPayload {
sub: string; // userId
email: string;
role: Role;
sessionId: string;
iat?: number;
exp?: number;
}
export interface AuthResponse {
accessToken: string;
refreshToken: string;
expiresIn: number;
user: AuthUser;
}
export interface AuthUser {
id: string;
email: string;
username: string;
firstName: string;
lastName: string;
displayName: string | null;
avatar: string | null;
role: Role;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface RefreshTokenRequest {
refreshToken: string;
}
\ No newline at end of file
import { BoardVisibility, BoardMemberRole } from '../enums';
export interface BoardSummary {
id: string;
name: string;
description: string | null;
visibility: BoardVisibility;
prefix: string | null;
color: string | null;
icon: string | null;
isArchived: boolean;
memberCount: number;
cardCount: number;
createdAt: string;
}
export interface BoardDetail extends BoardSummary {
columns: ColumnData[];
labels: LabelData[];
members: BoardMemberData[];
}
export interface ColumnData {
id: string;
name: string;
position: number;
color: string | null;
wipLimit: number | null;
isDone: boolean;
cardCount: number;
}
export interface LabelData {
id: string;
name: string;
color: string;
}
export interface BoardMemberData {
id: string;
userId: string;
role: BoardMemberRole;
user: {
id: string;
firstName: string;
lastName: string;
displayName: string | null;
avatar: string | null;
};
joinedAt: string;
}
\ No newline at end of file
import { CardPriority } from '../enums';
import { UserSummary } from './user.types';
import { LabelData } from './board.types';
export interface CardSummary {
id: string;
title: string;
boardId: string;
columnId: string;
position: number;
priority: CardPriority;
dueDate: string | null;
bountyPiasters: number;
coverImage: string | null;
isLocked: boolean;
assigneeCount: number;
commentCount: number;
checklistProgress: { completed: number; total: number } | null;
labels: LabelData[];
assignees: Array<Pick<UserSummary, 'id' | 'firstName' | 'lastName' | 'avatar'>>;
createdAt: string;
}
export interface CardDetail extends CardSummary {
description: string | null;
startDate: string | null;
estimatedHours: number | null;
actualHours: number | null;
lockedById: string | null;
parentCardId: string | null;
createdBy: Pick<UserSummary, 'id' | 'firstName' | 'lastName' | 'avatar'>;
watchers: Array<Pick<UserSummary, 'id' | 'firstName' | 'lastName' | 'avatar'>>;
checklists: ChecklistData[];
attachments: AttachmentData[];
subtasks: CardSummary[];
completedAt: string | null;
updatedAt: string;
}
export interface ChecklistData {
id: string;
title: string;
position: number;
items: ChecklistItemData[];
}
export interface ChecklistItemData {
id: string;
title: string;
isCompleted: boolean;
completedAt: string | null;
assigneeId: string | null;
position: number;
dueDate: string | null;
}
export interface AttachmentData {
id: string;
fileId: string;
originalName: string;
mimeType: string;
sizeBytes: number;
url: string;
createdAt: string;
}
export interface CardMovePayload {
cardId: string;
fromColumnId: string;
toColumnId: string;
position: number;
}
export interface CardFilterParams {
boardId?: string;
columnId?: string;
assigneeId?: string;
priority?: CardPriority[];
labelIds?: string[];
hasBounty?: boolean;
isOverdue?: boolean;
search?: string;
dueDateFrom?: string;
dueDateTo?: string;
}
\ No newline at end of file
import { EvaluationStatus, PIPStatus } from '../enums';
export interface EvaluationSummary {
id: string;
contractorId: string;
contractorName: string;
evaluatorName: string;
period: string;
status: EvaluationStatus;
overallScore: number | null;
createdAt: string;
}
export interface EvaluationDetail extends EvaluationSummary {
overallComments: string | null;
autoMetrics: AutoMetrics | null;
scores: EvaluationScoreData[];
acknowledgedAt: string | null;
}
export interface EvaluationScoreData {
id: string;
criteriaName: string;
criteriaWeight: number;
maxScore: number;
score: number;
comments: string | null;
}
export interface AutoMetrics {
reportSubmissionRate: number;
lateReportRate: number;
cardsCompleted: number;
averageCardCompletionDays: number;
totalBountiesEarned: number;
totalDeductions: number;
attendanceRate: number;
}
export interface PIPSummary {
id: string;
contractorId: string;
contractorName: string;
managerName: string;
status: PIPStatus;
startDate: string;
endDate: string;
goalProgress: { completed: number; total: number };
}
export interface PIPDetail extends PIPSummary {
reason: string;
goals: PIPGoalData[];
checkIns: PIPCheckInData[];
outcome: string | null;
}
export interface PIPGoalData {
id: string;
title: string;
description: string | null;
targetDate: string | null;
isCompleted: boolean;
completedAt: string | null;
order: number;
}
export interface PIPCheckInData {
id: string;
date: string;
notes: string | null;
progress: string | null;
meetingId: string | null;
}
\ No newline at end of file
export * from './api.types';
export * from './auth.types';
export * from './user.types';
export * from './board.types';
export * from './card.types';
export * from './salary.types';
export * from './notification.types';
export * from './message.types';
export * from './report.types';
export * from './evaluation.types';
export * from './pagination.types';
\ No newline at end of file
import { ConversationType, MessageType } from '../enums';
export interface ConversationSummary {
id: string;
type: ConversationType;
name: string | null;
avatar: string | null;
lastMessage: MessagePreview | null;
unreadCount: number;
participantCount: number;
participants: Array<{
id: string;
firstName: string;
lastName: string;
avatar: string | null;
isOnline: boolean;
}>;
lastMessageAt: string | null;
}
export interface MessagePreview {
id: string;
content: string | null;
type: MessageType;
senderName: string;
createdAt: string;
}
export interface MessageData {
id: string;
conversationId: string;
senderId: string;
senderName: string;
senderAvatar: string | null;
type: MessageType;
content: string | null;
fileUrl: string | null;
fileName: string | null;
replyToId: string | null;
replyToPreview: MessagePreview | null;
isEdited: boolean;
isPinned: boolean;
createdAt: string;
}
export interface SendMessagePayload {
conversationId: string;
content?: string;
type?: MessageType;
fileId?: string;
replyToId?: string;
}
\ No newline at end of file
import { NotificationType, NotificationCategory } from '../enums';
export interface NotificationData {
id: string;
type: NotificationType;
category: NotificationCategory;
title: string;
message: string;
actionUrl: string | null;
metadata: Record<string, unknown> | null;
isRead: boolean;
readAt: string | null;
acknowledgedAt: string | null;
createdAt: string;
}
export interface CreateNotificationPayload {
userId: string;
type: NotificationType;
category: NotificationCategory;
title: string;
message: string;
actionUrl?: string;
metadata?: Record<string, unknown>;
}
export interface NotificationCountData {
total: number;
unread: number;
blocking: number;
}
\ No newline at end of file
export interface PaginationParams {
page: number;
limit: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
export interface PaginatedResult<T> {
data: T[];
meta: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
export interface DateRangeFilter {
from?: string;
to?: string;
}
\ No newline at end of file
import { ReportStatus } from '../enums';
export interface DailyReportSummary {
id: string;
contractorId: string;
contractorName: string;
date: string;
status: ReportStatus;
totalHours: number;
entryCount: number;
isLate: boolean;
submittedAt: string | null;
}
export interface DailyReportDetail extends DailyReportSummary {
summary: string | null;
entries: ReportEntryData[];
reviewedBy: string | null;
reviewedAt: string | null;
reviewNotes: string | null;
amendments: ReportAmendmentData[];
}
export interface ReportEntryData {
id: string;
description: string;
hoursSpent: number;
cardId: string | null;
cardTitle: string | null;
}
export interface ReportAmendmentData {
id: string;
content: string;
requestedAt: string;
resolvedAt: string | null;
}
\ No newline at end of file
export interface LiveSalaryData {
contractorId: string;
month: number;
year: number;
baseSalaryPiasters: number;
totalBountiesPiasters: number;
totalAdjustmentsPiasters: number;
totalDeductionsPiasters: number;
netSalaryPiasters: number;
healthPercentage: number; // net / base * 100
dailyRate: number;
breakdown: SalaryBreakdown;
}
export interface SalaryBreakdown {
bounties: Array<{
id: string;
cardTitle: string;
amountPiasters: number;
earnedAt: string;
}>;
deductions: Array<{
id: string;
reason: string;
amountPiasters: number;
type: string;
createdAt: string;
}>;
adjustments: Array<{
id: string;
reason: string;
amountPiasters: number;
type: string;
createdAt: string;
}>;
}
export interface HUDDisplayData {
baseSalary: string; // formatted EGP
netSalary: string;
healthPercentage: number;
healthColor: string; // green, yellow, orange, red
bountiesEarned: number;
deductionsApplied: number;
adjustmentsApplied: number;
lastUpdated: string;
}
export interface PayrollSummary {
id: string;
month: number;
year: number;
status: string;
totalGrossPiasters: number;
totalNetPiasters: number;
contractorCount: number;
createdAt: string;
}
export interface PayrollItemDetail {
id: string;
contractorId: string;
contractorName: string;
baseSalaryPiasters: number;
totalBountiesPiasters: number;
totalAdjustmentsPiasters: number;
totalDeductionsPiasters: number;
netSalaryPiasters: number;
}
\ No newline at end of file
import { Role, ContractorType, UserStatus } from '../enums';
export interface UserSummary {
id: string;
email: string;
username: string;
firstName: string;
lastName: string;
displayName: string | null;
avatar: string | null;
role: Role;
status: UserStatus;
isOnline: boolean;
}
export interface UserProfile extends UserSummary {
contractorType: ContractorType | null;
department: string | null;
title: string | null;
phone: string | null;
timezone: string;
bio: string | null;
dateOfBirth: string | null;
startDate: string | null;
lastLoginAt: string | null;
createdAt: string;
}
export interface ContractorDirectoryEntry {
id: string;
firstName: string;
lastName: string;
displayName: string | null;
avatar: string | null;
role: Role;
contractorType: ContractorType | null;
department: string | null;
title: string | null;
status: UserStatus;
isOnline: boolean;
}
export interface UserFilterParams {
role?: Role;
status?: UserStatus;
contractorType?: ContractorType;
department?: string;
search?: string;
}
\ No newline at end of file
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
\ 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