add prisma/schema.prisma
This commit is contained in:
parent
55b96aadb2
commit
ba0429c26b
471
prisma/schema.prisma
Normal file
471
prisma/schema.prisma
Normal file
@ -0,0 +1,471 @@
|
||||
// ============================================================================
|
||||
// WG- & Vermieter-Management-App — Prisma Schema (PostgreSQL)
|
||||
// Objekt: 170 qm 4er-WG, Nackenheim (Mainz), Einzelzimmervermietung
|
||||
// ============================================================================
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ENUMS
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
enum UserRole {
|
||||
LANDLORD
|
||||
TENANT
|
||||
CRAFTSMAN
|
||||
ADMIN // Property-Manager / Vertretung des Vermieters
|
||||
}
|
||||
|
||||
enum RoomStatus {
|
||||
OCCUPIED
|
||||
VACANT
|
||||
MAINTENANCE
|
||||
}
|
||||
|
||||
enum RentAdjustmentType {
|
||||
NONE
|
||||
INDEX_MIETE // Indexmietklausel
|
||||
STAFFEL_MIETE // Staffelmietklausel
|
||||
}
|
||||
|
||||
enum PaymentStatus {
|
||||
PENDING
|
||||
PAID
|
||||
PARTIAL
|
||||
OVERDUE
|
||||
MATCH_UNCERTAIN // Betrag/Referenz weicht ab -> manuelle Prüfung nötig
|
||||
}
|
||||
|
||||
enum TicketCategory {
|
||||
SANITAER
|
||||
ELEKTRIK
|
||||
MOEBEL
|
||||
HEIZUNG
|
||||
SCHIMMEL
|
||||
SCHLUESSEL_SCHLOSS
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
enum TicketPriority {
|
||||
LOW
|
||||
MEDIUM
|
||||
HIGH
|
||||
EMERGENCY
|
||||
}
|
||||
|
||||
enum TicketStatus {
|
||||
OPEN
|
||||
IN_PROGRESS
|
||||
RESOLVED
|
||||
CLOSED
|
||||
}
|
||||
|
||||
enum CleaningArea {
|
||||
BATHROOM_1
|
||||
BATHROOM_2
|
||||
KITCHEN
|
||||
HALLWAY_LAUNDRY
|
||||
}
|
||||
|
||||
enum CleaningStatus {
|
||||
PENDING
|
||||
COMPLETED
|
||||
VERIFIED
|
||||
MISSED
|
||||
}
|
||||
|
||||
enum ItemCondition {
|
||||
NEW
|
||||
GOOD
|
||||
WEAR_AND_TEAR
|
||||
DAMAGED
|
||||
}
|
||||
|
||||
enum ExpenseCategory {
|
||||
HAUSHALT // Putzmittel, Klopapier etc.
|
||||
LEBENSMITTEL_GEMEINSAM
|
||||
REPARATUR_VORAUSLAGE
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
enum SettlementStatus {
|
||||
OPEN
|
||||
SETTLED
|
||||
}
|
||||
|
||||
enum HandoverType {
|
||||
MOVE_IN
|
||||
MOVE_OUT
|
||||
}
|
||||
|
||||
enum GuestCodeStatus {
|
||||
ACTIVE
|
||||
EXPIRED
|
||||
REVOKED
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// USERS
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
passwordHash String @map("password_hash")
|
||||
fullName String @map("full_name")
|
||||
phoneNumber String? @map("phone_number")
|
||||
role UserRole
|
||||
avatarUrl String? @map("avatar_url")
|
||||
pushToken String? @map("push_token") // OneSignal Player ID
|
||||
|
||||
// Ein Mieter ist aktuell genau einem Zimmer zugeordnet (Bequemlichkeits-FK,
|
||||
// die verbindliche Quelle bleibt Contract).
|
||||
roomId String? @map("room_id")
|
||||
room Room? @relation("RoomOccupant", fields: [roomId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// Relations
|
||||
contracts Contract[]
|
||||
createdTickets Ticket[] @relation("TicketCreator")
|
||||
assignedTickets Ticket[] @relation("TicketAssignee")
|
||||
cleaningTasks CleaningTask[]
|
||||
expensesPaid Expense[]
|
||||
expenseShares ExpenseShare[]
|
||||
handoverProtocols HandoverProtocol[]
|
||||
guestCodesIssued GuestCode[]
|
||||
notifications Notification[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ROOMS
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model Room {
|
||||
id String @id @default(uuid())
|
||||
roomNumber String @map("room_number") // "Zimmer 1" .. "Zimmer 4"
|
||||
sizeSqm Decimal @map("size_sqm") @db.Decimal(5, 2) // 20.0 / 16.0
|
||||
baseRent Decimal @map("base_rent") @db.Decimal(8, 2) // Nettokaltmiete
|
||||
utilityPauschal Decimal @map("utility_pauschal") @db.Decimal(8, 2)
|
||||
status RoomStatus @default(VACANT)
|
||||
floorPlanUrl String? @map("floor_plan_url")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
occupants User[] @relation("RoomOccupant")
|
||||
contracts Contract[]
|
||||
tickets Ticket[]
|
||||
inventoryItems Inventory[]
|
||||
meterReadings MeterReading[]
|
||||
|
||||
@@map("rooms")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CONTRACTS
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model Contract {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
roomId String @map("room_id")
|
||||
room Room @relation(fields: [roomId], references: [id])
|
||||
|
||||
startDate DateTime @map("start_date")
|
||||
endDate DateTime? @map("end_date") // NULL = unbefristet
|
||||
|
||||
totalWarmRent Decimal @map("total_warm_rent") @db.Decimal(8, 2)
|
||||
depositAmount Decimal @map("deposit_amount") @db.Decimal(8, 2)
|
||||
paymentDueDay Int @default(3) @map("payment_due_day") // Fälligkeit: 3. Werktag
|
||||
|
||||
// Kündigungsfristen & Anpassungsklauseln
|
||||
noticePeriodMonths Int @default(3) @map("notice_period_months")
|
||||
rentAdjustmentType RentAdjustmentType @default(NONE) @map("rent_adjustment_type")
|
||||
rentAdjustmentClause String? @map("rent_adjustment_clause") @db.Text // Freitext der Klausel
|
||||
nextAdjustmentDate DateTime? @map("next_adjustment_date")
|
||||
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
|
||||
contractPdfUrl String? @map("contract_pdf_url")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
payments Payment[]
|
||||
handoverProtocols HandoverProtocol[]
|
||||
|
||||
@@index([roomId, isActive])
|
||||
@@map("contracts")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PAYMENTS (Banking API Abgleich)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model Payment {
|
||||
id String @id @default(uuid())
|
||||
contractId String @map("contract_id")
|
||||
contract Contract @relation(fields: [contractId], references: [id])
|
||||
|
||||
amount Decimal @db.Decimal(8, 2)
|
||||
dueDate DateTime @map("due_date")
|
||||
paidAt DateTime? @map("paid_at")
|
||||
|
||||
status PaymentStatus @default(PENDING)
|
||||
|
||||
// Banking-API (FinAPI / Nordigen / GoCardless)
|
||||
bankTransactionId String? @unique @map("bank_transaction_id")
|
||||
bankReferenceText String? @map("bank_reference_text")
|
||||
matchedAutomatically Boolean @default(false) @map("matched_automatically")
|
||||
|
||||
dunningSentAt DateTime? @map("dunning_sent_at") // Zeitpunkt Mahn-Notification
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([contractId, dueDate])
|
||||
@@index([status])
|
||||
@@map("payments")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TICKETS (Schadensmelder)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model Ticket {
|
||||
id String @id @default(uuid())
|
||||
creatorId String @map("creator_id")
|
||||
creator User @relation("TicketCreator", fields: [creatorId], references: [id])
|
||||
assigneeId String? @map("assignee_id") // Handwerker
|
||||
assignee User? @relation("TicketAssignee", fields: [assigneeId], references: [id])
|
||||
roomId String? @map("room_id") // NULL = Gemeinschaftsbereich
|
||||
room Room? @relation(fields: [roomId], references: [id])
|
||||
|
||||
title String
|
||||
description String @db.Text
|
||||
category TicketCategory
|
||||
priority TicketPriority @default(MEDIUM)
|
||||
status TicketStatus @default(OPEN)
|
||||
|
||||
imageUrls String[] @map("image_urls") // Foto-Beweise
|
||||
pdfExportUrl String? @map("pdf_export_url") // 1-Klick-PDF für Handwerker
|
||||
|
||||
resolvedAt DateTime? @map("resolved_at")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([status, priority])
|
||||
@@map("tickets")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CLEANING TASKS (Putzplan)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model CleaningTask {
|
||||
id String @id @default(uuid())
|
||||
assignedUserId String @map("assigned_user_id")
|
||||
assignedUser User @relation(fields: [assignedUserId], references: [id])
|
||||
|
||||
area CleaningArea
|
||||
weekOf DateTime @map("week_of") // Montag der jeweiligen Rotationswoche
|
||||
|
||||
dueDate DateTime @map("due_date")
|
||||
completedAt DateTime? @map("completed_at")
|
||||
|
||||
proofImageUrl String? @map("proof_image_url")
|
||||
status CleaningStatus @default(PENDING)
|
||||
verifiedBy String? @map("verified_by") // optionale Mitbewohner-Bestätigung
|
||||
|
||||
reminderSentAt DateTime? @map("reminder_sent_at")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([assignedUserId, weekOf])
|
||||
@@index([status, dueDate])
|
||||
@@map("cleaning_tasks")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// INVENTORY (Möbel & Geräte)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model Inventory {
|
||||
id String @id @default(uuid())
|
||||
roomId String? @map("room_id") // NULL = Gemeinschaftsfläche
|
||||
room Room? @relation(fields: [roomId], references: [id])
|
||||
|
||||
itemName String @map("item_name")
|
||||
purchaseDate DateTime? @map("purchase_date")
|
||||
purchasePrice Decimal? @map("purchase_price") @db.Decimal(8, 2)
|
||||
condition ItemCondition @default(GOOD)
|
||||
manualPdfUrl String? @map("manual_pdf_url") // Anleitung (z.B. Entkalkung)
|
||||
photoUrl String? @map("photo_url")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
handoverItems HandoverItem[]
|
||||
|
||||
@@map("inventory")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// EXPENSES (WG-Kasse)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model Expense {
|
||||
id String @id @default(uuid())
|
||||
payerId String @map("payer_id")
|
||||
payer User @relation(fields: [payerId], references: [id])
|
||||
|
||||
amount Decimal @db.Decimal(8, 2)
|
||||
description String
|
||||
category ExpenseCategory @default(HAUSHALT)
|
||||
receiptUrl String? @map("receipt_url")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
shares ExpenseShare[]
|
||||
|
||||
@@map("expenses")
|
||||
}
|
||||
|
||||
// Automatischer Split (Standard: durch 4), pro Mieter ein Anteil
|
||||
model ExpenseShare {
|
||||
id String @id @default(uuid())
|
||||
expenseId String @map("expense_id")
|
||||
expense Expense @relation(fields: [expenseId], references: [id], onDelete: Cascade)
|
||||
userId String @map("user_id")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
shareAmount Decimal @map("share_amount") @db.Decimal(8, 2)
|
||||
settlementStatus SettlementStatus @default(OPEN) @map("settlement_status")
|
||||
settledAt DateTime? @map("settled_at")
|
||||
|
||||
@@unique([expenseId, userId])
|
||||
@@map("expense_shares")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// HANDOVER PROTOCOLS (Übergabeprotokoll bei Ein-/Auszug)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model HandoverProtocol {
|
||||
id String @id @default(uuid())
|
||||
contractId String @map("contract_id")
|
||||
contract Contract @relation(fields: [contractId], references: [id])
|
||||
userId String @map("user_id")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
type HandoverType
|
||||
protocolDate DateTime @map("protocol_date")
|
||||
signatureUrl String? @map("signature_url") // digitale Unterschrift (Bild/SVG)
|
||||
pdfUrl String? @map("pdf_url")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
items HandoverItem[]
|
||||
meterReadings MeterReading[]
|
||||
|
||||
@@map("handover_protocols")
|
||||
}
|
||||
|
||||
model HandoverItem {
|
||||
id String @id @default(uuid())
|
||||
protocolId String @map("protocol_id")
|
||||
protocol HandoverProtocol @relation(fields: [protocolId], references: [id], onDelete: Cascade)
|
||||
inventoryId String? @map("inventory_id")
|
||||
inventory Inventory? @relation(fields: [inventoryId], references: [id])
|
||||
|
||||
condition ItemCondition
|
||||
note String?
|
||||
photoUrl String? @map("photo_url")
|
||||
|
||||
@@map("handover_items")
|
||||
}
|
||||
|
||||
model MeterReading {
|
||||
id String @id @default(uuid())
|
||||
protocolId String? @map("protocol_id")
|
||||
protocol HandoverProtocol? @relation(fields: [protocolId], references: [id])
|
||||
roomId String? @map("room_id")
|
||||
room Room? @relation(fields: [roomId], references: [id])
|
||||
|
||||
meterType String @map("meter_type") // STROM, WASSER, GAS
|
||||
value Decimal @db.Decimal(10, 2)
|
||||
readAt DateTime @map("read_at")
|
||||
photoUrl String? @map("photo_url")
|
||||
|
||||
@@map("meter_readings")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// SMART LOCK / GUEST CODES
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model GuestCode {
|
||||
id String @id @default(uuid())
|
||||
issuedById String @map("issued_by_id")
|
||||
issuedBy User @relation(fields: [issuedById], references: [id])
|
||||
|
||||
code String @unique
|
||||
lockDeviceId String @map("lock_device_id") // Nuki/Tuya Device-ID
|
||||
validFrom DateTime @map("valid_from")
|
||||
validUntil DateTime @map("valid_until")
|
||||
status GuestCodeStatus @default(ACTIVE)
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("guest_codes")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// UNMATCHED TRANSACTIONS (Banking-Abgleich ohne eindeutigen Treffer)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model UnmatchedTransactionLog {
|
||||
id String @id @default(uuid())
|
||||
bankTransactionId String @unique @map("bank_transaction_id")
|
||||
amount Decimal @db.Decimal(8, 2)
|
||||
remittanceInfo String @map("remittance_info") @db.Text
|
||||
reason String @db.Text
|
||||
resolvedPaymentId String? @map("resolved_payment_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("unmatched_transaction_logs")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// NOTIFICATIONS (Push-Log)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
model Notification {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
type String // z.B. "RENT_OVERDUE", "CLEANING_REMINDER", "TRASH_TOMORROW"
|
||||
title String
|
||||
body String
|
||||
payload Json?
|
||||
sentAt DateTime @default(now()) @map("sent_at")
|
||||
readAt DateTime? @map("read_at")
|
||||
|
||||
@@index([userId, sentAt])
|
||||
@@map("notifications")
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user