// AOS — Agency Operating System // One deployment = one agency (single tenant). SQLite for this build. // Money is integer cents, tax rates are basis points. No floats for money. generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } // --------------------------------------------------------------------------- // Platform // --------------------------------------------------------------------------- model User { id String @id @default(cuid()) email String @unique name String role String @default("owner") // owner|admin|sales|pm|developer|finance|readonly|client createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // Module registry — installed/enabled/disabled state of each domain module model Module { id String @id // crm|leads|projects|licenses|sales-docs|finance|support|... version String @default("1.0.0") enabled Boolean @default(true) installed Boolean @default(true) nav String? // JSON: [{label,route,permission}] events String? // JSON: {emits:[],listens:[]} updatedAt DateTime @updatedAt createdAt DateTime @default(now()) } model Setting { key String @id value String // typed at app layer (encrypted secrets handled separately) updatedAt DateTime @updatedAt } model AuditLog { id String @id @default(cuid()) actor String? // user id or "system" action String // e.g. "crm.deal:update" entity String entityId String before String? // JSON after String? // JSON hash String? // hash chain createdAt DateTime @default(now()) } // --------------------------------------------------------------------------- // CRM (Phase 1) // --------------------------------------------------------------------------- model Company { id String @id @default(cuid()) name String legalForm String? // GmbH, UG, e.K., GbR, AG, Einzelunternehmen... vatId String? // USt-IdNr registerCourt String? registerNumber String? // HRB ... website String? email String? phone String? city String? zip String? street String? source String? // lead-finder|import|manual|referral tags String? // comma separated createdAt DateTime @default(now()) updatedAt DateTime @updatedAt persons Person[] deals Deal[] leads Lead[] licenses License[] invoices Invoice[] projects Project[] } model Person { id String @id @default(cuid()) companyId String? company Company? @relation(fields: [companyId], references: [id]) firstName String lastName String email String? phone String? role String? // Geschäftsführer, Marketing, IT... createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Deal { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id]) title String valueCents Int @default(0) probability Int @default(0) // 0-100 stage String @default("lead") // pipeline stage key expectedClose DateTime? lossReason String? ownerId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // --------------------------------------------------------------------------- // Leads Finder (Phase 2) // --------------------------------------------------------------------------- model Lead { id String @id @default(cuid()) // discovered company info (may become a Company when approved) companyName String website String? city String? branch String? // audit + scoring score Int @default(0) // 0-100 weighted rule score scoreLabel String? // new-site|redesign|seo|aeo|shop|maintenance|accessibility evidence String? // JSON array of {id,check,status,detail} explanation String? // LLM draft "why this lead" — every claim links to an evidence id suggestedAngle String? // proposed outreach angle status String @default("pending") // pending|approved|rejected|snoozed|converted reviewReason String? source String? // serpapi|tavily|apify|firecrawl radiusKm Int? // when converted, link to CRM company + deal companyId String? company Company? @relation(fields: [companyId], references: [id]) createdAt DateTime @default(now()) reviewedAt DateTime? updatedAt DateTime @updatedAt } // --------------------------------------------------------------------------- // Projects (Phase 3) // --------------------------------------------------------------------------- model Project { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id]) name String type String // website|shop|app|retainer status String @default("planning") // planning|active|review|done|on-hold|cancelled budgetType String @default("fixed") // fixed|time-materials|retainer budgetHours Int? spentHours Int @default(0) fixedPriceCents Int? startDate DateTime? dueDate DateTime? progress Int @default(0) // 0-100 createdAt DateTime @default(now()) updatedAt DateTime @updatedAt tasks Task[] timeEntries TimeEntry[] } model Task { id String @id @default(cuid()) projectId String project Project @relation(fields: [projectId], references: [id]) title String description String? status String @default("todo") // todo|in-progress|review|done assignee String? estimateMin Int? priority String @default("medium") // low|medium|high createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TimeEntry { id String @id @default(cuid()) projectId String project Project @relation(fields: [projectId], references: [id]) userId String? description String minutes Int billable Boolean @default(true) rateCentsPerHour Int? invoiced Boolean @default(false) date DateTime @default(now()) createdAt DateTime @default(now()) } // --------------------------------------------------------------------------- // Licenses (Phase 4) // --------------------------------------------------------------------------- model License { id String @id @default(cuid()) key String @unique companyId String company Company @relation(fields: [companyId], references: [id]) product String // e.g. aos-cms, wp-plugin-xyz plan String // starter|pro|enterprise status String @default("active") // active|suspended|revoked|expired maxActivations Int @default(1) validUntil DateTime? token String? // Ed25519-signed token (mocked) createdAt DateTime @default(now()) activations LicenseActivation[] } model LicenseActivation { id String @id @default(cuid()) licenseId String license License @relation(fields: [licenseId], references: [id]) domain String fingerprint String? environment String @default("production") // production|staging version String? lastCheck DateTime @default(now()) createdAt DateTime @default(now()) } // Asset & renewal register (what the agency manages/pays for on behalf of clients) model Asset { id String @id @default(cuid()) name String type String // domain|hosting|ssl|plugin-seat|saas provider String? // Ionos, Strato, Cloudflare... companyId String? // may be internal asset costCentsPerMonth Int? renewalDate DateTime rebill Boolean @default(false) markupPct Int @default(0) status String @default("active") // active|expiring|expired|cancelled createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // --------------------------------------------------------------------------- // Finance (Phase 5) // --------------------------------------------------------------------------- model Invoice { id String @id @default(cuid()) number String @unique // gapless, e.g. RE-2026-0001 companyId String company Company @relation(fields: [companyId], references: [id]) status String @default("draft") // draft|sent|paid|overdue|cancelled|storno type String @default("final") // final|partial|recurring|credit issueDate DateTime @default(now()) dueDate DateTime paidDate DateTime? netCents Int @default(0) taxBp Int @default(1900) // 19% as basis points grossCents Int @default(0) // E-Rechnung / compliance zugferd Boolean @default(false) xrechnung Boolean @default(false) datevExported Boolean @default(false) projectId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lines InvoiceLine[] } model InvoiceLine { id String @id @default(cuid()) invoiceId String invoice Invoice @relation(fields: [invoiceId], references: [id]) description String quantity Int @default(1) unitCents Int totalCents Int }