feat: add Workflows Collection for complex process management

Hybrid solution for process visualization:

## Timeline Collection (Simple Processes)
Enhanced with process-specific fields for simple linear flows:
- Step number, duration, responsible person
- Action required indicator (customer/internal/both/automatic)
- Deliverables/documents per step

Ideal for: Onboarding, Bewerbungsprozess, simple customer journeys

## Workflows Collection (Complex Processes)
New dedicated collection for multi-phase workflows with:

**Phases:**
- Named sections with icons, colors, estimated duration
- Responsible person/role assignment
- Phase-level deliverables

**Steps:**
- Multiple types: task, decision, milestone, approval, wait, automatic
- Priority levels: critical, high, normal, low, optional
- Dependencies between steps (blocking, parallel)
- Conditions/branches for decision steps
- Checklists with required/optional items
- Resources (documents, templates, links, tools)
- Outputs per step

**Properties:**
- Workflow types: project, business, approval, onboarding, support, development, marketing
- Complexity levels, iterative flag, parallel phases flag
- Display options: vertical, horizontal, flowchart, kanban, gantt layouts

**API Features:**
- Public endpoint at /api/workflows with tenant isolation
- Filter by type, complexity
- Statistics: phase count, step count, checklist count, step type breakdown

Database: 20 new tables (18 for workflows, 2 for timeline process fields)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2025-12-13 10:47:56 +00:00
parent 3f61050fb3
commit 5df2139bbd
8 changed files with 25902 additions and 3 deletions

View file

