BREAKING: Full rewrite from static HTML to production-grade stack. Features: - React 18 + Vite + Tailwind CSS (mobile-first) - Express + Prisma + PostgreSQL backend - JWT authentication with role-based access - Weekly Mon-Sun timesheets with auto-save - Multiple homeowner entries per day - Submit → Approve/Reject workflow - Server-side PDF generation - SMTP email integration - Admin panel with reporting & filters - Dark mode (system-aware) - Docker Compose one-command deploy - Non-root containers, Helmet, bcrypt, Zod validation
107 lines
3.2 KiB
Plaintext
107 lines
3.2 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum Role {
|
|
employee
|
|
admin
|
|
super_admin
|
|
}
|
|
|
|
enum TimesheetStatus {
|
|
draft
|
|
submitted
|
|
approved
|
|
rejected
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
name String
|
|
role Role @default(employee)
|
|
passwordHash String @map("password_hash")
|
|
isActive Boolean @default(true) @map("is_active")
|
|
refreshToken String? @map("refresh_token")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
timeEntries TimeEntry[]
|
|
timesheets Timesheet[] @relation("UserTimesheets")
|
|
approvals Timesheet[] @relation("ApprovedTimesheets")
|
|
|
|
@@map("users")
|
|
}
|
|
|
|
model Homeowner {
|
|
id String @id @default(uuid())
|
|
name String @unique
|
|
address String?
|
|
isActive Boolean @default(true) @map("is_active")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
timeEntries TimeEntry[]
|
|
|
|
@@map("homeowners")
|
|
}
|
|
|
|
model TimeEntry {
|
|
id String @id @default(uuid())
|
|
userId String @map("user_id")
|
|
date DateTime @db.Date
|
|
homeownerId String @map("homeowner_id")
|
|
hoursWorked Decimal @map("hours_worked") @db.Decimal(4, 2)
|
|
workDescription String @map("work_description")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
homeowner Homeowner @relation(fields: [homeownerId], references: [id])
|
|
timesheetLinks TimesheetEntry[]
|
|
|
|
@@index([userId, date])
|
|
@@index([homeownerId])
|
|
@@map("time_entries")
|
|
}
|
|
|
|
model Timesheet {
|
|
id String @id @default(uuid())
|
|
userId String @map("user_id")
|
|
weekStart DateTime @map("week_start") @db.Date
|
|
weekEnd DateTime @map("week_end") @db.Date
|
|
status TimesheetStatus @default(draft)
|
|
submittedAt DateTime? @map("submitted_at")
|
|
approvedBy String? @map("approved_by")
|
|
approvedAt DateTime? @map("approved_at")
|
|
notes String?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
user User @relation("UserTimesheets", fields: [userId], references: [id], onDelete: Cascade)
|
|
approver User? @relation("ApprovedTimesheets", fields: [approvedBy], references: [id])
|
|
entries TimesheetEntry[]
|
|
|
|
@@unique([userId, weekStart])
|
|
@@index([status])
|
|
@@index([userId, weekStart])
|
|
@@map("timesheets")
|
|
}
|
|
|
|
model TimesheetEntry {
|
|
id String @id @default(uuid())
|
|
timesheetId String @map("timesheet_id")
|
|
timeEntryId String @map("time_entry_id")
|
|
|
|
timesheet Timesheet @relation(fields: [timesheetId], references: [id], onDelete: Cascade)
|
|
timeEntry TimeEntry @relation(fields: [timeEntryId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([timesheetId, timeEntryId])
|
|
@@map("timesheet_entries")
|
|
}
|