Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/prisma/schema/formWidget.prisma
T

242 lines
7.5 KiB
Plaintext

// Embeddable Form-Widget (Phase 05 Task #58).
//
// `Form` is the tenant-authored definition + theme + delivery config that
// powers the iframe-embedded mini-app. `FormSubmission` is the inbox for
// payloads landing through the public submission endpoint. Both rows are
// tenant-scoped so existing tenant-isolation tooling applies as-is.
model Form {
id String @id @default(cuid())
tenantId String
/// Public key used in the embed `<script data-form-key="…">`.
key String @unique @default(cuid())
/// Human-readable public URL slug for `/f/:tenant/:slug`.
slug String?
name String
subtype FormSubtype @default(standard)
status FormStatus @default(draft)
createdByUserId String?
publishedAt DateTime?
isPublic Boolean @default(false)
isDemoData Boolean @default(false)
/// Schema-versioned form definition JSON (validated by `FormDefinitionSchema`).
definition Json
/// Theme JSON (validated by `FormThemeSchema`). Empty object inherits tenant brand.
theme Json @default("{}")
/// Legacy route target. New AK-P16c action routing lives in FormAction.
targetType String @default("generic")
targetEntityId String?
/// Mapping from form field-key -> target row property-key.
fieldMapping Json @default("{}")
/// Allowlist of origins permitted to POST submissions. Empty -> no submissions accepted.
allowedOrigins String[]
notifyEmails String[]
webhookUrl String?
/// Anti-spam / processing config (honeypotEnabled, turnstileEnabled, successMessage override).
config Json @default("{}")
isEnabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
submissions FormSubmission[]
fields FormField[]
actions FormAction[]
files FormFile[]
@@unique([tenantId, slug])
@@index([tenantId, createdAt(sort: Desc)])
@@index([tenantId, createdByUserId, createdAt(sort: Desc)])
@@index([tenantId, status, createdAt(sort: Desc)])
@@index([key])
}
model FormSubmission {
id String @id @default(cuid())
formId String
tenantId String
/// Authenticated submissions have exactly one internal or PortalUser actor.
/// The merge migration adds the database CHECK after legacy public-form
/// backfill; portal services enforce the invariant immediately.
createdByUserId String?
createdByPortalUserId String?
projectId String?
/// Validated payload as { values: Record<string, string|number|boolean|null> }.
payload Json
/// Origin header recorded at submission time, for audit / forensics.
sourceOrigin String?
/// SHA-256 hash of the submitter IP — no raw IP storage for PII reasons.
ipHash String?
userAgent String?
/// "received" | "processed" | "failed" | "spam".
status String @default("received")
/// JSON describing routing/webhook delivery outcome.
result Json?
linkedEntityType String?
linkedEntityId String?
idempotencyKey String?
gdprAccepted Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
createdByPortalUser PortalUser? @relation("PortalFormSubmissionActor", fields: [createdByPortalUserId, tenantId], references: [id, tenantId], onDelete: Restrict)
files FormFile[]
actionLogs FormActionLog[]
@@unique([formId, idempotencyKey])
@@index([formId, createdAt(sort: Desc)])
@@index([tenantId, createdAt(sort: Desc)])
@@index([status])
@@index([tenantId, linkedEntityType, linkedEntityId])
@@index([tenantId, createdByPortalUserId, createdAt(sort: Desc)])
@@index([tenantId, projectId, createdAt(sort: Desc)])
}
enum FormStatus {
draft
published
archived
}
enum FormSubtype {
standard
acceptance_form
onboarding
survey
booking_intake
hr_request
/// WF-P16 S01 type selection: submissions feed the lead inbox by default.
lead
/// WF-P16 S01 type selection: staff-only form without a public link.
internal
portal_form
}
enum FormFieldType {
text
textarea
number
date
time
select
multiselect
boolean
email
phone
file
photo
honeypot
}
enum FormActionType {
auto_match
create_lead
create_contact
create_project
create_task
send_email_to_team
send_email_to_submitter
webhook
trigger_automation
start_double_opt_in
}
enum FormActionLogStatus {
pending
success
failed
skipped
}
model FormField {
id String @id @default(cuid())
formId String
tenantId String
fieldKey String
fieldType FormFieldType
label String
helpText String?
placeholder String?
required Boolean @default(false)
position Int
options Json @default("[]")
validationRules Json @default("{}")
conditionalLogic Json @default("[]")
isSystem Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
@@unique([formId, fieldKey])
@@index([tenantId, formId, position])
}
model FormFile {
id String @id @default(cuid())
formId String
submissionId String
tenantId String
fieldKey String
fileId String
kind String @default("file")
createdAt DateTime @default(now())
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
submission FormSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
@@index([tenantId, formId, fieldKey])
@@index([tenantId, submissionId])
@@index([fileId])
}
model FormAction {
id String @id @default(cuid())
formId String
tenantId String
actionType FormActionType
name String
position Int
enabled Boolean @default(true)
fieldMapping Json @default("{}")
actionConfig Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
form Form @relation(fields: [formId], references: [id], onDelete: Cascade)
logs FormActionLog[]
@@index([tenantId, formId, position])
@@index([tenantId, actionType])
}
model FormActionLog {
id String @id @default(cuid())
formActionId String
submissionId String
formId String
tenantId String
actionType FormActionType
status FormActionLogStatus @default(pending)
attempt Int @default(1)
inputSnapshot Json @default("{}")
outputSnapshot Json?
errorMessage String?
targetType String?
targetId String?
idempotencyKey String
rerunOfLogId String?
startedAt DateTime @default(now())
finishedAt DateTime?
createdAt DateTime @default(now())
action FormAction @relation(fields: [formActionId], references: [id], onDelete: Cascade)
submission FormSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
@@unique([formActionId, submissionId, idempotencyKey])
@@index([tenantId, formId, createdAt(sort: Desc)])
@@index([tenantId, submissionId])
@@index([tenantId, status])
}