@ -0,0 +1,364 @@
// src/app/(frontend)/api/workflows/route.ts
// Dedizierte Workflows-API für Frontend-Anwendungen
import { NextRequest, NextResponse } from 'next/server'
import { getPayload } from 'payload'
import config from '@payload-config'
import type { Media } from '@/payload-types'
import {
searchLimiter,
rateLimitHeaders,
getClientIpFromRequest,
isIpBlocked,
} from '@/lib/security'
// Validation constants
const WORKFLOW_RATE_LIMIT = 30
// Valid workflow types
const WORKFLOW_TYPES = [
'project',
'business',
'approval',
'onboarding',
'support',
'development',
'marketing',
'other',
] as const
type WorkflowType = (typeof WORKFLOW_TYPES)[number]
// Step types for filtering
const STEP_TYPES = ['task', 'decision', 'milestone', 'approval', 'wait', 'automatic'] as const
interface WorkflowStep {
name: string
description?: unknown
shortDescription?: string
stepType?: string
priority?: string
estimatedDuration?: string
responsible?: string
icon?: string
dependencies?: {
dependsOnSteps?: string
canRunParallel?: boolean
isBlocking?: boolean
}
conditions?: Array<{
condition: string
nextStep?: string
color?: string
}>
checklist?: Array<{
item: string
isRequired?: boolean
}>
resources?: Array<{
name: string
type?: string
file?: Media | number
url?: string
description?: string
}>
outputs?: Array<{
name: string
description?: string
}>
metadata?: unknown
}
interface WorkflowPhase {
name: string
description?: string
icon?: string
color?: string
estimatedDuration?: string
responsible?: string
steps: WorkflowStep[]
deliverables?: Array<{
name: string
description?: string
}>
}
export async function GET(request: NextRequest) {
try {
// IP-Blocklist prüfen
const ip = getClientIpFromRequest(request)
if (isIpBlocked(ip)) {
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}
// Rate limiting
const rateLimit = await searchLimiter.check(ip)
if (!rateLimit.allowed) {
return NextResponse.json(
{ error: 'Too many requests. Please try again later.' },
{ status: 429, headers: rateLimitHeaders(rateLimit, WORKFLOW_RATE_LIMIT) }
)
}
// Parse query parameters
const { searchParams } = new URL(request.url)
const tenantParam = searchParams.get('tenant')
const slugParam = searchParams.get('slug')?.trim()
const typeParam = searchParams.get('type')?.trim()
const localeParam = searchParams.get('locale')?.trim()
const complexityParam = searchParams.get('complexity')?.trim()
// Validate tenant - REQUIRED for tenant isolation
if (!tenantParam) {
return NextResponse.json(
{ error: 'Tenant ID is required. Use ?tenant=<id> to specify the tenant.' },
{ status: 400 }
)
}
const tenantId = parseInt(tenantParam, 10)
if (isNaN(tenantId) || tenantId < 1) {
return NextResponse.json({ error: 'Invalid tenant ID' }, { status: 400 })
}
// Validate locale
const validLocales = ['de', 'en']
const locale = localeParam && validLocales.includes(localeParam) ? localeParam : 'de'
// Validate type if provided
if (typeParam && !WORKFLOW_TYPES.includes(typeParam as WorkflowType)) {
return NextResponse.json(
{ error: `Invalid type. Must be one of: ${WORKFLOW_TYPES.join(', ')}` },
{ status: 400 }
)
}
// Build where clause
const where: Record<string, unknown> = {
status: { equals: 'published' },
tenant: { equals: tenantId },
}
// Filter by slug (single workflow)
if (slugParam) {
where.slug = { equals: slugParam }
}
// Filter by type
if (typeParam) {
where.type = { equals: typeParam }
}
// Get payload instance
const payload = await getPayload({ config })
// Execute query
const result = await payload.find({
collection: 'workflows',
where,
sort: '-updatedAt',
limit: slugParam ? 1 : 100, // Single or list
locale,
depth: 2, // Load media relations
})
if (slugParam && result.docs.length === 0) {
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
}
// Transform workflows
const transformedDocs = result.docs.map((workflow) => {
const phases = (workflow.phases || []) as WorkflowPhase[]
// Filter by complexity if specified
if (
complexityParam &&
(workflow.properties as { complexity?: string })?.complexity !== complexityParam
) {
return null
}
// Calculate statistics
let totalSteps = 0
let totalChecklists = 0
const stepTypes: Record<string, number> = {}
// Transform phases
const transformedPhases = phases.map((phase, phaseIndex) => {
const steps = (phase.steps || []) as WorkflowStep[]
const transformedSteps = steps.map((step, stepIndex) => {
totalSteps++
// Count step types
const stepType = step.stepType || 'task'
stepTypes[stepType] = (stepTypes[stepType] || 0) + 1
// Count checklists
totalChecklists += (step.checklist || []).length
// Transform resources
const resources = (step.resources || []).map((resource) => {
const file = resource.file as Media | null
return {
name: resource.name,
type: resource.type || 'document',
fileUrl: file?.url || null,
url: resource.url || null,
description: resource.description || null,
}
})
return {
index: stepIndex + 1,
globalIndex: totalSteps,
name: step.name,
description: step.description || null,
shortDescription: step.shortDescription || null,
stepType: step.stepType || 'task',
priority: step.priority || 'normal',
estimatedDuration: step.estimatedDuration || null,
responsible: step.responsible || null,
icon: step.icon || null,
dependencies: step.dependencies
? {
dependsOnSteps: step.dependencies.dependsOnSteps || null,
canRunParallel: step.dependencies.canRunParallel || false,
isBlocking: step.dependencies.isBlocking !== false,
}
: null,
conditions:
step.stepType === 'decision'
? (step.conditions || []).map((c) => ({
condition: c.condition,
nextStep: c.nextStep || null,
color: c.color || null,
}))
: null,
checklist: (step.checklist || []).map((item) => ({
item: item.item,
isRequired: item.isRequired !== false,
})),
resources: resources.length > 0 ? resources : null,
outputs: (step.outputs || []).map((o) => ({
name: o.name,
description: o.description || null,
})),
metadata: step.metadata || null,
}
})
return {
index: phaseIndex + 1,
name: phase.name,
description: phase.description || null,
icon: phase.icon || null,
color: phase.color || null,
estimatedDuration: phase.estimatedDuration || null,
responsible: phase.responsible || null,
stepCount: transformedSteps.length,
steps: transformedSteps,
deliverables: (phase.deliverables || []).map((d) => ({
name: d.name,
description: d.description || null,
})),
}
})
// Transform global resources
const globalResources = (
workflow.globalResources as Array<{
name: string
type?: string
file?: Media | number
url?: string
description?: string
}>
)?.map((resource) => {
const file = resource.file as Media | null
return {
name: resource.name,
type: resource.type || 'document',
fileUrl: file?.url || null,
url: resource.url || null,
description: resource.description || null,
}
})
const image = workflow.image as Media | null
return {
id: workflow.id,
name: workflow.name,
slug: workflow.slug,
description: workflow.description || null,
shortDescription: workflow.shortDescription || null,
type: workflow.type,
image: image
? {
url: image.url,
alt: image.alt || workflow.name,
width: image.width,
height: image.height,
}
: null,
properties: workflow.properties || null,
displayOptions: workflow.displayOptions || null,
phases: transformedPhases,
globalResources: globalResources?.length ? globalResources : null,
statistics: {
phaseCount: transformedPhases.length,
totalSteps,
totalChecklists,
stepTypes,
},
seo: workflow.seo || null,
}
})
// Filter out null entries (from complexity filter)
const filteredDocs = transformedDocs.filter(Boolean)
// Single workflow response
if (slugParam) {
if (filteredDocs.length === 0) {
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
}
return NextResponse.json(
{
workflow: filteredDocs[0],
locale,
},
{
headers: {
...rateLimitHeaders(rateLimit, WORKFLOW_RATE_LIMIT),
'Cache-Control': 'public, max-age=60, s-maxage=300',
},
}
)
}
// List response
return NextResponse.json(
{
docs: filteredDocs,
total: filteredDocs.length,
filters: {
tenant: tenantId,
type: typeParam || null,
complexity: complexityParam || null,
locale,
},
},
{
headers: {
...rateLimitHeaders(rateLimit, WORKFLOW_RATE_LIMIT),
'Cache-Control': 'public, max-age=60, s-maxage=120',
},
}
)
} catch (error) {
console.error('[Workflow API] Error:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

View file

@ -398,6 +398,81 @@ export const Timelines: CollectionConfig = {
},
},
// Prozess-spezifische Felder (für einfache Prozesse wie Onboarding, Bewerbung)
{
name: 'stepNumber',
type: 'number',
label: 'Schritt-Nummer',
admin: {
description: 'Explizite Nummerierung (optional, sonst wird Reihenfolge verwendet)',
condition: (data) => data?.type === 'process',
},
},
{
name: 'duration',
type: 'text',
label: 'Dauer',
localized: true,
admin: {
description: 'z.B. "2-3 Tage", "1 Woche", "30 Minuten"',
condition: (data) => data?.type === 'process',
},
},
{
name: 'responsible',
type: 'text',
label: 'Verantwortlich',
localized: true,
admin: {
description: 'Person oder Rolle (z.B. "HR-Team", "Projektleiter")',
condition: (data) => data?.type === 'process',
},
},
{
name: 'actionRequired',
type: 'select',
label: 'Aktion erforderlich von',
admin: {
description: 'Wer muss in diesem Schritt aktiv werden?',
condition: (data) => data?.type === 'process',
},
options: [
{ label: 'Kunde/Bewerber', value: 'customer' },
{ label: 'Internes Team', value: 'internal' },
{ label: 'Beide Seiten', value: 'both' },
{ label: 'Automatisch', value: 'automatic' },
],
},
{
name: 'deliverables',
type: 'array',
label: 'Ergebnisse/Dokumente',
admin: {
description: 'Was wird in diesem Schritt erstellt oder benötigt?',
condition: (data) => data?.type === 'process',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Name',
localized: true,
},
{
name: 'type',
type: 'select',
defaultValue: 'output',
label: 'Typ',
options: [
{ label: 'Ergebnis (Output)', value: 'output' },
{ label: 'Benötigt (Input)', value: 'input' },
{ label: 'Dokument', value: 'document' },
],
},
],
},
// Icon & Styling
{
name: 'icon',

View file

@ -0,0 +1,683 @@
import type { CollectionConfig } from 'payload'
/**
* Workflows Collection
*
* Komplexe Prozess- und Workflow-Darstellungen mit:
* - Phasen und Schritten
* - Abhängigkeiten zwischen Schritten
* - Verzweigungen und Bedingungen
* - Status-Tracking
* - Checklisten und Dokumente
* - Verantwortlichkeiten und Zeitschätzungen
*
* Ideal für:
* - Projektabläufe
* - Komplexe Geschäftsprozesse
* - Genehmigungs-Workflows
* - Produktentwicklungs-Pipelines
*
* Multi-Tenant-fähig
*/
export const Workflows: CollectionConfig = {
slug: 'workflows',
labels: {
singular: 'Workflow',
plural: 'Workflows',
},
admin: {
useAsTitle: 'name',
group: 'Inhalte',
defaultColumns: ['name', 'type', 'status', 'updatedAt'],
description: 'Komplexe Prozesse und Workflows mit Phasen, Abhängigkeiten und Status-Tracking',
},
access: {
read: () => true, // Öffentlich lesbar
},
fields: [
// Workflow-Metadaten
{
name: 'name',
type: 'text',
required: true,
label: 'Workflow-Name',
localized: true,
admin: {
description: 'Name des Workflows (z.B. "Projektablauf Webentwicklung")',
},
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
label: 'Slug',
admin: {
description: 'URL-freundlicher Identifier',
},
},
{
name: 'description',
type: 'richText',
label: 'Beschreibung',
localized: true,
admin: {
description: 'Ausführliche Beschreibung des Workflows',
},
},
{
name: 'shortDescription',
type: 'textarea',
label: 'Kurzbeschreibung',
localized: true,
admin: {
description: 'Kurze Zusammenfassung für Übersichten',
},
},
{
name: 'type',
type: 'select',
required: true,
defaultValue: 'project',
label: 'Workflow-Typ',
options: [
{ label: 'Projektablauf', value: 'project' },
{ label: 'Geschäftsprozess', value: 'business' },
{ label: 'Genehmigung', value: 'approval' },
{ label: 'Onboarding', value: 'onboarding' },
{ label: 'Support/Service', value: 'support' },
{ label: 'Entwicklung', value: 'development' },
{ label: 'Marketing', value: 'marketing' },
{ label: 'Sonstiges', value: 'other' },
],
},
{
name: 'status',
type: 'select',
required: true,
defaultValue: 'draft',
label: 'Status',
options: [
{ label: 'Entwurf', value: 'draft' },
{ label: 'Veröffentlicht', value: 'published' },
{ label: 'Archiviert', value: 'archived' },
],
admin: {
position: 'sidebar',
},
},
{
name: 'image',
type: 'upload',
relationTo: 'media',
label: 'Vorschaubild',
admin: {
description: 'Optionales Bild für den Workflow',
},
},
// Workflow-Eigenschaften
{
name: 'properties',
type: 'group',
label: 'Workflow-Eigenschaften',
fields: [
{
name: 'estimatedDuration',
type: 'text',
label: 'Geschätzte Gesamtdauer',
localized: true,
admin: {
description: 'z.B. "4-6 Wochen", "3 Monate"',
},
},
{
name: 'complexity',
type: 'select',
label: 'Komplexität',
options: [
{ label: 'Einfach', value: 'simple' },
{ label: 'Mittel', value: 'medium' },
{ label: 'Komplex', value: 'complex' },
{ label: 'Sehr komplex', value: 'very_complex' },
],
},
{
name: 'isIterative',
type: 'checkbox',
label: 'Iterativer Prozess',
defaultValue: false,
admin: {
description: 'Kann der Workflow wiederholt durchlaufen werden?',
},
},
{
name: 'allowParallelPhases',
type: 'checkbox',
label: 'Parallele Phasen erlaubt',
defaultValue: false,
admin: {
description: 'Können mehrere Phasen gleichzeitig aktiv sein?',
},
},
],
},
// Display-Optionen
{
name: 'displayOptions',
type: 'group',
label: 'Darstellungsoptionen',
fields: [
{
name: 'layout',
type: 'select',
defaultValue: 'vertical',
label: 'Layout',
options: [
{ label: 'Vertikal (Schritte untereinander)', value: 'vertical' },
{ label: 'Horizontal (Schritte nebeneinander)', value: 'horizontal' },
{ label: 'Flowchart (mit Verbindungen)', value: 'flowchart' },
{ label: 'Kanban (Spalten)', value: 'kanban' },
{ label: 'Gantt (Zeitstrahl)', value: 'gantt' },
],
},
{
name: 'showPhaseNumbers',
type: 'checkbox',
defaultValue: true,
label: 'Phasen-Nummern anzeigen',
},
{
name: 'showStepNumbers',
type: 'checkbox',
defaultValue: true,
label: 'Schritt-Nummern anzeigen',
},
{
name: 'showDurations',
type: 'checkbox',
defaultValue: true,
label: 'Zeitangaben anzeigen',
},
{
name: 'showResponsible',
type: 'checkbox',
defaultValue: true,
label: 'Verantwortliche anzeigen',
},
{
name: 'showProgress',
type: 'checkbox',
defaultValue: false,
label: 'Fortschrittsanzeige',
admin: {
description: 'Zeigt Fortschrittsbalken basierend auf abgeschlossenen Schritten',
},
},
{
name: 'colorScheme',
type: 'select',
defaultValue: 'phase',
label: 'Farbschema',
options: [
{ label: 'Nach Phase', value: 'phase' },
{ label: 'Nach Status', value: 'status' },
{ label: 'Nach Priorität', value: 'priority' },
{ label: 'Einheitlich (Brand)', value: 'brand' },
],
},
],
},
// Phasen (Hauptabschnitte des Workflows)
{
name: 'phases',
type: 'array',
required: true,
minRows: 1,
label: 'Phasen',
admin: {
description: 'Hauptabschnitte des Workflows',
initCollapsed: false,
},
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Phasen-Name',
localized: true,
},
{
name: 'description',
type: 'textarea',
label: 'Beschreibung',
localized: true,
},
{
name: 'icon',
type: 'text',
label: 'Icon',
admin: {
description: 'Lucide Icon-Name (z.B. "rocket", "settings", "check-circle")',
},
},
{
name: 'color',
type: 'text',
label: 'Farbe',
admin: {
description: 'HEX-Farbe (z.B. "#3B82F6") oder Tailwind-Klasse',
},
},
{
name: 'estimatedDuration',
type: 'text',
label: 'Geschätzte Dauer',
localized: true,
admin: {
description: 'z.B. "1 Woche", "2-3 Tage"',
},
},
{
name: 'responsible',
type: 'text',
label: 'Verantwortlich',
localized: true,
admin: {
description: 'Rolle oder Person (z.B. "Projektleiter", "Design-Team")',
},
},
// Schritte innerhalb der Phase
{
name: 'steps',
type: 'array',
required: true,
minRows: 1,
label: 'Schritte',
admin: {
description: 'Einzelne Schritte in dieser Phase',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Schritt-Name',
localized: true,
},
{
name: 'description',
type: 'richText',
label: 'Beschreibung',
localized: true,
},
{
name: 'shortDescription',
type: 'textarea',
label: 'Kurzbeschreibung',
localized: true,
admin: {
description: 'Für kompakte Ansichten',
},
},
{
name: 'stepType',
type: 'select',
defaultValue: 'task',
label: 'Schritt-Typ',
options: [
{ label: 'Aufgabe', value: 'task' },
{ label: 'Entscheidung', value: 'decision' },
{ label: 'Meilenstein', value: 'milestone' },
{ label: 'Genehmigung', value: 'approval' },
{ label: 'Warten/Pause', value: 'wait' },
{ label: 'Automatisch', value: 'automatic' },
],
},
{
name: 'priority',
type: 'select',
defaultValue: 'normal',
label: 'Priorität',
options: [
{ label: 'Kritisch', value: 'critical' },
{ label: 'Hoch', value: 'high' },
{ label: 'Normal', value: 'normal' },
{ label: 'Niedrig', value: 'low' },
{ label: 'Optional', value: 'optional' },
],
},
{
name: 'estimatedDuration',
type: 'text',
label: 'Geschätzte Dauer',
localized: true,
admin: {
description: 'z.B. "2 Stunden", "1 Tag"',
},
},
{
name: 'responsible',
type: 'text',
label: 'Verantwortlich',
localized: true,
},
{
name: 'icon',
type: 'text',
label: 'Icon',
},
// Abhängigkeiten
{
name: 'dependencies',
type: 'group',
label: 'Abhängigkeiten',
admin: {
description: 'Welche Schritte müssen vorher abgeschlossen sein?',
},
fields: [
{
name: 'dependsOnSteps',
type: 'text',
label: 'Abhängig von Schritten',
admin: {
description:
'IDs oder Namen der Vorgänger-Schritte (komma-separiert). Leer = vorheriger Schritt',
},
},
{
name: 'canRunParallel',
type: 'checkbox',
label: 'Kann parallel laufen',
defaultValue: false,
},
{
name: 'isBlocking',
type: 'checkbox',
label: 'Blockierend',
defaultValue: true,
admin: {
description: 'Muss abgeschlossen sein, bevor der nächste Schritt beginnt',
},
},
],
},
// Bedingungen (für Entscheidungen)
{
name: 'conditions',
type: 'array',
label: 'Bedingungen/Verzweigungen',
admin: {
description: 'Für Entscheidungsschritte: Welche Wege gibt es?',
condition: (_, siblingData) => siblingData?.stepType === 'decision',
},
fields: [
{
name: 'condition',
type: 'text',
required: true,
label: 'Bedingung',
localized: true,
admin: {
description: 'z.B. "Wenn genehmigt", "Bei Budget > 10.000€"',
},
},
{
name: 'nextStep',
type: 'text',
label: 'Nächster Schritt',
admin: {
description: 'ID oder Name des nächsten Schritts',
},
},
{
name: 'color',
type: 'text',
label: 'Farbe',
admin: {
description: 'z.B. "green" für positiv, "red" für negativ',
},
},
],
},
// Checkliste
{
name: 'checklist',
type: 'array',
label: 'Checkliste',
admin: {
description: 'To-Do-Punkte für diesen Schritt',
},
fields: [
{
name: 'item',
type: 'text',
required: true,
label: 'Aufgabe',
localized: true,
},
{
name: 'isRequired',
type: 'checkbox',
label: 'Pflicht',
defaultValue: true,
},
],
},
// Dokumente und Ressourcen
{
name: 'resources',
type: 'array',
label: 'Ressourcen & Dokumente',
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Name',
localized: true,
},
{
name: 'type',
type: 'select',
defaultValue: 'document',
label: 'Typ',
options: [
{ label: 'Dokument', value: 'document' },
{ label: 'Template', value: 'template' },
{ label: 'Link', value: 'link' },
{ label: 'Tool', value: 'tool' },
{ label: 'Video', value: 'video' },
],
},
{
name: 'file',
type: 'upload',
relationTo: 'media',
label: 'Datei',
admin: {
condition: (_, siblingData) =>
['document', 'template'].includes(siblingData?.type),
},
},
{
name: 'url',
type: 'text',
label: 'URL',
admin: {
condition: (_, siblingData) =>
['link', 'tool', 'video'].includes(siblingData?.type),
},
},
{
name: 'description',
type: 'textarea',
label: 'Beschreibung',
localized: true,
},
],
},
// Outputs/Ergebnisse
{
name: 'outputs',
type: 'array',
label: 'Ergebnisse/Outputs',
admin: {
description: 'Was wird in diesem Schritt produziert?',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Output',
localized: true,
},
{
name: 'description',
type: 'textarea',
label: 'Beschreibung',
localized: true,
},
],
},
// Zusätzliche Daten
{
name: 'metadata',
type: 'json',
label: 'Zusätzliche Daten (JSON)',
admin: {
description: 'Für spezielle Anwendungsfälle',
},
},
],
},
// Phase-Level Outputs
{
name: 'deliverables',
type: 'array',
label: 'Liefergegenstände der Phase',
admin: {
description: 'Was wird am Ende dieser Phase abgeliefert?',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Deliverable',
localized: true,
},
{
name: 'description',
type: 'textarea',
label: 'Beschreibung',
localized: true,
},
],
},
],
},
// Globale Ressourcen
{
name: 'globalResources',
type: 'array',
label: 'Globale Ressourcen',
admin: {
description: 'Ressourcen, die für den gesamten Workflow relevant sind',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Name',
localized: true,
},
{
name: 'type',
type: 'select',
defaultValue: 'document',
label: 'Typ',
options: [
{ label: 'Dokument', value: 'document' },
{ label: 'Template', value: 'template' },
{ label: 'Checkliste', value: 'checklist' },
{ label: 'Link', value: 'link' },
{ label: 'Tool', value: 'tool' },
],
},
{
name: 'file',
type: 'upload',
relationTo: 'media',
label: 'Datei',
},
{
name: 'url',
type: 'text',
label: 'URL',
},
{
name: 'description',
type: 'textarea',
label: 'Beschreibung',
localized: true,
},
],
},
// SEO
{
name: 'seo',
type: 'group',
label: 'SEO',
admin: {
position: 'sidebar',
},
fields: [
{
name: 'metaTitle',
type: 'text',
label: 'Meta-Titel',
localized: true,
},
{
name: 'metaDescription',
type: 'textarea',
label: 'Meta-Beschreibung',
localized: true,
},
],
},
],
hooks: {
beforeChange: [
({ data }) => {
// Auto-generate slug from name if not provided
if (data && !data.slug && data.name) {
data.slug = data.name
.toLowerCase()
.replace(/[äöüß]/g, (match: string) => {
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
return map[match] || match
})
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
return data
},
],
},
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,324 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TYPE "public"."enum_timelines_events_deliverables_type" AS ENUM('output', 'input', 'document');
CREATE TYPE "public"."enum_timelines_events_action_required" AS ENUM('customer', 'internal', 'both', 'automatic');
CREATE TYPE "public"."enum_workflows_phases_steps_resources_type" AS ENUM('document', 'template', 'link', 'tool', 'video');
CREATE TYPE "public"."enum_workflows_phases_steps_step_type" AS ENUM('task', 'decision', 'milestone', 'approval', 'wait', 'automatic');
CREATE TYPE "public"."enum_workflows_phases_steps_priority" AS ENUM('critical', 'high', 'normal', 'low', 'optional');
CREATE TYPE "public"."enum_workflows_global_resources_type" AS ENUM('document', 'template', 'checklist', 'link', 'tool');
CREATE TYPE "public"."enum_workflows_type" AS ENUM('project', 'business', 'approval', 'onboarding', 'support', 'development', 'marketing', 'other');
CREATE TYPE "public"."enum_workflows_status" AS ENUM('draft', 'published', 'archived');
CREATE TYPE "public"."enum_workflows_properties_complexity" AS ENUM('simple', 'medium', 'complex', 'very_complex');
CREATE TYPE "public"."enum_workflows_display_options_layout" AS ENUM('vertical', 'horizontal', 'flowchart', 'kanban', 'gantt');
CREATE TYPE "public"."enum_workflows_display_options_color_scheme" AS ENUM('phase', 'status', 'priority', 'brand');
CREATE TABLE "timelines_events_deliverables" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"type" "enum_timelines_events_deliverables_type" DEFAULT 'output'
);
CREATE TABLE "timelines_events_deliverables_locales" (
"name" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_phases_steps_conditions" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"next_step" varchar,
"color" varchar
);
CREATE TABLE "workflows_phases_steps_conditions_locales" (
"condition" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_phases_steps_checklist" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"is_required" boolean DEFAULT true
);
CREATE TABLE "workflows_phases_steps_checklist_locales" (
"item" varchar NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_phases_steps_resources" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"type" "enum_workflows_phases_steps_resources_type" DEFAULT 'document',
"file_id" integer,
"url" varchar
);
CREATE TABLE "workflows_phases_steps_resources_locales" (
"name" varchar NOT NULL,
"description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_phases_steps_outputs" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL
);
CREATE TABLE "workflows_phases_steps_outputs_locales" (
"name" varchar NOT NULL,
"description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_phases_steps" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"step_type" "enum_workflows_phases_steps_step_type" DEFAULT 'task',
"priority" "enum_workflows_phases_steps_priority" DEFAULT 'normal',
"icon" varchar,
"dependencies_depends_on_steps" varchar,
"dependencies_can_run_parallel" boolean DEFAULT false,
"dependencies_is_blocking" boolean DEFAULT true,
"metadata" jsonb
);
CREATE TABLE "workflows_phases_steps_locales" (
"name" varchar NOT NULL,
"description" jsonb,
"short_description" varchar,
"estimated_duration" varchar,
"responsible" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_phases_deliverables" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL
);
CREATE TABLE "workflows_phases_deliverables_locales" (
"name" varchar NOT NULL,
"description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_phases" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"icon" varchar,
"color" varchar
);
CREATE TABLE "workflows_phases_locales" (
"name" varchar NOT NULL,
"description" varchar,
"estimated_duration" varchar,
"responsible" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows_global_resources" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"type" "enum_workflows_global_resources_type" DEFAULT 'document',
"file_id" integer,
"url" varchar
);
CREATE TABLE "workflows_global_resources_locales" (
"name" varchar NOT NULL,
"description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "workflows" (
"id" serial PRIMARY KEY NOT NULL,
"tenant_id" integer,
"slug" varchar NOT NULL,
"type" "enum_workflows_type" DEFAULT 'project' NOT NULL,
"status" "enum_workflows_status" DEFAULT 'draft' NOT NULL,
"image_id" integer,
"properties_complexity" "enum_workflows_properties_complexity",
"properties_is_iterative" boolean DEFAULT false,
"properties_allow_parallel_phases" boolean DEFAULT false,
"display_options_layout" "enum_workflows_display_options_layout" DEFAULT 'vertical',
"display_options_show_phase_numbers" boolean DEFAULT true,
"display_options_show_step_numbers" boolean DEFAULT true,
"display_options_show_durations" boolean DEFAULT true,
"display_options_show_responsible" boolean DEFAULT true,
"display_options_show_progress" boolean DEFAULT false,
"display_options_color_scheme" "enum_workflows_display_options_color_scheme" DEFAULT 'phase',
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE "workflows_locales" (
"name" varchar NOT NULL,
"description" jsonb,
"short_description" varchar,
"properties_estimated_duration" varchar,
"seo_meta_title" varchar,
"seo_meta_description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
ALTER TABLE "timelines_events" ADD COLUMN "step_number" numeric;
ALTER TABLE "timelines_events" ADD COLUMN "action_required" "enum_timelines_events_action_required";
ALTER TABLE "timelines_events_locales" ADD COLUMN "duration" varchar;
ALTER TABLE "timelines_events_locales" ADD COLUMN "responsible" varchar;
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "workflows_id" integer;
ALTER TABLE "timelines_events_deliverables" ADD CONSTRAINT "timelines_events_deliverables_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."timelines_events"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "timelines_events_deliverables_locales" ADD CONSTRAINT "timelines_events_deliverables_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."timelines_events_deliverables"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_conditions" ADD CONSTRAINT "workflows_phases_steps_conditions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_conditions_locales" ADD CONSTRAINT "workflows_phases_steps_conditions_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps_conditions"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_checklist" ADD CONSTRAINT "workflows_phases_steps_checklist_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_checklist_locales" ADD CONSTRAINT "workflows_phases_steps_checklist_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps_checklist"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_resources" ADD CONSTRAINT "workflows_phases_steps_resources_file_id_media_id_fk" FOREIGN KEY ("file_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_resources" ADD CONSTRAINT "workflows_phases_steps_resources_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_resources_locales" ADD CONSTRAINT "workflows_phases_steps_resources_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps_resources"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_outputs" ADD CONSTRAINT "workflows_phases_steps_outputs_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_outputs_locales" ADD CONSTRAINT "workflows_phases_steps_outputs_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps_outputs"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps" ADD CONSTRAINT "workflows_phases_steps_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_steps_locales" ADD CONSTRAINT "workflows_phases_steps_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_steps"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_deliverables" ADD CONSTRAINT "workflows_phases_deliverables_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_deliverables_locales" ADD CONSTRAINT "workflows_phases_deliverables_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases_deliverables"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases" ADD CONSTRAINT "workflows_phases_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_phases_locales" ADD CONSTRAINT "workflows_phases_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_phases"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_global_resources" ADD CONSTRAINT "workflows_global_resources_file_id_media_id_fk" FOREIGN KEY ("file_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "workflows_global_resources" ADD CONSTRAINT "workflows_global_resources_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows_global_resources_locales" ADD CONSTRAINT "workflows_global_resources_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows_global_resources"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "workflows" ADD CONSTRAINT "workflows_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "workflows" ADD CONSTRAINT "workflows_image_id_media_id_fk" FOREIGN KEY ("image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "workflows_locales" ADD CONSTRAINT "workflows_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."workflows"("id") ON DELETE cascade ON UPDATE no action;
CREATE INDEX "timelines_events_deliverables_order_idx" ON "timelines_events_deliverables" USING btree ("_order");
CREATE INDEX "timelines_events_deliverables_parent_id_idx" ON "timelines_events_deliverables" USING btree ("_parent_id");
CREATE UNIQUE INDEX "timelines_events_deliverables_locales_locale_parent_id_uniqu" ON "timelines_events_deliverables_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_phases_steps_conditions_order_idx" ON "workflows_phases_steps_conditions" USING btree ("_order");
CREATE INDEX "workflows_phases_steps_conditions_parent_id_idx" ON "workflows_phases_steps_conditions" USING btree ("_parent_id");
CREATE UNIQUE INDEX "workflows_phases_steps_conditions_locales_locale_parent_id_u" ON "workflows_phases_steps_conditions_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_phases_steps_checklist_order_idx" ON "workflows_phases_steps_checklist" USING btree ("_order");
CREATE INDEX "workflows_phases_steps_checklist_parent_id_idx" ON "workflows_phases_steps_checklist" USING btree ("_parent_id");
CREATE UNIQUE INDEX "workflows_phases_steps_checklist_locales_locale_parent_id_un" ON "workflows_phases_steps_checklist_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_phases_steps_resources_order_idx" ON "workflows_phases_steps_resources" USING btree ("_order");
CREATE INDEX "workflows_phases_steps_resources_parent_id_idx" ON "workflows_phases_steps_resources" USING btree ("_parent_id");
CREATE INDEX "workflows_phases_steps_resources_file_idx" ON "workflows_phases_steps_resources" USING btree ("file_id");
CREATE UNIQUE INDEX "workflows_phases_steps_resources_locales_locale_parent_id_un" ON "workflows_phases_steps_resources_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_phases_steps_outputs_order_idx" ON "workflows_phases_steps_outputs" USING btree ("_order");
CREATE INDEX "workflows_phases_steps_outputs_parent_id_idx" ON "workflows_phases_steps_outputs" USING btree ("_parent_id");
CREATE UNIQUE INDEX "workflows_phases_steps_outputs_locales_locale_parent_id_uniq" ON "workflows_phases_steps_outputs_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_phases_steps_order_idx" ON "workflows_phases_steps" USING btree ("_order");
CREATE INDEX "workflows_phases_steps_parent_id_idx" ON "workflows_phases_steps" USING btree ("_parent_id");
CREATE UNIQUE INDEX "workflows_phases_steps_locales_locale_parent_id_unique" ON "workflows_phases_steps_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_phases_deliverables_order_idx" ON "workflows_phases_deliverables" USING btree ("_order");
CREATE INDEX "workflows_phases_deliverables_parent_id_idx" ON "workflows_phases_deliverables" USING btree ("_parent_id");
CREATE UNIQUE INDEX "workflows_phases_deliverables_locales_locale_parent_id_uniqu" ON "workflows_phases_deliverables_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_phases_order_idx" ON "workflows_phases" USING btree ("_order");
CREATE INDEX "workflows_phases_parent_id_idx" ON "workflows_phases" USING btree ("_parent_id");
CREATE UNIQUE INDEX "workflows_phases_locales_locale_parent_id_unique" ON "workflows_phases_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_global_resources_order_idx" ON "workflows_global_resources" USING btree ("_order");
CREATE INDEX "workflows_global_resources_parent_id_idx" ON "workflows_global_resources" USING btree ("_parent_id");
CREATE INDEX "workflows_global_resources_file_idx" ON "workflows_global_resources" USING btree ("file_id");
CREATE UNIQUE INDEX "workflows_global_resources_locales_locale_parent_id_unique" ON "workflows_global_resources_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "workflows_tenant_idx" ON "workflows" USING btree ("tenant_id");
CREATE UNIQUE INDEX "workflows_slug_idx" ON "workflows" USING btree ("slug");
CREATE INDEX "workflows_image_idx" ON "workflows" USING btree ("image_id");
CREATE INDEX "workflows_updated_at_idx" ON "workflows" USING btree ("updated_at");
CREATE INDEX "workflows_created_at_idx" ON "workflows" USING btree ("created_at");
CREATE UNIQUE INDEX "workflows_locales_locale_parent_id_unique" ON "workflows_locales" USING btree ("_locale","_parent_id");
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_workflows_fk" FOREIGN KEY ("workflows_id") REFERENCES "public"."workflows"("id") ON DELETE cascade ON UPDATE no action;
CREATE INDEX "payload_locked_documents_rels_workflows_id_idx" ON "payload_locked_documents_rels" USING btree ("workflows_id");`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "timelines_events_deliverables" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "timelines_events_deliverables_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_conditions" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_conditions_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_checklist" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_checklist_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_resources" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_resources_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_outputs" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_outputs_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_steps_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_deliverables" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_deliverables_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_phases_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_global_resources" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_global_resources_locales" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "workflows_locales" DISABLE ROW LEVEL SECURITY;
DROP TABLE "timelines_events_deliverables" CASCADE;
DROP TABLE "timelines_events_deliverables_locales" CASCADE;
DROP TABLE "workflows_phases_steps_conditions" CASCADE;
DROP TABLE "workflows_phases_steps_conditions_locales" CASCADE;
DROP TABLE "workflows_phases_steps_checklist" CASCADE;
DROP TABLE "workflows_phases_steps_checklist_locales" CASCADE;
DROP TABLE "workflows_phases_steps_resources" CASCADE;
DROP TABLE "workflows_phases_steps_resources_locales" CASCADE;
DROP TABLE "workflows_phases_steps_outputs" CASCADE;
DROP TABLE "workflows_phases_steps_outputs_locales" CASCADE;
DROP TABLE "workflows_phases_steps" CASCADE;
DROP TABLE "workflows_phases_steps_locales" CASCADE;
DROP TABLE "workflows_phases_deliverables" CASCADE;
DROP TABLE "workflows_phases_deliverables_locales" CASCADE;
DROP TABLE "workflows_phases" CASCADE;
DROP TABLE "workflows_phases_locales" CASCADE;
DROP TABLE "workflows_global_resources" CASCADE;
DROP TABLE "workflows_global_resources_locales" CASCADE;
DROP TABLE "workflows" CASCADE;
DROP TABLE "workflows_locales" CASCADE;
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_workflows_fk";
DROP INDEX "payload_locked_documents_rels_workflows_id_idx";
ALTER TABLE "timelines_events" DROP COLUMN "step_number";
ALTER TABLE "timelines_events" DROP COLUMN "action_required";
ALTER TABLE "timelines_events_locales" DROP COLUMN "duration";
ALTER TABLE "timelines_events_locales" DROP COLUMN "responsible";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "workflows_id";
DROP TYPE "public"."enum_timelines_events_deliverables_type";
DROP TYPE "public"."enum_timelines_events_action_required";
DROP TYPE "public"."enum_workflows_phases_steps_resources_type";
DROP TYPE "public"."enum_workflows_phases_steps_step_type";
DROP TYPE "public"."enum_workflows_phases_steps_priority";
DROP TYPE "public"."enum_workflows_global_resources_type";
DROP TYPE "public"."enum_workflows_type";
DROP TYPE "public"."enum_workflows_status";
DROP TYPE "public"."enum_workflows_properties_complexity";
DROP TYPE "public"."enum_workflows_display_options_layout";
DROP TYPE "public"."enum_workflows_display_options_color_scheme";`)
}

View file

@ -10,6 +10,7 @@ import * as migration_20251210_073811_add_services_collections from './20251210_
import * as migration_20251210_090000_enhance_form_submissions from './20251210_090000_enhance_form_submissions';
import * as migration_20251212_211506_add_products_collections from './20251212_211506_add_products_collections';
import * as migration_20251213_100753_add_timelines_collection from './20251213_100753_add_timelines_collection';
import * as migration_20251213_104523_add_workflows_and_timeline_process_fields from './20251213_104523_add_workflows_and_timeline_process_fields';
export const migrations = [
{
@ -70,6 +71,11 @@ export const migrations = [
{
up: migration_20251213_100753_add_timelines_collection.up,
down: migration_20251213_100753_add_timelines_collection.down,
name: '20251213_100753_add_timelines_collection'
name: '20251213_100753_add_timelines_collection',
},
{
up: migration_20251213_104523_add_workflows_and_timeline_process_fields.up,
down: migration_20251213_104523_add_workflows_and_timeline_process_fields.down,
name: '20251213_104523_add_workflows_and_timeline_process_fields'
},
];

View file

@ -85,6 +85,7 @@ export interface Config {
'product-categories': ProductCategory;
products: Product;
timelines: Timeline;
workflows: Workflow;
'cookie-configurations': CookieConfiguration;
'cookie-inventory': CookieInventory;
'consent-logs': ConsentLog;
@ -119,6 +120,7 @@ export interface Config {
'product-categories': ProductCategoriesSelect<false> | ProductCategoriesSelect<true>;
products: ProductsSelect<false> | ProductsSelect<true>;
timelines: TimelinesSelect<false> | TimelinesSelect<true>;
workflows: WorkflowsSelect<false> | WorkflowsSelect<true>;
'cookie-configurations': CookieConfigurationsSelect<false> | CookieConfigurationsSelect<true>;
'cookie-inventory': CookieInventorySelect<false> | CookieInventorySelect<true>;
'consent-logs': ConsentLogsSelect<false> | ConsentLogsSelect<true>;
@ -1897,6 +1899,32 @@ export interface Timeline {
* Highlights werden visuell hervorgehoben
*/
importance?: ('highlight' | 'normal' | 'minor') | null;
/**
* Explizite Nummerierung (optional, sonst wird Reihenfolge verwendet)
*/
stepNumber?: number | null;
/**
* z.B. "2-3 Tage", "1 Woche", "30 Minuten"
*/
duration?: string | null;
/**
* Person oder Rolle (z.B. "HR-Team", "Projektleiter")
*/
responsible?: string | null;
/**
* Wer muss in diesem Schritt aktiv werden?
*/
actionRequired?: ('customer' | 'internal' | 'both' | 'automatic') | null;
/**
* Was wird in diesem Schritt erstellt oder benötigt?
*/
deliverables?:
| {
name: string;
type?: ('output' | 'input' | 'document') | null;
id?: string | null;
}[]
| null;
/**
* Emoji oder Lucide Icon-Name (z.B. "rocket", "award", "users")
*/
@ -1934,6 +1962,242 @@ export interface Timeline {
updatedAt: string;
createdAt: string;
}
/**
* Komplexe Prozesse und Workflows mit Phasen, Abhängigkeiten und Status-Tracking
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "workflows".
*/
export interface Workflow {
id: number;
tenant?: (number | null) | Tenant;
/**
* Name des Workflows (z.B. "Projektablauf Webentwicklung")
*/
name: string;
/**
* URL-freundlicher Identifier
*/
slug: string;
/**
* Ausführliche Beschreibung des Workflows
*/
description?: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
/**
* Kurze Zusammenfassung für Übersichten
*/
shortDescription?: string | null;
type: 'project' | 'business' | 'approval' | 'onboarding' | 'support' | 'development' | 'marketing' | 'other';
status: 'draft' | 'published' | 'archived';
/**
* Optionales Bild für den Workflow
*/
image?: (number | null) | Media;
properties?: {
/**
* z.B. "4-6 Wochen", "3 Monate"
*/
estimatedDuration?: string | null;
complexity?: ('simple' | 'medium' | 'complex' | 'very_complex') | null;
/**
* Kann der Workflow wiederholt durchlaufen werden?
*/
isIterative?: boolean | null;
/**
* Können mehrere Phasen gleichzeitig aktiv sein?
*/
allowParallelPhases?: boolean | null;
};
displayOptions?: {
layout?: ('vertical' | 'horizontal' | 'flowchart' | 'kanban' | 'gantt') | null;
showPhaseNumbers?: boolean | null;
showStepNumbers?: boolean | null;
showDurations?: boolean | null;
showResponsible?: boolean | null;
/**
* Zeigt Fortschrittsbalken basierend auf abgeschlossenen Schritten
*/
showProgress?: boolean | null;
colorScheme?: ('phase' | 'status' | 'priority' | 'brand') | null;
};
/**
* Hauptabschnitte des Workflows
*/
phases: {
name: string;
description?: string | null;
/**
* Lucide Icon-Name (z.B. "rocket", "settings", "check-circle")
*/
icon?: string | null;
/**
* HEX-Farbe (z.B. "#3B82F6") oder Tailwind-Klasse
*/
color?: string | null;
/**
* z.B. "1 Woche", "2-3 Tage"
*/
estimatedDuration?: string | null;
/**
* Rolle oder Person (z.B. "Projektleiter", "Design-Team")
*/
responsible?: string | null;
/**
* Einzelne Schritte in dieser Phase
*/
steps: {
name: string;
description?: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
/**
* Für kompakte Ansichten
*/
shortDescription?: string | null;
stepType?: ('task' | 'decision' | 'milestone' | 'approval' | 'wait' | 'automatic') | null;
priority?: ('critical' | 'high' | 'normal' | 'low' | 'optional') | null;
/**
* z.B. "2 Stunden", "1 Tag"
*/
estimatedDuration?: string | null;
responsible?: string | null;
icon?: string | null;
/**
* Welche Schritte müssen vorher abgeschlossen sein?
*/
dependencies?: {
/**
* IDs oder Namen der Vorgänger-Schritte (komma-separiert). Leer = vorheriger Schritt
*/
dependsOnSteps?: string | null;
canRunParallel?: boolean | null;
/**
* Muss abgeschlossen sein, bevor der nächste Schritt beginnt
*/
isBlocking?: boolean | null;
};
/**
* Für Entscheidungsschritte: Welche Wege gibt es?
*/
conditions?:
| {
/**
* z.B. "Wenn genehmigt", "Bei Budget > 10.000€"
*/
condition: string;
/**
* ID oder Name des nächsten Schritts
*/
nextStep?: string | null;
/**
* z.B. "green" für positiv, "red" für negativ
*/
color?: string | null;
id?: string | null;
}[]
| null;
/**
* To-Do-Punkte für diesen Schritt
*/
checklist?:
| {
item: string;
isRequired?: boolean | null;
id?: string | null;
}[]
| null;
resources?:
| {
name: string;
type?: ('document' | 'template' | 'link' | 'tool' | 'video') | null;
file?: (number | null) | Media;
url?: string | null;
description?: string | null;
id?: string | null;
}[]
| null;
/**
* Was wird in diesem Schritt produziert?
*/
outputs?:
| {
name: string;
description?: string | null;
id?: string | null;
}[]
| null;
/**
* Für spezielle Anwendungsfälle
*/
metadata?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
id?: string | null;
}[];
/**
* Was wird am Ende dieser Phase abgeliefert?
*/
deliverables?:
| {
name: string;
description?: string | null;
id?: string | null;
}[]
| null;
id?: string | null;
}[];
/**
* Ressourcen, die für den gesamten Workflow relevant sind
*/
globalResources?:
| {
name: string;
type?: ('document' | 'template' | 'checklist' | 'link' | 'tool') | null;
file?: (number | null) | Media;
url?: string | null;
description?: string | null;
id?: string | null;
}[]
| null;
seo?: {
metaTitle?: string | null;
metaDescription?: string | null;
};
updatedAt: string;
createdAt: string;
}
/**
* Cookie-Banner Konfiguration pro Tenant
*
@ -2591,6 +2855,10 @@ export interface PayloadLockedDocument {
relationTo: 'timelines';
value: number | Timeline;
} | null)
| ({
relationTo: 'workflows';
value: number | Workflow;
} | null)
| ({
relationTo: 'cookie-configurations';
value: number | CookieConfiguration;
@ -3699,6 +3967,17 @@ export interface TimelinesSelect<T extends boolean = true> {
};
category?: T;
importance?: T;
stepNumber?: T;
duration?: T;
responsible?: T;
actionRequired?: T;
deliverables?:
| T
| {
name?: T;
type?: T;
id?: T;
};
icon?: T;
color?: T;
links?:
@ -3721,6 +4000,128 @@ export interface TimelinesSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "workflows_select".
*/
export interface WorkflowsSelect<T extends boolean = true> {
tenant?: T;
name?: T;
slug?: T;
description?: T;
shortDescription?: T;
type?: T;
status?: T;
image?: T;
properties?:
| T
| {
estimatedDuration?: T;
complexity?: T;
isIterative?: T;
allowParallelPhases?: T;
};
displayOptions?:
| T
| {
layout?: T;
showPhaseNumbers?: T;
showStepNumbers?: T;
showDurations?: T;
showResponsible?: T;
showProgress?: T;
colorScheme?: T;
};
phases?:
| T
| {
name?: T;
description?: T;
icon?: T;
color?: T;
estimatedDuration?: T;
responsible?: T;
steps?:
| T
| {
name?: T;
description?: T;
shortDescription?: T;
stepType?: T;
priority?: T;
estimatedDuration?: T;
responsible?: T;
icon?: T;
dependencies?:
| T
| {
dependsOnSteps?: T;
canRunParallel?: T;
isBlocking?: T;
};
conditions?:
| T
| {
condition?: T;
nextStep?: T;
color?: T;
id?: T;
};
checklist?:
| T
| {
item?: T;
isRequired?: T;
id?: T;
};
resources?:
| T
| {
name?: T;
type?: T;
file?: T;
url?: T;
description?: T;
id?: T;
};
outputs?:
| T
| {
name?: T;
description?: T;
id?: T;
};
metadata?: T;
id?: T;
};
deliverables?:
| T
| {
name?: T;
description?: T;
id?: T;
};
id?: T;
};
globalResources?:
| T
| {
name?: T;
type?: T;
file?: T;
url?: T;
description?: T;
id?: T;
};
seo?:
| T
| {
metaTitle?: T;
metaDescription?: T;
};
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "cookie-configurations_select".

View file

@ -41,6 +41,9 @@ import { Products } from './collections/Products'
// Timeline Collection
import { Timelines } from './collections/Timelines'
// Workflow Collection
import { Workflows } from './collections/Workflows'
// Consent Management Collections
import { CookieConfigurations } from './collections/CookieConfigurations'
import { CookieInventory } from './collections/CookieInventory'
@ -155,8 +158,9 @@ export default buildConfig({
// Products
ProductCategories,
Products,
// Timelines
// Timelines & Workflows
Timelines,
Workflows,
// Consent Management
CookieConfigurations,
CookieInventory,
@ -205,8 +209,9 @@ export default buildConfig({
// Product Collections
'product-categories': {},
products: {},
// Timeline Collection
// Timeline & Workflow Collections
timelines: {},
workflows: {},
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
'cookie-configurations': { customTenantField: true },
'cookie-inventory': { customTenantField: true },