a6c0f3de-eeb0-4d76-a57f-f600dd133c06
This commit is contained in:
1 parent
2aa3496a90
commit
762f3d7373
141 files changed
+4485
-69
No files matched your search
+110
-4
@@ -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)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user