a6c0f3de-eeb0-4d76-a57f-f600dd133c06

This commit is contained in:
Z User committed 2026-10-28 15:39:30 +00:00
1 parent 2aa3496a90
commit 762f3d7373
141 files changed
+4485 -69

No files matched your search

+110 -4
View File
@@ -71,8 +71,28 @@ model Company {
city String?
zip String?
street String?
source String? // lead-finder|import|manual|referral
source String? // lead-finder|import|manual|referral|agency|self-signup
tags String? // comma separated
// --- B2B Agency / Reseller (PRD §6.5) ---
// A company may be a regular client OR an agency (reseller). Agencies
// onboard their own clients and license them; they cannot create invoices
// (they must use their own accounting system, e.g. lexoffice/sevdesk via REST API).
isAgency Boolean @default(false)
// Agency approval: pending (just signed up) → approved (admin allowed) → rejected.
agencyStatus String? // null when not an agency; "pending"|"approved"|"rejected"
agencyAppliedAt DateTime?
agencyApprovedAt DateTime?
agencyRejectedAt DateTime?
agencyRejectReason String?
// Admin-set discount percentage applied to all licenses this agency buys
// from us. Range 0-20 (admin decision only). Default 0.
agencyDiscountPct Int @default(0)
// Self-serve clients belong to an agency. NULL = direct client of ours.
parentAgencyId String? // FK to Company(id) — the agency that owns this client
parentAgency Company? @relation("AgencyClients", fields: [parentAgencyId], references: [id])
agencyClients Company[] @relation("AgencyClients")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
persons Person[]
@@ -83,6 +103,8 @@ model Company {
projects Project[]
contracts Contract[]
expenses Expense[]
tickets Ticket[]
agencyApplication AgencyApplication?
}
model Person {
@@ -216,12 +238,59 @@ model License {
key String @unique
companyId String
company Company @relation(fields: [companyId], references: [id])
product String // e.g. aos-cms, wp-plugin-xyz
product String // e.g. aos-cms, restov1, wp-plugin-xyz
plan String // starter|pro|enterprise
status String @default("active") // active|suspended|revoked|expired
status String @default("active") // active|suspended|revoked|expired|cancelled
// environment: demo licenses are feature-limited / watermarked / time-boxed
// and can be promoted to production via the license server.
environment String @default("production") // demo|production
// Modular licensing: a license may be the "core" license, or a module add-on.
// Module licenses reference their parent core license via parentId.
parentId String? // null = core license; non-null = module add-on
parent License? @relation("LicenseModules", fields: [parentId], references: [id])
modules License[] @relation("LicenseModules")
// The module key this license grants (only when parentId is set).
// e.g. "online-order", "payment", "reservation", "delivery", "table-service"
module String? // null for core licenses
maxActivations Int @default(1)
validUntil DateTime?
token String? // Ed25519-signed token (mocked)
// Suspend/bloke reason + timestamp — set when status -> suspended.
suspendReason String?
suspendedAt DateTime?
// --- B2B Subscription & Billing (German law) ---
// Kündigung frist: 3 months (90 days) notice period before the next renewal.
// A client can cancel, but the cancellation only takes effect at the end of
// the notice period. Set on issuance, defaults to 90 days.
noticePeriodDays Int @default(90)
// Trial: first 14 days are a free trial. Subscription can be cancelled within
// these 14 days at any time with immediate effect (no charge). Day 15 the
// paid subscription starts.
trialEndsAt DateTime? // null = no trial (e.g. agency-issued)
subscriptionStartsAt DateTime? // when the paid subscription begins (trial + 1 day)
// Billing cycle: monthly or yearly. Yearly gets a discount on the total.
billingCycle String @default("monthly") // monthly|yearly
// The price in cents per billing cycle BEFORE any discount.
// For monthly: this is the monthly price (e.g. 5000 = 50 €).
// For yearly: this is the yearly total (e.g. 60000 = 600 € before yearly discount).
priceCents Int @default(0)
// Discount percentage applied to this license (0-100).
// For agencies: copied from Company.agencyDiscountPct at issue time.
// For yearly billing: a yearly discount (e.g. 10%) is applied on top.
discountPct Int @default(0)
// The effective amount actually charged (priceCents - all discounts).
// Computed at issue time and stored for audit/GoBD.
netPriceCents Int @default(0)
// Cancellation tracking (Kündigung).
cancelledAt DateTime? // when the client cancelled
cancellationEffectiveAt DateTime? // when the cancellation takes effect (notice period end)
cancellationReason String?
// Stripe integration (optional — populated when paid via Stripe).
stripeCustomerId String?
stripeSubscriptionId String?
stripePriceId String?
createdAt DateTime @default(now())
activations LicenseActivation[]
}
@@ -367,8 +436,17 @@ model Ticket {
priority String @default("normal") // low|normal|high|urgent
categoryId String // bug|anfrage|aenderung|konto|rechnung|hosting
companyId String
company Company @relation(fields: [companyId], references: [id])
projectId String?
licenseId String?
// --- Ticket routing (agency model) ---
// If the client belongs to an agency, the ticket is routed to that agency
// (agencyCompanyId). The agency handles it; only if the agency escalates
// does the ticket reach us (routedToAgency = false, escalatedToAdmin = true).
agencyCompanyId String? // the reseller that owns this client (if any)
routedToAgency Boolean @default(false) // ticket sent to agency inbox
escalatedToAdmin Boolean @default(false) // agency escalated to us
escalatedAt DateTime?
// SLA
slaResponseHours Int @default(8) // first response target
slaResolveHours Int @default(48) // resolution target
@@ -387,12 +465,40 @@ model TicketMessage {
id String @id @default(cuid())
ticketId String
ticket Ticket @relation(fields: [ticketId], references: [id])
fromType String // customer|staff|system
fromType String // customer|staff|system|agency
fromName String
body String
createdAt DateTime @default(now())
}
// ---------------------------------------------------------------------------
// Agency Applications (PRD §6.5 — reseller onboarding)
// ---------------------------------------------------------------------------
// When a company signs up, they may choose to become an agency (reseller).
// The application is stored here and awaits admin approval. On approval,
// the Company.isAgency flag is set, Company.agencyStatus = "approved", and
// an email is sent to the applicant with their discount percentage.
model AgencyApplication {
id String @id @default(cuid())
companyId String @unique
company Company @relation(fields: [companyId], references: [id])
status String @default("pending") // pending|approved|rejected
// Discount the admin grants this agency (0-20%). Applied to all licenses
// the agency buys from us. Admin-only decision.
discountPct Int @default(0) // 0-20
// Business justification fields (filled by the applicant)
businessModel String? // e.g. "Webdesign agency", "IT consultant"
expectedClientVolume String? // e.g. "10-20 clients"
referredBy String? // how did they hear about us
// Review
reviewedBy String?
reviewedAt DateTime?
rejectReason String?
appliedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ---------------------------------------------------------------------------
// Estimates / Angebote (PRD §6.5 sales-docs — quotes that convert to invoice)
// ---------------------------------------------------------------------------